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,252 @@
|
|
1
|
+
import defineFunction from "../defineFunction";
|
2
|
+
import mathMLTree from "../mathMLTree";
|
3
|
+
import * as mml from "../buildMathML";
|
4
|
+
import { assertSymbolNodeType } from "../parseNode";
|
5
|
+
import ParseError from "../ParseError";
|
6
|
+
|
7
|
+
const cdArrowFunctionName = {
|
8
|
+
">": "\\\\cdrightarrow",
|
9
|
+
"<": "\\\\cdleftarrow",
|
10
|
+
"=": "\\\\cdlongequal",
|
11
|
+
A: "\\uparrow",
|
12
|
+
V: "\\downarrow",
|
13
|
+
"|": "\\Vert",
|
14
|
+
".": "no arrow"
|
15
|
+
};
|
16
|
+
|
17
|
+
const newCell = () => {
|
18
|
+
// Create an empty cell, to be filled below with parse nodes.
|
19
|
+
return { type: "styling", body: [], mode: "math", scriptLevel: "display" };
|
20
|
+
};
|
21
|
+
|
22
|
+
const isStartOfArrow = (node) => {
|
23
|
+
return node.type === "textord" && node.text === "@";
|
24
|
+
};
|
25
|
+
|
26
|
+
const isLabelEnd = (node, endChar) => {
|
27
|
+
return (node.type === "mathord" || node.type === "atom") && node.text === endChar;
|
28
|
+
};
|
29
|
+
|
30
|
+
function cdArrow(arrowChar, labels, parser) {
|
31
|
+
// Return a parse tree of an arrow and its labels.
|
32
|
+
// This acts in a way similar to a macro expansion.
|
33
|
+
const funcName = cdArrowFunctionName[arrowChar];
|
34
|
+
switch (funcName) {
|
35
|
+
case "\\\\cdrightarrow":
|
36
|
+
case "\\\\cdleftarrow":
|
37
|
+
return parser.callFunction(funcName, [labels[0]], [labels[1]]);
|
38
|
+
case "\\uparrow":
|
39
|
+
case "\\downarrow": {
|
40
|
+
const leftLabel = parser.callFunction("\\\\cdleft", [labels[0]], []);
|
41
|
+
const bareArrow = {
|
42
|
+
type: "atom",
|
43
|
+
text: funcName,
|
44
|
+
mode: "math",
|
45
|
+
family: "rel"
|
46
|
+
};
|
47
|
+
const sizedArrow = parser.callFunction("\\Big", [bareArrow], []);
|
48
|
+
const rightLabel = parser.callFunction("\\\\cdright", [labels[1]], []);
|
49
|
+
const arrowGroup = {
|
50
|
+
type: "ordgroup",
|
51
|
+
mode: "math",
|
52
|
+
body: [leftLabel, sizedArrow, rightLabel]
|
53
|
+
};
|
54
|
+
return parser.callFunction("\\\\cdparent", [arrowGroup], []);
|
55
|
+
}
|
56
|
+
case "\\\\cdlongequal":
|
57
|
+
return parser.callFunction("\\\\cdlongequal", [], []);
|
58
|
+
case "\\Vert": {
|
59
|
+
const arrow = { type: "textord", text: "\\Vert", mode: "math" };
|
60
|
+
return parser.callFunction("\\Big", [arrow], []);
|
61
|
+
}
|
62
|
+
default:
|
63
|
+
return { type: "textord", text: " ", mode: "math" };
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
export function parseCD(parser) {
|
68
|
+
// Get the array's parse nodes with \\ temporarily mapped to \cr.
|
69
|
+
const parsedRows = [];
|
70
|
+
parser.gullet.beginGroup();
|
71
|
+
parser.gullet.macros.set("\\cr", "\\\\\\relax");
|
72
|
+
parser.gullet.beginGroup();
|
73
|
+
while (true) { // eslint-disable-line no-constant-condition
|
74
|
+
// Get the parse nodes for the next row.
|
75
|
+
parsedRows.push(parser.parseExpression(false, "\\\\"));
|
76
|
+
parser.gullet.endGroup();
|
77
|
+
parser.gullet.beginGroup();
|
78
|
+
const next = parser.fetch().text;
|
79
|
+
if (next === "&" || next === "\\\\") {
|
80
|
+
parser.consume();
|
81
|
+
} else if (next === "\\end") {
|
82
|
+
if (parsedRows[parsedRows.length - 1].length === 0) {
|
83
|
+
parsedRows.pop(); // final row ended in \\
|
84
|
+
}
|
85
|
+
break;
|
86
|
+
} else {
|
87
|
+
throw new ParseError("Expected \\\\ or \\cr or \\end", parser.nextToken);
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
let row = [];
|
92
|
+
const body = [row];
|
93
|
+
|
94
|
+
// Loop thru the parse nodes. Collect them into cells and arrows.
|
95
|
+
for (let i = 0; i < parsedRows.length; i++) {
|
96
|
+
// Start a new row.
|
97
|
+
const rowNodes = parsedRows[i];
|
98
|
+
// Create the first cell.
|
99
|
+
let cell = newCell();
|
100
|
+
|
101
|
+
for (let j = 0; j < rowNodes.length; j++) {
|
102
|
+
if (!isStartOfArrow(rowNodes[j])) {
|
103
|
+
// If a parseNode is not an arrow, it goes into a cell.
|
104
|
+
cell.body.push(rowNodes[j]);
|
105
|
+
} else {
|
106
|
+
// Parse node j is an "@", the start of an arrow.
|
107
|
+
// Before starting on the arrow, push the cell into `row`.
|
108
|
+
row.push(cell);
|
109
|
+
|
110
|
+
// Now collect parseNodes into an arrow.
|
111
|
+
// The character after "@" defines the arrow type.
|
112
|
+
j += 1;
|
113
|
+
const arrowChar = assertSymbolNodeType(rowNodes[j]).text;
|
114
|
+
|
115
|
+
// Create two empty label nodes. We may or may not use them.
|
116
|
+
const labels = new Array(2);
|
117
|
+
labels[0] = { type: "ordgroup", mode: "math", body: [] };
|
118
|
+
labels[1] = { type: "ordgroup", mode: "math", body: [] };
|
119
|
+
|
120
|
+
// Process the arrow.
|
121
|
+
if ("=|.".indexOf(arrowChar) > -1) {
|
122
|
+
// Three "arrows", ``@=`, `@|`, and `@.`, do not take labels.
|
123
|
+
// Do nothing here.
|
124
|
+
} else if ("<>AV".indexOf(arrowChar) > -1) {
|
125
|
+
// Four arrows, `@>>>`, `@<<<`, `@AAA`, and `@VVV`, each take
|
126
|
+
// two optional labels. E.g. the right-point arrow syntax is
|
127
|
+
// really: @>{optional label}>{optional label}>
|
128
|
+
// Collect parseNodes into labels.
|
129
|
+
for (let labelNum = 0; labelNum < 2; labelNum++) {
|
130
|
+
let inLabel = true;
|
131
|
+
for (let k = j + 1; k < rowNodes.length; k++) {
|
132
|
+
if (isLabelEnd(rowNodes[k], arrowChar)) {
|
133
|
+
inLabel = false;
|
134
|
+
j = k;
|
135
|
+
break;
|
136
|
+
}
|
137
|
+
if (isStartOfArrow(rowNodes[k])) {
|
138
|
+
throw new ParseError(
|
139
|
+
"Missing a " + arrowChar + " character to complete a CD arrow.",
|
140
|
+
rowNodes[k]
|
141
|
+
);
|
142
|
+
}
|
143
|
+
|
144
|
+
labels[labelNum].body.push(rowNodes[k]);
|
145
|
+
}
|
146
|
+
if (inLabel) {
|
147
|
+
// isLabelEnd never returned a true.
|
148
|
+
throw new ParseError(
|
149
|
+
"Missing a " + arrowChar + " character to complete a CD arrow.",
|
150
|
+
rowNodes[j]
|
151
|
+
);
|
152
|
+
}
|
153
|
+
}
|
154
|
+
} else {
|
155
|
+
throw new ParseError(`Expected one of "<>AV=|." after @.`);
|
156
|
+
}
|
157
|
+
|
158
|
+
// Now join the arrow to its labels.
|
159
|
+
const arrow = cdArrow(arrowChar, labels, parser);
|
160
|
+
|
161
|
+
// Wrap the arrow in a styling node
|
162
|
+
row.push(arrow);
|
163
|
+
// In CD's syntax, cells are implicit. That is, everything that
|
164
|
+
// is not an arrow gets collected into a cell. So create an empty
|
165
|
+
// cell now. It will collect upcoming parseNodes.
|
166
|
+
cell = newCell();
|
167
|
+
}
|
168
|
+
}
|
169
|
+
if (i % 2 === 0) {
|
170
|
+
// Even-numbered rows consist of: cell, arrow, cell, arrow, ... cell
|
171
|
+
// The last cell is not yet pushed into `row`, so:
|
172
|
+
row.push(cell);
|
173
|
+
} else {
|
174
|
+
// Odd-numbered rows consist of: vert arrow, empty cell, ... vert arrow
|
175
|
+
// Remove the empty cell that was placed at the beginning of `row`.
|
176
|
+
row.shift();
|
177
|
+
}
|
178
|
+
row = [];
|
179
|
+
body.push(row);
|
180
|
+
}
|
181
|
+
body.pop()
|
182
|
+
|
183
|
+
// End row group
|
184
|
+
parser.gullet.endGroup();
|
185
|
+
// End array group defining \\
|
186
|
+
parser.gullet.endGroup();
|
187
|
+
|
188
|
+
return {
|
189
|
+
type: "array",
|
190
|
+
mode: "math",
|
191
|
+
body,
|
192
|
+
envClasses: ["jot", "cd"],
|
193
|
+
cols: [],
|
194
|
+
hLinesBeforeRow: new Array(body.length + 1).fill([])
|
195
|
+
};
|
196
|
+
}
|
197
|
+
|
198
|
+
// The functions below are not available for general use.
|
199
|
+
// They are here only for internal use by the {CD} environment in placing labels
|
200
|
+
// next to vertical arrows.
|
201
|
+
|
202
|
+
// We don't need any such functions for horizontal arrows because we can reuse
|
203
|
+
// the functionality that already exists for extensible arrows.
|
204
|
+
|
205
|
+
defineFunction({
|
206
|
+
type: "cdlabel",
|
207
|
+
names: ["\\\\cdleft", "\\\\cdright"],
|
208
|
+
props: {
|
209
|
+
numArgs: 1
|
210
|
+
},
|
211
|
+
handler({ parser, funcName }, args) {
|
212
|
+
return {
|
213
|
+
type: "cdlabel",
|
214
|
+
mode: parser.mode,
|
215
|
+
side: funcName.slice(4),
|
216
|
+
label: args[0]
|
217
|
+
};
|
218
|
+
},
|
219
|
+
mathmlBuilder(group, style) {
|
220
|
+
let label = new mathMLTree.MathNode("mrow", [mml.buildGroup(group.label, style)]);
|
221
|
+
label = new mathMLTree.MathNode("mpadded", [label]);
|
222
|
+
label.setAttribute("width", "0");
|
223
|
+
if (group.side === "left") {
|
224
|
+
label.setAttribute("lspace", "-1width");
|
225
|
+
}
|
226
|
+
// We have to guess at vertical alignment. We know the arrow is 1.8em tall,
|
227
|
+
// But we don't know the height or depth of the label.
|
228
|
+
label.setAttribute("voffset", "0.7em");
|
229
|
+
label = new mathMLTree.MathNode("mstyle", [label]);
|
230
|
+
label.setAttribute("displaystyle", "false");
|
231
|
+
label.setAttribute("scriptlevel", "1");
|
232
|
+
return label;
|
233
|
+
}
|
234
|
+
});
|
235
|
+
|
236
|
+
defineFunction({
|
237
|
+
type: "cdlabelparent",
|
238
|
+
names: ["\\\\cdparent"],
|
239
|
+
props: {
|
240
|
+
numArgs: 1
|
241
|
+
},
|
242
|
+
handler({ parser }, args) {
|
243
|
+
return {
|
244
|
+
type: "cdlabelparent",
|
245
|
+
mode: parser.mode,
|
246
|
+
fragment: args[0]
|
247
|
+
};
|
248
|
+
},
|
249
|
+
mathmlBuilder(group, style) {
|
250
|
+
return new mathMLTree.MathNode("mrow", [mml.buildGroup(group.fragment, style)]);
|
251
|
+
}
|
252
|
+
});
|
@@ -0,0 +1,127 @@
|
|
1
|
+
import defineFunction, { normalizeArgument } from "../defineFunction"
|
2
|
+
import mathMLTree from "../mathMLTree"
|
3
|
+
import stretchy from "../stretchy"
|
4
|
+
import * as mml from "../buildMathML"
|
5
|
+
|
6
|
+
const mathmlBuilder = (group, style) => {
|
7
|
+
const accentNode = group.isStretchy
|
8
|
+
? stretchy.mathMLnode(group.label)
|
9
|
+
: new mathMLTree.MathNode("mo", [mml.makeText(group.label, group.mode)]);
|
10
|
+
|
11
|
+
if (group.label === "\\vec") {
|
12
|
+
accentNode.style.transform = "scale(0.75) translate(10%, 30%)"
|
13
|
+
} else {
|
14
|
+
accentNode.style.mathStyle = "normal"
|
15
|
+
accentNode.style.mathDepth = "0"
|
16
|
+
}
|
17
|
+
if (!group.isStretchy) {
|
18
|
+
accentNode.setAttribute("stretchy", "false")
|
19
|
+
}
|
20
|
+
|
21
|
+
const node = new mathMLTree.MathNode((group.label === "\\c" ? "munder" : "mover"),
|
22
|
+
[mml.buildGroup(group.base, style), accentNode]
|
23
|
+
);
|
24
|
+
|
25
|
+
return node;
|
26
|
+
};
|
27
|
+
|
28
|
+
const NON_STRETCHY_ACCENT_REGEX = new RegExp(
|
29
|
+
[
|
30
|
+
"\\acute",
|
31
|
+
"\\grave",
|
32
|
+
"\\ddot",
|
33
|
+
"\\dddot",
|
34
|
+
"\\ddddot",
|
35
|
+
"\\tilde",
|
36
|
+
"\\bar",
|
37
|
+
"\\breve",
|
38
|
+
"\\check",
|
39
|
+
"\\hat",
|
40
|
+
"\\vec",
|
41
|
+
"\\dot",
|
42
|
+
"\\mathring"
|
43
|
+
]
|
44
|
+
.map((accent) => `\\${accent}`)
|
45
|
+
.join("|")
|
46
|
+
);
|
47
|
+
|
48
|
+
// Accents
|
49
|
+
defineFunction({
|
50
|
+
type: "accent",
|
51
|
+
names: [
|
52
|
+
"\\acute",
|
53
|
+
"\\grave",
|
54
|
+
"\\ddot",
|
55
|
+
"\\dddot",
|
56
|
+
"\\ddddot",
|
57
|
+
"\\tilde",
|
58
|
+
"\\bar",
|
59
|
+
"\\breve",
|
60
|
+
"\\check",
|
61
|
+
"\\hat",
|
62
|
+
"\\vec",
|
63
|
+
"\\dot",
|
64
|
+
"\\mathring",
|
65
|
+
"\\overparen",
|
66
|
+
"\\widecheck",
|
67
|
+
"\\widehat",
|
68
|
+
"\\wideparen",
|
69
|
+
"\\widetilde",
|
70
|
+
"\\overrightarrow",
|
71
|
+
"\\overleftarrow",
|
72
|
+
"\\Overrightarrow",
|
73
|
+
"\\overleftrightarrow",
|
74
|
+
"\\overgroup",
|
75
|
+
"\\overleftharpoon",
|
76
|
+
"\\overrightharpoon"
|
77
|
+
],
|
78
|
+
props: {
|
79
|
+
numArgs: 1
|
80
|
+
},
|
81
|
+
handler: (context, args) => {
|
82
|
+
const base = normalizeArgument(args[0]);
|
83
|
+
|
84
|
+
const isStretchy = !NON_STRETCHY_ACCENT_REGEX.test(context.funcName);
|
85
|
+
|
86
|
+
return {
|
87
|
+
type: "accent",
|
88
|
+
mode: context.parser.mode,
|
89
|
+
label: context.funcName,
|
90
|
+
isStretchy: isStretchy,
|
91
|
+
base: base
|
92
|
+
};
|
93
|
+
},
|
94
|
+
mathmlBuilder
|
95
|
+
});
|
96
|
+
|
97
|
+
// Text-mode accents
|
98
|
+
defineFunction({
|
99
|
+
type: "accent",
|
100
|
+
names: ["\\'", "\\`", "\\^", "\\~", "\\=", "\\c", "\\u", "\\.", '\\"', "\\r", "\\H", "\\v"],
|
101
|
+
props: {
|
102
|
+
numArgs: 1,
|
103
|
+
allowedInText: true,
|
104
|
+
allowedInMath: true,
|
105
|
+
argTypes: ["primitive"]
|
106
|
+
},
|
107
|
+
handler: (context, args) => {
|
108
|
+
const base = normalizeArgument(args[0]);
|
109
|
+
const mode = context.parser.mode;
|
110
|
+
|
111
|
+
if (mode === "math" && context.parser.settings.strict) {
|
112
|
+
// LaTeX only writes a warning. It doesn't stop. We'll issue the same warning.
|
113
|
+
// eslint-disable-next-line no-console
|
114
|
+
console.log(`Temml parse error: Command ${context.funcName} is invalid in math mode.`)
|
115
|
+
}
|
116
|
+
|
117
|
+
return {
|
118
|
+
type: "accent",
|
119
|
+
mode: mode,
|
120
|
+
label: context.funcName,
|
121
|
+
isStretchy: false,
|
122
|
+
isShifty: true,
|
123
|
+
base: base
|
124
|
+
};
|
125
|
+
},
|
126
|
+
mathmlBuilder
|
127
|
+
});
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import defineFunction from "../defineFunction";
|
2
|
+
import mathMLTree from "../mathMLTree";
|
3
|
+
import stretchy from "../stretchy";
|
4
|
+
|
5
|
+
import * as mml from "../buildMathML";
|
6
|
+
|
7
|
+
defineFunction({
|
8
|
+
type: "accentUnder",
|
9
|
+
names: [
|
10
|
+
"\\underleftarrow",
|
11
|
+
"\\underrightarrow",
|
12
|
+
"\\underleftrightarrow",
|
13
|
+
"\\undergroup",
|
14
|
+
"\\underparen",
|
15
|
+
"\\utilde"
|
16
|
+
],
|
17
|
+
props: {
|
18
|
+
numArgs: 1
|
19
|
+
},
|
20
|
+
handler: ({ parser, funcName }, args) => {
|
21
|
+
const base = args[0];
|
22
|
+
return {
|
23
|
+
type: "accentUnder",
|
24
|
+
mode: parser.mode,
|
25
|
+
label: funcName,
|
26
|
+
base: base
|
27
|
+
};
|
28
|
+
},
|
29
|
+
mathmlBuilder: (group, style) => {
|
30
|
+
const accentNode = stretchy.mathMLnode(group.label);
|
31
|
+
accentNode.style["math-depth"] = 0
|
32
|
+
const node = new mathMLTree.MathNode("munder", [
|
33
|
+
mml.buildGroup(group.base, style),
|
34
|
+
accentNode
|
35
|
+
]);
|
36
|
+
return node;
|
37
|
+
}
|
38
|
+
});
|
@@ -0,0 +1,204 @@
|
|
1
|
+
import defineFunction from "../defineFunction";
|
2
|
+
import mathMLTree from "../mathMLTree";
|
3
|
+
import stretchy from "../stretchy";
|
4
|
+
import { emScale } from "../units";
|
5
|
+
import * as mml from "../buildMathML";
|
6
|
+
|
7
|
+
// Helper functions
|
8
|
+
|
9
|
+
const padding = width => {
|
10
|
+
const node = new mathMLTree.MathNode("mspace")
|
11
|
+
node.setAttribute("width", width + "em")
|
12
|
+
return node
|
13
|
+
}
|
14
|
+
|
15
|
+
const paddedNode = (group, lspace = 0.3, rspace = 0) => {
|
16
|
+
if (group == null && rspace === 0) { return padding(lspace) }
|
17
|
+
const row = group ? [group] : [];
|
18
|
+
if (lspace !== 0) { row.unshift(padding(lspace)) }
|
19
|
+
if (rspace > 0) { row.push(padding(rspace)) }
|
20
|
+
return new mathMLTree.MathNode("mrow", row)
|
21
|
+
};
|
22
|
+
|
23
|
+
const labelSize = (size, scriptLevel) => (size / emScale(scriptLevel)).toFixed(4)
|
24
|
+
|
25
|
+
const munderoverNode = (name, body, below, style) => {
|
26
|
+
const arrowNode = stretchy.mathMLnode(name);
|
27
|
+
// Is this the short part of a mhchem equilibrium arrow?
|
28
|
+
const isEq = name.slice(1, 3) === "eq"
|
29
|
+
const minWidth = name.charAt(1) === "x"
|
30
|
+
? "1.75" // mathtools extensible arrows are 1.75em long
|
31
|
+
: name.slice(2, 4) === "cd"
|
32
|
+
? "3.0" // cd package arrows
|
33
|
+
: isEq
|
34
|
+
? "1.0" // The shorter harpoon of a mhchem equilibrium arrow
|
35
|
+
: "2.0"; // other mhchem arrows
|
36
|
+
arrowNode.setAttribute("minsize", String(minWidth) + "em");
|
37
|
+
arrowNode.setAttribute("lspace", "0")
|
38
|
+
arrowNode.setAttribute("rspace", (isEq ? "0.5em" : "0"))
|
39
|
+
|
40
|
+
// <munderover> upper and lower labels are set to scriptlevel by MathML
|
41
|
+
// So we have to adjust our dimensions accordingly.
|
42
|
+
const labelStyle = style.withLevel(style.level < 2 ? 2 : 3)
|
43
|
+
const emptyLabelWidth = labelSize(minWidth, labelStyle.level)
|
44
|
+
const lspace = labelSize((isEq ? 0 : 0.3), labelStyle.level)
|
45
|
+
const rspace = labelSize((isEq ? 0 : 0.3), labelStyle.level)
|
46
|
+
|
47
|
+
const upperNode = (body && body.body &&
|
48
|
+
// \hphantom visible content
|
49
|
+
(body.body.body || body.body.length > 0))
|
50
|
+
? paddedNode(mml.buildGroup(body, labelStyle), lspace, rspace)
|
51
|
+
// Since Firefox does not recognize minsize set on the arrow,
|
52
|
+
// create an upper node w/correct width.
|
53
|
+
: paddedNode(null, emptyLabelWidth, 0)
|
54
|
+
const lowerNode = (below && below.body &&
|
55
|
+
(below.body.body || below.body.length > 0))
|
56
|
+
? paddedNode(mml.buildGroup(below, labelStyle), lspace, rspace)
|
57
|
+
: paddedNode(null, emptyLabelWidth, 0)
|
58
|
+
const node = new mathMLTree.MathNode("munderover", [arrowNode, lowerNode, upperNode]);
|
59
|
+
if (minWidth === "3.0") { node.style.height = "1em" }
|
60
|
+
return node
|
61
|
+
}
|
62
|
+
|
63
|
+
// Stretchy arrows with an optional argument
|
64
|
+
defineFunction({
|
65
|
+
type: "xArrow",
|
66
|
+
names: [
|
67
|
+
"\\xleftarrow",
|
68
|
+
"\\xrightarrow",
|
69
|
+
"\\xLeftarrow",
|
70
|
+
"\\xRightarrow",
|
71
|
+
"\\xleftrightarrow",
|
72
|
+
"\\xLeftrightarrow",
|
73
|
+
"\\xhookleftarrow",
|
74
|
+
"\\xhookrightarrow",
|
75
|
+
"\\xmapsto",
|
76
|
+
"\\xrightharpoondown",
|
77
|
+
"\\xrightharpoonup",
|
78
|
+
"\\xleftharpoondown",
|
79
|
+
"\\xleftharpoonup",
|
80
|
+
"\\xlongequal",
|
81
|
+
"\\xtwoheadrightarrow",
|
82
|
+
"\\xtwoheadleftarrow",
|
83
|
+
// The next 5 functions are here only to support mhchem
|
84
|
+
"\\yields",
|
85
|
+
"\\yieldsLeft",
|
86
|
+
"\\mesomerism",
|
87
|
+
"\\longrightharpoonup",
|
88
|
+
"\\longleftharpoondown",
|
89
|
+
// The next 3 functions are here only to support the {CD} environment.
|
90
|
+
"\\\\cdrightarrow",
|
91
|
+
"\\\\cdleftarrow",
|
92
|
+
"\\\\cdlongequal"
|
93
|
+
],
|
94
|
+
props: {
|
95
|
+
numArgs: 1,
|
96
|
+
numOptionalArgs: 1
|
97
|
+
},
|
98
|
+
handler({ parser, funcName }, args, optArgs) {
|
99
|
+
return {
|
100
|
+
type: "xArrow",
|
101
|
+
mode: parser.mode,
|
102
|
+
name: funcName,
|
103
|
+
body: args[0],
|
104
|
+
below: optArgs[0]
|
105
|
+
};
|
106
|
+
},
|
107
|
+
mathmlBuilder(group, style) {
|
108
|
+
// Build the arrow and its labels.
|
109
|
+
const node = munderoverNode(group.name, group.body, group.below, style)
|
110
|
+
// Create operator spacing for a relation.
|
111
|
+
const row = [node]
|
112
|
+
row.unshift(padding(0.2778))
|
113
|
+
row.push(padding(0.2778))
|
114
|
+
return new mathMLTree.MathNode("mrow", row)
|
115
|
+
}
|
116
|
+
});
|
117
|
+
|
118
|
+
const arrowComponent = {
|
119
|
+
"\\xtofrom": ["\\xrightarrow", "\\xleftarrow"],
|
120
|
+
"\\xleftrightharpoons": ["\\xleftharpoonup", "\\xrightharpoondown"],
|
121
|
+
"\\xrightleftharpoons": ["\\xrightharpoonup", "\\xleftharpoondown"],
|
122
|
+
"\\yieldsLeftRight": ["\\yields", "\\yieldsLeft"],
|
123
|
+
// The next three all get the same harpoon glyphs. Only the lengths and paddings differ.
|
124
|
+
"\\equilibrium": ["\\longrightharpoonup", "\\longleftharpoondown"],
|
125
|
+
"\\equilibriumRight": ["\\longrightharpoonup", "\\eqleftharpoondown"],
|
126
|
+
"\\equilibriumLeft": ["\\eqrightharpoonup", "\\longleftharpoondown"]
|
127
|
+
}
|
128
|
+
|
129
|
+
// Browsers are not good at stretching a glyph that contains a pair of stacked arrows such as ⇄.
|
130
|
+
// So we stack a pair of single arrows.
|
131
|
+
defineFunction({
|
132
|
+
type: "stackedArrow",
|
133
|
+
names: [
|
134
|
+
"\\xtofrom", // expfeil
|
135
|
+
"\\xleftrightharpoons", // mathtools
|
136
|
+
"\\xrightleftharpoons", // mathtools
|
137
|
+
"\\yieldsLeftRight", // mhchem
|
138
|
+
"\\equilibrium", // mhchem
|
139
|
+
"\\equilibriumRight",
|
140
|
+
"\\equilibriumLeft"
|
141
|
+
],
|
142
|
+
props: {
|
143
|
+
numArgs: 1,
|
144
|
+
numOptionalArgs: 1
|
145
|
+
},
|
146
|
+
handler({ parser, funcName }, args, optArgs) {
|
147
|
+
const lowerArrowBody = args[0]
|
148
|
+
? {
|
149
|
+
type: "hphantom",
|
150
|
+
mode: parser.mode,
|
151
|
+
body: args[0]
|
152
|
+
}
|
153
|
+
: null;
|
154
|
+
const upperArrowBelow = optArgs[0]
|
155
|
+
? {
|
156
|
+
type: "hphantom",
|
157
|
+
mode: parser.mode,
|
158
|
+
body: optArgs[0]
|
159
|
+
}
|
160
|
+
: null;
|
161
|
+
return {
|
162
|
+
type: "stackedArrow",
|
163
|
+
mode: parser.mode,
|
164
|
+
name: funcName,
|
165
|
+
body: args[0],
|
166
|
+
upperArrowBelow,
|
167
|
+
lowerArrowBody,
|
168
|
+
below: optArgs[0]
|
169
|
+
};
|
170
|
+
},
|
171
|
+
mathmlBuilder(group, style) {
|
172
|
+
const topLabel = arrowComponent[group.name][0]
|
173
|
+
const botLabel = arrowComponent[group.name][1]
|
174
|
+
const topArrow = munderoverNode(topLabel, group.body, group.upperArrowBelow, style)
|
175
|
+
const botArrow = munderoverNode(botLabel, group.lowerArrowBody, group.below, style)
|
176
|
+
let wrapper
|
177
|
+
|
178
|
+
const raiseNode = new mathMLTree.MathNode("mpadded", [topArrow])
|
179
|
+
raiseNode.setAttribute("voffset", "0.3em")
|
180
|
+
raiseNode.setAttribute("height", "+0.3em")
|
181
|
+
raiseNode.setAttribute("depth", "-0.3em")
|
182
|
+
// One of the arrows is given ~zero width. so the other has the same horzontal alignment.
|
183
|
+
if (group.name === "\\equilibriumLeft") {
|
184
|
+
const botNode = new mathMLTree.MathNode("mpadded", [botArrow])
|
185
|
+
botNode.setAttribute("width", "0.5em")
|
186
|
+
wrapper = new mathMLTree.MathNode(
|
187
|
+
"mpadded",
|
188
|
+
[padding(0.2778), botNode, raiseNode, padding(0.2778)]
|
189
|
+
)
|
190
|
+
} else {
|
191
|
+
raiseNode.setAttribute("width", (group.name === "\\equilibriumRight" ? "0.5em" : "0"))
|
192
|
+
wrapper = new mathMLTree.MathNode(
|
193
|
+
"mpadded",
|
194
|
+
[padding(0.2778), raiseNode, botArrow, padding(0.2778)]
|
195
|
+
)
|
196
|
+
}
|
197
|
+
|
198
|
+
wrapper.setAttribute("voffset", "-0.18em")
|
199
|
+
wrapper.setAttribute("height", "-0.18em")
|
200
|
+
wrapper.setAttribute("depth", "+0.18em")
|
201
|
+
return wrapper
|
202
|
+
}
|
203
|
+
});
|
204
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import defineFunction from "../defineFunction";
|
2
|
+
import mathMLTree from "../mathMLTree";
|
3
|
+
import * as mml from "../buildMathML";
|
4
|
+
|
5
|
+
defineFunction({
|
6
|
+
type: "cancelto",
|
7
|
+
names: ["\\cancelto"],
|
8
|
+
props: {
|
9
|
+
numArgs: 2
|
10
|
+
},
|
11
|
+
handler({ parser }, args) {
|
12
|
+
return {
|
13
|
+
type: "cancelto",
|
14
|
+
mode: parser.mode,
|
15
|
+
value: args[0],
|
16
|
+
expression: args[1]
|
17
|
+
};
|
18
|
+
},
|
19
|
+
mathmlBuilder(group, style) {
|
20
|
+
const value = new mathMLTree.MathNode(
|
21
|
+
"mpadded",
|
22
|
+
[mml.buildGroup(group.value, style)]
|
23
|
+
)
|
24
|
+
value.setAttribute("depth", `-0.1em`)
|
25
|
+
value.setAttribute("height", `+0.1em`)
|
26
|
+
value.setAttribute("voffset", `0.1em`)
|
27
|
+
|
28
|
+
const expression = new mathMLTree.MathNode(
|
29
|
+
"menclose",
|
30
|
+
[mml.buildGroup(group.expression, style)]
|
31
|
+
)
|
32
|
+
expression.setAttribute("notation", `updiagonalarrow`)
|
33
|
+
|
34
|
+
return new mathMLTree.MathNode("msup", [expression, value])
|
35
|
+
}
|
36
|
+
})
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import defineFunction from "../defineFunction"
|
2
|
+
import ParseError from "../ParseError"
|
3
|
+
import { assertNodeType } from "../parseNode"
|
4
|
+
|
5
|
+
// \@char is an internal function that takes a grouped decimal argument like
|
6
|
+
// {123} and converts into symbol with code 123. It is used by the *macro*
|
7
|
+
// \char defined in macros.js.
|
8
|
+
defineFunction({
|
9
|
+
type: "textord",
|
10
|
+
names: ["\\@char"],
|
11
|
+
props: {
|
12
|
+
numArgs: 1,
|
13
|
+
allowedInText: true
|
14
|
+
},
|
15
|
+
handler({ parser, token }, args) {
|
16
|
+
const arg = assertNodeType(args[0], "ordgroup")
|
17
|
+
const group = arg.body
|
18
|
+
let number = ""
|
19
|
+
for (let i = 0; i < group.length; i++) {
|
20
|
+
const node = assertNodeType(group[i], "textord")
|
21
|
+
number += node.text
|
22
|
+
}
|
23
|
+
const code = parseInt(number)
|
24
|
+
if (isNaN(code)) {
|
25
|
+
throw new ParseError(`\\@char has non-numeric argument ${number}`, token)
|
26
|
+
}
|
27
|
+
return {
|
28
|
+
type: "textord",
|
29
|
+
mode: parser.mode,
|
30
|
+
text: String.fromCodePoint(code)
|
31
|
+
}
|
32
|
+
}
|
33
|
+
})
|