Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ Features added
Patch by Jean-François B.
* #13508: Initial support for :pep:`695` type aliases.
Patch by Martin Matouš, Jeremy Maitin-Shepard, and Adam Turner.
* #14023: Add the new :confval:`mathjax_config_path` option
to load MathJax configuration from a file.
Patch by Randolf Scholz and Adam Turner.

Bugs fixed
----------
Expand Down
38 changes: 34 additions & 4 deletions doc/usage/extensions/math.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ are built:
:synopsis: Render math using JavaScript via MathJax.

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

.. tip::

MathJax configuration can be supplied in a JavaScript file
by using the :confval:`mathjax_config_path` option.
This is useful for more complex configurations that are hard to express
only using a Python dictionary, for example JavaScript functions.


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

The configuration options for MathJax v3 (which is used by default).
The given dictionary is assigned to the JavaScript variable
``window.MathJax``.
If given, the dictionary is converted to a JSON object
and assigned to the JavaScript variable ``window.MathJax``.

For more information, please read `Configuring MathJax`__.

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

.. confval:: mathjax_config_path
:type: :code-py:`str`
:default: :code-py:`''`

If given, this must be the path of a JavaScript (:file:`.js`) file
(path relative to the :term:`configuration directory`)
that contains the configuration options for MathJax.
Example:

.. code-block:: python

mathjax_config_path = 'mathjax-config.js'

.. important:: The user is responsible for ensuring that the given file
is compatible with the version of MathJax being used.

For more information, please read `Configuring MathJax`__.

__ https://docs.mathjax.org/en/latest/web/configuration.html#configuration

.. versionadded:: 8.3

:mod:`sphinxcontrib.jsmath` -- Render math via JavaScript
---------------------------------------------------------

Expand Down
30 changes: 22 additions & 8 deletions sphinx/ext/mathjax.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,30 +95,43 @@ def install_mathjax(

builder = cast('StandaloneHTMLBuilder', app.builder)
page_has_equations = context.get('has_maths_elements', False)

# Enable mathjax only if equations exists
if app.registry.html_assets_policy == 'always' or page_has_equations:
# Enable mathjax only if equations exists
if app.config.mathjax2_config:
if app.config.mathjax_path == MATHJAX_URL:
logger.warning(
'mathjax_config/mathjax2_config does not work '
'for the current MathJax version, use mathjax3_config instead'
)
body = 'MathJax.Hub.Config(%s)' % json.dumps(app.config.mathjax2_config)
body = f'MathJax.Hub.Config({json.dumps(app.config.mathjax2_config)})'
builder.add_js_file('', type='text/x-mathjax-config', body=body)

if app.config.mathjax3_config:
body = 'window.MathJax = %s' % json.dumps(app.config.mathjax3_config)
body = f'window.MathJax = {json.dumps(app.config.mathjax3_config)}'
builder.add_js_file('', body=body)

if app.config.mathjax_config_path:
config_path = app.confdir / app.config.mathjax_config_path
if not config_path.exists():
msg = f'mathjax_config_path file not found: {config_path}'
raise ExtensionError(msg)
if not config_path.is_file() or config_path.suffix != '.js':
msg = f'mathjax_config_path: expected a .js file, but got {config_path}'
raise ExtensionError(msg)
body = config_path.read_text(encoding='utf-8')
builder.add_js_file('', body=body)

options = {}
if app.config.mathjax_options:
options.update(app.config.mathjax_options)
if 'async' not in options and 'defer' not in options:
if app.config.mathjax3_config:
# Load MathJax v3 via "defer" method
options['defer'] = 'defer'
else:
# Load other MathJax via "async" method
if app.config.mathjax2_config or app.config.mathjax_config:
# Load old MathJax versions via the 'async' method
options['async'] = 'async'
else:
# Load MathJax v3+ via the 'defer' method
options['defer'] = 'defer'
builder.add_js_file(app.config.mathjax_path, **options)


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

return {
Expand Down
1 change: 1 addition & 0 deletions tests/roots/test-ext-math/_static/custom_mathjax_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
window.MathJax = {"extensions": ["tex2jax.js"]}
25 changes: 22 additions & 3 deletions tests/test_extensions/test_ext_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def test_mathjax_options(app: SphinxTestApp) -> None:
content = (app.outdir / 'index.html').read_text(encoding='utf8')
shutil.rmtree(app.outdir)
assert (
'<script async="async" integrity="sha384-0123456789" '
'<script defer="defer" integrity="sha384-0123456789" '
'src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js">'
'</script>'
) in content
Expand Down Expand Up @@ -376,6 +376,25 @@ def test_mathjax3_config(app: SphinxTestApp) -> None:
assert '<script>window.MathJax = {"extensions": ["tex2jax.js"]}</script>' in content


@pytest.mark.sphinx(
'html',
testroot='ext-math',
confoverrides={
'extensions': ['sphinx.ext.mathjax'],
'mathjax_config_path': '_static/custom_mathjax_config.js',
},
)
def test_mathjax_config_path_config(app: SphinxTestApp) -> None:
app.build(force_all=True)

content = (app.outdir / 'index.html').read_text(encoding='utf8')
assert MATHJAX_URL in content
assert f'<script defer="defer" src="{MATHJAX_URL}">' in content
assert (
'<script>window.MathJax = {"extensions": ["tex2jax.js"]}\n</script>'
) in content


@pytest.mark.sphinx(
'html',
testroot='ext-math',
Expand Down Expand Up @@ -441,7 +460,7 @@ def test_mathjax_path(app: SphinxTestApp) -> None:
app.build(force_all=True)

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


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

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


Expand Down
Loading