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
|
# 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
|