8000 Simplify font_manager font enumeration logic. · matplotlib/matplotlib@3cde1af · GitHub
[go: up one dir, main page]

Skip to content

Commit 3cde1af

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 3cde1af

File tree

3 files changed

+69
-14
lines changed

3 files changed

+69
-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+
~~~~~ 10000 ~~~~~~~~~~~~~~~~~~~
3+
The following functions in `maptlotlib.font_manager` have been deprecated:
4+
``win32FontDirectory``, ``win32InstalledFonts``, ``get_fontconfig_fonts``.

lib/matplotlib/font_manager.py

Lines changed: 60 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,54 @@ 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+
# System fonts.
315+
(winreg.HKEY_LOCAL_MACHINE, [local_dir]),
316+
# User fonts.
317+
(winreg.HKEY_CURRENT_USER, MSUserFontDirectories),
318+
]:
319+
for base_dir in base_dirs:
320+
for reg_path in MSFontDirectories:
321+
try:
322+
with winreg.OpenKey(domain, reg_path) as local:
323+
for j in range(winreg.QueryInfoKey(local)[1]):
324+
# value may contain the filename of the font or its
325+
# absolute path.
326+
key, value, tp = winreg.EnumValue(local, j)
327+
if not isinstance(value, str):
328+
continue
329+
try:
330+
# If value contains already an absolute path,
331+
# then it is not changed further.
332+
path = Path(base_dir, value).resolve()
333+
except RuntimeError:
334+
# Don't fail with invalid entries.
335+
continue
336+
items.add(path)
337+
except (OSError, MemoryError):
338+
continue
339+
return items
340+
341+
294342
@lru_cache()
295-
def _call_fc_list():
296-
"""Cache and list the font filenames known to `fc-list`."""
343+
def _get_fontconfig_fonts():
344+
"""Cache and list the font paths known to `fc-list`."""
297345
try:
298346
if b'--format' not in subprocess.check_output(['fc-list', '--help']):
299347
_log.warning( # fontconfig 2.7 implemented --format.
@@ -302,14 +350,15 @@ def _call_fc_list():
302350
out = subprocess.check_output(['fc-list', '--format=%{file}\\n'])
303351
except (OSError, subprocess.CalledProcessError):
304352
return []
305-
return [os.fsdecode(fname) for fname in out.split(b'\n')]
353+
return [Path(os.fsdecode(fname)) for fname in out.split(b'\n')]
306354

307355

356+
@_api.deprecated("3.5")
308357
def get_fontconfig_fonts(fontext='ttf'):
309358
"""List font filenames known to `fc-list` having the given extension."""
310359
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]
360+
return [str(path) for path in _get_fontconfig_fonts()
361+
if path.suffix.lower() in fontext]
313362

314363

315364
def findSystemFonts(fontpaths=None, fontext='ttf'):
@@ -325,14 +374,16 @@ def findSystemFonts(fontpaths=None, fontext='ttf'):
325374

326375
if fontpaths is None:
327376
if sys.platform == 'win32':
377+
installed_fonts = _get_win32_installed_fonts()
328378
fontpaths = MSUserFontDirectories + [win32FontDirectory()]
329-
# now get all installed fonts directly...
330-
fontfiles.update(win32InstalledFonts(fontext=fontext))
331379
else:
332-
fontpaths = X11FontDirectories
380+
installed_fonts = _get_fontconfig_fonts()
333381
if sys.platform == 'darwin':
334382
fontpaths = [*X11FontDirectories, *OSXFontDirectories]
335-
fontfiles.update(get_fontconfig_fonts(fontext))
383+
else:
384+
fontpaths = X11FontDirectories
385+
fontfiles.update(str(path) for path in installed_fonts
386+
if path.suffix.lower()[1:] in fontexts)
336387

337388
elif isinstance(fontpaths, str):
338389
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