diff --git a/docs/tex.md b/docs/tex.md index 1c49abe12..d5bde354f 100644 --- a/docs/tex.md +++ b/docs/tex.md @@ -1,5 +1,50 @@ # TeX -```tex show +${tex`\TeX`} is a language for typesetting mathematical formulae. Observable Markdown’s implementation is powered by ${tex`\KaTeX`}. + +There are two ways to use TeX. The first is a `tex` fenced code block: + +````md +```tex E = mc^2 ``` +```` + +This produces a centered block: + +```tex +E = mc^2 +``` + +The second is an inline expression using the `tex` tagged template literal provided by the Observable standard library: + +```md +My favorite equation is ${tex`E = mc^2`}. +``` + +This produces: + +My favorite equation is ${tex`E = mc^2`}. + +Here are some more examples. + +```tex show +c = \pm\sqrt{a^2 + b^2} +``` + +```tex show +\Delta E^*_{00} = \sqrt{ + \Big(\frac{\Delta L'}{k_LS_L}\Big)^2 + + \Big(\frac{\Delta C'}{k_CS_C}\Big)^2 + + \Big(\frac{\Delta H'}{k_HS_H}\Big)^2 + + R_T + \frac{\Delta C'}{k_CS_C} + \frac{\Delta H'}{k_HS_H}} +``` + +```tex show +\def\f#1#2{#1f(#2)} +\f\relax{x} = \int_{-\infty}^\infty + \f\hat\xi\,e^{2 \pi i \xi x} + \,d\xi +``` diff --git a/public/client.js b/public/client.js index 70afb0d48..776387227 100644 --- a/public/client.js +++ b/public/client.js @@ -75,11 +75,36 @@ function recommendedLibraries() { document.head.append(link); return inputs; }, + tex, dot, mermaid }; } +// TODO Incorporate this into the standard library. +async function tex() { + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.href = "https://cdn.jsdelivr.net/npm/katex/dist/katex.min.css"; + link.crossOrigin = "anonymous"; + document.head.appendChild(link); + + const {default: katex} = await import("https://cdn.jsdelivr.net/npm/katex/+esm"); + const tex = renderer(); + + function renderer(options) { + return function () { + const root = document.createElement("div"); + katex.render(String.raw.apply(String, arguments), root, {...options, output: "html"}); + return root.removeChild(root.firstChild); + }; + } + + tex.options = renderer; + tex.block = renderer({displayMode: true}); + return tex; +} + // TODO Incorporate this into the standard library. async function dot() { const {instance} = await import("https://cdn.jsdelivr.net/npm/@viz-js/viz/+esm"); @@ -123,11 +148,9 @@ async function mermaid() { const {default: mer} = await import("https://cdn.jsdelivr.net/npm/mermaid/+esm"); mer.initialize({startOnLoad: false, securityLevel: "loose", theme: "neutral"}); return async function mermaid() { - const div = document.createElement("div"); - div.innerHTML = (await mer.render(`mermaid-${++nextId}`, String.raw.apply(String, arguments))).svg; - const svg = div.firstChild; - svg.remove(); - return svg; + const root = document.createElement("div"); + root.innerHTML = (await mer.render(`mermaid-${++nextId}`, String.raw.apply(String, arguments))).svg; + return root.removeChild(root.firstChild); }; } diff --git a/public/style.css b/public/style.css index 823d25245..0225196c9 100644 --- a/public/style.css +++ b/public/style.css @@ -308,6 +308,9 @@ pre { } pre .language-md::after, +pre .language-tex::after, +pre .language-dot::after, +pre .language-mermaid::after, pre .language-js::after, pre .language-sh::after, pre .language-sql::after { @@ -321,21 +324,13 @@ pre .language-sql::after { padding: 0.5rem 0.5rem 0.5rem 1rem; } -pre .language-md::after { - content: "md"; -} - -pre .language-js::after { - content: "js"; -} - -pre .language-sh::after { - content: "sh"; -} - -pre .language-sql::after { - content: "sql"; -} +pre .language-md::after { content: "md"; } +pre .language-js::after { content: "js"; } +pre .language-sh::after { content: "sh"; } +pre .language-sql::after { content: "sql"; } +pre .language-tex::after { content: "tex"; } +pre .language-dot::after { content: "dot"; } +pre .language-mermaid::after { content: "mermaid"; } input:not([type]), input[type="email"], diff --git a/src/render.ts b/src/render.ts index c4b3f5b45..240c62286 100644 --- a/src/render.ts +++ b/src/render.ts @@ -118,6 +118,7 @@ function getImportPreloads(parseResult: ParseResult): Iterable { if (inputs.has("Inputs")) specifiers.add("npm:@observablehq/inputs"); if (inputs.has("dot")) specifiers.add("npm:@viz-js/viz"); if (inputs.has("mermaid")) specifiers.add("npm:mermaid").add("npm:d3"); + if (inputs.has("tex")) specifiers.add("npm:katex"); const preloads: string[] = []; for (const specifier of specifiers) { preloads.push(resolveImport(specifier));