1 |
|
2 | import buildCommon from "../buildCommon";
|
3 | import defineFunction from "../defineFunction";
|
4 | import delimiter from "../delimiter";
|
5 | import mathMLTree from "../mathMLTree";
|
6 | import ParseError from "../ParseError";
|
7 | import utils from "../utils";
|
8 | import {assertNodeType, checkSymbolNodeType} from "../parseNode";
|
9 |
|
10 | import * as html from "../buildHTML";
|
11 | import * as mml from "../buildMathML";
|
12 |
|
13 | import type Options from "../Options";
|
14 | import type {AnyParseNode, ParseNode, SymbolParseNode} from "../parseNode";
|
15 | import type {FunctionContext} from "../defineFunction";
|
16 |
|
17 |
|
18 | const delimiterSizes = {
|
19 | "\\bigl" : {mclass: "mopen", size: 1},
|
20 | "\\Bigl" : {mclass: "mopen", size: 2},
|
21 | "\\biggl": {mclass: "mopen", size: 3},
|
22 | "\\Biggl": {mclass: "mopen", size: 4},
|
23 | "\\bigr" : {mclass: "mclose", size: 1},
|
24 | "\\Bigr" : {mclass: "mclose", size: 2},
|
25 | "\\biggr": {mclass: "mclose", size: 3},
|
26 | "\\Biggr": {mclass: "mclose", size: 4},
|
27 | "\\bigm" : {mclass: "mrel", size: 1},
|
28 | "\\Bigm" : {mclass: "mrel", size: 2},
|
29 | "\\biggm": {mclass: "mrel", size: 3},
|
30 | "\\Biggm": {mclass: "mrel", size: 4},
|
31 | "\\big" : {mclass: "mord", size: 1},
|
32 | "\\Big" : {mclass: "mord", size: 2},
|
33 | "\\bigg" : {mclass: "mord", size: 3},
|
34 | "\\Bigg" : {mclass: "mord", size: 4},
|
35 | };
|
36 |
|
37 | const delimiters = [
|
38 | "(", "\\lparen", ")", "\\rparen",
|
39 | "[", "\\lbrack", "]", "\\rbrack",
|
40 | "\\{", "\\lbrace", "\\}", "\\rbrace",
|
41 | "\\lfloor", "\\rfloor", "\u230a", "\u230b",
|
42 | "\\lceil", "\\rceil", "\u2308", "\u2309",
|
43 | "<", ">", "\\langle", "\u27e8", "\\rangle", "\u27e9", "\\lt", "\\gt",
|
44 | "\\lvert", "\\rvert", "\\lVert", "\\rVert",
|
45 | "\\lgroup", "\\rgroup", "\u27ee", "\u27ef",
|
46 | "\\lmoustache", "\\rmoustache", "\u23b0", "\u23b1",
|
47 | "/", "\\backslash",
|
48 | "|", "\\vert", "\\|", "\\Vert",
|
49 | "\\uparrow", "\\Uparrow",
|
50 | "\\downarrow", "\\Downarrow",
|
51 | "\\updownarrow", "\\Updownarrow",
|
52 | ".",
|
53 | ];
|
54 |
|
55 | type IsMiddle = {delim: string, options: Options};
|
56 |
|
57 |
|
58 | function checkDelimiter(
|
59 | delim: AnyParseNode,
|
60 | context: FunctionContext,
|
61 | ): SymbolParseNode {
|
62 | const symDelim = checkSymbolNodeType(delim);
|
63 | if (symDelim && utils.contains(delimiters, symDelim.text)) {
|
64 | return symDelim;
|
65 | } else {
|
66 | throw new ParseError(
|
67 | "Invalid delimiter: '" +
|
68 | (symDelim ? symDelim.text : JSON.stringify(delim)) +
|
69 | "' after '" + context.funcName + "'", delim);
|
70 | }
|
71 | }
|
72 |
|
73 | defineFunction({
|
74 | type: "delimsizing",
|
75 | names: [
|
76 | "\\bigl", "\\Bigl", "\\biggl", "\\Biggl",
|
77 | "\\bigr", "\\Bigr", "\\biggr", "\\Biggr",
|
78 | "\\bigm", "\\Bigm", "\\biggm", "\\Biggm",
|
79 | "\\big", "\\Big", "\\bigg", "\\Bigg",
|
80 | ],
|
81 | props: {
|
82 | numArgs: 1,
|
83 | },
|
84 | handler: (context, args) => {
|
85 | const delim = checkDelimiter(args[0], context);
|
86 |
|
87 | return {
|
88 | type: "delimsizing",
|
89 | mode: context.parser.mode,
|
90 | size: delimiterSizes[context.funcName].size,
|
91 | mclass: delimiterSizes[context.funcName].mclass,
|
92 | delim: delim.text,
|
93 | };
|
94 | },
|
95 | htmlBuilder: (group, options) => {
|
96 | if (group.delim === ".") {
|
97 |
|
98 |
|
99 | return buildCommon.makeSpan([group.mclass]);
|
100 | }
|
101 |
|
102 |
|
103 | return delimiter.sizedDelim(
|
104 | group.delim, group.size, options, group.mode, [group.mclass]);
|
105 | },
|
106 | mathmlBuilder: (group) => {
|
107 | const children = [];
|
108 |
|
109 | if (group.delim !== ".") {
|
110 | children.push(mml.makeText(group.delim, group.mode));
|
111 | }
|
112 |
|
113 | const node = new mathMLTree.MathNode("mo", children);
|
114 |
|
115 | if (group.mclass === "mopen" ||
|
116 | group.mclass === "mclose") {
|
117 |
|
118 |
|
119 | node.setAttribute("fence", "true");
|
120 | } else {
|
121 |
|
122 |
|
123 | node.setAttribute("fence", "false");
|
124 | }
|
125 |
|
126 | return node;
|
127 | },
|
128 | });
|
129 |
|
130 |
|
131 | function assertParsed(group: ParseNode<"leftright">) {
|
132 | if (!group.body) {
|
133 | throw new Error("Bug: The leftright ParseNode wasn't fully parsed.");
|
134 | }
|
135 | }
|
136 |
|
137 |
|
138 | defineFunction({
|
139 | type: "leftright-right",
|
140 | names: ["\\right"],
|
141 | props: {
|
142 | numArgs: 1,
|
143 | },
|
144 | handler: (context, args) => {
|
145 |
|
146 |
|
147 |
|
148 | return {
|
149 | type: "leftright-right",
|
150 | mode: context.parser.mode,
|
151 | delim: checkDelimiter(args[0], context).text,
|
152 | };
|
153 | },
|
154 | });
|
155 |
|
156 |
|
157 | defineFunction({
|
158 | type: "leftright",
|
159 | names: ["\\left"],
|
160 | props: {
|
161 | numArgs: 1,
|
162 | },
|
163 | handler: (context, args) => {
|
164 | const delim = checkDelimiter(args[0], context);
|
165 |
|
166 | const parser = context.parser;
|
167 |
|
168 | ++parser.leftrightDepth;
|
169 |
|
170 | const body = parser.parseExpression(false);
|
171 | --parser.leftrightDepth;
|
172 |
|
173 | parser.expect("\\right", false);
|
174 | const right = assertNodeType(parser.parseFunction(), "leftright-right");
|
175 | return {
|
176 | type: "leftright",
|
177 | mode: parser.mode,
|
178 | body,
|
179 | left: delim.text,
|
180 | right: right.delim,
|
181 | };
|
182 | },
|
183 | htmlBuilder: (group, options) => {
|
184 | assertParsed(group);
|
185 |
|
186 | const inner = html.buildExpression(group.body, options, true,
|
187 | ["mopen", "mclose"]);
|
188 |
|
189 | let innerHeight = 0;
|
190 | let innerDepth = 0;
|
191 | let hadMiddle = false;
|
192 |
|
193 |
|
194 | for (let i = 0; i < inner.length; i++) {
|
195 |
|
196 |
|
197 |
|
198 | if (inner[i].isMiddle) {
|
199 | hadMiddle = true;
|
200 | } else {
|
201 | innerHeight = Math.max(inner[i].height, innerHeight);
|
202 | innerDepth = Math.max(inner[i].depth, innerDepth);
|
203 | }
|
204 | }
|
205 |
|
206 |
|
207 |
|
208 |
|
209 | innerHeight *= options.sizeMultiplier;
|
210 | innerDepth *= options.sizeMultiplier;
|
211 |
|
212 | let leftDelim;
|
213 | if (group.left === ".") {
|
214 |
|
215 | leftDelim = html.makeNullDelimiter(options, ["mopen"]);
|
216 | } else {
|
217 |
|
218 |
|
219 | leftDelim = delimiter.leftRightDelim(
|
220 | group.left, innerHeight, innerDepth, options,
|
221 | group.mode, ["mopen"]);
|
222 | }
|
223 |
|
224 | inner.unshift(leftDelim);
|
225 |
|
226 |
|
227 | if (hadMiddle) {
|
228 | for (let i = 1; i < inner.length; i++) {
|
229 | const middleDelim = inner[i];
|
230 |
|
231 |
|
232 |
|
233 | const isMiddle: IsMiddle = middleDelim.isMiddle;
|
234 | if (isMiddle) {
|
235 |
|
236 | inner[i] = delimiter.leftRightDelim(
|
237 | isMiddle.delim, innerHeight, innerDepth,
|
238 | isMiddle.options, group.mode, []);
|
239 | }
|
240 | }
|
241 | }
|
242 |
|
243 | let rightDelim;
|
244 |
|
245 | if (group.right === ".") {
|
246 | rightDelim = html.makeNullDelimiter(options, ["mclose"]);
|
247 | } else {
|
248 | rightDelim = delimiter.leftRightDelim(
|
249 | group.right, innerHeight, innerDepth, options,
|
250 | group.mode, ["mclose"]);
|
251 | }
|
252 |
|
253 | inner.push(rightDelim);
|
254 |
|
255 | return buildCommon.makeSpan(["minner"], inner, options);
|
256 | },
|
257 | mathmlBuilder: (group, options) => {
|
258 | assertParsed(group);
|
259 | const inner = mml.buildExpression(group.body, options);
|
260 |
|
261 | if (group.left !== ".") {
|
262 | const leftNode = new mathMLTree.MathNode(
|
263 | "mo", [mml.makeText(group.left, group.mode)]);
|
264 |
|
265 | leftNode.setAttribute("fence", "true");
|
266 |
|
267 | inner.unshift(leftNode);
|
268 | }
|
269 |
|
270 | if (group.right !== ".") {
|
271 | const rightNode = new mathMLTree.MathNode(
|
272 | "mo", [mml.makeText(group.right, group.mode)]);
|
273 |
|
274 | rightNode.setAttribute("fence", "true");
|
275 |
|
276 | inner.push(rightNode);
|
277 | }
|
278 |
|
279 | return mml.makeRow(inner);
|
280 | },
|
281 | });
|
282 |
|
283 | defineFunction({
|
284 | type: "middle",
|
285 | names: ["\\middle"],
|
286 | props: {
|
287 | numArgs: 1,
|
288 | },
|
289 | handler: (context, args) => {
|
290 | const delim = checkDelimiter(args[0], context);
|
291 | if (!context.parser.leftrightDepth) {
|
292 | throw new ParseError("\\middle without preceding \\left", delim);
|
293 | }
|
294 |
|
295 | return {
|
296 | type: "middle",
|
297 | mode: context.parser.mode,
|
298 | delim: delim.text,
|
299 | };
|
300 | },
|
301 | htmlBuilder: (group, options) => {
|
302 | let middleDelim;
|
303 | if (group.delim === ".") {
|
304 | middleDelim = html.makeNullDelimiter(options, []);
|
305 | } else {
|
306 | middleDelim = delimiter.sizedDelim(
|
307 | group.delim, 1, options,
|
308 | group.mode, []);
|
309 |
|
310 | const isMiddle: IsMiddle = {delim: group.delim, options};
|
311 |
|
312 |
|
313 |
|
314 |
|
315 |
|
316 | middleDelim.isMiddle = isMiddle;
|
317 | }
|
318 | return middleDelim;
|
319 | },
|
320 | mathmlBuilder: (group, options) => {
|
321 |
|
322 |
|
323 |
|
324 |
|
325 | const textNode = (group.delim === "\\vert" || group.delim === "|")
|
326 | ? mml.makeText("|", "text")
|
327 | : mml.makeText(group.delim, group.mode);
|
328 | const middleNode = new mathMLTree.MathNode("mo", [textNode]);
|
329 | middleNode.setAttribute("fence", "true");
|
330 |
|
331 |
|
332 | middleNode.setAttribute("lspace", "0.05em");
|
333 | middleNode.setAttribute("rspace", "0.05em");
|
334 | return middleNode;
|
335 | },
|
336 | });
|