diff --git a/src/buildHTML.js b/src/buildHTML.js index a13b28bb9a..a106bb9d7d 100644 --- a/src/buildHTML.js +++ b/src/buildHTML.js @@ -9,7 +9,7 @@ import ParseError from "./ParseError"; import Style from "./Style"; import buildCommon from "./buildCommon"; -import {Span, Anchor} from "./domTree"; +import {Span, Anchor, SymbolNode} from "./domTree"; import utils from "./utils"; import {spacings, tightSpacings} from "./spacingData"; import {_htmlGroupBuilders as groupBuilders} from "./defineFunction"; @@ -310,7 +310,11 @@ function buildHTMLUnbreakable(children, options) { * Take an entire parse tree, and build it into an appropriate set of HTML * nodes. */ -export default function buildHTML(tree: AnyParseNode[], options: Options): DomSpan { +export default function buildHTML( + tree: AnyParseNode[], + options: Options, + isDisplayMode: boolean, +): DomSpan { // Strip off outer tag wrapper for processing below. let tag = null; if (tree.length === 1 && tree[0].type === "tag") { @@ -386,7 +390,24 @@ export default function buildHTML(tree: AnyParseNode[], options: Options): DomSp children.push(eqnNum); } - const htmlNode = makeSpan(["katex-html"], children); + // Add spacers _between_ unbreakable subtrees to allow for line breaks in + // inline documents, but not allowing a soft break before a punctuation + // in text _immediately_ following the inline math element. + // + // See https://github.com/KaTeX/KaTeX/issues/1233 for discussion. + const childrenWithSpacers = []; + for (let i = 0; i < children.length; i++) { + if (!isDisplayMode && i > 0) { + const spacer = new SymbolNode( + "\u200b", undefined, undefined, undefined, undefined, undefined, + ["spacer"], + ); + childrenWithSpacers.push(spacer); + } + childrenWithSpacers.push(children[i]); + } + + const htmlNode = makeSpan(["katex-html"], childrenWithSpacers); htmlNode.setAttribute("aria-hidden", "true"); // Adjust the strut of the tag to be the maximum height of all children diff --git a/src/buildTree.js b/src/buildTree.js index d7ad29c7e2..e4e94f3e45 100644 --- a/src/buildTree.js +++ b/src/buildTree.js @@ -41,12 +41,12 @@ export const buildTree = function( if (settings.output === "mathml") { return buildMathML(tree, expression, options, settings.displayMode, true); } else if (settings.output === "html") { - const htmlNode = buildHTML(tree, options); + const htmlNode = buildHTML(tree, options, settings.displayMode); katexNode = buildCommon.makeSpan(["katex"], [htmlNode]); } else { const mathMLNode = buildMathML(tree, expression, options, settings.displayMode, false); - const htmlNode = buildHTML(tree, options); + const htmlNode = buildHTML(tree, options, settings.displayMode); katexNode = buildCommon.makeSpan(["katex"], [mathMLNode, htmlNode]); } @@ -59,7 +59,7 @@ export const buildHTMLTree = function( settings: Settings, ): DomSpan { const options = optionsFromSettings(settings); - const htmlNode = buildHTML(tree, options); + const htmlNode = buildHTML(tree, options, settings.displayMode); const katexNode = buildCommon.makeSpan(["katex"], [htmlNode]); return displayWrap(katexNode, settings); }; diff --git a/src/katex.less b/src/katex.less index e2fe90f4fd..801f1d5f39 100644 --- a/src/katex.less +++ b/src/katex.less @@ -17,6 +17,9 @@ // Prevent a rendering bug that misplaces \vec in Chrome. text-rendering: auto; + /* Do not wrap math elements, unless in an inline spacer. */ + white-space: nowrap; + * { // Prevent background resetting on elements in Windows's high-contrast // mode, while still allowing background/foreground setting on root .katex @@ -47,6 +50,11 @@ > .newline { display: block; } + + /* Element containing ​ between .base elements */ + > .spacer { + white-space: normal; + } } .base { @@ -645,7 +653,6 @@ > .katex { display: block; text-align: center; - white-space: nowrap; > .katex-html { display: block; diff --git a/test/__snapshots__/katex-spec.js.snap b/test/__snapshots__/katex-spec.js.snap index 8cd15a12ec..6c2499903e 100755 --- a/test/__snapshots__/katex-spec.js.snap +++ b/test/__snapshots__/katex-spec.js.snap @@ -1186,8 +1186,14 @@ exports[`Newlines via \\\\ and \\newline \\\\ causes newline, even after mrel an = + + ​ + + + ​ + + + ​ + + + ​ + + + ​ + + + ​ + node.classes.indexOf("strut") < 0)); + const span = builtHTML.children[i]; + if (span.classes.indexOf("spacer") < 0) { + children.push(...span.children.filter( + (node) => node.classes.indexOf("strut") < 0)); + } } return children; } diff --git a/test/katex-spec.js b/test/katex-spec.js index c926229b24..26961c060f 100644 --- a/test/katex-spec.js +++ b/test/katex-spec.js @@ -3893,8 +3893,11 @@ describe("Newlines via \\\\ and \\newline", function() { const markup = katex.renderToString(r`M = \\ a + \\ b \\ c`); // Ensure newlines appear outside base spans (because, in this regexp, // base span occurs immediately after each newline span). - expect(markup).toMatch( - /(.*?<\/span><\/span>){3}/); + const base = `.*?<\\/span>`; + const newline = `<\\/span>`; + const spacer = `\u200b`; + const pattern = new RegExp(`(${base}${spacer}${newline}${spacer}){3}${base}`); + expect(markup).toMatch(pattern); expect(markup).toMatchSnapshot(); }); });