Rewrite spacing commands as macros (#1156)

* Rewrite spacing commands as macros

Fix #1130 by defining `\!`, `\negthinspace`, `\,`, `\thinspace`, `\:`,
`\medspace`, `\;`, `\thickspace`, `\negmedspace`, `\negthickspace`,
`\enspace`, `\enskip`, `\qquad`, `\quad` as macros.

Fix #1036 by defining a new `SpaceNode` in mathMLTree that recognizes
all special space amounts from
https://www.w3.org/TR/2000/WD-MathML2-20000328/chapter6.html

* Fix <mspace> rendering and add a test to catch it

* Update screenshots

* Wrap special space characters in <mtext>

* Update screenshots

* Fix MathML escaping behavior

* Fix flow typos

* Fix Unicode-chrome

* Reimplement mod operators in terms of macros

* Rerun ModScript

* Remove excess table entries (now already macros)

* Fix bmod
This commit is contained in:
Erik Demaine
2018-05-29 08:43:52 -04:00
committed by Kevin Barabash
parent 978148ec6e
commit 417383e002
21 changed files with 269 additions and 281 deletions

View File

@@ -649,45 +649,10 @@ const retrieveTextFontName = function(
return `${baseFontName}-${fontStylesName}`;
};
// A map of spacing functions to their attributes, like size and corresponding
// CSS class
const spacingFunctions: {[string]: {| size: string, className: string |}} = {
"\\qquad": {
size: "2em",
className: "qquad",
},
"\\quad": {
size: "1em",
className: "quad",
},
"\\enspace": {
size: "0.5em",
className: "enspace",
},
"\\;": {
size: "0.277778em",
className: "thickspace",
},
"\\:": {
size: "0.22222em",
className: "mediumspace",
},
"\\,": {
size: "0.16667em",
className: "thinspace",
},
"\\!": {
size: "-0.16667em",
className: "negativethinspace",
},
"\\nobreak": {
size: "0em",
className: "nobreak",
},
"\\allowbreak": {
size: "0em",
className: "allowbreak",
},
// A map of CSS-based spacing functions to their CSS class.
const cssSpace: {[string]: string} = {
"\\nobreak": "nobreak",
"\\allowbreak": "allowbreak",
};
// A lookup table to determine whether a spacing function/symbol should be
@@ -802,6 +767,6 @@ export default {
staticSvg,
svgData,
tryCombineChars,
spacingFunctions,
cssSpace,
regularSpace,
};

View File

@@ -8,7 +8,7 @@ import type Options from "./Options";
import type {ArgType, BreakToken, Mode} from "./types";
import type {HtmlDomNode} from "./domTree";
import type {Token} from "./Token";
import type {MathNode, TextNode} from "./mathMLTree";
import type {MathNodeClass} from "./mathMLTree";
/** Context provided to function handlers for error messages. */
export type FunctionContext = {|
@@ -28,7 +28,7 @@ export type HtmlBuilder<NODETYPE> = (ParseNode<NODETYPE>, Options) => HtmlDomNod
export type MathMLBuilder<NODETYPE> = (
group: ParseNode<NODETYPE>,
options: Options,
) => MathNode | TextNode | domTree.documentFragment;
) => MathNodeClass | domTree.documentFragment;
// More general version of `HtmlBuilder` for nodes (e.g. \sum, accent types)
// whose presence impacts super/subscripting. In this case, ParseNode<"supsub">

View File

@@ -24,7 +24,6 @@ import "./functions/lap";
import "./functions/math";
import "./functions/mathchoice";
import "./functions/mclass";
import "./functions/mod";
import "./functions/op";
import "./functions/operatorname";
import "./functions/ordgroup";

View File

@@ -48,11 +48,7 @@ defineFunction({
return buildCommon.makeGlue(group.value.dimension, options);
},
mathmlBuilder: (group, options) => {
const node = new mathMLTree.MathNode("mspace");
const dimension = calculateSize(group.value.dimension, options);
node.setAttribute("width", dimension + "em");
return node;
return new mathMLTree.SpaceNode(dimension);
},
});

View File

@@ -1,133 +0,0 @@
// @flow
// \mod-type functions
import defineFunction, {ordargument} from "../defineFunction";
import buildCommon from "../buildCommon";
import mathMLTree from "../mathMLTree";
import Style from "../Style";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
const htmlModBuilder = (group, options) => {
const inner = [];
if (group.value.modType === "bmod") {
// “\nonscript\mskip-\medmuskip\mkern5mu”, where \medmuskip is
// 4mu plus 2mu minus 1mu, translates to 1mu space in
// display/textstyle and 5mu space in script/scriptscriptstyle.
if (!options.style.isTight()) {
inner.push(buildCommon.makeSpan(
["mspace", "muspace"], [], options));
} else {
inner.push(buildCommon.makeSpan(
["mspace", "thickspace"], [], options));
}
} else if (options.style.size === Style.DISPLAY.size) {
inner.push(buildCommon.makeSpan(["mspace", "quad"], [], options));
} else if (group.value.modType === "mod") {
inner.push(
buildCommon.makeSpan(["mspace", "twelvemuspace"], [], options));
} else {
inner.push(
buildCommon.makeSpan(["mspace", "eightmuspace"], [], options));
}
if (group.value.modType === "pod" || group.value.modType === "pmod") {
inner.push(buildCommon.mathsym("(", group.mode));
}
if (group.value.modType !== "pod") {
const modInner = [
buildCommon.mathsym("m", group.mode),
buildCommon.mathsym("o", group.mode),
buildCommon.mathsym("d", group.mode)];
if (group.value.modType === "bmod") {
inner.push(buildCommon.makeSpan(["mbin"], modInner, options));
// “\mkern5mu\nonscript\mskip-\medmuskip” as above
if (!options.style.isTight()) {
inner.push(buildCommon.makeSpan(
["mspace", "muspace"], [], options));
} else {
inner.push(buildCommon.makeSpan(
["mspace", "thickspace"], [], options));
}
} else {
Array.prototype.push.apply(inner, modInner);
inner.push(
buildCommon.makeSpan(["mspace", "sixmuspace"], [], options));
}
}
if (group.value.value) {
Array.prototype.push.apply(inner,
html.buildExpression(group.value.value, options, false));
}
if (group.value.modType === "pod" || group.value.modType === "pmod") {
inner.push(buildCommon.mathsym(")", group.mode));
}
return buildCommon.makeFragment(inner);
};
const mmlModBuilder = (group, options) => {
let inner = [];
if (group.value.modType === "pod" || group.value.modType === "pmod") {
inner.push(new mathMLTree.MathNode(
"mo", [mml.makeText("(", group.mode)]));
}
if (group.value.modType !== "pod") {
inner.push(new mathMLTree.MathNode(
"mo", [mml.makeText("mod", group.mode)]));
}
if (group.value.value) {
const space = new mathMLTree.MathNode("mspace");
space.setAttribute("width", "0.333333em");
inner.push(space);
inner = inner.concat(mml.buildExpression(group.value.value, options));
}
if (group.value.modType === "pod" || group.value.modType === "pmod") {
inner.push(new mathMLTree.MathNode(
"mo", [mml.makeText(")", group.mode)]));
}
return new mathMLTree.MathNode("mo", inner);
};
defineFunction({
type: "mod",
names: ["\\bmod"],
props: {
numArgs: 0,
},
handler: (context, args) => {
return {
type: "mod",
modType: "bmod",
value: null,
};
},
htmlBuilder: htmlModBuilder,
mathmlBuilder: mmlModBuilder,
});
// Note: calling defineFunction with a type that's already been defined only
// works because the same htmlBuilder and mathmlBuilder are being used.
defineFunction({
type: "mod",
names: ["\\pod", "\\pmod", "\\mod"],
props: {
numArgs: 1,
},
handler: (context, args) => {
const body = args[0];
return {
type: "mod",
modType: context.funcName.substr(1),
value: ordargument(body),
};
},
htmlBuilder: htmlModBuilder,
mathmlBuilder: mmlModBuilder,
});

View File

@@ -77,10 +77,10 @@ defineFunction({
group.value.value, options.withFont("mathrm"));
let word = temp.map(node => node.toText()).join("");
word = word.replace(/\u2212/g, "-");
word = word.replace(/\u2217/g, "*");
output = [new mathMLTree.TextNode(word)];
// word has already been escaped by `node.toText()`
output = [new mathMLTree.TextNode(word, false)];
}
const identifier = new mathMLTree.MathNode("mi", output);
identifier.setAttribute("mathvariant", "normal");

View File

@@ -2,6 +2,7 @@
import {defineFunctionBuilders} from "../defineFunction";
import buildCommon from "../buildCommon";
import mathMLTree from "../mathMLTree";
import ParseError from "../ParseError";
// ParseNode<"spacing"> created in Parser.js from the "spacing" symbol Groups in
// src/symbols.js.
@@ -22,12 +23,13 @@ defineFunctionBuilders({
[buildCommon.mathsym(group.value, group.mode, options)],
options);
}
} else {
// Other kinds of spaces are of arbitrary width. We use CSS to
// generate these.
} else if (buildCommon.cssSpace.hasOwnProperty(group.value)) {
// Spaces based on just a CSS class.
return buildCommon.makeSpan(
["mspace", buildCommon.spacingFunctions[group.value].className],
["mspace", buildCommon.cssSpace[group.value]],
[], options);
} else {
throw new ParseError(`Unknown type of space "${group.value}"`);
}
},
mathmlBuilder(group, options) {
@@ -36,11 +38,11 @@ defineFunctionBuilders({
if (buildCommon.regularSpace.hasOwnProperty(group.value)) {
node = new mathMLTree.MathNode(
"mtext", [new mathMLTree.TextNode("\u00a0")]);
} else if (buildCommon.cssSpace.hasOwnProperty(group.value)) {
// CSS-based MathML spaces (\nobreak, \allowbreak) are ignored
return new mathMLTree.MathNode("mspace");
} else {
node = new mathMLTree.MathNode("mspace");
node.setAttribute(
"width", buildCommon.spacingFunctions[group.value].size);
throw new ParseError(`Unknown type of space "${group.value}"`);
}
return node;

View File

@@ -244,54 +244,6 @@
.mspace {
display: inline-block;
&.negativethinspace {
margin-left: -@thinspace;
}
&.muspace {
width: @muspace;
}
&.thinspace {
width: @thinspace;
}
&.negativemediumspace {
margin-left: -@mediumspace;
}
&.mediumspace {
width: @mediumspace;
}
&.thickspace {
width: @thickspace;
}
&.sixmuspace {
width: 0.333333em;
}
&.eightmuspace {
width: 0.444444em;
}
&.enspace {
width: 0.5em;
}
&.twelvemuspace {
width: 0.666667em;
}
&.quad {
width: 1em;
}
&.qquad {
width: 2em;
}
}
.llap,

View File

@@ -292,8 +292,6 @@ const dotsByToken = {
'\\bigoplus': '\\dotsb',
'\\bigodot': '\\dotsb',
'\\bigsqcup': '\\dotsb',
'\\implies': '\\dotsb',
'\\impliedby': '\\dotsb',
'\\And': '\\dotsb',
'\\longrightarrow': '\\dotsb',
'\\Longrightarrow': '\\dotsb',
@@ -304,11 +302,9 @@ const dotsByToken = {
'\\mapsto': '\\dotsb',
'\\longmapsto': '\\dotsb',
'\\hookrightarrow': '\\dotsb',
'\\iff': '\\dotsb',
'\\doteq': '\\dotsb',
// Symbols whose definition starts with \mathbin:
'\\mathbin': '\\dotsb',
'\\bmod': '\\dotsb',
// Symbols whose definition starts with \mathrel:
'\\mathrel': '\\dotsb',
'\\relbar': '\\dotsb',
@@ -416,10 +412,44 @@ defineMacro("\\DOTSI", "\\relax");
defineMacro("\\DOTSB", "\\relax");
defineMacro("\\DOTSX", "\\relax");
// http://texdoc.net/texmf-dist/doc/latex/amsmath/amsmath.pdf
defineMacro("\\thinspace", "\\,"); // \let\thinspace\,
defineMacro("\\medspace", "\\:"); // \let\medspace\:
defineMacro("\\thickspace", "\\;"); // \let\thickspace\;
// Spacing, based on amsmath.sty's override of LaTeX defaults
// \DeclareRobustCommand{\tmspace}[3]{%
// \ifmmode\mskip#1#2\else\kern#1#3\fi\relax}
defineMacro("\\tmspace", "\\TextOrMath{\\kern#1#3}{\\mskip#1#2}\\relax");
// \renewcommand{\,}{\tmspace+\thinmuskip{.1667em}}
// TODO: math mode should use \thinmuskip
defineMacro("\\,", "\\tmspace+{3mu}{.1667em}");
// \let\thinspace\,
defineMacro("\\thinspace", "\\,");
// \renewcommand{\:}{\tmspace+\medmuskip{.2222em}}
// TODO: math mode should use \medmuskip = 4mu plus 2mu minus 4mu
defineMacro("\\:", "\\tmspace+{4mu}{.2222em}");
// \let\medspace\:
defineMacro("\\medspace", "\\:");
// \renewcommand{\;}{\tmspace+\thickmuskip{.2777em}}
// TODO: math mode should use \thickmuskip = 5mu plus 5mu
defineMacro("\\;", "\\tmspace+{5mu}{.2777em}");
// \let\thickspace\;
defineMacro("\\thickspace", "\\;");
// \renewcommand{\!}{\tmspace-\thinmuskip{.1667em}}
// TODO: math mode should use \thinmuskip
defineMacro("\\!", "\\tmspace-{3mu}{.1667em}");
// \let\negthinspace\!
defineMacro("\\negthinspace", "\\!");
// \newcommand{\negmedspace}{\tmspace-\medmuskip{.2222em}}
// TODO: math mode should use \medmuskip
defineMacro("\\negmedspace", "\\tmspace-{4mu}{.2222em}");
// \newcommand{\negthickspace}{\tmspace-\thickmuskip{.2777em}}
// TODO: math mode should use \thickmuskip
defineMacro("\\negthickspace", "\\tmspace-{5mu}{.277em}");
// \def\enspace{\kern.5em }
defineMacro("\\enspace", "\\kern.5em ");
// \def\enskip{\hskip.5em\relax}
defineMacro("\\enskip", "\\hskip.5em\\relax");
// \def\quad{\hskip1em\relax}
defineMacro("\\quad", "\\hskip1em\\relax");
// \def\qquad{\hskip2em\relax}
defineMacro("\\qquad", "\\hskip2em\\relax");
// \tag@in@display form of \tag
defineMacro("\\tag", "\\@ifstar\\tag@literal\\tag@paren");
@@ -431,6 +461,26 @@ defineMacro("\\tag@literal", (context) => {
return "\\gdef\\df@tag{\\text{#1}}";
});
// \renewcommand{\bmod}{\nonscript\mskip-\medmuskip\mkern5mu\mathbin
// {\operator@font mod}\penalty900
// \mkern5mu\nonscript\mskip-\medmuskip}
// \newcommand{\pod}[1]{\allowbreak
// \if@display\mkern18mu\else\mkern8mu\fi(#1)}
// \renewcommand{\pmod}[1]{\pod{{\operator@font mod}\mkern6mu#1}}
// \newcommand{\mod}[1]{\allowbreak\if@display\mkern18mu
// \else\mkern12mu\fi{\operator@font mod}\,\,#1}
// TODO: math mode should use \medmuskip = 4mu plus 2mu minus 4mu
defineMacro("\\bmod",
"\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}" +
"\\mathbin{\\rm mod}" +
"\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}");
defineMacro("\\pod", "\\allowbreak" +
"\\mathchoice{\\mkern18mu}{\\mkern8mu}{\\mkern8mu}{\\mkern8mu}(#1)");
defineMacro("\\pmod", "\\pod{{\\rm mod}\\mkern6mu#1}");
defineMacro("\\mod", "\\allowbreak" +
"\\mathchoice{\\mkern18mu}{\\mkern12mu}{\\mkern12mu}{\\mkern12mu}" +
"{\\rm mod}\\,\\,#1");
//////////////////////////////////////////////////////////////////////
// LaTeX source2e

View File

@@ -24,6 +24,8 @@ export type MathNodeType =
"mrow" | "menclose" |
"mstyle" | "mpadded" | "mphantom";
export type MathNodeClass = MathNode | TextNode | SpaceNode;
/**
* This node represents a general purpose MathML node of any type. The
* constructor requires the type of node to create (for example, `"mo"` or
@@ -95,18 +97,9 @@ export class MathNode {
}
/**
* Converts the math node into a string, similar to innerText.
* Converts the math node into a string, similar to innerText, but escaped.
*/
toText(): string {
if (this.type === "mspace") {
if (this.attributes.width === "0.16667em") {
return "\u2006";
} else {
// TODO: Use other space characters for different widths.
// https://github.com/Khan/KaTeX/issues/1036
return " ";
}
}
return this.children.map(child => child.toText()).join("");
}
}
@@ -116,34 +109,114 @@ export class MathNode {
*/
export class TextNode {
text: string;
needsEscape: boolean;
constructor(text: string) {
constructor(text: string, needsEscape: boolean = true) {
this.text = text;
this.needsEscape = needsEscape;
}
/**
* Converts the text node into a DOM text node.
*/
toNode(): Node {
return document.createTextNode(this.text);
return document.createTextNode(this.toText());
}
/**
* Converts the text node into HTML markup (which is just the text itself).
* Converts the text node into escaped HTML markup
* (representing the text itself).
*/
toMarkup(): string {
return utils.escape(this.text);
return this.toText();
}
/**
* Converts the text node into a string (which is just the text iteself).
* Converts the text node into an escaped string
* (representing the text iteself).
*/
toText(): string {
return this.text;
return this.needsEscape ? utils.escape(this.text) : this.text;
}
}
/**
* This node represents a space, but may render as <mspace.../> or as text,
* depending on the width.
*/
class SpaceNode {
width: number;
character: ?string;
/**
* Create a Space node with width given in CSS ems.
*/
constructor(width: number) {
this.width = width;
// See https://www.w3.org/TR/2000/WD-MathML2-20000328/chapter6.html
// for a table of space-like characters. We consistently use the
// &LongNames; because Unicode does not have single characters for
// &ThickSpace; (\u2005\u200a) and all negative spaces.
if (width >= 0.05555 && width <= 0.05556) {
this.character = "&VeryThinSpace;"; // \u200a
} else if (width >= 0.1666 && width <= 0.1667) {
this.character = "&ThinSpace;"; // \u2009
} else if (width >= 0.2222 && width <= 0.2223) {
this.character = "&MediumSpace;"; // \u2005
} else if (width >= 0.2777 && width <= 0.2778) {
this.character = "&ThickSpace;"; // \u2005\u200a
} else if (width >= -0.05556 && width <= -0.05555) {
this.character = "&NegativeVeryThinSpace;";
} else if (width >= -0.1667 && width <= -0.1666) {
this.character = "&NegativeThinSpace;";
} else if (width >= -0.2223 && width <= -0.2222) {
this.character = "&NegativeMediumSpace;";
} else if (width >= -0.2778 && width <= -0.2777) {
this.character = "&NegativeThickSpace;";
} else {
this.character = null;
}
}
/**
* Converts the math node into a MathML-namespaced DOM element.
*/
toNode(): Node {
if (this.character) {
return document.createTextNode(this.character);
} else {
const node = document.createElementNS(
"http://www.w3.org/1998/Math/MathML", "mspace");
node.setAttribute("width", this.width + "em");
return node;
}
}
/**
* Converts the math node into an HTML markup string.
*/
toMarkup(): string {
if (this.character) {
return `<mtext>${this.character}</mtext>`;
} else {
return `<mspace width="${this.width}em"/>`;
}
}
/**
* Converts the math node into a string, similar to innerText.
*/
toText(): string {
if (this.character) {
return this.character;
} else {
return " ";
}
}
}
export default {
MathNode,
TextNode,
SpaceNode,
};

View File

@@ -571,27 +571,13 @@ defineSymbol(math, main, rel, "\u2192", "\\rightarrow", true);
defineSymbol(math, main, rel, "\u2192", "\\to");
defineSymbol(math, ams, rel, "\u2271", "\\ngeq", true);
defineSymbol(math, ams, rel, "\u2270", "\\nleq", true);
defineSymbol(math, main, spacing, null, "\\!");
defineSymbol(math, main, spacing, "\u00a0", "\\ ");
defineSymbol(math, main, spacing, "\u00a0", "~");
defineSymbol(math, main, spacing, null, "\\,");
defineSymbol(math, main, spacing, null, "\\:");
defineSymbol(math, main, spacing, null, "\\;");
defineSymbol(math, main, spacing, null, "\\enspace");
defineSymbol(math, main, spacing, null, "\\qquad");
defineSymbol(math, main, spacing, null, "\\quad");
defineSymbol(math, main, spacing, "\u00a0", "\\space");
// Ref: LaTeX Source 2e: \DeclareRobustCommand{\nobreakspace}{%
defineSymbol(math, main, spacing, "\u00a0", "\\nobreakspace");
defineSymbol(text, main, spacing, null, "\\!");
defineSymbol(text, main, spacing, "\u00a0", "\\ ");
defineSymbol(text, main, spacing, "\u00a0", "~");
defineSymbol(text, main, spacing, null, "\\,");
defineSymbol(text, main, spacing, null, "\\:");
defineSymbol(text, main, spacing, null, "\\;");
defineSymbol(text, main, spacing, null, "\\enspace");
defineSymbol(text, main, spacing, null, "\\qquad");
defineSymbol(text, main, spacing, null, "\\quad");
defineSymbol(text, main, spacing, "\u00a0", "\\space");
defineSymbol(text, main, spacing, "\u00a0", "\\nobreakspace");
defineSymbol(math, main, spacing, null, "\\nobreak");

View File

@@ -57,6 +57,24 @@ exports[`A MathML builder accents turn into <mover accent="true"> in MathML 1`]
`;
exports[`A MathML builder normal spaces render normally 1`] = `
<math>
<semantics>
<mrow>
<mspace width="1em">
</mspace>
<mspace width="0.431em">
</mspace>
</mrow>
<annotation encoding="application/x-tex">
\\kern1em\\kern1ex
</annotation>
</semantics>
</math>
`;
exports[`A MathML builder should concatenate digits into single <mn> 1`] = `
<math>
@@ -125,8 +143,9 @@ exports[`A MathML builder should generate the right types of nodes 1`] = `
<mn>
1
</mn>
<mspace width="0.277778em">
</mspace>
<mtext>
&ThickSpace;
</mtext>
<mtext>
a
</mtext>
@@ -169,7 +188,7 @@ exports[`A MathML builder should output \\limsup_{x \\rightarrow \\infty} correc
<msub>
<mo>
<mi mathvariant="normal">
limsup
lim&ThinSpace;sup
</mi>
<mo>
@@ -204,7 +223,7 @@ exports[`A MathML builder should output \\limsup_{x \\rightarrow \\infty} in dis
<munder>
<mo>
<mi mathvariant="normal">
limsup
lim&ThinSpace;sup
</mi>
<mo>
@@ -519,6 +538,74 @@ exports[`A MathML builder should use <munderover> for large operators 1`] = `
`;
exports[`A MathML builder special spaces render specially 1`] = `
<math>
<semantics>
<mrow>
<mtext>
&ThinSpace;
</mtext>
<mtext>
&ThinSpace;
</mtext>
<mtext>
&MediumSpace;
</mtext>
<mtext>
&MediumSpace;
</mtext>
<mtext>
&ThickSpace;
</mtext>
<mtext>
&ThickSpace;
</mtext>
<mtext>
&NegativeThinSpace;
</mtext>
<mtext>
&NegativeThinSpace;
</mtext>
<mtext>
&NegativeMediumSpace;
</mtext>
<mtext>
&NegativeThickSpace;
</mtext>
<mtext>
&VeryThinSpace;
</mtext>
<mtext>
&ThinSpace;
</mtext>
<mtext>
&MediumSpace;
</mtext>
<mtext>
&ThickSpace;
</mtext>
<mtext>
&NegativeVeryThinSpace;
</mtext>
<mtext>
&NegativeThinSpace;
</mtext>
<mtext>
&NegativeMediumSpace;
</mtext>
<mtext>
&NegativeThickSpace;
</mtext>
</mrow>
<annotation encoding="application/x-tex">
\\,\\thinspace\\:\\medspace\\;\\thickspace\\!\\negthinspace\\negmedspace\\negthickspace\\mkern1mu\\mkern3mu\\mkern4mu\\mkern5mu\\mkern-1mu\\mkern-3mu\\mkern-4mu\\mkern-5mu
</annotation>
</semantics>
</math>
`;
exports[`A MathML builder tags use <mlabeledtr> 1`] = `
<math>

View File

@@ -107,4 +107,15 @@ describe("A MathML builder", function() {
expect(getMathML("\\tag{hi} x+y^2", {displayMode: true}))
.toMatchSnapshot();
});
it('normal spaces render normally', function() {
expect(getMathML("\\kern1em\\kern1ex")).toMatchSnapshot();
});
it('special spaces render specially', function() {
expect(getMathML(
"\\,\\thinspace\\:\\medspace\\;\\thickspace" +
"\\!\\negthinspace\\negmedspace\\negthickspace" +
"\\mkern1mu\\mkern3mu\\mkern4mu\\mkern5mu" +
"\\mkern-1mu\\mkern-3mu\\mkern-4mu\\mkern-5mu")).toMatchSnapshot();
});
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB