@wxxb789/anc 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +295 -0
- package/astro.config.mjs +255 -0
- package/bin/anc.mjs +798 -0
- package/package.json +93 -0
- package/public/_headers +45 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +9 -0
- package/public/fonts/Temml.woff2 +0 -0
- package/scripts/build-snapshot.js +69 -0
- package/scripts/build-wasm.js +67 -0
- package/scripts/build-workspace.js +50 -0
- package/scripts/copy-snapshot.js +48 -0
- package/scripts/copy-wasm.js +57 -0
- package/scripts/emit-redirects.js +64 -0
- package/scripts/generate-corpus.js +421 -0
- package/scripts/init-repository.js +249 -0
- package/scripts/load-config.js +975 -0
- package/scripts/markdown-to-artifact.js +1286 -0
- package/scripts/preview-site.js +364 -0
- package/scripts/publish-set-review.js +157 -0
- package/scripts/resolve-links.js +640 -0
- package/scripts/run-pagefind.js +125 -0
- package/scripts/scan-residue.js +1016 -0
- package/scripts/scan-secrets.js +268 -0
- package/scripts/seed-gitignore.js +410 -0
- package/scripts/snapshot-rows.js +205 -0
- package/scripts/validate-content.js +95 -0
- package/scripts/vendor-provenance.js +307 -0
- package/scripts/verify-output-inventory.js +307 -0
- package/scripts/write-report.js +351 -0
- package/scripts/write-snapshot.js +136 -0
- package/src/components/CollectionExplorer.astro +149 -0
- package/src/components/FacetIndex.astro +45 -0
- package/src/components/LinkedNotes.astro +94 -0
- package/src/components/NoteGraph.astro +434 -0
- package/src/components/NoteList.astro +54 -0
- package/src/components/SiteMetadata.astro +143 -0
- package/src/components/TableOfContents.astro +69 -0
- package/src/components/TagBrowser.astro +70 -0
- package/src/layouts/Layout.astro +305 -0
- package/src/lib/artifact-source.js +72 -0
- package/src/lib/build-bindings.js +11 -0
- package/src/lib/collection-navigation.js +145 -0
- package/src/lib/content.js +92 -0
- package/src/lib/diagram-mode.js +18 -0
- package/src/lib/graph-layout.js +219 -0
- package/src/lib/graph-selection.js +84 -0
- package/src/lib/graph.js +42 -0
- package/src/lib/link-resolution.js +389 -0
- package/src/lib/markdown.js +1469 -0
- package/src/lib/math-mode.js +31 -0
- package/src/lib/math-output.js +131 -0
- package/src/lib/math.js +165 -0
- package/src/lib/mermaid-environment.js +475 -0
- package/src/lib/mermaid-render.js +769 -0
- package/src/lib/preview-model.js +96 -0
- package/src/lib/relations.js +242 -0
- package/src/lib/rendered-marker.js +55 -0
- package/src/lib/route-path.js +192 -0
- package/src/lib/routes.js +342 -0
- package/src/lib/schema.js +578 -0
- package/src/lib/site.js +578 -0
- package/src/lib/snapshot-contract.js +216 -0
- package/src/lib/snapshot-operations.js +147 -0
- package/src/lib/snapshot-queries.js +216 -0
- package/src/lib/snapshot-reader.js +210 -0
- package/src/lib/snapshot.js +233 -0
- package/src/lib/sqlite-warning.js +22 -0
- package/src/lib/sqlite.js +14 -0
- package/src/lib/tag-browser-model.js +18 -0
- package/src/lib/translations.js +533 -0
- package/src/lib/wasm-asset.js +47 -0
- package/src/lib/worker-protocol.js +93 -0
- package/src/pages/404.astro +35 -0
- package/src/pages/about.astro +100 -0
- package/src/pages/collections/[slug].astro +36 -0
- package/src/pages/collections/index.astro +33 -0
- package/src/pages/graph.astro +64 -0
- package/src/pages/index.astro +33 -0
- package/src/pages/notes/[slug].astro +509 -0
- package/src/pages/privacy.astro +121 -0
- package/src/pages/private.astro +59 -0
- package/src/pages/recent.astro +33 -0
- package/src/pages/robots.txt.js +15 -0
- package/src/pages/rss.xml.js +21 -0
- package/src/pages/sitemap.xml.js +20 -0
- package/src/pages/tags/[tag].astro +56 -0
- package/src/pages/tags/index.astro +37 -0
- package/src/scripts/code-copy.js +45 -0
- package/src/scripts/diagram-disabled.js +16 -0
- package/src/scripts/diagram.js +322 -0
- package/src/scripts/graph-client.js +222 -0
- package/src/scripts/graph-draw.js +166 -0
- package/src/scripts/link-preview.js +334 -0
- package/src/scripts/math-disabled.js +18 -0
- package/src/scripts/math.js +133 -0
- package/src/scripts/preferences.js +123 -0
- package/src/scripts/search-dialog.js +651 -0
- package/src/scripts/snapshot-client.js +157 -0
- package/src/scripts/snapshot-worker.js +341 -0
- package/src/scripts/tag-browser.js +153 -0
- package/src/scripts/theme-init.js +35 -0
- package/src/scripts/toc-active.js +38 -0
- package/src/styles/code.css +152 -0
- package/src/styles/diagram.css +149 -0
- package/src/styles/global.css +1739 -0
- package/src/styles/graph.css +317 -0
- package/src/styles/math.css +376 -0
- package/src/styles/tokens.css +171 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const MATH_MODE = 'client';
|
|
2
|
+
/*
|
|
3
|
+
* The costs carried by the selected client mode.
|
|
4
|
+
*
|
|
5
|
+
* Unlike Mermaid, temml needs no relaxation at all — measured at 0 CSP
|
|
6
|
+
* violations across every expression tried — and its single chunk carries no
|
|
7
|
+
* `[[`, no `data:` URL, and no drive-letter-shaped string, measured against the
|
|
8
|
+
* residue scan.
|
|
9
|
+
*
|
|
10
|
+
* What remains is the cost this selected mode moves onto the reader, recorded so
|
|
11
|
+
* the choice stays explicit rather than becoming an unexplained default:
|
|
12
|
+
*
|
|
13
|
+
* 1. **~116 KB gzip per math page**, against 0 in build-time mode. A page with
|
|
14
|
+
* no math pays nothing either way; the loader is gated on `hasMath`.
|
|
15
|
+
*
|
|
16
|
+
* **The received figure was ~59 KB and it is wrong**, which matters because
|
|
17
|
+
* it is the number the whole cost/benefit rests on. 59 KB is
|
|
18
|
+
* `temml.min.js`; the package's `exports` map sends `import('temml')` to the
|
|
19
|
+
* unminified `temml.cjs`/`temml.mjs`, and that is **115,541 B gzip**
|
|
20
|
+
* measured. Astro's bundler minifies what it emits, so the shipped figure
|
|
21
|
+
* should land nearer the 50 KB mark — but that is a prediction, and the
|
|
22
|
+
* number recorded here is the one that has actually been measured. Anyone
|
|
23
|
+
* flipping the mode should measure the emitted chunk rather than trust
|
|
24
|
+
* either.
|
|
25
|
+
* 2. **The TeX source is excluded from the search index**
|
|
26
|
+
* (`scripts/run-pagefind.ts`), so a reader cannot find a note by an
|
|
27
|
+
* expression in it. That is a real loss and it is stated where it is taken.
|
|
28
|
+
* 3. **A reader with JavaScript disabled sees the TeX source**, not typeset
|
|
29
|
+
* mathematics — the same bargain client diagrams make with their Mermaid
|
|
30
|
+
* source, and what the owner chose.
|
|
31
|
+
*/
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Temml post-processing both rendering modes need, with no Temml in it.
|
|
3
|
+
*
|
|
4
|
+
* `src/lib/math.ts` owns the build-time path and imports Temml, so a client
|
|
5
|
+
* runtime importing that module would pull Temml's server-side entry into the
|
|
6
|
+
* page bundle. These two functions are pure string transforms over Temml's
|
|
7
|
+
* output, so they live here and both modes call them.
|
|
8
|
+
*
|
|
9
|
+
* **Extracted because the client path was measured missing them, and the
|
|
10
|
+
* consequence was larger than any brief predicted.** Client mode without
|
|
11
|
+
* {@link extractStyles} lets every `style` attribute Temml writes reach a live
|
|
12
|
+
* DOM `setAttribute`, which is exactly what `style-src 'self'` blocks. Measured
|
|
13
|
+
* in Chromium under the shipped policy, violations per expression:
|
|
14
|
+
*
|
|
15
|
+
* | expression | violations |
|
|
16
|
+
* | --- | --- |
|
|
17
|
+
* | `x^2 + y^2 = z^2` | 0 |
|
|
18
|
+
* | `\begin{pmatrix} a & b \\ c & d \end{pmatrix}` | **4** |
|
|
19
|
+
* | `\begin{aligned}`, `\begin{array}` | **4** each |
|
|
20
|
+
* | `\boxed{E=mc^2}` | **1** |
|
|
21
|
+
* | `\pmb{x}` | 1 |
|
|
22
|
+
* | `\sideset{_a^b}{_c^d}\sum` | 2 |
|
|
23
|
+
*
|
|
24
|
+
* The received account was that `\pmb` and `\sideset` are the only violating
|
|
25
|
+
* commands, via three `MathNode.setAttribute` sites in `temml.mjs`. That is true
|
|
26
|
+
* of those three sites and false as a description of the whole: an ordinary
|
|
27
|
+
* matrix violates four times and goes nowhere near them. Running the same
|
|
28
|
+
* extraction the build-time path already runs removes the padding and border
|
|
29
|
+
* cases entirely and leaves the genuinely irreducible pair.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Inline declarations Temml emits that a class must reproduce, because dropping
|
|
33
|
+
* them changes the rendered geometry.
|
|
34
|
+
*
|
|
35
|
+
* The whole inline surface is 39 distinct declarations over a 77-expression
|
|
36
|
+
* corpus in both display and inline mode. Reproducing all 39 was rejected:
|
|
37
|
+
* measured in Chromium against Temml's own stylesheet, dropping each property
|
|
38
|
+
* group and re-measuring, only three groups move the box at all. The rest —
|
|
39
|
+
* `math-depth`, `width`, every `padding-left`/`padding-right` on `mtd`, and
|
|
40
|
+
* `color` — measured **0.0% deviation on all 24 test expressions**, because
|
|
41
|
+
* MathML Core's user-agent stylesheet already computes them. A class per
|
|
42
|
+
* declaration would have been 39 rules of which 36 changed nothing.
|
|
43
|
+
*
|
|
44
|
+
* What survives, with what it costs to drop it:
|
|
45
|
+
*
|
|
46
|
+
* | Declaration | Carrier | Dropping it |
|
|
47
|
+
* | --- | --- | --- |
|
|
48
|
+
* | `display:block math` | `math.tml-display` | up to 9% on a wide array |
|
|
49
|
+
* | `padding` / `padding-top` / `padding-bottom` | `mtd`, `mrow`, `mpadded` | up to 32% on `align`, `gather`, `bordermatrix` |
|
|
50
|
+
* | `border` / `border-right` / `border-bottom` | `mrow`, `mtd` | up to 8% on `\boxed`, and a lost rule in `array` |
|
|
51
|
+
*
|
|
52
|
+
* Each is reproduced by a rule in `src/styles/math.css` keyed on an element and
|
|
53
|
+
* a class this module writes. `display:block math` is additionally implied by
|
|
54
|
+
* `math.tml-display`, which Temml sets itself, and the `array` borders are
|
|
55
|
+
* structural rather than author-chosen.
|
|
56
|
+
*
|
|
57
|
+
* One value in the set is genuinely dynamic — `\raisebox`'s
|
|
58
|
+
* `padding: 2.8892pt 0 0 0`, whose length comes from the author — and it is
|
|
59
|
+
* *dropped*, not reproduced. The raise survives anyway, because Temml also
|
|
60
|
+
* emits `voffset`, a real MathML attribute that the sanitizer keeps and that
|
|
61
|
+
* does the same job. Nothing else in the set depends on author input.
|
|
62
|
+
*/
|
|
63
|
+
export const MATH_STYLE_CLASSES = {
|
|
64
|
+
'border:1px solid': 'math-boxed',
|
|
65
|
+
'padding:3pt': 'math-boxed-pad',
|
|
66
|
+
'border-right:0.06em solid': 'math-cell-rule-right',
|
|
67
|
+
'border-bottom:0.06em solid': 'math-cell-rule-bottom',
|
|
68
|
+
'padding:0': 'math-cell-tight',
|
|
69
|
+
'padding-top:0': 'math-cell-tight-top',
|
|
70
|
+
'padding-bottom:0': 'math-cell-tight-bottom',
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Rewrite every inline `style` attribute into the classes above, dropping the
|
|
74
|
+
* declarations that measured no layout effect.
|
|
75
|
+
*
|
|
76
|
+
* Each declaration inside an attribute is looked up on its own, because Temml
|
|
77
|
+
* groups several onto one element: an `array` cell carries
|
|
78
|
+
* `border-bottom:0.06em solid;padding-left:0pt;padding-right:5.9776pt;
|
|
79
|
+
* border-right:0.06em solid` in a single `style`. A declaration the table does
|
|
80
|
+
* not name is dropped — which is what the sanitizer would have done anyway at
|
|
81
|
+
* build time, the difference being that here it is a decision with a measurement
|
|
82
|
+
* behind it rather than an accident of the allowlist. The `mtd` paddings are
|
|
83
|
+
* exactly that case: measured at 0.0% layout effect across 24 expressions,
|
|
84
|
+
* because MathML Core's user-agent stylesheet already supplies them.
|
|
85
|
+
*
|
|
86
|
+
* **In client mode the sanitizer is not there at all**, which is what makes this
|
|
87
|
+
* function load-bearing rather than tidy: the string goes into `innerHTML`, so
|
|
88
|
+
* an unextracted `style` becomes a real attribute and a real CSP violation.
|
|
89
|
+
*/
|
|
90
|
+
export function extractStyles(html) {
|
|
91
|
+
return html.replace(/<([a-zA-Z0-9:-]+)((?:[^>"']|"[^"]*"|'[^']*')*?)\sstyle="([^"]*)"/g, (_match, tag, rest, css) => {
|
|
92
|
+
const classes = [];
|
|
93
|
+
for (const declaration of css.split(';')) {
|
|
94
|
+
const trimmed = declaration.trim();
|
|
95
|
+
if (trimmed === '')
|
|
96
|
+
continue;
|
|
97
|
+
const name = MATH_STYLE_CLASSES[trimmed];
|
|
98
|
+
if (name !== undefined && !classes.includes(name))
|
|
99
|
+
classes.push(name);
|
|
100
|
+
}
|
|
101
|
+
if (classes.length === 0)
|
|
102
|
+
return `<${tag}${rest}`;
|
|
103
|
+
const existing = /\sclass="([^"]*)"/.exec(rest);
|
|
104
|
+
if (existing) {
|
|
105
|
+
return `<${tag}${rest.replace(/\sclass="([^"]*)"/, ` class="$1 ${classes.join(' ')}"`)}`;
|
|
106
|
+
}
|
|
107
|
+
return `<${tag}${rest} class="${classes.join(' ')}"`;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* `\rule` draws a bar whose colour Temml hardcodes as
|
|
112
|
+
* `<mspace mathbackground="black">`. On the dark palette that is a black bar on
|
|
113
|
+
* a `#0d1013` background — invisible, and invisible in a way no gate would see,
|
|
114
|
+
* because the element is present and correctly sized.
|
|
115
|
+
*
|
|
116
|
+
* `currentColor` is the fix and it is exact rather than approximate: MathML
|
|
117
|
+
* resolves it against the inherited text colour, which is `--color-text` in
|
|
118
|
+
* both themes, so the rule renders in the same ink as the expression around it.
|
|
119
|
+
* That is what `\rule` means — a bar drawn in the current pen — and it is what
|
|
120
|
+
* every other Temml border already does, via `border-color: currentColor` in
|
|
121
|
+
* Temml's own stylesheet.
|
|
122
|
+
*
|
|
123
|
+
* Narrow by construction: only `mathbackground`, only the literal `black`
|
|
124
|
+
* Temml writes. An author cannot reach this attribute — `\colorbox`, the one
|
|
125
|
+
* command that sets a background of its own choosing, is rejected by
|
|
126
|
+
* `src/lib/math.ts` at build time and cannot produce a colour Temml would put
|
|
127
|
+
* here.
|
|
128
|
+
*/
|
|
129
|
+
export function currentColorRules(html) {
|
|
130
|
+
return html.replaceAll('<mspace mathbackground="black"', '<mspace mathbackground="currentColor"');
|
|
131
|
+
}
|
package/src/lib/math.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TeX to MathML at build time, with no client JavaScript.
|
|
3
|
+
*
|
|
4
|
+
* Owner decision 2 settles the engine: Temml, not KaTeX. Temml emits native
|
|
5
|
+
* MathML, which every current browser lays out itself — MathML Core has been
|
|
6
|
+
* Baseline widely available since January 2023 — so the page ships markup and a
|
|
7
|
+
* ~9 KB stylesheet instead of a 23 KB stylesheet, a webfont set, and a script.
|
|
8
|
+
* KaTeX's default HTML output additionally puts an inline `style` on nearly
|
|
9
|
+
* every span, which `style-src 'self'` blocks.
|
|
10
|
+
*
|
|
11
|
+
* Three properties are load-bearing:
|
|
12
|
+
*
|
|
13
|
+
* 1. **Zero inline styles reach the page.** Temml writes a small, closed set of
|
|
14
|
+
* `style` attributes. `sanitize-html` drops every one of them, so what has to
|
|
15
|
+
* be decided is which of them *matter*, not how to preserve them all. See
|
|
16
|
+
* {@link STYLE_CLASSES}.
|
|
17
|
+
* 2. **Rendering is deterministic.** Temml mints no ids, reads no clock, and
|
|
18
|
+
* calls no random source: the same TeX yields byte-identical MathML, verified
|
|
19
|
+
* across renders and across processes.
|
|
20
|
+
* 3. **A rejected expression fails loudly.** `throwOnError` is on, so a
|
|
21
|
+
* malformed expression aborts the build rather than shipping Temml's own
|
|
22
|
+
* error markup, which is a red `<mtext>` carrying an inline `color` that the
|
|
23
|
+
* sanitizer would then strip — leaving an unmarked, unreadable fragment.
|
|
24
|
+
*/
|
|
25
|
+
import temml from 'temml';
|
|
26
|
+
import { currentColorRules, extractStyles, MATH_STYLE_CLASSES } from "./math-output.js";
|
|
27
|
+
export { MATH_STYLE_CLASSES };
|
|
28
|
+
/**
|
|
29
|
+
* TeX constructs rejected outright, each because it would put something in the
|
|
30
|
+
* document that the projection cannot vouch for.
|
|
31
|
+
*
|
|
32
|
+
* Temml already refuses `\href`, `\url`, `\includegraphics`, `\class`,
|
|
33
|
+
* `\cssId`, `\style`, and `\htmlData` under `trust: false` — verified, not
|
|
34
|
+
* assumed — so the only rejection this module has to make itself is colour.
|
|
35
|
+
*
|
|
36
|
+
* **`\textcolor`, `\color`, and `\colorbox` are rejected rather than mapped.**
|
|
37
|
+
* They are the one unbounded, author-chosen part of Temml's output: every other
|
|
38
|
+
* inline declaration comes from a closed set, and these produce an arbitrary
|
|
39
|
+
* `color:#rrggbb`. There is no correct thing to do with such a value on a site
|
|
40
|
+
* with a light and a dark palette. Keeping it fails contrast in one theme —
|
|
41
|
+
* `\textcolor{red}` on `#0d1013` is 3.9:1, below the 4.5:1 AA floor, and
|
|
42
|
+
* `\textcolor{yellow}` on `#fbfaf7` is 1.1:1 in the other direction. Mapping it
|
|
43
|
+
* onto a palette token silently renders something other than what the author
|
|
44
|
+
* wrote. Dropping it silently — which is what the sanitizer does today — makes
|
|
45
|
+
* the emphasis vanish with no signal, and colour was in all likelihood the only
|
|
46
|
+
* thing distinguishing that term.
|
|
47
|
+
*
|
|
48
|
+
* So the build fails and names the expression. An author who wants emphasis
|
|
49
|
+
* inside math has `\mathbf`, `\underline`, `\boxed`, and `\overbrace`, all of
|
|
50
|
+
* which survive both themes and are not colour-alone distinctions (WCAG 1.4.1).
|
|
51
|
+
* Requirements §15.2 permits exactly this: a construct that cannot be rendered
|
|
52
|
+
* safely fails export rather than degrading silently.
|
|
53
|
+
*
|
|
54
|
+
* **The list is a courtesy, not the guarantee.** It exists to give an author a
|
|
55
|
+
* message naming the command they wrote. What actually holds the property is
|
|
56
|
+
* {@link assertNoAuthoredColour}, which inspects the rendered output — because
|
|
57
|
+
* a name list cannot be complete, and was not: `\fcolorbox` is a fifth colour
|
|
58
|
+
* command that this list originally missed, and it was *worse* than the four
|
|
59
|
+
* here, because it emits `mathbackground="#FFFF00"` as an attribute rather than
|
|
60
|
+
* a style, so the extraction pass never saw it and the zero-inline-styles gate
|
|
61
|
+
* stayed green while an author-chosen yellow shipped at 1.1:1 on the light
|
|
62
|
+
* palette. A blocklist in a module whose own policy is allowlists was the bug.
|
|
63
|
+
*/
|
|
64
|
+
const REJECTED_COMMANDS = [
|
|
65
|
+
'\\textcolor',
|
|
66
|
+
'\\color',
|
|
67
|
+
'\\colorbox',
|
|
68
|
+
'\\fcolorbox',
|
|
69
|
+
'\\pagecolor',
|
|
70
|
+
];
|
|
71
|
+
/**
|
|
72
|
+
* MathML attributes that carry a colour, and the only value this projection
|
|
73
|
+
* permits in one.
|
|
74
|
+
*
|
|
75
|
+
* `currentColor` resolves against the inherited text colour, which is
|
|
76
|
+
* `--color-text` in both themes — so it is the one value that cannot fail
|
|
77
|
+
* contrast, because it *is* the colour the surrounding prose already uses.
|
|
78
|
+
* Anything else is a literal an author chose, and the whole argument above says
|
|
79
|
+
* such a literal cannot hold contrast in both palettes.
|
|
80
|
+
*
|
|
81
|
+
* Checked over the rendered output rather than the source, which is what makes
|
|
82
|
+
* it total: it does not matter which command produced the colour, whether that
|
|
83
|
+
* command is named in {@link REJECTED_COMMANDS}, or whether a future Temml
|
|
84
|
+
* version adds a sixth way to set one.
|
|
85
|
+
*/
|
|
86
|
+
const COLOUR_ATTRIBUTES = ['mathbackground', 'mathcolor', 'color', 'background'];
|
|
87
|
+
function assertNoAuthoredColour(tex, html) {
|
|
88
|
+
for (const attribute of COLOUR_ATTRIBUTES) {
|
|
89
|
+
for (const [, value] of html.matchAll(new RegExp(`\\s${attribute}="([^"]*)"`, 'g'))) {
|
|
90
|
+
if (value !== 'currentColor') {
|
|
91
|
+
throw new MathRejectedError(tex, `the expression sets an author-chosen colour (${attribute}="${value}"), which cannot ` +
|
|
92
|
+
'hold contrast in both themes. Use \\mathbf, \\underline, \\boxed, or \\overbrace, ' +
|
|
93
|
+
'which survive both.');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Thrown when an expression uses a construct this projection rejects.
|
|
100
|
+
*
|
|
101
|
+
* Fields are assigned in the body rather than declared as parameter
|
|
102
|
+
* properties: `node --experimental-strip-types` — which is how every script in
|
|
103
|
+
* `scripts/` runs — rejects that syntax outright, because erasing the types
|
|
104
|
+
* would change the runtime behaviour rather than only the annotations.
|
|
105
|
+
*/
|
|
106
|
+
export class MathRejectedError extends Error {
|
|
107
|
+
tex;
|
|
108
|
+
reason;
|
|
109
|
+
constructor(tex, reason) {
|
|
110
|
+
super(`math rejected: ${reason}\n in: ${tex.trim().slice(0, 200)}`);
|
|
111
|
+
this.name = 'MathRejectedError';
|
|
112
|
+
this.tex = tex;
|
|
113
|
+
this.reason = reason;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* One expression's MathML.
|
|
118
|
+
*
|
|
119
|
+
* @param tex The expression body, without delimiters.
|
|
120
|
+
* @param isDisplay Display math (`$$…$$`) rather than inline.
|
|
121
|
+
* @throws {MathRejectedError} when the expression uses a rejected construct or
|
|
122
|
+
* Temml cannot parse it. Both abort the build; see the note on `throwOnError`.
|
|
123
|
+
*/
|
|
124
|
+
export function renderMath(tex, isDisplay) {
|
|
125
|
+
for (const command of REJECTED_COMMANDS) {
|
|
126
|
+
// Word-boundary on the command name, so `\colorbox` does not match inside
|
|
127
|
+
// `\colorboxes` and `\color` does not match inside `\colorbox` — the longer
|
|
128
|
+
// name is listed and would be reported under its own entry either way.
|
|
129
|
+
if (new RegExp(`${command.replace('\\', '\\\\')}(?![a-zA-Z])`).test(tex)) {
|
|
130
|
+
throw new MathRejectedError(tex, `${command} sets an author-chosen colour, which cannot hold contrast in both themes. ` +
|
|
131
|
+
'Use \\mathbf, \\underline, \\boxed, or \\overbrace, which survive both.');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
let html;
|
|
135
|
+
try {
|
|
136
|
+
html = temml.renderToString(tex, {
|
|
137
|
+
displayMode: isDisplay,
|
|
138
|
+
// A parse failure must stop the build. Temml's fallback is markup carrying
|
|
139
|
+
// an inline `color`, which the sanitizer strips — so the page would show
|
|
140
|
+
// the broken source with nothing marking it as broken.
|
|
141
|
+
throwOnError: true,
|
|
142
|
+
// Refuses `\href`, `\url`, `\includegraphics`, `\class`, `\cssId`,
|
|
143
|
+
// `\style`, and `\htmlData`: every command that would inject a URL, a
|
|
144
|
+
// class, or an attribute of the author's choosing.
|
|
145
|
+
trust: false,
|
|
146
|
+
// Bounds the two expansion vectors a hostile or looping macro would use.
|
|
147
|
+
// Temml's own defaults; restated because they are a security property
|
|
148
|
+
// rather than a rendering preference.
|
|
149
|
+
maxExpand: 1000,
|
|
150
|
+
maxSize: [500, 500],
|
|
151
|
+
// No `<annotation>` copy of the TeX source. It would double the byte cost
|
|
152
|
+
// of every expression and put a second copy of the text into the Pagefind
|
|
153
|
+
// index, where it reads as noise next to the rendered form.
|
|
154
|
+
annotate: false,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
throw new MathRejectedError(tex, error instanceof Error ? error.message.split('\n')[0] : String(error));
|
|
159
|
+
}
|
|
160
|
+
const extracted = currentColorRules(extractStyles(html));
|
|
161
|
+
// Last, and over the *output*: whatever produced a colour, and whichever
|
|
162
|
+
// command spelled it, it does not ship unless it is `currentColor`.
|
|
163
|
+
assertNoAuthoredColour(tex, extracted);
|
|
164
|
+
return extracted;
|
|
165
|
+
}
|