# 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) > 3:
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()
stylevals = values[2].strip()
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