Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a598811

Browse files
authored
Merge pull request #12957 from Milania1/win-user-fonts
Search also for user fonts on Windows (#12954)
2 parents 4cdd071 + 462dd7b commit a598811

File tree

2 files changed

+97
-23
lines changed

2 files changed

+97
-23
lines changed

lib/matplotlib/font_manager.py

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@
9999
r'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts',
100100
r'SOFTWARE\Microsoft\Windows\CurrentVersion\Fonts']
101101

102+
MSUserFontDirectories = [
103+
os.path.join(str(Path.home()), r'AppData\Local\Microsoft\Windows\Fonts'),
104+
os.path.join(str(Path.home()), r'AppData\Roaming\Microsoft\Windows\Fonts')]
105+
102106
X11FontDirectories = [
103107
# an old standard installation point
104108
"/usr/X11R6/lib/X11/fonts/TTF/",
@@ -167,41 +171,83 @@ def win32FontDirectory():
167171
return os.path.join(os.environ['WINDIR'], 'Fonts')
168172

169173

170-
def win32InstalledFonts(directory=None, fontext='ttf'):
171-
"""
172-
Search for fonts in the specified font directory, or use the
173-
system directories if none given. A list of TrueType font
174-
filenames are returned by default, or AFM fonts if *fontext* ==
175-
'afm'.
176-
"""
177-
import winreg
174+
def _win32RegistryFonts(reg_domain, base_dir):
175+
r"""
176+
Searches for fonts in the Windows registry.
178177
179-
if directory is None:
180-
directory = win32FontDirectory()
178+
Parameters
179+
----------
180+
reg_domain : int
181+
The top level registry domain (e.g. HKEY_LOCAL_MACHINE).
181182
182-
fontext = ['.' + ext for ext in get_fontext_synonyms(fontext)]
183+
base_dir : str
184+
The path to the folder where the font files are usually located (e.g.
185+
C:\Windows\Fonts). If only the filename of the font is stored in the
186+
registry, the absolute path is built relative to this base directory.
187+
188+
Returns
189+
-------
190+
`set`
191+
`pathlib.Path` objects with the absolute path to the font files found.
183192
193+
"""
194+
import winreg
184195
items = set()
185-
for fontdir in MSFontDirectories:
196+
197+
for reg_path in MSFontDirectories:
186198
try:
187-
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, fontdir) as local:
199+
with winreg.OpenKey(reg_domain, reg_path) as local:
188200
for j in range(winreg.QueryInfoKey(local)[1]):
189-
key, direc, tp = winreg.EnumValue(local, j)
190-
if not isinstance(direc, str):
201+
# value may contain the filename of the font or its
202+
# absolute path.
203+
key, value, tp = winreg.EnumValue(local, j)
204+
if not isinstance(value, str):
191205
continue
206+
192207
# Work around for https://bugs.python.org/issue25778, which
193208
# is fixed in Py>=3.6.1.
194-
direc = direc.split("\0", 1)[0]
209+
value = value.split("\0", 1)[0]
210+
195211
try:
196-
path = Path(directory, direc).resolve()
212+
# If value contains already an absolute path, then it
213+
# is not changed further.
214+
path = Path(base_dir, value).resolve()
197215
except RuntimeError:
198216
# Don't fail with invalid entries.
199217
continue
200-
if path.suffix.lower() in fontext:
201-
items.add(str(path))
218+
219+
items.add(path)
202220
except (OSError, MemoryError):
203221
continue
204-
return list(items)
222+
223+
return items
224+
225+
226+
def win32InstalledFonts(directory=None, fontext='ttf'):
227+
"""
228+
Search for fonts in the specified font directory, or use the
229+
system directories if none given. Additionally, it is searched for user
230+
fonts installed. A list of TrueType font filenames are returned by default,
231+
or AFM fonts if *fontext* == 'afm'.
232+
"""
233+
import winreg
234+
235+
if directory is None:
236+
directory = win32FontDirectory()
237+
238+
fontext = ['.' + ext for ext in get_fontext_synonyms(fontext)]
239+
240+
items = set()
241+
242+
# System fonts
243+
items.update(_win32RegistryFonts(winreg.HKEY_LOCAL_MACHINE, directory))
244+
245+
# User fonts
246+
for userdir in MSUserFontDirectories:
247+
items.update(_win32RegistryFonts(winreg.HKEY_CURRENT_USER, userdir))
248+
249+
# Keep only paths with matching file extension.
250+
return [str(path) for path in items if path.suffix.lower() in fontext]
205251

206252

207253
@cbook.deprecated("3.1")
@@ -253,7 +299,7 @@ def findSystemFonts(fontpaths=None, fontext='ttf'):
253299

254300
if fontpaths is None:
255301
if sys.platform == 'win32':
256-
fontpaths = [win32FontDirectory()]
302+
fontpaths = MSUserFontDirectories + [win32FontDirectory()]
257303
# now get all installed fonts directly...
258304
fontfiles.update(win32InstalledFonts(fontext=fontext))
259305
else:

lib/matplotlib/tests/test_font_manager.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
import pytest
1010

1111
from matplotlib.font_manager import (
12-
findfont, FontProperties, fontManager, json_dump, json_load, get_font,
13-
get_fontconfig_fonts, is_opentype_cff_font)
12+
findfont, findSystemFonts, FontProperties, fontManager, json_dump,
13+
json_load, get_font, get_fontconfig_fonts, is_opentype_cff_font,
14+
MSUserFontDirectories)
1415
from matplotlib import pyplot as plt, rc_context
1516

1617
has_fclist = shutil.which('fc-list') is not None
@@ -124,3 +125,30 @@ def test_find_ttc():
124125
fig.savefig(BytesIO(), format="pdf")
125126
with pytest.raises(RuntimeError):
126127
fig.savefig(BytesIO(), format="ps")
128+
129+
130+
def test_user_fonts():
131+
if not os.environ.get('APPVEYOR', False):
132+
pytest.xfail('This test does only work on appveyor since user fonts '
133+
'are Windows specific and the developer\'s font '
134+
'directory should remain unchanged')
135+
136+
font_test_file = 'mpltest.ttf'
137+
138+
# Precondition: the test font should not be available
139+
fonts = findSystemFonts()
140+
assert not any(font_test_file in font for font in fonts)
141+
142+
user_fonts_dir = MSUserFontDirectories[0]
143+
144+
# Make sure that the user font directory exists (this is probably not the
145+
# case on Windows versions < 1809)
146+
os.makedirs(user_fonts_dir)
147+
148+
# Copy the test font to the user font directory
149+
shutil.copyfile(os.path.join(os.path.dirname(__file__), font_test_file),
150+
os.path.join(user_fonts_dir, font_test_file))
151+
152+
# Now, the font should be available
153+
fonts = findSystemFonts()
154+
assert any(font_test_file in font for font in fonts)

0 commit comments

Comments
 (0)