temml 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/contrib/auto-render/README.md +89 -0
- package/contrib/auto-render/auto-render.js +128 -0
- package/contrib/auto-render/dist/auto-render.js +217 -0
- package/contrib/auto-render/dist/auto-render.min.js +1 -0
- package/contrib/auto-render/splitAtDelimiters.js +84 -0
- package/contrib/auto-render/test/auto-render-spec.js +234 -0
- package/contrib/auto-render/test/auto-render.js +217 -0
- package/contrib/auto-render/test/test_page.html +59 -0
- package/contrib/mhchem/README.md +26 -0
- package/contrib/mhchem/mhchem.js +1705 -0
- package/contrib/mhchem/mhchem.min.js +1 -0
- package/contrib/physics/README.md +20 -0
- package/contrib/physics/physics.js +131 -0
- package/contrib/texvc/README.md +23 -0
- package/contrib/texvc/texvc.js +61 -0
- package/dist/Temml-Asana.css +201 -0
- package/dist/Temml-Latin-Modern.css +216 -0
- package/dist/Temml-Libertinus.css +214 -0
- package/dist/Temml-Local.css +194 -0
- package/dist/Temml-STIX2.css +203 -0
- package/dist/Temml.woff2 +0 -0
- package/dist/temml.cjs +13122 -0
- package/dist/temml.js +11225 -0
- package/dist/temml.min.js +1 -0
- package/dist/temml.mjs +13120 -0
- package/dist/temmlPostProcess.js +70 -0
- package/package.json +34 -0
- package/src/Lexer.js +121 -0
- package/src/MacroExpander.js +437 -0
- package/src/Namespace.js +107 -0
- package/src/ParseError.js +64 -0
- package/src/Parser.js +977 -0
- package/src/Settings.js +49 -0
- package/src/SourceLocation.js +29 -0
- package/src/Style.js +144 -0
- package/src/Token.js +40 -0
- package/src/buildMathML.js +235 -0
- package/src/constants.js +25 -0
- package/src/defineEnvironment.js +25 -0
- package/src/defineFunction.js +69 -0
- package/src/defineMacro.js +11 -0
- package/src/domTree.js +185 -0
- package/src/environments/array.js +791 -0
- package/src/environments/cd.js +252 -0
- package/src/environments.js +8 -0
- package/src/functions/accent.js +127 -0
- package/src/functions/accentunder.js +38 -0
- package/src/functions/arrow.js +204 -0
- package/src/functions/cancelto.js +36 -0
- package/src/functions/char.js +33 -0
- package/src/functions/color.js +253 -0
- package/src/functions/cr.js +46 -0
- package/src/functions/def.js +259 -0
- package/src/functions/delimsizing.js +304 -0
- package/src/functions/enclose.js +193 -0
- package/src/functions/envTag.js +38 -0
- package/src/functions/environment.js +59 -0
- package/src/functions/font.js +123 -0
- package/src/functions/genfrac.js +333 -0
- package/src/functions/hbox.js +29 -0
- package/src/functions/horizBrace.js +32 -0
- package/src/functions/href.js +90 -0
- package/src/functions/html.js +95 -0
- package/src/functions/includegraphics.js +131 -0
- package/src/functions/kern.js +75 -0
- package/src/functions/label.js +29 -0
- package/src/functions/lap.js +75 -0
- package/src/functions/math.js +40 -0
- package/src/functions/mathchoice.js +41 -0
- package/src/functions/mclass.js +201 -0
- package/src/functions/multiscript.js +91 -0
- package/src/functions/not.js +46 -0
- package/src/functions/op.js +338 -0
- package/src/functions/operatorname.js +139 -0
- package/src/functions/ordgroup.js +9 -0
- package/src/functions/phantom.js +73 -0
- package/src/functions/pmb.js +31 -0
- package/src/functions/raise.js +68 -0
- package/src/functions/ref.js +28 -0
- package/src/functions/relax.js +16 -0
- package/src/functions/rule.js +52 -0
- package/src/functions/sizing.js +64 -0
- package/src/functions/smash.js +66 -0
- package/src/functions/sqrt.js +31 -0
- package/src/functions/styling.js +58 -0
- package/src/functions/supsub.js +135 -0
- package/src/functions/symbolsOp.js +53 -0
- package/src/functions/symbolsOrd.js +102 -0
- package/src/functions/symbolsSpacing.js +53 -0
- package/src/functions/tag.js +8 -0
- package/src/functions/text.js +75 -0
- package/src/functions/tip.js +63 -0
- package/src/functions/toggle.js +13 -0
- package/src/functions/verb.js +33 -0
- package/src/functions.js +57 -0
- package/src/linebreaking.js +159 -0
- package/src/macros.js +708 -0
- package/src/mathMLTree.js +175 -0
- package/src/parseNode.js +42 -0
- package/src/parseTree.js +40 -0
- package/src/postProcess.js +57 -0
- package/src/replace.js +225 -0
- package/src/stretchy.js +66 -0
- package/src/svg.js +110 -0
- package/src/symbols.js +972 -0
- package/src/tree.js +50 -0
- package/src/unicodeAccents.js +16 -0
- package/src/unicodeScripts.js +119 -0
- package/src/unicodeSupOrSub.js +108 -0
- package/src/unicodeSymbolBuilder.js +31 -0
- package/src/unicodeSymbols.js +320 -0
- package/src/units.js +109 -0
- package/src/utils.js +109 -0
- package/src/variant.js +103 -0
- package/temml.js +181 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import defineFunction from "../defineFunction"
|
|
2
|
+
import { calculateSize, validUnit } from "../units"
|
|
3
|
+
import ParseError from "../ParseError"
|
|
4
|
+
import { Img } from "../domTree"
|
|
5
|
+
import mathMLTree from "../mathMLTree"
|
|
6
|
+
import { assertNodeType } from "../parseNode"
|
|
7
|
+
|
|
8
|
+
const sizeData = function(str) {
|
|
9
|
+
if (/^[-+]? *(\d+(\.\d*)?|\.\d+)$/.test(str)) {
|
|
10
|
+
// str is a number with no unit specified.
|
|
11
|
+
// default unit is bp, per graphix package.
|
|
12
|
+
return { number: +str, unit: "bp" }
|
|
13
|
+
} else {
|
|
14
|
+
const match = /([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/.exec(str);
|
|
15
|
+
if (!match) {
|
|
16
|
+
throw new ParseError("Invalid size: '" + str + "' in \\includegraphics");
|
|
17
|
+
}
|
|
18
|
+
const data = {
|
|
19
|
+
number: +(match[1] + match[2]), // sign + magnitude, cast to number
|
|
20
|
+
unit: match[3]
|
|
21
|
+
}
|
|
22
|
+
if (!validUnit(data)) {
|
|
23
|
+
throw new ParseError("Invalid unit: '" + data.unit + "' in \\includegraphics.");
|
|
24
|
+
}
|
|
25
|
+
return data
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
defineFunction({
|
|
30
|
+
type: "includegraphics",
|
|
31
|
+
names: ["\\includegraphics"],
|
|
32
|
+
props: {
|
|
33
|
+
numArgs: 1,
|
|
34
|
+
numOptionalArgs: 1,
|
|
35
|
+
argTypes: ["raw", "url"],
|
|
36
|
+
allowedInText: false
|
|
37
|
+
},
|
|
38
|
+
handler: ({ parser, token }, args, optArgs) => {
|
|
39
|
+
let width = { number: 0, unit: "em" }
|
|
40
|
+
let height = { number: 0.9, unit: "em" } // sorta character sized.
|
|
41
|
+
let totalheight = { number: 0, unit: "em" }
|
|
42
|
+
let alt = ""
|
|
43
|
+
|
|
44
|
+
if (optArgs[0]) {
|
|
45
|
+
const attributeStr = assertNodeType(optArgs[0], "raw").string
|
|
46
|
+
|
|
47
|
+
// Parser.js does not parse key/value pairs. We get a string.
|
|
48
|
+
const attributes = attributeStr.split(",")
|
|
49
|
+
for (let i = 0; i < attributes.length; i++) {
|
|
50
|
+
const keyVal = attributes[i].split("=")
|
|
51
|
+
if (keyVal.length === 2) {
|
|
52
|
+
const str = keyVal[1].trim()
|
|
53
|
+
switch (keyVal[0].trim()) {
|
|
54
|
+
case "alt":
|
|
55
|
+
alt = str
|
|
56
|
+
break
|
|
57
|
+
case "width":
|
|
58
|
+
width = sizeData(str)
|
|
59
|
+
break
|
|
60
|
+
case "height":
|
|
61
|
+
height = sizeData(str)
|
|
62
|
+
break
|
|
63
|
+
case "totalheight":
|
|
64
|
+
totalheight = sizeData(str)
|
|
65
|
+
break
|
|
66
|
+
default:
|
|
67
|
+
throw new ParseError("Invalid key: '" + keyVal[0] + "' in \\includegraphics.")
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const src = assertNodeType(args[0], "url").url
|
|
74
|
+
|
|
75
|
+
if (alt === "") {
|
|
76
|
+
// No alt given. Use the file name. Strip away the path.
|
|
77
|
+
alt = src
|
|
78
|
+
alt = alt.replace(/^.*[\\/]/, "")
|
|
79
|
+
alt = alt.substring(0, alt.lastIndexOf("."))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (
|
|
83
|
+
!parser.settings.isTrusted({
|
|
84
|
+
command: "\\includegraphics",
|
|
85
|
+
url: src
|
|
86
|
+
})
|
|
87
|
+
) {
|
|
88
|
+
throw new ParseError(`Function "\\includegraphics" is not trusted`, token)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
type: "includegraphics",
|
|
93
|
+
mode: parser.mode,
|
|
94
|
+
alt: alt,
|
|
95
|
+
width: width,
|
|
96
|
+
height: height,
|
|
97
|
+
totalheight: totalheight,
|
|
98
|
+
src: src
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
mathmlBuilder: (group, style) => {
|
|
102
|
+
const height = calculateSize(group.height, style)
|
|
103
|
+
const depth = { number: 0, unit: "em" }
|
|
104
|
+
|
|
105
|
+
if (group.totalheight.number > 0) {
|
|
106
|
+
if (group.totalheight.unit === height.unit &&
|
|
107
|
+
group.totalheight.number > height.number) {
|
|
108
|
+
depth.number = group.totalheight.number - height.number
|
|
109
|
+
depth.unit = height.unit
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let width = 0
|
|
114
|
+
if (group.width.number > 0) {
|
|
115
|
+
width = calculateSize(group.width, style)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const graphicStyle = { height: height.number + depth.number + "em" }
|
|
119
|
+
if (width.number > 0) {
|
|
120
|
+
graphicStyle.width = width.number + width.unit
|
|
121
|
+
}
|
|
122
|
+
if (depth.number > 0) {
|
|
123
|
+
graphicStyle.verticalAlign = -depth.number + depth.unit
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const node = new Img(group.src, group.alt, graphicStyle)
|
|
127
|
+
node.height = height
|
|
128
|
+
node.depth = depth
|
|
129
|
+
return new mathMLTree.MathNode("mtext", [node])
|
|
130
|
+
}
|
|
131
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Horizontal spacing commands
|
|
2
|
+
|
|
3
|
+
import defineFunction from "../defineFunction";
|
|
4
|
+
import mathMLTree from "../mathMLTree";
|
|
5
|
+
import { calculateSize } from "../units";
|
|
6
|
+
import { assertNodeType } from "../parseNode";
|
|
7
|
+
import ParseError from "../ParseError"
|
|
8
|
+
|
|
9
|
+
// TODO: \hskip and \mskip should support plus and minus in lengths
|
|
10
|
+
|
|
11
|
+
defineFunction({
|
|
12
|
+
type: "kern",
|
|
13
|
+
names: ["\\kern", "\\mkern", "\\hskip", "\\mskip"],
|
|
14
|
+
props: {
|
|
15
|
+
numArgs: 1,
|
|
16
|
+
argTypes: ["size"],
|
|
17
|
+
primitive: true,
|
|
18
|
+
allowedInText: true
|
|
19
|
+
},
|
|
20
|
+
handler({ parser, funcName, token }, args) {
|
|
21
|
+
const size = assertNodeType(args[0], "size");
|
|
22
|
+
if (parser.settings.strict) {
|
|
23
|
+
const mathFunction = funcName[1] === "m"; // \mkern, \mskip
|
|
24
|
+
const muUnit = size.value.unit === "mu";
|
|
25
|
+
if (mathFunction) {
|
|
26
|
+
if (!muUnit) {
|
|
27
|
+
throw new ParseError(`LaTeX's ${funcName} supports only mu units, ` +
|
|
28
|
+
`not ${size.value.unit} units`, token)
|
|
29
|
+
}
|
|
30
|
+
if (parser.mode !== "math") {
|
|
31
|
+
throw new ParseError(`LaTeX's ${funcName} works only in math mode`, token)
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
// !mathFunction
|
|
35
|
+
if (muUnit) {
|
|
36
|
+
throw new ParseError(`LaTeX's ${funcName} doesn't support mu units`, token)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
type: "kern",
|
|
42
|
+
mode: parser.mode,
|
|
43
|
+
dimension: size.value
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
mathmlBuilder(group, style) {
|
|
47
|
+
const dimension = calculateSize(group.dimension, style);
|
|
48
|
+
const ch = dimension.unit === "em" ? spaceCharacter(dimension.number) : "";
|
|
49
|
+
if (group.mode === "text" && ch.length > 0) {
|
|
50
|
+
const character = new mathMLTree.TextNode(ch);
|
|
51
|
+
return new mathMLTree.MathNode("mtext", [character]);
|
|
52
|
+
} else {
|
|
53
|
+
const node = new mathMLTree.MathNode("mspace");
|
|
54
|
+
node.setAttribute("width", dimension.number + dimension.unit);
|
|
55
|
+
if (dimension.number < 0) {
|
|
56
|
+
node.style.marginLeft = dimension.number + dimension.unit
|
|
57
|
+
}
|
|
58
|
+
return node;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export const spaceCharacter = function(width) {
|
|
64
|
+
if (width >= 0.05555 && width <= 0.05556) {
|
|
65
|
+
return "\u200a"; //  
|
|
66
|
+
} else if (width >= 0.1666 && width <= 0.1667) {
|
|
67
|
+
return "\u2009"; //  
|
|
68
|
+
} else if (width >= 0.2222 && width <= 0.2223) {
|
|
69
|
+
return "\u2005"; //  
|
|
70
|
+
} else if (width >= 0.2777 && width <= 0.2778) {
|
|
71
|
+
return "\u2005\u200a"; //   
|
|
72
|
+
} else {
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import defineFunction from "../defineFunction";
|
|
2
|
+
import mathMLTree from "../mathMLTree";
|
|
3
|
+
|
|
4
|
+
// Limit valid characters to a small set, for safety.
|
|
5
|
+
export const invalidIdRegEx = /[^A-Za-z_0-9-]/g
|
|
6
|
+
|
|
7
|
+
defineFunction({
|
|
8
|
+
type: "label",
|
|
9
|
+
names: ["\\label"],
|
|
10
|
+
props: {
|
|
11
|
+
numArgs: 1,
|
|
12
|
+
argTypes: ["raw"]
|
|
13
|
+
},
|
|
14
|
+
handler({ parser }, args) {
|
|
15
|
+
return {
|
|
16
|
+
type: "label",
|
|
17
|
+
mode: parser.mode,
|
|
18
|
+
string: args[0].string.replace(invalidIdRegEx, "")
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
mathmlBuilder(group, style) {
|
|
22
|
+
// Return a no-width, no-ink element with an HTML id.
|
|
23
|
+
const node = new mathMLTree.MathNode("mrow", [], ["tml-label"])
|
|
24
|
+
if (group.string.length > 0) {
|
|
25
|
+
node.setAttribute("id", group.string)
|
|
26
|
+
}
|
|
27
|
+
return node
|
|
28
|
+
}
|
|
29
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Horizontal overlap functions
|
|
2
|
+
import defineFunction, { ordargument } from "../defineFunction";
|
|
3
|
+
import mathMLTree from "../mathMLTree"
|
|
4
|
+
import * as mml from "../buildMathML"
|
|
5
|
+
import ParseError from "../ParseError";
|
|
6
|
+
|
|
7
|
+
const textModeLap = ["\\clap", "\\llap", "\\rlap"]
|
|
8
|
+
|
|
9
|
+
defineFunction({
|
|
10
|
+
type: "lap",
|
|
11
|
+
names: ["\\mathllap", "\\mathrlap", "\\mathclap", "\\clap", "\\llap", "\\rlap"],
|
|
12
|
+
props: {
|
|
13
|
+
numArgs: 1,
|
|
14
|
+
allowedInText: true
|
|
15
|
+
},
|
|
16
|
+
handler: ({ parser, funcName, token }, args) => {
|
|
17
|
+
if (textModeLap.includes(funcName)) {
|
|
18
|
+
if (parser.settings.strict && parser.mode !== "text") {
|
|
19
|
+
throw new ParseError(`{${funcName}} can be used only in text mode.
|
|
20
|
+
Try \\math${funcName.slice(1)}`, token)
|
|
21
|
+
}
|
|
22
|
+
funcName = funcName.slice(1)
|
|
23
|
+
} else {
|
|
24
|
+
funcName = funcName.slice(5)
|
|
25
|
+
}
|
|
26
|
+
const body = args[0]
|
|
27
|
+
return {
|
|
28
|
+
type: "lap",
|
|
29
|
+
mode: parser.mode,
|
|
30
|
+
alignment: funcName,
|
|
31
|
+
body
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
mathmlBuilder: (group, style) => {
|
|
35
|
+
// mathllap, mathrlap, mathclap
|
|
36
|
+
let strut
|
|
37
|
+
if (group.alignment === "llap") {
|
|
38
|
+
// We need an invisible strut with the same depth as the group.
|
|
39
|
+
// We can't just read the depth, so we use \vphantom methods.
|
|
40
|
+
const phantomInner = mml.buildExpression(ordargument(group.body), style);
|
|
41
|
+
const phantom = new mathMLTree.MathNode("mphantom", phantomInner);
|
|
42
|
+
strut = new mathMLTree.MathNode("mpadded", [phantom]);
|
|
43
|
+
strut.setAttribute("width", "0px");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const inner = mml.buildGroup(group.body, style)
|
|
47
|
+
let node
|
|
48
|
+
if (group.alignment === "llap") {
|
|
49
|
+
inner.style.position = "absolute"
|
|
50
|
+
inner.style.right = "0"
|
|
51
|
+
inner.style.bottom = `0` // If we could have read the ink depth, it would go here.
|
|
52
|
+
node = new mathMLTree.MathNode("mpadded", [strut, inner])
|
|
53
|
+
} else {
|
|
54
|
+
node = new mathMLTree.MathNode("mpadded", [inner])
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (group.alignment === "rlap") {
|
|
58
|
+
if (group.body.body.length > 0 && group.body.body[0].type === "genfrac") {
|
|
59
|
+
// In Firefox, a <mpadded> squashes the 3/18em padding of a child \frac. Put it back.
|
|
60
|
+
node.setAttribute("lspace", "0.16667em")
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
const offset = group.alignment === "llap" ? "-1" : "-0.5"
|
|
64
|
+
node.setAttribute("lspace", offset + "width")
|
|
65
|
+
if (group.alignment === "llap") {
|
|
66
|
+
node.style.position = "relative"
|
|
67
|
+
} else {
|
|
68
|
+
node.style.display = "flex"
|
|
69
|
+
node.style.justifyContent = "center"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
node.setAttribute("width", "0px")
|
|
73
|
+
return node
|
|
74
|
+
}
|
|
75
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import defineFunction from "../defineFunction";
|
|
2
|
+
import ParseError from "../ParseError";
|
|
3
|
+
|
|
4
|
+
// Switching from text mode back to math mode
|
|
5
|
+
defineFunction({
|
|
6
|
+
type: "ordgroup",
|
|
7
|
+
names: ["\\(", "$"],
|
|
8
|
+
props: {
|
|
9
|
+
numArgs: 0,
|
|
10
|
+
allowedInText: true,
|
|
11
|
+
allowedInMath: false
|
|
12
|
+
},
|
|
13
|
+
handler({ funcName, parser }, args) {
|
|
14
|
+
const outerMode = parser.mode;
|
|
15
|
+
parser.switchMode("math");
|
|
16
|
+
const close = funcName === "\\(" ? "\\)" : "$";
|
|
17
|
+
const body = parser.parseExpression(false, close);
|
|
18
|
+
parser.expect(close);
|
|
19
|
+
parser.switchMode(outerMode);
|
|
20
|
+
return {
|
|
21
|
+
type: "ordgroup",
|
|
22
|
+
mode: parser.mode,
|
|
23
|
+
body
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Check for extra closing math delimiters
|
|
29
|
+
defineFunction({
|
|
30
|
+
type: "text", // Doesn't matter what this is.
|
|
31
|
+
names: ["\\)", "\\]"],
|
|
32
|
+
props: {
|
|
33
|
+
numArgs: 0,
|
|
34
|
+
allowedInText: true,
|
|
35
|
+
allowedInMath: false
|
|
36
|
+
},
|
|
37
|
+
handler(context, token) {
|
|
38
|
+
throw new ParseError(`Mismatched ${context.funcName}`, token);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import defineFunction, { ordargument } from "../defineFunction";
|
|
2
|
+
import { StyleLevel } from "../constants";
|
|
3
|
+
import * as mml from "../buildMathML";
|
|
4
|
+
|
|
5
|
+
const chooseStyle = (group, style) => {
|
|
6
|
+
switch (style.level) {
|
|
7
|
+
case StyleLevel.DISPLAY: // 0
|
|
8
|
+
return group.display;
|
|
9
|
+
case StyleLevel.TEXT: // 1
|
|
10
|
+
return group.text;
|
|
11
|
+
case StyleLevel.SCRIPT: // 2
|
|
12
|
+
return group.script;
|
|
13
|
+
case StyleLevel.SCRIPTSCRIPT: // 3
|
|
14
|
+
return group.scriptscript;
|
|
15
|
+
default:
|
|
16
|
+
return group.text;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
defineFunction({
|
|
21
|
+
type: "mathchoice",
|
|
22
|
+
names: ["\\mathchoice"],
|
|
23
|
+
props: {
|
|
24
|
+
numArgs: 4,
|
|
25
|
+
primitive: true
|
|
26
|
+
},
|
|
27
|
+
handler: ({ parser }, args) => {
|
|
28
|
+
return {
|
|
29
|
+
type: "mathchoice",
|
|
30
|
+
mode: parser.mode,
|
|
31
|
+
display: ordargument(args[0]),
|
|
32
|
+
text: ordargument(args[1]),
|
|
33
|
+
script: ordargument(args[2]),
|
|
34
|
+
scriptscript: ordargument(args[3])
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
mathmlBuilder: (group, style) => {
|
|
38
|
+
const body = chooseStyle(group, style);
|
|
39
|
+
return mml.buildExpressionRow(body, style);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import defineFunction, { ordargument } from "../defineFunction";
|
|
2
|
+
import mathMLTree from "../mathMLTree";
|
|
3
|
+
import utils from "../utils";
|
|
4
|
+
|
|
5
|
+
import * as mml from "../buildMathML";
|
|
6
|
+
|
|
7
|
+
const textAtomTypes = ["text", "textord", "mathord", "atom"]
|
|
8
|
+
|
|
9
|
+
const padding = width => {
|
|
10
|
+
const node = new mathMLTree.MathNode("mspace")
|
|
11
|
+
node.setAttribute("width", width + "em")
|
|
12
|
+
return node
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function mathmlBuilder(group, style) {
|
|
16
|
+
let node;
|
|
17
|
+
const inner = mml.buildExpression(group.body, style);
|
|
18
|
+
|
|
19
|
+
if (group.mclass === "minner") {
|
|
20
|
+
node = new mathMLTree.MathNode("mpadded", inner)
|
|
21
|
+
} else if (group.mclass === "mord") {
|
|
22
|
+
if (group.isCharacterBox || inner[0].type === "mathord") {
|
|
23
|
+
node = inner[0];
|
|
24
|
+
node.type = "mi";
|
|
25
|
+
} else {
|
|
26
|
+
node = new mathMLTree.MathNode("mi", inner);
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
node = new mathMLTree.MathNode("mrow", inner)
|
|
30
|
+
if (group.mustPromote) {
|
|
31
|
+
node = inner[0];
|
|
32
|
+
node.type = "mo";
|
|
33
|
+
if (group.isCharacterBox && group.body[0].text && /[A-Za-z]/.test(group.body[0].text)) {
|
|
34
|
+
node.setAttribute("mathvariant", "italic")
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
node = new mathMLTree.MathNode("mrow", inner);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Set spacing based on what is the most likely adjacent atom type.
|
|
41
|
+
// See TeXbook p170.
|
|
42
|
+
const doSpacing = style.level < 2 // Operator spacing is zero inside a (sub|super)script.
|
|
43
|
+
if (node.type === "mrow") {
|
|
44
|
+
if (doSpacing ) {
|
|
45
|
+
if (group.mclass === "mbin") {
|
|
46
|
+
// medium space
|
|
47
|
+
node.children.unshift(padding(0.2222))
|
|
48
|
+
node.children.push(padding(0.2222))
|
|
49
|
+
} else if (group.mclass === "mrel") {
|
|
50
|
+
// thickspace
|
|
51
|
+
node.children.unshift(padding(0.2778))
|
|
52
|
+
node.children.push(padding(0.2778))
|
|
53
|
+
} else if (group.mclass === "mpunct") {
|
|
54
|
+
node.children.push(padding(0.1667))
|
|
55
|
+
} else if (group.mclass === "minner") {
|
|
56
|
+
node.children.unshift(padding(0.0556)) // 1 mu is the most likely option
|
|
57
|
+
node.children.push(padding(0.0556))
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
if (group.mclass === "mbin") {
|
|
62
|
+
// medium space
|
|
63
|
+
node.attributes.lspace = (doSpacing ? "0.2222em" : "0")
|
|
64
|
+
node.attributes.rspace = (doSpacing ? "0.2222em" : "0")
|
|
65
|
+
} else if (group.mclass === "mrel") {
|
|
66
|
+
// thickspace
|
|
67
|
+
node.attributes.lspace = (doSpacing ? "0.2778em" : "0")
|
|
68
|
+
node.attributes.rspace = (doSpacing ? "0.2778em" : "0")
|
|
69
|
+
} else if (group.mclass === "mpunct") {
|
|
70
|
+
node.attributes.lspace = "0em";
|
|
71
|
+
node.attributes.rspace = (doSpacing ? "0.1667em" : "0")
|
|
72
|
+
} else if (group.mclass === "mopen" || group.mclass === "mclose") {
|
|
73
|
+
node.attributes.lspace = "0em"
|
|
74
|
+
node.attributes.rspace = "0em"
|
|
75
|
+
} else if (group.mclass === "minner" && doSpacing) {
|
|
76
|
+
node.attributes.lspace = "0.0556em" // 1 mu is the most likely option
|
|
77
|
+
node.attributes.width = "+0.1111em"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!(group.mclass === "mopen" || group.mclass === "mclose")) {
|
|
82
|
+
delete node.attributes.stretchy
|
|
83
|
+
delete node.attributes.form
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return node;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Math class commands except \mathop
|
|
90
|
+
defineFunction({
|
|
91
|
+
type: "mclass",
|
|
92
|
+
names: [
|
|
93
|
+
"\\mathord",
|
|
94
|
+
"\\mathbin",
|
|
95
|
+
"\\mathrel",
|
|
96
|
+
"\\mathopen",
|
|
97
|
+
"\\mathclose",
|
|
98
|
+
"\\mathpunct",
|
|
99
|
+
"\\mathinner"
|
|
100
|
+
],
|
|
101
|
+
props: {
|
|
102
|
+
numArgs: 1,
|
|
103
|
+
primitive: true
|
|
104
|
+
},
|
|
105
|
+
handler({ parser, funcName }, args) {
|
|
106
|
+
const body = args[0]
|
|
107
|
+
const isCharacterBox = utils.isCharacterBox(body)
|
|
108
|
+
// We should not wrap a <mo> around a <mi> or <mord>. That would be invalid MathML.
|
|
109
|
+
// In that case, we instead promote the text contents of the body to the parent.
|
|
110
|
+
let mustPromote = true
|
|
111
|
+
const mord = { type: "mathord", text: "", mode: parser.mode }
|
|
112
|
+
const arr = (body.body) ? body.body : [body];
|
|
113
|
+
for (const arg of arr) {
|
|
114
|
+
if (textAtomTypes.includes(arg.type)) {
|
|
115
|
+
if (arg.text) {
|
|
116
|
+
mord.text += arg.text
|
|
117
|
+
} else if (arg.body) {
|
|
118
|
+
arg.body.map(e => { mord.text += e.text })
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
mustPromote = false
|
|
122
|
+
break
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
type: "mclass",
|
|
127
|
+
mode: parser.mode,
|
|
128
|
+
mclass: "m" + funcName.slice(5),
|
|
129
|
+
body: ordargument(mustPromote ? mord : body),
|
|
130
|
+
isCharacterBox,
|
|
131
|
+
mustPromote
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
mathmlBuilder
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
export const binrelClass = (arg) => {
|
|
138
|
+
// \binrel@ spacing varies with (bin|rel|ord) of the atom in the argument.
|
|
139
|
+
// (by rendering separately and with {}s before and after, and measuring
|
|
140
|
+
// the change in spacing). We'll do roughly the same by detecting the
|
|
141
|
+
// atom type directly.
|
|
142
|
+
const atom = arg.type === "ordgroup" && arg.body.length ? arg.body[0] : arg;
|
|
143
|
+
if (atom.type === "atom" && (atom.family === "bin" || atom.family === "rel")) {
|
|
144
|
+
return "m" + atom.family;
|
|
145
|
+
} else {
|
|
146
|
+
return "mord";
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// \@binrel{x}{y} renders like y but as mbin/mrel/mord if x is mbin/mrel/mord.
|
|
151
|
+
// This is equivalent to \binrel@{x}\binrel@@{y} in AMSTeX.
|
|
152
|
+
defineFunction({
|
|
153
|
+
type: "mclass",
|
|
154
|
+
names: ["\\@binrel"],
|
|
155
|
+
props: {
|
|
156
|
+
numArgs: 2
|
|
157
|
+
},
|
|
158
|
+
handler({ parser }, args) {
|
|
159
|
+
return {
|
|
160
|
+
type: "mclass",
|
|
161
|
+
mode: parser.mode,
|
|
162
|
+
mclass: binrelClass(args[0]),
|
|
163
|
+
body: ordargument(args[1]),
|
|
164
|
+
isCharacterBox: utils.isCharacterBox(args[1])
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Build a relation or stacked op by placing one symbol on top of another
|
|
170
|
+
defineFunction({
|
|
171
|
+
type: "mclass",
|
|
172
|
+
names: ["\\stackrel", "\\overset", "\\underset"],
|
|
173
|
+
props: {
|
|
174
|
+
numArgs: 2
|
|
175
|
+
},
|
|
176
|
+
handler({ parser, funcName }, args) {
|
|
177
|
+
const baseArg = args[1];
|
|
178
|
+
const shiftedArg = args[0];
|
|
179
|
+
|
|
180
|
+
const baseOp = {
|
|
181
|
+
type: "op",
|
|
182
|
+
mode: baseArg.mode,
|
|
183
|
+
limits: true,
|
|
184
|
+
alwaysHandleSupSub: true,
|
|
185
|
+
parentIsSupSub: false,
|
|
186
|
+
symbol: false,
|
|
187
|
+
stack: true,
|
|
188
|
+
suppressBaseShift: funcName !== "\\stackrel",
|
|
189
|
+
body: ordargument(baseArg)
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
type: "supsub",
|
|
194
|
+
mode: shiftedArg.mode,
|
|
195
|
+
base: baseOp,
|
|
196
|
+
sup: funcName === "\\underset" ? null : shiftedArg,
|
|
197
|
+
sub: funcName === "\\underset" ? shiftedArg : null
|
|
198
|
+
};
|
|
199
|
+
},
|
|
200
|
+
mathmlBuilder
|
|
201
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import defineFunction from "../defineFunction";
|
|
2
|
+
import mathMLTree from "../mathMLTree";
|
|
3
|
+
import * as mml from "../buildMathML"
|
|
4
|
+
import ParseError from "../ParseError";
|
|
5
|
+
|
|
6
|
+
// Helper function
|
|
7
|
+
export const buildGroup = (el, style, noneNode) => {
|
|
8
|
+
if (!el) { return noneNode }
|
|
9
|
+
const node = mml.buildGroup(el, style)
|
|
10
|
+
if (node.type === "mrow" && node.children.length === 0) { return noneNode }
|
|
11
|
+
return node
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
defineFunction({
|
|
15
|
+
type: "multiscript",
|
|
16
|
+
names: ["\\sideset", "\\pres@cript"], // See macros.js for \prescript
|
|
17
|
+
props: {
|
|
18
|
+
numArgs: 3
|
|
19
|
+
},
|
|
20
|
+
handler({ parser, funcName, token }, args) {
|
|
21
|
+
if (args[2].body.length === 0) {
|
|
22
|
+
throw new ParseError(funcName + `cannot parse an empty base.`)
|
|
23
|
+
}
|
|
24
|
+
const base = args[2].body[0]
|
|
25
|
+
if (parser.settings.strict && funcName === "\\sideset" && !base.symbol) {
|
|
26
|
+
throw new ParseError(`The base of \\sideset must be a big operator. Try \\prescript.`)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if ((args[0].body.length > 0 && args[0].body[0].type !== "supsub") ||
|
|
30
|
+
(args[1].body.length > 0 && args[1].body[0].type !== "supsub")) {
|
|
31
|
+
throw new ParseError("\\sideset can parse only subscripts and " +
|
|
32
|
+
"superscripts in its first two arguments", token)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// The prescripts and postscripts come wrapped in a supsub.
|
|
36
|
+
const prescripts = args[0].body.length > 0 ? args[0].body[0] : null
|
|
37
|
+
const postscripts = args[1].body.length > 0 ? args[1].body[0] : null
|
|
38
|
+
|
|
39
|
+
if (!prescripts && !postscripts) {
|
|
40
|
+
return base
|
|
41
|
+
} else if (!prescripts) {
|
|
42
|
+
// It's not a multi-script. Get a \textstyle supsub.
|
|
43
|
+
return {
|
|
44
|
+
type: "styling",
|
|
45
|
+
mode: parser.mode,
|
|
46
|
+
scriptLevel: "text",
|
|
47
|
+
body: [{
|
|
48
|
+
type: "supsub",
|
|
49
|
+
mode: parser.mode,
|
|
50
|
+
base,
|
|
51
|
+
sup: postscripts.sup,
|
|
52
|
+
sub: postscripts.sub
|
|
53
|
+
}]
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
return {
|
|
57
|
+
type: "multiscript",
|
|
58
|
+
mode: parser.mode,
|
|
59
|
+
isSideset: funcName === "\\sideset",
|
|
60
|
+
prescripts,
|
|
61
|
+
postscripts,
|
|
62
|
+
base
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
mathmlBuilder(group, style) {
|
|
67
|
+
const base = mml.buildGroup(group.base, style)
|
|
68
|
+
|
|
69
|
+
const prescriptsNode = new mathMLTree.MathNode("mprescripts")
|
|
70
|
+
const noneNode = new mathMLTree.MathNode("none")
|
|
71
|
+
let children = []
|
|
72
|
+
|
|
73
|
+
const preSub = buildGroup(group.prescripts.sub, style, noneNode)
|
|
74
|
+
const preSup = buildGroup(group.prescripts.sup, style, noneNode)
|
|
75
|
+
if (group.isSideset) {
|
|
76
|
+
// This seems silly, but LaTeX does this. Firefox ignores it, which does not make me sad.
|
|
77
|
+
preSub.setAttribute("style", "text-align: left;")
|
|
78
|
+
preSup.setAttribute("style", "text-align: left;")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (group.postscripts) {
|
|
82
|
+
const postSub = buildGroup(group.postscripts.sub, style, noneNode)
|
|
83
|
+
const postSup = buildGroup(group.postscripts.sup, style, noneNode)
|
|
84
|
+
children = [base, postSub, postSup, prescriptsNode, preSub, preSup]
|
|
85
|
+
} else {
|
|
86
|
+
children = [base, prescriptsNode, preSub, preSup]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return new mathMLTree.MathNode("mmultiscripts", children);
|
|
90
|
+
}
|
|
91
|
+
});
|