diff --git a/src/buildMathML.js b/src/buildMathML.js index c49aff2d40..376814e89d 100644 --- a/src/buildMathML.js +++ b/src/buildMathML.js @@ -128,6 +128,30 @@ export const getVariant = function( return null; }; +/** + * Check for . which is how a dot renders in MathML, + * or , + * which is how a braced comma {,} renders in MathML + */ +function isNumberPunctuation(group: ?MathNode): boolean { + if (!group) { + return false; + } + if (group.type === 'mi' && group.children.length === 1) { + const child = group.children[0]; + return child instanceof TextNode && child.text === '.'; + } else if (group.type === 'mo' && group.children.length === 1 && + group.getAttribute('separator') === 'true' && + group.getAttribute('lspace') === '0em' && + group.getAttribute('rspace') === '0em' + ) { + const child = group.children[0]; + return child instanceof TextNode && child.text === ','; + } else { + return false; + } +} + /** * Takes a list of nodes, builds them, and returns a list of the generated * MathML nodes. Also combine consecutive outputs into a single @@ -165,13 +189,25 @@ export const buildExpression = function( lastGroup.children.push(...group.children); continue; // Concatenate ... followed by . - } else if (group.type === 'mi' && group.children.length === 1 && - lastGroup.type === 'mn') { - const child = group.children[0]; - if (child instanceof TextNode && child.text === '.') { - lastGroup.children.push(...group.children); - continue; + } else if (isNumberPunctuation(group) && lastGroup.type === 'mn') { + lastGroup.children.push(...group.children); + continue; + // Concatenate . followed by ... + } else if (group.type === 'mn' && isNumberPunctuation(lastGroup)) { + group.children = [...lastGroup.children, ...group.children]; + groups.pop(); + // Put preceding ... or . inside base of + // ...base......exponent... (or ) + } else if ((group.type === 'msup' || group.type === 'msub') && + group.children.length >= 1 && + (lastGroup.type === 'mn' || isNumberPunctuation(lastGroup)) + ) { + const base = group.children[0]; + if (base instanceof MathNode && base.type === 'mn') { + base.children = [...lastGroup.children, ...base.children]; + groups.pop(); } + // \not } else if (lastGroup.type === 'mi' && lastGroup.children.length === 1) { const lastChild = lastGroup.children[0]; if (lastChild instanceof TextNode && lastChild.text === '\u0338' && diff --git a/src/mathMLTree.js b/src/mathMLTree.js index 9e0cc9f570..3b59183682 100644 --- a/src/mathMLTree.js +++ b/src/mathMLTree.js @@ -95,7 +95,18 @@ export class MathNode implements MathDomNode { } for (let i = 0; i < this.children.length; i++) { - node.appendChild(this.children[i].toNode()); + // Combine multiple TextNodes into one TextNode, to prevent + // screen readers from reading each as a separate word [#3995] + if (this.children[i] instanceof TextNode && + this.children[i + 1] instanceof TextNode) { + let text = this.children[i].toText() + this.children[++i].toText(); + while (this.children[i + 1] instanceof TextNode) { + text += this.children[++i].toText(); + } + node.appendChild(new TextNode(text).toNode()); + } else { + node.appendChild(this.children[i].toNode()); + } } return node; diff --git a/test/__snapshots__/mathml-spec.js.snap b/test/__snapshots__/mathml-spec.js.snap index 8505c2718c..2a784ff1a2 100644 --- a/test/__snapshots__/mathml-spec.js.snap +++ b/test/__snapshots__/mathml-spec.js.snap @@ -428,9 +428,35 @@ exports[`A MathML builder should concatenate digits into single 1`] = ` 0.34 + + = + + + + .34 + + + 1 + + + + + \\sin{\\alpha}=0.34=.34^1 + + + +`; + +exports[`A MathML builder should concatenate digits into single 2`] = ` + + + + + 1,000,000 + - \\sin{\\alpha}=0.34 + 1{,}000{,}000 diff --git a/test/mathml-spec.js b/test/mathml-spec.js index 8965c597da..94a4b42468 100644 --- a/test/mathml-spec.js +++ b/test/mathml-spec.js @@ -29,7 +29,8 @@ describe("A MathML builder", function() { }); it('should concatenate digits into single ', () => { - expect(getMathML("\\sin{\\alpha}=0.34")).toMatchSnapshot(); + expect(getMathML("\\sin{\\alpha}=0.34=.34^1")).toMatchSnapshot(); + expect(getMathML("1{,}000{,}000")).toMatchSnapshot(); }); it('should make prime operators into nodes', () => {