SuperDoc converts Office Math (OMML) from Word documents into browser-native MathML. No KaTeX. No MathJax. No external dependencies. The browser renders the math directly.
How it works
.docx → OMML XML → ProseMirror → Layout Engine → DomPainter → <math> DOM
The importer extracts m:oMath elements and stores the full OMML tree as a JSON attribute on atomic ProseMirror nodes. At render time, the DomPainter walks the tree and converts each OMML element to its MathML equivalent via a converter registry. The browser does the rest.
Export round-trips cleanly — the original XML is preserved via carbonCopy.
What's built
- Import pipeline —
m:oMath (inline) and m:oMathPara (display) both import as proper PM nodes
- ProseMirror schema —
mathInline and mathBlock atomic nodes
- Layout engine —
MathRun type, measured as atomic units like images
- DomPainter rendering — converter registry,
renderMathRun(), cursor positioning
- Leaf nodes —
m:r / m:t text classification (<mi>, <mo>, <mn>)
- Fraction —
m:f → <mfrac>, working proof of concept
- 47 unit tests across the pipeline
What's left
Each converter is one function. ~20 lines of TypeScript. One file, two one-line registrations, a test. No schema changes. No adapter changes. No exporter changes.
Contribution guide: CONTRIBUTING.md
Done
Easy
Medium
Hard
How to pick one up
- Grab an issue from the list above
- Read the contribution guide
- Follow the pattern in
converters/fraction.ts
- Each issue has a
.docx test file attached — upload it to the dev app to verify your converter works
- Open a PR
Related
SuperDoc converts Office Math (OMML) from Word documents into browser-native MathML. No KaTeX. No MathJax. No external dependencies. The browser renders the math directly.
How it works
The importer extracts
m:oMathelements and stores the full OMML tree as a JSON attribute on atomic ProseMirror nodes. At render time, the DomPainter walks the tree and converts each OMML element to its MathML equivalent via a converter registry. The browser does the rest.Export round-trips cleanly — the original XML is preserved via carbonCopy.
What's built
m:oMath(inline) andm:oMathPara(display) both import as proper PM nodesmathInlineandmathBlockatomic nodesMathRuntype, measured as atomic units like imagesrenderMathRun(), cursor positioningm:r/m:ttext classification (<mi>,<mo>,<mn>)m:f→<mfrac>, working proof of conceptWhat's left
Each converter is one function. ~20 lines of TypeScript. One file, two one-line registrations, a test. No schema changes. No adapter changes. No exporter changes.
Contribution guide:
CONTRIBUTING.mdDone
m:sSupsuperscript →<msup>m:sSubsubscript →<msub>m:baroverbar/underbar →<mover>/<munder>Easy
m:sSubSupsub+superscript →<msubsup>m:radradical/sqrt →<msqrt>/<mroot>m:limLowlower limit →<munder>m:limUppupper limit →<mover>m:box+m:borderBox→<mrow>/<menclose>m:phantphantom →<mphantom>Medium
m:ddelimiter →<mrow>+<mo>m:naryn-ary (∫, ∑, ∏) →<munderover>m:accaccent →<mover accent="true">m:groupChrgroup character →<munder>/<mover>m:funcfunction apply →<mrow>m:sPrepre-sub-superscript →<mmultiscripts>Hard
m:mmatrix →<mtable>m:eqArrequation array →<mtable>How to pick one up
converters/fraction.ts.docxtest file attached — upload it to the dev app to verify your converter worksRelated