File size: 5,806 Bytes
00f42f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/* Math as pictures. The content writes math as plain text between $…$ (math.js shows it as <span class="m">), e.g.
   "1/(2x) + x² ≥ √3". This file turns that text into TeX (a/b becomes a stacked fraction, √ a root, ² a raised
   exponent) and MathJax draws it as an SVG image in place of the text. The text stays as the picture's label, and
   whatever cannot be read (Hebrew words, unknown signs), or everything when MathJax does not load, keeps showing as text. */
(()=>{
const SUP={"⁰":"0","¹":"1","²":"2","³":"3","⁴":"4","⁵":"5","⁶":"6","⁷":"7","⁸":"8","⁹":"9","⁻":"-","⁺":"+","ⁿ":"n","ᵐ":"m"};
const SUB={"₀":"0","₁":"1","₂":"2","₃":"3","₄":"4","₅":"5","₆":"6","₇":"7","₈":"8","₉":"9"};
const OPS={"−":"-","–":"-","·":"\\cdot ","×":"\\times ","÷":"\\div ","≠":"\\neq ","≥":"\\geq ","≤":"\\leq ","±":"\\pm ",
  "⇒":"\\Rightarrow ","→":"\\to ","↔":"\\leftrightarrow ","∥":"\\parallel ","⊥":"\\perp ","△":"\\triangle ",
  "∢":"\\measuredangle ","∼":"\\sim ","…":"\\ldots ","%":"\\%","π":"\\pi ","α":"\\alpha ","β":"\\beta ","∞":"\\infty ",
  "+":"+","-":"-","=":"=","<":"<",">":">",",":",",":":":","?":"?","!":"!","|":"|","'":"'",".":"."};
const TOKEN=/\d+(?:\.\d+)?|[A-Za-z]|[⁰¹²³⁴⁵⁶⁷⁸⁹⁻⁺ⁿᵐ]+|[₀₁₂₃₄₅₆₇₈₉]+|\s+|./gu;

// plain math text to TeX, or null when it cannot be read
function toTex(src){
  const toks=String(src).match(TOKEN)||[];
  if(!toks.length) return null;
  let i=0;
  const fail=()=>{ throw new Error("unreadable"); };
  // one atom: a number, a letter, a (group) or a √atom; `inner` is a group's content, used inside a fraction or root
  function atom(){
    const k=toks[i++];
    if(k===undefined) fail();
    if(/^[\d.]/.test(k)||/^[A-Za-zΔ]$/.test(k)) return {kind:"a",t:k==="Δ"?"\\Delta ":k};
    if(k==="("||k==="["){
      const c=k==="("?")":"]", inner=join(seq(c)), big=inner.includes("\\frac");
      return {kind:"a",t:big?`\\left${k}${inner}\\right${c}`:`${k}${inner}${c}`,inner};
    }
    if(k==="√"){ const a=atom(); return {kind:"a",t:`\\sqrt{${a.inner??a.t}}`}; }
    i--; return null;
  }
  function seq(close){
    const out=[];
    while(i<toks.length){
      const k=toks[i];
      if(k===close){ i++; return out; }
      if(k===")"||k==="]") fail();
      const a=atom();
      if(a){ out.push(a); continue; }
      i++;
      const prev=out[out.length-1];
      if(/^\s+$/.test(k)) out.push({kind:"s"});
      else if(SUP[k[0]]!==undefined||k==="^"||k==="°"){
        if(prev?.kind!=="a"||prev.sup) fail();
        let up;
        if(k==="°") up="\\circ";
        else if(k==="^"){ const b=atom(); if(!b) fail(); up=b.inner??b.t; }
        else up=[...k].map(c=>SUP[c]).join("");
        prev.t=`${prev.t}^{${up}}`; prev.sup=true; delete prev.inner;
      }
      else if(SUB[k[0]]!==undefined){
        if(prev?.kind!=="a"||prev.sup||prev.sub) fail();
        prev.t=`${prev.t}_{${[...k].map(c=>SUB[c]).join("")}}`; prev.sub=true; delete prev.inner;
      }
      else if(k==="/") out.push({kind:"/"});
      else if(OPS[k]!==undefined) out.push({kind:"o",t:OPS[k]});
      else fail();
    }
    if(close) fail();
    return out;
  }
  // atoms side by side make one term (2x, 3(x + 1)); a / between two terms is a fraction
  function join(items){
    const terms=[];
    for(const it of items){
      const prev=terms[terms.length-1];
      if(it.kind==="a"&&prev?.kind==="a"){ prev.t+=it.t; delete prev.inner; prev.sup=it.sup; }
      else terms.push({...it});
    }
    const near=(j,step)=>{ while(terms[j]?.kind==="s") j+=step; return j; };
    for(let j=0;j<terms.length;j++){
      if(terms[j].kind!=="/") continue;
      const a=near(j-1,-1), b=near(j+1,1), num=terms[a], den=terms[b];
      if(num?.kind!=="a"||den?.kind!=="a") continue;
      terms.splice(a,b-a+1,{kind:"a",t:`\\frac{${num.inner??num.t}}{${den.inner??den.t}}`});
      j=a;
      // a fraction followed by a term (1/2x) already took it as its denominator; one before it is a coefficient
    }
    return terms.map(x=>x.kind==="s"?" ":x.kind==="/"?"/":x.t).join("").replace(/\s+/g," ").trim();
  }
  try{ const tex=join(seq(null)); return tex||null; }catch{ return null; }
}

/* drawing: each span.m once, the same picture reused for the same text */
const cache=new Map();
function picture(text){
  if(cache.has(text)) return cache.get(text);
  const tex=toTex(text);
  let svg=null;
  if(tex) try{
    const out=window.MathJax.tex2svg(tex.includes("\\frac")?`\\displaystyle ${tex}`:tex,{display:false}).querySelector("svg");
    if(out&&!out.querySelector("[data-mml-node=merror]")){ out.setAttribute("aria-hidden","true"); out.removeAttribute("role"); svg=out; }
  }catch{}
  cache.set(text,svg);
  return svg;
}
function render(scope=document.body){
  if(!window.MathJax?.tex2svg) return;
  for(const el of scope.querySelectorAll("span.m:not([data-tex])")){
    const text=el.textContent, svg=picture(text);
    el.dataset.tex=svg?"1":"0";
    if(!svg) continue;
    el.replaceChildren(svg.cloneNode(true));
    el.setAttribute("role","img"); el.setAttribute("aria-label",text); el.classList.add("m-img");
  }
}
globalThis.MathlyTex={toTex,render};
if(typeof document==="undefined") return;
let queued=false;
const watch=new MutationObserver(()=>{ if(queued) return; queued=true; queueMicrotask(()=>{ queued=false; render(); }); });
window.MathJax={tex:{packages:{"[+]":["ams"]}},svg:{fontCache:"none"},options:{enableMenu:false},
  startup:{typeset:false,ready(){ window.MathJax.startup.defaultReady(); render(); watch.observe(document.body,{childList:true,subtree:true}); }}};
const s=document.createElement("script");
s.src="https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-svg.js"; s.async=true;
document.head.append(s);
})();