@embedpdf/react 3.0.0-next.4 → 3.0.0-next.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/{AnnotationListSnapshot-_Z600IzD-DYoq1Iu1.d.cts → AnnotationListSnapshot-CHjkcIaL-CMHGL8-D.d.cts} +2 -2
  2. package/dist/{AnnotationListSnapshot-_Z600IzD-DYoq1Iu1.d.ts → AnnotationListSnapshot-CHjkcIaL-CMHGL8-D.d.ts} +2 -2
  3. package/dist/anchored.cjs +89 -0
  4. package/dist/anchored.cjs.map +1 -0
  5. package/dist/anchored.d.cts +45 -0
  6. package/dist/anchored.d.ts +45 -0
  7. package/dist/anchored.js +60 -0
  8. package/dist/anchored.js.map +1 -0
  9. package/dist/annotation-menu.cjs +33 -84
  10. package/dist/annotation-menu.cjs.map +1 -1
  11. package/dist/annotation-menu.d.cts +28 -2
  12. package/dist/annotation-menu.d.ts +28 -2
  13. package/dist/annotation-menu.js +27 -78
  14. package/dist/annotation-menu.js.map +1 -1
  15. package/dist/annotation.cjs +689 -16
  16. package/dist/annotation.cjs.map +1 -0
  17. package/dist/annotation.d.cts +119 -2
  18. package/dist/annotation.d.ts +119 -2
  19. package/dist/annotation.js +678 -3
  20. package/dist/annotation.js.map +1 -0
  21. package/dist/form.cjs +1 -1
  22. package/dist/form.d.cts +2 -2
  23. package/dist/form.d.ts +2 -2
  24. package/dist/form.js +1 -1
  25. package/dist/index.cjs +14 -4
  26. package/dist/index.d.cts +6 -5
  27. package/dist/index.d.ts +6 -5
  28. package/dist/index.js +5 -4
  29. package/dist/interaction.cjs +1 -1
  30. package/dist/interaction.js +1 -1
  31. package/dist/page-view.cjs +58 -36
  32. package/dist/page-view.cjs.map +1 -1
  33. package/dist/page-view.js +54 -34
  34. package/dist/page-view.js.map +1 -1
  35. package/dist/render.cjs +64 -48
  36. package/dist/render.cjs.map +1 -1
  37. package/dist/render.d.cts +6 -19
  38. package/dist/render.d.ts +6 -19
  39. package/dist/render.js +67 -50
  40. package/dist/render.js.map +1 -1
  41. package/dist/search.d.cts +1 -1
  42. package/dist/search.d.ts +1 -1
  43. package/dist/selection.cjs +59 -4
  44. package/dist/selection.cjs.map +1 -1
  45. package/dist/selection.d.cts +35 -2
  46. package/dist/selection.d.ts +35 -2
  47. package/dist/selection.js +52 -5
  48. package/dist/selection.js.map +1 -1
  49. package/dist/stage.cjs +33 -1
  50. package/dist/stage.cjs.map +1 -1
  51. package/dist/stage.js +33 -1
  52. package/dist/stage.js.map +1 -1
  53. package/package.json +27 -23
  54. package/dist/annotation-BTlcOjcK.js +0 -829
  55. package/dist/annotation-BTlcOjcK.js.map +0 -1
  56. package/dist/annotation-BvNPtcxN.d.cts +0 -173
  57. package/dist/annotation-CPbPvbKa.d.ts +0 -173
  58. package/dist/annotation-DVwYpWyP.cjs +0 -913
  59. package/dist/annotation-DVwYpWyP.cjs.map +0 -1
package/dist/page-view.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { DocumentScope, PageProvider, makePageContext, useActiveDocumentId, useKernel } from "./runtime.js";
2
+ import { ProjectorProvider } from "./anchored.js";
2
3
  import { jsx, jsxs } from "react/jsx-runtime";
3
4
  import { useMemo, useRef } from "react";
5
+ import { observeClientGeometry } from "@embedpdf/web";
4
6
  import { NO_FRAME, pageTransform } from "@embedpdf/core-geometry";
5
7
  //#region src/page-view.tsx
6
8
  /** A single page surface with NO Stage — same layers + rotation + chrome frame,
@@ -42,6 +44,21 @@ function PageView({ page, documentId, width = 240, pageFrame = NO_FRAME, childre
42
44
  pageFrame,
43
45
  transform
44
46
  ]);
47
+ const projector = useMemo(() => ({
48
+ space: "client",
49
+ toScreen: (p, rect) => p === ctx.pon && ref.current ? ctx.toClientRect(rect) : null,
50
+ toScreenPoint: (p, at) => p === ctx.pon && ref.current ? ctx.toClientPoint(at) : null,
51
+ viewEnv: (p) => p === ctx.pon ? {
52
+ scale: ctx.transform.viewScale,
53
+ rotation: ctx.transform.rotation,
54
+ zoom: ctx.transform.zoom
55
+ } : null
56
+ }), [ctx]);
57
+ const projectorBinding = useMemo(() => ({
58
+ projector,
59
+ revision: ctx,
60
+ subscribe: observeClientGeometry
61
+ }), [projector, ctx]);
45
62
  if (!docId || !meta || !base) return null;
46
63
  const t = transform;
47
64
  const outerW = t.viewWidth + pageFrame.left + pageFrame.right;
@@ -50,41 +67,44 @@ function PageView({ page, documentId, width = 240, pageFrame = NO_FRAME, childre
50
67
  const contentTop = pageFrame.top + (t.viewHeight - t.contentHeight) / 2;
51
68
  return /* @__PURE__ */ jsx(DocumentScope, {
52
69
  id: docId,
53
- children: /* @__PURE__ */ jsx("div", {
54
- style: {
55
- position: "relative",
56
- width: outerW,
57
- height: outerH,
58
- ...style
59
- },
60
- children: /* @__PURE__ */ jsxs(PageProvider, {
61
- value: ctx,
62
- children: [
63
- /* @__PURE__ */ jsx("div", { style: {
64
- position: "absolute",
65
- left: pageFrame.left,
66
- top: pageFrame.top,
67
- width: t.viewWidth,
68
- height: t.viewHeight,
69
- boxShadow: "0 6px 18px rgba(0,0,0,.18)"
70
- } }),
71
- /* @__PURE__ */ jsx("div", {
72
- ref,
73
- style: {
70
+ children: /* @__PURE__ */ jsx(ProjectorProvider, {
71
+ value: projectorBinding,
72
+ children: /* @__PURE__ */ jsx("div", {
73
+ style: {
74
+ position: "relative",
75
+ width: outerW,
76
+ height: outerH,
77
+ ...style
78
+ },
79
+ children: /* @__PURE__ */ jsxs(PageProvider, {
80
+ value: ctx,
81
+ children: [
82
+ /* @__PURE__ */ jsx("div", { style: {
74
83
  position: "absolute",
75
- left: contentLeft,
76
- top: contentTop,
77
- width: t.contentWidth,
78
- height: t.contentHeight,
79
- background: "#fff",
80
- transform: rotation ? `rotate(${rotation}deg)` : void 0,
81
- userSelect: "none",
82
- WebkitUserSelect: "none"
83
- },
84
- children
85
- }),
86
- pageChrome
87
- ]
84
+ left: pageFrame.left,
85
+ top: pageFrame.top,
86
+ width: t.viewWidth,
87
+ height: t.viewHeight,
88
+ boxShadow: "0 6px 18px rgba(0,0,0,.18)"
89
+ } }),
90
+ /* @__PURE__ */ jsx("div", {
91
+ ref,
92
+ style: {
93
+ position: "absolute",
94
+ left: contentLeft,
95
+ top: contentTop,
96
+ width: t.contentWidth,
97
+ height: t.contentHeight,
98
+ background: "#fff",
99
+ transform: rotation ? `rotate(${rotation}deg)` : void 0,
100
+ userSelect: "none",
101
+ WebkitUserSelect: "none"
102
+ },
103
+ children
104
+ }),
105
+ pageChrome
106
+ ]
107
+ })
88
108
  })
89
109
  })
90
110
  });
@@ -1 +1 @@
1
- {"version":3,"file":"page-view.js","names":[],"sources":["../src/page-view.tsx"],"sourcesContent":["/**\n * <PageView> — a single page surface with NO Stage.\n *\n * Same layers + rotation + chrome frame as a `<Stage>` page, but no\n * camera/scroll/zoom and, crucially, NO dependency on `@embedpdf/plugin-stage`.\n * It builds its own `PageTransform` from a target content width and shares the\n * exact `PageContext` seam, so every layer (RenderLayer, AnnotationLayer,\n * PageAnnotationMenu, …) works here identically.\n */\nimport * as React from 'react';\nimport { useMemo, useRef } from 'react';\nimport { NO_FRAME, pageTransform, type PageFrame } from '@embedpdf/core-geometry';\nimport {\n DocumentScope,\n makePageContext,\n PageProvider,\n useActiveDocumentId,\n useKernel,\n} from './runtime';\n\nexport interface PageViewProps {\n page: number;\n /** Which document to show. Defaults to the active document. */\n documentId?: string;\n /** Target width for the page CONTENT; the display box is the rotated footprint. */\n width?: number;\n /** Reserved chrome bands around the page (screen px) — same model as `<Stage>`. */\n pageFrame?: PageFrame;\n /** Page-space content (rotates with the page). */\n children: React.ReactNode;\n /** Box-space chrome (label, border, …) — never rotated. Mirrors `<Stage pageChrome>`. */\n pageChrome?: React.ReactNode;\n style?: React.CSSProperties;\n}\n\n/** A single page surface with NO Stage — same layers + rotation + chrome frame,\n * no camera/scroll/zoom. */\nexport function PageView({\n page,\n documentId,\n width = 240,\n pageFrame = NO_FRAME,\n children,\n pageChrome,\n style,\n}: PageViewProps) {\n const kernel = useKernel();\n const active = useActiveDocumentId();\n const ref = useRef<HTMLDivElement>(null);\n const docId = documentId ?? active;\n const meta = docId ? kernel.getState().core.documents[docId] : undefined;\n const base = meta?.pages[page];\n const pon = base?.pageObjectNumber ?? page + 1;\n const rotation = base?.rotation ?? 0;\n const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n // Standalone (no Stage/camera): build the page's transform from the target\n // content `width` directly. `scale` is view px per point = width / pageWidthPts.\n const transform = useMemo(\n () =>\n pageTransform({\n pageSize: base\n ? { width: base.size.width, height: base.size.height }\n : { width: 1, height: 1 },\n rotation,\n scale: base ? width / base.size.width : 1,\n // Physical 100% on the web: 1pt = 96/72 CSS px, times the page's\n // /UserUnit — so `transform.zoom` is meaningful even without a Stage\n // (a thumbnail-sized PageView reads as zoomed out, as it should).\n baseScale: (96 / 72) * (base?.userUnit ?? 1),\n dpr,\n }),\n [base?.size.width, base?.size.height, base?.userUnit, rotation, width, dpr],\n );\n const ctx = useMemo(\n () =>\n makePageContext(docId ?? '', pon, page, pageFrame, transform, () =>\n ref.current!.getBoundingClientRect(),\n ),\n [docId, pon, page, pageFrame, transform],\n );\n if (!docId || !meta || !base) return null;\n const t = transform;\n const outerW = t.viewWidth + pageFrame.left + pageFrame.right;\n const outerH = t.viewHeight + pageFrame.top + pageFrame.bottom;\n const contentLeft = pageFrame.left + (t.viewWidth - t.contentWidth) / 2;\n const contentTop = pageFrame.top + (t.viewHeight - t.contentHeight) / 2;\n return (\n <DocumentScope id={docId}>\n <div style={{ position: 'relative', width: outerW, height: outerH, ...style }}>\n <PageProvider value={ctx}>\n {/* drop shadow ONLY — transparent, axis-aligned, can't leak behind the bitmap */}\n <div\n style={{\n position: 'absolute',\n left: pageFrame.left,\n top: pageFrame.top,\n width: t.viewWidth,\n height: t.viewHeight,\n boxShadow: '0 6px 18px rgba(0,0,0,.18)',\n }}\n />\n {/* white backing + content as ONE box; rotation 0 carries no transform */}\n <div\n ref={ref}\n style={{\n position: 'absolute',\n left: contentLeft,\n top: contentTop,\n width: t.contentWidth,\n height: t.contentHeight,\n background: '#fff',\n transform: rotation ? `rotate(${rotation}deg)` : undefined,\n userSelect: 'none',\n WebkitUserSelect: 'none',\n }}\n >\n {children}\n </div>\n {pageChrome}\n </PageProvider>\n </div>\n </DocumentScope>\n );\n}\n"],"mappings":";;;;;;;AAqCA,SAAgB,SAAS,EACvB,MACA,YACA,QAAQ,KACR,YAAY,UACZ,UACA,YACA,SACgB;CAChB,MAAM,SAAS,UAAU;CACzB,MAAM,SAAS,oBAAoB;CACnC,MAAM,MAAM,OAAuB,IAAI;CACvC,MAAM,QAAQ,cAAc;CAC5B,MAAM,OAAO,QAAQ,OAAO,SAAS,CAAC,CAAC,KAAK,UAAU,SAAS,KAAA;CAC/D,MAAM,OAAO,MAAM,MAAM;CACzB,MAAM,MAAM,MAAM,oBAAoB,OAAO;CAC7C,MAAM,WAAW,MAAM,YAAY;CACnC,MAAM,MAAM,OAAO,WAAW,cAAc,OAAO,oBAAoB,IAAI;CAG3E,MAAM,YAAY,cAEd,cAAc;EACZ,UAAU,OACN;GAAE,OAAO,KAAK,KAAK;GAAO,QAAQ,KAAK,KAAK;EAAO,IACnD;GAAE,OAAO;GAAG,QAAQ;EAAE;EAC1B;EACA,OAAO,OAAO,QAAQ,KAAK,KAAK,QAAQ;EAIxC,WAAY,KAAK,MAAO,MAAM,YAAY;EAC1C;CACF,CAAC,GACH;EAAC,MAAM,KAAK;EAAO,MAAM,KAAK;EAAQ,MAAM;EAAU;EAAU;EAAO;CAAG,CAC5E;CACA,MAAM,MAAM,cAER,gBAAgB,SAAS,IAAI,KAAK,MAAM,WAAW,iBACjD,IAAI,QAAS,sBAAsB,CACrC,GACF;EAAC;EAAO;EAAK;EAAM;EAAW;CAAS,CACzC;CACA,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,OAAO;CACrC,MAAM,IAAI;CACV,MAAM,SAAS,EAAE,YAAY,UAAU,OAAO,UAAU;CACxD,MAAM,SAAS,EAAE,aAAa,UAAU,MAAM,UAAU;CACxD,MAAM,cAAc,UAAU,QAAQ,EAAE,YAAY,EAAE,gBAAgB;CACtE,MAAM,aAAa,UAAU,OAAO,EAAE,aAAa,EAAE,iBAAiB;CACtE,OACE,oBAAC,eAAD;EAAe,IAAI;EACjB,UAAA,oBAAC,OAAD;GAAK,OAAO;IAAE,UAAU;IAAY,OAAO;IAAQ,QAAQ;IAAQ,GAAG;GAAM;GAC1E,UAAA,qBAAC,cAAD;IAAc,OAAO;IAArB,UAAA;KAEE,oBAAC,OAAD,EACE,OAAO;MACL,UAAU;MACV,MAAM,UAAU;MAChB,KAAK,UAAU;MACf,OAAO,EAAE;MACT,QAAQ,EAAE;MACV,WAAW;KACb,EACD,CAAA;KAED,oBAAC,OAAD;MACO;MACL,OAAO;OACL,UAAU;OACV,MAAM;OACN,KAAK;OACL,OAAO,EAAE;OACT,QAAQ,EAAE;OACV,YAAY;OACZ,WAAW,WAAW,UAAU,SAAS,QAAQ,KAAA;OACjD,YAAY;OACZ,kBAAkB;MACpB;MAEC;KACE,CAAA;KACJ;IACW;;EACX,CAAA;CACQ,CAAA;AAEnB"}
1
+ {"version":3,"file":"page-view.js","names":[],"sources":["../src/page-view.tsx"],"sourcesContent":["/**\n * <PageView> — a single page surface with NO Stage.\n *\n * Same layers + rotation + chrome frame as a `<Stage>` page, but no\n * camera/scroll/zoom and, crucially, NO dependency on `@embedpdf/plugin-stage`.\n * It builds its own `PageTransform` from a target content width and shares the\n * exact `PageContext` seam, so every layer (RenderLayer, AnnotationLayer, …)\n * works here identically — and it provides the measured `ViewProjector`, so\n * anchored UI (`<AnnotationMenu>`, `<SelectionMenu>`) works here too, portalled\n * and clipping-immune.\n */\nimport * as React from 'react';\nimport { useMemo, useRef } from 'react';\nimport { NO_FRAME, pageTransform, type PageFrame } from '@embedpdf/core-geometry';\nimport { observeClientGeometry } from '@embedpdf/web';\nimport { ProjectorProvider, type ProjectorBinding, type ViewProjector } from './anchored';\nimport {\n DocumentScope,\n makePageContext,\n PageProvider,\n useActiveDocumentId,\n useKernel,\n} from './runtime';\n\nexport interface PageViewProps {\n page: number;\n /** Which document to show. Defaults to the active document. */\n documentId?: string;\n /** Target width for the page CONTENT; the display box is the rotated footprint. */\n width?: number;\n /** Reserved chrome bands around the page (screen px) — same model as `<Stage>`. */\n pageFrame?: PageFrame;\n /** Page-space content (rotates with the page). */\n children: React.ReactNode;\n /** Box-space chrome (label, border, …) — never rotated. Mirrors `<Stage pageChrome>`. */\n pageChrome?: React.ReactNode;\n style?: React.CSSProperties;\n}\n\n/** A single page surface with NO Stage — same layers + rotation + chrome frame,\n * no camera/scroll/zoom. */\nexport function PageView({\n page,\n documentId,\n width = 240,\n pageFrame = NO_FRAME,\n children,\n pageChrome,\n style,\n}: PageViewProps) {\n const kernel = useKernel();\n const active = useActiveDocumentId();\n const ref = useRef<HTMLDivElement>(null);\n const docId = documentId ?? active;\n const meta = docId ? kernel.getState().core.documents[docId] : undefined;\n const base = meta?.pages[page];\n const pon = base?.pageObjectNumber ?? page + 1;\n const rotation = base?.rotation ?? 0;\n const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n // Standalone (no Stage/camera): build the page's transform from the target\n // content `width` directly. `scale` is view px per point = width / pageWidthPts.\n const transform = useMemo(\n () =>\n pageTransform({\n pageSize: base\n ? { width: base.size.width, height: base.size.height }\n : { width: 1, height: 1 },\n rotation,\n scale: base ? width / base.size.width : 1,\n // Physical 100% on the web: 1pt = 96/72 CSS px, times the page's\n // /UserUnit — so `transform.zoom` is meaningful even without a Stage\n // (a thumbnail-sized PageView reads as zoomed out, as it should).\n baseScale: (96 / 72) * (base?.userUnit ?? 1),\n dpr,\n }),\n [base?.size.width, base?.size.height, base?.userUnit, rotation, width, dpr],\n );\n const ctx = useMemo(\n () =>\n makePageContext(docId ?? '', pon, page, pageFrame, transform, () =>\n ref.current!.getBoundingClientRect(),\n ),\n [docId, pon, page, pageFrame, transform],\n );\n // The PageView's ViewProjector: no camera, so anchored UI positions by\n // MEASURING the DOM (client space → portal + position:fixed, immune to\n // ancestor overflow clipping). `toScreen` answers null until the page\n // element has committed — <Anchored> forces one post-commit pass to pick\n // it up. The binding's revision covers state-driven changes (a new\n // transform/page); `observeClientGeometry` covers the genuinely\n // browser-driven ones (document scroll, window resize) that no state\n // change announces.\n const projector = useMemo<ViewProjector>(\n () => ({\n space: 'client',\n toScreen: (p, rect) => (p === ctx.pon && ref.current ? ctx.toClientRect(rect) : null),\n toScreenPoint: (p, at) => (p === ctx.pon && ref.current ? ctx.toClientPoint(at) : null),\n viewEnv: (p) =>\n p === ctx.pon\n ? {\n scale: ctx.transform.viewScale,\n rotation: ctx.transform.rotation,\n zoom: ctx.transform.zoom,\n }\n : null,\n }),\n [ctx],\n );\n const projectorBinding = useMemo<ProjectorBinding>(\n () => ({ projector, revision: ctx, subscribe: observeClientGeometry }),\n [projector, ctx],\n );\n if (!docId || !meta || !base) return null;\n const t = transform;\n const outerW = t.viewWidth + pageFrame.left + pageFrame.right;\n const outerH = t.viewHeight + pageFrame.top + pageFrame.bottom;\n const contentLeft = pageFrame.left + (t.viewWidth - t.contentWidth) / 2;\n const contentTop = pageFrame.top + (t.viewHeight - t.contentHeight) / 2;\n return (\n <DocumentScope id={docId}>\n <ProjectorProvider value={projectorBinding}>\n <div style={{ position: 'relative', width: outerW, height: outerH, ...style }}>\n <PageProvider value={ctx}>\n {/* drop shadow ONLY — transparent, axis-aligned, can't leak behind the bitmap */}\n <div\n style={{\n position: 'absolute',\n left: pageFrame.left,\n top: pageFrame.top,\n width: t.viewWidth,\n height: t.viewHeight,\n boxShadow: '0 6px 18px rgba(0,0,0,.18)',\n }}\n />\n {/* white backing + content as ONE box; rotation 0 carries no transform */}\n <div\n ref={ref}\n style={{\n position: 'absolute',\n left: contentLeft,\n top: contentTop,\n width: t.contentWidth,\n height: t.contentHeight,\n background: '#fff',\n transform: rotation ? `rotate(${rotation}deg)` : undefined,\n userSelect: 'none',\n WebkitUserSelect: 'none',\n }}\n >\n {children}\n </div>\n {pageChrome}\n </PageProvider>\n </div>\n </ProjectorProvider>\n </DocumentScope>\n );\n}\n"],"mappings":";;;;;;;;;AAyCA,SAAgB,SAAS,EACvB,MACA,YACA,QAAQ,KACR,YAAY,UACZ,UACA,YACA,SACgB;CAChB,MAAM,SAAS,UAAU;CACzB,MAAM,SAAS,oBAAoB;CACnC,MAAM,MAAM,OAAuB,IAAI;CACvC,MAAM,QAAQ,cAAc;CAC5B,MAAM,OAAO,QAAQ,OAAO,SAAS,CAAC,CAAC,KAAK,UAAU,SAAS,KAAA;CAC/D,MAAM,OAAO,MAAM,MAAM;CACzB,MAAM,MAAM,MAAM,oBAAoB,OAAO;CAC7C,MAAM,WAAW,MAAM,YAAY;CACnC,MAAM,MAAM,OAAO,WAAW,cAAc,OAAO,oBAAoB,IAAI;CAG3E,MAAM,YAAY,cAEd,cAAc;EACZ,UAAU,OACN;GAAE,OAAO,KAAK,KAAK;GAAO,QAAQ,KAAK,KAAK;EAAO,IACnD;GAAE,OAAO;GAAG,QAAQ;EAAE;EAC1B;EACA,OAAO,OAAO,QAAQ,KAAK,KAAK,QAAQ;EAIxC,WAAY,KAAK,MAAO,MAAM,YAAY;EAC1C;CACF,CAAC,GACH;EAAC,MAAM,KAAK;EAAO,MAAM,KAAK;EAAQ,MAAM;EAAU;EAAU;EAAO;CAAG,CAC5E;CACA,MAAM,MAAM,cAER,gBAAgB,SAAS,IAAI,KAAK,MAAM,WAAW,iBACjD,IAAI,QAAS,sBAAsB,CACrC,GACF;EAAC;EAAO;EAAK;EAAM;EAAW;CAAS,CACzC;CASA,MAAM,YAAY,eACT;EACL,OAAO;EACP,WAAW,GAAG,SAAU,MAAM,IAAI,OAAO,IAAI,UAAU,IAAI,aAAa,IAAI,IAAI;EAChF,gBAAgB,GAAG,OAAQ,MAAM,IAAI,OAAO,IAAI,UAAU,IAAI,cAAc,EAAE,IAAI;EAClF,UAAU,MACR,MAAM,IAAI,MACN;GACE,OAAO,IAAI,UAAU;GACrB,UAAU,IAAI,UAAU;GACxB,MAAM,IAAI,UAAU;EACtB,IACA;CACR,IACA,CAAC,GAAG,CACN;CACA,MAAM,mBAAmB,eAChB;EAAE;EAAW,UAAU;EAAK,WAAW;CAAsB,IACpE,CAAC,WAAW,GAAG,CACjB;CACA,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,OAAO;CACrC,MAAM,IAAI;CACV,MAAM,SAAS,EAAE,YAAY,UAAU,OAAO,UAAU;CACxD,MAAM,SAAS,EAAE,aAAa,UAAU,MAAM,UAAU;CACxD,MAAM,cAAc,UAAU,QAAQ,EAAE,YAAY,EAAE,gBAAgB;CACtE,MAAM,aAAa,UAAU,OAAO,EAAE,aAAa,EAAE,iBAAiB;CACtE,OACE,oBAAC,eAAD;EAAe,IAAI;EACjB,UAAA,oBAAC,mBAAD;GAAmB,OAAO;GAC1B,UAAA,oBAAC,OAAD;IAAK,OAAO;KAAE,UAAU;KAAY,OAAO;KAAQ,QAAQ;KAAQ,GAAG;IAAM;IAC1E,UAAA,qBAAC,cAAD;KAAc,OAAO;KAArB,UAAA;MAEE,oBAAC,OAAD,EACE,OAAO;OACL,UAAU;OACV,MAAM,UAAU;OAChB,KAAK,UAAU;OACf,OAAO,EAAE;OACT,QAAQ,EAAE;OACV,WAAW;MACb,EACD,CAAA;MAED,oBAAC,OAAD;OACO;OACL,OAAO;QACL,UAAU;QACV,MAAM;QACN,KAAK;QACL,OAAO,EAAE;QACT,QAAQ,EAAE;QACV,YAAY;QACZ,WAAW,WAAW,UAAU,SAAS,QAAQ,KAAA;QACjD,YAAY;QACZ,kBAAkB;OACpB;OAEC;MACE,CAAA;MACJ;KACW;;GACX,CAAA;EACc,CAAA;CACN,CAAA;AAEnB"}
package/dist/render.cjs CHANGED
@@ -4,11 +4,14 @@ const require_runtime = require("./runtime.cjs");
4
4
  let react_jsx_runtime = require("react/jsx-runtime");
5
5
  let react = require("react");
6
6
  react = require_rolldown_runtime.__toESM(react, 1);
7
+ let _embedpdf_web = require("@embedpdf/web");
7
8
  let _embedpdf_plugin_render = require("@embedpdf/plugin-render");
8
9
  //#region src/render.tsx
9
- function RenderLayer({ annotations = true } = {}) {
10
+ let warnedTileSize = false;
11
+ function RenderLayer({ annotations = true, tiles = true } = {}) {
10
12
  const page = require_runtime.usePage();
11
13
  const render = require_runtime.useCapability(_embedpdf_plugin_render.RenderToken);
14
+ const settings = render.paintSettings();
12
15
  const ref = (0, react.useRef)(null);
13
16
  const sourceKey = require_runtime.useSelector(_embedpdf_plugin_render.RenderToken, (c) => c.renderSourceKey(page.pon, {
14
17
  scale: page.transform.renderScale,
@@ -41,7 +44,7 @@ function RenderLayer({ annotations = true } = {}) {
41
44
  page.pon,
42
45
  sourceKey
43
46
  ]);
44
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
47
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
45
48
  ref,
46
49
  alt: "",
47
50
  draggable: false,
@@ -52,19 +55,26 @@ function RenderLayer({ annotations = true } = {}) {
52
55
  height: "100%",
53
56
  pointerEvents: "none"
54
57
  }
55
- });
58
+ }), tiles && settings.tiles ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TilePlane, {
59
+ annotations,
60
+ fadeMs: settings.fadeMs
61
+ }) : null] });
56
62
  }
57
63
  /**
58
- * TileLayer — the sharp plane above the base rung.
64
+ * The sharp plane above the base — the plugin's retention-safe paint plan
65
+ * as keyed <img>s. Each tile reports painted after its first presentation
66
+ * opportunity (and its inverse on unmount), so retained coarser generations
67
+ * release only when their replacement is truly compositable.
59
68
  *
60
- * Mounting it is the per-lens OPT-IN; whether it spends anything is demand
61
- * arithmetic (a thumbnail rail's demand never engages). The paint list is the
62
- * plugin's retention-safe plan: keyed <img>s inside ONE container that carries
63
- * the page scale as a single transform (one rounding context — no tile seams),
64
- * with `onLoad` reporting the decode boundary back so retained coarser
65
- * generations release only when their replacement is truly on screen.
69
+ * Tiles are placed in VIEW (CSS px) space directly — never in page points
70
+ * under a scaled container. Blink quantizes layout lengths to 1/64 CSS px
71
+ * BEFORE transforms apply, so a pt-space rect under a ×25 zoom transform
72
+ * turns that harmless 1/64 into ±0.4px+ of per-tile misplacement — visible
73
+ * seams and per-zoom-step letter shifts that grow with depth. In view
74
+ * 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.)
66
76
  */
67
- function TileLayer({ annotations = true } = {}) {
77
+ function TilePlane({ annotations, fadeMs }) {
68
78
  const page = require_runtime.usePage();
69
79
  const render = require_runtime.useCapability(_embedpdf_plugin_render.RenderToken);
70
80
  const demand = page.getViewDemand?.() ?? { desiredDeviceWidth: page.transform.deviceWidth };
@@ -72,64 +82,70 @@ function TileLayer({ annotations = true } = {}) {
72
82
  (0, react.useEffect)(() => () => render.releaseTiles(page.pon), [render, page.pon]);
73
83
  if (plan.paint.length === 0) return null;
74
84
  const t = page.transform;
75
- const scale = t.viewScale;
76
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
85
+ const s = t.viewScale;
86
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
77
87
  style: {
78
88
  position: "absolute",
79
89
  left: 0,
80
90
  top: 0,
81
- width: t.contentWidth / scale,
82
- height: t.contentHeight / scale,
83
- transform: `scale(${scale})`,
84
- transformOrigin: "0 0",
85
- pointerEvents: "none"
91
+ width: t.contentWidth,
92
+ height: t.contentHeight,
93
+ pointerEvents: "none",
94
+ isolation: "isolate"
86
95
  },
87
- children: plan.paint.map((source) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TileImg, {
96
+ children: [fadeMs > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: `@keyframes epdf-tile-in { from { opacity: 0 } to { opacity: 1 } }` }) : null, plan.paint.map((source) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TileImg, {
88
97
  source,
89
- onPainted: () => render.tilePainted(page.pon, source.key)
90
- }, source.key))
98
+ view: {
99
+ x: source.rect.x * s,
100
+ y: source.rect.y * s,
101
+ width: source.rect.width * s,
102
+ height: source.rect.height * s
103
+ },
104
+ fadeMs,
105
+ onPainted: () => render.tilePainted(page.pon, source.key),
106
+ onUnpainted: () => render.tileUnpainted(page.pon, source.key)
107
+ }, source.key))]
91
108
  });
92
109
  }
93
- /** One tile: object-URL lifecycle + the painted (decode-boundary) report. */
94
- function TileImg({ source, onPainted }) {
110
+ /** One tile: geometry plus the shared browser image binding. The binding
111
+ * hides the incomplete image, reports painted after its first presentation
112
+ * opportunity, and inverts that report when the element leaves the DOM. */
113
+ function TileImg({ source, view, fadeMs, onPainted, onUnpainted }) {
95
114
  const ref = (0, react.useRef)(null);
96
- (0, react.useEffect)(() => {
97
- const controller = new AbortController();
98
- let revoke;
99
- (async () => {
100
- try {
101
- const obj = await source.handle.objectUrl(controller.signal);
102
- if (controller.signal.aborted) {
103
- obj.revoke();
104
- return;
105
- }
106
- revoke = obj.revoke;
107
- if (ref.current) ref.current.src = obj.url;
108
- } catch {}
109
- })();
110
- return () => {
111
- controller.abort();
112
- revoke?.();
113
- };
115
+ (0, react.useLayoutEffect)(() => {
116
+ if (!ref.current) return;
117
+ return (0, _embedpdf_web.bindPaintedImage)(ref.current, source.handle, {
118
+ onPainted,
119
+ onUnpainted
120
+ });
114
121
  }, [source.handle]);
115
122
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
116
123
  ref,
117
124
  alt: "",
118
125
  draggable: false,
119
- onLoad: onPainted,
126
+ onLoad: (e) => {
127
+ const w = e.currentTarget.naturalWidth;
128
+ if (w > 0 && !warnedTileSize) {
129
+ const expected = Math.round(source.rect.width * source.scale);
130
+ if (Math.abs(w - expected) > 1) {
131
+ warnedTileSize = true;
132
+ console.warn(`[render] tile bitmap ${w}px wide does not match its rect (expected ~${expected}px) — stale raster identity? key=${source.key}`);
133
+ }
134
+ }
135
+ },
120
136
  style: {
121
137
  position: "absolute",
122
- left: source.rect.x,
123
- top: source.rect.y,
124
- width: source.rect.width,
125
- height: source.rect.height,
126
- zIndex: source.z
138
+ left: view.x,
139
+ top: view.y,
140
+ width: view.width,
141
+ height: view.height,
142
+ zIndex: source.z,
143
+ ...fadeMs > 0 ? { animation: `epdf-tile-in ${fadeMs}ms ease-out` } : null
127
144
  }
128
145
  });
129
146
  }
130
147
  //#endregion
131
148
  exports.RenderLayer = RenderLayer;
132
- exports.TileLayer = TileLayer;
133
149
  Object.keys(_embedpdf_plugin_render).forEach(function(k) {
134
150
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
135
151
  enumerable: true,
@@ -1 +1 @@
1
- {"version":3,"file":"render.cjs","names":["usePage","useCapability","RenderToken","useSelector"],"sources":["../src/render.tsx"],"sourcesContent":["/**\n * RenderLayer + TileLayer — the React views of @embedpdf/plugin-render.\n *\n * RenderLayer paints the BASE rung to an <img> from the engine's ENCODED\n * image() (identical for local & cloud). TileLayer paints the tile paint-plan\n * ABOVE it when the deployment lattice caps the base below the demand — mount\n * it per lens (the main stage yes, a thumbnail rail no); where the demand is\n * small it engages nothing. Both are dumb painters: every decision (policy\n * conformance, want sets, retention, release) is plugin-render's.\n */\n\n// One-line-per-feature: registration travels with the UI.\nexport * from '@embedpdf/plugin-render';\nimport * as React from 'react';\nimport { useEffect, useRef } from 'react';\nimport { RenderToken } from '@embedpdf/plugin-render';\nimport type { PageViewDemand, TilePaintSource } from '@embedpdf/plugin-render';\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\nexport function RenderLayer({ annotations = true }: RenderLayerProps = {}) {\n const page = usePage();\n const render = useCapability(RenderToken);\n const ref = useRef<HTMLImageElement>(null);\n // ONE dependency: the raster's canonical identity — conformed viewport +\n // annotations flag + epoch. Inside a lattice\n // rung, zoom changes don't move it: no refetch, no DOM churn — the stage's\n // CSS transform does the scaling. It changes exactly at rung crossings and\n // on CONFIRMED mutations (epoch bumps at commit, never mid-gesture). Under\n // `continuous` it embeds the exact scale — v2 behavior byte-for-byte. The\n // policy behind it is a document fact the kernel materialized before\n // publish, so the key is always computable.\n const sourceKey = useSelector(RenderToken, (c) =>\n c.renderSourceKey(page.pon, {\n scale: page.transform.renderScale,\n includeAnnotations: annotations,\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 policy (lattice → ladder width,\n // continuous → this exact scale) and collapses same-key asks in its\n // raster store. A stale-closure scale is harmless by construction:\n // any scale in this rung 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 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 return (\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 );\n}\n\nexport interface TileLayerProps {\n /**\n * Bake annotations into tile bitmaps (default true). Pass false when an\n * <AnnotationLayer> owns annotation rendering — the composition that keeps\n * tile artifacts annotation-version-free (CDN-stable across edits).\n */\n annotations?: boolean;\n}\n\n/**\n * TileLayer — the sharp plane above the base rung.\n *\n * Mounting it is the per-lens OPT-IN; whether it spends anything is demand\n * arithmetic (a thumbnail rail's demand never engages). The paint list is the\n * plugin's retention-safe plan: keyed <img>s inside ONE container that carries\n * the page scale as a single transform (one rounding context — no tile seams),\n * with `onLoad` reporting the decode boundary back so retained coarser\n * generations release only when their replacement is truly on screen.\n */\nexport function TileLayer({ annotations = true }: TileLayerProps = {}) {\n const page = usePage();\n const render = useCapability(RenderToken);\n // Demand: the host's live camera view (Stage) or whole-page (PageView).\n const demand: PageViewDemand = page.getViewDemand?.() ?? {\n desiredDeviceWidth: page.transform.deviceWidth,\n };\n const plan = useSelector(RenderToken, (c) =>\n c.tilePlan(page.pon, demand, { includeAnnotations: annotations }),\n );\n // Lens unmounted: stop in-flight tile fetches; resolved bytes stay cached.\n useEffect(() => () => render.releaseTiles(page.pon), [render, page.pon]);\n if (plan.paint.length === 0) return null;\n const t = page.transform;\n const scale = t.viewScale;\n return (\n <div\n style={{\n position: 'absolute',\n left: 0,\n top: 0,\n width: t.contentWidth / scale,\n height: t.contentHeight / scale,\n transform: `scale(${scale})`,\n transformOrigin: '0 0',\n pointerEvents: 'none',\n }}\n >\n {plan.paint.map((source) => (\n <TileImg\n key={source.key}\n source={source}\n onPainted={() => render.tilePainted(page.pon, source.key)}\n />\n ))}\n </div>\n );\n}\n\n/** One tile: object-URL lifecycle + the painted (decode-boundary) report. */\nfunction TileImg({ source, onPainted }: { source: TilePaintSource; onPainted: () => void }) {\n const ref = useRef<HTMLImageElement>(null);\n useEffect(() => {\n const controller = new AbortController();\n let revoke: (() => void) | undefined;\n (async () => {\n try {\n const obj = await source.handle.objectUrl(controller.signal);\n if (controller.signal.aborted) {\n obj.revoke();\n return;\n }\n revoke = obj.revoke;\n if (ref.current) ref.current.src = obj.url;\n } catch {\n /* aborted (plan moved on) */\n }\n })();\n return () => {\n controller.abort();\n revoke?.();\n };\n }, [source.handle]);\n return (\n <img\n ref={ref}\n alt=\"\"\n draggable={false}\n onLoad={onPainted}\n style={{\n position: 'absolute',\n left: source.rect.x,\n top: source.rect.y,\n width: source.rect.width,\n height: source.rect.height,\n zIndex: source.z,\n }}\n />\n );\n}\n"],"mappings":"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"}
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 const plan = useSelector(RenderToken, (c) =>\n c.tilePlan(page.pon, demand, { includeAnnotations: annotations }),\n );\n // Lens unmounted: stop in-flight tile fetches; resolved bytes stay cached.\n useEffect(() => () => render.releaseTiles(page.pon), [render, 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={() => render.tilePainted(page.pon, source.key)}\n onUnpainted={() => render.tileUnpainted(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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"}
package/dist/render.d.cts CHANGED
@@ -6,27 +6,14 @@ interface RenderLayerProps {
6
6
  * <AnnotationLayer> owns annotation rendering, so they aren't drawn twice.
7
7
  */
8
8
  annotations?: boolean;
9
- }
10
- declare function RenderLayer({ annotations }?: RenderLayerProps): import("react/jsx-runtime").JSX.Element;
11
- interface TileLayerProps {
12
9
  /**
13
- * Bake annotations into tile bitmaps (default true). Pass false when an
14
- * <AnnotationLayer> owns annotation rendering — the composition that keeps
15
- * tile artifacts annotation-version-free (CDN-stable across edits).
10
+ * Mount the tile plane (default true). Whether it SPENDS anything is
11
+ * demand arithmetic — leave it on; pass false only for a lens that must
12
+ * never tile even under deep zoom.
16
13
  */
17
- annotations?: boolean;
14
+ tiles?: boolean;
18
15
  }
19
- /**
20
- * TileLayer — the sharp plane above the base rung.
21
- *
22
- * Mounting it is the per-lens OPT-IN; whether it spends anything is demand
23
- * arithmetic (a thumbnail rail's demand never engages). The paint list is the
24
- * plugin's retention-safe plan: keyed <img>s inside ONE container that carries
25
- * the page scale as a single transform (one rounding context — no tile seams),
26
- * with `onLoad` reporting the decode boundary back so retained coarser
27
- * generations release only when their replacement is truly on screen.
28
- */
29
- declare function TileLayer({ annotations }?: TileLayerProps): import("react/jsx-runtime").JSX.Element | null;
16
+ declare function RenderLayer({ annotations, tiles }?: RenderLayerProps): import("react/jsx-runtime").JSX.Element;
30
17
  //#endregion
31
- export { RenderLayer, RenderLayerProps, TileLayer, TileLayerProps };
18
+ export { RenderLayer, RenderLayerProps };
32
19
  //# sourceMappingURL=render.d.cts.map
package/dist/render.d.ts CHANGED
@@ -6,27 +6,14 @@ interface RenderLayerProps {
6
6
  * <AnnotationLayer> owns annotation rendering, so they aren't drawn twice.
7
7
  */
8
8
  annotations?: boolean;
9
- }
10
- declare function RenderLayer({ annotations }?: RenderLayerProps): import("react/jsx-runtime").JSX.Element;
11
- interface TileLayerProps {
12
9
  /**
13
- * Bake annotations into tile bitmaps (default true). Pass false when an
14
- * <AnnotationLayer> owns annotation rendering — the composition that keeps
15
- * tile artifacts annotation-version-free (CDN-stable across edits).
10
+ * Mount the tile plane (default true). Whether it SPENDS anything is
11
+ * demand arithmetic — leave it on; pass false only for a lens that must
12
+ * never tile even under deep zoom.
16
13
  */
17
- annotations?: boolean;
14
+ tiles?: boolean;
18
15
  }
19
- /**
20
- * TileLayer — the sharp plane above the base rung.
21
- *
22
- * Mounting it is the per-lens OPT-IN; whether it spends anything is demand
23
- * arithmetic (a thumbnail rail's demand never engages). The paint list is the
24
- * plugin's retention-safe plan: keyed <img>s inside ONE container that carries
25
- * the page scale as a single transform (one rounding context — no tile seams),
26
- * with `onLoad` reporting the decode boundary back so retained coarser
27
- * generations release only when their replacement is truly on screen.
28
- */
29
- declare function TileLayer({ annotations }?: TileLayerProps): import("react/jsx-runtime").JSX.Element | null;
16
+ declare function RenderLayer({ annotations, tiles }?: RenderLayerProps): import("react/jsx-runtime").JSX.Element;
30
17
  //#endregion
31
- export { RenderLayer, RenderLayerProps, TileLayer, TileLayerProps };
18
+ export { RenderLayer, RenderLayerProps };
32
19
  //# sourceMappingURL=render.d.ts.map
package/dist/render.js CHANGED
@@ -1,12 +1,15 @@
1
1
  import { useCapability, usePage, useSelector } from "./runtime.js";
2
- import { jsx } from "react/jsx-runtime";
3
- import { useEffect, useRef } from "react";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useLayoutEffect, useRef } from "react";
4
+ import { bindPaintedImage } from "@embedpdf/web";
4
5
  import { RenderToken } from "@embedpdf/plugin-render";
5
6
  export * from "@embedpdf/plugin-render";
6
7
  //#region src/render.tsx
7
- function RenderLayer({ annotations = true } = {}) {
8
+ let warnedTileSize = false;
9
+ function RenderLayer({ annotations = true, tiles = true } = {}) {
8
10
  const page = usePage();
9
11
  const render = useCapability(RenderToken);
12
+ const settings = render.paintSettings();
10
13
  const ref = useRef(null);
11
14
  const sourceKey = useSelector(RenderToken, (c) => c.renderSourceKey(page.pon, {
12
15
  scale: page.transform.renderScale,
@@ -39,7 +42,7 @@ function RenderLayer({ annotations = true } = {}) {
39
42
  page.pon,
40
43
  sourceKey
41
44
  ]);
42
- return /* @__PURE__ */ jsx("img", {
45
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("img", {
43
46
  ref,
44
47
  alt: "",
45
48
  draggable: false,
@@ -50,19 +53,26 @@ function RenderLayer({ annotations = true } = {}) {
50
53
  height: "100%",
51
54
  pointerEvents: "none"
52
55
  }
53
- });
56
+ }), tiles && settings.tiles ? /* @__PURE__ */ jsx(TilePlane, {
57
+ annotations,
58
+ fadeMs: settings.fadeMs
59
+ }) : null] });
54
60
  }
55
61
  /**
56
- * TileLayer — the sharp plane above the base rung.
62
+ * The sharp plane above the base — the plugin's retention-safe paint plan
63
+ * as keyed <img>s. Each tile reports painted after its first presentation
64
+ * opportunity (and its inverse on unmount), so retained coarser generations
65
+ * release only when their replacement is truly compositable.
57
66
  *
58
- * Mounting it is the per-lens OPT-IN; whether it spends anything is demand
59
- * arithmetic (a thumbnail rail's demand never engages). The paint list is the
60
- * plugin's retention-safe plan: keyed <img>s inside ONE container that carries
61
- * the page scale as a single transform (one rounding context — no tile seams),
62
- * with `onLoad` reporting the decode boundary back so retained coarser
63
- * generations release only when their replacement is truly on screen.
67
+ * Tiles are placed in VIEW (CSS px) space directly — never in page points
68
+ * under a scaled container. Blink quantizes layout lengths to 1/64 CSS px
69
+ * BEFORE transforms apply, so a pt-space rect under a ×25 zoom transform
70
+ * turns that harmless 1/64 into ±0.4px+ of per-tile misplacement — visible
71
+ * seams and per-zoom-step letter shifts that grow with depth. In view
72
+ * space the same quantization is a fixed ~1/64 CSS px at every zoom.
73
+ * (v2 placed tiles in screen px for exactly this reason.)
64
74
  */
65
- function TileLayer({ annotations = true } = {}) {
75
+ function TilePlane({ annotations, fadeMs }) {
66
76
  const page = usePage();
67
77
  const render = useCapability(RenderToken);
68
78
  const demand = page.getViewDemand?.() ?? { desiredDeviceWidth: page.transform.deviceWidth };
@@ -70,62 +80,69 @@ function TileLayer({ annotations = true } = {}) {
70
80
  useEffect(() => () => render.releaseTiles(page.pon), [render, page.pon]);
71
81
  if (plan.paint.length === 0) return null;
72
82
  const t = page.transform;
73
- const scale = t.viewScale;
74
- return /* @__PURE__ */ jsx("div", {
83
+ const s = t.viewScale;
84
+ return /* @__PURE__ */ jsxs("div", {
75
85
  style: {
76
86
  position: "absolute",
77
87
  left: 0,
78
88
  top: 0,
79
- width: t.contentWidth / scale,
80
- height: t.contentHeight / scale,
81
- transform: `scale(${scale})`,
82
- transformOrigin: "0 0",
83
- pointerEvents: "none"
89
+ width: t.contentWidth,
90
+ height: t.contentHeight,
91
+ pointerEvents: "none",
92
+ isolation: "isolate"
84
93
  },
85
- children: plan.paint.map((source) => /* @__PURE__ */ jsx(TileImg, {
94
+ children: [fadeMs > 0 ? /* @__PURE__ */ jsx("style", { children: `@keyframes epdf-tile-in { from { opacity: 0 } to { opacity: 1 } }` }) : null, plan.paint.map((source) => /* @__PURE__ */ jsx(TileImg, {
86
95
  source,
87
- onPainted: () => render.tilePainted(page.pon, source.key)
88
- }, source.key))
96
+ view: {
97
+ x: source.rect.x * s,
98
+ y: source.rect.y * s,
99
+ width: source.rect.width * s,
100
+ height: source.rect.height * s
101
+ },
102
+ fadeMs,
103
+ onPainted: () => render.tilePainted(page.pon, source.key),
104
+ onUnpainted: () => render.tileUnpainted(page.pon, source.key)
105
+ }, source.key))]
89
106
  });
90
107
  }
91
- /** One tile: object-URL lifecycle + the painted (decode-boundary) report. */
92
- function TileImg({ source, onPainted }) {
108
+ /** One tile: geometry plus the shared browser image binding. The binding
109
+ * hides the incomplete image, reports painted after its first presentation
110
+ * opportunity, and inverts that report when the element leaves the DOM. */
111
+ function TileImg({ source, view, fadeMs, onPainted, onUnpainted }) {
93
112
  const ref = useRef(null);
94
- useEffect(() => {
95
- const controller = new AbortController();
96
- let revoke;
97
- (async () => {
98
- try {
99
- const obj = await source.handle.objectUrl(controller.signal);
100
- if (controller.signal.aborted) {
101
- obj.revoke();
102
- return;
103
- }
104
- revoke = obj.revoke;
105
- if (ref.current) ref.current.src = obj.url;
106
- } catch {}
107
- })();
108
- return () => {
109
- controller.abort();
110
- revoke?.();
111
- };
113
+ useLayoutEffect(() => {
114
+ if (!ref.current) return;
115
+ return bindPaintedImage(ref.current, source.handle, {
116
+ onPainted,
117
+ onUnpainted
118
+ });
112
119
  }, [source.handle]);
113
120
  return /* @__PURE__ */ jsx("img", {
114
121
  ref,
115
122
  alt: "",
116
123
  draggable: false,
117
- onLoad: onPainted,
124
+ onLoad: (e) => {
125
+ const w = e.currentTarget.naturalWidth;
126
+ if (w > 0 && !warnedTileSize) {
127
+ const expected = Math.round(source.rect.width * source.scale);
128
+ if (Math.abs(w - expected) > 1) {
129
+ warnedTileSize = true;
130
+ console.warn(`[render] tile bitmap ${w}px wide does not match its rect (expected ~${expected}px) — stale raster identity? key=${source.key}`);
131
+ }
132
+ }
133
+ },
118
134
  style: {
119
135
  position: "absolute",
120
- left: source.rect.x,
121
- top: source.rect.y,
122
- width: source.rect.width,
123
- height: source.rect.height,
124
- zIndex: source.z
136
+ left: view.x,
137
+ top: view.y,
138
+ width: view.width,
139
+ height: view.height,
140
+ zIndex: source.z,
141
+ ...fadeMs > 0 ? { animation: `epdf-tile-in ${fadeMs}ms ease-out` } : null
125
142
  }
126
143
  });
127
144
  }
128
145
  //#endregion
129
- export { RenderLayer, TileLayer };
146
+ export { RenderLayer };
130
147
 
131
148
  //# sourceMappingURL=render.js.map