Enable automatic formatting for `sphinx/ext/imgmath.py`

This commit is contained in:
Adam Turner 2024-12-24 19:57:14 +00:00
parent f592962b8d
commit da5a67d5a4
2 changed files with 79 additions and 54 deletions

View File

@ -415,7 +415,6 @@ exclude = [
"sphinx/domains/python/_object.py", "sphinx/domains/python/_object.py",
"sphinx/domains/rst.py", "sphinx/domains/rst.py",
"sphinx/domains/std/__init__.py", "sphinx/domains/std/__init__.py",
"sphinx/ext/imgmath.py",
"sphinx/ext/inheritance_diagram.py", "sphinx/ext/inheritance_diagram.py",
"sphinx/ext/linkcode.py", "sphinx/ext/linkcode.py",
"sphinx/ext/mathjax.py", "sphinx/ext/mathjax.py",

View File

@ -29,7 +29,6 @@ from sphinx.util.png import read_png_depth, write_png_depth
from sphinx.util.template import LaTeXRenderer from sphinx.util.template import LaTeXRenderer
if TYPE_CHECKING: if TYPE_CHECKING:
from docutils.nodes import Element from docutils.nodes import Element
from sphinx.application import Sphinx from sphinx.application import Sphinx
@ -47,7 +46,7 @@ class MathExtError(SphinxError):
category = 'Math extension error' category = 'Math extension error'
def __init__( def __init__(
self, msg: str, stderr: str | None = None, stdout: str | None = None, self, msg: str, stderr: str | None = None, stdout: str | None = None
) -> None: ) -> None:
if stderr: if stderr:
msg += '\n[stderr]\n' + stderr msg += '\n[stderr]\n' + stderr
@ -68,9 +67,8 @@ depthsvgcomment_re = re.compile(r'<!-- DEPTH=(-?\d+) -->')
def read_svg_depth(filename: str) -> int | None: def read_svg_depth(filename: str) -> int | None:
"""Read the depth from comment at last line of SVG file """Read the depth from comment at last line of SVG file"""
""" with open(filename, encoding='utf-8') as f:
with open(filename, encoding="utf-8") as f:
for line in f: # NoQA: B007 for line in f: # NoQA: B007
pass pass
# Only last line is checked # Only last line is checked
@ -81,16 +79,17 @@ def read_svg_depth(filename: str) -> int | None:
def write_svg_depth(filename: str, depth: int) -> None: def write_svg_depth(filename: str, depth: int) -> None:
"""Write the depth to SVG file as a comment at end of file """Write the depth to SVG file as a comment at end of file"""
""" with open(filename, 'a', encoding='utf-8') as f:
with open(filename, 'a', encoding="utf-8") as f:
f.write('\n<!-- DEPTH=%s -->' % depth) f.write('\n<!-- DEPTH=%s -->' % depth)
def generate_latex_macro(image_format: str, def generate_latex_macro(
math: str, image_format: str,
config: Config, math: str,
confdir: str | os.PathLike[str] = '') -> str: config: Config,
confdir: str | os.PathLike[str] = '',
) -> str:
"""Generate LaTeX macro.""" """Generate LaTeX macro."""
variables = { variables = {
'fontsize': config.imgmath_font_size, 'fontsize': config.imgmath_font_size,
@ -109,7 +108,9 @@ def generate_latex_macro(image_format: str,
for template_dir in config.templates_path: for template_dir in config.templates_path:
for template_suffix in ('.jinja', '_t'): for template_suffix in ('.jinja', '_t'):
template = os.path.join(confdir, template_dir, template_name + template_suffix) template = os.path.join(
confdir, template_dir, template_name + template_suffix
)
if os.path.exists(template): if os.path.exists(template):
return LaTeXRenderer().render(template, variables) return LaTeXRenderer().render(template, variables)
@ -149,16 +150,21 @@ def compile_math(latex: str, builder: Builder) -> str:
command.append('math.tex') command.append('math.tex')
try: try:
subprocess.run(command, capture_output=True, cwd=tempdir, check=True, subprocess.run(
encoding='ascii') command, capture_output=True, cwd=tempdir, check=True, encoding='ascii'
)
if imgmath_latex_name in {'xelatex', 'tectonic'}: if imgmath_latex_name in {'xelatex', 'tectonic'}:
return os.path.join(tempdir, 'math.xdv') return os.path.join(tempdir, 'math.xdv')
else: else:
return os.path.join(tempdir, 'math.dvi') return os.path.join(tempdir, 'math.dvi')
except OSError as exc: except OSError as exc:
logger.warning(__('LaTeX command %r cannot be run (needed for math ' logger.warning(
'display), check the imgmath_latex setting'), __(
builder.config.imgmath_latex) 'LaTeX command %r cannot be run (needed for math '
'display), check the imgmath_latex setting'
),
builder.config.imgmath_latex,
)
raise InvokeError from exc raise InvokeError from exc
except CalledProcessError as exc: except CalledProcessError as exc:
msg = 'latex exited with error' msg = 'latex exited with error'
@ -171,12 +177,19 @@ def convert_dvi_to_image(command: list[str], name: str) -> tuple[str, str]:
ret = subprocess.run(command, capture_output=True, check=True, encoding='ascii') ret = subprocess.run(command, capture_output=True, check=True, encoding='ascii')
return ret.stdout, ret.stderr return ret.stdout, ret.stderr
except OSError as exc: except OSError as exc:
logger.warning(__('%s command %r cannot be run (needed for math ' logger.warning(
'display), check the imgmath_%s setting'), __(
name, command[0], name) '%s command %r cannot be run (needed for math '
'display), check the imgmath_%s setting'
),
name,
command[0],
name,
)
raise InvokeError from exc raise InvokeError from exc
except CalledProcessError as exc: except CalledProcessError as exc:
raise MathExtError('%s exited with error' % name, exc.stderr, exc.stdout) from exc msg = f'{name} exited with error'
raise MathExtError(msg, exc.stderr, exc.stdout) from exc
def convert_dvi_to_png(dvipath: str, builder: Builder, out_path: str) -> int | None: def convert_dvi_to_png(dvipath: str, builder: Builder, out_path: str) -> int | None:
@ -245,13 +258,16 @@ def render_math(
unsupported_format_msg = 'imgmath_image_format must be either "png" or "svg"' unsupported_format_msg = 'imgmath_image_format must be either "png" or "svg"'
raise MathExtError(unsupported_format_msg) raise MathExtError(unsupported_format_msg)
latex = generate_latex_macro(image_format, latex = generate_latex_macro(
math, image_format, math, self.builder.config, self.builder.confdir
self.builder.config, )
self.builder.confdir)
filename = f"{sha1(latex.encode(), usedforsecurity=False).hexdigest()}.{image_format}" filename = (
generated_path = os.path.join(self.builder.outdir, self.builder.imagedir, 'math', filename) f'{sha1(latex.encode(), usedforsecurity=False).hexdigest()}.{image_format}'
)
generated_path = os.path.join(
self.builder.outdir, self.builder.imagedir, 'math', filename
)
ensuredir(os.path.dirname(generated_path)) ensuredir(os.path.dirname(generated_path))
if os.path.isfile(generated_path): if os.path.isfile(generated_path):
if image_format == 'png': if image_format == 'png':
@ -261,8 +277,9 @@ def render_math(
return generated_path, depth return generated_path, depth
# if latex or dvipng (dvisvgm) has failed once, don't bother to try again # if latex or dvipng (dvisvgm) has failed once, don't bother to try again
if hasattr(self.builder, '_imgmath_warned_latex') or \ latex_failed = hasattr(self.builder, '_imgmath_warned_latex')
hasattr(self.builder, '_imgmath_warned_image_translator'): trans_failed = hasattr(self.builder, '_imgmath_warned_image_translator')
if latex_failed or trans_failed:
return None, None return None, None
# .tex -> .dvi # .tex -> .dvi
@ -286,7 +303,7 @@ def render_math(
def render_maths_to_base64(image_format: str, generated_path: str) -> str: def render_maths_to_base64(image_format: str, generated_path: str) -> str:
with open(generated_path, "rb") as f: with open(generated_path, 'rb') as f:
encoded = base64.b64encode(f.read()).decode(encoding='utf-8') encoded = base64.b64encode(f.read()).decode(encoding='utf-8')
if image_format == 'png': if image_format == 'png':
return f'data:image/png;base64,{encoded}' return f'data:image/png;base64,{encoded}'
@ -308,12 +325,14 @@ def clean_up_files(app: Sphinx, exc: Exception) -> None:
# in embed mode, the images are still generated in the math output dir # in embed mode, the images are still generated in the math output dir
# to be shared across workers, but are not useful to the final document # to be shared across workers, but are not useful to the final document
with contextlib.suppress(Exception): with contextlib.suppress(Exception):
shutil.rmtree(os.path.join(app.builder.outdir, app.builder.imagedir, 'math')) shutil.rmtree(
os.path.join(app.builder.outdir, app.builder.imagedir, 'math')
)
def get_tooltip(self: HTML5Translator, node: Element) -> str: def get_tooltip(self: HTML5Translator, node: Element) -> str:
if self.builder.config.imgmath_add_tooltips: if self.builder.config.imgmath_add_tooltips:
return ' alt="%s"' % self.encode(node.astext()).strip() return f' alt="{self.encode(node.astext()).strip()}"'
return '' return ''
@ -322,16 +341,18 @@ def html_visit_math(self: HTML5Translator, node: nodes.math) -> None:
rendered_path, depth = render_math(self, '$' + node.astext() + '$') rendered_path, depth = render_math(self, '$' + node.astext() + '$')
except MathExtError as exc: except MathExtError as exc:
msg = str(exc) msg = str(exc)
sm = nodes.system_message(msg, type='WARNING', level=2, sm = nodes.system_message(
backrefs=[], source=node.astext()) msg, type='WARNING', level=2, backrefs=[], source=node.astext()
)
sm.walkabout(self) sm.walkabout(self)
logger.warning(__('display latex %r: %s'), node.astext(), msg) logger.warning(__('display latex %r: %s'), node.astext(), msg)
raise nodes.SkipNode from exc raise nodes.SkipNode from exc
if rendered_path is None: if rendered_path is None:
# something failed -- use text-only as a bad substitute # something failed -- use text-only as a bad substitute
self.body.append('<span class="math">%s</span>' % self.body.append(
self.encode(node.astext()).strip()) f'<span class="math">{self.encode(node.astext()).strip()}</span>'
)
else: else:
if self.builder.config.imgmath_embed: if self.builder.config.imgmath_embed:
image_format = self.builder.config.imgmath_image_format.lower() image_format = self.builder.config.imgmath_image_format.lower()
@ -340,10 +361,10 @@ def html_visit_math(self: HTML5Translator, node: nodes.math) -> None:
bname = os.path.basename(rendered_path) bname = os.path.basename(rendered_path)
relative_path = os.path.join(self.builder.imgpath, 'math', bname) relative_path = os.path.join(self.builder.imgpath, 'math', bname)
img_src = relative_path.replace(os.path.sep, '/') img_src = relative_path.replace(os.path.sep, '/')
c = f'<img class="math" src="{img_src}"' + get_tooltip(self, node) align = f' style="vertical-align: {-depth:d}px"' if depth is not None else ''
if depth is not None: self.body.append(
c += f' style="vertical-align: {-depth:d}px"' f'<img class="math" src="{img_src}"{get_tooltip(self, node)}{align}/>'
self.body.append(c + '/>') )
raise nodes.SkipNode raise nodes.SkipNode
@ -356,8 +377,9 @@ def html_visit_displaymath(self: HTML5Translator, node: nodes.math_block) -> Non
rendered_path, depth = render_math(self, latex) rendered_path, depth = render_math(self, latex)
except MathExtError as exc: except MathExtError as exc:
msg = str(exc) msg = str(exc)
sm = nodes.system_message(msg, type='WARNING', level=2, sm = nodes.system_message(
backrefs=[], source=node.astext()) msg, type='WARNING', level=2, backrefs=[], source=node.astext()
)
sm.walkabout(self) sm.walkabout(self)
logger.warning(__('inline latex %r: %s'), node.astext(), msg) logger.warning(__('inline latex %r: %s'), node.astext(), msg)
raise nodes.SkipNode from exc raise nodes.SkipNode from exc
@ -371,8 +393,9 @@ def html_visit_displaymath(self: HTML5Translator, node: nodes.math_block) -> Non
if rendered_path is None: if rendered_path is None:
# something failed -- use text-only as a bad substitute # something failed -- use text-only as a bad substitute
self.body.append('<span class="math">%s</span></p>\n</div>' % self.body.append(
self.encode(node.astext()).strip()) f'<span class="math">{self.encode(node.astext()).strip()}</span></p>\n</div>'
)
else: else:
if self.builder.config.imgmath_embed: if self.builder.config.imgmath_embed:
image_format = self.builder.config.imgmath_image_format.lower() image_format = self.builder.config.imgmath_image_format.lower()
@ -381,24 +404,27 @@ def html_visit_displaymath(self: HTML5Translator, node: nodes.math_block) -> Non
bname = os.path.basename(rendered_path) bname = os.path.basename(rendered_path)
relative_path = os.path.join(self.builder.imgpath, 'math', bname) relative_path = os.path.join(self.builder.imgpath, 'math', bname)
img_src = relative_path.replace(os.path.sep, '/') img_src = relative_path.replace(os.path.sep, '/')
self.body.append(f'<img src="{img_src}"' + get_tooltip(self, node) + self.body.append(f'<img src="{img_src}"{get_tooltip(self, node)}/></p>\n</div>')
'/></p>\n</div>')
raise nodes.SkipNode raise nodes.SkipNode
def setup(app: Sphinx) -> ExtensionMetadata: def setup(app: Sphinx) -> ExtensionMetadata:
app.add_html_math_renderer('imgmath', app.add_html_math_renderer(
(html_visit_math, None), 'imgmath',
(html_visit_displaymath, None)) inline_renderers=(html_visit_math, None),
block_renderers=(html_visit_displaymath, None),
)
app.add_config_value('imgmath_image_format', 'png', 'html') app.add_config_value('imgmath_image_format', 'png', 'html')
app.add_config_value('imgmath_dvipng', 'dvipng', 'html') app.add_config_value('imgmath_dvipng', 'dvipng', 'html')
app.add_config_value('imgmath_dvisvgm', 'dvisvgm', 'html') app.add_config_value('imgmath_dvisvgm', 'dvisvgm', 'html')
app.add_config_value('imgmath_latex', 'latex', 'html') app.add_config_value('imgmath_latex', 'latex', 'html')
app.add_config_value('imgmath_use_preview', False, 'html') app.add_config_value('imgmath_use_preview', False, 'html')
app.add_config_value('imgmath_dvipng_args', app.add_config_value(
['-gamma', '1.5', '-D', '110', '-bg', 'Transparent'], 'imgmath_dvipng_args',
'html') ['-gamma', '1.5', '-D', '110', '-bg', 'Transparent'],
'html',
)
app.add_config_value('imgmath_dvisvgm_args', ['--no-fonts'], 'html') app.add_config_value('imgmath_dvisvgm_args', ['--no-fonts'], 'html')
app.add_config_value('imgmath_latex_args', [], 'html') app.add_config_value('imgmath_latex_args', [], 'html')
app.add_config_value('imgmath_latex_preamble', '', 'html') app.add_config_value('imgmath_latex_preamble', '', 'html')