@embedpdf/react 3.0.0-next.13 → 3.0.0-next.15
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/AnnotationList-CkijRHet-HEFNUIZe.d.cts +877 -0
- package/dist/AnnotationList-CkijRHet-HEFNUIZe.d.ts +877 -0
- package/dist/actions.cjs +25 -5
- package/dist/actions.cjs.map +1 -1
- package/dist/actions.d.cts +18 -5
- package/dist/actions.d.ts +18 -5
- package/dist/actions.js +22 -6
- package/dist/actions.js.map +1 -1
- package/dist/anchored-DeX95xSd.d.ts +85 -0
- package/dist/anchored-DzN3JywS.d.cts +85 -0
- package/dist/anchored.cjs +71 -28
- package/dist/anchored.cjs.map +1 -1
- package/dist/anchored.d.cts +2 -45
- package/dist/anchored.d.ts +2 -45
- package/dist/anchored.js +56 -19
- package/dist/anchored.js.map +1 -1
- package/dist/annotation-B48UT2ln.d.ts +302 -0
- package/dist/annotation-C1ttM8Wj.js +708 -0
- package/dist/annotation-C1ttM8Wj.js.map +1 -0
- package/dist/annotation-C9dVwo9b.d.cts +302 -0
- package/dist/annotation-Dex5EBda.cjs +830 -0
- package/dist/annotation-Dex5EBda.cjs.map +1 -0
- package/dist/annotation.cjs +20 -783
- package/dist/annotation.d.cts +2 -2
- package/dist/annotation.d.ts +2 -2
- package/dist/annotation.js +2 -768
- package/dist/commands.cjs +68 -28
- package/dist/commands.cjs.map +1 -1
- package/dist/commands.d.cts +37 -10
- package/dist/commands.d.ts +37 -10
- package/dist/commands.js +68 -31
- package/dist/commands.js.map +1 -1
- package/dist/form-55izw0jS.js +511 -0
- package/dist/form-55izw0jS.js.map +1 -0
- package/dist/form-BNqQfGGH.cjs +548 -0
- package/dist/form-BNqQfGGH.cjs.map +1 -0
- package/dist/form.cjs +5 -5
- package/dist/form.d.cts +32 -25
- package/dist/form.d.ts +32 -25
- package/dist/form.js +2 -3
- package/dist/history.cjs +38 -0
- package/dist/history.cjs.map +1 -0
- package/dist/history.d.cts +27 -0
- package/dist/history.d.ts +27 -0
- package/dist/history.js +27 -0
- package/dist/history.js.map +1 -0
- package/dist/i18n.cjs +26 -30
- package/dist/i18n.cjs.map +1 -1
- package/dist/i18n.d.cts +24 -21
- package/dist/i18n.d.ts +24 -21
- package/dist/i18n.js +24 -31
- package/dist/i18n.js.map +1 -1
- package/dist/index.cjs +213 -57
- package/dist/index.d.cts +32 -31
- package/dist/index.d.ts +32 -31
- package/dist/index.js +33 -29
- package/dist/inert-CmBEbKCn.cjs +19 -0
- package/dist/inert-CmBEbKCn.cjs.map +1 -0
- package/dist/inert-D8fkennb.js +14 -0
- package/dist/inert-D8fkennb.js.map +1 -0
- package/dist/interaction.cjs +48 -102
- package/dist/interaction.cjs.map +1 -1
- package/dist/interaction.d.cts +23 -32
- package/dist/interaction.d.ts +23 -32
- package/dist/interaction.js +33 -102
- package/dist/interaction.js.map +1 -1
- package/dist/link.cjs +65 -104
- package/dist/link.cjs.map +1 -1
- package/dist/link.d.cts +21 -16
- package/dist/link.d.ts +22 -17
- package/dist/link.js +67 -107
- package/dist/link.js.map +1 -1
- package/dist/measurement.cjs +56 -0
- package/dist/measurement.cjs.map +1 -0
- package/dist/measurement.d.cts +38 -0
- package/dist/measurement.d.ts +38 -0
- package/dist/measurement.js +42 -0
- package/dist/measurement.js.map +1 -0
- package/dist/metadata.cjs +20 -7
- package/dist/metadata.cjs.map +1 -1
- package/dist/metadata.d.cts +22 -7
- package/dist/metadata.d.ts +22 -7
- package/dist/metadata.js +20 -10
- package/dist/metadata.js.map +1 -1
- package/dist/page-edit.cjs +7 -3
- package/dist/page-edit.cjs.map +1 -1
- package/dist/page-edit.d.cts +6 -2
- package/dist/page-edit.d.ts +6 -2
- package/dist/page-edit.js +7 -4
- package/dist/page-edit.js.map +1 -1
- package/dist/page-layers-Bkaaz8Zf.js +26 -0
- package/dist/page-layers-Bkaaz8Zf.js.map +1 -0
- package/dist/page-layers-BloZDoOV.cjs +37 -0
- package/dist/page-layers-BloZDoOV.cjs.map +1 -0
- package/dist/page-view.cjs +89 -82
- package/dist/page-view.cjs.map +1 -1
- package/dist/page-view.d.cts +14 -7
- package/dist/page-view.d.ts +18 -11
- package/dist/page-view.js +86 -81
- package/dist/page-view.js.map +1 -1
- package/dist/redaction.cjs +30 -15
- package/dist/redaction.cjs.map +1 -1
- package/dist/redaction.d.cts +27 -9
- package/dist/redaction.d.ts +27 -9
- package/dist/redaction.js +29 -18
- package/dist/redaction.js.map +1 -1
- package/dist/render.cjs +71 -46
- package/dist/render.cjs.map +1 -1
- package/dist/render.d.cts +17 -5
- package/dist/render.d.ts +17 -5
- package/dist/render.js +70 -49
- package/dist/render.js.map +1 -1
- package/dist/runtime-B6xcPsEb.js +398 -0
- package/dist/runtime-B6xcPsEb.js.map +1 -0
- package/dist/runtime-DBEBlpQV.d.cts +237 -0
- package/dist/runtime-V3MVMb91.d.ts +237 -0
- package/dist/runtime-fzpGv78v.cjs +555 -0
- package/dist/runtime-fzpGv78v.cjs.map +1 -0
- package/dist/runtime.cjs +45 -324
- package/dist/runtime.d.cts +2 -2
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +2 -306
- package/dist/search.cjs +81 -50
- package/dist/search.cjs.map +1 -1
- package/dist/search.d.cts +36 -24
- package/dist/search.d.ts +36 -24
- package/dist/search.js +81 -53
- package/dist/search.js.map +1 -1
- package/dist/selection.cjs +105 -108
- package/dist/selection.cjs.map +1 -1
- package/dist/selection.d.cts +54 -26
- package/dist/selection.d.ts +56 -28
- package/dist/selection.js +103 -110
- package/dist/selection.js.map +1 -1
- package/dist/shell.cjs +36 -21
- package/dist/shell.cjs.map +1 -1
- package/dist/shell.d.cts +32 -17
- package/dist/shell.d.ts +32 -17
- package/dist/shell.js +36 -24
- package/dist/shell.js.map +1 -1
- package/dist/signature.cjs +29 -48
- package/dist/signature.cjs.map +1 -1
- package/dist/signature.d.cts +26 -26
- package/dist/signature.d.ts +26 -26
- package/dist/signature.js +30 -48
- package/dist/signature.js.map +1 -1
- package/dist/stage-BIG6aeVd.cjs +369 -0
- package/dist/stage-BIG6aeVd.cjs.map +1 -0
- package/dist/stage-ByFPD-Fv.d.cts +117 -0
- package/dist/stage-DaboaheR.d.ts +117 -0
- package/dist/stage-DoVekb83.js +302 -0
- package/dist/stage-DoVekb83.js.map +1 -0
- package/dist/stage-scope-CQgCuf1w.cjs +46 -0
- package/dist/stage-scope-CQgCuf1w.cjs.map +1 -0
- package/dist/stage-scope-D7ZeNduT.js +23 -0
- package/dist/stage-scope-D7ZeNduT.js.map +1 -0
- package/dist/stage.cjs +12 -235
- package/dist/stage.d.cts +2 -116
- package/dist/stage.d.ts +2 -116
- package/dist/stage.js +3 -228
- package/dist/stamp.cjs +41 -35
- package/dist/stamp.cjs.map +1 -1
- package/dist/stamp.d.cts +25 -18
- package/dist/stamp.d.ts +25 -18
- package/dist/stamp.js +40 -37
- package/dist/stamp.js.map +1 -1
- package/dist/state-0K1I5cM4.d.cts +29 -0
- package/dist/state-0K1I5cM4.d.ts +29 -0
- package/dist/state-CbgxhwLA.js +62 -0
- package/dist/state-CbgxhwLA.js.map +1 -0
- package/dist/state-CsfZy_Eq.cjs +73 -0
- package/dist/state-CsfZy_Eq.cjs.map +1 -0
- package/dist/svg-shapes-Dm1yTR1b.js +40 -0
- package/dist/svg-shapes-Dm1yTR1b.js.map +1 -0
- package/dist/svg-shapes-zo0GNkc9.cjs +53 -0
- package/dist/svg-shapes-zo0GNkc9.cjs.map +1 -0
- package/dist/toolbar.cjs +297 -295
- package/dist/toolbar.cjs.map +1 -1
- package/dist/toolbar.d.cts +58 -68
- package/dist/toolbar.d.ts +67 -77
- package/dist/toolbar.js +271 -293
- package/dist/toolbar.js.map +1 -1
- package/dist/view-manager.cjs +34 -0
- package/dist/view-manager.cjs.map +1 -0
- package/dist/view-manager.d.cts +21 -0
- package/dist/view-manager.d.ts +21 -0
- package/dist/view-manager.js +23 -0
- package/dist/view-manager.js.map +1 -0
- package/package.json +41 -38
- package/dist/AnnotationListSnapshot-D3mFut1e-C_GMpK25.d.cts +0 -546
- package/dist/AnnotationListSnapshot-D3mFut1e-C_GMpK25.d.ts +0 -546
- package/dist/annotation-BCED_p9H.d.cts +0 -160
- package/dist/annotation-anchors-CetwM1g7.js +0 -15
- package/dist/annotation-anchors-CetwM1g7.js.map +0 -1
- package/dist/annotation-anchors-DuYRBJLl.cjs +0 -26
- package/dist/annotation-anchors-DuYRBJLl.cjs.map +0 -1
- package/dist/annotation-hooks-BB5ZhPpf.cjs +0 -16
- package/dist/annotation-hooks-BB5ZhPpf.cjs.map +0 -1
- package/dist/annotation-hooks-Df8EmzDO.js +0 -11
- package/dist/annotation-hooks-Df8EmzDO.js.map +0 -1
- package/dist/annotation-k38KDlIZ.d.ts +0 -160
- package/dist/annotation-menu.cjs +0 -64
- package/dist/annotation-menu.cjs.map +0 -1
- package/dist/annotation-menu.d.cts +0 -33
- package/dist/annotation-menu.d.ts +0 -33
- package/dist/annotation-menu.js +0 -48
- package/dist/annotation-menu.js.map +0 -1
- package/dist/annotation.cjs.map +0 -1
- package/dist/annotation.js.map +0 -1
- package/dist/form-BVumS7up.cjs +0 -926
- package/dist/form-BVumS7up.cjs.map +0 -1
- package/dist/form-CK3mp99m.js +0 -895
- package/dist/form-CK3mp99m.js.map +0 -1
- package/dist/rolldown-runtime-BBjsoOtd.js +0 -27
- package/dist/runtime-DE76hlnl.d.ts +0 -216
- package/dist/runtime-umHA_xHt.d.cts +0 -216
- package/dist/runtime.cjs.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/scrollbar.cjs +0 -241
- package/dist/scrollbar.cjs.map +0 -1
- package/dist/scrollbar.d.cts +0 -42
- package/dist/scrollbar.d.ts +0 -42
- package/dist/scrollbar.js +0 -213
- package/dist/scrollbar.js.map +0 -1
- package/dist/stage.cjs.map +0 -1
- package/dist/stage.js.map +0 -1
- package/dist/views.cjs +0 -35
- package/dist/views.cjs.map +0 -1
- package/dist/views.d.cts +0 -21
- package/dist/views.d.ts +0 -21
- package/dist/views.js +0 -27
- package/dist/views.js.map +0 -1
package/dist/render.cjs
CHANGED
|
@@ -1,32 +1,54 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_rolldown_runtime = require("./rolldown-runtime-DDXJien1.cjs");
|
|
3
|
-
const require_runtime = require("./runtime.cjs");
|
|
3
|
+
const require_runtime = require("./runtime-fzpGv78v.cjs");
|
|
4
|
+
const require_state = require("./state-CsfZy_Eq.cjs");
|
|
5
|
+
const require_page_layers = require("./page-layers-BloZDoOV.cjs");
|
|
4
6
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
7
|
+
let _embedpdf_web = require("@embedpdf/web");
|
|
5
8
|
let react = require("react");
|
|
6
9
|
react = require_rolldown_runtime.__toESM(react, 1);
|
|
7
|
-
let _embedpdf_web = require("@embedpdf/web");
|
|
8
10
|
let _embedpdf_plugin_render = require("@embedpdf/plugin-render");
|
|
11
|
+
let _embedpdf_plugin_render_contract_host = require("@embedpdf/plugin-render/contract/host");
|
|
9
12
|
//#region src/render.tsx
|
|
13
|
+
/** The render capability (renderPage / renderThumbnail / invalidation / settings) for app code. */
|
|
14
|
+
function useRender() {
|
|
15
|
+
return require_runtime.useCapability(_embedpdf_plugin_render.RenderToken);
|
|
16
|
+
}
|
|
17
|
+
/** The render settings (`fullPage`, `tiles`, `format`, …), with or without a document. Takes a selector. */
|
|
18
|
+
const useRenderSettings = require_state.settingsHook(_embedpdf_plugin_render.RenderToken);
|
|
19
|
+
/** Subscribe to one render event for the mounted lifetime: `useRenderEvent((render) => render.onInvalidated, handler)`. */
|
|
20
|
+
function useRenderEvent(select, handler) {
|
|
21
|
+
require_runtime.useCapabilityEvent(_embedpdf_plugin_render.RenderToken, select, handler);
|
|
22
|
+
}
|
|
10
23
|
let warnedTileSize = false;
|
|
11
|
-
function RenderLayer({ annotations
|
|
24
|
+
function RenderLayer({ annotations, formFields, tiles = true } = {}) {
|
|
12
25
|
const page = require_runtime.usePage();
|
|
13
|
-
const render = require_runtime.useCapability(
|
|
14
|
-
const settings = render.
|
|
26
|
+
const render = require_runtime.useCapability(_embedpdf_plugin_render_contract_host.RenderToken);
|
|
27
|
+
const settings = require_runtime.useSelector(_embedpdf_plugin_render_contract_host.RenderToken, (render) => render.getPaintSettings());
|
|
15
28
|
const ref = (0, react.useRef)(null);
|
|
16
|
-
const
|
|
29
|
+
const parts = {
|
|
30
|
+
annotations,
|
|
31
|
+
formFields
|
|
32
|
+
};
|
|
33
|
+
const painted = require_page_layers.usePaintedParts(page.ref);
|
|
34
|
+
const layers = (0, _embedpdf_web.pictureLayerOptionsOf)(painted, parts, require_runtime.useSelector(_embedpdf_plugin_render_contract_host.RenderToken, (render) => render.getLayerRights()));
|
|
35
|
+
if ((0, _embedpdf_web.partsDrawnTwice)(painted, parts).length > 0) require_runtime.devWarn("render-layer-draws-what-a-layer-paints", "<RenderLayer annotations> or <RenderLayer formFields> draws what an <AnnotationLayer> or a <FormLayer> on the page paints too, so it shows twice. Leave the prop out: the picture leaves out what a layer paints.");
|
|
36
|
+
const sourceKey = require_runtime.useSelector(_embedpdf_plugin_render_contract_host.RenderToken, (render) => render.getSourceKey(page.ref, {
|
|
17
37
|
scale: page.transform.renderScale,
|
|
18
|
-
|
|
38
|
+
...layers
|
|
19
39
|
}));
|
|
20
40
|
(0, react.useEffect)(() => {
|
|
41
|
+
if ((0, _embedpdf_web.pageLayersOf)(page.ref).painted() !== painted) return;
|
|
21
42
|
const controller = new AbortController();
|
|
22
43
|
let revoke;
|
|
23
44
|
(async () => {
|
|
24
45
|
try {
|
|
25
|
-
const obj = await (await render.
|
|
46
|
+
const obj = await (await render.renderSource(page.ref, {
|
|
26
47
|
scale: page.transform.renderScale,
|
|
27
|
-
|
|
48
|
+
...layers,
|
|
49
|
+
view: page.view,
|
|
28
50
|
signal: controller.signal
|
|
29
|
-
})).objectUrl(controller.signal);
|
|
51
|
+
})).objectUrl().abortWith(controller.signal);
|
|
30
52
|
if (controller.signal.aborted) {
|
|
31
53
|
obj.revoke();
|
|
32
54
|
return;
|
|
@@ -41,8 +63,9 @@ function RenderLayer({ annotations = true, tiles = true } = {}) {
|
|
|
41
63
|
};
|
|
42
64
|
}, [
|
|
43
65
|
render,
|
|
44
|
-
page.
|
|
45
|
-
sourceKey
|
|
66
|
+
page.ref,
|
|
67
|
+
sourceKey,
|
|
68
|
+
painted
|
|
46
69
|
]);
|
|
47
70
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
48
71
|
ref,
|
|
@@ -55,10 +78,7 @@ function RenderLayer({ annotations = true, tiles = true } = {}) {
|
|
|
55
78
|
height: "100%",
|
|
56
79
|
pointerEvents: "none"
|
|
57
80
|
}
|
|
58
|
-
}), tiles && settings.tiles ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TilePlane, {
|
|
59
|
-
annotations,
|
|
60
|
-
fadeMs: settings.fadeMs
|
|
61
|
-
}) : null] });
|
|
81
|
+
}), tiles && settings.tiles ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TilePlane, { parts }) : null] });
|
|
62
82
|
}
|
|
63
83
|
/**
|
|
64
84
|
* The sharp plane above the base — the plugin's retention-safe paint plan
|
|
@@ -66,52 +86,55 @@ function RenderLayer({ annotations = true, tiles = true } = {}) {
|
|
|
66
86
|
* opportunity (and its inverse on unmount), so retained coarser generations
|
|
67
87
|
* release only when their replacement is truly compositable.
|
|
68
88
|
*
|
|
69
|
-
* Tiles are placed in
|
|
89
|
+
* Tiles are placed in view (CSS px) space directly — never in page points
|
|
70
90
|
* under a scaled container. Blink quantizes layout lengths to 1/64 CSS px
|
|
71
|
-
*
|
|
91
|
+
* before transforms apply, so a pt-space rect under a ×25 zoom transform
|
|
72
92
|
* turns that harmless 1/64 into ±0.4px+ of per-tile misplacement — visible
|
|
73
93
|
* seams and per-zoom-step letter shifts that grow with depth. In view
|
|
74
94
|
* space the same quantization is a fixed ~1/64 CSS px at every zoom.
|
|
75
|
-
* (v2 placed tiles in screen px for exactly this reason.)
|
|
76
95
|
*/
|
|
77
|
-
function TilePlane({
|
|
96
|
+
function TilePlane({ parts }) {
|
|
78
97
|
const page = require_runtime.usePage();
|
|
79
|
-
const render = require_runtime.useCapability(
|
|
98
|
+
const render = require_runtime.useCapability(_embedpdf_plugin_render_contract_host.RenderToken);
|
|
80
99
|
const demand = page.getViewDemand?.() ?? { desiredDeviceWidth: page.transform.deviceWidth };
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
(0, react.
|
|
100
|
+
const view = (0, react.useMemo)(() => render.createViewDemand(page.view), [render, page.view]);
|
|
101
|
+
(0, react.useEffect)(() => () => view.dispose(), [view]);
|
|
102
|
+
(0, react.useLayoutEffect)(() => {
|
|
103
|
+
const layers = (0, _embedpdf_web.pictureLayerOptionsOf)((0, _embedpdf_web.pageLayersOf)(page.ref).painted(), parts, render.getLayerRights());
|
|
104
|
+
view.setDemand(page.ref, demand, layers);
|
|
105
|
+
});
|
|
106
|
+
const plan = require_runtime.useSelector(_embedpdf_plugin_render_contract_host.RenderToken, () => view.getPlan(page.ref));
|
|
107
|
+
(0, react.useEffect)(() => () => view.release(page.ref), [view, page.ref]);
|
|
84
108
|
if (plan.paint.length === 0) return null;
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
109
|
+
const transform = page.transform;
|
|
110
|
+
const viewScale = transform.viewScale;
|
|
111
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
88
112
|
style: {
|
|
89
113
|
position: "absolute",
|
|
90
114
|
left: 0,
|
|
91
115
|
top: 0,
|
|
92
|
-
width:
|
|
93
|
-
height:
|
|
116
|
+
width: transform.contentWidth,
|
|
117
|
+
height: transform.contentHeight,
|
|
94
118
|
pointerEvents: "none",
|
|
95
119
|
isolation: "isolate"
|
|
96
120
|
},
|
|
97
|
-
children:
|
|
121
|
+
children: plan.paint.map((source) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TileImg, {
|
|
98
122
|
source,
|
|
99
123
|
view: {
|
|
100
|
-
x: source.rect.x *
|
|
101
|
-
y: source.rect.y *
|
|
102
|
-
width: source.rect.width *
|
|
103
|
-
height: source.rect.height *
|
|
124
|
+
x: source.rect.x * viewScale,
|
|
125
|
+
y: source.rect.y * viewScale,
|
|
126
|
+
width: source.rect.width * viewScale,
|
|
127
|
+
height: source.rect.height * viewScale
|
|
104
128
|
},
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}, source.key))]
|
|
129
|
+
onPainted: () => view.markPainted(page.ref, source.key),
|
|
130
|
+
onUnpainted: () => view.markUnpainted(page.ref, source.key)
|
|
131
|
+
}, source.key))
|
|
109
132
|
});
|
|
110
133
|
}
|
|
111
134
|
/** One tile: geometry plus the shared browser image binding. The binding
|
|
112
135
|
* hides the incomplete image, reports painted after its first presentation
|
|
113
136
|
* opportunity, and inverts that report when the element leaves the DOM. */
|
|
114
|
-
function TileImg({ source, view,
|
|
137
|
+
function TileImg({ source, view, onPainted, onUnpainted }) {
|
|
115
138
|
const ref = (0, react.useRef)(null);
|
|
116
139
|
(0, react.useLayoutEffect)(() => {
|
|
117
140
|
if (!ref.current) return;
|
|
@@ -124,13 +147,13 @@ function TileImg({ source, view, fadeMs, onPainted, onUnpainted }) {
|
|
|
124
147
|
ref,
|
|
125
148
|
alt: "",
|
|
126
149
|
draggable: false,
|
|
127
|
-
onLoad: (
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
150
|
+
onLoad: (event) => {
|
|
151
|
+
const naturalWidth = event.currentTarget.naturalWidth;
|
|
152
|
+
if (naturalWidth > 0 && !warnedTileSize) {
|
|
130
153
|
const expected = Math.round(source.rect.width * source.scale);
|
|
131
|
-
if (Math.abs(
|
|
154
|
+
if (Math.abs(naturalWidth - expected) > 1) {
|
|
132
155
|
warnedTileSize = true;
|
|
133
|
-
console.warn(`[render] tile bitmap ${
|
|
156
|
+
console.warn(`[render] tile bitmap ${naturalWidth}px wide does not match its rect (expected ~${expected}px) — stale raster identity? key=${source.key}`);
|
|
134
157
|
}
|
|
135
158
|
}
|
|
136
159
|
},
|
|
@@ -140,13 +163,15 @@ function TileImg({ source, view, fadeMs, onPainted, onUnpainted }) {
|
|
|
140
163
|
top: view.y,
|
|
141
164
|
width: view.width,
|
|
142
165
|
height: view.height,
|
|
143
|
-
zIndex: source.z
|
|
144
|
-
...fadeMs > 0 ? { animation: `epdf-tile-in ${fadeMs}ms ease-out` } : null
|
|
166
|
+
zIndex: source.z
|
|
145
167
|
}
|
|
146
168
|
});
|
|
147
169
|
}
|
|
148
170
|
//#endregion
|
|
149
171
|
exports.RenderLayer = RenderLayer;
|
|
172
|
+
exports.useRender = useRender;
|
|
173
|
+
exports.useRenderEvent = useRenderEvent;
|
|
174
|
+
exports.useRenderSettings = useRenderSettings;
|
|
150
175
|
Object.keys(_embedpdf_plugin_render).forEach(function(k) {
|
|
151
176
|
if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
152
177
|
enumerable: true,
|
package/dist/render.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.cjs","names":["usePage","useCapability","RenderToken","useSelector"],"sources":["../src/render.tsx"],"sourcesContent":["/**\n * RenderLayer — the React view of @embedpdf/plugin-render. The page raster\n * is ONE concern with two planes:\n *\n * - the BASE plane: a whole-page <img> at the plugin's resolved render\n * points — the exact settled demand capped at the pixel budget on a\n * continuous (local) engine, the advertised ladder on a lattice (cloud)\n * deployment. Always present; the instant backdrop.\n * - the TILE plane: the plugin's retention-safe paint plan above it,\n * engaging by demand arithmetic when the view wants more pixels than\n * the base may spend. A thumbnail-sized demand engages nothing — the\n * arithmetic is the configuration.\n *\n * The layer is a DUMB painter, deliberately thin: plain <img>s bound through\n * the shared browser adapter, with painted/unpainted reports around their\n * visible lifetime. Every decision — strategy ∧ policy conformance, want\n * sets, level settling, retention, release — is plugin-render's; anything\n * mid-gesture simply CSS-scales until the plugin hands down new pixels.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-render';\nimport * as React from 'react';\nimport { useEffect, useLayoutEffect, useRef } from 'react';\nimport { RenderToken } from '@embedpdf/plugin-render';\nimport type { PageViewDemand, TilePaintSource } from '@embedpdf/plugin-render';\nimport { bindPaintedImage } from '@embedpdf/web';\nimport { useCapability, usePage, useSelector } from './runtime';\n\nexport interface RenderLayerProps {\n /**\n * Bake annotations into the page bitmap (default true). Pass false when an\n * <AnnotationLayer> owns annotation rendering, so they aren't drawn twice.\n */\n annotations?: boolean;\n /**\n * Mount the tile plane (default true). Whether it SPENDS anything is\n * demand arithmetic — leave it on; pass false only for a lens that must\n * never tile even under deep zoom.\n */\n tiles?: boolean;\n}\n\nlet warnedTileSize = false;\n\nexport function RenderLayer({ annotations = true, tiles = true }: RenderLayerProps = {}) {\n const page = usePage();\n const render = useCapability(RenderToken);\n const settings = render.paintSettings();\n const ref = useRef<HTMLImageElement>(null);\n\n // ONE dependency: the raster's canonical identity — conformed width +\n // annotations flag + epoch. Under a lattice it moves only at rung\n // crossings; under exact mode it tracks the demand and is CONSTANT above\n // the budget — so the deep-zoom backdrop never refetches, and the sub-\n // budget range refetches per settled demand exactly like v2 did.\n const sourceKey = useSelector(RenderToken, (c) =>\n c.renderSourceKey(page.pon, {\n scale: page.transform.renderScale,\n includeAnnotations: annotations,\n }),\n );\n\n useEffect(() => {\n const controller = new AbortController();\n let revoke: (() => void) | undefined;\n (async () => {\n try {\n // The capability conforms this to the resolved render points and\n // collapses same-key asks in its raster store. A stale-closure scale\n // is harmless by construction: any scale mapping to this key\n // produces this key's canonical request.\n const image = await render.renderPage(page.pon, {\n scale: page.transform.renderScale,\n includeAnnotations: annotations,\n signal: controller.signal,\n });\n const obj = await image.objectUrl(controller.signal);\n if (controller.signal.aborted) {\n obj.revoke();\n return;\n }\n revoke = obj.revoke;\n // Imperative src on a STABLE element: the browser keeps the old\n // bitmap until the new one decodes, and nothing ever re-requests the\n // old URL — so revoke-on-cleanup is safe here by construction.\n if (ref.current) ref.current.src = obj.url;\n } catch {\n /* aborted (camera moved / unmounted) or render failed */\n }\n })();\n return () => {\n controller.abort();\n revoke?.();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- sourceKey IS the\n // render identity; scale/annotations/epoch are folded into it upstream.\n }, [render, page.pon, sourceKey]);\n\n return (\n <>\n <img\n ref={ref}\n alt=\"\"\n draggable={false}\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none',\n }}\n />\n {tiles && settings.tiles ? (\n <TilePlane annotations={annotations} fadeMs={settings.fadeMs} />\n ) : null}\n </>\n );\n}\n\n/**\n * The sharp plane above the base — the plugin's retention-safe paint plan\n * as keyed <img>s. Each tile reports painted after its first presentation\n * opportunity (and its inverse on unmount), so retained coarser generations\n * release only when their replacement is truly compositable.\n *\n * Tiles are placed in VIEW (CSS px) space directly — never in page points\n * under a scaled container. Blink quantizes layout lengths to 1/64 CSS px\n * BEFORE transforms apply, so a pt-space rect under a ×25 zoom transform\n * turns that harmless 1/64 into ±0.4px+ of per-tile misplacement — visible\n * seams and per-zoom-step letter shifts that grow with depth. In view\n * space the same quantization is a fixed ~1/64 CSS px at every zoom.\n * (v2 placed tiles in screen px for exactly this reason.)\n */\nfunction TilePlane({ annotations, fadeMs }: { annotations: boolean; fadeMs: number }) {\n const page = usePage();\n const render = useCapability(RenderToken);\n // Demand: the host's live camera view (Stage) or whole-page (PageView).\n // Level settling lives in the plugin (its settle gate), pan stays live.\n const demand: PageViewDemand = page.getViewDemand?.() ?? {\n desiredDeviceWidth: page.transform.deviceWidth,\n };\n // THIS view's tile surface: state is per view × page, so a thumbnail\n // rail's never-engaging demand cannot disturb the main lens's plan (shared\n // state made the main view lose its tiles whenever a rail opened). The\n // handle is reference-stable per view — a clean dependency.\n const tiles = render.tilesFor(page.view);\n const plan = useSelector(RenderToken, () =>\n tiles.plan(page.pon, demand, { includeAnnotations: annotations }),\n );\n // View unmounted its plane: stop in-flight fetches; resolved bytes stay cached.\n useEffect(() => () => tiles.release(page.pon), [tiles, page.pon]);\n if (plan.paint.length === 0) return null;\n const t = page.transform;\n const s = t.viewScale;\n return (\n <div\n style={{\n position: 'absolute',\n left: 0,\n top: 0,\n width: t.contentWidth,\n height: t.contentHeight,\n pointerEvents: 'none',\n // STACKING CONTRACT: tile <img>s carry zIndex ranks (coarse under\n // fine) that exist only while generations are mixed — mid-zoom. This\n // container MUST be a stacking context so those ranks stay internal;\n // without it they join the Stage's context and paint above every\n // z-auto sibling (annotations, page chrome, menus) exactly while\n // zooming. The old scaled container created one implicitly via its\n // transform; `isolation` is the explicit, no-side-effect way.\n isolation: 'isolate',\n }}\n >\n {fadeMs > 0 ? (\n <style>{`@keyframes epdf-tile-in { from { opacity: 0 } to { opacity: 1 } }`}</style>\n ) : null}\n {plan.paint.map((source) => (\n <TileImg\n key={source.key}\n source={source}\n view={{\n x: source.rect.x * s,\n y: source.rect.y * s,\n width: source.rect.width * s,\n height: source.rect.height * s,\n }}\n fadeMs={fadeMs}\n onPainted={() => tiles.painted(page.pon, source.key)}\n onUnpainted={() => tiles.unpainted(page.pon, source.key)}\n />\n ))}\n </div>\n );\n}\n\n/** One tile: geometry plus the shared browser image binding. The binding\n * hides the incomplete image, reports painted after its first presentation\n * opportunity, and inverts that report when the element leaves the DOM. */\nfunction TileImg({\n source,\n view,\n fadeMs,\n onPainted,\n onUnpainted,\n}: {\n source: TilePaintSource;\n /** Placement rect in VIEW (CSS px) space. */\n view: { x: number; y: number; width: number; height: number };\n fadeMs: number;\n onPainted: () => void;\n onUnpainted: () => void;\n}) {\n const ref = useRef<HTMLImageElement>(null);\n useLayoutEffect(() => {\n if (!ref.current) return;\n return bindPaintedImage(ref.current, source.handle, { onPainted, onUnpainted });\n // eslint-disable-next-line react-hooks/exhaustive-deps -- the handle is\n // the bitmap's identity; the report callbacks carry stable values.\n }, [source.handle]);\n return (\n <img\n ref={ref}\n alt=\"\"\n draggable={false}\n onLoad={(e) => {\n // A raster whose intrinsic size disagrees with its rect would be\n // silently stretched into place — the stale-handle bug class.\n const w = e.currentTarget.naturalWidth;\n if (w > 0 && !warnedTileSize) {\n const expected = Math.round(source.rect.width * source.scale);\n if (Math.abs(w - expected) > 1) {\n warnedTileSize = true;\n console.warn(\n `[render] tile bitmap ${w}px wide does not match its rect ` +\n `(expected ~${expected}px) — stale raster identity? key=${source.key}`,\n );\n }\n }\n }}\n style={{\n position: 'absolute',\n left: view.x,\n top: view.y,\n width: view.width,\n height: view.height,\n zIndex: source.z,\n ...(fadeMs > 0 ? { animation: `epdf-tile-in ${fadeMs}ms ease-out` } : null),\n }}\n />\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"render.cjs","names":["useCapability","RenderPublicToken","settingsHook","usePage","RenderToken","useSelector","usePaintedParts"],"sources":["../src/render.tsx"],"sourcesContent":["/**\n * RenderLayer — the React view of @embedpdf/plugin-render. The page raster\n * is one concern with two planes:\n *\n * - the base plane: a whole-page <img> at the plugin's resolved render\n * points — the exact settled demand capped at the pixel budget on a\n * continuous (local) engine, the advertised ladder on a lattice (cloud)\n * deployment. Always present; the instant backdrop.\n * - the tile plane: the plugin's retention-safe paint plan above it,\n * engaging by demand arithmetic when the view wants more pixels than\n * the base may spend. A thumbnail-sized demand engages nothing — the\n * arithmetic is the configuration.\n *\n * The layer is a dumb painter, deliberately thin: plain <img>s bound through\n * the shared browser adapter, with painted/unpainted reports around their\n * visible lifetime. Every decision — strategy ∧ policy conformance, want\n * sets, level settling, retention, release — is plugin-render's; anything\n * mid-gesture simply CSS-scales until the plugin hands down new pixels.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-render';\nimport * as React from 'react';\nimport { useEffect, useLayoutEffect, useMemo, useRef } from 'react';\n// The layer is a host of the render plugin: it paints conformed sources and\n// drives a view's tile demand. The host lens is the same runtime token.\nimport { RenderToken } from '@embedpdf/plugin-render/contract/host';\nimport type { PageViewDemand, TilePaintSource } from '@embedpdf/plugin-render/contract/host';\nimport { RenderToken as RenderPublicToken } from '@embedpdf/plugin-render';\nimport type { RenderCapability } from '@embedpdf/plugin-render';\nimport type { EventHook } from '@embedpdf/core';\nimport {\n bindPaintedImage,\n pageLayersOf,\n partsDrawnTwice,\n pictureLayerOptionsOf,\n type PicturePartProps,\n} from '@embedpdf/web';\nimport { devWarn } from './dev';\nimport { usePaintedParts } from './page-layers';\nimport { useCapability, useCapabilityEvent, usePage, useSelector } from './runtime';\nimport { settingsHook } from './state';\n\n/** The render capability (renderPage / renderThumbnail / invalidation / settings) for app code. */\nexport function useRender(): RenderCapability {\n return useCapability(RenderPublicToken);\n}\n\n/** The render settings (`fullPage`, `tiles`, `format`, …), with or without a document. Takes a selector. */\nexport const useRenderSettings = settingsHook(RenderPublicToken);\n\n/** Subscribe to one render event for the mounted lifetime: `useRenderEvent((render) => render.onInvalidated, handler)`. */\nexport function useRenderEvent<T>(\n select: (render: RenderCapability) => EventHook<T>,\n handler: (event: T) => void,\n): void {\n useCapabilityEvent(RenderPublicToken, select, handler);\n}\n\nexport interface RenderLayerProps {\n /**\n * Draw the annotations into the page's picture (`true`) or leave them out\n * (`false`). Left unset, the picture leaves them to an `<AnnotationLayer>`\n * on the page, and otherwise draws them when the user may read them.\n */\n annotations?: boolean;\n /** The same for the form fields, which a `<FormLayer>` paints. */\n formFields?: boolean;\n /**\n * Mount the tile plane (default true). Whether it spends anything is\n * demand arithmetic — leave it on; pass false only for a lens that must\n * never tile even under deep zoom.\n */\n tiles?: boolean;\n}\n\nlet warnedTileSize = false;\n\nexport function RenderLayer({ annotations, formFields, tiles = true }: RenderLayerProps = {}) {\n const page = usePage();\n const render = useCapability(RenderToken);\n // The same object until a setting it reads changes, so a settings change re-renders the layer.\n const settings = useSelector(RenderToken, (render) => render.getPaintSettings());\n const ref = useRef<HTMLImageElement>(null);\n const parts: PicturePartProps = { annotations, formFields };\n const painted = usePaintedParts(page.ref);\n const may = useSelector(RenderToken, (render) => render.getLayerRights());\n const layers = pictureLayerOptionsOf(painted, parts, may);\n if (partsDrawnTwice(painted, parts).length > 0) {\n devWarn(\n 'render-layer-draws-what-a-layer-paints',\n '<RenderLayer annotations> or <RenderLayer formFields> draws what an <AnnotationLayer> or a ' +\n '<FormLayer> on the page paints too, so it shows twice. Leave the prop out: the picture ' +\n 'leaves out what a layer paints.',\n );\n }\n\n // One dependency: the raster's canonical identity — conformed width +\n // the parts it draws + epoch. Under a lattice it moves only at rung\n // crossings; under exact mode it tracks the demand and is constant above\n // the budget — so the deep-zoom backdrop never refetches, and the sub-\n // budget range refetches once per settled demand.\n const sourceKey = useSelector(RenderToken, (render) =>\n render.getSourceKey(page.ref, { scale: page.transform.renderScale, ...layers }),\n );\n\n useEffect(() => {\n // A layer that paints part of the page arrived in this commit, after this\n // render read the page's layers: the render it causes fetches instead.\n if (pageLayersOf(page.ref).painted() !== painted) return;\n const controller = new AbortController();\n let revoke: (() => void) | undefined;\n (async () => {\n try {\n // The capability conforms this to the resolved render points and\n // collapses same-key asks in its raster store. A stale-closure scale\n // is harmless by construction: any scale mapping to this key\n // produces this key's canonical request.\n const image = await render.renderSource(page.ref, {\n scale: page.transform.renderScale,\n ...layers,\n view: page.view,\n signal: controller.signal,\n });\n const obj = await image.objectUrl().abortWith(controller.signal);\n if (controller.signal.aborted) {\n obj.revoke();\n return;\n }\n revoke = obj.revoke;\n // Imperative src on a stable element: the browser keeps the old\n // bitmap until the new one decodes, and nothing ever re-requests the\n // old URL — so revoke-on-cleanup is safe here by construction.\n if (ref.current) ref.current.src = obj.url;\n } catch {\n /* aborted (camera moved / unmounted) or render failed */\n }\n })();\n return () => {\n controller.abort();\n revoke?.();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- sourceKey is the\n // render identity; scale/parts/epoch are folded into it upstream. `painted`\n // runs the fetch skipped above even when the key comes out the same.\n }, [render, page.ref, sourceKey, painted]);\n\n return (\n <>\n <img\n ref={ref}\n alt=\"\"\n draggable={false}\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none',\n }}\n />\n {tiles && settings.tiles ? <TilePlane parts={parts} /> : null}\n </>\n );\n}\n\n/**\n * The sharp plane above the base — the plugin's retention-safe paint plan\n * as keyed <img>s. Each tile reports painted after its first presentation\n * opportunity (and its inverse on unmount), so retained coarser generations\n * release only when their replacement is truly compositable.\n *\n * Tiles are placed in view (CSS px) space directly — never in page points\n * under a scaled container. Blink quantizes layout lengths to 1/64 CSS px\n * before transforms apply, so a pt-space rect under a ×25 zoom transform\n * turns that harmless 1/64 into ±0.4px+ of per-tile misplacement — visible\n * seams and per-zoom-step letter shifts that grow with depth. In view\n * space the same quantization is a fixed ~1/64 CSS px at every zoom.\n */\nfunction TilePlane({ parts }: { parts: PicturePartProps }) {\n const page = usePage();\n const render = useCapability(RenderToken);\n // Demand: the host's live camera view (Stage) or whole-page (PageView).\n // Level settling lives in the plugin (its settle gate), pan stays live.\n const demand: PageViewDemand = page.getViewDemand?.() ?? {\n desiredDeviceWidth: page.transform.deviceWidth,\n };\n // This view's tile surface: state is per view × page, so a thumbnail\n // rail's never-engaging demand cannot disturb the main lens's plan (shared\n // state made the main view lose its tiles whenever a rail opened). The\n // handle is reference-stable per view and reference-counted — one\n // `dispose` per `createViewDemand`.\n const view = useMemo(() => render.createViewDemand(page.view), [render, page.view]);\n useEffect(() => () => view.dispose(), [view]);\n // Demand in, plan out: setting the demand is the one call that schedules\n // fetches (and re-plans); the read below is pure. Layout-timed so a camera\n // move re-plans before the browser paints this commit. The page's layers\n // are read now, not at render: one mounted in this commit has said what it\n // paints by the time layout effects run.\n useLayoutEffect(() => {\n const painted = pageLayersOf(page.ref).painted();\n const layers = pictureLayerOptionsOf(painted, parts, render.getLayerRights());\n view.setDemand(page.ref, demand, layers);\n });\n const plan = useSelector(RenderToken, () => view.getPlan(page.ref));\n // View unmounted its plane: stop in-flight fetches; resolved bytes stay cached.\n useEffect(() => () => view.release(page.ref), [view, page.ref]);\n if (plan.paint.length === 0) return null;\n const transform = page.transform;\n const viewScale = transform.viewScale;\n return (\n <div\n style={{\n position: 'absolute',\n left: 0,\n top: 0,\n width: transform.contentWidth,\n height: transform.contentHeight,\n pointerEvents: 'none',\n // Stacking contract: tile <img>s carry zIndex ranks (coarse under\n // fine) that exist only while generations are mixed — mid-zoom. This\n // container must be a stacking context so those ranks stay internal;\n // without it they join the Stage's context and paint above every\n // z-auto sibling (annotations, page chrome, menus) exactly while\n // zooming. The old scaled container created one implicitly via its\n // transform; `isolation` is the explicit, no-side-effect way.\n isolation: 'isolate',\n }}\n >\n {plan.paint.map((source) => (\n <TileImg\n key={source.key}\n source={source}\n view={{\n x: source.rect.x * viewScale,\n y: source.rect.y * viewScale,\n width: source.rect.width * viewScale,\n height: source.rect.height * viewScale,\n }}\n onPainted={() => view.markPainted(page.ref, source.key)}\n onUnpainted={() => view.markUnpainted(page.ref, source.key)}\n />\n ))}\n </div>\n );\n}\n\n/** One tile: geometry plus the shared browser image binding. The binding\n * hides the incomplete image, reports painted after its first presentation\n * opportunity, and inverts that report when the element leaves the DOM. */\nfunction TileImg({\n source,\n view,\n onPainted,\n onUnpainted,\n}: {\n source: TilePaintSource;\n /** Placement rect in view (CSS px) space. */\n view: { x: number; y: number; width: number; height: number };\n onPainted: () => void;\n onUnpainted: () => void;\n}) {\n const ref = useRef<HTMLImageElement>(null);\n useLayoutEffect(() => {\n if (!ref.current) return;\n return bindPaintedImage(ref.current, source.handle, { onPainted, onUnpainted });\n // eslint-disable-next-line react-hooks/exhaustive-deps -- the handle is\n // the bitmap's identity; the report callbacks carry stable values.\n }, [source.handle]);\n return (\n <img\n ref={ref}\n alt=\"\"\n draggable={false}\n onLoad={(event) => {\n // A raster whose intrinsic size disagrees with its rect would be\n // silently stretched into place — the stale-handle bug class.\n const naturalWidth = event.currentTarget.naturalWidth;\n if (naturalWidth > 0 && !warnedTileSize) {\n const expected = Math.round(source.rect.width * source.scale);\n if (Math.abs(naturalWidth - expected) > 1) {\n warnedTileSize = true;\n console.warn(\n `[render] tile bitmap ${naturalWidth}px wide does not match its rect ` +\n `(expected ~${expected}px) — stale raster identity? key=${source.key}`,\n );\n }\n }\n }}\n style={{\n position: 'absolute',\n left: view.x,\n top: view.y,\n width: view.width,\n height: view.height,\n zIndex: source.z,\n }}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA4CA,SAAgB,YAA8B;CAC5C,OAAOA,gBAAAA,cAAcC,wBAAAA,WAAiB;AACxC;;AAGA,MAAa,oBAAoBC,cAAAA,aAAaD,wBAAAA,WAAiB;;AAG/D,SAAgB,eACd,QACA,SACM;CACN,gBAAA,mBAAmBA,wBAAAA,aAAmB,QAAQ,OAAO;AACvD;AAmBA,IAAI,iBAAiB;AAErB,SAAgB,YAAY,EAAE,aAAa,YAAY,QAAQ,SAA2B,CAAC,GAAG;CAC5F,MAAM,OAAOE,gBAAAA,QAAQ;CACrB,MAAM,SAASH,gBAAAA,cAAcI,sCAAAA,WAAW;CAExC,MAAM,WAAWC,gBAAAA,YAAYD,sCAAAA,cAAc,WAAW,OAAO,iBAAiB,CAAC;CAC/E,MAAM,OAAA,GAAA,MAAA,OAAA,CAA+B,IAAI;CACzC,MAAM,QAA0B;EAAE;EAAa;CAAW;CAC1D,MAAM,UAAUE,oBAAAA,gBAAgB,KAAK,GAAG;CAExC,MAAM,UAAA,GAAA,cAAA,sBAAA,CAA+B,SAAS,OADlCD,gBAAAA,YAAYD,sCAAAA,cAAc,WAAW,OAAO,eAAe,CAChB,CAAC;CACxD,KAAA,GAAA,cAAA,gBAAA,CAAoB,SAAS,KAAK,CAAC,CAAC,SAAS,GAC3C,gBAAA,QACE,0CACA,mNAGF;CAQF,MAAM,YAAYC,gBAAAA,YAAYD,sCAAAA,cAAc,WAC1C,OAAO,aAAa,KAAK,KAAK;EAAE,OAAO,KAAK,UAAU;EAAa,GAAG;CAAO,CAAC,CAChF;CAEA,CAAA,GAAA,MAAA,UAAA,OAAgB;EAGd,KAAA,GAAA,cAAA,aAAA,CAAiB,KAAK,GAAG,CAAC,CAAC,QAAQ,MAAM,SAAS;EAClD,MAAM,aAAa,IAAI,gBAAgB;EACvC,IAAI;EACJ,CAAC,YAAY;GACX,IAAI;IAWF,MAAM,MAAM,OAAM,MANE,OAAO,aAAa,KAAK,KAAK;KAChD,OAAO,KAAK,UAAU;KACtB,GAAG;KACH,MAAM,KAAK;KACX,QAAQ,WAAW;IACrB,CAAC,EAAA,CACuB,UAAU,CAAC,CAAC,UAAU,WAAW,MAAM;IAC/D,IAAI,WAAW,OAAO,SAAS;KAC7B,IAAI,OAAO;KACX;IACF;IACA,SAAS,IAAI;IAIb,IAAI,IAAI,SAAS,IAAI,QAAQ,MAAM,IAAI;GACzC,QAAQ,CAER;EACF,EAAA,CAAG;EACH,aAAa;GACX,WAAW,MAAM;GACjB,SAAS;EACX;CAIF,GAAG;EAAC;EAAQ,KAAK;EAAK;EAAW;CAAO,CAAC;CAEzC,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAA,kBAAA,UAAA,EAAA,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EACO;EACL,KAAI;EACJ,WAAW;EACX,OAAO;GACL,UAAU;GACV,OAAO;GACP,OAAO;GACP,QAAQ;GACR,eAAe;EACjB;CACD,CAAA,GACA,SAAS,SAAS,QAAQ,iBAAA,GAAA,kBAAA,IAAA,CAAC,WAAD,EAAkB,MAAQ,CAAA,IAAI,IACzD,EAAA,CAAA;AAEN;;;;;;;;;;;;;;AAeA,SAAS,UAAU,EAAE,SAAsC;CACzD,MAAM,OAAOD,gBAAAA,QAAQ;CACrB,MAAM,SAASH,gBAAAA,cAAcI,sCAAAA,WAAW;CAGxC,MAAM,SAAyB,KAAK,gBAAgB,KAAK,EACvD,oBAAoB,KAAK,UAAU,YACrC;CAMA,MAAM,QAAA,GAAA,MAAA,QAAA,OAAqB,OAAO,iBAAiB,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC;CAClF,CAAA,GAAA,MAAA,UAAA,aAAsB,KAAK,QAAQ,GAAG,CAAC,IAAI,CAAC;CAM5C,CAAA,GAAA,MAAA,gBAAA,OAAsB;EAEpB,MAAM,UAAA,GAAA,cAAA,sBAAA,EAAA,GAAA,cAAA,aAAA,CADuB,KAAK,GAAG,CAAC,CAAC,QACI,GAAG,OAAO,OAAO,eAAe,CAAC;EAC5E,KAAK,UAAU,KAAK,KAAK,QAAQ,MAAM;CACzC,CAAC;CACD,MAAM,OAAOC,gBAAAA,YAAYD,sCAAAA,mBAAmB,KAAK,QAAQ,KAAK,GAAG,CAAC;CAElE,CAAA,GAAA,MAAA,UAAA,aAAsB,KAAK,QAAQ,KAAK,GAAG,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC;CAC9D,IAAI,KAAK,MAAM,WAAW,GAAG,OAAO;CACpC,MAAM,YAAY,KAAK;CACvB,MAAM,YAAY,UAAU;CAC5B,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EACE,OAAO;GACL,UAAU;GACV,MAAM;GACN,KAAK;GACL,OAAO,UAAU;GACjB,QAAQ,UAAU;GAClB,eAAe;GAQf,WAAW;EACb;EAEC,UAAA,KAAK,MAAM,KAAK,WACf,iBAAA,GAAA,kBAAA,IAAA,CAAC,SAAD;GAEU;GACR,MAAM;IACJ,GAAG,OAAO,KAAK,IAAI;IACnB,GAAG,OAAO,KAAK,IAAI;IACnB,OAAO,OAAO,KAAK,QAAQ;IAC3B,QAAQ,OAAO,KAAK,SAAS;GAC/B;GACA,iBAAiB,KAAK,YAAY,KAAK,KAAK,OAAO,GAAG;GACtD,mBAAmB,KAAK,cAAc,KAAK,KAAK,OAAO,GAAG;EAC3D,GAVM,OAAO,GAUb,CACF;CACE,CAAA;AAET;;;;AAKA,SAAS,QAAQ,EACf,QACA,MACA,WACA,eAOC;CACD,MAAM,OAAA,GAAA,MAAA,OAAA,CAA+B,IAAI;CACzC,CAAA,GAAA,MAAA,gBAAA,OAAsB;EACpB,IAAI,CAAC,IAAI,SAAS;EAClB,QAAA,GAAA,cAAA,iBAAA,CAAwB,IAAI,SAAS,OAAO,QAAQ;GAAE;GAAW;EAAY,CAAC;CAGhF,GAAG,CAAC,OAAO,MAAM,CAAC;CAClB,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;EACO;EACL,KAAI;EACJ,WAAW;EACX,SAAS,UAAU;GAGjB,MAAM,eAAe,MAAM,cAAc;GACzC,IAAI,eAAe,KAAK,CAAC,gBAAgB;IACvC,MAAM,WAAW,KAAK,MAAM,OAAO,KAAK,QAAQ,OAAO,KAAK;IAC5D,IAAI,KAAK,IAAI,eAAe,QAAQ,IAAI,GAAG;KACzC,iBAAiB;KACjB,QAAQ,KACN,wBAAwB,aAAa,6CACrB,SAAS,mCAAmC,OAAO,KACrE;IACF;GACF;EACF;EACA,OAAO;GACL,UAAU;GACV,MAAM,KAAK;GACX,KAAK,KAAK;GACV,OAAO,KAAK;GACZ,QAAQ,KAAK;GACb,QAAQ,OAAO;EACjB;CACD,CAAA;AAEL"}
|
package/dist/render.d.cts
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
|
+
import { t as SettingsHook } from "./state-0K1I5cM4.cjs";
|
|
2
|
+
import { EventHook } from "@embedpdf/core";
|
|
3
|
+
import { RenderCapability } from "@embedpdf/plugin-render";
|
|
1
4
|
export * from "@embedpdf/plugin-render";
|
|
2
5
|
//#region src/render.d.ts
|
|
6
|
+
/** The render capability (renderPage / renderThumbnail / invalidation / settings) for app code. */
|
|
7
|
+
declare function useRender(): RenderCapability;
|
|
8
|
+
/** The render settings (`fullPage`, `tiles`, `format`, …), with or without a document. Takes a selector. */
|
|
9
|
+
declare const useRenderSettings: SettingsHook<import("@embedpdf/plugin-render").RenderSettings>;
|
|
10
|
+
/** Subscribe to one render event for the mounted lifetime: `useRenderEvent((render) => render.onInvalidated, handler)`. */
|
|
11
|
+
declare function useRenderEvent<T>(select: (render: RenderCapability) => EventHook<T>, handler: (event: T) => void): void;
|
|
3
12
|
interface RenderLayerProps {
|
|
4
13
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
14
|
+
* Draw the annotations into the page's picture (`true`) or leave them out
|
|
15
|
+
* (`false`). Left unset, the picture leaves them to an `<AnnotationLayer>`
|
|
16
|
+
* on the page, and otherwise draws them when the user may read them.
|
|
7
17
|
*/
|
|
8
18
|
annotations?: boolean;
|
|
19
|
+
/** The same for the form fields, which a `<FormLayer>` paints. */
|
|
20
|
+
formFields?: boolean;
|
|
9
21
|
/**
|
|
10
|
-
* Mount the tile plane (default true). Whether it
|
|
22
|
+
* Mount the tile plane (default true). Whether it spends anything is
|
|
11
23
|
* demand arithmetic — leave it on; pass false only for a lens that must
|
|
12
24
|
* never tile even under deep zoom.
|
|
13
25
|
*/
|
|
14
26
|
tiles?: boolean;
|
|
15
27
|
}
|
|
16
|
-
declare function RenderLayer({ annotations, tiles }?: RenderLayerProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
declare function RenderLayer({ annotations, formFields, tiles }?: RenderLayerProps): import("react/jsx-runtime").JSX.Element;
|
|
17
29
|
//#endregion
|
|
18
|
-
export { RenderLayer, RenderLayerProps };
|
|
30
|
+
export { RenderLayer, RenderLayerProps, useRender, useRenderEvent, useRenderSettings };
|
|
19
31
|
//# sourceMappingURL=render.d.cts.map
|
package/dist/render.d.ts
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
|
+
import { t as SettingsHook } from "./state-0K1I5cM4.js";
|
|
2
|
+
import { EventHook } from "@embedpdf/core";
|
|
3
|
+
import { RenderCapability } from "@embedpdf/plugin-render";
|
|
1
4
|
export * from "@embedpdf/plugin-render";
|
|
2
5
|
//#region src/render.d.ts
|
|
6
|
+
/** The render capability (renderPage / renderThumbnail / invalidation / settings) for app code. */
|
|
7
|
+
declare function useRender(): RenderCapability;
|
|
8
|
+
/** The render settings (`fullPage`, `tiles`, `format`, …), with or without a document. Takes a selector. */
|
|
9
|
+
declare const useRenderSettings: SettingsHook<import("@embedpdf/plugin-render").RenderSettings>;
|
|
10
|
+
/** Subscribe to one render event for the mounted lifetime: `useRenderEvent((render) => render.onInvalidated, handler)`. */
|
|
11
|
+
declare function useRenderEvent<T>(select: (render: RenderCapability) => EventHook<T>, handler: (event: T) => void): void;
|
|
3
12
|
interface RenderLayerProps {
|
|
4
13
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
14
|
+
* Draw the annotations into the page's picture (`true`) or leave them out
|
|
15
|
+
* (`false`). Left unset, the picture leaves them to an `<AnnotationLayer>`
|
|
16
|
+
* on the page, and otherwise draws them when the user may read them.
|
|
7
17
|
*/
|
|
8
18
|
annotations?: boolean;
|
|
19
|
+
/** The same for the form fields, which a `<FormLayer>` paints. */
|
|
20
|
+
formFields?: boolean;
|
|
9
21
|
/**
|
|
10
|
-
* Mount the tile plane (default true). Whether it
|
|
22
|
+
* Mount the tile plane (default true). Whether it spends anything is
|
|
11
23
|
* demand arithmetic — leave it on; pass false only for a lens that must
|
|
12
24
|
* never tile even under deep zoom.
|
|
13
25
|
*/
|
|
14
26
|
tiles?: boolean;
|
|
15
27
|
}
|
|
16
|
-
declare function RenderLayer({ annotations, tiles }?: RenderLayerProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
declare function RenderLayer({ annotations, formFields, tiles }?: RenderLayerProps): import("react/jsx-runtime").JSX.Element;
|
|
17
29
|
//#endregion
|
|
18
|
-
export { RenderLayer, RenderLayerProps };
|
|
30
|
+
export { RenderLayer, RenderLayerProps, useRender, useRenderEvent, useRenderSettings };
|
|
19
31
|
//# sourceMappingURL=render.d.ts.map
|
package/dist/render.js
CHANGED
|
@@ -1,31 +1,52 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import {
|
|
1
|
+
import { E as useSelector, O as devWarn, f as useCapability, p as useCapabilityEvent, w as usePage } from "./runtime-B6xcPsEb.js";
|
|
2
|
+
import { t as settingsHook } from "./state-CbgxhwLA.js";
|
|
3
|
+
import { t as usePaintedParts } from "./page-layers-Bkaaz8Zf.js";
|
|
3
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { bindPaintedImage, pageLayersOf, partsDrawnTwice, pictureLayerOptionsOf } from "@embedpdf/web";
|
|
6
|
+
import { useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
|
6
7
|
import { RenderToken } from "@embedpdf/plugin-render";
|
|
8
|
+
import { RenderToken as RenderToken$1 } from "@embedpdf/plugin-render/contract/host";
|
|
7
9
|
export * from "@embedpdf/plugin-render";
|
|
8
10
|
//#region src/render.tsx
|
|
11
|
+
/** The render capability (renderPage / renderThumbnail / invalidation / settings) for app code. */
|
|
12
|
+
function useRender() {
|
|
13
|
+
return useCapability(RenderToken);
|
|
14
|
+
}
|
|
15
|
+
/** The render settings (`fullPage`, `tiles`, `format`, …), with or without a document. Takes a selector. */
|
|
16
|
+
const useRenderSettings = settingsHook(RenderToken);
|
|
17
|
+
/** Subscribe to one render event for the mounted lifetime: `useRenderEvent((render) => render.onInvalidated, handler)`. */
|
|
18
|
+
function useRenderEvent(select, handler) {
|
|
19
|
+
useCapabilityEvent(RenderToken, select, handler);
|
|
20
|
+
}
|
|
9
21
|
let warnedTileSize = false;
|
|
10
|
-
function RenderLayer({ annotations
|
|
22
|
+
function RenderLayer({ annotations, formFields, tiles = true } = {}) {
|
|
11
23
|
const page = usePage();
|
|
12
|
-
const render = useCapability(RenderToken);
|
|
13
|
-
const settings = render.
|
|
24
|
+
const render = useCapability(RenderToken$1);
|
|
25
|
+
const settings = useSelector(RenderToken$1, (render) => render.getPaintSettings());
|
|
14
26
|
const ref = useRef(null);
|
|
15
|
-
const
|
|
27
|
+
const parts = {
|
|
28
|
+
annotations,
|
|
29
|
+
formFields
|
|
30
|
+
};
|
|
31
|
+
const painted = usePaintedParts(page.ref);
|
|
32
|
+
const layers = pictureLayerOptionsOf(painted, parts, useSelector(RenderToken$1, (render) => render.getLayerRights()));
|
|
33
|
+
if (partsDrawnTwice(painted, parts).length > 0) devWarn("render-layer-draws-what-a-layer-paints", "<RenderLayer annotations> or <RenderLayer formFields> draws what an <AnnotationLayer> or a <FormLayer> on the page paints too, so it shows twice. Leave the prop out: the picture leaves out what a layer paints.");
|
|
34
|
+
const sourceKey = useSelector(RenderToken$1, (render) => render.getSourceKey(page.ref, {
|
|
16
35
|
scale: page.transform.renderScale,
|
|
17
|
-
|
|
36
|
+
...layers
|
|
18
37
|
}));
|
|
19
38
|
useEffect(() => {
|
|
39
|
+
if (pageLayersOf(page.ref).painted() !== painted) return;
|
|
20
40
|
const controller = new AbortController();
|
|
21
41
|
let revoke;
|
|
22
42
|
(async () => {
|
|
23
43
|
try {
|
|
24
|
-
const obj = await (await render.
|
|
44
|
+
const obj = await (await render.renderSource(page.ref, {
|
|
25
45
|
scale: page.transform.renderScale,
|
|
26
|
-
|
|
46
|
+
...layers,
|
|
47
|
+
view: page.view,
|
|
27
48
|
signal: controller.signal
|
|
28
|
-
})).objectUrl(controller.signal);
|
|
49
|
+
})).objectUrl().abortWith(controller.signal);
|
|
29
50
|
if (controller.signal.aborted) {
|
|
30
51
|
obj.revoke();
|
|
31
52
|
return;
|
|
@@ -40,8 +61,9 @@ function RenderLayer({ annotations = true, tiles = true } = {}) {
|
|
|
40
61
|
};
|
|
41
62
|
}, [
|
|
42
63
|
render,
|
|
43
|
-
page.
|
|
44
|
-
sourceKey
|
|
64
|
+
page.ref,
|
|
65
|
+
sourceKey,
|
|
66
|
+
painted
|
|
45
67
|
]);
|
|
46
68
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("img", {
|
|
47
69
|
ref,
|
|
@@ -54,10 +76,7 @@ function RenderLayer({ annotations = true, tiles = true } = {}) {
|
|
|
54
76
|
height: "100%",
|
|
55
77
|
pointerEvents: "none"
|
|
56
78
|
}
|
|
57
|
-
}), tiles && settings.tiles ? /* @__PURE__ */ jsx(TilePlane, {
|
|
58
|
-
annotations,
|
|
59
|
-
fadeMs: settings.fadeMs
|
|
60
|
-
}) : null] });
|
|
79
|
+
}), tiles && settings.tiles ? /* @__PURE__ */ jsx(TilePlane, { parts }) : null] });
|
|
61
80
|
}
|
|
62
81
|
/**
|
|
63
82
|
* The sharp plane above the base — the plugin's retention-safe paint plan
|
|
@@ -65,52 +84,55 @@ function RenderLayer({ annotations = true, tiles = true } = {}) {
|
|
|
65
84
|
* opportunity (and its inverse on unmount), so retained coarser generations
|
|
66
85
|
* release only when their replacement is truly compositable.
|
|
67
86
|
*
|
|
68
|
-
* Tiles are placed in
|
|
87
|
+
* Tiles are placed in view (CSS px) space directly — never in page points
|
|
69
88
|
* under a scaled container. Blink quantizes layout lengths to 1/64 CSS px
|
|
70
|
-
*
|
|
89
|
+
* before transforms apply, so a pt-space rect under a ×25 zoom transform
|
|
71
90
|
* turns that harmless 1/64 into ±0.4px+ of per-tile misplacement — visible
|
|
72
91
|
* seams and per-zoom-step letter shifts that grow with depth. In view
|
|
73
92
|
* space the same quantization is a fixed ~1/64 CSS px at every zoom.
|
|
74
|
-
* (v2 placed tiles in screen px for exactly this reason.)
|
|
75
93
|
*/
|
|
76
|
-
function TilePlane({
|
|
94
|
+
function TilePlane({ parts }) {
|
|
77
95
|
const page = usePage();
|
|
78
|
-
const render = useCapability(RenderToken);
|
|
96
|
+
const render = useCapability(RenderToken$1);
|
|
79
97
|
const demand = page.getViewDemand?.() ?? { desiredDeviceWidth: page.transform.deviceWidth };
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
98
|
+
const view = useMemo(() => render.createViewDemand(page.view), [render, page.view]);
|
|
99
|
+
useEffect(() => () => view.dispose(), [view]);
|
|
100
|
+
useLayoutEffect(() => {
|
|
101
|
+
const layers = pictureLayerOptionsOf(pageLayersOf(page.ref).painted(), parts, render.getLayerRights());
|
|
102
|
+
view.setDemand(page.ref, demand, layers);
|
|
103
|
+
});
|
|
104
|
+
const plan = useSelector(RenderToken$1, () => view.getPlan(page.ref));
|
|
105
|
+
useEffect(() => () => view.release(page.ref), [view, page.ref]);
|
|
83
106
|
if (plan.paint.length === 0) return null;
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
return /* @__PURE__ */
|
|
107
|
+
const transform = page.transform;
|
|
108
|
+
const viewScale = transform.viewScale;
|
|
109
|
+
return /* @__PURE__ */ jsx("div", {
|
|
87
110
|
style: {
|
|
88
111
|
position: "absolute",
|
|
89
112
|
left: 0,
|
|
90
113
|
top: 0,
|
|
91
|
-
width:
|
|
92
|
-
height:
|
|
114
|
+
width: transform.contentWidth,
|
|
115
|
+
height: transform.contentHeight,
|
|
93
116
|
pointerEvents: "none",
|
|
94
117
|
isolation: "isolate"
|
|
95
118
|
},
|
|
96
|
-
children:
|
|
119
|
+
children: plan.paint.map((source) => /* @__PURE__ */ jsx(TileImg, {
|
|
97
120
|
source,
|
|
98
121
|
view: {
|
|
99
|
-
x: source.rect.x *
|
|
100
|
-
y: source.rect.y *
|
|
101
|
-
width: source.rect.width *
|
|
102
|
-
height: source.rect.height *
|
|
122
|
+
x: source.rect.x * viewScale,
|
|
123
|
+
y: source.rect.y * viewScale,
|
|
124
|
+
width: source.rect.width * viewScale,
|
|
125
|
+
height: source.rect.height * viewScale
|
|
103
126
|
},
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}, source.key))]
|
|
127
|
+
onPainted: () => view.markPainted(page.ref, source.key),
|
|
128
|
+
onUnpainted: () => view.markUnpainted(page.ref, source.key)
|
|
129
|
+
}, source.key))
|
|
108
130
|
});
|
|
109
131
|
}
|
|
110
132
|
/** One tile: geometry plus the shared browser image binding. The binding
|
|
111
133
|
* hides the incomplete image, reports painted after its first presentation
|
|
112
134
|
* opportunity, and inverts that report when the element leaves the DOM. */
|
|
113
|
-
function TileImg({ source, view,
|
|
135
|
+
function TileImg({ source, view, onPainted, onUnpainted }) {
|
|
114
136
|
const ref = useRef(null);
|
|
115
137
|
useLayoutEffect(() => {
|
|
116
138
|
if (!ref.current) return;
|
|
@@ -123,13 +145,13 @@ function TileImg({ source, view, fadeMs, onPainted, onUnpainted }) {
|
|
|
123
145
|
ref,
|
|
124
146
|
alt: "",
|
|
125
147
|
draggable: false,
|
|
126
|
-
onLoad: (
|
|
127
|
-
const
|
|
128
|
-
if (
|
|
148
|
+
onLoad: (event) => {
|
|
149
|
+
const naturalWidth = event.currentTarget.naturalWidth;
|
|
150
|
+
if (naturalWidth > 0 && !warnedTileSize) {
|
|
129
151
|
const expected = Math.round(source.rect.width * source.scale);
|
|
130
|
-
if (Math.abs(
|
|
152
|
+
if (Math.abs(naturalWidth - expected) > 1) {
|
|
131
153
|
warnedTileSize = true;
|
|
132
|
-
console.warn(`[render] tile bitmap ${
|
|
154
|
+
console.warn(`[render] tile bitmap ${naturalWidth}px wide does not match its rect (expected ~${expected}px) — stale raster identity? key=${source.key}`);
|
|
133
155
|
}
|
|
134
156
|
}
|
|
135
157
|
},
|
|
@@ -139,12 +161,11 @@ function TileImg({ source, view, fadeMs, onPainted, onUnpainted }) {
|
|
|
139
161
|
top: view.y,
|
|
140
162
|
width: view.width,
|
|
141
163
|
height: view.height,
|
|
142
|
-
zIndex: source.z
|
|
143
|
-
...fadeMs > 0 ? { animation: `epdf-tile-in ${fadeMs}ms ease-out` } : null
|
|
164
|
+
zIndex: source.z
|
|
144
165
|
}
|
|
145
166
|
});
|
|
146
167
|
}
|
|
147
168
|
//#endregion
|
|
148
|
-
export { RenderLayer };
|
|
169
|
+
export { RenderLayer, useRender, useRenderEvent, useRenderSettings };
|
|
149
170
|
|
|
150
171
|
//# sourceMappingURL=render.js.map
|
package/dist/render.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","names":[],"sources":["../src/render.tsx"],"sourcesContent":["/**\n * RenderLayer — the React view of @embedpdf/plugin-render. The page raster\n * is ONE concern with two planes:\n *\n * - the BASE plane: a whole-page <img> at the plugin's resolved render\n * points — the exact settled demand capped at the pixel budget on a\n * continuous (local) engine, the advertised ladder on a lattice (cloud)\n * deployment. Always present; the instant backdrop.\n * - the TILE plane: the plugin's retention-safe paint plan above it,\n * engaging by demand arithmetic when the view wants more pixels than\n * the base may spend. A thumbnail-sized demand engages nothing — the\n * arithmetic is the configuration.\n *\n * The layer is a DUMB painter, deliberately thin: plain <img>s bound through\n * the shared browser adapter, with painted/unpainted reports around their\n * visible lifetime. Every decision — strategy ∧ policy conformance, want\n * sets, level settling, retention, release — is plugin-render's; anything\n * mid-gesture simply CSS-scales until the plugin hands down new pixels.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-render';\nimport * as React from 'react';\nimport { useEffect, useLayoutEffect, useRef } from 'react';\nimport { RenderToken } from '@embedpdf/plugin-render';\nimport type { PageViewDemand, TilePaintSource } from '@embedpdf/plugin-render';\nimport { bindPaintedImage } from '@embedpdf/web';\nimport { useCapability, usePage, useSelector } from './runtime';\n\nexport interface RenderLayerProps {\n /**\n * Bake annotations into the page bitmap (default true). Pass false when an\n * <AnnotationLayer> owns annotation rendering, so they aren't drawn twice.\n */\n annotations?: boolean;\n /**\n * Mount the tile plane (default true). Whether it SPENDS anything is\n * demand arithmetic — leave it on; pass false only for a lens that must\n * never tile even under deep zoom.\n */\n tiles?: boolean;\n}\n\nlet warnedTileSize = false;\n\nexport function RenderLayer({ annotations = true, tiles = true }: RenderLayerProps = {}) {\n const page = usePage();\n const render = useCapability(RenderToken);\n const settings = render.paintSettings();\n const ref = useRef<HTMLImageElement>(null);\n\n // ONE dependency: the raster's canonical identity — conformed width +\n // annotations flag + epoch. Under a lattice it moves only at rung\n // crossings; under exact mode it tracks the demand and is CONSTANT above\n // the budget — so the deep-zoom backdrop never refetches, and the sub-\n // budget range refetches per settled demand exactly like v2 did.\n const sourceKey = useSelector(RenderToken, (c) =>\n c.renderSourceKey(page.pon, {\n scale: page.transform.renderScale,\n includeAnnotations: annotations,\n }),\n );\n\n useEffect(() => {\n const controller = new AbortController();\n let revoke: (() => void) | undefined;\n (async () => {\n try {\n // The capability conforms this to the resolved render points and\n // collapses same-key asks in its raster store. A stale-closure scale\n // is harmless by construction: any scale mapping to this key\n // produces this key's canonical request.\n const image = await render.renderPage(page.pon, {\n scale: page.transform.renderScale,\n includeAnnotations: annotations,\n signal: controller.signal,\n });\n const obj = await image.objectUrl(controller.signal);\n if (controller.signal.aborted) {\n obj.revoke();\n return;\n }\n revoke = obj.revoke;\n // Imperative src on a STABLE element: the browser keeps the old\n // bitmap until the new one decodes, and nothing ever re-requests the\n // old URL — so revoke-on-cleanup is safe here by construction.\n if (ref.current) ref.current.src = obj.url;\n } catch {\n /* aborted (camera moved / unmounted) or render failed */\n }\n })();\n return () => {\n controller.abort();\n revoke?.();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- sourceKey IS the\n // render identity; scale/annotations/epoch are folded into it upstream.\n }, [render, page.pon, sourceKey]);\n\n return (\n <>\n <img\n ref={ref}\n alt=\"\"\n draggable={false}\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none',\n }}\n />\n {tiles && settings.tiles ? (\n <TilePlane annotations={annotations} fadeMs={settings.fadeMs} />\n ) : null}\n </>\n );\n}\n\n/**\n * The sharp plane above the base — the plugin's retention-safe paint plan\n * as keyed <img>s. Each tile reports painted after its first presentation\n * opportunity (and its inverse on unmount), so retained coarser generations\n * release only when their replacement is truly compositable.\n *\n * Tiles are placed in VIEW (CSS px) space directly — never in page points\n * under a scaled container. Blink quantizes layout lengths to 1/64 CSS px\n * BEFORE transforms apply, so a pt-space rect under a ×25 zoom transform\n * turns that harmless 1/64 into ±0.4px+ of per-tile misplacement — visible\n * seams and per-zoom-step letter shifts that grow with depth. In view\n * space the same quantization is a fixed ~1/64 CSS px at every zoom.\n * (v2 placed tiles in screen px for exactly this reason.)\n */\nfunction TilePlane({ annotations, fadeMs }: { annotations: boolean; fadeMs: number }) {\n const page = usePage();\n const render = useCapability(RenderToken);\n // Demand: the host's live camera view (Stage) or whole-page (PageView).\n // Level settling lives in the plugin (its settle gate), pan stays live.\n const demand: PageViewDemand = page.getViewDemand?.() ?? {\n desiredDeviceWidth: page.transform.deviceWidth,\n };\n // THIS view's tile surface: state is per view × page, so a thumbnail\n // rail's never-engaging demand cannot disturb the main lens's plan (shared\n // state made the main view lose its tiles whenever a rail opened). The\n // handle is reference-stable per view — a clean dependency.\n const tiles = render.tilesFor(page.view);\n const plan = useSelector(RenderToken, () =>\n tiles.plan(page.pon, demand, { includeAnnotations: annotations }),\n );\n // View unmounted its plane: stop in-flight fetches; resolved bytes stay cached.\n useEffect(() => () => tiles.release(page.pon), [tiles, page.pon]);\n if (plan.paint.length === 0) return null;\n const t = page.transform;\n const s = t.viewScale;\n return (\n <div\n style={{\n position: 'absolute',\n left: 0,\n top: 0,\n width: t.contentWidth,\n height: t.contentHeight,\n pointerEvents: 'none',\n // STACKING CONTRACT: tile <img>s carry zIndex ranks (coarse under\n // fine) that exist only while generations are mixed — mid-zoom. This\n // container MUST be a stacking context so those ranks stay internal;\n // without it they join the Stage's context and paint above every\n // z-auto sibling (annotations, page chrome, menus) exactly while\n // zooming. The old scaled container created one implicitly via its\n // transform; `isolation` is the explicit, no-side-effect way.\n isolation: 'isolate',\n }}\n >\n {fadeMs > 0 ? (\n <style>{`@keyframes epdf-tile-in { from { opacity: 0 } to { opacity: 1 } }`}</style>\n ) : null}\n {plan.paint.map((source) => (\n <TileImg\n key={source.key}\n source={source}\n view={{\n x: source.rect.x * s,\n y: source.rect.y * s,\n width: source.rect.width * s,\n height: source.rect.height * s,\n }}\n fadeMs={fadeMs}\n onPainted={() => tiles.painted(page.pon, source.key)}\n onUnpainted={() => tiles.unpainted(page.pon, source.key)}\n />\n ))}\n </div>\n );\n}\n\n/** One tile: geometry plus the shared browser image binding. The binding\n * hides the incomplete image, reports painted after its first presentation\n * opportunity, and inverts that report when the element leaves the DOM. */\nfunction TileImg({\n source,\n view,\n fadeMs,\n onPainted,\n onUnpainted,\n}: {\n source: TilePaintSource;\n /** Placement rect in VIEW (CSS px) space. */\n view: { x: number; y: number; width: number; height: number };\n fadeMs: number;\n onPainted: () => void;\n onUnpainted: () => void;\n}) {\n const ref = useRef<HTMLImageElement>(null);\n useLayoutEffect(() => {\n if (!ref.current) return;\n return bindPaintedImage(ref.current, source.handle, { onPainted, onUnpainted });\n // eslint-disable-next-line react-hooks/exhaustive-deps -- the handle is\n // the bitmap's identity; the report callbacks carry stable values.\n }, [source.handle]);\n return (\n <img\n ref={ref}\n alt=\"\"\n draggable={false}\n onLoad={(e) => {\n // A raster whose intrinsic size disagrees with its rect would be\n // silently stretched into place — the stale-handle bug class.\n const w = e.currentTarget.naturalWidth;\n if (w > 0 && !warnedTileSize) {\n const expected = Math.round(source.rect.width * source.scale);\n if (Math.abs(w - expected) > 1) {\n warnedTileSize = true;\n console.warn(\n `[render] tile bitmap ${w}px wide does not match its rect ` +\n `(expected ~${expected}px) — stale raster identity? key=${source.key}`,\n );\n }\n }\n }}\n style={{\n position: 'absolute',\n left: view.x,\n top: view.y,\n width: view.width,\n height: view.height,\n zIndex: source.z,\n ...(fadeMs > 0 ? { animation: `epdf-tile-in ${fadeMs}ms ease-out` } : null),\n }}\n />\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"render.js","names":["RenderPublicToken","RenderToken"],"sources":["../src/render.tsx"],"sourcesContent":["/**\n * RenderLayer — the React view of @embedpdf/plugin-render. The page raster\n * is one concern with two planes:\n *\n * - the base plane: a whole-page <img> at the plugin's resolved render\n * points — the exact settled demand capped at the pixel budget on a\n * continuous (local) engine, the advertised ladder on a lattice (cloud)\n * deployment. Always present; the instant backdrop.\n * - the tile plane: the plugin's retention-safe paint plan above it,\n * engaging by demand arithmetic when the view wants more pixels than\n * the base may spend. A thumbnail-sized demand engages nothing — the\n * arithmetic is the configuration.\n *\n * The layer is a dumb painter, deliberately thin: plain <img>s bound through\n * the shared browser adapter, with painted/unpainted reports around their\n * visible lifetime. Every decision — strategy ∧ policy conformance, want\n * sets, level settling, retention, release — is plugin-render's; anything\n * mid-gesture simply CSS-scales until the plugin hands down new pixels.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-render';\nimport * as React from 'react';\nimport { useEffect, useLayoutEffect, useMemo, useRef } from 'react';\n// The layer is a host of the render plugin: it paints conformed sources and\n// drives a view's tile demand. The host lens is the same runtime token.\nimport { RenderToken } from '@embedpdf/plugin-render/contract/host';\nimport type { PageViewDemand, TilePaintSource } from '@embedpdf/plugin-render/contract/host';\nimport { RenderToken as RenderPublicToken } from '@embedpdf/plugin-render';\nimport type { RenderCapability } from '@embedpdf/plugin-render';\nimport type { EventHook } from '@embedpdf/core';\nimport {\n bindPaintedImage,\n pageLayersOf,\n partsDrawnTwice,\n pictureLayerOptionsOf,\n type PicturePartProps,\n} from '@embedpdf/web';\nimport { devWarn } from './dev';\nimport { usePaintedParts } from './page-layers';\nimport { useCapability, useCapabilityEvent, usePage, useSelector } from './runtime';\nimport { settingsHook } from './state';\n\n/** The render capability (renderPage / renderThumbnail / invalidation / settings) for app code. */\nexport function useRender(): RenderCapability {\n return useCapability(RenderPublicToken);\n}\n\n/** The render settings (`fullPage`, `tiles`, `format`, …), with or without a document. Takes a selector. */\nexport const useRenderSettings = settingsHook(RenderPublicToken);\n\n/** Subscribe to one render event for the mounted lifetime: `useRenderEvent((render) => render.onInvalidated, handler)`. */\nexport function useRenderEvent<T>(\n select: (render: RenderCapability) => EventHook<T>,\n handler: (event: T) => void,\n): void {\n useCapabilityEvent(RenderPublicToken, select, handler);\n}\n\nexport interface RenderLayerProps {\n /**\n * Draw the annotations into the page's picture (`true`) or leave them out\n * (`false`). Left unset, the picture leaves them to an `<AnnotationLayer>`\n * on the page, and otherwise draws them when the user may read them.\n */\n annotations?: boolean;\n /** The same for the form fields, which a `<FormLayer>` paints. */\n formFields?: boolean;\n /**\n * Mount the tile plane (default true). Whether it spends anything is\n * demand arithmetic — leave it on; pass false only for a lens that must\n * never tile even under deep zoom.\n */\n tiles?: boolean;\n}\n\nlet warnedTileSize = false;\n\nexport function RenderLayer({ annotations, formFields, tiles = true }: RenderLayerProps = {}) {\n const page = usePage();\n const render = useCapability(RenderToken);\n // The same object until a setting it reads changes, so a settings change re-renders the layer.\n const settings = useSelector(RenderToken, (render) => render.getPaintSettings());\n const ref = useRef<HTMLImageElement>(null);\n const parts: PicturePartProps = { annotations, formFields };\n const painted = usePaintedParts(page.ref);\n const may = useSelector(RenderToken, (render) => render.getLayerRights());\n const layers = pictureLayerOptionsOf(painted, parts, may);\n if (partsDrawnTwice(painted, parts).length > 0) {\n devWarn(\n 'render-layer-draws-what-a-layer-paints',\n '<RenderLayer annotations> or <RenderLayer formFields> draws what an <AnnotationLayer> or a ' +\n '<FormLayer> on the page paints too, so it shows twice. Leave the prop out: the picture ' +\n 'leaves out what a layer paints.',\n );\n }\n\n // One dependency: the raster's canonical identity — conformed width +\n // the parts it draws + epoch. Under a lattice it moves only at rung\n // crossings; under exact mode it tracks the demand and is constant above\n // the budget — so the deep-zoom backdrop never refetches, and the sub-\n // budget range refetches once per settled demand.\n const sourceKey = useSelector(RenderToken, (render) =>\n render.getSourceKey(page.ref, { scale: page.transform.renderScale, ...layers }),\n );\n\n useEffect(() => {\n // A layer that paints part of the page arrived in this commit, after this\n // render read the page's layers: the render it causes fetches instead.\n if (pageLayersOf(page.ref).painted() !== painted) return;\n const controller = new AbortController();\n let revoke: (() => void) | undefined;\n (async () => {\n try {\n // The capability conforms this to the resolved render points and\n // collapses same-key asks in its raster store. A stale-closure scale\n // is harmless by construction: any scale mapping to this key\n // produces this key's canonical request.\n const image = await render.renderSource(page.ref, {\n scale: page.transform.renderScale,\n ...layers,\n view: page.view,\n signal: controller.signal,\n });\n const obj = await image.objectUrl().abortWith(controller.signal);\n if (controller.signal.aborted) {\n obj.revoke();\n return;\n }\n revoke = obj.revoke;\n // Imperative src on a stable element: the browser keeps the old\n // bitmap until the new one decodes, and nothing ever re-requests the\n // old URL — so revoke-on-cleanup is safe here by construction.\n if (ref.current) ref.current.src = obj.url;\n } catch {\n /* aborted (camera moved / unmounted) or render failed */\n }\n })();\n return () => {\n controller.abort();\n revoke?.();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- sourceKey is the\n // render identity; scale/parts/epoch are folded into it upstream. `painted`\n // runs the fetch skipped above even when the key comes out the same.\n }, [render, page.ref, sourceKey, painted]);\n\n return (\n <>\n <img\n ref={ref}\n alt=\"\"\n draggable={false}\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none',\n }}\n />\n {tiles && settings.tiles ? <TilePlane parts={parts} /> : null}\n </>\n );\n}\n\n/**\n * The sharp plane above the base — the plugin's retention-safe paint plan\n * as keyed <img>s. Each tile reports painted after its first presentation\n * opportunity (and its inverse on unmount), so retained coarser generations\n * release only when their replacement is truly compositable.\n *\n * Tiles are placed in view (CSS px) space directly — never in page points\n * under a scaled container. Blink quantizes layout lengths to 1/64 CSS px\n * before transforms apply, so a pt-space rect under a ×25 zoom transform\n * turns that harmless 1/64 into ±0.4px+ of per-tile misplacement — visible\n * seams and per-zoom-step letter shifts that grow with depth. In view\n * space the same quantization is a fixed ~1/64 CSS px at every zoom.\n */\nfunction TilePlane({ parts }: { parts: PicturePartProps }) {\n const page = usePage();\n const render = useCapability(RenderToken);\n // Demand: the host's live camera view (Stage) or whole-page (PageView).\n // Level settling lives in the plugin (its settle gate), pan stays live.\n const demand: PageViewDemand = page.getViewDemand?.() ?? {\n desiredDeviceWidth: page.transform.deviceWidth,\n };\n // This view's tile surface: state is per view × page, so a thumbnail\n // rail's never-engaging demand cannot disturb the main lens's plan (shared\n // state made the main view lose its tiles whenever a rail opened). The\n // handle is reference-stable per view and reference-counted — one\n // `dispose` per `createViewDemand`.\n const view = useMemo(() => render.createViewDemand(page.view), [render, page.view]);\n useEffect(() => () => view.dispose(), [view]);\n // Demand in, plan out: setting the demand is the one call that schedules\n // fetches (and re-plans); the read below is pure. Layout-timed so a camera\n // move re-plans before the browser paints this commit. The page's layers\n // are read now, not at render: one mounted in this commit has said what it\n // paints by the time layout effects run.\n useLayoutEffect(() => {\n const painted = pageLayersOf(page.ref).painted();\n const layers = pictureLayerOptionsOf(painted, parts, render.getLayerRights());\n view.setDemand(page.ref, demand, layers);\n });\n const plan = useSelector(RenderToken, () => view.getPlan(page.ref));\n // View unmounted its plane: stop in-flight fetches; resolved bytes stay cached.\n useEffect(() => () => view.release(page.ref), [view, page.ref]);\n if (plan.paint.length === 0) return null;\n const transform = page.transform;\n const viewScale = transform.viewScale;\n return (\n <div\n style={{\n position: 'absolute',\n left: 0,\n top: 0,\n width: transform.contentWidth,\n height: transform.contentHeight,\n pointerEvents: 'none',\n // Stacking contract: tile <img>s carry zIndex ranks (coarse under\n // fine) that exist only while generations are mixed — mid-zoom. This\n // container must be a stacking context so those ranks stay internal;\n // without it they join the Stage's context and paint above every\n // z-auto sibling (annotations, page chrome, menus) exactly while\n // zooming. The old scaled container created one implicitly via its\n // transform; `isolation` is the explicit, no-side-effect way.\n isolation: 'isolate',\n }}\n >\n {plan.paint.map((source) => (\n <TileImg\n key={source.key}\n source={source}\n view={{\n x: source.rect.x * viewScale,\n y: source.rect.y * viewScale,\n width: source.rect.width * viewScale,\n height: source.rect.height * viewScale,\n }}\n onPainted={() => view.markPainted(page.ref, source.key)}\n onUnpainted={() => view.markUnpainted(page.ref, source.key)}\n />\n ))}\n </div>\n );\n}\n\n/** One tile: geometry plus the shared browser image binding. The binding\n * hides the incomplete image, reports painted after its first presentation\n * opportunity, and inverts that report when the element leaves the DOM. */\nfunction TileImg({\n source,\n view,\n onPainted,\n onUnpainted,\n}: {\n source: TilePaintSource;\n /** Placement rect in view (CSS px) space. */\n view: { x: number; y: number; width: number; height: number };\n onPainted: () => void;\n onUnpainted: () => void;\n}) {\n const ref = useRef<HTMLImageElement>(null);\n useLayoutEffect(() => {\n if (!ref.current) return;\n return bindPaintedImage(ref.current, source.handle, { onPainted, onUnpainted });\n // eslint-disable-next-line react-hooks/exhaustive-deps -- the handle is\n // the bitmap's identity; the report callbacks carry stable values.\n }, [source.handle]);\n return (\n <img\n ref={ref}\n alt=\"\"\n draggable={false}\n onLoad={(event) => {\n // A raster whose intrinsic size disagrees with its rect would be\n // silently stretched into place — the stale-handle bug class.\n const naturalWidth = event.currentTarget.naturalWidth;\n if (naturalWidth > 0 && !warnedTileSize) {\n const expected = Math.round(source.rect.width * source.scale);\n if (Math.abs(naturalWidth - expected) > 1) {\n warnedTileSize = true;\n console.warn(\n `[render] tile bitmap ${naturalWidth}px wide does not match its rect ` +\n `(expected ~${expected}px) — stale raster identity? key=${source.key}`,\n );\n }\n }\n }}\n style={{\n position: 'absolute',\n left: view.x,\n top: view.y,\n width: view.width,\n height: view.height,\n zIndex: source.z,\n }}\n />\n );\n}\n"],"mappings":"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"}
|