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.
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,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"; // &VeryThinSpace;
66
+ } else if (width >= 0.1666 && width <= 0.1667) {
67
+ return "\u2009"; // &ThinSpace;
68
+ } else if (width >= 0.2222 && width <= 0.2223) {
69
+ return "\u2005"; // &MediumSpace;
70
+ } else if (width >= 0.2777 && width <= 0.2778) {
71
+ return "\u2005\u200a"; // &ThickSpace;
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
+ });