@@ -61,37 +61,44 @@ class TexManager:
6161 """
6262
6363 texcache = os .path .join (mpl .get_cachedir (), 'tex.cache' )
64-
6564 _grey_arrayd = {}
66- _font_family = 'serif'
65+
6766 _font_families = ('serif' , 'sans-serif' , 'cursive' , 'monospace' )
68- _font_info = {
69- 'new century schoolbook' : ( 'pnc' , r'\renewcommand{\rmdefault}{pnc}' ) ,
70- 'bookman' : ( 'pbk' , r'\renewcommand{\rmdefault}{pbk}' ) ,
71- 'times' : ( 'ptm' , r'\usepackage{mathptmx}' ) ,
72- 'palatino' : ( 'ppl' , r'\usepackage{mathpazo}' ) ,
73- 'zapf chancery' : ( 'pzc' , r'\usepackage{chancery}' ) ,
74- 'cursive' : ( 'pzc' , r'\usepackage{chancery}' ) ,
75- 'charter' : ( 'pch' , r'\usepackage{charter}' ) ,
76- 'serif' : ( 'cmr' , '' ) ,
77- 'sans-serif' : ( 'cmss' , '' ) ,
78- 'helvetica' : ( 'phv' , r'\usepackage{helvet}' ) ,
79- 'avant garde' : ( 'pag' , r'\usepackage{avant}' ) ,
80- 'courier' : ( 'pcr' , r'\usepackage{courier}' ) ,
67+ _font_preambles = {
68+ 'new century schoolbook' : r'\renewcommand{\rmdefault}{pnc}' ,
69+ 'bookman' : r'\renewcommand{\rmdefault}{pbk}' ,
70+ 'times' : r'\usepackage{mathptmx}' ,
71+ 'palatino' : r'\usepackage{mathpazo}' ,
72+ 'zapf chancery' : r'\usepackage{chancery}' ,
73+ 'cursive' : r'\usepackage{chancery}' ,
74+ 'charter' : r'\usepackage{charter}' ,
75+ 'serif' : '' ,
76+ 'sans-serif' : '' ,
77+ 'helvetica' : r'\usepackage{helvet}' ,
78+ 'avant garde' : r'\usepackage{avant}' ,
79+ 'courier' : r'\usepackage{courier}' ,
8180 # Loading the type1ec package ensures that cm-super is installed, which
8281 # is necessary for Unicode computer modern. (It also allows the use of
8382 # computer modern at arbitrary sizes, but that's just a side effect.)
84- 'monospace' : ('cmtt' , r'\usepackage{type1ec}' ),
85- 'computer modern roman' : ('cmr' , r'\usepackage{type1ec}' ),
86- 'computer modern sans serif' : ('cmss' , r'\usepackage{type1ec}' ),
87- 'computer modern typewriter' : ('cmtt' , r'\usepackage{type1ec}' )}
83+ 'monospace' : r'\usepackage{type1ec}' ,
84+ 'computer modern roman' : r'\usepackage{type1ec}' ,
85+ 'computer modern sans serif' : r'\usepackage{type1ec}' ,
86+ 'computer modern typewriter' : r'\usepackage{type1ec}' ,
87+ }
8888 _font_types = {
89- 'new century schoolbook' : 'serif' , 'bookman' : 'serif' ,
90- 'times' : 'serif' , 'palatino' : 'serif' , 'charter' : 'serif' ,
91- 'computer modern roman' : 'serif' , 'zapf chancery' : 'cursive' ,
92- 'helvetica' : 'sans-serif' , 'avant garde' : 'sans-serif' ,
89+ 'new century schoolbook' : 'serif' ,
90+ 'bookman' : 'serif' ,
91+ 'times' : 'serif' ,
92+ 'palatino' : 'serif' ,
93+ 'zapf chancery' : 'cursive' ,
94+ 'charter' : 'serif' ,
95+ 'helvetica' : 'sans-serif' ,
96+ 'avant garde' : 'sans-serif' ,
97+ 'courier' : 'monospace' ,
98+ 'computer modern roman' : 'serif' ,
9399 'computer modern sans serif' : 'sans-serif' ,
94- 'courier' : 'monospace' , 'computer modern typewriter' : 'monospace' }
100+ 'computer modern typewriter' : 'monospace' ,
101+ }
95102
96103 grey_arrayd = _api .deprecate_privatize_attribute ("3.5" )
97104 font_family = _api .deprecate_privatize_attribute ("3.5" )
@@ -103,33 +110,48 @@ def __new__(cls):
103110 Path (cls .texcache ).mkdir (parents = True , exist_ok = True )
104111 return object .__new__ (cls )
105112
113+ @_api .deprecated ("3.6" )
106114 def get_font_config (self ):
115+ preamble , font_cmd = self ._get_font_preamble_and_command ()
116+ # Add a hash of the latex preamble to fontconfig so that the
117+ # correct png is selected for strings rendered with same font and dpi
118+ # even if the latex preamble changes within the session
119+ preambles = preamble + font_cmd + self .get_custom_preamble ()
120+ return hashlib .md5 (preambles .encode ('utf-8' )).hexdigest ()
121+
122+ @classmethod
123+ def _get_font_family_and_reduced (cls ):
124+ """Return the font family name and whether the font is reduced."""
107125 ff = rcParams ['font.family' ]
108126 ff_val = ff [0 ].lower () if len (ff ) == 1 else None
109- reduced_notation = False
110- if len (ff ) == 1 and ff_val in self ._font_families :
111- self ._font_family = ff_val
112- elif len (ff ) == 1 and ff_val in self ._font_info :
113- reduced_notation = True
114- self ._font_family = self ._font_types [ff_val ]
127+ if len (ff ) == 1 and ff_val in cls ._font_families :
128+ return ff_val , False
129+ elif len (ff ) == 1 and ff_val in cls ._font_preambles :
130+ return cls ._font_types [ff_val ], True
115131 else :
116132 _log .info ('font.family must be one of (%s) when text.usetex is '
117133 'True. serif will be used by default.' ,
118- ', ' .join (self ._font_families ))
119- self ._font_family = 'serif'
120-
121- fontconfig = [self ._font_family ]
122- fonts = {}
123- for font_family in self ._font_families :
124- if reduced_notation and self ._font_family == font_family :
125- fonts [font_family ] = self ._font_info [ff_val ]
134+ ', ' .join (cls ._font_families ))
135+ return 'serif' , False
136+
137+ @classmethod
138+ def _get_font_preamble_and_command (cls ):
139+ requested_family , is_reduced_font = cls ._get_font_family_and_reduced ()
140+
141+ preambles = {}
142+ for font_family in cls ._font_families :
143+ if is_reduced_font and font_family == requested_family :
144+ preambles [font_family ] = cls ._font_preambles [
145+ rcParams ['font.family' ][0 ].lower ()]
126146 else :
127147 for font in rcParams ['font.' + font_family ]:
128- if font .lower () in self ._font_info :
129- fonts [font_family ] = self ._font_info [font .lower ()]
148+ if font .lower () in cls ._font_preambles :
149+ preambles [font_family ] = \
150+ cls ._font_preambles [font .lower ()]
130151 _log .debug (
131152 'family: %s, font: %s, info: %s' ,
132- font_family , font , self ._font_info [font .lower ()])
153+ font_family , font ,
154+ cls ._font_preambles [font .lower ()])
133155 break
134156 else :
135157 _log .debug ('%s font is not compatible with usetex.' ,
@@ -138,64 +160,62 @@ def get_font_config(self):
138160 _log .info ('No LaTeX-compatible font found for the %s font'
139161 'family in rcParams. Using default.' ,
140162 font_family )
141- fonts [font_family ] = self ._font_info [font_family ]
142- fontconfig .append (fonts [font_family ][0 ])
143- # Add a hash of the latex preamble to fontconfig so that the
144- # correct png is selected for strings rendered with same font and dpi
145- # even if the latex preamble changes within the session
146- preamble_bytes = self .get_custom_preamble ().encode ('utf-8' )
147- fontconfig .append (hashlib .md5 (preamble_bytes ).hexdigest ())
163+ preambles [font_family ] = cls ._font_preambles [font_family ]
148164
149165 # The following packages and commands need to be included in the latex
150166 # file's preamble:
151- cmd = {fonts [family ][ 1 ]
167+ cmd = {preambles [family ]
152168 for family in ['serif' , 'sans-serif' , 'monospace' ]}
153- if self . _font_family == 'cursive' :
154- cmd .add (fonts ['cursive' ][ 1 ])
169+ if requested_family == 'cursive' :
170+ cmd .add (preambles ['cursive' ])
155171 cmd .add (r'\usepackage{type1cm}' )
156- self ._font_preamble = '\n ' .join (sorted (cmd ))
157-
158- return '' .join (fontconfig )
172+ preamble = '\n ' .join (sorted (cmd ))
173+ fontcmd = (r'\sffamily' if requested_family == 'sans-serif' else
174+ r'\ttfamily' if requested_family == 'monospace' else
175+ r'\rmfamily' )
176+ return preamble , fontcmd
159177
160- def get_basefile (self , tex , fontsize , dpi = None ):
178+ @classmethod
179+ def get_basefile (cls , tex , fontsize , dpi = None ):
161180 """
162181 Return a filename based on a hash of the string, fontsize, and dpi.
163182 """
164- src = self ._get_tex_source (tex , fontsize ) + str (dpi )
183+ src = cls ._get_tex_source (tex , fontsize ) + str (dpi )
165184 return os .path .join (
166- self .texcache , hashlib .md5 (src .encode ('utf-8' )).hexdigest ())
185+ cls .texcache , hashlib .md5 (src .encode ('utf-8' )).hexdigest ())
167186
168- def get_font_preamble (self ):
187+ @classmethod
188+ def get_font_preamble (cls ):
169189 """
170190 Return a string containing font configuration for the tex preamble.
171191 """
172- return self ._font_preamble
192+ font_preamble , command = cls ._get_font_preamble_and_command ()
193+ return font_preamble
173194
174- def get_custom_preamble (self ):
195+ @classmethod
196+ def get_custom_preamble (cls ):
175197 """Return a string containing user additions to the tex preamble."""
176198 return rcParams ['text.latex.preamble' ]
177199
178- def _get_tex_source (self , tex , fontsize ):
200+ @classmethod
201+ def _get_tex_source (cls , tex , fontsize ):
179202 """Return the complete TeX source for processing a TeX string."""
180- self . get_font_config () # Updates self._font_preamble.
203+ font_preamble , fontcmd = cls . _get_font_preamble_and_command ()
181204 baselineskip = 1.25 * fontsize
182- fontcmd = (r'\sffamily' if self ._font_family == 'sans-serif' else
183- r'\ttfamily' if self ._font_family == 'monospace' else
184- r'\rmfamily' )
185205 return "\n " .join ([
186206 r"\documentclass{article}" ,
187207 r"% Pass-through \mathdefault, which is used in non-usetex mode" ,
188208 r"% to use the default text font but was historically suppressed" ,
189209 r"% in usetex mode." ,
190210 r"\newcommand{\mathdefault}[1]{#1}" ,
191- self . _font_preamble ,
211+ font_preamble ,
192212 r"\usepackage[utf8]{inputenc}" ,
193213 r"\DeclareUnicodeCharacter{2212}{\ensuremath{-}}" ,
194214 r"% geometry is loaded before the custom preamble as " ,
195215 r"% convert_psfrags relies on a custom preamble to change the " ,
196216 r"% geometry." ,
197217 r"\usepackage[papersize=72in, margin=1in]{geometry}" ,
198- self .get_custom_preamble (),
218+ cls .get_custom_preamble (),
199219 r"% Use `underscore` package to take care of underscores in text." ,
200220 r"% The [strings] option allows to use underscores in file names." ,
201221 _usepackage_if_not_loaded ("underscore" , option = "strings" ),
@@ -215,21 +235,23 @@ def _get_tex_source(self, tex, fontsize):
215235 r"\end{document}" ,
216236 ])
217237
218- def make_tex (self , tex , fontsize ):
238+ @classmethod
239+ def make_tex (cls , tex , fontsize ):
219240 """
220241 Generate a tex file to render the tex string at a specific font size.
221242
222243 Return the file name.
223244 """
224- texfile = self .get_basefile (tex , fontsize ) + ".tex"
225- Path (texfile ).write_text (self ._get_tex_source (tex , fontsize ))
245+ texfile = cls .get_basefile (tex , fontsize ) + ".tex"
246+ Path (texfile ).write_text (cls ._get_tex_source (tex , fontsize ))
226247 return texfile
227248
228- def _run_checked_subprocess (self , command , tex , * , cwd = None ):
249+ @classmethod
250+ def _run_checked_subprocess (cls , command , tex , * , cwd = None ):
229251 _log .debug (cbook ._pformat_subprocess (command ))
230252 try :
231253 report = subprocess .check_output (
232- command , cwd = cwd if cwd is not None else self .texcache ,
254+ command , cwd = cwd if cwd is not None else cls .texcache ,
233255 stderr = subprocess .STDOUT )
234256 except FileNotFoundError as exc :
235257 raise RuntimeError (
@@ -247,16 +269,17 @@ def _run_checked_subprocess(self, command, tex, *, cwd=None):
247269 _log .debug (report )
248270 return report
249271
250- def make_dvi (self , tex , fontsize ):
272+ @classmethod
273+ def make_dvi (cls , tex , fontsize ):
251274 """
252275 Generate a dvi file containing latex's layout of tex string.
253276
254277 Return the file name.
255278 """
256- basefile = self .get_basefile (tex , fontsize )
279+ basefile = cls .get_basefile (tex , fontsize )
257280 dvifile = '%s.dvi' % basefile
258281 if not os .path .exists (dvifile ):
259- texfile = Path (self .make_tex (tex , fontsize ))
282+ texfile = Path (cls .make_tex (tex , fontsize ))
260283 # Generate the dvi in a temporary directory to avoid race
261284 # conditions e.g. if multiple processes try to process the same tex
262285 # string at the same time. Having tmpdir be a subdirectory of the
@@ -266,23 +289,24 @@ def make_dvi(self, tex, fontsize):
266289 # the absolute path may contain characters (e.g. ~) that TeX does
267290 # not support.)
268291 with TemporaryDirectory (dir = Path (dvifile ).parent ) as tmpdir :
269- self ._run_checked_subprocess (
292+ cls ._run_checked_subprocess (
270293 ["latex" , "-interaction=nonstopmode" , "--halt-on-error" ,
271294 f"../{ texfile .name } " ], tex , cwd = tmpdir )
272295 (Path (tmpdir ) / Path (dvifile ).name ).replace (dvifile )
273296 return dvifile
274297
275- def make_png (self , tex , fontsize , dpi ):
298+ @classmethod
299+ def make_png (cls , tex , fontsize , dpi ):
276300 """
277301 Generate a png file containing latex's rendering of tex string.
278302
279303 Return the file name.
280304 """
281- basefile = self .get_basefile (tex , fontsize , dpi )
305+ basefile = cls .get_basefile (tex , fontsize , dpi )
282306 pngfile = '%s.png' % basefile
283307 # see get_rgba for a discussion of the background
284308 if not os .path .exists (pngfile ):
285- dvifile = self .make_dvi (tex , fontsize )
309+ dvifile = cls .make_dvi (tex , fontsize )
286310 cmd = ["dvipng" , "-bg" , "Transparent" , "-D" , str (dpi ),
287311 "-T" , "tight" , "-o" , pngfile , dvifile ]
288312 # When testing, disable FreeType rendering for reproducibility; but
@@ -292,24 +316,26 @@ def make_png(self, tex, fontsize, dpi):
292316 if (getattr (mpl , "_called_from_pytest" , False ) and
293317 mpl ._get_executable_info ("dvipng" ).raw_version != "1.16" ):
294318 cmd .insert (1 , "--freetype0" )
295- self ._run_checked_subprocess (cmd , tex )
319+ cls ._run_checked_subprocess (cmd , tex )
296320 return pngfile
297321
298- def get_grey (self , tex , fontsize = None , dpi = None ):
322+ @classmethod
323+ def get_grey (cls , tex , fontsize = None , dpi = None ):
299324 """Return the alpha channel."""
300325 if not fontsize :
301326 fontsize = rcParams ['font.size' ]
302327 if not dpi :
303328 dpi = rcParams ['savefig.dpi' ]
304- key = tex , self . get_font_config () , fontsize , dpi
305- alpha = self ._grey_arrayd .get (key )
329+ key = cls . _get_tex_source ( tex , fontsize ) , dpi
330+ alpha = cls ._grey_arrayd .get (key )
306331 if alpha is None :
307- pngfile = self .make_png (tex , fontsize , dpi )
308- rgba = mpl .image .imread (os .path .join (self .texcache , pngfile ))
309- self ._grey_arrayd [key ] = alpha = rgba [:, :, - 1 ]
332+ pngfile = cls .make_png (tex , fontsize , dpi )
333+ rgba = mpl .image .imread (os .path .join (cls .texcache , pngfile ))
334+ cls ._grey_arrayd [key ] = alpha = rgba [:, :, - 1 ]
310335 return alpha
311336
312- def get_rgba (self , tex , fontsize = None , dpi = None , rgb = (0 , 0 , 0 )):
337+ @classmethod
338+ def get_rgba (cls , tex , fontsize = None , dpi = None , rgb = (0 , 0 , 0 )):
313339 r"""
314340 Return latex's rendering of the tex string as an rgba array.
315341
@@ -319,17 +345,18 @@ def get_rgba(self, tex, fontsize=None, dpi=None, rgb=(0, 0, 0)):
319345 >>> s = r"\TeX\ is $\displaystyle\sum_n\frac{-e^{i\pi}}{2^n}$!"
320346 >>> Z = texmanager.get_rgba(s, fontsize=12, dpi=80, rgb=(1, 0, 0))
321347 """
322- alpha = self .get_grey (tex , fontsize , dpi )
348+ alpha = cls .get_grey (tex , fontsize , dpi )
323349 rgba = np .empty ((* alpha .shape , 4 ))
324350 rgba [..., :3 ] = mpl .colors .to_rgb (rgb )
325351 rgba [..., - 1 ] = alpha
326352 return rgba
327353
328- def get_text_width_height_descent (self , tex , fontsize , renderer = None ):
354+ @classmethod
355+ def get_text_width_height_descent (cls , tex , fontsize , renderer = None ):
329356 """Return width, height and descent of the text."""
330357 if tex .strip () == '' :
331358 return 0 , 0 , 0
332- dvifile = self .make_dvi (tex , fontsize )
359+ dvifile = cls .make_dvi (tex , fontsize )
333360 dpi_fraction = renderer .points_to_pixels (1. ) if renderer else 1
334361 with dviread .Dvi (dvifile , 72 * dpi_fraction ) as dvi :
335362 page , = dvi
0 commit comments