@embedpdf/plugin-render 3.0.0-next.1 → 3.0.0-next.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contract-Bvlo2Ukp.cjs +11 -0
- package/dist/contract-Bvlo2Ukp.cjs.map +1 -0
- package/dist/contract-BvwW7rrm.js +7 -0
- package/dist/contract-BvwW7rrm.js.map +1 -0
- package/dist/contract-CWPMd1OW.d.cts +332 -0
- package/dist/contract-CWPMd1OW.d.ts +332 -0
- package/dist/contract.cjs +3 -0
- package/dist/contract.d.cts +2 -0
- package/dist/contract.d.ts +2 -0
- package/dist/contract.js +2 -0
- package/dist/index.cjs +480 -157
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -218
- package/dist/index.d.ts +2 -218
- package/dist/index.js +479 -156
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_contract = require("./contract-Bvlo2Ukp.cjs");
|
|
2
3
|
let _embedpdf_core = require("@embedpdf/core");
|
|
3
4
|
let _embedpdf_core_geometry = require("@embedpdf/core-geometry");
|
|
4
5
|
//#region src/paint-plan.ts
|
|
@@ -8,36 +9,61 @@ const EMPTY_TILE_PLAN = {
|
|
|
8
9
|
fetching: [],
|
|
9
10
|
stamp: "empty"
|
|
10
11
|
};
|
|
11
|
-
|
|
12
|
+
/** The ×2 client pyramid used for lattice deployments without a tiles block. */
|
|
13
|
+
const DEFAULT_TILE_PYRAMID = [
|
|
14
|
+
1,
|
|
15
|
+
2,
|
|
16
|
+
4,
|
|
17
|
+
8,
|
|
18
|
+
16,
|
|
19
|
+
32
|
|
20
|
+
];
|
|
21
|
+
function resolveRenderOptions(options) {
|
|
22
|
+
const tiles = options.tiles === false ? void 0 : options.tiles;
|
|
12
23
|
return {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
fullPage: {
|
|
25
|
+
maxWidth: options.fullPage?.maxWidth ?? 640,
|
|
26
|
+
maxWidthExplicit: options.fullPage?.maxWidth !== void 0,
|
|
27
|
+
quantize: options.fullPage?.quantize ?? "exact"
|
|
28
|
+
},
|
|
29
|
+
tiles: {
|
|
30
|
+
enabled: options.tiles !== false,
|
|
31
|
+
size: tiles?.size ?? 512,
|
|
32
|
+
quantize: tiles?.quantize ?? "exact",
|
|
33
|
+
maxScale: tiles?.maxScale ?? 128,
|
|
34
|
+
bleedPx: tiles?.bleed ?? 1,
|
|
35
|
+
engageAt: tiles?.engageAt,
|
|
36
|
+
prefetchMargin: tiles?.prefetch?.margin ?? .5,
|
|
37
|
+
velocityBias: tiles?.prefetch?.velocityBias ?? true,
|
|
38
|
+
settleMs: tiles?.settleMs ?? 150,
|
|
39
|
+
fadeMs: tiles?.fadeMs ?? 0,
|
|
40
|
+
fallbackPyramid: DEFAULT_TILE_PYRAMID
|
|
41
|
+
},
|
|
42
|
+
format: options.format,
|
|
43
|
+
quality: options.quality,
|
|
44
|
+
debug: options.debug ?? false
|
|
26
45
|
};
|
|
27
46
|
}
|
|
28
47
|
//#endregion
|
|
29
48
|
//#region src/raster-store.ts
|
|
30
49
|
/**
|
|
31
50
|
* Client-side mirror of the server's one-door read-through: per-key
|
|
32
|
-
* SINGLEFLIGHT + LRU over encoded page images.
|
|
51
|
+
* SINGLEFLIGHT + cost-budgeted LRU over encoded page images.
|
|
33
52
|
*
|
|
34
53
|
* The key is the raster's canonical identity (conformed viewport +
|
|
35
|
-
* annotations flag + epoch — see `renderSourceKey`), so the
|
|
36
|
-
* caching work: every ask
|
|
54
|
+
* annotations flag + epoch — see `renderSourceKey`), so the render points do
|
|
55
|
+
* the caching work: every ask at one point is the same key, and
|
|
37
56
|
* re-crossings (zoom 1280 → 2560 → back) resolve instantly from the LRU
|
|
38
57
|
* instead of re-rendering. Epoch bumps mint new keys; stale-epoch entries
|
|
39
58
|
* age out through the LRU (they are never re-requested).
|
|
40
59
|
*
|
|
60
|
+
* The budget is COST, approximately bytes: an entry costs its encoded byte
|
|
61
|
+
* length when the handle carries bytes (local), its pixel count as a
|
|
62
|
+
* decoded-order estimate otherwise (URL-backed cloud handles), and 1 when
|
|
63
|
+
* neither is known. Entry counts lie — a 512² webp tile and a BMP full page
|
|
64
|
+
* differ by 100× — and long deep-zoom sessions cycle hundreds of tile keys,
|
|
65
|
+
* so the budget is what actually bounds memory.
|
|
66
|
+
*
|
|
41
67
|
* Consumer aborts are REFCOUNTED: a caller abandoning a fetch (camera moved,
|
|
42
68
|
* layer unmounted) detaches without killing it for other consumers; the
|
|
43
69
|
* underlying engine call aborts only when the LAST in-flight consumer leaves.
|
|
@@ -45,10 +71,11 @@ function resolveTiling(config) {
|
|
|
45
71
|
* is a no-op by design.
|
|
46
72
|
*/
|
|
47
73
|
var RasterStore = class {
|
|
48
|
-
constructor(
|
|
49
|
-
this.
|
|
74
|
+
constructor(maxCost = 128 * 1024 * 1024) {
|
|
75
|
+
this.maxCost = maxCost;
|
|
50
76
|
this.entries = /* @__PURE__ */ new Map();
|
|
51
77
|
this.tick = 0;
|
|
78
|
+
this.total = 0;
|
|
52
79
|
}
|
|
53
80
|
acquire(key, fetch, signal) {
|
|
54
81
|
const existing = this.entries.get(key);
|
|
@@ -65,9 +92,12 @@ var RasterStore = class {
|
|
|
65
92
|
};
|
|
66
93
|
entry.promise = fetch(abort.signal).then((handle) => {
|
|
67
94
|
entry.handle = handle;
|
|
95
|
+
entry.cost = costOf(handle);
|
|
96
|
+
this.total += entry.cost;
|
|
97
|
+
this.evict();
|
|
68
98
|
return handle;
|
|
69
99
|
}, (err) => {
|
|
70
|
-
|
|
100
|
+
this.drop(key, entry);
|
|
71
101
|
throw err;
|
|
72
102
|
});
|
|
73
103
|
entry.promise.catch(() => {});
|
|
@@ -84,6 +114,10 @@ var RasterStore = class {
|
|
|
84
114
|
get size() {
|
|
85
115
|
return this.entries.size;
|
|
86
116
|
}
|
|
117
|
+
/** Total cost (~bytes) of resolved entries — the budgeted quantity. */
|
|
118
|
+
get costUsed() {
|
|
119
|
+
return this.total;
|
|
120
|
+
}
|
|
87
121
|
attach(key, entry, signal) {
|
|
88
122
|
if (signal?.aborted) return Promise.reject(abortReason(signal));
|
|
89
123
|
entry.refs += 1;
|
|
@@ -102,7 +136,7 @@ var RasterStore = class {
|
|
|
102
136
|
entry.refs -= 1;
|
|
103
137
|
if (entry.refs <= 0 && entry.handle === void 0) {
|
|
104
138
|
entry.abort.abort(abortReason(signal));
|
|
105
|
-
|
|
139
|
+
this.drop(key, entry);
|
|
106
140
|
}
|
|
107
141
|
reject(abortReason(signal));
|
|
108
142
|
};
|
|
@@ -122,19 +156,72 @@ var RasterStore = class {
|
|
|
122
156
|
});
|
|
123
157
|
});
|
|
124
158
|
}
|
|
159
|
+
/** Remove an entry, keeping the cost ledger honest. */
|
|
160
|
+
drop(key, entry) {
|
|
161
|
+
if (this.entries.get(key) !== entry) return;
|
|
162
|
+
this.entries.delete(key);
|
|
163
|
+
if (entry.cost !== void 0) this.total -= entry.cost;
|
|
164
|
+
}
|
|
125
165
|
evict() {
|
|
126
|
-
if (this.
|
|
166
|
+
if (this.total <= this.maxCost) return;
|
|
127
167
|
const candidates = [...this.entries.entries()].filter(([, e]) => e.refs <= 0 && e.handle !== void 0).sort((a, b) => a[1].used - b[1].used);
|
|
128
|
-
for (const [key] of candidates) {
|
|
129
|
-
if (this.
|
|
130
|
-
this.
|
|
168
|
+
for (const [key, entry] of candidates) {
|
|
169
|
+
if (this.total <= this.maxCost) return;
|
|
170
|
+
this.drop(key, entry);
|
|
131
171
|
}
|
|
132
172
|
}
|
|
133
173
|
};
|
|
174
|
+
/** Approximate memory cost: encoded bytes when known, pixel count as a
|
|
175
|
+
* decoded-order estimate for URL handles, 1 when nothing is known. */
|
|
176
|
+
function costOf(handle) {
|
|
177
|
+
if (handle.source?.kind === "bytes") return handle.source.bytes.byteLength;
|
|
178
|
+
if (handle.width !== void 0 && handle.height !== void 0) return handle.width * handle.height;
|
|
179
|
+
return 1;
|
|
180
|
+
}
|
|
134
181
|
function abortReason(signal) {
|
|
135
182
|
return signal.reason ?? new DOMException("aborted", "AbortError");
|
|
136
183
|
}
|
|
137
184
|
//#endregion
|
|
185
|
+
//#region src/strategy.ts
|
|
186
|
+
const ascending = (values) => [...values].sort((a, b) => a - b);
|
|
187
|
+
/** Rung list capped by the budget — always keeps at least the smallest rung. */
|
|
188
|
+
const capWidths = (widths, maxWidth) => {
|
|
189
|
+
const sorted = ascending(widths);
|
|
190
|
+
const kept = sorted.filter((w) => w <= maxWidth);
|
|
191
|
+
return kept.length ? kept : sorted.slice(0, 1);
|
|
192
|
+
};
|
|
193
|
+
function resolveStrategy(policy, options) {
|
|
194
|
+
const lattice = policy.kind === "lattice";
|
|
195
|
+
const widths = lattice ? options.fullPage.maxWidthExplicit ? capWidths(policy.fullPage.widths, options.fullPage.maxWidth) : ascending(policy.fullPage.widths) : options.fullPage.quantize === "exact" ? null : capWidths(options.fullPage.quantize, options.fullPage.maxWidth);
|
|
196
|
+
const clientPyramid = options.tiles.quantize === "exact" ? null : ascending(options.tiles.quantize);
|
|
197
|
+
const pyramid = lattice ? policy.tiles?.scales ? ascending(policy.tiles.scales) : clientPyramid ?? options.tiles.fallbackPyramid : clientPyramid;
|
|
198
|
+
const tileSize = lattice && policy.tiles?.tileSizes.length ? policy.tiles.tileSizes.includes(options.tiles.size) ? options.tiles.size : policy.tiles.tileSizes[0] : options.tiles.size;
|
|
199
|
+
const engageAt = options.tiles.engageAt ?? (widths === null ? 1 : 1.25);
|
|
200
|
+
const requested = options.format;
|
|
201
|
+
const format = requested === void 0 ? void 0 : !lattice || policy.formats.includes(requested) ? requested : policy.formats[0];
|
|
202
|
+
return {
|
|
203
|
+
widths,
|
|
204
|
+
maxWidth: options.fullPage.maxWidth,
|
|
205
|
+
pyramid,
|
|
206
|
+
tileSize,
|
|
207
|
+
engageAt,
|
|
208
|
+
tileMaxScale: options.tiles.maxScale,
|
|
209
|
+
format,
|
|
210
|
+
quality: options.quality
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* The ONE base-sizing function — four callers: `renderPage`,
|
|
215
|
+
* `renderSourceKey`, `conformViewport`, and tile engagement. Exact mode
|
|
216
|
+
* returns the (settled) demand itself up to the budget; ladder mode snaps
|
|
217
|
+
* UP and caps at the top rung.
|
|
218
|
+
*/
|
|
219
|
+
function baseAskWidth(strategy, demandWidth) {
|
|
220
|
+
const wanted = Math.max(1, Math.round(demandWidth));
|
|
221
|
+
if (strategy.widths === null) return Math.min(wanted, strategy.maxWidth);
|
|
222
|
+
return strategy.widths.find((w) => w >= wanted) ?? strategy.widths[strategy.widths.length - 1];
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
138
225
|
//#region src/tiles.ts
|
|
139
226
|
function tileGrid(page, scale, tileSize) {
|
|
140
227
|
return {
|
|
@@ -175,14 +262,32 @@ function tilePaintRect(grid, page, c) {
|
|
|
175
262
|
height: Math.min(span, page.height - y)
|
|
176
263
|
};
|
|
177
264
|
}
|
|
178
|
-
/** Engine target rect
|
|
179
|
-
|
|
180
|
-
|
|
265
|
+
/** Engine target rect (y-UP PDF user space, top > bottom) for an arbitrary
|
|
266
|
+
* y-down page-point rect. */
|
|
267
|
+
function toEngineRect(page, rect) {
|
|
181
268
|
return {
|
|
182
|
-
left:
|
|
183
|
-
right:
|
|
184
|
-
top: page.height -
|
|
185
|
-
bottom: page.height - (
|
|
269
|
+
left: rect.x,
|
|
270
|
+
right: rect.x + rect.width,
|
|
271
|
+
top: page.height - rect.y,
|
|
272
|
+
bottom: page.height - (rect.y + rect.height)
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Expand a paint rect by `pt` page points per side, clamped to the page —
|
|
277
|
+
* the tile BLEED. Neighboring tiles rendered with bleed overlap by twice
|
|
278
|
+
* this amount, and the overlapping strips contain identical content (same
|
|
279
|
+
* page region, same scale), so every img edge composites over its
|
|
280
|
+
* neighbor's duplicated pixels instead of the backdrop: no AA hairlines,
|
|
281
|
+
* no bilinear edge smear, at any CSS stretch.
|
|
282
|
+
*/
|
|
283
|
+
function bleedRect(rect, pt, page) {
|
|
284
|
+
const x = Math.max(0, rect.x - pt);
|
|
285
|
+
const y = Math.max(0, rect.y - pt);
|
|
286
|
+
return {
|
|
287
|
+
x,
|
|
288
|
+
y,
|
|
289
|
+
width: Math.min(page.width, rect.x + rect.width + pt) - x,
|
|
290
|
+
height: Math.min(page.height, rect.y + rect.height + pt) - y
|
|
186
291
|
};
|
|
187
292
|
}
|
|
188
293
|
/**
|
|
@@ -248,56 +353,108 @@ function snapToPyramid(scales, needed) {
|
|
|
248
353
|
* pixels available; quality per region only goes up until the want set
|
|
249
354
|
* resolves. Mechanics:
|
|
250
355
|
* - want vs paint: `plan()` schedules fetches for the want set (P0
|
|
251
|
-
* visible first
|
|
252
|
-
* paint list drawn ONLY from resolved entries —
|
|
253
|
-
* retained older generations.
|
|
254
|
-
* - release-on-occlusion: when a want-level tile reports PAINTED (the
|
|
255
|
-
* layer's
|
|
256
|
-
* visible footprint is
|
|
257
|
-
*
|
|
258
|
-
* zoom-back re-promotes
|
|
356
|
+
* visible first — center-out — then P1 prefetch when P0 is fully
|
|
357
|
+
* resolved) and returns a paint list drawn ONLY from resolved entries —
|
|
358
|
+
* current level AND retained older generations.
|
|
359
|
+
* - release-on-occlusion: when a want-level tile reports PAINTED (after the
|
|
360
|
+
* layer's first presentation opportunity), retained sources whose
|
|
361
|
+
* visible footprint is
|
|
362
|
+
* covered by painted want tiles leave the paint list. Their bytes stay
|
|
363
|
+
* in the RasterStore (demotion, not eviction) — zoom-back re-promotes
|
|
364
|
+
* from cache.
|
|
259
365
|
* - epoch exception: an invalidation bump means retained pixels are
|
|
260
366
|
* WRONG — everything of the old epoch drops immediately.
|
|
261
367
|
*
|
|
368
|
+
* Levels come from the resolved STRATEGY (policy ∧ options): a pyramid
|
|
369
|
+
* under a lattice (or the opt-in client ladder), the EXACT settled scale
|
|
370
|
+
* under exact mode — where the level identity is the demand's device width
|
|
371
|
+
* across the page, so keys stay integer and stable. The retention/coverage
|
|
372
|
+
* math is generic over any mix of retained scales.
|
|
373
|
+
*
|
|
262
374
|
* `plan()` is called from selectors: it MUST NOT dispatch. Fetch kickoff
|
|
263
375
|
* is idempotent (the store singleflights); resolution handlers dispatch
|
|
264
376
|
* the wake-up (`onAdvance`) that makes subscribed layers recompute.
|
|
265
377
|
*/
|
|
378
|
+
/** One state entry per VIEW-of-page (see the class doc). */
|
|
379
|
+
const stateKey = (view, pon) => `${view}\u0000${pon}`;
|
|
380
|
+
/** Backpressure: raw rasters in flight at once (render + encode transit). */
|
|
381
|
+
const MAX_IN_FLIGHT = 8;
|
|
382
|
+
/** Stage-less (no visibleRect) demand is capped to this many whole-page tiles. */
|
|
383
|
+
const STAGELESS_TILE_CAP = 64;
|
|
266
384
|
var TileManager = class {
|
|
267
385
|
constructor(deps) {
|
|
268
386
|
this.deps = deps;
|
|
269
387
|
this.pages = /* @__PURE__ */ new Map();
|
|
388
|
+
this.strategyMemo = null;
|
|
389
|
+
this.inFlight = 0;
|
|
390
|
+
this.warnedStageless = false;
|
|
270
391
|
}
|
|
271
|
-
|
|
272
|
-
const { config } = this.deps;
|
|
392
|
+
strategy() {
|
|
273
393
|
const policy = this.deps.getPolicy();
|
|
394
|
+
if (this.strategyMemo?.policy !== policy) this.strategyMemo = {
|
|
395
|
+
policy,
|
|
396
|
+
strategy: resolveStrategy(policy, this.deps.options)
|
|
397
|
+
};
|
|
398
|
+
return this.strategyMemo.strategy;
|
|
399
|
+
}
|
|
400
|
+
plan(view, pon, demand, includeAnnotations) {
|
|
401
|
+
const { options } = this.deps;
|
|
402
|
+
if (!options.tiles.enabled) return EMPTY_TILE_PLAN;
|
|
403
|
+
const strategy = this.strategy();
|
|
274
404
|
const page = this.deps.getPageSize(pon);
|
|
275
405
|
if (!page) return EMPTY_TILE_PLAN;
|
|
276
406
|
const epoch = this.deps.getEpoch(pon, includeAnnotations);
|
|
277
|
-
const state = this.pageState(pon);
|
|
407
|
+
const state = this.pageState(pon, view);
|
|
278
408
|
if (state.epoch !== epoch) {
|
|
279
409
|
this.abortAll(state);
|
|
280
410
|
state.entries.clear();
|
|
411
|
+
state.failedKeys.clear();
|
|
281
412
|
state.epoch = epoch;
|
|
282
413
|
state.wantScale = null;
|
|
414
|
+
state.wantWidth = null;
|
|
283
415
|
state.planCache = null;
|
|
284
416
|
}
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
})) : demand.desiredDeviceWidth;
|
|
289
|
-
if (demand.desiredDeviceWidth / baseSupplied <= config.engageAt) {
|
|
417
|
+
const supplied = baseAskWidth(strategy, demand.desiredDeviceWidth);
|
|
418
|
+
const deficit = demand.desiredDeviceWidth / supplied;
|
|
419
|
+
if (deficit <= strategy.engageAt) {
|
|
290
420
|
if (state.entries.size || state.wantScale !== null) {
|
|
421
|
+
this.deps.debug?.(`disengage pon=${pon} (deficit ${deficit.toFixed(2)})`);
|
|
291
422
|
this.abortAll(state);
|
|
292
423
|
state.entries.clear();
|
|
424
|
+
state.failedKeys.clear();
|
|
293
425
|
state.wantScale = null;
|
|
426
|
+
state.wantWidth = null;
|
|
294
427
|
state.planCache = null;
|
|
295
428
|
}
|
|
296
429
|
return EMPTY_TILE_PLAN;
|
|
297
430
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
431
|
+
let wantWidth;
|
|
432
|
+
let wantScale;
|
|
433
|
+
if (strategy.pyramid) {
|
|
434
|
+
wantScale = snapToPyramid(strategy.pyramid, demand.desiredDeviceWidth / page.width);
|
|
435
|
+
wantWidth = Math.round(wantScale * page.width);
|
|
436
|
+
} else {
|
|
437
|
+
wantWidth = Math.min(Math.max(1, Math.round(demand.desiredDeviceWidth)), Math.round(strategy.tileMaxScale * page.width));
|
|
438
|
+
wantScale = wantWidth / page.width;
|
|
439
|
+
}
|
|
440
|
+
if (!demand.visibleRect) {
|
|
441
|
+
const maxStagelessWidth = Math.max(strategy.tileSize, Math.floor(strategy.tileSize * Math.sqrt(STAGELESS_TILE_CAP * page.width / page.height)));
|
|
442
|
+
if (wantWidth > maxStagelessWidth) {
|
|
443
|
+
if (!this.warnedStageless) {
|
|
444
|
+
this.warnedStageless = true;
|
|
445
|
+
console.warn(`[render] stage-less tile demand of ${Math.round(demand.desiredDeviceWidth)} device px would tile the whole page — clamped to ${maxStagelessWidth}. Supply a visibleRect (host the page in a Stage) for sharp deep zoom.`);
|
|
446
|
+
}
|
|
447
|
+
if (strategy.pyramid) {
|
|
448
|
+
const fitting = strategy.pyramid.filter((s) => Math.round(s * page.width) <= maxStagelessWidth);
|
|
449
|
+
wantScale = fitting.length ? fitting[fitting.length - 1] : strategy.pyramid[0];
|
|
450
|
+
wantWidth = Math.round(wantScale * page.width);
|
|
451
|
+
} else {
|
|
452
|
+
wantWidth = maxStagelessWidth;
|
|
453
|
+
wantScale = wantWidth / page.width;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const grid = tileGrid(page, wantScale, strategy.tileSize);
|
|
301
458
|
const visible = demand.visibleRect ?? {
|
|
302
459
|
x: 0,
|
|
303
460
|
y: 0,
|
|
@@ -306,26 +463,43 @@ var TileManager = class {
|
|
|
306
463
|
};
|
|
307
464
|
state.lastVisible = visible;
|
|
308
465
|
const p0 = tilesInRect(grid, page, visible);
|
|
309
|
-
const ring = inflateRect(visible,
|
|
466
|
+
const ring = inflateRect(visible, options.tiles.prefetchMargin, options.tiles.velocityBias ? demand.velocity : void 0);
|
|
310
467
|
const p0Keys = new Set(p0.map((c) => coordKey(c)));
|
|
311
468
|
const p1 = tilesInRect(grid, page, ring).filter((c) => !p0Keys.has(coordKey(c)));
|
|
312
|
-
this.schedule(pon, state, page, grid, wantScale, includeAnnotations, epoch, p0, p1);
|
|
313
|
-
|
|
469
|
+
this.schedule(pon, state, page, grid, wantScale, wantWidth, includeAnnotations, epoch, p0, p1, visible);
|
|
470
|
+
if (this.releaseCovered(pon, state, page, includeAnnotations)) {
|
|
471
|
+
state.version += 1;
|
|
472
|
+
state.planCache = null;
|
|
473
|
+
}
|
|
474
|
+
const demandKey = `w${wantWidth}|${rectKey(visible)}|e${epoch}`;
|
|
314
475
|
if (state.planCache && state.planCache.demandKey === demandKey && state.planCache.version === state.version) return state.planCache.plan;
|
|
315
476
|
const paint = [];
|
|
316
477
|
const fetching = [];
|
|
478
|
+
const stale = [];
|
|
479
|
+
const bleedPx = this.deps.options.tiles.bleedPx;
|
|
317
480
|
for (const entry of state.entries.values()) {
|
|
318
481
|
const visIntersect = (0, _embedpdf_core_geometry.intersectRects)(entry.rect, visible);
|
|
319
|
-
if (entry.
|
|
320
|
-
if (visIntersect.width > 0 && visIntersect.height > 0)
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
482
|
+
if (entry.resolved) {
|
|
483
|
+
if (visIntersect.width > 0 && visIntersect.height > 0) {
|
|
484
|
+
const handle = this.deps.store.peek(entry.key);
|
|
485
|
+
if (!handle) {
|
|
486
|
+
stale.push(entry.key);
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
paint.push({
|
|
490
|
+
key: entry.key,
|
|
491
|
+
scale: entry.scale,
|
|
492
|
+
rect: bleedPx > 0 ? bleedRect(entry.rect, bleedPx / entry.scale, page) : entry.rect,
|
|
493
|
+
z: 0,
|
|
494
|
+
handle
|
|
495
|
+
});
|
|
496
|
+
}
|
|
327
497
|
} else if (entry.scale === wantScale) fetching.push(entry.key);
|
|
328
498
|
}
|
|
499
|
+
for (const key of stale) state.entries.delete(key);
|
|
500
|
+
const rank = /* @__PURE__ */ new Map();
|
|
501
|
+
for (const s of [...new Set(paint.map((p) => p.scale))].sort((a, b) => a - b)) rank.set(s, rank.size);
|
|
502
|
+
for (const p of paint) p.z = rank.get(p.scale);
|
|
329
503
|
paint.sort((a, b) => a.z - b.z || a.key.localeCompare(b.key));
|
|
330
504
|
const plan = {
|
|
331
505
|
engaged: true,
|
|
@@ -340,62 +514,95 @@ var TileManager = class {
|
|
|
340
514
|
};
|
|
341
515
|
return plan;
|
|
342
516
|
}
|
|
343
|
-
/** The layer's
|
|
344
|
-
sourcePainted(pon, key) {
|
|
345
|
-
const state = this.pages.get(pon);
|
|
517
|
+
/** The layer's painted report: this key's pixels had a presentation opportunity. */
|
|
518
|
+
sourcePainted(view, pon, key) {
|
|
519
|
+
const state = this.pages.get(stateKey(view, pon));
|
|
346
520
|
const entry = state?.entries.get(key);
|
|
347
521
|
if (!state || !entry || entry.painted) return;
|
|
348
522
|
entry.painted = true;
|
|
349
|
-
this.
|
|
523
|
+
const page = this.deps.getPageSize(pon);
|
|
524
|
+
if (page) this.releaseCovered(pon, state, page, annotationsOf(key));
|
|
350
525
|
state.version += 1;
|
|
351
526
|
state.planCache = null;
|
|
352
527
|
this.deps.onAdvance(pon);
|
|
353
528
|
}
|
|
354
|
-
/**
|
|
529
|
+
/**
|
|
530
|
+
* The inverse report: this key's <img> left the DOM (pan-away, plan drop),
|
|
531
|
+
* so its pixels are NOT currently compositable. Without this, a tile that
|
|
532
|
+
* unmounts and later remounts is still counted as painted while its new
|
|
533
|
+
* <img> re-decodes — and an adjacent fresh `sourcePainted` could release
|
|
534
|
+
* retained coarse coverage over a region that momentarily has no sharp
|
|
535
|
+
* pixels. Painted is a statement about the SCREEN, so it follows the DOM.
|
|
536
|
+
*/
|
|
537
|
+
sourceUnpainted(view, pon, key) {
|
|
538
|
+
const entry = this.pages.get(stateKey(view, pon))?.entries.get(key);
|
|
539
|
+
if (entry) entry.painted = false;
|
|
540
|
+
}
|
|
541
|
+
/** A lens unmounted its tile plane: stop fetching, drop bookkeeping.
|
|
355
542
|
* Resolved bytes stay in the RasterStore for a re-mount. */
|
|
356
|
-
releasePage(pon) {
|
|
357
|
-
const state = this.pages.get(pon);
|
|
543
|
+
releasePage(view, pon) {
|
|
544
|
+
const state = this.pages.get(stateKey(view, pon));
|
|
358
545
|
if (!state) return;
|
|
359
546
|
this.abortAll(state);
|
|
360
|
-
this.pages.delete(pon);
|
|
547
|
+
this.pages.delete(stateKey(view, pon));
|
|
361
548
|
}
|
|
362
|
-
pageState(pon) {
|
|
363
|
-
let state = this.pages.get(pon);
|
|
549
|
+
pageState(pon, view) {
|
|
550
|
+
let state = this.pages.get(stateKey(view, pon));
|
|
364
551
|
if (!state) {
|
|
365
552
|
state = {
|
|
366
553
|
epoch: -1,
|
|
367
554
|
wantScale: null,
|
|
555
|
+
wantWidth: null,
|
|
368
556
|
entries: /* @__PURE__ */ new Map(),
|
|
557
|
+
failedKeys: /* @__PURE__ */ new Set(),
|
|
369
558
|
version: 0,
|
|
370
559
|
planCache: null,
|
|
371
560
|
settleTimer: null,
|
|
372
561
|
pendingLevel: null,
|
|
373
562
|
lastVisible: null
|
|
374
563
|
};
|
|
375
|
-
this.pages.set(pon, state);
|
|
564
|
+
this.pages.set(stateKey(view, pon), state);
|
|
376
565
|
}
|
|
377
566
|
return state;
|
|
378
567
|
}
|
|
379
|
-
schedule(pon, state, page, grid, wantScale, includeAnnotations, epoch, p0, p1) {
|
|
380
|
-
const {
|
|
381
|
-
const firstEngage = state.
|
|
382
|
-
const levelChanged = state.
|
|
568
|
+
schedule(pon, state, page, grid, wantScale, wantWidth, includeAnnotations, epoch, p0, p1, visible) {
|
|
569
|
+
const { options } = this.deps;
|
|
570
|
+
const firstEngage = state.wantWidth === null;
|
|
571
|
+
const levelChanged = state.wantWidth !== null && state.wantWidth !== wantWidth;
|
|
572
|
+
if (levelChanged) state.failedKeys.clear();
|
|
383
573
|
state.wantScale = wantScale;
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
574
|
+
state.wantWidth = wantWidth;
|
|
575
|
+
const wanted = new Set([...p0, ...p1].map((c) => this.tileKey(pon, wantWidth, c, includeAnnotations, epoch)));
|
|
576
|
+
for (const [key, entry] of state.entries) {
|
|
577
|
+
if (wanted.has(key)) continue;
|
|
578
|
+
if (!entry.resolved) {
|
|
579
|
+
entry.abort?.abort();
|
|
580
|
+
this.releaseSlot(entry);
|
|
581
|
+
state.entries.delete(key);
|
|
582
|
+
} else if (entry.scale === wantScale) state.entries.delete(key);
|
|
388
583
|
}
|
|
584
|
+
const cx = visible.x + visible.width / 2;
|
|
585
|
+
const cy = visible.y + visible.height / 2;
|
|
586
|
+
const span = grid.tileSize / grid.scale;
|
|
587
|
+
const orderedP0 = [...p0].sort((a, b) => {
|
|
588
|
+
const da = (a.ix + .5) * span - cx;
|
|
589
|
+
const db = (b.ix + .5) * span - cx;
|
|
590
|
+
const ea = (a.iy + .5) * span - cy;
|
|
591
|
+
const eb = (b.iy + .5) * span - cy;
|
|
592
|
+
return da * da + ea * ea - (db * db + eb * eb);
|
|
593
|
+
});
|
|
389
594
|
const kickoff = () => {
|
|
390
|
-
if (this.ensureFetches(pon, state, page, grid, includeAnnotations, epoch,
|
|
595
|
+
if (this.ensureFetches(pon, state, page, grid, wantWidth, includeAnnotations, epoch, orderedP0)) this.ensureFetches(pon, state, page, grid, wantWidth, includeAnnotations, epoch, p1);
|
|
391
596
|
};
|
|
392
|
-
if (levelChanged &&
|
|
393
|
-
|
|
597
|
+
if (levelChanged && options.tiles.settleMs > 0) {
|
|
598
|
+
this.deps.debug?.(`arm settle pon=${pon} level=${wantWidth}`);
|
|
599
|
+
state.pendingLevel = wantWidth;
|
|
394
600
|
if (state.settleTimer !== null) clearTimeout(state.settleTimer);
|
|
395
601
|
state.settleTimer = setTimeout(() => {
|
|
396
602
|
state.settleTimer = null;
|
|
397
|
-
|
|
398
|
-
|
|
603
|
+
this.deps.debug?.(`settle fired pon=${pon} level=${wantWidth} current=${state.pendingLevel === wantWidth}`);
|
|
604
|
+
if (state.pendingLevel === wantWidth) kickoff();
|
|
605
|
+
}, options.tiles.settleMs);
|
|
399
606
|
return;
|
|
400
607
|
}
|
|
401
608
|
if (state.settleTimer !== null && !levelChanged && !firstEngage) {
|
|
@@ -406,95 +613,178 @@ var TileManager = class {
|
|
|
406
613
|
kickoff();
|
|
407
614
|
}
|
|
408
615
|
/** Start missing fetches; true when every coord is already resolved. */
|
|
409
|
-
ensureFetches(pon, state, page, grid, includeAnnotations, epoch, coords) {
|
|
616
|
+
ensureFetches(pon, state, page, grid, wantWidth, includeAnnotations, epoch, coords) {
|
|
410
617
|
let allReady = true;
|
|
618
|
+
let started = 0;
|
|
619
|
+
const bleedPt = this.deps.options.tiles.bleedPx / grid.scale;
|
|
411
620
|
for (const coord of coords) {
|
|
412
|
-
const key = this.tileKey(pon,
|
|
621
|
+
const key = this.tileKey(pon, wantWidth, coord, includeAnnotations, epoch);
|
|
622
|
+
if (state.failedKeys.has(key)) continue;
|
|
413
623
|
const existing = state.entries.get(key);
|
|
414
624
|
if (existing) {
|
|
415
|
-
if (existing.
|
|
625
|
+
if (!existing.resolved) allReady = false;
|
|
416
626
|
continue;
|
|
417
627
|
}
|
|
418
628
|
allReady = false;
|
|
629
|
+
if (this.inFlight >= MAX_IN_FLIGHT) continue;
|
|
630
|
+
this.inFlight += 1;
|
|
631
|
+
started += 1;
|
|
419
632
|
const abort = new AbortController();
|
|
633
|
+
const logical = tilePaintRect(grid, page, coord);
|
|
420
634
|
const entry = {
|
|
421
635
|
key,
|
|
422
636
|
scale: grid.scale,
|
|
423
637
|
coord,
|
|
424
|
-
rect:
|
|
638
|
+
rect: logical,
|
|
639
|
+
resolved: false,
|
|
425
640
|
painted: false,
|
|
641
|
+
charged: true,
|
|
426
642
|
abort
|
|
427
643
|
};
|
|
428
644
|
state.entries.set(key, entry);
|
|
429
|
-
this.deps.store.acquire(key, (signal) => this.deps.fetchTile(pon,
|
|
645
|
+
this.deps.store.acquire(key, (signal) => this.deps.fetchTile(pon, toEngineRect(page, bleedPt > 0 ? bleedRect(logical, bleedPt, page) : logical), grid.scale, includeAnnotations, signal), abort.signal).then(() => {
|
|
646
|
+
this.releaseSlot(entry);
|
|
430
647
|
if (state.entries.get(key) !== entry) return;
|
|
431
|
-
entry.
|
|
648
|
+
entry.resolved = true;
|
|
432
649
|
state.version += 1;
|
|
433
650
|
state.planCache = null;
|
|
434
651
|
this.deps.onAdvance(pon);
|
|
435
|
-
}, () => {
|
|
436
|
-
|
|
652
|
+
}, (err) => {
|
|
653
|
+
this.releaseSlot(entry);
|
|
654
|
+
if (state.entries.get(key) !== entry) return;
|
|
655
|
+
state.entries.delete(key);
|
|
656
|
+
if (!abort.signal.aborted) {
|
|
657
|
+
state.failedKeys.add(key);
|
|
658
|
+
this.deps.debug?.(`tile failed ${key}: ${String(err)}`);
|
|
659
|
+
state.version += 1;
|
|
660
|
+
state.planCache = null;
|
|
661
|
+
this.deps.onAdvance(pon);
|
|
662
|
+
}
|
|
437
663
|
});
|
|
438
664
|
}
|
|
665
|
+
if (started > 0) this.deps.debug?.(`fetch pon=${pon} level=${wantWidth} +${started} tiles`);
|
|
439
666
|
return allReady;
|
|
440
667
|
}
|
|
441
668
|
/**
|
|
442
|
-
* The release rule
|
|
443
|
-
* sources (any
|
|
444
|
-
* covered by PAINTED want-level tiles leave the paint list.
|
|
445
|
-
*
|
|
669
|
+
* The release rule, evaluated as a PURE FUNCTION of the current painted
|
|
670
|
+
* set: retained sources (any non-want level) whose VISIBLE footprint is
|
|
671
|
+
* covered by PAINTED want-level tiles leave the paint list. Runs on every
|
|
672
|
+
* `plan()` and on every painted report — never only "against the tile
|
|
673
|
+
* that just painted": release must not depend on paint ORDER (center-out
|
|
674
|
+
* scheduling means the report that completes a retained tile's coverage
|
|
675
|
+
* routinely lands far away from it), and re-evaluating from current state
|
|
676
|
+
* also self-heals any painted-flag transition the reports raced past.
|
|
677
|
+
* Coverage is index arithmetic over the WANT grid — generic over whatever
|
|
678
|
+
* mix of retained scales exists (exact levels included).
|
|
446
679
|
*/
|
|
447
|
-
|
|
448
|
-
const wantScale = state
|
|
449
|
-
if (wantScale === null ||
|
|
450
|
-
const
|
|
451
|
-
if (!page) return;
|
|
452
|
-
const grid = tileGrid(page, wantScale, this.deps.config.tileSize);
|
|
680
|
+
releaseCovered(pon, state, page, includeAnnotations) {
|
|
681
|
+
const { wantScale, wantWidth } = state;
|
|
682
|
+
if (wantScale === null || wantWidth === null) return false;
|
|
683
|
+
const grid = tileGrid(page, wantScale, this.strategy().tileSize);
|
|
453
684
|
const paintedAt = (c) => {
|
|
454
|
-
const key = this.tileKey(
|
|
685
|
+
const key = this.tileKey(pon, wantWidth, c, includeAnnotations, state.epoch);
|
|
455
686
|
return state.entries.get(key)?.painted === true;
|
|
456
687
|
};
|
|
688
|
+
let released = false;
|
|
457
689
|
for (const [key, entry] of state.entries) {
|
|
458
|
-
if (entry.scale === wantScale || entry.
|
|
459
|
-
const
|
|
460
|
-
if (
|
|
461
|
-
|
|
690
|
+
if (entry.scale === wantScale || !entry.resolved) continue;
|
|
691
|
+
const region = state.lastVisible ? (0, _embedpdf_core_geometry.intersectRects)(entry.rect, state.lastVisible) : entry.rect;
|
|
692
|
+
if (regionCovered(grid, page, region, paintedAt)) {
|
|
693
|
+
state.entries.delete(key);
|
|
694
|
+
released = true;
|
|
695
|
+
} else if (this.deps.debug) {
|
|
696
|
+
const missing = tilesInRect(grid, page, region).filter((c) => !paintedAt(c));
|
|
697
|
+
const detail = missing.slice(0, 3).map((c) => {
|
|
698
|
+
const k = this.tileKey(pon, wantWidth, c, includeAnnotations, state.epoch);
|
|
699
|
+
const e = state.entries.get(k);
|
|
700
|
+
const st = !e ? "NO-ENTRY" : !e.resolved ? "PENDING" : "RESOLVED-unpainted";
|
|
701
|
+
return `${c.ix},${c.iy}=${st}`;
|
|
702
|
+
});
|
|
703
|
+
this.deps.debug(`retained ${key} blocked by: ${detail.join(" ")} (${missing.length} missing)`);
|
|
704
|
+
}
|
|
462
705
|
}
|
|
706
|
+
return released;
|
|
707
|
+
}
|
|
708
|
+
/** Test/diagnostic introspection: bookkeeping size for one page. */
|
|
709
|
+
stats(view, pon) {
|
|
710
|
+
return {
|
|
711
|
+
entries: this.pages.get(stateKey(view, pon))?.entries.size ?? 0,
|
|
712
|
+
inFlight: this.inFlight
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
/** Idempotent transit-slot release — the ONE place inFlight decrements. */
|
|
716
|
+
releaseSlot(entry) {
|
|
717
|
+
if (!entry.charged) return;
|
|
718
|
+
entry.charged = false;
|
|
719
|
+
this.inFlight -= 1;
|
|
463
720
|
}
|
|
464
721
|
abortAll(state) {
|
|
465
722
|
if (state.settleTimer !== null) {
|
|
466
723
|
clearTimeout(state.settleTimer);
|
|
467
724
|
state.settleTimer = null;
|
|
468
725
|
}
|
|
469
|
-
for (const entry of state.entries.values()) if (entry.
|
|
726
|
+
for (const entry of state.entries.values()) if (!entry.resolved) {
|
|
727
|
+
entry.abort?.abort();
|
|
728
|
+
this.releaseSlot(entry);
|
|
729
|
+
}
|
|
470
730
|
}
|
|
471
|
-
|
|
472
|
-
|
|
731
|
+
/** Level identity is the integer device width across the page — integer
|
|
732
|
+
* and stable for pyramid rungs and exact levels alike. The key also
|
|
733
|
+
* carries everything that determines the fetched BITMAP for a coord
|
|
734
|
+
* (tile size, bleed, encode format): a raster's identity must include
|
|
735
|
+
* its geometry, or a cached handle from one configuration could be
|
|
736
|
+
* stretched into another's rect. Format genuinely varies at runtime —
|
|
737
|
+
* the cloud policy arrives async after open and can change the resolved
|
|
738
|
+
* format under a live store. */
|
|
739
|
+
tileKey(pon, levelWidth, c, includeAnnotations, epoch) {
|
|
740
|
+
const { tiles } = this.deps.options;
|
|
741
|
+
const format = this.strategy().format;
|
|
742
|
+
const geometry = `g${tiles.size}.${tiles.bleedPx}${format ? `.${format}` : ""}`;
|
|
743
|
+
return `t:${pon}|w${levelWidth}|${c.ix},${c.iy}|a${includeAnnotations ? 1 : 0}|e${epoch}|${geometry}`;
|
|
473
744
|
}
|
|
474
745
|
};
|
|
475
|
-
const widthOf = (v) => v.kind === "width" && v.width !== void 0 ? v.width : Number.POSITIVE_INFINITY;
|
|
476
746
|
const coordKey = (c) => `${c.ix},${c.iy}`;
|
|
477
747
|
const rectKey = (r) => `${Math.round(r.x)},${Math.round(r.y)},${Math.round(r.width)},${Math.round(r.height)}`;
|
|
478
|
-
/** Stacking: position in the ascending pyramid (coarse under fine). */
|
|
479
|
-
const zOf = (pyramid, scale) => {
|
|
480
|
-
const sorted = [...pyramid].sort((a, b) => a - b);
|
|
481
|
-
const idx = sorted.indexOf(scale);
|
|
482
|
-
return idx === -1 ? sorted.findIndex((s) => s > scale) : idx;
|
|
483
|
-
};
|
|
484
|
-
const pagePonOf = (key) => Number(key.slice(2, key.indexOf("|")));
|
|
485
748
|
const annotationsOf = (key) => key.includes("|a1|");
|
|
486
749
|
//#endregion
|
|
487
750
|
//#region src/capability.ts
|
|
488
751
|
/**
|
|
489
|
-
* The render capability: the ONE
|
|
490
|
-
* (the
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
* store, exposing stable source
|
|
752
|
+
* The render capability: the ONE place STRATEGY (plugin options — what the
|
|
753
|
+
* viewer spends) composes with POLICY (the engine fact — what the deployment
|
|
754
|
+
* serves). The engine never snaps; framework layers never see either;
|
|
755
|
+
* everything between — conforming demand to the resolved render points,
|
|
756
|
+
* collapsing same-key asks in the raster store, exposing stable source
|
|
757
|
+
* keys — happens here.
|
|
494
758
|
*/
|
|
495
759
|
function createRenderCapability(ctx, options = {}) {
|
|
760
|
+
const resolved = resolveRenderOptions(options);
|
|
761
|
+
const canRender = () => ctx.doc?.security.allows("doc.render") ?? false;
|
|
496
762
|
const store = new RasterStore();
|
|
497
763
|
const policy = () => ctx.document()?.renderPolicy ?? _embedpdf_core.CONTINUOUS_RENDER_POLICY;
|
|
764
|
+
let strategyMemo = null;
|
|
765
|
+
const strategy = () => {
|
|
766
|
+
const p = policy();
|
|
767
|
+
if (strategyMemo?.policy !== p) {
|
|
768
|
+
strategyMemo = {
|
|
769
|
+
policy: p,
|
|
770
|
+
strategy: resolveStrategy(p, resolved)
|
|
771
|
+
};
|
|
772
|
+
warnDroppedFormat(strategyMemo.strategy);
|
|
773
|
+
}
|
|
774
|
+
return strategyMemo.strategy;
|
|
775
|
+
};
|
|
776
|
+
let warnedFormat = false;
|
|
777
|
+
const warnDroppedFormat = (s) => {
|
|
778
|
+
if (warnedFormat || resolved.format === void 0 || s.format === resolved.format) return;
|
|
779
|
+
warnedFormat = true;
|
|
780
|
+
console.warn(`[render] format '${resolved.format}' is not in the deployment's formats — using '${s.format}'.`);
|
|
781
|
+
};
|
|
782
|
+
let warnedBudget = false;
|
|
783
|
+
const warnPastBudgetNoTiles = (demandWidth, supplied) => {
|
|
784
|
+
if (warnedBudget || resolved.tiles.enabled || demandWidth <= supplied * 2) return;
|
|
785
|
+
warnedBudget = true;
|
|
786
|
+
console.warn(`[render] demand is ${(demandWidth / supplied).toFixed(1)}× over the base budget and the tile plane is disabled (tiles: false) — the page rests blurry. Raise fullPage.maxWidth or re-enable tiles.`);
|
|
787
|
+
};
|
|
498
788
|
const pageOf = (pon) => (ctx.document()?.pages ?? []).find((p) => p.pageObjectNumber === pon);
|
|
499
789
|
const pageWidthOf = (pon) => {
|
|
500
790
|
const page = pageOf(pon);
|
|
@@ -502,32 +792,54 @@ function createRenderCapability(ctx, options = {}) {
|
|
|
502
792
|
return page.size.width;
|
|
503
793
|
};
|
|
504
794
|
const conformViewport = (pon, scale) => {
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
795
|
+
const s = strategy();
|
|
796
|
+
const demandWidth = scale * pageWidthOf(pon);
|
|
797
|
+
const width = baseAskWidth(s, demandWidth);
|
|
798
|
+
warnPastBudgetNoTiles(demandWidth, width);
|
|
799
|
+
return {
|
|
800
|
+
kind: "width",
|
|
801
|
+
width
|
|
509
802
|
};
|
|
510
|
-
return (0, _embedpdf_core.snapFullPageViewport)(p, {
|
|
511
|
-
kind: "scale",
|
|
512
|
-
scale
|
|
513
|
-
}, { pageWidth: pageWidthOf(pon) });
|
|
514
803
|
};
|
|
515
|
-
const viewportKey = (viewport) => viewport.kind === "width" ? `w${viewport.width}` : `s${viewport.scale ?? 1}`;
|
|
516
804
|
const epochOf = (pon, includeAnnotations) => {
|
|
517
805
|
const s = ctx.getState();
|
|
518
806
|
const content = s.contentEpochs[pon] ?? 0;
|
|
519
807
|
if (!includeAnnotations) return content;
|
|
520
808
|
return content + (s.annotatedEpochs[pon] ?? 0);
|
|
521
809
|
};
|
|
810
|
+
const baseKey = (pon, viewport, annotations) => {
|
|
811
|
+
const format = strategy().format;
|
|
812
|
+
return `${pon}|w${viewport.kind === "width" ? viewport.width : 0}|a${annotations ? 1 : 0}|e${epochOf(pon, annotations)}${format ? `|f${format}` : ""}`;
|
|
813
|
+
};
|
|
814
|
+
const pendingAdvance = /* @__PURE__ */ new Set();
|
|
815
|
+
let advanceScheduled = false;
|
|
816
|
+
const raf = typeof requestAnimationFrame === "function" ? (cb) => requestAnimationFrame(() => cb()) : (cb) => void setTimeout(cb, 16);
|
|
817
|
+
const wake = (pon) => {
|
|
818
|
+
pendingAdvance.add(pon);
|
|
819
|
+
if (advanceScheduled) return;
|
|
820
|
+
advanceScheduled = true;
|
|
821
|
+
raf(() => {
|
|
822
|
+
advanceScheduled = false;
|
|
823
|
+
const pons = [...pendingAdvance];
|
|
824
|
+
pendingAdvance.clear();
|
|
825
|
+
for (const p of pons) ctx.dispatch({
|
|
826
|
+
type: "PAINT_ADVANCED",
|
|
827
|
+
pon: p
|
|
828
|
+
});
|
|
829
|
+
});
|
|
830
|
+
};
|
|
831
|
+
const viewTiles = /* @__PURE__ */ new Map();
|
|
522
832
|
const tiles = new TileManager({
|
|
523
833
|
store,
|
|
524
|
-
|
|
834
|
+
options: resolved,
|
|
525
835
|
getPolicy: policy,
|
|
526
836
|
getPageSize: (pon) => pageOf(pon)?.size,
|
|
527
837
|
getEpoch: epochOf,
|
|
528
838
|
fetchTile: (pon, rect, scale, includeAnnotations, signal) => {
|
|
529
839
|
const doc = ctx.doc;
|
|
530
840
|
if (!doc) return Promise.reject(/* @__PURE__ */ new Error("render: no document bound"));
|
|
841
|
+
if (!canRender()) return Promise.reject(new _embedpdf_core.PermissionDenied("doc.render", "render.tile"));
|
|
842
|
+
const s = strategy();
|
|
531
843
|
const task = doc.page(pon).render.image({
|
|
532
844
|
target: {
|
|
533
845
|
kind: "rect",
|
|
@@ -537,28 +849,33 @@ function createRenderCapability(ctx, options = {}) {
|
|
|
537
849
|
kind: "scale",
|
|
538
850
|
scale
|
|
539
851
|
},
|
|
540
|
-
includeAnnotations
|
|
852
|
+
includeAnnotations,
|
|
853
|
+
...s.format !== void 0 ? { format: s.format } : {},
|
|
854
|
+
...s.quality !== void 0 ? { quality: s.quality } : {}
|
|
541
855
|
});
|
|
542
856
|
if (signal.aborted) task.abort(signal.reason);
|
|
543
857
|
else signal.addEventListener("abort", () => task.abort(signal.reason), { once: true });
|
|
544
858
|
return task;
|
|
545
859
|
},
|
|
546
|
-
onAdvance:
|
|
547
|
-
|
|
548
|
-
pon
|
|
549
|
-
})
|
|
860
|
+
onAdvance: wake,
|
|
861
|
+
...resolved.debug ? { debug: (msg) => console.debug(`[render] ${msg}`) } : {}
|
|
550
862
|
});
|
|
551
863
|
return {
|
|
864
|
+
canRender,
|
|
552
865
|
renderPage(pon, { scale, includeAnnotations, signal }) {
|
|
553
866
|
const doc = ctx.doc;
|
|
554
867
|
if (!doc) return Promise.reject(/* @__PURE__ */ new Error("render: no document bound"));
|
|
868
|
+
if (!canRender()) return Promise.reject(new _embedpdf_core.PermissionDenied("doc.render", "render.renderPage"));
|
|
555
869
|
const annotations = includeAnnotations ?? true;
|
|
556
870
|
const viewport = conformViewport(pon, scale);
|
|
557
|
-
const key =
|
|
871
|
+
const key = baseKey(pon, viewport, annotations);
|
|
558
872
|
return store.acquire(key, (storeSignal) => {
|
|
873
|
+
const s = strategy();
|
|
559
874
|
const task = doc.page(pon).render.image({
|
|
560
875
|
viewport,
|
|
561
|
-
includeAnnotations: annotations
|
|
876
|
+
includeAnnotations: annotations,
|
|
877
|
+
...s.format !== void 0 ? { format: s.format } : {},
|
|
878
|
+
...s.quality !== void 0 ? { quality: s.quality } : {}
|
|
562
879
|
});
|
|
563
880
|
if (storeSignal.aborted) task.abort(storeSignal.reason);
|
|
564
881
|
else storeSignal.addEventListener("abort", () => task.abort(storeSignal.reason), { once: true });
|
|
@@ -567,19 +884,28 @@ function createRenderCapability(ctx, options = {}) {
|
|
|
567
884
|
},
|
|
568
885
|
renderSourceKey(pon, { scale, includeAnnotations }) {
|
|
569
886
|
const annotations = includeAnnotations ?? true;
|
|
570
|
-
|
|
571
|
-
return `${pon}|${viewportKey(viewport)}|a${annotations ? 1 : 0}|e${epochOf(pon, annotations)}`;
|
|
887
|
+
return baseKey(pon, conformViewport(pon, scale), annotations);
|
|
572
888
|
},
|
|
573
889
|
conformViewport,
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
tiles.sourcePainted(pon, key);
|
|
890
|
+
paintSettings() {
|
|
891
|
+
return {
|
|
892
|
+
fadeMs: resolved.tiles.fadeMs,
|
|
893
|
+
tiles: resolved.tiles.enabled
|
|
894
|
+
};
|
|
580
895
|
},
|
|
581
|
-
|
|
582
|
-
|
|
896
|
+
renderPolicy: policy,
|
|
897
|
+
tilesFor(view) {
|
|
898
|
+
let handle = viewTiles.get(view);
|
|
899
|
+
if (!handle) {
|
|
900
|
+
handle = {
|
|
901
|
+
plan: (pon, demand, opts) => tiles.plan(view, pon, demand, opts?.includeAnnotations ?? true),
|
|
902
|
+
painted: (pon, key) => tiles.sourcePainted(view, pon, key),
|
|
903
|
+
unpainted: (pon, key) => tiles.sourceUnpainted(view, pon, key),
|
|
904
|
+
release: (pon) => tiles.releasePage(view, pon)
|
|
905
|
+
};
|
|
906
|
+
viewTiles.set(view, handle);
|
|
907
|
+
}
|
|
908
|
+
return handle;
|
|
583
909
|
},
|
|
584
910
|
renderEpoch(pon, includeAnnotations = true) {
|
|
585
911
|
return epochOf(pon, includeAnnotations);
|
|
@@ -678,9 +1004,6 @@ const renderReducer = (state, a) => {
|
|
|
678
1004
|
};
|
|
679
1005
|
};
|
|
680
1006
|
//#endregion
|
|
681
|
-
//#region src/types.ts
|
|
682
|
-
const RenderToken = (0, _embedpdf_core.createCapabilityToken)("render");
|
|
683
|
-
//#endregion
|
|
684
1007
|
//#region src/render.plugin.ts
|
|
685
1008
|
/**
|
|
686
1009
|
* Document-scoped. The ONE policy consumer in the client stack: the doc-bind
|
|
@@ -696,14 +1019,14 @@ const RenderToken = (0, _embedpdf_core.createCapabilityToken)("render");
|
|
|
696
1019
|
const renderPlugin = (options = {}) => (0, _embedpdf_core.definePlugin)({
|
|
697
1020
|
id: "render",
|
|
698
1021
|
scope: "document",
|
|
699
|
-
token: RenderToken,
|
|
1022
|
+
token: require_contract.RenderToken,
|
|
700
1023
|
initialState: initialRenderState,
|
|
701
1024
|
reduce: renderReducer,
|
|
702
1025
|
capability: (ctx) => createRenderCapability(ctx, options),
|
|
703
1026
|
effects: registerRenderEffects
|
|
704
1027
|
});
|
|
705
1028
|
//#endregion
|
|
706
|
-
exports.RenderToken = RenderToken;
|
|
1029
|
+
exports.RenderToken = require_contract.RenderToken;
|
|
707
1030
|
exports.renderPlugin = renderPlugin;
|
|
708
1031
|
|
|
709
1032
|
//# sourceMappingURL=index.cjs.map
|