@embedpdf/plugin-render 3.0.0-next.14 → 3.0.0-next.16
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/host.cjs +18 -0
- package/dist/contract/host.cjs.map +1 -0
- package/dist/contract/host.d.cts +3 -0
- package/dist/contract/host.d.ts +3 -0
- package/dist/contract/host.js +16 -0
- package/dist/contract/host.js.map +1 -0
- package/dist/contract-CGcu-84H.cjs +124 -0
- package/dist/contract-CGcu-84H.cjs.map +1 -0
- package/dist/contract-Dy5TiQDy.js +95 -0
- package/dist/contract-Dy5TiQDy.js.map +1 -0
- package/dist/contract-uc5nOice.d.cts +279 -0
- package/dist/contract-uc5nOice.d.ts +279 -0
- package/dist/contract.cjs +3 -1
- package/dist/contract.d.cts +2 -2
- package/dist/contract.d.ts +2 -2
- package/dist/contract.js +2 -2
- package/dist/host-contract-CTOQn0Xz.d.cts +127 -0
- package/dist/host-contract-CsjZjWjb.d.ts +127 -0
- package/dist/index.cjs +869 -493
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -12
- package/dist/index.d.ts +29 -12
- package/dist/index.js +866 -492
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
- package/dist/contract-Bvlo2Ukp.cjs +0 -11
- package/dist/contract-Bvlo2Ukp.cjs.map +0 -1
- package/dist/contract-BvwW7rrm.js +0 -7
- package/dist/contract-BvwW7rrm.js.map +0 -1
- package/dist/contract-CWPMd1OW.d.cts +0 -332
- package/dist/contract-CWPMd1OW.d.ts +0 -332
package/dist/index.js
CHANGED
|
@@ -1,71 +1,254 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { a as samePageViewDemand, i as resolveRenderOptions, n as RenderToken, r as EMPTY_TILE_PLAN, t as RENDER_DEFAULTS } from "./contract-Dy5TiQDy.js";
|
|
2
|
+
import { RenderToken as RenderToken$1 } from "./contract/host.js";
|
|
3
|
+
import { CONTINUOUS_RENDER_POLICY, annotationKey, definePlugin, isPluginError, memo, resolvePageLayers, snapAppearanceScale, toPageRef, toPluginError, toPluginErrorInfo } from "@embedpdf/core";
|
|
3
4
|
import { intersectRects } from "@embedpdf/core-geometry";
|
|
4
|
-
//#region src/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
//#region src/appearance-store.ts
|
|
6
|
+
/**
|
|
7
|
+
* The engine's pictures of a page's annotations or form widgets, shared
|
|
8
|
+
* between the views and plugins that draw them. One ask is one key (the
|
|
9
|
+
* page, the family, the scale and the owner's items at their versions):
|
|
10
|
+
* asks with the same key share one fetch.
|
|
11
|
+
*
|
|
12
|
+
* Consumer aborts are refcounted, as in the raster store: a view that leaves
|
|
13
|
+
* detaches without stopping the fetch for the others; the fetch stops only
|
|
14
|
+
* when the last one leaves before it resolved.
|
|
15
|
+
*
|
|
16
|
+
* Kept for undo:
|
|
17
|
+
* - the last {@link SETS_KEPT} sets of each page and family: an undo that
|
|
18
|
+
* brings a page back to an earlier set asks the engine for nothing;
|
|
19
|
+
* - each item's last pictures ({@link RecentPictures}), a while after it left
|
|
20
|
+
* its page: an undo that brings it back shows them at once.
|
|
21
|
+
*/
|
|
22
|
+
var AppearanceStore = class {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
25
|
+
this.kept = /* @__PURE__ */ new Map();
|
|
26
|
+
}
|
|
27
|
+
/** `group` names the page and family; `key` the whole ask. */
|
|
28
|
+
acquire(group, key, fetch, signal) {
|
|
29
|
+
const existing = this.entries.get(key);
|
|
30
|
+
if (existing) {
|
|
31
|
+
if (existing.resolved) this.keep(group, key);
|
|
32
|
+
return this.attach(key, existing, signal);
|
|
33
|
+
}
|
|
34
|
+
const entry = {
|
|
35
|
+
group,
|
|
36
|
+
promise: void 0,
|
|
37
|
+
refs: 0,
|
|
38
|
+
abort: new AbortController(),
|
|
39
|
+
resolved: false
|
|
40
|
+
};
|
|
41
|
+
entry.promise = fetch(entry.abort.signal).then((pictures) => {
|
|
42
|
+
entry.resolved = true;
|
|
43
|
+
this.keep(group, key);
|
|
44
|
+
return pictures;
|
|
45
|
+
}, (error) => {
|
|
46
|
+
this.drop(key, entry);
|
|
47
|
+
throw error;
|
|
48
|
+
});
|
|
49
|
+
entry.promise.catch(() => {});
|
|
50
|
+
this.entries.set(key, entry);
|
|
51
|
+
return this.attach(key, entry, signal);
|
|
52
|
+
}
|
|
53
|
+
/** Keep `key` as the group's newest set, letting go of the oldest beyond the few kept. */
|
|
54
|
+
keep(group, key) {
|
|
55
|
+
const kept = (this.kept.get(group) ?? []).filter((each) => each !== key);
|
|
56
|
+
kept.push(key);
|
|
57
|
+
const gone = kept.splice(0, Math.max(0, kept.length - 4));
|
|
58
|
+
this.kept.set(group, kept);
|
|
59
|
+
for (const old of gone) {
|
|
60
|
+
const entry = this.entries.get(old);
|
|
61
|
+
if (entry && entry.refs <= 0) this.entries.delete(old);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
isKept(group, key) {
|
|
65
|
+
return this.kept.get(group)?.includes(key) ?? false;
|
|
66
|
+
}
|
|
67
|
+
attach(key, entry, signal) {
|
|
68
|
+
if (signal?.aborted) return Promise.reject(abortReason$1(signal));
|
|
69
|
+
entry.refs += 1;
|
|
70
|
+
if (!signal) {
|
|
71
|
+
const release = () => this.release(key, entry);
|
|
72
|
+
entry.promise.then(release, release);
|
|
73
|
+
return entry.promise;
|
|
74
|
+
}
|
|
75
|
+
let settled = false;
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const onAbort = () => {
|
|
78
|
+
if (settled) return;
|
|
79
|
+
settled = true;
|
|
80
|
+
entry.refs -= 1;
|
|
81
|
+
if (entry.refs <= 0 && !entry.resolved) {
|
|
82
|
+
entry.abort.abort(abortReason$1(signal));
|
|
83
|
+
this.drop(key, entry);
|
|
84
|
+
}
|
|
85
|
+
reject(abortReason$1(signal));
|
|
86
|
+
};
|
|
87
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
88
|
+
entry.promise.then((pictures) => {
|
|
89
|
+
if (settled) return;
|
|
90
|
+
settled = true;
|
|
91
|
+
this.release(key, entry);
|
|
92
|
+
signal.removeEventListener("abort", onAbort);
|
|
93
|
+
resolve(pictures);
|
|
94
|
+
}, (error) => {
|
|
95
|
+
if (settled) return;
|
|
96
|
+
settled = true;
|
|
97
|
+
this.release(key, entry);
|
|
98
|
+
signal.removeEventListener("abort", onAbort);
|
|
99
|
+
reject(error);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/** A consumer let go: a set no longer kept goes with the last one. */
|
|
104
|
+
release(key, entry) {
|
|
105
|
+
entry.refs -= 1;
|
|
106
|
+
if (entry.refs <= 0 && entry.resolved && !this.isKept(entry.group, key)) this.drop(key, entry);
|
|
107
|
+
}
|
|
108
|
+
drop(key, entry) {
|
|
109
|
+
if (this.entries.get(key) === entry) this.entries.delete(key);
|
|
110
|
+
}
|
|
10
111
|
};
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
112
|
+
/**
|
|
113
|
+
* The last pictures of each item (all its states), the newest {@link ITEMS_KEPT}
|
|
114
|
+
* items per family, kept after the item left its page.
|
|
115
|
+
*/
|
|
116
|
+
var RecentPictures = class {
|
|
117
|
+
constructor() {
|
|
118
|
+
this.families = /* @__PURE__ */ new Map();
|
|
119
|
+
}
|
|
120
|
+
/** A fetch's pictures, grouped by item: each replaces what its item had. */
|
|
121
|
+
remember(family, pictures, keyOf) {
|
|
122
|
+
const items = this.families.get(family) ?? /* @__PURE__ */ new Map();
|
|
123
|
+
const byItem = /* @__PURE__ */ new Map();
|
|
124
|
+
for (const picture of pictures) {
|
|
125
|
+
const key = keyOf(picture);
|
|
126
|
+
byItem.set(key, [...byItem.get(key) ?? [], picture]);
|
|
127
|
+
}
|
|
128
|
+
for (const [key, itemPictures] of byItem) {
|
|
129
|
+
items.delete(key);
|
|
130
|
+
items.set(key, itemPictures);
|
|
131
|
+
}
|
|
132
|
+
while (items.size > 200) items.delete(items.keys().next().value);
|
|
133
|
+
this.families.set(family, items);
|
|
134
|
+
}
|
|
135
|
+
of(family, key) {
|
|
136
|
+
return this.families.get(family)?.get(key) ?? [];
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
function abortReason$1(signal) {
|
|
140
|
+
return signal.reason ?? new DOMException("aborted", "AbortError");
|
|
141
|
+
}
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/invalidation.ts
|
|
144
|
+
const placedPages = (widgets) => widgets.flatMap((widget) => widget.page ? [widget.page.objectNumber] : []);
|
|
145
|
+
const changeOf = (pages, scope) => pages.length ? {
|
|
146
|
+
pages: [...new Set(pages)],
|
|
147
|
+
scope
|
|
148
|
+
} : null;
|
|
149
|
+
/**
|
|
150
|
+
* The pages a form fact repaints. Every form fact names the widgets whose
|
|
151
|
+
* look changed or that went (`meta.changedWidgets`), each with its page; the
|
|
152
|
+
* widget rows it carries, and a deleted widget's page, are counted too. An
|
|
153
|
+
* unplaced widget (`page: null`) has no pixels to repaint.
|
|
154
|
+
*/
|
|
155
|
+
function formPagesOf(event) {
|
|
156
|
+
const pages = placedPages(event.meta.changedWidgets);
|
|
157
|
+
if ("widgets" in event) pages.push(...placedPages(event.widgets));
|
|
158
|
+
if ("page" in event && event.page) pages.push(event.page.objectNumber);
|
|
159
|
+
return pages;
|
|
160
|
+
}
|
|
161
|
+
function pixelChangeOf(event, allPageObjectNumbers) {
|
|
162
|
+
switch (event.type) {
|
|
163
|
+
case "annotations.created":
|
|
164
|
+
case "annotations.updated":
|
|
165
|
+
case "annotations.deleted":
|
|
166
|
+
case "annotations.restored":
|
|
167
|
+
case "annotations.reordered": return changeOf([event.page.objectNumber], "annotations");
|
|
168
|
+
case "forms.valueSet":
|
|
169
|
+
case "forms.created":
|
|
170
|
+
case "forms.updated":
|
|
171
|
+
case "forms.deleted":
|
|
172
|
+
case "forms.restored":
|
|
173
|
+
case "forms.widgetAdded":
|
|
174
|
+
case "forms.widgetRemoved":
|
|
175
|
+
case "forms.widgetDeleted":
|
|
176
|
+
case "forms.widgetRestored":
|
|
177
|
+
case "forms.widgetUpdated":
|
|
178
|
+
case "forms.widgetsReordered": return changeOf(formPagesOf(event), "fields");
|
|
179
|
+
case "forms.repaired": return changeOf(allPageObjectNumbers(), "fields");
|
|
180
|
+
case "signatures.completed": return changeOf(event.signature.widget ? placedPages([event.signature.widget]) : [], "fields");
|
|
181
|
+
case "redaction.applied":
|
|
182
|
+
case "pages.flattened": return changeOf(event.results.filter((result) => result.status === "applied").map((result) => result.page.objectNumber), "content");
|
|
183
|
+
case "annotations.flattened": return changeOf(event.results.some((result) => result.status === "applied") ? [event.page.objectNumber] : [], "content");
|
|
184
|
+
case "stream.desynced": return changeOf(allPageObjectNumbers(), "content");
|
|
185
|
+
default: return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/model.ts
|
|
190
|
+
const initialRenderState = () => ({
|
|
191
|
+
contentEpochs: {},
|
|
192
|
+
annotationEpochs: {},
|
|
193
|
+
fieldEpochs: {}
|
|
194
|
+
});
|
|
195
|
+
const bump = (epochs, pageObjectNumbers) => {
|
|
196
|
+
const next = { ...epochs };
|
|
197
|
+
for (const pageObjectNumber of pageObjectNumbers) next[pageObjectNumber] = (next[pageObjectNumber] ?? 0) + 1;
|
|
198
|
+
return next;
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* Bump the pages' versions in the ledger the scope names. A content fact
|
|
202
|
+
* bumps only the content ledger; every picture counts content (see
|
|
203
|
+
* {@link renderEpochOf}), which is how content invalidation reaches them all.
|
|
204
|
+
*/
|
|
205
|
+
function invalidatePages(state, pageObjectNumbers, scope) {
|
|
206
|
+
if (pageObjectNumbers.length === 0) return state;
|
|
207
|
+
switch (scope) {
|
|
208
|
+
case "content": return {
|
|
209
|
+
...state,
|
|
210
|
+
contentEpochs: bump(state.contentEpochs, pageObjectNumbers)
|
|
211
|
+
};
|
|
212
|
+
case "annotations": return {
|
|
213
|
+
...state,
|
|
214
|
+
annotationEpochs: bump(state.annotationEpochs, pageObjectNumbers)
|
|
215
|
+
};
|
|
216
|
+
case "fields": return {
|
|
217
|
+
...state,
|
|
218
|
+
fieldEpochs: bump(state.fieldEpochs, pageObjectNumbers)
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/** The version of one picture of a page: its content, plus the annotations and form fields it draws. */
|
|
223
|
+
function renderEpochOf(state, pageObjectNumber, layers) {
|
|
224
|
+
return (state.contentEpochs[pageObjectNumber] ?? 0) + (layers.includeAnnotations ? state.annotationEpochs[pageObjectNumber] ?? 0 : 0) + (layers.includeFormFields ? state.fieldEpochs[pageObjectNumber] ?? 0 : 0);
|
|
45
225
|
}
|
|
226
|
+
/** What a picture draws, as one key part: `a1f0` draws annotations and no form fields. */
|
|
227
|
+
const layersKeyOf = (layers) => `a${layers.includeAnnotations ? 1 : 0}f${layers.includeFormFields ? 1 : 0}`;
|
|
46
228
|
//#endregion
|
|
47
229
|
//#region src/raster-store.ts
|
|
48
230
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
231
|
+
* The client-side counterpart of the server's read-through cache: per-key
|
|
232
|
+
* singleflight + cost-budgeted LRU over encoded page images.
|
|
51
233
|
*
|
|
52
|
-
* The key is the raster's canonical identity (
|
|
53
|
-
* annotations flag
|
|
234
|
+
* The key is the raster's canonical identity (for page rasters: width,
|
|
235
|
+
* annotations flag, epoch and format, built by `rasterKey` in the
|
|
236
|
+
* controller; for tiles: `TileManager`'s tile key), so the render points do
|
|
54
237
|
* the caching work: every ask at one point is the same key, and
|
|
55
238
|
* re-crossings (zoom 1280 → 2560 → back) resolve instantly from the LRU
|
|
56
239
|
* instead of re-rendering. Epoch bumps mint new keys; stale-epoch entries
|
|
57
240
|
* age out through the LRU (they are never re-requested).
|
|
58
241
|
*
|
|
59
|
-
* The budget is
|
|
242
|
+
* The budget is cost, approximately bytes: an entry costs its encoded byte
|
|
60
243
|
* length when the handle carries bytes (local), its pixel count as a
|
|
61
244
|
* decoded-order estimate otherwise (URL-backed cloud handles), and 1 when
|
|
62
245
|
* neither is known. Entry counts lie — a 512² webp tile and a BMP full page
|
|
63
246
|
* differ by 100× — and long deep-zoom sessions cycle hundreds of tile keys,
|
|
64
247
|
* so the budget is what actually bounds memory.
|
|
65
248
|
*
|
|
66
|
-
* Consumer aborts are
|
|
249
|
+
* Consumer aborts are refcounted: a caller abandoning a fetch (camera moved,
|
|
67
250
|
* layer unmounted) detaches without killing it for other consumers; the
|
|
68
|
-
* underlying engine call aborts only when the
|
|
251
|
+
* underlying engine call aborts only when the last in-flight consumer leaves.
|
|
69
252
|
* Resolved entries are kept (evictable by LRU) — an abort after resolution
|
|
70
253
|
* is a no-op by design.
|
|
71
254
|
*/
|
|
@@ -95,9 +278,9 @@ var RasterStore = class {
|
|
|
95
278
|
this.total += entry.cost;
|
|
96
279
|
this.evict();
|
|
97
280
|
return handle;
|
|
98
|
-
}, (
|
|
281
|
+
}, (error) => {
|
|
99
282
|
this.drop(key, entry);
|
|
100
|
-
throw
|
|
283
|
+
throw error;
|
|
101
284
|
});
|
|
102
285
|
entry.promise.catch(() => {});
|
|
103
286
|
this.entries.set(key, entry);
|
|
@@ -146,12 +329,12 @@ var RasterStore = class {
|
|
|
146
329
|
entry.refs -= 1;
|
|
147
330
|
signal.removeEventListener("abort", onAbort);
|
|
148
331
|
resolve(handle);
|
|
149
|
-
}, (
|
|
332
|
+
}, (error) => {
|
|
150
333
|
if (settled) return;
|
|
151
334
|
settled = true;
|
|
152
335
|
entry.refs -= 1;
|
|
153
336
|
signal.removeEventListener("abort", onAbort);
|
|
154
|
-
reject(
|
|
337
|
+
reject(error);
|
|
155
338
|
});
|
|
156
339
|
});
|
|
157
340
|
}
|
|
@@ -163,7 +346,7 @@ var RasterStore = class {
|
|
|
163
346
|
}
|
|
164
347
|
evict() {
|
|
165
348
|
if (this.total <= this.maxCost) return;
|
|
166
|
-
const candidates = [...this.entries.entries()].filter(([,
|
|
349
|
+
const candidates = [...this.entries.entries()].filter(([, entry]) => entry.refs <= 0 && entry.handle !== void 0).sort((left, right) => left[1].used - right[1].used);
|
|
167
350
|
for (const [key, entry] of candidates) {
|
|
168
351
|
if (this.total <= this.maxCost) return;
|
|
169
352
|
this.drop(key, entry);
|
|
@@ -182,11 +365,11 @@ function abortReason(signal) {
|
|
|
182
365
|
}
|
|
183
366
|
//#endregion
|
|
184
367
|
//#region src/strategy.ts
|
|
185
|
-
const ascending = (values) => [...values].sort((
|
|
368
|
+
const ascending = (values) => [...values].sort((left, right) => left - right);
|
|
186
369
|
/** Rung list capped by the budget — always keeps at least the smallest rung. */
|
|
187
370
|
const capWidths = (widths, maxWidth) => {
|
|
188
371
|
const sorted = ascending(widths);
|
|
189
|
-
const kept = sorted.filter((
|
|
372
|
+
const kept = sorted.filter((width) => width <= maxWidth);
|
|
190
373
|
return kept.length ? kept : sorted.slice(0, 1);
|
|
191
374
|
};
|
|
192
375
|
function resolveStrategy(policy, options) {
|
|
@@ -210,15 +393,16 @@ function resolveStrategy(policy, options) {
|
|
|
210
393
|
};
|
|
211
394
|
}
|
|
212
395
|
/**
|
|
213
|
-
* The
|
|
214
|
-
* `
|
|
396
|
+
* The one base-sizing function. Its callers: the controller's
|
|
397
|
+
* `conformViewport` (which `renderSource` and `getSourceKey` go through)
|
|
398
|
+
* and tile engagement in the tile manager. Exact mode
|
|
215
399
|
* returns the (settled) demand itself up to the budget; ladder mode snaps
|
|
216
|
-
*
|
|
400
|
+
* up and caps at the top rung.
|
|
217
401
|
*/
|
|
218
402
|
function baseAskWidth(strategy, demandWidth) {
|
|
219
403
|
const wanted = Math.max(1, Math.round(demandWidth));
|
|
220
404
|
if (strategy.widths === null) return Math.min(wanted, strategy.maxWidth);
|
|
221
|
-
return strategy.widths.find((
|
|
405
|
+
return strategy.widths.find((width) => width >= wanted) ?? strategy.widths[strategy.widths.length - 1];
|
|
222
406
|
}
|
|
223
407
|
//#endregion
|
|
224
408
|
//#region src/tiles.ts
|
|
@@ -238,22 +422,22 @@ const tileSpanPt = (grid) => grid.tileSize / grid.scale;
|
|
|
238
422
|
*/
|
|
239
423
|
function tilesInRect(grid, page, rect) {
|
|
240
424
|
const span = tileSpanPt(grid);
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
for (let iy =
|
|
425
|
+
const firstColumn = Math.max(0, Math.floor(rect.x / span));
|
|
426
|
+
const firstRow = Math.max(0, Math.floor(rect.y / span));
|
|
427
|
+
const lastColumn = Math.min(grid.cols - 1, Math.ceil((rect.x + rect.width) / span) - 1);
|
|
428
|
+
const lastRow = Math.min(grid.rows - 1, Math.ceil((rect.y + rect.height) / span) - 1);
|
|
429
|
+
const coords = [];
|
|
430
|
+
for (let iy = firstRow; iy <= lastRow; iy++) for (let ix = firstColumn; ix <= lastColumn; ix++) coords.push({
|
|
247
431
|
ix,
|
|
248
432
|
iy
|
|
249
433
|
});
|
|
250
|
-
return
|
|
434
|
+
return coords;
|
|
251
435
|
}
|
|
252
436
|
/** Paint rect: y-down page points, clamped to the page edge. */
|
|
253
|
-
function tilePaintRect(grid, page,
|
|
437
|
+
function tilePaintRect(grid, page, coord) {
|
|
254
438
|
const span = tileSpanPt(grid);
|
|
255
|
-
const x =
|
|
256
|
-
const y =
|
|
439
|
+
const x = coord.ix * span;
|
|
440
|
+
const y = coord.iy * span;
|
|
257
441
|
return {
|
|
258
442
|
x,
|
|
259
443
|
y,
|
|
@@ -261,43 +445,33 @@ function tilePaintRect(grid, page, c) {
|
|
|
261
445
|
height: Math.min(span, page.height - y)
|
|
262
446
|
};
|
|
263
447
|
}
|
|
264
|
-
/** Engine target rect (y-UP PDF user space, top > bottom) for an arbitrary
|
|
265
|
-
* y-down page-point rect. */
|
|
266
|
-
function toEngineRect(page, rect) {
|
|
267
|
-
return {
|
|
268
|
-
left: rect.x,
|
|
269
|
-
right: rect.x + rect.width,
|
|
270
|
-
top: page.height - rect.y,
|
|
271
|
-
bottom: page.height - (rect.y + rect.height)
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
448
|
/**
|
|
275
|
-
* Expand a paint rect by `
|
|
276
|
-
* the tile
|
|
449
|
+
* Expand a paint rect by `amountPt` page points per side, clamped to the page —
|
|
450
|
+
* the tile bleed. Neighboring tiles rendered with bleed overlap by twice
|
|
277
451
|
* this amount, and the overlapping strips contain identical content (same
|
|
278
452
|
* page region, same scale), so every img edge composites over its
|
|
279
453
|
* neighbor's duplicated pixels instead of the backdrop: no AA hairlines,
|
|
280
454
|
* no bilinear edge smear, at any CSS stretch.
|
|
281
455
|
*/
|
|
282
|
-
function bleedRect(rect,
|
|
283
|
-
const x = Math.max(0, rect.x -
|
|
284
|
-
const y = Math.max(0, rect.y -
|
|
456
|
+
function bleedRect(rect, amountPt, page) {
|
|
457
|
+
const x = Math.max(0, rect.x - amountPt);
|
|
458
|
+
const y = Math.max(0, rect.y - amountPt);
|
|
285
459
|
return {
|
|
286
460
|
x,
|
|
287
461
|
y,
|
|
288
|
-
width: Math.min(page.width, rect.x + rect.width +
|
|
289
|
-
height: Math.min(page.height, rect.y + rect.height +
|
|
462
|
+
width: Math.min(page.width, rect.x + rect.width + amountPt) - x,
|
|
463
|
+
height: Math.min(page.height, rect.y + rect.height + amountPt) - y
|
|
290
464
|
};
|
|
291
465
|
}
|
|
292
466
|
/**
|
|
293
|
-
* Does a set of
|
|
467
|
+
* Does a set of painted want-level tiles cover `region` (y-down points)?
|
|
294
468
|
* The retention release check: a retained source may leave the paint list
|
|
295
469
|
* once its visible footprint answers true here. Pure index arithmetic on
|
|
296
470
|
* the aligned grid — the whole reason the pyramid is aligned.
|
|
297
471
|
*/
|
|
298
472
|
function regionCovered(grid, page, region, isPainted) {
|
|
299
473
|
if (region.width <= 0 || region.height <= 0) return true;
|
|
300
|
-
for (const
|
|
474
|
+
for (const coord of tilesInRect(grid, page, region)) if (!isPainted(coord)) return false;
|
|
301
475
|
return true;
|
|
302
476
|
}
|
|
303
477
|
/**
|
|
@@ -307,26 +481,26 @@ function regionCovered(grid, page, region, isPainted) {
|
|
|
307
481
|
* velocity is just data.
|
|
308
482
|
*/
|
|
309
483
|
function inflateRect(rect, margin, velocity) {
|
|
310
|
-
const
|
|
311
|
-
const
|
|
312
|
-
let left =
|
|
313
|
-
let right =
|
|
314
|
-
let up =
|
|
315
|
-
let down =
|
|
484
|
+
const marginX = rect.width * margin;
|
|
485
|
+
const marginY = rect.height * margin;
|
|
486
|
+
let left = marginX;
|
|
487
|
+
let right = marginX;
|
|
488
|
+
let up = marginY;
|
|
489
|
+
let down = marginY;
|
|
316
490
|
if (velocity) {
|
|
317
491
|
if (velocity.dx > 0) {
|
|
318
|
-
right =
|
|
319
|
-
left =
|
|
492
|
+
right = marginX * 2;
|
|
493
|
+
left = marginX / 2;
|
|
320
494
|
} else if (velocity.dx < 0) {
|
|
321
|
-
left =
|
|
322
|
-
right =
|
|
495
|
+
left = marginX * 2;
|
|
496
|
+
right = marginX / 2;
|
|
323
497
|
}
|
|
324
498
|
if (velocity.dy > 0) {
|
|
325
|
-
down =
|
|
326
|
-
up =
|
|
499
|
+
down = marginY * 2;
|
|
500
|
+
up = marginY / 2;
|
|
327
501
|
} else if (velocity.dy < 0) {
|
|
328
|
-
up =
|
|
329
|
-
down =
|
|
502
|
+
up = marginY * 2;
|
|
503
|
+
down = marginY / 2;
|
|
330
504
|
}
|
|
331
505
|
}
|
|
332
506
|
return {
|
|
@@ -336,74 +510,70 @@ function inflateRect(rect, margin, velocity) {
|
|
|
336
510
|
height: rect.height + up + down
|
|
337
511
|
};
|
|
338
512
|
}
|
|
339
|
-
/** Snap
|
|
513
|
+
/** Snap up through a pyramid's sorted scales; cap at the top. */
|
|
340
514
|
function snapToPyramid(scales, needed) {
|
|
341
|
-
const sorted = [...scales].sort((
|
|
342
|
-
return sorted.find((
|
|
515
|
+
const sorted = [...scales].sort((left, right) => left - right);
|
|
516
|
+
return sorted.find((scale) => scale >= needed) ?? sorted[sorted.length - 1];
|
|
343
517
|
}
|
|
344
518
|
//#endregion
|
|
345
519
|
//#region src/tile-manager.ts
|
|
520
|
+
/** One state entry per view of a page (see {@link TileManager}'s `pages`). */
|
|
521
|
+
const stateKey = (view, pageObjectNumber) => `${view}\u0000${pageObjectNumber}`;
|
|
522
|
+
/** Backpressure: raw rasters in flight at once (render + encode transit). */
|
|
523
|
+
const MAX_IN_FLIGHT = 8;
|
|
524
|
+
/** Stage-less (no visibleRect) demand is capped to this many whole-page tiles. */
|
|
525
|
+
const STAGELESS_TILE_CAP = 64;
|
|
346
526
|
/**
|
|
347
|
-
* The tile retention state machine. One instance per
|
|
348
|
-
*
|
|
349
|
-
* the
|
|
527
|
+
* The tile retention state machine. One instance per plugin instance (per
|
|
528
|
+
* document), all levels and pages in one place, over the same RasterStore
|
|
529
|
+
* the base renders use.
|
|
350
530
|
*
|
|
351
|
-
* Invariant it enforces: every screen region paints the sharpest
|
|
531
|
+
* Invariant it enforces: every screen region paints the sharpest painted
|
|
352
532
|
* pixels available; quality per region only goes up until the want set
|
|
353
533
|
* resolves. Mechanics:
|
|
354
|
-
* - want vs paint: `plan()`
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
* current level
|
|
358
|
-
* -
|
|
534
|
+
* - want vs paint: `plan()` queues the want set (visible tiles first,
|
|
535
|
+
* center-out, then the prefetch ring once every visible tile resolved)
|
|
536
|
+
* and returns a paint list drawn only from resolved entries: the
|
|
537
|
+
* current level and retained older generations.
|
|
538
|
+
* - one line for every view and page: a transit slot goes to an on-screen
|
|
539
|
+
* tile of any page before a tile of any prefetch ring, so a ring never
|
|
540
|
+
* takes the slots an on-screen tile needs. The engine ranks the tiles it
|
|
541
|
+
* is given by where each one is on screen.
|
|
542
|
+
* - release-on-occlusion: when a want-level tile reports painted (after the
|
|
359
543
|
* layer's first presentation opportunity), retained sources whose
|
|
360
|
-
* visible footprint is
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
* from cache.
|
|
544
|
+
* visible footprint is covered by painted want tiles leave the paint
|
|
545
|
+
* list. Their bytes stay in the RasterStore (demotion, not eviction), so
|
|
546
|
+
* zooming back re-promotes from cache.
|
|
364
547
|
* - epoch exception: an invalidation bump means retained pixels are
|
|
365
|
-
*
|
|
548
|
+
* wrong, so everything of the old epoch drops immediately.
|
|
366
549
|
*
|
|
367
|
-
* Levels come from the resolved
|
|
368
|
-
* under a lattice (or the opt-in client ladder), the
|
|
369
|
-
* under exact mode
|
|
550
|
+
* Levels come from the resolved strategy (policy ∧ options): a pyramid
|
|
551
|
+
* under a lattice (or the opt-in client ladder), the exact settled scale
|
|
552
|
+
* under exact mode, where the level identity is the demand's device width
|
|
370
553
|
* across the page, so keys stay integer and stable. The retention/coverage
|
|
371
554
|
* math is generic over any mix of retained scales.
|
|
372
555
|
*
|
|
373
|
-
* `plan()`
|
|
374
|
-
* is idempotent (the store
|
|
375
|
-
*
|
|
556
|
+
* `plan()` never wakes readers itself: its callers do, once, when the
|
|
557
|
+
* returned plan changed. Fetch kickoff is idempotent (the store
|
|
558
|
+
* singleflights); resolution handlers call `onAdvance`, which re-plans the
|
|
559
|
+
* page and wakes subscribed layers, and hand the freed slot on.
|
|
376
560
|
*/
|
|
377
|
-
/** One state entry per VIEW-of-page (see the class doc). */
|
|
378
|
-
const stateKey = (view, pon) => `${view}\u0000${pon}`;
|
|
379
|
-
/** Backpressure: raw rasters in flight at once (render + encode transit). */
|
|
380
|
-
const MAX_IN_FLIGHT = 8;
|
|
381
|
-
/** Stage-less (no visibleRect) demand is capped to this many whole-page tiles. */
|
|
382
|
-
const STAGELESS_TILE_CAP = 64;
|
|
383
561
|
var TileManager = class {
|
|
384
562
|
constructor(deps) {
|
|
385
563
|
this.deps = deps;
|
|
386
564
|
this.pages = /* @__PURE__ */ new Map();
|
|
387
|
-
this.
|
|
565
|
+
this.strategy = memo(() => [this.deps.getPolicy(), this.deps.getOptions()], (policy, options) => resolveStrategy(policy, options));
|
|
388
566
|
this.inFlight = 0;
|
|
389
567
|
this.warnedStageless = false;
|
|
390
568
|
}
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
if (this.strategyMemo?.policy !== policy) this.strategyMemo = {
|
|
394
|
-
policy,
|
|
395
|
-
strategy: resolveStrategy(policy, this.deps.options)
|
|
396
|
-
};
|
|
397
|
-
return this.strategyMemo.strategy;
|
|
398
|
-
}
|
|
399
|
-
plan(view, pon, demand, includeAnnotations) {
|
|
400
|
-
const { options } = this.deps;
|
|
569
|
+
plan(view, pageObjectNumber, demand, layers) {
|
|
570
|
+
const options = this.deps.getOptions();
|
|
401
571
|
if (!options.tiles.enabled) return EMPTY_TILE_PLAN;
|
|
402
572
|
const strategy = this.strategy();
|
|
403
|
-
const page = this.deps.getPageSize(
|
|
573
|
+
const page = this.deps.getPageSize(pageObjectNumber);
|
|
404
574
|
if (!page) return EMPTY_TILE_PLAN;
|
|
405
|
-
const epoch = this.deps.getEpoch(
|
|
406
|
-
const state = this.pageState(
|
|
575
|
+
const epoch = this.deps.getEpoch(pageObjectNumber, layers);
|
|
576
|
+
const state = this.pageState(pageObjectNumber, view);
|
|
407
577
|
if (state.epoch !== epoch) {
|
|
408
578
|
this.abortAll(state);
|
|
409
579
|
state.entries.clear();
|
|
@@ -417,13 +587,14 @@ var TileManager = class {
|
|
|
417
587
|
const deficit = demand.desiredDeviceWidth / supplied;
|
|
418
588
|
if (deficit <= strategy.engageAt) {
|
|
419
589
|
if (state.entries.size || state.wantScale !== null) {
|
|
420
|
-
this.deps.debug?.(`disengage
|
|
590
|
+
this.deps.debug?.(`disengage page=${pageObjectNumber} (deficit ${deficit.toFixed(2)})`);
|
|
421
591
|
this.abortAll(state);
|
|
422
592
|
state.entries.clear();
|
|
423
593
|
state.failedKeys.clear();
|
|
424
594
|
state.wantScale = null;
|
|
425
595
|
state.wantWidth = null;
|
|
426
596
|
state.planCache = null;
|
|
597
|
+
this.pump();
|
|
427
598
|
}
|
|
428
599
|
return EMPTY_TILE_PLAN;
|
|
429
600
|
}
|
|
@@ -444,7 +615,7 @@ var TileManager = class {
|
|
|
444
615
|
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.`);
|
|
445
616
|
}
|
|
446
617
|
if (strategy.pyramid) {
|
|
447
|
-
const fitting = strategy.pyramid.filter((
|
|
618
|
+
const fitting = strategy.pyramid.filter((scale) => Math.round(scale * page.width) <= maxStagelessWidth);
|
|
448
619
|
wantScale = fitting.length ? fitting[fitting.length - 1] : strategy.pyramid[0];
|
|
449
620
|
wantWidth = Math.round(wantScale * page.width);
|
|
450
621
|
} else {
|
|
@@ -461,12 +632,12 @@ var TileManager = class {
|
|
|
461
632
|
height: page.height
|
|
462
633
|
};
|
|
463
634
|
state.lastVisible = visible;
|
|
464
|
-
const
|
|
635
|
+
const visibleCoords = tilesInRect(grid, page, visible);
|
|
465
636
|
const ring = inflateRect(visible, options.tiles.prefetchMargin, options.tiles.velocityBias ? demand.velocity : void 0);
|
|
466
|
-
const
|
|
467
|
-
const
|
|
468
|
-
this.schedule(
|
|
469
|
-
if (this.releaseCovered(
|
|
637
|
+
const visibleKeys = new Set(visibleCoords.map((coord) => coordKey(coord)));
|
|
638
|
+
const prefetchCoords = tilesInRect(grid, page, ring).filter((coord) => !visibleKeys.has(coordKey(coord)));
|
|
639
|
+
this.schedule(pageObjectNumber, state, page, grid, wantScale, wantWidth, layers, epoch, visibleCoords, prefetchCoords, visible);
|
|
640
|
+
if (this.releaseCovered(pageObjectNumber, state, page, layers)) {
|
|
470
641
|
state.version += 1;
|
|
471
642
|
state.planCache = null;
|
|
472
643
|
}
|
|
@@ -475,11 +646,11 @@ var TileManager = class {
|
|
|
475
646
|
const paint = [];
|
|
476
647
|
const fetching = [];
|
|
477
648
|
const stale = [];
|
|
478
|
-
const bleedPx = this.deps.
|
|
649
|
+
const bleedPx = this.deps.getOptions().tiles.bleedPx;
|
|
479
650
|
for (const entry of state.entries.values()) {
|
|
480
|
-
const
|
|
651
|
+
const visiblePart = intersectRects(entry.rect, visible);
|
|
481
652
|
if (entry.resolved) {
|
|
482
|
-
if (
|
|
653
|
+
if (visiblePart.width > 0 && visiblePart.height > 0) {
|
|
483
654
|
const handle = this.deps.store.peek(entry.key);
|
|
484
655
|
if (!handle) {
|
|
485
656
|
stale.push(entry.key);
|
|
@@ -497,9 +668,10 @@ var TileManager = class {
|
|
|
497
668
|
}
|
|
498
669
|
for (const key of stale) state.entries.delete(key);
|
|
499
670
|
const rank = /* @__PURE__ */ new Map();
|
|
500
|
-
|
|
501
|
-
for (const
|
|
502
|
-
|
|
671
|
+
const scales = [...new Set(paint.map((source) => source.scale))];
|
|
672
|
+
for (const scale of scales.sort((left, right) => left - right)) rank.set(scale, rank.size);
|
|
673
|
+
for (const source of paint) source.z = rank.get(source.scale);
|
|
674
|
+
paint.sort((left, right) => left.z - right.z || left.key.localeCompare(right.key));
|
|
503
675
|
const plan = {
|
|
504
676
|
engaged: true,
|
|
505
677
|
paint,
|
|
@@ -514,41 +686,46 @@ var TileManager = class {
|
|
|
514
686
|
return plan;
|
|
515
687
|
}
|
|
516
688
|
/** The layer's painted report: this key's pixels had a presentation opportunity. */
|
|
517
|
-
sourcePainted(view,
|
|
518
|
-
const state = this.pages.get(stateKey(view,
|
|
689
|
+
sourcePainted(view, pageObjectNumber, key) {
|
|
690
|
+
const state = this.pages.get(stateKey(view, pageObjectNumber));
|
|
519
691
|
const entry = state?.entries.get(key);
|
|
520
692
|
if (!state || !entry || entry.painted) return;
|
|
521
693
|
entry.painted = true;
|
|
522
|
-
const page = this.deps.getPageSize(
|
|
523
|
-
if (page) this.releaseCovered(
|
|
694
|
+
const page = this.deps.getPageSize(pageObjectNumber);
|
|
695
|
+
if (page) this.releaseCovered(pageObjectNumber, state, page, layersOfKey(key));
|
|
524
696
|
state.version += 1;
|
|
525
697
|
state.planCache = null;
|
|
526
|
-
this.deps.onAdvance(
|
|
698
|
+
this.deps.onAdvance(pageObjectNumber);
|
|
527
699
|
}
|
|
528
700
|
/**
|
|
529
701
|
* The inverse report: this key's <img> left the DOM (pan-away, plan drop),
|
|
530
|
-
* so its pixels are
|
|
702
|
+
* so its pixels are not currently compositable. Without this, a tile that
|
|
531
703
|
* unmounts and later remounts is still counted as painted while its new
|
|
532
704
|
* <img> re-decodes — and an adjacent fresh `sourcePainted` could release
|
|
533
705
|
* retained coarse coverage over a region that momentarily has no sharp
|
|
534
|
-
* pixels. Painted is a statement about the
|
|
706
|
+
* pixels. Painted is a statement about the screen, so it follows the DOM.
|
|
535
707
|
*/
|
|
536
|
-
sourceUnpainted(view,
|
|
537
|
-
const entry = this.pages.get(stateKey(view,
|
|
708
|
+
sourceUnpainted(view, pageObjectNumber, key) {
|
|
709
|
+
const entry = this.pages.get(stateKey(view, pageObjectNumber))?.entries.get(key);
|
|
538
710
|
if (entry) entry.painted = false;
|
|
539
711
|
}
|
|
540
|
-
/** A lens
|
|
541
|
-
*
|
|
542
|
-
|
|
543
|
-
|
|
712
|
+
/** A lens stopped painting tiles for the page (it left the view, or its
|
|
713
|
+
* layer paints none): stop fetching, drop bookkeeping. Resolved bytes stay
|
|
714
|
+
* in the RasterStore for a re-mount. */
|
|
715
|
+
releasePage(view, pageObjectNumber) {
|
|
716
|
+
const state = this.pages.get(stateKey(view, pageObjectNumber));
|
|
544
717
|
if (!state) return;
|
|
545
718
|
this.abortAll(state);
|
|
546
|
-
this.pages.delete(stateKey(view,
|
|
719
|
+
this.pages.delete(stateKey(view, pageObjectNumber));
|
|
720
|
+
this.pump();
|
|
547
721
|
}
|
|
548
|
-
pageState(
|
|
549
|
-
let state = this.pages.get(stateKey(view,
|
|
722
|
+
pageState(pageObjectNumber, view) {
|
|
723
|
+
let state = this.pages.get(stateKey(view, pageObjectNumber));
|
|
550
724
|
if (!state) {
|
|
551
725
|
state = {
|
|
726
|
+
view,
|
|
727
|
+
pageObjectNumber,
|
|
728
|
+
queue: null,
|
|
552
729
|
epoch: -1,
|
|
553
730
|
wantScale: null,
|
|
554
731
|
wantWidth: null,
|
|
@@ -556,22 +733,23 @@ var TileManager = class {
|
|
|
556
733
|
failedKeys: /* @__PURE__ */ new Set(),
|
|
557
734
|
version: 0,
|
|
558
735
|
planCache: null,
|
|
559
|
-
|
|
736
|
+
cancelSettle: null,
|
|
560
737
|
pendingLevel: null,
|
|
561
738
|
lastVisible: null
|
|
562
739
|
};
|
|
563
|
-
this.pages.set(stateKey(view,
|
|
740
|
+
this.pages.set(stateKey(view, pageObjectNumber), state);
|
|
564
741
|
}
|
|
565
742
|
return state;
|
|
566
743
|
}
|
|
567
|
-
schedule(
|
|
568
|
-
const
|
|
744
|
+
schedule(pageObjectNumber, state, page, grid, wantScale, wantWidth, layers, epoch, visibleCoords, prefetchCoords, visible) {
|
|
745
|
+
const options = this.deps.getOptions();
|
|
569
746
|
const firstEngage = state.wantWidth === null;
|
|
570
747
|
const levelChanged = state.wantWidth !== null && state.wantWidth !== wantWidth;
|
|
571
748
|
if (levelChanged) state.failedKeys.clear();
|
|
572
749
|
state.wantScale = wantScale;
|
|
573
750
|
state.wantWidth = wantWidth;
|
|
574
|
-
const
|
|
751
|
+
const keyOf = (coord) => this.tileKey(pageObjectNumber, wantWidth, coord, layers, epoch);
|
|
752
|
+
const wanted = new Set([...visibleCoords, ...prefetchCoords].map(keyOf));
|
|
575
753
|
for (const [key, entry] of state.entries) {
|
|
576
754
|
if (wanted.has(key)) continue;
|
|
577
755
|
if (!entry.resolved) {
|
|
@@ -580,110 +758,154 @@ var TileManager = class {
|
|
|
580
758
|
state.entries.delete(key);
|
|
581
759
|
} else if (entry.scale === wantScale) state.entries.delete(key);
|
|
582
760
|
}
|
|
583
|
-
const
|
|
584
|
-
const
|
|
761
|
+
const centerX = visible.x + visible.width / 2;
|
|
762
|
+
const centerY = visible.y + visible.height / 2;
|
|
585
763
|
const span = grid.tileSize / grid.scale;
|
|
586
|
-
const
|
|
587
|
-
const
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
});
|
|
764
|
+
const distanceToCenter = (coord) => {
|
|
765
|
+
const dx = (coord.ix + .5) * span - centerX;
|
|
766
|
+
const dy = (coord.iy + .5) * span - centerY;
|
|
767
|
+
return dx * dx + dy * dy;
|
|
768
|
+
};
|
|
769
|
+
const orderedVisible = [...visibleCoords].sort((left, right) => distanceToCenter(left) - distanceToCenter(right));
|
|
593
770
|
const kickoff = () => {
|
|
594
|
-
|
|
771
|
+
state.queue = {
|
|
772
|
+
coords: orderedVisible.every((coord) => {
|
|
773
|
+
const key = keyOf(coord);
|
|
774
|
+
return state.failedKeys.has(key) || state.entries.get(key)?.resolved === true;
|
|
775
|
+
}) ? [...orderedVisible, ...prefetchCoords] : orderedVisible,
|
|
776
|
+
ringFrom: orderedVisible.length,
|
|
777
|
+
next: 0,
|
|
778
|
+
page,
|
|
779
|
+
grid,
|
|
780
|
+
wantWidth,
|
|
781
|
+
layers,
|
|
782
|
+
epoch
|
|
783
|
+
};
|
|
784
|
+
this.pump();
|
|
595
785
|
};
|
|
596
786
|
if (levelChanged && options.tiles.settleMs > 0) {
|
|
597
|
-
this.deps.debug?.(`arm settle
|
|
787
|
+
this.deps.debug?.(`arm settle page=${pageObjectNumber} level=${wantWidth}`);
|
|
788
|
+
state.queue = null;
|
|
598
789
|
state.pendingLevel = wantWidth;
|
|
599
|
-
|
|
600
|
-
state.
|
|
601
|
-
state.
|
|
602
|
-
this.deps.debug?.(`settle fired
|
|
790
|
+
state.cancelSettle?.();
|
|
791
|
+
state.cancelSettle = this.deps.after(options.tiles.settleMs, () => {
|
|
792
|
+
state.cancelSettle = null;
|
|
793
|
+
this.deps.debug?.(`settle fired page=${pageObjectNumber} level=${wantWidth} current=${state.pendingLevel === wantWidth}`);
|
|
603
794
|
if (state.pendingLevel === wantWidth) kickoff();
|
|
604
|
-
}
|
|
795
|
+
});
|
|
605
796
|
return;
|
|
606
797
|
}
|
|
607
|
-
if (state.
|
|
608
|
-
|
|
609
|
-
state.
|
|
798
|
+
if (state.cancelSettle !== null && !levelChanged && !firstEngage) {
|
|
799
|
+
state.cancelSettle();
|
|
800
|
+
state.cancelSettle = null;
|
|
610
801
|
state.pendingLevel = null;
|
|
611
802
|
}
|
|
612
803
|
kickoff();
|
|
613
804
|
}
|
|
614
|
-
/**
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
805
|
+
/**
|
|
806
|
+
* Starts queued tiles while transit slots are free: the first on-screen
|
|
807
|
+
* tile of any view and page, in the order their queues were set, before
|
|
808
|
+
* any tile of a prefetch ring. Within one view of a page, its queue's order
|
|
809
|
+
* holds. Backpressure bounds raw rasters in transit (render + encode); each
|
|
810
|
+
* resolution hands its slot on through here, so no other pump is needed.
|
|
811
|
+
*/
|
|
812
|
+
pump() {
|
|
813
|
+
const started = /* @__PURE__ */ new Map();
|
|
814
|
+
while (this.inFlight < MAX_IN_FLIGHT) {
|
|
815
|
+
let next = null;
|
|
816
|
+
for (const state of this.pages.values()) {
|
|
817
|
+
const queued = this.nextToStart(state);
|
|
818
|
+
if (!queued || next && queued.prefetch) continue;
|
|
819
|
+
next = {
|
|
820
|
+
state,
|
|
821
|
+
coord: queued.coord
|
|
822
|
+
};
|
|
823
|
+
if (!queued.prefetch) break;
|
|
626
824
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
825
|
+
if (!next) break;
|
|
826
|
+
this.start(next.state, next.coord);
|
|
827
|
+
started.set(next.state, (started.get(next.state) ?? 0) + 1);
|
|
828
|
+
}
|
|
829
|
+
for (const [state, count] of started) this.deps.debug?.(`fetch page=${state.pageObjectNumber} level=${state.queue?.wantWidth} +${count} tiles`);
|
|
830
|
+
}
|
|
831
|
+
/** The first tile of the state's queue not started yet, nor failed at this level. */
|
|
832
|
+
nextToStart(state) {
|
|
833
|
+
const queue = state.queue;
|
|
834
|
+
if (!queue) return null;
|
|
835
|
+
for (; queue.next < queue.coords.length; queue.next += 1) {
|
|
836
|
+
const coord = queue.coords[queue.next];
|
|
837
|
+
const key = this.tileKey(state.pageObjectNumber, queue.wantWidth, coord, queue.layers, queue.epoch);
|
|
838
|
+
if (state.failedKeys.has(key) || state.entries.has(key)) continue;
|
|
839
|
+
return {
|
|
636
840
|
coord,
|
|
637
|
-
|
|
638
|
-
resolved: false,
|
|
639
|
-
painted: false,
|
|
640
|
-
charged: true,
|
|
641
|
-
abort
|
|
841
|
+
prefetch: queue.next >= queue.ringFrom
|
|
642
842
|
};
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
843
|
+
}
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
/** Starts one queued tile's fetch, in a transit slot. */
|
|
847
|
+
start(state, coord) {
|
|
848
|
+
const { page, grid, wantWidth, layers, epoch } = state.queue;
|
|
849
|
+
const pageObjectNumber = state.pageObjectNumber;
|
|
850
|
+
const key = this.tileKey(pageObjectNumber, wantWidth, coord, layers, epoch);
|
|
851
|
+
const bleedPt = this.deps.getOptions().tiles.bleedPx / grid.scale;
|
|
852
|
+
this.inFlight += 1;
|
|
853
|
+
const abort = new AbortController();
|
|
854
|
+
const logical = tilePaintRect(grid, page, coord);
|
|
855
|
+
const entry = {
|
|
856
|
+
key,
|
|
857
|
+
scale: grid.scale,
|
|
858
|
+
coord,
|
|
859
|
+
rect: logical,
|
|
860
|
+
resolved: false,
|
|
861
|
+
painted: false,
|
|
862
|
+
charged: true,
|
|
863
|
+
abort
|
|
864
|
+
};
|
|
865
|
+
state.entries.set(key, entry);
|
|
866
|
+
state.planCache = null;
|
|
867
|
+
this.deps.store.acquire(key, (signal) => this.deps.fetchTile(state.view, pageObjectNumber, bleedPt > 0 ? bleedRect(logical, bleedPt, page) : logical, grid.scale, layers, signal), abort.signal).then(() => {
|
|
868
|
+
this.releaseSlot(entry);
|
|
869
|
+
if (state.entries.get(key) === entry) {
|
|
647
870
|
entry.resolved = true;
|
|
648
871
|
state.version += 1;
|
|
649
872
|
state.planCache = null;
|
|
650
|
-
this.deps.onAdvance(
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
|
|
873
|
+
this.deps.onAdvance(pageObjectNumber);
|
|
874
|
+
}
|
|
875
|
+
this.pump();
|
|
876
|
+
}, (error) => {
|
|
877
|
+
this.releaseSlot(entry);
|
|
878
|
+
if (state.entries.get(key) === entry) {
|
|
654
879
|
state.entries.delete(key);
|
|
655
880
|
if (!abort.signal.aborted) {
|
|
656
881
|
state.failedKeys.add(key);
|
|
657
|
-
this.deps.debug?.(`tile failed ${key}: ${String(
|
|
882
|
+
this.deps.debug?.(`tile failed ${key}: ${String(error)}`);
|
|
658
883
|
state.version += 1;
|
|
659
884
|
state.planCache = null;
|
|
660
|
-
this.deps.onAdvance(
|
|
885
|
+
this.deps.onAdvance(pageObjectNumber);
|
|
661
886
|
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
return allReady;
|
|
887
|
+
}
|
|
888
|
+
this.pump();
|
|
889
|
+
});
|
|
666
890
|
}
|
|
667
891
|
/**
|
|
668
|
-
* The release rule, evaluated as a
|
|
669
|
-
* set: retained sources (any non-want level) whose
|
|
670
|
-
* covered by
|
|
892
|
+
* The release rule, evaluated as a pure function of the current painted
|
|
893
|
+
* set: retained sources (any non-want level) whose visible footprint is
|
|
894
|
+
* covered by painted want-level tiles leave the paint list. Runs on every
|
|
671
895
|
* `plan()` and on every painted report — never only "against the tile
|
|
672
|
-
* that just painted": release must not depend on paint
|
|
896
|
+
* that just painted": release must not depend on paint order (center-out
|
|
673
897
|
* scheduling means the report that completes a retained tile's coverage
|
|
674
898
|
* routinely lands far away from it), and re-evaluating from current state
|
|
675
899
|
* also self-heals any painted-flag transition the reports raced past.
|
|
676
|
-
* Coverage is index arithmetic over the
|
|
900
|
+
* Coverage is index arithmetic over the want grid — generic over whatever
|
|
677
901
|
* mix of retained scales exists (exact levels included).
|
|
678
902
|
*/
|
|
679
|
-
releaseCovered(
|
|
903
|
+
releaseCovered(pageObjectNumber, state, page, layers) {
|
|
680
904
|
const { wantScale, wantWidth } = state;
|
|
681
905
|
if (wantScale === null || wantWidth === null) return false;
|
|
682
906
|
const grid = tileGrid(page, wantScale, this.strategy().tileSize);
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
return state.entries.get(key)?.painted === true;
|
|
686
|
-
};
|
|
907
|
+
const wantKeyOf = (coord) => this.tileKey(pageObjectNumber, wantWidth, coord, layers, state.epoch);
|
|
908
|
+
const paintedAt = (coord) => state.entries.get(wantKeyOf(coord))?.painted === true;
|
|
687
909
|
let released = false;
|
|
688
910
|
for (const [key, entry] of state.entries) {
|
|
689
911
|
if (entry.scale === wantScale || !entry.resolved) continue;
|
|
@@ -691,13 +913,12 @@ var TileManager = class {
|
|
|
691
913
|
if (regionCovered(grid, page, region, paintedAt)) {
|
|
692
914
|
state.entries.delete(key);
|
|
693
915
|
released = true;
|
|
694
|
-
} else if (this.deps.debug) {
|
|
695
|
-
const missing = tilesInRect(grid, page, region).filter((
|
|
696
|
-
const detail = missing.slice(0, 3).map((
|
|
697
|
-
const
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
return `${c.ix},${c.iy}=${st}`;
|
|
916
|
+
} else if (this.deps.debug && this.deps.getOptions().debug) {
|
|
917
|
+
const missing = tilesInRect(grid, page, region).filter((coord) => !paintedAt(coord));
|
|
918
|
+
const detail = missing.slice(0, 3).map((coord) => {
|
|
919
|
+
const missingEntry = state.entries.get(wantKeyOf(coord));
|
|
920
|
+
const status = !missingEntry ? "NO-ENTRY" : !missingEntry.resolved ? "PENDING" : "RESOLVED-unpainted";
|
|
921
|
+
return `${coord.ix},${coord.iy}=${status}`;
|
|
701
922
|
});
|
|
702
923
|
this.deps.debug(`retained ${key} blocked by: ${detail.join(" ")} (${missing.length} missing)`);
|
|
703
924
|
}
|
|
@@ -705,23 +926,22 @@ var TileManager = class {
|
|
|
705
926
|
return released;
|
|
706
927
|
}
|
|
707
928
|
/** Test/diagnostic introspection: bookkeeping size for one page. */
|
|
708
|
-
stats(view,
|
|
929
|
+
stats(view, pageObjectNumber) {
|
|
709
930
|
return {
|
|
710
|
-
entries: this.pages.get(stateKey(view,
|
|
931
|
+
entries: this.pages.get(stateKey(view, pageObjectNumber))?.entries.size ?? 0,
|
|
711
932
|
inFlight: this.inFlight
|
|
712
933
|
};
|
|
713
934
|
}
|
|
714
|
-
/** Idempotent transit-slot release — the
|
|
935
|
+
/** Idempotent transit-slot release — the one place inFlight decrements. */
|
|
715
936
|
releaseSlot(entry) {
|
|
716
937
|
if (!entry.charged) return;
|
|
717
938
|
entry.charged = false;
|
|
718
939
|
this.inFlight -= 1;
|
|
719
940
|
}
|
|
720
941
|
abortAll(state) {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
}
|
|
942
|
+
state.queue = null;
|
|
943
|
+
state.cancelSettle?.();
|
|
944
|
+
state.cancelSettle = null;
|
|
725
945
|
for (const entry of state.entries.values()) if (!entry.resolved) {
|
|
726
946
|
entry.abort?.abort();
|
|
727
947
|
this.releaseSlot(entry);
|
|
@@ -729,117 +949,306 @@ var TileManager = class {
|
|
|
729
949
|
}
|
|
730
950
|
/** Level identity is the integer device width across the page — integer
|
|
731
951
|
* and stable for pyramid rungs and exact levels alike. The key also
|
|
732
|
-
* carries everything that determines the fetched
|
|
952
|
+
* carries everything that determines the fetched bitmap for a coord
|
|
733
953
|
* (tile size, bleed, encode format): a raster's identity must include
|
|
734
954
|
* its geometry, or a cached handle from one configuration could be
|
|
735
955
|
* stretched into another's rect. Format genuinely varies at runtime —
|
|
736
956
|
* the cloud policy arrives async after open and can change the resolved
|
|
737
957
|
* format under a live store. */
|
|
738
|
-
tileKey(
|
|
739
|
-
const { tiles } = this.deps.
|
|
958
|
+
tileKey(pageObjectNumber, levelWidth, coord, layers, epoch) {
|
|
959
|
+
const { tiles } = this.deps.getOptions();
|
|
740
960
|
const format = this.strategy().format;
|
|
741
961
|
const geometry = `g${tiles.size}.${tiles.bleedPx}${format ? `.${format}` : ""}`;
|
|
742
|
-
return `t:${
|
|
962
|
+
return `t:${pageObjectNumber}|w${levelWidth}|${coord.ix},${coord.iy}|${layersKeyOf(layers)}|e${epoch}|${geometry}`;
|
|
743
963
|
}
|
|
744
964
|
};
|
|
745
|
-
const coordKey = (
|
|
746
|
-
const rectKey = (
|
|
747
|
-
|
|
965
|
+
const coordKey = (coord) => `${coord.ix},${coord.iy}`;
|
|
966
|
+
const rectKey = (rect) => `${Math.round(rect.x)},${Math.round(rect.y)},${Math.round(rect.width)},${Math.round(rect.height)}`;
|
|
967
|
+
/** What a tile key's picture draws, as {@link layersKeyOf} wrote it into the key. */
|
|
968
|
+
const layersOfKey = (key) => {
|
|
969
|
+
const drawn = /\|a([01])f([01])\|/.exec(key);
|
|
970
|
+
return {
|
|
971
|
+
includeAnnotations: drawn?.[1] === "1",
|
|
972
|
+
includeFormFields: drawn?.[2] === "1"
|
|
973
|
+
};
|
|
974
|
+
};
|
|
748
975
|
//#endregion
|
|
749
|
-
//#region src/
|
|
976
|
+
//#region src/controller.ts
|
|
977
|
+
const RENDER_SCOPE = "doc.render";
|
|
978
|
+
const FORMS_SCOPE = "doc.forms.read";
|
|
979
|
+
const ANNOTATIONS_SCOPE = "doc.annotate.read";
|
|
980
|
+
/** Every answer `getLayerRights()` gives, one object each, so the same rights are the same object. */
|
|
981
|
+
const LAYER_RIGHTS = [false, true].map((annotations) => [false, true].map((formFields) => Object.freeze({
|
|
982
|
+
annotations,
|
|
983
|
+
formFields
|
|
984
|
+
})));
|
|
985
|
+
/** An abort reason the engine task accepts (signals carry `unknown`). */
|
|
986
|
+
const reasonOf = (signal) => typeof signal.reason === "string" ? signal.reason : void 0;
|
|
987
|
+
const DEFAULT_BATCH_CONCURRENCY = 4;
|
|
750
988
|
/**
|
|
751
|
-
* The render
|
|
752
|
-
*
|
|
753
|
-
* serves). The engine never snaps
|
|
754
|
-
*
|
|
755
|
-
* collapsing same-key asks in the raster store, exposing stable source
|
|
756
|
-
*
|
|
989
|
+
* The render controller: the one place strategy (config: what the viewer
|
|
990
|
+
* spends) composes with policy (the engine fact: what the deployment
|
|
991
|
+
* serves). The engine never snaps and layers never see either; everything
|
|
992
|
+
* between happens here: conforming demand to the resolved render points,
|
|
993
|
+
* collapsing same-key asks in the raster store, exposing stable source keys,
|
|
994
|
+
* and turning a view's demand into a retention-safe tile plan.
|
|
995
|
+
*
|
|
996
|
+
* Two doors for rasters: `renderPage` (public, exact size) and
|
|
997
|
+
* `renderSource` (host, conformed). Both share the raster store, so a
|
|
998
|
+
* developer's thumbnail and the rail's base plane never render twice.
|
|
999
|
+
*
|
|
1000
|
+
* Every render it asks for is the picture of a page itself, so it asks the
|
|
1001
|
+
* engine with `priority: 'high'`, naming the view it's for when it knows it:
|
|
1002
|
+
* the engine runs it before anything else about the same place (annotation
|
|
1003
|
+
* appearances, form fields, text geometry, links).
|
|
1004
|
+
*
|
|
1005
|
+
* Two doors for staleness: confirmed document events (see `connect`) and
|
|
1006
|
+
* the `invalidate` verb. Both go through `publishInvalidation`, the one
|
|
1007
|
+
* place the ledger bumps and `onInvalidated` fires.
|
|
1008
|
+
*
|
|
1009
|
+
* The tile manager and the per-view plans live outside state (they hold
|
|
1010
|
+
* live handles and abort controllers); a re-plan wakes readers with
|
|
1011
|
+
* `ctx.notify()`.
|
|
757
1012
|
*/
|
|
758
|
-
function
|
|
759
|
-
const
|
|
760
|
-
const
|
|
1013
|
+
function createRenderController(ctx) {
|
|
1014
|
+
const settings = ctx.settings();
|
|
1015
|
+
const resolvedOptions = memo(() => [settings.get()], (current) => resolveRenderOptions(current));
|
|
761
1016
|
const store = new RasterStore();
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1017
|
+
const appearances = new AppearanceStore();
|
|
1018
|
+
const recentAppearances = new RecentPictures();
|
|
1019
|
+
const invalidated = ctx.events.source();
|
|
1020
|
+
const renderCompleted = ctx.events.source();
|
|
1021
|
+
const renderFailed = ctx.events.source();
|
|
1022
|
+
const canRender = () => ctx.allows(RENDER_SCOPE);
|
|
1023
|
+
const rights = () => LAYER_RIGHTS[ctx.allows("doc.annotate.read") ? 1 : 0][ctx.allows("doc.forms.read") ? 1 : 0];
|
|
1024
|
+
const layersOf = (options) => {
|
|
1025
|
+
const may = rights();
|
|
1026
|
+
return resolvePageLayers({
|
|
1027
|
+
...options.includeAnnotations !== void 0 && (may.annotations || !options.includeAnnotations) ? { includeAnnotations: options.includeAnnotations } : {},
|
|
1028
|
+
...options.includeFormFields !== void 0 && (may.formFields || !options.includeFormFields) ? { includeFormFields: options.includeFormFields } : {}
|
|
1029
|
+
}, may);
|
|
1030
|
+
};
|
|
1031
|
+
const pictureLayers = (options, operation) => {
|
|
1032
|
+
if (options.includeAnnotations === true) ctx.assertAllowed("doc.annotate.read", operation);
|
|
1033
|
+
if (options.includeFormFields === true) ctx.assertAllowed(FORMS_SCOPE, operation);
|
|
1034
|
+
return layersOf(options);
|
|
774
1035
|
};
|
|
775
1036
|
let warnedFormat = false;
|
|
776
|
-
const warnDroppedFormat = (
|
|
777
|
-
if (warnedFormat ||
|
|
1037
|
+
const warnDroppedFormat = (strategy, requested) => {
|
|
1038
|
+
if (warnedFormat || requested === void 0 || strategy.format === requested) return;
|
|
778
1039
|
warnedFormat = true;
|
|
779
|
-
console.warn(`[render] format '${
|
|
1040
|
+
console.warn(`[render] format '${requested}' is not in the deployment's formats — using '${strategy.format}'.`);
|
|
780
1041
|
};
|
|
781
1042
|
let warnedBudget = false;
|
|
782
1043
|
const warnPastBudgetNoTiles = (demandWidth, supplied) => {
|
|
783
|
-
if (warnedBudget ||
|
|
1044
|
+
if (warnedBudget || resolvedOptions().tiles.enabled || demandWidth <= supplied * 2) return;
|
|
784
1045
|
warnedBudget = true;
|
|
785
1046
|
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.`);
|
|
786
1047
|
};
|
|
787
|
-
const
|
|
788
|
-
const
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
return
|
|
1048
|
+
const renderPolicy = () => ctx.document()?.renderPolicy ?? CONTINUOUS_RENDER_POLICY;
|
|
1049
|
+
const currentStrategy = memo(() => [renderPolicy(), resolvedOptions()], (policy, options) => {
|
|
1050
|
+
const strategy = resolveStrategy(policy, options);
|
|
1051
|
+
warnDroppedFormat(strategy, options.format);
|
|
1052
|
+
return strategy;
|
|
1053
|
+
});
|
|
1054
|
+
const pageWidthOf = (page) => {
|
|
1055
|
+
const info = ctx.getPage(page);
|
|
1056
|
+
if (!info) ctx.assertPageRef(page);
|
|
1057
|
+
return info.size.width;
|
|
792
1058
|
};
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
const demandWidth = scale * pageWidthOf(
|
|
796
|
-
const width = baseAskWidth(
|
|
1059
|
+
const allPageObjectNumbers = () => (ctx.document()?.pages ?? []).map((info) => info.ref.objectNumber);
|
|
1060
|
+
const conformViewport = (page, scale) => {
|
|
1061
|
+
const demandWidth = scale * pageWidthOf(page);
|
|
1062
|
+
const width = baseAskWidth(currentStrategy(), demandWidth);
|
|
797
1063
|
warnPastBudgetNoTiles(demandWidth, width);
|
|
798
1064
|
return {
|
|
799
1065
|
kind: "width",
|
|
800
1066
|
width
|
|
801
1067
|
};
|
|
802
1068
|
};
|
|
803
|
-
const epochOf = (
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
1069
|
+
const epochOf = (pageObjectNumber, layers) => renderEpochOf(ctx.state.get(), pageObjectNumber, layers);
|
|
1070
|
+
const rasterKey = (pageObjectNumber, width, layers, format, quality) => `${pageObjectNumber}|w${width}|${layersKeyOf(layers)}|e${epochOf(pageObjectNumber, layers)}${format ? `|f${format}` : ""}${quality !== void 0 ? `|q${quality}` : ""}`;
|
|
1071
|
+
function acquireRaster(page, key, request, signal) {
|
|
1072
|
+
return store.acquire(key, (storeSignal) => {
|
|
1073
|
+
const task = ctx.doc.with({
|
|
1074
|
+
priority: "high",
|
|
1075
|
+
...request.view !== void 0 ? { view: request.view } : {}
|
|
1076
|
+
}).page(page).render.image({
|
|
1077
|
+
viewport: request.viewport,
|
|
1078
|
+
...request.layers,
|
|
1079
|
+
...request.format !== void 0 ? { format: request.format } : {},
|
|
1080
|
+
...request.quality !== void 0 ? { quality: request.quality } : {}
|
|
1081
|
+
});
|
|
1082
|
+
if (storeSignal.aborted) task.abort(reasonOf(storeSignal));
|
|
1083
|
+
else storeSignal.addEventListener("abort", () => task.abort(reasonOf(storeSignal)), { once: true });
|
|
1084
|
+
return task;
|
|
1085
|
+
}, signal).then((image) => {
|
|
1086
|
+
renderCompleted.emit({
|
|
1087
|
+
page,
|
|
1088
|
+
key
|
|
1089
|
+
});
|
|
1090
|
+
return image;
|
|
1091
|
+
}, (error) => {
|
|
1092
|
+
const mapped = toPluginError("render", error);
|
|
1093
|
+
if (!isPluginError(mapped, "operation-cancelled") && !isPluginError(mapped, "instance-closed")) renderFailed.emit({
|
|
1094
|
+
page,
|
|
1095
|
+
key,
|
|
1096
|
+
error: toPluginErrorInfo(mapped)
|
|
1097
|
+
});
|
|
1098
|
+
throw mapped;
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
/** The host door: conformed to the render points. Async so refusals reject. */
|
|
1102
|
+
async function renderSource(page, { scale, view, signal, ...options }) {
|
|
1103
|
+
ctx.assertAllowed(RENDER_SCOPE, "render.renderSource");
|
|
1104
|
+
const layers = pictureLayers(options, "render.renderSource");
|
|
1105
|
+
const viewport = conformViewport(page, scale);
|
|
1106
|
+
const strategy = currentStrategy();
|
|
1107
|
+
return acquireRaster(page, rasterKey(page.objectNumber, viewport.kind === "width" ? viewport.width : 0, layers, strategy.format, void 0), {
|
|
1108
|
+
viewport,
|
|
1109
|
+
layers,
|
|
1110
|
+
format: strategy.format,
|
|
1111
|
+
quality: strategy.quality,
|
|
1112
|
+
view
|
|
1113
|
+
}, signal);
|
|
1114
|
+
}
|
|
1115
|
+
/** The public door: the requested size, exactly. Async so refusals reject. */
|
|
1116
|
+
async function renderPage(pageArgument, options = {}) {
|
|
1117
|
+
ctx.assertAllowed(RENDER_SCOPE, "render.renderPage");
|
|
1118
|
+
const { ref: page, size } = ctx.pageOf(pageArgument);
|
|
1119
|
+
const width = Math.max(1, Math.round(options.width ?? (options.scale ?? 1) * size.width));
|
|
1120
|
+
const layers = pictureLayers(options, "render.renderPage");
|
|
1121
|
+
const strategy = currentStrategy();
|
|
1122
|
+
const format = options.format ?? strategy.format;
|
|
1123
|
+
const quality = options.quality ?? strategy.quality;
|
|
1124
|
+
const key = rasterKey(page.objectNumber, width, layers, format, options.quality);
|
|
1125
|
+
return ctx.cancellable(options.signal, acquireRaster(page, key, {
|
|
1126
|
+
viewport: {
|
|
1127
|
+
kind: "width",
|
|
1128
|
+
width
|
|
1129
|
+
},
|
|
1130
|
+
layers,
|
|
1131
|
+
format,
|
|
1132
|
+
quality,
|
|
1133
|
+
view: void 0
|
|
1134
|
+
}, options.signal));
|
|
1135
|
+
}
|
|
1136
|
+
async function renderPages(pages, options = {}) {
|
|
1137
|
+
ctx.assertAllowed(RENDER_SCOPE, "render.renderPages");
|
|
1138
|
+
const concurrency = Math.max(1, options.concurrency ?? DEFAULT_BATCH_CONCURRENCY);
|
|
1139
|
+
const applied = [];
|
|
1140
|
+
const failed = [];
|
|
1141
|
+
const queue = pages.map((page, index) => ({
|
|
1142
|
+
page,
|
|
1143
|
+
index
|
|
1144
|
+
}));
|
|
1145
|
+
const worker = async () => {
|
|
1146
|
+
for (let next = queue.shift(); next; next = queue.shift()) {
|
|
1147
|
+
const { page, index } = next;
|
|
1148
|
+
try {
|
|
1149
|
+
const image = await renderPage(page, options);
|
|
1150
|
+
applied.push({
|
|
1151
|
+
index,
|
|
1152
|
+
render: {
|
|
1153
|
+
page: ctx.pageOf(page).ref,
|
|
1154
|
+
image
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
} catch (error) {
|
|
1158
|
+
failed.push({
|
|
1159
|
+
index,
|
|
1160
|
+
ref: page,
|
|
1161
|
+
error: toPluginErrorInfo(toPluginError("render", error))
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
const batch = Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, worker));
|
|
1167
|
+
await ctx.cancellable(options.signal, batch);
|
|
1168
|
+
const byInput = (left, right) => left.index - right.index;
|
|
1169
|
+
return {
|
|
1170
|
+
applied: applied.sort(byInput).map(({ render }) => render),
|
|
1171
|
+
skipped: [],
|
|
1172
|
+
failed: failed.sort(byInput).map(({ ref, error }) => ({
|
|
1173
|
+
ref,
|
|
1174
|
+
error
|
|
1175
|
+
}))
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
async function renderAppearances(page, { family, scale, items, signal }) {
|
|
1179
|
+
ctx.assertAllowed(family === "widgets" ? FORMS_SCOPE : ANNOTATIONS_SCOPE, "render.renderAppearances");
|
|
1180
|
+
const pageObjectNumber = ctx.pageOf(page).ref.objectNumber;
|
|
1181
|
+
const versions = new Map(items.map((item) => [annotationKey(item.ref), item.version]));
|
|
1182
|
+
const asked = [...versions].map(([item, version]) => `${item}@${version}`).sort();
|
|
1183
|
+
const group = `${family}|${pageObjectNumber}`;
|
|
1184
|
+
const pictures = appearances.acquire(group, `${group}|s${scale}|${asked.join(",")}`, async (fetchSignal) => {
|
|
1185
|
+
const pageHandle = ctx.doc.page(toPageRef(pageObjectNumber));
|
|
1186
|
+
const options = {
|
|
1187
|
+
viewport: {
|
|
1188
|
+
kind: "scale",
|
|
1189
|
+
scale
|
|
1190
|
+
},
|
|
1191
|
+
modes: ["normal"]
|
|
1192
|
+
};
|
|
1193
|
+
const task = family === "widgets" ? pageHandle.forms.renderAppearances(options) : pageHandle.annotations.renderAppearances(options);
|
|
1194
|
+
if (fetchSignal.aborted) task.abort(reasonOf(fetchSignal));
|
|
1195
|
+
else fetchSignal.addEventListener("abort", () => task.abort(reasonOf(fetchSignal)), { once: true });
|
|
1196
|
+
const drawn = (await task).appearances.flatMap((picture) => {
|
|
1197
|
+
const version = versions.get(annotationKey(picture.ref));
|
|
1198
|
+
return version === void 0 ? [] : [{
|
|
1199
|
+
...picture,
|
|
1200
|
+
version
|
|
1201
|
+
}];
|
|
1202
|
+
});
|
|
1203
|
+
recentAppearances.remember(family, drawn, (picture) => annotationKey(picture.ref));
|
|
1204
|
+
return drawn;
|
|
1205
|
+
}, signal);
|
|
1206
|
+
return ctx.cancellable(signal, pictures.catch((error) => {
|
|
1207
|
+
throw toPluginError("render", error);
|
|
1208
|
+
}));
|
|
1209
|
+
}
|
|
1210
|
+
function publishInvalidation(pageObjectNumbers, scope, origin) {
|
|
1211
|
+
if (pageObjectNumbers.length === 0) return;
|
|
1212
|
+
ctx.state.update(invalidatePages, pageObjectNumbers, scope);
|
|
1213
|
+
for (const pageObjectNumber of pageObjectNumbers) replanPage(pageObjectNumber);
|
|
1214
|
+
invalidated.emit({
|
|
1215
|
+
pages: pageObjectNumbers.map((pageObjectNumber) => toPageRef(pageObjectNumber)),
|
|
1216
|
+
scope,
|
|
1217
|
+
origin
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
function invalidate({ pages, scope = "content" } = {}) {
|
|
1221
|
+
const named = pages?.flatMap((page) => ctx.getPage(page)?.ref.objectNumber ?? []);
|
|
1222
|
+
publishInvalidation(named ?? allPageObjectNumbers(), scope, null);
|
|
1223
|
+
}
|
|
813
1224
|
const pendingAdvance = /* @__PURE__ */ new Set();
|
|
814
1225
|
let advanceScheduled = false;
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
pendingAdvance.add(pon);
|
|
1226
|
+
const wake = (pageObjectNumber) => {
|
|
1227
|
+
pendingAdvance.add(pageObjectNumber);
|
|
818
1228
|
if (advanceScheduled) return;
|
|
819
1229
|
advanceScheduled = true;
|
|
820
|
-
|
|
1230
|
+
ctx.clock.nextFrame(() => {
|
|
821
1231
|
advanceScheduled = false;
|
|
822
|
-
const
|
|
1232
|
+
const advanced = [...pendingAdvance];
|
|
823
1233
|
pendingAdvance.clear();
|
|
824
|
-
for (const
|
|
825
|
-
type: "PAINT_ADVANCED",
|
|
826
|
-
pon: p
|
|
827
|
-
});
|
|
1234
|
+
for (const advancedPage of advanced) replanPage(advancedPage);
|
|
828
1235
|
});
|
|
829
1236
|
};
|
|
830
|
-
const viewTiles = /* @__PURE__ */ new Map();
|
|
831
1237
|
const tiles = new TileManager({
|
|
832
1238
|
store,
|
|
833
|
-
|
|
834
|
-
getPolicy:
|
|
835
|
-
getPageSize: (
|
|
1239
|
+
getOptions: resolvedOptions,
|
|
1240
|
+
getPolicy: renderPolicy,
|
|
1241
|
+
getPageSize: (pageObjectNumber) => ctx.getPage(toPageRef(pageObjectNumber))?.size,
|
|
836
1242
|
getEpoch: epochOf,
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
const
|
|
842
|
-
const task = doc.
|
|
1243
|
+
after: ctx.clock.after,
|
|
1244
|
+
fetchTile: async (view, pageObjectNumber, rect, scale, layers, signal) => {
|
|
1245
|
+
ctx.assertAllowed(RENDER_SCOPE, "render.tile");
|
|
1246
|
+
const page = toPageRef(pageObjectNumber);
|
|
1247
|
+
const strategy = currentStrategy();
|
|
1248
|
+
const task = ctx.doc.with({
|
|
1249
|
+
priority: "high",
|
|
1250
|
+
view
|
|
1251
|
+
}).page(page).render.image({
|
|
843
1252
|
target: {
|
|
844
1253
|
kind: "rect",
|
|
845
1254
|
rect
|
|
@@ -848,183 +1257,148 @@ function createRenderCapability(ctx, options = {}) {
|
|
|
848
1257
|
kind: "scale",
|
|
849
1258
|
scale
|
|
850
1259
|
},
|
|
851
|
-
|
|
852
|
-
...
|
|
853
|
-
...
|
|
1260
|
+
...layers,
|
|
1261
|
+
...strategy.format !== void 0 ? { format: strategy.format } : {},
|
|
1262
|
+
...strategy.quality !== void 0 ? { quality: strategy.quality } : {}
|
|
854
1263
|
});
|
|
855
|
-
if (signal.aborted) task.abort(signal
|
|
856
|
-
else signal.addEventListener("abort", () => task.abort(signal
|
|
1264
|
+
if (signal.aborted) task.abort(reasonOf(signal));
|
|
1265
|
+
else signal.addEventListener("abort", () => task.abort(reasonOf(signal)), { once: true });
|
|
857
1266
|
return task;
|
|
858
1267
|
},
|
|
859
1268
|
onAdvance: wake,
|
|
860
|
-
|
|
1269
|
+
debug: (message) => {
|
|
1270
|
+
if (resolvedOptions().debug) console.debug(`[render] ${message}`);
|
|
1271
|
+
}
|
|
861
1272
|
});
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
includeAnnotations: annotations,
|
|
876
|
-
...s.format !== void 0 ? { format: s.format } : {},
|
|
877
|
-
...s.quality !== void 0 ? { quality: s.quality } : {}
|
|
878
|
-
});
|
|
879
|
-
if (storeSignal.aborted) task.abort(storeSignal.reason);
|
|
880
|
-
else storeSignal.addEventListener("abort", () => task.abort(storeSignal.reason), { once: true });
|
|
881
|
-
return task;
|
|
882
|
-
}, signal);
|
|
883
|
-
},
|
|
884
|
-
renderSourceKey(pon, { scale, includeAnnotations }) {
|
|
885
|
-
const annotations = includeAnnotations ?? true;
|
|
886
|
-
return baseKey(pon, conformViewport(pon, scale), annotations);
|
|
887
|
-
},
|
|
888
|
-
conformViewport,
|
|
889
|
-
paintSettings() {
|
|
890
|
-
return {
|
|
891
|
-
fadeMs: resolved.tiles.fadeMs,
|
|
892
|
-
tiles: resolved.tiles.enabled
|
|
893
|
-
};
|
|
894
|
-
},
|
|
895
|
-
renderPolicy: policy,
|
|
896
|
-
tilesFor(view) {
|
|
897
|
-
let handle = viewTiles.get(view);
|
|
898
|
-
if (!handle) {
|
|
899
|
-
handle = {
|
|
900
|
-
plan: (pon, demand, opts) => tiles.plan(view, pon, demand, opts?.includeAnnotations ?? true),
|
|
901
|
-
painted: (pon, key) => tiles.sourcePainted(view, pon, key),
|
|
902
|
-
unpainted: (pon, key) => tiles.sourceUnpainted(view, pon, key),
|
|
903
|
-
release: (pon) => tiles.releasePage(view, pon)
|
|
904
|
-
};
|
|
905
|
-
viewTiles.set(view, handle);
|
|
1273
|
+
/** One handle per view id, reference-counted; demands and plans per page. */
|
|
1274
|
+
const views = /* @__PURE__ */ new Map();
|
|
1275
|
+
/** Re-plan a page for every view that wants it (schedules the next want
|
|
1276
|
+
* set), and wake readers once when any plan changed. */
|
|
1277
|
+
function replanPage(pageObjectNumber) {
|
|
1278
|
+
let changed = false;
|
|
1279
|
+
for (const [viewId, view] of views) {
|
|
1280
|
+
const entry = view.pages.get(pageObjectNumber);
|
|
1281
|
+
if (!entry) continue;
|
|
1282
|
+
const plan = tiles.plan(viewId, pageObjectNumber, entry.demand, entry.layers);
|
|
1283
|
+
if (plan !== entry.plan) {
|
|
1284
|
+
entry.plan = plan;
|
|
1285
|
+
changed = true;
|
|
906
1286
|
}
|
|
907
|
-
return handle;
|
|
908
|
-
},
|
|
909
|
-
renderEpoch(pon, includeAnnotations = true) {
|
|
910
|
-
return epochOf(pon, includeAnnotations);
|
|
911
|
-
},
|
|
912
|
-
invalidate({ pons, scope = "content" } = {}) {
|
|
913
|
-
const target = pons ?? (ctx.document()?.pages ?? []).map((p) => p.pageObjectNumber);
|
|
914
|
-
if (target.length) ctx.dispatch({
|
|
915
|
-
type: "INVALIDATE",
|
|
916
|
-
scope,
|
|
917
|
-
pons: target
|
|
918
|
-
});
|
|
919
1287
|
}
|
|
920
|
-
|
|
921
|
-
}
|
|
922
|
-
//#endregion
|
|
923
|
-
//#region src/effects.ts
|
|
924
|
-
/**
|
|
925
|
-
* Which pages' ANNOTATED raster a confirmed mutation repaints — the whole
|
|
926
|
-
* event→scope map. Everything the engine emits today is appearance-scoped;
|
|
927
|
-
* when it grows content-mutation events (redaction.applied, content.edited),
|
|
928
|
-
* they get a case here dispatching scope 'content'. Over-invalidation is
|
|
929
|
-
* acceptable (a z-order move that changes nothing repaints one thumb);
|
|
930
|
-
* under-invalidation is the bug.
|
|
931
|
-
*/
|
|
932
|
-
function annotatedPons(event, allPons) {
|
|
933
|
-
switch (event.type) {
|
|
934
|
-
case "annotation.created":
|
|
935
|
-
case "annotation.updated":
|
|
936
|
-
case "annotation.deleted":
|
|
937
|
-
case "annotation.moved": return [event.pageObjectNumber];
|
|
938
|
-
case "form.valueChanged":
|
|
939
|
-
case "form.effectsApplied": return event.changedWidgets.map((w) => w.pageObjectNumber);
|
|
940
|
-
case "form.fieldDeleted": return event.removedWidgets.map((w) => w.pageObjectNumber);
|
|
941
|
-
case "form.fieldCreated":
|
|
942
|
-
case "form.fieldUpdated":
|
|
943
|
-
case "form.widgetAttached":
|
|
944
|
-
case "form.widgetDetached": return event.field.widgets.map((w) => w.pageObjectNumber);
|
|
945
|
-
case "form.imported":
|
|
946
|
-
case "form.repaired": return allPons();
|
|
947
|
-
default: return [];
|
|
1288
|
+
if (changed) ctx.notify();
|
|
948
1289
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
const unsubscribe = doc.events.subscribe((event) => {
|
|
955
|
-
if (event.type === "redaction.applied") {
|
|
956
|
-
const pons = event.results.filter((r) => r.status === "applied").map((r) => r.pageObjectNumber);
|
|
957
|
-
if (pons.length) ctx.dispatch({
|
|
958
|
-
type: "INVALIDATE",
|
|
959
|
-
scope: "content",
|
|
960
|
-
pons
|
|
961
|
-
});
|
|
962
|
-
return;
|
|
1290
|
+
function createViewDemand(viewId) {
|
|
1291
|
+
const existing = views.get(viewId);
|
|
1292
|
+
if (existing) {
|
|
1293
|
+
existing.refs += 1;
|
|
1294
|
+
return existing.handle;
|
|
963
1295
|
}
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1296
|
+
const pages = /* @__PURE__ */ new Map();
|
|
1297
|
+
const handle = {
|
|
1298
|
+
setDemand: (page, demand, options) => {
|
|
1299
|
+
const pageObjectNumber = page.objectNumber;
|
|
1300
|
+
const layers = layersOf(options ?? {});
|
|
1301
|
+
const previous = pages.get(pageObjectNumber);
|
|
1302
|
+
const plan = tiles.plan(viewId, pageObjectNumber, demand, layers);
|
|
1303
|
+
pages.set(pageObjectNumber, {
|
|
1304
|
+
demand,
|
|
1305
|
+
layers,
|
|
1306
|
+
plan
|
|
1307
|
+
});
|
|
1308
|
+
if (plan !== previous?.plan) ctx.notify();
|
|
1309
|
+
},
|
|
1310
|
+
getPlan: (page) => pages.get(page.objectNumber)?.plan ?? EMPTY_TILE_PLAN,
|
|
1311
|
+
markPainted: (page, key) => tiles.sourcePainted(viewId, page.objectNumber, key),
|
|
1312
|
+
markUnpainted: (page, key) => tiles.sourceUnpainted(viewId, page.objectNumber, key),
|
|
1313
|
+
release: (page) => {
|
|
1314
|
+
pages.delete(page.objectNumber);
|
|
1315
|
+
tiles.releasePage(viewId, page.objectNumber);
|
|
1316
|
+
},
|
|
1317
|
+
dispose: () => {
|
|
1318
|
+
const view = views.get(viewId);
|
|
1319
|
+
if (!view || view.refs === 0) return;
|
|
1320
|
+
view.refs -= 1;
|
|
1321
|
+
if (view.refs > 0) return;
|
|
1322
|
+
for (const pageObjectNumber of [...view.pages.keys()]) tiles.releasePage(viewId, pageObjectNumber);
|
|
1323
|
+
view.pages.clear();
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
views.set(viewId, {
|
|
1327
|
+
handle,
|
|
1328
|
+
refs: 1,
|
|
1329
|
+
pages
|
|
969
1330
|
});
|
|
1331
|
+
return handle;
|
|
1332
|
+
}
|
|
1333
|
+
const paintSettings = memo(() => [resolvedOptions().tiles.enabled], (enabled) => Object.freeze({ tiles: enabled }));
|
|
1334
|
+
ctx.listen(settings.api.onSettingsChanged, () => {
|
|
1335
|
+
const shown = /* @__PURE__ */ new Set();
|
|
1336
|
+
for (const view of views.values()) for (const page of view.pages.keys()) shown.add(page);
|
|
1337
|
+
for (const page of shown) replanPage(page);
|
|
970
1338
|
});
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
})
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1339
|
+
return {
|
|
1340
|
+
api: {
|
|
1341
|
+
...settings.api,
|
|
1342
|
+
canRender,
|
|
1343
|
+
renderPage,
|
|
1344
|
+
renderThumbnail: (page, { maxWidth, ...options }) => renderPage(page, {
|
|
1345
|
+
width: maxWidth,
|
|
1346
|
+
...options
|
|
1347
|
+
}),
|
|
1348
|
+
renderPages,
|
|
1349
|
+
getRenderPolicy: renderPolicy,
|
|
1350
|
+
getRenderEpoch: (page, layers = {}) => {
|
|
1351
|
+
const info = ctx.getPage(page);
|
|
1352
|
+
return info ? epochOf(info.ref.objectNumber, layersOf(layers)) : 0;
|
|
1353
|
+
},
|
|
1354
|
+
invalidate,
|
|
1355
|
+
onInvalidated: invalidated.on,
|
|
1356
|
+
renderSource,
|
|
1357
|
+
getSourceKey: (page, { scale, view: _view, ...options }) => {
|
|
1358
|
+
const viewport = conformViewport(page, scale);
|
|
1359
|
+
return rasterKey(page.objectNumber, viewport.kind === "width" ? viewport.width : 0, layersOf(options), currentStrategy().format, void 0);
|
|
1360
|
+
},
|
|
1361
|
+
conformViewport,
|
|
1362
|
+
getPaintSettings: paintSettings,
|
|
1363
|
+
getLayerRights: rights,
|
|
1364
|
+
createViewDemand,
|
|
1365
|
+
renderAppearances,
|
|
1366
|
+
keptAppearances: (family, item) => recentAppearances.of(family, annotationKey(item)),
|
|
1367
|
+
getAppearanceScale: (renderScale) => snapAppearanceScale(renderPolicy(), renderScale),
|
|
1368
|
+
onRenderCompleted: renderCompleted.on,
|
|
1369
|
+
onRenderFailed: renderFailed.on
|
|
1370
|
+
},
|
|
1371
|
+
connect() {
|
|
1372
|
+
ctx.listen(ctx.doc.events, (event) => {
|
|
1373
|
+
const change = pixelChangeOf(event, allPageObjectNumbers);
|
|
1374
|
+
if (!change) return;
|
|
1375
|
+
publishInvalidation(change.pages, change.scope, "origin" in event ? event.origin : null);
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1003
1378
|
};
|
|
1004
|
-
}
|
|
1379
|
+
}
|
|
1005
1380
|
//#endregion
|
|
1006
1381
|
//#region src/render.plugin.ts
|
|
1007
1382
|
/**
|
|
1008
|
-
* Document-scoped. The
|
|
1009
|
-
*
|
|
1010
|
-
* policy
|
|
1011
|
-
* same-
|
|
1012
|
-
* demand into a retention-safe paint plan over the
|
|
1013
|
-
* per-page
|
|
1014
|
-
* built-in map and the `invalidate` verb), the policy latch, and the tile
|
|
1015
|
-
* wake-up counter. Layers key on `renderSourceKey`/`tilePlan` and refetch
|
|
1016
|
-
* exactly when those change.
|
|
1383
|
+
* Document-scoped page rasters. The only render-policy consumer in the
|
|
1384
|
+
* client stack: the kernel materializes the engine's advertised render
|
|
1385
|
+
* policy on the document, the controller conforms desired scales to it and
|
|
1386
|
+
* collapses same-key asks in its raster store, and the tile manager turns
|
|
1387
|
+
* host-supplied demand into a retention-safe paint plan over the same store.
|
|
1388
|
+
* State is the per-page raster-version ledger.
|
|
1017
1389
|
*/
|
|
1018
|
-
const renderPlugin = (
|
|
1390
|
+
const renderPlugin = (config) => definePlugin({
|
|
1019
1391
|
id: "render",
|
|
1020
1392
|
scope: "document",
|
|
1021
|
-
token: RenderToken,
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1393
|
+
token: RenderToken$1,
|
|
1394
|
+
state: initialRenderState,
|
|
1395
|
+
settings: {
|
|
1396
|
+
defaults: RENDER_DEFAULTS,
|
|
1397
|
+
registered: config
|
|
1398
|
+
},
|
|
1399
|
+
create: createRenderController
|
|
1026
1400
|
});
|
|
1027
1401
|
//#endregion
|
|
1028
|
-
export { RenderToken, renderPlugin };
|
|
1402
|
+
export { RENDER_DEFAULTS, RenderToken, renderPlugin, samePageViewDemand };
|
|
1029
1403
|
|
|
1030
1404
|
//# sourceMappingURL=index.js.map
|