-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Py3fy font_manager. #10522
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
Py3fy font_manager. #10522
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
Removal of deprecated functions | ||
``````````````````````````````` | ||
The following previously deprecated functions have been removed: | ||
- ``matplotlib.font_manager.ttfdict_to_fnames`` | ||
- ``matplotlib.font_manager.weight_as_number`` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,28 +20,16 @@ | |
found. | ||
""" | ||
|
||
import six | ||
|
||
""" | ||
KNOWN ISSUES | ||
|
||
- documentation | ||
- font variant is untested | ||
- font stretch is incomplete | ||
- font size is incomplete | ||
- default font algorithm needs improvement and testing | ||
- setWeights function needs improvement | ||
- 'light' is an invalid weight value, remove it. | ||
- update_fonts not implemented | ||
|
||
Authors : John Hunter <[email protected]> | ||
Paul Barrett <[email protected]> | ||
Michael Droettboom <[email protected]> | ||
Copyright : John Hunter (2004,2005), Paul Barrett (2004,2005) | ||
License : matplotlib license (PSF compatible) | ||
The font directory code is from ttfquery, | ||
see license/LICENSE_TTFQUERY. | ||
""" | ||
# KNOWN ISSUES | ||
# | ||
# - documentation | ||
# - font variant is untested | ||
# - font stretch is incomplete | ||
# - font size is incomplete | ||
# - default font algorithm needs improvement and testing | ||
# - setWeights function needs improvement | ||
# - 'light' is an invalid weight value, remove it. | ||
# - update_fonts not implemented | ||
|
||
from collections import Iterable | ||
from functools import lru_cache | ||
|
@@ -179,22 +167,17 @@ def win32FontDirectory(): | |
|
||
If the key is not found, $WINDIR/Fonts will be returned. | ||
""" | ||
import winreg | ||
try: | ||
from six.moves import winreg | ||
except ImportError: | ||
pass # Fall through to default | ||
else: | ||
user = winreg.OpenKey(winreg.HKEY_CURRENT_USER, MSFolders) | ||
try: | ||
user = winreg.OpenKey(winreg.HKEY_CURRENT_USER, MSFolders) | ||
try: | ||
try: | ||
return winreg.QueryValueEx(user, 'Fonts')[0] | ||
except OSError: | ||
pass # Fall through to default | ||
finally: | ||
winreg.CloseKey(user) | ||
return winreg.QueryValueEx(user, 'Fonts')[0] | ||
except OSError: | ||
pass # Fall through to default | ||
finally: | ||
winreg.CloseKey(user) | ||
except OSError: | ||
pass # Fall through to default | ||
return os.path.join(os.environ['WINDIR'], 'Fonts') | ||
|
||
|
||
|
@@ -206,7 +189,8 @@ def win32InstalledFonts(directory=None, fontext='ttf'): | |
'afm'. | ||
""" | ||
|
||
from six.moves import winreg | ||
import winreg | ||
|
||
if directory is None: | ||
directory = win32FontDirectory() | ||
|
||
|
@@ -224,7 +208,7 @@ def win32InstalledFonts(directory=None, fontext='ttf'): | |
for j in range(winreg.QueryInfoKey(local)[1]): | ||
try: | ||
key, direc, tp = winreg.EnumValue(local, j) | ||
if not isinstance(direc, six.string_types): | ||
if not isinstance(direc, str): | ||
continue | ||
# Work around for https://bugs.python.org/issue25778, which | ||
# is fixed in Py>=3.6.1. | ||
|
@@ -274,19 +258,12 @@ def _call_fc_list(): | |
'This may take a moment.')) | ||
timer.start() | ||
try: | ||
out = subprocess.check_output([str('fc-list'), '--format=%{file}\\n']) | ||
out = subprocess.check_output(['fc-list', '--format=%{file}\\n']) | ||
except (OSError, subprocess.CalledProcessError): | ||
return [] | ||
finally: | ||
timer.cancel() | ||
fnames = [] | ||
for fname in out.split(b'\n'): | ||
try: | ||
fname = six.text_type(fname, sys.getfilesystemencoding()) | ||
except UnicodeDecodeError: | ||
continue | ||
fnames.append(fname) | ||
return fnames | ||
return [os.fsdecode(fname) for fname in out.split(b'\n')] | ||
|
||
|
||
def get_fontconfig_fonts(fontext='ttf'): | ||
|
@@ -328,7 +305,7 @@ def findSystemFonts(fontpaths=None, fontext='ttf'): | |
for f in get_fontconfig_fonts(fontext): | ||
fontfiles.add(f) | ||
|
||
elif isinstance(fontpaths, six.string_types): | ||
elif isinstance(fontpaths, str): | ||
fontpaths = [fontpaths] | ||
|
||
for path in fontpaths: | ||
|
@@ -478,9 +455,9 @@ def afmFontProperty(fontpath, font): | |
|
||
# Styles are: italic, oblique, and normal (default) | ||
|
||
if font.get_angle() != 0 or name.lower().find('italic') >= 0: | ||
if font.get_angle() != 0 or 'italic' in name.lower(): | ||
style = 'italic' | ||
elif name.lower().find('oblique') >= 0: | ||
elif 'oblique' in name.lower(): | ||
style = 'oblique' | ||
else: | ||
style = 'normal' | ||
|
@@ -501,12 +478,11 @@ def afmFontProperty(fontpath, font): | |
# and ultra-expanded. | ||
# Relative stretches are: wider, narrower | ||
# Child value is: inherit | ||
if fontname.find('narrow') >= 0 or fontname.find('condensed') >= 0 or \ | ||
fontname.find('cond') >= 0: | ||
stretch = 'condensed' | ||
elif fontname.find('demi cond') >= 0: | ||
if 'demi cond' in fontname: | ||
stretch = 'semi-condensed' | ||
elif fontname.find('wide') >= 0 or fontname.find('expanded') >= 0: | ||
elif 'narrow' in fontname or 'cond' in fontname: | ||
stretch = 'condensed' | ||
elif 'wide' in fontname or 'expanded' in fontname: | ||
stretch = 'expanded' | ||
else: | ||
stretch = 'normal' | ||
|
@@ -568,7 +544,7 @@ def createFontList(fontfiles, fontext='ttf'): | |
except UnicodeError: | ||
_log.info("Cannot handle unicode filenames") | ||
continue | ||
except IOError: | ||
except OSError: | ||
_log.info("IO error - cannot open font file %s", fpath) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If its now an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, from the POV of the user it is indeed an input/output error... (and IOError == OSError anyways on Py3) |
||
continue | ||
try: | ||
|
@@ -646,7 +622,7 @@ def __init__(self, | |
weight = None, | ||
stretch= None, | ||
size = None, | ||
fname = None, # if this is set, it's a hardcoded filename to use | ||
fname = None, # if set, it's a hardcoded filename to use | ||
_init = None # used only by copy() | ||
): | ||
self._family = _normalize_font_family(rcParams['font.family']) | ||
|
@@ -662,7 +638,7 @@ def __init__(self, | |
self.__dict__.update(_init.__dict__) | ||
return | ||
|
||
if isinstance(family, six.string_types): | ||
if isinstance(family, str): | ||
# Treat family as a fontconfig pattern if it is the only | ||
# parameter provided. | ||
if (style is None and | ||
|
@@ -712,23 +688,20 @@ def get_family(self): | |
|
||
def get_name(self): | ||
""" | ||
Return the name of the font that best matches the font | ||
properties. | ||
Return the name of the font that best matches the font properties. | ||
""" | ||
return get_font(findfont(self)).family_name | ||
|
||
def get_style(self): | ||
""" | ||
Return the font style. Values are: 'normal', 'italic' or | ||
'oblique'. | ||
Return the font style. Values are: 'normal', 'italic' or 'oblique'. | ||
""" | ||
return self._slant | ||
get_slant = get_style | ||
|
||
def get_variant(self): | ||
""" | ||
Return the font variant. Values are: 'normal' or | ||
'small-caps'. | ||
Return the font variant. Values are: 'normal' or 'small-caps'. | ||
""" | ||
return self._variant | ||
|
||
|
@@ -793,8 +766,7 @@ def set_family(self, family): | |
|
||
def set_style(self, style): | ||
""" | ||
Set the font style. Values are: 'normal', 'italic' or | ||
'oblique'. | ||
Set the font style. Values are: 'normal', 'italic' or 'oblique'. | ||
""" | ||
if style is None: | ||
style = rcParams['font.style'] | ||
|
@@ -892,7 +864,7 @@ def set_fontconfig_pattern(self, pattern): | |
support for it to be enabled. We are merely borrowing its | ||
pattern syntax for use here. | ||
""" | ||
for key, val in six.iteritems(self._parse_fontconfig_pattern(pattern)): | ||
for key, val in self._parse_fontconfig_pattern(pattern).items(): | ||
if type(val) == list: | ||
getattr(self, "set_" + key)(val[0]) | ||
else: | ||
|
@@ -936,9 +908,10 @@ def json_dump(data, filename): | |
with open(filename, 'w') as fh: | ||
try: | ||
json.dump(data, fh, cls=JSONEncoder, indent=2) | ||
except IOError as e: | ||
except OSError as e: | ||
warnings.warn('Could not save font_manager cache ', e) | ||
|
||
|
||
def json_load(filename): | ||
"""Loads a data structure as JSON from the named file. | ||
Handles FontManager and its fields.""" | ||
|
@@ -948,10 +921,8 @@ def json_load(filename): | |
|
||
|
||
def _normalize_font_family(family): | ||
if isinstance(family, six.string_types): | ||
family = [six.text_type(family)] | ||
elif isinstance(family, Iterable): | ||
family = [six.text_type(f) for f in family] | ||
if isinstance(family, str): | ||
family = [family] | ||
return family | ||
|
||
|
||
|
@@ -1170,14 +1141,14 @@ def score_weight(self, weight1, weight2): | |
The result is 0.0 if both weight1 and weight 2 are given as strings | ||
and have the same value. | ||
|
||
Otherwise, the result is the absolute value of the difference between the | ||
CSS numeric values of *weight1* and *weight2*, normalized | ||
between 0.05 and 1.0. | ||
Otherwise, the result is the absolute value of the difference between | ||
the CSS numeric values of *weight1* and *weight2*, normalized between | ||
0.05 and 1.0. | ||
""" | ||
|
||
# exact match of the weight names (e.g. weight1 == weight2 == "regular") | ||
if (isinstance(weight1, six.string_types) and | ||
isinstance(weight2, six.string_types) and | ||
# exact match of the weight names, e.g. weight1 == weight2 == "regular" | ||
if (isinstance(weight1, str) and | ||
isinstance(weight2, str) and | ||
weight1 == weight2): | ||
return 0.0 | ||
try: | ||
|
@@ -1364,26 +1335,22 @@ def fc_match(pattern, fontext): | |
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE) | ||
output = pipe.communicate()[0] | ||
except (OSError, IOError): | ||
except OSError: | ||
return None | ||
|
||
# The bulk of the output from fc-list is ascii, so we keep the | ||
# result in bytes and parse it as bytes, until we extract the | ||
# filename, which is in sys.filesystemencoding(). | ||
if pipe.returncode == 0: | ||
for fname in output.split(b'\n'): | ||
try: | ||
fname = six.text_type(fname, sys.getfilesystemencoding()) | ||
except UnicodeDecodeError: | ||
continue | ||
for fname in map(os.fsdecode, output.split(b'\n')): | ||
if os.path.splitext(fname)[1][1:] in fontexts: | ||
return fname | ||
return None | ||
|
||
_fc_match_cache = {} | ||
|
||
def findfont(prop, fontext='ttf'): | ||
if not isinstance(prop, six.string_types): | ||
if not isinstance(prop, str): | ||
prop = prop.get_fontconfig_pattern() | ||
cached = _fc_match_cache.get(prop) | ||
if cached is not None: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why have these if statements swapped order?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the search for "demi cond" has to go before the search for "cond" (otherwise the latter always happens before the former...).