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

Skip to content

Commit 96c2ca0

Browse files
committed
Kill FontconfigPatternParser.
Remove the single-method class in favor of plain functions. The backcompat class can be moved to the deprecated public fontconfig_parser (no underscore) module, so they'll get removed at the same time. While we're at it do the same for {family,value}_{escape,unescape}; however, despite the comment, I decided to keep the new versions of _{family,value}_{escape,unescape} still underscore-prefixed within the private module (signalling that these aren't intended to be used even from other Matplotlib modules).
1 parent f2b5486 commit 96c2ca0

File tree

4 files changed

+100
-110
lines changed

4 files changed

+100
-110
lines changed

doc/api/fontconfig_pattern_api.rst

Lines changed: 0 additions & 8 deletions
This file was deleted.

doc/api/index.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ Alphabetical list of modules:
118118
dviread.rst
119119
figure_api.rst
120120
font_manager_api.rst
121-
fontconfig_pattern_api.rst
122121
ft2font.rst
123122
gridspec_api.rst
124123
hatch_api.rst

lib/matplotlib/_fontconfig_pattern.py

Lines changed: 83 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -18,113 +18,95 @@
1818
from matplotlib import _api
1919

2020

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+
)
12174

12275

12376
# `parse_fontconfig_pattern` is a bottleneck during the tests because it is
12477
# repeatedly called when the rcParams are reset (to validate the default
12578
# fonts). In practice, the cache size doesn't grow beyond a few dozen entries
12679
# 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
128110

129111

130112
def generate_fontconfig_pattern(d):

lib/matplotlib/fontconfig_pattern.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
import re
2+
from pyparsing import ParseException
3+
14
from matplotlib._fontconfig_pattern import * # noqa: F401, F403
5+
from matplotlib._fontconfig_pattern import (
6+
parse_fontconfig_pattern, _family_punc, _value_punc)
27
from matplotlib import _api
38
_api.warn_deprecated("3.6", name=__name__, obj_type="module")
9+
10+
11+
family_unescape = re.compile(r'\\([%s])' % _family_punc).sub
12+
value_unescape = re.compile(r'\\([%s])' % _value_punc).sub
13+
family_escape = re.compile(r'([%s])' % _family_punc).sub
14+
value_escape = re.compile(r'([%s])' % _value_punc).sub
15+
16+
17+
class FontconfigPatternParser:
18+
ParseException = ParseException
19+
20+
def parse(self, pattern): return parse_fontconfig_pattern(pattern)

0 commit comments

Comments
 (0)