@zinejs/pdf 0.9.0 → 0.9.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/LICENSE ADDED
@@ -0,0 +1,73 @@
1
+ # PolyForm Noncommercial License 1.0.0
2
+
3
+ <https://polyformproject.org/licenses/noncommercial/1.0.0>
4
+
5
+ ## Acceptance
6
+
7
+ In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
8
+
9
+ ## Copyright License
10
+
11
+ The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
12
+
13
+ ## Distribution License
14
+
15
+ The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
16
+
17
+ ## Notices
18
+
19
+ You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
20
+
21
+ > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
22
+
23
+ ## Changes and New Works License
24
+
25
+ The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
26
+
27
+ ## Patent License
28
+
29
+ The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
30
+
31
+ ## Noncommercial Purposes
32
+
33
+ Any noncommercial purpose is a permitted purpose.
34
+
35
+ ## Personal Uses
36
+
37
+ Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.
38
+
39
+ ## Noncommercial Organizations
40
+
41
+ Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.
42
+
43
+ ## Fair Use
44
+
45
+ You may have "fair use" rights for the software under the law. These terms do not limit them.
46
+
47
+ ## No Other Rights
48
+
49
+ These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
50
+
51
+ ## Patent Defense
52
+
53
+ If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
54
+
55
+ ## Violations
56
+
57
+ The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
58
+
59
+ ## No Liability
60
+
61
+ ***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
62
+
63
+ ## Definitions
64
+
65
+ The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
66
+
67
+ **You** refers to the individual or entity agreeing to these terms.
68
+
69
+ **Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
70
+
71
+ **Your licenses** are all the licenses granted to you for the software under these terms.
72
+
73
+ **Use** means anything you do with the software requiring one of your licenses.
package/README.md CHANGED
@@ -34,23 +34,60 @@ new PdfSource(await getDocument(url).promise)
34
34
 
35
35
  ## The pdf.js worker
36
36
 
37
- pdf.js parses and rasterizes in a Web Worker. By default `PdfSource` points at the **legacy** worker (because `legacy` defaults to `true`):
37
+ pdf.js parses and rasterizes in a Web Worker. By default `PdfSource` finds the worker via Vite's
38
+ `?url` import, webpack's `new URL('pdfjs-dist/…', import.meta.url)` emit, or the sibling
39
+ `pdfjs-dist` install. If that cannot be resolved, it falls back to a jsDelivr URL pinned to the
40
+ same `pdfjs-dist` version that loaded, and warns once in the console. Set `cdnFallback: false`
41
+ for CSP-restricted or offline apps (then pass `workerSrc`, or the open fails clearly instead of
42
+ loading a network worker that CSP would block).
43
+
44
+ Resolution order: `workerSrc` option, then `pdfjsLib.GlobalWorkerOptions.workerSrc`, then that
45
+ auto-default. Pass `workerSrc` for CDN / UMD, offline / CSP setups, or if auto-resolve fails in
46
+ your bundler:
38
47
 
39
48
  ```js
40
- new URL('pdfjs-dist/legacy/build/pdf.worker.min.mjs', import.meta.url).href
49
+ import { PdfSource } from '@zinejs/pdf'
50
+ import workerSrc from 'pdfjs-dist/legacy/build/pdf.worker.min.mjs?url'
51
+
52
+ new PdfSource('document.pdf', { workerSrc })
53
+ // CSP / offline: also disable the CDN last resort
54
+ new PdfSource('document.pdf', { workerSrc, cdnFallback: false })
41
55
  ```
42
56
 
43
- Vite, webpack 5, and esbuild rewrite that literal and emit the asset. Resolution order: `workerSrc` option, then `pdfjsLib.GlobalWorkerOptions.workerSrc`, then the auto-default. Pass `workerSrc` only for CDN / UMD or a file you copied yourself, and keep it on the same build as the library (legacy vs modern).
57
+ Keep the worker on the same build as the library (`legacy` vs modern). If you already set
58
+ `GlobalWorkerOptions.workerSrc`, leave `workerSrc` off. A pre-created pdf.js document needs no
59
+ worker from this package.
60
+
61
+ ### Vite: exclude from dependency pre-bundling
62
+
63
+ Vite's dependency optimizer may not be able to follow the worker's `?url` import, so pre-bundling
64
+ `@zinejs/pdf` fails to start the dev server (`Could not load .../pdf.worker.min.mjs?url`).
65
+ Exclude it so Vite's normal pipeline resolves the worker:
66
+
67
+ ```js
68
+ // vite.config.js
69
+ import { defineConfig } from 'vite'
70
+
71
+ export default defineConfig({
72
+ optimizeDeps: { exclude: ['@zinejs/pdf'] },
73
+ })
74
+ ```
44
75
 
45
- If you already set `GlobalWorkerOptions.workerSrc`, leave `workerSrc` off. A pre-created pdf.js document needs no worker from this package.
76
+ (`npm create @zinejs` sets this up for you in the PDF starter.)
46
77
 
47
- CDN / no bundler: load pdf.js first, expose `globalThis.pdfjsLib`, then the UMD builds (core first). See the [HTML CDN example](https://zinejs.com/examples/html-cdn).
78
+ CDN / no bundler: load pdf.js first, expose `globalThis.pdfjsLib`, then the UMD builds (core
79
+ first). See the [HTML CDN example](https://zinejs.com/examples/html-cdn). A UMD host has no
80
+ bundler to resolve the worker locally, so it uses your `workerSrc`, an already-set
81
+ `GlobalWorkerOptions.workerSrc`, or the version-matched CDN fallback (matched to the global
82
+ pdf.js's version; silent here, since a CSP/offline failure surfaces through the browser and pdf.js
83
+ anyway). Set `cdnFallback: false` to require explicit configuration instead.
48
84
 
49
85
  ## Options
50
86
 
51
87
  ```js
52
88
  new PdfSource(src, {
53
89
  workerSrc, // override the auto-resolved worker URL
90
+ cdnFallback: true, // false: never load the worker from jsDelivr (CSP / offline)
54
91
  renderScale: 1, // pages rasterize at renderScale × devicePixelRatio
55
92
  preload: 1, // adjacent pages to prefetch (default 1)
56
93
  maxCacheBytes, // soft cap on cached page bytes (default ~256 MB)
package/dist/index.js CHANGED
@@ -13,73 +13,149 @@ function n(e) {
13
13
  }
14
14
  }
15
15
  async function r(e) {
16
+ try {
17
+ let t = await fetch(e, { method: "HEAD" });
18
+ if (t.ok) return !0;
19
+ if (t.status === 405 || t.status === 501) {
20
+ let t = await fetch(e, {
21
+ method: "GET",
22
+ headers: { Range: "bytes=0-0" }
23
+ });
24
+ return t.ok || t.status === 206;
25
+ }
26
+ return !1;
27
+ } catch {
28
+ return !1;
29
+ }
30
+ }
31
+ function i(e, t) {
32
+ return `https://cdn.jsdelivr.net/npm/pdfjs-dist@${e}/${t}`;
33
+ }
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.`)));
37
+ }
38
+ async function s(e) {
39
+ try {
40
+ 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
+ return typeof t == "string" && t ? t : void 0;
42
+ } catch {
43
+ return;
44
+ }
45
+ }
46
+ async function c(e) {
47
+ try {
48
+ 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;
50
+ } catch {
51
+ return;
52
+ }
53
+ }
54
+ async function l(e) {
55
+ let t = import.meta.url;
56
+ if (t) try {
57
+ let n = new URL("../../../pdfjs-dist/" + e, t).href;
58
+ return await r(n) ? n : void 0;
59
+ } catch {
60
+ return;
61
+ }
62
+ }
63
+ var u = {
64
+ vite: s,
65
+ bundled: c,
66
+ sibling: l
67
+ };
68
+ async function d(e, t = u) {
69
+ if (e.workerSrc) return e.workerSrc;
70
+ if (e.configured) return e.configured;
71
+ let n = e.legacy ? "legacy/build/pdf.worker.min.mjs" : "build/pdf.worker.min.mjs", r = await t.vite(e.legacy);
72
+ 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;
77
+ if (e.pdfjsVersion && e.cdnFallback) {
78
+ let t = i(e.pdfjsVersion, n);
79
+ return o(t), t;
80
+ }
81
+ 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
+ }
83
+ async function f(e) {
16
84
  let t = globalThis;
17
85
  return t.pdfjsLib ? t.pdfjsLib : e ? await import("pdfjs-dist/legacy/build/pdf.mjs") : await import("pdfjs-dist");
18
86
  }
19
- var i = class {
87
+ var p = class {
20
88
  pageCount = 0;
21
89
  #e;
22
90
  #t;
23
91
  #n;
24
92
  #r;
25
- #i = null;
26
- #a;
93
+ #i;
94
+ #a = null;
27
95
  #o;
28
96
  #s;
29
97
  #c;
30
- #l = null;
31
- #u = /* @__PURE__ */ new Map();
32
- #d = 0;
33
- #f = null;
34
- #p = /* @__PURE__ */ new Map();
98
+ #l;
99
+ #u = null;
100
+ #d = /* @__PURE__ */ new Map();
101
+ #f = 0;
102
+ #p = null;
35
103
  #m = /* @__PURE__ */ new Map();
104
+ #h = /* @__PURE__ */ new Map();
105
+ #g = /* @__PURE__ */ new Map();
36
106
  constructor(e, t = {}) {
37
- this.#e = e, this.#t = t.workerSrc, this.#n = t.renderScale ?? 1, this.#r = t.preload ?? 1, this.#a = t.maxCacheBytes ?? 268435456, this.#o = t.progressive ?? !1, this.#s = t.disableAutoFetch ?? !1, this.#c = t.legacy ?? !0;
107
+ this.#e = e, this.#t = t.workerSrc, this.#n = t.cdnFallback ?? !0, this.#r = t.renderScale ?? 1, this.#i = t.preload ?? 1, this.#o = t.maxCacheBytes ?? 268435456, this.#s = t.progressive ?? !1, this.#c = t.disableAutoFetch ?? !1, this.#l = t.legacy ?? !0;
38
108
  }
39
109
  onPageUpdate(e) {
40
- this.#f = e;
110
+ this.#p = e;
41
111
  }
42
112
  onProgress(e) {
43
- this.#l = e;
113
+ this.#u = e;
44
114
  }
45
115
  async open() {
46
- if (t(this.#e)) this.#i = this.#e;
116
+ if (t(this.#e)) this.#a = this.#e;
47
117
  else {
48
- let e = await r(this.#c);
49
- e.GlobalWorkerOptions.workerSrc = this.#h(e.GlobalWorkerOptions.workerSrc);
118
+ let e = await f(this.#l);
119
+ e.GlobalWorkerOptions.workerSrc = await this.#_(e.GlobalWorkerOptions.workerSrc, e.version);
50
120
  let t = typeof this.#e == "string" ? { url: this.#e } : { data: this.#e };
51
- this.#s && (t.disableAutoFetch = !0);
121
+ this.#c && (t.disableAutoFetch = !0);
52
122
  let n = e.getDocument(t);
53
- this.#l && (n.onProgress = this.#l), this.#i = await n.promise;
123
+ this.#u && (n.onProgress = this.#u), this.#a = await n.promise;
54
124
  }
55
- this.pageCount = this.#i.numPages;
125
+ this.pageCount = this.#a.numPages;
56
126
  }
57
- #h(e) {
58
- if (this.#t) return this.#t;
59
- if (e) return e;
60
- try {
61
- return this.#c ? (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);
62
- } catch {
63
- 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, or pass a pre-created pdf.js document.");
64
- }
127
+ #_(e, t) {
128
+ return d({
129
+ workerSrc: this.#t,
130
+ configured: e,
131
+ legacy: this.#l,
132
+ cdnFallback: this.#n,
133
+ pdfjsVersion: t
134
+ });
65
135
  }
66
136
  get(e, t) {
67
- if (t && t.scale > 1) return this.#x(e, t.scale, t.maxSize);
68
- let n = this.#v(e);
69
- for (let t = 1; t <= this.#r; t++) this.prefetch([e - t, e + t]);
70
- return n;
137
+ if (t && t.scale > 1) return this.#T(e, t.scale, t.maxSize);
138
+ let n = this.#d.has(e) ? void 0 : this.#h.get(e);
139
+ if (n) {
140
+ this.#h.delete(e), this.#b(e).then(() => this.#p?.(e)).catch(() => {});
141
+ for (let t = 1; t <= this.#i; t++) this.prefetch([e - t, e + t]);
142
+ return Promise.resolve(n);
143
+ }
144
+ let r = this.#b(e);
145
+ for (let t = 1; t <= this.#i; t++) this.prefetch([e - t, e + t]);
146
+ return r;
71
147
  }
72
148
  prefetch(e) {
73
- for (let t of e) t >= 0 && t < this.pageCount && this.#v(t).catch(() => {});
149
+ for (let t of e) t >= 0 && t < this.pageCount && this.#b(t).catch(() => {});
74
150
  }
75
151
  getText(e) {
76
- let t = this.#m.get(e);
152
+ let t = this.#g.get(e);
77
153
  if (t) return t;
78
- let n = this.#_(e).catch(() => "");
79
- return this.#m.set(e, n), n;
154
+ let n = this.#y(e).catch(() => "");
155
+ return this.#g.set(e, n), n;
80
156
  }
81
157
  async getOutline() {
82
- let e = this.#i;
158
+ let e = this.#a;
83
159
  if (!e || typeof e.getOutline != "function") return [];
84
160
  let t;
85
161
  try {
@@ -89,14 +165,14 @@ var i = class {
89
165
  }
90
166
  if (!t?.length) return [];
91
167
  let n = async (e) => Promise.all(e.map(async (e) => ({
92
- title: (e.title ?? "").trim() || "Untitled",
93
- page: await this.#g(e.dest),
168
+ title: (e.title ?? "").trim(),
169
+ page: await this.#v(e.dest),
94
170
  children: e.items?.length ? await n(e.items) : []
95
171
  })));
96
172
  return n(t);
97
173
  }
98
- async #g(e) {
99
- let t = this.#i;
174
+ async #v(e) {
175
+ let t = this.#a;
100
176
  if (!t || e == null) return null;
101
177
  try {
102
178
  let n = (typeof e == "string" ? await t.getDestination?.(e) : e)?.[0];
@@ -124,53 +200,72 @@ var i = class {
124
200
  }
125
201
  return null;
126
202
  }
127
- async #_(e) {
128
- if (this.#i === null) throw Error("PdfSource: use after open() — the document is not loaded.");
129
- let t = await this.#i.getPage(e + 1);
203
+ async #y(e) {
204
+ if (this.#a === null) throw Error("PdfSource: use after open() — the document is not loaded.");
205
+ let t = await this.#a.getPage(e + 1);
130
206
  if (typeof t.getTextContent != "function") return "";
131
207
  let n = await t.getTextContent(), r = "";
132
208
  for (let e of n.items) typeof e.str == "string" && (r += e.str, r += e.hasEOL ? "\n" : "");
133
209
  return r;
134
210
  }
135
211
  destroy() {
136
- this.#i?.destroy?.(), this.#u.clear();
137
- for (let t of this.#p.values()) t.then(e).catch(() => {});
138
- this.#p.clear(), this.#m.clear(), this.#d = 0, this.#i = null;
139
- }
140
- #v(e) {
141
- let t = this.#u.get(e);
142
- if (t) return this.#u.delete(e), this.#u.set(e, t), t.promise;
143
- let n = typeof devicePixelRatio == "number" ? devicePixelRatio : 1, r = this.#n * n, i = this.#o ? r * .35 : r, a = {
212
+ this.#a?.destroy?.(), this.#d.clear();
213
+ for (let t of this.#m.values()) t.then(e).catch(() => {});
214
+ this.#m.clear();
215
+ for (let t of this.#h.values()) e(t);
216
+ this.#h.clear(), this.#g.clear(), this.#f = 0, this.#a = null;
217
+ }
218
+ #b(e) {
219
+ let t = this.#d.get(e);
220
+ if (t) return this.#d.delete(e), this.#d.set(e, t), t.promise;
221
+ let n = typeof devicePixelRatio == "number" ? devicePixelRatio : 1, r = this.#r * n, i = this.#s ? r * .35 : r, a = {
144
222
  promise: void 0,
145
223
  bytes: 0
146
224
  };
147
- return a.promise = this.#S(e, i).then((t) => (a.bytes = t.width * t.height * 4, this.#d += a.bytes, this.#b(e), this.#o && this.#y(e, a, r), t), (t) => {
148
- throw this.#u.delete(e), t;
149
- }), this.#u.set(e, a), a.promise;
225
+ return a.promise = this.#E(e, i).then((t) => (a.bytes = t.width * t.height * 4, a.content = t, this.#f += a.bytes, this.#S(e), this.#s && this.#x(e, a, r), t), (t) => {
226
+ throw this.#d.delete(e), t;
227
+ }), this.#d.set(e, a), a.promise;
150
228
  }
151
- #y(e, t, n) {
152
- this.#S(e, n).then((n) => {
153
- this.#u.get(e) === t && (this.#d -= t.bytes, t.promise = Promise.resolve(n), t.bytes = n.width * n.height * 4, this.#d += t.bytes, this.#b(e), this.#f?.(e));
229
+ #x(e, t, n) {
230
+ this.#E(e, n).then((n) => {
231
+ this.#d.get(e) === t && (this.#f -= t.bytes, t.promise = Promise.resolve(n), t.content = n, t.bytes = n.width * n.height * 4, this.#f += t.bytes, this.#S(e), this.#p?.(e));
154
232
  }).catch(() => {});
155
233
  }
156
- #b(e) {
157
- for (let [t, n] of this.#u) {
158
- if (this.#d <= this.#a) break;
159
- t === e || n.bytes === 0 || (this.#u.delete(t), this.#d -= n.bytes);
234
+ #S(e) {
235
+ for (let [t, n] of this.#d) {
236
+ if (this.#f <= this.#o) break;
237
+ t === e || n.bytes === 0 || (this.#C(t, n), this.#d.delete(t), this.#f -= n.bytes);
238
+ }
239
+ }
240
+ #C(t, n) {
241
+ let r = n.content ? this.#w(n.content) : null;
242
+ if (!r) return;
243
+ let i = this.#h.get(t);
244
+ for (i && e(i), this.#h.delete(t), this.#h.set(t, r); this.#h.size > 200;) {
245
+ let t = this.#h.keys().next().value;
246
+ e(this.#h.get(t)), this.#h.delete(t);
160
247
  }
161
248
  }
162
- #x(t, n, r) {
163
- let i = Math.min(n, 6), a = `${t}@${i}`, o = this.#p.get(a);
249
+ #w(e) {
250
+ let { width: t, height: n } = e;
251
+ if (!t || !n || typeof document > "u") return null;
252
+ let r = Math.min(1, 96 / Math.max(t, n)), i = document.createElement("canvas");
253
+ i.width = Math.max(1, Math.round(t * r)), i.height = Math.max(1, Math.round(n * r));
254
+ let a = i.getContext("2d");
255
+ return a ? (a.drawImage(e, 0, 0, i.width, i.height), i) : null;
256
+ }
257
+ #T(t, n, r) {
258
+ let i = Math.min(n, 6), a = `${t}@${i}`, o = this.#m.get(a);
164
259
  if (o) return o;
165
- let s = typeof devicePixelRatio == "number" ? devicePixelRatio : 1, c = this.#S(t, this.#n * s * i, r).catch((e) => {
166
- throw this.#p.delete(a), e;
260
+ let s = typeof devicePixelRatio == "number" ? devicePixelRatio : 1, c = this.#E(t, this.#r * s * i, r).catch((e) => {
261
+ throw this.#m.delete(a), e;
167
262
  });
168
- for (let [t, n] of this.#p) t.endsWith(`@${i}`) || (this.#p.delete(t), n.then(e).catch(() => {}));
169
- return this.#p.set(a, c), c;
263
+ for (let [t, n] of this.#m) t.endsWith(`@${i}`) || (this.#m.delete(t), n.then(e).catch(() => {}));
264
+ return this.#m.set(a, c), c;
170
265
  }
171
- async #S(e, t, n) {
172
- if (this.#i === null) throw Error("PdfSource: use after open() — the document is not loaded.");
173
- let r = await this.#i.getPage(e + 1);
266
+ async #E(e, t, n) {
267
+ if (this.#a === null) throw Error("PdfSource: use after open() — the document is not loaded.");
268
+ let r = await this.#a.getPage(e + 1);
174
269
  if (n !== void 0 && n > 0) {
175
270
  let e = r.getViewport({ scale: 1 }), i = n / Math.max(e.width, e.height);
176
271
  t = Math.min(t, i);
@@ -183,6 +278,6 @@ var i = class {
183
278
  }
184
279
  };
185
280
  //#endregion
186
- export { i as PdfSource };
281
+ export { p as PdfSource };
187
282
 
188
283
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["#src","#workerSrc","#renderScale","#preload","#maxCacheBytes","#progressive","#disableAutoFetch","#legacy","#onUpdate","#onProgress","#doc","#resolveWorkerSrc","#renderZoomed","#renderPage","#textCache","#extractText","#destinationPage","#cache","#zoomCache","#cachedBytes","#rasterize","#evictToFit","#upgrade"],"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}\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}\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 under bundlers; pass it for CDN / UMD / custom paths. */\n workerSrc?: 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\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}\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/**\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 #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 /** 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.#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 = this.#resolveWorkerSrc(pdfjs.GlobalWorkerOptions.workerSrc);\n const params: DocParams =\n typeof this.#src === 'string' ? { url: this.#src } : { data: this.#src };\n if (this.#disableAutoFetch) params.disableAutoFetch = true;\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 /** Precedence: an explicit `workerSrc` → an already-configured global → an auto-resolved default. */\n #resolveWorkerSrc(configured: string): string {\n if (this.#workerSrc) return this.#workerSrc;\n if (configured) return configured;\n try {\n // Bundlers (Vite / webpack 5 / esbuild) statically rewrite these and emit the worker,\n // so the common case needs no `workerSrc`. Non-bundler / UMD hosts pass it explicitly. The\n // worker must match the build loaded in loadPdfjs — a legacy main with a modern worker (or\n // vice versa) mismatches — so the same #legacy flag chooses here too.\n return this.#legacy\n ? \"@@ZINE_PDF_WORKER:pdfjs-dist/legacy/build/pdf.worker.min.mjs@@\"\n : \"@@ZINE_PDF_WORKER:pdfjs-dist/build/pdf.worker.min.mjs@@\";\n } catch {\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, or pass a pre-created pdf.js document.',\n );\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 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 title: (node.title ?? '').trim() || 'Untitled',\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 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 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.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. */\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.#cache.delete(index);\n this.#cachedBytes -= entry.bytes;\n }\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","#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}\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 /** 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 * 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 #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.#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 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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"}
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;function i(e){typeof HTMLCanvasElement<`u`&&e instanceof HTMLCanvasElement&&(e.width=0,e.height=0)}function a(e){return typeof e==`object`&&!!e&&`numPages`in e&&typeof e.getPage==`function`}function o(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 s(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=null;#a;#o;#s;#c;#l=null;#u=new Map;#d=0;#f=null;#p=new Map;#m=new Map;constructor(e,n={}){this.#e=e,this.#t=n.workerSrc,this.#n=n.renderScale??1,this.#r=n.preload??1,this.#a=n.maxCacheBytes??t,this.#o=n.progressive??!1,this.#s=n.disableAutoFetch??!1,this.#c=n.legacy??!0}onPageUpdate(e){this.#f=e}onProgress(e){this.#l=e}async open(){if(a(this.#e))this.#i=this.#e;else{let e=await s(this.#c);e.GlobalWorkerOptions.workerSrc=this.#h(e.GlobalWorkerOptions.workerSrc);let t=typeof this.#e==`string`?{url:this.#e}:{data:this.#e};this.#s&&(t.disableAutoFetch=!0);let n=e.getDocument(t);this.#l&&(n.onProgress=this.#l),this.#i=await n.promise}this.pageCount=this.#i.numPages}#h(e){if(this.#t)return this.#t;if(e)return e;try{return this.#c?(() => { throw new Error('PdfSource: pass workerSrc for UMD/CDN'); })():(() => { throw new Error('PdfSource: pass workerSrc for UMD/CDN'); })()}catch{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, or pass a pre-created pdf.js document.`)}}get(e,t){if(t&&t.scale>1)return this.#x(e,t.scale,t.maxSize);let n=this.#v(e);for(let t=1;t<=this.#r;t++)this.prefetch([e-t,e+t]);return n}prefetch(e){for(let t of e)t>=0&&t<this.pageCount&&this.#v(t).catch(()=>{})}getText(e){let t=this.#m.get(e);if(t)return t;let n=this.#_(e).catch(()=>``);return this.#m.set(e,n),n}async getOutline(){let e=this.#i;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()||`Untitled`,page:await this.#g(e.dest),children:e.items?.length?await n(e.items):[]})));return n(t)}async#g(e){let t=this.#i;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:o(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#_(e){if(this.#i===null)throw Error(`PdfSource: use after open() — the document is not loaded.`);let t=await this.#i.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.#i?.destroy?.(),this.#u.clear();for(let e of this.#p.values())e.then(i).catch(()=>{});this.#p.clear(),this.#m.clear(),this.#d=0,this.#i=null}#v(e){let t=this.#u.get(e);if(t)return this.#u.delete(e),this.#u.set(e,t),t.promise;let r=typeof devicePixelRatio==`number`?devicePixelRatio:1,i=this.#n*r,a=this.#o?i*n:i,o={promise:void 0,bytes:0};return o.promise=this.#S(e,a).then(t=>(o.bytes=t.width*t.height*4,this.#d+=o.bytes,this.#b(e),this.#o&&this.#y(e,o,i),t),t=>{throw this.#u.delete(e),t}),this.#u.set(e,o),o.promise}#y(e,t,n){this.#S(e,n).then(n=>{this.#u.get(e)===t&&(this.#d-=t.bytes,t.promise=Promise.resolve(n),t.bytes=n.width*n.height*4,this.#d+=t.bytes,this.#b(e),this.#f?.(e))}).catch(()=>{})}#b(e){for(let[t,n]of this.#u){if(this.#d<=this.#a)break;t===e||n.bytes===0||(this.#u.delete(t),this.#d-=n.bytes)}}#x(e,t,n){let a=Math.min(t,r),o=`${e}@${a}`,s=this.#p.get(o);if(s)return s;let c=typeof devicePixelRatio==`number`?devicePixelRatio:1,l=this.#S(e,this.#n*c*a,n).catch(e=>{throw this.#p.delete(o),e});for(let[e,t]of this.#p)e.endsWith(`@${a}`)||(this.#p.delete(e),t.then(i).catch(()=>{}));return this.#p.set(o,l),l}async#S(e,t,n){if(this.#i===null)throw Error(`PdfSource: use after open() — the document is not loaded.`);let r=await this.#i.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=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.")}async function v(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=null;#o;#s;#c;#l;#u=null;#d=new Map;#f=0;#p=null;#m=new Map;#h=new Map;#g=new Map;constructor(e,n={}){this.#e=e,this.#t=n.workerSrc,this.#n=n.cdnFallback??!0,this.#r=n.renderScale??1,this.#i=n.preload??1,this.#o=n.maxCacheBytes??t,this.#s=n.progressive??!1,this.#c=n.disableAutoFetch??!1,this.#l=n.legacy??!0}onPageUpdate(e){this.#p=e}onProgress(e){this.#u=e}async open(){if(s(this.#e))this.#a=this.#e;else{let e=await v(this.#l);e.GlobalWorkerOptions.workerSrc=await this.#_(e.GlobalWorkerOptions.workerSrc,e.version);let t=typeof this.#e==`string`?{url:this.#e}:{data:this.#e};this.#c&&(t.disableAutoFetch=!0);let n=e.getDocument(t);this.#u&&(n.onProgress=this.#u),this.#a=await n.promise}this.pageCount=this.#a.numPages}#_(e,t){return _({workerSrc:this.#t,configured:e,legacy:this.#l,cdnFallback:this.#n,pdfjsVersion:t})}get(e,t){if(t&&t.scale>1)return this.#T(e,t.scale,t.maxSize);let n=this.#d.has(e)?void 0:this.#h.get(e);if(n){this.#h.delete(e),this.#b(e).then(()=>this.#p?.(e)).catch(()=>{});for(let t=1;t<=this.#i;t++)this.prefetch([e-t,e+t]);return Promise.resolve(n)}let r=this.#b(e);for(let t=1;t<=this.#i;t++)this.prefetch([e-t,e+t]);return r}prefetch(e){for(let t of e)t>=0&&t<this.pageCount&&this.#b(t).catch(()=>{})}getText(e){let t=this.#g.get(e);if(t)return t;let n=this.#y(e).catch(()=>``);return this.#g.set(e,n),n}async getOutline(){let e=this.#a;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.#v(e.dest),children:e.items?.length?await n(e.items):[]})));return n(t)}async#v(e){let t=this.#a;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#y(e){if(this.#a===null)throw Error(`PdfSource: use after open() — the document is not loaded.`);let t=await this.#a.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.#a?.destroy?.(),this.#d.clear();for(let e of this.#m.values())e.then(o).catch(()=>{});this.#m.clear();for(let e of this.#h.values())o(e);this.#h.clear(),this.#g.clear(),this.#f=0,this.#a=null}#b(e){let t=this.#d.get(e);if(t)return this.#d.delete(e),this.#d.set(e,t),t.promise;let r=typeof devicePixelRatio==`number`?devicePixelRatio:1,i=this.#r*r,a=this.#s?i*n:i,o={promise:void 0,bytes:0};return o.promise=this.#E(e,a).then(t=>(o.bytes=t.width*t.height*4,o.content=t,this.#f+=o.bytes,this.#S(e),this.#s&&this.#x(e,o,i),t),t=>{throw this.#d.delete(e),t}),this.#d.set(e,o),o.promise}#x(e,t,n){this.#E(e,n).then(n=>{this.#d.get(e)===t&&(this.#f-=t.bytes,t.promise=Promise.resolve(n),t.content=n,t.bytes=n.width*n.height*4,this.#f+=t.bytes,this.#S(e),this.#p?.(e))}).catch(()=>{})}#S(e){for(let[t,n]of this.#d){if(this.#f<=this.#o)break;t===e||n.bytes===0||(this.#C(t,n),this.#d.delete(t),this.#f-=n.bytes)}}#C(e,t){let n=t.content?this.#w(t.content):null;if(!n)return;let r=this.#h.get(e);for(r&&o(r),this.#h.delete(e),this.#h.set(e,n);this.#h.size>a;){let e=this.#h.keys().next().value;o(this.#h.get(e)),this.#h.delete(e)}}#w(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}#T(e,t,n){let i=Math.min(t,r),a=`${e}@${i}`,s=this.#m.get(a);if(s)return s;let c=typeof devicePixelRatio==`number`?devicePixelRatio:1,l=this.#E(e,this.#r*c*i,n).catch(e=>{throw this.#m.delete(a),e});for(let[e,t]of this.#m)e.endsWith(`@${i}`)||(this.#m.delete(e),t.then(o).catch(()=>{}));return this.#m.set(a,l),l}async#E(e,t,n){if(this.#a===null)throw Error(`PdfSource: use after open() — the document is not loaded.`);let r=await this.#a.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}}});
3
3
  //# sourceMappingURL=index.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.umd.js","names":["#src","#workerSrc","#renderScale","#preload","#maxCacheBytes","#progressive","#disableAutoFetch","#legacy","#onUpdate","#onProgress","#doc","#resolveWorkerSrc","#renderZoomed","#renderPage","#textCache","#extractText","#destinationPage","#cache","#zoomCache","#cachedBytes","#rasterize","#evictToFit","#upgrade"],"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}\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}\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 under bundlers; pass it for CDN / UMD / custom paths. */\n workerSrc?: 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\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}\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/**\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 #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 /** 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.#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 = this.#resolveWorkerSrc(pdfjs.GlobalWorkerOptions.workerSrc);\n const params: DocParams =\n typeof this.#src === 'string' ? { url: this.#src } : { data: this.#src };\n if (this.#disableAutoFetch) params.disableAutoFetch = true;\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 /** Precedence: an explicit `workerSrc` → an already-configured global → an auto-resolved default. */\n #resolveWorkerSrc(configured: string): string {\n if (this.#workerSrc) return this.#workerSrc;\n if (configured) return configured;\n try {\n // Bundlers (Vite / webpack 5 / esbuild) statically rewrite these and emit the worker,\n // so the common case needs no `workerSrc`. Non-bundler / UMD hosts pass it explicitly. The\n // worker must match the build loaded in loadPdfjs — a legacy main with a modern worker (or\n // vice versa) mismatches — so the same #legacy flag chooses here too.\n return this.#legacy\n ? \"@@ZINE_PDF_WORKER:pdfjs-dist/legacy/build/pdf.worker.min.mjs@@\"\n : \"@@ZINE_PDF_WORKER:pdfjs-dist/build/pdf.worker.min.mjs@@\";\n } catch {\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, or pass a pre-created pdf.js document.',\n );\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 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 title: (node.title ?? '').trim() || 'Untitled',\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 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 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.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. */\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.#cache.delete(index);\n this.#cachedBytes -= entry.bytes;\n }\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","#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}\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 /** 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 * 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 #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.#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 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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"}
@@ -42,8 +42,15 @@ interface PdfDocumentLike {
42
42
  /** A URL, raw bytes, or a pre-created pdf.js document. */
43
43
  export type PdfSrc = string | ArrayBuffer | Uint8Array | PdfDocumentLike;
44
44
  export interface PdfSourceOptions {
45
- /** URL to pdf.js's worker. Optional: auto-resolved under bundlers; pass it for CDN / UMD / custom paths. */
45
+ /** URL to pdf.js's worker. Optional: auto-resolved from your bundler (`?url` / `new URL`) or
46
+ * `pdfjs-dist` install, then a version-matched CDN when `cdnFallback` is true. Pass it for
47
+ * CDN / UMD / CSP / offline, or if auto-resolve fails. */
46
48
  workerSrc?: string;
49
+ /** Allow falling back to a version-matched jsDelivr worker when no local worker can be
50
+ * resolved (no bundler rewrite, no reachable install). Default true. Set false for
51
+ * CSP-restricted or offline apps, where a network worker would fail opaquely — you must
52
+ * then pass `workerSrc` (or set GlobalWorkerOptions.workerSrc). */
53
+ cdnFallback?: boolean;
47
54
  /** Base render scale; each page is rasterized at `renderScale × devicePixelRatio`. Default 1. */
48
55
  renderScale?: number;
49
56
  /** How many adjacent pages to prefetch around a requested page. Default 1. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zinejs/pdf",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "PDF content source for zinejs, powered by pdf.js.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -36,14 +36,21 @@
36
36
  "publishConfig": {
37
37
  "access": "public"
38
38
  },
39
+ "scripts": {
40
+ "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.build.json",
41
+ "typecheck": "tsc --noEmit -p tsconfig.json",
42
+ "test": "pnpm typecheck && vitest run",
43
+ "size": "pnpm build && size-limit",
44
+ "prepublishOnly": "pnpm build"
45
+ },
39
46
  "dependencies": {
40
47
  "pdfjs-dist": ">=4"
41
48
  },
42
49
  "peerDependencies": {
43
- "@zinejs/core": "^0.9.0"
50
+ "@zinejs/core": "^0.9.2"
44
51
  },
45
52
  "devDependencies": {
46
- "@zinejs/core": "^0.9.0"
53
+ "@zinejs/core": "^0.9.2"
47
54
  },
48
55
  "size-limit": [
49
56
  {
@@ -52,11 +59,5 @@
52
59
  "limit": "8 kB",
53
60
  "gzip": true
54
61
  }
55
- ],
56
- "scripts": {
57
- "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.build.json",
58
- "typecheck": "tsc --noEmit -p tsconfig.json",
59
- "test": "pnpm typecheck && vitest run",
60
- "size": "pnpm build && size-limit"
61
- }
62
- }
62
+ ]
63
+ }