1
1
"""Render math in HTML via dvipng or dvisvgm."""
2
2
3
3
import base64
4
- import posixpath
5
4
import re
6
5
import shutil
7
6
import subprocess
@@ -157,13 +156,10 @@ def convert_dvi_to_image(command: List[str], name: str) -> Tuple[str, str]:
157
156
raise MathExtError ('%s exited with error' % name , exc .stderr , exc .stdout ) from exc
158
157
159
158
160
- def convert_dvi_to_png (dvipath : str , builder : Builder ) -> Tuple [ str , Optional [int ] ]:
159
+ def convert_dvi_to_png (dvipath : str , builder : Builder , out_path : str ) -> Optional [int ]:
161
160
"""Convert DVI file to PNG image."""
162
- tempdir = ensure_tempdir (builder )
163
- filename = path .join (tempdir , 'math.png' )
164
-
165
161
name = 'dvipng'
166
- command = [builder .config .imgmath_dvipng , '-o' , filename , '-T' , 'tight' , '-z9' ]
162
+ command = [builder .config .imgmath_dvipng , '-o' , out_path , '-T' , 'tight' , '-z9' ]
167
163
command .extend (builder .config .imgmath_dvipng_args )
168
164
if builder .config .imgmath_use_preview :
169
165
command .append ('--depth' )
@@ -177,19 +173,16 @@ def convert_dvi_to_png(dvipath: str, builder: Builder) -> Tuple[str, Optional[in
177
173
matched = depth_re .match (line )
178
174
if matched :
179
175
depth = int (matched .group (1 ))
180
- write_png_depth (filename , depth )
176
+ write_png_depth (out_path , depth )
181
177
break
182
178
183
- return filename , depth
179
+ return depth
184
180
185
181
186
- def convert_dvi_to_svg (dvipath : str , builder : Builder ) -> Tuple [ str , Optional [int ] ]:
182
+ def convert_dvi_to_svg (dvipath : str , builder : Builder , out_path : str ) -> Optional [int ]:
187
183
"""Convert DVI file to SVG image."""
188
- tempdir = ensure_tempdir (builder )
189
- filename = path .join (tempdir , 'math.svg' )
190
-
191
184
name = 'dvisvgm'
192
- command = [builder .config .imgmath_dvisvgm , '-o' , filename ]
185
+ command = [builder .config .imgmath_dvisvgm , '-o' , out_path ]
193
186
command .extend (builder .config .imgmath_dvisvgm_args )
194
187
command .append (dvipath )
195
188
@@ -201,16 +194,16 @@ def convert_dvi_to_svg(dvipath: str, builder: Builder) -> Tuple[str, Optional[in
201
194
matched = depthsvg_re .match (line )
202
195
if matched :
203
196
depth = round (float (matched .group (1 )) * 100 / 72.27 ) # assume 100ppi
204
- write_svg_depth (filename , depth )
197
+ write_svg_depth (out_path , depth )
205
198
break
206
199
207
- return filename , depth
200
+ return depth
208
201
209
202
210
203
def render_math (
211
204
self : HTMLTranslator ,
212
205
math : str ,
213
- ) -> Tuple [Optional [str ], Optional [int ], Optional [ str ], Optional [ str ] ]:
206
+ ) -> Tuple [Optional [str ], Optional [int ]]:
214
207
"""Render the LaTeX math expression *math* using latex and dvipng or
215
208
dvisvgm.
216
209
@@ -234,43 +227,43 @@ def render_math(
234
227
self .builder .config ,
235
228
self .builder .confdir )
236
229
237
- filename = "%s.%s" % ( sha1 (latex .encode ()).hexdigest (), image_format )
238
- relfn = posixpath .join (self .builder .imgpath , 'math' , filename )
239
- outfn = path .join ( self . builder . outdir , self . builder . imagedir , 'math' , filename )
240
- if path .isfile (outfn ):
230
+ filename = f" { sha1 (latex .encode ()).hexdigest ()} . { image_format } "
231
+ generated_path = path .join (self .builder .outdir , self . builder . imagedir , 'math' , filename )
232
+ ensuredir ( path .dirname ( generated_path ) )
233
+ if path .isfile (generated_path ):
241
234
if image_format == 'png' :
242
- depth = read_png_depth (outfn )
235
+ depth = read_png_depth (generated_path )
243
236
elif image_format == 'svg' :
244
- depth = read_svg_depth (outfn )
245
- return relfn , depth , None , outfn
237
+ depth = read_svg_depth (generated_path )
238
+ return generated_path , depth
246
239
247
240
# if latex or dvipng (dvisvgm) has failed once, don't bother to try again
248
241
if hasattr (self .builder , '_imgmath_warned_latex' ) or \
249
242
hasattr (self .builder , '_imgmath_warned_image_translator' ):
250
- return None , None , None , None
243
+ return None , None
251
244
252
245
# .tex -> .dvi
253
246
try :
254
247
dvipath = compile_math (latex , self .builder )
255
248
except InvokeError :
256
249
self .builder ._imgmath_warned_latex = True # type: ignore
257
- return None , None , None , None
250
+ return None , None
258
251
259
252
# .dvi -> .png/.svg
260
253
try :
261
254
if image_format == 'png' :
262
- imgpath , depth = convert_dvi_to_png (dvipath , self .builder )
255
+ depth = convert_dvi_to_png (dvipath , self .builder , generated_path )
263
256
elif image_format == 'svg' :
264
- imgpath , depth = convert_dvi_to_svg (dvipath , self .builder )
257
+ depth = convert_dvi_to_svg (dvipath , self .builder , generated_path )
265
258
except InvokeError :
266
259
self .builder ._imgmath_warned_image_translator = True # type: ignore
267
- return None , None , None , None
260
+ return None , None
268
261
269
- return relfn , depth , imgpath , outfn
262
+ return generated_path , depth
270
263
271
264
272
- def render_maths_to_base64 (image_format : str , outfn : Optional [str ]) -> str :
273
- with open (outfn , "rb" ) as f :
265
+ def render_maths_to_base64 (image_format : str , generated_path : Optional [str ]) -> str :
266
+ with open (generated_path , "rb" ) as f :
274
267
encoded = base64 .b64encode (f .read ()).decode (encoding = 'utf-8' )
275
268
if image_format == 'png' :
276
269
return f'data:image/png;base64,{ encoded } '
@@ -279,15 +272,23 @@ def render_maths_to_base64(image_format: str, outfn: Optional[str]) -> str:
279
272
raise MathExtError ('imgmath_image_format must be either "png" or "svg"' )
280
273
281
274
282
- def cleanup_tempdir (app : Sphinx , exc : Exception ) -> None :
275
+ def clean_up_files (app : Sphinx , exc : Exception ) -> None :
283
276
if exc :
284
277
return
285
- if not hasattr (app .builder , '_imgmath_tempdir' ):
286
- return
287
- try :
288
- shutil .rmtree (app .builder ._imgmath_tempdir ) # type: ignore
289
- except Exception :
290
- pass
278
+
279
+ if hasattr (app .builder , '_imgmath_tempdir' ):
280
+ try :
281
+ shutil .rmtree (app .builder ._imgmath_tempdir ) # type: ignore
282
+ except Exception :
283
+ pass
284
+
285
+ if app .builder .config .imgmath_embed :
286
+ # in embed mode, the images are still generated in the math output dir
287
+ # to be shared across workers, but are not useful to the final document
288
+ try :
289
+ shutil .rmtree (path .join (app .builder .outdir , app .builder .imagedir , 'math' ))
290
+ except Exception :
291
+ pass
291
292
292
293
293
294
def get_tooltip (self : HTMLTranslator , node : Element ) -> str :
@@ -298,28 +299,26 @@ def get_tooltip(self: HTMLTranslator, node: Element) -> str:
298
299
299
300
def html_visit_math (self : HTMLTranslator , node : nodes .math ) -> None :
300
301
try :
301
- fname , depth , imgpath , outfn = render_math (self , '$' + node .astext () + '$' )
302
+ rendered_path , depth = render_math (self , '$' + node .astext () + '$' )
302
303
except MathExtError as exc :
303
304
msg = str (exc )
304
305
sm = nodes .system_message (msg , type = 'WARNING' , level = 2 ,
305
306
backrefs = [], source = node .astext ())
306
307
sm .walkabout (self )
307
308
logger .warning (__ ('display latex %r: %s' ), node .astext (), msg )
308
309
raise nodes .SkipNode from exc
309
- if self .builder .config .imgmath_embed :
310
- image_format = self .builder .config .imgmath_image_format .lower ()
311
- img_src = render_maths_to_base64 (image_format , imgpath )
312
- else :
313
- # Move generated image on tempdir to build dir
314
- if imgpath is not None :
315
- ensuredir (path .dirname (outfn ))
316
- shutil .move (imgpath , outfn )
317
- img_src = fname
318
- if img_src is None :
310
+
311
+ if rendered_path is None :
319
312
# something failed -- use text-only as a bad substitute
320
313
self .body .append ('<span class="math">%s</span>' %
321
314
self .encode (node .astext ()).strip ())
322
315
else :
316
+ if self .builder .config .imgmath_embed :
317
+ image_format = self .builder .config .imgmath_image_format .lower ()
318
+ img_src = render_maths_to_base64 (image_format , rendered_path )
319
+ else :
320
+ relative_path = path .relpath (rendered_path , self .builder .outdir )
321
+ img_src = relative_path .replace (path .sep , '/' )
323
322
c = f'<img class="math" src="{ img_src } "' + get_tooltip (self , node )
324
323
if depth is not None :
325
324
c += f' style="vertical-align: { - depth :d} px"'
@@ -333,7 +332,7 @@ def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None
333
332
else :
334
333
latex = wrap_displaymath (node .astext (), None , False )
335
334
try :
336
- fname , depth , imgpath , outfn = render_math (self , latex )
335
+ rendered_path , depth = render_math (self , latex )
337
336
except MathExtError as exc :
338
337
msg = str (exc )
339
338
sm = nodes .system_message (msg , type = 'WARNING' , level = 2 ,
@@ -348,20 +347,18 @@ def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None
348
347
self .body .append ('<span class="eqno">(%s)' % number )
349
348
self .add_permalink_ref (node , _ ('Permalink to this equation' ))
350
349
self .body .append ('</span>' )
351
- if self .builder .config .imgmath_embed :
352
- image_format = self .builder .config .imgmath_image_format .lower ()
353
- img_src = render_maths_to_base64 (image_format , imgpath )
354
- else :
355
- # Move generated image on tempdir to build dir
356
- if imgpath is not None :
357
- ensuredir (path .dirname (outfn ))
358
- shutil .move (imgpath , outfn )
359
- img_src = fname
360
- if img_src is None :
350
+
351
+ if rendered_path is None :
361
352
# something failed -- use text-only as a bad substitute
362
353
self .body .append ('<span class="math">%s</span></p>\n </div>' %
363
354
self .encode (node .astext ()).strip ())
364
355
else :
356
+ if self .builder .config .imgmath_embed :
357
+ image_format = self .builder .config .imgmath_image_format .lower ()
358
+ img_src = render_maths_to_base64 (image_format , rendered_path )
359
+ else :
360
+ relative_path = path .relpath (rendered_path , self .builder .outdir )
361
+ img_src = relative_path .replace (path .sep , '/' )
365
362
self .body .append (f'<img src="{ img_src } "' + get_tooltip (self , node ) +
366
363
'/></p>\n </div>' )
367
364
raise nodes .SkipNode
@@ -386,5 +383,5 @@ def setup(app: Sphinx) -> Dict[str, Any]:
386
383
app .add_config_value ('imgmath_add_tooltips' , True , 'html' )
387
384
app .add_config_value ('imgmath_font_size' , 12 , 'html' )
388
385
app .add_config_value ('imgmath_embed' , False , 'html' , [bool ])
389
- app .connect ('build-finished' , cleanup_tempdir )
386
+ app .connect ('build-finished' , clean_up_files )
390
387
return {'version' : sphinx .__display_version__ , 'parallel_read_safe' : True }
0 commit comments