10000 Simplify font_manager font enumeration logic. · matplotlib/matplotlib@1933dd3 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1933dd3

Browse files
committed
Simplify font_manager font enumeration logic.
The font enumeration logic on Windows was relatively difficult to follow, being split over three different functions (win32FontDirectory, _win32RegistryFonts, and win32InstalledFonts), with variable names that did not clearly indicate whether they are directories (such as MSUserFontDirectories) or registry keys (such as MSFontDirectories). Inline all the logic to a single _get_win32_installed_fonts, and also get rid of the extension filtering (both here and in get_fontconfig_fonts), as it can be done once for all at the call site (in `findSystemFonts`); also make the helpers return Paths, for simpler manipulation (we still convert to strs at the end).
1 parent 95b4527 commit 1933dd3

File tree

3 files changed

+67
-14
lines changed

3 files changed

+67
-14
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
``font_manager`` helpers
2+
~~~~~~~~~~~~~~~~~~~~~~~~
3+
The following functions in `maptlotlib.font_manager` have been deprecated:
4+
``win32FontDirectory``, ``win32InstalledFonts``, ``get_fontconfig_fonts``.

lib/matplotlib/font_manager.py

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ def list_fonts(directory, extensions):
195195
if Path(filename).suffix.lower() in extensions]
196196

197197

198+
@_api.deprecated("3.5")
198199
def win32FontDirectory():
199200
r"""
200201
Return the user-specified font directory for Win32. This is
@@ -212,6 +213,7 @@ def win32FontDirectory():
212213
return os.path.join(os.environ['WINDIR'], 'Fonts')
213214

214215

216+
@_api.deprecated("3.5")
215217
def _win32RegistryFonts(reg_domain, base_dir):
216218
r"""
217219
Search for fonts in the Windows registry.
@@ -264,6 +266,7 @@ def _win32RegistryFonts(reg_domain, base_dir):
264266
return items
265267

266268

269+
@_api.deprecated("3.5")
267270
def win32InstalledFonts(directory=None, fontext='ttf'):
268271
"""
269272
Search for fonts in the specified font directory, or use the
@@ -291,9 +294,52 @@ def win32InstalledFonts(directory=None, fontext='ttf'):
291294
return [str(path) for path in items if path.suffix.lower() in fontext]
292295

293296

297+
def _get_win32_installed_fonts():
298+
"""List the font paths known to the Windows registry."""
299+
import winreg
300+
301+
# Compute the user-specified font directory for Win32, which is the
302+
# registry entry for
303+
# \\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Fonts
304+
# if it exists, or %WINDIR%\Fonts.
305+
try:
306+
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, MSFolders) as user:
307+
local_dir = winreg.QueryValueEx(user, 'Fonts')[0]
308+
except OSError:
309+
local_dir = os.path.join(os.environ['WINDIR'], 'Fonts')
310+
311+
items = set()
312+
# Search and resolve fonts listed in the registry.
313+
for domain, base_dirs in [
314+
(winreg.HKEY_LOCAL_MACHINE, [local_dir]), # System fonts.
315+
(winreg.HKEY_CURRENT_USER, MSUserFontDirectories), # User fonts.
316+
]:
317+
for base_dir in base_dirs:
318+
for reg_path in MSFontDirectories:
319+
try:
320+
with winreg.OpenKey(domain, reg_path) as local:
321+
for j in range(winreg.QueryInfoKey(local)[1]):
322+
# value may contain the filename of the font or its
323+
# absolute path.
324+
key, value, tp = winreg.EnumValue(local, j)
325+
if not isinstance(value, str):
326+
continue
327+
try:
328+
# If value contains already an absolute path,
D7AE 329+
# then it is not changed further.
330+
path = Path(base_dir, value).resolve()
331+
except RuntimeError:
332+
# Don't fail with invalid entries.
333+
continue
334+
items.add(path)
335+
except (OSError, MemoryError):
336+
continue
337+
return items
338+
339+
294340
@lru_cache()
295-
def _call_fc_list():
296-
"""Cache and list the font filenames known to `fc-list`."""
341+
def _get_fontconfig_fonts():
342+
"""Cache and list the font paths known to `fc-list`."""
297343
try:
298344
if b'--format' not in subprocess.check_output(['fc-list', '--help']):
299345
_log.warning( # fontconfig 2.7 implemented --format.
@@ -302,14 +348,15 @@ def _call_fc_list():
302348
out = subprocess.check_output(['fc-list', '--format=%{file}\\n'])
303349
except (OSError, subprocess.CalledProcessError):
304350
return []
305-
return [os.fsdecode(fname) for fname in out.split(b'\n')]
351+
return [Path(os.fsdecode(fname)) for fname in out.split(b'\n')]
306352

307353

354+
@_api.deprecated("3.5")
308355
def get_fontconfig_fonts(fontext='ttf'):
309356
"""List font filenames known to `fc-list` having the given extension."""
310357
fontext = ['.' + ext for ext in get_fontext_synonyms(fontext)]
311-
return [fname for fname in _call_fc_list()
312-
if Path(fname).suffix.lower() in fontext]
358+
return [str(path) for path in _get_fontconfig_fonts()
359+
if path.suffix.lower() in fontext]
313360

314361

315362
def findSystemFonts(fontpaths=None, fontext='ttf'):
@@ -325,14 +372,16 @@ def findSystemFonts(fontpaths=None, fontext='ttf'):
325372

326373
if fontpaths is None:
327374
if sys.platform == 'win32':
375+
installed_fonts = _get_win32_installed_fonts()
328376
fontpaths = MSUserFontDirectories + [win32FontDirectory()]
329-
# now get all installed fonts directly...
330-
fontfiles.update(win32InstalledFonts(fontext=fontext))
331377
else:
332-
fontpaths = X11FontDirectories
378+
installed_fonts = _get_fontconfig_fonts()
333379
if sys.platform == 'darwin':
334380
fontpaths = [*X11FontDirectories, *OSXFontDirectories]
335-
fontfiles.update(get_fontconfig_fonts(fontext))
381+
else:
382+
fontpaths = X11FontDirectories
383+
fontfiles.update(str(path) for path in installed_fonts
384+
if path.suffix.lower()[1:] in fontexts)
336385

337386
elif isinstance(fontpaths, str):
338387
fontpaths = [fontpaths]

lib/matplotlib/tests/test_font_manager.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
from matplotlib.font_manager import (
1414
findfont, findSystemFonts, FontProperties, fontManager, json_dump,
15-
json_load, get_font, get_fontconfig_fonts, is_opentype_cff_font,
16-
MSUserFontDirectories, _call_fc_list)
15+
json_load, get_font, is_opentype_cff_font, MSUserFontDirectories,
16+
_get_fontconfig_fonts)
1717
from matplotlib import pyplot as plt, rc_context
1818

1919
has_fclist = shutil.which('fc-list') is not None
@@ -73,7 +73,7 @@ def test_otf():
7373

7474
@pytest.mark.skipif(not has_fclist, reason='no fontconfig installed')
7575
def test_get_fontconfig_fonts():
76-
assert len(get_fontconfig_fonts()) > 1
76+
assert len(_get_fontconfig_fonts()) > 1
7777

7878

7979
@pytest.mark.parametrize('factor', [2, 4, 6, 8])
@@ -154,13 +154,13 @@ def test_user_fonts_linux(tmpdir, monkeypatch):
154154

155155
with monkeypatch.context() as m:
156156
m.setenv('XDG_DATA_HOME', str(tmpdir))
157-
_call_fc_list.cache_clear()
157+
_get_fontconfig_fonts.cache_clear()
158158
# Now, the font should be available
159159
fonts = findSystemFonts()
160160
assert any(font_test_file in font for font in fonts)
161161

162162
# Make sure the temporary directory is no longer cached.
163-
_call_fc_list.cache_clear()
163+
_get_fontconfig_fonts.cache_clear()
164164

165165

166166
@pytest.mark.skipif(sys.platform != 'win32', reason='Windows only')

0 commit comments

Comments
 (0)
0