From 881f1b1ef77e5630f8e7b53b5cd3d4ed903945fb Mon Sep 17 00:00:00 2001 From: Jean Abou Samra Date: Sat, 29 Nov 2025 19:09:06 +0100 Subject: [PATCH] Support dollar math syntax (fixes #1402, #662, #400) Letting MathJax scrape the output HTML for math delimiters makes it awkward to write complex formulas because characters with a special meaning in Markdown must be escaped. This commit implements a new `output.html.math` option that uses the support for parsing `$...$` and `$$...$$` in Markdown introduced in pulldown-cmark 0.11.0. The old option `output.html.mathjax-support` is left for backwards compatibility. pulldown-cmark renders formulas into `` tags with special classes, so we have to configure MathJax to look for these instead of textual delimiters. The code for this was helpfully provided by David Cervone on . The latest version of MathJax 4 is used rather than MathJax 2.7.1 like the mathjax-support option, because the way to do this configuration has changed between MathJax 2 and MathJax 3, I'm not sure how to do it on MathJax 2, and it is an old version anyway. The Cloudflare CDN doesn't seem to work for MathJax 4, so it uses the jsdelivr.net one recommended by the MathJax documentation. In the future, it would be good to use a bundled version of MathJax by default and/or make the CDN URL configurable. Fixes #1402, fixes #662, fixes #400 --- crates/mdbook-core/src/config.rs | 5 +- .../mdbook-html/front-end/templates/index.hbs | 32 ++++++++++++- crates/mdbook-html/src/html/mod.rs | 1 + .../src/html_handlebars/hbs_renderer.rs | 4 ++ crates/mdbook-markdown/src/lib.rs | 9 ++++ guide/book.toml | 2 +- guide/src/format/configuration/renderers.md | 4 +- guide/src/format/mathjax.md | 48 ++++++++----------- tests/testsuite/markdown.rs | 13 +++++ tests/testsuite/markdown/math/book.toml | 2 + .../markdown/math/expected/math.html | 2 + .../markdown/math/expected_enabled/math.html | 2 + tests/testsuite/markdown/math/src/SUMMARY.md | 3 ++ tests/testsuite/markdown/math/src/math.md | 3 ++ 14 files changed, 95 insertions(+), 35 deletions(-) create mode 100644 tests/testsuite/markdown/math/book.toml create mode 100644 tests/testsuite/markdown/math/expected/math.html create mode 100644 tests/testsuite/markdown/math/expected_enabled/math.html create mode 100644 tests/testsuite/markdown/math/src/SUMMARY.md create mode 100644 tests/testsuite/markdown/math/src/math.md diff --git a/crates/mdbook-core/src/config.rs b/crates/mdbook-core/src/config.rs index b2c6862c30..8cf8d39ee1 100644 --- a/crates/mdbook-core/src/config.rs +++ b/crates/mdbook-core/src/config.rs @@ -460,7 +460,9 @@ pub struct HtmlConfig { pub definition_lists: bool, /// Support for admonitions. pub admonitions: bool, - /// Should mathjax be enabled? + /// Parse and render `$...$` and `$$...$$` as math formulas. + pub math: bool, + /// Legacy math support. pub mathjax_support: bool, /// Additional CSS stylesheets to include in the rendered page's ``. pub additional_css: Vec, @@ -529,6 +531,7 @@ impl Default for HtmlConfig { smart_punctuation: true, definition_lists: true, admonitions: true, + math: false, mathjax_support: false, additional_css: Vec::new(), additional_js: Vec::new(), diff --git a/crates/mdbook-html/front-end/templates/index.hbs b/crates/mdbook-html/front-end/templates/index.hbs index b1834189f9..894c2c0e7b 100644 --- a/crates/mdbook-html/front-end/templates/index.hbs +++ b/crates/mdbook-html/front-end/templates/index.hbs @@ -45,8 +45,36 @@ {{/each}} - {{#if mathjax_support}} - + {{#if math}} + + {{! MathJax normally looks for math tags in the page content. In our case, + the math tags are parsed in the Markdown input by pulldown-cmark and + converted to tags with special CSS classes. This bit of code + tells MathJax to render these and only these. Adapted from + }} + + + {{else if mathjax_support}} + + {{! Legacy MathJax support that just tells MathJax to look for math tags in the page content }} {{/if}} diff --git a/crates/mdbook-html/src/html/mod.rs b/crates/mdbook-html/src/html/mod.rs index 8a70700f7e..164682b028 100644 --- a/crates/mdbook-html/src/html/mod.rs +++ b/crates/mdbook-html/src/html/mod.rs @@ -53,6 +53,7 @@ impl<'a> HtmlRenderOptions<'a> { markdown_options.smart_punctuation = config.smart_punctuation; markdown_options.definition_lists = config.definition_lists; markdown_options.admonitions = config.admonitions; + markdown_options.math = config.math; HtmlRenderOptions { markdown_options, path, diff --git a/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs b/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs index 8edac3cace..172958efb0 100644 --- a/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs +++ b/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs @@ -506,6 +506,10 @@ fn make_data( json!(preferred_dark_theme), ); + if html_config.math { + data.insert("math".to_owned(), json!(true)); + } + if html_config.mathjax_support { data.insert("mathjax_support".to_owned(), json!(true)); } diff --git a/crates/mdbook-markdown/src/lib.rs b/crates/mdbook-markdown/src/lib.rs index 7e2ce176ab..49d8955fe8 100644 --- a/crates/mdbook-markdown/src/lib.rs +++ b/crates/mdbook-markdown/src/lib.rs @@ -28,6 +28,11 @@ pub struct MarkdownOptions { /// /// This is `true` by default. pub admonitions: bool, + /// Enables `$...$` and `$$...$$` syntax for inline and display math, + /// respectively. + /// + /// This is `false` by default. + pub math: bool, } impl Default for MarkdownOptions { @@ -36,6 +41,7 @@ impl Default for MarkdownOptions { smart_punctuation: true, definition_lists: true, admonitions: true, + math: false, } } } @@ -57,5 +63,8 @@ pub fn new_cmark_parser<'text>(text: &'text str, options: &MarkdownOptions) -> P if options.admonitions { opts.insert(Options::ENABLE_GFM); } + if options.math { + opts.insert(Options::ENABLE_MATH); + } Parser::new_ext(text, opts) } diff --git a/guide/book.toml b/guide/book.toml index d8863339a3..c4e5fae054 100644 --- a/guide/book.toml +++ b/guide/book.toml @@ -9,7 +9,7 @@ edition = "2018" [output.html] smart-punctuation = true -mathjax-support = true +math = true site-url = "/mdBook/" git-repository-url = "https://github.com/rust-lang/mdBook/tree/master/guide" edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}" diff --git a/guide/src/format/configuration/renderers.md b/guide/src/format/configuration/renderers.md index 22dfd425fb..799632606b 100644 --- a/guide/src/format/configuration/renderers.md +++ b/guide/src/format/configuration/renderers.md @@ -100,7 +100,7 @@ preferred-dark-theme = "navy" smart-punctuation = true definition-lists = true admonitions = true -mathjax-support = false +math = false additional-css = ["custom.css", "custom2.css"] additional-js = ["custom.js"] no-section-label = false @@ -129,7 +129,7 @@ The following configuration options are available: Defaults to `true`. - **definition-lists:** Enables [definition lists](../markdown.md#definition-lists). Defaults to `true`. - **admonitions:** Enables [admonitions](../markdown.md#admonitions). Defaults to `true`. -- **mathjax-support:** Adds support for [MathJax](../mathjax.md). Defaults to +- **math:** Enables [support for math formulas](../mathjax.md). Defaults to `false`. - **additional-css:** If you need to slightly change the appearance of your book without overwriting the whole style, you can specify a set of stylesheets that diff --git a/guide/src/format/mathjax.md b/guide/src/format/mathjax.md index d60ed0a5fd..f21aef7902 100644 --- a/guide/src/format/mathjax.md +++ b/guide/src/format/mathjax.md @@ -1,43 +1,33 @@ # MathJax support -mdBook has optional support for math equations through -[MathJax](https://www.mathjax.org/). - -To enable MathJax, you need to add the `mathjax-support` key to your `book.toml` -under the `output.html` section. +mdBook has optional support for math formulas, which are rendered through +[MathJax](https://www.mathjax.org/) (more precisely, the latest release of +MathJax version 4). To enable it, set `output.html.math` to `true` in your +`book.toml`: ```toml [output.html] -mathjax-support = true +math = true ``` ->**Note:** The usual delimiters MathJax uses are not yet supported. You can't -currently use `$$ ... $$` as delimiters and the `\[ ... \]` delimiters need an -extra backslash to work. Hopefully this limitation will be lifted soon. +Inline equations are delimited by `$...$` and block equations are delimited +by `$$...$$`. For example, to obtain ->**Note:** When you use double backslashes in MathJax blocks (for example in -> commands such as `\begin{cases} \frac 1 2 \\ \frac 3 4 \end{cases}`) you need -> to add _two extra_ backslashes (e.g., `\begin{cases} \frac 1 2 \\\\ \frac 3 4 -> \end{cases}`). +> If $n \geq 3$ then there are no integers $a, b, c \geq 1$ satisfying $$a^n + b^n = c^n$$ +you would write the following: -### Inline equations -Inline equations are delimited by `\\(` and `\\)`. So for example, to render the -following inline equation \\( \int x dx = \frac{x^2}{2} + C \\) you would write -the following: ``` -\\( \int x dx = \frac{x^2}{2} + C \\) +If $n \geq 3$ then there are no integers $a, b, c \geq 1$ satisfying $$a^n + b^n = c^n$$ ``` -### Block equations -Block equations are delimited by `\\[` and `\\]`. To render the following -equation - -\\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\] - +## Legacy MathJax support -you would write: - -```bash -\\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\] -``` +The legacy option `output.html.mathjax-support` enables equations with a +different syntax: `\\( ... \\)` for inline equations and `\\[ ... \\]` for +block equations. Because it does not parse formulas in the Markdown input +but instead lets MathJax find the delimiters in the HTML output, it has +the limitation that characters which have an effect in Markdown, such as +underscores, need to be escaped in formulas. This option is kept for +backwards compatibility; use `output.html.math` in new books. It uses +MathJax 2.7.1. diff --git a/tests/testsuite/markdown.rs b/tests/testsuite/markdown.rs index 7e46ffa228..98101a2a6f 100644 --- a/tests/testsuite/markdown.rs +++ b/tests/testsuite/markdown.rs @@ -164,6 +164,19 @@ fn definition_lists() { ); } +#[test] +fn math() { + BookTest::from_dir("markdown/math") + .check_all_main_files() + .run("build", |cmd| { + cmd.env("MDBOOK_OUTPUT__HTML__MATH", "true"); + }) + .check_main_file( + "book/math.html", + file!["markdown/math/expected_enabled/math.html"], + ); +} + #[test] fn admonitions() { BookTest::from_dir("markdown/admonitions") diff --git a/tests/testsuite/markdown/math/book.toml b/tests/testsuite/markdown/math/book.toml new file mode 100644 index 0000000000..4e6448db36 --- /dev/null +++ b/tests/testsuite/markdown/math/book.toml @@ -0,0 +1,2 @@ +[book] +title = "math" diff --git a/tests/testsuite/markdown/math/expected/math.html b/tests/testsuite/markdown/math/expected/math.html new file mode 100644 index 0000000000..85e27dad04 --- /dev/null +++ b/tests/testsuite/markdown/math/expected/math.html @@ -0,0 +1,2 @@ +

Inline math: $(a+b)^2 = a^2 + 2ab + b^2$

+

Display math: $$(a+b)^2 = a^2 + 2ab + b^2$$

\ No newline at end of file diff --git a/tests/testsuite/markdown/math/expected_enabled/math.html b/tests/testsuite/markdown/math/expected_enabled/math.html new file mode 100644 index 0000000000..0af44f771b --- /dev/null +++ b/tests/testsuite/markdown/math/expected_enabled/math.html @@ -0,0 +1,2 @@ +

Inline math: (a+b)^2 = a^2 + 2ab + b^2

+

Display math: (a+b)^2 = a^2 + 2ab + b^2

\ No newline at end of file diff --git a/tests/testsuite/markdown/math/src/SUMMARY.md b/tests/testsuite/markdown/math/src/SUMMARY.md new file mode 100644 index 0000000000..1b1a719697 --- /dev/null +++ b/tests/testsuite/markdown/math/src/SUMMARY.md @@ -0,0 +1,3 @@ +# Summary + +- [Math formulas](math.md) diff --git a/tests/testsuite/markdown/math/src/math.md b/tests/testsuite/markdown/math/src/math.md new file mode 100644 index 0000000000..304b9ecd45 --- /dev/null +++ b/tests/testsuite/markdown/math/src/math.md @@ -0,0 +1,3 @@ +Inline math: $(a+b)^2 = a^2 + 2ab + b^2$ + +Display math: $$(a+b)^2 = a^2 + 2ab + b^2$$