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,23 @@ 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
+ tmpdir = property (lambda self : self ._tmpdir .name )
202
+ texcommand = property (lambda self : self ._texcmd )
203
+ latex = property (
204
+ lambda self : self ._tex if self ._tex .returncode is not None else None )
205
+ latex_stdin_utf8 = _api .deprecated ("3.3" )(
206
+ property (lambda self : self ._tex .stdin ))
207
+
210
208
@staticmethod
211
209
def _build_latex_header ():
212
210
latex_preamble = get_preamble ()
@@ -225,7 +223,6 @@ def _build_latex_header():
225
223
latex_fontspec ,
226
224
r"\begin{document}" ,
227
225
r"text $math \mu$" , # force latex to load fonts now
228
- r"\typeout{pgf_backend_query_start}" ,
229
226
]
230
227
return "\n " .join (latex_header )
231
228
@@ -242,87 +239,10 @@ def _get_cached_or_new(cls):
242
239
def _get_cached_or_new_impl (cls , header ): # Helper for _get_cached_or_new.
243
240
return cls ()
244
241
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
242
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
243
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
244
+ self .latex_header = LatexManager ._build_latex_header ()
245
+ super ().__init__ (mpl .rcParams ["pgf.texsystem" ], self .latex_header )
326
246
327
247
def get_width_height_descent (self , text , prop ):
328
248
"""
@@ -347,7 +267,7 @@ def get_width_height_descent(self, text, prop):
347
267
.format (text , e .latex_output )) from e
348
268
349
269
# typeout width, height and text offset of the last textbox
350
- self ._stdin_writeln (r"\typeout {\the\wd0,\the\ht0,\the\dp0}" )
270
+ self ._stdin_writeln (r"\message {\the\wd0,\the\ht0,\the\dp0}" )
351
271
# read answer from latex and advance to the next prompt
352
272
try :
353
273
answer = self ._expect_prompt ()
@@ -357,7 +277,7 @@ def get_width_height_descent(self, text, prop):
357
277
358
278
# parse metrics from the answer string
359
279
try :
360
- width , height , offset = answer .splitlines ()[ 0 ]. split ("," )
280
+ width , height , offset = answer .split ("," )
361
281
except Exception as err :
362
282
raise ValueError ("Error processing '{}'\n LaTeX Output:\n {}"
363
283
.format (text , answer )) from err
0 commit comments