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

Skip to content

Search also for user fonts on Windows (#12954) #12957

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 67 additions & 21 deletions lib/matplotlib/font_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@
r'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts',
r'SOFTWARE\Microsoft\Windows\CurrentVersion\Fonts']

MSUserFontDirectories = [
os.path.join(str(Path.home()), r'AppData\Local\Microsoft\Windows\Fonts'),
os.path.join(str(Path.home()), r'AppData\Roaming\Microsoft\Windows\Fonts')]

X11FontDirectories = [
# an old standard installation point
"/usr/X11R6/lib/X11/fonts/TTF/",
Expand Down Expand Up @@ -167,41 +171,83 @@ def win32FontDirectory():
return os.path.join(os.environ['WINDIR'], 'Fonts')


def win32InstalledFonts(directory=None, fontext='ttf'):
"""
Search for fonts in the specified font directory, or use the
system directories if none given. A list of TrueType font
filenames are returned by default, or AFM fonts if *fontext* ==
'afm'.
"""
import winreg
def _win32RegistryFonts(reg_domain, base_dir):
r"""
Searches for fonts in the Windows registry.

if directory is None:
directory = win32FontDirectory()
Parameters
----------
reg_domain : int
The top level registry domain (e.g. HKEY_LOCAL_MACHINE).

fontext = ['.' + ext for ext in get_fontext_synonyms(fontext)]
base_dir : str
The path to the folder where the font files are usually located (e.g.
C:\Windows\Fonts). If only the filename of the font is stored in the
registry, the absolute path is built relative to this base directory.

Returns
-------
`set`
`pathlib.Path` objects with the absolute path to the font files found.

"""
import winreg
items = set()
for fontdir in MSFontDirectories:

for reg_path in MSFontDirectories:
try:
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, fontdir) as local:
with winreg.OpenKey(reg_domain, reg_path) as local:
for j in range(winreg.QueryInfoKey(local)[1]):
key, direc, tp = winreg.EnumValue(local, j)
if not isinstance(direc, str):
# value may contain the filename of the font or its
# absolute path.
key, value, tp = winreg.EnumValue(local, j)
if not isinstance(value, str):
continue

# Work around for https://bugs.python.org/issue25778, which
# is fixed in Py>=3.6.1.
direc = direc.split("\0", 1)[0]
value = value.split("\0", 1)[0]

try:
path = Path(directory, direc).resolve()
# If value contains already an absolute path, then it
# is not changed further.
path = Path(base_dir, value).resolve()
except RuntimeError:
# Don't fail with invalid entries.
continue
if path.suffix.lower() in fontext:
items.add(str(path))

items.add(path)
except (OSError, MemoryError):
continue
return list(items)

return items


def win32InstalledFonts(directory=None, fontext='ttf'):
"""
Search for fonts in the specified font directory, or use the
system directories if none given. Additionally, it is searched for user
fonts installed. A list of TrueType font filenames are returned by default,
or AFM fonts if *fontext* == 'afm'.
"""
import winreg

if directory is None:
directory = win32FontDirectory()

fontext = ['.' + ext for ext in get_fontext_synonyms(fontext)]

items = set()

# System fonts
items.update(_win32RegistryFonts(winreg.HKEY_LOCAL_MACHINE, directory))

# User fonts
for userdir in MSUserFontDirectories:
items.update(_win32RegistryFonts(winreg.HKEY_CURRENT_USER, userdir))

# Keep only paths with matching file extension.
return [str(path) for path in items if path.suffix.lower() in fontext]


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

if fontpaths is None:
if sys.platform == 'win32':
fontpaths = [win32FontDirectory()]
fontpaths = MSUserFontDirectories + [win32FontDirectory()]
# now get all installed fonts directly...
fontfiles.update(win32InstalledFonts(fontext=fontext))
else:
Expand Down
32 changes: 30 additions & 2 deletions lib/matplotlib/tests/test_font_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
import pytest

from matplotlib.font_manager import (
findfont, FontProperties, fontManager, json_dump, json_load, get_font,
get_fontconfig_fonts, is_opentype_cff_font)
findfont, findSystemFonts, FontProperties, fontManager, json_dump,
json_load, get_font, get_fontconfig_fonts, is_opentype_cff_font,
MSUserFontDirectories)
from matplotlib import pyplot as plt, rc_context

has_fclist = shutil.which('fc-list') is not None
Expand Down Expand Up @@ -124,3 +125,30 @@ def test_find_ttc():
fig.savefig(BytesIO(), format="pdf")
with pytest.raises(RuntimeError):
fig.savefig(BytesIO(), format="ps")


def test_user_fonts():
if not os.environ.get('APPVEYOR', False):
pytest.xfail('This test does only work on appveyor since user fonts '
'are Windows specific and the developer\'s font '
'directory should remain unchanged')

font_test_file = 'mpltest.ttf'

# Precondition: the test font should not be available
fonts = findSystemFonts()
assert not any(font_test_file in font for font in fonts)

user_fonts_dir = MSUserFontDirectories[0]

# Make sure that the user font directory exists (this is probably not the
# case on Windows versions < 1809)
os.makedirs(user_fonts_dir)

# Copy the test font to the user font directory
shutil.copyfile(os.path.join(os.path.dirname(__file__), font_test_file),
os.path.join(user_fonts_dir, font_test_file))

# Now, the font should be available
fonts = findSystemFonts()
assert any(font_test_file in font for font in fonts)