# This file was imported from # https://bitbucket.org/marcusva/python-utils/overview # And slightly adapted """OS-specific font detection.""" import os import sys from subprocess import Popen, PIPE if sys.platform in ("win32", "cli"): import winreg __all__ = ["STYLE_NORMAL", "STYLE_BOLD", "STYLE_ITALIC", "STYLE_LIGHT", "init", "list_fonts", "get_fonts", "get_font" ] # Font cache entries: # { family : [..., # (name, styles, fonttype, filename) # ... # ] # } __FONTCACHE = None STYLE_NONE = 0x00 STYLE_NORMAL = 0x01 STYLE_BOLD = 0x02 STYLE_ITALIC = 0x04 STYLE_LIGHT = 0x08 STYLE_MEDIUM = 0x10 def _add_font(family, name, styles, fonttype, filename): """Adds a font to the internal font cache.""" global __FONTCACHE if family not in __FONTCACHE: __FONTCACHE[family] = [] __FONTCACHE[family].append((name, styles, fonttype, filename)) def _cache_fonts_win32(): """Caches fonts on a Win32 platform.""" key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts" regfonts = [] try: with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key) as fontkey: idx = 0 enumval = winreg.EnumValue rappend = regfonts.append while True: rappend(enumval(fontkey, idx)[:2]) idx += 1 except WindowsError: pass # TODO: integrate alias handling for fonts within the registry. # TODO: Scan and index fonts from %SystemRoot%\\Fonts that are not in the # registry # Received all fonts from the registry. for name, filename in regfonts: fonttype = os.path.splitext(filename)[1][1:].lower() if name.endswith("(TrueType)"): name = name[:-10].strip() if name.endswith("(All Res)"): name = name[:-9].strip() style = STYLE_NORMAL if name.find(" Bold") >= 0: style |= STYLE_BOLD if name.find(" Italic") >= 0 or name.find(" Oblique") >= 0: style |= STYLE_ITALIC family = name for rm in ("Bold", "Italic", "Oblique"): family = family.replace(rm, "") family = family.lower().strip() fontpath = os.environ.get("SystemRoot", "C:\\Windows") fontpath = os.path.join(fontpath, "Fonts") if filename.find("\\") == -1: # No path delimiter is given; we assume it to be a font in # %SystemRoot%\Fonts filename = os.path.join(fontpath, filename) _add_font(family, name, style, fonttype, filename) def _cache_fonts_darwin(): """Caches fonts on Mac OS.""" raise NotImplementedError("Mac OS X support is not given yet") def _cache_fonts_fontconfig(): """Caches font on POSIX-alike platforms.""" try: command = "fc-list : file family style fullname fullnamelang" proc = Popen(command, stdout=PIPE, shell=True, stderr=PIPE) pout = proc.communicate()[0] output = pout.decode("utf-8") except OSError: return for entry in output.split(os.linesep): if entry.strip() == "": continue values = entry.split(":") filename = values[0] # get the font type fname, fonttype = os.path.splitext(filename) if fonttype == ".gz": fonttype = os.path.splitext(fname)[1][1:].lower() else: fonttype = fonttype.lstrip(".").lower() # get the font name name = None if len(values) > 4: fullnames, fullnamelangs = values[3:] langs = fullnamelangs.split(",") try: offset = langs.index("fullnamelang=en") except ValueError: offset = -1 if offset == -1: try: offset = langs.index("en") except ValueError: offset = -1 if offset != -1: # got an english name, use that one name = fullnames.split(",")[offset] if name.startswith("fullname="): name = name[9:] if name is None: if fname.endswith(".pcf") or fname.endswith(".bdf"): name = os.path.basename(fname[:-4]) else: name = os.path.basename(fname) name = name.lower() # family and styles family = values[1].strip().lower() if len(values) > 2: stylevals = values[2].strip() else: stylevals = "" style = STYLE_NONE if stylevals.find("Bold") >= 0: style |= STYLE_BOLD if stylevals.find("Light") >= 0: style |= STYLE_LIGHT if stylevals.find("Italic") >= 0 or stylevals.find("Oblique") >= 0: style |= STYLE_ITALIC if stylevals.find("Medium") >= 0: style |= STYLE_MEDIUM if style == STYLE_NONE: style = STYLE_NORMAL _add_font(family, name, style, fonttype, filename) def init(): """Initialises the internal font cache. It does not need to be called explicitly. """ global __FONTCACHE if __FONTCACHE is not None: return __FONTCACHE = {} if sys.platform in ("win32", "cli"): _cache_fonts_win32() elif sys.platform == "darwin": _cache_fonts_darwin() else: _cache_fonts_fontconfig() def list_fonts(): """Returns an iterator over the cached fonts.""" if __FONTCACHE is None: init() if len(__FONTCACHE) == 0: yield None for family, entries in __FONTCACHE.items(): for fname, styles, fonttype, filename in entries: yield (family, fname, styles, fonttype, filename) def get_fonts(name, style=STYLE_NONE, ftype=None): """Retrieves all fonts matching the given family or font name.""" if __FONTCACHE is None: init() if len(__FONTCACHE) == 0: return None results = [] rappend = results.append name = name.lower() if ftype: ftype = ftype.lower() fonts = __FONTCACHE.get(name, []) for fname, fstyles, fonttype, filename in fonts: if ftype and fonttype != ftype: # ignore font filetype mismatches continue if style == STYLE_NONE or fstyles == style: rappend((name, fname, fstyles, fonttype, filename)) for family, fonts in __FONTCACHE.items(): for fname, fstyles, fonttype, filename in fonts: if fname.lower() == name and filename not in results: rappend((family, fname, fstyles, fonttype, filename)) return results def get_font(name, style=STYLE_NONE, ftype=None): """Retrieves the best matching font file for the given name and criteria. """ retvals = get_fonts(name, style, ftype) if len(retvals) > 0: return retvals[0] return None