|
18 | 18 | from matplotlib import _api
|
19 | 19 |
|
20 | 20 |
|
21 |
| -family_punc = r'\\\-:,' |
22 |
| -_family_unescape = partial(re.compile(r'\\(?=[%s])' % family_punc).sub, '') |
23 |
| -_family_escape = partial(re.compile(r'(?=[%s])' % family_punc).sub, r'\\') |
24 |
| -value_punc = r'\\=_:,' |
25 |
| -_value_unescape = partial(re.compile(r'\\(?=[%s])' % value_punc).sub, '') |
26 |
| -_value_escape = partial(re.compile(r'(?=[%s])' % value_punc).sub, r'\\') |
27 |
| - |
28 |
| -# Remove after module deprecation elapses (3.8); then remove underscores |
29 |
| -# from _{family,value}_{un,}escape. |
30 |
| -family_unescape = re.compile(r'\\([%s])' % family_punc).sub |
31 |
| -value_unescape = re.compile(r'\\([%s])' % value_punc).sub |
32 |
| -family_escape = re.compile(r'([%s])' % family_punc).sub |
33 |
| -value_escape = re.compile(r'([%s])' % value_punc).sub |
34 |
| - |
35 |
| - |
36 |
| -class FontconfigPatternParser: |
37 |
| - """ |
38 |
| - A simple pyparsing-based parser for `fontconfig patterns`_. |
39 |
| -
|
40 |
| - .. _fontconfig patterns: |
41 |
| - https://www.freedesktop.org/software/fontconfig/fontconfig-user.html |
42 |
| - """ |
43 |
| - |
44 |
| - _constants = { |
45 |
| - 'thin': ('weight', 'light'), |
46 |
| - 'extralight': ('weight', 'light'), |
47 |
| - 'ultralight': ('weight', 'light'), |
48 |
| - 'light': ('weight', 'light'), |
49 |
| - 'book': ('weight', 'book'), |
50 |
| - 'regular': ('weight', 'regular'), |
51 |
| - 'normal': ('weight', 'normal'), |
52 |
| - 'medium': ('weight', 'medium'), |
53 |
| - 'demibold': ('weight', 'demibold'), |
54 |
| - 'semibold': ('weight', 'semibold'), |
55 |
| - 'bold': ('weight', 'bold'), |
56 |
| - 'extrabold': ('weight', 'extra bold'), |
57 |
| - 'black': ('weight', 'black'), |
58 |
| - 'heavy': ('weight', 'heavy'), |
59 |
| - 'roman': ('slant', 'normal'), |
60 |
| - 'italic': ('slant', 'italic'), |
61 |
| - 'oblique': ('slant', 'oblique'), |
62 |
| - 'ultracondensed': ('width', 'ultra-condensed'), |
63 |
| - 'extracondensed': ('width', 'extra-condensed'), |
64 |
| - 'condensed': ('width', 'condensed'), |
65 |
| - 'semicondensed': ('width', 'semi-condensed'), |
66 |
| - 'expanded': ('width', 'expanded'), |
67 |
| - 'extraexpanded': ('width', 'extra-expanded'), |
68 |
| - 'ultraexpanded': ('width', 'ultra-expanded'), |
69 |
| - } |
70 |
| - |
71 |
| - def __init__(self): |
72 |
| - def comma_separated(elem): |
73 |
| - return elem + ZeroOrMore(Suppress(",") + elem) |
74 |
| - |
75 |
| - family = Regex(r"([^%s]|(\\[%s]))*" % (family_punc, family_punc)) |
76 |
| - size = Regex(r"([0-9]+\.?[0-9]*|\.[0-9]+)") |
77 |
| - name = Regex(r"[a-z]+") |
78 |
| - value = Regex(r"([^%s]|(\\[%s]))*" % (value_punc, value_punc)) |
79 |
| - prop = ( |
80 |
| - (name + Suppress("=") + comma_separated(value)) |
81 |
| - | name # replace by oneOf(self._constants) in mpl 3.9. |
82 |
| - ) |
83 |
| - pattern = ( |
84 |
| - Optional(comma_separated(family)("families")) |
85 |
| - + Optional("-" + comma_separated(size)("sizes")) |
86 |
| - + ZeroOrMore(":" + prop("properties*")) |
87 |
| - + StringEnd() |
88 |
| - ) |
89 |
| - self._parser = pattern |
90 |
| - self.ParseException = ParseException |
91 |
| - |
92 |
| - def parse(self, pattern): |
93 |
| - """ |
94 |
| - Parse the given fontconfig *pattern* and return a dictionary |
95 |
| - of key/value pairs useful for initializing a |
96 |
| - `.font_manager.FontProperties` object. |
97 |
| - """ |
98 |
| - try: |
99 |
| - parse = self._parser.parseString(pattern) |
100 |
| - except ParseException as err: |
101 |
| - # explain becomes a plain method on pyparsing 3 (err.explain(0)). |
102 |
| - raise ValueError("\n" + ParseException.explain(err, 0)) from None |
103 |
| - self._parser.resetCache() |
104 |
| - props = {} |
105 |
| - if "families" in parse: |
106 |
| - props["family"] = [*map(_family_unescape, parse["families"])] |
107 |
| - if "sizes" in parse: |
108 |
| - props["size"] = [*parse["sizes"]] |
109 |
| - for prop in parse.get("properties", []): |
110 |
| - if len(prop) == 1: |
111 |
| - if prop[0] not in self._constants: |
112 |
| - _api.warn_deprecated( |
113 |
| - "3.7", message=f"Support for unknown constants " |
114 |
| - f"({prop[0]!r}) is deprecated since %(since)s and " |
115 |
| - f"will be removed %(removal)s.") |
116 |
| - continue |
117 |
| - prop = self._constants[prop[0]] |
118 |
| - k, *v = prop |
119 |
| - props.setdefault(k, []).extend(map(_value_unescape, v)) |
120 |
| - return props |
| 21 | +_family_punc = r'\\\-:,' |
| 22 | +_family_unescape = partial(re.compile(r'\\(?=[%s])' % _family_punc).sub, '') |
| 23 | +_family_escape = partial(re.compile(r'(?=[%s])' % _family_punc).sub, r'\\') |
| 24 | +_value_punc = r'\\=_:,' |
| 25 | +_value_unescape = partial(re.compile(r'\\(?=[%s])' % _value_punc).sub, '') |
| 26 | +_value_escape = partial(re.compile(r'(?=[%s])' % _value_punc).sub, r'\\') |
| 27 | + |
| 28 | + |
| 29 | +_CONSTANTS = { |
| 30 | + 'thin': ('weight', 'light'), |
| 31 | + 'extralight': ('weight', 'light'), |
| 32 | + 'ultralight': ('weight', 'light'), |
| 33 | + 'light': ('weight', 'light'), |
| 34 | + 'book': ('weight', 'book'), |
| 35 | + 'regular': ('weight', 'regular'), |
| 36 | + 'normal': ('weight', 'normal'), |
| 37 | + 'medium': ('weight', 'medium'), |
| 38 | + 'demibold': ('weight', 'demibold'), |
| 39 | + 'semibold': ('weight', 'semibold'), |
| 40 | + 'bold': ('weight', 'bold'), |
| 41 | + 'extrabold': ('weight', 'extra bold'), |
| 42 | + 'black': ('weight', 'black'), |
| 43 | + 'heavy': ('weight', 'heavy'), |
| 44 | + 'roman': ('slant', 'normal'), |
| 45 | + 'italic': ('slant', 'italic'), |
| 46 | + 'oblique': ('slant', 'oblique'), |
| 47 | + 'ultracondensed': ('width', 'ultra-condensed'), |
| 48 | + 'extracondensed': ('width', 'extra-condensed'), |
| 49 | + 'condensed': ('width', 'condensed'), |
| 50 | + 'semicondensed': ('width', 'semi-condensed'), |
| 51 | + 'expanded': ('width', 'expanded'), |
| 52 | + 'extraexpanded': ('width', 'extra-expanded'), |
| 53 | + 'ultraexpanded': ('width', 'ultra-expanded'), |
| 54 | +} |
| 55 | + |
| 56 | + |
| 57 | +@lru_cache # The parser instance is a singleton. |
| 58 | +def _make_fontconfig_parser(): |
| 59 | + def comma_separated(elem): |
| 60 | + return elem + ZeroOrMore(Suppress(",") + elem) |
| 61 | + |
| 62 | + family = Regex(r"([^%s]|(\\[%s]))*" % (_family_punc, _family_punc)) |
| 63 | + size = Regex(r"([0-9]+\.?[0-9]*|\.[0-9]+)") |
| 64 | + name = Regex(r"[a-z]+") |
| 65 | + value = Regex(r"([^%s]|(\\[%s]))*" % (_value_punc, _value_punc)) |
| 66 | + # replace trailing `| name` by oneOf(_CONSTANTS) in mpl 3.9. |
| 67 | + prop = (name + Suppress("=") + comma_separated(value)) | name |
| 68 | + return ( |
| 69 | + Optional(comma_separated(family)("families")) |
| 70 | + + Optional("-" + comma_separated(size)("sizes")) |
| 71 | + + ZeroOrMore(":" + prop("properties*")) |
| 72 | + + StringEnd() |
| 73 | + ) |
121 | 74 |
|
122 | 75 |
|
123 | 76 | # `parse_fontconfig_pattern` is a bottleneck during the tests because it is
|
124 | 77 | # repeatedly called when the rcParams are reset (to validate the default
|
125 | 78 | # fonts). In practice, the cache size doesn't grow beyond a few dozen entries
|
126 | 79 | # during the test suite.
|
127 |
| -parse_fontconfig_pattern = lru_cache()(FontconfigPatternParser().parse) |
| 80 | +@lru_cache |
| 81 | +def parse_fontconfig_pattern(pattern): |
| 82 | + """ |
| 83 | + Parse a fontconfig *pattern* into a dict that can initialize a |
| 84 | + `.font_manager.FontProperties` object. |
| 85 | + """ |
| 86 | + parser = _make_fontconfig_parser() |
| 87 | + try: |
| 88 | + parse = parser.parseString(pattern) |
| 89 | + except ParseException as err: |
| 90 | + # explain becomes a plain method on pyparsing 3 (err.explain(0)). |
| 91 | + raise ValueError("\n" + ParseException.explain(err, 0)) from None |
| 92 | + parser.resetCache() |
| 93 | + props = {} |
| 94 | + if "families" in parse: |
| 95 | + props["family"] = [*map(_family_unescape, parse["families"])] |
| 96 | + if "sizes" in parse: |
| 97 | + props["size"] = [*parse["sizes"]] |
| 98 | + for prop in parse.get("properties", []): |
| 99 | + if len(prop) == 1: |
| 100 | + if prop[0] not in _CONSTANTS: |
| 101 | + _api.warn_deprecated( |
| 102 | + "3.7", message=f"Support for unknown constants " |
| 103 | + f"({prop[0]!r}) is deprecated since %(since)s and " |
| 104 | + f"will be removed %(removal)s.") |
| 105 | + continue |
| 106 | + prop = _CONSTANTS[prop[0]] |
| 107 | + k, *v = prop |
| 108 | + props.setdefault(k, []).extend(map(_value_unescape, v)) |
| 109 | + return props |
128 | 110 |
|
129 | 111 |
|
130 | 112 | def generate_fontconfig_pattern(d):
|
|
0 commit comments