1 | import katex from '../katex.mjs';
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | const stringMap = {
|
19 | "(": "left parenthesis",
|
20 | ")": "right parenthesis",
|
21 | "[": "open bracket",
|
22 | "]": "close bracket",
|
23 | "\\{": "left brace",
|
24 | "\\}": "right brace",
|
25 | "\\lvert": "open vertical bar",
|
26 | "\\rvert": "close vertical bar",
|
27 | "|": "vertical bar",
|
28 | "\\uparrow": "up arrow",
|
29 | "\\Uparrow": "up arrow",
|
30 | "\\downarrow": "down arrow",
|
31 | "\\Downarrow": "down arrow",
|
32 | "\\updownarrow": "up down arrow",
|
33 | "\\leftarrow": "left arrow",
|
34 | "\\Leftarrow": "left arrow",
|
35 | "\\rightarrow": "right arrow",
|
36 | "\\Rightarrow": "right arrow",
|
37 | "\\langle": "open angle",
|
38 | "\\rangle": "close angle",
|
39 | "\\lfloor": "open floor",
|
40 | "\\rfloor": "close floor",
|
41 | "\\int": "integral",
|
42 | "\\intop": "integral",
|
43 | "\\lim": "limit",
|
44 | "\\ln": "natural log",
|
45 | "\\log": "log",
|
46 | "\\sin": "sine",
|
47 | "\\cos": "cosine",
|
48 | "\\tan": "tangent",
|
49 | "\\cot": "cotangent",
|
50 | "\\sum": "sum",
|
51 | "/": "slash",
|
52 | ",": "comma",
|
53 | ".": "point",
|
54 | "-": "negative",
|
55 | "+": "plus",
|
56 | "~": "tilde",
|
57 | ":": "colon",
|
58 | "?": "question mark",
|
59 | "'": "apostrophe",
|
60 | "\\%": "percent",
|
61 | " ": "space",
|
62 | "\\ ": "space",
|
63 | "\\$": "dollar sign",
|
64 | "\\angle": "angle",
|
65 | "\\degree": "degree",
|
66 | "\\circ": "circle",
|
67 | "\\vec": "vector",
|
68 | "\\triangle": "triangle",
|
69 | "\\pi": "pi",
|
70 | "\\prime": "prime",
|
71 | "\\infty": "infinity",
|
72 | "\\alpha": "alpha",
|
73 | "\\beta": "beta",
|
74 | "\\gamma": "gamma",
|
75 | "\\omega": "omega",
|
76 | "\\theta": "theta",
|
77 | "\\sigma": "sigma",
|
78 | "\\lambda": "lambda",
|
79 | "\\tau": "tau",
|
80 | "\\Delta": "delta",
|
81 | "\\delta": "delta",
|
82 | "\\mu": "mu",
|
83 | "\\rho": "rho",
|
84 | "\\nabla": "del",
|
85 | "\\ell": "ell",
|
86 | "\\ldots": "dots",
|
87 |
|
88 | "\\hat": "hat",
|
89 | "\\acute": "acute"
|
90 | };
|
91 | const powerMap = {
|
92 | "prime": "prime",
|
93 | "degree": "degrees",
|
94 | "circle": "degrees",
|
95 | "2": "squared",
|
96 | "3": "cubed"
|
97 | };
|
98 | const openMap = {
|
99 | "|": "open vertical bar",
|
100 | ".": ""
|
101 | };
|
102 | const closeMap = {
|
103 | "|": "close vertical bar",
|
104 | ".": ""
|
105 | };
|
106 | const binMap = {
|
107 | "+": "plus",
|
108 | "-": "minus",
|
109 | "\\pm": "plus minus",
|
110 | "\\cdot": "dot",
|
111 | "*": "times",
|
112 | "/": "divided by",
|
113 | "\\times": "times",
|
114 | "\\div": "divided by",
|
115 | "\\circ": "circle",
|
116 | "\\bullet": "bullet"
|
117 | };
|
118 | const relMap = {
|
119 | "=": "equals",
|
120 | "\\approx": "approximately equals",
|
121 | "≠": "does not equal",
|
122 | "\\geq": "is greater than or equal to",
|
123 | "\\ge": "is greater than or equal to",
|
124 | "\\leq": "is less than or equal to",
|
125 | "\\le": "is less than or equal to",
|
126 | ">": "is greater than",
|
127 | "<": "is less than",
|
128 | "\\leftarrow": "left arrow",
|
129 | "\\Leftarrow": "left arrow",
|
130 | "\\rightarrow": "right arrow",
|
131 | "\\Rightarrow": "right arrow",
|
132 | ":": "colon"
|
133 | };
|
134 | const accentUnderMap = {
|
135 | "\\underleftarrow": "left arrow",
|
136 | "\\underrightarrow": "right arrow",
|
137 | "\\underleftrightarrow": "left-right arrow",
|
138 | "\\undergroup": "group",
|
139 | "\\underlinesegment": "line segment",
|
140 | "\\utilde": "tilde"
|
141 | };
|
142 |
|
143 | const buildString = (str, type, a11yStrings) => {
|
144 | if (!str) {
|
145 | return;
|
146 | }
|
147 |
|
148 | let ret;
|
149 |
|
150 | if (type === "open") {
|
151 | ret = str in openMap ? openMap[str] : stringMap[str] || str;
|
152 | } else if (type === "close") {
|
153 | ret = str in closeMap ? closeMap[str] : stringMap[str] || str;
|
154 | } else if (type === "bin") {
|
155 | ret = binMap[str] || str;
|
156 | } else if (type === "rel") {
|
157 | ret = relMap[str] || str;
|
158 | } else {
|
159 | ret = stringMap[str] || str;
|
160 | }
|
161 |
|
162 |
|
163 |
|
164 |
|
165 | if (/^\d+$/.test(ret) && a11yStrings.length > 0 &&
|
166 |
|
167 |
|
168 | /^\d+$/.test(a11yStrings[a11yStrings.length - 1])) {
|
169 | a11yStrings[a11yStrings.length - 1] += ret;
|
170 | } else if (ret) {
|
171 | a11yStrings.push(ret);
|
172 | }
|
173 | };
|
174 |
|
175 | const buildRegion = (a11yStrings, callback) => {
|
176 | const regionStrings = [];
|
177 | a11yStrings.push(regionStrings);
|
178 | callback(regionStrings);
|
179 | };
|
180 |
|
181 | const handleObject = (tree, a11yStrings, atomType) => {
|
182 |
|
183 | switch (tree.type) {
|
184 | case "accent":
|
185 | {
|
186 | buildRegion(a11yStrings, a11yStrings => {
|
187 | buildA11yStrings(tree.base, a11yStrings, atomType);
|
188 | a11yStrings.push("with");
|
189 | buildString(tree.label, "normal", a11yStrings);
|
190 | a11yStrings.push("on top");
|
191 | });
|
192 | break;
|
193 | }
|
194 |
|
195 | case "accentUnder":
|
196 | {
|
197 | buildRegion(a11yStrings, a11yStrings => {
|
198 | buildA11yStrings(tree.base, a11yStrings, atomType);
|
199 | a11yStrings.push("with");
|
200 | buildString(accentUnderMap[tree.label], "normal", a11yStrings);
|
201 | a11yStrings.push("underneath");
|
202 | });
|
203 | break;
|
204 | }
|
205 |
|
206 | case "accent-token":
|
207 | {
|
208 |
|
209 | break;
|
210 | }
|
211 |
|
212 | case "atom":
|
213 | {
|
214 | const text = tree.text;
|
215 |
|
216 | switch (tree.family) {
|
217 | case "bin":
|
218 | {
|
219 | buildString(text, "bin", a11yStrings);
|
220 | break;
|
221 | }
|
222 |
|
223 | case "close":
|
224 | {
|
225 | buildString(text, "close", a11yStrings);
|
226 | break;
|
227 | }
|
228 |
|
229 |
|
230 | case "inner":
|
231 | {
|
232 | buildString(tree.text, "inner", a11yStrings);
|
233 | break;
|
234 | }
|
235 |
|
236 | case "open":
|
237 | {
|
238 | buildString(text, "open", a11yStrings);
|
239 | break;
|
240 | }
|
241 |
|
242 | case "punct":
|
243 | {
|
244 | buildString(text, "punct", a11yStrings);
|
245 | break;
|
246 | }
|
247 |
|
248 | case "rel":
|
249 | {
|
250 | buildString(text, "rel", a11yStrings);
|
251 | break;
|
252 | }
|
253 |
|
254 | default:
|
255 | {
|
256 | tree.family;
|
257 | throw new Error(`"${tree.family}" is not a valid atom type`);
|
258 | }
|
259 | }
|
260 |
|
261 | break;
|
262 | }
|
263 |
|
264 | case "color":
|
265 | {
|
266 | const color = tree.color.replace(/katex-/, "");
|
267 | buildRegion(a11yStrings, regionStrings => {
|
268 | regionStrings.push("start color " + color);
|
269 | buildA11yStrings(tree.body, regionStrings, atomType);
|
270 | regionStrings.push("end color " + color);
|
271 | });
|
272 | break;
|
273 | }
|
274 |
|
275 | case "color-token":
|
276 | {
|
277 |
|
278 |
|
279 | break;
|
280 | }
|
281 |
|
282 | case "delimsizing":
|
283 | {
|
284 | if (tree.delim && tree.delim !== ".") {
|
285 | buildString(tree.delim, "normal", a11yStrings);
|
286 | }
|
287 |
|
288 | break;
|
289 | }
|
290 |
|
291 | case "genfrac":
|
292 | {
|
293 | buildRegion(a11yStrings, regionStrings => {
|
294 |
|
295 | const leftDelim = tree.leftDelim,
|
296 | rightDelim = tree.rightDelim;
|
297 |
|
298 |
|
299 | if (tree.hasBarLine) {
|
300 | regionStrings.push("start fraction");
|
301 | leftDelim && buildString(leftDelim, "open", regionStrings);
|
302 | buildA11yStrings(tree.numer, regionStrings, atomType);
|
303 | regionStrings.push("divided by");
|
304 | buildA11yStrings(tree.denom, regionStrings, atomType);
|
305 | rightDelim && buildString(rightDelim, "close", regionStrings);
|
306 | regionStrings.push("end fraction");
|
307 | } else {
|
308 | regionStrings.push("start binomial");
|
309 | leftDelim && buildString(leftDelim, "open", regionStrings);
|
310 | buildA11yStrings(tree.numer, regionStrings, atomType);
|
311 | regionStrings.push("over");
|
312 | buildA11yStrings(tree.denom, regionStrings, atomType);
|
313 | rightDelim && buildString(rightDelim, "close", regionStrings);
|
314 | regionStrings.push("end binomial");
|
315 | }
|
316 | });
|
317 | break;
|
318 | }
|
319 |
|
320 | case "kern":
|
321 | {
|
322 |
|
323 |
|
324 | break;
|
325 | }
|
326 |
|
327 | case "leftright":
|
328 | {
|
329 | buildRegion(a11yStrings, regionStrings => {
|
330 | buildString(tree.left, "open", regionStrings);
|
331 | buildA11yStrings(tree.body, regionStrings, atomType);
|
332 | buildString(tree.right, "close", regionStrings);
|
333 | });
|
334 | break;
|
335 | }
|
336 |
|
337 | case "leftright-right":
|
338 | {
|
339 |
|
340 | break;
|
341 | }
|
342 |
|
343 | case "lap":
|
344 | {
|
345 | buildA11yStrings(tree.body, a11yStrings, atomType);
|
346 | break;
|
347 | }
|
348 |
|
349 | case "mathord":
|
350 | {
|
351 | buildString(tree.text, "normal", a11yStrings);
|
352 | break;
|
353 | }
|
354 |
|
355 | case "op":
|
356 | {
|
357 | const body = tree.body,
|
358 | name = tree.name;
|
359 |
|
360 | if (body) {
|
361 | buildA11yStrings(body, a11yStrings, atomType);
|
362 | } else if (name) {
|
363 | buildString(name, "normal", a11yStrings);
|
364 | }
|
365 |
|
366 | break;
|
367 | }
|
368 |
|
369 | case "op-token":
|
370 | {
|
371 |
|
372 | buildString(tree.text, atomType, a11yStrings);
|
373 | break;
|
374 | }
|
375 |
|
376 | case "ordgroup":
|
377 | {
|
378 | buildA11yStrings(tree.body, a11yStrings, atomType);
|
379 | break;
|
380 | }
|
381 |
|
382 | case "overline":
|
383 | {
|
384 | buildRegion(a11yStrings, function (a11yStrings) {
|
385 | a11yStrings.push("start overline");
|
386 | buildA11yStrings(tree.body, a11yStrings, atomType);
|
387 | a11yStrings.push("end overline");
|
388 | });
|
389 | break;
|
390 | }
|
391 |
|
392 | case "phantom":
|
393 | {
|
394 | a11yStrings.push("empty space");
|
395 | break;
|
396 | }
|
397 |
|
398 | case "raisebox":
|
399 | {
|
400 | buildA11yStrings(tree.body, a11yStrings, atomType);
|
401 | break;
|
402 | }
|
403 |
|
404 | case "rule":
|
405 | {
|
406 | a11yStrings.push("rectangle");
|
407 | break;
|
408 | }
|
409 |
|
410 | case "sizing":
|
411 | {
|
412 | buildA11yStrings(tree.body, a11yStrings, atomType);
|
413 | break;
|
414 | }
|
415 |
|
416 | case "spacing":
|
417 | {
|
418 | a11yStrings.push("space");
|
419 | break;
|
420 | }
|
421 |
|
422 | case "styling":
|
423 | {
|
424 |
|
425 | buildA11yStrings(tree.body, a11yStrings, atomType);
|
426 | break;
|
427 | }
|
428 |
|
429 | case "sqrt":
|
430 | {
|
431 | buildRegion(a11yStrings, regionStrings => {
|
432 | const body = tree.body,
|
433 | index = tree.index;
|
434 |
|
435 | if (index) {
|
436 | const indexString = flatten(buildA11yStrings(index, [], atomType)).join(",");
|
437 |
|
438 | if (indexString === "3") {
|
439 | regionStrings.push("cube root of");
|
440 | buildA11yStrings(body, regionStrings, atomType);
|
441 | regionStrings.push("end cube root");
|
442 | return;
|
443 | }
|
444 |
|
445 | regionStrings.push("root");
|
446 | regionStrings.push("start index");
|
447 | buildA11yStrings(index, regionStrings, atomType);
|
448 | regionStrings.push("end index");
|
449 | return;
|
450 | }
|
451 |
|
452 | regionStrings.push("square root of");
|
453 | buildA11yStrings(body, regionStrings, atomType);
|
454 | regionStrings.push("end square root");
|
455 | });
|
456 | break;
|
457 | }
|
458 |
|
459 | case "supsub":
|
460 | {
|
461 | const base = tree.base,
|
462 | sub = tree.sub,
|
463 | sup = tree.sup;
|
464 | let isLog = false;
|
465 |
|
466 | if (base) {
|
467 | buildA11yStrings(base, a11yStrings, atomType);
|
468 | isLog = base.type === "op" && base.name === "\\log";
|
469 | }
|
470 |
|
471 | if (sub) {
|
472 | const regionName = isLog ? "base" : "subscript";
|
473 | buildRegion(a11yStrings, function (regionStrings) {
|
474 | regionStrings.push(`start ${regionName}`);
|
475 | buildA11yStrings(sub, regionStrings, atomType);
|
476 | regionStrings.push(`end ${regionName}`);
|
477 | });
|
478 | }
|
479 |
|
480 | if (sup) {
|
481 | buildRegion(a11yStrings, function (regionStrings) {
|
482 | const supString = flatten(buildA11yStrings(sup, [], atomType)).join(",");
|
483 |
|
484 | if (supString in powerMap) {
|
485 | regionStrings.push(powerMap[supString]);
|
486 | return;
|
487 | }
|
488 |
|
489 | regionStrings.push("start superscript");
|
490 | buildA11yStrings(sup, regionStrings, atomType);
|
491 | regionStrings.push("end superscript");
|
492 | });
|
493 | }
|
494 |
|
495 | break;
|
496 | }
|
497 |
|
498 | case "text":
|
499 | {
|
500 |
|
501 | if (tree.font === "\\textbf") {
|
502 | buildRegion(a11yStrings, function (regionStrings) {
|
503 | regionStrings.push("start bold text");
|
504 | buildA11yStrings(tree.body, regionStrings, atomType);
|
505 | regionStrings.push("end bold text");
|
506 | });
|
507 | break;
|
508 | }
|
509 |
|
510 | buildRegion(a11yStrings, function (regionStrings) {
|
511 | regionStrings.push("start text");
|
512 | buildA11yStrings(tree.body, regionStrings, atomType);
|
513 | regionStrings.push("end text");
|
514 | });
|
515 | break;
|
516 | }
|
517 |
|
518 | case "textord":
|
519 | {
|
520 | buildString(tree.text, atomType, a11yStrings);
|
521 | break;
|
522 | }
|
523 |
|
524 | case "smash":
|
525 | {
|
526 | buildA11yStrings(tree.body, a11yStrings, atomType);
|
527 | break;
|
528 | }
|
529 |
|
530 | case "enclose":
|
531 | {
|
532 |
|
533 |
|
534 |
|
535 | if (/cancel/.test(tree.label)) {
|
536 | buildRegion(a11yStrings, function (regionStrings) {
|
537 | regionStrings.push("start cancel");
|
538 | buildA11yStrings(tree.body, regionStrings, atomType);
|
539 | regionStrings.push("end cancel");
|
540 | });
|
541 | break;
|
542 | } else if (/box/.test(tree.label)) {
|
543 | buildRegion(a11yStrings, function (regionStrings) {
|
544 | regionStrings.push("start box");
|
545 | buildA11yStrings(tree.body, regionStrings, atomType);
|
546 | regionStrings.push("end box");
|
547 | });
|
548 | break;
|
549 | } else if (/sout/.test(tree.label)) {
|
550 | buildRegion(a11yStrings, function (regionStrings) {
|
551 | regionStrings.push("start strikeout");
|
552 | buildA11yStrings(tree.body, regionStrings, atomType);
|
553 | regionStrings.push("end strikeout");
|
554 | });
|
555 | break;
|
556 | }
|
557 |
|
558 | throw new Error(`KaTeX-a11y: enclose node with ${tree.label} not supported yet`);
|
559 | }
|
560 |
|
561 | case "vphantom":
|
562 | {
|
563 | throw new Error("KaTeX-a11y: vphantom not implemented yet");
|
564 | }
|
565 |
|
566 | case "hphantom":
|
567 | {
|
568 | throw new Error("KaTeX-a11y: hphantom not implemented yet");
|
569 | }
|
570 |
|
571 | case "operatorname":
|
572 | {
|
573 | buildA11yStrings(tree.body, a11yStrings, atomType);
|
574 | break;
|
575 | }
|
576 |
|
577 | case "array":
|
578 | {
|
579 | throw new Error("KaTeX-a11y: array not implemented yet");
|
580 | }
|
581 |
|
582 | case "raw":
|
583 | {
|
584 | throw new Error("KaTeX-a11y: raw not implemented yet");
|
585 | }
|
586 |
|
587 | case "size":
|
588 | {
|
589 |
|
590 |
|
591 | break;
|
592 | }
|
593 |
|
594 | case "url":
|
595 | {
|
596 | throw new Error("KaTeX-a11y: url not implemented yet");
|
597 | }
|
598 |
|
599 | case "tag":
|
600 | {
|
601 | throw new Error("KaTeX-a11y: tag not implemented yet");
|
602 | }
|
603 |
|
604 | case "verb":
|
605 | {
|
606 | buildString(`start verbatim`, "normal", a11yStrings);
|
607 | buildString(tree.body, "normal", a11yStrings);
|
608 | buildString(`end verbatim`, "normal", a11yStrings);
|
609 | break;
|
610 | }
|
611 |
|
612 | case "environment":
|
613 | {
|
614 | throw new Error("KaTeX-a11y: environment not implemented yet");
|
615 | }
|
616 |
|
617 | case "horizBrace":
|
618 | {
|
619 | buildString(`start ${tree.label.slice(1)}`, "normal", a11yStrings);
|
620 | buildA11yStrings(tree.base, a11yStrings, atomType);
|
621 | buildString(`end ${tree.label.slice(1)}`, "normal", a11yStrings);
|
622 | break;
|
623 | }
|
624 |
|
625 | case "infix":
|
626 | {
|
627 |
|
628 | break;
|
629 | }
|
630 |
|
631 | case "includegraphics":
|
632 | {
|
633 | throw new Error("KaTeX-a11y: includegraphics not implemented yet");
|
634 | }
|
635 |
|
636 | case "font":
|
637 | {
|
638 |
|
639 |
|
640 | buildA11yStrings(tree.body, a11yStrings, atomType);
|
641 | break;
|
642 | }
|
643 |
|
644 | case "href":
|
645 | {
|
646 | throw new Error("KaTeX-a11y: href not implemented yet");
|
647 | }
|
648 |
|
649 | case "cr":
|
650 | {
|
651 |
|
652 | throw new Error("KaTeX-a11y: cr not implemented yet");
|
653 | }
|
654 |
|
655 | case "underline":
|
656 | {
|
657 | buildRegion(a11yStrings, function (a11yStrings) {
|
658 | a11yStrings.push("start underline");
|
659 | buildA11yStrings(tree.body, a11yStrings, atomType);
|
660 | a11yStrings.push("end underline");
|
661 | });
|
662 | break;
|
663 | }
|
664 |
|
665 | case "xArrow":
|
666 | {
|
667 | throw new Error("KaTeX-a11y: xArrow not implemented yet");
|
668 | }
|
669 |
|
670 | case "mclass":
|
671 | {
|
672 |
|
673 |
|
674 | const atomType = tree.mclass.slice(1);
|
675 |
|
676 | buildA11yStrings(tree.body, a11yStrings, atomType);
|
677 | break;
|
678 | }
|
679 |
|
680 | case "mathchoice":
|
681 | {
|
682 |
|
683 |
|
684 | buildA11yStrings(tree.text, a11yStrings, atomType);
|
685 | break;
|
686 | }
|
687 |
|
688 | case "htmlmathml":
|
689 | {
|
690 | buildA11yStrings(tree.mathml, a11yStrings, atomType);
|
691 | break;
|
692 | }
|
693 |
|
694 | case "middle":
|
695 | {
|
696 | buildString(tree.delim, atomType, a11yStrings);
|
697 | break;
|
698 | }
|
699 |
|
700 | case "internal":
|
701 | {
|
702 |
|
703 | break;
|
704 | }
|
705 |
|
706 | case "html":
|
707 | {
|
708 | buildA11yStrings(tree.body, a11yStrings, atomType);
|
709 | break;
|
710 | }
|
711 |
|
712 | default:
|
713 | tree.type;
|
714 | throw new Error("KaTeX a11y un-recognized type: " + tree.type);
|
715 | }
|
716 | };
|
717 |
|
718 | const buildA11yStrings = function buildA11yStrings(tree, a11yStrings, atomType) {
|
719 | if (a11yStrings === void 0) {
|
720 | a11yStrings = [];
|
721 | }
|
722 |
|
723 | if (tree instanceof Array) {
|
724 | for (let i = 0; i < tree.length; i++) {
|
725 | buildA11yStrings(tree[i], a11yStrings, atomType);
|
726 | }
|
727 | } else {
|
728 | handleObject(tree, a11yStrings, atomType);
|
729 | }
|
730 |
|
731 | return a11yStrings;
|
732 | };
|
733 |
|
734 | const flatten = function flatten(array) {
|
735 | let result = [];
|
736 | array.forEach(function (item) {
|
737 | if (item instanceof Array) {
|
738 | result = result.concat(flatten(item));
|
739 | } else {
|
740 | result.push(item);
|
741 | }
|
742 | });
|
743 | return result;
|
744 | };
|
745 |
|
746 | const renderA11yString = function renderA11yString(text, settings) {
|
747 | const tree = katex.__parse(text, settings);
|
748 |
|
749 | const a11yStrings = buildA11yStrings(tree, [], "normal");
|
750 | return flatten(a11yStrings).join(", ");
|
751 | };
|
752 |
|
753 | export default renderA11yString;
|