-
Notifications
You must be signed in to change notification settings - Fork 2
Fix MathML rendering #149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Fix MathML rendering #149
Changes from 11 commits
Commits
Show all changes
51 commits
Select commit
Hold shift + click to select a range
d6f622a
Use mathml-polyfills to resolve legacy mathml codes
akhuoa f14ef2d
Format MathML into table style
akhuoa bf8abcb
Fix mathml polyfills mismatch closing fence
akhuoa 9b5e20a
Fix math view overflow
akhuoa 9625f7a
Fix mathml-polyfills loading
akhuoa 4cd5714
Update math elements spacing
akhuoa b59bc14
Update MathML styles
akhuoa 055e701
Update maths response json transform
akhuoa 4590601
Add empty content fallback
akhuoa 0338017
Format
akhuoa 088ca5c
Format
akhuoa fbbff80
Use MathML namespace to create element
akhuoa c9ab80b
Update mathml-polyfills dynamic import
akhuoa a7825ff
Update comment
akhuoa ad01442
Fix mathml transform
akhuoa bba0e3e
Fix DOMParser performance
akhuoa a435c7f
Add unit test for mathmlTransform
akhuoa b19fcd2
Fix v-for key usage
akhuoa 2734528
Format
akhuoa 0639785
Fix format and lint errors
akhuoa b74be98
Merge branch 'main' into bugfix/mathml
akhuoa dbe3573
Merge branch 'main' into bugfix/mathml
akhuoa c1d41d4
Add mathml-polyfills in vendor folder
akhuoa 68a6489
Update mathml-polyfills from https to local bundle
akhuoa 5e9c43e
Add a readme file for mathml-polyfills vendor
akhuoa 1e0d9d1
Change mathml-polyfills dynamic import to static import to fix build …
akhuoa 14445bb
Fix unit test for mathml transform
akhuoa b0b27c5
Fix formatting and exclude vendor folder
akhuoa 5b4e27b
Fix linting
akhuoa 0aaa7b6
Format
akhuoa eec4ad8
Merge branch 'main' into bugfix/mathml
akhuoa e35d2eb
Remove unnecessary global resizeObserver mock used by vendor as vendo…
akhuoa 83e7f39
Merge branch 'bugfix/mathml' of github.com:akhuoa/pmrapp-frontend int…
akhuoa 7e84352
Merge branch 'Physiome:main' into bugfix/mathml
akhuoa b975a0a
Replace invisible times with visible dots
akhuoa afba3e4
Align the conditions in math equations
akhuoa 4a5ccca
Update math base font size
akhuoa c1d39a0
Make math font sizes in different levels bigger
akhuoa b3b1fa8
Add vertical spacing between the fraction bar and content
akhuoa 58bda98
Make all variables in mathml italic
akhuoa 3a125c2
Keep first-level fraction content at parent math font size
akhuoa 17d641a
Align left to math keywords
akhuoa 958d6f7
Convert underscore-delimited identifiers into subscripts
akhuoa 65b1ff6
Replace logical operator symbols with text labels
akhuoa 99511ad
Update font sizes for math sup and sub
akhuoa 1875327
Format math numbers
akhuoa 3517a60
Replace exponential function e
akhuoa 48fd97d
Convert greek letters in math
akhuoa 8b6f5ca
Convert scientific e-notation tokens in math
akhuoa 6c944aa
Rename variables and functions
akhuoa 1f8e494
Format
akhuoa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| const MATH_POLYFILLS_MODULE_URL = 'https://w3c.github.io/mathml-polyfills/all-polyfills.js' | ||
|
akhuoa marked this conversation as resolved.
Outdated
|
||
|
|
||
| type MathTransformsModule = { | ||
| _MathTransforms?: { | ||
| getCSSStyleSheet: () => HTMLStyleElement | ||
| transform: (container: HTMLElement) => void | ||
| } | ||
| } | ||
|
|
||
| let isMathPolyfillsInitialized = false | ||
| let isMathPolyfillsInitializing = false | ||
| let loadedMathTransforms: MathTransformsModule['_MathTransforms'] | null = null | ||
| const MATH_POLYFILLS_STYLE_ATTR = 'data-math-polyfills' | ||
| const OPEN_TO_CLOSE_FENCE: Record<string, string> = { | ||
| '(': ')', | ||
| '[': ']', | ||
| '{': '}', | ||
| '<': '>', | ||
| '⟨': '⟩', | ||
| '⌊': '⌋', | ||
| '⌈': '⌉', | ||
| } | ||
|
|
||
| const isFenceOperator = (element: Element): element is HTMLElement => | ||
| element.tagName.toLowerCase() === 'mo' && element.getAttribute('fence') === 'true' | ||
|
|
||
| const getFenceText = (element: Element): string => (element.textContent || '').trim() | ||
|
|
||
| const fixMismatchedFencePairs = (root: ParentNode) => { | ||
| const rows = root.querySelectorAll('mrow') | ||
|
|
||
| rows.forEach((row) => { | ||
| const children = Array.from(row.children) | ||
| if (children.length < 2) return | ||
|
|
||
| const firstFence = children.find(isFenceOperator) | ||
| const lastFence = [...children].reverse().find(isFenceOperator) | ||
|
|
||
| if (!firstFence || !lastFence || firstFence === lastFence) return | ||
|
|
||
| const openingFence = getFenceText(firstFence) | ||
| const closingFence = getFenceText(lastFence) | ||
| const expectedClosingFence = OPEN_TO_CLOSE_FENCE[openingFence] | ||
|
|
||
| if (expectedClosingFence && closingFence && closingFence !== expectedClosingFence) { | ||
| // Remove only the mismatched trailing fence injected by polyfills. | ||
| lastFence.remove() | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| const loadMathTransforms = async () => { | ||
| if (loadedMathTransforms) return loadedMathTransforms | ||
| if (typeof window === 'undefined') return null | ||
|
|
||
| try { | ||
| // Avoid static URL imports so Node-based test runners don't fail at module parse time. | ||
| const dynamicImport = new Function('path', 'return import(path)') as ( | ||
| path: string, | ||
| ) => Promise<MathTransformsModule> | ||
| const module = await dynamicImport(MATH_POLYFILLS_MODULE_URL) | ||
|
akhuoa marked this conversation as resolved.
Outdated
|
||
| loadedMathTransforms = module._MathTransforms || null | ||
| } catch (err) { | ||
| console.warn('Unable to load MathML polyfills module:', err) | ||
| loadedMathTransforms = null | ||
| } | ||
|
|
||
| return loadedMathTransforms | ||
| } | ||
|
|
||
| /** | ||
| * Injects the necessary CSS for polyfills into the document head. | ||
| */ | ||
| export async function initMathPolyfills() { | ||
| if ( | ||
| typeof document === 'undefined' || | ||
| isMathPolyfillsInitialized || | ||
| isMathPolyfillsInitializing | ||
| ) { | ||
| return | ||
| } | ||
|
|
||
| isMathPolyfillsInitializing = true | ||
|
|
||
| try { | ||
| const existingStyle = document.head.querySelector(`[${MATH_POLYFILLS_STYLE_ATTR}="true"]`) | ||
| if (existingStyle) { | ||
| isMathPolyfillsInitialized = true | ||
| return | ||
| } | ||
|
|
||
| const mathTransforms = await loadMathTransforms() | ||
| if (!mathTransforms) return | ||
|
|
||
| const style = mathTransforms.getCSSStyleSheet() | ||
| style.setAttribute(MATH_POLYFILLS_STYLE_ATTR, 'true') | ||
| document.head.appendChild(style) | ||
| isMathPolyfillsInitialized = true | ||
| } finally { | ||
| isMathPolyfillsInitializing = false | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Transforms a raw MathML string from an API into modern MathML Core. | ||
| * @param rawMathML The string containing legacy <math> tags. | ||
| */ | ||
| export function transformMathString(rawMathML: string): string { | ||
| const container = document.createElement('div') | ||
| container.innerHTML = rawMathML | ||
|
|
||
| loadedMathTransforms?.transform(container) | ||
|
|
||
| return container.innerHTML | ||
| } | ||
|
akhuoa marked this conversation as resolved.
|
||
|
|
||
| /** | ||
| * Formats a MathML string into a table structure for better rendering. | ||
| * @param rawMathML The string containing updated <math> tags. | ||
| * @returns The formatted MathML string. | ||
| */ | ||
| export const formatMathMLTable = (rawMathML: string): string => { | ||
| const parser = new DOMParser() | ||
| const doc = parser.parseFromString(rawMathML, 'text/html') | ||
| const mathBlocks = doc.querySelectorAll('math') | ||
|
akhuoa marked this conversation as resolved.
|
||
|
|
||
|
akhuoa marked this conversation as resolved.
|
||
| mathBlocks.forEach((math) => { | ||
| fixMismatchedFencePairs(math) | ||
|
|
||
| const rows = Array.from(math.children).filter((child) => child.tagName.toLowerCase() === 'mrow') | ||
|
|
||
| if (rows.length) { | ||
| const mtable = doc.createElement('mtable') | ||
| mtable.setAttribute('columnalign', 'right center left') | ||
| mtable.setAttribute('rowspacing', '0.75em') | ||
|
|
||
| rows.forEach((row) => { | ||
| const mtr = doc.createElement('mtr') | ||
|
|
||
| Array.from(row.childNodes).forEach((node) => { | ||
| const mtd = doc.createElement('mtd') | ||
|
|
||
|
akhuoa marked this conversation as resolved.
Outdated
|
||
| if ( | ||
| node.nodeType === Node.ELEMENT_NODE && | ||
| (node as Element).tagName.toLowerCase() === 'mo' && | ||
| ((node.textContent || '').trim() === '=' || | ||
| (node as Element).getAttribute('form') === 'infix') | ||
| ) { | ||
| mtd.setAttribute('data-math-operator', 'equals') | ||
| } | ||
|
|
||
| mtd.appendChild(node) | ||
| mtr.appendChild(mtd) | ||
| }) | ||
|
|
||
| mtable.appendChild(mtr) | ||
| row.remove() | ||
| }) | ||
|
|
||
| math.appendChild(mtable) | ||
| } | ||
| }) | ||
|
|
||
| return doc.body.innerHTML | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.