Commit 02543d6
feat(math): implement m:nary n-ary operator converter (#2752)
* feat(math): implement m:nary n-ary operator converter (closes #2602)
Made-with: Cursor
* fix(math): parse full ST_OnOff values in nary converter
Made-with: Cursor
* fix(math): spec-compliant m:nary defaults + fill missing naryPr props (SD-2381)
- m:limLoc (§22.1.2.53): absent element now uses operator-character
heuristic — integrals default to subSup, non-integrals to undOvr.
<m:limLoc/> with no val attribute defaults to undOvr per spec.
- m:sub/m:sup (§22.1.2.70): treat as actually absent when the element
is missing, not just when a hide flag is set — indefinite integrals
now render as bare <mo> instead of <msubsup> with empty <mrow/> slots.
- m:chr (§22.1.2.20): <m:chr/> with no val renders an empty operator
instead of silently defaulting to the integral glyph.
- m:grow (§22.1.2.72): when explicitly OFF, emit largeop="false"
stretchy="false" on the <mo> so the renderer doesn't enlarge.
- Helper type is OmmlJsonNode (no more typeof subHide). JSDoc documents
all 6 output shapes.
Tests:
- 10 new unit tests in omml-to-mathml.test.ts covering every ECMA-376
spec path for m:nary (subHide true/bare/OFF, limLoc no-val, chr no-val,
operator heuristic, m:grow suppression, etc).
- New behavior fixture math-nary-tests.docx (13 scenarios) + 9 Playwright
tests in math-equations.spec.ts. Fixture uploaded to the shared R2
corpus as rendering/sd-2381-nary-scenarios.docx for layout/visual.
* fix(math): subHide/supHide only hide empty placeholders, not content (SD-2381)
Per ECMA-376 §22.1.2.72, the subHide/supHide flags control whether EMPTY
m:sub/m:sup limits are rendered as a placeholder character or hidden.
When the limit has content, it must always be rendered regardless of
the flag — matching Word's actual behavior.
Previous code hid the entire slot whenever the hide flag was ON, which
silently dropped content. For example, an integral with sub="0", sup="1",
and subHide=true was rendering as ∫¹ instead of ∫₀¹.
- hasSub/hasSup now check meaningful content (ignoring m:ctrlPr, the
formatting-hint child Word emits inside empty limit elements).
- The hide flag only suppresses the slot when the limit is empty/absent.
- Updated 3 unit tests that encoded the incorrect semantics; added a new
regression test for "content overrides hide" and a test confirming
m:ctrlPr-only limits are treated as empty.
- Updated the behavior test for the same scenarios.
* fix(math): promote hidden limit content into opposite slot (matches Word)
Follow-up to the previous subHide/supHide fix. When m:subHide is ON and
m:sub has content, Word doesn't simply suppress the slot — it promotes
the sub content into the sup slot, prepended to any existing sup content
(symmetric for supHide: sup content appended to sub). This preserves any
author-entered content even when a file is hand-crafted with conflicting
hide+content OMML.
Before: an integral with m:sub="0", m:sup="1", m:subHide="true" rendered
as <msubsup><mo>∫</mo><mrow>0</mrow><mrow>1</mrow></msubsup> (∫ with 0
below and 1 above — stacked).
After: renders as <msup><mo>∫</mo><mrow>01</mrow></msup>, matching
Word's ∫^{01} where 0 appears to the left of 1 in the superscript.
- Compute renderSubChildren / renderSupChildren with promotion, then pass
those arrays to convertChildren.
- Strip m:ctrlPr before the check so Word's "empty with formatting hint"
pattern still resolves to bare <mo>.
- Replaced the "content always wins over hide" unit test with two tests
anchoring the promotion (sub→sup, sup→sub).
- Updated the behavior test for scenarios 8/9 to assert msup with "01".
---------
Co-authored-by: Caio Pizzol <caiopizzol@icloud.com>
Co-authored-by: Caio Pizzol <caio@harbourshare.com>1 parent 4227ebe commit 02543d6
6 files changed
Lines changed: 939 additions & 1 deletion
File tree
- packages/layout-engine/painters/dom/src/features/math
- converters
- tests/behavior/tests/importing
- fixtures
Lines changed: 1 addition & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
Lines changed: 150 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
0 commit comments