@laminapdf/viewer 0.0.0-stage → 0.2.0-alpha.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.
Files changed (58) hide show
  1. package/README.md +142 -2
  2. package/dist/canvas/cache.d.ts +35 -0
  3. package/dist/canvas/cache.d.ts.map +1 -0
  4. package/dist/canvas/cache.js +89 -0
  5. package/dist/canvas/cache.js.map +1 -0
  6. package/dist/canvas/pool.d.ts +35 -0
  7. package/dist/canvas/pool.d.ts.map +1 -0
  8. package/dist/canvas/pool.js +65 -0
  9. package/dist/canvas/pool.js.map +1 -0
  10. package/dist/client.d.ts +112 -0
  11. package/dist/client.d.ts.map +1 -0
  12. package/dist/client.js +236 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/index.d.ts +10 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +8 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/layout/page-layout.d.ts +58 -0
  19. package/dist/layout/page-layout.d.ts.map +1 -0
  20. package/dist/layout/page-layout.js +110 -0
  21. package/dist/layout/page-layout.js.map +1 -0
  22. package/dist/protocol.d.ts +95 -0
  23. package/dist/protocol.d.ts.map +1 -0
  24. package/dist/protocol.js +36 -0
  25. package/dist/protocol.js.map +1 -0
  26. package/dist/render/tiles.d.ts +17 -0
  27. package/dist/render/tiles.d.ts.map +1 -0
  28. package/dist/render/tiles.js +29 -0
  29. package/dist/render/tiles.js.map +1 -0
  30. package/dist/source.d.ts +9 -0
  31. package/dist/source.d.ts.map +1 -0
  32. package/dist/source.js +27 -0
  33. package/dist/source.js.map +1 -0
  34. package/dist/view/viewer.d.ts +135 -0
  35. package/dist/view/viewer.d.ts.map +1 -0
  36. package/dist/view/viewer.js +774 -0
  37. package/dist/view/viewer.js.map +1 -0
  38. package/dist/worker/document.d.ts +20 -0
  39. package/dist/worker/document.d.ts.map +1 -0
  40. package/dist/worker/document.js +117 -0
  41. package/dist/worker/document.js.map +1 -0
  42. package/dist/worker/entry.d.ts +2 -0
  43. package/dist/worker/entry.d.ts.map +1 -0
  44. package/dist/worker/entry.js +7 -0
  45. package/dist/worker/entry.js.map +1 -0
  46. package/dist/worker/methods.d.ts +4 -0
  47. package/dist/worker/methods.d.ts.map +1 -0
  48. package/dist/worker/methods.js +18 -0
  49. package/dist/worker/methods.js.map +1 -0
  50. package/dist/worker/render.d.ts +7 -0
  51. package/dist/worker/render.d.ts.map +1 -0
  52. package/dist/worker/render.js +22 -0
  53. package/dist/worker/render.js.map +1 -0
  54. package/dist/zoom/zoom.d.ts +44 -0
  55. package/dist/zoom/zoom.d.ts.map +1 -0
  56. package/dist/zoom/zoom.js +56 -0
  57. package/dist/zoom/zoom.js.map +1 -0
  58. package/package.json +54 -3
package/dist/client.js ADDED
@@ -0,0 +1,236 @@
1
+ // The page side of the document worker (C3a, ADR 0009 §3): a ViewerClient owns one worker for one
2
+ // document, through `@laminapdf/sdk/worker`'s client (`request` with a priority and a coalescing
3
+ // key). On top of it:
4
+ //
5
+ // - typed calls (`call(method, params)`, checked against `Methods`), and `open`/`render`/…;
6
+ // - cancellation that rejects with an `AbortError` at once; a late answer's bitmap is closed by
7
+ // the worker client, so a cancelled render never paints;
8
+ // - render timers on the page, started when the worker reports it began drawing (a render waiting
9
+ // in the queue isn't slow) and kept on the page because a worker stuck in a synchronous render
10
+ // can't post: `render-slow` after the soft timeout, and after the hard cap either a restart
11
+ // (`hard-timeout`) or, for an encrypted document, `render-stuck` and a restart only on `stop()`;
12
+ // - restart after a crash (the worker's error, messageerror or a reported trap; never silence):
13
+ // pending calls reject with `worker-crashed`, a new worker reopens the document from the same
14
+ // source, but the call that was running is never re-sent (the UI offers Retry). An encrypted
15
+ // document isn't reopened: the password lived only in the dead worker, so `password-required`
16
+ // fires and the caller opens again with the password.
17
+ import { isLaminaPdfError } from "@laminapdf/sdk";
18
+ import { connect, Emitter, } from "@laminapdf/sdk/worker";
19
+ import { PRIORITY, renderKey, } from "./protocol.js";
20
+ /** Default render timeouts (ADR 0009 §3.7), in ms. */
21
+ const DEFAULT_TIMEOUTS = Object.freeze({ soft: 10_000, hard: 120_000 });
22
+ function workerFactory() {
23
+ return new Worker(new URL("./worker/entry.js", import.meta.url), { type: "module", name: "lamina-viewer" });
24
+ }
25
+ function abortError(e) {
26
+ return isLaminaPdfError(e, "cancelled") ? new DOMException("the call was cancelled", "AbortError") : e;
27
+ }
28
+ /** One document worker and the typed RPC to it. */
29
+ export class ViewerClient {
30
+ #pdf;
31
+ #backend;
32
+ #timeouts;
33
+ #events = new Emitter();
34
+ /** What to reopen after a restart: the source and the options without the password. */
35
+ #reopen;
36
+ #document;
37
+ #recovering;
38
+ #nextRender = 1;
39
+ /** Renders waiting to start, by coalescing key: their timers start on `render-start`. */
40
+ #waiting = new Map();
41
+ /** The key the worker is drawing now: a render that joins it starts its timers at once. */
42
+ #drawing;
43
+ constructor(pdf, options) {
44
+ this.#pdf = pdf;
45
+ this.#backend = options.backend;
46
+ this.#timeouts = { ...DEFAULT_TIMEOUTS, ...options.timeouts };
47
+ pdf.on("event", (name, data) => {
48
+ if (name === "warning")
49
+ this.#events.emit("warning", data);
50
+ else if (name === "render-start") {
51
+ const started = data;
52
+ this.#drawing = started.key;
53
+ for (const start of this.#waiting.get(started.key) ?? [])
54
+ start();
55
+ this.#waiting.delete(started.key);
56
+ this.#events.emit("render-start", started);
57
+ }
58
+ });
59
+ pdf.on("crashed", (reason) => {
60
+ this.#events.emit("crashed", { reason });
61
+ this.#recover(reason).catch((error) => this.#events.emit("restart-failed", { reason: reason, error }));
62
+ });
63
+ }
64
+ /**
65
+ * Starts the document worker and loads the backend at once, so the WebAssembly downloads while
66
+ * the user picks a file.
67
+ */
68
+ static async create(options) {
69
+ const pdf = await connect({
70
+ worker: options.worker ?? workerFactory,
71
+ // A late answer to an aborted render: its bitmap is closed, never painted.
72
+ onDiscard: (value) => {
73
+ const bitmap = value?.bitmap;
74
+ if (bitmap instanceof ImageBitmap)
75
+ bitmap.close();
76
+ },
77
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
78
+ ...(options.lamina === undefined ? {} : { init: options.lamina }),
79
+ });
80
+ const client = new ViewerClient(pdf, options);
81
+ try {
82
+ await pdf.request("initBackend", [options.backend], { priority: PRIORITY.document });
83
+ }
84
+ catch (e) {
85
+ // Nobody will hold this client: end its worker.
86
+ pdf.terminate();
87
+ throw e;
88
+ }
89
+ return client;
90
+ }
91
+ /** Listens for `event`; returns a function that stops listening. */
92
+ on(event, listener) {
93
+ return this.#events.on(event, listener);
94
+ }
95
+ /** A typed call: `method` and `params` are checked against `Methods`. */
96
+ async call(method, params, options = {}) {
97
+ if (this.#recovering !== undefined)
98
+ await this.#recovering.catch(() => { });
99
+ const { signal, priority, key } = options;
100
+ const level = method === "render" ? PRIORITY[priority ?? "visible"] : PRIORITY.document;
101
+ try {
102
+ return (await this.#pdf.request(method, params, {
103
+ priority: level,
104
+ ...(signal === undefined ? {} : { signal }),
105
+ ...(key === undefined ? {} : { key }),
106
+ }));
107
+ }
108
+ catch (e) {
109
+ throw abortError(e);
110
+ }
111
+ }
112
+ /**
113
+ * Opens `source` in the worker (Lamina PDF and the backend, from the same bytes). The password is
114
+ * sent once and kept only in the worker; a Blob is sent by reference, bytes are copied (and
115
+ * kept here, so a restarted worker can reopen them).
116
+ */
117
+ async open(source, options = {}, call = {}) {
118
+ const { password: _, ...rest } = options;
119
+ const opened = await this.call("openDocument", [source, options], call);
120
+ this.#reopen = { source, options: rest };
121
+ this.#document = opened;
122
+ return opened;
123
+ }
124
+ /** The open document, if any. */
125
+ get document() {
126
+ return this.#document;
127
+ }
128
+ /** Closes the document in the worker (the worker stays, ready for another `open`). */
129
+ async close() {
130
+ this.#reopen = undefined;
131
+ this.#document = undefined;
132
+ await this.call("closeDocument", []);
133
+ }
134
+ /** Page `index`'s size, rotation, ID and label. */
135
+ pageInfo(index, options) {
136
+ return this.call("pageInfo", [index], options);
137
+ }
138
+ /**
139
+ * Renders `request` as an ImageBitmap (close it when done). Identical requests in flight are
140
+ * drawn once. An aborted render rejects with `AbortError` and never paints.
141
+ */
142
+ async render(request, options = {}) {
143
+ const id = this.#nextRender++;
144
+ // A restarted worker numbers its documents again: read the ID once it serves.
145
+ if (this.#recovering !== undefined)
146
+ await this.#recovering.catch(() => { });
147
+ // 0 names no document: the worker answers `disposed`.
148
+ const documentId = this.#document?.id ?? 0;
149
+ const needsPassword = this.#document?.needsPassword === true;
150
+ const key = renderKey(request, documentId);
151
+ let soft;
152
+ let hard;
153
+ const start = () => {
154
+ soft = setTimeout(() => this.#events.emit("render-slow", { id, page: request.page }), this.#timeouts.soft);
155
+ hard = setTimeout(() => {
156
+ if (needsPassword)
157
+ this.#events.emit("render-stuck", { id, page: request.page, needsPassword: true });
158
+ else
159
+ this.stop("hard-timeout").catch((error) => this.#events.emit("restart-failed", { reason: "hard-timeout", error }));
160
+ }, this.#timeouts.hard);
161
+ };
162
+ let waiting = this.#waiting.get(key);
163
+ if (waiting === undefined) {
164
+ waiting = new Set();
165
+ this.#waiting.set(key, waiting);
166
+ }
167
+ // Joining a render already being drawn: its render-start has passed.
168
+ if (this.#drawing === key)
169
+ start();
170
+ else
171
+ waiting.add(start);
172
+ try {
173
+ return await this.call("render", [request, documentId], { ...options, key });
174
+ }
175
+ finally {
176
+ const set = this.#waiting.get(key);
177
+ set?.delete(start);
178
+ if (set?.size === 0)
179
+ this.#waiting.delete(key);
180
+ if (this.#drawing === key)
181
+ this.#drawing = undefined;
182
+ clearTimeout(soft);
183
+ clearTimeout(hard);
184
+ }
185
+ }
186
+ /**
187
+ * Stops whatever the worker is doing by terminating it (the user's Stop, or the hard cap), then
188
+ * restarts it as after a crash: pending calls reject, the running one isn't re-sent, and an
189
+ * encrypted document asks for its password again.
190
+ */
191
+ stop(reason = "cancelled") {
192
+ // A restart already under way serves: a second would end the worker it is starting.
193
+ if (this.#recovering !== undefined)
194
+ return this.#recovering;
195
+ this.#pdf.terminate();
196
+ return this.#recover(reason);
197
+ }
198
+ /** Ends the worker for good. */
199
+ terminate() {
200
+ this.#reopen = undefined;
201
+ this.#document = undefined;
202
+ this.#pdf.terminate();
203
+ }
204
+ #recover(reason) {
205
+ if (this.#recovering !== undefined)
206
+ return this.#recovering;
207
+ const recovering = (async () => {
208
+ await this.#pdf.restart();
209
+ await this.#pdf.request("initBackend", [this.#backend], { priority: PRIORITY.document });
210
+ let reopened = false;
211
+ const reopen = this.#reopen;
212
+ if (reopen !== undefined) {
213
+ if (this.#document?.needsPassword === true) {
214
+ // The password died with the worker; it is never kept on the page.
215
+ this.#reopen = undefined;
216
+ this.#document = undefined;
217
+ this.#events.emit("password-required", {});
218
+ }
219
+ else {
220
+ this.#document = (await this.#pdf.request("openDocument", [reopen.source, reopen.options], {
221
+ priority: PRIORITY.document,
222
+ }));
223
+ reopened = true;
224
+ }
225
+ }
226
+ this.#events.emit("restarted", { reason, reopened });
227
+ })();
228
+ const tracked = recovering.finally(() => {
229
+ if (this.#recovering === tracked)
230
+ this.#recovering = undefined;
231
+ });
232
+ this.#recovering = tracked;
233
+ return tracked;
234
+ }
235
+ }
236
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,iGAAiG;AACjG,sBAAsB;AACtB,EAAE;AACF,4FAA4F;AAC5F,gGAAgG;AAChG,2DAA2D;AAC3D,kGAAkG;AAClG,iGAAiG;AACjG,8FAA8F;AAC9F,mGAAmG;AACnG,gGAAgG;AAChG,gGAAgG;AAChG,+FAA+F;AAC/F,gGAAgG;AAChG,wDAAwD;AAGxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EACL,OAAO,EACP,OAAO,GAIR,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAKL,QAAQ,EAIR,SAAS,GAGV,MAAM,eAAe,CAAC;AAEvB,sDAAsD;AACtD,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AAiDxE,SAAS,aAAa;IACpB,OAAO,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,mBAAmB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC;AAC9G,CAAC;AAED,SAAS,UAAU,CAAC,CAAU;IAC5B,OAAO,gBAAgB,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACzG,CAAC;AAED,mDAAmD;AACnD,MAAM,OAAO,YAAY;IACd,IAAI,CAAe;IACnB,QAAQ,CAAc;IACtB,SAAS,CAAmD;IAC5D,OAAO,GAAG,IAAI,OAAO,EAAgB,CAAC;IAC/C,uFAAuF;IACvF,OAAO,CAAqG;IAC5G,SAAS,CAA6B;IACtC,WAAW,CAA4B;IACvC,WAAW,GAAG,CAAC,CAAC;IAChB,yFAAyF;IAChF,QAAQ,GAAG,IAAI,GAAG,EAA2B,CAAC;IACvD,2FAA2F;IAC3F,QAAQ,CAAqB;IAE7B,YAAoB,GAAiB,EAAE,OAAsB;QAC3D,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;QAChC,IAAI,CAAC,SAAS,GAAG,EAAE,GAAG,gBAAgB,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;QAC9D,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;YAC7B,IAAI,IAAI,KAAK,SAAS;gBAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,IAA+B,CAAC,CAAC;iBACjF,IAAI,IAAI,KAAK,cAAc,EAAE,CAAC;gBACjC,MAAM,OAAO,GAAG,IAAoC,CAAC;gBACrD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;gBAC5B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE;oBAAE,KAAK,EAAE,CAAC;gBAClE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBAClC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE;YAC3B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;YACzC,IAAI,CAAC,QAAQ,CAAC,MAAuB,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CACrD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,MAAM,EAAE,MAAuB,EAAE,KAAK,EAAE,CAAC,CAChF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAsB;QACxC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC;YACxB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,aAAa;YACvC,2EAA2E;YAC3E,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;gBACnB,MAAM,MAAM,GAAI,KAA0C,EAAE,MAAM,CAAC;gBACnE,IAAI,MAAM,YAAY,WAAW;oBAAE,MAAM,CAAC,KAAK,EAAE,CAAC;YACpD,CAAC;YACD,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;YACnE,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;SAClE,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC9C,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QACvF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,gDAAgD;YAChD,GAAG,CAAC,SAAS,EAAE,CAAC;YAChB,MAAM,CAAC,CAAC;QACV,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,oEAAoE;IACpE,EAAE,CAAwB,KAAQ,EAAE,QAAyC;QAC3E,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAC1C,CAAC;IAED,yEAAyE;IACzE,KAAK,CAAC,IAAI,CACR,MAAS,EACT,MAAiB,EACjB,OAAO,GAAyC,EAAE;QAElD,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;YAAE,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC3E,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;QAC1C,MAAM,KAAK,GAAG,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACxF,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE;gBAC9C,QAAQ,EAAE,KAAK;gBACf,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;gBAC3C,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;aACtC,CAAC,CAAc,CAAC;QACnB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,UAAU,CAAC,CAAC,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAI,CACR,MAAkB,EAClB,OAAO,GAAsB,EAAE,EAC/B,IAAI,GAAsB,EAAE;QAE5B,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;QACzC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC;QACxE,IAAI,CAAC,OAAO,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACzC,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC;QACxB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,iCAAiC;IACjC,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,sFAAsF;IACtF,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,MAAM,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;IACvC,CAAC;IAED,mDAAmD;IACnD,QAAQ,CAAC,KAAa,EAAE,OAA2B;QACjD,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;IACjD,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,OAAsB,EAAE,OAAO,GAAsB,EAAE;QAClE,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAC9B,8EAA8E;QAC9E,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;YAAE,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC3E,sDAAsD;QACtD,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,CAAC,CAAC;QAC3C,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,EAAE,aAAa,KAAK,IAAI,CAAC;QAC7D,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QAC3C,IAAI,IAA+C,CAAC;QACpD,IAAI,IAA+C,CAAC;QACpD,MAAM,KAAK,GAAG,GAAG,EAAE;YACjB,IAAI,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC3G,IAAI,GAAG,UAAU,CAAC,GAAG,EAAE;gBACrB,IAAI,aAAa;oBAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;;oBAEpG,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CACxC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CACvE,CAAC;YACN,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC,CAAC;QACF,IAAI,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;YACpB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAClC,CAAC;QACD,qEAAqE;QACrE,IAAI,IAAI,CAAC,QAAQ,KAAK,GAAG;YAAE,KAAK,EAAE,CAAC;;YAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACxB,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QAC/E,CAAC;gBAAS,CAAC;YACT,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACnC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACnB,IAAI,GAAG,EAAE,IAAI,KAAK,CAAC;gBAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC/C,IAAI,IAAI,CAAC,QAAQ,KAAK,GAAG;gBAAE,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;YACrD,YAAY,CAAC,IAAI,CAAC,CAAC;YACnB,YAAY,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,IAAI,CAAC,MAAM,GAAiC,WAAW;QACrD,oFAAoF;QACpF,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC,WAAW,CAAC;QAC5D,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;IAED,gCAAgC;IAChC,SAAS;QACP,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;IACxB,CAAC;IAED,QAAQ,CAAC,MAAqB;QAC5B,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC,WAAW,CAAC;QAC5D,MAAM,UAAU,GAAG,CAAC,KAAK,IAAI,EAAE;YAC7B,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAC1B,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;YACzF,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC;YAC5B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,IAAI,IAAI,CAAC,SAAS,EAAE,aAAa,KAAK,IAAI,EAAE,CAAC;oBAC3C,mEAAmE;oBACnE,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;oBACzB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;oBAC3B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;gBAC7C,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,SAAS,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE;wBACzF,QAAQ,EAAE,QAAQ,CAAC,QAAQ;qBAC5B,CAAC,CAAmB,CAAC;oBACtB,QAAQ,GAAG,IAAI,CAAC;gBAClB,CAAC;YACH,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QACvD,CAAC,CAAC,EAAE,CAAC;QACL,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE;YACtC,IAAI,IAAI,CAAC,WAAW,KAAK,OAAO;gBAAE,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;QACjE,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;QAC3B,OAAO,OAAO,CAAC;IACjB,CAAC;CACF"}
@@ -0,0 +1,10 @@
1
+ export type { CanvasPoolOptions } from "./canvas/pool.js";
2
+ export type { RestartReason, ViewerCallOptions, ViewerEvent, ViewerEvents, ViewerOptions } from "./client.js";
3
+ export { ViewerClient } from "./client.js";
4
+ export type { Rotation } from "./layout/page-layout.js";
5
+ export type { BackendInit, BackendName, MethodName, Methods, OpenedDocument, Params, Priority, RenderedBitmap, Result, SourceInit, ViewerOpenOptions, } from "./protocol.js";
6
+ export type { CreateViewerOptions, PageLayer, PageRenderState, RenderStats, ViewerEventMap, ViewState, } from "./view/viewer.js";
7
+ export { createViewer, Viewer } from "./view/viewer.js";
8
+ export type { DocumentPoint, Point, Zoom, ZoomMode } from "./zoom/zoom.js";
9
+ export { MAX_ZOOM, MIN_ZOOM } from "./zoom/zoom.js";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC9G,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,YAAY,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACxD,YAAY,EACV,WAAW,EACX,WAAW,EACX,UAAU,EACV,OAAO,EACP,cAAc,EACd,MAAM,EACN,QAAQ,EACR,cAAc,EACd,MAAM,EACN,UAAU,EACV,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,mBAAmB,EACnB,SAAS,EACT,eAAe,EACf,WAAW,EACX,cAAc,EACd,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACxD,YAAY,EAAE,aAAa,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ // `@laminapdf/viewer` (ADR 0009): the web reader's viewer core: the document worker and its typed
2
+ // RPC (ViewerClient), and the viewer over them (createViewer). The parts inside (the layout, the
3
+ // canvas pool, the bitmap cache, the tile and zoom maths) are not exported: the README describes
4
+ // them, and their unit tests import them by path within the package.
5
+ export { ViewerClient } from "./client.js";
6
+ export { createViewer, Viewer } from "./view/viewer.js";
7
+ export { MAX_ZOOM, MIN_ZOOM } from "./zoom/zoom.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,iGAAiG;AACjG,iGAAiG;AACjG,qEAAqE;AAIrE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAuB3C,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { type ViewRotation } from "@laminapdf/render";
2
+ /** A page's displayed size in points. */
3
+ export interface PageSize {
4
+ readonly width: number;
5
+ readonly height: number;
6
+ }
7
+ /** View rotation, clockwise, on top of the page's own `/Rotate`: the render backends' own type. */
8
+ export type Rotation = ViewRotation;
9
+ /** CSS pixels per point at 100 % ("actual size"): 96 CSS px per inch, 72 pt per inch. */
10
+ export declare const CSS_PX_PER_PT: number;
11
+ /** `size` as displayed after a view rotation: width and height swap at 90° and 270°. */
12
+ export declare function rotated(size: PageSize, rotation: Rotation): PageSize;
13
+ /** Whether `value` is a view rotation (0, 90, 180 or 270). */
14
+ export declare function isRotation(value: number): value is Rotation;
15
+ /** How to lay pages out: the zoom as CSS px per point, the view rotation and the gap. */
16
+ export interface LayoutOptions {
17
+ /** CSS pixels per point (100 % is {@link CSS_PX_PER_PT}). */
18
+ readonly scale: number;
19
+ readonly rotation?: Rotation;
20
+ /** CSS pixels between pages, and above the first and below the last. */
21
+ readonly gap?: number;
22
+ }
23
+ /** Page geometry in CSS pixels for one zoom and rotation. */
24
+ export declare class PageLayout {
25
+ readonly scale: number;
26
+ readonly rotation: Rotation;
27
+ readonly gap: number;
28
+ /** Each page's box width and height in CSS px. */
29
+ readonly widths: Float64Array;
30
+ readonly heights: Float64Array;
31
+ /** `tops[i]` is page i's top edge; `tops[n]` is the bottom of the last gap (the content height). */
32
+ readonly tops: Float64Array;
33
+ readonly maxWidth: number;
34
+ /** Elementary steps the last build and searches took (tests assert the cost by count, not time). */
35
+ operations: number;
36
+ constructor(sizes: readonly PageSize[], options: LayoutOptions);
37
+ /** How many pages the layout holds. */
38
+ get pageCount(): number;
39
+ /** The content height in CSS px (every page and gap). */
40
+ get height(): number;
41
+ /** Page `index`'s top edge in CSS px. */
42
+ offsetOf(index: number): number;
43
+ /**
44
+ * The page at content offset `y`: the last page whose top is at or above `y` (a gap belongs to
45
+ * the page above it), clamped to the first and last page. -1 for an empty document.
46
+ */
47
+ pageAt(y: number): number;
48
+ /** The pages any part of which lies in `[top, bottom)`, as an inclusive range. */
49
+ rangeIn(top: number, bottom: number): {
50
+ readonly first: number;
51
+ readonly last: number;
52
+ };
53
+ /** Page `index`'s left edge in CSS px when pages are centred in a viewport `viewportWidth` wide. */
54
+ leftOf(index: number, viewportWidth: number): number;
55
+ /** The content width in CSS px for a viewport `viewportWidth` wide. */
56
+ contentWidth(viewportWidth: number): number;
57
+ }
58
+ //# sourceMappingURL=page-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-layout.d.ts","sourceRoot":"","sources":["../../src/layout/page-layout.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAErE,yCAAyC;AACzC,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,mGAAmG;AACnG,MAAM,MAAM,QAAQ,GAAG,YAAY,CAAC;AAEpC,yFAAyF;AACzF,eAAO,MAAM,aAAa,QAAU,CAAC;AAErC,wFAAwF;AACxF,wBAAgB,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAEpE;AAED,8DAA8D;AAC9D,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,QAAQ,CAE3D;AAED,yFAAyF;AACzF,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,6DAA6D;AAC7D,qBAAa,UAAU;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,oGAAoG;IACpG,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oGAAoG;IACpG,UAAU,SAAK;IAEf,YAAY,KAAK,EAAE,SAAS,QAAQ,EAAE,EAAE,OAAO,EAAE,aAAa,EAyB7D;IAED,uCAAuC;IACvC,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,yDAAyD;IACzD,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,yCAAyC;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAK9B;IAED;;;OAGG;IACH,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAYxB;IAED,kFAAkF;IAClF,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAMtF;IAED,oGAAoG;IACpG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAEnD;IAED,uEAAuE;IACvE,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAE1C;CACF"}
@@ -0,0 +1,110 @@
1
+ // The viewer's page layout: one continuous column of pages, each sized from its displayed
2
+ // size in points (ADR 0027: `/Rotate` applied) times the zoom, turned by the view rotation, with a
3
+ // gap between pages. Offsets are prefix sums, so finding the page at a scroll position is a binary
4
+ // search and a zoom or rotation change costs one O(n) pass. No DOM: the view reads it.
5
+ import { isQuarterTurn } from "@laminapdf/render";
6
+ /** CSS pixels per point at 100 % ("actual size"): 96 CSS px per inch, 72 pt per inch. */
7
+ export const CSS_PX_PER_PT = 96 / 72;
8
+ /** `size` as displayed after a view rotation: width and height swap at 90° and 270°. */
9
+ export function rotated(size, rotation) {
10
+ return isQuarterTurn(rotation) ? { width: size.height, height: size.width } : size;
11
+ }
12
+ /** Whether `value` is a view rotation (0, 90, 180 or 270). */
13
+ export function isRotation(value) {
14
+ return value === 0 || value === 90 || value === 180 || value === 270;
15
+ }
16
+ /** Page geometry in CSS pixels for one zoom and rotation. */
17
+ export class PageLayout {
18
+ scale;
19
+ rotation;
20
+ gap;
21
+ /** Each page's box width and height in CSS px. */
22
+ widths;
23
+ heights;
24
+ /** `tops[i]` is page i's top edge; `tops[n]` is the bottom of the last gap (the content height). */
25
+ tops;
26
+ maxWidth;
27
+ /** Elementary steps the last build and searches took (tests assert the cost by count, not time). */
28
+ operations = 0;
29
+ constructor(sizes, options) {
30
+ const { scale, rotation = 0, gap = 16 } = options;
31
+ if (!(scale > 0) || !Number.isFinite(scale))
32
+ throw new RangeError(`scale must be positive, got ${scale}`);
33
+ this.scale = scale;
34
+ this.rotation = rotation;
35
+ this.gap = gap;
36
+ const n = sizes.length;
37
+ this.widths = new Float64Array(n);
38
+ this.heights = new Float64Array(n);
39
+ this.tops = new Float64Array(n + 1);
40
+ let y = gap;
41
+ let max = 0;
42
+ for (let i = 0; i < n; i++) {
43
+ const shown = rotated(sizes[i], rotation);
44
+ const w = shown.width * scale;
45
+ const h = shown.height * scale;
46
+ this.widths[i] = w;
47
+ this.heights[i] = h;
48
+ this.tops[i] = y;
49
+ y += h + gap;
50
+ if (w > max)
51
+ max = w;
52
+ this.operations++;
53
+ }
54
+ this.tops[n] = y;
55
+ this.maxWidth = max;
56
+ }
57
+ /** How many pages the layout holds. */
58
+ get pageCount() {
59
+ return this.widths.length;
60
+ }
61
+ /** The content height in CSS px (every page and gap). */
62
+ get height() {
63
+ return this.tops[this.pageCount] ?? 0;
64
+ }
65
+ /** Page `index`'s top edge in CSS px. */
66
+ offsetOf(index) {
67
+ if (!Number.isInteger(index) || index < 0 || index >= this.pageCount) {
68
+ throw new RangeError(`page ${index} is outside 0..${this.pageCount - 1}`);
69
+ }
70
+ return this.tops[index];
71
+ }
72
+ /**
73
+ * The page at content offset `y`: the last page whose top is at or above `y` (a gap belongs to
74
+ * the page above it), clamped to the first and last page. -1 for an empty document.
75
+ */
76
+ pageAt(y) {
77
+ const n = this.pageCount;
78
+ if (n === 0)
79
+ return -1;
80
+ let lo = 0;
81
+ let hi = n - 1;
82
+ while (lo < hi) {
83
+ this.operations++;
84
+ const mid = (lo + hi + 1) >> 1;
85
+ if (this.tops[mid] <= y)
86
+ lo = mid;
87
+ else
88
+ hi = mid - 1;
89
+ }
90
+ return lo;
91
+ }
92
+ /** The pages any part of which lies in `[top, bottom)`, as an inclusive range. */
93
+ rangeIn(top, bottom) {
94
+ const first = this.pageAt(top);
95
+ let last = this.pageAt(Math.max(top, bottom - 1e-6));
96
+ // `pageAt` puts a gap with the page above; a range that ends in a gap ends at that page.
97
+ if (last < first)
98
+ last = first;
99
+ return { first, last };
100
+ }
101
+ /** Page `index`'s left edge in CSS px when pages are centred in a viewport `viewportWidth` wide. */
102
+ leftOf(index, viewportWidth) {
103
+ return (this.contentWidth(viewportWidth) - this.widths[index]) / 2;
104
+ }
105
+ /** The content width in CSS px for a viewport `viewportWidth` wide. */
106
+ contentWidth(viewportWidth) {
107
+ return Math.max(viewportWidth, this.maxWidth + 2 * this.gap);
108
+ }
109
+ }
110
+ //# sourceMappingURL=page-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-layout.js","sourceRoot":"","sources":["../../src/layout/page-layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ import { type CloneableWasmSource, type RenderLimits, type RenderRequest } from "@laminapdf/render";
2
+ import type { Limits, PageInfo } from "@laminapdf/sdk";
3
+ /** Which engine draws the pages (ADR 0004: PDFium by default, hayro the CI oracle). */
4
+ export type BackendName = "pdfium" | "hayro";
5
+ /** What the worker loads a backend from. */
6
+ export interface BackendInit {
7
+ readonly backend: BackendName;
8
+ /** The engine's `.wasm`: required for PDFium (its URL from your bundler); hayro finds its own. */
9
+ readonly wasm?: CloneableWasmSource;
10
+ }
11
+ /** What a document is opened from: sent to the worker, which reads it there. */
12
+ export type SourceInit = Blob | ArrayBuffer | Uint8Array;
13
+ /** `open`'s options. The password crosses to the worker once and is kept only there. */
14
+ export interface ViewerOpenOptions {
15
+ readonly password?: string;
16
+ /** Lamina PDF's reading limits (ADR 0003). */
17
+ readonly limits?: Partial<Limits>;
18
+ /** The backend's render limits (ADR 0004). */
19
+ readonly renderLimits?: Partial<RenderLimits>;
20
+ }
21
+ /** An open document, as layout needs it up front. */
22
+ export interface OpenedDocument {
23
+ /** Which open of this worker it is: renders name it, so one queued for a replaced document fails. */
24
+ readonly id: number;
25
+ readonly pageCount: number;
26
+ /** Every page's displayed size in points (`/Rotate` applied), rotation, ID and label. */
27
+ readonly pages: readonly PageInfo[];
28
+ /** Whether the file can't be opened without its password, so a restarted worker must ask again. */
29
+ readonly needsPassword: boolean;
30
+ readonly backend: {
31
+ readonly name: BackendName;
32
+ readonly version: string;
33
+ };
34
+ }
35
+ /** A rendered tile on the page: an ImageBitmap transferred from the worker. Close it when done. */
36
+ export interface RenderedBitmap {
37
+ readonly bitmap: ImageBitmap;
38
+ readonly width: number;
39
+ readonly height: number;
40
+ }
41
+ /** The viewer's methods: params and result per method. `client.call` is typed by it. */
42
+ export interface Methods {
43
+ initBackend: {
44
+ params: [BackendInit];
45
+ result: {
46
+ name: BackendName;
47
+ version: string;
48
+ };
49
+ };
50
+ openDocument: {
51
+ params: [source: SourceInit, options: ViewerOpenOptions];
52
+ result: OpenedDocument;
53
+ };
54
+ closeDocument: {
55
+ params: [];
56
+ result: undefined;
57
+ };
58
+ pageInfo: {
59
+ params: [index: number];
60
+ result: PageInfo;
61
+ };
62
+ render: {
63
+ params: [request: RenderRequest, document: number];
64
+ result: RenderedBitmap;
65
+ };
66
+ }
67
+ /** A viewer method's name. */
68
+ export type MethodName = keyof Methods;
69
+ /** Method `M`'s parameters. */
70
+ export type Params<M extends MethodName> = Methods[M]["params"];
71
+ /** Method `M`'s result. */
72
+ export type Result<M extends MethodName> = Methods[M]["result"];
73
+ /**
74
+ * Who is waiting (ADR 0009 §3.3): `visible` tiles first, then `prefetch` (just off screen), then
75
+ * `background` (thumbnails, search, text). Document calls run above all three.
76
+ */
77
+ export type Priority = "visible" | "prefetch" | "background";
78
+ /** The worker queue's numbers for each priority (higher first). */
79
+ export declare const PRIORITY: {
80
+ readonly [P in Priority | "document"]: number;
81
+ };
82
+ /** The fields of a render key that say which pixels it holds (see {@link renderKey}). */
83
+ export interface RenderKeyFields {
84
+ readonly scale: number;
85
+ readonly rotation: number;
86
+ }
87
+ /** Reads back the scale and rotation from a {@link renderKey}. */
88
+ export declare function renderKeyFields(key: string): RenderKeyFields;
89
+ /**
90
+ * The coalescing key of a render on document `id`: the document and the whole normalised
91
+ * RenderRequest (page, scale, clip, rotation, flags and background), so two requests share a run
92
+ * only when their pixels are the same (ADR 0009 §3.3).
93
+ */
94
+ export declare function renderKey(request: RenderRequest, id: number): string;
95
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ // The viewer's methods on the document worker (C3a, ADR 0009 §3). They extend `@laminapdf/sdk/worker`
2
+ // (L2a): one protocol, one queue and one cancellation path for both, with the viewer's methods
3
+ // added through `serve({ methods })` in the worker and `client.request` on the page. This module
4
+ // holds what both sides agree on: the method map (params and results), the priorities and the
5
+ // coalescing key.
6
+ import { flagsOf } from "@laminapdf/render";
7
+ /** The worker queue's numbers for each priority (higher first). */
8
+ export const PRIORITY = Object.freeze({
9
+ document: 4,
10
+ visible: 3,
11
+ prefetch: 2,
12
+ background: 1,
13
+ });
14
+ /** Reads back the scale and rotation from a {@link renderKey}. */
15
+ export function renderKeyFields(key) {
16
+ const fields = JSON.parse(key);
17
+ return { scale: fields[2], rotation: fields[4] };
18
+ }
19
+ /**
20
+ * The coalescing key of a render on document `id`: the document and the whole normalised
21
+ * RenderRequest (page, scale, clip, rotation, flags and background), so two requests share a run
22
+ * only when their pixels are the same (ADR 0009 §3.3).
23
+ */
24
+ export function renderKey(request, id) {
25
+ const { page, scale, clip, rotation = 0, flags, background } = request;
26
+ return JSON.stringify([
27
+ id,
28
+ page,
29
+ scale,
30
+ clip === undefined ? null : [clip.x, clip.y, clip.width, clip.height],
31
+ rotation,
32
+ flagsOf(flags),
33
+ background === undefined ? "white" : background,
34
+ ]);
35
+ }
36
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,+FAA+F;AAC/F,iGAAiG;AACjG,8FAA8F;AAC9F,kBAAkB;AAElB,OAAO,EAA4B,OAAO,EAAyC,MAAM,mBAAmB,CAAC;AAkE7G,mEAAmE;AACnE,MAAM,CAAC,MAAM,QAAQ,GAAsD,MAAM,CAAC,MAAM,CAAC;IACvF,QAAQ,EAAE,CAAC;IACX,OAAO,EAAE,CAAC;IACV,QAAQ,EAAE,CAAC;IACX,UAAU,EAAE,CAAC;CACd,CAAC,CAAC;AAQH,kEAAkE;AAClE,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAc,CAAC;IAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAW,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAW,EAAE,CAAC;AACvE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,OAAsB,EAAE,EAAU;IAC1D,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,GAAG,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IACvE,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,EAAE;QACF,IAAI;QACJ,KAAK;QACL,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC;QACrE,QAAQ;QACR,OAAO,CAAC,KAAK,CAAC;QACd,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU;KAChD,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { PixelRect } from "@laminapdf/render";
2
+ /** The render scale (output px per pt) of the low-resolution pass for a page `widthPt` wide. */
3
+ export declare function lowScale(sharpScale: number, widthPt: number): number;
4
+ /**
5
+ * The tiles (device px, in the page's pixel space at the sharp scale) covering `visible`, the
6
+ * part of the page in view in CSS px (x, y from the page's top-left), clamped to the page.
7
+ */
8
+ export declare function tilesFor(visible: {
9
+ readonly x: number;
10
+ readonly y: number;
11
+ readonly width: number;
12
+ readonly height: number;
13
+ }, pageCss: {
14
+ readonly width: number;
15
+ readonly height: number;
16
+ }, dpr: number): PixelRect[];
17
+ //# sourceMappingURL=tiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tiles.d.ts","sourceRoot":"","sources":["../../src/render/tiles.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAOnD,gGAAgG;AAChG,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,OAAO,EAAE;IAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACpG,OAAO,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAC5D,GAAG,EAAE,MAAM,GACV,SAAS,EAAE,CAeb"}