1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | import buildCommon from "./buildCommon";
|
9 | import {getCharacterMetrics} from "./fontMetrics";
|
10 | import mathMLTree from "./mathMLTree";
|
11 | import ParseError from "./ParseError";
|
12 | import symbols, {ligatures} from "./symbols";
|
13 | import utils from "./utils";
|
14 | import {_mathmlGroupBuilders as groupBuilders} from "./defineFunction";
|
15 | import {MathNode, TextNode} from "./mathMLTree";
|
16 |
|
17 | import type Options from "./Options";
|
18 | import type {AnyParseNode, SymbolParseNode} from "./parseNode";
|
19 | import type {DomSpan} from "./domTree";
|
20 | import type {MathDomNode} from "./mathMLTree";
|
21 | import type {FontVariant, Mode} from "./types";
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | export const makeText = function(
|
28 | text: string,
|
29 | mode: Mode,
|
30 | options?: Options,
|
31 | ): TextNode {
|
32 | if (symbols[mode][text] && symbols[mode][text].replace &&
|
33 | text.charCodeAt(0) !== 0xD835 &&
|
34 | !(ligatures.hasOwnProperty(text) && options &&
|
35 | ((options.fontFamily && options.fontFamily.substr(4, 2) === "tt") ||
|
36 | (options.font && options.font.substr(4, 2) === "tt")))) {
|
37 | text = symbols[mode][text].replace;
|
38 | }
|
39 |
|
40 | return new mathMLTree.TextNode(text);
|
41 | };
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 | export const makeRow = function(body: MathDomNode[]): MathDomNode {
|
48 | if (body.length === 1) {
|
49 | return body[0];
|
50 | } else {
|
51 | return new mathMLTree.MathNode("mrow", body);
|
52 | }
|
53 | };
|
54 |
|
55 |
|
56 |
|
57 |
|
58 | export const getVariant = function(
|
59 | group: SymbolParseNode,
|
60 | options: Options,
|
61 | ): ?FontVariant {
|
62 |
|
63 |
|
64 |
|
65 | if (options.fontFamily === "texttt") {
|
66 | return "monospace";
|
67 | } else if (options.fontFamily === "textsf") {
|
68 | if (options.fontShape === "textit" &&
|
69 | options.fontWeight === "textbf") {
|
70 | return "sans-serif-bold-italic";
|
71 | } else if (options.fontShape === "textit") {
|
72 | return "sans-serif-italic";
|
73 | } else if (options.fontWeight === "textbf") {
|
74 | return "bold-sans-serif";
|
75 | } else {
|
76 | return "sans-serif";
|
77 | }
|
78 | } else if (options.fontShape === "textit" &&
|
79 | options.fontWeight === "textbf") {
|
80 | return "bold-italic";
|
81 | } else if (options.fontShape === "textit") {
|
82 | return "italic";
|
83 | } else if (options.fontWeight === "textbf") {
|
84 | return "bold";
|
85 | }
|
86 |
|
87 | const font = options.font;
|
88 | if (!font || font === "mathnormal") {
|
89 | return null;
|
90 | }
|
91 |
|
92 | const mode = group.mode;
|
93 | if (font === "mathit") {
|
94 | return "italic";
|
95 | } else if (font === "boldsymbol") {
|
96 | return "bold-italic";
|
97 | }
|
98 |
|
99 | let text = group.text;
|
100 | if (utils.contains(["\\imath", "\\jmath"], text)) {
|
101 | return null;
|
102 | }
|
103 |
|
104 | if (symbols[mode][text] && symbols[mode][text].replace) {
|
105 | text = symbols[mode][text].replace;
|
106 | }
|
107 |
|
108 | const fontName = buildCommon.fontMap[font].fontName;
|
109 | if (getCharacterMetrics(text, fontName, mode)) {
|
110 | return buildCommon.fontMap[font].variant;
|
111 | }
|
112 |
|
113 | return null;
|
114 | };
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 | export const buildExpression = function(
|
122 | expression: AnyParseNode[],
|
123 | options: Options,
|
124 | ): MathDomNode[] {
|
125 | const groups = [];
|
126 | let lastGroup;
|
127 | for (let i = 0; i < expression.length; i++) {
|
128 | const group = buildGroup(expression[i], options);
|
129 | if (group instanceof MathNode && lastGroup instanceof MathNode) {
|
130 |
|
131 | if (group.type === 'mtext' && lastGroup.type === 'mtext'
|
132 | && group.getAttribute('mathvariant') ===
|
133 | lastGroup.getAttribute('mathvariant')) {
|
134 | lastGroup.children.push(...group.children);
|
135 | continue;
|
136 |
|
137 | } else if (group.type === 'mn' && lastGroup.type === 'mn') {
|
138 | lastGroup.children.push(...group.children);
|
139 | continue;
|
140 |
|
141 | } else if (group.type === 'mi' && group.children.length === 1 &&
|
142 | lastGroup.type === 'mn') {
|
143 | const child = group.children[0];
|
144 | if (child instanceof TextNode && child.text === '.') {
|
145 | lastGroup.children.push(...group.children);
|
146 | continue;
|
147 | }
|
148 | } else if (lastGroup.type === 'mi' && lastGroup.children.length === 1) {
|
149 | const lastChild = lastGroup.children[0];
|
150 | if (lastChild instanceof TextNode && lastChild.text === '\u0338' &&
|
151 | (group.type === 'mo' || group.type === 'mi' ||
|
152 | group.type === 'mn')) {
|
153 | const child = group.children[0];
|
154 | if (child instanceof TextNode && child.text.length > 0) {
|
155 |
|
156 | child.text = child.text.slice(0, 1) + "\u0338" +
|
157 | child.text.slice(1);
|
158 | groups.pop();
|
159 | }
|
160 | }
|
161 | }
|
162 | }
|
163 | groups.push(group);
|
164 | lastGroup = group;
|
165 | }
|
166 | return groups;
|
167 | };
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 | export const buildExpressionRow = function(
|
174 | expression: AnyParseNode[],
|
175 | options: Options,
|
176 | ): MathDomNode {
|
177 | return makeRow(buildExpression(expression, options));
|
178 | };
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 | export const buildGroup = function(
|
185 | group: ?AnyParseNode,
|
186 | options: Options,
|
187 | ): MathDomNode {
|
188 | if (!group) {
|
189 | return new mathMLTree.MathNode("mrow");
|
190 | }
|
191 |
|
192 | if (groupBuilders[group.type]) {
|
193 |
|
194 |
|
195 | const result: MathDomNode = groupBuilders[group.type](group, options);
|
196 | return result;
|
197 | } else {
|
198 | throw new ParseError(
|
199 | "Got group of unknown type: '" + group.type + "'");
|
200 | }
|
201 | };
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | export default function buildMathML(
|
212 | tree: AnyParseNode[],
|
213 | texExpression: string,
|
214 | options: Options,
|
215 | ): DomSpan {
|
216 | const expression = buildExpression(tree, options);
|
217 |
|
218 |
|
219 |
|
220 | let wrapper;
|
221 | if (expression.length === 1 && expression[0] instanceof MathNode &&
|
222 | utils.contains(["mrow", "mtable"], expression[0].type)) {
|
223 | wrapper = expression[0];
|
224 | } else {
|
225 | wrapper = new mathMLTree.MathNode("mrow", expression);
|
226 | }
|
227 |
|
228 |
|
229 | const annotation = new mathMLTree.MathNode(
|
230 | "annotation", [new mathMLTree.TextNode(texExpression)]);
|
231 |
|
232 | annotation.setAttribute("encoding", "application/x-tex");
|
233 |
|
234 | const semantics = new mathMLTree.MathNode(
|
235 | "semantics", [wrapper, annotation]);
|
236 |
|
237 | const math = new mathMLTree.MathNode("math", [semantics]);
|
238 |
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 | return buildCommon.makeSpan(["katex-mathml"], [math]);
|
245 | }
|