aboutsummaryrefslogtreecommitdiff
path: root/music_sampler/sysfont.py
blob: cdb3bf3ba64f5099943193276f5af6981b23d818 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# 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