feat(math): implement m:bar overbar/underbar converter#2619
Conversation
Closes superdoc-dev#2610 - Add convertBar() in converters/bar.ts: - Reads m:barPr/m:pos@m:val to determine position - 'top' (default) → <mover> with U+203E (overline) - 'bot' → <munder> with U+0332 (combining low line) - stretchy='true' so the bar stretches over the base expression - Register 'm:bar': convertBar in MATH_OBJECT_REGISTRY - Export convertBar from converters/index.ts - Add 3 unit tests: overbar, underbar, missing barPr fallback
There was a problem hiding this comment.
Pull request overview
Implements support for converting OMML m:bar (overbar/underbar) nodes into MathML, extending the OMML→MathML converter’s registry-based coverage.
Changes:
- Added a new
convertBarconverter (m:bar→<mover>/<munder>with a stretchy<mo>). - Registered
m:barin the math object registry and re-exported the converter. - Added unit tests covering overbar, underbar, and default behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| packages/layout-engine/painters/dom/src/features/math/omml-to-mathml.ts | Registers m:bar converter in the OMML→MathML registry. |
| packages/layout-engine/painters/dom/src/features/math/omml-to-mathml.test.ts | Adds unit tests for m:bar conversion behavior. |
| packages/layout-engine/painters/dom/src/features/math/converters/index.ts | Exports the new convertBar converter. |
| packages/layout-engine/painters/dom/src/features/math/converters/bar.ts | Implements m:bar → MathML conversion logic. |
| const barPr = elements.find((e) => e.name === 'm:barPr'); | ||
| const pos = barPr?.elements?.find((e) => e.name === 'm:pos'); | ||
| const posVal = pos?.attributes?.['m:val']; | ||
| const isUnder = posVal === 'bot'; | ||
|
|
There was a problem hiding this comment.
m:bar default position appears to be inverted. The implementation defaults to <mover> when m:barPr/m:pos is missing (since isUnder is only true for m:val === 'bot'), but the spec/issue description indicates 'bot' is the default. Consider treating missing/unknown m:pos@m:val as underbar and only using overbar when m:val === 'top', and update the doc comment accordingly.
There was a problem hiding this comment.
@Muhammad-Nur-Alamsyah-Anwar this is right — we tested in Word, default is underbar.
| const base = elements.find((e) => e.name === 'm:e'); | ||
|
|
||
| const wrapper = doc.createElementNS(MATHML_NS, isUnder ? 'munder' : 'mover'); | ||
| wrapper.appendChild(convertChildren(base?.elements ?? [])); |
There was a problem hiding this comment.
wrapper.appendChild(convertChildren(base?.elements ?? [])) can expand to multiple nodes (e.g., x+1 becomes <mi/><mo/><mn/>), which would make <mover>/<munder> have more than two children and produce invalid MathML. Consider converting the m:e container itself (so it can wrap multiple children into a single <mrow>), or otherwise ensure the base is a single node before appending the accent.
| wrapper.appendChild(convertChildren(base?.elements ?? [])); | |
| // Convert the m:e container itself so that the base becomes a single MathML node. | |
| wrapper.appendChild(convertChildren(base ? [base] : [])); |
There was a problem hiding this comment.
@Muhammad-Nur-Alamsyah-Anwar correct — bar breaks on multi-letter expressions. see our review comment.
| it('defaults to overbar when m:barPr is missing', () => { | ||
| const omml = { | ||
| name: 'm:oMath', | ||
| elements: [{ | ||
| name: 'm:bar', | ||
| elements: [ | ||
| { name: 'm:e', elements: [{ name: 'm:r', elements: [{ name: 'm:t', elements: [{ type: 'text', text: 'z' }] }] }] }, | ||
| ], | ||
| }], | ||
| }; | ||
| const result = convertOmmlToMathml(omml, doc); | ||
| expect(result).not.toBeNull(); | ||
| const mover = result!.querySelector('mover'); | ||
| expect(mover).not.toBeNull(); | ||
| }); |
There was a problem hiding this comment.
This test asserts the default behavior is an overbar (<mover>) when m:barPr is missing, but the issue/PR description states 'bot' is the default (i.e., <munder>). Update the expectation (and the converter) so the default matches the intended OMML behavior.
There was a problem hiding this comment.
@Muhammad-Nur-Alamsyah-Anwar yes, test expectation needs to flip to underbar.
packages/layout-engine/painters/dom/src/features/math/omml-to-mathml.test.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 983df1346b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const base = elements.find((e) => e.name === 'm:e'); | ||
|
|
||
| const wrapper = doc.createElementNS(MATHML_NS, isUnder ? 'munder' : 'mover'); | ||
| wrapper.appendChild(convertChildren(base?.elements ?? [])); |
There was a problem hiding this comment.
Wrap m:bar base in one node before adding accent
mover/munder are 2-argument MathML elements (base + script), but this line appends the converted children of m:e as a fragment, so expressions like x+1 under a bar become multiple base siblings plus the accent. In that case the bar is applied incorrectly (typically only to the first child) or the MathML tree is invalid for consumers. Wrap the converted base in a single container (e.g., <mrow>) before appending the accent so the bar applies to the full expression.
Useful? React with 👍 / 👎.
caio-pizzol
left a comment
There was a problem hiding this comment.
@Muhammad-Nur-Alamsyah-Anwar nice contribution! two things to fix: the default position should be underbar (verified against Word), and bars over multi-part expressions (like x + y) need the base grouped so the bar stretches across. left inline comments.
packages/layout-engine/painters/dom/src/features/math/converters/bar.ts
Outdated
Show resolved
Hide resolved
packages/layout-engine/painters/dom/src/features/math/converters/bar.ts
Outdated
Show resolved
Hide resolved
packages/layout-engine/painters/dom/src/features/math/omml-to-mathml.ts
Outdated
Show resolved
Hide resolved
packages/layout-engine/painters/dom/src/features/math/omml-to-mathml.test.ts
Show resolved
Hide resolved
- Default to munder (underbar) when no position is specified, matching Word's rendering behavior (posVal !== 'top') - Wrap base content in <mrow> to correctly group multi-token expressions like 'x + y' as a single MathML child - Move m:bar entry from 'Not yet implemented' to 'Implemented' section in the registry - Strengthen tests: assert base content text and <mo> character for all three cases (top, bot, default)
a89b98c to
7ab09f9
Compare
caio-pizzol
left a comment
There was a problem hiding this comment.
@Muhammad-Nur-Alamsyah-Anwar all four points from last round are fixed — default position, mrow wrapping, registry section, and test assertions. lgtm, approving.
|
🎉 This PR is included in vscode-ext v1.1.0-next.36 |
|
🎉 This PR is included in superdoc v1.24.0-next.35 The release is available on GitHub release |
|
🎉 This PR is included in superdoc-cli v0.5.0-next.35 The release is available on GitHub release |
|
🎉 This PR is included in superdoc-sdk v1.3.0-next.35 |
Closes #2610
Implemented the
m:bar(overbar/underbar) OMML → MathML converter.OMML:
m:bar→m:barPr(pos: top/bot) +m:eMathML:
<mover>(top) or<munder>(bottom) with stretchy<mo>converters/bar.tswith proper Unicode (U+203E overline, U+0332 low line)MATH_OBJECT_REGISTRYomml-to-mathml.test.ts(overbar, underbar, default)fraction.ts)Fixes #2610.