@zinejs/pdf 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -12
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +2 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/pdfSource.d.ts +4 -3
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -5,10 +5,10 @@ PDF content source for [zinejs](https://github.com/), powered by [pdf.js](https:
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @zinejs/core @zinejs/pdf
|
|
8
|
+
npm install @zinejs/core @zinejs/pdf
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
`pdfjs-dist` is a
|
|
11
|
+
`pdfjs-dist` is a normal dependency of `@zinejs/pdf`, so it installs with the package. The plugin still loads it lazily (dynamic `import`) so it never lands in the `@zinejs/core` bundle. If your app already depends on `pdfjs-dist`, the package manager will typically dedupe to one copy — keep major versions compatible so the worker matches the library.
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
@@ -23,6 +23,53 @@ const book = new Zine(document.getElementById('book'), {
|
|
|
23
23
|
|
|
24
24
|
Under a bundler that's all you need — the pdf.js worker is resolved for you.
|
|
25
25
|
|
|
26
|
+
### CDN / no bundler
|
|
27
|
+
|
|
28
|
+
Both packages ship **UMD** builds (`dist/index.umd.js`) that share the `ZineJS` global — load **core first**, then pdf (the pdf build uses `extend: true` so it merges into `ZineJS` instead of replacing it).
|
|
29
|
+
|
|
30
|
+
Modern `pdfjs-dist` is ESM-only, so expose it as `globalThis.pdfjsLib` before opening a PDF, and pass an explicit `workerSrc` (no bundler rewrites the worker URL):
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<script type="module">
|
|
34
|
+
import * as pdfjsLib from 'https://cdn.jsdelivr.net/npm/pdfjs-dist/build/pdf.min.mjs';
|
|
35
|
+
globalThis.pdfjsLib = pdfjsLib;
|
|
36
|
+
|
|
37
|
+
await new Promise((resolve, reject) => {
|
|
38
|
+
const s = document.createElement('script');
|
|
39
|
+
s.src = 'https://cdn.jsdelivr.net/npm/@zinejs/core/dist/index.umd.js';
|
|
40
|
+
s.onload = resolve;
|
|
41
|
+
s.onerror = reject;
|
|
42
|
+
document.head.append(s);
|
|
43
|
+
});
|
|
44
|
+
await new Promise((resolve, reject) => {
|
|
45
|
+
const s = document.createElement('script');
|
|
46
|
+
s.src = 'https://cdn.jsdelivr.net/npm/@zinejs/pdf/dist/index.umd.js';
|
|
47
|
+
s.onload = resolve;
|
|
48
|
+
s.onerror = reject;
|
|
49
|
+
document.head.append(s);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const book = new ZineJS.Zine(document.getElementById('book'), {
|
|
53
|
+
source: new ZineJS.PdfSource('document.pdf', {
|
|
54
|
+
workerSrc: 'https://cdn.jsdelivr.net/npm/pdfjs-dist/build/pdf.worker.min.mjs',
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
</script>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Image-only books can skip pdf.js and use a plain classic script:
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
<script src="https://cdn.jsdelivr.net/npm/@zinejs/core/dist/index.umd.js"></script>
|
|
64
|
+
<script>
|
|
65
|
+
new ZineJS.Zine(document.getElementById('book'), {
|
|
66
|
+
source: new ZineJS.ImageSource(['page-01.png', 'page-02.png']),
|
|
67
|
+
});
|
|
68
|
+
</script>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`PdfSource` prefers `globalThis.pdfjsLib` when present (CDN), otherwise dynamic-imports `pdfjs-dist` (bundlers).
|
|
72
|
+
|
|
26
73
|
## The pdf.js worker (`workerSrc`)
|
|
27
74
|
|
|
28
75
|
pdf.js parses and rasterizes in a Web Worker, a separate file the host must locate. By default `PdfSource` resolves it to:
|
|
@@ -41,16 +88,6 @@ Nothing to do — these bundlers statically rewrite the `new URL(..., import.met
|
|
|
41
88
|
new PdfSource('document.pdf');
|
|
42
89
|
```
|
|
43
90
|
|
|
44
|
-
### CDN / `<script>` (UMD)
|
|
45
|
-
|
|
46
|
-
No bundler to rewrite the URL, so point at a hosted worker matching your pdf.js version:
|
|
47
|
-
|
|
48
|
-
```js
|
|
49
|
-
new ZineJS.PdfSource('document.pdf', {
|
|
50
|
-
workerSrc: 'https://cdn.jsdelivr.net/npm/pdfjs-dist/build/pdf.worker.min.mjs',
|
|
51
|
-
});
|
|
52
|
-
```
|
|
53
|
-
|
|
54
91
|
### Custom path
|
|
55
92
|
|
|
56
93
|
Serving the worker yourself (copied to your public dir, etc.):
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
function e(e) {
|
|
2
2
|
return typeof e == "object" && !!e && "numPages" in e && typeof e.getPage == "function";
|
|
3
3
|
}
|
|
4
|
-
|
|
4
|
+
async function t() {
|
|
5
|
+
let e = globalThis;
|
|
6
|
+
return e.pdfjsLib ? e.pdfjsLib : await import("pdfjs-dist");
|
|
7
|
+
}
|
|
8
|
+
var n = class {
|
|
5
9
|
pageCount = 0;
|
|
6
10
|
#e;
|
|
7
11
|
#t;
|
|
@@ -23,10 +27,10 @@ var t = class {
|
|
|
23
27
|
async open() {
|
|
24
28
|
if (e(this.#e)) this.#i = this.#e;
|
|
25
29
|
else {
|
|
26
|
-
let e = await
|
|
30
|
+
let e = await t();
|
|
27
31
|
e.GlobalWorkerOptions.workerSrc = this.#d(e.GlobalWorkerOptions.workerSrc);
|
|
28
|
-
let
|
|
29
|
-
this.#s && (
|
|
32
|
+
let n = typeof this.#e == "string" ? { url: this.#e } : { data: this.#e };
|
|
33
|
+
this.#s && (n.disableAutoFetch = !0), this.#i = await e.getDocument(n).promise;
|
|
30
34
|
}
|
|
31
35
|
this.pageCount = this.#i.numPages;
|
|
32
36
|
}
|
|
@@ -82,6 +86,6 @@ var t = class {
|
|
|
82
86
|
}
|
|
83
87
|
};
|
|
84
88
|
//#endregion
|
|
85
|
-
export {
|
|
89
|
+
export { n as PdfSource };
|
|
86
90
|
|
|
87
91
|
//# 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","#onUpdate","#doc","#resolveWorkerSrc","#renderPage","#cache","#cachedBytes","#rasterize","#evictToFit","#upgrade"],"sources":["../src/pdfSource.ts"],"sourcesContent":["import type { PageContent, Source } 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 render(opts: { canvasContext: unknown; viewport: unknown }): { promise: Promise<void> };\n}\ninterface PdfDocumentLike {\n numPages: number;\n getPage(pageNumber: number): Promise<PdfPageLike>;\n destroy?(): void;\n}\ninterface DocParams {\n url?: string;\n data?: ArrayBuffer | Uint8Array;\n disableAutoFetch?: boolean;\n}\ninterface PdfjsModule {\n GlobalWorkerOptions: { workerSrc: string };\n getDocument(src: DocParams): { promise: Promise<PdfDocumentLike> };\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\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\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/**\n * Renders PDF pages to canvases for zinejs. pdf.js is a peer dependency, loaded\n * lazily so it never lands in the core bundle. Async — pass it to `new Zine`,\n * which calls `open()` (learning the page count) before building 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 #cache = new Map<number, CacheEntry>();\n #cachedBytes = 0;\n #onUpdate: ((index: number) => void) | null = null;\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 }\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 async open(): Promise<void> {\n if (isPdfDocument(this.#src)) {\n this.#doc = this.#src;\n } else {\n const pdfjs = (await import('pdfjs-dist')) as unknown as PdfjsModule;\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 this.#doc = await pdfjs.getDocument(params).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 this and emit the worker,\n // so the common case needs no `workerSrc`. Non-bundler / UMD hosts pass it explicitly.\n return \"@@ZINE_PDF_WORKER_URL@@\";\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): Promise<PageContent> {\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 destroy(): void {\n this.#doc?.destroy?.();\n this.#cache.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 async #rasterize(index: number, scale: number): Promise<PageContent> {\n if (this.#doc === null) throw new Error('PdfSource: use after open() — the document is not loaded.');\n const page = await this.#doc.getPage(index + 1); // pdf.js pages are 1-indexed\n const 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","#renderScale","#preload","#maxCacheBytes","#progressive","#disableAutoFetch","#onUpdate","#doc","#resolveWorkerSrc","#renderPage","#cache","#cachedBytes","#rasterize","#evictToFit","#upgrade"],"sources":["../src/pdfSource.ts"],"sourcesContent":["import type { PageContent, Source } 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 render(opts: { canvasContext: unknown; viewport: unknown }): { promise: Promise<void> };\n}\ninterface PdfDocumentLike {\n numPages: number;\n getPage(pageNumber: number): Promise<PdfPageLike>;\n destroy?(): void;\n}\ninterface DocParams {\n url?: string;\n data?: ArrayBuffer | Uint8Array;\n disableAutoFetch?: boolean;\n}\ninterface PdfjsModule {\n GlobalWorkerOptions: { workerSrc: string };\n getDocument(src: DocParams): { promise: Promise<PdfDocumentLike> };\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\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\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/**\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 */\nasync function loadPdfjs(): Promise<PdfjsModule> {\n const g = globalThis as typeof globalThis & { pdfjsLib?: PdfjsModule };\n if (g.pdfjsLib) return g.pdfjsLib;\n return (await import('pdfjs-dist')) 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 #cache = new Map<number, CacheEntry>();\n #cachedBytes = 0;\n #onUpdate: ((index: number) => void) | null = null;\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 }\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 async open(): Promise<void> {\n if (isPdfDocument(this.#src)) {\n this.#doc = this.#src;\n } else {\n const pdfjs = await loadPdfjs();\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 this.#doc = await pdfjs.getDocument(params).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 this and emit the worker,\n // so the common case needs no `workerSrc`. Non-bundler / UMD hosts pass it explicitly.\n return \"@@ZINE_PDF_WORKER_URL@@\";\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): Promise<PageContent> {\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 destroy(): void {\n this.#doc?.destroy?.();\n this.#cache.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 async #rasterize(index: number, scale: number): Promise<PageContent> {\n if (this.#doc === null) throw new Error('PdfSource: use after open() — the document is not loaded.');\n const page = await this.#doc.getPage(index + 1); // pdf.js pages are 1-indexed\n const 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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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
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;function r(e){return typeof e==`object`&&!!e&&`numPages`in e&&typeof e.getPage==`function`}async function i(){let e=globalThis;return e.pdfjsLib?e.pdfjsLib:await import(`pdfjs-dist`)}e.PdfSource=class{pageCount=0;#e;#t;#n;#r;#i=null;#a;#o;#s;#c=new Map;#l=0;#u=null;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}onPageUpdate(e){this.#u=e}async open(){if(r(this.#e))this.#i=this.#e;else{let e=await i();e.GlobalWorkerOptions.workerSrc=this.#d(e.GlobalWorkerOptions.workerSrc);let t=typeof this.#e==`string`?{url:this.#e}:{data:this.#e};this.#s&&(t.disableAutoFetch=!0),this.#i=await e.getDocument(t).promise}this.pageCount=this.#i.numPages}#d(e){if(this.#t)return this.#t;if(e)return e;try{return(new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).href)}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){let t=this.#f(e);for(let t=1;t<=this.#r;t++)this.prefetch([e-t,e+t]);return t}prefetch(e){for(let t of e)t>=0&&t<this.pageCount&&this.#f(t).catch(()=>{})}destroy(){this.#i?.destroy?.(),this.#c.clear(),this.#l=0,this.#i=null}#f(e){let t=this.#c.get(e);if(t)return this.#c.delete(e),this.#c.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.#h(e,a).then(t=>(o.bytes=t.width*t.height*4,this.#l+=o.bytes,this.#m(e),this.#o&&this.#p(e,o,i),t),t=>{throw this.#c.delete(e),t}),this.#c.set(e,o),o.promise}#p(e,t,n){this.#h(e,n).then(n=>{this.#c.get(e)===t&&(this.#l-=t.bytes,t.promise=Promise.resolve(n),t.bytes=n.width*n.height*4,this.#l+=t.bytes,this.#m(e),this.#u?.(e))}).catch(()=>{})}#m(e){for(let[t,n]of this.#c){if(this.#l<=this.#a)break;t===e||n.bytes===0||(this.#c.delete(t),this.#l-=n.bytes)}}async#h(e,t){if(this.#i===null)throw Error(`PdfSource: use after open() — the document is not loaded.`);let n=await this.#i.getPage(e+1),r=n.getViewport({scale:t}),i=document.createElement(`canvas`);return i.width=Math.ceil(r.width),i.height=Math.ceil(r.height),await n.render({canvasContext:i.getContext(`2d`),viewport:r}).promise,i}}});
|
|
2
|
+
//# sourceMappingURL=index.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.umd.js","names":["#src","#workerSrc","#renderScale","#preload","#maxCacheBytes","#progressive","#disableAutoFetch","#onUpdate","#doc","#resolveWorkerSrc","#renderPage","#cache","#cachedBytes","#rasterize","#evictToFit","#upgrade"],"sources":["../src/pdfSource.ts"],"sourcesContent":["import type { PageContent, Source } 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 render(opts: { canvasContext: unknown; viewport: unknown }): { promise: Promise<void> };\n}\ninterface PdfDocumentLike {\n numPages: number;\n getPage(pageNumber: number): Promise<PdfPageLike>;\n destroy?(): void;\n}\ninterface DocParams {\n url?: string;\n data?: ArrayBuffer | Uint8Array;\n disableAutoFetch?: boolean;\n}\ninterface PdfjsModule {\n GlobalWorkerOptions: { workerSrc: string };\n getDocument(src: DocParams): { promise: Promise<PdfDocumentLike> };\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\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\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/**\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 */\nasync function loadPdfjs(): Promise<PdfjsModule> {\n const g = globalThis as typeof globalThis & { pdfjsLib?: PdfjsModule };\n if (g.pdfjsLib) return g.pdfjsLib;\n return (await import('pdfjs-dist')) 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 #cache = new Map<number, CacheEntry>();\n #cachedBytes = 0;\n #onUpdate: ((index: number) => void) | null = null;\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 }\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 async open(): Promise<void> {\n if (isPdfDocument(this.#src)) {\n this.#doc = this.#src;\n } else {\n const pdfjs = await loadPdfjs();\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 this.#doc = await pdfjs.getDocument(params).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 this and emit the worker,\n // so the common case needs no `workerSrc`. Non-bundler / UMD hosts pass it explicitly.\n return \"@@ZINE_PDF_WORKER_URL@@\";\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): Promise<PageContent> {\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 destroy(): void {\n this.#doc?.destroy?.();\n this.#cache.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 async #rasterize(index: number, scale: number): Promise<PageContent> {\n if (this.#doc === null) throw new Error('PdfSource: use after open() — the document is not loaded.');\n const page = await this.#doc.getPage(index + 1); // pdf.js pages are 1-indexed\n const 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/pdfSource.d.ts
CHANGED
|
@@ -36,9 +36,10 @@ export interface PdfSourceOptions {
|
|
|
36
36
|
disableAutoFetch?: boolean;
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
|
-
* Renders PDF pages to canvases for zinejs. pdf.js is a
|
|
40
|
-
* lazily so it never lands in the core bundle. Async — pass it
|
|
41
|
-
* which calls `open()` (learning the page count) before building
|
|
39
|
+
* Renders PDF pages to canvases for zinejs. pdf.js is a dependency of this
|
|
40
|
+
* package, loaded lazily so it never lands in the core bundle. Async — pass it
|
|
41
|
+
* to `new Zine`, which calls `open()` (learning the page count) before building
|
|
42
|
+
* spreads.
|
|
42
43
|
*/
|
|
43
44
|
export declare class PdfSource implements Source {
|
|
44
45
|
#private;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zinejs/pdf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "PDF content source for zinejs, powered by pdf.js.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -25,21 +25,25 @@
|
|
|
25
25
|
],
|
|
26
26
|
"types": "./dist/index.d.ts",
|
|
27
27
|
"module": "./dist/index.js",
|
|
28
|
+
"main": "./dist/index.umd.js",
|
|
28
29
|
"exports": {
|
|
29
30
|
".": {
|
|
30
31
|
"types": "./dist/index.d.ts",
|
|
31
|
-
"import": "./dist/index.js"
|
|
32
|
+
"import": "./dist/index.js",
|
|
33
|
+
"require": "./dist/index.umd.js"
|
|
32
34
|
}
|
|
33
35
|
},
|
|
34
36
|
"publishConfig": {
|
|
35
37
|
"access": "public"
|
|
36
38
|
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"pdfjs-dist": ">=4"
|
|
41
|
+
},
|
|
37
42
|
"peerDependencies": {
|
|
38
|
-
"
|
|
39
|
-
"@zinejs/core": "^0.3.0"
|
|
43
|
+
"@zinejs/core": "^0.4.1"
|
|
40
44
|
},
|
|
41
45
|
"devDependencies": {
|
|
42
|
-
"@zinejs/core": "^0.
|
|
46
|
+
"@zinejs/core": "^0.4.1"
|
|
43
47
|
},
|
|
44
48
|
"size-limit": [
|
|
45
49
|
{
|