16
16
from PIL import Image
17
17
18
18
import matplotlib as mpl
19
- from matplotlib import _api , cbook , font_manager as fm
19
+ from matplotlib import _api , cbook , font_manager as fm , texmanager
20
20
from matplotlib .backend_bases import (
21
21
_Backend , _check_savefig_extra_args , FigureCanvasBase , FigureManagerBase ,
22
22
GraphicsContextBase , RendererBase , _no_output_draw
@@ -188,25 +188,24 @@ def gs_convert(pdffile, pngfile, dpi):
188
188
raise RuntimeError ("No suitable pdf to png renderer found." )
189
189
190
190
191
- class LatexError (Exception ):
192
- def __init__ (self , message , latex_output = "" ):
193
- super ().__init__ (message )
194
- self .latex_output = latex_output
191
+ LatexError = texmanager ._InteractiveTex .TexError
195
192
196
- def __str__ (self ):
197
- s , = self .args
198
- if self .latex_output :
199
- s += "\n " + self .latex_output
200
- return s
201
193
202
-
203
- class LatexManager :
194
+ class LatexManager (texmanager ._InteractiveTex ):
204
195
"""
205
196
The LatexManager opens an instance of the LaTeX application for
206
197
determining the metrics of text elements. The LaTeX environment can be
207
198
modified by setting fonts and/or a custom preamble in `.rcParams`.
208
199
"""
209
200
201
+ # Backcompat properties.
202
+ tmpdir = property (lambda self : self ._tmpdir .name )
203
+ texcommand = property (lambda self : self ._texcmd )
204
+ latex = property (
205
+ lambda self : self ._tex if self ._tex .poll () is None else None )
206
+ latex_stdin_utf8 = _api .deprecated ("3.3" )(
207
+ property (lambda self : self ._tex .stdin ))
208
+
210
209
@staticmethod
211
210
def _build_latex_header ():
212
211
latex_preamble = get_preamble ()
@@ -225,7 +224,6 @@ def _build_latex_header():
225
224
latex_fontspec ,
226
225
r"\begin{document}" ,
227
226
r"text $math \mu$" , # force latex to load fonts now
228
- r"\typeout{pgf_backend_query_start}" ,
229
227
]
230
228
return "\n " .join (latex_header )
231
229
@@ -242,87 +240,10 @@ def _get_cached_or_new(cls):
242
240
def _get_cached_or_new_impl (cls , header ): # Helper for _get_cached_or_new.
243
241
return cls ()
244
242
245
- def _stdin_writeln (self , s ):
246
- if self .latex is None :
247
- self ._setup_latex_process ()
248
- self .latex .stdin .write (s )
249
- self .latex .stdin .write ("\n " )
250
- self .latex .stdin .flush ()
251
-
252
- def _expect (self , s ):
253
- s = list (s )
254
- chars = []
255
- while True :
256
- c = self .latex .stdout .read (1 )
257
- chars .append (c )
258
- if chars [- len (s ):] == s :
259
- break
260
- if not c :
261
- self .latex .kill ()
262
- self .latex = None
263
- raise LatexError ("LaTeX process halted" , "" .join (chars ))
264
- return "" .join (chars )
265
-
266
- def _expect_prompt (self ):
267
- return self ._expect ("\n *" )
268
-
269
243
def __init__ (self ):
270
- # create a tmp directory for running latex, register it for deletion
271
- self ._tmpdir = TemporaryDirectory ()
272
- self .tmpdir = self ._tmpdir .name
273
- self ._finalize_tmpdir = weakref .finalize (self , self ._tmpdir .cleanup )
274
-
275
- # test the LaTeX setup to ensure a clean startup of the subprocess
276
- self .texcommand = mpl .rcParams ["pgf.texsystem" ]
277
- self .latex_header = LatexManager ._build_latex_header ()
278
- latex_end = "\n \\ makeatletter\n \\ @@end\n "
279
- try :
280
- latex = subprocess .Popen (
281
- [self .texcommand , "-halt-on-error" ],
282
- stdin = subprocess .PIPE , stdout = subprocess .PIPE ,
283
- encoding = "utf-8" , cwd = self .tmpdir )
284
- except FileNotFoundError as err :
285
- raise RuntimeError (
286
- f"{ self .texcommand } not found. Install it or change "
287
- f"rcParams['pgf.texsystem'] to an available TeX "
288
- f"implementation." ) from err
289
- except OSError as err :
290
- raise RuntimeError ("Error starting process %r" %
291
- self .texcommand ) from err
292
- test_input = self .latex_header + latex_end
293
- stdout , stderr = latex .communicate (test_input )
294
- if latex .returncode != 0 :
295
- raise LatexError ("LaTeX returned an error, probably missing font "
296
- "or error in preamble." , stdout )
297
-
298
- self .latex = None # Will be set up on first use.
299
244
self .str_cache = {} # cache for strings already processed
300
-
301
- def _setup_latex_process (self ):
302
- # Open LaTeX process for real work; register it for deletion. On
303
- # Windows, we must ensure that the subprocess has quit before being
304
- # able to delete the tmpdir in which it runs; in order to do so, we
305
- # must first `kill()` it, and then `communicate()` with it.
306
- self .latex = subprocess .Popen (
307
- [self .texcommand , "-halt-on-error" ],
308
- stdin = subprocess .PIPE , stdout = subprocess .PIPE ,
309
- encoding = "utf-8" , cwd = self .tmpdir )
310
-
311
- def finalize_latex (latex ):
312
- latex .kill ()
313
- latex .communicate ()
314
-
315
- self ._finalize_latex = weakref .finalize (
316
- self , finalize_latex , self .latex )
317
- # write header with 'pgf_backend_query_start' token
318
- self ._stdin_writeln (self ._build_latex_header ())
319
- # read all lines until our 'pgf_backend_query_start' token appears
320
- self ._expect ("*pgf_backend_query_start" )
321
- self ._expect_prompt ()
322
-
323
- @_api .deprecated ("3.3" )
324
- def latex_stdin_utf8 (self ):
325
- return self .latex .stdin
245
+ self .latex_header = LatexManager ._build_latex_header ()
246
+ super ().__init__ (mpl .rcParams ["pgf.texsystem" ], self .latex_header )
326
247
327
248
def get_width_height_descent (self , text , prop ):
328
249
"""
@@ -347,7 +268,7 @@ def get_width_height_descent(self, text, prop):
347
268
.format (text , e .latex_output )) from e
348
269
349
270
# typeout width, height and text offset of the last textbox
350
- self ._stdin_writeln (r"\typeout {\the\wd0,\the\ht0,\the\dp0}" )
271
+ self ._stdin_writeln (r"\message {\the\wd0,\the\ht0,\the\dp0}" )
351
272
# read answer from latex and advance to the next prompt
352
273
try :
353
274
answer = self ._expect_prompt ()
@@ -357,7 +278,7 @@ def get_width_height_descent(self, text, prop):
357
278
358
279
# parse metrics from the answer string
359
280
try :
360
- width , height , offset = answer .splitlines ()[ 0 ]. split ("," )
281
+ width , height , offset = answer .split ("," )
361
282
except Exception as err :
362
283
raise ValueError ("Error processing '{}'\n LaTeX Output:\n {}"
363
284
.format (text , answer )) from err
0 commit comments