@zinejs/pdf 1.0.1 → 1.0.2

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/dist/index.js CHANGED
@@ -1,10 +1,12 @@
1
- function e(e) {
1
+ //#region src/pdfSource.ts
2
+ var e = 16e6;
3
+ function t(e) {
2
4
  typeof HTMLCanvasElement < "u" && e instanceof HTMLCanvasElement && (e.width = 0, e.height = 0);
3
5
  }
4
- function t(e) {
6
+ function n(e) {
5
7
  return typeof e == "object" && !!e && "numPages" in e && typeof e.getPage == "function";
6
8
  }
7
- function n(e) {
9
+ function r(e) {
8
10
  try {
9
11
  let t = new URL(e, "http://x").pathname.split("/").filter(Boolean).pop();
10
12
  return t && /\.[a-z0-9]+$/i.test(t) ? decodeURIComponent(t) : "document.pdf";
@@ -12,7 +14,7 @@ function n(e) {
12
14
  return "document.pdf";
13
15
  }
14
16
  }
15
- async function r(e) {
17
+ async function i(e) {
16
18
  try {
17
19
  let t = await fetch(e, { method: "HEAD" });
18
20
  if (t.ok) return !0;
@@ -28,14 +30,14 @@ async function r(e) {
28
30
  return !1;
29
31
  }
30
32
  }
31
- function i(e, t) {
33
+ function a(e, t) {
32
34
  return `https://cdn.jsdelivr.net/npm/pdfjs-dist@${e}/${t}`;
33
35
  }
34
- var a = !1;
35
- function o(e) {
36
- a || (a = !0, import.meta.url && (typeof console > "u" || typeof console.warn != "function" || console.warn(`@zinejs/pdf: couldn't resolve the pdf.js worker from your bundler or install; loading it from a CDN (${e}). This fails under strict CSP or offline. Pass workerSrc (or set pdfjsLib.GlobalWorkerOptions.workerSrc) to self-host, or set cdnFallback: false to disable this fallback.`)));
36
+ var o = !1;
37
+ function s(e) {
38
+ o || (o = !0, import.meta.url && (typeof console > "u" || typeof console.warn != "function" || console.warn(`@zinejs/pdf: couldn't resolve the pdf.js worker from your bundler or install; loading it from a CDN (${e}). This fails under strict CSP or offline. Pass workerSrc (or set pdfjsLib.GlobalWorkerOptions.workerSrc) to self-host, or set cdnFallback: false to disable this fallback.`)));
37
39
  }
38
- async function s(e) {
40
+ async function c(e) {
39
41
  try {
40
42
  let t = (e ? await import(/* webpackIgnore: true */ 'pdfjs-dist/legacy/build/pdf.worker.min.mjs?url') : await import(/* webpackIgnore: true */ 'pdfjs-dist/build/pdf.worker.min.mjs?url')).default;
41
43
  return typeof t == "string" && t ? t : void 0;
@@ -43,54 +45,54 @@ async function s(e) {
43
45
  return;
44
46
  }
45
47
  }
46
- async function c(e) {
48
+ async function l(e) {
47
49
  try {
48
50
  let t = e ? (new URL('pdfjs-dist/legacy/build/pdf.worker.min.mjs', import.meta.url).href) : (new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).href);
49
- return await r(t) ? t : void 0;
51
+ return await i(t) ? t : void 0;
50
52
  } catch {
51
53
  return;
52
54
  }
53
55
  }
54
- async function l(e) {
56
+ async function u(e) {
55
57
  let t = import.meta.url;
56
58
  if (t) try {
57
59
  let n = new URL("../../../pdfjs-dist/" + e, t).href;
58
- return await r(n) ? n : void 0;
60
+ return await i(n) ? n : void 0;
59
61
  } catch {
60
62
  return;
61
63
  }
62
64
  }
63
- var u = {
64
- vite: s,
65
- bundled: c,
66
- sibling: l
65
+ var d = {
66
+ vite: c,
67
+ bundled: l,
68
+ sibling: u
67
69
  };
68
- async function d(e, t = u) {
70
+ async function f(e, t = d) {
69
71
  if (e.workerSrc) return e.workerSrc;
70
72
  if (e.configured) return e.configured;
71
73
  let n = e.legacy ? "legacy/build/pdf.worker.min.mjs" : "build/pdf.worker.min.mjs", r = await t.vite(e.legacy);
72
74
  if (r) return r;
73
- let a = await t.bundled(e.legacy);
74
- if (a) return a;
75
- let s = await t.sibling(n);
76
- if (s) return s;
75
+ let i = await t.bundled(e.legacy);
76
+ if (i) return i;
77
+ let o = await t.sibling(n);
78
+ if (o) return o;
77
79
  if (e.pdfjsVersion && e.cdnFallback) {
78
- let t = i(e.pdfjsVersion, n);
79
- return o(t), t;
80
+ let t = a(e.pdfjsVersion, n);
81
+ return s(t), t;
80
82
  }
81
83
  throw Error("PdfSource: couldn't auto-resolve pdf.js's worker. Pass a `workerSrc` URL (see the @zinejs/pdf README, \"The pdf.js worker\"), set pdfjs GlobalWorkerOptions.workerSrc yourself, enable `cdnFallback`, or pass a pre-created pdf.js document.");
82
84
  }
83
- function f(e) {
85
+ function p(e) {
84
86
  if (e.assetsUrl) return e.assetsUrl.endsWith("/") ? e.assetsUrl : e.assetsUrl + "/";
85
87
  let t = /^(.*\/pdfjs-dist(?:@[^/]+)?\/)(?:legacy\/)?build\/pdf\.worker[^/]*$/.exec(e.workerSrc.split(/[?#]/)[0] ?? "");
86
88
  if (t) return t[1];
87
- if (e.pdfjsVersion && e.cdnFallback) return i(e.pdfjsVersion, "");
89
+ if (e.pdfjsVersion && e.cdnFallback) return a(e.pdfjsVersion, "");
88
90
  }
89
- async function p(e) {
91
+ async function m(e) {
90
92
  let t = globalThis;
91
93
  return t.pdfjsLib ? t.pdfjsLib : e ? await import("pdfjs-dist/legacy/build/pdf.mjs") : await import("pdfjs-dist");
92
94
  }
93
- var m = class {
95
+ var h = class {
94
96
  pageCount = 0;
95
97
  #e;
96
98
  #t;
@@ -120,13 +122,13 @@ var m = class {
120
122
  this.#d = e;
121
123
  }
122
124
  async open() {
123
- if (t(this.#e)) this.#o = this.#e;
125
+ if (n(this.#e)) this.#o = this.#e;
124
126
  else {
125
- let e = await p(this.#u);
127
+ let e = await m(this.#u);
126
128
  e.GlobalWorkerOptions.workerSrc = await this.#v(e.GlobalWorkerOptions.workerSrc, e.version);
127
129
  let t = typeof this.#e == "string" ? { url: this.#e } : { data: this.#e };
128
130
  this.#l && (t.disableAutoFetch = !0);
129
- let n = f({
131
+ let n = p({
130
132
  assetsUrl: this.#r,
131
133
  workerSrc: e.GlobalWorkerOptions.workerSrc,
132
134
  cdnFallback: this.#n,
@@ -139,7 +141,7 @@ var m = class {
139
141
  this.pageCount = this.#o.numPages;
140
142
  }
141
143
  #v(e, t) {
142
- return d({
144
+ return f({
143
145
  workerSrc: this.#t,
144
146
  configured: e,
145
147
  legacy: this.#u,
@@ -202,7 +204,7 @@ var m = class {
202
204
  let e = this.#e;
203
205
  if (typeof e == "string") return {
204
206
  url: e,
205
- filename: n(e)
207
+ filename: r(e)
206
208
  };
207
209
  if (e instanceof ArrayBuffer || ArrayBuffer.isView(e)) {
208
210
  let t = new Blob([e], { type: "application/pdf" });
@@ -224,9 +226,9 @@ var m = class {
224
226
  }
225
227
  destroy() {
226
228
  this.#o?.destroy?.(), this.#f.clear();
227
- for (let t of this.#h.values()) t.then(e).catch(() => {});
229
+ for (let e of this.#h.values()) e.then(t).catch(() => {});
228
230
  this.#h.clear();
229
- for (let t of this.#g.values()) e(t);
231
+ for (let e of this.#g.values()) t(e);
230
232
  this.#g.clear(), this.#_.clear(), this.#p = 0, this.#o = null;
231
233
  }
232
234
  #x(e) {
@@ -251,13 +253,13 @@ var m = class {
251
253
  t === e || n.bytes === 0 || (this.#w(t, n), this.#f.delete(t), this.#p -= n.bytes);
252
254
  }
253
255
  }
254
- #w(t, n) {
256
+ #w(e, n) {
255
257
  let r = n.content ? this.#T(n.content) : null;
256
258
  if (!r) return;
257
- let i = this.#g.get(t);
258
- for (i && e(i), this.#g.delete(t), this.#g.set(t, r); this.#g.size > 200;) {
259
- let t = this.#g.keys().next().value;
260
- e(this.#g.get(t)), this.#g.delete(t);
259
+ let i = this.#g.get(e);
260
+ for (i && t(i), this.#g.delete(e), this.#g.set(e, r); this.#g.size > 200;) {
261
+ let e = this.#g.keys().next().value;
262
+ t(this.#g.get(e)), this.#g.delete(e);
261
263
  }
262
264
  }
263
265
  #T(e) {
@@ -268,30 +270,29 @@ var m = class {
268
270
  let a = i.getContext("2d");
269
271
  return a ? (a.drawImage(e, 0, 0, i.width, i.height), i) : null;
270
272
  }
271
- #E(t, n, r) {
272
- let i = Math.min(n, 6), a = `${t}@${i}`, o = this.#h.get(a);
273
+ #E(e, n, r) {
274
+ let i = Math.min(n, 6), a = `${e}@${i}`, o = this.#h.get(a);
273
275
  if (o) return o;
274
- let s = typeof devicePixelRatio == "number" ? devicePixelRatio : 1, c = this.#D(t, this.#i * s * i, r).catch((e) => {
276
+ let s = typeof devicePixelRatio == "number" ? devicePixelRatio : 1, c = this.#D(e, this.#i * s * i, r).catch((e) => {
275
277
  throw this.#h.delete(a), e;
276
278
  });
277
- for (let [t, n] of this.#h) t.endsWith(`@${i}`) || (this.#h.delete(t), n.then(e).catch(() => {}));
279
+ for (let [e, n] of this.#h) e.endsWith(`@${i}`) || (this.#h.delete(e), n.then(t).catch(() => {}));
278
280
  return this.#h.set(a, c), c;
279
281
  }
280
- async #D(e, t, n) {
282
+ async #D(t, n, r) {
281
283
  if (this.#o === null) throw Error("PdfSource: use after open() — the document is not loaded.");
282
- let r = await this.#o.getPage(e + 1);
283
- if (n !== void 0 && n > 0) {
284
- let e = r.getViewport({ scale: 1 }), i = n / Math.max(e.width, e.height);
285
- t = Math.min(t, i);
286
- }
287
- let i = r.getViewport({ scale: t }), a = document.createElement("canvas");
288
- return a.width = Math.ceil(i.width), a.height = Math.ceil(i.height), await r.render({
289
- canvasContext: a.getContext("2d"),
290
- viewport: i
291
- }).promise, a;
284
+ let i = await this.#o.getPage(t + 1), a = i.getViewport({ scale: 1 }), o = Math.max(a.width, a.height), s = typeof screen > "u" ? 0 : Math.max(screen.width, screen.height);
285
+ s > 0 && o > s && (n *= s / o), r !== void 0 && r > 0 && (n = Math.min(n, r / o));
286
+ let c = a.width * a.height * n * n;
287
+ c > e && (n *= Math.sqrt(e / c));
288
+ let l = i.getViewport({ scale: n }), u = document.createElement("canvas");
289
+ return u.width = Math.ceil(l.width), u.height = Math.ceil(l.height), await i.render({
290
+ canvasContext: u.getContext("2d"),
291
+ viewport: l
292
+ }).promise, i.cleanup?.(), u;
292
293
  }
293
294
  };
294
295
  //#endregion
295
- export { m as PdfSource };
296
+ export { h as PdfSource };
296
297
 
297
298
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["#src","#workerSrc","#cdnFallback","#assetsUrl","#renderScale","#preload","#maxCacheBytes","#progressive","#disableAutoFetch","#legacy","#onUpdate","#onProgress","#doc","#resolveWorkerSrc","#renderZoomed","#cache","#thumbs","#renderPage","#textCache","#extractText","#destinationPage","#zoomCache","#cachedBytes","#rasterize","#evictToFit","#upgrade","#stashThumb","#makeThumb"],"sources":["../src/pdfSource.ts"],"sourcesContent":["import type {\n DownloadInfo,\n LoadProgress,\n OutlineItem,\n PageContent,\n PageRequest,\n Source,\n} from '@zinejs/core';\n\n/** Minimal shapes of the pdf.js API we rely on (avoids a hard type dependency). */\ninterface PdfPageLike {\n getViewport(opts: { scale: number }): { width: number; height: number };\n /** `transform` is an affine matrix applied before painting, used to offset a cropped tile. */\n render(opts: { canvasContext: unknown; viewport: unknown; transform?: number[] }): {\n promise: Promise<void>;\n };\n /** Optional: absent on scanned PDFs with no text layer, and on hand-rolled document stubs. */\n getTextContent?(): Promise<{ items: { str?: string; hasEOL?: boolean }[] }>;\n}\n/** A pdf.js outline entry. `dest` is either a named destination or an explicit array whose first\n * element references the target page. */\ninterface PdfOutlineNode {\n title?: string;\n dest?: string | unknown[] | null;\n items?: PdfOutlineNode[];\n}\ninterface PdfDocumentLike {\n numPages: number;\n getPage(pageNumber: number): Promise<PdfPageLike>;\n destroy?(): void;\n /** Optional: absent on documents without a table of contents, and on hand-rolled stubs. */\n getOutline?(): Promise<PdfOutlineNode[] | null>;\n getDestination?(id: string): Promise<unknown[] | null>;\n getPageIndex?(ref: unknown): Promise<number>;\n}\ninterface DocParams {\n url?: string;\n data?: ArrayBuffer | Uint8Array;\n disableAutoFetch?: boolean;\n cMapUrl?: string;\n standardFontDataUrl?: string;\n iccUrl?: string;\n wasmUrl?: string;\n}\n/** pdf.js's loading task: the document promise, plus an assignable download-progress callback. */\ninterface PdfLoadingTask {\n promise: Promise<PdfDocumentLike>;\n onProgress?: (p: LoadProgress) => void;\n}\ninterface PdfjsModule {\n GlobalWorkerOptions: { workerSrc: string };\n getDocument(src: DocParams): PdfLoadingTask;\n /** Present on real pdf.js builds; used to pick a matching CDN worker as a last resort. */\n version?: string;\n}\n\n/** A URL, raw bytes, or a pre-created pdf.js document. */\nexport type PdfSrc = string | ArrayBuffer | Uint8Array | PdfDocumentLike;\n\nexport interface PdfSourceOptions {\n /** URL to pdf.js's worker. Optional: auto-resolved from your bundler (`?url` / `new URL`) or\n * `pdfjs-dist` install, then a version-matched CDN when `cdnFallback` is true. Pass it for\n * CDN / UMD / CSP / offline, or if auto-resolve fails. */\n workerSrc?: string;\n /** Allow falling back to a version-matched jsDelivr worker when no local worker can be\n * resolved (no bundler rewrite, no reachable install). Default true. Set false for\n * CSP-restricted or offline apps, where a network worker would fail opaquely — you must\n * then pass `workerSrc` (or set GlobalWorkerOptions.workerSrc). */\n cdnFallback?: boolean;\n /** Root of a `pdfjs-dist` copy holding its data folders: `cmaps/` (CJK and other non-embedded\n * encodings), `standard_fonts/` (PDFs that rely on the base-14 fonts), `iccs/` (CMYK colour)\n * and `wasm/` (JPEG 2000 and JBIG2 image decoders, pdf.js 5+). Without them pdf.js draws blank\n * images or wrong glyphs. Optional: derived from the worker URL when it points into a\n * `pdfjs-dist` install, then a version-matched CDN when `cdnFallback` is true. Pass it when you\n * self-host the worker under another path (copy those folders under one base). */\n assetsUrl?: string;\n /** Base render scale; each page is rasterized at `renderScale × devicePixelRatio`. Default 1. */\n renderScale?: number;\n /** How many adjacent pages to prefetch around a requested page. Default 1. */\n preload?: number;\n /** Soft cap on cached page bytes; least-recently-used pages evict beyond it. Default ~256 MB. */\n maxCacheBytes?: number;\n /** Paint a low-res page first, then swap to crisp (faster first paint). Default false. */\n progressive?: boolean;\n /** Ask pdf.js to fetch only the byte ranges visible pages need (range-capable servers). Default false. */\n disableAutoFetch?: boolean;\n /**\n * Load pdf.js's legacy build (transpiled with polyfills) instead of the modern one. The legacy\n * build runs on both old and modern browsers; the modern build is smaller and a touch faster but\n * needs a recent engine. Default true, favouring reach. Set false to serve the lean modern build\n * when your audience is on current browsers. Ignored when a pre-created pdf.js document or a\n * global `pdfjsLib` is supplied — those already picked their build.\n */\n legacy?: boolean;\n}\n\nconst DEFAULT_MAX_CACHE_BYTES = 256 * 1024 * 1024;\n// Linear-resolution fraction for the progressive low-res first pass (~1/9 the pixels).\nconst PROGRESSIVE_LOW_RATIO = 0.35;\n// Ceiling on zoom-tile magnification. Past roughly this point a screen pixel already holds more\n// detail than the reader can resolve, and rasterizing further only costs memory and time.\nconst MAX_ZOOM_RENDER_SCALE = 6;\n// Largest dimension (px) of the thumbnail kept when a full page is evicted from the byte cap, so\n// flipping back to an out-of-budget page paints a blurry placeholder at once instead of a blank\n// while it re-rasterizes. A few KB each.\nconst THUMB_MAX_PX = 96;\n// Bound on retained thumbnails (LRU), so a very large document does not accumulate them without end.\nconst MAX_THUMBS = 200;\n\n/** Free a canvas's backing store. Safari in particular holds the memory until the surface is\n * resized away, and a zoomed page is large enough for that to matter. */\nfunction releaseCanvas(content: PageContent): void {\n if (typeof HTMLCanvasElement !== 'undefined' && content instanceof HTMLCanvasElement) {\n content.width = 0;\n content.height = 0;\n }\n}\n\ninterface CacheEntry {\n promise: Promise<PageContent>;\n bytes: number; // 0 until the render resolves and its size is known\n content?: PageContent; // the resolved raster, kept so eviction can downscale it to a thumbnail\n}\n\nfunction isPdfDocument(src: PdfSrc): src is PdfDocumentLike {\n return (\n typeof src === 'object' &&\n src !== null &&\n 'numPages' in src &&\n typeof (src as PdfDocumentLike).getPage === 'function'\n );\n}\n\n/** Last path segment of a URL, for naming a download. Falls back when the URL has no filename\n * (a directory, a data: URI, or something unparseable). */\nfunction filenameFromUrl(url: string): string {\n try {\n const path = new URL(url, 'http://x').pathname;\n const last = path.split('/').filter(Boolean).pop();\n return last && /\\.[a-z0-9]+$/i.test(last) ? decodeURIComponent(last) : 'document.pdf';\n } catch {\n return 'document.pdf';\n }\n}\n\n/** True when `url` responds OK (HEAD, then GET). Used to pick a local worker path that exists. */\nasync function resourceExists(url: string): Promise<boolean> {\n try {\n const head = await fetch(url, { method: 'HEAD' });\n if (head.ok) return true;\n // Some hosts reject HEAD; a tiny ranged GET still proves the file is there.\n if (head.status === 405 || head.status === 501) {\n const get = await fetch(url, { method: 'GET', headers: { Range: 'bytes=0-0' } });\n return get.ok || get.status === 206;\n }\n return false;\n } catch {\n return false;\n }\n}\n\n/** @internal Version-matched jsDelivr URL for the pdf.js worker (CDN last resort). */\nexport function cdnWorkerUrl(version: string, subpath: string): string {\n return `https://cdn.jsdelivr.net/npm/pdfjs-dist@${version}/${subpath}`;\n}\n\nlet cdnWarned = false;\n\nfunction warnCdnOnce(cdn: string): void {\n if (cdnWarned) return;\n cdnWarned = true;\n // Only notable in an ES module: a bundler was in play and could have resolved the worker, yet we\n // still fell through to the CDN. In a classic <script> (UMD, no `import.meta.url`) there is no\n // bundler and the CDN is the expected fallback, so stay quiet — a CSP/offline failure there\n // announces itself through the browser and pdf.js anyway.\n if (!import.meta.url) return;\n if (typeof console === 'undefined' || typeof console.warn !== 'function') return;\n console.warn(\n `@zinejs/pdf: couldn't resolve the pdf.js worker from your bundler or install; ` +\n `loading it from a CDN (${cdn}). This fails under strict CSP or offline. ` +\n `Pass workerSrc (or set pdfjsLib.GlobalWorkerOptions.workerSrc) to self-host, ` +\n `or set cdnFallback: false to disable this fallback.`,\n );\n}\n\n/** @internal Reset the CDN warn-once flag between unit tests. Tree-shaken from the bundle. */\nexport function __resetCdnWarnedForTests(): void {\n cdnWarned = false;\n}\n\nasync function tryViteWorkerUrl(legacy: boolean): Promise<string | undefined> {\n // Vite (and Nuxt) rewrite `?url` imports to a served asset when they process the dep.\n // `webpackIgnore` keeps webpack/rspack from emitting an orphan worker chunk for this\n // Vite-only specifier; they throw at runtime, we catch, and the static new URL branch wins.\n try {\n const mod = legacy\n ? await import(\"@@ZINE_PDF_WORKER_IMPORT:pdfjs-dist/legacy/build/pdf.worker.min.mjs?url@@\")\n : await import(\"@@ZINE_PDF_WORKER_IMPORT:pdfjs-dist/build/pdf.worker.min.mjs?url@@\");\n const url = (mod as { default?: unknown }).default;\n return typeof url === 'string' && url ? url : undefined;\n } catch {\n return undefined;\n }\n}\n\nasync function tryBundledWorkerUrl(legacy: boolean): Promise<string | undefined> {\n // Webpack 5 / rspack: static package-specifier new URL is rewritten to an emitted asset.\n // Specifiers must be string literals (not concatenated) for the bundler to see them.\n // Vite leaves this as a runtime join against @zinejs/pdf/dist → 404; resourceExists skips it.\n try {\n const bundled = legacy\n ? \"@@ZINE_PDF_WORKER_NEWURL:pdfjs-dist/legacy/build/pdf.worker.min.mjs@@\"\n : \"@@ZINE_PDF_WORKER_NEWURL:pdfjs-dist/build/pdf.worker.min.mjs@@\";\n return (await resourceExists(bundled)) ? bundled : undefined;\n } catch {\n return undefined;\n }\n}\n\nasync function trySiblingWorkerUrl(subpath: string): Promise<string | undefined> {\n // From `@zinejs/pdf/dist/index.js`, `../../../pdfjs-dist` is the package's node_modules\n // sibling under both pnpm (nested) and npm/yarn (hoisted). Concatenate so Vite does not\n // treat this as an import.meta.url asset rewrite (those need a static relative literal).\n // UMD rewrites `import.meta` to `{}`, so `.url` is undefined — skip rather than\n // `new URL(..., undefined)` throwing into the catch.\n const base = import.meta.url;\n if (!base) return undefined;\n try {\n const local = new URL('../../../pdfjs-dist/' + subpath, base).href;\n return (await resourceExists(local)) ? local : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** @internal The local worker probes, in resolution order. Real functions in production; unit tests\n * pass a substitute set to {@link resolveWorkerSrc} to drive the CDN / opt-out branches. */\nexport type WorkerProbes = {\n vite: (legacy: boolean) => Promise<string | undefined>;\n bundled: (legacy: boolean) => Promise<string | undefined>;\n sibling: (subpath: string) => Promise<string | undefined>;\n};\n\nconst defaultWorkerProbes: WorkerProbes = {\n vite: tryViteWorkerUrl,\n bundled: tryBundledWorkerUrl,\n sibling: trySiblingWorkerUrl,\n};\n\n/**\n * @internal\n * Resolve pdf.js's worker URL: explicit `workerSrc` → already-configured global → local probes\n * (Vite `?url`, webpack `new URL`, sibling install) → version-matched CDN (when `cdnFallback`).\n * Exported (not from the package entry) so tests can inject probe results; production calls it via\n * {@link PdfSource} with {@link defaultWorkerProbes}, so no test-only code ships in the bundle.\n */\nexport async function resolveWorkerSrc(\n args: {\n workerSrc: string | undefined;\n configured: string;\n legacy: boolean;\n cdnFallback: boolean;\n pdfjsVersion: string | undefined;\n },\n probes: WorkerProbes = defaultWorkerProbes,\n): Promise<string> {\n if (args.workerSrc) return args.workerSrc;\n if (args.configured) return args.configured;\n\n const subpath = args.legacy ? 'legacy/build/pdf.worker.min.mjs' : 'build/pdf.worker.min.mjs';\n\n const vite = await probes.vite(args.legacy);\n if (vite) return vite;\n const bundled = await probes.bundled(args.legacy);\n if (bundled) return bundled;\n const sibling = await probes.sibling(subpath);\n if (sibling) return sibling;\n\n // Last resort: same version as the loaded library (avoids API/worker mismatch).\n if (args.pdfjsVersion && args.cdnFallback) {\n const cdn = cdnWorkerUrl(args.pdfjsVersion, subpath);\n warnCdnOnce(cdn);\n return cdn;\n }\n\n throw new Error(\n \"PdfSource: couldn't auto-resolve pdf.js's worker. Pass a `workerSrc` URL \" +\n '(see the @zinejs/pdf README, \"The pdf.js worker\"), set pdfjs ' +\n 'GlobalWorkerOptions.workerSrc yourself, enable `cdnFallback`, ' +\n 'or pass a pre-created pdf.js document.',\n );\n}\n\n/**\n * @internal\n * Resolve the `pdfjs-dist` root that pdf.js's data folders (cmaps, standard fonts, ICC profiles,\n * wasm decoders) hang off. Explicit `assetsUrl` → the install the worker was served from →\n * version-matched CDN (when `cdnFallback`). A bundler that emits the worker as a hashed asset\n * leaves no install path to derive from, hence the CDN step.\n */\nexport function resolveAssetsUrl(args: {\n assetsUrl: string | undefined;\n workerSrc: string;\n cdnFallback: boolean;\n pdfjsVersion: string | undefined;\n}): string | undefined {\n if (args.assetsUrl) return args.assetsUrl.endsWith('/') ? args.assetsUrl : args.assetsUrl + '/';\n const install = /^(.*\\/pdfjs-dist(?:@[^/]+)?\\/)(?:legacy\\/)?build\\/pdf\\.worker[^/]*$/.exec(\n args.workerSrc.split(/[?#]/)[0] ?? '',\n );\n if (install) return install[1];\n if (args.pdfjsVersion && args.cdnFallback) return cdnWorkerUrl(args.pdfjsVersion, '');\n return undefined;\n}\n\n/**\n * Load pdf.js. CDN / UMD hosts typically expose `globalThis.pdfjsLib` from a\n * `<script>` of pdf.js; bundlers have no global and resolve the dynamic import.\n *\n * `legacy` picks the transpiled build, which runs on older engines too. Its entry and worker are a\n * matched pair, so `#resolveWorkerSrc` reads the same flag. Each specifier is a plain string literal\n * so a bundler can see and emit the chosen chunk; the branch means only the selected one is fetched.\n */\nasync function loadPdfjs(legacy: boolean): Promise<PdfjsModule> {\n const g = globalThis as typeof globalThis & { pdfjsLib?: PdfjsModule };\n if (g.pdfjsLib) return g.pdfjsLib;\n const mod = legacy ? await import('pdfjs-dist/legacy/build/pdf.mjs') : await import('pdfjs-dist');\n return mod as unknown as PdfjsModule;\n}\n\n/**\n * Renders PDF pages to canvases for zinejs. pdf.js is a dependency of this\n * package, loaded lazily so it never lands in the core bundle. Async — pass it\n * to `new Zine`, which calls `open()` (learning the page count) before building\n * spreads.\n */\nexport class PdfSource implements Source {\n pageCount = 0;\n #src: PdfSrc;\n #workerSrc: string | undefined;\n #cdnFallback: boolean;\n #assetsUrl: string | undefined;\n #renderScale: number;\n #preload: number;\n #doc: PdfDocumentLike | null = null;\n #maxCacheBytes: number;\n #progressive: boolean;\n #disableAutoFetch: boolean;\n #legacy: boolean;\n #onProgress: ((progress: LoadProgress) => void) | null = null;\n #cache = new Map<number, CacheEntry>();\n #cachedBytes = 0;\n #onUpdate: ((index: number) => void) | null = null;\n /** Zoomed page rasters keyed `index@scale`, apart from #cache: one is worth many normal pages\n * and would evict the whole spread from a byte cap sized for fit-to-screen. */\n #zoomCache = new Map<string, Promise<PageContent>>();\n\n /** Tiny downscaled stand-ins for pages evicted from #cache, so a revisit paints immediately and\n * then upgrades. Kept out of the byte cap (a few KB each, bounded by MAX_THUMBS). */\n #thumbs = new Map<number, PageContent>();\n\n /** Extracted page text, kept apart from #cache: text is scale-independent and negligible next\n * to a raster, so it has no place in the byte budget that evicts bitmaps. */\n #textCache = new Map<number, Promise<string>>();\n\n constructor(src: PdfSrc, options: PdfSourceOptions = {}) {\n this.#src = src;\n this.#workerSrc = options.workerSrc;\n this.#cdnFallback = options.cdnFallback ?? true;\n this.#assetsUrl = options.assetsUrl;\n this.#renderScale = options.renderScale ?? 1;\n this.#preload = options.preload ?? 1;\n this.#maxCacheBytes = options.maxCacheBytes ?? DEFAULT_MAX_CACHE_BYTES;\n this.#progressive = options.progressive ?? false;\n this.#disableAutoFetch = options.disableAutoFetch ?? false;\n this.#legacy = options.legacy ?? true;\n }\n\n /** Register a handler called when a page upgrades from its progressive low-res pass to crisp. */\n onPageUpdate(handler: (index: number) => void): void {\n this.#onUpdate = handler;\n }\n\n /** Register a handler called as the document downloads, driving a loading indicator. */\n onProgress(handler: (progress: LoadProgress) => void): void {\n this.#onProgress = handler;\n }\n\n async open(): Promise<void> {\n if (isPdfDocument(this.#src)) {\n this.#doc = this.#src;\n } else {\n const pdfjs = await loadPdfjs(this.#legacy);\n pdfjs.GlobalWorkerOptions.workerSrc = await this.#resolveWorkerSrc(\n pdfjs.GlobalWorkerOptions.workerSrc,\n pdfjs.version,\n );\n const params: DocParams =\n typeof this.#src === 'string' ? { url: this.#src } : { data: this.#src };\n if (this.#disableAutoFetch) params.disableAutoFetch = true;\n const assets = resolveAssetsUrl({\n assetsUrl: this.#assetsUrl,\n workerSrc: pdfjs.GlobalWorkerOptions.workerSrc,\n cdnFallback: this.#cdnFallback,\n pdfjsVersion: pdfjs.version,\n });\n if (assets) {\n params.cMapUrl = assets + 'cmaps/';\n params.standardFontDataUrl = assets + 'standard_fonts/';\n params.iccUrl = assets + 'iccs/';\n params.wasmUrl = assets + 'wasm/';\n }\n const task = pdfjs.getDocument(params);\n if (this.#onProgress) task.onProgress = this.#onProgress;\n this.#doc = await task.promise;\n }\n this.pageCount = this.#doc.numPages;\n }\n\n /**\n * Precedence: explicit `workerSrc` → already-configured global → auto default (Vite `?url`,\n * webpack `new URL`, sibling install, then version-matched CDN when `cdnFallback`). The ordering\n * and probes live in {@link resolveWorkerSrc}; this just forwards this source's settings.\n */\n #resolveWorkerSrc(configured: string, pdfjsVersion: string | undefined): Promise<string> {\n return resolveWorkerSrc({\n workerSrc: this.#workerSrc,\n configured,\n legacy: this.#legacy,\n cdnFallback: this.#cdnFallback,\n pdfjsVersion,\n });\n }\n\n get(index: number, opts?: PageRequest): Promise<PageContent> {\n // A zoomed request wants detail the fit-to-screen raster never had. PDF pages are vector, so\n // re-render at the magnification being viewed. Memoized per zoom level, not in the byte-capped\n // page cache: a zoomed page is many times the size of a normal one and would evict the whole\n // spread, and a reader who zooms out and back in should not pay to render it twice.\n if (opts && opts.scale > 1) return this.#renderZoomed(index, opts.scale, opts.maxSize);\n\n // Revisiting a page that was evicted under memory pressure: paint its thumbnail now, re-render\n // the full raster in the background, and signal an upgrade so the engine swaps it in when ready.\n const thumb = this.#cache.has(index) ? undefined : this.#thumbs.get(index);\n if (thumb) {\n this.#thumbs.delete(index); // re-created if this page is evicted again\n void this.#renderPage(index)\n .then(() => this.#onUpdate?.(index))\n .catch(() => {});\n for (let d = 1; d <= this.#preload; d++) this.prefetch([index - d, index + d]);\n return Promise.resolve(thumb);\n }\n\n const decoded = this.#renderPage(index);\n for (let d = 1; d <= this.#preload; d++) {\n this.prefetch([index - d, index + d]);\n }\n return decoded;\n }\n\n prefetch(indices: number[]): void {\n for (const i of indices) {\n if (i >= 0 && i < this.pageCount) {\n void this.#renderPage(i).catch(() => {}); // prefetch failures surface on a real get()\n }\n }\n }\n\n /**\n * The page's text, for search. Empty for a page with no text layer (a scan, or a pdf.js build\n * without text extraction) — callers treat that as \"nothing to match\" rather than an error.\n */\n getText(index: number): Promise<string> {\n const cached = this.#textCache.get(index);\n if (cached) return cached;\n const pending = this.#extractText(index).catch(() => '');\n this.#textCache.set(index, pending);\n return pending;\n }\n\n /**\n * The document's table of contents, flattened into the shape the outline panel wants.\n *\n * Empty when the PDF has no outline, which is common. Destinations are resolved to page\n * indices here rather than in the UI: doing it once, up front, keeps clicking an entry\n * instant, and an entry whose destination cannot be resolved is kept with a null page so the\n * heading still shows.\n */\n async getOutline(): Promise<OutlineItem[]> {\n const doc = this.#doc;\n if (!doc || typeof doc.getOutline !== 'function') return [];\n let nodes: PdfOutlineNode[] | null;\n try {\n nodes = await doc.getOutline();\n } catch {\n return [];\n }\n if (!nodes?.length) return [];\n const convert = async (list: PdfOutlineNode[]): Promise<OutlineItem[]> =>\n Promise.all(\n list.map(async (node) => ({\n // Raw title; the outline UI supplies a localized fallback when it is empty.\n title: (node.title ?? '').trim(),\n page: await this.#destinationPage(node.dest),\n children: node.items?.length ? await convert(node.items) : [],\n })),\n );\n return convert(nodes);\n }\n\n /** Resolve a pdf.js destination to a zero-based page index, or null if it cannot be. */\n async #destinationPage(dest: string | unknown[] | null | undefined): Promise<number | null> {\n const doc = this.#doc;\n if (!doc || dest == null) return null;\n try {\n // A named destination has to be looked up before it yields the usual explicit array.\n const explicit =\n typeof dest === 'string' ? await doc.getDestination?.(dest) : (dest as unknown[]);\n const ref = explicit?.[0];\n if (ref == null) return null;\n // Some destinations carry a bare page number instead of a reference.\n if (typeof ref === 'number') return ref;\n const index = await doc.getPageIndex?.(ref);\n return typeof index === 'number' ? index : null;\n } catch {\n return null; // a broken destination should not cost us the whole outline\n }\n }\n\n /**\n * The original PDF, for a download control.\n *\n * Null when the source was handed a pre-opened pdf.js document: the bytes belong to whoever\n * created it, and re-fetching them is not this class's call to make.\n */\n async getDownload(): Promise<DownloadInfo | null> {\n const src = this.#src;\n if (typeof src === 'string') {\n return { url: src, filename: filenameFromUrl(src) };\n }\n if (src instanceof ArrayBuffer || ArrayBuffer.isView(src)) {\n const blob = new Blob([src as BlobPart], { type: 'application/pdf' });\n return { url: URL.createObjectURL(blob), filename: 'document.pdf', revoke: true };\n }\n return null;\n }\n\n async #extractText(index: number): Promise<string> {\n if (this.#doc === null) {\n throw new Error('PdfSource: use after open() — the document is not loaded.');\n }\n const page = await this.#doc.getPage(index + 1); // pdf.js pages are 1-indexed\n if (typeof page.getTextContent !== 'function') return '';\n const content = await page.getTextContent();\n let text = '';\n for (const item of content.items) {\n if (typeof item.str !== 'string') continue; // marked-content entries carry no text\n text += item.str;\n // pdf.js splits a visual line into runs; only hasEOL ends one.\n text += item.hasEOL ? '\\n' : '';\n }\n return text;\n }\n\n destroy(): void {\n this.#doc?.destroy?.();\n this.#cache.clear();\n for (const pending of this.#zoomCache.values()) {\n void pending.then(releaseCanvas).catch(() => {});\n }\n this.#zoomCache.clear();\n for (const thumb of this.#thumbs.values()) releaseCanvas(thumb);\n this.#thumbs.clear();\n this.#textCache.clear();\n this.#cachedBytes = 0;\n this.#doc = null;\n }\n\n #renderPage(index: number): Promise<PageContent> {\n const existing = this.#cache.get(index);\n if (existing) {\n // Mark most-recently-used: re-insert so it sits at the end of the Map's order.\n this.#cache.delete(index);\n this.#cache.set(index, existing);\n return existing.promise;\n }\n\n const dpr = typeof devicePixelRatio === 'number' ? devicePixelRatio : 1;\n const full = this.#renderScale * dpr;\n const firstScale = this.#progressive ? full * PROGRESSIVE_LOW_RATIO : full;\n\n const entry: CacheEntry = { promise: undefined as unknown as Promise<PageContent>, bytes: 0 };\n entry.promise = this.#rasterize(index, firstScale).then(\n (content) => {\n entry.bytes = content.width * content.height * 4; // RGBA\n entry.content = content; // retained so eviction can downscale it to a thumbnail\n this.#cachedBytes += entry.bytes;\n this.#evictToFit(index);\n if (this.#progressive) this.#upgrade(index, entry, full);\n return content;\n },\n (error: unknown) => {\n this.#cache.delete(index); // let a later get() retry\n throw error;\n },\n );\n this.#cache.set(index, entry);\n return entry.promise;\n }\n\n /** Progressive: re-render at full resolution, swap it into the cache, and signal an update. */\n #upgrade(index: number, entry: CacheEntry, fullScale: number): void {\n void this.#rasterize(index, fullScale)\n .then((hi) => {\n if (this.#cache.get(index) !== entry) return; // evicted or replaced meanwhile\n this.#cachedBytes -= entry.bytes;\n entry.promise = Promise.resolve(hi);\n entry.content = hi;\n entry.bytes = hi.width * hi.height * 4;\n this.#cachedBytes += entry.bytes;\n this.#evictToFit(index);\n this.#onUpdate?.(index);\n })\n .catch(() => {}); // keep the low-res if the upgrade fails\n }\n\n /** Drop least-recently-used pages (front of the Map) until under the byte cap, keeping a small\n * thumbnail of each so a later revisit paints at once instead of blank while it re-renders. */\n #evictToFit(keepIndex: number): void {\n for (const [index, entry] of this.#cache) {\n if (this.#cachedBytes <= this.#maxCacheBytes) break;\n if (index === keepIndex || entry.bytes === 0) continue; // keep the newest; skip in-flight renders\n this.#stashThumb(index, entry);\n this.#cache.delete(index);\n this.#cachedBytes -= entry.bytes;\n }\n }\n\n /** Retain a downscaled placeholder for an evicted page, as the most-recent thumb, bounded LRU. */\n #stashThumb(index: number, entry: CacheEntry): void {\n const thumb = entry.content ? this.#makeThumb(entry.content) : null;\n if (!thumb) return;\n const prev = this.#thumbs.get(index);\n if (prev) releaseCanvas(prev);\n this.#thumbs.delete(index);\n this.#thumbs.set(index, thumb);\n while (this.#thumbs.size > MAX_THUMBS) {\n const oldest = this.#thumbs.keys().next().value as number;\n releaseCanvas(this.#thumbs.get(oldest)!);\n this.#thumbs.delete(oldest);\n }\n }\n\n /** Draw a page raster onto a tiny canvas. Null when no 2D canvas is available (e.g. SSR). */\n #makeThumb(content: PageContent): PageContent | null {\n const { width, height } = content;\n if (!width || !height || typeof document === 'undefined') return null;\n const factor = Math.min(1, THUMB_MAX_PX / Math.max(width, height));\n const canvas = document.createElement('canvas');\n canvas.width = Math.max(1, Math.round(width * factor));\n canvas.height = Math.max(1, Math.round(height * factor));\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n ctx.drawImage(content, 0, 0, canvas.width, canvas.height);\n return canvas;\n }\n\n /**\n * Render a whole page at zoom magnification, memoized by page and zoom level.\n *\n * Whole-page rather than a crop of what is visible: the renderer draws a page as one texture, so\n * a partial raster cannot be handed to it, and a crop would have to be re-rendered on every pan\n * anyway. Held apart from the byte-capped page cache, which is sized for fit-to-screen rasters.\n */\n #renderZoomed(index: number, zoom: number, maxSize?: number): Promise<PageContent> {\n const scale = Math.min(zoom, MAX_ZOOM_RENDER_SCALE);\n const key = `${index}@${scale}`;\n const cached = this.#zoomCache.get(key);\n if (cached) return cached;\n\n const dpr = typeof devicePixelRatio === 'number' ? devicePixelRatio : 1;\n const promise = this.#rasterize(index, this.#renderScale * dpr * scale, maxSize).catch(\n (error: unknown) => {\n this.#zoomCache.delete(key); // let a later zoom retry\n throw error;\n },\n );\n // One zoom level at a time, but both pages of the spread at that level.\n for (const [k, pending] of this.#zoomCache) {\n if (!k.endsWith(`@${scale}`)) {\n this.#zoomCache.delete(k);\n void pending.then(releaseCanvas).catch(() => {});\n }\n }\n this.#zoomCache.set(key, promise);\n return promise;\n }\n\n async #rasterize(index: number, scale: number, maxSize?: number): Promise<PageContent> {\n if (this.#doc === null) throw new Error('PdfSource: use after open() — the document is not loaded.');\n const page = await this.#doc.getPage(index + 1); // pdf.js pages are 1-indexed\n // Cap the scale so neither axis exceeds the GPU's texture limit: past it the upload paints black\n // (the renderer downscales as a backstop, but rendering to fit avoids the wasted memory spike).\n if (maxSize !== undefined && maxSize > 0) {\n const base = page.getViewport({ scale: 1 });\n const fit = maxSize / Math.max(base.width, base.height);\n scale = Math.min(scale, fit);\n }\n const viewport = page.getViewport({ scale });\n const canvas = document.createElement('canvas');\n canvas.width = Math.ceil(viewport.width);\n canvas.height = Math.ceil(viewport.height);\n await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;\n return canvas;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["#src","#workerSrc","#cdnFallback","#assetsUrl","#renderScale","#preload","#maxCacheBytes","#progressive","#disableAutoFetch","#legacy","#onUpdate","#onProgress","#doc","#resolveWorkerSrc","#renderZoomed","#cache","#thumbs","#renderPage","#textCache","#extractText","#destinationPage","#zoomCache","#cachedBytes","#rasterize","#evictToFit","#upgrade","#stashThumb","#makeThumb"],"sources":["../src/pdfSource.ts"],"sourcesContent":["import type {\n DownloadInfo,\n LoadProgress,\n OutlineItem,\n PageContent,\n PageRequest,\n Source,\n} from '@zinejs/core';\n\n/** Minimal shapes of the pdf.js API we rely on (avoids a hard type dependency). */\ninterface PdfPageLike {\n getViewport(opts: { scale: number }): { width: number; height: number };\n /** `transform` is an affine matrix applied before painting, used to offset a cropped tile. */\n render(opts: { canvasContext: unknown; viewport: unknown; transform?: number[] }): {\n promise: Promise<void>;\n };\n /** Optional: absent on scanned PDFs with no text layer, and on hand-rolled document stubs. */\n getTextContent?(): Promise<{ items: { str?: string; hasEOL?: boolean }[] }>;\n /** Optional: frees the page's decoded images; a no-op while another render of it is running. */\n cleanup?(): boolean;\n}\n/** A pdf.js outline entry. `dest` is either a named destination or an explicit array whose first\n * element references the target page. */\ninterface PdfOutlineNode {\n title?: string;\n dest?: string | unknown[] | null;\n items?: PdfOutlineNode[];\n}\ninterface PdfDocumentLike {\n numPages: number;\n getPage(pageNumber: number): Promise<PdfPageLike>;\n destroy?(): void;\n /** Optional: absent on documents without a table of contents, and on hand-rolled stubs. */\n getOutline?(): Promise<PdfOutlineNode[] | null>;\n getDestination?(id: string): Promise<unknown[] | null>;\n getPageIndex?(ref: unknown): Promise<number>;\n}\ninterface DocParams {\n url?: string;\n data?: ArrayBuffer | Uint8Array;\n disableAutoFetch?: boolean;\n cMapUrl?: string;\n standardFontDataUrl?: string;\n iccUrl?: string;\n wasmUrl?: string;\n}\n/** pdf.js's loading task: the document promise, plus an assignable download-progress callback. */\ninterface PdfLoadingTask {\n promise: Promise<PdfDocumentLike>;\n onProgress?: (p: LoadProgress) => void;\n}\ninterface PdfjsModule {\n GlobalWorkerOptions: { workerSrc: string };\n getDocument(src: DocParams): PdfLoadingTask;\n /** Present on real pdf.js builds; used to pick a matching CDN worker as a last resort. */\n version?: string;\n}\n\n/** A URL, raw bytes, or a pre-created pdf.js document. */\nexport type PdfSrc = string | ArrayBuffer | Uint8Array | PdfDocumentLike;\n\nexport interface PdfSourceOptions {\n /** URL to pdf.js's worker. Optional: auto-resolved from your bundler (`?url` / `new URL`) or\n * `pdfjs-dist` install, then a version-matched CDN when `cdnFallback` is true. Pass it for\n * CDN / UMD / CSP / offline, or if auto-resolve fails. */\n workerSrc?: string;\n /** Allow falling back to a version-matched jsDelivr worker when no local worker can be\n * resolved (no bundler rewrite, no reachable install). Default true. Set false for\n * CSP-restricted or offline apps, where a network worker would fail opaquely — you must\n * then pass `workerSrc` (or set GlobalWorkerOptions.workerSrc). */\n cdnFallback?: boolean;\n /** Root of a `pdfjs-dist` copy holding its data folders: `cmaps/` (CJK and other non-embedded\n * encodings), `standard_fonts/` (PDFs that rely on the base-14 fonts), `iccs/` (CMYK colour)\n * and `wasm/` (JPEG 2000 and JBIG2 image decoders, pdf.js 5+). Without them pdf.js draws blank\n * images or wrong glyphs. Optional: derived from the worker URL when it points into a\n * `pdfjs-dist` install, then a version-matched CDN when `cdnFallback` is true. Pass it when you\n * self-host the worker under another path (copy those folders under one base). */\n assetsUrl?: string;\n /** Base render scale; each page is rasterized at `renderScale × devicePixelRatio`. Default 1. */\n renderScale?: number;\n /** How many adjacent pages to prefetch around a requested page. Default 1. */\n preload?: number;\n /** Soft cap on cached page bytes; least-recently-used pages evict beyond it. Default ~256 MB. */\n maxCacheBytes?: number;\n /** Paint a low-res page first, then swap to crisp (faster first paint). Default false. */\n progressive?: boolean;\n /** Ask pdf.js to fetch only the byte ranges visible pages need (range-capable servers). Default false. */\n disableAutoFetch?: boolean;\n /**\n * Load pdf.js's legacy build (transpiled with polyfills) instead of the modern one. The legacy\n * build runs on both old and modern browsers; the modern build is smaller and a touch faster but\n * needs a recent engine. Default true, favouring reach. Set false to serve the lean modern build\n * when your audience is on current browsers. Ignored when a pre-created pdf.js document or a\n * global `pdfjsLib` is supplied — those already picked their build.\n */\n legacy?: boolean;\n}\n\nconst DEFAULT_MAX_CACHE_BYTES = 256 * 1024 * 1024;\n// Linear-resolution fraction for the progressive low-res first pass (~1/9 the pixels).\nconst PROGRESSIVE_LOW_RATIO = 0.35;\n// Ceiling on zoom-tile magnification. Past roughly this point a screen pixel already holds more\n// detail than the reader can resolve, and rasterizing further only costs memory and time.\nconst MAX_ZOOM_RENDER_SCALE = 6;\n// Largest raster area (px). Safari refuses canvases over 16,777,216 px (4096²); this stays a little\n// under so rounding each side up cannot cross it. About 64 MB of RGBA.\nconst MAX_CANVAS_PIXELS = 16_000_000;\n// Largest dimension (px) of the thumbnail kept when a full page is evicted from the byte cap, so\n// flipping back to an out-of-budget page paints a blurry placeholder at once instead of a blank\n// while it re-rasterizes. A few KB each.\nconst THUMB_MAX_PX = 96;\n// Bound on retained thumbnails (LRU), so a very large document does not accumulate them without end.\nconst MAX_THUMBS = 200;\n\n/** Free a canvas's backing store. Safari in particular holds the memory until the surface is\n * resized away, and a zoomed page is large enough for that to matter. */\nfunction releaseCanvas(content: PageContent): void {\n if (typeof HTMLCanvasElement !== 'undefined' && content instanceof HTMLCanvasElement) {\n content.width = 0;\n content.height = 0;\n }\n}\n\ninterface CacheEntry {\n promise: Promise<PageContent>;\n bytes: number; // 0 until the render resolves and its size is known\n content?: PageContent; // the resolved raster, kept so eviction can downscale it to a thumbnail\n}\n\nfunction isPdfDocument(src: PdfSrc): src is PdfDocumentLike {\n return (\n typeof src === 'object' &&\n src !== null &&\n 'numPages' in src &&\n typeof (src as PdfDocumentLike).getPage === 'function'\n );\n}\n\n/** Last path segment of a URL, for naming a download. Falls back when the URL has no filename\n * (a directory, a data: URI, or something unparseable). */\nfunction filenameFromUrl(url: string): string {\n try {\n const path = new URL(url, 'http://x').pathname;\n const last = path.split('/').filter(Boolean).pop();\n return last && /\\.[a-z0-9]+$/i.test(last) ? decodeURIComponent(last) : 'document.pdf';\n } catch {\n return 'document.pdf';\n }\n}\n\n/** True when `url` responds OK (HEAD, then GET). Used to pick a local worker path that exists. */\nasync function resourceExists(url: string): Promise<boolean> {\n try {\n const head = await fetch(url, { method: 'HEAD' });\n if (head.ok) return true;\n // Some hosts reject HEAD; a tiny ranged GET still proves the file is there.\n if (head.status === 405 || head.status === 501) {\n const get = await fetch(url, { method: 'GET', headers: { Range: 'bytes=0-0' } });\n return get.ok || get.status === 206;\n }\n return false;\n } catch {\n return false;\n }\n}\n\n/** @internal Version-matched jsDelivr URL for the pdf.js worker (CDN last resort). */\nexport function cdnWorkerUrl(version: string, subpath: string): string {\n return `https://cdn.jsdelivr.net/npm/pdfjs-dist@${version}/${subpath}`;\n}\n\nlet cdnWarned = false;\n\nfunction warnCdnOnce(cdn: string): void {\n if (cdnWarned) return;\n cdnWarned = true;\n // Only notable in an ES module: a bundler was in play and could have resolved the worker, yet we\n // still fell through to the CDN. In a classic <script> (UMD, no `import.meta.url`) there is no\n // bundler and the CDN is the expected fallback, so stay quiet — a CSP/offline failure there\n // announces itself through the browser and pdf.js anyway.\n if (!import.meta.url) return;\n if (typeof console === 'undefined' || typeof console.warn !== 'function') return;\n console.warn(\n `@zinejs/pdf: couldn't resolve the pdf.js worker from your bundler or install; ` +\n `loading it from a CDN (${cdn}). This fails under strict CSP or offline. ` +\n `Pass workerSrc (or set pdfjsLib.GlobalWorkerOptions.workerSrc) to self-host, ` +\n `or set cdnFallback: false to disable this fallback.`,\n );\n}\n\n/** @internal Reset the CDN warn-once flag between unit tests. Tree-shaken from the bundle. */\nexport function __resetCdnWarnedForTests(): void {\n cdnWarned = false;\n}\n\nasync function tryViteWorkerUrl(legacy: boolean): Promise<string | undefined> {\n // Vite (and Nuxt) rewrite `?url` imports to a served asset when they process the dep.\n // `webpackIgnore` keeps webpack/rspack from emitting an orphan worker chunk for this\n // Vite-only specifier; they throw at runtime, we catch, and the static new URL branch wins.\n try {\n const mod = legacy\n ? await import(\"@@ZINE_PDF_WORKER_IMPORT:pdfjs-dist/legacy/build/pdf.worker.min.mjs?url@@\")\n : await import(\"@@ZINE_PDF_WORKER_IMPORT:pdfjs-dist/build/pdf.worker.min.mjs?url@@\");\n const url = (mod as { default?: unknown }).default;\n return typeof url === 'string' && url ? url : undefined;\n } catch {\n return undefined;\n }\n}\n\nasync function tryBundledWorkerUrl(legacy: boolean): Promise<string | undefined> {\n // Webpack 5 / rspack: static package-specifier new URL is rewritten to an emitted asset.\n // Specifiers must be string literals (not concatenated) for the bundler to see them.\n // Vite leaves this as a runtime join against @zinejs/pdf/dist → 404; resourceExists skips it.\n try {\n const bundled = legacy\n ? \"@@ZINE_PDF_WORKER_NEWURL:pdfjs-dist/legacy/build/pdf.worker.min.mjs@@\"\n : \"@@ZINE_PDF_WORKER_NEWURL:pdfjs-dist/build/pdf.worker.min.mjs@@\";\n return (await resourceExists(bundled)) ? bundled : undefined;\n } catch {\n return undefined;\n }\n}\n\nasync function trySiblingWorkerUrl(subpath: string): Promise<string | undefined> {\n // From `@zinejs/pdf/dist/index.js`, `../../../pdfjs-dist` is the package's node_modules\n // sibling under both pnpm (nested) and npm/yarn (hoisted). Concatenate so Vite does not\n // treat this as an import.meta.url asset rewrite (those need a static relative literal).\n // UMD rewrites `import.meta` to `{}`, so `.url` is undefined — skip rather than\n // `new URL(..., undefined)` throwing into the catch.\n const base = import.meta.url;\n if (!base) return undefined;\n try {\n const local = new URL('../../../pdfjs-dist/' + subpath, base).href;\n return (await resourceExists(local)) ? local : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** @internal The local worker probes, in resolution order. Real functions in production; unit tests\n * pass a substitute set to {@link resolveWorkerSrc} to drive the CDN / opt-out branches. */\nexport type WorkerProbes = {\n vite: (legacy: boolean) => Promise<string | undefined>;\n bundled: (legacy: boolean) => Promise<string | undefined>;\n sibling: (subpath: string) => Promise<string | undefined>;\n};\n\nconst defaultWorkerProbes: WorkerProbes = {\n vite: tryViteWorkerUrl,\n bundled: tryBundledWorkerUrl,\n sibling: trySiblingWorkerUrl,\n};\n\n/**\n * @internal\n * Resolve pdf.js's worker URL: explicit `workerSrc` → already-configured global → local probes\n * (Vite `?url`, webpack `new URL`, sibling install) → version-matched CDN (when `cdnFallback`).\n * Exported (not from the package entry) so tests can inject probe results; production calls it via\n * {@link PdfSource} with {@link defaultWorkerProbes}, so no test-only code ships in the bundle.\n */\nexport async function resolveWorkerSrc(\n args: {\n workerSrc: string | undefined;\n configured: string;\n legacy: boolean;\n cdnFallback: boolean;\n pdfjsVersion: string | undefined;\n },\n probes: WorkerProbes = defaultWorkerProbes,\n): Promise<string> {\n if (args.workerSrc) return args.workerSrc;\n if (args.configured) return args.configured;\n\n const subpath = args.legacy ? 'legacy/build/pdf.worker.min.mjs' : 'build/pdf.worker.min.mjs';\n\n const vite = await probes.vite(args.legacy);\n if (vite) return vite;\n const bundled = await probes.bundled(args.legacy);\n if (bundled) return bundled;\n const sibling = await probes.sibling(subpath);\n if (sibling) return sibling;\n\n // Last resort: same version as the loaded library (avoids API/worker mismatch).\n if (args.pdfjsVersion && args.cdnFallback) {\n const cdn = cdnWorkerUrl(args.pdfjsVersion, subpath);\n warnCdnOnce(cdn);\n return cdn;\n }\n\n throw new Error(\n \"PdfSource: couldn't auto-resolve pdf.js's worker. Pass a `workerSrc` URL \" +\n '(see the @zinejs/pdf README, \"The pdf.js worker\"), set pdfjs ' +\n 'GlobalWorkerOptions.workerSrc yourself, enable `cdnFallback`, ' +\n 'or pass a pre-created pdf.js document.',\n );\n}\n\n/**\n * @internal\n * Resolve the `pdfjs-dist` root that pdf.js's data folders (cmaps, standard fonts, ICC profiles,\n * wasm decoders) hang off. Explicit `assetsUrl` → the install the worker was served from →\n * version-matched CDN (when `cdnFallback`). A bundler that emits the worker as a hashed asset\n * leaves no install path to derive from, hence the CDN step.\n */\nexport function resolveAssetsUrl(args: {\n assetsUrl: string | undefined;\n workerSrc: string;\n cdnFallback: boolean;\n pdfjsVersion: string | undefined;\n}): string | undefined {\n if (args.assetsUrl) return args.assetsUrl.endsWith('/') ? args.assetsUrl : args.assetsUrl + '/';\n const install = /^(.*\\/pdfjs-dist(?:@[^/]+)?\\/)(?:legacy\\/)?build\\/pdf\\.worker[^/]*$/.exec(\n args.workerSrc.split(/[?#]/)[0] ?? '',\n );\n if (install) return install[1];\n if (args.pdfjsVersion && args.cdnFallback) return cdnWorkerUrl(args.pdfjsVersion, '');\n return undefined;\n}\n\n/**\n * Load pdf.js. CDN / UMD hosts typically expose `globalThis.pdfjsLib` from a\n * `<script>` of pdf.js; bundlers have no global and resolve the dynamic import.\n *\n * `legacy` picks the transpiled build, which runs on older engines too. Its entry and worker are a\n * matched pair, so `#resolveWorkerSrc` reads the same flag. Each specifier is a plain string literal\n * so a bundler can see and emit the chosen chunk; the branch means only the selected one is fetched.\n */\nasync function loadPdfjs(legacy: boolean): Promise<PdfjsModule> {\n const g = globalThis as typeof globalThis & { pdfjsLib?: PdfjsModule };\n if (g.pdfjsLib) return g.pdfjsLib;\n const mod = legacy ? await import('pdfjs-dist/legacy/build/pdf.mjs') : await import('pdfjs-dist');\n return mod as unknown as PdfjsModule;\n}\n\n/**\n * Renders PDF pages to canvases for zinejs. pdf.js is a dependency of this\n * package, loaded lazily so it never lands in the core bundle. Async — pass it\n * to `new Zine`, which calls `open()` (learning the page count) before building\n * spreads.\n */\nexport class PdfSource implements Source {\n pageCount = 0;\n #src: PdfSrc;\n #workerSrc: string | undefined;\n #cdnFallback: boolean;\n #assetsUrl: string | undefined;\n #renderScale: number;\n #preload: number;\n #doc: PdfDocumentLike | null = null;\n #maxCacheBytes: number;\n #progressive: boolean;\n #disableAutoFetch: boolean;\n #legacy: boolean;\n #onProgress: ((progress: LoadProgress) => void) | null = null;\n #cache = new Map<number, CacheEntry>();\n #cachedBytes = 0;\n #onUpdate: ((index: number) => void) | null = null;\n /** Zoomed page rasters keyed `index@scale`, apart from #cache: one is worth many normal pages\n * and would evict the whole spread from a byte cap sized for fit-to-screen. */\n #zoomCache = new Map<string, Promise<PageContent>>();\n\n /** Tiny downscaled stand-ins for pages evicted from #cache, so a revisit paints immediately and\n * then upgrades. Kept out of the byte cap (a few KB each, bounded by MAX_THUMBS). */\n #thumbs = new Map<number, PageContent>();\n\n /** Extracted page text, kept apart from #cache: text is scale-independent and negligible next\n * to a raster, so it has no place in the byte budget that evicts bitmaps. */\n #textCache = new Map<number, Promise<string>>();\n\n constructor(src: PdfSrc, options: PdfSourceOptions = {}) {\n this.#src = src;\n this.#workerSrc = options.workerSrc;\n this.#cdnFallback = options.cdnFallback ?? true;\n this.#assetsUrl = options.assetsUrl;\n this.#renderScale = options.renderScale ?? 1;\n this.#preload = options.preload ?? 1;\n this.#maxCacheBytes = options.maxCacheBytes ?? DEFAULT_MAX_CACHE_BYTES;\n this.#progressive = options.progressive ?? false;\n this.#disableAutoFetch = options.disableAutoFetch ?? false;\n this.#legacy = options.legacy ?? true;\n }\n\n /** Register a handler called when a page upgrades from its progressive low-res pass to crisp. */\n onPageUpdate(handler: (index: number) => void): void {\n this.#onUpdate = handler;\n }\n\n /** Register a handler called as the document downloads, driving a loading indicator. */\n onProgress(handler: (progress: LoadProgress) => void): void {\n this.#onProgress = handler;\n }\n\n async open(): Promise<void> {\n if (isPdfDocument(this.#src)) {\n this.#doc = this.#src;\n } else {\n const pdfjs = await loadPdfjs(this.#legacy);\n pdfjs.GlobalWorkerOptions.workerSrc = await this.#resolveWorkerSrc(\n pdfjs.GlobalWorkerOptions.workerSrc,\n pdfjs.version,\n );\n const params: DocParams =\n typeof this.#src === 'string' ? { url: this.#src } : { data: this.#src };\n if (this.#disableAutoFetch) params.disableAutoFetch = true;\n const assets = resolveAssetsUrl({\n assetsUrl: this.#assetsUrl,\n workerSrc: pdfjs.GlobalWorkerOptions.workerSrc,\n cdnFallback: this.#cdnFallback,\n pdfjsVersion: pdfjs.version,\n });\n if (assets) {\n params.cMapUrl = assets + 'cmaps/';\n params.standardFontDataUrl = assets + 'standard_fonts/';\n params.iccUrl = assets + 'iccs/';\n params.wasmUrl = assets + 'wasm/';\n }\n const task = pdfjs.getDocument(params);\n if (this.#onProgress) task.onProgress = this.#onProgress;\n this.#doc = await task.promise;\n }\n this.pageCount = this.#doc.numPages;\n }\n\n /**\n * Precedence: explicit `workerSrc` → already-configured global → auto default (Vite `?url`,\n * webpack `new URL`, sibling install, then version-matched CDN when `cdnFallback`). The ordering\n * and probes live in {@link resolveWorkerSrc}; this just forwards this source's settings.\n */\n #resolveWorkerSrc(configured: string, pdfjsVersion: string | undefined): Promise<string> {\n return resolveWorkerSrc({\n workerSrc: this.#workerSrc,\n configured,\n legacy: this.#legacy,\n cdnFallback: this.#cdnFallback,\n pdfjsVersion,\n });\n }\n\n get(index: number, opts?: PageRequest): Promise<PageContent> {\n // A zoomed request wants detail the fit-to-screen raster never had. PDF pages are vector, so\n // re-render at the magnification being viewed. Memoized per zoom level, not in the byte-capped\n // page cache: a zoomed page is many times the size of a normal one and would evict the whole\n // spread, and a reader who zooms out and back in should not pay to render it twice.\n if (opts && opts.scale > 1) return this.#renderZoomed(index, opts.scale, opts.maxSize);\n\n // Revisiting a page that was evicted under memory pressure: paint its thumbnail now, re-render\n // the full raster in the background, and signal an upgrade so the engine swaps it in when ready.\n const thumb = this.#cache.has(index) ? undefined : this.#thumbs.get(index);\n if (thumb) {\n this.#thumbs.delete(index); // re-created if this page is evicted again\n void this.#renderPage(index)\n .then(() => this.#onUpdate?.(index))\n .catch(() => {});\n for (let d = 1; d <= this.#preload; d++) this.prefetch([index - d, index + d]);\n return Promise.resolve(thumb);\n }\n\n const decoded = this.#renderPage(index);\n for (let d = 1; d <= this.#preload; d++) {\n this.prefetch([index - d, index + d]);\n }\n return decoded;\n }\n\n prefetch(indices: number[]): void {\n for (const i of indices) {\n if (i >= 0 && i < this.pageCount) {\n void this.#renderPage(i).catch(() => {}); // prefetch failures surface on a real get()\n }\n }\n }\n\n /**\n * The page's text, for search. Empty for a page with no text layer (a scan, or a pdf.js build\n * without text extraction) — callers treat that as \"nothing to match\" rather than an error.\n */\n getText(index: number): Promise<string> {\n const cached = this.#textCache.get(index);\n if (cached) return cached;\n const pending = this.#extractText(index).catch(() => '');\n this.#textCache.set(index, pending);\n return pending;\n }\n\n /**\n * The document's table of contents, flattened into the shape the outline panel wants.\n *\n * Empty when the PDF has no outline, which is common. Destinations are resolved to page\n * indices here rather than in the UI: doing it once, up front, keeps clicking an entry\n * instant, and an entry whose destination cannot be resolved is kept with a null page so the\n * heading still shows.\n */\n async getOutline(): Promise<OutlineItem[]> {\n const doc = this.#doc;\n if (!doc || typeof doc.getOutline !== 'function') return [];\n let nodes: PdfOutlineNode[] | null;\n try {\n nodes = await doc.getOutline();\n } catch {\n return [];\n }\n if (!nodes?.length) return [];\n const convert = async (list: PdfOutlineNode[]): Promise<OutlineItem[]> =>\n Promise.all(\n list.map(async (node) => ({\n // Raw title; the outline UI supplies a localized fallback when it is empty.\n title: (node.title ?? '').trim(),\n page: await this.#destinationPage(node.dest),\n children: node.items?.length ? await convert(node.items) : [],\n })),\n );\n return convert(nodes);\n }\n\n /** Resolve a pdf.js destination to a zero-based page index, or null if it cannot be. */\n async #destinationPage(dest: string | unknown[] | null | undefined): Promise<number | null> {\n const doc = this.#doc;\n if (!doc || dest == null) return null;\n try {\n // A named destination has to be looked up before it yields the usual explicit array.\n const explicit =\n typeof dest === 'string' ? await doc.getDestination?.(dest) : (dest as unknown[]);\n const ref = explicit?.[0];\n if (ref == null) return null;\n // Some destinations carry a bare page number instead of a reference.\n if (typeof ref === 'number') return ref;\n const index = await doc.getPageIndex?.(ref);\n return typeof index === 'number' ? index : null;\n } catch {\n return null; // a broken destination should not cost us the whole outline\n }\n }\n\n /**\n * The original PDF, for a download control.\n *\n * Null when the source was handed a pre-opened pdf.js document: the bytes belong to whoever\n * created it, and re-fetching them is not this class's call to make.\n */\n async getDownload(): Promise<DownloadInfo | null> {\n const src = this.#src;\n if (typeof src === 'string') {\n return { url: src, filename: filenameFromUrl(src) };\n }\n if (src instanceof ArrayBuffer || ArrayBuffer.isView(src)) {\n const blob = new Blob([src as BlobPart], { type: 'application/pdf' });\n return { url: URL.createObjectURL(blob), filename: 'document.pdf', revoke: true };\n }\n return null;\n }\n\n async #extractText(index: number): Promise<string> {\n if (this.#doc === null) {\n throw new Error('PdfSource: use after open() — the document is not loaded.');\n }\n const page = await this.#doc.getPage(index + 1); // pdf.js pages are 1-indexed\n if (typeof page.getTextContent !== 'function') return '';\n const content = await page.getTextContent();\n let text = '';\n for (const item of content.items) {\n if (typeof item.str !== 'string') continue; // marked-content entries carry no text\n text += item.str;\n // pdf.js splits a visual line into runs; only hasEOL ends one.\n text += item.hasEOL ? '\\n' : '';\n }\n return text;\n }\n\n destroy(): void {\n this.#doc?.destroy?.();\n this.#cache.clear();\n for (const pending of this.#zoomCache.values()) {\n void pending.then(releaseCanvas).catch(() => {});\n }\n this.#zoomCache.clear();\n for (const thumb of this.#thumbs.values()) releaseCanvas(thumb);\n this.#thumbs.clear();\n this.#textCache.clear();\n this.#cachedBytes = 0;\n this.#doc = null;\n }\n\n #renderPage(index: number): Promise<PageContent> {\n const existing = this.#cache.get(index);\n if (existing) {\n // Mark most-recently-used: re-insert so it sits at the end of the Map's order.\n this.#cache.delete(index);\n this.#cache.set(index, existing);\n return existing.promise;\n }\n\n const dpr = typeof devicePixelRatio === 'number' ? devicePixelRatio : 1;\n const full = this.#renderScale * dpr;\n const firstScale = this.#progressive ? full * PROGRESSIVE_LOW_RATIO : full;\n\n const entry: CacheEntry = { promise: undefined as unknown as Promise<PageContent>, bytes: 0 };\n entry.promise = this.#rasterize(index, firstScale).then(\n (content) => {\n entry.bytes = content.width * content.height * 4; // RGBA\n entry.content = content; // retained so eviction can downscale it to a thumbnail\n this.#cachedBytes += entry.bytes;\n this.#evictToFit(index);\n if (this.#progressive) this.#upgrade(index, entry, full);\n return content;\n },\n (error: unknown) => {\n this.#cache.delete(index); // let a later get() retry\n throw error;\n },\n );\n this.#cache.set(index, entry);\n return entry.promise;\n }\n\n /** Progressive: re-render at full resolution, swap it into the cache, and signal an update. */\n #upgrade(index: number, entry: CacheEntry, fullScale: number): void {\n void this.#rasterize(index, fullScale)\n .then((hi) => {\n if (this.#cache.get(index) !== entry) return; // evicted or replaced meanwhile\n this.#cachedBytes -= entry.bytes;\n entry.promise = Promise.resolve(hi);\n entry.content = hi;\n entry.bytes = hi.width * hi.height * 4;\n this.#cachedBytes += entry.bytes;\n this.#evictToFit(index);\n this.#onUpdate?.(index);\n })\n .catch(() => {}); // keep the low-res if the upgrade fails\n }\n\n /** Drop least-recently-used pages (front of the Map) until under the byte cap, keeping a small\n * thumbnail of each so a later revisit paints at once instead of blank while it re-renders. */\n #evictToFit(keepIndex: number): void {\n for (const [index, entry] of this.#cache) {\n if (this.#cachedBytes <= this.#maxCacheBytes) break;\n if (index === keepIndex || entry.bytes === 0) continue; // keep the newest; skip in-flight renders\n this.#stashThumb(index, entry);\n this.#cache.delete(index);\n this.#cachedBytes -= entry.bytes;\n }\n }\n\n /** Retain a downscaled placeholder for an evicted page, as the most-recent thumb, bounded LRU. */\n #stashThumb(index: number, entry: CacheEntry): void {\n const thumb = entry.content ? this.#makeThumb(entry.content) : null;\n if (!thumb) return;\n const prev = this.#thumbs.get(index);\n if (prev) releaseCanvas(prev);\n this.#thumbs.delete(index);\n this.#thumbs.set(index, thumb);\n while (this.#thumbs.size > MAX_THUMBS) {\n const oldest = this.#thumbs.keys().next().value as number;\n releaseCanvas(this.#thumbs.get(oldest)!);\n this.#thumbs.delete(oldest);\n }\n }\n\n /** Draw a page raster onto a tiny canvas. Null when no 2D canvas is available (e.g. SSR). */\n #makeThumb(content: PageContent): PageContent | null {\n const { width, height } = content;\n if (!width || !height || typeof document === 'undefined') return null;\n const factor = Math.min(1, THUMB_MAX_PX / Math.max(width, height));\n const canvas = document.createElement('canvas');\n canvas.width = Math.max(1, Math.round(width * factor));\n canvas.height = Math.max(1, Math.round(height * factor));\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n ctx.drawImage(content, 0, 0, canvas.width, canvas.height);\n return canvas;\n }\n\n /**\n * Render a whole page at zoom magnification, memoized by page and zoom level.\n *\n * Whole-page rather than a crop of what is visible: the renderer draws a page as one texture, so\n * a partial raster cannot be handed to it, and a crop would have to be re-rendered on every pan\n * anyway. Held apart from the byte-capped page cache, which is sized for fit-to-screen rasters.\n */\n #renderZoomed(index: number, zoom: number, maxSize?: number): Promise<PageContent> {\n const scale = Math.min(zoom, MAX_ZOOM_RENDER_SCALE);\n const key = `${index}@${scale}`;\n const cached = this.#zoomCache.get(key);\n if (cached) return cached;\n\n const dpr = typeof devicePixelRatio === 'number' ? devicePixelRatio : 1;\n const promise = this.#rasterize(index, this.#renderScale * dpr * scale, maxSize).catch(\n (error: unknown) => {\n this.#zoomCache.delete(key); // let a later zoom retry\n throw error;\n },\n );\n // One zoom level at a time, but both pages of the spread at that level.\n for (const [k, pending] of this.#zoomCache) {\n if (!k.endsWith(`@${scale}`)) {\n this.#zoomCache.delete(k);\n void pending.then(releaseCanvas).catch(() => {});\n }\n }\n this.#zoomCache.set(key, promise);\n return promise;\n }\n\n async #rasterize(index: number, scale: number, maxSize?: number): Promise<PageContent> {\n if (this.#doc === null) throw new Error('PdfSource: use after open() — the document is not loaded.');\n const page = await this.#doc.getPage(index + 1); // pdf.js pages are 1-indexed\n const base = page.getViewport({ scale: 1 });\n const longest = Math.max(base.width, base.height);\n // A page whose size in points exceeds the screen (print-sized exports, posters) can never be\n // shown whole at more than screen size, so rasterize it as if it fit the screen. Zoom and\n // progressive scales are multiples of this, so they shrink with it.\n const screenPx = typeof screen === 'undefined' ? 0 : Math.max(screen.width, screen.height);\n if (screenPx > 0 && longest > screenPx) scale *= screenPx / longest;\n // Cap the scale so neither axis exceeds the GPU's texture limit: past it the upload paints black\n // (the renderer downscales as a backstop, but rendering to fit avoids the wasted memory spike).\n if (maxSize !== undefined && maxSize > 0) scale = Math.min(scale, maxSize / longest);\n // Past Safari's canvas area limit the canvas fails to allocate, and iOS kills the tab well\n // before that from the memory alone, with no error to catch.\n const area = base.width * base.height * scale * scale;\n if (area > MAX_CANVAS_PIXELS) scale *= Math.sqrt(MAX_CANVAS_PIXELS / area);\n const viewport = page.getViewport({ scale });\n const canvas = document.createElement('canvas');\n canvas.width = Math.ceil(viewport.width);\n canvas.height = Math.ceil(viewport.height);\n await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;\n // The raster is all we keep. pdf.js otherwise holds the page's decoded images (64 MB for one\n // 16-megapixel photo) until the document closes, and on a photo-heavy PDF that alone outgrows\n // an iOS tab's memory.\n page.cleanup?.();\n return canvas;\n }\n}\n"],"mappings":"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"}
package/dist/index.umd.js CHANGED
@@ -1,3 +1,3 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ZineJS=e.ZineJS||{}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=256*1024*1024,n=.35,r=6,i=96,a=200;function o(e){typeof HTMLCanvasElement<`u`&&e instanceof HTMLCanvasElement&&(e.width=0,e.height=0)}function s(e){return typeof e==`object`&&!!e&&`numPages`in e&&typeof e.getPage==`function`}function c(e){try{let t=new URL(e,`http://x`).pathname.split(`/`).filter(Boolean).pop();return t&&/\.[a-z0-9]+$/i.test(t)?decodeURIComponent(t):`document.pdf`}catch{return`document.pdf`}}async function l(e){try{let t=await fetch(e,{method:`HEAD`});if(t.ok)return!0;if(t.status===405||t.status===501){let t=await fetch(e,{method:`GET`,headers:{Range:`bytes=0-0`}});return t.ok||t.status===206}return!1}catch{return!1}}function u(e,t){return`https://cdn.jsdelivr.net/npm/pdfjs-dist@${e}/${t}`}var d=!1;function f(e){d||(d=!0,{}.url&&(typeof console>`u`||typeof console.warn!=`function`||console.warn(`@zinejs/pdf: couldn't resolve the pdf.js worker from your bundler or install; loading it from a CDN (${e}). This fails under strict CSP or offline. Pass workerSrc (or set pdfjsLib.GlobalWorkerOptions.workerSrc) to self-host, or set cdnFallback: false to disable this fallback.`)))}async function p(e){try{let t=(e?await Promise.reject(new Error('PdfSource: pass workerSrc for UMD/CDN')):await Promise.reject(new Error('PdfSource: pass workerSrc for UMD/CDN'))).default;return typeof t==`string`&&t?t:void 0}catch{return}}async function m(e){try{let t=e?(() => { throw new Error('PdfSource: pass workerSrc for UMD/CDN'); })():(() => { throw new Error('PdfSource: pass workerSrc for UMD/CDN'); })();return await l(t)?t:void 0}catch{return}}async function h(e){let t={}.url;if(t)try{let n=new URL(`../../../pdfjs-dist/`+e,t).href;return await l(n)?n:void 0}catch{return}}var g={vite:p,bundled:m,sibling:h};async function _(e,t=g){if(e.workerSrc)return e.workerSrc;if(e.configured)return e.configured;let n=e.legacy?`legacy/build/pdf.worker.min.mjs`:`build/pdf.worker.min.mjs`,r=await t.vite(e.legacy);if(r)return r;let i=await t.bundled(e.legacy);if(i)return i;let a=await t.sibling(n);if(a)return a;if(e.pdfjsVersion&&e.cdnFallback){let t=u(e.pdfjsVersion,n);return f(t),t}throw Error("PdfSource: couldn't auto-resolve pdf.js's worker. Pass a `workerSrc` URL (see the @zinejs/pdf README, \"The pdf.js worker\"), set pdfjs GlobalWorkerOptions.workerSrc yourself, enable `cdnFallback`, or pass a pre-created pdf.js document.")}function v(e){if(e.assetsUrl)return e.assetsUrl.endsWith(`/`)?e.assetsUrl:e.assetsUrl+`/`;let t=/^(.*\/pdfjs-dist(?:@[^/]+)?\/)(?:legacy\/)?build\/pdf\.worker[^/]*$/.exec(e.workerSrc.split(/[?#]/)[0]??``);if(t)return t[1];if(e.pdfjsVersion&&e.cdnFallback)return u(e.pdfjsVersion,``)}async function y(e){let t=globalThis;return t.pdfjsLib?t.pdfjsLib:e?await import(`pdfjs-dist/legacy/build/pdf.mjs`):await import(`pdfjs-dist`)}e.PdfSource=class{pageCount=0;#e;#t;#n;#r;#i;#a;#o=null;#s;#c;#l;#u;#d=null;#f=new Map;#p=0;#m=null;#h=new Map;#g=new Map;#_=new Map;constructor(e,n={}){this.#e=e,this.#t=n.workerSrc,this.#n=n.cdnFallback??!0,this.#r=n.assetsUrl,this.#i=n.renderScale??1,this.#a=n.preload??1,this.#s=n.maxCacheBytes??t,this.#c=n.progressive??!1,this.#l=n.disableAutoFetch??!1,this.#u=n.legacy??!0}onPageUpdate(e){this.#m=e}onProgress(e){this.#d=e}async open(){if(s(this.#e))this.#o=this.#e;else{let e=await y(this.#u);e.GlobalWorkerOptions.workerSrc=await this.#v(e.GlobalWorkerOptions.workerSrc,e.version);let t=typeof this.#e==`string`?{url:this.#e}:{data:this.#e};this.#l&&(t.disableAutoFetch=!0);let n=v({assetsUrl:this.#r,workerSrc:e.GlobalWorkerOptions.workerSrc,cdnFallback:this.#n,pdfjsVersion:e.version});n&&(t.cMapUrl=n+`cmaps/`,t.standardFontDataUrl=n+`standard_fonts/`,t.iccUrl=n+`iccs/`,t.wasmUrl=n+`wasm/`);let r=e.getDocument(t);this.#d&&(r.onProgress=this.#d),this.#o=await r.promise}this.pageCount=this.#o.numPages}#v(e,t){return _({workerSrc:this.#t,configured:e,legacy:this.#u,cdnFallback:this.#n,pdfjsVersion:t})}get(e,t){if(t&&t.scale>1)return this.#E(e,t.scale,t.maxSize);let n=this.#f.has(e)?void 0:this.#g.get(e);if(n){this.#g.delete(e),this.#x(e).then(()=>this.#m?.(e)).catch(()=>{});for(let t=1;t<=this.#a;t++)this.prefetch([e-t,e+t]);return Promise.resolve(n)}let r=this.#x(e);for(let t=1;t<=this.#a;t++)this.prefetch([e-t,e+t]);return r}prefetch(e){for(let t of e)t>=0&&t<this.pageCount&&this.#x(t).catch(()=>{})}getText(e){let t=this.#_.get(e);if(t)return t;let n=this.#b(e).catch(()=>``);return this.#_.set(e,n),n}async getOutline(){let e=this.#o;if(!e||typeof e.getOutline!=`function`)return[];let t;try{t=await e.getOutline()}catch{return[]}if(!t?.length)return[];let n=async e=>Promise.all(e.map(async e=>({title:(e.title??``).trim(),page:await this.#y(e.dest),children:e.items?.length?await n(e.items):[]})));return n(t)}async#y(e){let t=this.#o;if(!t||e==null)return null;try{let n=(typeof e==`string`?await t.getDestination?.(e):e)?.[0];if(n==null)return null;if(typeof n==`number`)return n;let r=await t.getPageIndex?.(n);return typeof r==`number`?r:null}catch{return null}}async getDownload(){let e=this.#e;if(typeof e==`string`)return{url:e,filename:c(e)};if(e instanceof ArrayBuffer||ArrayBuffer.isView(e)){let t=new Blob([e],{type:`application/pdf`});return{url:URL.createObjectURL(t),filename:`document.pdf`,revoke:!0}}return null}async#b(e){if(this.#o===null)throw Error(`PdfSource: use after open() — the document is not loaded.`);let t=await this.#o.getPage(e+1);if(typeof t.getTextContent!=`function`)return``;let n=await t.getTextContent(),r=``;for(let e of n.items)typeof e.str==`string`&&(r+=e.str,r+=e.hasEOL?`
2
- `:``);return r}destroy(){this.#o?.destroy?.(),this.#f.clear();for(let e of this.#h.values())e.then(o).catch(()=>{});this.#h.clear();for(let e of this.#g.values())o(e);this.#g.clear(),this.#_.clear(),this.#p=0,this.#o=null}#x(e){let t=this.#f.get(e);if(t)return this.#f.delete(e),this.#f.set(e,t),t.promise;let r=typeof devicePixelRatio==`number`?devicePixelRatio:1,i=this.#i*r,a=this.#c?i*n:i,o={promise:void 0,bytes:0};return o.promise=this.#D(e,a).then(t=>(o.bytes=t.width*t.height*4,o.content=t,this.#p+=o.bytes,this.#C(e),this.#c&&this.#S(e,o,i),t),t=>{throw this.#f.delete(e),t}),this.#f.set(e,o),o.promise}#S(e,t,n){this.#D(e,n).then(n=>{this.#f.get(e)===t&&(this.#p-=t.bytes,t.promise=Promise.resolve(n),t.content=n,t.bytes=n.width*n.height*4,this.#p+=t.bytes,this.#C(e),this.#m?.(e))}).catch(()=>{})}#C(e){for(let[t,n]of this.#f){if(this.#p<=this.#s)break;t===e||n.bytes===0||(this.#w(t,n),this.#f.delete(t),this.#p-=n.bytes)}}#w(e,t){let n=t.content?this.#T(t.content):null;if(!n)return;let r=this.#g.get(e);for(r&&o(r),this.#g.delete(e),this.#g.set(e,n);this.#g.size>a;){let e=this.#g.keys().next().value;o(this.#g.get(e)),this.#g.delete(e)}}#T(e){let{width:t,height:n}=e;if(!t||!n||typeof document>`u`)return null;let r=Math.min(1,i/Math.max(t,n)),a=document.createElement(`canvas`);a.width=Math.max(1,Math.round(t*r)),a.height=Math.max(1,Math.round(n*r));let o=a.getContext(`2d`);return o?(o.drawImage(e,0,0,a.width,a.height),a):null}#E(e,t,n){let i=Math.min(t,r),a=`${e}@${i}`,s=this.#h.get(a);if(s)return s;let c=typeof devicePixelRatio==`number`?devicePixelRatio:1,l=this.#D(e,this.#i*c*i,n).catch(e=>{throw this.#h.delete(a),e});for(let[e,t]of this.#h)e.endsWith(`@${i}`)||(this.#h.delete(e),t.then(o).catch(()=>{}));return this.#h.set(a,l),l}async#D(e,t,n){if(this.#o===null)throw Error(`PdfSource: use after open() — the document is not loaded.`);let r=await this.#o.getPage(e+1);if(n!==void 0&&n>0){let e=r.getViewport({scale:1}),i=n/Math.max(e.width,e.height);t=Math.min(t,i)}let i=r.getViewport({scale:t}),a=document.createElement(`canvas`);return a.width=Math.ceil(i.width),a.height=Math.ceil(i.height),await r.render({canvasContext:a.getContext(`2d`),viewport:i}).promise,a}}});
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ZineJS=e.ZineJS||{}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=256*1024*1024,n=.35,r=6,i=16e6,a=96,o=200;function s(e){typeof HTMLCanvasElement<`u`&&e instanceof HTMLCanvasElement&&(e.width=0,e.height=0)}function c(e){return typeof e==`object`&&!!e&&`numPages`in e&&typeof e.getPage==`function`}function l(e){try{let t=new URL(e,`http://x`).pathname.split(`/`).filter(Boolean).pop();return t&&/\.[a-z0-9]+$/i.test(t)?decodeURIComponent(t):`document.pdf`}catch{return`document.pdf`}}async function u(e){try{let t=await fetch(e,{method:`HEAD`});if(t.ok)return!0;if(t.status===405||t.status===501){let t=await fetch(e,{method:`GET`,headers:{Range:`bytes=0-0`}});return t.ok||t.status===206}return!1}catch{return!1}}function d(e,t){return`https://cdn.jsdelivr.net/npm/pdfjs-dist@${e}/${t}`}var f=!1;function p(e){f||(f=!0,{}.url&&(typeof console>`u`||typeof console.warn!=`function`||console.warn(`@zinejs/pdf: couldn't resolve the pdf.js worker from your bundler or install; loading it from a CDN (${e}). This fails under strict CSP or offline. Pass workerSrc (or set pdfjsLib.GlobalWorkerOptions.workerSrc) to self-host, or set cdnFallback: false to disable this fallback.`)))}async function m(e){try{let t=(e?await Promise.reject(new Error('PdfSource: pass workerSrc for UMD/CDN')):await Promise.reject(new Error('PdfSource: pass workerSrc for UMD/CDN'))).default;return typeof t==`string`&&t?t:void 0}catch{return}}async function h(e){try{let t=e?(() => { throw new Error('PdfSource: pass workerSrc for UMD/CDN'); })():(() => { throw new Error('PdfSource: pass workerSrc for UMD/CDN'); })();return await u(t)?t:void 0}catch{return}}async function g(e){let t={}.url;if(t)try{let n=new URL(`../../../pdfjs-dist/`+e,t).href;return await u(n)?n:void 0}catch{return}}var _={vite:m,bundled:h,sibling:g};async function v(e,t=_){if(e.workerSrc)return e.workerSrc;if(e.configured)return e.configured;let n=e.legacy?`legacy/build/pdf.worker.min.mjs`:`build/pdf.worker.min.mjs`,r=await t.vite(e.legacy);if(r)return r;let i=await t.bundled(e.legacy);if(i)return i;let a=await t.sibling(n);if(a)return a;if(e.pdfjsVersion&&e.cdnFallback){let t=d(e.pdfjsVersion,n);return p(t),t}throw Error("PdfSource: couldn't auto-resolve pdf.js's worker. Pass a `workerSrc` URL (see the @zinejs/pdf README, \"The pdf.js worker\"), set pdfjs GlobalWorkerOptions.workerSrc yourself, enable `cdnFallback`, or pass a pre-created pdf.js document.")}function y(e){if(e.assetsUrl)return e.assetsUrl.endsWith(`/`)?e.assetsUrl:e.assetsUrl+`/`;let t=/^(.*\/pdfjs-dist(?:@[^/]+)?\/)(?:legacy\/)?build\/pdf\.worker[^/]*$/.exec(e.workerSrc.split(/[?#]/)[0]??``);if(t)return t[1];if(e.pdfjsVersion&&e.cdnFallback)return d(e.pdfjsVersion,``)}async function b(e){let t=globalThis;return t.pdfjsLib?t.pdfjsLib:e?await import(`pdfjs-dist/legacy/build/pdf.mjs`):await import(`pdfjs-dist`)}e.PdfSource=class{pageCount=0;#e;#t;#n;#r;#i;#a;#o=null;#s;#c;#l;#u;#d=null;#f=new Map;#p=0;#m=null;#h=new Map;#g=new Map;#_=new Map;constructor(e,n={}){this.#e=e,this.#t=n.workerSrc,this.#n=n.cdnFallback??!0,this.#r=n.assetsUrl,this.#i=n.renderScale??1,this.#a=n.preload??1,this.#s=n.maxCacheBytes??t,this.#c=n.progressive??!1,this.#l=n.disableAutoFetch??!1,this.#u=n.legacy??!0}onPageUpdate(e){this.#m=e}onProgress(e){this.#d=e}async open(){if(c(this.#e))this.#o=this.#e;else{let e=await b(this.#u);e.GlobalWorkerOptions.workerSrc=await this.#v(e.GlobalWorkerOptions.workerSrc,e.version);let t=typeof this.#e==`string`?{url:this.#e}:{data:this.#e};this.#l&&(t.disableAutoFetch=!0);let n=y({assetsUrl:this.#r,workerSrc:e.GlobalWorkerOptions.workerSrc,cdnFallback:this.#n,pdfjsVersion:e.version});n&&(t.cMapUrl=n+`cmaps/`,t.standardFontDataUrl=n+`standard_fonts/`,t.iccUrl=n+`iccs/`,t.wasmUrl=n+`wasm/`);let r=e.getDocument(t);this.#d&&(r.onProgress=this.#d),this.#o=await r.promise}this.pageCount=this.#o.numPages}#v(e,t){return v({workerSrc:this.#t,configured:e,legacy:this.#u,cdnFallback:this.#n,pdfjsVersion:t})}get(e,t){if(t&&t.scale>1)return this.#E(e,t.scale,t.maxSize);let n=this.#f.has(e)?void 0:this.#g.get(e);if(n){this.#g.delete(e),this.#x(e).then(()=>this.#m?.(e)).catch(()=>{});for(let t=1;t<=this.#a;t++)this.prefetch([e-t,e+t]);return Promise.resolve(n)}let r=this.#x(e);for(let t=1;t<=this.#a;t++)this.prefetch([e-t,e+t]);return r}prefetch(e){for(let t of e)t>=0&&t<this.pageCount&&this.#x(t).catch(()=>{})}getText(e){let t=this.#_.get(e);if(t)return t;let n=this.#b(e).catch(()=>``);return this.#_.set(e,n),n}async getOutline(){let e=this.#o;if(!e||typeof e.getOutline!=`function`)return[];let t;try{t=await e.getOutline()}catch{return[]}if(!t?.length)return[];let n=async e=>Promise.all(e.map(async e=>({title:(e.title??``).trim(),page:await this.#y(e.dest),children:e.items?.length?await n(e.items):[]})));return n(t)}async#y(e){let t=this.#o;if(!t||e==null)return null;try{let n=(typeof e==`string`?await t.getDestination?.(e):e)?.[0];if(n==null)return null;if(typeof n==`number`)return n;let r=await t.getPageIndex?.(n);return typeof r==`number`?r:null}catch{return null}}async getDownload(){let e=this.#e;if(typeof e==`string`)return{url:e,filename:l(e)};if(e instanceof ArrayBuffer||ArrayBuffer.isView(e)){let t=new Blob([e],{type:`application/pdf`});return{url:URL.createObjectURL(t),filename:`document.pdf`,revoke:!0}}return null}async#b(e){if(this.#o===null)throw Error(`PdfSource: use after open() — the document is not loaded.`);let t=await this.#o.getPage(e+1);if(typeof t.getTextContent!=`function`)return``;let n=await t.getTextContent(),r=``;for(let e of n.items)typeof e.str==`string`&&(r+=e.str,r+=e.hasEOL?`
2
+ `:``);return r}destroy(){this.#o?.destroy?.(),this.#f.clear();for(let e of this.#h.values())e.then(s).catch(()=>{});this.#h.clear();for(let e of this.#g.values())s(e);this.#g.clear(),this.#_.clear(),this.#p=0,this.#o=null}#x(e){let t=this.#f.get(e);if(t)return this.#f.delete(e),this.#f.set(e,t),t.promise;let r=typeof devicePixelRatio==`number`?devicePixelRatio:1,i=this.#i*r,a=this.#c?i*n:i,o={promise:void 0,bytes:0};return o.promise=this.#D(e,a).then(t=>(o.bytes=t.width*t.height*4,o.content=t,this.#p+=o.bytes,this.#C(e),this.#c&&this.#S(e,o,i),t),t=>{throw this.#f.delete(e),t}),this.#f.set(e,o),o.promise}#S(e,t,n){this.#D(e,n).then(n=>{this.#f.get(e)===t&&(this.#p-=t.bytes,t.promise=Promise.resolve(n),t.content=n,t.bytes=n.width*n.height*4,this.#p+=t.bytes,this.#C(e),this.#m?.(e))}).catch(()=>{})}#C(e){for(let[t,n]of this.#f){if(this.#p<=this.#s)break;t===e||n.bytes===0||(this.#w(t,n),this.#f.delete(t),this.#p-=n.bytes)}}#w(e,t){let n=t.content?this.#T(t.content):null;if(!n)return;let r=this.#g.get(e);for(r&&s(r),this.#g.delete(e),this.#g.set(e,n);this.#g.size>o;){let e=this.#g.keys().next().value;s(this.#g.get(e)),this.#g.delete(e)}}#T(e){let{width:t,height:n}=e;if(!t||!n||typeof document>`u`)return null;let r=Math.min(1,a/Math.max(t,n)),i=document.createElement(`canvas`);i.width=Math.max(1,Math.round(t*r)),i.height=Math.max(1,Math.round(n*r));let o=i.getContext(`2d`);return o?(o.drawImage(e,0,0,i.width,i.height),i):null}#E(e,t,n){let i=Math.min(t,r),a=`${e}@${i}`,o=this.#h.get(a);if(o)return o;let c=typeof devicePixelRatio==`number`?devicePixelRatio:1,l=this.#D(e,this.#i*c*i,n).catch(e=>{throw this.#h.delete(a),e});for(let[e,t]of this.#h)e.endsWith(`@${i}`)||(this.#h.delete(e),t.then(s).catch(()=>{}));return this.#h.set(a,l),l}async#D(e,t,n){if(this.#o===null)throw Error(`PdfSource: use after open() — the document is not loaded.`);let r=await this.#o.getPage(e+1),a=r.getViewport({scale:1}),o=Math.max(a.width,a.height),s=typeof screen>`u`?0:Math.max(screen.width,screen.height);s>0&&o>s&&(t*=s/o),n!==void 0&&n>0&&(t=Math.min(t,n/o));let c=a.width*a.height*t*t;c>i&&(t*=Math.sqrt(i/c));let l=r.getViewport({scale:t}),u=document.createElement(`canvas`);return u.width=Math.ceil(l.width),u.height=Math.ceil(l.height),await r.render({canvasContext:u.getContext(`2d`),viewport:l}).promise,r.cleanup?.(),u}}});
3
3
  //# sourceMappingURL=index.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.umd.js","names":["#src","#workerSrc","#cdnFallback","#assetsUrl","#renderScale","#preload","#maxCacheBytes","#progressive","#disableAutoFetch","#legacy","#onUpdate","#onProgress","#doc","#resolveWorkerSrc","#renderZoomed","#cache","#thumbs","#renderPage","#textCache","#extractText","#destinationPage","#zoomCache","#cachedBytes","#rasterize","#evictToFit","#upgrade","#stashThumb","#makeThumb"],"sources":["../src/pdfSource.ts"],"sourcesContent":["import type {\n DownloadInfo,\n LoadProgress,\n OutlineItem,\n PageContent,\n PageRequest,\n Source,\n} from '@zinejs/core';\n\n/** Minimal shapes of the pdf.js API we rely on (avoids a hard type dependency). */\ninterface PdfPageLike {\n getViewport(opts: { scale: number }): { width: number; height: number };\n /** `transform` is an affine matrix applied before painting, used to offset a cropped tile. */\n render(opts: { canvasContext: unknown; viewport: unknown; transform?: number[] }): {\n promise: Promise<void>;\n };\n /** Optional: absent on scanned PDFs with no text layer, and on hand-rolled document stubs. */\n getTextContent?(): Promise<{ items: { str?: string; hasEOL?: boolean }[] }>;\n}\n/** A pdf.js outline entry. `dest` is either a named destination or an explicit array whose first\n * element references the target page. */\ninterface PdfOutlineNode {\n title?: string;\n dest?: string | unknown[] | null;\n items?: PdfOutlineNode[];\n}\ninterface PdfDocumentLike {\n numPages: number;\n getPage(pageNumber: number): Promise<PdfPageLike>;\n destroy?(): void;\n /** Optional: absent on documents without a table of contents, and on hand-rolled stubs. */\n getOutline?(): Promise<PdfOutlineNode[] | null>;\n getDestination?(id: string): Promise<unknown[] | null>;\n getPageIndex?(ref: unknown): Promise<number>;\n}\ninterface DocParams {\n url?: string;\n data?: ArrayBuffer | Uint8Array;\n disableAutoFetch?: boolean;\n cMapUrl?: string;\n standardFontDataUrl?: string;\n iccUrl?: string;\n wasmUrl?: string;\n}\n/** pdf.js's loading task: the document promise, plus an assignable download-progress callback. */\ninterface PdfLoadingTask {\n promise: Promise<PdfDocumentLike>;\n onProgress?: (p: LoadProgress) => void;\n}\ninterface PdfjsModule {\n GlobalWorkerOptions: { workerSrc: string };\n getDocument(src: DocParams): PdfLoadingTask;\n /** Present on real pdf.js builds; used to pick a matching CDN worker as a last resort. */\n version?: string;\n}\n\n/** A URL, raw bytes, or a pre-created pdf.js document. */\nexport type PdfSrc = string | ArrayBuffer | Uint8Array | PdfDocumentLike;\n\nexport interface PdfSourceOptions {\n /** URL to pdf.js's worker. Optional: auto-resolved from your bundler (`?url` / `new URL`) or\n * `pdfjs-dist` install, then a version-matched CDN when `cdnFallback` is true. Pass it for\n * CDN / UMD / CSP / offline, or if auto-resolve fails. */\n workerSrc?: string;\n /** Allow falling back to a version-matched jsDelivr worker when no local worker can be\n * resolved (no bundler rewrite, no reachable install). Default true. Set false for\n * CSP-restricted or offline apps, where a network worker would fail opaquely — you must\n * then pass `workerSrc` (or set GlobalWorkerOptions.workerSrc). */\n cdnFallback?: boolean;\n /** Root of a `pdfjs-dist` copy holding its data folders: `cmaps/` (CJK and other non-embedded\n * encodings), `standard_fonts/` (PDFs that rely on the base-14 fonts), `iccs/` (CMYK colour)\n * and `wasm/` (JPEG 2000 and JBIG2 image decoders, pdf.js 5+). Without them pdf.js draws blank\n * images or wrong glyphs. Optional: derived from the worker URL when it points into a\n * `pdfjs-dist` install, then a version-matched CDN when `cdnFallback` is true. Pass it when you\n * self-host the worker under another path (copy those folders under one base). */\n assetsUrl?: string;\n /** Base render scale; each page is rasterized at `renderScale × devicePixelRatio`. Default 1. */\n renderScale?: number;\n /** How many adjacent pages to prefetch around a requested page. Default 1. */\n preload?: number;\n /** Soft cap on cached page bytes; least-recently-used pages evict beyond it. Default ~256 MB. */\n maxCacheBytes?: number;\n /** Paint a low-res page first, then swap to crisp (faster first paint). Default false. */\n progressive?: boolean;\n /** Ask pdf.js to fetch only the byte ranges visible pages need (range-capable servers). Default false. */\n disableAutoFetch?: boolean;\n /**\n * Load pdf.js's legacy build (transpiled with polyfills) instead of the modern one. The legacy\n * build runs on both old and modern browsers; the modern build is smaller and a touch faster but\n * needs a recent engine. Default true, favouring reach. Set false to serve the lean modern build\n * when your audience is on current browsers. Ignored when a pre-created pdf.js document or a\n * global `pdfjsLib` is supplied — those already picked their build.\n */\n legacy?: boolean;\n}\n\nconst DEFAULT_MAX_CACHE_BYTES = 256 * 1024 * 1024;\n// Linear-resolution fraction for the progressive low-res first pass (~1/9 the pixels).\nconst PROGRESSIVE_LOW_RATIO = 0.35;\n// Ceiling on zoom-tile magnification. Past roughly this point a screen pixel already holds more\n// detail than the reader can resolve, and rasterizing further only costs memory and time.\nconst MAX_ZOOM_RENDER_SCALE = 6;\n// Largest dimension (px) of the thumbnail kept when a full page is evicted from the byte cap, so\n// flipping back to an out-of-budget page paints a blurry placeholder at once instead of a blank\n// while it re-rasterizes. A few KB each.\nconst THUMB_MAX_PX = 96;\n// Bound on retained thumbnails (LRU), so a very large document does not accumulate them without end.\nconst MAX_THUMBS = 200;\n\n/** Free a canvas's backing store. Safari in particular holds the memory until the surface is\n * resized away, and a zoomed page is large enough for that to matter. */\nfunction releaseCanvas(content: PageContent): void {\n if (typeof HTMLCanvasElement !== 'undefined' && content instanceof HTMLCanvasElement) {\n content.width = 0;\n content.height = 0;\n }\n}\n\ninterface CacheEntry {\n promise: Promise<PageContent>;\n bytes: number; // 0 until the render resolves and its size is known\n content?: PageContent; // the resolved raster, kept so eviction can downscale it to a thumbnail\n}\n\nfunction isPdfDocument(src: PdfSrc): src is PdfDocumentLike {\n return (\n typeof src === 'object' &&\n src !== null &&\n 'numPages' in src &&\n typeof (src as PdfDocumentLike).getPage === 'function'\n );\n}\n\n/** Last path segment of a URL, for naming a download. Falls back when the URL has no filename\n * (a directory, a data: URI, or something unparseable). */\nfunction filenameFromUrl(url: string): string {\n try {\n const path = new URL(url, 'http://x').pathname;\n const last = path.split('/').filter(Boolean).pop();\n return last && /\\.[a-z0-9]+$/i.test(last) ? decodeURIComponent(last) : 'document.pdf';\n } catch {\n return 'document.pdf';\n }\n}\n\n/** True when `url` responds OK (HEAD, then GET). Used to pick a local worker path that exists. */\nasync function resourceExists(url: string): Promise<boolean> {\n try {\n const head = await fetch(url, { method: 'HEAD' });\n if (head.ok) return true;\n // Some hosts reject HEAD; a tiny ranged GET still proves the file is there.\n if (head.status === 405 || head.status === 501) {\n const get = await fetch(url, { method: 'GET', headers: { Range: 'bytes=0-0' } });\n return get.ok || get.status === 206;\n }\n return false;\n } catch {\n return false;\n }\n}\n\n/** @internal Version-matched jsDelivr URL for the pdf.js worker (CDN last resort). */\nexport function cdnWorkerUrl(version: string, subpath: string): string {\n return `https://cdn.jsdelivr.net/npm/pdfjs-dist@${version}/${subpath}`;\n}\n\nlet cdnWarned = false;\n\nfunction warnCdnOnce(cdn: string): void {\n if (cdnWarned) return;\n cdnWarned = true;\n // Only notable in an ES module: a bundler was in play and could have resolved the worker, yet we\n // still fell through to the CDN. In a classic <script> (UMD, no `import.meta.url`) there is no\n // bundler and the CDN is the expected fallback, so stay quiet — a CSP/offline failure there\n // announces itself through the browser and pdf.js anyway.\n if (!import.meta.url) return;\n if (typeof console === 'undefined' || typeof console.warn !== 'function') return;\n console.warn(\n `@zinejs/pdf: couldn't resolve the pdf.js worker from your bundler or install; ` +\n `loading it from a CDN (${cdn}). This fails under strict CSP or offline. ` +\n `Pass workerSrc (or set pdfjsLib.GlobalWorkerOptions.workerSrc) to self-host, ` +\n `or set cdnFallback: false to disable this fallback.`,\n );\n}\n\n/** @internal Reset the CDN warn-once flag between unit tests. Tree-shaken from the bundle. */\nexport function __resetCdnWarnedForTests(): void {\n cdnWarned = false;\n}\n\nasync function tryViteWorkerUrl(legacy: boolean): Promise<string | undefined> {\n // Vite (and Nuxt) rewrite `?url` imports to a served asset when they process the dep.\n // `webpackIgnore` keeps webpack/rspack from emitting an orphan worker chunk for this\n // Vite-only specifier; they throw at runtime, we catch, and the static new URL branch wins.\n try {\n const mod = legacy\n ? await import(\"@@ZINE_PDF_WORKER_IMPORT:pdfjs-dist/legacy/build/pdf.worker.min.mjs?url@@\")\n : await import(\"@@ZINE_PDF_WORKER_IMPORT:pdfjs-dist/build/pdf.worker.min.mjs?url@@\");\n const url = (mod as { default?: unknown }).default;\n return typeof url === 'string' && url ? url : undefined;\n } catch {\n return undefined;\n }\n}\n\nasync function tryBundledWorkerUrl(legacy: boolean): Promise<string | undefined> {\n // Webpack 5 / rspack: static package-specifier new URL is rewritten to an emitted asset.\n // Specifiers must be string literals (not concatenated) for the bundler to see them.\n // Vite leaves this as a runtime join against @zinejs/pdf/dist → 404; resourceExists skips it.\n try {\n const bundled = legacy\n ? \"@@ZINE_PDF_WORKER_NEWURL:pdfjs-dist/legacy/build/pdf.worker.min.mjs@@\"\n : \"@@ZINE_PDF_WORKER_NEWURL:pdfjs-dist/build/pdf.worker.min.mjs@@\";\n return (await resourceExists(bundled)) ? bundled : undefined;\n } catch {\n return undefined;\n }\n}\n\nasync function trySiblingWorkerUrl(subpath: string): Promise<string | undefined> {\n // From `@zinejs/pdf/dist/index.js`, `../../../pdfjs-dist` is the package's node_modules\n // sibling under both pnpm (nested) and npm/yarn (hoisted). Concatenate so Vite does not\n // treat this as an import.meta.url asset rewrite (those need a static relative literal).\n // UMD rewrites `import.meta` to `{}`, so `.url` is undefined — skip rather than\n // `new URL(..., undefined)` throwing into the catch.\n const base = import.meta.url;\n if (!base) return undefined;\n try {\n const local = new URL('../../../pdfjs-dist/' + subpath, base).href;\n return (await resourceExists(local)) ? local : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** @internal The local worker probes, in resolution order. Real functions in production; unit tests\n * pass a substitute set to {@link resolveWorkerSrc} to drive the CDN / opt-out branches. */\nexport type WorkerProbes = {\n vite: (legacy: boolean) => Promise<string | undefined>;\n bundled: (legacy: boolean) => Promise<string | undefined>;\n sibling: (subpath: string) => Promise<string | undefined>;\n};\n\nconst defaultWorkerProbes: WorkerProbes = {\n vite: tryViteWorkerUrl,\n bundled: tryBundledWorkerUrl,\n sibling: trySiblingWorkerUrl,\n};\n\n/**\n * @internal\n * Resolve pdf.js's worker URL: explicit `workerSrc` → already-configured global → local probes\n * (Vite `?url`, webpack `new URL`, sibling install) → version-matched CDN (when `cdnFallback`).\n * Exported (not from the package entry) so tests can inject probe results; production calls it via\n * {@link PdfSource} with {@link defaultWorkerProbes}, so no test-only code ships in the bundle.\n */\nexport async function resolveWorkerSrc(\n args: {\n workerSrc: string | undefined;\n configured: string;\n legacy: boolean;\n cdnFallback: boolean;\n pdfjsVersion: string | undefined;\n },\n probes: WorkerProbes = defaultWorkerProbes,\n): Promise<string> {\n if (args.workerSrc) return args.workerSrc;\n if (args.configured) return args.configured;\n\n const subpath = args.legacy ? 'legacy/build/pdf.worker.min.mjs' : 'build/pdf.worker.min.mjs';\n\n const vite = await probes.vite(args.legacy);\n if (vite) return vite;\n const bundled = await probes.bundled(args.legacy);\n if (bundled) return bundled;\n const sibling = await probes.sibling(subpath);\n if (sibling) return sibling;\n\n // Last resort: same version as the loaded library (avoids API/worker mismatch).\n if (args.pdfjsVersion && args.cdnFallback) {\n const cdn = cdnWorkerUrl(args.pdfjsVersion, subpath);\n warnCdnOnce(cdn);\n return cdn;\n }\n\n throw new Error(\n \"PdfSource: couldn't auto-resolve pdf.js's worker. Pass a `workerSrc` URL \" +\n '(see the @zinejs/pdf README, \"The pdf.js worker\"), set pdfjs ' +\n 'GlobalWorkerOptions.workerSrc yourself, enable `cdnFallback`, ' +\n 'or pass a pre-created pdf.js document.',\n );\n}\n\n/**\n * @internal\n * Resolve the `pdfjs-dist` root that pdf.js's data folders (cmaps, standard fonts, ICC profiles,\n * wasm decoders) hang off. Explicit `assetsUrl` → the install the worker was served from →\n * version-matched CDN (when `cdnFallback`). A bundler that emits the worker as a hashed asset\n * leaves no install path to derive from, hence the CDN step.\n */\nexport function resolveAssetsUrl(args: {\n assetsUrl: string | undefined;\n workerSrc: string;\n cdnFallback: boolean;\n pdfjsVersion: string | undefined;\n}): string | undefined {\n if (args.assetsUrl) return args.assetsUrl.endsWith('/') ? args.assetsUrl : args.assetsUrl + '/';\n const install = /^(.*\\/pdfjs-dist(?:@[^/]+)?\\/)(?:legacy\\/)?build\\/pdf\\.worker[^/]*$/.exec(\n args.workerSrc.split(/[?#]/)[0] ?? '',\n );\n if (install) return install[1];\n if (args.pdfjsVersion && args.cdnFallback) return cdnWorkerUrl(args.pdfjsVersion, '');\n return undefined;\n}\n\n/**\n * Load pdf.js. CDN / UMD hosts typically expose `globalThis.pdfjsLib` from a\n * `<script>` of pdf.js; bundlers have no global and resolve the dynamic import.\n *\n * `legacy` picks the transpiled build, which runs on older engines too. Its entry and worker are a\n * matched pair, so `#resolveWorkerSrc` reads the same flag. Each specifier is a plain string literal\n * so a bundler can see and emit the chosen chunk; the branch means only the selected one is fetched.\n */\nasync function loadPdfjs(legacy: boolean): Promise<PdfjsModule> {\n const g = globalThis as typeof globalThis & { pdfjsLib?: PdfjsModule };\n if (g.pdfjsLib) return g.pdfjsLib;\n const mod = legacy ? await import('pdfjs-dist/legacy/build/pdf.mjs') : await import('pdfjs-dist');\n return mod as unknown as PdfjsModule;\n}\n\n/**\n * Renders PDF pages to canvases for zinejs. pdf.js is a dependency of this\n * package, loaded lazily so it never lands in the core bundle. Async — pass it\n * to `new Zine`, which calls `open()` (learning the page count) before building\n * spreads.\n */\nexport class PdfSource implements Source {\n pageCount = 0;\n #src: PdfSrc;\n #workerSrc: string | undefined;\n #cdnFallback: boolean;\n #assetsUrl: string | undefined;\n #renderScale: number;\n #preload: number;\n #doc: PdfDocumentLike | null = null;\n #maxCacheBytes: number;\n #progressive: boolean;\n #disableAutoFetch: boolean;\n #legacy: boolean;\n #onProgress: ((progress: LoadProgress) => void) | null = null;\n #cache = new Map<number, CacheEntry>();\n #cachedBytes = 0;\n #onUpdate: ((index: number) => void) | null = null;\n /** Zoomed page rasters keyed `index@scale`, apart from #cache: one is worth many normal pages\n * and would evict the whole spread from a byte cap sized for fit-to-screen. */\n #zoomCache = new Map<string, Promise<PageContent>>();\n\n /** Tiny downscaled stand-ins for pages evicted from #cache, so a revisit paints immediately and\n * then upgrades. Kept out of the byte cap (a few KB each, bounded by MAX_THUMBS). */\n #thumbs = new Map<number, PageContent>();\n\n /** Extracted page text, kept apart from #cache: text is scale-independent and negligible next\n * to a raster, so it has no place in the byte budget that evicts bitmaps. */\n #textCache = new Map<number, Promise<string>>();\n\n constructor(src: PdfSrc, options: PdfSourceOptions = {}) {\n this.#src = src;\n this.#workerSrc = options.workerSrc;\n this.#cdnFallback = options.cdnFallback ?? true;\n this.#assetsUrl = options.assetsUrl;\n this.#renderScale = options.renderScale ?? 1;\n this.#preload = options.preload ?? 1;\n this.#maxCacheBytes = options.maxCacheBytes ?? DEFAULT_MAX_CACHE_BYTES;\n this.#progressive = options.progressive ?? false;\n this.#disableAutoFetch = options.disableAutoFetch ?? false;\n this.#legacy = options.legacy ?? true;\n }\n\n /** Register a handler called when a page upgrades from its progressive low-res pass to crisp. */\n onPageUpdate(handler: (index: number) => void): void {\n this.#onUpdate = handler;\n }\n\n /** Register a handler called as the document downloads, driving a loading indicator. */\n onProgress(handler: (progress: LoadProgress) => void): void {\n this.#onProgress = handler;\n }\n\n async open(): Promise<void> {\n if (isPdfDocument(this.#src)) {\n this.#doc = this.#src;\n } else {\n const pdfjs = await loadPdfjs(this.#legacy);\n pdfjs.GlobalWorkerOptions.workerSrc = await this.#resolveWorkerSrc(\n pdfjs.GlobalWorkerOptions.workerSrc,\n pdfjs.version,\n );\n const params: DocParams =\n typeof this.#src === 'string' ? { url: this.#src } : { data: this.#src };\n if (this.#disableAutoFetch) params.disableAutoFetch = true;\n const assets = resolveAssetsUrl({\n assetsUrl: this.#assetsUrl,\n workerSrc: pdfjs.GlobalWorkerOptions.workerSrc,\n cdnFallback: this.#cdnFallback,\n pdfjsVersion: pdfjs.version,\n });\n if (assets) {\n params.cMapUrl = assets + 'cmaps/';\n params.standardFontDataUrl = assets + 'standard_fonts/';\n params.iccUrl = assets + 'iccs/';\n params.wasmUrl = assets + 'wasm/';\n }\n const task = pdfjs.getDocument(params);\n if (this.#onProgress) task.onProgress = this.#onProgress;\n this.#doc = await task.promise;\n }\n this.pageCount = this.#doc.numPages;\n }\n\n /**\n * Precedence: explicit `workerSrc` → already-configured global → auto default (Vite `?url`,\n * webpack `new URL`, sibling install, then version-matched CDN when `cdnFallback`). The ordering\n * and probes live in {@link resolveWorkerSrc}; this just forwards this source's settings.\n */\n #resolveWorkerSrc(configured: string, pdfjsVersion: string | undefined): Promise<string> {\n return resolveWorkerSrc({\n workerSrc: this.#workerSrc,\n configured,\n legacy: this.#legacy,\n cdnFallback: this.#cdnFallback,\n pdfjsVersion,\n });\n }\n\n get(index: number, opts?: PageRequest): Promise<PageContent> {\n // A zoomed request wants detail the fit-to-screen raster never had. PDF pages are vector, so\n // re-render at the magnification being viewed. Memoized per zoom level, not in the byte-capped\n // page cache: a zoomed page is many times the size of a normal one and would evict the whole\n // spread, and a reader who zooms out and back in should not pay to render it twice.\n if (opts && opts.scale > 1) return this.#renderZoomed(index, opts.scale, opts.maxSize);\n\n // Revisiting a page that was evicted under memory pressure: paint its thumbnail now, re-render\n // the full raster in the background, and signal an upgrade so the engine swaps it in when ready.\n const thumb = this.#cache.has(index) ? undefined : this.#thumbs.get(index);\n if (thumb) {\n this.#thumbs.delete(index); // re-created if this page is evicted again\n void this.#renderPage(index)\n .then(() => this.#onUpdate?.(index))\n .catch(() => {});\n for (let d = 1; d <= this.#preload; d++) this.prefetch([index - d, index + d]);\n return Promise.resolve(thumb);\n }\n\n const decoded = this.#renderPage(index);\n for (let d = 1; d <= this.#preload; d++) {\n this.prefetch([index - d, index + d]);\n }\n return decoded;\n }\n\n prefetch(indices: number[]): void {\n for (const i of indices) {\n if (i >= 0 && i < this.pageCount) {\n void this.#renderPage(i).catch(() => {}); // prefetch failures surface on a real get()\n }\n }\n }\n\n /**\n * The page's text, for search. Empty for a page with no text layer (a scan, or a pdf.js build\n * without text extraction) — callers treat that as \"nothing to match\" rather than an error.\n */\n getText(index: number): Promise<string> {\n const cached = this.#textCache.get(index);\n if (cached) return cached;\n const pending = this.#extractText(index).catch(() => '');\n this.#textCache.set(index, pending);\n return pending;\n }\n\n /**\n * The document's table of contents, flattened into the shape the outline panel wants.\n *\n * Empty when the PDF has no outline, which is common. Destinations are resolved to page\n * indices here rather than in the UI: doing it once, up front, keeps clicking an entry\n * instant, and an entry whose destination cannot be resolved is kept with a null page so the\n * heading still shows.\n */\n async getOutline(): Promise<OutlineItem[]> {\n const doc = this.#doc;\n if (!doc || typeof doc.getOutline !== 'function') return [];\n let nodes: PdfOutlineNode[] | null;\n try {\n nodes = await doc.getOutline();\n } catch {\n return [];\n }\n if (!nodes?.length) return [];\n const convert = async (list: PdfOutlineNode[]): Promise<OutlineItem[]> =>\n Promise.all(\n list.map(async (node) => ({\n // Raw title; the outline UI supplies a localized fallback when it is empty.\n title: (node.title ?? '').trim(),\n page: await this.#destinationPage(node.dest),\n children: node.items?.length ? await convert(node.items) : [],\n })),\n );\n return convert(nodes);\n }\n\n /** Resolve a pdf.js destination to a zero-based page index, or null if it cannot be. */\n async #destinationPage(dest: string | unknown[] | null | undefined): Promise<number | null> {\n const doc = this.#doc;\n if (!doc || dest == null) return null;\n try {\n // A named destination has to be looked up before it yields the usual explicit array.\n const explicit =\n typeof dest === 'string' ? await doc.getDestination?.(dest) : (dest as unknown[]);\n const ref = explicit?.[0];\n if (ref == null) return null;\n // Some destinations carry a bare page number instead of a reference.\n if (typeof ref === 'number') return ref;\n const index = await doc.getPageIndex?.(ref);\n return typeof index === 'number' ? index : null;\n } catch {\n return null; // a broken destination should not cost us the whole outline\n }\n }\n\n /**\n * The original PDF, for a download control.\n *\n * Null when the source was handed a pre-opened pdf.js document: the bytes belong to whoever\n * created it, and re-fetching them is not this class's call to make.\n */\n async getDownload(): Promise<DownloadInfo | null> {\n const src = this.#src;\n if (typeof src === 'string') {\n return { url: src, filename: filenameFromUrl(src) };\n }\n if (src instanceof ArrayBuffer || ArrayBuffer.isView(src)) {\n const blob = new Blob([src as BlobPart], { type: 'application/pdf' });\n return { url: URL.createObjectURL(blob), filename: 'document.pdf', revoke: true };\n }\n return null;\n }\n\n async #extractText(index: number): Promise<string> {\n if (this.#doc === null) {\n throw new Error('PdfSource: use after open() — the document is not loaded.');\n }\n const page = await this.#doc.getPage(index + 1); // pdf.js pages are 1-indexed\n if (typeof page.getTextContent !== 'function') return '';\n const content = await page.getTextContent();\n let text = '';\n for (const item of content.items) {\n if (typeof item.str !== 'string') continue; // marked-content entries carry no text\n text += item.str;\n // pdf.js splits a visual line into runs; only hasEOL ends one.\n text += item.hasEOL ? '\\n' : '';\n }\n return text;\n }\n\n destroy(): void {\n this.#doc?.destroy?.();\n this.#cache.clear();\n for (const pending of this.#zoomCache.values()) {\n void pending.then(releaseCanvas).catch(() => {});\n }\n this.#zoomCache.clear();\n for (const thumb of this.#thumbs.values()) releaseCanvas(thumb);\n this.#thumbs.clear();\n this.#textCache.clear();\n this.#cachedBytes = 0;\n this.#doc = null;\n }\n\n #renderPage(index: number): Promise<PageContent> {\n const existing = this.#cache.get(index);\n if (existing) {\n // Mark most-recently-used: re-insert so it sits at the end of the Map's order.\n this.#cache.delete(index);\n this.#cache.set(index, existing);\n return existing.promise;\n }\n\n const dpr = typeof devicePixelRatio === 'number' ? devicePixelRatio : 1;\n const full = this.#renderScale * dpr;\n const firstScale = this.#progressive ? full * PROGRESSIVE_LOW_RATIO : full;\n\n const entry: CacheEntry = { promise: undefined as unknown as Promise<PageContent>, bytes: 0 };\n entry.promise = this.#rasterize(index, firstScale).then(\n (content) => {\n entry.bytes = content.width * content.height * 4; // RGBA\n entry.content = content; // retained so eviction can downscale it to a thumbnail\n this.#cachedBytes += entry.bytes;\n this.#evictToFit(index);\n if (this.#progressive) this.#upgrade(index, entry, full);\n return content;\n },\n (error: unknown) => {\n this.#cache.delete(index); // let a later get() retry\n throw error;\n },\n );\n this.#cache.set(index, entry);\n return entry.promise;\n }\n\n /** Progressive: re-render at full resolution, swap it into the cache, and signal an update. */\n #upgrade(index: number, entry: CacheEntry, fullScale: number): void {\n void this.#rasterize(index, fullScale)\n .then((hi) => {\n if (this.#cache.get(index) !== entry) return; // evicted or replaced meanwhile\n this.#cachedBytes -= entry.bytes;\n entry.promise = Promise.resolve(hi);\n entry.content = hi;\n entry.bytes = hi.width * hi.height * 4;\n this.#cachedBytes += entry.bytes;\n this.#evictToFit(index);\n this.#onUpdate?.(index);\n })\n .catch(() => {}); // keep the low-res if the upgrade fails\n }\n\n /** Drop least-recently-used pages (front of the Map) until under the byte cap, keeping a small\n * thumbnail of each so a later revisit paints at once instead of blank while it re-renders. */\n #evictToFit(keepIndex: number): void {\n for (const [index, entry] of this.#cache) {\n if (this.#cachedBytes <= this.#maxCacheBytes) break;\n if (index === keepIndex || entry.bytes === 0) continue; // keep the newest; skip in-flight renders\n this.#stashThumb(index, entry);\n this.#cache.delete(index);\n this.#cachedBytes -= entry.bytes;\n }\n }\n\n /** Retain a downscaled placeholder for an evicted page, as the most-recent thumb, bounded LRU. */\n #stashThumb(index: number, entry: CacheEntry): void {\n const thumb = entry.content ? this.#makeThumb(entry.content) : null;\n if (!thumb) return;\n const prev = this.#thumbs.get(index);\n if (prev) releaseCanvas(prev);\n this.#thumbs.delete(index);\n this.#thumbs.set(index, thumb);\n while (this.#thumbs.size > MAX_THUMBS) {\n const oldest = this.#thumbs.keys().next().value as number;\n releaseCanvas(this.#thumbs.get(oldest)!);\n this.#thumbs.delete(oldest);\n }\n }\n\n /** Draw a page raster onto a tiny canvas. Null when no 2D canvas is available (e.g. SSR). */\n #makeThumb(content: PageContent): PageContent | null {\n const { width, height } = content;\n if (!width || !height || typeof document === 'undefined') return null;\n const factor = Math.min(1, THUMB_MAX_PX / Math.max(width, height));\n const canvas = document.createElement('canvas');\n canvas.width = Math.max(1, Math.round(width * factor));\n canvas.height = Math.max(1, Math.round(height * factor));\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n ctx.drawImage(content, 0, 0, canvas.width, canvas.height);\n return canvas;\n }\n\n /**\n * Render a whole page at zoom magnification, memoized by page and zoom level.\n *\n * Whole-page rather than a crop of what is visible: the renderer draws a page as one texture, so\n * a partial raster cannot be handed to it, and a crop would have to be re-rendered on every pan\n * anyway. Held apart from the byte-capped page cache, which is sized for fit-to-screen rasters.\n */\n #renderZoomed(index: number, zoom: number, maxSize?: number): Promise<PageContent> {\n const scale = Math.min(zoom, MAX_ZOOM_RENDER_SCALE);\n const key = `${index}@${scale}`;\n const cached = this.#zoomCache.get(key);\n if (cached) return cached;\n\n const dpr = typeof devicePixelRatio === 'number' ? devicePixelRatio : 1;\n const promise = this.#rasterize(index, this.#renderScale * dpr * scale, maxSize).catch(\n (error: unknown) => {\n this.#zoomCache.delete(key); // let a later zoom retry\n throw error;\n },\n );\n // One zoom level at a time, but both pages of the spread at that level.\n for (const [k, pending] of this.#zoomCache) {\n if (!k.endsWith(`@${scale}`)) {\n this.#zoomCache.delete(k);\n void pending.then(releaseCanvas).catch(() => {});\n }\n }\n this.#zoomCache.set(key, promise);\n return promise;\n }\n\n async #rasterize(index: number, scale: number, maxSize?: number): Promise<PageContent> {\n if (this.#doc === null) throw new Error('PdfSource: use after open() — the document is not loaded.');\n const page = await this.#doc.getPage(index + 1); // pdf.js pages are 1-indexed\n // Cap the scale so neither axis exceeds the GPU's texture limit: past it the upload paints black\n // (the renderer downscales as a backstop, but rendering to fit avoids the wasted memory spike).\n if (maxSize !== undefined && maxSize > 0) {\n const base = page.getViewport({ scale: 1 });\n const fit = maxSize / Math.max(base.width, base.height);\n scale = Math.min(scale, fit);\n }\n const viewport = page.getViewport({ scale });\n const canvas = document.createElement('canvas');\n canvas.width = Math.ceil(viewport.width);\n canvas.height = Math.ceil(viewport.height);\n await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;\n return canvas;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.umd.js","names":["#src","#workerSrc","#cdnFallback","#assetsUrl","#renderScale","#preload","#maxCacheBytes","#progressive","#disableAutoFetch","#legacy","#onUpdate","#onProgress","#doc","#resolveWorkerSrc","#renderZoomed","#cache","#thumbs","#renderPage","#textCache","#extractText","#destinationPage","#zoomCache","#cachedBytes","#rasterize","#evictToFit","#upgrade","#stashThumb","#makeThumb"],"sources":["../src/pdfSource.ts"],"sourcesContent":["import type {\n DownloadInfo,\n LoadProgress,\n OutlineItem,\n PageContent,\n PageRequest,\n Source,\n} from '@zinejs/core';\n\n/** Minimal shapes of the pdf.js API we rely on (avoids a hard type dependency). */\ninterface PdfPageLike {\n getViewport(opts: { scale: number }): { width: number; height: number };\n /** `transform` is an affine matrix applied before painting, used to offset a cropped tile. */\n render(opts: { canvasContext: unknown; viewport: unknown; transform?: number[] }): {\n promise: Promise<void>;\n };\n /** Optional: absent on scanned PDFs with no text layer, and on hand-rolled document stubs. */\n getTextContent?(): Promise<{ items: { str?: string; hasEOL?: boolean }[] }>;\n /** Optional: frees the page's decoded images; a no-op while another render of it is running. */\n cleanup?(): boolean;\n}\n/** A pdf.js outline entry. `dest` is either a named destination or an explicit array whose first\n * element references the target page. */\ninterface PdfOutlineNode {\n title?: string;\n dest?: string | unknown[] | null;\n items?: PdfOutlineNode[];\n}\ninterface PdfDocumentLike {\n numPages: number;\n getPage(pageNumber: number): Promise<PdfPageLike>;\n destroy?(): void;\n /** Optional: absent on documents without a table of contents, and on hand-rolled stubs. */\n getOutline?(): Promise<PdfOutlineNode[] | null>;\n getDestination?(id: string): Promise<unknown[] | null>;\n getPageIndex?(ref: unknown): Promise<number>;\n}\ninterface DocParams {\n url?: string;\n data?: ArrayBuffer | Uint8Array;\n disableAutoFetch?: boolean;\n cMapUrl?: string;\n standardFontDataUrl?: string;\n iccUrl?: string;\n wasmUrl?: string;\n}\n/** pdf.js's loading task: the document promise, plus an assignable download-progress callback. */\ninterface PdfLoadingTask {\n promise: Promise<PdfDocumentLike>;\n onProgress?: (p: LoadProgress) => void;\n}\ninterface PdfjsModule {\n GlobalWorkerOptions: { workerSrc: string };\n getDocument(src: DocParams): PdfLoadingTask;\n /** Present on real pdf.js builds; used to pick a matching CDN worker as a last resort. */\n version?: string;\n}\n\n/** A URL, raw bytes, or a pre-created pdf.js document. */\nexport type PdfSrc = string | ArrayBuffer | Uint8Array | PdfDocumentLike;\n\nexport interface PdfSourceOptions {\n /** URL to pdf.js's worker. Optional: auto-resolved from your bundler (`?url` / `new URL`) or\n * `pdfjs-dist` install, then a version-matched CDN when `cdnFallback` is true. Pass it for\n * CDN / UMD / CSP / offline, or if auto-resolve fails. */\n workerSrc?: string;\n /** Allow falling back to a version-matched jsDelivr worker when no local worker can be\n * resolved (no bundler rewrite, no reachable install). Default true. Set false for\n * CSP-restricted or offline apps, where a network worker would fail opaquely — you must\n * then pass `workerSrc` (or set GlobalWorkerOptions.workerSrc). */\n cdnFallback?: boolean;\n /** Root of a `pdfjs-dist` copy holding its data folders: `cmaps/` (CJK and other non-embedded\n * encodings), `standard_fonts/` (PDFs that rely on the base-14 fonts), `iccs/` (CMYK colour)\n * and `wasm/` (JPEG 2000 and JBIG2 image decoders, pdf.js 5+). Without them pdf.js draws blank\n * images or wrong glyphs. Optional: derived from the worker URL when it points into a\n * `pdfjs-dist` install, then a version-matched CDN when `cdnFallback` is true. Pass it when you\n * self-host the worker under another path (copy those folders under one base). */\n assetsUrl?: string;\n /** Base render scale; each page is rasterized at `renderScale × devicePixelRatio`. Default 1. */\n renderScale?: number;\n /** How many adjacent pages to prefetch around a requested page. Default 1. */\n preload?: number;\n /** Soft cap on cached page bytes; least-recently-used pages evict beyond it. Default ~256 MB. */\n maxCacheBytes?: number;\n /** Paint a low-res page first, then swap to crisp (faster first paint). Default false. */\n progressive?: boolean;\n /** Ask pdf.js to fetch only the byte ranges visible pages need (range-capable servers). Default false. */\n disableAutoFetch?: boolean;\n /**\n * Load pdf.js's legacy build (transpiled with polyfills) instead of the modern one. The legacy\n * build runs on both old and modern browsers; the modern build is smaller and a touch faster but\n * needs a recent engine. Default true, favouring reach. Set false to serve the lean modern build\n * when your audience is on current browsers. Ignored when a pre-created pdf.js document or a\n * global `pdfjsLib` is supplied — those already picked their build.\n */\n legacy?: boolean;\n}\n\nconst DEFAULT_MAX_CACHE_BYTES = 256 * 1024 * 1024;\n// Linear-resolution fraction for the progressive low-res first pass (~1/9 the pixels).\nconst PROGRESSIVE_LOW_RATIO = 0.35;\n// Ceiling on zoom-tile magnification. Past roughly this point a screen pixel already holds more\n// detail than the reader can resolve, and rasterizing further only costs memory and time.\nconst MAX_ZOOM_RENDER_SCALE = 6;\n// Largest raster area (px). Safari refuses canvases over 16,777,216 px (4096²); this stays a little\n// under so rounding each side up cannot cross it. About 64 MB of RGBA.\nconst MAX_CANVAS_PIXELS = 16_000_000;\n// Largest dimension (px) of the thumbnail kept when a full page is evicted from the byte cap, so\n// flipping back to an out-of-budget page paints a blurry placeholder at once instead of a blank\n// while it re-rasterizes. A few KB each.\nconst THUMB_MAX_PX = 96;\n// Bound on retained thumbnails (LRU), so a very large document does not accumulate them without end.\nconst MAX_THUMBS = 200;\n\n/** Free a canvas's backing store. Safari in particular holds the memory until the surface is\n * resized away, and a zoomed page is large enough for that to matter. */\nfunction releaseCanvas(content: PageContent): void {\n if (typeof HTMLCanvasElement !== 'undefined' && content instanceof HTMLCanvasElement) {\n content.width = 0;\n content.height = 0;\n }\n}\n\ninterface CacheEntry {\n promise: Promise<PageContent>;\n bytes: number; // 0 until the render resolves and its size is known\n content?: PageContent; // the resolved raster, kept so eviction can downscale it to a thumbnail\n}\n\nfunction isPdfDocument(src: PdfSrc): src is PdfDocumentLike {\n return (\n typeof src === 'object' &&\n src !== null &&\n 'numPages' in src &&\n typeof (src as PdfDocumentLike).getPage === 'function'\n );\n}\n\n/** Last path segment of a URL, for naming a download. Falls back when the URL has no filename\n * (a directory, a data: URI, or something unparseable). */\nfunction filenameFromUrl(url: string): string {\n try {\n const path = new URL(url, 'http://x').pathname;\n const last = path.split('/').filter(Boolean).pop();\n return last && /\\.[a-z0-9]+$/i.test(last) ? decodeURIComponent(last) : 'document.pdf';\n } catch {\n return 'document.pdf';\n }\n}\n\n/** True when `url` responds OK (HEAD, then GET). Used to pick a local worker path that exists. */\nasync function resourceExists(url: string): Promise<boolean> {\n try {\n const head = await fetch(url, { method: 'HEAD' });\n if (head.ok) return true;\n // Some hosts reject HEAD; a tiny ranged GET still proves the file is there.\n if (head.status === 405 || head.status === 501) {\n const get = await fetch(url, { method: 'GET', headers: { Range: 'bytes=0-0' } });\n return get.ok || get.status === 206;\n }\n return false;\n } catch {\n return false;\n }\n}\n\n/** @internal Version-matched jsDelivr URL for the pdf.js worker (CDN last resort). */\nexport function cdnWorkerUrl(version: string, subpath: string): string {\n return `https://cdn.jsdelivr.net/npm/pdfjs-dist@${version}/${subpath}`;\n}\n\nlet cdnWarned = false;\n\nfunction warnCdnOnce(cdn: string): void {\n if (cdnWarned) return;\n cdnWarned = true;\n // Only notable in an ES module: a bundler was in play and could have resolved the worker, yet we\n // still fell through to the CDN. In a classic <script> (UMD, no `import.meta.url`) there is no\n // bundler and the CDN is the expected fallback, so stay quiet — a CSP/offline failure there\n // announces itself through the browser and pdf.js anyway.\n if (!import.meta.url) return;\n if (typeof console === 'undefined' || typeof console.warn !== 'function') return;\n console.warn(\n `@zinejs/pdf: couldn't resolve the pdf.js worker from your bundler or install; ` +\n `loading it from a CDN (${cdn}). This fails under strict CSP or offline. ` +\n `Pass workerSrc (or set pdfjsLib.GlobalWorkerOptions.workerSrc) to self-host, ` +\n `or set cdnFallback: false to disable this fallback.`,\n );\n}\n\n/** @internal Reset the CDN warn-once flag between unit tests. Tree-shaken from the bundle. */\nexport function __resetCdnWarnedForTests(): void {\n cdnWarned = false;\n}\n\nasync function tryViteWorkerUrl(legacy: boolean): Promise<string | undefined> {\n // Vite (and Nuxt) rewrite `?url` imports to a served asset when they process the dep.\n // `webpackIgnore` keeps webpack/rspack from emitting an orphan worker chunk for this\n // Vite-only specifier; they throw at runtime, we catch, and the static new URL branch wins.\n try {\n const mod = legacy\n ? await import(\"@@ZINE_PDF_WORKER_IMPORT:pdfjs-dist/legacy/build/pdf.worker.min.mjs?url@@\")\n : await import(\"@@ZINE_PDF_WORKER_IMPORT:pdfjs-dist/build/pdf.worker.min.mjs?url@@\");\n const url = (mod as { default?: unknown }).default;\n return typeof url === 'string' && url ? url : undefined;\n } catch {\n return undefined;\n }\n}\n\nasync function tryBundledWorkerUrl(legacy: boolean): Promise<string | undefined> {\n // Webpack 5 / rspack: static package-specifier new URL is rewritten to an emitted asset.\n // Specifiers must be string literals (not concatenated) for the bundler to see them.\n // Vite leaves this as a runtime join against @zinejs/pdf/dist → 404; resourceExists skips it.\n try {\n const bundled = legacy\n ? \"@@ZINE_PDF_WORKER_NEWURL:pdfjs-dist/legacy/build/pdf.worker.min.mjs@@\"\n : \"@@ZINE_PDF_WORKER_NEWURL:pdfjs-dist/build/pdf.worker.min.mjs@@\";\n return (await resourceExists(bundled)) ? bundled : undefined;\n } catch {\n return undefined;\n }\n}\n\nasync function trySiblingWorkerUrl(subpath: string): Promise<string | undefined> {\n // From `@zinejs/pdf/dist/index.js`, `../../../pdfjs-dist` is the package's node_modules\n // sibling under both pnpm (nested) and npm/yarn (hoisted). Concatenate so Vite does not\n // treat this as an import.meta.url asset rewrite (those need a static relative literal).\n // UMD rewrites `import.meta` to `{}`, so `.url` is undefined — skip rather than\n // `new URL(..., undefined)` throwing into the catch.\n const base = import.meta.url;\n if (!base) return undefined;\n try {\n const local = new URL('../../../pdfjs-dist/' + subpath, base).href;\n return (await resourceExists(local)) ? local : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** @internal The local worker probes, in resolution order. Real functions in production; unit tests\n * pass a substitute set to {@link resolveWorkerSrc} to drive the CDN / opt-out branches. */\nexport type WorkerProbes = {\n vite: (legacy: boolean) => Promise<string | undefined>;\n bundled: (legacy: boolean) => Promise<string | undefined>;\n sibling: (subpath: string) => Promise<string | undefined>;\n};\n\nconst defaultWorkerProbes: WorkerProbes = {\n vite: tryViteWorkerUrl,\n bundled: tryBundledWorkerUrl,\n sibling: trySiblingWorkerUrl,\n};\n\n/**\n * @internal\n * Resolve pdf.js's worker URL: explicit `workerSrc` → already-configured global → local probes\n * (Vite `?url`, webpack `new URL`, sibling install) → version-matched CDN (when `cdnFallback`).\n * Exported (not from the package entry) so tests can inject probe results; production calls it via\n * {@link PdfSource} with {@link defaultWorkerProbes}, so no test-only code ships in the bundle.\n */\nexport async function resolveWorkerSrc(\n args: {\n workerSrc: string | undefined;\n configured: string;\n legacy: boolean;\n cdnFallback: boolean;\n pdfjsVersion: string | undefined;\n },\n probes: WorkerProbes = defaultWorkerProbes,\n): Promise<string> {\n if (args.workerSrc) return args.workerSrc;\n if (args.configured) return args.configured;\n\n const subpath = args.legacy ? 'legacy/build/pdf.worker.min.mjs' : 'build/pdf.worker.min.mjs';\n\n const vite = await probes.vite(args.legacy);\n if (vite) return vite;\n const bundled = await probes.bundled(args.legacy);\n if (bundled) return bundled;\n const sibling = await probes.sibling(subpath);\n if (sibling) return sibling;\n\n // Last resort: same version as the loaded library (avoids API/worker mismatch).\n if (args.pdfjsVersion && args.cdnFallback) {\n const cdn = cdnWorkerUrl(args.pdfjsVersion, subpath);\n warnCdnOnce(cdn);\n return cdn;\n }\n\n throw new Error(\n \"PdfSource: couldn't auto-resolve pdf.js's worker. Pass a `workerSrc` URL \" +\n '(see the @zinejs/pdf README, \"The pdf.js worker\"), set pdfjs ' +\n 'GlobalWorkerOptions.workerSrc yourself, enable `cdnFallback`, ' +\n 'or pass a pre-created pdf.js document.',\n );\n}\n\n/**\n * @internal\n * Resolve the `pdfjs-dist` root that pdf.js's data folders (cmaps, standard fonts, ICC profiles,\n * wasm decoders) hang off. Explicit `assetsUrl` → the install the worker was served from →\n * version-matched CDN (when `cdnFallback`). A bundler that emits the worker as a hashed asset\n * leaves no install path to derive from, hence the CDN step.\n */\nexport function resolveAssetsUrl(args: {\n assetsUrl: string | undefined;\n workerSrc: string;\n cdnFallback: boolean;\n pdfjsVersion: string | undefined;\n}): string | undefined {\n if (args.assetsUrl) return args.assetsUrl.endsWith('/') ? args.assetsUrl : args.assetsUrl + '/';\n const install = /^(.*\\/pdfjs-dist(?:@[^/]+)?\\/)(?:legacy\\/)?build\\/pdf\\.worker[^/]*$/.exec(\n args.workerSrc.split(/[?#]/)[0] ?? '',\n );\n if (install) return install[1];\n if (args.pdfjsVersion && args.cdnFallback) return cdnWorkerUrl(args.pdfjsVersion, '');\n return undefined;\n}\n\n/**\n * Load pdf.js. CDN / UMD hosts typically expose `globalThis.pdfjsLib` from a\n * `<script>` of pdf.js; bundlers have no global and resolve the dynamic import.\n *\n * `legacy` picks the transpiled build, which runs on older engines too. Its entry and worker are a\n * matched pair, so `#resolveWorkerSrc` reads the same flag. Each specifier is a plain string literal\n * so a bundler can see and emit the chosen chunk; the branch means only the selected one is fetched.\n */\nasync function loadPdfjs(legacy: boolean): Promise<PdfjsModule> {\n const g = globalThis as typeof globalThis & { pdfjsLib?: PdfjsModule };\n if (g.pdfjsLib) return g.pdfjsLib;\n const mod = legacy ? await import('pdfjs-dist/legacy/build/pdf.mjs') : await import('pdfjs-dist');\n return mod as unknown as PdfjsModule;\n}\n\n/**\n * Renders PDF pages to canvases for zinejs. pdf.js is a dependency of this\n * package, loaded lazily so it never lands in the core bundle. Async — pass it\n * to `new Zine`, which calls `open()` (learning the page count) before building\n * spreads.\n */\nexport class PdfSource implements Source {\n pageCount = 0;\n #src: PdfSrc;\n #workerSrc: string | undefined;\n #cdnFallback: boolean;\n #assetsUrl: string | undefined;\n #renderScale: number;\n #preload: number;\n #doc: PdfDocumentLike | null = null;\n #maxCacheBytes: number;\n #progressive: boolean;\n #disableAutoFetch: boolean;\n #legacy: boolean;\n #onProgress: ((progress: LoadProgress) => void) | null = null;\n #cache = new Map<number, CacheEntry>();\n #cachedBytes = 0;\n #onUpdate: ((index: number) => void) | null = null;\n /** Zoomed page rasters keyed `index@scale`, apart from #cache: one is worth many normal pages\n * and would evict the whole spread from a byte cap sized for fit-to-screen. */\n #zoomCache = new Map<string, Promise<PageContent>>();\n\n /** Tiny downscaled stand-ins for pages evicted from #cache, so a revisit paints immediately and\n * then upgrades. Kept out of the byte cap (a few KB each, bounded by MAX_THUMBS). */\n #thumbs = new Map<number, PageContent>();\n\n /** Extracted page text, kept apart from #cache: text is scale-independent and negligible next\n * to a raster, so it has no place in the byte budget that evicts bitmaps. */\n #textCache = new Map<number, Promise<string>>();\n\n constructor(src: PdfSrc, options: PdfSourceOptions = {}) {\n this.#src = src;\n this.#workerSrc = options.workerSrc;\n this.#cdnFallback = options.cdnFallback ?? true;\n this.#assetsUrl = options.assetsUrl;\n this.#renderScale = options.renderScale ?? 1;\n this.#preload = options.preload ?? 1;\n this.#maxCacheBytes = options.maxCacheBytes ?? DEFAULT_MAX_CACHE_BYTES;\n this.#progressive = options.progressive ?? false;\n this.#disableAutoFetch = options.disableAutoFetch ?? false;\n this.#legacy = options.legacy ?? true;\n }\n\n /** Register a handler called when a page upgrades from its progressive low-res pass to crisp. */\n onPageUpdate(handler: (index: number) => void): void {\n this.#onUpdate = handler;\n }\n\n /** Register a handler called as the document downloads, driving a loading indicator. */\n onProgress(handler: (progress: LoadProgress) => void): void {\n this.#onProgress = handler;\n }\n\n async open(): Promise<void> {\n if (isPdfDocument(this.#src)) {\n this.#doc = this.#src;\n } else {\n const pdfjs = await loadPdfjs(this.#legacy);\n pdfjs.GlobalWorkerOptions.workerSrc = await this.#resolveWorkerSrc(\n pdfjs.GlobalWorkerOptions.workerSrc,\n pdfjs.version,\n );\n const params: DocParams =\n typeof this.#src === 'string' ? { url: this.#src } : { data: this.#src };\n if (this.#disableAutoFetch) params.disableAutoFetch = true;\n const assets = resolveAssetsUrl({\n assetsUrl: this.#assetsUrl,\n workerSrc: pdfjs.GlobalWorkerOptions.workerSrc,\n cdnFallback: this.#cdnFallback,\n pdfjsVersion: pdfjs.version,\n });\n if (assets) {\n params.cMapUrl = assets + 'cmaps/';\n params.standardFontDataUrl = assets + 'standard_fonts/';\n params.iccUrl = assets + 'iccs/';\n params.wasmUrl = assets + 'wasm/';\n }\n const task = pdfjs.getDocument(params);\n if (this.#onProgress) task.onProgress = this.#onProgress;\n this.#doc = await task.promise;\n }\n this.pageCount = this.#doc.numPages;\n }\n\n /**\n * Precedence: explicit `workerSrc` → already-configured global → auto default (Vite `?url`,\n * webpack `new URL`, sibling install, then version-matched CDN when `cdnFallback`). The ordering\n * and probes live in {@link resolveWorkerSrc}; this just forwards this source's settings.\n */\n #resolveWorkerSrc(configured: string, pdfjsVersion: string | undefined): Promise<string> {\n return resolveWorkerSrc({\n workerSrc: this.#workerSrc,\n configured,\n legacy: this.#legacy,\n cdnFallback: this.#cdnFallback,\n pdfjsVersion,\n });\n }\n\n get(index: number, opts?: PageRequest): Promise<PageContent> {\n // A zoomed request wants detail the fit-to-screen raster never had. PDF pages are vector, so\n // re-render at the magnification being viewed. Memoized per zoom level, not in the byte-capped\n // page cache: a zoomed page is many times the size of a normal one and would evict the whole\n // spread, and a reader who zooms out and back in should not pay to render it twice.\n if (opts && opts.scale > 1) return this.#renderZoomed(index, opts.scale, opts.maxSize);\n\n // Revisiting a page that was evicted under memory pressure: paint its thumbnail now, re-render\n // the full raster in the background, and signal an upgrade so the engine swaps it in when ready.\n const thumb = this.#cache.has(index) ? undefined : this.#thumbs.get(index);\n if (thumb) {\n this.#thumbs.delete(index); // re-created if this page is evicted again\n void this.#renderPage(index)\n .then(() => this.#onUpdate?.(index))\n .catch(() => {});\n for (let d = 1; d <= this.#preload; d++) this.prefetch([index - d, index + d]);\n return Promise.resolve(thumb);\n }\n\n const decoded = this.#renderPage(index);\n for (let d = 1; d <= this.#preload; d++) {\n this.prefetch([index - d, index + d]);\n }\n return decoded;\n }\n\n prefetch(indices: number[]): void {\n for (const i of indices) {\n if (i >= 0 && i < this.pageCount) {\n void this.#renderPage(i).catch(() => {}); // prefetch failures surface on a real get()\n }\n }\n }\n\n /**\n * The page's text, for search. Empty for a page with no text layer (a scan, or a pdf.js build\n * without text extraction) — callers treat that as \"nothing to match\" rather than an error.\n */\n getText(index: number): Promise<string> {\n const cached = this.#textCache.get(index);\n if (cached) return cached;\n const pending = this.#extractText(index).catch(() => '');\n this.#textCache.set(index, pending);\n return pending;\n }\n\n /**\n * The document's table of contents, flattened into the shape the outline panel wants.\n *\n * Empty when the PDF has no outline, which is common. Destinations are resolved to page\n * indices here rather than in the UI: doing it once, up front, keeps clicking an entry\n * instant, and an entry whose destination cannot be resolved is kept with a null page so the\n * heading still shows.\n */\n async getOutline(): Promise<OutlineItem[]> {\n const doc = this.#doc;\n if (!doc || typeof doc.getOutline !== 'function') return [];\n let nodes: PdfOutlineNode[] | null;\n try {\n nodes = await doc.getOutline();\n } catch {\n return [];\n }\n if (!nodes?.length) return [];\n const convert = async (list: PdfOutlineNode[]): Promise<OutlineItem[]> =>\n Promise.all(\n list.map(async (node) => ({\n // Raw title; the outline UI supplies a localized fallback when it is empty.\n title: (node.title ?? '').trim(),\n page: await this.#destinationPage(node.dest),\n children: node.items?.length ? await convert(node.items) : [],\n })),\n );\n return convert(nodes);\n }\n\n /** Resolve a pdf.js destination to a zero-based page index, or null if it cannot be. */\n async #destinationPage(dest: string | unknown[] | null | undefined): Promise<number | null> {\n const doc = this.#doc;\n if (!doc || dest == null) return null;\n try {\n // A named destination has to be looked up before it yields the usual explicit array.\n const explicit =\n typeof dest === 'string' ? await doc.getDestination?.(dest) : (dest as unknown[]);\n const ref = explicit?.[0];\n if (ref == null) return null;\n // Some destinations carry a bare page number instead of a reference.\n if (typeof ref === 'number') return ref;\n const index = await doc.getPageIndex?.(ref);\n return typeof index === 'number' ? index : null;\n } catch {\n return null; // a broken destination should not cost us the whole outline\n }\n }\n\n /**\n * The original PDF, for a download control.\n *\n * Null when the source was handed a pre-opened pdf.js document: the bytes belong to whoever\n * created it, and re-fetching them is not this class's call to make.\n */\n async getDownload(): Promise<DownloadInfo | null> {\n const src = this.#src;\n if (typeof src === 'string') {\n return { url: src, filename: filenameFromUrl(src) };\n }\n if (src instanceof ArrayBuffer || ArrayBuffer.isView(src)) {\n const blob = new Blob([src as BlobPart], { type: 'application/pdf' });\n return { url: URL.createObjectURL(blob), filename: 'document.pdf', revoke: true };\n }\n return null;\n }\n\n async #extractText(index: number): Promise<string> {\n if (this.#doc === null) {\n throw new Error('PdfSource: use after open() — the document is not loaded.');\n }\n const page = await this.#doc.getPage(index + 1); // pdf.js pages are 1-indexed\n if (typeof page.getTextContent !== 'function') return '';\n const content = await page.getTextContent();\n let text = '';\n for (const item of content.items) {\n if (typeof item.str !== 'string') continue; // marked-content entries carry no text\n text += item.str;\n // pdf.js splits a visual line into runs; only hasEOL ends one.\n text += item.hasEOL ? '\\n' : '';\n }\n return text;\n }\n\n destroy(): void {\n this.#doc?.destroy?.();\n this.#cache.clear();\n for (const pending of this.#zoomCache.values()) {\n void pending.then(releaseCanvas).catch(() => {});\n }\n this.#zoomCache.clear();\n for (const thumb of this.#thumbs.values()) releaseCanvas(thumb);\n this.#thumbs.clear();\n this.#textCache.clear();\n this.#cachedBytes = 0;\n this.#doc = null;\n }\n\n #renderPage(index: number): Promise<PageContent> {\n const existing = this.#cache.get(index);\n if (existing) {\n // Mark most-recently-used: re-insert so it sits at the end of the Map's order.\n this.#cache.delete(index);\n this.#cache.set(index, existing);\n return existing.promise;\n }\n\n const dpr = typeof devicePixelRatio === 'number' ? devicePixelRatio : 1;\n const full = this.#renderScale * dpr;\n const firstScale = this.#progressive ? full * PROGRESSIVE_LOW_RATIO : full;\n\n const entry: CacheEntry = { promise: undefined as unknown as Promise<PageContent>, bytes: 0 };\n entry.promise = this.#rasterize(index, firstScale).then(\n (content) => {\n entry.bytes = content.width * content.height * 4; // RGBA\n entry.content = content; // retained so eviction can downscale it to a thumbnail\n this.#cachedBytes += entry.bytes;\n this.#evictToFit(index);\n if (this.#progressive) this.#upgrade(index, entry, full);\n return content;\n },\n (error: unknown) => {\n this.#cache.delete(index); // let a later get() retry\n throw error;\n },\n );\n this.#cache.set(index, entry);\n return entry.promise;\n }\n\n /** Progressive: re-render at full resolution, swap it into the cache, and signal an update. */\n #upgrade(index: number, entry: CacheEntry, fullScale: number): void {\n void this.#rasterize(index, fullScale)\n .then((hi) => {\n if (this.#cache.get(index) !== entry) return; // evicted or replaced meanwhile\n this.#cachedBytes -= entry.bytes;\n entry.promise = Promise.resolve(hi);\n entry.content = hi;\n entry.bytes = hi.width * hi.height * 4;\n this.#cachedBytes += entry.bytes;\n this.#evictToFit(index);\n this.#onUpdate?.(index);\n })\n .catch(() => {}); // keep the low-res if the upgrade fails\n }\n\n /** Drop least-recently-used pages (front of the Map) until under the byte cap, keeping a small\n * thumbnail of each so a later revisit paints at once instead of blank while it re-renders. */\n #evictToFit(keepIndex: number): void {\n for (const [index, entry] of this.#cache) {\n if (this.#cachedBytes <= this.#maxCacheBytes) break;\n if (index === keepIndex || entry.bytes === 0) continue; // keep the newest; skip in-flight renders\n this.#stashThumb(index, entry);\n this.#cache.delete(index);\n this.#cachedBytes -= entry.bytes;\n }\n }\n\n /** Retain a downscaled placeholder for an evicted page, as the most-recent thumb, bounded LRU. */\n #stashThumb(index: number, entry: CacheEntry): void {\n const thumb = entry.content ? this.#makeThumb(entry.content) : null;\n if (!thumb) return;\n const prev = this.#thumbs.get(index);\n if (prev) releaseCanvas(prev);\n this.#thumbs.delete(index);\n this.#thumbs.set(index, thumb);\n while (this.#thumbs.size > MAX_THUMBS) {\n const oldest = this.#thumbs.keys().next().value as number;\n releaseCanvas(this.#thumbs.get(oldest)!);\n this.#thumbs.delete(oldest);\n }\n }\n\n /** Draw a page raster onto a tiny canvas. Null when no 2D canvas is available (e.g. SSR). */\n #makeThumb(content: PageContent): PageContent | null {\n const { width, height } = content;\n if (!width || !height || typeof document === 'undefined') return null;\n const factor = Math.min(1, THUMB_MAX_PX / Math.max(width, height));\n const canvas = document.createElement('canvas');\n canvas.width = Math.max(1, Math.round(width * factor));\n canvas.height = Math.max(1, Math.round(height * factor));\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n ctx.drawImage(content, 0, 0, canvas.width, canvas.height);\n return canvas;\n }\n\n /**\n * Render a whole page at zoom magnification, memoized by page and zoom level.\n *\n * Whole-page rather than a crop of what is visible: the renderer draws a page as one texture, so\n * a partial raster cannot be handed to it, and a crop would have to be re-rendered on every pan\n * anyway. Held apart from the byte-capped page cache, which is sized for fit-to-screen rasters.\n */\n #renderZoomed(index: number, zoom: number, maxSize?: number): Promise<PageContent> {\n const scale = Math.min(zoom, MAX_ZOOM_RENDER_SCALE);\n const key = `${index}@${scale}`;\n const cached = this.#zoomCache.get(key);\n if (cached) return cached;\n\n const dpr = typeof devicePixelRatio === 'number' ? devicePixelRatio : 1;\n const promise = this.#rasterize(index, this.#renderScale * dpr * scale, maxSize).catch(\n (error: unknown) => {\n this.#zoomCache.delete(key); // let a later zoom retry\n throw error;\n },\n );\n // One zoom level at a time, but both pages of the spread at that level.\n for (const [k, pending] of this.#zoomCache) {\n if (!k.endsWith(`@${scale}`)) {\n this.#zoomCache.delete(k);\n void pending.then(releaseCanvas).catch(() => {});\n }\n }\n this.#zoomCache.set(key, promise);\n return promise;\n }\n\n async #rasterize(index: number, scale: number, maxSize?: number): Promise<PageContent> {\n if (this.#doc === null) throw new Error('PdfSource: use after open() — the document is not loaded.');\n const page = await this.#doc.getPage(index + 1); // pdf.js pages are 1-indexed\n const base = page.getViewport({ scale: 1 });\n const longest = Math.max(base.width, base.height);\n // A page whose size in points exceeds the screen (print-sized exports, posters) can never be\n // shown whole at more than screen size, so rasterize it as if it fit the screen. Zoom and\n // progressive scales are multiples of this, so they shrink with it.\n const screenPx = typeof screen === 'undefined' ? 0 : Math.max(screen.width, screen.height);\n if (screenPx > 0 && longest > screenPx) scale *= screenPx / longest;\n // Cap the scale so neither axis exceeds the GPU's texture limit: past it the upload paints black\n // (the renderer downscales as a backstop, but rendering to fit avoids the wasted memory spike).\n if (maxSize !== undefined && maxSize > 0) scale = Math.min(scale, maxSize / longest);\n // Past Safari's canvas area limit the canvas fails to allocate, and iOS kills the tab well\n // before that from the memory alone, with no error to catch.\n const area = base.width * base.height * scale * scale;\n if (area > MAX_CANVAS_PIXELS) scale *= Math.sqrt(MAX_CANVAS_PIXELS / area);\n const viewport = page.getViewport({ scale });\n const canvas = document.createElement('canvas');\n canvas.width = Math.ceil(viewport.width);\n canvas.height = Math.ceil(viewport.height);\n await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;\n // The raster is all we keep. pdf.js otherwise holds the page's decoded images (64 MB for one\n // 16-megapixel photo) until the document closes, and on a photo-heavy PDF that alone outgrows\n // an iOS tab's memory.\n page.cleanup?.();\n return canvas;\n }\n}\n"],"mappings":"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"}
@@ -22,6 +22,8 @@ interface PdfPageLike {
22
22
  hasEOL?: boolean;
23
23
  }[];
24
24
  }>;
25
+ /** Optional: frees the page's decoded images; a no-op while another render of it is running. */
26
+ cleanup?(): boolean;
25
27
  }
26
28
  /** A pdf.js outline entry. `dest` is either a named destination or an explicit array whose first
27
29
  * element references the target page. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zinejs/pdf",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "PDF content source for zinejs, powered by pdf.js.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -47,10 +47,10 @@
47
47
  "pdfjs-dist": ">=4"
48
48
  },
49
49
  "peerDependencies": {
50
- "@zinejs/core": "^1.0.1"
50
+ "@zinejs/core": "^1.0.2"
51
51
  },
52
52
  "devDependencies": {
53
- "@zinejs/core": "^1.0.1"
53
+ "@zinejs/core": "^1.0.2"
54
54
  },
55
55
  "size-limit": [
56
56
  {