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 ):
@@ -243,17 +249,16 @@ def make_tex(cls, tex, fontsize):
243
249
244
250
Return the file name.
245
251
"""
246
- texfile = cls .get_basefile (tex , fontsize ) + ".tex"
247
- Path (texfile ).write_text (cls ._get_tex_source (tex , fontsize ),
248
- encoding = 'utf-8' )
249
- return texfile
252
+ texpath = cls ._get_base_path (tex , fontsize ).with_suffix (".tex" )
253
+ texpath .write_text (cls ._get_tex_source (tex , fontsize ), encoding = 'utf-8' )
254
+ return str (texpath )
250
255
251
256
@classmethod
252
257
def _run_checked_subprocess (cls , command , tex , * , cwd = None ):
253
258
_log .debug (cbook ._pformat_subprocess (command ))
254
259
try :
255
260
report = subprocess .check_output (
256
- command , cwd = cwd if cwd is not None else cls ._texcache ,
261
+ command , cwd = cwd if cwd is not None else cls ._cache_dir ,
257
262
stderr = subprocess .STDOUT )
258
263
except FileNotFoundError as exc :
259
264
raise RuntimeError (
@@ -281,8 +286,8 @@ def make_dvi(cls, tex, fontsize):
281
286
282
287
Return the file name.
283
288
"""
284
- dvifile = Path ( cls .get_basefile (tex , fontsize ) ).with_suffix (".dvi" )
285
- if not dvifile .exists ():
289
+ dvipath = cls ._get_base_path (tex , fontsize ).with_suffix (".dvi" )
290
+ if not dvipath .exists ():
286
291
# Generate the tex and dvi in a temporary directory to avoid race
287
292
# conditions e.g. if multiple processes try to process the same tex
288
293
# string at the same time. Having tmpdir be a subdirectory of the
@@ -292,17 +297,17 @@ def make_dvi(cls, tex, fontsize):
292
297
# the absolute path may contain characters (e.g. ~) that TeX does
293
298
# not support; n.b. relative paths cannot traverse parents, or it
294
299
# will be blocked when `openin_any = p` in texmf.cnf).
295
- with TemporaryDirectory (dir = dvifile .parent ) as tmpdir :
300
+ with TemporaryDirectory (dir = dvipath .parent ) as tmpdir :
296
301
Path (tmpdir , "file.tex" ).write_text (
297
302
cls ._get_tex_source (tex , fontsize ), encoding = 'utf-8' )
298
303
cls ._run_checked_subprocess (
299
304
["latex" , "-interaction=nonstopmode" , "--halt-on-error" ,
300
305
"file.tex" ], tex , cwd = tmpdir )
301
- Path (tmpdir , "file.dvi" ).replace (dvifile )
306
+ Path (tmpdir , "file.dvi" ).replace (dvipath )
302
307
# Also move the tex source to the main cache directory, but
303
308
# only for backcompat.
304
- Path (tmpdir , "file.tex" ).replace (dvifile .with_suffix (".tex" ))
305
- return str (dvifile )
309
+ Path (tmpdir , "file.tex" ).replace (dvipath .with_suffix (".tex" ))
310
+ return str (dvipath )
306
311
307
312
@classmethod
308
313
def make_png (cls , tex , fontsize , dpi ):
@@ -311,13 +316,12 @@ def make_png(cls, tex, fontsize, dpi):
311
316
312
317
Return the file name.
313
318
"""
314
- pngfile = Path (cls .get_basefile (tex , fontsize )).with_suffix (".png" )
315
- # see get_rgba for a discussion of the background
316
- if not pngfile .exists ():
317
- dvifile = cls .make_dvi (tex , fontsize )
318
- with TemporaryDirectory (dir = pngfile .parent ) as tmpdir :
319
+ pngpath = cls ._get_base_path (tex , fontsize , dpi ).with_suffix (".png" )
320
+ if not pngpath .exists ():
321
+ dvipath = cls .make_dvi (tex , fontsize )
322
+ with TemporaryDirectory (dir = pngpath .parent ) as tmpdir :
319
323
cmd = ["dvipng" , "-bg" , "Transparent" , "-D" , str (dpi ),
320
- "-T" , "tight" , "-o" , "file.png" , dvifile ]
324
+ "-T" , "tight" , "-o" , "file.png" , dvipath ]
321
325
# When testing, disable FreeType rendering for reproducibility;
322
326
# but dvipng 1.16 has a bug (fixed in f3ff241) that breaks
323
327
# --freetype0 mode, so for it we keep FreeType enabled; the
@@ -326,8 +330,8 @@ def make_png(cls, tex, fontsize, dpi):
326
330
mpl ._get_executable_info ("dvipng" ).raw_version != "1.16" ):
327
331
cmd .insert (1 , "--freetype0" )
328
332
cls ._run_checked_subprocess (cmd , tex , cwd = tmpdir )
329
- Path (tmpdir , "file.png" ).replace (pngfile )
330
- return str (pngfile )
333
+ Path (tmpdir , "file.png" ).replace (pngpath )
334
+ return str (pngpath )
331
335
332
336
@classmethod
333
337
def get_grey (cls , tex , fontsize = None , dpi = None ):
@@ -338,7 +342,7 @@ def get_grey(cls, tex, fontsize=None, dpi=None):
338
342
alpha = cls ._grey_arrayd .get (key )
339
343
if alpha is None :
340
344
pngfile = cls .make_png (tex , fontsize , dpi )
341
- rgba = mpl .image .imread (os . path . join ( cls . _texcache , pngfile ) )
345
+ rgba = mpl .image .imread (pngfile )
342
346
cls ._grey_arrayd [key ] = alpha = rgba [:, :, - 1 ]
343
347
return alpha
344
348
@@ -364,9 +368,9 @@ def get_text_width_height_descent(cls, tex, fontsize, renderer=None):
364
368
"""Return width, height and descent of the text."""
365
369
if tex .strip () == '' :
366
370
return 0 , 0 , 0
367
- dvifile = cls .make_dvi (tex , fontsize )
371
+ dvipath = cls .make_dvi (tex , fontsize )
368
372
dpi_fraction = renderer .points_to_pixels (1. ) if renderer else 1
369
- with dviread .Dvi (dvifile , 72 * dpi_fraction ) as dvi :
373
+ with dviread .Dvi (dvipath , 72 * dpi_fraction ) as dvi :
370
374
page , = dvi
371
375
# A total height (including the descent) needs to be returned.
372
376
return page .width , page .height + page .descent , page .descent
0 commit comments