html-math-method: mathml — native MathML for format: html (bd-3evfzwal) - #714
Merged
Merged
Conversation
Adds `Target::MathMl` and `crates/quarto-math/src/mathml.rs`: `MathAst`
to a complete `<math>` element for `format: html` (consumed by the
`MathMlStage` of bd-3evfzwal). Written against MathML Core only:
- coarse runs split into `mi`/`mn`/`mo` via `split::split_run`, one
letter per `mi`; `-` becomes U+2212, `'` a prime;
- symbols classify by alphabet (Greek → `mi`, uppercase Greek upright,
relations/arrows → `mo`); bare delimiters get `stretchy="false"`,
`\left…\right` fences stretch;
- big operators place limits by mode and the integral family keeps
side limits, `\limits` pins `movablelimits="false"`; functions get
U+2061 function application;
- `\mathbf`/`\mathbb`/`\mathcal`/… map characters into the Mathematical
Alphanumeric Symbols block (holes included) since Core keeps only
`mathvariant="normal"`; `\text` keeps edge spaces as NBSP;
- environments become `mtable` (fenced for matrices, `{` for cases,
right/left `columnalign` for aligned); a top-level `\\` becomes a
table of lines; `\cancel` emits `menclose` (plan decision 3).
The source TeX rides in `<semantics>` as an `application/x-tex`
annotation, so `Normalized` gains a `text` field.
Tests: a corpus-wide validity check (quick-xml walk: Core element
allowlist, fixed child counts, `mathvariant` only `normal`), 263
per-fixture snapshots, 13 structural tests, and unit tests for the
variant table and accents.
Also fixes a normalizer bug the snapshots exposed: primes were dropped
(`f'` → `f`; the committed Typst snapshot for `basic/prime` read
`f ( x ) = f ( x )`). mitex parses `f'` as an attachment without a
`^`/`_` operator and `attach` returned only the base. Now a prime is a
superscript `Sym`, repeated primes merge into `″`/`‴`/`⁗`, and `x'^2`
shares the superscript slot (5 tests in `normalize.rs`). The same
fixture shows mitex taking `= f` as the base of `f''` after a relation;
filed as bd-0mzhnxft.
Snapshots: 263 added (`integration__mathml__mathml__*`), 6 modified
(`basic/prime` and `scripts/prime-with-sup` for normalize, OMML and
Typst — the prime fix; the Typst one now shows the bd-0mzhnxft quirk
as `(= f)^(″)`).
Verification: `cargo xtask verify --skip-hub-build` green; quarto-math
checks for wasm32-unknown-unknown.
Plan: claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Phase 3 (bd-3evfzwal) needs both the equation-numbering restructure
(Phase 1, off main) and the MathML writer (Phase 2, off the quarto-math
branch). Conflicts resolved:
- `crossref_render.rs`: `render_equation` keeps Phase 1's shape (the
number rides on `quarto-eq-number`, the TeX is untouched). The
`Math.text_source` extension that the quarto-math branch did for the
appended `\tag{N}` moves to `EquationNumberStage::encode_number`
(`append_to_tex`), which is the only place that appends to the text
now; its provenance test moves with it, and crossref-render gains a
test that the text and its mapping pass through untouched.
- the plan file: the Phase 2 copy (a superset).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
New `MathMlStage` (after `EquationNumberStage`, before `MathJsStage`):
under `html-math-method: mathml` every `Inline::Math` is converted with
`quarto_math::convert(Target::MathMl)` and replaced by the usual
`span.math.inline|display` wrapper around the `<math>` element, so a
document whose math all converts loads no JavaScript. Expressions the
converter rejects stay TeX; `MathJsStage` then loads MathJax only for
those (the hybrid fallback, plan decision 2), and the `Q-22-*`
diagnostic is reported as a located warning since the page renders.
Before this, `html-math-method: mathml` was a silent no-op (raw TeX,
no renderer).
- quarto-core depends on quarto-math; `MathEngine::from_meta` maps
`mathml` to the MathJax default, which is inert unless math survives
the MathML stage (pinned by the stage-order test and a unit test).
- New `crate::ast_walk::for_each_inline_mut`, the mutable inline walk
both post-filter stages now share (the numbering stage's private
walker is gone).
- `math-ml` joins the q2-preview exclusion list (the preview keeps
KaTeX client-side).
- MathJax config fix: q2's `skipHtmlTags` overrode MathJax's defaults
and dropped `annotation`/`annotation-xml`, so a loader brought in for
a leftover expression re-typeset the `\begin{…}` text inside the
converted math's annotations (found in a headless browser as
zero-size assistive MathML). Restored with a unit test and an
end-to-end assertion.
- Docs: `docs/guides/formats/html/math.qmd` (methods table, the
MathML mode, fallback, fonts, `\cancel`), linked from `Q-22-1`.
Tests (written first): 5 end-to-end tests in `math_mode_pipeline.rs`
(native MathML + sibling label + no loader; MathJax fallback with a
`Q-22-1` warning; math-free; math in headers/lists/tables/footnotes;
two-page website), 5 stage unit tests, the fallback test in math-js,
and the stage-list assertions (27 stages). Three Phase 1 tests that
asserted the TeX stayed under `mathml` now assert the annotation.
End-to-end (real binary + headless Chromium, recorded in the plan):
four native `<math>` elements with real heights, exactly one
MathJax-typeset leftover, the `(1)` label right of the equation on the
same row, one `Q-22-1` warning pointing at `\bogus` in the `.qmd`.
Plan: claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…vfzwal-mathml-stage equation_number.rs keeps this branch's version: the text_source-preserving append_to_tex that #708's merge lifted from here, plus the shared for_each_inline_mut walk. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A document with html-math-method: mathml rendered --to docx crashed the
Pandoc leg: the stage converted every equation to a RawInline before
pandoc ran, and the vendored crossref/equations.lua died on it
('attempt to concatenate a nil value (field text)', exit 83, Q-20-3).
#704 landed after this branch was cut, so its exact Pandoc stage-list
test only reddened at the merge.
The key is an HTML option that commonly lives in shared metadata of a
project rendering docx or typst too, so it is ignored silently on the
Pandoc leg — like EquationNumberStage's Writer encoding and like Quarto
1, which forwards it to pandoc whose non-HTML writers ignore it.
- applies_to(format, method) gates the stage on is_html_based(); unit test
- math-ml joins PANDOC_STAGE_EXCLUDED; the exact surviving-name list
gains equation-number (a deliberate no-op there)
- end-to-end: a mathml-method document renders to docx with one native
OMML equation numbered (1) and no MathML in the body
- plan: claude-notes/plans/2026-09-23-merge-math-stack.md records the
stack's conflict assessment and merge order
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds `Target::MathMl` and `crates/quarto-math/src/mathml.rs`: `MathAst`
to a complete `<math>` element for `format: html` (consumed by the
`MathMlStage` of bd-3evfzwal). Written against MathML Core only:
- coarse runs split into `mi`/`mn`/`mo` via `split::split_run`, one
letter per `mi`; `-` becomes U+2212, `'` a prime;
- symbols classify by alphabet (Greek → `mi`, uppercase Greek upright,
relations/arrows → `mo`); bare delimiters get `stretchy="false"`,
`\left…\right` fences stretch;
- big operators place limits by mode and the integral family keeps
side limits, `\limits` pins `movablelimits="false"`; functions get
U+2061 function application;
- `\mathbf`/`\mathbb`/`\mathcal`/… map characters into the Mathematical
Alphanumeric Symbols block (holes included) since Core keeps only
`mathvariant="normal"`; `\text` keeps edge spaces as NBSP;
- environments become `mtable` (fenced for matrices, `{` for cases,
right/left `columnalign` for aligned); a top-level `\\` becomes a
table of lines; `\cancel` emits `menclose` (plan decision 3).
The source TeX rides in `<semantics>` as an `application/x-tex`
annotation, so `Normalized` gains a `text` field.
Tests: a corpus-wide validity check (quick-xml walk: Core element
allowlist, fixed child counts, `mathvariant` only `normal`), 263
per-fixture snapshots, 13 structural tests, and unit tests for the
variant table and accents.
Also fixes a normalizer bug the snapshots exposed: primes were dropped
(`f'` → `f`; the committed Typst snapshot for `basic/prime` read
`f ( x ) = f ( x )`). mitex parses `f'` as an attachment without a
`^`/`_` operator and `attach` returned only the base. Now a prime is a
superscript `Sym`, repeated primes merge into `″`/`‴`/`⁗`, and `x'^2`
shares the superscript slot (5 tests in `normalize.rs`). The same
fixture shows mitex taking `= f` as the base of `f''` after a relation;
filed as bd-0mzhnxft.
Snapshots: 263 added (`integration__mathml__mathml__*`), 6 modified
(`basic/prime` and `scripts/prime-with-sup` for normalize, OMML and
Typst — the prime fix; the Typst one now shows the bd-0mzhnxft quirk
as `(= f)^(″)`).
Verification: `cargo xtask verify --skip-hub-build` green; quarto-math
checks for wasm32-unknown-unknown.
Plan: claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Plan doc: taken from the Phase 3 branch, which already carries the Phase 1 and Phase 2 checkbox updates both sides made. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 3 of
claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md(strand bd-3evfzwal). Stacked on the MathML writer PR (and so on #706 and #705). This branch also merges the Phase 1 equation-numbering branch (its own PR againstmain), which it needs for the sibling-label encoding; that part of the diff disappears once Phase 1 lands onmainand the stack takesmain.What this adds
html-math-method: mathmlrenders natively. A newMathMlStage(afterEquationNumberStage, beforeMathJsStage) converts everyInline::Mathwithquarto_math::convert(Target::MathMl)and replaces it by the usualspan.math.inline|displaywrapper around the<math>element. A document whose math all converts loads no JavaScript. Before this, the option was a silent no-op (raw TeX, no renderer).Hybrid fallback (plan decision 2). An expression the converter rejects stays TeX;
MathJsStagethen loads MathJax only for those, and theQ-22-*diagnostic is reported as a located warning since the page renders.MathEngine::from_metamapsmathmlto the MathJax default, inert unless math survives the MathML stage (pinned by the stage-order test and a unit test).Also:
crate::ast_walk::for_each_inline_mut, the mutable inline walk both post-filter stages now share.math-mljoins the q2-preview exclusion list (the preview keeps KaTeX client-side).skipHtmlTagsoverrode MathJax's defaults and droppedannotation/annotation-xml, so a loader brought in for a leftover expression re-typeset the\begin{…}text inside converted math's annotations (found in a headless browser as zero-size assistive MathML). Restored, with a unit test and an end-to-end assertion.docs/guides/formats/html/math.qmd(methods table, the MathML mode, fallback, fonts,\cancel), linked from theQ-22-1page.Math.text_sourceextension for the appended tag moved fromrender_equationtoEquationNumberStage::encode_number, with its test.Tests
Written first: 5 end-to-end tests in
math_mode_pipeline.rs(native MathML + sibling label + no loader; MathJax fallback with aQ-22-1warning; math-free; math in headers/lists/tables/footnotes; two-page website), 5 stage unit tests, the fallback test in math-js, stage-list assertions (27 stages).Verification
Full
cargo xtask verifygreen (14248 Rust tests, ts-packages, hub-client build incl. the WASM leg with quarto-math linked in, hub-client tests). End-to-end through the real binary and headless Chromium (recorded in the plan): four native<math>elements with real heights, exactly one MathJax-typeset leftover, the(1)label right of the equation on the same row, oneQ-22-1warning pointing at\bogusin the.qmd.🤖 Generated with Claude Code
Re-opened against
main(replaces #710). The original PR was part of a GitHub stack, whose auto-rebase rewrote #709's branch when #706 merged and would do the same to this branch's merge history when #709 lands; the stack also refuses a base change. Same branch, same content, plus the post-#704 fix:MathMlStagenow ignoreshtml-math-methodon non-HTML formats (amathmldocument rendered--to docxcrashed the vendoredcrossref/equations.lua),math-mljoinsPANDOC_STAGE_EXCLUDED, and an end-to-end docx test pins it. Conflict assessment and merge order for the whole stack:claude-notes/plans/2026-09-23-merge-math-stack.md.