]>
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 | |
118 | if len(values) > 3: | |
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() | |
144 | stylevals = values[2].strip() | |
145 | style = STYLE_NONE | |
146 | ||
147 | if stylevals.find("Bold") >= 0: | |
148 | style |= STYLE_BOLD | |
149 | if stylevals.find("Light") >= 0: | |
150 | style |= STYLE_LIGHT | |
151 | if stylevals.find("Italic") >= 0 or stylevals.find("Oblique") >= 0: | |
152 | style |= STYLE_ITALIC | |
153 | if stylevals.find("Medium") >= 0: | |
154 | style |= STYLE_MEDIUM | |
155 | if style == STYLE_NONE: | |
156 | style = STYLE_NORMAL | |
157 | _add_font(family, name, style, fonttype, filename) | |
158 | ||
159 | ||
160 | def init(): | |
161 | """Initialises the internal font cache. | |
162 | ||
163 | It does not need to be called explicitly. | |
164 | """ | |
165 | global __FONTCACHE | |
166 | if __FONTCACHE is not None: | |
167 | return | |
168 | __FONTCACHE = {} | |
169 | if sys.platform in ("win32", "cli"): | |
170 | _cache_fonts_win32() | |
171 | elif sys.platform == "darwin": | |
172 | _cache_fonts_darwin() | |
173 | else: | |
174 | _cache_fonts_fontconfig() | |
175 | ||
176 | ||
177 | def list_fonts(): | |
178 | """Returns an iterator over the cached fonts.""" | |
179 | if __FONTCACHE is None: | |
180 | init() | |
181 | if len(__FONTCACHE) == 0: | |
182 | yield None | |
183 | for family, entries in __FONTCACHE.items(): | |
184 | for fname, styles, fonttype, filename in entries: | |
185 | yield (family, fname, styles, fonttype, filename) | |
186 | ||
187 | ||
188 | def get_fonts(name, style=STYLE_NONE, ftype=None): | |
189 | """Retrieves all fonts matching the given family or font name.""" | |
190 | if __FONTCACHE is None: | |
191 | init() | |
192 | if len(__FONTCACHE) == 0: | |
193 | return None | |
194 | ||
195 | results = [] | |
196 | rappend = results.append | |
197 | ||
198 | name = name.lower() | |
199 | if ftype: | |
200 | ftype = ftype.lower() | |
201 | ||
202 | fonts = __FONTCACHE.get(name, []) | |
203 | for fname, fstyles, fonttype, filename in fonts: | |
204 | if ftype and fonttype != ftype: | |
205 | # ignore font filetype mismatches | |
206 | continue | |
207 | if style == STYLE_NONE or fstyles == style: | |
208 | rappend((name, fname, fstyles, fonttype, filename)) | |
209 | ||
210 | for family, fonts in __FONTCACHE.items(): | |
211 | for fname, fstyles, fonttype, filename in fonts: | |
212 | if fname.lower() == name and filename not in results: | |
213 | rappend((family, fname, fstyles, fonttype, filename)) | |
214 | return results | |
215 | ||
216 | ||
217 | def get_font(name, style=STYLE_NONE, ftype=None): | |
218 | """Retrieves the best matching font file for the given name and | |
219 | criteria. | |
220 | """ | |
221 | retvals = get_fonts(name, style, ftype) | |
222 | if len(retvals) > 0: | |
223 | return retvals[0] | |
224 | return None |