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
package/src/Settings.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is a module for storing settings passed into Temml. It correctly handles
|
|
3
|
+
* default settings.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import utils from "./utils";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The main Settings object
|
|
10
|
+
*/
|
|
11
|
+
export default class Settings {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
// allow null options
|
|
14
|
+
options = options || {};
|
|
15
|
+
this.displayMode = utils.deflt(options.displayMode, false); // boolean
|
|
16
|
+
this.annotate = utils.deflt(options.annotate, false) // boolean
|
|
17
|
+
this.leqno = utils.deflt(options.leqno, false); // boolean
|
|
18
|
+
this.errorColor = utils.deflt(options.errorColor, "#b22222"); // string
|
|
19
|
+
this.macros = options.macros || {};
|
|
20
|
+
this.wrap = utils.deflt(options.wrap, "none") // "none" | "tex" | "="
|
|
21
|
+
this.xml = utils.deflt(options.xml, false); // boolean
|
|
22
|
+
this.colorIsTextColor = utils.deflt(options.colorIsTextColor, false); // booelean
|
|
23
|
+
this.strict = utils.deflt(options.strict, false); // boolean
|
|
24
|
+
this.trust = utils.deflt(options.trust, false); // trust context. See html.js.
|
|
25
|
+
this.maxSize = (options.maxSize === undefined
|
|
26
|
+
? [Infinity, Infinity]
|
|
27
|
+
: Array.isArray(options.maxSize)
|
|
28
|
+
? options.maxSize
|
|
29
|
+
: [Infinity, Infinity]
|
|
30
|
+
)
|
|
31
|
+
this.maxExpand = Math.max(0, utils.deflt(options.maxExpand, 1000)); // number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check whether to test potentially dangerous input, and return
|
|
36
|
+
* `true` (trusted) or `false` (untrusted). The sole argument `context`
|
|
37
|
+
* should be an object with `command` field specifying the relevant LaTeX
|
|
38
|
+
* command (as a string starting with `\`), and any other arguments, etc.
|
|
39
|
+
* If `context` has a `url` field, a `protocol` field will automatically
|
|
40
|
+
* get added by this function (changing the specified object).
|
|
41
|
+
*/
|
|
42
|
+
isTrusted(context) {
|
|
43
|
+
if (context.url && !context.protocol) {
|
|
44
|
+
context.protocol = utils.protocolFromUrl(context.url);
|
|
45
|
+
}
|
|
46
|
+
const trust = typeof this.trust === "function" ? this.trust(context) : this.trust;
|
|
47
|
+
return Boolean(trust);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lexing or parsing positional information for error reporting.
|
|
3
|
+
* This object is immutable.
|
|
4
|
+
*/
|
|
5
|
+
export default class SourceLocation {
|
|
6
|
+
constructor(lexer, start, end) {
|
|
7
|
+
this.lexer = lexer; // Lexer holding the input string.
|
|
8
|
+
this.start = start; // Start offset, zero-based inclusive.
|
|
9
|
+
this.end = end; // End offset, zero-based exclusive.
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Merges two `SourceLocation`s from location providers, given they are
|
|
14
|
+
* provided in order of appearance.
|
|
15
|
+
* - Returns the first one's location if only the first is provided.
|
|
16
|
+
* - Returns a merged range of the first and the last if both are provided
|
|
17
|
+
* and their lexers match.
|
|
18
|
+
* - Otherwise, returns null.
|
|
19
|
+
*/
|
|
20
|
+
static range(first, second) {
|
|
21
|
+
if (!second) {
|
|
22
|
+
return first && first.loc;
|
|
23
|
+
} else if (!first || !first.loc || !second.loc || first.loc.lexer !== second.loc.lexer) {
|
|
24
|
+
return null;
|
|
25
|
+
} else {
|
|
26
|
+
return new SourceLocation(first.loc.lexer, first.loc.start, second.loc.end);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/Style.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains information about the style that the mathmlBuilder carries
|
|
3
|
+
* around with it. Data is held in an `Style` object, and when
|
|
4
|
+
* recursing, a new `Style` object can be created with the `.with*` functions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const subOrSupLevel = [2, 2, 3, 3];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* This is the main Style class. It contains the current style.level, color, and font.
|
|
11
|
+
*
|
|
12
|
+
* Style objects should not be modified. To create a new Style with
|
|
13
|
+
* different properties, call a `.with*` method.
|
|
14
|
+
*/
|
|
15
|
+
class Style {
|
|
16
|
+
constructor(data) {
|
|
17
|
+
// Style.level can be 0 | 1 | 2 | 3, which correspond to
|
|
18
|
+
// displaystyle, textstyle, scriptstyle, and scriptscriptstyle.
|
|
19
|
+
// style.level does not directly set MathML's script level. MathML does that itself.
|
|
20
|
+
// We use style.level to track, not set, math style so that we can get the
|
|
21
|
+
// correct scriptlevel when needed in supsub.js, mathchoice.js, or for dimensions in em.
|
|
22
|
+
this.level = data.level;
|
|
23
|
+
this.color = data.color; // string | void
|
|
24
|
+
// A font family applies to a group of fonts (i.e. SansSerif), while a font
|
|
25
|
+
// represents a specific font (i.e. SansSerif Bold).
|
|
26
|
+
// See: https://tex.stackexchange.com/questions/22350/difference-between-textrm-and-mathrm
|
|
27
|
+
this.font = data.font || ""; // string
|
|
28
|
+
this.fontFamily = data.fontFamily || ""; // string
|
|
29
|
+
this.fontSize = data.fontSize || 1.0; // number
|
|
30
|
+
this.fontWeight = data.fontWeight || "";
|
|
31
|
+
this.fontShape = data.fontShape || "";
|
|
32
|
+
this.maxSize = data.maxSize; // [number, number]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns a new style object with the same properties as "this". Properties
|
|
37
|
+
* from "extension" will be copied to the new style object.
|
|
38
|
+
*/
|
|
39
|
+
extend(extension) {
|
|
40
|
+
const data = {
|
|
41
|
+
level: this.level,
|
|
42
|
+
color: this.color,
|
|
43
|
+
font: this.font,
|
|
44
|
+
fontFamily: this.fontFamily,
|
|
45
|
+
fontSize: this.fontSize,
|
|
46
|
+
fontWeight: this.fontWeight,
|
|
47
|
+
fontShape: this.fontShape,
|
|
48
|
+
maxSize: this.maxSize
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
for (const key in extension) {
|
|
52
|
+
if (Object.prototype.hasOwnProperty.call(extension, key)) {
|
|
53
|
+
data[key] = extension[key];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return new Style(data);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
withLevel(n) {
|
|
61
|
+
return this.extend({
|
|
62
|
+
level: n
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
incrementLevel() {
|
|
67
|
+
return this.extend({
|
|
68
|
+
level: Math.min(this.level + 1, 3)
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
inSubOrSup() {
|
|
73
|
+
return this.extend({
|
|
74
|
+
level: subOrSupLevel[this.level]
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create a new style object with the given color.
|
|
80
|
+
*/
|
|
81
|
+
withColor(color) {
|
|
82
|
+
return this.extend({
|
|
83
|
+
color: color
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Creates a new style object with the given math font or old text font.
|
|
89
|
+
* @type {[type]}
|
|
90
|
+
*/
|
|
91
|
+
withFont(font) {
|
|
92
|
+
return this.extend({
|
|
93
|
+
font
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Create a new style objects with the given fontFamily.
|
|
99
|
+
*/
|
|
100
|
+
withTextFontFamily(fontFamily) {
|
|
101
|
+
return this.extend({
|
|
102
|
+
fontFamily,
|
|
103
|
+
font: ""
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Creates a new style object with the given font size
|
|
109
|
+
*/
|
|
110
|
+
withFontSize(num) {
|
|
111
|
+
return this.extend({
|
|
112
|
+
fontSize: num
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Creates a new style object with the given font weight
|
|
118
|
+
*/
|
|
119
|
+
withTextFontWeight(fontWeight) {
|
|
120
|
+
return this.extend({
|
|
121
|
+
fontWeight,
|
|
122
|
+
font: ""
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Creates a new style object with the given font weight
|
|
128
|
+
*/
|
|
129
|
+
withTextFontShape(fontShape) {
|
|
130
|
+
return this.extend({
|
|
131
|
+
fontShape,
|
|
132
|
+
font: ""
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Gets the CSS color of the current style object
|
|
138
|
+
*/
|
|
139
|
+
getColor() {
|
|
140
|
+
return this.color;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default Style;
|
package/src/Token.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import SourceLocation from "./SourceLocation";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interface required to break circular dependency between Token, Lexer, and
|
|
5
|
+
* ParseError.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The resulting token returned from `lex`.
|
|
10
|
+
*
|
|
11
|
+
* It consists of the token text plus some position information.
|
|
12
|
+
* The position information is essentially a range in an input string,
|
|
13
|
+
* but instead of referencing the bare input string, we refer to the lexer.
|
|
14
|
+
* That way it is possible to attach extra metadata to the input string,
|
|
15
|
+
* like for example a file name or similar.
|
|
16
|
+
*
|
|
17
|
+
* The position information is optional, so it is OK to construct synthetic
|
|
18
|
+
* tokens if appropriate. Not providing available position information may
|
|
19
|
+
* lead to degraded error reporting, though.
|
|
20
|
+
*/
|
|
21
|
+
export class Token {
|
|
22
|
+
constructor(
|
|
23
|
+
text, // the text of this token
|
|
24
|
+
loc
|
|
25
|
+
) {
|
|
26
|
+
this.text = text;
|
|
27
|
+
this.loc = loc;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Given a pair of tokens (this and endToken), compute a `Token` encompassing
|
|
32
|
+
* the whole input range enclosed by these two.
|
|
33
|
+
*/
|
|
34
|
+
range(
|
|
35
|
+
endToken, // last token of the range, inclusive
|
|
36
|
+
text // the text of the newly constructed token
|
|
37
|
+
) {
|
|
38
|
+
return new Token(text, SourceLocation.range(this, endToken));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file converts a parse tree into a cooresponding MathML tree. The main
|
|
3
|
+
* entry point is the `buildMathML` function, which takes a parse tree from the
|
|
4
|
+
* parser.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import mathMLTree from "./mathMLTree"
|
|
8
|
+
import ParseError from "./ParseError"
|
|
9
|
+
import symbols, { ligatures } from "./symbols"
|
|
10
|
+
import { _mathmlGroupBuilders as groupBuilders } from "./defineFunction"
|
|
11
|
+
import { MathNode } from "./mathMLTree"
|
|
12
|
+
import setLineBreaks from "./linebreaking"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Takes a symbol and converts it into a MathML text node after performing
|
|
16
|
+
* optional replacement from symbols.js.
|
|
17
|
+
*/
|
|
18
|
+
export const makeText = function(text, mode, style) {
|
|
19
|
+
if (
|
|
20
|
+
symbols[mode][text] &&
|
|
21
|
+
symbols[mode][text].replace &&
|
|
22
|
+
text.charCodeAt(0) !== 0xd835 &&
|
|
23
|
+
!(
|
|
24
|
+
Object.prototype.hasOwnProperty.call(ligatures, text) &&
|
|
25
|
+
style &&
|
|
26
|
+
((style.fontFamily && style.fontFamily.slice(4, 6) === "tt") ||
|
|
27
|
+
(style.font && style.font.slice(4, 6) === "tt"))
|
|
28
|
+
)
|
|
29
|
+
) {
|
|
30
|
+
text = symbols[mode][text].replace;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return new mathMLTree.TextNode(text);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const consolidateText = mrow => {
|
|
37
|
+
// If possible, consolidate adjacent <mtext> elements into a single element.
|
|
38
|
+
if (mrow.type !== "mrow") { return mrow }
|
|
39
|
+
if (mrow.children.length === 0) { return mrow } // empty group, e.g., \text{}
|
|
40
|
+
if (!mrow.children[0].attributes || mrow.children[0].type !== "mtext") { return mrow }
|
|
41
|
+
const variant = mrow.children[0].attributes.mathvariant || ""
|
|
42
|
+
const mtext = new mathMLTree.MathNode(
|
|
43
|
+
"mtext",
|
|
44
|
+
[new mathMLTree.TextNode(mrow.children[0].children[0].text)]
|
|
45
|
+
)
|
|
46
|
+
for (let i = 1; i < mrow.children.length; i++) {
|
|
47
|
+
// Check each child and, if possible, copy the character into child[0].
|
|
48
|
+
const localVariant = mrow.children[i].attributes.mathvariant || ""
|
|
49
|
+
if (mrow.children[i].type === "mrow") {
|
|
50
|
+
const childRow = mrow.children[i]
|
|
51
|
+
for (let j = 0; j < childRow.children.length; j++) {
|
|
52
|
+
// We'll also check the children of a mrow. One level only. No recursion.
|
|
53
|
+
const childVariant = childRow.children[j].attributes.mathvariant || ""
|
|
54
|
+
if (childVariant !== variant || childRow.children[j].type !== "mtext") {
|
|
55
|
+
return mrow // At least one element cannot be consolidated. Get out.
|
|
56
|
+
} else {
|
|
57
|
+
mtext.children[0].text += childRow.children[j].children[0].text
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
} else if (localVariant !== variant || mrow.children[i].type !== "mtext") {
|
|
61
|
+
return mrow
|
|
62
|
+
} else {
|
|
63
|
+
mtext.children[0].text += mrow.children[i].children[0].text
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Firefox does not render a space at either end of an <mtext> string.
|
|
67
|
+
// To get proper rendering, we replace leading or trailing spaces with no-break spaces.
|
|
68
|
+
if (mtext.children[0].text.charAt(0) === " ") {
|
|
69
|
+
mtext.children[0].text = "\u00a0" + mtext.children[0].text.slice(1)
|
|
70
|
+
}
|
|
71
|
+
const L = mtext.children[0].text.length
|
|
72
|
+
if (L > 0 && mtext.children[0].text.charAt(L - 1) === " ") {
|
|
73
|
+
mtext.children[0].text = mtext.children[0].text.slice(0, -1) + "\u00a0"
|
|
74
|
+
}
|
|
75
|
+
return mtext
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Wrap the given array of nodes in an <mrow> node if needed, i.e.,
|
|
80
|
+
* unless the array has length 1. Always returns a single node.
|
|
81
|
+
*/
|
|
82
|
+
export const makeRow = function(body) {
|
|
83
|
+
if (body.length === 1) {
|
|
84
|
+
return body[0];
|
|
85
|
+
} else {
|
|
86
|
+
return new mathMLTree.MathNode("mrow", body);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const isRel = item => {
|
|
91
|
+
return (item.type === "atom" && item.family === "rel") ||
|
|
92
|
+
(item.type === "mclass" && item.mclass === "mrel")
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Takes a list of nodes, builds them, and returns a list of the generated
|
|
97
|
+
* MathML nodes. Also do a couple chores along the way:
|
|
98
|
+
* (1) Suppress spacing when an author wraps an operator w/braces, as in {=}.
|
|
99
|
+
* (2) Suppress spacing between two adjacent relations.
|
|
100
|
+
*/
|
|
101
|
+
export const buildExpression = function(expression, style, isOrdgroup) {
|
|
102
|
+
if (expression.length === 1) {
|
|
103
|
+
const group = buildGroup(expression[0], style);
|
|
104
|
+
if (isOrdgroup && group instanceof MathNode && group.type === "mo") {
|
|
105
|
+
// When TeX writers want to suppress spacing on an operator,
|
|
106
|
+
// they often put the operator by itself inside braces.
|
|
107
|
+
group.setAttribute("lspace", "0em");
|
|
108
|
+
group.setAttribute("rspace", "0em");
|
|
109
|
+
}
|
|
110
|
+
return [group];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const groups = [];
|
|
114
|
+
for (let i = 0; i < expression.length; i++) {
|
|
115
|
+
const group = buildGroup(expression[i], style);
|
|
116
|
+
// Suppress spacing between adjacent relations
|
|
117
|
+
if (i < expression.length - 1 && isRel(expression[i]) && isRel(expression[i + 1])) {
|
|
118
|
+
group.setAttribute("rspace", "0em")
|
|
119
|
+
}
|
|
120
|
+
if (i > 0 && isRel(expression[i]) && isRel(expression[i - 1])) {
|
|
121
|
+
group.setAttribute("lspace", "0em")
|
|
122
|
+
}
|
|
123
|
+
groups.push(group);
|
|
124
|
+
}
|
|
125
|
+
return groups;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Equivalent to buildExpression, but wraps the elements in an <mrow>
|
|
130
|
+
* if there's more than one. Returns a single node instead of an array.
|
|
131
|
+
*/
|
|
132
|
+
export const buildExpressionRow = function(expression, style, isOrdgroup) {
|
|
133
|
+
return makeRow(buildExpression(expression, style, isOrdgroup));
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Takes a group from the parser and calls the appropriate groupBuilders function
|
|
138
|
+
* on it to produce a MathML node.
|
|
139
|
+
*/
|
|
140
|
+
export const buildGroup = function(group, style) {
|
|
141
|
+
if (!group) {
|
|
142
|
+
return new mathMLTree.MathNode("mrow");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (groupBuilders[group.type]) {
|
|
146
|
+
// Call the groupBuilders function
|
|
147
|
+
const result = groupBuilders[group.type](group, style);
|
|
148
|
+
return result;
|
|
149
|
+
} else {
|
|
150
|
+
throw new ParseError("Got group of unknown type: '" + group.type + "'");
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const glue = _ => {
|
|
155
|
+
return new mathMLTree.MathNode("mtd", [], [], { padding: "0", width: "50%" })
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const taggedExpression = (expression, tag, style, leqno) => {
|
|
159
|
+
tag = buildExpressionRow(tag[0].body, style)
|
|
160
|
+
tag = consolidateText(tag)
|
|
161
|
+
tag.classes.push("tml-tag")
|
|
162
|
+
|
|
163
|
+
expression = new mathMLTree.MathNode("mtd", [expression])
|
|
164
|
+
const rowArray = [glue(), expression, glue()]
|
|
165
|
+
if (leqno) {
|
|
166
|
+
rowArray[0].children.push(tag)
|
|
167
|
+
rowArray[0].style.textAlign = "-webkit-left"
|
|
168
|
+
} else {
|
|
169
|
+
rowArray[2].children.push(tag)
|
|
170
|
+
rowArray[2].style.textAlign = "-webkit-right"
|
|
171
|
+
}
|
|
172
|
+
const mtr = new mathMLTree.MathNode("mtr", rowArray, ["tml-tageqn"])
|
|
173
|
+
const table = new mathMLTree.MathNode("mtable", [mtr])
|
|
174
|
+
table.style.width = "100%"
|
|
175
|
+
table.setAttribute("displaystyle", "true")
|
|
176
|
+
return table
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Takes a full parse tree and settings and builds a MathML representation of
|
|
181
|
+
* it.
|
|
182
|
+
*/
|
|
183
|
+
export default function buildMathML(tree, texExpression, style, settings) {
|
|
184
|
+
// Strip off outer tag wrapper for processing below.
|
|
185
|
+
let tag = null
|
|
186
|
+
if (tree.length === 1 && tree[0].type === "tag") {
|
|
187
|
+
tag = tree[0].tag
|
|
188
|
+
tree = tree[0].body
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const expression = buildExpression(tree, style);
|
|
192
|
+
const wrap = (settings.displayMode || settings.annotate) ? "none" : settings.wrap
|
|
193
|
+
|
|
194
|
+
const n1 = expression.length === 0 ? null : expression[0]
|
|
195
|
+
let wrapper = expression.length === 1 && tag === null && (n1 instanceof MathNode)
|
|
196
|
+
&& !(n1.type === "mstyle" && n1.attributes.mathcolor)
|
|
197
|
+
? expression[0]
|
|
198
|
+
: expression.length > 1 && wrap === "none"
|
|
199
|
+
? new mathMLTree.MathNode("mrow", expression)
|
|
200
|
+
: setLineBreaks(expression, wrap, settings.displayMode)
|
|
201
|
+
|
|
202
|
+
if (tag) {
|
|
203
|
+
wrapper = taggedExpression(wrapper, tag, style, settings.leqno)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (settings.annotate) {
|
|
207
|
+
// Build a TeX annotation of the source
|
|
208
|
+
const annotation = new mathMLTree.MathNode(
|
|
209
|
+
"annotation", [new mathMLTree.TextNode(texExpression)]);
|
|
210
|
+
annotation.setAttribute("encoding", "application/x-tex");
|
|
211
|
+
wrapper = new mathMLTree.MathNode("semantics", [wrapper, annotation]);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (wrap !== "none") {
|
|
215
|
+
const maths = []
|
|
216
|
+
for (let i = 0; i < wrapper.children.length; i++) {
|
|
217
|
+
const math = new mathMLTree.MathNode("math", [wrapper.children[i]])
|
|
218
|
+
if (settings.xml) {
|
|
219
|
+
math.setAttribute("xmlns", "http://www.w3.org/1998/Math/MathML")
|
|
220
|
+
}
|
|
221
|
+
maths.push(math)
|
|
222
|
+
}
|
|
223
|
+
return mathMLTree.newDocumentFragment(maths)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const math = new mathMLTree.MathNode("math", [wrapper])
|
|
227
|
+
|
|
228
|
+
if (settings.xml) {
|
|
229
|
+
math.setAttribute("xmlns", "http://www.w3.org/1998/Math/MathML")
|
|
230
|
+
}
|
|
231
|
+
if (settings.displayMode) {
|
|
232
|
+
math.setAttribute("display", "block");
|
|
233
|
+
}
|
|
234
|
+
return math;
|
|
235
|
+
}
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// In TeX, there are actually three sets of dimensions, one for each of
|
|
2
|
+
// textstyle, scriptstyle, and scriptscriptstyle. These are
|
|
3
|
+
// provided in the the arrays below, in that order.
|
|
4
|
+
//
|
|
5
|
+
export const metrics = {
|
|
6
|
+
quad: [1.0, 1.171, 1.472], // Extracted from TeX
|
|
7
|
+
|
|
8
|
+
// The TeX default rule thickness, 0.04 em, often disappears from a browser screen.
|
|
9
|
+
// So we use a thicker rule.
|
|
10
|
+
defaultRuleThickness: [0.06, 0.074, 0.074],
|
|
11
|
+
|
|
12
|
+
// The space between adjacent `|` columns in an array definition.
|
|
13
|
+
doubleRuleSep: [0.3, 0.3, 0.3],
|
|
14
|
+
|
|
15
|
+
// The width of separator lines in {array} environments.
|
|
16
|
+
arrayRuleWidth: [0.06, 0.06, 0.04]
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Math style is not quite the same thing as script level.
|
|
20
|
+
export const StyleLevel = {
|
|
21
|
+
DISPLAY: 0,
|
|
22
|
+
TEXT: 1,
|
|
23
|
+
SCRIPT: 2,
|
|
24
|
+
SCRIPTSCRIPT: 3
|
|
25
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { _mathmlGroupBuilders } from "./defineFunction";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* All registered environments.
|
|
5
|
+
* `environments.js` exports this same dictionary again and makes it public.
|
|
6
|
+
* `Parser.js` requires this dictionary via `environments.js`.
|
|
7
|
+
*/
|
|
8
|
+
export const _environments = {};
|
|
9
|
+
|
|
10
|
+
export default function defineEnvironment({ type, names, props, handler, mathmlBuilder }) {
|
|
11
|
+
// Set default values of environments.
|
|
12
|
+
const data = {
|
|
13
|
+
type,
|
|
14
|
+
numArgs: props.numArgs || 0,
|
|
15
|
+
allowedInText: false,
|
|
16
|
+
numOptionalArgs: 0,
|
|
17
|
+
handler
|
|
18
|
+
};
|
|
19
|
+
for (let i = 0; i < names.length; ++i) {
|
|
20
|
+
_environments[names[i]] = data;
|
|
21
|
+
}
|
|
22
|
+
if (mathmlBuilder) {
|
|
23
|
+
_mathmlGroupBuilders[type] = mathmlBuilder;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All registered functions.
|
|
3
|
+
* `functions.js` just exports this same dictionary again and makes it public.
|
|
4
|
+
* `Parser.js` requires this dictionary.
|
|
5
|
+
*/
|
|
6
|
+
export const _functions = {}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* All MathML builders. Should be only used in the `define*` and the `build*ML`
|
|
10
|
+
* functions.
|
|
11
|
+
*/
|
|
12
|
+
export const _mathmlGroupBuilders = {}
|
|
13
|
+
|
|
14
|
+
export default function defineFunction({
|
|
15
|
+
type,
|
|
16
|
+
names,
|
|
17
|
+
props,
|
|
18
|
+
handler,
|
|
19
|
+
mathmlBuilder
|
|
20
|
+
}) {
|
|
21
|
+
// Set default values of functions
|
|
22
|
+
const data = {
|
|
23
|
+
type,
|
|
24
|
+
numArgs: props.numArgs,
|
|
25
|
+
argTypes: props.argTypes,
|
|
26
|
+
allowedInArgument: !!props.allowedInArgument,
|
|
27
|
+
allowedInText: !!props.allowedInText,
|
|
28
|
+
allowedInMath: props.allowedInMath === undefined ? true : props.allowedInMath,
|
|
29
|
+
numOptionalArgs: props.numOptionalArgs || 0,
|
|
30
|
+
infix: !!props.infix,
|
|
31
|
+
primitive: !!props.primitive,
|
|
32
|
+
handler: handler
|
|
33
|
+
}
|
|
34
|
+
for (let i = 0; i < names.length; ++i) {
|
|
35
|
+
_functions[names[i]] = data
|
|
36
|
+
}
|
|
37
|
+
if (type) {
|
|
38
|
+
if (mathmlBuilder) {
|
|
39
|
+
_mathmlGroupBuilders[type] = mathmlBuilder
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Use this to register only the MathML builder for a function(e.g.
|
|
46
|
+
* if the function's ParseNode is generated in Parser.js rather than via a
|
|
47
|
+
* stand-alone handler provided to `defineFunction`).
|
|
48
|
+
*/
|
|
49
|
+
export function defineFunctionBuilders({ type, mathmlBuilder }) {
|
|
50
|
+
defineFunction({
|
|
51
|
+
type,
|
|
52
|
+
names: [],
|
|
53
|
+
props: { numArgs: 0 },
|
|
54
|
+
handler() {
|
|
55
|
+
throw new Error("Should never be called.")
|
|
56
|
+
},
|
|
57
|
+
mathmlBuilder
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const normalizeArgument = function(arg) {
|
|
62
|
+
return arg.type === "ordgroup" && arg.body.length === 1 ? arg.body[0] : arg
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Since the corresponding buildMathML function expects a
|
|
66
|
+
// list of elements, we normalize for different kinds of arguments
|
|
67
|
+
export const ordargument = function(arg) {
|
|
68
|
+
return arg.type === "ordgroup" ? arg.body : [arg]
|
|
69
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All registered global/built-in macros.
|
|
3
|
+
* `macros.js` exports this same dictionary again and makes it public.
|
|
4
|
+
* `Parser.js` requires this dictionary via `macros.js`.
|
|
5
|
+
*/
|
|
6
|
+
export const _macros = {};
|
|
7
|
+
|
|
8
|
+
// This function might one day accept an additional argument and do more things.
|
|
9
|
+
export default function defineMacro(name, body) {
|
|
10
|
+
_macros[name] = body;
|
|
11
|
+
}
|