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
@@ -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,
|
||||
};
|
||||
|
@@ -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">
|
||||
|
@@ -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";
|
||||
|
@@ -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);
|
||||
},
|
||||
});
|
||||
|
@@ -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,
|
||||
});
|
@@ -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");
|
||||
|
@@ -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;
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
//    (\u2005\u200a) and all negative spaces.
|
||||
if (width >= 0.05555 && width <= 0.05556) {
|
||||
this.character = " "; // \u200a
|
||||
} else if (width >= 0.1666 && width <= 0.1667) {
|
||||
this.character = " "; // \u2009
|
||||
} else if (width >= 0.2222 && width <= 0.2223) {
|
||||
this.character = " "; // \u2005
|
||||
} else if (width >= 0.2777 && width <= 0.2778) {
|
||||
this.character = "  "; // \u2005\u200a
|
||||
} else if (width >= -0.05556 && width <= -0.05555) {
|
||||
this.character = "​";
|
||||
} else if (width >= -0.1667 && width <= -0.1666) {
|
||||
this.character = "​";
|
||||
} else if (width >= -0.2223 && width <= -0.2222) {
|
||||
this.character = "​";
|
||||
} else if (width >= -0.2778 && width <= -0.2777) {
|
||||
this.character = "​";
|
||||
} 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,
|
||||
};
|
||||
|
@@ -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");
|
||||
|
@@ -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>
|
||||
  
|
||||
</mtext>
|
||||
<mtext>
|
||||
a
|
||||
</mtext>
|
||||
@@ -169,7 +188,7 @@ exports[`A MathML builder should output \\limsup_{x \\rightarrow \\infty} correc
|
||||
<msub>
|
||||
<mo>
|
||||
<mi mathvariant="normal">
|
||||
lim sup
|
||||
lim sup
|
||||
</mi>
|
||||
<mo>
|
||||
|
||||
@@ -204,7 +223,7 @@ exports[`A MathML builder should output \\limsup_{x \\rightarrow \\infty} in dis
|
||||
<munder>
|
||||
<mo>
|
||||
<mi mathvariant="normal">
|
||||
lim sup
|
||||
lim 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>
|
||||
 
|
||||
</mtext>
|
||||
<mtext>
|
||||
 
|
||||
</mtext>
|
||||
<mtext>
|
||||
 
|
||||
</mtext>
|
||||
<mtext>
|
||||
 
|
||||
</mtext>
|
||||
<mtext>
|
||||
  
|
||||
</mtext>
|
||||
<mtext>
|
||||
  
|
||||
</mtext>
|
||||
<mtext>
|
||||
​
|
||||
</mtext>
|
||||
<mtext>
|
||||
​
|
||||
</mtext>
|
||||
<mtext>
|
||||
​
|
||||
</mtext>
|
||||
<mtext>
|
||||
​
|
||||
</mtext>
|
||||
<mtext>
|
||||
 
|
||||
</mtext>
|
||||
<mtext>
|
||||
 
|
||||
</mtext>
|
||||
<mtext>
|
||||
 
|
||||
</mtext>
|
||||
<mtext>
|
||||
  
|
||||
</mtext>
|
||||
<mtext>
|
||||
​
|
||||
</mtext>
|
||||
<mtext>
|
||||
​
|
||||
</mtext>
|
||||
<mtext>
|
||||
​
|
||||
</mtext>
|
||||
<mtext>
|
||||
​
|
||||
</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>
|
||||
|
@@ -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();
|
||||
});
|
||||
});
|
||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |