temml 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -0
  3. package/contrib/auto-render/README.md +89 -0
  4. package/contrib/auto-render/auto-render.js +128 -0
  5. package/contrib/auto-render/dist/auto-render.js +217 -0
  6. package/contrib/auto-render/dist/auto-render.min.js +1 -0
  7. package/contrib/auto-render/splitAtDelimiters.js +84 -0
  8. package/contrib/auto-render/test/auto-render-spec.js +234 -0
  9. package/contrib/auto-render/test/auto-render.js +217 -0
  10. package/contrib/auto-render/test/test_page.html +59 -0
  11. package/contrib/mhchem/README.md +26 -0
  12. package/contrib/mhchem/mhchem.js +1705 -0
  13. package/contrib/mhchem/mhchem.min.js +1 -0
  14. package/contrib/physics/README.md +20 -0
  15. package/contrib/physics/physics.js +131 -0
  16. package/contrib/texvc/README.md +23 -0
  17. package/contrib/texvc/texvc.js +61 -0
  18. package/dist/Temml-Asana.css +201 -0
  19. package/dist/Temml-Latin-Modern.css +216 -0
  20. package/dist/Temml-Libertinus.css +214 -0
  21. package/dist/Temml-Local.css +194 -0
  22. package/dist/Temml-STIX2.css +203 -0
  23. package/dist/Temml.woff2 +0 -0
  24. package/dist/temml.cjs +13122 -0
  25. package/dist/temml.js +11225 -0
  26. package/dist/temml.min.js +1 -0
  27. package/dist/temml.mjs +13120 -0
  28. package/dist/temmlPostProcess.js +70 -0
  29. package/package.json +34 -0
  30. package/src/Lexer.js +121 -0
  31. package/src/MacroExpander.js +437 -0
  32. package/src/Namespace.js +107 -0
  33. package/src/ParseError.js +64 -0
  34. package/src/Parser.js +977 -0
  35. package/src/Settings.js +49 -0
  36. package/src/SourceLocation.js +29 -0
  37. package/src/Style.js +144 -0
  38. package/src/Token.js +40 -0
  39. package/src/buildMathML.js +235 -0
  40. package/src/constants.js +25 -0
  41. package/src/defineEnvironment.js +25 -0
  42. package/src/defineFunction.js +69 -0
  43. package/src/defineMacro.js +11 -0
  44. package/src/domTree.js +185 -0
  45. package/src/environments/array.js +791 -0
  46. package/src/environments/cd.js +252 -0
  47. package/src/environments.js +8 -0
  48. package/src/functions/accent.js +127 -0
  49. package/src/functions/accentunder.js +38 -0
  50. package/src/functions/arrow.js +204 -0
  51. package/src/functions/cancelto.js +36 -0
  52. package/src/functions/char.js +33 -0
  53. package/src/functions/color.js +253 -0
  54. package/src/functions/cr.js +46 -0
  55. package/src/functions/def.js +259 -0
  56. package/src/functions/delimsizing.js +304 -0
  57. package/src/functions/enclose.js +193 -0
  58. package/src/functions/envTag.js +38 -0
  59. package/src/functions/environment.js +59 -0
  60. package/src/functions/font.js +123 -0
  61. package/src/functions/genfrac.js +333 -0
  62. package/src/functions/hbox.js +29 -0
  63. package/src/functions/horizBrace.js +32 -0
  64. package/src/functions/href.js +90 -0
  65. package/src/functions/html.js +95 -0
  66. package/src/functions/includegraphics.js +131 -0
  67. package/src/functions/kern.js +75 -0
  68. package/src/functions/label.js +29 -0
  69. package/src/functions/lap.js +75 -0
  70. package/src/functions/math.js +40 -0
  71. package/src/functions/mathchoice.js +41 -0
  72. package/src/functions/mclass.js +201 -0
  73. package/src/functions/multiscript.js +91 -0
  74. package/src/functions/not.js +46 -0
  75. package/src/functions/op.js +338 -0
  76. package/src/functions/operatorname.js +139 -0
  77. package/src/functions/ordgroup.js +9 -0
  78. package/src/functions/phantom.js +73 -0
  79. package/src/functions/pmb.js +31 -0
  80. package/src/functions/raise.js +68 -0
  81. package/src/functions/ref.js +28 -0
  82. package/src/functions/relax.js +16 -0
  83. package/src/functions/rule.js +52 -0
  84. package/src/functions/sizing.js +64 -0
  85. package/src/functions/smash.js +66 -0
  86. package/src/functions/sqrt.js +31 -0
  87. package/src/functions/styling.js +58 -0
  88. package/src/functions/supsub.js +135 -0
  89. package/src/functions/symbolsOp.js +53 -0
  90. package/src/functions/symbolsOrd.js +102 -0
  91. package/src/functions/symbolsSpacing.js +53 -0
  92. package/src/functions/tag.js +8 -0
  93. package/src/functions/text.js +75 -0
  94. package/src/functions/tip.js +63 -0
  95. package/src/functions/toggle.js +13 -0
  96. package/src/functions/verb.js +33 -0
  97. package/src/functions.js +57 -0
  98. package/src/linebreaking.js +159 -0
  99. package/src/macros.js +708 -0
  100. package/src/mathMLTree.js +175 -0
  101. package/src/parseNode.js +42 -0
  102. package/src/parseTree.js +40 -0
  103. package/src/postProcess.js +57 -0
  104. package/src/replace.js +225 -0
  105. package/src/stretchy.js +66 -0
  106. package/src/svg.js +110 -0
  107. package/src/symbols.js +972 -0
  108. package/src/tree.js +50 -0
  109. package/src/unicodeAccents.js +16 -0
  110. package/src/unicodeScripts.js +119 -0
  111. package/src/unicodeSupOrSub.js +108 -0
  112. package/src/unicodeSymbolBuilder.js +31 -0
  113. package/src/unicodeSymbols.js +320 -0
  114. package/src/units.js +109 -0
  115. package/src/utils.js +109 -0
  116. package/src/variant.js +103 -0
  117. 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,8 @@
1
+ import { _environments } from "./defineEnvironment";
2
+
3
+ const environments = _environments;
4
+
5
+ export default environments;
6
+
7
+ // All environment definitions should be imported below
8
+ import "./environments/array";
@@ -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
+ })