Sphinx 8.1.3__py3-none-any.whl → 8.2.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of Sphinx might be problematic. Click here for more details.
- sphinx/__init__.py +8 -4
- sphinx/__main__.py +2 -0
- sphinx/_cli/__init__.py +2 -5
- sphinx/_cli/util/colour.py +34 -11
- sphinx/_cli/util/errors.py +128 -61
- sphinx/addnodes.py +51 -35
- sphinx/application.py +362 -230
- sphinx/builders/__init__.py +87 -64
- sphinx/builders/_epub_base.py +65 -56
- sphinx/builders/changes.py +17 -23
- sphinx/builders/dirhtml.py +8 -13
- sphinx/builders/epub3.py +70 -38
- sphinx/builders/gettext.py +93 -73
- sphinx/builders/html/__init__.py +240 -186
- sphinx/builders/html/_assets.py +9 -2
- sphinx/builders/html/_build_info.py +3 -0
- sphinx/builders/latex/__init__.py +64 -54
- sphinx/builders/latex/constants.py +14 -11
- sphinx/builders/latex/nodes.py +2 -0
- sphinx/builders/latex/theming.py +8 -9
- sphinx/builders/latex/transforms.py +7 -5
- sphinx/builders/linkcheck.py +193 -149
- sphinx/builders/manpage.py +17 -17
- sphinx/builders/singlehtml.py +28 -16
- sphinx/builders/texinfo.py +28 -21
- sphinx/builders/text.py +10 -15
- sphinx/builders/xml.py +10 -19
- sphinx/cmd/build.py +49 -119
- sphinx/cmd/make_mode.py +35 -31
- sphinx/cmd/quickstart.py +78 -62
- sphinx/config.py +265 -163
- sphinx/directives/__init__.py +51 -54
- sphinx/directives/admonitions.py +107 -0
- sphinx/directives/code.py +24 -19
- sphinx/directives/other.py +21 -42
- sphinx/directives/patches.py +28 -16
- sphinx/domains/__init__.py +54 -31
- sphinx/domains/_domains_container.py +22 -17
- sphinx/domains/_index.py +5 -8
- sphinx/domains/c/__init__.py +366 -245
- sphinx/domains/c/_ast.py +378 -256
- sphinx/domains/c/_ids.py +89 -31
- sphinx/domains/c/_parser.py +283 -214
- sphinx/domains/c/_symbol.py +269 -198
- sphinx/domains/changeset.py +39 -24
- sphinx/domains/citation.py +54 -24
- sphinx/domains/cpp/__init__.py +517 -362
- sphinx/domains/cpp/_ast.py +999 -682
- sphinx/domains/cpp/_ids.py +133 -65
- sphinx/domains/cpp/_parser.py +746 -588
- sphinx/domains/cpp/_symbol.py +692 -489
- sphinx/domains/index.py +10 -8
- sphinx/domains/javascript.py +152 -74
- sphinx/domains/math.py +48 -40
- sphinx/domains/python/__init__.py +402 -211
- sphinx/domains/python/_annotations.py +114 -57
- sphinx/domains/python/_object.py +151 -67
- sphinx/domains/rst.py +94 -49
- sphinx/domains/std/__init__.py +510 -249
- sphinx/environment/__init__.py +345 -61
- sphinx/environment/adapters/asset.py +7 -1
- sphinx/environment/adapters/indexentries.py +15 -20
- sphinx/environment/adapters/toctree.py +19 -9
- sphinx/environment/collectors/__init__.py +3 -1
- sphinx/environment/collectors/asset.py +18 -15
- sphinx/environment/collectors/dependencies.py +8 -10
- sphinx/environment/collectors/metadata.py +6 -4
- sphinx/environment/collectors/title.py +3 -1
- sphinx/environment/collectors/toctree.py +4 -4
- sphinx/errors.py +1 -3
- sphinx/events.py +4 -4
- sphinx/ext/apidoc/__init__.py +21 -0
- sphinx/ext/apidoc/__main__.py +9 -0
- sphinx/ext/apidoc/_cli.py +356 -0
- sphinx/ext/apidoc/_generate.py +356 -0
- sphinx/ext/apidoc/_shared.py +66 -0
- sphinx/ext/autodoc/__init__.py +829 -480
- sphinx/ext/autodoc/directive.py +57 -21
- sphinx/ext/autodoc/importer.py +184 -67
- sphinx/ext/autodoc/mock.py +25 -10
- sphinx/ext/autodoc/preserve_defaults.py +17 -9
- sphinx/ext/autodoc/type_comment.py +56 -29
- sphinx/ext/autodoc/typehints.py +49 -26
- sphinx/ext/autosectionlabel.py +28 -11
- sphinx/ext/autosummary/__init__.py +271 -143
- sphinx/ext/autosummary/generate.py +121 -51
- sphinx/ext/coverage.py +152 -91
- sphinx/ext/doctest.py +169 -101
- sphinx/ext/duration.py +12 -6
- sphinx/ext/extlinks.py +33 -21
- sphinx/ext/githubpages.py +8 -8
- sphinx/ext/graphviz.py +175 -109
- sphinx/ext/ifconfig.py +11 -6
- sphinx/ext/imgconverter.py +48 -25
- sphinx/ext/imgmath.py +127 -97
- sphinx/ext/inheritance_diagram.py +177 -103
- sphinx/ext/intersphinx/__init__.py +22 -13
- sphinx/ext/intersphinx/__main__.py +3 -1
- sphinx/ext/intersphinx/_cli.py +18 -14
- sphinx/ext/intersphinx/_load.py +91 -82
- sphinx/ext/intersphinx/_resolve.py +108 -74
- sphinx/ext/intersphinx/_shared.py +2 -2
- sphinx/ext/linkcode.py +28 -12
- sphinx/ext/mathjax.py +60 -29
- sphinx/ext/napoleon/__init__.py +19 -7
- sphinx/ext/napoleon/docstring.py +229 -231
- sphinx/ext/todo.py +44 -49
- sphinx/ext/viewcode.py +105 -57
- sphinx/extension.py +3 -1
- sphinx/highlighting.py +13 -7
- sphinx/io.py +9 -13
- sphinx/jinja2glue.py +29 -26
- sphinx/locale/__init__.py +8 -9
- sphinx/parsers.py +8 -7
- sphinx/project.py +2 -2
- sphinx/pycode/__init__.py +31 -21
- sphinx/pycode/ast.py +6 -3
- sphinx/pycode/parser.py +14 -8
- sphinx/pygments_styles.py +4 -5
- sphinx/registry.py +192 -92
- sphinx/roles.py +58 -7
- sphinx/search/__init__.py +75 -54
- sphinx/search/en.py +11 -13
- sphinx/search/fi.py +1 -1
- sphinx/search/ja.py +8 -6
- sphinx/search/nl.py +1 -1
- sphinx/search/zh.py +19 -21
- sphinx/testing/fixtures.py +26 -29
- sphinx/testing/path.py +26 -62
- sphinx/testing/restructuredtext.py +14 -8
- sphinx/testing/util.py +21 -19
- sphinx/texinputs/make.bat.jinja +50 -50
- sphinx/texinputs/sphinx.sty +4 -3
- sphinx/texinputs/sphinxlatexadmonitions.sty +1 -1
- sphinx/texinputs/sphinxlatexobjects.sty +29 -10
- sphinx/themes/basic/static/searchtools.js +8 -5
- sphinx/theming.py +49 -61
- sphinx/transforms/__init__.py +17 -38
- sphinx/transforms/compact_bullet_list.py +5 -3
- sphinx/transforms/i18n.py +8 -21
- sphinx/transforms/post_transforms/__init__.py +142 -93
- sphinx/transforms/post_transforms/code.py +5 -5
- sphinx/transforms/post_transforms/images.py +28 -24
- sphinx/transforms/references.py +3 -1
- sphinx/util/__init__.py +109 -60
- sphinx/util/_files.py +39 -23
- sphinx/util/_importer.py +4 -1
- sphinx/util/_inventory_file_reader.py +76 -0
- sphinx/util/_io.py +2 -2
- sphinx/util/_lines.py +6 -3
- sphinx/util/_pathlib.py +40 -2
- sphinx/util/build_phase.py +2 -0
- sphinx/util/cfamily.py +19 -14
- sphinx/util/console.py +44 -179
- sphinx/util/display.py +9 -10
- sphinx/util/docfields.py +140 -122
- sphinx/util/docstrings.py +1 -1
- sphinx/util/docutils.py +118 -77
- sphinx/util/fileutil.py +25 -26
- sphinx/util/http_date.py +2 -0
- sphinx/util/i18n.py +77 -64
- sphinx/util/images.py +8 -6
- sphinx/util/inspect.py +147 -38
- sphinx/util/inventory.py +215 -116
- sphinx/util/logging.py +33 -33
- sphinx/util/matching.py +12 -4
- sphinx/util/nodes.py +18 -13
- sphinx/util/osutil.py +38 -39
- sphinx/util/parallel.py +22 -13
- sphinx/util/parsing.py +2 -1
- sphinx/util/png.py +6 -2
- sphinx/util/requests.py +33 -2
- sphinx/util/rst.py +3 -2
- sphinx/util/tags.py +1 -1
- sphinx/util/template.py +18 -10
- sphinx/util/texescape.py +8 -6
- sphinx/util/typing.py +148 -122
- sphinx/versioning.py +3 -3
- sphinx/writers/html.py +3 -1
- sphinx/writers/html5.py +61 -50
- sphinx/writers/latex.py +80 -65
- sphinx/writers/manpage.py +19 -38
- sphinx/writers/texinfo.py +44 -45
- sphinx/writers/text.py +48 -30
- sphinx/writers/xml.py +11 -8
- {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/LICENSE.rst +1 -1
- {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/METADATA +23 -15
- {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/RECORD +190 -186
- {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/WHEEL +1 -1
- sphinx/builders/html/transforms.py +0 -90
- sphinx/ext/apidoc.py +0 -721
- sphinx/util/exceptions.py +0 -74
- {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/entry_points.txt +0 -0
sphinx/ext/imgconverter.py
CHANGED
|
@@ -14,6 +14,8 @@ from sphinx.transforms.post_transforms.images import ImageConverter
|
|
|
14
14
|
from sphinx.util import logging
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
|
+
import os
|
|
18
|
+
|
|
17
19
|
from sphinx.application import Sphinx
|
|
18
20
|
from sphinx.util.typing import ExtensionMetadata
|
|
19
21
|
|
|
@@ -37,42 +39,57 @@ class ImagemagickConverter(ImageConverter):
|
|
|
37
39
|
subprocess.run(args, capture_output=True, check=True)
|
|
38
40
|
return True
|
|
39
41
|
except OSError as exc:
|
|
40
|
-
logger.warning(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
logger.warning(
|
|
43
|
+
__(
|
|
44
|
+
'Unable to run the image conversion command %r. '
|
|
45
|
+
"'sphinx.ext.imgconverter' requires ImageMagick by default. "
|
|
46
|
+
"Ensure it is installed, or set the 'image_converter' option "
|
|
47
|
+
'to a custom conversion command.\n\n'
|
|
48
|
+
'Traceback: %s',
|
|
49
|
+
),
|
|
50
|
+
self.config.image_converter,
|
|
51
|
+
exc,
|
|
52
|
+
)
|
|
47
53
|
return False
|
|
48
54
|
except CalledProcessError as exc:
|
|
49
|
-
logger.warning(
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
logger.warning(
|
|
56
|
+
__('convert exited with error:\n[stderr]\n%r\n[stdout]\n%r'),
|
|
57
|
+
exc.stderr,
|
|
58
|
+
exc.stdout,
|
|
59
|
+
)
|
|
52
60
|
return False
|
|
53
61
|
|
|
54
|
-
def convert(
|
|
62
|
+
def convert(
|
|
63
|
+
self, _from: str | os.PathLike[str], _to: str | os.PathLike[str]
|
|
64
|
+
) -> bool:
|
|
55
65
|
"""Converts the image to expected one."""
|
|
56
66
|
try:
|
|
57
67
|
# append an index 0 to source filename to pick up the first frame
|
|
58
68
|
# (or first page) of image (ex. Animation GIF, PDF)
|
|
59
|
-
|
|
69
|
+
from_ = f'{_from}[0]'
|
|
60
70
|
|
|
61
|
-
args =
|
|
62
|
-
self.config.image_converter,
|
|
63
|
-
|
|
71
|
+
args = [
|
|
72
|
+
self.config.image_converter,
|
|
73
|
+
*self.config.image_converter_args,
|
|
74
|
+
from_,
|
|
75
|
+
_to,
|
|
76
|
+
]
|
|
64
77
|
logger.debug('Invoking %r ...', args)
|
|
65
78
|
subprocess.run(args, capture_output=True, check=True)
|
|
66
79
|
return True
|
|
67
80
|
except OSError:
|
|
68
|
-
logger.warning(
|
|
69
|
-
|
|
70
|
-
|
|
81
|
+
logger.warning(
|
|
82
|
+
__(
|
|
83
|
+
'convert command %r cannot be run, check the image_converter setting'
|
|
84
|
+
),
|
|
85
|
+
self.config.image_converter,
|
|
86
|
+
)
|
|
71
87
|
return False
|
|
72
88
|
except CalledProcessError as exc:
|
|
73
|
-
raise ExtensionError(
|
|
74
|
-
|
|
75
|
-
|
|
89
|
+
raise ExtensionError(
|
|
90
|
+
__('convert exited with error:\n[stderr]\n%r\n[stdout]\n%r')
|
|
91
|
+
% (exc.stderr, exc.stdout)
|
|
92
|
+
) from exc
|
|
76
93
|
|
|
77
94
|
|
|
78
95
|
def setup(app: Sphinx) -> ExtensionMetadata:
|
|
@@ -80,14 +97,20 @@ def setup(app: Sphinx) -> ExtensionMetadata:
|
|
|
80
97
|
if sys.platform == 'win32':
|
|
81
98
|
# On Windows, we use Imagemagik v7 by default to avoid the trouble for
|
|
82
99
|
# convert.exe bundled with Windows.
|
|
83
|
-
app.add_config_value('image_converter', 'magick', 'env')
|
|
84
|
-
app.add_config_value(
|
|
100
|
+
app.add_config_value('image_converter', 'magick', 'env', types=frozenset({str}))
|
|
101
|
+
app.add_config_value(
|
|
102
|
+
'image_converter_args', ['convert'], 'env', types=frozenset({list, tuple})
|
|
103
|
+
)
|
|
85
104
|
else:
|
|
86
105
|
# On other platform, we use Imagemagick v6 by default. Especially,
|
|
87
106
|
# Debian/Ubuntu are still based of v6. So we can't use "magick" command
|
|
88
107
|
# for these platforms.
|
|
89
|
-
app.add_config_value(
|
|
90
|
-
|
|
108
|
+
app.add_config_value(
|
|
109
|
+
'image_converter', 'convert', 'env', types=frozenset({str})
|
|
110
|
+
)
|
|
111
|
+
app.add_config_value(
|
|
112
|
+
'image_converter_args', [], 'env', types=frozenset({list, tuple})
|
|
113
|
+
)
|
|
91
114
|
|
|
92
115
|
return {
|
|
93
116
|
'version': sphinx.__display_version__,
|
sphinx/ext/imgmath.py
CHANGED
|
@@ -6,12 +6,14 @@ __all__ = ()
|
|
|
6
6
|
|
|
7
7
|
import base64
|
|
8
8
|
import contextlib
|
|
9
|
+
import os
|
|
10
|
+
import os.path
|
|
9
11
|
import re
|
|
10
12
|
import shutil
|
|
11
13
|
import subprocess
|
|
12
14
|
import tempfile
|
|
13
15
|
from hashlib import sha1
|
|
14
|
-
from
|
|
16
|
+
from pathlib import Path
|
|
15
17
|
from subprocess import CalledProcessError
|
|
16
18
|
from typing import TYPE_CHECKING
|
|
17
19
|
|
|
@@ -23,31 +25,29 @@ from sphinx.errors import SphinxError
|
|
|
23
25
|
from sphinx.locale import _, __
|
|
24
26
|
from sphinx.util import logging
|
|
25
27
|
from sphinx.util.math import get_node_equation_number, wrap_displaymath
|
|
26
|
-
from sphinx.util.osutil import ensuredir
|
|
27
28
|
from sphinx.util.png import read_png_depth, write_png_depth
|
|
28
29
|
from sphinx.util.template import LaTeXRenderer
|
|
29
30
|
|
|
30
31
|
if TYPE_CHECKING:
|
|
31
|
-
import os
|
|
32
|
-
|
|
33
32
|
from docutils.nodes import Element
|
|
34
33
|
|
|
35
34
|
from sphinx.application import Sphinx
|
|
36
35
|
from sphinx.builders import Builder
|
|
37
36
|
from sphinx.config import Config
|
|
37
|
+
from sphinx.util._pathlib import _StrPath
|
|
38
38
|
from sphinx.util.typing import ExtensionMetadata
|
|
39
39
|
from sphinx.writers.html5 import HTML5Translator
|
|
40
40
|
|
|
41
41
|
logger = logging.getLogger(__name__)
|
|
42
42
|
|
|
43
|
-
templates_path =
|
|
43
|
+
templates_path = package_dir.joinpath('templates', 'imgmath')
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class MathExtError(SphinxError):
|
|
47
47
|
category = 'Math extension error'
|
|
48
48
|
|
|
49
49
|
def __init__(
|
|
50
|
-
self, msg: str, stderr: str | None = None, stdout: str | None = None
|
|
50
|
+
self, msg: str, stderr: str | None = None, stdout: str | None = None
|
|
51
51
|
) -> None:
|
|
52
52
|
if stderr:
|
|
53
53
|
msg += '\n[stderr]\n' + stderr
|
|
@@ -67,10 +67,9 @@ depthsvg_re = re.compile(r'.*, depth=(.*)pt')
|
|
|
67
67
|
depthsvgcomment_re = re.compile(r'<!-- DEPTH=(-?\d+) -->')
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
def read_svg_depth(filename: str) -> int | None:
|
|
71
|
-
"""Read the depth from comment at last line of SVG file
|
|
72
|
-
|
|
73
|
-
with open(filename, encoding="utf-8") as f:
|
|
70
|
+
def read_svg_depth(filename: str | os.PathLike[str]) -> int | None:
|
|
71
|
+
"""Read the depth from comment at last line of SVG file"""
|
|
72
|
+
with open(filename, encoding='utf-8') as f:
|
|
74
73
|
for line in f: # NoQA: B007
|
|
75
74
|
pass
|
|
76
75
|
# Only last line is checked
|
|
@@ -80,23 +79,24 @@ def read_svg_depth(filename: str) -> int | None:
|
|
|
80
79
|
return None
|
|
81
80
|
|
|
82
81
|
|
|
83
|
-
def write_svg_depth(filename:
|
|
84
|
-
"""Write the depth to SVG file as a comment at end of file
|
|
85
|
-
|
|
86
|
-
with open(filename, 'a', encoding="utf-8") as f:
|
|
82
|
+
def write_svg_depth(filename: Path, depth: int) -> None:
|
|
83
|
+
"""Write the depth to SVG file as a comment at end of file"""
|
|
84
|
+
with open(filename, 'a', encoding='utf-8') as f:
|
|
87
85
|
f.write('\n<!-- DEPTH=%s -->' % depth)
|
|
88
86
|
|
|
89
87
|
|
|
90
|
-
def generate_latex_macro(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
def generate_latex_macro(
|
|
89
|
+
image_format: str,
|
|
90
|
+
math: str,
|
|
91
|
+
config: Config,
|
|
92
|
+
confdir: _StrPath,
|
|
93
|
+
) -> str:
|
|
94
94
|
"""Generate LaTeX macro."""
|
|
95
95
|
variables = {
|
|
96
96
|
'fontsize': config.imgmath_font_size,
|
|
97
|
-
'baselineskip':
|
|
97
|
+
'baselineskip': round(config.imgmath_font_size * 1.2),
|
|
98
98
|
'preamble': config.imgmath_latex_preamble,
|
|
99
|
-
# the dvips option is important when imgmath_latex in
|
|
99
|
+
# the dvips option is important when imgmath_latex in {"xelatex", "tectonic"},
|
|
100
100
|
# it has no impact when imgmath_latex="latex"
|
|
101
101
|
'tightpage': '' if image_format == 'png' else ',dvips,tightpage',
|
|
102
102
|
'math': math,
|
|
@@ -109,14 +109,14 @@ def generate_latex_macro(image_format: str,
|
|
|
109
109
|
|
|
110
110
|
for template_dir in config.templates_path:
|
|
111
111
|
for template_suffix in ('.jinja', '_t'):
|
|
112
|
-
template =
|
|
113
|
-
if
|
|
112
|
+
template = confdir / template_dir / (template_name + template_suffix)
|
|
113
|
+
if template.exists():
|
|
114
114
|
return LaTeXRenderer().render(template, variables)
|
|
115
115
|
|
|
116
|
-
return LaTeXRenderer(templates_path).render(template_name + '.jinja', variables)
|
|
116
|
+
return LaTeXRenderer([templates_path]).render(template_name + '.jinja', variables)
|
|
117
117
|
|
|
118
118
|
|
|
119
|
-
def ensure_tempdir(builder: Builder) ->
|
|
119
|
+
def ensure_tempdir(builder: Builder) -> Path:
|
|
120
120
|
"""Create temporary directory.
|
|
121
121
|
|
|
122
122
|
use only one tempdir per build -- the use of a directory is cleaner
|
|
@@ -124,19 +124,19 @@ def ensure_tempdir(builder: Builder) -> str:
|
|
|
124
124
|
just removing the whole directory (see cleanup_tempdir)
|
|
125
125
|
"""
|
|
126
126
|
if not hasattr(builder, '_imgmath_tempdir'):
|
|
127
|
-
builder._imgmath_tempdir = tempfile.mkdtemp() # type: ignore[attr-defined]
|
|
127
|
+
builder._imgmath_tempdir = Path(tempfile.mkdtemp()) # type: ignore[attr-defined]
|
|
128
128
|
|
|
129
129
|
return builder._imgmath_tempdir # type: ignore[attr-defined]
|
|
130
130
|
|
|
131
131
|
|
|
132
|
-
def compile_math(latex: str, builder: Builder) ->
|
|
132
|
+
def compile_math(latex: str, builder: Builder) -> Path:
|
|
133
133
|
"""Compile LaTeX macros for math to DVI."""
|
|
134
134
|
tempdir = ensure_tempdir(builder)
|
|
135
|
-
filename =
|
|
135
|
+
filename = tempdir / 'math.tex'
|
|
136
136
|
with open(filename, 'w', encoding='utf-8') as f:
|
|
137
137
|
f.write(latex)
|
|
138
138
|
|
|
139
|
-
imgmath_latex_name = path.basename(builder.config.imgmath_latex)
|
|
139
|
+
imgmath_latex_name = os.path.basename(builder.config.imgmath_latex)
|
|
140
140
|
|
|
141
141
|
# build latex command; old versions of latex don't have the
|
|
142
142
|
# --output-directory option, so we have to manually chdir to the
|
|
@@ -149,16 +149,21 @@ def compile_math(latex: str, builder: Builder) -> str:
|
|
|
149
149
|
command.append('math.tex')
|
|
150
150
|
|
|
151
151
|
try:
|
|
152
|
-
subprocess.run(
|
|
153
|
-
|
|
152
|
+
subprocess.run(
|
|
153
|
+
command, capture_output=True, cwd=tempdir, check=True, encoding='ascii'
|
|
154
|
+
)
|
|
154
155
|
if imgmath_latex_name in {'xelatex', 'tectonic'}:
|
|
155
|
-
return
|
|
156
|
+
return tempdir / 'math.xdv'
|
|
156
157
|
else:
|
|
157
|
-
return
|
|
158
|
+
return tempdir / 'math.dvi'
|
|
158
159
|
except OSError as exc:
|
|
159
|
-
logger.warning(
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
logger.warning(
|
|
161
|
+
__(
|
|
162
|
+
'LaTeX command %r cannot be run (needed for math '
|
|
163
|
+
'display), check the imgmath_latex setting'
|
|
164
|
+
),
|
|
165
|
+
builder.config.imgmath_latex,
|
|
166
|
+
)
|
|
162
167
|
raise InvokeError from exc
|
|
163
168
|
except CalledProcessError as exc:
|
|
164
169
|
msg = 'latex exited with error'
|
|
@@ -171,15 +176,22 @@ def convert_dvi_to_image(command: list[str], name: str) -> tuple[str, str]:
|
|
|
171
176
|
ret = subprocess.run(command, capture_output=True, check=True, encoding='ascii')
|
|
172
177
|
return ret.stdout, ret.stderr
|
|
173
178
|
except OSError as exc:
|
|
174
|
-
logger.warning(
|
|
175
|
-
|
|
176
|
-
|
|
179
|
+
logger.warning(
|
|
180
|
+
__(
|
|
181
|
+
'%s command %r cannot be run (needed for math '
|
|
182
|
+
'display), check the imgmath_%s setting'
|
|
183
|
+
),
|
|
184
|
+
name,
|
|
185
|
+
command[0],
|
|
186
|
+
name,
|
|
187
|
+
)
|
|
177
188
|
raise InvokeError from exc
|
|
178
189
|
except CalledProcessError as exc:
|
|
179
|
-
|
|
190
|
+
msg = f'{name} exited with error'
|
|
191
|
+
raise MathExtError(msg, exc.stderr, exc.stdout) from exc
|
|
180
192
|
|
|
181
193
|
|
|
182
|
-
def convert_dvi_to_png(dvipath:
|
|
194
|
+
def convert_dvi_to_png(dvipath: Path, builder: Builder, out_path: Path) -> int | None:
|
|
183
195
|
"""Convert DVI file to PNG image."""
|
|
184
196
|
name = 'dvipng'
|
|
185
197
|
command = [builder.config.imgmath_dvipng, '-o', out_path, '-T', 'tight', '-z9']
|
|
@@ -202,7 +214,7 @@ def convert_dvi_to_png(dvipath: str, builder: Builder, out_path: str) -> int | N
|
|
|
202
214
|
return depth
|
|
203
215
|
|
|
204
216
|
|
|
205
|
-
def convert_dvi_to_svg(dvipath:
|
|
217
|
+
def convert_dvi_to_svg(dvipath: Path, builder: Builder, out_path: Path) -> int | None:
|
|
206
218
|
"""Convert DVI file to SVG image."""
|
|
207
219
|
name = 'dvisvgm'
|
|
208
220
|
command = [builder.config.imgmath_dvisvgm, '-o', out_path]
|
|
@@ -226,7 +238,7 @@ def convert_dvi_to_svg(dvipath: str, builder: Builder, out_path: str) -> int | N
|
|
|
226
238
|
def render_math(
|
|
227
239
|
self: HTML5Translator,
|
|
228
240
|
math: str,
|
|
229
|
-
) -> tuple[
|
|
241
|
+
) -> tuple[_StrPath | None, int | None]:
|
|
230
242
|
"""Render the LaTeX math expression *math* using latex and dvipng or
|
|
231
243
|
dvisvgm.
|
|
232
244
|
|
|
@@ -245,15 +257,16 @@ def render_math(
|
|
|
245
257
|
unsupported_format_msg = 'imgmath_image_format must be either "png" or "svg"'
|
|
246
258
|
raise MathExtError(unsupported_format_msg)
|
|
247
259
|
|
|
248
|
-
latex = generate_latex_macro(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
self.builder.confdir)
|
|
260
|
+
latex = generate_latex_macro(
|
|
261
|
+
image_format, math, self.builder.config, self.builder.confdir
|
|
262
|
+
)
|
|
252
263
|
|
|
253
|
-
filename =
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
264
|
+
filename = (
|
|
265
|
+
f'{sha1(latex.encode(), usedforsecurity=False).hexdigest()}.{image_format}'
|
|
266
|
+
)
|
|
267
|
+
generated_path = self.builder.outdir / self.builder.imagedir / 'math' / filename
|
|
268
|
+
generated_path.parent.mkdir(parents=True, exist_ok=True)
|
|
269
|
+
if generated_path.is_file():
|
|
257
270
|
if image_format == 'png':
|
|
258
271
|
depth = read_png_depth(generated_path)
|
|
259
272
|
elif image_format == 'svg':
|
|
@@ -261,8 +274,9 @@ def render_math(
|
|
|
261
274
|
return generated_path, depth
|
|
262
275
|
|
|
263
276
|
# if latex or dvipng (dvisvgm) has failed once, don't bother to try again
|
|
264
|
-
|
|
265
|
-
|
|
277
|
+
latex_failed = hasattr(self.builder, '_imgmath_warned_latex')
|
|
278
|
+
trans_failed = hasattr(self.builder, '_imgmath_warned_image_translator')
|
|
279
|
+
if latex_failed or trans_failed:
|
|
266
280
|
return None, None
|
|
267
281
|
|
|
268
282
|
# .tex -> .dvi
|
|
@@ -285,9 +299,10 @@ def render_math(
|
|
|
285
299
|
return generated_path, depth
|
|
286
300
|
|
|
287
301
|
|
|
288
|
-
def render_maths_to_base64(image_format: str, generated_path:
|
|
289
|
-
with open(generated_path,
|
|
290
|
-
|
|
302
|
+
def render_maths_to_base64(image_format: str, generated_path: Path) -> str:
|
|
303
|
+
with open(generated_path, 'rb') as f:
|
|
304
|
+
content = f.read()
|
|
305
|
+
encoded = base64.b64encode(content).decode(encoding='utf-8')
|
|
291
306
|
if image_format == 'png':
|
|
292
307
|
return f'data:image/png;base64,{encoded}'
|
|
293
308
|
if image_format == 'svg':
|
|
@@ -308,12 +323,12 @@ def clean_up_files(app: Sphinx, exc: Exception) -> None:
|
|
|
308
323
|
# in embed mode, the images are still generated in the math output dir
|
|
309
324
|
# to be shared across workers, but are not useful to the final document
|
|
310
325
|
with contextlib.suppress(Exception):
|
|
311
|
-
shutil.rmtree(
|
|
326
|
+
shutil.rmtree(app.builder.outdir / app.builder.imagedir / 'math')
|
|
312
327
|
|
|
313
328
|
|
|
314
329
|
def get_tooltip(self: HTML5Translator, node: Element) -> str:
|
|
315
330
|
if self.builder.config.imgmath_add_tooltips:
|
|
316
|
-
return ' alt="
|
|
331
|
+
return f' alt="{self.encode(node.astext()).strip()}"'
|
|
317
332
|
return ''
|
|
318
333
|
|
|
319
334
|
|
|
@@ -322,33 +337,35 @@ def html_visit_math(self: HTML5Translator, node: nodes.math) -> None:
|
|
|
322
337
|
rendered_path, depth = render_math(self, '$' + node.astext() + '$')
|
|
323
338
|
except MathExtError as exc:
|
|
324
339
|
msg = str(exc)
|
|
325
|
-
sm = nodes.system_message(
|
|
326
|
-
|
|
340
|
+
sm = nodes.system_message(
|
|
341
|
+
msg, type='WARNING', level=2, backrefs=[], source=node.astext()
|
|
342
|
+
)
|
|
327
343
|
sm.walkabout(self)
|
|
328
344
|
logger.warning(__('display latex %r: %s'), node.astext(), msg)
|
|
329
345
|
raise nodes.SkipNode from exc
|
|
330
346
|
|
|
331
347
|
if rendered_path is None:
|
|
332
348
|
# something failed -- use text-only as a bad substitute
|
|
333
|
-
self.body.append(
|
|
334
|
-
|
|
349
|
+
self.body.append(
|
|
350
|
+
f'<span class="math">{self.encode(node.astext()).strip()}</span>'
|
|
351
|
+
)
|
|
335
352
|
else:
|
|
336
353
|
if self.builder.config.imgmath_embed:
|
|
337
354
|
image_format = self.builder.config.imgmath_image_format.lower()
|
|
338
355
|
img_src = render_maths_to_base64(image_format, rendered_path)
|
|
339
356
|
else:
|
|
340
|
-
bname = path.basename(rendered_path)
|
|
341
|
-
relative_path =
|
|
342
|
-
img_src = relative_path.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
357
|
+
bname = os.path.basename(rendered_path)
|
|
358
|
+
relative_path = Path(self.builder.imgpath, 'math', bname)
|
|
359
|
+
img_src = relative_path.as_posix()
|
|
360
|
+
align = f' style="vertical-align: {-depth:d}px"' if depth is not None else ''
|
|
361
|
+
self.body.append(
|
|
362
|
+
f'<img class="math" src="{img_src}"{get_tooltip(self, node)}{align}/>'
|
|
363
|
+
)
|
|
347
364
|
raise nodes.SkipNode
|
|
348
365
|
|
|
349
366
|
|
|
350
367
|
def html_visit_displaymath(self: HTML5Translator, node: nodes.math_block) -> None:
|
|
351
|
-
if node
|
|
368
|
+
if node.get('no-wrap', node.get('nowrap', False)):
|
|
352
369
|
latex = node.astext()
|
|
353
370
|
else:
|
|
354
371
|
latex = wrap_displaymath(node.astext(), None, False)
|
|
@@ -356,8 +373,9 @@ def html_visit_displaymath(self: HTML5Translator, node: nodes.math_block) -> Non
|
|
|
356
373
|
rendered_path, depth = render_math(self, latex)
|
|
357
374
|
except MathExtError as exc:
|
|
358
375
|
msg = str(exc)
|
|
359
|
-
sm = nodes.system_message(
|
|
360
|
-
|
|
376
|
+
sm = nodes.system_message(
|
|
377
|
+
msg, type='WARNING', level=2, backrefs=[], source=node.astext()
|
|
378
|
+
)
|
|
361
379
|
sm.walkabout(self)
|
|
362
380
|
logger.warning(__('inline latex %r: %s'), node.astext(), msg)
|
|
363
381
|
raise nodes.SkipNode from exc
|
|
@@ -371,39 +389,51 @@ def html_visit_displaymath(self: HTML5Translator, node: nodes.math_block) -> Non
|
|
|
371
389
|
|
|
372
390
|
if rendered_path is None:
|
|
373
391
|
# something failed -- use text-only as a bad substitute
|
|
374
|
-
self.body.append(
|
|
375
|
-
|
|
392
|
+
self.body.append(
|
|
393
|
+
f'<span class="math">{self.encode(node.astext()).strip()}</span></p>\n</div>'
|
|
394
|
+
)
|
|
376
395
|
else:
|
|
377
396
|
if self.builder.config.imgmath_embed:
|
|
378
397
|
image_format = self.builder.config.imgmath_image_format.lower()
|
|
379
398
|
img_src = render_maths_to_base64(image_format, rendered_path)
|
|
380
399
|
else:
|
|
381
|
-
bname = path.basename(rendered_path)
|
|
382
|
-
relative_path =
|
|
383
|
-
img_src = relative_path.
|
|
384
|
-
self.body.append(f'<img src="{img_src}"
|
|
385
|
-
'/></p>\n</div>')
|
|
400
|
+
bname = os.path.basename(rendered_path)
|
|
401
|
+
relative_path = Path(self.builder.imgpath, 'math', bname)
|
|
402
|
+
img_src = relative_path.as_posix()
|
|
403
|
+
self.body.append(f'<img src="{img_src}"{get_tooltip(self, node)}/></p>\n</div>')
|
|
386
404
|
raise nodes.SkipNode
|
|
387
405
|
|
|
388
406
|
|
|
389
407
|
def setup(app: Sphinx) -> ExtensionMetadata:
|
|
390
|
-
app.add_html_math_renderer(
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
app.add_config_value('
|
|
397
|
-
app.add_config_value('
|
|
398
|
-
app.add_config_value('
|
|
399
|
-
app.add_config_value('
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
app.add_config_value(
|
|
408
|
+
app.add_html_math_renderer(
|
|
409
|
+
'imgmath',
|
|
410
|
+
inline_renderers=(html_visit_math, None),
|
|
411
|
+
block_renderers=(html_visit_displaymath, None),
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
app.add_config_value('imgmath_image_format', 'png', 'html', types=frozenset({str}))
|
|
415
|
+
app.add_config_value('imgmath_dvipng', 'dvipng', 'html', types=frozenset({str}))
|
|
416
|
+
app.add_config_value('imgmath_dvisvgm', 'dvisvgm', 'html', types=frozenset({str}))
|
|
417
|
+
app.add_config_value('imgmath_latex', 'latex', 'html', types=frozenset({str}))
|
|
418
|
+
app.add_config_value('imgmath_use_preview', False, 'html', types=frozenset({bool}))
|
|
419
|
+
app.add_config_value(
|
|
420
|
+
'imgmath_dvipng_args',
|
|
421
|
+
['-gamma', '1.5', '-D', '110', '-bg', 'Transparent'],
|
|
422
|
+
'html',
|
|
423
|
+
types=frozenset({list}),
|
|
424
|
+
)
|
|
425
|
+
app.add_config_value(
|
|
426
|
+
'imgmath_dvisvgm_args', ['--no-fonts'], 'html', types=frozenset({list, tuple})
|
|
427
|
+
)
|
|
428
|
+
app.add_config_value(
|
|
429
|
+
'imgmath_latex_args', [], 'html', types=frozenset({list, tuple})
|
|
430
|
+
)
|
|
431
|
+
app.add_config_value('imgmath_latex_preamble', '', 'html', types=frozenset({str}))
|
|
432
|
+
app.add_config_value('imgmath_add_tooltips', True, 'html', types=frozenset({bool}))
|
|
433
|
+
app.add_config_value('imgmath_font_size', 12, 'html', types=frozenset({int}))
|
|
434
|
+
app.add_config_value('imgmath_embed', False, 'html', types=frozenset({bool}))
|
|
408
435
|
app.connect('build-finished', clean_up_files)
|
|
409
|
-
return {
|
|
436
|
+
return {
|
|
437
|
+
'version': sphinx.__display_version__,
|
|
438
|
+
'parallel_read_safe': True,
|
|
439
|
+
}
|