forked from sheetjs/docs.sheetjs.com
		
	
		
			
				
	
	
		
			754 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			754 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import katex from '../katex.mjs';
 | |
| 
 | |
| /**
 | |
|  * renderA11yString returns a readable string.
 | |
|  *
 | |
|  * In some cases the string will have the proper semantic math
 | |
|  * meaning,:
 | |
|  *   renderA11yString("\\frac{1}{2}"")
 | |
|  *   -> "start fraction, 1, divided by, 2, end fraction"
 | |
|  *
 | |
|  * However, other cases do not:
 | |
|  *   renderA11yString("f(x) = x^2")
 | |
|  *   -> "f, left parenthesis, x, right parenthesis, equals, x, squared"
 | |
|  *
 | |
|  * The commas in the string aim to increase ease of understanding
 | |
|  * when read by a screenreader.
 | |
|  */
 | |
| const stringMap = {
 | |
|   "(": "left parenthesis",
 | |
|   ")": "right parenthesis",
 | |
|   "[": "open bracket",
 | |
|   "]": "close bracket",
 | |
|   "\\{": "left brace",
 | |
|   "\\}": "right brace",
 | |
|   "\\lvert": "open vertical bar",
 | |
|   "\\rvert": "close vertical bar",
 | |
|   "|": "vertical bar",
 | |
|   "\\uparrow": "up arrow",
 | |
|   "\\Uparrow": "up arrow",
 | |
|   "\\downarrow": "down arrow",
 | |
|   "\\Downarrow": "down arrow",
 | |
|   "\\updownarrow": "up down arrow",
 | |
|   "\\leftarrow": "left arrow",
 | |
|   "\\Leftarrow": "left arrow",
 | |
|   "\\rightarrow": "right arrow",
 | |
|   "\\Rightarrow": "right arrow",
 | |
|   "\\langle": "open angle",
 | |
|   "\\rangle": "close angle",
 | |
|   "\\lfloor": "open floor",
 | |
|   "\\rfloor": "close floor",
 | |
|   "\\int": "integral",
 | |
|   "\\intop": "integral",
 | |
|   "\\lim": "limit",
 | |
|   "\\ln": "natural log",
 | |
|   "\\log": "log",
 | |
|   "\\sin": "sine",
 | |
|   "\\cos": "cosine",
 | |
|   "\\tan": "tangent",
 | |
|   "\\cot": "cotangent",
 | |
|   "\\sum": "sum",
 | |
|   "/": "slash",
 | |
|   ",": "comma",
 | |
|   ".": "point",
 | |
|   "-": "negative",
 | |
|   "+": "plus",
 | |
|   "~": "tilde",
 | |
|   ":": "colon",
 | |
|   "?": "question mark",
 | |
|   "'": "apostrophe",
 | |
|   "\\%": "percent",
 | |
|   " ": "space",
 | |
|   "\\ ": "space",
 | |
|   "\\$": "dollar sign",
 | |
|   "\\angle": "angle",
 | |
|   "\\degree": "degree",
 | |
|   "\\circ": "circle",
 | |
|   "\\vec": "vector",
 | |
|   "\\triangle": "triangle",
 | |
|   "\\pi": "pi",
 | |
|   "\\prime": "prime",
 | |
|   "\\infty": "infinity",
 | |
|   "\\alpha": "alpha",
 | |
|   "\\beta": "beta",
 | |
|   "\\gamma": "gamma",
 | |
|   "\\omega": "omega",
 | |
|   "\\theta": "theta",
 | |
|   "\\sigma": "sigma",
 | |
|   "\\lambda": "lambda",
 | |
|   "\\tau": "tau",
 | |
|   "\\Delta": "delta",
 | |
|   "\\delta": "delta",
 | |
|   "\\mu": "mu",
 | |
|   "\\rho": "rho",
 | |
|   "\\nabla": "del",
 | |
|   "\\ell": "ell",
 | |
|   "\\ldots": "dots",
 | |
|   // TODO: add entries for all accents
 | |
|   "\\hat": "hat",
 | |
|   "\\acute": "acute"
 | |
| };
 | |
| const powerMap = {
 | |
|   "prime": "prime",
 | |
|   "degree": "degrees",
 | |
|   "circle": "degrees",
 | |
|   "2": "squared",
 | |
|   "3": "cubed"
 | |
| };
 | |
| const openMap = {
 | |
|   "|": "open vertical bar",
 | |
|   ".": ""
 | |
| };
 | |
| const closeMap = {
 | |
|   "|": "close vertical bar",
 | |
|   ".": ""
 | |
| };
 | |
| const binMap = {
 | |
|   "+": "plus",
 | |
|   "-": "minus",
 | |
|   "\\pm": "plus minus",
 | |
|   "\\cdot": "dot",
 | |
|   "*": "times",
 | |
|   "/": "divided by",
 | |
|   "\\times": "times",
 | |
|   "\\div": "divided by",
 | |
|   "\\circ": "circle",
 | |
|   "\\bullet": "bullet"
 | |
| };
 | |
| const relMap = {
 | |
|   "=": "equals",
 | |
|   "\\approx": "approximately equals",
 | |
|   "≠": "does not equal",
 | |
|   "\\geq": "is greater than or equal to",
 | |
|   "\\ge": "is greater than or equal to",
 | |
|   "\\leq": "is less than or equal to",
 | |
|   "\\le": "is less than or equal to",
 | |
|   ">": "is greater than",
 | |
|   "<": "is less than",
 | |
|   "\\leftarrow": "left arrow",
 | |
|   "\\Leftarrow": "left arrow",
 | |
|   "\\rightarrow": "right arrow",
 | |
|   "\\Rightarrow": "right arrow",
 | |
|   ":": "colon"
 | |
| };
 | |
| const accentUnderMap = {
 | |
|   "\\underleftarrow": "left arrow",
 | |
|   "\\underrightarrow": "right arrow",
 | |
|   "\\underleftrightarrow": "left-right arrow",
 | |
|   "\\undergroup": "group",
 | |
|   "\\underlinesegment": "line segment",
 | |
|   "\\utilde": "tilde"
 | |
| };
 | |
| 
 | |
| const buildString = (str, type, a11yStrings) => {
 | |
|   if (!str) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   let ret;
 | |
| 
 | |
|   if (type === "open") {
 | |
|     ret = str in openMap ? openMap[str] : stringMap[str] || str;
 | |
|   } else if (type === "close") {
 | |
|     ret = str in closeMap ? closeMap[str] : stringMap[str] || str;
 | |
|   } else if (type === "bin") {
 | |
|     ret = binMap[str] || str;
 | |
|   } else if (type === "rel") {
 | |
|     ret = relMap[str] || str;
 | |
|   } else {
 | |
|     ret = stringMap[str] || str;
 | |
|   } // If the text to add is a number and there is already a string
 | |
|   // in the list and the last string is a number then we should
 | |
|   // combine them into a single number
 | |
| 
 | |
| 
 | |
|   if (/^\d+$/.test(ret) && a11yStrings.length > 0 && // TODO(kevinb): check that the last item in a11yStrings is a string
 | |
|   // I think we might be able to drop the nested arrays, which would make
 | |
|   // this easier to type - $FlowFixMe
 | |
|   /^\d+$/.test(a11yStrings[a11yStrings.length - 1])) {
 | |
|     a11yStrings[a11yStrings.length - 1] += ret;
 | |
|   } else if (ret) {
 | |
|     a11yStrings.push(ret);
 | |
|   }
 | |
| };
 | |
| 
 | |
| const buildRegion = (a11yStrings, callback) => {
 | |
|   const regionStrings = [];
 | |
|   a11yStrings.push(regionStrings);
 | |
|   callback(regionStrings);
 | |
| };
 | |
| 
 | |
| const handleObject = (tree, a11yStrings, atomType) => {
 | |
|   // Everything else is assumed to be an object...
 | |
|   switch (tree.type) {
 | |
|     case "accent":
 | |
|       {
 | |
|         buildRegion(a11yStrings, a11yStrings => {
 | |
|           buildA11yStrings(tree.base, a11yStrings, atomType);
 | |
|           a11yStrings.push("with");
 | |
|           buildString(tree.label, "normal", a11yStrings);
 | |
|           a11yStrings.push("on top");
 | |
|         });
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "accentUnder":
 | |
|       {
 | |
|         buildRegion(a11yStrings, a11yStrings => {
 | |
|           buildA11yStrings(tree.base, a11yStrings, atomType);
 | |
|           a11yStrings.push("with");
 | |
|           buildString(accentUnderMap[tree.label], "normal", a11yStrings);
 | |
|           a11yStrings.push("underneath");
 | |
|         });
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "accent-token":
 | |
|       {
 | |
|         // Used internally by accent symbols.
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "atom":
 | |
|       {
 | |
|         const text = tree.text;
 | |
| 
 | |
|         switch (tree.family) {
 | |
|           case "bin":
 | |
|             {
 | |
|               buildString(text, "bin", a11yStrings);
 | |
|               break;
 | |
|             }
 | |
| 
 | |
|           case "close":
 | |
|             {
 | |
|               buildString(text, "close", a11yStrings);
 | |
|               break;
 | |
|             }
 | |
|           // TODO(kevinb): figure out what should be done for inner
 | |
| 
 | |
|           case "inner":
 | |
|             {
 | |
|               buildString(tree.text, "inner", a11yStrings);
 | |
|               break;
 | |
|             }
 | |
| 
 | |
|           case "open":
 | |
|             {
 | |
|               buildString(text, "open", a11yStrings);
 | |
|               break;
 | |
|             }
 | |
| 
 | |
|           case "punct":
 | |
|             {
 | |
|               buildString(text, "punct", a11yStrings);
 | |
|               break;
 | |
|             }
 | |
| 
 | |
|           case "rel":
 | |
|             {
 | |
|               buildString(text, "rel", a11yStrings);
 | |
|               break;
 | |
|             }
 | |
| 
 | |
|           default:
 | |
|             {
 | |
|               tree.family;
 | |
|               throw new Error(`"${tree.family}" is not a valid atom type`);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "color":
 | |
|       {
 | |
|         const color = tree.color.replace(/katex-/, "");
 | |
|         buildRegion(a11yStrings, regionStrings => {
 | |
|           regionStrings.push("start color " + color);
 | |
|           buildA11yStrings(tree.body, regionStrings, atomType);
 | |
|           regionStrings.push("end color " + color);
 | |
|         });
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "color-token":
 | |
|       {
 | |
|         // Used by \color, \colorbox, and \fcolorbox but not directly rendered.
 | |
|         // It's a leaf node and has no children so just break.
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "delimsizing":
 | |
|       {
 | |
|         if (tree.delim && tree.delim !== ".") {
 | |
|           buildString(tree.delim, "normal", a11yStrings);
 | |
|         }
 | |
| 
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "genfrac":
 | |
|       {
 | |
|         buildRegion(a11yStrings, regionStrings => {
 | |
|           // genfrac can have unbalanced delimiters
 | |
|           const leftDelim = tree.leftDelim,
 | |
|                 rightDelim = tree.rightDelim; // NOTE: Not sure if this is a safe assumption
 | |
|           // hasBarLine true -> fraction, false -> binomial
 | |
| 
 | |
|           if (tree.hasBarLine) {
 | |
|             regionStrings.push("start fraction");
 | |
|             leftDelim && buildString(leftDelim, "open", regionStrings);
 | |
|             buildA11yStrings(tree.numer, regionStrings, atomType);
 | |
|             regionStrings.push("divided by");
 | |
|             buildA11yStrings(tree.denom, regionStrings, atomType);
 | |
|             rightDelim && buildString(rightDelim, "close", regionStrings);
 | |
|             regionStrings.push("end fraction");
 | |
|           } else {
 | |
|             regionStrings.push("start binomial");
 | |
|             leftDelim && buildString(leftDelim, "open", regionStrings);
 | |
|             buildA11yStrings(tree.numer, regionStrings, atomType);
 | |
|             regionStrings.push("over");
 | |
|             buildA11yStrings(tree.denom, regionStrings, atomType);
 | |
|             rightDelim && buildString(rightDelim, "close", regionStrings);
 | |
|             regionStrings.push("end binomial");
 | |
|           }
 | |
|         });
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "kern":
 | |
|       {
 | |
|         // No op: we don't attempt to present kerning information
 | |
|         // to the screen reader.
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "leftright":
 | |
|       {
 | |
|         buildRegion(a11yStrings, regionStrings => {
 | |
|           buildString(tree.left, "open", regionStrings);
 | |
|           buildA11yStrings(tree.body, regionStrings, atomType);
 | |
|           buildString(tree.right, "close", regionStrings);
 | |
|         });
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "leftright-right":
 | |
|       {
 | |
|         // TODO: double check that this is a no-op
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "lap":
 | |
|       {
 | |
|         buildA11yStrings(tree.body, a11yStrings, atomType);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "mathord":
 | |
|       {
 | |
|         buildString(tree.text, "normal", a11yStrings);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "op":
 | |
|       {
 | |
|         const body = tree.body,
 | |
|               name = tree.name;
 | |
| 
 | |
|         if (body) {
 | |
|           buildA11yStrings(body, a11yStrings, atomType);
 | |
|         } else if (name) {
 | |
|           buildString(name, "normal", a11yStrings);
 | |
|         }
 | |
| 
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "op-token":
 | |
|       {
 | |
|         // Used internally by operator symbols.
 | |
|         buildString(tree.text, atomType, a11yStrings);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "ordgroup":
 | |
|       {
 | |
|         buildA11yStrings(tree.body, a11yStrings, atomType);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "overline":
 | |
|       {
 | |
|         buildRegion(a11yStrings, function (a11yStrings) {
 | |
|           a11yStrings.push("start overline");
 | |
|           buildA11yStrings(tree.body, a11yStrings, atomType);
 | |
|           a11yStrings.push("end overline");
 | |
|         });
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "phantom":
 | |
|       {
 | |
|         a11yStrings.push("empty space");
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "raisebox":
 | |
|       {
 | |
|         buildA11yStrings(tree.body, a11yStrings, atomType);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "rule":
 | |
|       {
 | |
|         a11yStrings.push("rectangle");
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "sizing":
 | |
|       {
 | |
|         buildA11yStrings(tree.body, a11yStrings, atomType);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "spacing":
 | |
|       {
 | |
|         a11yStrings.push("space");
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "styling":
 | |
|       {
 | |
|         // We ignore the styling and just pass through the contents
 | |
|         buildA11yStrings(tree.body, a11yStrings, atomType);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "sqrt":
 | |
|       {
 | |
|         buildRegion(a11yStrings, regionStrings => {
 | |
|           const body = tree.body,
 | |
|                 index = tree.index;
 | |
| 
 | |
|           if (index) {
 | |
|             const indexString = flatten(buildA11yStrings(index, [], atomType)).join(",");
 | |
| 
 | |
|             if (indexString === "3") {
 | |
|               regionStrings.push("cube root of");
 | |
|               buildA11yStrings(body, regionStrings, atomType);
 | |
|               regionStrings.push("end cube root");
 | |
|               return;
 | |
|             }
 | |
| 
 | |
|             regionStrings.push("root");
 | |
|             regionStrings.push("start index");
 | |
|             buildA11yStrings(index, regionStrings, atomType);
 | |
|             regionStrings.push("end index");
 | |
|             return;
 | |
|           }
 | |
| 
 | |
|           regionStrings.push("square root of");
 | |
|           buildA11yStrings(body, regionStrings, atomType);
 | |
|           regionStrings.push("end square root");
 | |
|         });
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "supsub":
 | |
|       {
 | |
|         const base = tree.base,
 | |
|               sub = tree.sub,
 | |
|               sup = tree.sup;
 | |
|         let isLog = false;
 | |
| 
 | |
|         if (base) {
 | |
|           buildA11yStrings(base, a11yStrings, atomType);
 | |
|           isLog = base.type === "op" && base.name === "\\log";
 | |
|         }
 | |
| 
 | |
|         if (sub) {
 | |
|           const regionName = isLog ? "base" : "subscript";
 | |
|           buildRegion(a11yStrings, function (regionStrings) {
 | |
|             regionStrings.push(`start ${regionName}`);
 | |
|             buildA11yStrings(sub, regionStrings, atomType);
 | |
|             regionStrings.push(`end ${regionName}`);
 | |
|           });
 | |
|         }
 | |
| 
 | |
|         if (sup) {
 | |
|           buildRegion(a11yStrings, function (regionStrings) {
 | |
|             const supString = flatten(buildA11yStrings(sup, [], atomType)).join(",");
 | |
| 
 | |
|             if (supString in powerMap) {
 | |
|               regionStrings.push(powerMap[supString]);
 | |
|               return;
 | |
|             }
 | |
| 
 | |
|             regionStrings.push("start superscript");
 | |
|             buildA11yStrings(sup, regionStrings, atomType);
 | |
|             regionStrings.push("end superscript");
 | |
|           });
 | |
|         }
 | |
| 
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "text":
 | |
|       {
 | |
|         // TODO: handle other fonts
 | |
|         if (tree.font === "\\textbf") {
 | |
|           buildRegion(a11yStrings, function (regionStrings) {
 | |
|             regionStrings.push("start bold text");
 | |
|             buildA11yStrings(tree.body, regionStrings, atomType);
 | |
|             regionStrings.push("end bold text");
 | |
|           });
 | |
|           break;
 | |
|         }
 | |
| 
 | |
|         buildRegion(a11yStrings, function (regionStrings) {
 | |
|           regionStrings.push("start text");
 | |
|           buildA11yStrings(tree.body, regionStrings, atomType);
 | |
|           regionStrings.push("end text");
 | |
|         });
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "textord":
 | |
|       {
 | |
|         buildString(tree.text, atomType, a11yStrings);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "smash":
 | |
|       {
 | |
|         buildA11yStrings(tree.body, a11yStrings, atomType);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "enclose":
 | |
|       {
 | |
|         // TODO: create a map for these.
 | |
|         // TODO: differentiate between a body with a single atom, e.g.
 | |
|         // "cancel a" instead of "start cancel, a, end cancel"
 | |
|         if (/cancel/.test(tree.label)) {
 | |
|           buildRegion(a11yStrings, function (regionStrings) {
 | |
|             regionStrings.push("start cancel");
 | |
|             buildA11yStrings(tree.body, regionStrings, atomType);
 | |
|             regionStrings.push("end cancel");
 | |
|           });
 | |
|           break;
 | |
|         } else if (/box/.test(tree.label)) {
 | |
|           buildRegion(a11yStrings, function (regionStrings) {
 | |
|             regionStrings.push("start box");
 | |
|             buildA11yStrings(tree.body, regionStrings, atomType);
 | |
|             regionStrings.push("end box");
 | |
|           });
 | |
|           break;
 | |
|         } else if (/sout/.test(tree.label)) {
 | |
|           buildRegion(a11yStrings, function (regionStrings) {
 | |
|             regionStrings.push("start strikeout");
 | |
|             buildA11yStrings(tree.body, regionStrings, atomType);
 | |
|             regionStrings.push("end strikeout");
 | |
|           });
 | |
|           break;
 | |
|         }
 | |
| 
 | |
|         throw new Error(`KaTeX-a11y: enclose node with ${tree.label} not supported yet`);
 | |
|       }
 | |
| 
 | |
|     case "vphantom":
 | |
|       {
 | |
|         throw new Error("KaTeX-a11y: vphantom not implemented yet");
 | |
|       }
 | |
| 
 | |
|     case "hphantom":
 | |
|       {
 | |
|         throw new Error("KaTeX-a11y: hphantom not implemented yet");
 | |
|       }
 | |
| 
 | |
|     case "operatorname":
 | |
|       {
 | |
|         buildA11yStrings(tree.body, a11yStrings, atomType);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "array":
 | |
|       {
 | |
|         throw new Error("KaTeX-a11y: array not implemented yet");
 | |
|       }
 | |
| 
 | |
|     case "raw":
 | |
|       {
 | |
|         throw new Error("KaTeX-a11y: raw not implemented yet");
 | |
|       }
 | |
| 
 | |
|     case "size":
 | |
|       {
 | |
|         // Although there are nodes of type "size" in the parse tree, they have
 | |
|         // no semantic meaning and should be ignored.
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "url":
 | |
|       {
 | |
|         throw new Error("KaTeX-a11y: url not implemented yet");
 | |
|       }
 | |
| 
 | |
|     case "tag":
 | |
|       {
 | |
|         throw new Error("KaTeX-a11y: tag not implemented yet");
 | |
|       }
 | |
| 
 | |
|     case "verb":
 | |
|       {
 | |
|         buildString(`start verbatim`, "normal", a11yStrings);
 | |
|         buildString(tree.body, "normal", a11yStrings);
 | |
|         buildString(`end verbatim`, "normal", a11yStrings);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "environment":
 | |
|       {
 | |
|         throw new Error("KaTeX-a11y: environment not implemented yet");
 | |
|       }
 | |
| 
 | |
|     case "horizBrace":
 | |
|       {
 | |
|         buildString(`start ${tree.label.slice(1)}`, "normal", a11yStrings);
 | |
|         buildA11yStrings(tree.base, a11yStrings, atomType);
 | |
|         buildString(`end ${tree.label.slice(1)}`, "normal", a11yStrings);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "infix":
 | |
|       {
 | |
|         // All infix nodes are replace with other nodes.
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "includegraphics":
 | |
|       {
 | |
|         throw new Error("KaTeX-a11y: includegraphics not implemented yet");
 | |
|       }
 | |
| 
 | |
|     case "font":
 | |
|       {
 | |
|         // TODO: callout the start/end of specific fonts
 | |
|         // TODO: map \BBb{N} to "the naturals" or something like that
 | |
|         buildA11yStrings(tree.body, a11yStrings, atomType);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "href":
 | |
|       {
 | |
|         throw new Error("KaTeX-a11y: href not implemented yet");
 | |
|       }
 | |
| 
 | |
|     case "cr":
 | |
|       {
 | |
|         // This is used by environments.
 | |
|         throw new Error("KaTeX-a11y: cr not implemented yet");
 | |
|       }
 | |
| 
 | |
|     case "underline":
 | |
|       {
 | |
|         buildRegion(a11yStrings, function (a11yStrings) {
 | |
|           a11yStrings.push("start underline");
 | |
|           buildA11yStrings(tree.body, a11yStrings, atomType);
 | |
|           a11yStrings.push("end underline");
 | |
|         });
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "xArrow":
 | |
|       {
 | |
|         throw new Error("KaTeX-a11y: xArrow not implemented yet");
 | |
|       }
 | |
| 
 | |
|     case "mclass":
 | |
|       {
 | |
|         // \neq and \ne are macros so we let "htmlmathml" render the mathmal
 | |
|         // side of things and extract the text from that.
 | |
|         const atomType = tree.mclass.slice(1); // $FlowFixMe: drop the leading "m" from the values in mclass
 | |
| 
 | |
|         buildA11yStrings(tree.body, a11yStrings, atomType);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "mathchoice":
 | |
|       {
 | |
|         // TODO: track which which style we're using, e.g. dispaly, text, etc.
 | |
|         // default to text style if even that may not be the correct style
 | |
|         buildA11yStrings(tree.text, a11yStrings, atomType);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "htmlmathml":
 | |
|       {
 | |
|         buildA11yStrings(tree.mathml, a11yStrings, atomType);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "middle":
 | |
|       {
 | |
|         buildString(tree.delim, atomType, a11yStrings);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "internal":
 | |
|       {
 | |
|         // internal nodes are never included in the parse tree
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     case "html":
 | |
|       {
 | |
|         buildA11yStrings(tree.body, a11yStrings, atomType);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|     default:
 | |
|       tree.type;
 | |
|       throw new Error("KaTeX a11y un-recognized type: " + tree.type);
 | |
|   }
 | |
| };
 | |
| 
 | |
| const buildA11yStrings = function buildA11yStrings(tree, a11yStrings, atomType) {
 | |
|   if (a11yStrings === void 0) {
 | |
|     a11yStrings = [];
 | |
|   }
 | |
| 
 | |
|   if (tree instanceof Array) {
 | |
|     for (let i = 0; i < tree.length; i++) {
 | |
|       buildA11yStrings(tree[i], a11yStrings, atomType);
 | |
|     }
 | |
|   } else {
 | |
|     handleObject(tree, a11yStrings, atomType);
 | |
|   }
 | |
| 
 | |
|   return a11yStrings;
 | |
| };
 | |
| 
 | |
| const flatten = function flatten(array) {
 | |
|   let result = [];
 | |
|   array.forEach(function (item) {
 | |
|     if (item instanceof Array) {
 | |
|       result = result.concat(flatten(item));
 | |
|     } else {
 | |
|       result.push(item);
 | |
|     }
 | |
|   });
 | |
|   return result;
 | |
| };
 | |
| 
 | |
| const renderA11yString = function renderA11yString(text, settings) {
 | |
|   const tree = katex.__parse(text, settings);
 | |
| 
 | |
|   const a11yStrings = buildA11yStrings(tree, [], "normal");
 | |
|   return flatten(a11yStrings).join(", ");
 | |
| };
 | |
| 
 | |
| export default renderA11yString;
 |