Skip to content

Commit 67762d8

Browse files
Add mathjax_config_file to load MathJax configuration from a file (#14025)
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
1 parent 5d0ad16 commit 67762d8

File tree

5 files changed

+82
-15
lines changed

5 files changed

+82
-15
lines changed

CHANGES.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ Features added
6868
Patch by Jean-François B.
6969
* #13508: Initial support for :pep:`695` type aliases.
7070
Patch by Martin Matouš, Jeremy Maitin-Shepard, and Adam Turner.
71+
* #14023: Add the new :confval:`mathjax_config_path` option
72+
to load MathJax configuration from a file.
73+
Patch by Randolf Scholz and Adam Turner.
7174

7275
Bugs fixed
7376
----------

doc/usage/extensions/math.rst

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,8 @@ are built:
197197
:synopsis: Render math using JavaScript via MathJax.
198198

199199
.. warning::
200-
Version 4.0 changes the version of MathJax used to version 3. You may need to
201-
override ``mathjax_path`` to
200+
Sphinx 4.0 changes the version of MathJax used to version 3.
201+
You may need to override :confval:`mathjax_path` to
202202
``https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js?config=TeX-AMS-MML_HTMLorMML``
203203
or update your configuration options for version 3
204204
(see :confval:`mathjax3_config`).
@@ -217,6 +217,13 @@ Sphinx but is set to automatically include it from a third-party site.
217217
You should use the math :rst:dir:`directive <math>` and
218218
:rst:role:`role <math>`, not the native MathJax ``$$``, ``\(``, etc.
219219

220+
.. tip::
221+
222+
MathJax configuration can be supplied in a JavaScript file
223+
by using the :confval:`mathjax_config_path` option.
224+
This is useful for more complex configurations that are hard to express
225+
only using a Python dictionary, for example JavaScript functions.
226+
220227

221228
.. confval:: mathjax_path
222229
:type: :code-py:`str`
@@ -268,8 +275,9 @@ Sphinx but is set to automatically include it from a third-party site.
268275
:default: :code-py:`None`
269276

270277
The configuration options for MathJax v3 (which is used by default).
271-
The given dictionary is assigned to the JavaScript variable
272-
``window.MathJax``.
278+
If given, the dictionary is converted to a JSON object
279+
and assigned to the JavaScript variable ``window.MathJax``.
280+
273281
For more information, please read `Configuring MathJax`__.
274282

275283
__ https://docs.mathjax.org/en/latest/web/configuration.html#configuration
@@ -318,6 +326,28 @@ Sphinx but is set to automatically include it from a third-party site.
318326
This has been renamed to :confval:`mathjax2_config`.
319327
:confval:`mathjax_config` is still supported for backwards compatibility.
320328

329+
.. confval:: mathjax_config_path
330+
:type: :code-py:`str`
331+
:default: :code-py:`''`
332+
333+
If given, this must be the path of a JavaScript (:file:`.js`) file
334+
(path relative to the :term:`configuration directory`)
335+
that contains the configuration options for MathJax.
336+
Example:
337+
338+
.. code-block:: python
339+
340+
mathjax_config_path = 'mathjax-config.js'
341+
342+
.. important:: The user is responsible for ensuring that the given file
343+
is compatible with the version of MathJax being used.
344+
345+
For more information, please read `Configuring MathJax`__.
346+
347+
__ https://docs.mathjax.org/en/latest/web/configuration.html#configuration
348+
349+
.. versionadded:: 8.3
350+
321351
:mod:`sphinxcontrib.jsmath` -- Render math via JavaScript
322352
---------------------------------------------------------
323353

sphinx/ext/mathjax.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,30 +95,43 @@ def install_mathjax(
9595

9696
builder = cast('StandaloneHTMLBuilder', app.builder)
9797
page_has_equations = context.get('has_maths_elements', False)
98+
99+
# Enable mathjax only if equations exists
98100
if app.registry.html_assets_policy == 'always' or page_has_equations:
99-
# Enable mathjax only if equations exists
100101
if app.config.mathjax2_config:
101102
if app.config.mathjax_path == MATHJAX_URL:
102103
logger.warning(
103104
'mathjax_config/mathjax2_config does not work '
104105
'for the current MathJax version, use mathjax3_config instead'
105106
)
106-
body = 'MathJax.Hub.Config(%s)' % json.dumps(app.config.mathjax2_config)
107+
body = f'MathJax.Hub.Config({json.dumps(app.config.mathjax2_config)})'
107108
builder.add_js_file('', type='text/x-mathjax-config', body=body)
109+
108110
if app.config.mathjax3_config:
109-
body = 'window.MathJax = %s' % json.dumps(app.config.mathjax3_config)
111+
body = f'window.MathJax = {json.dumps(app.config.mathjax3_config)}'
112+
builder.add_js_file('', body=body)
113+
114+
if app.config.mathjax_config_path:
115+
config_path = app.confdir / app.config.mathjax_config_path
116+
if not config_path.exists():
117+
msg = f'mathjax_config_path file not found: {config_path}'
118+
raise ExtensionError(msg)
119+
if not config_path.is_file() or config_path.suffix != '.js':
120+
msg = f'mathjax_config_path: expected a .js file, but got {config_path}'
121+
raise ExtensionError(msg)
122+
body = config_path.read_text(encoding='utf-8')
110123
builder.add_js_file('', body=body)
111124

112125
options = {}
113126
if app.config.mathjax_options:
114127
options.update(app.config.mathjax_options)
115128
if 'async' not in options and 'defer' not in options:
116-
if app.config.mathjax3_config:
117-
# Load MathJax v3 via "defer" method
118-
options['defer'] = 'defer'
119-
else:
120-
# Load other MathJax via "async" method
129+
if app.config.mathjax2_config or app.config.mathjax_config:
130+
# Load old MathJax versions via the 'async' method
121131
options['async'] = 'async'
132+
else:
133+
# Load MathJax v3+ via the 'defer' method
134+
options['defer'] = 'defer'
122135
builder.add_js_file(app.config.mathjax_path, **options)
123136

124137

@@ -149,6 +162,7 @@ def setup(app: Sphinx) -> ExtensionMetadata:
149162
app.add_config_value(
150163
'mathjax3_config', None, 'html', types=frozenset({dict, NoneType})
151164
)
165+
app.add_config_value('mathjax_config_path', '', 'html', types=frozenset({str}))
152166
app.connect('html-page-context', install_mathjax)
153167

154168
return {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
window.MathJax = {"extensions": ["tex2jax.js"]}

tests/test_extensions/test_ext_math.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def test_mathjax_options(app: SphinxTestApp) -> None:
124124
content = (app.outdir / 'index.html').read_text(encoding='utf8')
125125
shutil.rmtree(app.outdir)
126126
assert (
127-
'<script async="async" integrity="sha384-0123456789" '
127+
'<script defer="defer" integrity="sha384-0123456789" '
128128
'src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js">'
129129
'</script>'
130130
) in content
@@ -376,6 +376,25 @@ def test_mathjax3_config(app: SphinxTestApp) -> None:
376376
assert '<script>window.MathJax = {"extensions": ["tex2jax.js"]}</script>' in content
377377

378378

379+
@pytest.mark.sphinx(
380+
'html',
381+
testroot='ext-math',
382+
confoverrides={
383+
'extensions': ['sphinx.ext.mathjax'],
384+
'mathjax_config_path': '_static/custom_mathjax_config.js',
385+
},
386+
)
387+
def test_mathjax_config_path_config(app: SphinxTestApp) -> None:
388+
app.build(force_all=True)
389+
390+
content = (app.outdir / 'index.html').read_text(encoding='utf8')
391+
assert MATHJAX_URL in content
392+
assert f'<script defer="defer" src="{MATHJAX_URL}">' in content
393+
assert (
394+
'<script>window.MathJax = {"extensions": ["tex2jax.js"]}\n</script>'
395+
) in content
396+
397+
379398
@pytest.mark.sphinx(
380399
'html',
381400
testroot='ext-math',
@@ -441,7 +460,7 @@ def test_mathjax_path(app: SphinxTestApp) -> None:
441460
app.build(force_all=True)
442461

443462
content = (app.outdir / 'index.html').read_text(encoding='utf8')
444-
assert '<script async="async" src="_static/MathJax.js"></script>' in content
463+
assert '<script defer="defer" src="_static/MathJax.js"></script>' in content
445464

446465

447466
@pytest.mark.sphinx(
@@ -457,7 +476,7 @@ def test_mathjax_path_config(app: SphinxTestApp) -> None:
457476

458477
content = (app.outdir / 'index.html').read_text(encoding='utf8')
459478
assert (
460-
'<script async="async" src="_static/MathJax.js?config=scipy-mathjax"></script>'
479+
'<script defer="defer" src="_static/MathJax.js?config=scipy-mathjax"></script>'
461480
) in content
462481

463482

0 commit comments

Comments
 (0)