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
import numpy as np
15
- from pyparsing import (Literal , ZeroOrMore , Optional , Regex , StringEnd ,
16
- ParseException , Suppress )
15
+ from pyparsing import (
16
+ Optional , Group , ParseException , Regex , StringEnd , Suppress , ZeroOrMore )
17
+
18
+ from matplotlib import _api
19
+
17
20
18
21
family_punc = r'\\\-:,'
19
- family_unescape = re .compile (r'\\([%s])' % family_punc ).sub
22
+ _family_unescape = partial ( re .compile (r'\\([%s])' % family_punc ).sub , r'\1' )
20
23
family_escape = re .compile (r'([%s])' % family_punc ).sub
21
24
22
25
value_punc = r'\\=_:,'
23
- value_unescape = re .compile (r'\\([%s])' % value_punc ).sub
26
+ _value_unescape = partial ( re .compile (r'\\([%s])' % value_punc ).sub , r'\1' )
24
27
value_escape = re .compile (r'([%s])' % value_punc ).sub
25
28
29
+ # Remove after module deprecation elapses (3.8); then remove underscores
30
+ # from _family_unescape and _value_unescape. Also remove the unused
31
+ # FontconfigPatternParser.ParseException then.
32
+ family_unescape = re .compile (r'\\([%s])' % family_punc ).sub
33
+ value_unescape = re .compile (r'\\([%s])' % value_punc ).sub
34
+
26
35
27
36
class FontconfigPatternParser :
28
37
"""
@@ -33,86 +42,53 @@ class FontconfigPatternParser:
33
42
"""
34
43
35
44
_constants = {
36
- 'thin' : ('weight' , 'light' ),
37
- 'extralight' : ('weight' , 'light' ),
38
- 'ultralight' : ('weight' , 'light' ),
39
- 'light' : ('weight' , 'light' ),
40
- 'book' : ('weight' , 'book' ),
41
- 'regular' : ('weight' , 'regular' ),
42
- 'normal' : ('weight' , 'normal' ),
43
- 'medium' : ('weight' , 'medium' ),
44
- 'demibold' : ('weight' , 'demibold' ),
45
- 'semibold' : ('weight' , 'semibold' ),
46
- 'bold' : ('weight' , 'bold' ),
47
- 'extrabold' : ('weight' , 'extra bold' ),
48
- 'black' : ('weight' , 'black' ),
49
- 'heavy' : ('weight' , 'heavy' ),
50
- 'roman' : ('slant' , 'normal' ),
51
- 'italic' : ('slant' , 'italic' ),
52
- 'oblique' : ('slant' , 'oblique' ),
53
- 'ultracondensed' : ('width' , 'ultra-condensed' ),
54
- 'extracondensed' : ('width' , 'extra-condensed' ),
55
- 'condensed' : ('width' , 'condensed' ),
56
- 'semicondensed' : ('width' , 'semi-condensed' ),
57
- 'expanded' : ('width' , 'expanded' ),
58
- 'extraexpanded' : ('width' , 'extra-expanded' ),
59
- 'ultraexpanded' : ('width' , 'ultra-expanded' )
60
- }
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
+ }
61
70
62
71
def __init__ (self ):
63
-
64
- family = Regex (
65
- r'([^%s]|(\\[%s]))*' % (family_punc , family_punc )
66
- ).setParseAction (self ._family )
67
-
68
- size = Regex (
69
- r"([0-9]+\.?[0-9]*|\.[0-9]+)"
70
- ).setParseAction (self ._size )
71
-
72
- name = Regex (
73
- r'[a-z]+'
74
- ).setParseAction (self ._name )
75
-
76
- value = Regex (
77
- r'([^%s]|(\\[%s]))*' % (value_punc , value_punc )
78
- ).setParseAction (self ._value )
79
-
80
- families = (
81
- family
82
- + ZeroOrMore (
83
- Literal (',' )
84
- + family )
85
- ).setParseAction (self ._families )
86
-
87
- point_sizes = (
88
- size
89
- + ZeroOrMore (
90
- Literal (',' )
91
- + size )
92
- ).setParseAction (self ._point_sizes )
93
-
94
- property = (
95
- (name
96
- + Suppress (Literal ('=' ))
97
- + value
98
- + ZeroOrMore (
99
- Suppress (Literal (',' ))
100
- + value ))
101
- | name
102
- ).setParseAction (self ._property )
103
-
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
+ Suppress (":" )
81
+ + (
82
+ Group (name + Suppress ("=" ) + comma_separated (value ))
83
+ | name # replace by oneOf(self._constants) in mpl 3.9.
84
+ )
85
+ )
104
86
pattern = (
105
- Optional (
106
- families )
107
- + Optional (
108
- Literal ('-' )
109
- + point_sizes )
110
- + ZeroOrMore (
111
- Literal (':' )
112
- + property )
87
+ Optional (comma_separated (family )("families" ))
88
+ + Optional ("-" + comma_separated (size )("sizes" ))
89
+ + ZeroOrMore (prop )("properties" )
113
90
+ StringEnd ()
114
91
)
115
-
116
92
self ._parser = pattern
117
93
self .ParseException = ParseException
118
94
@@ -122,50 +98,31 @@ def parse(self, pattern):
122
98
of key/value pairs useful for initializing a
123
99
`.font_manager.FontProperties` object.
124
100
"""
125
- props = self ._properties = {}
126
101
try :
127
- self ._parser .parseString (pattern )
128
- except self . ParseException as e :
102
+ parse = self ._parser .parseString (pattern )
103
+ except ParseException as e :
129
104
raise ValueError (
130
105
"Could not parse font string: '%s'\n %s" % (pattern , e )) from e
131
-
132
- self ._properties = None
133
-
134
106
self ._parser .resetCache ()
135
-
107
+ props = {}
108
+ if "families" in parse :
109
+ props ["family" ] = [* map (_family_unescape , parse ['families' ])]
110
+ if "size" in parse :
111
+ props ["size" ] = parse ["sizes" ]
112
+ for prop in parse .get ("properties" , []):
113
+ if isinstance (prop , str ):
114
+ if prop not in self ._constants :
115
+ _api .warn_deprecated (
116
+ "3.7" , message = f"Support for unknown constants "
117
+ f"({ prop !r} ) is deprecated since %(since)s and will "
118
+ f"be removed %(removal)s." )
119
+ continue
120
+ k , v = self ._constants [prop ]
121
+ else :
122
+ k , * v = prop
123
+ props .setdefault (k , []).extend (map (_value_unescape , v ))
136
124
return props
137
125
138
- def _family (self , s , loc , tokens ):
139
- return [family_unescape (r'\1' , str (tokens [0 ]))]
140
-
141
- def _size (self , s , loc , tokens ):
142
- return [float (tokens [0 ])]
143
-
144
- def _name (self , s , loc , tokens ):
145
- return [str (tokens [0 ])]
146
-
147
- def _value (self , s , loc , tokens ):
148
- return [value_unescape (r'\1' , str (tokens [0 ]))]
149
-
150
- def _families (self , s , loc , tokens ):
151
- self ._properties ['family' ] = [str (x ) for x in tokens ]
152
- return []
153
-
154
- def _point_sizes (self , s , loc , tokens ):
155
- self ._properties ['size' ] = [str (x ) for x in tokens ]
156
- return []
157
-
158
- def _property (self , s , loc , tokens ):
159
- if len (tokens ) == 1 :
160
- if tokens [0 ] in self ._constants :
161
- key , val = self ._constants [tokens [0 ]]
162
- self ._properties .setdefault (key , []).append (val )
163
- else :
164
- key = tokens [0 ]
165
- val = tokens [1 :]
166
- self ._properties .setdefault (key , []).extend (val )
167
- return []
168
-
169
126
170
127
# `parse_fontconfig_pattern` is a bottleneck during the tests because it is
171
128
# repeatedly called when the rcParams are reset (to validate the default
0 commit comments