From 7f1eb421c90424203a67a805ff5223be4c76d9e9 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Tue, 3 Dec 2019 06:11:21 +0100 Subject: [PATCH] Backport PR #15601: Fix FontProperties conversion to/from strings --- lib/matplotlib/fontconfig_pattern.py | 33 ++++++--- .../tests/test_fontconfig_pattern.py | 70 +++++++++++++++++++ 2 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 lib/matplotlib/tests/test_fontconfig_pattern.py diff --git a/lib/matplotlib/fontconfig_pattern.py b/lib/matplotlib/fontconfig_pattern.py index d15b937b249a..1f4df32c0fcd 100644 --- a/lib/matplotlib/fontconfig_pattern.py +++ b/lib/matplotlib/fontconfig_pattern.py @@ -14,7 +14,7 @@ from functools import lru_cache import re - +import numpy as np from pyparsing import (Literal, ZeroOrMore, Optional, Regex, StringEnd, ParseException, Suppress) @@ -177,19 +177,36 @@ def _property(self, s, loc, tokens): parse_fontconfig_pattern = lru_cache()(FontconfigPatternParser().parse) +def _escape_val(val, escape_func): + """ + Given a string value or a list of string values, run each value through + the input escape function to make the values into legal font config + strings. The result is returned as a string. + """ + if not np.iterable(val) or isinstance(val, str): + val = [val] + + return ','.join(escape_func(r'\\\1', str(x)) for x in val + if x is not None) + + def generate_fontconfig_pattern(d): """ Given a dictionary of key/value pairs, generates a fontconfig pattern string. """ props = [] - for key in 'family style variant weight stretch file size'.split(): + + # Family is added first w/o a keyword + family = d.get_family() + if family is not None and family != []: + props.append(_escape_val(family, family_escape)) + + # The other keys are added as key=value + for key in ['style', 'variant', 'weight', 'stretch', 'file', 'size']: val = getattr(d, 'get_' + key)() + # Don't use 'if not val' because 0 is a valid input. if val is not None and val != []: - if type(val) == list: - val = [value_escape(r'\\\1', str(x)) for x in val - if x is not None] - if val != []: - val = ','.join(val) - props.append(":%s=%s" % (key, val)) + props.append(":%s=%s" % (key, _escape_val(val, value_escape))) + return ''.join(props) diff --git a/lib/matplotlib/tests/test_fontconfig_pattern.py b/lib/matplotlib/tests/test_fontconfig_pattern.py new file mode 100644 index 000000000000..521a3e882d70 --- /dev/null +++ b/lib/matplotlib/tests/test_fontconfig_pattern.py @@ -0,0 +1,70 @@ +from matplotlib.font_manager import FontProperties + + +# Attributes on FontProperties object to check for consistency +keys = [ + "get_family", + "get_style", + "get_variant", + "get_weight", + "get_size", + ] + + +def test_fontconfig_pattern(): + "Test converting a FontProperties to string then back." + + # Defaults + test = "defaults " + f1 = FontProperties() + s = str(f1) + + f2 = FontProperties(s) + for k in keys: + assert getattr(f1, k)() == getattr(f2, k)(), test + k + + # Basic inputs + test = "basic " + f1 = FontProperties(family="serif", size=20, style="italic") + s = str(f1) + + f2 = FontProperties(s) + for k in keys: + assert getattr(f1, k)() == getattr(f2, k)(), test + k + + # Full set of inputs. + test = "full " + f1 = FontProperties(family="sans-serif", size=24, weight="bold", + style="oblique", variant="small-caps", + stretch="expanded") + s = str(f1) + + f2 = FontProperties(s) + for k in keys: + assert getattr(f1, k)() == getattr(f2, k)(), test + k + + +def test_fontconfig_str(): + "Test FontProperties string conversions for correctness" + + # Known good strings taken from actual font config specs on a linux box + # and modified for MPL defaults. + + # Default values found by inspection. + test = "defaults " + s = ("sans\\-serif:style=normal:variant=normal:weight=normal" + ":stretch=normal:size=12.0") + font = FontProperties(s) + right = FontProperties() + for k in keys: + assert getattr(font, k)() == getattr(right, k)(), test + k + + test = "full " + s = ("serif:size=24:style=oblique:variant=small-caps:weight=bold" + ":stretch=expanded") + font = FontProperties(s) + right = FontProperties(family="serif", size=24, weight="bold", + style="oblique", variant="small-caps", + stretch="expanded") + for k in keys: + assert getattr(font, k)() == getattr(right, k)(), test + k