@@ -61,37 +61,44 @@ class TexManager:
61
61
"""
62
62
63
63
texcache = os .path .join (mpl .get_cachedir (), 'tex.cache' )
64
-
65
64
_grey_arrayd = {}
66
- _font_family = 'serif'
65
+
67
66
_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}' ,
81
80
# Loading the type1ec package ensures that cm-super is installed, which
82
81
# is necessary for Unicode computer modern. (It also allows the use of
83
82
# 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
+ }
88
88
_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' ,
93
99
'computer modern sans serif' : 'sans-serif' ,
94
- 'courier' : 'monospace' , 'computer modern typewriter' : 'monospace' }
100
+ 'computer modern typewriter' : 'monospace' ,
101
+ }
95
102
96
103
grey_arrayd = _api .deprecate_privatize_attribute ("3.5" )
97
104
font_family = _api .deprecate_privatize_attribute ("3.5" )
@@ -103,33 +110,48 @@ def __new__(cls):
103
110
Path (cls .texcache ).mkdir (parents = True , exist_ok = True )
104
111
return object .__new__ (cls )
105
112
113
+ @_api .deprecated ("3.6" )
106
114
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."""
107
125
ff = rcParams ['font.family' ]
108
126
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
115
131
else :
116
132
_log .info ('font.family must be one of (%s) when text.usetex is '
117
133
'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 ()]
126
146
else :
127
147
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 ()]
130
151
_log .debug (
131
152
'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 ()])
133
155
break
134
156
else :
135
157
_log .debug ('%s font is not compatible with usetex.' ,
@@ -138,64 +160,62 @@ def get_font_config(self):
138
160
_log .info ('No LaTeX-compatible font found for the %s font'
139
161
'family in rcParams. Using default.' ,
140
162
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 ]
148
164
149
165
# The following packages and commands need to be included in the latex
150
166
# file's preamble:
151
- cmd = {fonts [family ][ 1 ]
167
+ cmd = {preambles [family ]
152
168
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' ])
155
171
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
159
177
160
- def get_basefile (self , tex , fontsize , dpi = None ):
178
+ @classmethod
179
+ def get_basefile (cls , tex , fontsize , dpi = None ):
161
180
"""
162
181
Return a filename based on a hash of the string, fontsize, and dpi.
163
182
"""
164
- src = self ._get_tex_source (tex , fontsize ) + str (dpi )
183
+ src = cls ._get_tex_source (tex , fontsize ) + str (dpi )
165
184
return os .path .join (
166
- self .texcache , hashlib .md5 (src .encode ('utf-8' )).hexdigest ())
185
+ cls .texcache , hashlib .md5 (src .encode ('utf-8' )).hexdigest ())
167
186
168
- def get_font_preamble (self ):
187
+ @classmethod
188
+ def get_font_preamble (cls ):
169
189
"""
170
190
Return a string containing font configuration for the tex preamble.
171
191
"""
172
- return self ._font_preamble
192
+ font_preamble , command = cls ._get_font_preamble_and_command ()
193
+ return font_preamble
173
194
174
- def get_custom_preamble (self ):
195
+ @classmethod
196
+ def get_custom_preamble (cls ):
175
197
"""Return a string containing user additions to the tex preamble."""
176
198
return rcParams ['text.latex.preamble' ]
177
199
178
- def _get_tex_source (self , tex , fontsize ):
200
+ @classmethod
201
+ def _get_tex_source (cls , tex , fontsize ):
179
202
"""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 ()
181
204
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' )
185
205
return "\n " .join ([
186
206
r"\documentclass{article}" ,
187
207
r"% Pass-through \mathdefault, which is used in non-usetex mode" ,
188
208
r"% to use the default text font but was historically suppressed" ,
189
209
r"% in usetex mode." ,
190
210
r"\newcommand{\mathdefault}[1]{#1}" ,
191
- self . _font_preamble ,
211
+ font_preamble ,
192
212
r"\usepackage[utf8]{inputenc}" ,
193
213
r"\DeclareUnicodeCharacter{2212}{\ensuremath{-}}" ,
194
214
r"% geometry is loaded before the custom preamble as " ,
195
215
r"% convert_psfrags relies on a custom preamble to change the " ,
196
216
r"% geometry." ,
197
217
r"\usepackage[papersize=72in, margin=1in]{geometry}" ,
198
- self .get_custom_preamble (),
218
+ cls .get_custom_preamble (),
199
219
r"% Use `underscore` package to take care of underscores in text." ,
200
220
r"% The [strings] option allows to use underscores in file names." ,
201
221
_usepackage_if_not_loaded ("underscore" , option = "strings" ),
@@ -215,21 +235,23 @@ def _get_tex_source(self, tex, fontsize):
215
235
r"\end{document}" ,
216
236
])
217
237
218
- def make_tex (self , tex , fontsize ):
238
+ @classmethod
239
+ def make_tex (cls , tex , fontsize ):
219
240
"""
220
241
Generate a tex file to render the tex string at a specific font size.
221
242
222
243
Return the file name.
223
244
"""
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 ))
226
247
return texfile
227
248
228
- def _run_checked_subprocess (self , command , tex , * , cwd = None ):
249
+ @classmethod
250
+ def _run_checked_subprocess (cls , command , tex , * , cwd = None ):
229
251
_log .debug (cbook ._pformat_subprocess (command ))
230
252
try :
231
253
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 ,
233
255
stderr = subprocess .STDOUT )
234
256
except FileNotFoundError as exc :
235
257
raise RuntimeError (
@@ -247,16 +269,17 @@ def _run_checked_subprocess(self, command, tex, *, cwd=None):
247
269
_log .debug (report )
248
270
return report
249
271
250
- def make_dvi (self , tex , fontsize ):
272
+ @classmethod
273
+ def make_dvi (cls , tex , fontsize ):
251
274
"""
252
275
Generate a dvi file containing latex's layout of tex string.
253
276
254
277
Return the file name.
255
278
"""
256
- basefile = self .get_basefile (tex , fontsize )
279
+ basefile = cls .get_basefile (tex , fontsize )
257
280
dvifile = '%s.dvi' % basefile
258
281
if not os .path .exists (dvifile ):
259
- texfile = Path (self .make_tex (tex , fontsize ))
282
+ texfile = Path (cls .make_tex (tex , fontsize ))
260
283
# Generate the dvi in a temporary directory to avoid race
261
284
# conditions e.g. if multiple processes try to process the same tex
262
285
# string at the same time. Having tmpdir be a subdirectory of the
@@ -266,23 +289,24 @@ def make_dvi(self, tex, fontsize):
266
289
# the absolute path may contain characters (e.g. ~) that TeX does
267
290
# not support.)
268
291
with TemporaryDirectory (dir = Path (dvifile ).parent ) as tmpdir :
269
- self ._run_checked_subprocess (
292
+ cls ._run_checked_subprocess (
270
293
["latex" , "-interaction=nonstopmode" , "--halt-on-error" ,
271
294
f"../{ texfile .name } " ], tex , cwd = tmpdir )
272
295
(Path (tmpdir ) / Path (dvifile ).name ).replace (dvifile )
273
296
return dvifile
274
297
275
- def make_png (self , tex , fontsize , dpi ):
298
+ @classmethod
299
+ def make_png (cls , tex , fontsize , dpi ):
276
300
"""
277
301
Generate a png file containing latex's rendering of tex string.
278
302
279
303
Return the file name.
280
304
"""
281
- basefile = self .get_basefile (tex , fontsize , dpi )
305
+ basefile = cls .get_basefile (tex , fontsize , dpi )
282
306
pngfile = '%s.png' % basefile
283
307
# see get_rgba for a discussion of the background
284
308
if not os .path .exists (pngfile ):
285
- dvifile = self .make_dvi (tex , fontsize )
309
+ dvifile = cls .make_dvi (tex , fontsize )
286
310
cmd = ["dvipng" , "-bg" , "Transparent" , "-D" , str (dpi ),
287
311
"-T" , "tight" , "-o" , pngfile , dvifile ]
288
312
# When testing, disable FreeType rendering for reproducibility; but
@@ -292,24 +316,26 @@ def make_png(self, tex, fontsize, dpi):
292
316
if (getattr (mpl , "_called_from_pytest" , False ) and
293
317
mpl ._get_executable_info ("dvipng" ).raw_version != "1.16" ):
294
318
cmd .insert (1 , "--freetype0" )
295
- self ._run_checked_subprocess (cmd , tex )
319
+ cls ._run_checked_subprocess (cmd , tex )
296
320
return pngfile
297
321
298
- def get_grey (self , tex , fontsize = None , dpi = None ):
322
+ @classmethod
323
+ def get_grey (cls , tex , fontsize = None , dpi = None ):
299
324
"""Return the alpha channel."""
300
325
if not fontsize :
301
326
fontsize = rcParams ['font.size' ]
302
327
if not dpi :
303
328
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 )
306
331
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 ]
310
335
return alpha
311
336
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 )):
313
339
r"""
314
340
Return latex's rendering of the tex string as an rgba array.
315
341
@@ -319,17 +345,18 @@ def get_rgba(self, tex, fontsize=None, dpi=None, rgb=(0, 0, 0)):
319
345
>>> s = r"\TeX\ is $\displaystyle\sum_n\frac{-e^{i\pi}}{2^n}$!"
320
346
>>> Z = texmanager.get_rgba(s, fontsize=12, dpi=80, rgb=(1, 0, 0))
321
347
"""
322
- alpha = self .get_grey (tex , fontsize , dpi )
348
+ alpha = cls .get_grey (tex , fontsize , dpi )
323
349
rgba = np .empty ((* alpha .shape , 4 ))
324
350
rgba [..., :3 ] = mpl .colors .to_rgb (rgb )
325
351
rgba [..., - 1 ] = alpha
326
352
return rgba
327
353
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 ):
329
356
"""Return width, height and descent of the text."""
330
357
if tex .strip () == '' :
331
358
return 0 , 0 , 0
332
- dvifile = self .make_dvi (tex , fontsize )
359
+ dvifile = cls .make_dvi (tex , fontsize )
333
360
dpi_fraction = renderer .points_to_pixels (1. ) if renderer else 1
334
361
with dviread .Dvi (dvifile , 72 * dpi_fraction ) as dvi :
335
362
page , = dvi
0 commit comments