From 3644a9a241d382740a1fe0b91fda350b1a55cb26 Mon Sep 17 00:00:00 2001 From: Carlos Scheidegger Date: Mon, 21 Sep 2026 18:19:39 -0500 Subject: [PATCH 1/4] quarto-math: MathML Core writer (bd-9z83tcv0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `Target::MathMl` and `crates/quarto-math/src/mathml.rs`: `MathAst` to a complete `` 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 `` 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 --- Cargo.lock | 1 + ...026-09-21-equation-numbering-and-mathml.md | 351 ++++++++ crates/quarto-math/Cargo.toml | 2 + crates/quarto-math/src/convert.rs | 5 +- crates/quarto-math/src/lib.rs | 5 +- crates/quarto-math/src/mathml.rs | 833 ++++++++++++++++++ crates/quarto-math/src/normalize.rs | 85 +- crates/quarto-math/tests/integration/main.rs | 1 + .../quarto-math/tests/integration/mathml.rs | 522 +++++++++++ .../tests/integration/normalize.rs | 89 ++ ...hml__mathml__accents__accent-bare-arg.snap | 5 + ...ml__mathml__accents__accent-on-accent.snap | 5 + ...gration__mathml__mathml__accents__bar.snap | 5 + ...tion__mathml__mathml__accents__cancel.snap | 5 + ...hml__accents__check-breve-acute-grave.snap | 5 + ...on__mathml__mathml__accents__dot-ddot.snap | 5 + ...gration__mathml__mathml__accents__hat.snap | 5 + ...on__mathml__mathml__accents__mathring.snap | 5 + ...gration__mathml__mathml__accents__not.snap | 5 + ...n__mathml__mathml__accents__overbrace.snap | 5 + ...athml__mathml__accents__overleftarrow.snap | 5 + ...on__mathml__mathml__accents__overline.snap | 5 + ...thml__mathml__accents__overrightarrow.snap | 5 + ...ml__mathml__accents__overset-underset.snap | 5 + ...on__mathml__mathml__accents__stackrel.snap | 5 + ...ation__mathml__mathml__accents__tilde.snap | 5 + ...__mathml__mathml__accents__underbrace.snap | 5 + ...n__mathml__mathml__accents__underline.snap | 5 + ...gration__mathml__mathml__accents__vec.snap | 5 + ...l__mathml__accents__widehat-widetilde.snap | 5 + ...ation__mathml__mathml__basic__comment.snap | 6 + ...hml__mathml__basic__decimal-and-comma.snap | 5 + ...athml__basic__digits-adjacent-letters.snap | 5 + ...mathml__mathml__basic__doubled-braces.snap | 5 + ...n__mathml__mathml__basic__empty-group.snap | 5 + ...__mathml__mathml__basic__equals-chain.snap | 5 + ...ion__mathml__mathml__basic__factorial.snap | 5 + ...gration__mathml__mathml__basic__greek.snap | 5 + ...mathml__basic__leading-trailing-space.snap | 5 + ...l__mathml__basic__mixed-brackets-bare.snap | 5 + ...hml__mathml__basic__multi-line-inline.snap | 6 + ...athml__mathml__basic__parens-brackets.snap | 5 + ...gration__mathml__mathml__basic__prime.snap | 5 + ...mathml__mathml__basic__relation-chain.snap | 5 + ..._mathml__mathml__basic__single-letter.snap | 5 + ..._mathml__mathml__basic__unicode-input.snap | 5 + ...athml__mathml__basic__word-and-number.snap | 5 + ...ration__mathml__mathml__corpus__bayes.snap | 5 + ...mathml__corpus__black-scholes_display.snap | 8 + ...athml__mathml__corpus__cancel-partial.snap | 5 + ...ml__corpus__conditional-prob-positive.snap | 5 + ...gration__mathml__mathml__corpus__emc2.snap | 5 + ..._mathml__corpus__exponential-survival.snap | 5 + ...__mathml__mathml__corpus__ftc_display.snap | 5 + ...ml__mathml__corpus__gauss-law_display.snap | 5 + ...ml__corpus__gaussian-integral_display.snap | 5 + ...__mathml__corpus__glm-poisson_display.snap | 7 + ...mathml__corpus__half-gaussian_display.snap | 5 + ...__mathml__mathml__corpus__index-range.snap | 5 + ...__mathml__corpus__integral-ftc-inline.snap | 5 + ...hml__mathml__corpus__limit-continuity.snap | 5 + ...athml__corpus__maxwell-ampere_display.snap | 5 + ...ation__mathml__mathml__corpus__nernst.snap | 5 + ...athml__mathml__corpus__newton-gravity.snap | 5 + ..._mathml__mathml__corpus__ols-estimate.snap | 5 + ...__mathml__mathml__corpus__poisson-pmf.snap | 5 + ...ml__mathml__corpus__power-set-unicode.snap | 5 + ...n__mathml__mathml__corpus__pythagoras.snap | 5 + ...on__mathml__mathml__corpus__quadratic.snap | 5 + ...n__mathml__mathml__corpus__regression.snap | 5 + ...athml__corpus__sir-two-groups_display.snap | 12 + ...n__mathml__mathml__corpus__sum-of-set.snap | 5 + ...thml__mathml__corpus__weibull_display.snap | 8 + ...thml__mathml__delimiters__bare-delims.snap | 5 + ...athml__mathml__delimiters__big-delims.snap | 5 + ...mathml__mathml__delimiters__bigl-bigr.snap | 5 + ..._mathml__delimiters__left-right-angle.snap | 5 + ..._mathml__delimiters__left-right-brace.snap | 5 + ...athml__delimiters__left-right-bracket.snap | 5 + ..._mathml__delimiters__left-right-floor.snap | 5 + ...athml__delimiters__left-right-in-frac.snap | 5 + ..._mathml__delimiters__left-right-mixed.snap | 5 + ...mathml__delimiters__left-right-nested.snap | 5 + ...__mathml__delimiters__left-right-norm.snap | 5 + ...hml__delimiters__left-right-null-left.snap | 5 + ...ml__delimiters__left-right-null-right.snap | 5 + ...left-right-paren-with-newline_display.snap | 7 + ..._mathml__delimiters__left-right-paren.snap | 5 + ...__mathml__delimiters__left-right-vert.snap | 5 + ...l__delimiters__left-right-with-middle.snap | 5 + ...l__delimiters__left-right-with-script.snap | 5 + ...hml__mathml__delimiters__lfloor-lceil.snap | 5 + ...hml__environments__align-star_display.snap | 8 + ...__mathml__environments__align_display.snap | 8 + ...mathml__environments__alignat_display.snap | 7 + ...nvironments__aligned-one-line_display.snap | 5 + ...nments__aligned-three-columns_display.snap | 8 + ...mathml__environments__aligned_display.snap | 8 + ...nvironments__array-with-lines_display.snap | 9 + ...__mathml__environments__array_display.snap | 8 + ...athml__environments__bbmatrix_display.snap | 8 + ...mathml__environments__bmatrix_display.snap | 8 + ...environments__cases-with-text_display.snap | 8 + ...__mathml__environments__cases_display.snap | 8 + ...athml__environments__equation_display.snap | 7 + ...athml__environments__gathered_display.snap | 8 + ...ml__environments__matrix-dots_display.snap | 9 + ...ironments__matrix-empty-cells_display.snap | 8 + ...__environments__matrix-ragged_display.snap | 8 + ...ts__matrix-trailing-row-break_display.snap | 8 + ...ments__matrix-with-frac-cells_display.snap | 8 + ..._mathml__environments__matrix_display.snap | 8 + ...athml__environments__multline_display.snap | 8 + ...hml__environments__nested-env_display.snap | 8 + ..._mathml__environments__pmatrix-inline.snap | 5 + ...mathml__environments__pmatrix_display.snap | 8 + ...ml__environments__row-spacing_display.snap | 8 + ...ml__mathml__environments__smallmatrix.snap | 5 + ...__mathml__environments__split_display.snap | 8 + ...athml__environments__substack_display.snap | 5 + ...mathml__environments__vmatrix_display.snap | 8 + ...athml__environments__vvmatrix_display.snap | 8 + ...athml__mathml__errors__bare-ampersand.snap | 5 + ...on__mathml__mathml__errors__bare-hash.snap | 5 + ...hml__mathml__errors__double-subscript.snap | 5 + ...l__mathml__errors__double-superscript.snap | 5 + ...ration__mathml__mathml__errors__empty.snap | 5 + ..._mathml__mathml__errors__env-mismatch.snap | 5 + ...l__mathml__errors__left-without-right.snap | 5 + ...n__mathml__mathml__errors__lone-caret.snap | 5 + ...thml__mathml__errors__lone-underscore.snap | 5 + ..._mathml__errors__macro-arity-mismatch.snap | 5 + ...__mathml__mathml__errors__missing-arg.snap | 5 + ...thml__mathml__errors__only-whitespace.snap | 5 + ...ml__mathml__errors__raw-dollar-inside.snap | 5 + ...thml__mathml__errors__recursive-macro.snap | 5 + ...l__mathml__errors__right-without-left.snap | 5 + ...l__mathml__errors__sqrt-index-no-body.snap | 5 + ...on__mathml__mathml__errors__stray-end.snap | 5 + ...l__mathml__errors__trailing-backslash.snap | 5 + ...hml__mathml__errors__unbalanced-close.snap | 5 + ...hml__errors__unbalanced-in-macro-body.snap | 5 + ...errors__unbalanced-left-right-nesting.snap | 5 + ...thml__mathml__errors__unbalanced-open.snap | 5 + ..._mathml__mathml__errors__unclosed-env.snap | 5 + ...rrors__undefined-blackboard-shortcuts.snap | 5 + ...hml__errors__undefined-macro-argument.snap | 5 + ...mathml__errors__unknown-command-noarg.snap | 5 + ...thml__mathml__errors__unknown-command.snap | 5 + ...__mathml__mathml__errors__unknown-env.snap | 5 + ...ion__mathml__mathml__fractions__binom.snap | 5 + ...ion__mathml__mathml__fractions__cfrac.snap | 5 + ...athml__mathml__fractions__dfrac-tfrac.snap | 5 + ..._mathml__fractions__frac-command-args.snap | 5 + ...athml__mathml__fractions__frac-in-sup.snap | 5 + ...athml__mathml__fractions__frac-nested.snap | 5 + ...hml__fractions__frac-single-char-args.snap | 5 + ...tion__mathml__mathml__fractions__frac.snap | 5 + ..._mathml__mathml__fractions__over-bare.snap | 5 + ...thml__mathml__fractions__over-grouped.snap | 5 + ..._mathml__mathml__macros__def-with-arg.snap | 5 + ...egration__mathml__mathml__macros__def.snap | 5 + ...mathml__macros__macro-arg-with-braces.snap | 5 + ...athml__macros__macro-defined-not-used.snap | 5 + ...hml__mathml__macros__macro-in-scripts.snap | 5 + ...hml__macros__macro-multi-line_display.snap | 6 + ...hml__mathml__macros__macro-used-twice.snap | 5 + ...thml__mathml__macros__newcommand-args.snap | 5 + ...mathml__macros__newcommand-nested-use.snap | 5 + ...hml__mathml__macros__newcommand-noarg.snap | 5 + ...thml__macros__newcommand-optional-arg.snap | 5 + ...thml__mathml__macros__newcommand-star.snap | 5 + ...__mathml__macros__newcommand-two-args.snap | 5 + ..._mathml__mathml__macros__renewcommand.snap | 5 + ...hml__mathml__operators__bigcup-bigcap.snap | 5 + ...l__mathml__operators__coprod-bigoplus.snap | 5 + ...athml__mathml__operators__det-dim-ker.snap | 5 + ...thml__operators__function-with-parens.snap | 5 + ...mathml__mathml__operators__iint-iiint.snap | 5 + ...mathml__mathml__operators__int-limits.snap | 5 + ...hml__mathml__operators__int-no-limits.snap | 5 + ...ation__mathml__mathml__operators__lim.snap | 5 + ...mathml__mathml__operators__log-ln-sin.snap | 5 + ...n__mathml__mathml__operators__max-min.snap | 5 + ...tion__mathml__mathml__operators__oint.snap | 5 + ..._mathml__operators__operatorname-star.snap | 5 + ...thml__mathml__operators__operatorname.snap | 5 + ...l__mathml__operators__prod-limits-cmd.snap | 5 + ...mathml__mathml__operators__sum-limits.snap | 5 + ...mathml__operators__sum-limits_display.snap | 5 + ...thml__operators__sum-nolimits_display.snap | 5 + ...thml__operators__sum-only-sub_display.snap | 5 + ...thml__operators__sum-only-sup_display.snap | 5 + ...n__mathml__mathml__operators__sup-inf.snap | 5 + ...thml__mathml__radicals__sqrt-bare-arg.snap | 5 + ...thml__radicals__sqrt-index-expression.snap | 5 + ..._mathml__mathml__radicals__sqrt-index.snap | 5 + ...mathml__mathml__radicals__sqrt-nested.snap | 5 + ...athml__mathml__radicals__sqrt-of-frac.snap | 5 + ...ation__mathml__mathml__radicals__sqrt.snap | 5 + ...n__mathml__mathml__scripts__empty-sup.snap | 5 + ...__mathml__mathml__scripts__nested-sup.snap | 5 + ...thml__mathml__scripts__prime-with-sup.snap | 5 + ...ml__mathml__scripts__script-digit-run.snap | 5 + ...thml__mathml__scripts__script-on-frac.snap | 5 + ...hml__mathml__scripts__script-on-group.snap | 5 + ...hml__mathml__scripts__script-on-paren.snap | 5 + ...ml__scripts__script-with-space-before.snap | 5 + ...n__mathml__mathml__scripts__sub-group.snap | 5 + ...__mathml__mathml__scripts__sub-of-sub.snap | 5 + ...gration__mathml__mathml__scripts__sub.snap | 5 + ...tion__mathml__mathml__scripts__subsup.snap | 5 + ...hml__mathml__scripts__sup-command-arg.snap | 5 + ...__mathml__scripts__sup-then-space-sub.snap | 5 + ...gration__mathml__mathml__scripts__sup.snap | 5 + ...tion__mathml__mathml__scripts__supsub.snap | 5 + ...hml__mathml__spacing__backslash-space.snap | 5 + ...l__mathml__spacing__enspace-and-comma.snap | 5 + ..._explicit-newline-outside-env_display.snap | 5 + ...l__mathml__spacing__hphantom-vphantom.snap | 5 + ...tion__mathml__mathml__spacing__hspace.snap | 5 + ...mathml__mathml__spacing__medium-thick.snap | 5 + ..._mathml__mathml__spacing__mkern-mskip.snap | 5 + ...ml__spacing__multiple-spaces-collapse.snap | 5 + ...athml__mathml__spacing__negative-thin.snap | 5 + ...ion__mathml__mathml__spacing__phantom.snap | 5 + ...__mathml__mathml__spacing__quad-qquad.snap | 5 + ...__mathml__spacing__space-inside-group.snap | 5 + ...__mathml__mathml__spacing__thin-space.snap | 5 + ...__mathml__mathml__spacing__tilde-nbsp.snap | 5 + ...tion__mathml__mathml__symbols__arrows.snap | 5 + ...__mathml__mathml__symbols__binary-ops.snap | 5 + ...ion__mathml__mathml__symbols__degrees.snap | 5 + ...ration__mathml__mathml__symbols__dots.snap | 5 + ...ml__mathml__symbols__escaped-specials.snap | 5 + ..._mathml__mathml__symbols__greek-lower.snap | 5 + ..._mathml__mathml__symbols__greek-upper.snap | 5 + ...on__mathml__mathml__symbols__harpoons.snap | 5 + ...ation__mathml__mathml__symbols__logic.snap | 5 + ..._mathml__mathml__symbols__long-arrows.snap | 5 + ..._mathml__mathml__symbols__lvert-rvert.snap | 5 + ...gration__mathml__mathml__symbols__mid.snap | 5 + ...ration__mathml__mathml__symbols__misc.snap | 5 + ...mathml__mathml__symbols__neq-variants.snap | 5 + ...n__mathml__mathml__symbols__relations.snap | 5 + ...ion__mathml__mathml__symbols__set-ops.snap | 5 + ...mathml__mathml__symbols__text-symbols.snap | 5 + ...l__symbols__unicode-and-command-mixed.snap | 5 + ...ion__mathml__mathml__symbols__xarrows.snap | 5 + ...__mathml__mathml__text-and-styles__bm.snap | 5 + ...__mathml__text-and-styles__boldsymbol.snap | 5 + ...mathml__text-and-styles__color-switch.snap | 5 + ...mathml__text-and-styles__displaystyle.snap | 5 + ...thml__mathml__text-and-styles__mathbb.snap | 5 + ...thml__mathml__text-and-styles__mathbf.snap | 5 + ...hml__mathml__text-and-styles__mathcal.snap | 5 + ...ml__mathml__text-and-styles__mathfrak.snap | 5 + ...text-and-styles__mathit-mathsf-mathtt.snap | 5 + ...hml__text-and-styles__mathrm-bare-arg.snap | 5 + ...mathml__text-and-styles__mathrm-multi.snap | 5 + ...thml__mathml__text-and-styles__mathrm.snap | 5 + ...hml__mathml__text-and-styles__mathscr.snap | 5 + ...mathml__mathml__text-and-styles__mbox.snap | 5 + ...mathml__text-and-styles__old-style-bf.snap | 5 + ...hml__text-and-styles__old-style-rm-it.snap | 5 + ...text-and-styles__style-on-multi-token.snap | 5 + ...ml__text-and-styles__text-nested-math.snap | 5 + ...__text-and-styles__text-special-chars.snap | 5 + ...ml__text-and-styles__text-with-spaces.snap | 5 + ...mathml__mathml__text-and-styles__text.snap | 5 + ...athml__text-and-styles__textbf-textit.snap | 5 + ...l__mathml__text-and-styles__textcolor.snap | 5 + ...l__text-and-styles__textstyle_display.snap | 5 + ...gration__normalize__ast__basic__prime.snap | 9 +- ...rmalize__ast__scripts__prime-with-sup.snap | 7 +- ...integration__omml__omml__basic__prime.snap | 2 +- ...__omml__omml__scripts__prime-with-sup.snap | 2 +- ...tegration__typst__typst__basic__prime.snap | 2 +- ...typst__typst__scripts__prime-with-sup.snap | 2 +- 279 files changed, 3312 insertions(+), 19 deletions(-) create mode 100644 claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md create mode 100644 crates/quarto-math/src/mathml.rs create mode 100644 crates/quarto-math/tests/integration/mathml.rs create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__accent-bare-arg.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__accent-on-accent.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__bar.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__cancel.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__check-breve-acute-grave.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__dot-ddot.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__hat.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__mathring.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__not.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overbrace.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overleftarrow.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overline.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overrightarrow.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overset-underset.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__stackrel.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__tilde.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__underbrace.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__underline.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__vec.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__widehat-widetilde.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__comment.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__decimal-and-comma.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__digits-adjacent-letters.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__doubled-braces.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__empty-group.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__equals-chain.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__factorial.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__greek.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__leading-trailing-space.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__mixed-brackets-bare.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__multi-line-inline.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__parens-brackets.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__prime.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__relation-chain.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__single-letter.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__unicode-input.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__word-and-number.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__bayes.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__black-scholes_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__cancel-partial.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__conditional-prob-positive.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__emc2.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__exponential-survival.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__ftc_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__gauss-law_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__gaussian-integral_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__glm-poisson_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__half-gaussian_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__index-range.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__integral-ftc-inline.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__limit-continuity.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__maxwell-ampere_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__nernst.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__newton-gravity.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__ols-estimate.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__poisson-pmf.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__power-set-unicode.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__pythagoras.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__quadratic.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__regression.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__sir-two-groups_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__sum-of-set.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__weibull_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__bare-delims.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__big-delims.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__bigl-bigr.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-angle.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-brace.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-bracket.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-floor.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-in-frac.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-mixed.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-nested.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-norm.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-null-left.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-null-right.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-paren-with-newline_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-paren.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-vert.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-with-middle.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-with-script.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__lfloor-lceil.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__align-star_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__align_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__alignat_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__aligned-one-line_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__aligned-three-columns_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__aligned_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__array-with-lines_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__array_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__bbmatrix_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__bmatrix_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__cases-with-text_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__cases_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__equation_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__gathered_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-dots_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-empty-cells_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-ragged_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-trailing-row-break_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-with-frac-cells_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__multline_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__nested-env_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__pmatrix-inline.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__pmatrix_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__row-spacing_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__smallmatrix.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__split_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__substack_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__vmatrix_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__vvmatrix_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__bare-ampersand.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__bare-hash.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__double-subscript.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__double-superscript.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__empty.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__env-mismatch.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__left-without-right.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__lone-caret.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__lone-underscore.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__macro-arity-mismatch.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__missing-arg.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__only-whitespace.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__raw-dollar-inside.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__recursive-macro.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__right-without-left.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__sqrt-index-no-body.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__stray-end.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__trailing-backslash.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unbalanced-close.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unbalanced-in-macro-body.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unbalanced-left-right-nesting.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unbalanced-open.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unclosed-env.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__undefined-blackboard-shortcuts.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__undefined-macro-argument.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unknown-command-noarg.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unknown-command.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unknown-env.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__binom.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__cfrac.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__dfrac-tfrac.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac-command-args.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac-in-sup.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac-nested.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac-single-char-args.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__over-bare.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__over-grouped.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__def-with-arg.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__def.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-arg-with-braces.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-defined-not-used.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-in-scripts.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-multi-line_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-used-twice.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-args.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-nested-use.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-noarg.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-optional-arg.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-star.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-two-args.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__renewcommand.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__bigcup-bigcap.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__coprod-bigoplus.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__det-dim-ker.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__function-with-parens.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__iint-iiint.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__int-limits.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__int-no-limits.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__lim.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__log-ln-sin.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__max-min.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__oint.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__operatorname-star.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__operatorname.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__prod-limits-cmd.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-limits.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-limits_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-nolimits_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-only-sub_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-only-sup_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sup-inf.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-bare-arg.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-index-expression.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-index.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-nested.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-of-frac.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__empty-sup.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__nested-sup.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__prime-with-sup.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-digit-run.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-on-frac.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-on-group.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-on-paren.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-with-space-before.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sub-group.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sub-of-sub.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sub.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__subsup.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sup-command-arg.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sup-then-space-sub.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sup.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__supsub.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__backslash-space.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__enspace-and-comma.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__explicit-newline-outside-env_display.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__hphantom-vphantom.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__hspace.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__medium-thick.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__mkern-mskip.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__multiple-spaces-collapse.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__negative-thin.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__phantom.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__quad-qquad.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__space-inside-group.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__thin-space.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__tilde-nbsp.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__arrows.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__binary-ops.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__degrees.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__dots.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__escaped-specials.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__greek-lower.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__greek-upper.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__harpoons.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__logic.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__long-arrows.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__lvert-rvert.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__mid.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__misc.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__neq-variants.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__relations.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__set-ops.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__text-symbols.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__unicode-and-command-mixed.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__xarrows.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__bm.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__boldsymbol.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__color-switch.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__displaystyle.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathbb.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathbf.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathcal.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathfrak.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathit-mathsf-mathtt.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathrm-bare-arg.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathrm-multi.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathrm.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathscr.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mbox.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__old-style-bf.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__old-style-rm-it.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__style-on-multi-token.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__text-nested-math.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__text-special-chars.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__text-with-spaces.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__text.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__textbf-textit.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__textcolor.snap create mode 100644 crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__textstyle_display.snap diff --git a/Cargo.lock b/Cargo.lock index 1f3ec4179..4d7779656 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5751,6 +5751,7 @@ dependencies = [ "quarto-error-reporting", "quarto-math", "quarto-source-map", + "quick-xml 0.39.3", "rowan", "rustc-hash 2.1.2", "serde", diff --git a/claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md b/claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md new file mode 100644 index 000000000..be906ee94 --- /dev/null +++ b/claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md @@ -0,0 +1,351 @@ +# Format-specific equation numbering and `html-math-method: mathml` + +**Status:** approved 2026-09-21 (all five open decisions settled with the +user, each as recommended); executing Phase 1. +**Strands:** bd-vlhi2zkj (Phase 1, equation numbering; branch +`braid/bd-vlhi2zkj-equation-numbering` off `main`), bd-9z83tcv0 (Phase 2, +MathML writer, p2), bd-3evfzwal (Phase 3, `MathMlStage`; blocked on the +other two). +**Parent work:** `claude-notes/plans/2026-09-21-quarto-math-and-native-docx.md` +(quarto-math, PR #706). This plan is the "interlude": it gives quarto-math a +real consumer (`format: html`) before the docx writer exists. + +## Overview + +Two things, in dependency order. + +1. **Equation numbering becomes a format-specific presentation step.** + Today `render_equation` (`crates/quarto-core/src/transforms/crossref_render.rs`) + appends `\tag{N}` to the TeX of every numbered equation, unconditionally, + inside the format-agnostic `CrossrefRenderTransform`. `\tag` is an + `amsmath` command that only MathJax and KaTeX understand: Quarto 1 + (`crossref/equations.lua`, `renderEquation`) emits it *only* for HTML with + `mathjax`/`katex`, uses `\qquad(N)` for every other math engine, and + defers to the writer for LaTeX (`equation` + `\label`) and Typst + (`#math.equation(numbering:)`). Pandoc's converter rejects `\tag` in + `$$…$$` (verified with pandoc 3.9: warning + verbatim fallback under + `--mathml` and `-t docx`), and quarto-math's spec marks it unsupported + (verified: an `Error` node, output withheld). The number encoding is + presentation, so it moves out of crossref-render into a step selected by + the format and `html-math-method`. + + The hand-off between the two is a **reserved attribute on the equation + `Span`**. Crossref-render leaves the `Math` node byte-identical to the + source and records the number as `quarto-eq-number="N"`; the numbering + step consumes and removes it. Because the step runs *after* user post + filters, a Lua filter can read, rewrite or drop the attribute — the + escape hatch — without knowing anything else about the pipeline. + +2. **`html-math-method: mathml`** renders each `Inline::Math` to MathML Core + at render time through a new `quarto_math` writer, so common documents + ship no MathJax. Today the option is a silent no-op (verified on the PR + #706 tip: raw `\(…\)` TeX, no renderer loaded). With (1) in place the + MathML path needs no `\tag` support: the number is a sibling inline + outside ``, which MathML Core requires anyway (`mlabeledtr` was + dropped from Core). + +### Why the numbering step is a *stage*, not a Finalization transform + +The HTML stage list (`build_html_pipeline_stages_with_options`, +`crates/quarto-core/src/pipeline.rs`) is + +``` +… → UserFiltersStage::pre → AstTransformsStage (all four phases, incl. +CrossrefRenderTransform) → UserFiltersStage::post → ResourceReportStage → +CodeHighlightStage → MathJsStage → RenderHtmlBodyStage → ApplyTemplateStage +``` + +Every Finalization transform runs inside `AstTransformsStage`, *before* post +filters. A numbering transform there would consume the reserved attribute +before any user filter could see it. Placing the step as a stage between +`UserFiltersStage::post` and `MathJsStage` (the slot `CodeHighlightStage` +already occupies: AST-level, format-aware, post-filter) keeps the attribute +visible to post filters and still satisfies the phase contract's intent +(presentation after crossref-render). The design doc gets a paragraph naming +this slot (item in Phase 1). + +### The reserved attribute + +- Key: `quarto-eq-number` (kv on the `Span#eq-… .quarto-math-with-attribute` + that `render_equation` already emits). `quarto-` prefix matches the other + reserved names transforms consume (`quarto-template-params`, + `quarto-reuse`, `quarto-xref`). +- Value: the number as text, exactly what would be typeset (`1`, later + `2.3` when section-prefixed numbering lands; that is out of scope here). +- Lifetime: written by `CrossrefRenderTransform`, readable by post filters, + removed by `EquationNumberStage` in every strategy (so it never reaches the + writer, in any format). +- Lua view: `el.attributes["quarto-eq-number"]` on a `Span` whose + `identifier` starts with `eq-`. Deleting the attribute suppresses the + number; changing it changes the label. Documented for filter authors. + +### Numbering strategies + +Selected by `EquationNumberStage` from `ctx.format` and the document's +`html-math-method` (already flattened to top level by `resolve_format_config`; +parsed by a shared `MathMethod` enum, see Phase 1): + +| Strategy | When | Effect on `[Math(Display, t)]` | +| --- | --- | --- | +| `TexTag` | HTML-based format, method `mathjax` / `katex` / absent | `t` becomes `t\tag{N}` (today's behaviour, with the same `text_source` concat provenance) | +| `Qquad` | HTML-based, method `plain` / `webtex` / `gladtex` / unknown | `t` becomes `t \qquad(N)` (Quarto 1's non-JS encoding) | +| `Sibling` | HTML-based, method `mathml` | `Math` untouched; a `Span.quarto-eq-number` containing `Str("(N)")` is appended after it inside the equation span, and the equation span gains class `quarto-eq-sibling-number` for CSS | +| `Writer` | LaTeX / Typst / docx (native writers, future) | `Math` untouched; the writer numbers. Placeholder: q2 renders only HTML and revealjs natively today, and PR #704 routes docx/Typst through Quarto 1's Lua filters, which do their own numbering | + +`Sibling` is robust to a failed MathML conversion: if the expression falls +back to TeX + MathJax (Phase 3 hybrid), the number is still there, outside +the math. + +### MathML output shape + +```html + + + + + ORIGINAL TEX + + + +``` + +The existing `span.math.inline` / `span.math.display` wrappers stay (user +CSS and the preview-parity tooling key on them; Pandoc drops the span for +display math, we do not). The annotation carries the source TeX for +copy/paste and assistive tools. Emitted as `RawInline("html", …)` replacing +the `Inline::Math`, which the HTML writer passes through verbatim. + +## Checklist + +### Phase 0 — setup + +- [x] (done 2026-09-21: bd-vlhi2zkj, bd-3evfzwal; bd-9z83tcv0 → p2) File the two new strands (numbering restructure; HTML `mathml` stage) + with `discovered-from: bd-entbg6x3`, `related: bd-9z83tcv0`; link this + plan. Reprioritise bd-9z83tcv0 from p3 to p2. +- [x] (branch created 2026-09-21) Phase 1 branches off `main` (it does not touch quarto-math) and lands + as its own PR, same pattern as decision 5 of the parent plan. Phases 2 + and 3 branch off `feature/bd-entbg6x3-quarto-math` and stack on #706; + Phase 3 merges `main` once Phase 1 is in. + +### Phase 1 — equation numbering as a format-specific stage (new strand, PR against `main`) + +Tests first, in this order. + +- [x] (2026-09-21) **Unit tests in `crossref_render.rs`**: `render_equation` leaves + `math.text` and `math.text_source` untouched and sets + `quarto-eq-number` on the span; an unnumbered equation sets no + attribute. Rewrite the three existing tests that assert `\tag{1}` in + the math text (`…:2494`, `:2536`, `:2595`) to assert the attribute. +- [x] (2026-09-21; 13 tests in `equation_number.rs`) **`EquationNumberStage` unit tests** (one per strategy): `TexTag` + appends `\tag{N}` and extends `text_source` as a concat with a + synthesized piece (move the provenance code from `render_equation`); + `Qquad` appends ` \qquad(N)`; `Sibling` appends the label span and the + modifier class; every strategy removes the attribute; a span without + the attribute is untouched; an equation span whose first inline is not + `Math(DisplayMath)` is left alone with a debug trace (mirrors the + preview's `Equation.tsx` fallback rules). +- [x] (2026-09-21; `math_method.rs` + `NumberEncoding::for_document` tests) **Strategy selection tests**: `MathMethod::from_meta` for the string + and object forms (`mathjax`, `katex`, `mathml`, `plain`, `webtex`, + `gladtex`, unknown, absent); `strategy_for(format, method)` table. +- [x] (2026-09-21; new file `tests/integration/equation_numbering_pipeline.rs`, 13 tests, so the math-js suite stays about engine injection) **End-to-end tests** + (drive `render_to_file`): default → `\tag{1}` present and MathJax + loaded (the existing `labelled_equation_emits_mathjax_and_tag` keeps + passing); `html-math-method: plain` → `\qquad(1)` present, no + `\tag`, no loader; `html-math-method: mathml` → `span.quarto-eq-number` + present, `\tag` absent (the math itself stays TeX until Phase 3); + revealjs → `\tag{1}`. +- [x] (2026-09-21; same file; also pins that a *pre* filter does not see the attribute) **Lua escape-hatch test**: a post filter that reads + `el.attributes["quarto-eq-number"]` and rewrites it to `A` yields + `\tag{A}` in the output; a filter that deletes it yields an unnumbered + equation. Place next to the existing user-filter integration tests. +- [x] (2026-09-21) Implement: `MathMethod` enum in a new + `crates/quarto-core/src/math_method.rs` (string + object forms; the + only parser of `html-math-method`), and make `MathEngine::from_meta` + in `math_js.rs` a thin mapping over it so the two stages cannot drift. +- [x] (2026-09-21; `EQ_NUMBER_ATTR` lives in `crossref/mod.rs` next to the `EQUATION` type name) Implement: `render_equation` writes the attribute instead of the tag. +- [x] (2026-09-21; registered between `ResourceReportStage` and `CodeHighlightStage`) Implement: `crates/quarto-core/src/stage/stages/equation_number.rs`, + registered between `UserFiltersStage::post` and `CodeHighlightStage` + in `build_html_pipeline_stages_with_options`. Included in the + q2-preview stage list (it is a no-op there: the preview excludes + crossref-render, so no span carries the attribute, and `Equation.tsx` + keeps its own KaTeX `\tag` append). Add the stage name to the + preview-exclusion validator's known list if the test requires it. +- [x] (2026-09-21; **placement changed from the plan**: not `_bootstrap-rules.scss` but a shared layer `resources/scss/html/templates/equation-number.scss`, loaded by `load_equation_number_layer` in `quarto-sass` at the five HTML compile sites and in `assemble_reveal_scss`, exactly like `copy-code.scss`. Reason: revealjs is HTML-based and gets the `Sibling` encoding too, and the bootstrap rules file is not bundled into decks. Tests: `test_compile_default_css`, `test_compile_reveal_theme_includes_equation_number_rules`.) CSS for `Sibling`: `.quarto-eq-sibling-number` + as a flex row with the math centered and the label pushed right. +- [x] (2026-09-21; new section "The post-filter presentation slot") Design doc: add the "post-filter presentation stage" slot to + `claude-notes/designs/transform-pipeline-phases.md`, naming + `CodeHighlightStage` and `EquationNumberStage` as its members and + stating the rule: a step that must remain visible to user post filters + is a stage here, not a Finalization transform. +- [x] (2026-09-21; placed in `docs/guides/authoring/lua-filters.qmd` as its own section, since that is where filter authors look; the cross-reference page in that directory is misnamed `cross-references.cmd`, flagged to the user) Docs (user-facing): + one short "for filter authors" note on `quarto-eq-number`. +- [x] (2026-09-21) Update the `Equation.tsx` comment that cites `render_equation`'s + line numbers and the `\tag` port, so the two stay traceable. +- [x] (2026-09-21: full `cargo xtask verify` green — 14049 Rust tests, ts-packages, hub-client build + tests. Two collateral test updates: the HTML stage-list/count assertions in `pipeline.rs` (25 → 26 stages) and the `styles.css` byte-identity baseline in `tests/fixtures/phase5-single-doc-baseline/expected_hashes.txt`, re-captured with a dated note because the new SCSS layer is additive to every compiled stylesheet.) `cargo xtask verify` (full: `quarto-core` changed). Record the + end-to-end invocation and the inspected output here. + +**End-to-end verification (2026-09-21, real binary, output inspected).** +Three copies of the same document, differing only in front matter +(`html-math-method` absent / `mathml` / `plain`), each with +`$$\sum_{i=1}^{n} \alpha_i = \int_0^\infty e^{-x}\,dx$$ {#eq-one}` and a +`@eq-one` reference, rendered with +`cargo run --bin q2 -- render /{default,mathml,plain}.qmd`. The +equation markup in each `.html`: + +```html + +\[ +\sum_{i=1}^{n} \alpha_i = \int_0^\infty e^{-x}\,dx +\tag{1}\] + +\[ +\sum_{i=1}^{n} \alpha_i = \int_0^\infty e^{-x}\,dx +\](1) + +\[ +\sum_{i=1}^{n} \alpha_i = \int_0^\infty e^{-x}\,dx + \qquad(1)\] +``` + +`See Equation 1.` in all three; +`quarto-eq-number="…"` appears in none. `_files/styles.css` of each +contains the three `.quarto-eq-sibling-number…` rules from +`equation-number.scss`. + +**Follow-up for the #706 rebase.** On `main` today `Math` has no +`text_source`; PR #705 adds it and PR #706's `render_equation` extends the +mapping with a synthesized piece for the appended `\tag`. After this phase +merges, that provenance code belongs in `EquationNumberStage::encode_number` +(the `TexTag` and `Qquad` arms are the only places that append to the +text). Noted in the parent plan's merge-readiness section on the #706 +branch when it rebases. + +### Phase 2 — MathML writer in quarto-math (bd-9z83tcv0, stacked on #706) + +Mirrors `typst.rs`: one file, one snapshot per fixture, a corpus-wide +validity check standing in for the compile check Typst has. + +- [x] (2026-09-21; `every_fixture_is_valid_mathml_core`, a quick-xml walk checking element allowlist, fixed child counts for `mfrac`/`msub`/…/`munderover`, and `mathvariant`) **Validity test** (`tests/integration/mathml.rs`): every fixture's + output parses with `quick-xml`, uses only MathML Core elements + (`math mrow mi mn mo mtext mspace ms msub msup msubsup munder mover + munderover mfrac msqrt mroot mtable mtr mtd mstyle mpadded mphantom + merror semantics annotation`, plus `menclose`, see decisions), and + the only `mathvariant` value is `normal`. Snapshot per fixture. +- [x] (2026-09-21; 13 structural tests) **Structural tests**: `Run` splits to `mi`/`mn`/`mo` via + `split::split_run` with a single-letter `mi` italic by default; + `Nary` + `Scripts` → `munderover` in display / `msubsup` inline + (`LimLoc` rules identical to OMML); `Func` → `mi` + U+2061 function + application; `Delimited` → `mrow` with stretchy fence `mo`s and + `\middle` separators; `Frac` styles (`NoBar` → `linethickness="0"`, + `Binom` wrapped in parentheses, `Display`/`Text` → `mstyle + displaystyle`); `Sqrt` → `msqrt`/`mroot`; `Accent` → `mover + accent="true"`; `Bar`/`GroupChr`/`LimPos`/`XArrow` → `munder`/`mover`; + `Matrix` layouts → `mtable` (`Cases` with a left `{` fence and + `columnalign="left"`, `Aligned` with alternating right/left + alignment, `Gathered` centered, `Matrix` inside its fences); + `Break` → the whole row becomes a two-row `mtable`; `Space` → + `mspace width="…em"` (negative widths allowed in Core); + `Phantom` → `mphantom` (+ `mpadded` for `h`/`v` only); + `Color` → `mstyle mathcolor`; `Text` → `mtext`. +- [x] (2026-09-21; `styled_char` + unit test over every variant and hole) **Style variants**: `Style { variant }` maps each letter and digit of + its body into the Mathematical Alphanumeric Symbols block + (`U+1D400…`), with the reserved-codepoint holes table (`ℎ ℬ ℰ ℱ ℋ ℐ + ℒ ℳ ℛ ℂ ℍ ℕ ℙ ℚ ℝ ℤ ℭ ℌ ℑ ℜ ℨ`, plus the `Roman` variant which is + `mathvariant="normal"` on a single-letter `mi`). Unit test the table + against the Unicode chart for one letter per variant and every hole. +- [x] (2026-09-21) **Escaping**: `<`, `&`, `>` in `mo`/`mi`/`mtext`/`annotation`. +- [x] (2026-09-21; `Normalized` gained a `text` field so the writer can emit the ``; `-` in runs is emitted as U+2212) Implement `crates/quarto-math/src/mathml.rs`, `Target::MathMl` in + `convert.rs`, `render()` arm, crate docs. Runs `split_run` on every + `Run` (the pass the parent plan reserved for this). +- [x] (2026-09-21: `cargo xtask verify --skip-hub-build` green; `cargo check --target wasm32-unknown-unknown -p quarto-math` clean) `cargo xtask verify --skip-hub-build` (quarto-math only), then the + full verify once Phase 3 touches `quarto-core`. + +**Found while snapshotting (2026-09-21):** the normalizer dropped primes +(`f'` → `f`; the committed Typst snapshot for `basic/prime` read +`f ( x ) = f ( x )`). mitex parses `f'` as an attachment with no `^`/`_` +operator and `attach` returned only the base. Fixed under TDD in this +phase (5 tests in `normalize.rs`): a prime is a superscript `Sym`, repeated +primes merge into `″`/`‴`/`⁗`, and `x'^2` shares the superscript slot +instead of raising a double-superscript error. Six snapshots changed +(`basic/prime`, `scripts/prime-with-sup` × normalize/OMML/Typst). The +same fixture exposes a separate mitex quirk, `f''` after `=` taking `= f` +as its base, filed as bd-0mzhnxft. + +### Phase 3 — `MathMlStage` for `format: html` (new strand, stacked on #706 + Phase 1) + +- [ ] **End-to-end tests in `math_mode_pipeline.rs`**: `html-math-method: + mathml` with inline + display + numbered math → `` element (bd-9z83tcv0). + MathMl, } /// Result of [`convert`]. @@ -79,6 +81,7 @@ pub fn render(tree: &Normalized, target: Target) -> String { match target { Target::Omml => omml::to_omml(tree), Target::Typst => typst::to_typst(tree), + Target::MathMl => mathml::to_mathml(tree), } } diff --git a/crates/quarto-math/src/lib.rs b/crates/quarto-math/src/lib.rs index a487ef25b..be5363b2e 100644 --- a/crates/quarto-math/src/lib.rs +++ b/crates/quarto-math/src/lib.rs @@ -7,8 +7,8 @@ //! //! One reader (a vendored copy of mitex's LaTeX lexer + parser, under //! `vendor/`), a q2-owned command spec, a normalization pass to a small -//! `MathAst`, and one writer per target (OMML for docx, Typst; MathML is a -//! follow-up, bd-9z83tcv0). Every node carries a `SourceInfo` derived from +//! `MathAst`, and one writer per target (OMML for docx, Typst, and MathML +//! Core for HTML). Every node carries a `SourceInfo` derived from //! the math text's own `SourceInfo`, so diagnostics point into the `.qmd`. //! //! The crate deliberately does **not** depend on `quarto-pandoc-types`: the @@ -21,6 +21,7 @@ pub mod ast; pub mod convert; pub mod diagnostics; +pub mod mathml; pub mod normalize; pub mod omml; pub mod reader; diff --git a/crates/quarto-math/src/mathml.rs b/crates/quarto-math/src/mathml.rs new file mode 100644 index 000000000..7310aa5f3 --- /dev/null +++ b/crates/quarto-math/src/mathml.rs @@ -0,0 +1,833 @@ +/* + * mathml.rs + * Copyright (c) 2026 Posit, PBC + */ + +//! `MathAst` → MathML Core (bd-9z83tcv0). +//! +//! The output is one complete `` element, ready to drop into HTML, +//! written against [MathML Core] only: the subset every current browser +//! renders natively. Two consequences shape the writer: +//! +//! - **Token classification is ours.** A coarse mitex run (`4ac`, `i=1`) +//! is split by [`crate::split`] into `mi` / `mn` / `mo`, one letter per +//! `mi` (TeX semantics, where `ab` is two variables). A single-letter +//! `mi` is italic by default in MathML, as in TeX. +//! - **No `mathvariant` but `normal`.** Core dropped the other values, so +//! `\mathbf`, `\mathbb`, `\mathcal`, … map each character into the +//! Mathematical Alphanumeric Symbols block (`𝐱`, `ℝ`, `𝒫`), holes +//! included; `\mathrm` alone uses `mathvariant="normal"`. +//! +//! The source TeX rides along in `` as an +//! ``, for copy/paste and +//! assistive tools. One knowingly non-Core element remains: `\cancel` +//! emits ``, which Firefox draws and +//! Chrome ignores (the content still shows); see the plan's decision 3. +//! +//! [MathML Core]: https://www.w3.org/TR/mathml-core/ + +use std::fmt::Write; + +use crate::ast::{EnvLayout, FracStyle, LimLoc, Node, NodeKind, Pos, Variant}; +use crate::normalize::{Mode, Normalized}; +use crate::split::{PieceKind, split_run}; + +/// Big operators whose limits TeX sets beside the operator even in display +/// math (the integral family); everything else gets them under and over. +const SIDE_LIMIT_OPERATORS: &str = "∫∬∭∮∯∰⨌"; + +/// Delimiter characters that appear bare in the source (`(x)`, `[a]`, +/// `\{`, `|`). TeX does not stretch them, so neither do we; `\left…\right` +/// fences are emitted separately and stretch. +const BARE_DELIMITERS: &str = "()[]{}|‖⟨⟩⌈⌉⌊⌋/\\"; + +/// A complete `` element for a normalized expression. +pub fn to_mathml(n: &Normalized) -> String { + let mut w = Writer { + out: String::new(), + mode: n.mode, + }; + match n.mode { + Mode::Display => w + .out + .push_str(r#""#), + Mode::Inline => w + .out + .push_str(r#""#), + } + w.out.push_str(""); + w.root(&n.root); + let _ = write!( + w.out, + r#"{}"#, + escape_text(&n.text) + ); + w.out +} + +struct Writer { + out: String, + mode: Mode, +} + +/// Text content escaping (`&`, `<`, `>`). +fn escape_text(text: &str) -> String { + let mut out = String::with_capacity(text.len()); + for c in text.chars() { + match c { + '&' => out.push_str("&"), + '<' => out.push_str("<"), + '>' => out.push_str(">"), + _ => out.push(c), + } + } + out +} + +/// Attribute value escaping (adds `"`). +fn escape_attr(text: &str) -> String { + escape_text(text).replace('"', """) +} + +/// The spacing character MathML uses for a TeX accent, given the combining +/// character the spec resolves the command to. Combining marks render +/// poorly as `mover` content; the spacing forms are what the operator +/// dictionary knows as accents. +fn accent_char(combining: &str) -> &str { + match combining { + "\u{302}" => "\u{2c6}", // hat → ˆ + "\u{303}" => "\u{2dc}", // tilde → ˜ + "\u{304}" => "\u{af}", // bar → ¯ + "\u{306}" => "\u{2d8}", // breve → ˘ + "\u{307}" => "\u{2d9}", // dot → ˙ + "\u{308}" => "\u{a8}", // ddot → ¨ + "\u{20db}" => "\u{20db}", // dddot has no spacing form + "\u{20dc}" => "\u{20dc}", // ddddot has no spacing form + "\u{301}" => "\u{b4}", // acute → ´ + "\u{30b}" => "\u{2dd}", // double acute → ˝ + "\u{300}" => "`", // grave + "\u{30c}" => "\u{2c7}", // check → ˇ + "\u{30a}" => "\u{2da}", // ring → ˚ + "\u{20d7}" => "\u{2192}", // vec → → + "\u{20d6}" => "\u{2190}", // overleftarrow accent → ← + "\u{20e1}" => "\u{2194}", // overleftrightarrow accent → ↔ + "\u{20d1}" => "\u{21c0}", // harpoon → ⇀ + "\u{20d0}" => "\u{21bc}", // left harpoon → ↼ + other => other, + } +} + +/// `\textcolor` names to CSS colors. Named CSS colors are passed through +/// (CSS knows every name TeX's `color` package knows); `#rrggbb` and bare +/// hex triplets are normalized to `#rrggbb`. +fn css_color(name: &str) -> String { + let name = name.trim(); + let bare = name.trim_start_matches('#'); + if bare.len() == 6 && bare.chars().all(|c| c.is_ascii_hexdigit()) { + return format!("#{}", bare.to_ascii_lowercase()); + } + name.to_ascii_lowercase() +} + +fn format_em(em: f32) -> String { + let s = format!("{em:.4}"); + let s = s.trim_end_matches('0').trim_end_matches('.'); + s.to_string() +} + +/// Map one character into the Mathematical Alphanumeric Symbols block for +/// a font variant. Characters the variant has no glyph for (a digit in +/// script, punctuation) are returned unchanged. +pub fn styled_char(c: char, variant: Variant) -> char { + use Variant::*; + let holes: &[(char, char)] = match variant { + Script => &[ + ('B', 'ℬ'), + ('E', 'ℰ'), + ('F', 'ℱ'), + ('H', 'ℋ'), + ('I', 'ℐ'), + ('L', 'ℒ'), + ('M', 'ℳ'), + ('R', 'ℛ'), + ('e', 'ℯ'), + ('g', 'ℊ'), + ('o', 'ℴ'), + ], + Fraktur => &[('C', 'ℭ'), ('H', 'ℌ'), ('I', 'ℑ'), ('R', 'ℜ'), ('Z', 'ℨ')], + DoubleStruck => &[ + ('C', 'ℂ'), + ('H', 'ℍ'), + ('N', 'ℕ'), + ('P', 'ℙ'), + ('Q', 'ℚ'), + ('R', 'ℝ'), + ('Z', 'ℤ'), + ], + Italic => &[('h', 'ℎ')], + _ => &[], + }; + if let Some((_, mapped)) = holes.iter().find(|(from, _)| *from == c) { + return *mapped; + } + // (upper A, lower a, digit 0, upper Α, lower α) block starts; 0 = none. + let (upper, lower, digit, greek_upper, greek_lower) = match variant { + Roman => return c, + Bold => (0x1D400, 0x1D41A, 0x1D7CE, 0x1D6A8, 0x1D6C2), + Italic => (0x1D434, 0x1D44E, 0, 0x1D6E2, 0x1D6FC), + BoldItalic => (0x1D468, 0x1D482, 0, 0x1D71C, 0x1D736), + Script => (0x1D49C, 0x1D4B6, 0, 0, 0), + Fraktur => (0x1D504, 0x1D51E, 0, 0, 0), + DoubleStruck => (0x1D538, 0x1D552, 0x1D7D8, 0, 0), + Sans => (0x1D5A0, 0x1D5BA, 0x1D7E2, 0, 0), + Mono => (0x1D670, 0x1D68A, 0x1D7F6, 0, 0), + }; + let mapped = match c { + 'A'..='Z' if upper != 0 => upper + (c as u32 - 'A' as u32), + 'a'..='z' if lower != 0 => lower + (c as u32 - 'a' as u32), + '0'..='9' if digit != 0 => digit + (c as u32 - '0' as u32), + // Α..Ω (U+0391..U+03A9; U+03A2 is unassigned and the block puts ϴ + // there, so the offset is direct). + 'Α'..='Ω' if greek_upper != 0 && c != '\u{3a2}' => greek_upper + (c as u32 - 'Α' as u32), + 'α'..='ω' if greek_lower != 0 => greek_lower + (c as u32 - 'α' as u32), + _ => return c, + }; + char::from_u32(mapped).unwrap_or(c) +} + +fn styled(text: &str, variant: Option) -> String { + match variant { + None | Some(Variant::Roman) => text.to_string(), + Some(v) => text.chars().map(|c| styled_char(c, v)).collect(), + } +} + +/// Uppercase Greek is upright in TeX; a lone `mi` would italicize it. +fn is_upright_greek(text: &str) -> bool { + let mut chars = text.chars(); + matches!((chars.next(), chars.next()), (Some('Α'..='Ω'), None)) +} + +impl Writer { + /// The root: an `mrow`, or an `mtable` of lines when `\\` appears at + /// the top level (TeX would need an environment; we do what the author + /// meant). + fn root(&mut self, root: &Node) { + let items = match &root.kind { + NodeKind::Row(items) => items.as_slice(), + _ => std::slice::from_ref(root), + }; + if items.iter().any(|n| matches!(n.kind, NodeKind::Break)) { + self.out.push_str(""); + for line in split_at_breaks(items) { + self.out.push_str(""); + self.mrow(line, None); + self.out.push_str(""); + } + self.out.push_str(""); + } else { + self.mrow(items, None); + } + } + + /// `` around a sequence. + fn mrow(&mut self, items: &[Node], variant: Option) { + self.out.push_str(""); + for item in items { + self.node(item, variant); + } + self.out.push_str(""); + } + + /// One node in a position that needs exactly one element (a script + /// base, a fraction part): a single-item row unwraps, and anything + /// that would emit several elements (a sequence, a multi-token run, a + /// function name with its application operator) gets an `mrow`. + fn arg(&mut self, node: &Node, variant: Option) { + match &node.kind { + NodeKind::Row(items) if items.len() == 1 => self.arg(&items[0], variant), + NodeKind::Row(items) => self.mrow(items, variant), + _ if !emits_one_element(node) => { + self.out.push_str(""); + self.node(node, variant); + self.out.push_str(""); + } + _ => self.node(node, variant), + } + } + + fn run(&mut self, text: &str, variant: Option) { + for piece in split_run(text, None) { + match piece.kind { + PieceKind::Identifier => self.mi(&piece.text, variant), + PieceKind::Number => { + let _ = write!( + self.out, + "{}", + escape_text(&styled(&piece.text, variant)) + ); + } + PieceKind::Operator => { + // TeX's `-` is a minus sign in math; a hyphen renders + // short. `'` outside an attachment is a prime. + let text = match piece.text.as_str() { + "-" => "\u{2212}", + "'" => "′", + other => other, + }; + self.mo(text, variant, false); + } + } + } + } + + fn mi(&mut self, text: &str, variant: Option) { + let upright = matches!(variant, Some(Variant::Roman)) || is_upright_greek(text); + if upright && text.chars().count() == 1 { + let _ = write!( + self.out, + r#"{}"#, + escape_text(text) + ); + } else { + let _ = write!(self.out, "{}", escape_text(&styled(text, variant))); + } + } + + /// An operator; `fence` marks a `\left…\right` delimiter, which + /// stretches. Bare delimiter characters explicitly do not. + fn mo(&mut self, text: &str, variant: Option, fence: bool) { + let bare = !fence + && text.chars().count() == 1 + && text + .chars() + .next() + .is_some_and(|c| BARE_DELIMITERS.contains(c)); + let attrs = if fence { + r#" stretchy="true""# + } else if bare { + r#" stretchy="false""# + } else { + "" + }; + let _ = write!( + self.out, + "{}", + escape_text(&styled(text, variant)) + ); + } + + fn sym(&mut self, text: &str, variant: Option) { + let alphabetic = text.chars().all(|c| c.is_alphabetic()); + if alphabetic { + self.mi(text, variant); + } else { + self.mo(text, variant, false); + } + } + + fn under_over(&self, limits: LimLoc, operator: &str) -> bool { + match limits { + LimLoc::UndOvr => true, + LimLoc::SubSup => false, + LimLoc::Auto => { + let side = operator + .chars() + .next() + .is_some_and(|c| SIDE_LIMIT_OPERATORS.contains(c)); + self.mode == Mode::Display && !side + } + } + } + + /// Attach `sub`/`sup` to an already-emitted-by-closure base as scripts + /// (`msub`…) or limits (`munder`…). + fn attach( + &mut self, + base: impl FnOnce(&mut Self), + sub: Option<&Node>, + sup: Option<&Node>, + under_over: bool, + variant: Option, + ) { + let tag = match (sub.is_some(), sup.is_some(), under_over) { + (false, false, _) => { + base(self); + return; + } + (true, true, false) => "msubsup", + (true, false, false) => "msub", + (false, true, false) => "msup", + (true, true, true) => "munderover", + (true, false, true) => "munder", + (false, true, true) => "mover", + }; + let _ = write!(self.out, "<{tag}>"); + base(self); + if let Some(sub) = sub { + self.arg(sub, variant); + } + if let Some(sup) = sup { + self.arg(sup, variant); + } + let _ = write!(self.out, ""); + } + + fn nary( + &mut self, + text: &str, + limits: LimLoc, + sub: Option<&Node>, + sup: Option<&Node>, + variant: Option, + ) { + let under_over = self.under_over(limits, text); + // `movablelimits` is what lets the operator dictionary move limits + // to the side inline; when the author forced `\limits`, pin them. + let attrs = if limits == LimLoc::UndOvr { + r#" movablelimits="false""# + } else { + "" + }; + let text = text.to_string(); + self.attach( + |w| { + let _ = write!(w.out, "{}", escape_text(&text)); + }, + sub, + sup, + under_over, + variant, + ); + } + + fn func( + &mut self, + name: &str, + limits: LimLoc, + sub: Option<&Node>, + sup: Option<&Node>, + variant: Option, + ) { + let under_over = self.under_over(limits, name); + let name = name.to_string(); + self.attach( + |w| { + let _ = write!(w.out, "{}", escape_text(&name)); + }, + sub, + sup, + under_over, + variant, + ); + // U+2061 FUNCTION APPLICATION: invisible, but it tells layout and + // assistive technology that `sin x` is an application. + self.out.push_str("\u{2061}"); + } + + fn group_chr(&mut self, text: &str, pos: Pos, body: &Node, variant: Option) { + let (tag, attr) = match pos { + Pos::Top => ("mover", "accent"), + Pos::Bot => ("munder", "accentunder"), + }; + let _ = write!(self.out, r#"<{tag} {attr}="true">"#); + self.arg(body, variant); + let _ = write!( + self.out, + r#"{}"#, + escape_text(text) + ); + } + + fn scripts( + &mut self, + base: &Node, + sub: Option<&Node>, + sup: Option<&Node>, + limits: LimLoc, + variant: Option, + ) { + match &unwrap_row(base).kind { + NodeKind::Nary { text, .. } => return self.nary(text, limits, sub, sup, variant), + NodeKind::Func { name, .. } => return self.func(name, limits, sub, sup, variant), + // `\underbrace{x}_{n}`: the script is the brace's label. + NodeKind::GroupChr { + text, + pos: Pos::Bot, + body, + } if sub.is_some() && sup.is_none() => { + self.out.push_str(""); + self.group_chr(text, Pos::Bot, body, variant); + self.arg(sub.unwrap(), variant); + self.out.push_str(""); + return; + } + NodeKind::GroupChr { + text, + pos: Pos::Top, + body, + } if sup.is_some() && sub.is_none() => { + self.out.push_str(""); + self.group_chr(text, Pos::Top, body, variant); + self.arg(sup.unwrap(), variant); + self.out.push_str(""); + return; + } + _ => {} + } + self.attach(|w| w.arg(base, variant), sub, sup, false, variant); + } + + fn matrix( + &mut self, + layout: EnvLayout, + delims: Option<&(String, String)>, + rows: &[Vec], + variant: Option, + ) { + let cols = rows.iter().map(Vec::len).max().unwrap_or(1).max(1); + let (open, close, align): (Option<&str>, Option<&str>, Option) = match layout { + EnvLayout::Matrix => ( + delims.map(|(l, _)| l.as_str()), + delims.map(|(_, r)| r.as_str()), + None, + ), + EnvLayout::Cases => (Some("{"), None, Some("left".to_string())), + EnvLayout::Rcases => (None, Some("}"), Some("left".to_string())), + EnvLayout::Aligned => ( + None, + None, + Some( + (0..cols) + .map(|i| if i % 2 == 0 { "right" } else { "left" }) + .collect::>() + .join(" "), + ), + ), + EnvLayout::Gathered => (None, None, None), + }; + let fenced = open.is_some() || close.is_some(); + if fenced { + self.out.push_str(""); + if let Some(l) = open { + self.mo(l, None, true); + } + } + match align { + Some(a) => { + let _ = write!(self.out, r#""#); + } + None => self.out.push_str(""), + } + if rows.is_empty() { + self.out.push_str(""); + } + for row in rows { + self.out.push_str(""); + for cell in row { + self.out.push_str(""); + self.arg(cell, variant); + self.out.push_str(""); + } + for _ in row.len()..cols { + self.out.push_str(""); + } + self.out.push_str(""); + } + self.out.push_str(""); + if fenced { + if let Some(r) = close { + self.mo(r, None, true); + } + self.out.push_str(""); + } + } + + fn node(&mut self, node: &Node, variant: Option) { + use NodeKind::*; + match &node.kind { + Row(items) => self.mrow(items, variant), + Run(t) => self.run(t, variant), + Sym(t) => self.sym(t, variant), + Space(em) => { + let _ = write!( + self.out, + r#""#, + format_em(*em) + ); + } + Frac { style, num, den } => { + let (open, close) = match style { + FracStyle::Display => (r#""#, ""), + FracStyle::Text => (r#""#, ""), + FracStyle::Binom => (r#"("#, ""), + _ => ("", ""), + }; + self.out.push_str(open); + match style { + FracStyle::NoBar | FracStyle::Binom => { + self.out.push_str(r#""#); + } + _ => self.out.push_str(""), + } + self.arg(num, variant); + self.arg(den, variant); + self.out.push_str(""); + if *style == FracStyle::Binom { + self.out.push_str(r#")"#); + } else { + self.out.push_str(close); + } + } + Sqrt { degree, body } => match degree { + Some(d) => { + self.out.push_str(""); + self.arg(body, variant); + self.arg(d, variant); + self.out.push_str(""); + } + None => { + self.out.push_str(""); + self.arg(body, variant); + self.out.push_str(""); + } + }, + Scripts { + base, + sub, + sup, + limits, + } => self.scripts(base, sub.as_deref(), sup.as_deref(), *limits, variant), + Nary { text, limits } => self.nary(text, *limits, None, None, variant), + Func { name, limits } => self.func(name, *limits, None, None, variant), + Delimited { left, right, parts } => { + self.out.push_str(""); + if let Some(l) = left { + self.mo(l, None, true); + } + for (i, part) in parts.iter().enumerate() { + if i > 0 { + self.mo("|", None, true); + } + self.arg(part, variant); + } + if let Some(r) = right { + self.mo(r, None, true); + } + self.out.push_str(""); + } + Accent { text, body } => { + self.out.push_str(r#""#); + self.arg(body, variant); + let _ = write!( + self.out, + "{}", + escape_text(accent_char(text)) + ); + } + Bar { pos, body } => { + let (tag, attr, line) = match pos { + Pos::Top => ("mover", "accent", "\u{203e}"), + Pos::Bot => ("munder", "accentunder", "\u{5f}"), + }; + let _ = write!(self.out, r#"<{tag} {attr}="true">"#); + self.arg(body, variant); + let _ = write!(self.out, r#"{line}"#); + } + GroupChr { text, pos, body } => self.group_chr(text, *pos, body, variant), + LimPos { + pos, + annotation, + body, + } => { + let tag = match pos { + Pos::Top => "mover", + Pos::Bot => "munder", + }; + let _ = write!(self.out, "<{tag}>"); + self.arg(body, variant); + self.arg(annotation, variant); + let _ = write!(self.out, ""); + } + XArrow { text, above, below } => { + let text = text.clone(); + self.attach( + |w| { + let _ = write!(w.out, r#"{}"#, escape_text(&text)); + }, + below.as_deref(), + above.as_deref(), + true, + variant, + ); + } + Style { variant: v, body } => self.arg(body, Some(*v)), + Text { variant: v, text } => { + // Token elements trim edge whitespace; TeX keeps it. + let mut s = styled(text, Some(*v)); + if s.starts_with(' ') { + s.replace_range(..1, "\u{a0}"); + } + if s.ends_with(' ') { + let n = s.len(); + s.replace_range(n - 1.., "\u{a0}"); + } + let _ = write!(self.out, "{}", escape_text(&s)); + } + Phantom { h, v, body } => { + let (open, close) = match (h, v) { + (true, true) => ("", ""), + (true, false) => (r#""#, ""), + (false, true) => (r#""#, ""), + (false, false) => ("", ""), + }; + self.out.push_str(open); + self.out.push_str(""); + self.arg(body, variant); + self.out.push_str(""); + self.out.push_str(close); + } + Cancel(body) => { + self.out + .push_str(r#""#); + self.arg(body, variant); + self.out.push_str(""); + } + Color { color, body } => { + let _ = write!( + self.out, + r#""#, + escape_attr(&css_color(color)) + ); + self.arg(body, variant); + self.out.push_str(""); + } + Matrix { + layout, + delims, + rows, + } => self.matrix(*layout, delims.as_ref(), rows, variant), + Break => { + // Handled at the root; inside environments normalization + // consumed it. + } + Error { verbatim, .. } => { + let _ = write!( + self.out, + "{}", + escape_text(verbatim) + ); + } + } + } +} + +/// Whether [`Writer::node`] emits exactly one element for `node`. Rows +/// are handled by the caller; a function name is followed by its +/// U+2061 application operator, so it (and scripts on it) emit two. +fn emits_one_element(node: &Node) -> bool { + match &node.kind { + NodeKind::Row(items) => items.len() != 1 || emits_one_element(&items[0]), + NodeKind::Run(text) => split_run(text, None).len() == 1, + NodeKind::Func { .. } => false, + NodeKind::Scripts { base, .. } => !matches!(unwrap_row(base).kind, NodeKind::Func { .. }), + NodeKind::Style { body, .. } => emits_one_element(body), + NodeKind::Break => false, + _ => true, + } +} + +/// Look through single-item rows (brace groups around one node). +fn unwrap_row(node: &Node) -> &Node { + match &node.kind { + NodeKind::Row(items) if items.len() == 1 => unwrap_row(&items[0]), + _ => node, + } +} + +fn split_at_breaks(items: &[Node]) -> Vec<&[Node]> { + let mut lines = Vec::new(); + let mut start = 0; + for (i, item) in items.iter().enumerate() { + if matches!(item.kind, NodeKind::Break) { + lines.push(&items[start..i]); + start = i + 1; + } + } + lines.push(&items[start..]); + lines +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn styled_char_covers_every_variant_and_hole() { + use Variant::*; + assert_eq!(styled_char('A', Bold), '𝐀'); + assert_eq!(styled_char('z', Bold), '𝐳'); + assert_eq!(styled_char('7', Bold), '𝟕'); + assert_eq!(styled_char('β', Bold), '𝛃'); + assert_eq!(styled_char('Σ', Bold), '𝚺'); + assert_eq!(styled_char('a', Italic), '𝑎'); + assert_eq!(styled_char('h', Italic), 'ℎ'); + assert_eq!(styled_char('β', BoldItalic), '𝜷'); + assert_eq!(styled_char('P', Script), '𝒫'); + for (c, expected) in [ + ('B', 'ℬ'), + ('E', 'ℰ'), + ('F', 'ℱ'), + ('H', 'ℋ'), + ('I', 'ℐ'), + ('L', 'ℒ'), + ('M', 'ℳ'), + ('R', 'ℛ'), + ('e', 'ℯ'), + ('g', 'ℊ'), + ('o', 'ℴ'), + ] { + assert_eq!(styled_char(c, Script), expected, "script {c}"); + } + assert_eq!(styled_char('A', Fraktur), '𝔄'); + for (c, expected) in [('C', 'ℭ'), ('H', 'ℌ'), ('I', 'ℑ'), ('R', 'ℜ'), ('Z', 'ℨ')] + { + assert_eq!(styled_char(c, Fraktur), expected, "fraktur {c}"); + } + assert_eq!(styled_char('A', DoubleStruck), '𝔸'); + assert_eq!(styled_char('1', DoubleStruck), '𝟙'); + for (c, expected) in [ + ('C', 'ℂ'), + ('H', 'ℍ'), + ('N', 'ℕ'), + ('P', 'ℙ'), + ('Q', 'ℚ'), + ('R', 'ℝ'), + ('Z', 'ℤ'), + ] { + assert_eq!(styled_char(c, DoubleStruck), expected, "double-struck {c}"); + } + assert_eq!(styled_char('A', Sans), '𝖠'); + assert_eq!(styled_char('0', Sans), '𝟢'); + assert_eq!(styled_char('a', Mono), '𝚊'); + assert_eq!(styled_char('9', Mono), '𝟿'); + // No glyph for the variant: unchanged. + assert_eq!(styled_char('3', Script), '3'); + assert_eq!(styled_char('+', Bold), '+'); + assert_eq!(styled_char('x', Roman), 'x'); + } + + #[test] + fn accents_use_spacing_forms() { + assert_eq!(accent_char("\u{302}"), "ˆ"); + assert_eq!(accent_char("\u{20d7}"), "→"); + assert_eq!(accent_char("\u{20db}"), "\u{20db}"); + } + + #[test] + fn colors_become_css() { + assert_eq!(css_color("red"), "red"); + assert_eq!(css_color("#FF00aa"), "#ff00aa"); + assert_eq!(css_color("00FF00"), "#00ff00"); + } +} diff --git a/crates/quarto-math/src/normalize.rs b/crates/quarto-math/src/normalize.rs index 62da177c1..d8df18509 100644 --- a/crates/quarto-math/src/normalize.rs +++ b/crates/quarto-math/src/normalize.rs @@ -63,6 +63,10 @@ pub struct Problem { #[derive(Debug, Clone)] pub struct Normalized { pub mode: Mode, + /// The math text the tree was built from; every node span indexes it. + /// Writers that annotate their output with the source (MathML's + /// ``) read it from here. + pub text: String, /// Always a `Row` spanning the whole text. pub root: Node, pub problems: Vec, @@ -77,6 +81,7 @@ pub fn normalize(text: &str, mode: Mode, spec: &Spec) -> Normalized { let root = Node::new(NodeKind::Row(items), Some(0..text.len())); Normalized { mode, + text: text.to_string(), root, problems: n.problems, } @@ -101,6 +106,41 @@ fn join(a: Option>, b: Option>) -> Option } } +/// A `Sym` made only of prime marks (`′`, `″`, `‴`, `⁗`). +fn is_prime(node: &Node) -> bool { + matches!(&node.kind, NodeKind::Sym(t) if !t.is_empty() && t.chars().all(|c| matches!(c, '′' | '″' | '‴' | '⁗'))) +} + +/// How many primes a prime `Sym` stands for. +fn prime_count(node: &Node) -> usize { + match &node.kind { + NodeKind::Sym(t) => t + .chars() + .map(|c| match c { + '′' => 1, + '″' => 2, + '‴' => 3, + '⁗' => 4, + _ => 0, + }) + .sum(), + _ => 0, + } +} + +/// The glyph for `n` primes: Unicode has single to quadruple, longer runs +/// spell out the rest. +fn prime_glyph(n: usize) -> String { + match n { + 0 => String::new(), + 1 => "′".to_string(), + 2 => "″".to_string(), + 3 => "‴".to_string(), + 4 => "⁗".to_string(), + more => "⁗".to_string() + &prime_glyph(more - 4), + } +} + fn span_of_nodes(nodes: &[Node]) -> Option> { nodes.iter().fold(None, |acc, n| join(acc, n.span.clone())) } @@ -833,10 +873,25 @@ impl<'a> Normalizer<'a> { } } let script_span = span_of_nodes(&script_items); - let script = unwrap_single(script_items, script_span); let base = base.unwrap_or_else(|| Node::new(NodeKind::Row(vec![]), None)); - let Some(op) = op else { - return base; + let (op, script) = match op { + Some(op) => (op, unwrap_single(script_items, script_span)), + None => { + // No `^`/`_`: this is mitex's shape for `f'`. The prime is + // a superscript (`f'` ≡ `f^{\prime}`); several merge into + // one glyph. Anything else here is unexpected; keep it + // rather than drop it. + let primes = script_items.iter().filter(|n| is_prime(n)).count(); + if primes == 0 || primes != script_items.len() { + let mut items = vec![base]; + items.extend(script_items); + return Node::new(NodeKind::Row(items), span); + } + ( + '^', + Node::new(NodeKind::Sym(prime_glyph(primes)), script_span), + ) + } }; // Fold onto an existing Scripts node from the inner attachment. @@ -845,10 +900,26 @@ impl<'a> Normalizer<'a> { match &mut target.kind { NodeKind::Scripts { sub, sup, .. } => { let slot = if op == '^' { sup } else { sub }; - if slot.is_none() { - *slot = Some(Box::new(script)); - } else { - rejected = Some(script); + match slot.as_deref_mut() { + None => *slot = Some(Box::new(script)), + // `f'^2` is `f^{\prime 2}`, and `f''` folds prime onto + // prime: the superscript slot is shared, not doubled. + Some(existing) if op == '^' && is_prime(existing) => { + let merged_span = match (&existing.span, &script.span) { + (Some(a), Some(b)) => Some(a.start.min(b.start)..a.end.max(b.end)), + _ => None, + }; + if is_prime(&script) { + let count = prime_count(existing) + prime_count(&script); + existing.kind = NodeKind::Sym(prime_glyph(count)); + existing.span = merged_span; + } else { + let prime = + std::mem::replace(existing, Node::new(NodeKind::Row(vec![]), None)); + *existing = Node::new(NodeKind::Row(vec![prime, script]), merged_span); + } + } + Some(_) => rejected = Some(script), } target.span = span.clone(); } diff --git a/crates/quarto-math/tests/integration/main.rs b/crates/quarto-math/tests/integration/main.rs index d324f3b1b..d2931a125 100644 --- a/crates/quarto-math/tests/integration/main.rs +++ b/crates/quarto-math/tests/integration/main.rs @@ -4,6 +4,7 @@ pub mod convert; pub mod diagnostics; pub mod fixture_corpus; +pub mod mathml; pub mod normalize; pub mod omml; pub mod omml_schema; diff --git a/crates/quarto-math/tests/integration/mathml.rs b/crates/quarto-math/tests/integration/mathml.rs new file mode 100644 index 000000000..2bc58773d --- /dev/null +++ b/crates/quarto-math/tests/integration/mathml.rs @@ -0,0 +1,522 @@ +//! `MathAst` → MathML Core (bd-9z83tcv0). +//! +//! Every fixture is snapshotted, and the whole corpus is checked for +//! *validity* the way the Typst corpus is checked by compiling: each +//! output must be well-formed XML, use only elements MathML Core defines +//! (plus `menclose`, decision 3 of the plan), give every fixed-arity +//! element exactly the children it takes, and never set `mathvariant` to +//! anything but `normal`. Then the syntax choices are asserted directly. + +use std::collections::BTreeSet; + +use quick_xml::Reader; +use quick_xml::events::Event; + +use quarto_math::mathml::to_mathml; +use quarto_math::normalize::{Mode, normalize}; +use quarto_math::spec::Spec; + +use crate::fixture_corpus::{Fixture, load_all}; + +fn mml(text: &str) -> String { + to_mathml(&normalize(text, Mode::Inline, Spec::builtin())) +} + +fn mml_display(text: &str) -> String { + to_mathml(&normalize(text, Mode::Display, Spec::builtin())) +} + +/// The body between `` and ` String { + let full = mml(text); + inner(&full) +} + +fn body_display(text: &str) -> String { + inner(&mml_display(text)) +} + +fn inner(full: &str) -> String { + let start = full.find("").expect("semantics") + "".len(); + let end = full.find(" Mode { + if fx.display { + Mode::Display + } else { + Mode::Inline + } +} + +fn assert_contains(haystack: &str, needle: &str) { + assert!( + haystack.contains(needle), + "expected {needle:?} in:\n{haystack}" + ); +} + +// --------------------------------------------------------------------------- +// Validity +// --------------------------------------------------------------------------- + +/// Elements MathML Core defines, plus `menclose` (decision 3). +const CORE_ELEMENTS: &[&str] = &[ + "math", + "semantics", + "annotation", + "mrow", + "mi", + "mn", + "mo", + "mtext", + "mspace", + "ms", + "msub", + "msup", + "msubsup", + "munder", + "mover", + "munderover", + "mfrac", + "msqrt", + "mroot", + "mtable", + "mtr", + "mtd", + "mstyle", + "mpadded", + "mphantom", + "merror", + "menclose", +]; + +/// Elements whose child count MathML fixes. +fn required_children(name: &str) -> Option { + Some(match name { + "mfrac" | "msub" | "msup" | "munder" | "mover" | "mroot" => 2, + "msubsup" | "munderover" => 3, + _ => return None, + }) +} + +/// Check one `` document; returns the problems found. +fn validity_problems(xml: &str) -> Vec { + let mut problems = Vec::new(); + let mut reader = Reader::from_str(xml); + // (element name, children so far) + let mut stack: Vec<(String, usize)> = Vec::new(); + let count_child = |stack: &mut Vec<(String, usize)>| { + if let Some(top) = stack.last_mut() { + top.1 += 1; + } + }; + loop { + match reader.read_event() { + Ok(ev @ (Event::Start(_) | Event::Empty(_))) => { + let (e, self_closing) = match ev { + Event::Start(e) => (e, false), + Event::Empty(e) => (e, true), + _ => unreachable!(), + }; + let name = String::from_utf8_lossy(e.name().as_ref()).to_string(); + if !CORE_ELEMENTS.contains(&name.as_str()) { + problems.push(format!("non-Core element <{name}>")); + } + for attr in e.attributes().flatten() { + let key = String::from_utf8_lossy(attr.key.as_ref()).to_string(); + let value = String::from_utf8_lossy(&attr.value).to_string(); + if key == "mathvariant" && value != "normal" { + problems.push(format!("mathvariant={value:?} on <{name}> is not Core")); + } + if key == "mathvariant" && name != "mi" { + problems.push(format!("mathvariant on <{name}> (Core allows it on mi)")); + } + } + count_child(&mut stack); + if !self_closing { + stack.push((name, 0)); + } + } + Ok(Event::End(e)) => { + let name = String::from_utf8_lossy(e.name().as_ref()).to_string(); + let (open, children) = stack.pop().expect("balanced"); + assert_eq!(open, name, "mismatched close tag"); + if let Some(n) = required_children(&name) + && children != n + { + problems.push(format!("<{name}> has {children} children, needs {n}")); + } + } + Ok(Event::Text(_)) => {} + Ok(Event::Eof) => break, + Ok(_) => {} + Err(e) => { + problems.push(format!("not well-formed: {e}")); + break; + } + } + } + problems +} + +#[test] +fn every_fixture_is_valid_mathml_core() { + let mut failures = Vec::new(); + let mut elements = BTreeSet::new(); + for fx in load_all() { + let n = normalize(&fx.text, mode_of(&fx), Spec::builtin()); + let xml = to_mathml(&n); + let problems = validity_problems(&xml); + if !problems.is_empty() { + failures.push(format!( + "{}:\n {}\n {xml}", + fx.id(), + problems.join("\n ") + )); + } + let mut reader = Reader::from_str(&xml); + while let Ok(ev) = reader.read_event() { + match ev { + Event::Start(e) | Event::Empty(e) => { + elements.insert(String::from_utf8_lossy(e.name().as_ref()).to_string()); + } + Event::Eof => break, + _ => {} + } + } + } + assert!( + failures.is_empty(), + "{} fixture(s) produced invalid MathML:\n{}", + failures.len(), + failures.join("\n") + ); + // The corpus exercises the structural elements, not just tokens. + for must in [ + "mfrac", + "msqrt", + "mroot", + "msubsup", + "munderover", + "mtable", + "mover", + ] { + assert!(elements.contains(must), "corpus never emitted <{must}>"); + } +} + +#[test] +fn snapshot_every_fixture() { + for fx in load_all() { + let n = normalize(&fx.text, mode_of(&fx), Spec::builtin()); + let name = format!("mathml__{}", fx.id().replace('/', "__").replace('.', "_")); + insta::assert_snapshot!(name, to_mathml(&n)); + } +} + +// --------------------------------------------------------------------------- +// Envelope +// --------------------------------------------------------------------------- + +#[test] +fn envelope_carries_display_and_the_source_tex() { + let inline = mml("x"#)); + assert!(inline.ends_with( + r#"x<y"# + )); + let display = mml_display("x"); + assert!(display.starts_with( + r#""# + )); +} + +// --------------------------------------------------------------------------- +// Tokens +// --------------------------------------------------------------------------- + +#[test] +fn runs_split_into_mi_mn_mo() { + assert_eq!(body("4ac"), "4ac"); + assert_eq!(body("3.14"), "3.14"); + assert_eq!( + body("x>0"), + "x>0" + ); + assert_eq!( + body("a-b"), + "a\u{2212}b" + ); + // A prime is a superscript (`f'` ≡ `f^{\prime}`), so it scripts. + assert_eq!(body("f'"), "f"); + assert_eq!( + body("x'^2"), + "x2" + ); +} + +#[test] +fn symbols_classify_by_alphabet() { + assert_eq!( + body(r"\alpha \to \infty"), + "α" + ); + // Uppercase Greek is upright in TeX. + assert_eq!( + body(r"\Gamma"), + r#"Γ"# + ); + assert_eq!( + body(r"a \leq b"), + "ab" + ); +} + +#[test] +fn bare_delimiters_do_not_stretch_but_left_right_fences_do() { + assert_eq!( + body("(x)"), + r#"(x)"# + ); + assert_eq!( + body(r"\left( x \right)"), + r#"(x)"# + ); + assert_eq!( + body(r"\left. x \right|"), + r#"x|"# + ); + assert_contains( + &body(r"\left\{ x \middle| y \right\}"), + r#"{x|y}"#, + ); +} + +#[test] +fn spaces_become_mspace_including_negative() { + assert_contains(&body(r"a\,b"), r#""#); + assert_contains(&body(r"a\qquad b"), r#""#); + assert_contains(&body(r"a\!b"), r#""#); +} + +#[test] +fn text_keeps_edge_spaces_and_escapes() { + assert_eq!( + body(r"\text{ if }"), + "\u{a0}if\u{a0}" + ); + assert_eq!(body(r"\text{aa<b"); + assert_eq!(body(r"\textbf{ok}"), "𝐨𝐤"); +} + +// --------------------------------------------------------------------------- +// Structure +// --------------------------------------------------------------------------- + +#[test] +fn fractions_roots_and_scripts() { + assert_eq!( + body(r"\frac{a}{b}"), + "ab" + ); + assert_eq!( + body(r"\frac{a+b}{2}"), + "a+b2" + ); + assert_eq!( + body(r"\dfrac{a}{b}"), + r#"ab"# + ); + assert_eq!( + body(r"\binom{n}{k}"), + r#"(nk)"# + ); + assert_eq!(body(r"\sqrt{x}"), "x"); + assert_eq!( + body(r"\sqrt[3]{x}"), + "x3" + ); + assert_eq!( + body("x^2"), + "x2" + ); + assert_eq!( + body("x_i^2"), + "xi2" + ); + assert_eq!( + body("x_{i+1}"), + "xi+1" + ); + // A multi-token run as a base is grouped. + assert_eq!( + body("{ab}^2"), + "ab2" + ); +} + +#[test] +fn big_operators_place_limits_by_mode() { + assert_eq!( + body_display(r"\sum_{i=1}^{n} i"), + "i=1ni" + ); + assert_eq!( + body(r"\sum_{i=1}^{n} i"), + "i=1ni" + ); + // Integrals keep side limits even in display math. + assert_eq!( + body_display(r"\int_0^1 f"), + "01f" + ); + // `\limits` pins them under/over regardless of mode. + assert_eq!( + body(r"\sum\limits_{i} x"), + r#"ix"# + ); + assert_eq!( + body_display(r"\sum\nolimits_{i} x"), + "ix" + ); +} + +#[test] +fn functions_get_function_application() { + assert_eq!( + body(r"\sin x"), + "sin\u{2061}x" + ); + assert_eq!( + body_display(r"\lim_{x \to 0} f"), + "limx0\u{2061}f" + ); + // The spec pins `\lim` to under/over limits (`limits: und-ovr`), so + // inline math keeps them under too; `auto` functions follow the mode. + assert_eq!( + body(r"\lim_{x \to 0} f"), + "limx0\u{2061}f" + ); + assert_eq!( + body(r"\operatorname{argmax}_x f"), + "argmaxx\u{2061}f" + ); +} + +#[test] +fn accents_bars_braces_and_stacks() { + assert_eq!( + body(r"\hat{x}"), + r#"xˆ"# + ); + assert_eq!( + body(r"\vec{v}"), + r#"v"# + ); + assert_eq!( + body(r"\overline{x+y}"), + r#"x+y"# + ); + assert_eq!( + body(r"\underline{x}"), + r#"x_"# + ); + assert_eq!( + body(r"\underbrace{a+b}_{n}"), + r#"a+bn"# + ); + assert_eq!( + body(r"\overbrace{a+b}"), + r#"a+b"# + ); + assert_eq!( + body(r"\overset{!}{=}"), + "=!" + ); + assert_eq!( + body(r"a \xrightarrow{f} b"), + r#"afb"# + ); +} + +#[test] +fn styles_map_into_the_alphanumeric_block() { + assert_eq!(body(r"\mathbf{x}"), "𝐱"); + assert_eq!( + body(r"\mathbf{ab}"), + "𝐚𝐛" + ); + assert_eq!(body(r"\mathbb{R}"), ""); + assert_eq!(body(r"\mathcal{P}"), "𝒫"); + assert_eq!(body(r"\mathfrak{g}"), "𝔤"); + assert_eq!(body(r"\boldsymbol{\beta}"), "𝜷"); + assert_eq!( + body(r"\mathrm{d}x"), + r#"dx"# + ); + assert_eq!(body(r"\mathbf{2}"), "𝟐"); +} + +#[test] +fn phantoms_cancel_and_color() { + assert_eq!( + body(r"\phantom{x}"), + "x" + ); + assert_eq!( + body(r"\hphantom{x}"), + r#"x"# + ); + assert_eq!( + body(r"\vphantom{x}"), + r#"x"# + ); + assert_eq!( + body(r"\cancel{x}"), + r#"x"# + ); + assert_eq!( + body(r"\textcolor{red}{x}"), + r#"x"# + ); +} + +#[test] +fn environments_become_tables() { + assert_eq!( + body_display("\\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix}"), + r#"(abcd)"# + ); + assert_eq!( + body_display("\\begin{matrix} a \\\\ b \\end{matrix}"), + "ab" + ); + assert_eq!( + body_display("\\begin{cases} x & x \\geq 0 \\\\ -x & x < 0 \\end{cases}"), + r#"{xx0xx<0"# + ); + assert_eq!( + body_display("\\begin{aligned} a &= b \\\\ c &= d \\end{aligned}"), + r#"a=bc=d"# + ); + assert_eq!( + body_display("\\begin{gathered} a \\\\ b \\end{gathered}"), + "ab" + ); + // A top-level `\\` becomes a table of lines. + assert_eq!( + body_display(r"a \\ b"), + "ab" + ); +} + +#[test] +fn errors_render_as_merror() { + assert_eq!( + body(r"x \foo y"), + r"x\fooy" + ); +} diff --git a/crates/quarto-math/tests/integration/normalize.rs b/crates/quarto-math/tests/integration/normalize.rs index 91294afba..9921af7e8 100644 --- a/crates/quarto-math/tests/integration/normalize.rs +++ b/crates/quarto-math/tests/integration/normalize.rs @@ -675,3 +675,92 @@ fn macros_expand_before_normalization() { assert!(saw, "{}", n.root); assert!(n.problems.is_empty(), "{}", rendered(&n)); } + +// --------------------------------------------------------------------------- +// Primes (bd-9z83tcv0 found the normalizer dropping them) +// --------------------------------------------------------------------------- + +fn scripts(node: &Node) -> (&Node, Option<&Node>, Option<&Node>) { + match &node.kind { + NodeKind::Scripts { base, sub, sup, .. } => (base, sub.as_deref(), sup.as_deref()), + other => panic!("expected Scripts, got {other:?}"), + } +} + +fn sym(node: &Node) -> &str { + match &node.kind { + NodeKind::Sym(t) => t, + NodeKind::Row(items) if items.len() == 1 => sym(&items[0]), + other => panic!("expected a Sym, got {other:?}"), + } +} + +/// mitex parses `f'` as an attachment without a `^`/`_` operator; the +/// prime is a superscript, as TeX defines it (`f'` ≡ `f^{\prime}`). +#[test] +fn a_prime_is_a_superscript_prime_symbol() { + let n = norm("f'"); + let (base, sub, sup) = scripts(only(&n)); + assert_eq!(run(base), "f"); + assert!(sub.is_none()); + let sup = sup.expect("prime becomes the superscript"); + assert_eq!(sym(sup), "′"); + assert_eq!(sup.span, Some(1..2), "the prime's span is the apostrophe"); + assert!(n.problems.is_empty(), "{:?}", n.problems); +} + +#[test] +fn repeated_primes_merge_into_one_symbol() { + let n = norm("f''"); + let (_, _, sup) = scripts(only(&n)); + assert_eq!(sym(sup.unwrap()), "″"); + assert_eq!(sup.unwrap().span, Some(1..3)); + let n = norm("f'''"); + let (_, _, sup) = scripts(only(&n)); + assert_eq!(sym(sup.unwrap()), "‴"); + assert!(n.problems.is_empty()); +} + +/// `x'^2` is `x^{\prime 2}` in TeX, not a double superscript. +#[test] +fn a_prime_followed_by_a_superscript_shares_the_slot() { + let n = norm("x'^2"); + let (base, sub, sup) = scripts(only(&n)); + assert_eq!(run(base), "x"); + assert!(sub.is_none()); + let sup = sup.expect("superscript"); + let NodeKind::Row(items) = &sup.kind else { + panic!("expected a Row of prime + script, got {sup}"); + }; + assert_eq!(items.len(), 2); + assert_eq!(sym(&items[0]), "′"); + assert_eq!(run(&items[1]), "2"); + assert!(n.problems.is_empty(), "{:?}", n.problems); +} + +#[test] +fn a_prime_and_a_subscript_fill_both_slots() { + let n = norm("f'_a"); + let (base, sub, sup) = scripts(only(&n)); + assert_eq!(run(base), "f"); + assert_eq!(run(sub.unwrap()), "a"); + assert_eq!(sym(sup.unwrap()), "′"); + assert!(n.problems.is_empty(), "{:?}", n.problems); +} + +#[test] +fn primes_survive_inside_a_sequence() { + let n = norm("f'(x) = f''(x)"); + let primes: Vec<&str> = { + let mut out = Vec::new(); + n.root.walk(&mut |node| { + if let NodeKind::Sym(t) = &node.kind + && (t.starts_with('′') || t.starts_with('″')) + { + out.push(t.as_str()); + } + }); + out + }; + assert_eq!(primes, vec!["′", "″"]); +} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__accent-bare-arg.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__accent-bare-arg.snap new file mode 100644 index 000000000..e92160a93 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__accent-bare-arg.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xˆ\hat x diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__accent-on-accent.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__accent-on-accent.snap new file mode 100644 index 000000000..cf05f3f41 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__accent-on-accent.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xˆˆ\hat{\hat{x}} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__bar.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__bar.snap new file mode 100644 index 000000000..1ba133d3e --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__bar.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x¯\bar{x} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__cancel.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__cancel.snap new file mode 100644 index 000000000..76cebddc3 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__cancel.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x\cancel{x} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__check-breve-acute-grave.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__check-breve-acute-grave.snap new file mode 100644 index 000000000..21e89fd56 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__check-breve-acute-grave.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +aˇb˘c´d`\check{a} \breve{b} \acute{c} \grave{d} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__dot-ddot.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__dot-ddot.snap new file mode 100644 index 000000000..2e0c3a01f --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__dot-ddot.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x˙+x¨\dot{x} + \ddot{x} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__hat.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__hat.snap new file mode 100644 index 000000000..d6ce9b7d9 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__hat.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +βˆ0\hat{\beta}_0 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__mathring.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__mathring.snap new file mode 100644 index 000000000..2797a3d67 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__mathring.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +A˚\mathring{A} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__not.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__not.snap new file mode 100644 index 000000000..ecb39b0d8 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__not.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +aba \not= b diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overbrace.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overbrace.snap new file mode 100644 index 000000000..3b944798c --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overbrace.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a+bn\overbrace{a+b}^{n} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overleftarrow.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overleftarrow.snap new file mode 100644 index 000000000..43388f710 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overleftarrow.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +AB\overleftarrow{AB} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overline.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overline.snap new file mode 100644 index 000000000..caedc0bf5 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overline.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x+y\overline{x+y} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overrightarrow.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overrightarrow.snap new file mode 100644 index 000000000..2a2a70c68 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overrightarrow.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +AB\overrightarrow{AB} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overset-underset.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overset-underset.snap new file mode 100644 index 000000000..afbaf684c --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__overset-underset.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +=!maxn\overset{!}{=} \underset{n}{\max} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__stackrel.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__stackrel.snap new file mode 100644 index 000000000..f5f19fe15 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__stackrel.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +=def\stackrel{def}{=} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__tilde.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__tilde.snap new file mode 100644 index 000000000..a91c28722 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__tilde.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x˜\tilde{x} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__underbrace.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__underbrace.snap new file mode 100644 index 000000000..0c3014839 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__underbrace.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a+bn\underbrace{a+b}_{n} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__underline.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__underline.snap new file mode 100644 index 000000000..2f254034b --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__underline.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x_\underline{x} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__vec.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__vec.snap new file mode 100644 index 000000000..ef4e525f0 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__vec.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +v\vec{v} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__widehat-widetilde.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__widehat-widetilde.snap new file mode 100644 index 000000000..3e707d0fc --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__accents__widehat-widetilde.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +abcˆxyz˜\widehat{abc} \widetilde{xyz} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__comment.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__comment.snap new file mode 100644 index 000000000..75a01b70b --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__comment.snap @@ -0,0 +1,6 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x+yx % a comment ++ y diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__decimal-and-comma.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__decimal-and-comma.snap new file mode 100644 index 000000000..81325005c --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__decimal-and-comma.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +3.14,2.713.14, 2.71 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__digits-adjacent-letters.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__digits-adjacent-letters.snap new file mode 100644 index 000000000..04c89cc1f --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__digits-adjacent-letters.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +2x+10y2x + 10y diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__doubled-braces.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__doubled-braces.snap new file mode 100644 index 000000000..d1cb36192 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__doubled-braces.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +RTn\frac{{RT}}{{n}} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__empty-group.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__empty-group.snap new file mode 100644 index 000000000..02893585e --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__empty-group.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xyx{}y diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__equals-chain.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__equals-chain.snap new file mode 100644 index 000000000..d6f013a9a --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__equals-chain.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a=b=ca = b = c diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__factorial.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__factorial.snap new file mode 100644 index 000000000..6340fb0e8 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__factorial.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +n!n! diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__greek.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__greek.snap new file mode 100644 index 000000000..efb6d9742 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__greek.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +α+β=γ\alpha + \beta = \gamma diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__leading-trailing-space.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__leading-trailing-space.snap new file mode 100644 index 000000000..4b6fd60f1 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__leading-trailing-space.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x+y x + y diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__mixed-brackets-bare.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__mixed-brackets-bare.snap new file mode 100644 index 000000000..7e372a282 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__mixed-brackets-bare.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(a+b][c,d)(a + b] \cdot [c, d) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__multi-line-inline.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__multi-line-inline.snap new file mode 100644 index 000000000..939d44e31 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__multi-line-inline.snap @@ -0,0 +1,6 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a+ba + +b diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__parens-brackets.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__parens-brackets.snap new file mode 100644 index 000000000..56d41d45f --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__parens-brackets.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +f(x)=[a,b]f(x) = [a, b] diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__prime.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__prime.snap new file mode 100644 index 000000000..33f0a5529 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__prime.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +f(x)=f(x)f'(x) = f''(x) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__relation-chain.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__relation-chain.snap new file mode 100644 index 000000000..b918bd64f --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__relation-chain.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a<bcda < b \leq c \neq d diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__single-letter.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__single-letter.snap new file mode 100644 index 000000000..cafe58556 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__single-letter.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xx diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__unicode-input.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__unicode-input.snap new file mode 100644 index 000000000..0aa65ca9a --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__unicode-input.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +αα ≠ ∅ → ∞ diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__word-and-number.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__word-and-number.snap new file mode 100644 index 000000000..7265067d2 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__basic__word-and-number.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x=42x = 42 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__bayes.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__bayes.snap new file mode 100644 index 000000000..2d4405d91 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__bayes.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +P(A|B)=P(B|A)P(A)P(B)P(A|B) = \frac{P(B|A)P(A)}{P(B)} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__black-scholes_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__black-scholes_display.snap new file mode 100644 index 000000000..7b9211c24 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__black-scholes_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +Ct+12σ2S22CC2+rSCS=rC\frac{\partial \mathrm C}{ \partial \mathrm t } + \frac{1}{2}\sigma^{2} \mathrm S^{2} +\frac{\partial^{2} \mathrm C}{\partial \mathrm C^2} + + \mathrm r \mathrm S \frac{\partial \mathrm C}{\partial \mathrm S}\ = + \mathrm r \mathrm C diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__cancel-partial.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__cancel-partial.snap new file mode 100644 index 000000000..ab51f6846 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__cancel-partial.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +g=(vz)g = \left(\cancel{\frac{\partial v}{\partial z}}\right) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__conditional-prob-positive.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__conditional-prob-positive.snap new file mode 100644 index 000000000..14e79fda4 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__conditional-prob-positive.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +P(B)>0P(B) > 0 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__emc2.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__emc2.snap new file mode 100644 index 000000000..50005af61 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__emc2.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +E=mc2E = mc^2 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__exponential-survival.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__exponential-survival.snap new file mode 100644 index 000000000..2953c6193 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__exponential-survival.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +pe=1etλp_e = 1-\mathrm{e}^{-t \lambda} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__ftc_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__ftc_display.snap new file mode 100644 index 000000000..02a36bced --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__ftc_display.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +ddx(axf(u)du)=f(x).\frac{d}{dx}\left( \int_{a}^{x} f(u)\,du\right)=f(x). diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__gauss-law_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__gauss-law_display.snap new file mode 100644 index 000000000..b15a1d468 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__gauss-law_display.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +EdA=Qϵ0\oint \vec{E} \cdot d\vec{A} = \frac{Q}{\epsilon_0} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__gaussian-integral_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__gaussian-integral_display.snap new file mode 100644 index 000000000..b9ba3c6d3 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__gaussian-integral_display.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +ex2dx=π\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__glm-poisson_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__glm-poisson_display.snap new file mode 100644 index 000000000..04f1b6ae3 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__glm-poisson_display.snap @@ -0,0 +1,7 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +log(μi)=xiβ,yiPois(μi),E[yi|xi]=μi\log(\mu_i) \quad = \quad x_i^\top \beta, +\quad y_i \sim \mathrm{Pois}(\mu_i), +\quad E[y_i \, | \, x_i] = \mu_i diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__half-gaussian_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__half-gaussian_display.snap new file mode 100644 index 000000000..b59671593 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__half-gaussian_display.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +0ex2dx=π2\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__index-range.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__index-range.snap new file mode 100644 index 000000000..23c20bb7d --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__index-range.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +i=1,,ni = 1, \dots, n diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__integral-ftc-inline.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__integral-ftc-inline.snap new file mode 100644 index 000000000..73d8b22d7 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__integral-ftc-inline.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +abf(x)dx=F(b)F(a)\int_a^b f(x)\,dx = F(b) - F(a) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__limit-continuity.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__limit-continuity.snap new file mode 100644 index 000000000..daa5df984 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__limit-continuity.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +limxcf(x)=f(c)\lim_{x \to c} f(x) = f(c) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__maxwell-ampere_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__maxwell-ampere_display.snap new file mode 100644 index 000000000..d8df525d6 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__maxwell-ampere_display.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +×B=μ0J+μ0ϵ0Et\nabla \times \vec{B} = \mu_0 \vec{J} + \mu_0 \epsilon_0 \frac{\partial \vec{E}}{\partial t} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__nernst.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__nernst.snap new file mode 100644 index 000000000..24c091188 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__nernst.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +Ecell=EcellRTnlnQ=Ecell0.0592nlogQE_{cell} = E^\circ _{cell} - \frac{{RT}}{{n\Im }}\ln Q = E^\circ _{cell} - \frac{{0.0592}}{n}\log Q diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__newton-gravity.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__newton-gravity.snap new file mode 100644 index 000000000..643bf06fe --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__newton-gravity.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +F=Gm1m2r2F = G\frac{m_1 m_2}{r^2} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__ols-estimate.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__ols-estimate.snap new file mode 100644 index 000000000..34669b31f --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__ols-estimate.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +βˆ=(XX)1Xy\hat{\beta} = (X^\top X)^{-1} X^\top y diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__poisson-pmf.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__poisson-pmf.snap new file mode 100644 index 000000000..3cea97092 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__poisson-pmf.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +p(x)=eλλxx!p(x)=\frac{e^{-\lambda} \lambda^{x}}{x !} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__power-set-unicode.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__power-set-unicode.snap new file mode 100644 index 000000000..4fa19b1fd --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__power-set-unicode.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +𝒫()\mathcal P (\mathbb N) ≠ \varnothing diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__pythagoras.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__pythagoras.snap new file mode 100644 index 000000000..4bf914c8d --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__pythagoras.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a2+b2=c2a^2 + b^2 = c^2 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__quadratic.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__quadratic.snap new file mode 100644 index 000000000..62613ff4c --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__quadratic.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x=b±b24ac2ax = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__regression.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__regression.snap new file mode 100644 index 000000000..09db2beac --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__regression.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +price=βˆ0+βˆ1×area+ϵprice = \hat{\beta}_0 + \hat{\beta}_1 \times area + \epsilon diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__sir-two-groups_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__sir-two-groups_display.snap new file mode 100644 index 000000000..9d312df3a --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__sir-two-groups_display.snap @@ -0,0 +1,12 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +dS1dt=ρβS1(c11I1+c12I2)NdS2dt=βS2(c12I1+c22I2)NdI1dt=ρβS1(c11I1+c12I2)NγI1dI2dt=βS2(c12I1+c22I2)NγI2dR1dt=γI1dR2dt=γI2\begin{aligned} +\frac{dS_1}{dt} &= -\frac{\rho \beta S_1(c_{11} I_1 + c_{12} I_2)}{N} \\ +\frac{dS_2}{dt} &= -\frac{\beta S_2(c_{12} I_1 + c_{22} I_2)}{N} \\ +\frac{dI_1}{dt} &= \frac{ \rho \beta S_1(c_{11} I_1 + c_{12} I_2)}{N} - \gamma I_1 \\ +\frac{dI_2}{dt} &= \frac{\beta S_2(c_{12} I_1 + c_{22} I_2)}{N} - \gamma I_2 \\ +\frac{dR_1}{dt} &= \gamma I_1 \\ +\frac{dR_2}{dt} &= \gamma I_2 +\end{aligned} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__sum-of-set.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__sum-of-set.snap new file mode 100644 index 000000000..b7007aed3 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__sum-of-set.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +i=2{αiβ}\sum_{i = 2}^\infty\{\alpha_i^\beta\} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__weibull_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__weibull_display.snap new file mode 100644 index 000000000..9e5e78980 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__corpus__weibull_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +fX(x)=(αβ)(xβ)α1e(xβ)α;α,β,x>0.f_{X}(x) = \left(\frac{\alpha}{\beta}\right) + \left(\frac{x}{\beta}\right)^{\alpha-1} + e^{-\left(\frac{x}{\beta}\right)^{\alpha}}; + \alpha,\beta,x > 0 . diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__bare-delims.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__bare-delims.snap new file mode 100644 index 000000000..b6882aa7a --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__bare-delims.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +{x}y|z|\{ x \} \langle y \rangle |z| diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__big-delims.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__big-delims.snap new file mode 100644 index 000000000..8b1b3cd54 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__big-delims.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +([{|x|}])\big( \Big[ \bigg\{ \Bigg| x \Bigg| \bigg\} \Big] \big) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__bigl-bigr.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__bigl-bigr.snap new file mode 100644 index 000000000..cb33b2d9c --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__bigl-bigr.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(x)[y]\bigl( x \bigr) \Bigl[ y \Bigr] diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-angle.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-angle.snap new file mode 100644 index 000000000..cf0409ed6 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-angle.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x\left\langle x \right\rangle diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-brace.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-brace.snap new file mode 100644 index 000000000..f1a08216a --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-brace.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +{x}\left\{ x \right\} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-bracket.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-bracket.snap new file mode 100644 index 000000000..88d1761fc --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-bracket.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +[x]\left[ x \right] diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-floor.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-floor.snap new file mode 100644 index 000000000..9d4374f93 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-floor.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +n2\left\lfloor \frac{n}{2} \right\rfloor diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-in-frac.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-in-frac.snap new file mode 100644 index 000000000..2876c1361 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-in-frac.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +ddx(axf(u)du)\frac{d}{dx}\left( \int_{a}^{x} f(u)\,du\right) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-mixed.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-mixed.snap new file mode 100644 index 000000000..c4be971ad --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-mixed.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(x]\left( x \right] diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-nested.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-nested.snap new file mode 100644 index 000000000..1dc2e3a49 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-nested.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +([x])\left( \left[ x \right] \right) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-norm.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-norm.snap new file mode 100644 index 000000000..63404091d --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-norm.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x\left\| x \right\| diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-null-left.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-null-left.snap new file mode 100644 index 000000000..e37bd033f --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-null-left.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(x\left( x \right. diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-null-right.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-null-right.snap new file mode 100644 index 000000000..ce324c5aa --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-null-right.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +dfdx|x=0\left. \frac{df}{dx} \right|_{x=0} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-paren-with-newline_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-paren-with-newline_display.snap new file mode 100644 index 000000000..b6958352e --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-paren-with-newline_display.snap @@ -0,0 +1,7 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(ab)\left( +\frac{a}{b} +\right) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-paren.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-paren.snap new file mode 100644 index 000000000..b93be2395 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-paren.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(ab)\left( \frac{a}{b} \right) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-vert.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-vert.snap new file mode 100644 index 000000000..4ce3a4484 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-vert.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +|x|\left| x \right| diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-with-middle.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-with-middle.snap new file mode 100644 index 000000000..62cf3817f --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-with-middle.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +{x|x>0}\left\{ x \middle| x > 0 \right\} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-with-script.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-with-script.snap new file mode 100644 index 000000000..9ab4a6550 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__left-right-with-script.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(ab)2\left( \frac{a}{b} \right)^2 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__lfloor-lceil.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__lfloor-lceil.snap new file mode 100644 index 000000000..9ece6a2df --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__delimiters__lfloor-lceil.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x+y\lfloor x \rfloor + \lceil y \rceil diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__align-star_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__align-star_display.snap new file mode 100644 index 000000000..63939ae43 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__align-star_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a=bc=d\begin{align*} +a &= b \\ +c &= d +\end{align*} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__align_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__align_display.snap new file mode 100644 index 000000000..f8ed04ae5 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__align_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a=bc=d\begin{align} +a &= b \\ +c &= d +\end{align} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__alignat_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__alignat_display.snap new file mode 100644 index 000000000..b63d28210 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__alignat_display.snap @@ -0,0 +1,7 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a=bc=d\begin{alignat}{2} +a &= b & c &= d +\end{alignat} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__aligned-one-line_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__aligned-one-line_display.snap new file mode 100644 index 000000000..e5404a8cc --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__aligned-one-line_display.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a=bc=d\begin{aligned} a &= b \\ c &= d \end{aligned} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__aligned-three-columns_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__aligned-three-columns_display.snap new file mode 100644 index 000000000..f99ea2ead --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__aligned-three-columns_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a=bc=de=fg=h\begin{aligned} +a &= b & c &= d \\ +e &= f & g &= h +\end{aligned} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__aligned_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__aligned_display.snap new file mode 100644 index 000000000..75598a6d2 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__aligned_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a=bc=d\begin{aligned} +a &= b \\ +c &= d +\end{aligned} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__array-with-lines_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__array-with-lines_display.snap new file mode 100644 index 000000000..3c66637ce --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__array-with-lines_display.snap @@ -0,0 +1,9 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +abcd\begin{array}{c|c} +a & b \\ +\hline +c & d +\end{array} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__array_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__array_display.snap new file mode 100644 index 000000000..9506bfc54 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__array_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +abcd\begin{array}{cc} +a & b \\ +c & d +\end{array} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__bbmatrix_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__bbmatrix_display.snap new file mode 100644 index 000000000..bfba28dd1 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__bbmatrix_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +{abcd}\begin{Bmatrix} +a & b \\ +c & d +\end{Bmatrix} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__bmatrix_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__bmatrix_display.snap new file mode 100644 index 000000000..e4ab8eab6 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__bmatrix_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +[abcd]\begin{bmatrix} +a & b \\ +c & d +\end{bmatrix} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__cases-with-text_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__cases-with-text_display.snap new file mode 100644 index 000000000..af4f1a0f3 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__cases-with-text_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +f(x)={1if xA0otherwisef(x) = \begin{cases} +1 & \text{if } x \in A \\ +0 & \text{otherwise} +\end{cases} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__cases_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__cases_display.snap new file mode 100644 index 000000000..d2782ad19 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__cases_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +{xx0xx<0\begin{cases} +x & x \geq 0 \\ +-x & x < 0 +\end{cases} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__equation_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__equation_display.snap new file mode 100644 index 000000000..0029521b4 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__equation_display.snap @@ -0,0 +1,7 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +E=mc2\begin{equation} +E = mc^2 +\end{equation} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__gathered_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__gathered_display.snap new file mode 100644 index 000000000..3b4f32189 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__gathered_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a=bc=d\begin{gathered} +a = b \\ +c = d +\end{gathered} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-dots_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-dots_display.snap new file mode 100644 index 000000000..6f216f425 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-dots_display.snap @@ -0,0 +1,9 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(a11a1nam1amn)\begin{pmatrix} +a_{11} & \cdots & a_{1n} \\ +\vdots & \ddots & \vdots \\ +a_{m1} & \cdots & a_{mn} +\end{pmatrix} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-empty-cells_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-empty-cells_display.snap new file mode 100644 index 000000000..d3e0d320a --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-empty-cells_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(ad)\begin{pmatrix} +a & \\ +& d +\end{pmatrix} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-ragged_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-ragged_display.snap new file mode 100644 index 000000000..783035132 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-ragged_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(abcd)\begin{pmatrix} +a & b & c \\ +d +\end{pmatrix} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-trailing-row-break_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-trailing-row-break_display.snap new file mode 100644 index 000000000..acd43bac3 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-trailing-row-break_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(abcd)\begin{pmatrix} +a & b \\ +c & d \\ +\end{pmatrix} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-with-frac-cells_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-with-frac-cells_display.snap new file mode 100644 index 000000000..bf7d039f7 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix-with-frac-cells_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(120012)\begin{pmatrix} +\frac{1}{2} & 0 \\ +0 & \frac{1}{2} +\end{pmatrix} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix_display.snap new file mode 100644 index 000000000..c1a29776e --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__matrix_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +abcd\begin{matrix} +a & b \\ +c & d +\end{matrix} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__multline_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__multline_display.snap new file mode 100644 index 000000000..b1933334f --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__multline_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a+b+c\begin{multline} +a + b \\ ++ c +\end{multline} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__nested-env_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__nested-env_display.snap new file mode 100644 index 000000000..26c5b380f --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__nested-env_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +{a=bc=dif x0else\begin{cases} +\begin{aligned} a &= b \\ c &= d \end{aligned} & \text{if } x \\ +0 & \text{else} +\end{cases} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__pmatrix-inline.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__pmatrix-inline.snap new file mode 100644 index 000000000..2c565d1ce --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__pmatrix-inline.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(abcd)\begin{pmatrix} a & b \\ c & d \end{pmatrix} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__pmatrix_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__pmatrix_display.snap new file mode 100644 index 000000000..13a5d550c --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__pmatrix_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(abcd)\begin{pmatrix} +a & b \\ +c & d +\end{pmatrix} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__row-spacing_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__row-spacing_display.snap new file mode 100644 index 000000000..ce87ce030 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__row-spacing_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a=b[2ex]c=d\begin{aligned} +a &= b \\[2ex] +c &= d +\end{aligned} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__smallmatrix.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__smallmatrix.snap new file mode 100644 index 000000000..32f7f0a7a --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__smallmatrix.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(abcd)\left(\begin{smallmatrix} a & b \\ c & d \end{smallmatrix}\right) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__split_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__split_display.snap new file mode 100644 index 000000000..5d916a3fa --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__split_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a=b=c\begin{split} +a &= b \\ +&= c +\end{split} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__substack_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__substack_display.snap new file mode 100644 index 000000000..beadeacc3 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__substack_display.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +i<jj<kaij\sum_{\substack{i<j \\ j<k}} a_{ij} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__vmatrix_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__vmatrix_display.snap new file mode 100644 index 000000000..ee1eccd71 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__vmatrix_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +|abcd|\begin{vmatrix} +a & b \\ +c & d +\end{vmatrix} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__vvmatrix_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__vvmatrix_display.snap new file mode 100644 index 000000000..87f60c1e5 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__environments__vvmatrix_display.snap @@ -0,0 +1,8 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +abcd\begin{Vmatrix} +a & b \\ +c & d +\end{Vmatrix} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__bare-ampersand.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__bare-ampersand.snap new file mode 100644 index 000000000..9ced7a330 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__bare-ampersand.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a&ba & b diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__bare-hash.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__bare-hash.snap new file mode 100644 index 000000000..ee6f0b7a5 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__bare-hash.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +#1#1 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__double-subscript.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__double-subscript.snap new file mode 100644 index 000000000..0ab43a354 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__double-subscript.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x12x_1_2 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__double-superscript.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__double-superscript.snap new file mode 100644 index 000000000..44ef61a00 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__double-superscript.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x23x^2^3 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__empty.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__empty.snap new file mode 100644 index 000000000..5d5e2c7ce --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__empty.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- + diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__env-mismatch.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__env-mismatch.snap new file mode 100644 index 000000000..5106a3463 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__env-mismatch.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +\begin{aligned} x \end{cases}\begin{aligned} x \end{cases} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__left-without-right.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__left-without-right.snap new file mode 100644 index 000000000..251efdf68 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__left-without-right.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(x\left( x\left( x diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__lone-caret.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__lone-caret.snap new file mode 100644 index 000000000..b6f0aeada --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__lone-caret.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +2^2 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__lone-underscore.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__lone-underscore.snap new file mode 100644 index 000000000..6108fbf74 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__lone-underscore.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +i_i diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__macro-arity-mismatch.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__macro-arity-mismatch.snap new file mode 100644 index 000000000..06bd7d342 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__macro-arity-mismatch.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +invalid number of arguments\newcommand{\pr}[2]{P(#1|#2)} \pr{x} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__missing-arg.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__missing-arg.snap new file mode 100644 index 000000000..69908390e --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__missing-arg.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +\frac{a}\frac{a} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__only-whitespace.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__only-whitespace.snap new file mode 100644 index 000000000..df8faf48d --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__only-whitespace.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- + diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__raw-dollar-inside.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__raw-dollar-inside.snap new file mode 100644 index 000000000..b2b2b17d3 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__raw-dollar-inside.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xyx $ y diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__recursive-macro.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__recursive-macro.snap new file mode 100644 index 000000000..894fa4afd --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__recursive-macro.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +macro expansion limit exceeded\newcommand{\loop}{\loop} \loop diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__right-without-left.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__right-without-left.snap new file mode 100644 index 000000000..7655d962c --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__right-without-left.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x\right)x \right) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__sqrt-index-no-body.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__sqrt-index-no-body.snap new file mode 100644 index 000000000..46feaaa4e --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__sqrt-index-no-body.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +\sqrt[3]\sqrt[3] diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__stray-end.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__stray-end.snap new file mode 100644 index 000000000..a7d24c4d5 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__stray-end.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xalignedx \end{aligned} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__trailing-backslash.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__trailing-backslash.snap new file mode 100644 index 000000000..1789b7915 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__trailing-backslash.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xx \ diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unbalanced-close.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unbalanced-close.snap new file mode 100644 index 000000000..93f478372 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unbalanced-close.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a}a} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unbalanced-in-macro-body.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unbalanced-in-macro-body.snap new file mode 100644 index 000000000..e8bcf7fb4 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unbalanced-in-macro-body.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +\frac{a}\newcommand{\bad}{\frac{a}} \bad diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unbalanced-left-right-nesting.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unbalanced-left-right-nesting.snap new file mode 100644 index 000000000..c11db81be --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unbalanced-left-right-nesting.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +([x)]\left( \left[ x \right) \right] diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unbalanced-open.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unbalanced-open.snap new file mode 100644 index 000000000..afabfc090 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unbalanced-open.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +ab{b\frac{a}{b diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unclosed-env.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unclosed-env.snap new file mode 100644 index 000000000..7df362c67 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unclosed-env.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +\begin{aligned} x\begin{aligned} x diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__undefined-blackboard-shortcuts.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__undefined-blackboard-shortcuts.snap new file mode 100644 index 000000000..482d435c7 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__undefined-blackboard-shortcuts.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +\C\R \N \Z \Q \C diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__undefined-macro-argument.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__undefined-macro-argument.snap new file mode 100644 index 000000000..96e10ded4 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__undefined-macro-argument.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +P()\newcommand{\pr}[1]{P(#2)} \pr{x} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unknown-command-noarg.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unknown-command-noarg.snap new file mode 100644 index 000000000..a62ba9a65 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unknown-command-noarg.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x\fooyx \foo y diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unknown-command.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unknown-command.snap new file mode 100644 index 000000000..edacc1114 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unknown-command.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +\foox\foo{x} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unknown-env.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unknown-env.snap new file mode 100644 index 000000000..c9aaf4925 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__errors__unknown-env.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +\begin{foo} x \end{foo}x\begin{foo} x \end{foo} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__binom.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__binom.snap new file mode 100644 index 000000000..678e2c62e --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__binom.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(nk)\binom{n}{k} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__cfrac.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__cfrac.snap new file mode 100644 index 000000000..57cf3aea0 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__cfrac.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +11+1x\cfrac{1}{1+\cfrac{1}{x}} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__dfrac-tfrac.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__dfrac-tfrac.snap new file mode 100644 index 000000000..9bbbafe87 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__dfrac-tfrac.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +ab+cd\dfrac{a}{b} + \tfrac{c}{d} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac-command-args.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac-command-args.snap new file mode 100644 index 000000000..b379757db --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac-command-args.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +αβ\frac\alpha\beta diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac-in-sup.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac-in-sup.snap new file mode 100644 index 000000000..c0ab1f6dc --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac-in-sup.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +e12e^{\frac{1}{2}} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac-nested.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac-nested.snap new file mode 100644 index 000000000..6f87b739d --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac-nested.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +11+1x\frac{1}{1+\frac{1}{x}} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac-single-char-args.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac-single-char-args.snap new file mode 100644 index 000000000..d8753e2ae --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac-single-char-args.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +12\frac12 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac.snap new file mode 100644 index 000000000..148fe7c55 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__frac.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +ab\frac{a}{b} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__over-bare.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__over-bare.snap new file mode 100644 index 000000000..2715d76af --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__over-bare.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a+bc+da+b \over c+d diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__over-grouped.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__over-grouped.snap new file mode 100644 index 000000000..a3fb57968 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__fractions__over-grouped.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +ab{a \over b} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__def-with-arg.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__def-with-arg.snap new file mode 100644 index 000000000..d4c2e2ed8 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__def-with-arg.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +\pr#1P(#1)\prx\def\pr#1{P(#1)} \pr{x} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__def.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__def.snap new file mode 100644 index 000000000..bb4c7487e --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__def.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +\def\R{\mathbb{R}} \R diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-arg-with-braces.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-arg-with-braces.snap new file mode 100644 index 000000000..f3cb43c67 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-arg-with-braces.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +P(ab)\newcommand{\pr}[1]{P(#1)} \pr{\frac{a}{b}} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-defined-not-used.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-defined-not-used.snap new file mode 100644 index 000000000..415ad1cc2 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-defined-not-used.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x\newcommand{\R}{\mathbb{R}} x diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-in-scripts.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-in-scripts.snap new file mode 100644 index 000000000..fe3753572 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-in-scripts.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x12\newcommand{\x}{x_1} \x^2 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-multi-line_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-multi-line_display.snap new file mode 100644 index 000000000..e99c53aa5 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-multi-line_display.snap @@ -0,0 +1,6 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +P(x>0)\newcommand{\pr}[1]{P(#1)} +\pr{x>0} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-used-twice.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-used-twice.snap new file mode 100644 index 000000000..1aab9458c --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__macro-used-twice.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +P(A)+P(B)\newcommand{\pr}[1]{P(#1)} \pr{A} + \pr{B} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-args.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-args.snap new file mode 100644 index 000000000..6a16d7969 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-args.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +P(x>0)\newcommand{\pr}[1]{P(#1)} \pr{x>0} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-nested-use.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-nested-use.snap new file mode 100644 index 000000000..bf5c2ab53 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-nested-use.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xn\newcommand{\R}{\mathbb{R}} \newcommand{\Rn}{\R^n} x \in \Rn diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-noarg.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-noarg.snap new file mode 100644 index 000000000..4170bbfc3 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-noarg.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x\newcommand{\R}{\mathbb{R}} x \in \R diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-optional-arg.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-optional-arg.snap new file mode 100644 index 000000000..f6118e0dc --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-optional-arg.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +\|_{#1}x[1]y\newcommand{\norm}[2][2]{\|#2\|_{#1}} \norm{x} \norm[1]{y} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-star.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-star.snap new file mode 100644 index 000000000..7ee5ba7de --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-star.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +\newcommand*{\R}{\mathbb{R}} \R diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-two-args.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-two-args.snap new file mode 100644 index 000000000..65085c804 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__newcommand-two-args.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +u,v\newcommand{\inner}[2]{\langle #1, #2 \rangle} \inner{u}{v} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__renewcommand.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__renewcommand.snap new file mode 100644 index 000000000..7b112cb36 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__macros__renewcommand.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +𝐯\renewcommand{\vec}[1]{\mathbf{#1}} \vec{v} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__bigcup-bigcap.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__bigcup-bigcap.snap new file mode 100644 index 000000000..53ba8893e --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__bigcup-bigcap.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +iAijBj\bigcup_{i} A_i \cap \bigcap_{j} B_j diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__coprod-bigoplus.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__coprod-bigoplus.snap new file mode 100644 index 000000000..7b4f33897 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__coprod-bigoplus.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +iXijYj\coprod_i X_i \oplus \bigoplus_j Y_j diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__det-dim-ker.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__det-dim-ker.snap new file mode 100644 index 000000000..f096cdc30 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__det-dim-ker.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +detA+dimV+kerT\det A + \dim V + \ker T diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__function-with-parens.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__function-with-parens.snap new file mode 100644 index 000000000..72e907f0c --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__function-with-parens.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +sin(x)+cos2(x)\sin(x) + \cos^2(x) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__iint-iiint.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__iint-iiint.snap new file mode 100644 index 000000000..4b0bb0e5f --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__iint-iiint.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +Df+Vg\iint_D f + \iiint_V g diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__int-limits.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__int-limits.snap new file mode 100644 index 000000000..c1971abf7 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__int-limits.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +0ex2dx\int_0^\infty e^{-x^2}\,dx diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__int-no-limits.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__int-no-limits.snap new file mode 100644 index 000000000..3ad847c44 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__int-no-limits.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +f\int f diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__lim.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__lim.snap new file mode 100644 index 000000000..920e5e741 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__lim.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +limx0f(x)\lim_{x \to 0} f(x) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__log-ln-sin.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__log-ln-sin.snap new file mode 100644 index 000000000..1ed223edb --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__log-ln-sin.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +logx+lny+sinθ\log x + \ln y + \sin \theta diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__max-min.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__max-min.snap new file mode 100644 index 000000000..66657b870 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__max-min.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +maxiai+minjbj\max_{i} a_i + \min_{j} b_j diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__oint.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__oint.snap new file mode 100644 index 000000000..032c68709 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__oint.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +EdA\oint \vec{E} \cdot d\vec{A} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__operatorname-star.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__operatorname-star.snap new file mode 100644 index 000000000..2cdd94b8a --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__operatorname-star.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +argmaxxf(x)\operatorname*{argmax}_x f(x) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__operatorname.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__operatorname.snap new file mode 100644 index 000000000..587bc2b7e --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__operatorname.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +argmaxxf(x)\operatorname{argmax}_x f(x) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__prod-limits-cmd.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__prod-limits-cmd.snap new file mode 100644 index 000000000..0012029d8 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__prod-limits-cmd.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +ixi\prod\limits_{i} x_i diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-limits.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-limits.snap new file mode 100644 index 000000000..cc435075d --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-limits.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +i=1ni\sum_{i=1}^{n} i diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-limits_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-limits_display.snap new file mode 100644 index 000000000..433c2c8a8 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-limits_display.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +i=1ni\sum_{i=1}^{n} i diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-nolimits_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-nolimits_display.snap new file mode 100644 index 000000000..530dff60b --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-nolimits_display.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +ixi\sum\nolimits_{i} x_i diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-only-sub_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-only-sub_display.snap new file mode 100644 index 000000000..991e321e2 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-only-sub_display.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +kak\sum_{k} a_k diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-only-sup_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-only-sup_display.snap new file mode 100644 index 000000000..67b728897 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sum-only-sup_display.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +Nak\sum^{N} a_k diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sup-inf.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sup-inf.snap new file mode 100644 index 000000000..94c32b714 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__operators__sup-inf.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +supxf(x)infxf(x)\sup_{x} f(x) - \inf_{x} f(x) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-bare-arg.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-bare-arg.snap new file mode 100644 index 000000000..da702fd78 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-bare-arg.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x\sqrt x diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-index-expression.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-index-expression.snap new file mode 100644 index 000000000..f2b7d7d32 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-index-expression.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x2+y2n+1\sqrt[n+1]{x^2+y^2} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-index.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-index.snap new file mode 100644 index 000000000..f20d13bb2 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-index.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x3\sqrt[3]{x} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-nested.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-nested.snap new file mode 100644 index 000000000..af85e121b --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-nested.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +1+x\sqrt{1+\sqrt{x}} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-of-frac.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-of-frac.snap new file mode 100644 index 000000000..719e89a81 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt-of-frac.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +ab\sqrt{\frac{a}{b}} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt.snap new file mode 100644 index 000000000..6a192d08d --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__radicals__sqrt.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x\sqrt{x} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__empty-sup.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__empty-sup.snap new file mode 100644 index 000000000..c94939332 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__empty-sup.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xx^{} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__nested-sup.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__nested-sup.snap new file mode 100644 index 000000000..a87bc3698 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__nested-sup.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +ex2e^{x^2} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__prime-with-sup.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__prime-with-sup.snap new file mode 100644 index 000000000..b6165bfa0 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__prime-with-sup.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x2x'^2 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-digit-run.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-digit-run.snap new file mode 100644 index 000000000..770fbf01d --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-digit-run.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x23x^23 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-on-frac.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-on-frac.snap new file mode 100644 index 000000000..559dae6a9 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-on-frac.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +ab2\frac{a}{b}^2 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-on-group.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-on-group.snap new file mode 100644 index 000000000..83fe1dd4e --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-on-group.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a+b2{a+b}^2 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-on-paren.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-on-paren.snap new file mode 100644 index 000000000..338e409ba --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-on-paren.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +(a+b)2(a+b)^2 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-with-space-before.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-with-space-before.snap new file mode 100644 index 000000000..2ff5775d6 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__script-with-space-before.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x2ix ^2 _i diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sub-group.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sub-group.snap new file mode 100644 index 000000000..94eef01f6 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sub-group.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xi+1x_{i+1} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sub-of-sub.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sub-of-sub.snap new file mode 100644 index 000000000..341331de6 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sub-of-sub.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xijx_{i_j} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sub.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sub.snap new file mode 100644 index 000000000..3bd65d97a --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sub.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xix_i diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__subsup.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__subsup.snap new file mode 100644 index 000000000..55959b26c --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__subsup.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xi2x_i^2 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sup-command-arg.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sup-command-arg.snap new file mode 100644 index 000000000..a0467beb8 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sup-command-arg.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +eαe^\alpha diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sup-then-space-sub.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sup-then-space-sub.snap new file mode 100644 index 000000000..e26c122a2 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sup-then-space-sub.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +EcellE^\circ _{cell} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sup.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sup.snap new file mode 100644 index 000000000..60af21c01 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__sup.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x2x^2 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__supsub.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__supsub.snap new file mode 100644 index 000000000..61fd6c306 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__scripts__supsub.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xi2x^2_i diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__backslash-space.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__backslash-space.snap new file mode 100644 index 000000000..b824f115a --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__backslash-space.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +aba\ b diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__enspace-and-comma.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__enspace-and-comma.snap new file mode 100644 index 000000000..76c7415bf --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__enspace-and-comma.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +abca\enspace b\, c diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__explicit-newline-outside-env_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__explicit-newline-outside-env_display.snap new file mode 100644 index 000000000..cc7dbbf46 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__explicit-newline-outside-env_display.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +aba \\ b diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__hphantom-vphantom.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__hphantom-vphantom.snap new file mode 100644 index 000000000..866e82fc8 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__hphantom-vphantom.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xyabz\hphantom{x} y \vphantom{\frac{a}{b}} z diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__hspace.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__hspace.snap new file mode 100644 index 000000000..f4208fcdf --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__hspace.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +aba \hspace{1em} b diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__medium-thick.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__medium-thick.snap new file mode 100644 index 000000000..edc71a306 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__medium-thick.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +abca\:b\;c diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__mkern-mskip.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__mkern-mskip.snap new file mode 100644 index 000000000..4dc4f1d2f --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__mkern-mskip.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a3mub6muca\mkern3mu b\mskip 6mu c diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__multiple-spaces-collapse.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__multiple-spaces-collapse.snap new file mode 100644 index 000000000..d1a064611 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__multiple-spaces-collapse.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a+ba + b diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__negative-thin.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__negative-thin.snap new file mode 100644 index 000000000..fb4463e39 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__negative-thin.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +aba\!b diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__phantom.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__phantom.snap new file mode 100644 index 000000000..c0732336a --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__phantom.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xy\phantom{x} y diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__quad-qquad.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__quad-qquad.snap new file mode 100644 index 000000000..9f6142463 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__quad-qquad.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +abca \quad b \qquad c diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__space-inside-group.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__space-inside-group.snap new file mode 100644 index 000000000..940d2e870 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__space-inside-group.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a{ a } diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__thin-space.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__thin-space.snap new file mode 100644 index 000000000..77e083e33 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__thin-space.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +f(x)dx\int f(x)\,dx diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__tilde-nbsp.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__tilde-nbsp.snap new file mode 100644 index 000000000..7795e5910 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__spacing__tilde-nbsp.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a ba~b diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__arrows.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__arrows.snap new file mode 100644 index 000000000..2b1d4af69 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__arrows.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +abc=>d<->efg<=>ha \to b \rightarrow c \Rightarrow d \leftrightarrow e \mapsto f \leftarrow g \Leftrightarrow h diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__binary-ops.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__binary-ops.snap new file mode 100644 index 000000000..f7acc0c16 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__binary-ops.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a±bc×d÷efghijklmna \pm b \mp c \times d \div e \cdot f \circ g \ast h \oplus i \otimes j \wedge k \vee l \star m \bullet n diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__degrees.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__degrees.snap new file mode 100644 index 000000000..5292f3394 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__degrees.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +9090^\circ diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__dots.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__dots.snap new file mode 100644 index 000000000..e3e6c64b7 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__dots.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +a,,bcda, \dots, b \ldots c \cdots d \vdots \ddots \dotsc \dotsb diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__escaped-specials.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__escaped-specials.snap new file mode 100644 index 000000000..3a3cf18fd --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__escaped-specials.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +{}%$&#_\{ \} \% \$ \& \# \_ diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__greek-lower.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__greek-lower.snap new file mode 100644 index 000000000..ad1eb9b88 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__greek-lower.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +αβγδϵεζηθϑικλμνξπϖρϱσςτυϕφχψω\alpha \beta \gamma \delta \epsilon \varepsilon \zeta \eta \theta \vartheta \iota \kappa \lambda \mu \nu \xi \pi \varpi \rho \varrho \sigma \varsigma \tau \upsilon \phi \varphi \chi \psi \omega diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__greek-upper.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__greek-upper.snap new file mode 100644 index 000000000..0ab4e8321 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__greek-upper.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +ΓΔΘΛΞΠΣΥΦΨΩ\Gamma \Delta \Theta \Lambda \Xi \Pi \Sigma \Upsilon \Phi \Psi \Omega diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__harpoons.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__harpoons.snap new file mode 100644 index 000000000..abc4ed676 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__harpoons.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +\rightharpoonup \leftharpoondown \rightleftharpoons diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__logic.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__logic.snap new file mode 100644 index 000000000..5db3fa38e --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__logic.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +pq¬rstuvp \land q \lor \neg r \implies s \iff t \vdash u \models v diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__long-arrows.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__long-arrows.snap new file mode 100644 index 000000000..eb1eca4bd --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__long-arrows.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +--><--==>\longrightarrow \longleftarrow \Longrightarrow \longmapsto diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__lvert-rvert.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__lvert-rvert.snap new file mode 100644 index 000000000..7ae45b730 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__lvert-rvert.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x+|y|\lVert x \rVert + \lvert y \rvert diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__mid.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__mid.snap new file mode 100644 index 000000000..58a49345a --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__mid.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +{xx>0}\{ x \mid x > 0 \} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__misc.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__misc.snap new file mode 100644 index 000000000..11f332cd7 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__misc.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +אħ\infty \partial \nabla \forall \exists \emptyset \varnothing \top \bot \Im \Re \aleph \hbar \ell \wp \prime \angle \triangle \square diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__neq-variants.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__neq-variants.snap new file mode 100644 index 000000000..4c4eb4c36 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__neq-variants.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +abc≢da \neq b \ne c \not\equiv d diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__relations.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__relations.snap new file mode 100644 index 000000000..f2f466887 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__relations.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +abcdefghijklmnopa \leq b \geq c \approx d \equiv e \sim f \propto g \in h \subset i \subseteq j \ll k \gg l \simeq m \cong n \perp o \parallel p diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__set-ops.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__set-ops.snap new file mode 100644 index 000000000..29cc77854 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__set-ops.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +ABCDEFGHA \cup B \cap C \setminus D \subset E \supset F \notin G \ni H diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__text-symbols.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__text-symbols.snap new file mode 100644 index 000000000..626993e49 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__text-symbols.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +§©£\S \P \dag \ddag \copyright \pounds diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__unicode-and-command-mixed.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__unicode-and-command-mixed.snap new file mode 100644 index 000000000..4e8f6f943 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__unicode-and-command-mixed.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +α+β=γα + \beta = γ diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__xarrows.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__xarrows.snap new file mode 100644 index 000000000..b5b26298b --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__symbols__xarrows.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +afb[g]hca \xrightarrow{f} b \xleftarrow[g]{h} c diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__bm.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__bm.snap new file mode 100644 index 000000000..f24254b43 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__bm.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +𝜷\bm{\beta} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__boldsymbol.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__boldsymbol.snap new file mode 100644 index 000000000..7b4ea8e14 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__boldsymbol.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +𝜷\boldsymbol{\beta} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__color-switch.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__color-switch.snap new file mode 100644 index 000000000..2c46aa8c8 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__color-switch.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +xy{\color{blue} x} y diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__displaystyle.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__displaystyle.snap new file mode 100644 index 000000000..c0d953f35 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__displaystyle.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +ixi\displaystyle \sum_i x_i diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathbb.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathbb.snap new file mode 100644 index 000000000..6d8afd4af --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathbb.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +n\mathbb{R}^n diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathbf.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathbf.snap new file mode 100644 index 000000000..99023b3bb --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathbf.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +𝐱𝐲\mathbf{x}^\top \mathbf{y} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathcal.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathcal.snap new file mode 100644 index 000000000..44333faed --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathcal.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +𝒫()\mathcal{P}(\mathbb{N}) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathfrak.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathfrak.snap new file mode 100644 index 000000000..694b76edc --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathfrak.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +𝔤\mathfrak{g} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathit-mathsf-mathtt.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathit-mathsf-mathtt.snap new file mode 100644 index 000000000..1c7d524a8 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathit-mathsf-mathtt.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +𝑥𝗒𝚣\mathit{x} \mathsf{y} \mathtt{z} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathrm-bare-arg.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathrm-bare-arg.snap new file mode 100644 index 000000000..be4910c8f --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathrm-bare-arg.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +rS\mathrm r \mathrm S diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathrm-multi.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathrm-multi.snap new file mode 100644 index 000000000..de7486b55 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathrm-multi.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +Pois(μ)\mathrm{Pois}(\mu) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathrm.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathrm.snap new file mode 100644 index 000000000..81427ddd5 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathrm.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +dx\mathrm{d}x diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathscr.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathscr.snap new file mode 100644 index 000000000..184dfea6c --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mathscr.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +\mathscr{L} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mbox.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mbox.snap new file mode 100644 index 000000000..92345365c --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__mbox.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +for all x\mbox{for all } x diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__old-style-bf.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__old-style-bf.snap new file mode 100644 index 000000000..bcd1b91b2 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__old-style-bf.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +𝐱y{\bf x} y diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__old-style-rm-it.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__old-style-rm-it.snap new file mode 100644 index 000000000..63e788c50 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__old-style-rm-it.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +d𝑥{\rm d}{\it x} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__style-on-multi-token.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__style-on-multi-token.snap new file mode 100644 index 000000000..93ab412d0 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__style-on-multi-token.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +𝐚+𝐛\mathbf{a + b} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__text-nested-math.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__text-nested-math.snap new file mode 100644 index 000000000..5501d596b --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__text-nested-math.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +if $x > 0$ then\text{if $x > 0$ then} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__text-special-chars.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__text-special-chars.snap new file mode 100644 index 000000000..696121b65 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__text-special-chars.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +50\% of \$x\text{50\% of \$x} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__text-with-spaces.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__text-with-spaces.snap new file mode 100644 index 000000000..3f1433098 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__text-with-spaces.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +the quick brown fox\text{the quick brown fox} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__text.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__text.snap new file mode 100644 index 000000000..9a9aefcbb --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__text.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x=1 if y>0x = 1 \text{ if } y > 0 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__textbf-textit.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__textbf-textit.snap new file mode 100644 index 000000000..1cee94cf4 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__textbf-textit.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +𝐛𝐨𝐥𝐝𝑖𝑡\textbf{bold} \textit{it} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__textcolor.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__textcolor.snap new file mode 100644 index 000000000..f381a4d0f --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__textcolor.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +x\textcolor{red}{x} diff --git a/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__textstyle_display.snap b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__textstyle_display.snap new file mode 100644 index 000000000..26b5e0289 --- /dev/null +++ b/crates/quarto-math/tests/integration/snapshots/integration__mathml__mathml__text-and-styles__textstyle_display.snap @@ -0,0 +1,5 @@ +--- +source: crates/quarto-math/tests/integration/mathml.rs +expression: to_mathml(&n) +--- +ixi\textstyle \sum_i x_i diff --git a/crates/quarto-math/tests/integration/snapshots/integration__normalize__ast__basic__prime.snap b/crates/quarto-math/tests/integration/snapshots/integration__normalize__ast__basic__prime.snap index 665908274..9bedd3ea8 100644 --- a/crates/quarto-math/tests/integration/snapshots/integration__normalize__ast__basic__prime.snap +++ b/crates/quarto-math/tests/integration/snapshots/integration__normalize__ast__basic__prime.snap @@ -3,14 +3,19 @@ source: crates/quarto-math/tests/integration/normalize.rs expression: rendered(&n) --- row @0..14 - run "f" @0..1 + scripts Auto @0..2 + run "f" @0..1 + sup: + sym "′" @1..2 run "(" @2..3 run "x" @3..4 run ")" @4..5 - row @6..10 + scripts Auto @6..11 row @6..9 run "=" @6..7 run "f" @8..9 + sup: + sym "″" @9..11 run "(" @11..12 run "x" @12..13 run ")" @13..14 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__normalize__ast__scripts__prime-with-sup.snap b/crates/quarto-math/tests/integration/snapshots/integration__normalize__ast__scripts__prime-with-sup.snap index bb29ee9c6..ab1d07762 100644 --- a/crates/quarto-math/tests/integration/snapshots/integration__normalize__ast__scripts__prime-with-sup.snap +++ b/crates/quarto-math/tests/integration/snapshots/integration__normalize__ast__scripts__prime-with-sup.snap @@ -4,8 +4,9 @@ expression: rendered(&n) --- row @0..4 scripts Auto @0..4 - row @0..2 - run "x" @0..1 + run "x" @0..1 sup: - run "2" @3..4 + row @1..4 + sym "′" @1..2 + run "2" @3..4 -- problems -- diff --git a/crates/quarto-math/tests/integration/snapshots/integration__omml__omml__basic__prime.snap b/crates/quarto-math/tests/integration/snapshots/integration__omml__omml__basic__prime.snap index e87824b8b..3c54fb0c0 100644 --- a/crates/quarto-math/tests/integration/snapshots/integration__omml__omml__basic__prime.snap +++ b/crates/quarto-math/tests/integration/snapshots/integration__omml__omml__basic__prime.snap @@ -2,4 +2,4 @@ source: crates/quarto-math/tests/integration/omml.rs expression: to_omml(&n) --- -f(x)=f(x) +f(x)=f(x) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__omml__omml__scripts__prime-with-sup.snap b/crates/quarto-math/tests/integration/snapshots/integration__omml__omml__scripts__prime-with-sup.snap index 95d40eb32..2238404c1 100644 --- a/crates/quarto-math/tests/integration/snapshots/integration__omml__omml__scripts__prime-with-sup.snap +++ b/crates/quarto-math/tests/integration/snapshots/integration__omml__omml__scripts__prime-with-sup.snap @@ -2,4 +2,4 @@ source: crates/quarto-math/tests/integration/omml.rs expression: to_omml(&n) --- -x2 +x2 diff --git a/crates/quarto-math/tests/integration/snapshots/integration__typst__typst__basic__prime.snap b/crates/quarto-math/tests/integration/snapshots/integration__typst__typst__basic__prime.snap index 00d87b9ed..579d2a7a0 100644 --- a/crates/quarto-math/tests/integration/snapshots/integration__typst__typst__basic__prime.snap +++ b/crates/quarto-math/tests/integration/snapshots/integration__typst__typst__basic__prime.snap @@ -2,4 +2,4 @@ source: crates/quarto-math/tests/integration/typst.rs expression: to_typst(&n) --- -f ( x ) = f ( x ) +f^(′) ( x ) (= f)^(″) ( x ) diff --git a/crates/quarto-math/tests/integration/snapshots/integration__typst__typst__scripts__prime-with-sup.snap b/crates/quarto-math/tests/integration/snapshots/integration__typst__typst__scripts__prime-with-sup.snap index 91703116a..650229fe7 100644 --- a/crates/quarto-math/tests/integration/snapshots/integration__typst__typst__scripts__prime-with-sup.snap +++ b/crates/quarto-math/tests/integration/snapshots/integration__typst__typst__scripts__prime-with-sup.snap @@ -2,4 +2,4 @@ source: crates/quarto-math/tests/integration/typst.rs expression: to_typst(&n) --- -x^(2) +x^(′ 2) From 9c9fe83f357c5a81ed5ef4ded5567c276221edd9 Mon Sep 17 00:00:00 2001 From: Carlos Scheidegger Date: Mon, 21 Sep 2026 18:41:09 -0500 Subject: [PATCH 2/4] =?UTF-8?q?html-math-method:=20mathml=20=E2=80=94=20na?= =?UTF-8?q?tive=20MathML=20for=20format:=20html=20(bd-3evfzwal)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 `` 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 `` 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 --- Cargo.lock | 1 + ...026-09-21-equation-numbering-and-mathml.md | 54 ++- crates/quarto-core/Cargo.toml | 2 + crates/quarto-core/src/ast_walk.rs | 151 +++++++ crates/quarto-core/src/lib.rs | 1 + crates/quarto-core/src/pipeline.rs | 35 +- crates/quarto-core/src/stage/mod.rs | 2 +- .../src/stage/stages/equation_number.rs | 180 ++------- .../quarto-core/src/stage/stages/math_js.rs | 70 +++- .../quarto-core/src/stage/stages/math_ml.rs | 372 ++++++++++++++++++ crates/quarto-core/src/stage/stages/mod.rs | 5 + .../equation_numbering_pipeline.rs | 21 +- .../tests/integration/math_mode_pipeline.rs | 164 ++++++++ crates/wasm-quarto-hub-client/Cargo.lock | 174 +++++++- docs/errors/math/Q-22-1.qmd | 2 +- docs/guides/formats/html/math.qmd | 66 ++++ 16 files changed, 1100 insertions(+), 200 deletions(-) create mode 100644 crates/quarto-core/src/ast_walk.rs create mode 100644 crates/quarto-core/src/stage/stages/math_ml.rs create mode 100644 docs/guides/formats/html/math.qmd diff --git a/Cargo.lock b/Cargo.lock index 4d7779656..136986978 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5494,6 +5494,7 @@ dependencies = [ "quarto-error-catalog", "quarto-error-reporting", "quarto-highlight", + "quarto-math", "quarto-navigation", "quarto-pandoc-types", "quarto-sass", diff --git a/claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md b/claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md index be906ee94..39f7b1966 100644 --- a/claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md +++ b/claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md @@ -1,7 +1,8 @@ # Format-specific equation numbering and `html-math-method: mathml` **Status:** approved 2026-09-21 (all five open decisions settled with the -user, each as recommended); executing Phase 1. +user, each as recommended). Phases 1–3 implemented and committed the same +day on three branches (see Phase 0); nothing pushed yet. **Strands:** bd-vlhi2zkj (Phase 1, equation numbering; branch `braid/bd-vlhi2zkj-equation-numbering` off `main`), bd-9z83tcv0 (Phase 2, MathML writer, p2), bd-3evfzwal (Phase 3, `MathMlStage`; blocked on the @@ -273,7 +274,7 @@ as its base, filed as bd-0mzhnxft. ### Phase 3 — `MathMlStage` for `format: html` (new strand, stacked on #706 + Phase 1) -- [ ] **End-to-end tests in `math_mode_pipeline.rs`**: `html-math-method: +- [x] (2026-09-21; 5 tests appended to `math_mode_pipeline.rs`) **End-to-end tests in `math_mode_pipeline.rs`**: `html-math-method: mathml` with inline + display + numbered math → `` elements (two `display="block"`), the +numbered equation as +`…(1)`, the +leftover as `\(x + \bogus y\)`, and one +MathJax config whose `skipHtmlTags` now lists `annotation`. Headless +Chromium (Playwright, MathJax allowed to load from the CDN) reports: the +four native `` boxes have heights 22/16/27/39 px; exactly one +MathJax-typeset element exists (`x+\bogus y`); the `(1)` label sits to +the right of the equation, flush with the row's right edge, vertically +centred on it. Screenshot reviewed: fractions, the sum with under/over +limits, the integral with side limits, the fenced matrix and the cases +brace all render natively. + +The first browser pass caught a real bug: q2's MathJax config overrode +MathJax's default `skipHtmlTags`, dropping `annotation`, so the fallback +loader re-typeset the `\begin{pmatrix}`/`\begin{cases}` text inside the +converted math's annotations (zero-size assistive MathML in the DOM). +Fixed with a unit test on the config and an end-to-end assertion. + ## Decisions (all settled 2026-09-21, each as recommended) 1. **Attribute key.** `quarto-eq-number` as proposed, or a shorter diff --git a/crates/quarto-core/Cargo.toml b/crates/quarto-core/Cargo.toml index b282d1059..0de69ad38 100644 --- a/crates/quarto-core/Cargo.toml +++ b/crates/quarto-core/Cargo.toml @@ -61,6 +61,8 @@ pampa = { workspace = true, features = ["lua-filter", "json-filter"] } # `quarto-highlight` crate itself to avoid compiling wasmtime for the # wasm32-unknown-unknown target. quarto-highlight.workspace = true +# `MathMlStage` (html-math-method: mathml) converts math at render time. +quarto-math = { path = "../quarto-math" } # Used by the listings module's `b64_encode_unicode` helper (mirroring # Q1's `b64EncodeUnicode` for the categories click handler). Available diff --git a/crates/quarto-core/src/ast_walk.rs b/crates/quarto-core/src/ast_walk.rs new file mode 100644 index 000000000..30b9475cb --- /dev/null +++ b/crates/quarto-core/src/ast_walk.rs @@ -0,0 +1,151 @@ +/* + * ast_walk.rs + * Copyright (c) 2026 Posit, PBC + */ + +//! Mutable traversal of every inline in a document. +//! +//! Several post-filter stages need to visit each `Inline` wherever it +//! sits — paragraph, header, list item, table cell, caption, footnote, or +//! a custom node's slots — and either edit it in place or replace it. +//! This is that walk, written once. It is pre-order: `f` sees a node +//! before the walk descends into the node's own content, so a +//! replacement's children are visited too. + +use quarto_pandoc_types::block::Block; +use quarto_pandoc_types::custom::Slot; +use quarto_pandoc_types::inline::{Inline, Inlines}; + +/// Call `f` on every inline under `blocks`, in document order. +pub fn for_each_inline_mut(blocks: &mut [Block], f: &mut dyn FnMut(&mut Inline)) { + for block in blocks.iter_mut() { + visit_block(block, f); + } +} + +fn visit_block(block: &mut Block, f: &mut dyn FnMut(&mut Inline)) { + match block { + Block::Plain(p) => visit_inlines(&mut p.content, f), + Block::Paragraph(p) => visit_inlines(&mut p.content, f), + Block::LineBlock(lb) => { + for line in lb.content.iter_mut() { + visit_inlines(line, f); + } + } + Block::BlockQuote(bq) => for_each_inline_mut(&mut bq.content, f), + Block::OrderedList(ol) => { + for item in ol.content.iter_mut() { + for_each_inline_mut(item, f); + } + } + Block::BulletList(bl) => { + for item in bl.content.iter_mut() { + for_each_inline_mut(item, f); + } + } + Block::DefinitionList(dl) => { + for (term, defs) in dl.content.iter_mut() { + visit_inlines(term, f); + for def in defs.iter_mut() { + for_each_inline_mut(def, f); + } + } + } + Block::Header(h) => visit_inlines(&mut h.content, f), + Block::Div(d) => for_each_inline_mut(&mut d.content, f), + Block::Figure(fig) => { + if let Some(short) = fig.caption.short.as_mut() { + visit_inlines(short, f); + } + if let Some(long) = fig.caption.long.as_mut() { + for_each_inline_mut(long, f); + } + for_each_inline_mut(&mut fig.content, f); + } + Block::Table(t) => { + if let Some(short) = t.caption.short.as_mut() { + visit_inlines(short, f); + } + if let Some(long) = t.caption.long.as_mut() { + for_each_inline_mut(long, f); + } + for row in t.head.rows.iter_mut().chain(t.foot.rows.iter_mut()) { + for cell in row.cells.iter_mut() { + for_each_inline_mut(&mut cell.content, f); + } + } + for body in t.bodies.iter_mut() { + for row in body.head.iter_mut().chain(body.body.iter_mut()) { + for cell in row.cells.iter_mut() { + for_each_inline_mut(&mut cell.content, f); + } + } + } + } + Block::CaptionBlock(cb) => visit_inlines(&mut cb.content, f), + Block::Custom(c) => { + for (_name, slot) in c.slots.iter_mut() { + visit_slot(slot, f); + } + } + Block::CodeBlock(_) + | Block::RawBlock(_) + | Block::HorizontalRule(_) + | Block::BlockMetadata(_) + | Block::NoteDefinitionPara(_) + | Block::NoteDefinitionFencedBlock(_) => {} + } +} + +fn visit_inlines(inlines: &mut Inlines, f: &mut dyn FnMut(&mut Inline)) { + for inline in inlines.iter_mut() { + visit_inline(inline, f); + } +} + +fn visit_inline(inline: &mut Inline, f: &mut dyn FnMut(&mut Inline)) { + f(inline); + match inline { + Inline::Emph(e) => visit_inlines(&mut e.content, f), + Inline::Underline(u) => visit_inlines(&mut u.content, f), + Inline::Strong(s) => visit_inlines(&mut s.content, f), + Inline::Strikeout(s) => visit_inlines(&mut s.content, f), + Inline::Superscript(s) => visit_inlines(&mut s.content, f), + Inline::Subscript(s) => visit_inlines(&mut s.content, f), + Inline::SmallCaps(s) => visit_inlines(&mut s.content, f), + Inline::Quoted(q) => visit_inlines(&mut q.content, f), + Inline::Link(l) => visit_inlines(&mut l.content, f), + Inline::Image(i) => visit_inlines(&mut i.content, f), + Inline::Span(s) => visit_inlines(&mut s.content, f), + Inline::Note(n) => for_each_inline_mut(&mut n.content, f), + Inline::Insert(i) => visit_inlines(&mut i.content, f), + Inline::Delete(d) => visit_inlines(&mut d.content, f), + Inline::Highlight(h) => visit_inlines(&mut h.content, f), + Inline::Custom(c) => { + for (_name, slot) in c.slots.iter_mut() { + visit_slot(slot, f); + } + } + Inline::Str(_) + | Inline::Cite(_) + | Inline::Code(_) + | Inline::Space(_) + | Inline::SoftBreak(_) + | Inline::LineBreak(_) + | Inline::Math(_) + | Inline::RawInline(_) + | Inline::Shortcode(_) + | Inline::NoteReference(_) + | Inline::Attr(_) + | Inline::EditComment(_) => {} + } +} + +fn visit_slot(slot: &mut Slot, f: &mut dyn FnMut(&mut Inline)) { + match slot { + Slot::Block(b) => visit_block(b, f), + Slot::Blocks(bs) => for_each_inline_mut(bs, f), + Slot::Inline(i) => visit_inline(i, f), + Slot::Inlines(is) => visit_inlines(is, f), + } +} diff --git a/crates/quarto-core/src/lib.rs b/crates/quarto-core/src/lib.rs index b98de68b7..ee668fe1d 100644 --- a/crates/quarto-core/src/lib.rs +++ b/crates/quarto-core/src/lib.rs @@ -38,6 +38,7 @@ pub mod artifact; pub mod artifact_flush; +pub mod ast_walk; pub mod attribution; pub mod brand_fonts; pub mod cell_options; diff --git a/crates/quarto-core/src/pipeline.rs b/crates/quarto-core/src/pipeline.rs index e126db139..2e65d1fdb 100644 --- a/crates/quarto-core/src/pipeline.rs +++ b/crates/quarto-core/src/pipeline.rs @@ -62,8 +62,8 @@ use crate::stage::{ ApplyTemplateStage, AstTransformsStage, AttributionGenerateStage, CompileThemeCssStage, DocumentProfileStage, EngineExecutionStage, EquationNumberStage, IncludeExpansionStage, IncludeResolveStage, LanguageResolveStage, LinkResolutionStage, ListingItemInfoStage, - LoadedSource, MathJsStage, MetadataMergeStage, ParseDocumentStage, Pipeline, PipelineData, - PipelineStage, PreEngineSugaringStage, RenderHtmlBodyStage, ResourceReportStage, + LoadedSource, MathJsStage, MathMlStage, MetadataMergeStage, ParseDocumentStage, Pipeline, + PipelineData, PipelineStage, PreEngineSugaringStage, RenderHtmlBodyStage, ResourceReportStage, SourceConversionStage, StageContext, UnwrapProfileStage, UserFiltersStage, }; use crate::transform::TransformPipeline; @@ -362,6 +362,12 @@ pub fn build_html_pipeline_stages_with_options( // and this stage honours the result. A no-op in q2-preview, where // crossref-render is excluded and `Equation.tsx` numbers client-side. stages.push(Box::new(EquationNumberStage::new())); + // Native MathML (bd-3evfzwal): under `html-math-method: mathml`, + // convert every `Inline::Math` to `` with quarto-math. Runs + // after equation-number (the number is already a sibling label, so + // the TeX is the author's) and before math-js, which loads MathJax + // only for the expressions this stage had to leave as TeX. + stages.push(Box::new(MathMlStage::new())); stages.push(Box::new(CodeHighlightStage::new())); // Math-mode (bd-w5ov): walk the post-transform AST and, when math // is present, populate `meta.math` with the engine's config + loader @@ -401,7 +407,8 @@ pub fn build_html_pipeline_stages_with_options( /// (`q2_preview_stage_excluded_names_exist_in_html_pipeline`) /// fails the test suite if any name here is not an actual stage in /// the full HTML pipeline (typo / rename guard). -const Q2_PREVIEW_STAGE_EXCLUDED: &[&str] = &["math-js", "render-html-body", "apply-template"]; +const Q2_PREVIEW_STAGE_EXCLUDED: &[&str] = + &["math-ml", "math-js", "render-html-body", "apply-template"]; /// Build the q2-preview pipeline stages (Plan 1). /// @@ -2178,9 +2185,9 @@ mod tests { // Merged pipeline: SourceConversionStage at [0] (branch) plus two // stages main added — LanguageResolveStage after metadata-merge // (bd-llhlzd7p) and TabsetsJsStage in the JS block - // (bd-toc-tabset-titles-zq93gjvf) — plus EquationNumberStage after - // the post filters (bd-vlhi2zkj): 26. - assert_eq!(stages.len(), 26); + // (bd-toc-tabset-titles-zq93gjvf) — plus EquationNumberStage and + // MathMlStage after the post filters (bd-vlhi2zkj, bd-3evfzwal): 27. + assert_eq!(stages.len(), 27); // Pre-parse file-claim/convert (Task 10). assert_eq!(stages[0].name(), "source-conversion"); assert_eq!(stages[1].name(), "parse-document"); @@ -2233,14 +2240,18 @@ mod tests { // user-filters-post: a Lua post filter may rewrite or delete the // reserved `quarto-eq-number` attribute the stage consumes. assert_eq!(stages[21].name(), "equation-number"); - assert_eq!(stages[22].name(), "code-highlight"); + // Native MathML (bd-3evfzwal) follows equation-number (the TeX it + // converts carries no `\tag`) and precedes math-js (which loads + // MathJax only for what this stage left as TeX). + assert_eq!(stages[22].name(), "math-ml"); + assert_eq!(stages[23].name(), "code-highlight"); // Math-mode (bd-w5ov) walks the post-transform AST and // populates meta.math when math is present. Sits just before // render-html-body so any late-introduced math (sugar, user // filters, the `\tag{N}` equation-number encoding) is visible. - assert_eq!(stages[23].name(), "math-js"); - assert_eq!(stages[24].name(), "render-html-body"); - assert_eq!(stages[25].name(), "apply-template"); + assert_eq!(stages[24].name(), "math-js"); + assert_eq!(stages[25].name(), "render-html-body"); + assert_eq!(stages[26].name(), "apply-template"); } #[test] @@ -2248,8 +2259,8 @@ mod tests { let pipeline = build_html_pipeline(); // Merged pipeline carries both SourceConversionStage (Task 10, branch) // LanguageResolveStage and TabsetsJsStage (main), plus - // EquationNumberStage (bd-vlhi2zkj) → 26 stages. - assert_eq!(pipeline.len(), 26); + // EquationNumberStage and MathMlStage → 27 stages. + assert_eq!(pipeline.len(), 27); } #[test] diff --git a/crates/quarto-core/src/stage/mod.rs b/crates/quarto-core/src/stage/mod.rs index e2e185dcd..a717bc447 100644 --- a/crates/quarto-core/src/stage/mod.rs +++ b/crates/quarto-core/src/stage/mod.rs @@ -117,7 +117,7 @@ pub use stages::{ ApplyTemplateStage, AstTransformsStage, AttributionGenerateStage, CaptureSpliceStage, CompileThemeCssStage, DocumentProfileStage, EngineExecutionStage, EquationNumberStage, IncludeExpansionStage, IncludeResolveStage, LanguageResolveStage, LinkResolutionStage, - ListingItemInfoStage, MathJsStage, MetadataMergeStage, ParseDocumentStage, + ListingItemInfoStage, MathJsStage, MathMlStage, MetadataMergeStage, ParseDocumentStage, PreEngineSugaringStage, RenderHtmlBodyStage, ResourceReportStage, SourceConversionStage, UnwrapProfileStage, UserFiltersStage, expand_document_includes, }; diff --git a/crates/quarto-core/src/stage/stages/equation_number.rs b/crates/quarto-core/src/stage/stages/equation_number.rs index 3fb0f26e8..a0aaada7c 100644 --- a/crates/quarto-core/src/stage/stages/equation_number.rs +++ b/crates/quarto-core/src/stage/stages/equation_number.rs @@ -43,10 +43,10 @@ use async_trait::async_trait; use quarto_pandoc_types::attr::AttrSourceInfo; use quarto_pandoc_types::block::Block; -use quarto_pandoc_types::custom::Slot; -use quarto_pandoc_types::inline::{Inline, Inlines, Math, MathType, Span, Str}; +use quarto_pandoc_types::inline::{Inline, Math, MathType, Span, Str}; use quarto_source_map::SourceInfo; +use crate::ast_walk::for_each_inline_mut; use crate::crossref::EQ_NUMBER_ATTR; use crate::format::Format; use crate::math_method::{MathMethod, MathMethodConfig}; @@ -136,10 +136,7 @@ impl PipelineStage for EquationNumberStage { let method = MathMethodConfig::from_meta(&doc.ast.meta).method; let encoding = NumberEncoding::for_document(&ctx.format, &method); - let mut outcome = Outcome::default(); - for block in doc.ast.blocks.iter_mut() { - visit_block(block, encoding, &mut outcome); - } + let outcome = encode_document(&mut doc.ast.blocks, encoding); if outcome.encoded > 0 || outcome.non_canonical > 0 { trace_event!( @@ -156,10 +153,14 @@ impl PipelineStage for EquationNumberStage { } } +/// What one pass over a document did. #[derive(Default)] -struct Outcome { - encoded: usize, - non_canonical: usize, +pub struct Outcome { + /// Spans whose number was encoded. + pub encoded: usize, + /// Spans that carried the attribute but not `[Math(DisplayMath), …]`; + /// only the attribute was removed. + pub non_canonical: usize, } /// Apply `encoding` to one equation span that carried `number`. Returns @@ -214,146 +215,23 @@ fn append_to_tex(math: &mut Math, suffix: &str) { math.text.push_str(suffix); } -fn visit_block(block: &mut Block, encoding: NumberEncoding, out: &mut Outcome) { - match block { - Block::Plain(p) => visit_inlines(&mut p.content, encoding, out), - Block::Paragraph(p) => visit_inlines(&mut p.content, encoding, out), - Block::LineBlock(lb) => { - for line in lb.content.iter_mut() { - visit_inlines(line, encoding, out); - } - } - Block::BlockQuote(bq) => visit_blocks(&mut bq.content, encoding, out), - Block::OrderedList(ol) => { - for item in ol.content.iter_mut() { - visit_blocks(item, encoding, out); - } - } - Block::BulletList(bl) => { - for item in bl.content.iter_mut() { - visit_blocks(item, encoding, out); - } - } - Block::DefinitionList(dl) => { - for (term, defs) in dl.content.iter_mut() { - visit_inlines(term, encoding, out); - for def in defs.iter_mut() { - visit_blocks(def, encoding, out); - } - } - } - Block::Header(h) => visit_inlines(&mut h.content, encoding, out), - Block::Div(d) => visit_blocks(&mut d.content, encoding, out), - Block::Figure(f) => { - if let Some(short) = f.caption.short.as_mut() { - visit_inlines(short, encoding, out); - } - if let Some(long) = f.caption.long.as_mut() { - visit_blocks(long, encoding, out); - } - visit_blocks(&mut f.content, encoding, out); - } - Block::Table(t) => { - if let Some(short) = t.caption.short.as_mut() { - visit_inlines(short, encoding, out); - } - if let Some(long) = t.caption.long.as_mut() { - visit_blocks(long, encoding, out); - } - for row in t.head.rows.iter_mut().chain(t.foot.rows.iter_mut()) { - for cell in row.cells.iter_mut() { - visit_blocks(&mut cell.content, encoding, out); - } - } - for body in t.bodies.iter_mut() { - for row in body.head.iter_mut().chain(body.body.iter_mut()) { - for cell in row.cells.iter_mut() { - visit_blocks(&mut cell.content, encoding, out); - } - } - } - } - Block::CaptionBlock(cb) => visit_inlines(&mut cb.content, encoding, out), - Block::Custom(c) => { - for (_name, slot) in c.slots.iter_mut() { - visit_slot(slot, encoding, out); - } - } - Block::CodeBlock(_) - | Block::RawBlock(_) - | Block::HorizontalRule(_) - | Block::BlockMetadata(_) - | Block::NoteDefinitionPara(_) - | Block::NoteDefinitionFencedBlock(_) => {} - } -} - -fn visit_blocks(blocks: &mut [Block], encoding: NumberEncoding, out: &mut Outcome) { - for block in blocks.iter_mut() { - visit_block(block, encoding, out); - } -} - -fn visit_inlines(inlines: &mut Inlines, encoding: NumberEncoding, out: &mut Outcome) { - for inline in inlines.iter_mut() { - visit_inline(inline, encoding, out); - } -} - -fn visit_inline(inline: &mut Inline, encoding: NumberEncoding, out: &mut Outcome) { - match inline { - Inline::Span(s) => { - if let Some(number) = s.attr.2.remove(EQ_NUMBER_ATTR) { - if encode_number(s, &number, encoding) { - out.encoded += 1; - } else { - out.non_canonical += 1; - } - } - // A filter may have nested things; keep walking. - visit_inlines(&mut s.content, encoding, out); - } - Inline::Emph(e) => visit_inlines(&mut e.content, encoding, out), - Inline::Underline(u) => visit_inlines(&mut u.content, encoding, out), - Inline::Strong(s) => visit_inlines(&mut s.content, encoding, out), - Inline::Strikeout(s) => visit_inlines(&mut s.content, encoding, out), - Inline::Superscript(s) => visit_inlines(&mut s.content, encoding, out), - Inline::Subscript(s) => visit_inlines(&mut s.content, encoding, out), - Inline::SmallCaps(s) => visit_inlines(&mut s.content, encoding, out), - Inline::Quoted(q) => visit_inlines(&mut q.content, encoding, out), - Inline::Link(l) => visit_inlines(&mut l.content, encoding, out), - Inline::Image(i) => visit_inlines(&mut i.content, encoding, out), - Inline::Note(n) => visit_blocks(&mut n.content, encoding, out), - Inline::Insert(i) => visit_inlines(&mut i.content, encoding, out), - Inline::Delete(d) => visit_inlines(&mut d.content, encoding, out), - Inline::Highlight(h) => visit_inlines(&mut h.content, encoding, out), - Inline::Custom(c) => { - for (_name, slot) in c.slots.iter_mut() { - visit_slot(slot, encoding, out); - } +/// Encode every `quarto-eq-number` attribute under `blocks` and remove it. +pub fn encode_document(blocks: &mut [Block], encoding: NumberEncoding) -> Outcome { + let mut out = Outcome::default(); + for_each_inline_mut(blocks, &mut |inline| { + let Inline::Span(span) = inline else { + return; + }; + let Some(number) = span.attr.2.remove(EQ_NUMBER_ATTR) else { + return; + }; + if encode_number(span, &number, encoding) { + out.encoded += 1; + } else { + out.non_canonical += 1; } - Inline::Str(_) - | Inline::Cite(_) - | Inline::Code(_) - | Inline::Space(_) - | Inline::SoftBreak(_) - | Inline::LineBreak(_) - | Inline::Math(_) - | Inline::RawInline(_) - | Inline::Shortcode(_) - | Inline::NoteReference(_) - | Inline::Attr(_) - | Inline::EditComment(_) => {} - } -} - -fn visit_slot(slot: &mut Slot, encoding: NumberEncoding, out: &mut Outcome) { - match slot { - Slot::Block(b) => visit_block(b, encoding, out), - Slot::Blocks(bs) => visit_blocks(bs, encoding, out), - Slot::Inline(i) => visit_inline(i, encoding, out), - Slot::Inlines(is) => visit_inlines(is, encoding, out), - } + }); + out } #[cfg(test)] @@ -631,9 +509,7 @@ mod tests { // ── the walker ───────────────────────────────────────────────── fn walk(blocks: &mut [Block], encoding: NumberEncoding) -> Outcome { - let mut out = Outcome::default(); - visit_blocks(blocks, encoding, &mut out); - out + encode_document(blocks, encoding) } fn para(content: Vec) -> Block { @@ -646,7 +522,7 @@ mod tests { #[test] fn walker_reaches_spans_nested_in_blocks_inlines_and_custom_slots() { use quarto_pandoc_types::block::{BulletList, Div}; - use quarto_pandoc_types::custom::CustomNode; + use quarto_pandoc_types::custom::{CustomNode, Slot}; use quarto_pandoc_types::inline::Emph; let eq = || Inline::Span(numbered_span("1", vec![math(MathType::DisplayMath, "x")])); diff --git a/crates/quarto-core/src/stage/stages/math_js.rs b/crates/quarto-core/src/stage/stages/math_js.rs index f1279b9c2..011131ce2 100644 --- a/crates/quarto-core/src/stage/stages/math_js.rs +++ b/crates/quarto-core/src/stage/stages/math_js.rs @@ -111,11 +111,18 @@ impl MathEngine { /// engine's default loader location). /// /// Returns `None` for methods q2 does not load an engine for: - /// `plain`, `mathml` (converted at render time by bd-3evfzwal), - /// `webtex`, `gladtex` and unknown strings. The caller then leaves - /// `meta.math` unset so the author can supply their own approach via - /// includes / a custom template. Absent, `mathjax` and `katex` never - /// yield `None`. + /// `plain`, `webtex`, `gladtex` and unknown strings. The caller then + /// leaves `meta.math` unset so the author can supply their own + /// approach via includes / a custom template. Absent, `mathjax` and + /// `katex` never yield `None`. + /// + /// `mathml` yields the default MathJax engine: `MathMlStage` runs + /// before this stage and converts every expression it can, so any + /// `Inline::Math` still in the document is one it had to leave as TeX + /// (bd-3evfzwal, the hybrid fallback). This stage only injects when + /// the walk finds math, so a fully converted document loads nothing. + /// The object form's `url` is ignored for `mathml` (it names a + /// MathML converter, not a loader). pub fn from_meta(meta: &ConfigValue) -> Option { let MathMethodConfig { method, url } = MathMethodConfig::from_meta(meta); match method { @@ -125,7 +132,8 @@ impl MathEngine { MathMethod::Katex => Some(Self::Katex { url_base: url.unwrap_or_else(|| DEFAULT_KATEX_URL_BASE.to_string()), }), - MathMethod::MathMl | MathMethod::Plain | MathMethod::Unknown(_) => None, + MathMethod::MathMl => Some(Self::default_engine()), + MathMethod::Plain | MathMethod::Unknown(_) => None, } } @@ -154,7 +162,11 @@ impl MathEngine { /// equations — `CrossrefRenderTransform` injects `\tag{N}` so this /// flag is required for equation numbering to render. /// - `options.skipHtmlTags` excludes elements MathJax must not typeset -/// (code blocks, scripts, pre, etc.) — Pandoc's defaults. +/// (scripts, pre, etc.). `annotation`/`annotation-xml` are MathJax's own +/// defaults and must stay: under `html-math-method: mathml` the converted +/// `` elements carry their TeX in an ``, and a loader +/// brought in for a leftover expression (bd-3evfzwal) would otherwise +/// re-typeset every `\begin{…}` it finds there. fn mathjax_slot_html(url: &str) -> String { format!( "\n\ @@ -410,7 +422,7 @@ impl PipelineStage for MathJsStage { // Read the user-selected engine (or fall back to default). // `from_meta` returns `None` for explicitly-unsupported methods - // (`webtex` / `gladtex` / `mathml` / `plain` — deferred). In + // (`webtex` / `gladtex` / `plain`). In // that case we leave `meta.math` unset so the document author // can supply their own approach via includes / custom template. let Some(engine) = MathEngine::from_meta(&doc.ast.meta) else { @@ -969,6 +981,46 @@ mod tests { ); } + /// `html-math-method: mathml` (bd-3evfzwal): MathJax is the fallback + /// for expressions `MathMlStage` left as TeX, so injection happens + /// iff `Inline::Math` survives — never for a fully converted page. + #[tokio::test] + async fn mathml_method_injects_mathjax_only_for_leftover_math() { + let leftover = run_and_get_math( + vec![paragraph(vec![inline_math("x + \\bogus y")])], + meta_with_string("html-math-method", "mathml"), + ) + .await + .expect("leftover TeX needs MathJax"); + assert!(leftover.contains(DEFAULT_MATHJAX_URL)); + + let converted = run_and_get_math( + vec![paragraph(vec![str_inline("only a element here")])], + meta_with_string("html-math-method", "mathml"), + ) + .await; + assert!(converted.is_none(), "no leftover math → nothing to load"); + } + + /// The MathJax skip list must keep MathJax's own `annotation` and + /// `annotation-xml` entries: under `html-math-method: mathml` the + /// converted `` elements carry the source TeX in an + /// ``, and when MathJax is loaded for a leftover + /// expression (bd-3evfzwal) it would otherwise re-typeset every + /// `\begin{…}` it finds in those annotations (seen in a browser: + /// zero-size assistive MathML inside the converted matrices). + #[test] + fn mathjax_skip_list_excludes_annotations() { + let slot = MathEngine::default_engine().render_math_slot(); + let skip = slot + .lines() + .find(|l| l.contains("skipHtmlTags")) + .expect("skipHtmlTags in the MathJax config"); + for tag in ["'annotation'", "'annotation-xml'", "'pre'", "'script'"] { + assert!(skip.contains(tag), "{tag} missing from {skip}"); + } + } + // ── Tests for the engine config parser (independent of stage) ── #[test] diff --git a/crates/quarto-core/src/stage/stages/math_ml.rs b/crates/quarto-core/src/stage/stages/math_ml.rs new file mode 100644 index 000000000..b34c8f521 --- /dev/null +++ b/crates/quarto-core/src/stage/stages/math_ml.rs @@ -0,0 +1,372 @@ +/* + * stage/stages/math_ml.rs + * Copyright (c) 2026 Posit, PBC + * + * Convert math to MathML at render time (`html-math-method: mathml`). + */ + +//! Native MathML for `format: html` (bd-3evfzwal). +//! +//! When the document selects `html-math-method: mathml`, every +//! `Inline::Math` is converted with `quarto_math` (`Target::MathMl`) and +//! replaced by +//! +//! ```html +//! +//! ``` +//! +//! (as a `Span` around a `RawInline`), the same wrapper the HTML writer +//! emits around TeX, so stylesheets and the preview-parity tooling keep +//! working. Every current browser renders MathML Core natively, so a +//! document whose math all converts ships no MathJax. +//! +//! An expression the converter cannot fully handle (an unknown command, an +//! unbalanced `\left`) is left as `Inline::Math`. `MathJsStage`, which runs +//! next, then sees leftover math and loads MathJax for the page, so the +//! reader still gets rendered math; the author gets the `Q-22-*` +//! diagnostic pointing at the offending characters in the `.qmd`, +//! downgraded to a warning because the page did render. The plan calls +//! this the hybrid fallback (decision 2 of +//! `claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md`). +//! +//! Numbered equations reach this stage with their number already encoded +//! as a sibling label by `EquationNumberStage` (the `Sibling` encoding), +//! so the TeX handed to the converter is the author's, with no `\tag`. +//! +//! Excluded from the q2-preview pipeline, which renders `Inline::Math` +//! client-side with KaTeX. + +use async_trait::async_trait; + +use quarto_error_reporting::{DiagnosticKind, DiagnosticMessage}; +use quarto_math::convert::{Target, convert}; +use quarto_math::normalize::Mode; +use quarto_math::spec::Spec; +use quarto_pandoc_types::attr::AttrSourceInfo; +use quarto_pandoc_types::inline::{Inline, Math, MathType, RawInline, Span}; + +use crate::ast_walk::for_each_inline_mut; +use crate::math_method::{MathMethod, MathMethodConfig}; +use crate::stage::{ + EventLevel, PipelineData, PipelineDataKind, PipelineError, PipelineStage, StageContext, +}; +use crate::trace_event; + +/// Convert every `Inline::Math` to MathML when `html-math-method: mathml`. +pub struct MathMlStage; + +impl MathMlStage { + pub fn new() -> Self { + Self + } +} + +impl Default for MathMlStage { + fn default() -> Self { + Self::new() + } +} + +#[async_trait(?Send)] +impl PipelineStage for MathMlStage { + fn name(&self) -> &str { + "math-ml" + } + + fn input_kind(&self) -> PipelineDataKind { + PipelineDataKind::DocumentAst + } + + fn output_kind(&self) -> PipelineDataKind { + PipelineDataKind::DocumentAst + } + + async fn run( + &self, + input: PipelineData, + ctx: &mut StageContext, + ) -> Result { + let PipelineData::DocumentAst(mut doc) = input else { + return Err(PipelineError::unexpected_input( + self.name(), + self.input_kind(), + input.kind(), + )); + }; + + if MathMethodConfig::from_meta(&doc.ast.meta).method != MathMethod::MathMl { + return Ok(PipelineData::DocumentAst(doc)); + } + + let outcome = convert_document(&mut doc.ast.blocks); + ctx.add_diagnostics(outcome.diagnostics); + if outcome.converted > 0 || outcome.left > 0 { + trace_event!( + ctx, + EventLevel::Debug, + "math-ml: converted {} expression(s) to MathML, left {} as TeX for MathJax", + outcome.converted, + outcome.left + ); + } + + Ok(PipelineData::DocumentAst(doc)) + } +} + +/// What one pass over a document did. +#[derive(Default)] +pub struct Outcome { + /// Expressions replaced by MathML. + pub converted: usize, + /// Expressions left as `Inline::Math` for the JS fallback. + pub left: usize, + /// Every diagnostic the converter raised, located in the `.qmd`; + /// errors downgraded to warnings (see the module docs). + pub diagnostics: Vec, +} + +/// Convert every `Inline::Math` under `blocks` in place. +pub fn convert_document(blocks: &mut [quarto_pandoc_types::block::Block]) -> Outcome { + let mut outcome = Outcome::default(); + for_each_inline_mut(blocks, &mut |inline| { + let Inline::Math(math) = inline else { + return; + }; + match convert_math(math) { + (Some(replacement), diagnostics) => { + outcome.converted += 1; + outcome.diagnostics.extend(diagnostics); + *inline = replacement; + } + (None, diagnostics) => { + outcome.left += 1; + outcome + .diagnostics + .extend(diagnostics.into_iter().map(downgrade_to_warning)); + } + } + }); + outcome +} + +/// Convert one expression. `Some` is the replacement inline; `None` means +/// the converter withheld output and the TeX must stay. +fn convert_math(math: &Math) -> (Option, Vec) { + let mode = match math.math_type { + MathType::InlineMath => Mode::Inline, + MathType::DisplayMath => Mode::Display, + }; + // The text's own mapping when the reader recorded one (bd-ieldbghj), + // else the node: diagnostics then still land on the `$…$` block. + let text_source = math + .text_source + .clone() + .unwrap_or_else(|| math.source_info.clone()); + let conversion = convert( + &math.text, + mode, + Target::MathMl, + &text_source, + Spec::builtin(), + ); + let Some(mathml) = conversion.output else { + return (None, conversion.diagnostics); + }; + let class = match math.math_type { + MathType::InlineMath => "inline", + MathType::DisplayMath => "display", + }; + let replacement = Inline::Span(Span { + attr: ( + String::new(), + vec!["math".to_string(), class.to_string()], + Default::default(), + ), + content: vec![Inline::RawInline(RawInline { + format: "html".to_string(), + text: mathml, + source_info: math.source_info.clone(), + })], + source_info: math.source_info.clone(), + attr_source: AttrSourceInfo::empty(), + }); + (Some(replacement), conversion.diagnostics) +} + +/// The page still renders (MathJax takes the expression), so a conversion +/// error is a warning to the author, not a failed render. +fn downgrade_to_warning(mut d: DiagnosticMessage) -> DiagnosticMessage { + if d.kind == DiagnosticKind::Error { + d.kind = DiagnosticKind::Warning; + } + d +} + +#[cfg(test)] +mod tests { + use super::*; + use quarto_pandoc_types::block::{Block, Header, Paragraph}; + use quarto_pandoc_types::custom::{CustomNode, Slot}; + use quarto_pandoc_types::inline::Str; + use quarto_source_map::{FileId, SourceInfo}; + + fn si() -> SourceInfo { + SourceInfo::for_test() + } + + fn math(math_type: MathType, text: &str) -> Inline { + Inline::Math(Math { + math_type, + text: text.to_string(), + source_info: si(), + text_source: None, + }) + } + + fn para(content: Vec) -> Block { + Block::Paragraph(Paragraph { + content, + source_info: si(), + }) + } + + fn raw_html(inline: &Inline) -> &str { + let Inline::Span(span) = inline else { + panic!("expected the math span, got {inline:?}"); + }; + assert_eq!(span.attr.0, ""); + assert!(span.attr.2.is_empty()); + let [Inline::RawInline(raw)] = span.content.as_slice() else { + panic!("expected one RawInline, got {:?}", span.content); + }; + assert_eq!(raw.format, "html"); + &raw.text + } + + #[test] + fn inline_and_display_math_become_math_spans() { + let mut blocks = vec![para(vec![ + math(MathType::InlineMath, "x^2"), + Inline::Str(Str { + text: "and".to_string(), + source_info: si(), + }), + math(MathType::DisplayMath, "\\frac{a}{b}"), + ])]; + let out = convert_document(&mut blocks); + assert_eq!((out.converted, out.left), (2, 0)); + assert!(out.diagnostics.is_empty()); + let Block::Paragraph(p) = &blocks[0] else { + panic!() + }; + let Inline::Span(inline_span) = &p.content[0] else { + panic!() + }; + assert_eq!(inline_span.attr.1, vec!["math", "inline"]); + let inline_html = raw_html(&p.content[0]); + assert!( + inline_html + .starts_with(r#""#) + ); + assert!(inline_html.contains("x2")); + assert!(matches!(&p.content[1], Inline::Str(s) if s.text == "and")); + let Inline::Span(display_span) = &p.content[2] else { + panic!() + }; + assert_eq!(display_span.attr.1, vec!["math", "display"]); + let display_html = raw_html(&p.content[2]); + assert!(display_html.contains(r#"display="block""#)); + assert!(display_html.contains("ab")); + } + + #[test] + fn an_unconvertible_expression_stays_math_with_a_warning() { + let mut blocks = vec![para(vec![math(MathType::InlineMath, "x + \\bogus y")])]; + let out = convert_document(&mut blocks); + assert_eq!((out.converted, out.left), (0, 1)); + let Block::Paragraph(p) = &blocks[0] else { + panic!() + }; + assert!( + matches!(&p.content[0], Inline::Math(m) if m.text == "x + \\bogus y"), + "the TeX must be untouched for MathJax, got {:?}", + p.content[0] + ); + assert_eq!(out.diagnostics.len(), 1); + assert_eq!(out.diagnostics[0].code.as_deref(), Some("Q-22-1")); + assert_eq!(out.diagnostics[0].kind, DiagnosticKind::Warning); + } + + /// A conversion that succeeds with a caveat keeps its warning as-is. + #[test] + fn a_warning_on_a_converted_expression_is_kept() { + // `\not` on a non-symbol is unsupported and produces an Error node... + // so pick a real warning: ragged rows in an environment. + let mut blocks = vec![para(vec![math( + MathType::DisplayMath, + "\\begin{matrix} a & b \\\\ c \\end{matrix}", + )])]; + let out = convert_document(&mut blocks); + assert_eq!((out.converted, out.left), (1, 0)); + assert_eq!(out.diagnostics.len(), 1, "{:?}", out.diagnostics); + assert_eq!(out.diagnostics[0].kind, DiagnosticKind::Warning); + } + + #[test] + fn diagnostics_point_into_the_text_source_when_present() { + // `$x + \bogus y$` at file bytes 0..14; text at 1..13. + let node = SourceInfo::original(FileId(0), 0, 14); + let text = SourceInfo::original(FileId(0), 1, 13); + let mut blocks = vec![para(vec![Inline::Math(Math { + math_type: MathType::InlineMath, + text: "x + \\bogus y".to_string(), + source_info: node, + text_source: Some(text), + })])]; + let out = convert_document(&mut blocks); + let loc = out.diagnostics[0].location.as_ref().expect("located"); + // `\bogus` is at text offsets 4..10, so file bytes 5..11. + assert_eq!(loc.preimage_in(FileId(0)), Some(5..11)); + } + + #[test] + fn math_inside_headers_notes_and_custom_slots_is_converted() { + let mut custom = CustomNode::new("Anything", quarto_pandoc_types::attr::empty_attr(), si()); + custom.slots.insert( + "content".to_string(), + Slot::Inlines(vec![math(MathType::InlineMath, "c^2")]), + ); + let mut blocks = vec![ + Block::Header(Header { + level: 1, + attr: quarto_pandoc_types::attr::empty_attr(), + content: vec![math(MathType::InlineMath, "h^2")], + source_info: si(), + attr_source: AttrSourceInfo::empty(), + }), + para(vec![Inline::Note(quarto_pandoc_types::inline::Note { + content: vec![para(vec![math(MathType::InlineMath, "n^2")])], + source_info: si(), + })]), + Block::Custom(custom), + ]; + let out = convert_document(&mut blocks); + assert_eq!((out.converted, out.left), (3, 0)); + let mut seen = Vec::new(); + for_each_inline_mut(&mut blocks, &mut |inline| { + if let Inline::RawInline(raw) = inline { + for var in ["h", "n", "c"] { + if raw + .text + .contains(&format!("{var}2")) + { + seen.push(var); + } + } + } + }); + seen.sort_unstable(); + assert_eq!(seen, vec!["c", "h", "n"]); + } +} diff --git a/crates/quarto-core/src/stage/stages/mod.rs b/crates/quarto-core/src/stage/stages/mod.rs index a57f2ccfa..e163f8688 100644 --- a/crates/quarto-core/src/stage/stages/mod.rs +++ b/crates/quarto-core/src/stage/stages/mod.rs @@ -61,6 +61,10 @@ mod source_conversion; // when the document contains Math elements. Included on both native // and WASM pipelines (math display is safe under iframe reinit). mod math_js; +// Native MathML (bd-3evfzwal): converts every `Inline::Math` with +// quarto-math when `html-math-method: mathml`; leftovers fall back to +// MathJax via math_js. Included on native and WASM. +mod math_ml; mod metadata_merge; mod parse_document; mod pre_engine_sugaring; @@ -106,6 +110,7 @@ pub use language_resolve::LanguageResolveStage; pub use link_resolution::LinkResolutionStage; pub use listing_item_info::ListingItemInfoStage; pub use math_js::{DEFAULT_KATEX_URL_BASE, DEFAULT_MATHJAX_URL, MathEngine, MathJsStage}; +pub use math_ml::MathMlStage; pub use metadata_merge::MetadataMergeStage; pub use parse_document::ParseDocumentStage; pub use pre_engine_sugaring::PreEngineSugaringStage; diff --git a/crates/quarto-core/tests/integration/equation_numbering_pipeline.rs b/crates/quarto-core/tests/integration/equation_numbering_pipeline.rs index 21766d052..80cb5ca51 100644 --- a/crates/quarto-core/tests/integration/equation_numbering_pipeline.rs +++ b/crates/quarto-core/tests/integration/equation_numbering_pipeline.rs @@ -135,15 +135,17 @@ fn unknown_method_encodes_qquad_inside_tex() { assert!(html.contains("\\[E = mc^2 \\qquad(1)\\]"), "got:\n{html}"); } -/// `mathml`: the TeX is untouched (the MathML stage, bd-3evfzwal, will -/// convert it later) and the number is a sibling label outside the math, +/// `mathml`: the TeX is untouched (the MathML stage, bd-3evfzwal, converts +/// it next) and the number is a sibling label outside the math, /// with a modifier class on the equation span for the CSS that lays the /// two out. #[test] fn mathml_method_places_number_as_sibling_label() { let html = render(&labelled_doc("html-math-method: mathml\n"), "html"); + // The MathML stage (bd-3evfzwal) converts the math; the annotation + // carries the author's TeX, untouched (no `\tag`). assert!( - html.contains("\\[E = mc^2\\]"), + html.contains(r#"E = mc^2"#), "the math text must be untouched; got:\n{html}" ); assert!( @@ -164,7 +166,7 @@ fn mathml_method_places_number_as_sibling_label() { fn mathml_sibling_label_is_inside_the_equation_span() { let html = render(&labelled_doc("html-math-method: mathml\n"), "html"); let start = html.find("id=\"eq-einstein\"").expect("equation span"); - let math_end = html[start..].find("\\]").expect("math span end") + start; + let math_end = html[start..].find("").expect("math span end") + start; let label = html[start..].find("quarto-eq-number\">(1)").expect("label") + start; assert!( label > math_end, @@ -193,10 +195,13 @@ fn unlabelled_display_math_is_untouched() { &format!("---\ntitle: Unlabelled\n{method}---\n\n$$x^2$$\n"), "html", ); - assert!( - html.contains("\\[x^2\\]"), - "method {method:?}: got:\n{html}" - ); + let untouched = if method.contains("mathml") { + // Converted to MathML; the annotation carries the TeX. + r#"x^2"# + } else { + "\\[x^2\\]" + }; + assert!(html.contains(untouched), "method {method:?}: got:\n{html}"); assert!(!html.contains("\\tag{")); assert!(!html.contains("qquad")); assert!(!html.contains("quarto-eq-number")); diff --git a/crates/quarto-core/tests/integration/math_mode_pipeline.rs b/crates/quarto-core/tests/integration/math_mode_pipeline.rs index 81eed0f4c..9cfacdd7e 100644 --- a/crates/quarto-core/tests/integration/math_mode_pipeline.rs +++ b/crates/quarto-core/tests/integration/math_mode_pipeline.rs @@ -310,3 +310,167 @@ fn website_only_math_page_references_mathjax() { "math-free page must NOT reference MathJax CDN" ); } + +// ── html-math-method: mathml (bd-3evfzwal) ────────────────────────────── + +/// Render `doc.qmd` with the given body under `html-math-method: mathml` +/// and return the HTML plus the render diagnostics. +fn render_mathml(body: &str) -> (String, Vec) { + let temp = TempDir::new().unwrap(); + let qmd_path = temp.path().join("doc.qmd"); + write_file( + &qmd_path, + &format!("---\ntitle: MathML\nhtml-math-method: mathml\n---\n\n{body}\n"), + ); + let runtime = runtime_arc(); + let options = RenderToFileOptions::default(); + let result = render_to_file(&qmd_path, "html", &options, runtime).expect("render"); + let html = read(&result.output_path); + (html, result.render_output.diagnostics) +} + +/// Every expression converts: native `` in the page, the TeX +/// delimiters gone, no JS engine loaded, and a numbered equation's label +/// sits outside the math. +#[test] +fn mathml_method_emits_native_mathml_and_no_loader() { + let (html, diagnostics) = render_mathml( + "Inline $x^2$ and display:\n\n$$\\frac{a}{b}$$\n\n$$E = mc^2$$ {#eq-e}\n\nSee @eq-e.", + ); + assert!( + html.contains(r#"x2"#), + "inline math must be native MathML inside the usual span; got:\n{html}" + ); + assert!( + html.contains(r#"ab"#), + "display math must be block MathML; got:\n{html}" + ); + assert!( + html.contains(r#"E = mc^2(1)"#), + "the numbered equation keeps its TeX untouched and carries the label as a sibling; got:\n{html}" + ); + assert!( + !html.contains("\\("), + "no inline TeX delimiters may remain:\n{html}" + ); + assert!( + !html.contains("\\["), + "no display TeX delimiters may remain:\n{html}" + ); + assert!(!html.contains("\\tag{")); + assert!( + !html.contains(MATHJAX_CONFIG_SENTINEL), + "no MathJax when everything converted" + ); + assert!(!html.contains("cdn.jsdelivr.net/npm/mathjax")); + assert!(!html.contains("cdn.jsdelivr.net/npm/katex")); + assert!( + html.contains("Equation\u{a0}1"), + "the crossref link still resolves" + ); + let math_codes: Vec<&str> = diagnostics + .iter() + .filter_map(|d| d.code.as_deref()) + .filter(|c| c.starts_with("Q-22-")) + .collect(); + assert!( + math_codes.is_empty(), + "no math diagnostics expected, got {math_codes:?}" + ); +} + +/// An expression the converter rejects stays TeX, and MathJax is loaded +/// for the page so the reader still sees it rendered (plan decision 2). +/// The author gets a `Q-22-1` *warning* pointing at the command. +#[test] +fn mathml_method_falls_back_to_mathjax_for_an_unconvertible_expression() { + let (html, diagnostics) = render_mathml("Good $a^2$ and bad $x + \\bogus y$."); + assert!( + html.contains("a2"), + "the convertible expression is MathML; got:\n{html}" + ); + assert!( + html.contains(r#"\(x + \bogus y\)"#), + "the unconvertible expression stays TeX for MathJax; got:\n{html}" + ); + assert!( + html.contains(MATHJAX_CONFIG_SENTINEL) && html.contains(DEFAULT_MATHJAX_URL), + "MathJax must be loaded for the leftover expression; got:\n{html}" + ); + // …and must not re-typeset the TeX inside the converted math's + // `` (MathJax's default skip list covers it; ours must too). + assert!( + html.contains("'annotation', 'annotation-xml'"), + "the MathJax skip list must include annotation elements; got:\n{html}" + ); + let bogus: Vec<_> = diagnostics + .iter() + .filter(|d| d.code.as_deref() == Some("Q-22-1")) + .collect(); + assert_eq!( + bogus.len(), + 1, + "one unknown-command diagnostic; got {diagnostics:?}" + ); + assert_eq!( + bogus[0].kind, + quarto_error_reporting::DiagnosticKind::Warning, + "the page still renders (MathJax), so this is a warning, not an error" + ); + assert!( + bogus[0].location.is_some(), + "the diagnostic points into the .qmd" + ); +} + +/// No math: neither `` nor a loader. +#[test] +fn mathml_method_without_math_emits_nothing() { + let (html, _) = render_mathml("Just prose."); + assert!(!html.contains("{var}2")), + "math in a container ({var}) must be converted; got:\n{html}" + ); + } + assert!(!html.contains("\\(")); + assert!(!html.contains(MATHJAX_CONFIG_SENTINEL)); +} + +/// Two-page site: the mathml page gets `` and no loader; the +/// math-free page gets neither. +#[test] +fn website_mathml_page_has_math_and_no_loader() { + let project_dir = render_website(|dir| { + write_file( + &dir.join("_quarto.yml"), + "project:\n type: website\nformat:\n html:\n html-math-method: mathml\n", + ); + write_file( + &dir.join("index.qmd"), + "---\ntitle: Home\n---\n\nNothing here.\n", + ); + write_file( + &dir.join("equations.qmd"), + "---\ntitle: Equations\n---\n\nWe have $x + 1 = y$.\n", + ); + }); + let site = project_dir.join("_site"); + let index_html = read(&site.join("index.html")); + let eq_html = read(&site.join("equations.html")); + assert!(eq_html.contains("` element. Every current browser +renders MathML Core natively, so the page loads no math library and the +math is part of the document rather than something a script draws after +the page loads. The converted element also carries the original TeX in an +annotation, so copying an equation still gives you its source. + +A few things to know: + +- **Fallback.** If an expression uses a command the converter does not + know, that expression is left as TeX and MathJax is loaded for the + page so it still renders. Quarto reports a `Q-22-*` warning that points + at the command in your `.qmd`; fixing it (or defining the macro in the + expression with `\newcommand`) removes the dependency on MathJax. See + [Q-22-1](../../../errors/math/Q-22-1.qmd) for the most common case. +- **Equation numbers.** Numbered equations (`$$…$$ {#eq-name}`) show + their number as a label to the right of the equation, outside the math. +- **Fonts.** Browsers lay out MathML with a math font when one is + installed (Windows ships Cambria Math; Firefox bundles STIX). Without + one, the browser falls back to the page font and some constructs, such + as large operators and stretchy delimiters, look less refined than with + MathJax or KaTeX. Quarto does not ship a math font. +- **`\cancel`.** The strike-through is drawn by Firefox but not by + Chrome, which shows the content without the line. + +## Unsupported methods + +Quarto 1's `webtex` and `gladtex` methods are not available. They are +treated like `plain`: the TeX stays in the page unrendered. From 66d215b46ccd59a9a34f020a78fbf0cd02ad4acc Mon Sep 17 00:00:00 2001 From: Carlos Scheidegger Date: Mon, 21 Sep 2026 19:10:46 -0500 Subject: [PATCH 3/4] plan: record PRs #708/#709/#710 and the GitHub stack Co-Authored-By: Claude Fable 5.1 --- .../plans/2026-09-21-equation-numbering-and-mathml.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md b/claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md index 39f7b1966..d664dd9ea 100644 --- a/claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md +++ b/claude-notes/plans/2026-09-21-equation-numbering-and-mathml.md @@ -1,8 +1,9 @@ # Format-specific equation numbering and `html-math-method: mathml` **Status:** approved 2026-09-21 (all five open decisions settled with the -user, each as recommended). Phases 1–3 implemented and committed the same -day on three branches (see Phase 0); nothing pushed yet. +user, each as recommended). Phases 1–3 implemented the same day. PRs: #708 (Phase 1, +against `main`), #709 (Phase 2, stacked on #706) and #710 (Phase 3, stacked +on #709), the last two linked into the GitHub stack #705 → #706 → #709 → #710. **Strands:** bd-vlhi2zkj (Phase 1, equation numbering; branch `braid/bd-vlhi2zkj-equation-numbering` off `main`), bd-9z83tcv0 (Phase 2, MathML writer, p2), bd-3evfzwal (Phase 3, `MathMlStage`; blocked on the From 56b93af78c25a1782dbf63a5735432e8da2ecaa8 Mon Sep 17 00:00:00 2001 From: t Date: Wed, 23 Sep 2026 13:32:41 -0500 Subject: [PATCH 4/4] MathMlStage: ignore html-math-method on non-HTML formats (bd-3evfzwal) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../plans/2026-09-23-merge-math-stack.md | 65 +++++++++++++++++++ crates/quarto-core/src/pipeline.rs | 5 ++ .../quarto-core/src/stage/stages/math_ml.rs | 48 +++++++++++++- .../tests/integration/math_mode_pipeline.rs | 55 +++++++++++++++- 4 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 claude-notes/plans/2026-09-23-merge-math-stack.md diff --git a/claude-notes/plans/2026-09-23-merge-math-stack.md b/claude-notes/plans/2026-09-23-merge-math-stack.md new file mode 100644 index 000000000..9e02225b3 --- /dev/null +++ b/claude-notes/plans/2026-09-23-merge-math-stack.md @@ -0,0 +1,65 @@ +# Merging the math stack (PRs 705, 706, 708, 709, 710) onto main after #704 + +## Overview + +PR #704 (Pandoc-hybrid docx/pptx/epub/typst) landed on main on 2026-09-21. +The math stack — `Math.text_source` (#705), `quarto-math` (#706), the +equation-numbering stage (#708), the MathML writer (#709) and the +`MathMlStage` (#710) — was branched before it. This plan records the +conflict assessment (2026-09-23) and tracks the merge, one PR at a time. + +Parent plans: `2026-09-21-quarto-math-and-native-docx.md`, +`2026-09-21-equation-numbering-and-mathml.md`. + +## Assessment (2026-09-23, trial merges against origin/main 6d648d92) + +- **#705**: conflicts only in two regenerable artifacts + (`crates/pampa/snapshots/json/math-with-attr.snap`, + `ts-packages/annotated-qmd/examples/academic-paper.json`). Rust + auto-merges and compiles; pampa/quarto-core/pandoc-types tests pass after + regeneration. Pandoc 3.11 ignores the new `textS` sidecar on the + Pandoc leg. docx/typst output of a labelled equation unchanged. +- **#706**: `Cargo.lock` (both sides add packages; refresh from main), + `error_catalog.json` (main added Q-20/Q-21, 706 adds Q-22; union), + `docs/_quarto.yml` (errors sidebar: `pandoc`/`typst` vs `math` sections; + union). Lint passes on the union. +- **#708**: `tests/integration/main.rs` module list; and once #705 is on + main, `crossref_render.rs` (705 extended `text_source` in + `render_equation`, 708 rewrote it). #710 already carries the + resolution (`append_to_tex` in `equation_number.rs`) — take 710's + `crossref_render.rs`. docx/typst equation numbers are unaffected: + pandoc drops q2's `\tag{N}` anyway, and the number comes from the + vendored Quarto 1 `crossref/equations.lua` (`\qquad(N)`). Verified by + rendering before/after with the real binary. +- **#709**: plan-doc checkbox conflict only. +- **#710**: one real regression against #704 — `MathMlStage` gates on + `html-math-method` only, not on the format, and is not on + `PANDOC_STAGE_EXCLUDED`. A document with `html-math-method: mathml` + rendered `--to docx` converts its math to `RawInline` before the + Pandoc leg, and the vendored `equations.lua` crashes + (`attempt to concatenate a nil value (field 'text')`, pandoc exit 83, + Q-20-3). Decision (user, 2026-09-23): **ignore the option silently on + non-HTML formats**, matching `EquationNumberStage`'s `Writer` no-op and + Quarto 1 (which forwards the key to pandoc, whose non-HTML writers + ignore it). No warning: the `html-` prefix is what makes the key safe + in shared metadata for multi-format projects. + +## Merge order and checklist + +- [ ] **#705** — merge main, regenerate the two artifacts, tests, push, + mark ready, merge when CI is green. +- [ ] **#706** — retarget to main, merge main, union-resolve catalog + + sidebar, refresh `Cargo.lock`, build + tests + lint, push, merge. +- [ ] **#708** — merge main, take #710's `crossref_render.rs`, fix the + module list, build + tests, push, merge. +- [ ] **#709** — retarget to main, merge main, resolve plan doc, tests, + push, merge. +- [ ] **#710** — retarget to main, merge main; gate `MathMlStage` on + `is_html_based()`, add `math-ml` to `PANDOC_STAGE_EXCLUDED`, update + `t6_2_pandoc_stage_list_produces_exact_surviving_name_list`, add an + end-to-end test (mathml-method document `--to docx` keeps its OMML + equation); build + tests; push; merge. + +## Verification log + +(filled in as each PR lands) diff --git a/crates/quarto-core/src/pipeline.rs b/crates/quarto-core/src/pipeline.rs index 4fa6e4e44..768a1c41a 100644 --- a/crates/quarto-core/src/pipeline.rs +++ b/crates/quarto-core/src/pipeline.rs @@ -526,6 +526,10 @@ const PANDOC_STAGE_EXCLUDED: &[&str] = &[ "tabsets-js", "code-highlight", "math-js", + // `html-math-method` is an HTML option; MathMlStage is a no-op for + // every other format (see its module docs) and is excluded here so + // the Pandoc leg never hands a converted RawInline to pandoc. + "math-ml", "render-html-body", "apply-template", ]; @@ -5202,6 +5206,7 @@ mod tests { "ast-transforms", "user-filters-post", "resource-report", + "equation-number", "pandoc-write", ] ); diff --git a/crates/quarto-core/src/stage/stages/math_ml.rs b/crates/quarto-core/src/stage/stages/math_ml.rs index b34c8f521..d61458025 100644 --- a/crates/quarto-core/src/stage/stages/math_ml.rs +++ b/crates/quarto-core/src/stage/stages/math_ml.rs @@ -35,6 +35,16 @@ //! //! Excluded from the q2-preview pipeline, which renders `Inline::Math` //! client-side with KaTeX. +//! +//! **HTML-based formats only.** `html-math-method` is an HTML option, and +//! it commonly sits in shared metadata of a project that also renders +//! docx or typst. On the Pandoc leg the stage is a no-op (and `math-ml` +//! is on `PANDOC_STAGE_EXCLUDED`), matching Quarto 1, which forwards the +//! key to pandoc, whose non-HTML writers ignore it, and matching +//! `EquationNumberStage`'s `Writer` encoding. Converting there would +//! hand a `RawInline` to the vendored `crossref/equations.lua`, which +//! crashes on it (pandoc exit 83, Q-20-3). No diagnostic is raised: the +//! `html-` prefix is exactly what makes the key safe to share. use async_trait::async_trait; @@ -46,6 +56,7 @@ use quarto_pandoc_types::attr::AttrSourceInfo; use quarto_pandoc_types::inline::{Inline, Math, MathType, RawInline, Span}; use crate::ast_walk::for_each_inline_mut; +use crate::format::Format; use crate::math_method::{MathMethod, MathMethodConfig}; use crate::stage::{ EventLevel, PipelineData, PipelineDataKind, PipelineError, PipelineStage, StageContext, @@ -94,7 +105,8 @@ impl PipelineStage for MathMlStage { )); }; - if MathMethodConfig::from_meta(&doc.ast.meta).method != MathMethod::MathMl { + let method = MathMethodConfig::from_meta(&doc.ast.meta).method; + if !applies_to(&ctx.format, &method) { return Ok(PipelineData::DocumentAst(doc)); } @@ -114,6 +126,14 @@ impl PipelineStage for MathMlStage { } } +/// Whether the stage converts anything for a document rendered to +/// `format` with `method`: only an HTML-based format that selected +/// `html-math-method: mathml`. Every other format ignores the option (see +/// the module docs). +pub fn applies_to(format: &Format, method: &MathMethod) -> bool { + format.identifier.is_html_based() && *method == MathMethod::MathMl +} + /// What one pass over a document did. #[derive(Default)] pub struct Outcome { @@ -369,4 +389,30 @@ mod tests { seen.sort_unstable(); assert_eq!(seen, vec!["c", "h", "n"]); } + + /// `html-math-method` is an HTML option: the stage runs for html and + /// revealjs with `mathml`, and for nothing else. + #[test] + fn applies_only_to_html_based_formats_with_mathml() { + use crate::format::FormatIdentifier; + let format = |identifier| Format { + identifier, + ..Format::html() + }; + for id in [FormatIdentifier::Html, FormatIdentifier::Revealjs] { + assert!(applies_to(&format(id), &MathMethod::MathMl), "{id:?}"); + assert!(!applies_to(&format(id), &MathMethod::Mathjax), "{id:?}"); + } + for id in [ + FormatIdentifier::Docx, + FormatIdentifier::Pptx, + FormatIdentifier::Typst, + FormatIdentifier::Pdf, + ] { + assert!( + !applies_to(&format(id), &MathMethod::MathMl), + "{id:?} must ignore html-math-method" + ); + } + } } diff --git a/crates/quarto-core/tests/integration/math_mode_pipeline.rs b/crates/quarto-core/tests/integration/math_mode_pipeline.rs index 9cfacdd7e..e61c25411 100644 --- a/crates/quarto-core/tests/integration/math_mode_pipeline.rs +++ b/crates/quarto-core/tests/integration/math_mode_pipeline.rs @@ -33,7 +33,7 @@ use tempfile::TempDir; use quarto_core::format::Format; use quarto_core::project::ProjectContext; use quarto_core::project::orchestrator::{ProjectPipeline, project_type_for}; -use quarto_core::render_to_file::{RenderToFileOptions, render_to_file}; +use quarto_core::render_to_file::{RenderToFileOptions, render_document_to_file, render_to_file}; use quarto_core::stage::stages::{DEFAULT_KATEX_URL_BASE, DEFAULT_MATHJAX_URL}; use quarto_system_runtime::{NativeRuntime, SystemRuntime}; @@ -67,6 +67,59 @@ const KATEX_AUTO_RENDER_SENTINEL: &str = "renderMathInElement"; // ── Single-doc tests ──────────────────────────────────────────────────── +/// `html-math-method` is an HTML option. A document that selects `mathml` +/// and is rendered to docx (a common shape: the key sits in shared +/// metadata of a project that renders both) must reach pandoc with its +/// `Inline::Math` intact and come out with a native OMML equation, +/// numbered by the vendored `crossref/equations.lua`. Before `MathMlStage` +/// was gated on the format it converted the math to a `RawInline` on the +/// Pandoc leg too, and that Lua filter crashed on it (pandoc exit 83). +/// Requires a real `pandoc` on `PATH`, like every Pandoc-leg test. +#[test] +fn mathml_method_is_ignored_for_docx() { + let temp = TempDir::new().unwrap(); + let project_dir = temp.path().canonicalize().unwrap(); + let qmd_path = project_dir.join("doc.qmd"); + write_file( + &qmd_path, + "---\ntitle: Math\nhtml-math-method: mathml\n---\n\n$$\nE = mc^2\n$$ {#eq-e}\n\nSee @eq-e.\n", + ); + + let result = render_document_to_file( + &qmd_path, + "docx", + &RenderToFileOptions::default(), + None, + runtime_arc(), + None, + None, + Some("docx"), + ) + .expect("docx render must succeed with html-math-method: mathml"); + let bytes = std::fs::read(&result.output_path).unwrap(); + let extraction = quarto_ooxml_extract::extract_docx(&bytes).expect("extract docx"); + + assert_eq!( + extraction.math.len(), + 1, + "exactly one OMML equation expected; got {extraction}" + ); + let math = &extraction.math[0]; + assert!( + math.contains("mc") && math.contains("(1)"), + "the equation must be native OMML, numbered by the vendored Lua: {math:?}" + ); + let body: String = extraction + .paragraphs + .iter() + .map(|p| p.text.as_str()) + .collect(); + assert!( + !body.contains("