temml 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
});
|