23
23
import functools
24
24
import hashlib
25
25
import logging
26
- import os
27
26
from pathlib import Path
28
27
import subprocess
29
28
from tempfile import TemporaryDirectory
@@ -63,7 +62,7 @@ class TexManager:
63
62
Repeated calls to this constructor always return the same instance.
64
63
"""
65
64
66
- _texcache = os . path . join (mpl .get_cachedir (), 'tex.cache' )
65
+ _cache_dir = Path (mpl .get_cachedir (), 'tex.cache' )
67
66
_grey_arrayd = {}
68
67
69
68
_font_families = ('serif' , 'sans-serif' , 'cursive' , 'monospace' )
@@ -109,7 +108,7 @@ class TexManager:
109
108
110
109
@functools .lru_cache # Always return the same instance.
111
110
def __new__ (cls ):
112
- Path ( cls ._texcache ) .mkdir (parents = True , exist_ok = True )
111
+ cls ._cache_dir .mkdir (parents = True , exist_ok = True )
113
112
return object .__new__ (cls )
114
113
115
114
@classmethod
@@ -167,23 +166,30 @@ def _get_font_preamble_and_command(cls):
167
166
return preamble , fontcmd
168
167
169
168
@classmethod
170
- def get_basefile (cls , tex , fontsize , dpi = None ):
169
+ def _get_base_path (cls , tex , fontsize , dpi = None ):
171
170
"""
172
- Return a filename based on a hash of the string, fontsize, and dpi.
171
+ Return a file path based on a hash of the string, fontsize, and dpi.
173
172
"""
174
173
src = cls ._get_tex_source (tex , fontsize ) + str (dpi )
175
174
filehash = hashlib .sha256 (
176
175
src .encode ('utf-8' ),
177
176
usedforsecurity = False
178
177
).hexdigest ()
179
- filepath = Path ( cls ._texcache )
178
+ filepath = cls ._cache_dir
180
179
181
180
num_letters , num_levels = 2 , 2
182
181
for i in range (0 , num_letters * num_levels , num_letters ):
183
- filepath = filepath / Path ( filehash [i :i + 2 ])
182
+ filepath = filepath / filehash [i :i + 2 ]
184
183
185
184
filepath .mkdir (parents = True , exist_ok = True )
186
- return os .path .join (filepath , filehash )
185
+ return filepath / filehash
186
+
187
+ @classmethod
188
+ def get_basefile (cls , tex , fontsize , dpi = None ): # Kept for backcompat.
189
+ """
190
+ Return a filename based on a hash of the string, fontsize, and dpi.
191
+ """
192
+ return str (cls ._get_base_path (tex , fontsize , dpi ))
187
193
188
194
@classmethod
189
195
def get_font_preamble (cls ):
@@ -241,17 +247,16 @@ def make_tex(cls, tex, fontsize):
241
247
242
248
Return the file name.
243
249
"""
244
- texfile = cls .get_basefile (tex , fontsize ) + ".tex"
245
- Path (texfile ).write_text (cls ._get_tex_source (tex , fontsize ),
246
- encoding = 'utf-8' )
247
- return texfile
250
+ texpath = cls ._get_base_path (tex , fontsize ).with_suffix (".tex" )
251
+ texpath .write_text (cls ._get_tex_source (tex , fontsize ), encoding = 'utf-8' )
252
+ return str (texpath )
248
253
249
254
@classmethod
250
255
def _run_checked_subprocess (cls , command , tex , * , cwd = None ):
251
256
_log .debug (cbook ._pformat_subprocess (command ))
252
257
try :
253
258
report = subprocess .check_output (
254
- command , cwd = cwd if cwd is not None else cls ._texcache ,
259
+ command , cwd = cwd if cwd is not None else cls ._cache_dir ,
255
260
stderr = subprocess .STDOUT )
256
261
except FileNotFoundError as exc :
257
262
raise RuntimeError (
@@ -279,8 +284,8 @@ def make_dvi(cls, tex, fontsize):
279
284
280
285
Return the file name.
281
286
"""
282
- dvifile = Path ( cls .get_basefile (tex , fontsize ) ).with_suffix (".dvi" )
283
- if not dvifile .exists ():
287
+ dvipath = cls ._get_base_path (tex , fontsize ).with_suffix (".dvi" )
288
+ if not dvipath .exists ():
284
289
# Generate the tex and dvi in a temporary directory to avoid race
285
290
# conditions e.g. if multiple processes try to process the same tex
286
291
# string at the same time. Having tmpdir be a subdirectory of the
@@ -290,17 +295,17 @@ def make_dvi(cls, tex, fontsize):
290
295
# the absolute path may contain characters (e.g. ~) that TeX does
291
296
# not support; n.b. relative paths cannot traverse parents, or it
292
297
# will be blocked when `openin_any = p` in texmf.cnf).
293
- with TemporaryDirectory (dir = dvifile .parent ) as tmpdir :
298
+ with TemporaryDirectory (dir = dvipath .parent ) as tmpdir :
294
299
Path (tmpdir , "file.tex" ).write_text (
295
300
cls ._get_tex_source (tex , fontsize ), encoding = 'utf-8' )
296
301
cls ._run_checked_subprocess (
297
302
["latex" , "-interaction=nonstopmode" , "--halt-on-error" ,
298
303
"file.tex" ], tex , cwd = tmpdir )
299
- Path (tmpdir , "file.dvi" ).replace (dvifile )
304
+ Path (tmpdir , "file.dvi" ).replace (dvipath )
300
305
# Also move the tex source to the main cache directory, but
301
306
# only for backcompat.
302
- Path (tmpdir , "file.tex" ).replace (dvifile .with_suffix (".tex" ))
303
- return str (dvifile )
307
+ Path (tmpdir , "file.tex" ).replace (dvipath .with_suffix (".tex" ))
308
+ return str (dvipath )
304
309
305
310
@classmethod
306
311
def make_png (cls , tex , fontsize , dpi ):
@@ -309,13 +314,12 @@ def make_png(cls, tex, fontsize, dpi):
309
314
310
315
Return the file name.
311
316
"""
312
- pngfile = Path (cls .get_basefile (tex , fontsize )).with_suffix (".png" )
313
- # see get_rgba for a discussion of the background
314
- if not pngfile .exists ():
315
- dvifile = cls .make_dvi (tex , fontsize )
316
- with TemporaryDirectory (dir = pngfile .parent ) as tmpdir :
317
+ pngpath = cls ._get_base_path (tex , fontsize , dpi ).with_suffix (".png" )
318
+ if not pngpath .exists ():
319
+ dvipath = cls .make_dvi (tex , fontsize )
320
+ with TemporaryDirectory (dir = pngpath .parent ) as tmpdir :
317
321
cmd = ["dvipng" , "-bg" , "Transparent" , "-D" , str (dpi ),
318
- "-T" , "tight" , "-o" , "file.png" , dvifile ]
322
+ "-T" , "tight" , "-o" , "file.png" , dvipath ]
319
323
# When testing, disable FreeType rendering for reproducibility;
320
324
# but dvipng 1.16 has a bug (fixed in f3ff241) that breaks
321
325
# --freetype0 mode, so for it we keep FreeType enabled; the
@@ -324,8 +328,8 @@ def make_png(cls, tex, fontsize, dpi):
324
328
mpl ._get_executable_info ("dvipng" ).raw_version != "1.16" ):
325
329
cmd .insert (1 , "--freetype0" )
326
330
cls ._run_checked_subprocess (cmd , tex , cwd = tmpdir )
327
- Path (tmpdir , "file.png" ).replace (pngfile )
328
- return str (pngfile )
331
+ Path (tmpdir , "file.png" ).replace (pngpath )
332
+ return str (pngpath )
329
333
330
334
@classmethod
331
335
def get_grey (cls , tex , fontsize = None , dpi = None ):
@@ -336,7 +340,7 @@ def get_grey(cls, tex, fontsize=None, dpi=None):
336
340
alpha = cls ._grey_arrayd .get (key )
337
341
if alpha is None :
338
342
pngfile = cls .make_png (tex , fontsize , dpi )
339
- rgba = mpl .image .imread (os . path . join ( cls . _texcache , pngfile ) )
343
+ rgba = mpl .image .imread (pngfile )
340
344
cls ._grey_arrayd [key ] = alpha = rgba [:, :, - 1 ]
341
345
return alpha
342
346
@@ -362,9 +366,9 @@ def get_text_width_height_descent(cls, tex, fontsize, renderer=None):
362
366
"""Return width, height and descent of the text."""
363
367
if tex .strip () == '' :
364
368
return 0 , 0 , 0
365
- dvifile = cls .make_dvi (tex , fontsize )
369
+ dvipath = cls .make_dvi (tex , fontsize )
366
370
dpi_fraction = renderer .points_to_pixels (1. ) if renderer else 1
367
- with dviread .Dvi (dvifile , 72 * dpi_fraction ) as dvi :
371
+ with dviread .Dvi (dvipath , 72 * dpi_fraction ) as dvi :
368
372
page , = dvi
369
373
# A total height (including the descent) needs to be returned.
370
374
return page .width , page .height + page .descent , page .descent
0 commit comments