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}`;
|
return `${baseFontName}-${fontStylesName}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// A map of spacing functions to their attributes, like size and corresponding
|
// A map of CSS-based spacing functions to their CSS class.
|
||||||
// CSS class
|
const cssSpace: {[string]: string} = {
|
||||||
const spacingFunctions: {[string]: {| size: string, className: string |}} = {
|
"\\nobreak": "nobreak",
|
||||||
"\\qquad": {
|
"\\allowbreak": "allowbreak",
|
||||||
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 lookup table to determine whether a spacing function/symbol should be
|
// A lookup table to determine whether a spacing function/symbol should be
|
||||||
@@ -802,6 +767,6 @@ export default {
|
|||||||
staticSvg,
|
staticSvg,
|
||||||
svgData,
|
svgData,
|
||||||
tryCombineChars,
|
tryCombineChars,
|
||||||
spacingFunctions,
|
cssSpace,
|
||||||
regularSpace,
|
regularSpace,
|
||||||
};
|
};
|
||||||
|
@@ -8,7 +8,7 @@ import type Options from "./Options";
|
|||||||
import type {ArgType, BreakToken, Mode} from "./types";
|
import type {ArgType, BreakToken, Mode} from "./types";
|
||||||
import type {HtmlDomNode} from "./domTree";
|
import type {HtmlDomNode} from "./domTree";
|
||||||
import type {Token} from "./Token";
|
import type {Token} from "./Token";
|
||||||
import type {MathNode, TextNode} from "./mathMLTree";
|
import type {MathNodeClass} from "./mathMLTree";
|
||||||
|
|
||||||
/** Context provided to function handlers for error messages. */
|
/** Context provided to function handlers for error messages. */
|
||||||
export type FunctionContext = {|
|
export type FunctionContext = {|
|
||||||
@@ -28,7 +28,7 @@ export type HtmlBuilder<NODETYPE> = (ParseNode<NODETYPE>, Options) => HtmlDomNod
|
|||||||
export type MathMLBuilder<NODETYPE> = (
|
export type MathMLBuilder<NODETYPE> = (
|
||||||
group: ParseNode<NODETYPE>,
|
group: ParseNode<NODETYPE>,
|
||||||
options: Options,
|
options: Options,
|
||||||
) => MathNode | TextNode | domTree.documentFragment;
|
) => MathNodeClass | domTree.documentFragment;
|
||||||
|
|
||||||
// More general version of `HtmlBuilder` for nodes (e.g. \sum, accent types)
|
// More general version of `HtmlBuilder` for nodes (e.g. \sum, accent types)
|
||||||
// whose presence impacts super/subscripting. In this case, ParseNode<"supsub">
|
// whose presence impacts super/subscripting. In this case, ParseNode<"supsub">
|
||||||
|
@@ -24,7 +24,6 @@ import "./functions/lap";
|
|||||||
import "./functions/math";
|
import "./functions/math";
|
||||||
import "./functions/mathchoice";
|
import "./functions/mathchoice";
|
||||||
import "./functions/mclass";
|
import "./functions/mclass";
|
||||||
import "./functions/mod";
|
|
||||||
import "./functions/op";
|
import "./functions/op";
|
||||||
import "./functions/operatorname";
|
import "./functions/operatorname";
|
||||||
import "./functions/ordgroup";
|
import "./functions/ordgroup";
|
||||||
|
@@ -48,11 +48,7 @@ defineFunction({
|
|||||||
return buildCommon.makeGlue(group.value.dimension, options);
|
return buildCommon.makeGlue(group.value.dimension, options);
|
||||||
},
|
},
|
||||||
mathmlBuilder: (group, options) => {
|
mathmlBuilder: (group, options) => {
|
||||||
const node = new mathMLTree.MathNode("mspace");
|
|
||||||
|
|
||||||
const dimension = calculateSize(group.value.dimension, options);
|
const dimension = calculateSize(group.value.dimension, options);
|
||||||
node.setAttribute("width", dimension + "em");
|
return new mathMLTree.SpaceNode(dimension);
|
||||||
|
|
||||||
return node;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@@ -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"));
|
group.value.value, options.withFont("mathrm"));
|
||||||
|
|
||||||
let word = temp.map(node => node.toText()).join("");
|
let word = temp.map(node => node.toText()).join("");
|
||||||
|
|
||||||
word = word.replace(/\u2212/g, "-");
|
word = word.replace(/\u2212/g, "-");
|
||||||
word = word.replace(/\u2217/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);
|
const identifier = new mathMLTree.MathNode("mi", output);
|
||||||
identifier.setAttribute("mathvariant", "normal");
|
identifier.setAttribute("mathvariant", "normal");
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
import {defineFunctionBuilders} from "../defineFunction";
|
import {defineFunctionBuilders} from "../defineFunction";
|
||||||
import buildCommon from "../buildCommon";
|
import buildCommon from "../buildCommon";
|
||||||
import mathMLTree from "../mathMLTree";
|
import mathMLTree from "../mathMLTree";
|
||||||
|
import ParseError from "../ParseError";
|
||||||
|
|
||||||
// ParseNode<"spacing"> created in Parser.js from the "spacing" symbol Groups in
|
// ParseNode<"spacing"> created in Parser.js from the "spacing" symbol Groups in
|
||||||
// src/symbols.js.
|
// src/symbols.js.
|
||||||
@@ -22,12 +23,13 @@ defineFunctionBuilders({
|
|||||||
[buildCommon.mathsym(group.value, group.mode, options)],
|
[buildCommon.mathsym(group.value, group.mode, options)],
|
||||||
options);
|
options);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (buildCommon.cssSpace.hasOwnProperty(group.value)) {
|
||||||
// Other kinds of spaces are of arbitrary width. We use CSS to
|
// Spaces based on just a CSS class.
|
||||||
// generate these.
|
|
||||||
return buildCommon.makeSpan(
|
return buildCommon.makeSpan(
|
||||||
["mspace", buildCommon.spacingFunctions[group.value].className],
|
["mspace", buildCommon.cssSpace[group.value]],
|
||||||
[], options);
|
[], options);
|
||||||
|
} else {
|
||||||
|
throw new ParseError(`Unknown type of space "${group.value}"`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mathmlBuilder(group, options) {
|
mathmlBuilder(group, options) {
|
||||||
@@ -36,11 +38,11 @@ defineFunctionBuilders({
|
|||||||
if (buildCommon.regularSpace.hasOwnProperty(group.value)) {
|
if (buildCommon.regularSpace.hasOwnProperty(group.value)) {
|
||||||
node = new mathMLTree.MathNode(
|
node = new mathMLTree.MathNode(
|
||||||
"mtext", [new mathMLTree.TextNode("\u00a0")]);
|
"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 {
|
} else {
|
||||||
node = new mathMLTree.MathNode("mspace");
|
throw new ParseError(`Unknown type of space "${group.value}"`);
|
||||||
|
|
||||||
node.setAttribute(
|
|
||||||
"width", buildCommon.spacingFunctions[group.value].size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
|
@@ -244,54 +244,6 @@
|
|||||||
|
|
||||||
.mspace {
|
.mspace {
|
||||||
display: inline-block;
|
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,
|
.llap,
|
||||||
|
@@ -292,8 +292,6 @@ const dotsByToken = {
|
|||||||
'\\bigoplus': '\\dotsb',
|
'\\bigoplus': '\\dotsb',
|
||||||
'\\bigodot': '\\dotsb',
|
'\\bigodot': '\\dotsb',
|
||||||
'\\bigsqcup': '\\dotsb',
|
'\\bigsqcup': '\\dotsb',
|
||||||
'\\implies': '\\dotsb',
|
|
||||||
'\\impliedby': '\\dotsb',
|
|
||||||
'\\And': '\\dotsb',
|
'\\And': '\\dotsb',
|
||||||
'\\longrightarrow': '\\dotsb',
|
'\\longrightarrow': '\\dotsb',
|
||||||
'\\Longrightarrow': '\\dotsb',
|
'\\Longrightarrow': '\\dotsb',
|
||||||
@@ -304,11 +302,9 @@ const dotsByToken = {
|
|||||||
'\\mapsto': '\\dotsb',
|
'\\mapsto': '\\dotsb',
|
||||||
'\\longmapsto': '\\dotsb',
|
'\\longmapsto': '\\dotsb',
|
||||||
'\\hookrightarrow': '\\dotsb',
|
'\\hookrightarrow': '\\dotsb',
|
||||||
'\\iff': '\\dotsb',
|
|
||||||
'\\doteq': '\\dotsb',
|
'\\doteq': '\\dotsb',
|
||||||
// Symbols whose definition starts with \mathbin:
|
// Symbols whose definition starts with \mathbin:
|
||||||
'\\mathbin': '\\dotsb',
|
'\\mathbin': '\\dotsb',
|
||||||
'\\bmod': '\\dotsb',
|
|
||||||
// Symbols whose definition starts with \mathrel:
|
// Symbols whose definition starts with \mathrel:
|
||||||
'\\mathrel': '\\dotsb',
|
'\\mathrel': '\\dotsb',
|
||||||
'\\relbar': '\\dotsb',
|
'\\relbar': '\\dotsb',
|
||||||
@@ -416,10 +412,44 @@ defineMacro("\\DOTSI", "\\relax");
|
|||||||
defineMacro("\\DOTSB", "\\relax");
|
defineMacro("\\DOTSB", "\\relax");
|
||||||
defineMacro("\\DOTSX", "\\relax");
|
defineMacro("\\DOTSX", "\\relax");
|
||||||
|
|
||||||
// http://texdoc.net/texmf-dist/doc/latex/amsmath/amsmath.pdf
|
// Spacing, based on amsmath.sty's override of LaTeX defaults
|
||||||
defineMacro("\\thinspace", "\\,"); // \let\thinspace\,
|
// \DeclareRobustCommand{\tmspace}[3]{%
|
||||||
defineMacro("\\medspace", "\\:"); // \let\medspace\:
|
// \ifmmode\mskip#1#2\else\kern#1#3\fi\relax}
|
||||||
defineMacro("\\thickspace", "\\;"); // \let\thickspace\;
|
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
|
// \tag@in@display form of \tag
|
||||||
defineMacro("\\tag", "\\@ifstar\\tag@literal\\tag@paren");
|
defineMacro("\\tag", "\\@ifstar\\tag@literal\\tag@paren");
|
||||||
@@ -431,6 +461,26 @@ defineMacro("\\tag@literal", (context) => {
|
|||||||
return "\\gdef\\df@tag{\\text{#1}}";
|
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
|
// LaTeX source2e
|
||||||
|
|
||||||
|
@@ -24,6 +24,8 @@ export type MathNodeType =
|
|||||||
"mrow" | "menclose" |
|
"mrow" | "menclose" |
|
||||||
"mstyle" | "mpadded" | "mphantom";
|
"mstyle" | "mpadded" | "mphantom";
|
||||||
|
|
||||||
|
export type MathNodeClass = MathNode | TextNode | SpaceNode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This node represents a general purpose MathML node of any type. The
|
* This node represents a general purpose MathML node of any type. The
|
||||||
* constructor requires the type of node to create (for example, `"mo"` or
|
* 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 {
|
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("");
|
return this.children.map(child => child.toText()).join("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,34 +109,114 @@ export class MathNode {
|
|||||||
*/
|
*/
|
||||||
export class TextNode {
|
export class TextNode {
|
||||||
text: string;
|
text: string;
|
||||||
|
needsEscape: boolean;
|
||||||
|
|
||||||
constructor(text: string) {
|
constructor(text: string, needsEscape: boolean = true) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
|
this.needsEscape = needsEscape;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the text node into a DOM text node.
|
* Converts the text node into a DOM text node.
|
||||||
*/
|
*/
|
||||||
toNode(): 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 {
|
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 {
|
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 {
|
export default {
|
||||||
MathNode,
|
MathNode,
|
||||||
TextNode,
|
TextNode,
|
||||||
|
SpaceNode,
|
||||||
};
|
};
|
||||||
|
@@ -571,27 +571,13 @@ defineSymbol(math, main, rel, "\u2192", "\\rightarrow", true);
|
|||||||
defineSymbol(math, main, rel, "\u2192", "\\to");
|
defineSymbol(math, main, rel, "\u2192", "\\to");
|
||||||
defineSymbol(math, ams, rel, "\u2271", "\\ngeq", true);
|
defineSymbol(math, ams, rel, "\u2271", "\\ngeq", true);
|
||||||
defineSymbol(math, ams, rel, "\u2270", "\\nleq", 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, "\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");
|
defineSymbol(math, main, spacing, "\u00a0", "\\space");
|
||||||
// Ref: LaTeX Source 2e: \DeclareRobustCommand{\nobreakspace}{%
|
// Ref: LaTeX Source 2e: \DeclareRobustCommand{\nobreakspace}{%
|
||||||
defineSymbol(math, main, spacing, "\u00a0", "\\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, "\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", "\\space");
|
||||||
defineSymbol(text, main, spacing, "\u00a0", "\\nobreakspace");
|
defineSymbol(text, main, spacing, "\u00a0", "\\nobreakspace");
|
||||||
defineSymbol(math, main, spacing, null, "\\nobreak");
|
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`] = `
|
exports[`A MathML builder should concatenate digits into single <mn> 1`] = `
|
||||||
|
|
||||||
<math>
|
<math>
|
||||||
@@ -125,8 +143,9 @@ exports[`A MathML builder should generate the right types of nodes 1`] = `
|
|||||||
<mn>
|
<mn>
|
||||||
1
|
1
|
||||||
</mn>
|
</mn>
|
||||||
<mspace width="0.277778em">
|
<mtext>
|
||||||
</mspace>
|
  
|
||||||
|
</mtext>
|
||||||
<mtext>
|
<mtext>
|
||||||
a
|
a
|
||||||
</mtext>
|
</mtext>
|
||||||
@@ -169,7 +188,7 @@ exports[`A MathML builder should output \\limsup_{x \\rightarrow \\infty} correc
|
|||||||
<msub>
|
<msub>
|
||||||
<mo>
|
<mo>
|
||||||
<mi mathvariant="normal">
|
<mi mathvariant="normal">
|
||||||
lim sup
|
lim sup
|
||||||
</mi>
|
</mi>
|
||||||
<mo>
|
<mo>
|
||||||
|
|
||||||
@@ -204,7 +223,7 @@ exports[`A MathML builder should output \\limsup_{x \\rightarrow \\infty} in dis
|
|||||||
<munder>
|
<munder>
|
||||||
<mo>
|
<mo>
|
||||||
<mi mathvariant="normal">
|
<mi mathvariant="normal">
|
||||||
lim sup
|
lim sup
|
||||||
</mi>
|
</mi>
|
||||||
<mo>
|
<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`] = `
|
exports[`A MathML builder tags use <mlabeledtr> 1`] = `
|
||||||
|
|
||||||
<math>
|
<math>
|
||||||
|
@@ -107,4 +107,15 @@ describe("A MathML builder", function() {
|
|||||||
expect(getMathML("\\tag{hi} x+y^2", {displayMode: true}))
|
expect(getMathML("\\tag{hi} x+y^2", {displayMode: true}))
|
||||||
.toMatchSnapshot();
|
.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 |