diff options
Diffstat (limited to 'music_sampler/sysfont.py')
-rw-r--r-- | music_sampler/sysfont.py | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/music_sampler/sysfont.py b/music_sampler/sysfont.py new file mode 100644 index 0000000..f47693e --- /dev/null +++ b/music_sampler/sysfont.py | |||
@@ -0,0 +1,224 @@ | |||
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 | ||