]>
Commit | Line | Data |
---|---|---|
35bde798 IB |
1 | # This file was imported from |
2 | # https://bitbucket.org/marcusva/python-utils/overview | |
3 | # And slightly adapted | |
4 | ||
5 | """OS-specific font detection.""" | |
6 | import os | |
7 | import sys | |
8 | from subprocess import Popen, PIPE | |
9 | ||
10 | if sys.platform in ("win32", "cli"): | |
11 | import winreg | |
12 | ||
13 | __all__ = ["STYLE_NORMAL", "STYLE_BOLD", "STYLE_ITALIC", "STYLE_LIGHT", | |
14 | "init", "list_fonts", "get_fonts", "get_font" | |
15 | ] | |
16 | ||
17 | # Font cache entries: | |
18 | # { family : [..., | |
19 | # (name, styles, fonttype, filename) | |
20 | # ... | |
21 | # ] | |
22 | # } | |
23 | __FONTCACHE = None | |
24 | ||
25 | ||
26 | STYLE_NONE = 0x00 | |
27 | STYLE_NORMAL = 0x01 | |
28 | STYLE_BOLD = 0x02 | |
29 | STYLE_ITALIC = 0x04 | |
30 | STYLE_LIGHT = 0x08 | |
31 | STYLE_MEDIUM = 0x10 | |
32 | ||
33 | def _add_font(family, name, styles, fonttype, filename): | |
34 | """Adds a font to the internal font cache.""" | |
35 | global __FONTCACHE | |
36 | ||
37 | if family not in __FONTCACHE: | |
38 | __FONTCACHE[family] = [] | |
39 | __FONTCACHE[family].append((name, styles, fonttype, filename)) | |
40 | ||
41 | ||
42 | def _cache_fonts_win32(): | |
43 | """Caches fonts on a Win32 platform.""" | |
44 | key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts" | |
45 | regfonts = [] | |
46 | try: | |
47 | with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key) as fontkey: | |
48 | idx = 0 | |
49 | enumval = winreg.EnumValue | |
50 | rappend = regfonts.append | |
51 | while True: | |
52 | rappend(enumval(fontkey, idx)[:2]) | |
53 | idx += 1 | |
54 | except WindowsError: | |
55 | pass | |
56 | ||
57 | # TODO: integrate alias handling for fonts within the registry. | |
58 | # TODO: Scan and index fonts from %SystemRoot%\\Fonts that are not in the | |
59 | # registry | |
60 | ||
61 | # Received all fonts from the registry. | |
62 | for name, filename in regfonts: | |
63 | fonttype = os.path.splitext(filename)[1][1:].lower() | |
64 | if name.endswith("(TrueType)"): | |
65 | name = name[:-10].strip() | |
66 | if name.endswith("(All Res)"): | |
67 | name = name[:-9].strip() | |
68 | style = STYLE_NORMAL | |
69 | if name.find(" Bold") >= 0: | |
70 | style |= STYLE_BOLD | |
71 | if name.find(" Italic") >= 0 or name.find(" Oblique") >= 0: | |
72 | style |= STYLE_ITALIC | |
73 | ||
74 | family = name | |
75 | for rm in ("Bold", "Italic", "Oblique"): | |
76 | family = family.replace(rm, "") | |
77 | family = family.lower().strip() | |
78 | ||
79 | fontpath = os.environ.get("SystemRoot", "C:\\Windows") | |
80 | fontpath = os.path.join(fontpath, "Fonts") | |
81 | if filename.find("\\") == -1: | |
82 | # No path delimiter is given; we assume it to be a font in | |
83 | # %SystemRoot%\Fonts | |
84 | filename = os.path.join(fontpath, filename) | |
85 | _add_font(family, name, style, fonttype, filename) | |
86 | ||
87 | ||
88 | def _cache_fonts_darwin(): | |
89 | """Caches fonts on Mac OS.""" | |
90 | raise NotImplementedError("Mac OS X support is not given yet") | |
91 | ||
92 | ||
93 | def _cache_fonts_fontconfig(): | |
94 | """Caches font on POSIX-alike platforms.""" | |
95 | try: | |
96 | command = "fc-list : file family style fullname fullnamelang" | |
97 | proc = Popen(command, stdout=PIPE, shell=True, stderr=PIPE) | |
98 | pout = proc.communicate()[0] | |
99 | output = pout.decode("utf-8") | |
100 | except OSError: | |
101 | return | |
102 | ||
103 | for entry in output.split(os.linesep): | |
104 | if entry.strip() == "": | |
105 | continue | |
106 | values = entry.split(":") | |
107 | filename = values[0] | |
108 | ||
109 | # get the font type | |
110 | fname, fonttype = os.path.splitext(filename) | |
111 | if fonttype == ".gz": | |
112 | fonttype = os.path.splitext(fname)[1][1:].lower() | |
113 | else: | |
114 | fonttype = fonttype.lstrip(".").lower() | |
115 | ||
116 | # get the font name | |
117 | name = None | |
de71c01c | 118 | if len(values) > 4: |
35bde798 IB |
119 | fullnames, fullnamelangs = values[3:] |
120 | langs = fullnamelangs.split(",") | |
121 | try: | |
122 | offset = langs.index("fullnamelang=en") | |
123 | except ValueError: | |
124 | offset = -1 | |
125 | if offset == -1: | |
126 | try: | |
127 | offset = langs.index("en") | |
128 | except ValueError: | |
129 | offset = -1 | |
130 | if offset != -1: | |
131 | # got an english name, use that one | |
132 | name = fullnames.split(",")[offset] | |
133 | if name.startswith("fullname="): | |
134 | name = name[9:] | |
135 | if name is None: | |
136 | if fname.endswith(".pcf") or fname.endswith(".bdf"): | |
137 | name = os.path.basename(fname[:-4]) | |
138 | else: | |
139 | name = os.path.basename(fname) | |
140 | name = name.lower() | |
141 | ||
142 | # family and styles | |
143 | family = values[1].strip().lower() | |
2fd73270 IB |
144 | if len(values) > 2: |
145 | stylevals = values[2].strip() | |
146 | else: | |
147 | stylevals = "" | |
35bde798 IB |
148 | style = STYLE_NONE |
149 | ||
150 | if stylevals.find("Bold") >= 0: | |
151 | style |= STYLE_BOLD | |
152 | if stylevals.find("Light") >= 0: | |
153 | style |= STYLE_LIGHT | |
154 | if stylevals.find("Italic") >= 0 or stylevals.find("Oblique") >= 0: | |
155 | style |= STYLE_ITALIC | |
156 | if stylevals.find("Medium") >= 0: | |
157 | style |= STYLE_MEDIUM | |
158 | if style == STYLE_NONE: | |
159 | style = STYLE_NORMAL | |
160 | _add_font(family, name, style, fonttype, filename) | |
161 | ||
162 | ||
163 | def init(): | |
164 | """Initialises the internal font cache. | |
165 | ||
166 | It does not need to be called explicitly. | |
167 | """ | |
168 | global __FONTCACHE | |
169 | if __FONTCACHE is not None: | |
170 | return | |
171 | __FONTCACHE = {} | |
172 | if sys.platform in ("win32", "cli"): | |
173 | _cache_fonts_win32() | |
174 | elif sys.platform == "darwin": | |
175 | _cache_fonts_darwin() | |
176 | else: | |
177 | _cache_fonts_fontconfig() | |
178 | ||
179 | ||
180 | def list_fonts(): | |
181 | """Returns an iterator over the cached fonts.""" | |
182 | if __FONTCACHE is None: | |
183 | init() | |
184 | if len(__FONTCACHE) == 0: | |
185 | yield None | |
186 | for family, entries in __FONTCACHE.items(): | |
187 | for fname, styles, fonttype, filename in entries: | |
188 | yield (family, fname, styles, fonttype, filename) | |
189 | ||
190 | ||
191 | def get_fonts(name, style=STYLE_NONE, ftype=None): | |
192 | """Retrieves all fonts matching the given family or font name.""" | |
193 | if __FONTCACHE is None: | |
194 | init() | |
195 | if len(__FONTCACHE) == 0: | |
196 | return None | |
197 | ||
198 | results = [] | |
199 | rappend = results.append | |
200 | ||
201 | name = name.lower() | |
202 | if ftype: | |
203 | ftype = ftype.lower() | |
204 | ||
205 | fonts = __FONTCACHE.get(name, []) | |
206 | for fname, fstyles, fonttype, filename in fonts: | |
207 | if ftype and fonttype != ftype: | |
208 | # ignore font filetype mismatches | |
209 | continue | |
210 | if style == STYLE_NONE or fstyles == style: | |
211 | rappend((name, fname, fstyles, fonttype, filename)) | |
212 | ||
213 | for family, fonts in __FONTCACHE.items(): | |
214 | for fname, fstyles, fonttype, filename in fonts: | |
215 | if fname.lower() == name and filename not in results: | |
216 | rappend((family, fname, fstyles, fonttype, filename)) | |
217 | return results | |
218 | ||
219 | ||
220 | def get_font(name, style=STYLE_NONE, ftype=None): | |
221 | """Retrieves the best matching font file for the given name and | |
222 | criteria. | |
223 | """ | |
224 | retvals = get_fonts(name, style, ftype) | |
225 | if len(retvals) > 0: | |
226 | return retvals[0] | |
227 | return None |