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,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
+ });