aboutsummaryrefslogblamecommitdiff
path: root/music_sampler/sysfont.py
blob: f47693ea15865663d9ba6a6b2719f1f82363d8d6 (plain) (tree)































































































































































































































                                                                             
# 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