9
9
# there would have created cyclical dependency problems, because it also needs
10
10
# to be available from `matplotlib.rcsetup` (for parsing matplotlibrc files).
11
11
12
- from functools import lru_cache
12
+ from functools import lru_cache , partial
13
13
import re
14
14
15
15
import numpy as np
16
16
from pyparsing import (
17
- Literal , Optional , ParseException , Regex , StringEnd , Suppress , ZeroOrMore ,
18
- )
17
+ Optional , ParseException , Regex , StringEnd , Suppress , ZeroOrMore )
18
+
19
+ from matplotlib import _api
20
+
19
21
20
22
family_punc = r'\\\-:,'
21
- family_unescape = re .compile (r'\\([%s])' % family_punc ).sub
23
+ _family_unescape = partial ( re .compile (r'\\(?= [%s])' % family_punc ).sub , '' )
22
24
family_escape = re .compile (r'([%s])' % family_punc ).sub
23
25
24
26
value_punc = r'\\=_:,'
25
- value_unescape = re .compile (r'\\([%s])' % value_punc ).sub
27
+ _value_unescape = partial ( re .compile (r'\\(?= [%s])' % value_punc ).sub , '' )
26
28
value_escape = re .compile (r'([%s])' % value_punc ).sub
27
29
30
+ # Remove after module deprecation elapses (3.8); then remove underscores
31
+ # from _family_unescape and _value_unescape.
32
+ family_unescape = re .compile (r'\\([%s])' % family_punc ).sub
33
+ value_unescape = re .compile (r'\\([%s])' % value_punc ).sub
34
+
28
35
29
36
class FontconfigPatternParser :
30
37
"""
@@ -58,63 +65,27 @@ class FontconfigPatternParser:
58
65
'semicondensed' : ('width' , 'semi-condensed' ),
59
66
'expanded' : ('width' , 'expanded' ),
60
67
'extraexpanded' : ('width' , 'extra-expanded' ),
61
- 'ultraexpanded' : ('width' , 'ultra-expanded' )
62
- }
68
+ 'ultraexpanded' : ('width' , 'ultra-expanded' ),
69
+ }
63
70
64
71
def __init__ (self ):
65
-
66
- family = Regex (
67
- r'([^%s]|(\\[%s]))*' % (family_punc , family_punc )
68
- ).setParseAction (self ._family )
69
-
70
- size = Regex (
71
- r"([0-9]+\.?[0-9]*|\.[0-9]+)"
72
- ).setParseAction (self ._size )
73
-
74
- name = Regex (
75
- r'[a-z]+'
76
- ).setParseAction (self ._name )
77
-
78
- value = Regex (
79
- r'([^%s]|(\\[%s]))*' % (value_punc , value_punc )
80
- ).setParseAction (self ._value )
81
-
82
- families = (
83
- family
84
- + ZeroOrMore (
85
- Literal (',' )
86
- + family )
87
- ).setParseAction (self ._families )
88
-
89
- point_sizes = (
90
- size
91
- + ZeroOrMore (
92
- Literal (',' )
93
- + size )
94
- ).setParseAction (self ._point_sizes )
95
-
96
- property = (
97
- (name
98
- + Suppress (Literal ('=' ))
99
- + value
100
- + ZeroOrMore (
101
- Suppress (Literal (',' ))
102
- + value ))
103
- | name
104
- ).setParseAction (self ._property )
105
-
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
+ )
106
83
pattern = (
107
- Optional (
108
- families )
109
- + Optional (
110
- Literal ('-' )
111
- + point_sizes )
112
- + ZeroOrMore (
113
- Literal (':' )
114
- + property )
84
+ Optional (comma_separated (family )("families" ))
85
+ + Optional ("-" + comma_separated (size )("sizes" ))
86
+ + ZeroOrMore (":" + prop ("properties*" ))
115
87
+ StringEnd ()
116
88
)
117
-
118
89
self ._parser = pattern
119
90
self .ParseException = ParseException
120
91
@@ -124,47 +95,30 @@ def parse(self, pattern):
124
95
of key/value pairs useful for initializing a
125
96
`.font_manager.FontProperties` object.
126
97
"""
127
- props = self ._properties = {}
128
98
try :
129
- self ._parser .parseString (pattern )
99
+ parse = self ._parser .parseString (pattern )
130
100
except ParseException as err :
131
101
# explain becomes a plain method on pyparsing 3 (err.explain(0)).
132
102
raise ValueError ("\n " + ParseException .explain (err , 0 )) from None
133
- self ._properties = None
134
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 ))
135
120
return props
136
121
137
- def _family (self , s , loc , tokens ):
138
- return [family_unescape (r'\1' , str (tokens [0 ]))]
139
-
140
- def _size (self , s , loc , tokens ):
141
- return [float (tokens [0 ])]
142
-
143
- def _name (self , s , loc , tokens ):
144
- return [str (tokens [0 ])]
145
-
146
- def _value (self , s , loc , tokens ):
147
- return [value_unescape (r'\1' , str (tokens [0 ]))]
148
-
149
- def _families (self , s , loc , tokens ):
150
- self ._properties ['family' ] = [str (x ) for x in tokens ]
151
- return []
152
-
153
- def _point_sizes (self , s , loc , tokens ):
154
- self ._properties ['size' ] = [str (x ) for x in tokens ]
155
- return []
156
-
157
- def _property (self , s , loc , tokens ):
158
- if len (tokens ) == 1 :
159
- if tokens [0 ] in self ._constants :
160
- key , val = self ._constants [tokens [0 ]]
161
- self ._properties .setdefault (key , []).append (val )
162
- else :
163
- key = tokens [0 ]
164
- val = tokens [1 :]
165
- self ._properties .setdefault (key , []).extend (val )
166
- return []
167
-
168
122
169
123
# `parse_fontconfig_pattern` is a bottleneck during the tests because it is
170
124
# repeatedly called when the rcParams are reset (to validate the default
0 commit comments