@pretextbook/pretext-html 0.2.0 → 0.3.0

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/README.md CHANGED
@@ -219,11 +219,31 @@ the message helper.
219
219
  | JSPI required (flagged on Node) | Loader fetches resources mid-transform via JSPI | Feature-detect with `isJspiAvailable()`; engines without it need a fallback |
220
220
  | No generated images (latex-image, sageplot, …) | Produced by the Python toolchain, not XSLT | Out of scope; run `pretext generate` and the preview will pick the files up |
221
221
  | `xi:include` with `xpointer` unsupported | JS resolver stands in for libxml2's | Rebuild adding `xmlXIncludeProcessFlags` to the JSPI export list |
222
+ | Renders run one at a time, never in parallel | Shared libxslt state; the transform suspends mid-run | Inherent; `renderHtml` queues concurrent calls for you |
222
223
 
223
224
  The whole-book stack overflow ("memory access out of bounds") that the stock
224
225
  `libxslt-wasm` build hit on large documents is **fixed** in the
225
226
  `@pretextbook/libxslt-wasm` fork, which links with a larger WASM stack.
226
227
 
228
+ ### Concurrency
229
+
230
+ A render is not reentrant. It drives a single cached compiled stylesheet
231
+ through a patched `globalThis.fetch` and shared mount tables, and it *suspends*
232
+ mid-transform to fetch stylesheets — which is precisely the window in which
233
+ another render would get to run. Overlapping renders interleave inside libxslt
234
+ and corrupt it.
235
+
236
+ `renderHtml` therefore **queues**: concurrent calls are safe, and each resolves
237
+ with its own result, but they execute one at a time rather than in parallel. A
238
+ render that rejects does not stall the queue.
239
+
240
+ This matters mainly if you drive the internals yourself. If you do, serialize
241
+ your own calls — the corruption is not self-describing: it surfaces as an
242
+ out-of-bounds memory fault, and afterwards the WASM instance aborts on *every*
243
+ later call for the lifetime of the process. Once that has happened, only a
244
+ restart (or a page reload) recovers; `renderHtml` detects the state and says so
245
+ rather than repeating the underlying assertion.
246
+
227
247
  Missing generated assets degrade gracefully: the transform serves a stub for
228
248
  missing files, PreTeXt emits its usual `PTX:ERROR` advice, and the preview
229
249
  renders without the image.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Retargeting the asset URLs PreTeXt emits.
3
+ *
4
+ * A portable build inlines `latex-image` and `prefigure` SVGs directly into
5
+ * the page (pretext-html.xsl, mode="svg-embedded"), so those need nothing from
6
+ * us — they are read at transform time through the fetch shim. Everything else
7
+ * — author-supplied images and media, sageplot, asymptote — is emitted as a
8
+ * *runtime* URL under one of two fixed prefixes:
9
+ *
10
+ * <img src="external/kitten.png">
11
+ * <img src="generated/sageplot/plot.svg">
12
+ *
13
+ * Those prefixes (`$external-directory` / `$generated-directory` in
14
+ * publisher-variables.xsl) name directories a real build populates by copying
15
+ * files next to the output HTML. The preview has no output directory, so the
16
+ * URLs dangle and the assets silently fail to load.
17
+ *
18
+ * The prefixes are hardcoded rather than derived from the publication file,
19
+ * which is what makes retargeting reliable: find them, and hand each path to a
20
+ * host that knows how to serve it — `Webview.asWebviewUri` under VS Code, a
21
+ * blob or hosted URL in a browser. Mapping the prefix back to a real directory
22
+ * needs the publication file's `@external`/`@generated`, which the renderer
23
+ * reports as {@link RenderResult.assetDirs}.
24
+ */
25
+ /** Which of PreTeXt's two asset directories a URL points into. */
26
+ export type AssetKind = "external" | "generated";
27
+ export { describeAsset, missingAssetPlaceholder, missingAssetSvg, } from './missing-asset.js';
28
+ export type { MissingAsset } from './missing-asset.js';
29
+ /**
30
+ * Maps one asset path to a URL the host can actually load.
31
+ *
32
+ * `relPath` is percent-decoded and relative to the directory `kind` names
33
+ * (e.g. `"kitten.png"`, `"sageplot/plot.svg"`). Return `undefined` to leave
34
+ * the URL untouched — appropriate when the file does not exist, so the page
35
+ * keeps PreTeXt's own broken-image markup rather than pointing at a URL that
36
+ * will 404 anyway.
37
+ */
38
+ export type AssetUrlResolver = (kind: AssetKind, relPath: string) => string | undefined;
39
+ /**
40
+ * Rewrite every `external/`- or `generated/`-prefixed asset URL in `html`
41
+ * through `resolve`. Paths the resolver declines are left untouched.
42
+ */
43
+ export declare function rewriteAssetUrls(html: string, resolve: AssetUrlResolver): string;
package/dist/assets.js ADDED
@@ -0,0 +1,76 @@
1
+ const l = {
2
+ png: "image",
3
+ jpg: "image",
4
+ jpeg: "image",
5
+ gif: "image",
6
+ svg: "image",
7
+ webp: "image",
8
+ avif: "image",
9
+ mp4: "video",
10
+ webm: "video",
11
+ ogv: "video",
12
+ mov: "video",
13
+ mp3: "audio",
14
+ wav: "audio",
15
+ ogg: "audio",
16
+ pdf: "PDF"
17
+ };
18
+ function p(e, t) {
19
+ const n = t.split("/").filter(Boolean), i = n[n.length - 1] ?? t;
20
+ if (e === "generated")
21
+ return {
22
+ kind: e,
23
+ assetType: n.length > 1 ? n[0] : "generated",
24
+ fileName: i
25
+ };
26
+ const r = i.split(".").pop()?.toLowerCase() ?? "";
27
+ return { kind: e, assetType: l[r] ?? "file", fileName: i };
28
+ }
29
+ function o(e) {
30
+ return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
31
+ }
32
+ function g(e, t = 38) {
33
+ return e.length <= t ? e : `${e.slice(0, t - 1)}…`;
34
+ }
35
+ function f(e) {
36
+ return e.kind === "generated" ? [
37
+ "Asset not generated yet",
38
+ `${e.assetType} · ${g(e.fileName)}`,
39
+ `Run: pretext generate ${e.assetType}`
40
+ ] : [
41
+ "External asset not found",
42
+ `${e.assetType} · ${g(e.fileName)}`,
43
+ "Check @source and the external directory"
44
+ ];
45
+ }
46
+ const d = "#fffbeb", u = "#f59e0b", m = "#92400e", s = "#b45309", a = "ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif";
47
+ function $(e) {
48
+ const [t, n, i] = f(e), r = `${t}: ${e.fileName}`;
49
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 420 130" role="img" aria-label="${o(r)}" data-ptx-missing="${o(`${e.kind}:${e.assetType}`)}"><rect x="1" y="1" width="418" height="128" rx="8" fill="${d}" stroke="${u}" stroke-width="2" stroke-dasharray="6 4"/><path d="M40 44 L60 80 L20 80 Z" fill="none" stroke="${s}" stroke-width="3" stroke-linejoin="round"/><rect x="38.5" y="56" width="3" height="12" fill="${s}"/><rect x="38.5" y="71" width="3" height="3" fill="${s}"/><text x="78" y="54" font-family="${a}" font-size="15" font-weight="600" fill="${m}">${o(t)}</text><text x="78" y="77" font-family="${a}" font-size="13" fill="${s}">${o(n)}</text><text x="78" y="99" font-family="${a}" font-size="12" fill="${s}" opacity="0.85">${o(i)}</text></svg>`;
50
+ }
51
+ function v(e, t) {
52
+ return `data:image/svg+xml,${$(p(e, t)).replace(/%/g, "%25").replace(/#/g, "%23").replace(/&/g, "%26").replace(/"/g, "%22").replace(/'/g, "%27").replace(/</g, "%3C").replace(/>/g, "%3E").replace(/\s+/g, " ")}`;
53
+ }
54
+ const h = /\bsrc="(external|generated)\/([^"]*)"/g;
55
+ function x(e) {
56
+ return e.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
57
+ }
58
+ function y(e) {
59
+ try {
60
+ return decodeURIComponent(e);
61
+ } catch {
62
+ return e;
63
+ }
64
+ }
65
+ function w(e, t) {
66
+ return e.replace(h, (n, i, r) => {
67
+ const c = t(i, y(r));
68
+ return c === void 0 ? n : `src="${x(c)}"`;
69
+ });
70
+ }
71
+ export {
72
+ p as describeAsset,
73
+ v as missingAssetPlaceholder,
74
+ $ as missingAssetSvg,
75
+ w as rewriteAssetUrls
76
+ };