99# there would have created cyclical dependency problems, because it also needs
1010# to be available from `matplotlib.rcsetup` (for parsing matplotlibrc files).
1111
12- from functools import lru_cache
12+ from functools import lru_cache , partial
1313import re
1414
1515import numpy as np
1616from 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+
1921
2022family_punc = r'\\\-:,'
21- family_unescape = re .compile (r'\\([%s])' % family_punc ).sub
23+ _family_unescape = partial ( re .compile (r'\\(?= [%s])' % family_punc ).sub , '' )
2224family_escape = re .compile (r'([%s])' % family_punc ).sub
2325
2426value_punc = r'\\=_:,'
25- value_unescape = re .compile (r'\\([%s])' % value_punc ).sub
27+ _value_unescape = partial ( re .compile (r'\\(?= [%s])' % value_punc ).sub , '' )
2628value_escape = re .compile (r'([%s])' % value_punc ).sub
2729
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+
2835
2936class FontconfigPatternParser :
3037 """
@@ -58,63 +65,27 @@ class FontconfigPatternParser:
5865 'semicondensed' : ('width' , 'semi-condensed' ),
5966 'expanded' : ('width' , 'expanded' ),
6067 'extraexpanded' : ('width' , 'extra-expanded' ),
61- 'ultraexpanded' : ('width' , 'ultra-expanded' )
62- }
68+ 'ultraexpanded' : ('width' , 'ultra-expanded' ),
69+ }
6370
6471 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+ )
10683 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*" ))
11587 + StringEnd ()
11688 )
117-
11889 self ._parser = pattern
11990 self .ParseException = ParseException
12091
@@ -124,47 +95,30 @@ def parse(self, pattern):
12495 of key/value pairs useful for initializing a
12596 `.font_manager.FontProperties` object.
12697 """
127- props = self ._properties = {}
12898 try :
129- self ._parser .parseString (pattern )
99+ parse = self ._parser .parseString (pattern )
130100 except ParseException as err :
131101 # explain becomes a plain method on pyparsing 3 (err.explain(0)).
132102 raise ValueError ("\n " + ParseException .explain (err , 0 )) from None
133- self ._properties = None
134103 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 ))
135120 return props
136121
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-
168122
169123# `parse_fontconfig_pattern` is a bottleneck during the tests because it is
170124# repeatedly called when the rcParams are reset (to validate the default
0 commit comments