@embedpdf/plugin-stage 3.0.0-next.1 → 3.0.0-next.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ let _embedpdf_core = require("@embedpdf/core");
2
+ require("@embedpdf/core-stage");
3
+ //#region src/types.ts
4
+ const StageToken = (0, _embedpdf_core.createCapabilityToken)("stage");
5
+ //#endregion
6
+ Object.defineProperty(exports, "StageToken", {
7
+ enumerable: true,
8
+ get: function() {
9
+ return StageToken;
10
+ }
11
+ });
12
+
13
+ //# sourceMappingURL=contract-BU33R4oL.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-BU33R4oL.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["import { createCapabilityToken } from '@embedpdf/core';\nimport type { PageObjectNumber } from '@embedpdf/core';\nimport type { PageRotation, PageTransform, Rect } from '@embedpdf/core-geometry';\nimport type {\n Alignment,\n AlignmentValue,\n AlignValue,\n Anchor,\n Direction,\n Camera,\n PageBox,\n PageFrame,\n Point,\n ScrollMetrics,\n Size,\n SizingMode,\n SpreadMode,\n ZoomModeValue,\n ZoomSpec,\n} from '@embedpdf/core-stage';\n\nexport type LayoutKind = 'vertical' | 'horizontal' | 'grid';\n/** Navigation (goToPage) either tweens the camera or jumps instantly. */\nexport type ScrollBehaviorKind = 'smooth' | 'instant';\n/**\n * Presentation flow:\n * 'continuous' — the whole document is scrollable; the camera roams the full scene.\n * 'paged' — one item (page or spread) at a time: the scene is a one-item\n * slice at the cursor, and next/prev step between items.\n */\nexport type FlowMode = 'continuous' | 'paged';\n/**\n * Grid column policy:\n * 'square' — ≈√n columns (the classic canvas arrangement)\n * 'auto' — WRAPPED: as many columns as fit the viewport line at the current\n * zoom (the responsive thumbnail-sidebar behavior; re-wraps on resize)\n * number — a fixed column count\n */\nexport type GridColumns = 'square' | 'auto' | number;\n\n/**\n * Space between items — the value's shape carries the unit (like ZoomSpec):\n * number — WORLD units: the gap is part of the canvas and scales with zoom,\n * so the whole scene zooms as one rigid object (the document feel).\n * { px: n } — SCREEN px: UI-stable spacing, the same in every document at every\n * zoom (the browser-of-items feel: thumbnails, organizers).\n */\nexport type Gap = number | { px: number };\n\n/**\n * The one environmental fact a headless stage has: the box it was told about\n * (`setViewport`). Responsive rules can query nothing else — no user agent, no\n * pointer type (modality is per-event, on `PointerSample`), no window. Space,\n * not device: a narrow pane on a desktop is compact too, and each stage\n * instance resolves against ITS OWN box.\n */\nexport interface StageBox {\n width: number;\n height: number;\n /** Of the CONTAINER, not the device. A square box is 'portrait' (the CSS rule). */\n orientation: 'portrait' | 'landscape';\n}\n\n/** Declarative box query — all bounds inclusive, all fields optional (AND-ed). */\nexport interface BoxQuery {\n minWidth?: number;\n maxWidth?: number;\n minHeight?: number;\n maxHeight?: number;\n orientation?: 'portrait' | 'landscape';\n}\n\n/**\n * One `@container` block for the settings bag: when the box matches, assert\n * this settings patch. Rules evaluate in source order and ALL matching rules\n * apply, later winning per key (each key replaced whole — no deep merges).\n * Effective settings = base (config + runtime setters) ⊕ matching patches.\n *\n * Semantics, stated honestly:\n * • Runtime setters write the BASE; a matching rule wins over it. Apps\n * needing situational absolute control edit the rules (`setResponsive`).\n * • Rules assert at TRANSITIONS (box/base/rules changes), not continuously —\n * between crossings, interaction owns the state. A rule containing `zoom`\n * re-fits when the box crosses it (the rotate-an-iPad behavior) and then\n * leaves the pinch alone.\n *\n * A rule with a `name` is a queryable fact (`matches(name)`), reactive in\n * every framework; a named rule with NO settings is a pure shared breakpoint\n * the app chrome can key its own presentation off — one definition serving\n * both the layout math and the UI.\n */\nexport interface ResponsiveRule {\n name?: string;\n /** Declarative box query, or a predicate for anything the box can answer. */\n when: BoxQuery | ((box: StageBox) => boolean);\n /** The settings this situation asserts. Omit for a pure named query. */\n settings?: Partial<StageSettings>;\n}\n\n/**\n * One axis of the ARRIVAL policy — stage-core's AlignValue ('start' |\n * 'center' | 'end' | viewport fraction 0–1) plus one navigation-only word:\n * 'keep' — this axis does not move on arrival: page forward, hold your\n * pan (the two-column-paper feel; the PDF /XYZ null semantic).\n */\nexport type ArrivalAlignValue = AlignValue | 'keep';\nexport interface ArrivalAlignment {\n x: ArrivalAlignValue;\n y: ArrivalAlignValue;\n}\n\n/**\n * The Stage's orthogonal, independently-settable primitives. Every field can be set\n * on its own (setLayout, setBounded, …) or several at once via `update()`. A\n * \"preset\" is just a `Partial<StageSettings>` the app keeps and applies — no preset\n * machinery lives here.\n */\nexport interface StageSettings {\n /** Continuous scroll, or one item at a time. */\n flow: FlowMode;\n layout: LayoutKind;\n spread: SpreadMode;\n /** Page sizing: true PDF sizes, or equalize the cross axis so pages sit flush. */\n sizing: SizingMode;\n /** Grid column policy (grid layout only): 'square', 'auto' (wrapped), or a count. */\n columns: GridColumns;\n /** Clamp the camera to the content? Off = free infinite pan (plans / CAD). */\n bounded: boolean;\n /**\n * Breathing room (screen px) around the content — the one spacing concept.\n * Fit-modes inset by it, arrivals leave it as a gutter, and the clamp lets the\n * camera reveal exactly this much beyond each content edge.\n */\n padding: number;\n /**\n * Space BETWEEN items — and between the halves of a spread. A number is world\n * units (scales with zoom — the canvas feel); `{ px }` is screen px (UI-stable\n * — the thumbnail feel). See {@link Gap}.\n */\n gap: Gap;\n /**\n * Reserved chrome real estate around EACH PAGE, in SCREEN px — one thickness\n * per side. The page content is inset by these; the bands hold box-space\n * chrome (a label below, a button row above, side rails) painted by the app\n * via the adapter's `pageChrome` slot. Per page, not per item: in a spread\n * every page keeps its own flanks. Constant screen px (unaffected by zoom).\n *\n * Naming rule for this settings bag: every setting describes the STAGE itself\n * (the container) — `padding`, `gap`, `layout`, … The rare setting owned by the\n * page carries the `page` prefix (`pageFrame`; `pageWidth` in zoom).\n */\n pageFrame: PageFrame;\n /**\n * Reading direction. RTL: horizontal items advance leftward, spreads bind on the\n * right, grid rows fill right→left, and alignment 'start' on x means the RIGHT\n * edge (logical, CSS-style). Navigation is index-based and never changes.\n */\n direction: Direction;\n /**\n * The alignment family — EVERY camera move is defined by what it holds\n * fixed. Gestures (pan/pinch/wheel) hold the pointer: physics, no setting.\n * Explicit arrivals (positioned reveal, destinations, viewpoints) hold\n * whatever the call specifies. These four settings govern the rest:\n *\n * fitAlign — the standing CONSTRAINT: where content rests on an axis\n * the camera cannot travel (it fits the TRUE bounds — the\n * scene in continuous flow, the item slice in paged). The\n * clamp enforces it on every camera write — which is why a\n * fitting axis settles identically whatever arrivalAlign\n * says. center/center = document feel; y:'start' = sidebar\n * thumbnails hugging the top.\n * arrivalAlign — the landing POLICY: where navigation (goToPage, next/\n * prev, reset) puts the target — THE SAME at every zoom.\n * start/start = reading (top-left, direction-aware);\n * center/center = presentation/drawings (Drawboard feel);\n * y: 0.35 = the find-bar line; 'keep' = don't move an axis.\n * zoomAlign — the FOCAL point of a pointer-less zoom (zoomIn/zoomOut,\n * zoomTo, fit-mode switches). Pinch/ctrl+wheel always hold\n * the pointer instead — that is physics, not policy.\n * center/center = the view inflates around its middle;\n * y:'start' = the first visible line holds still.\n * anchorAlign — the viewport point that SURVIVES a reframe (viewport\n * resize, page rotation, spread/gap change): the view\n * anchor is captured there and restored there. start/start\n * = the browser scroll model (growth reveals below — a\n * container that mounts small and expands never shoves the\n * document down); center/center = canvas-style symmetric\n * resizes (the Figma feel).\n *\n * Named x values are LOGICAL (CSS-style: 'start' = reading start — the\n * right edge in RTL); fractions are physical, like screen coordinates.\n */\n fitAlign: Alignment;\n /** See {@link StageSettings.fitAlign} — where navigation lands, per axis. */\n arrivalAlign: ArrivalAlignment;\n /** See {@link StageSettings.fitAlign} — the pointer-less zoom focal point. */\n zoomAlign: AlignmentValue;\n /** See {@link StageSettings.fitAlign} — the viewport point reframes hold. */\n anchorAlign: AlignmentValue;\n /**\n * NON-PERSISTENT view rotation: a quarter-turn (clockwise) applied to how\n * EVERY page is DISPLAYED in this lens, on top of each page's own /Rotate —\n * Adobe's \"Rotate View\". A display setting like `zoom` or `layout`: per lens\n * (the main viewer can rotate while a thumbnail lens stays upright), never\n * written to the document, gone when the lens resets. The PERMANENT\n * counterpart — writing /Rotate into the PDF — is plugin-page-edit's\n * `rotateBy`/`setRotation`.\n */\n viewRotation: PageRotation;\n /** Zoom intent: a fit-mode (automatic/fit-page/fit-width/fit-all) or a fixed level. */\n zoom: ZoomSpec;\n /** Default behaviour for goToPage/next/prev. */\n scrollBehavior: ScrollBehaviorKind;\n /**\n * View pixels per PDF point — the platform's physical unit factor, folded into\n * the layout so 100% (zoom 1) is physically accurate. Web = 96/72 (1 pt = 1/72\",\n * 1 CSS px = 1/96\"); a native platform injects its own (iOS pt, Android dp). It\n * scales every page's world size (and thus `contentScale`), so the camera math,\n * `gap`/`padding`/`pageFrame` (world units), and absolute-px zoom modes\n * (`pageWidth`) are all unaffected — only the pages themselves resize.\n */\n viewUnitsPerPoint: number;\n}\n\n/**\n * A laid-out page handed to the shell.\n * - PageBox + `pon`: LAYOUT truth (world coords + identity) — the shell uses\n * `x/y/width/height` only to POSITION the page container.\n * - `transform`: PRESENTATION truth — the single bridge between PDF points,\n * view px, and device px for this page. Plugins do ALL coordinate work\n * through it (`contentToView` / `viewToContent` / `deviceWidth` / `cssMatrix`),\n * never by re-deriving `x * scale` / `* dpr`. Page-local, so it's\n * camera/pan-invariant.\n */\nexport interface VisiblePage extends PageBox {\n pon: PageObjectNumber;\n /**\n * The page's DISPLAY-box (footprint) top-left in screen px, camera-resolved and\n * snapped to the device grid. The shell positions the page container at this —\n * snapping here (not in the adapter) keeps a CSS-rotated page on the pixel grid\n * for every framework, with no hand-rounding.\n */\n screenX: number;\n screenY: number;\n transform: PageTransform;\n /**\n * The page region actually ON SCREEN, in UN-rotated page points (y-down) —\n * viewport ∩ footprint inverted through the transform (exact for\n * quarter-turns). Zero-sized when the page sits outside the viewport.\n * Visibility is the STAGE's data (it already intersects viewport × pages to\n * virtualize); adapters and demand consumers (tiling's `PageViewDemand`)\n * read it instead of re-deriving camera math per framework.\n */\n visibleRect: Rect;\n}\n\nexport interface StageState extends StageSettings {\n camera: Camera;\n /**\n * One-way render-commit latch. False while the initial viewport-driven\n * placement is unresolved; true only after its camera/settings writes have\n * completed. Page/scroll screen-space selectors refuse to answer while\n * false, so an adapter can never paint the placeholder camera at the origin.\n */\n placed: boolean;\n /**\n * Names of the responsive rules currently matching the box, in source order.\n * State (not derived) so `matches()` is reactive through the ordinary\n * selector machinery in every framework.\n */\n activeRules: readonly string[];\n /**\n * False while the ZOOM is in motion, true once it has rested (~150ms of\n * frames without a zoom write). Device-snapping of page origins is gated\n * on this: a continuous zoom and a snapped origin cannot coexist without\n * the anchor point jittering ±0.5 device px per step (the rounding lands\n * differently every frame), so pages place fractionally while zooming and\n * snap once at rest — when crispness actually matters. Pans always snap\n * (a pure translation snap has no anchor error). Transient like `camera`.\n */\n cameraResting: boolean;\n vp: Size;\n /**\n * Device pixels per view pixel (web: `window.devicePixelRatio`). Reported by\n * the shell like the viewport; feeds each page's transform so bitmaps render\n * crisp (exact device px) and boxes land on the device grid. Defaults to 1.\n */\n dpr: number;\n /**\n * The current page — transient like `camera` (NOT a setting), valid in BOTH flows.\n * Navigation sets it; in continuous flow scrolling syncs it from the camera; in\n * paged flow panning never moves it (the scene is a one-item slice at this page).\n * Stored as a page index so it survives spread/layout regrouping.\n */\n cursor: number;\n /**\n * What last drove the camera/cursor: `'programmatic'` for the arrival and\n * reveal doors (goToPage/next/prev/reset/reveal — the doors action\n * executors use), `'user'` for direct camera manipulation (wheel, drag,\n * scrollbars, embedder scroll APIs). Transient like `camera`. Consumed by\n * the page-state feed as the action engine's cascade-budget fuel: only\n * programmatic rounds burn budget, so user scrolling never starves.\n */\n motionCause: 'user' | 'programmatic';\n}\n\nexport type StageAction =\n | { type: 'CAMERA'; camera: Camera }\n | { type: 'CAMERA_REST'; resting: boolean }\n | { type: 'PLACED' }\n | { type: 'VP'; vp: Size }\n | { type: 'DPR'; dpr: number }\n | { type: 'CURSOR'; cursor: number }\n | { type: 'MOTION_CAUSE'; cause: 'user' | 'programmatic' }\n | { type: 'PATCH'; patch: Partial<StageSettings> }\n | { type: 'RESPONSIVE'; active: readonly string[] };\n\n/**\n * A page-relative view memento: \"what I'm looking at and how zoomed\". The durable\n * currency for per-page view memory (construction worksheets) — capture with\n * `viewpoint()`, restore with `goToPage(i, { viewpoint })`. Survives resizes\n * because the anchor is page-relative and fit-modes re-resolve.\n */\nexport interface Viewpoint {\n anchor: Anchor;\n zoom: ZoomSpec;\n}\n\n/** Durable, serializable view state — the unit of session persistence. */\nexport interface StageViewState extends StageSettings {\n cursor: number;\n anchor: Anchor;\n}\n\n/**\n * Host timing seam. The pure core (stage-core) never touches time; the camera tween\n * lives in this (impure) shell and asks for frames through a Scheduler. The default\n * is the browser's requestAnimationFrame; inject a fake in tests, or an instant one\n * in Node/SSR.\n */\nexport interface Scheduler {\n /** Run the callback on the next frame; returns a handle for cancellation. */\n raf(callback: (timestampMs: number) => void): number;\n /** Cancel a scheduled callback. */\n caf(handle: number): void;\n}\n\n/**\n * Options for the scroller writes — `Element.scrollTo` semantics: absolute\n * offsets (screen px) into the current scroll range (see\n * {@link StageCapability.scrollMetrics}); an omitted axis does not move.\n * `behavior` defaults to 'instant' (the DOM's 'auto'), NOT the stage's\n * `scrollBehavior` setting — that setting governs navigation verbs, and a\n * scrollbar thumb must track the pointer exactly.\n */\nexport interface StageScrollToOptions {\n left?: number;\n top?: number;\n behavior?: ScrollBehaviorKind;\n}\n\n/** Options for navigation intents. */\nexport interface GoToOptions {\n behavior?: ScrollBehaviorKind;\n /** Restore this exact viewpoint instead of fresh placement (per-page memory). */\n viewpoint?: Viewpoint;\n /** Override the landing for THIS navigation only (explicit beats default). */\n arrivalAlign?: Partial<ArrivalAlignment>;\n}\n\n/**\n * One axis of a reveal arrival — `scrollIntoView` vocabulary plus two\n * PDF-protocol necessities:\n * absent → minimal movement: scroll only if the target is off-screen\n * (CSS 'nearest'; today's reveal semantics)\n * 'keep' → this axis does not move AT ALL (PDF /XYZ null coordinate)\n * 'start' → target edge at the viewport start (plus padding)\n * 'center' → target centered\n * 'end' → target edge at the viewport end (minus padding)\n * number 0–1 → target CENTER at this viewport fraction (0.35 = \"top middle\",\n * the browser find-bar feel)\n */\nexport type RevealAnchorValue = 'keep' | 'start' | 'center' | 'end' | number;\n\nexport interface RevealAnchor {\n x?: RevealAnchorValue;\n y?: RevealAnchorValue;\n}\n\n/**\n * What happens to zoom on a reveal — always relative to the reveal's target\n * rect (the whole page when no `rect` is given):\n * 'keep' → pure pan, zoom untouched (search hits, /XYZ null zoom)\n * { level } → explicit factor (/XYZ zoom)\n * 'fit' → the rect fully visible (/FitR; /Fit, /FitB via rect=page/bbox)\n * 'fit-width' → the rect's width fills the viewport (/FitH, /FitBH)\n * 'fit-height' → the rect's height fills the viewport (/FitV, /FitBV)\n */\nexport type RevealZoom = 'keep' | 'fit' | 'fit-width' | 'fit-height' | { level: number };\n\n/**\n * Options for `reveal` — the follower-UI arrival verb (search hits, outline\n * clicks, PDF destinations, \"jump to comment\").\n *\n * With none of `rect`/`zoom`/`anchor` set, reveal keeps its original\n * semantics: minimal movement to make the page visible, cursor untouched.\n * A POSITIONED reveal (any of the three set) is \"you are now looking at\n * THIS spot\": the camera places the target per the anchor, a zoom\n * directive resolves to a concrete level (recorded as the zoom intent),\n * and the cursor follows the camera — while still clamping against the\n * normal camera bounds, so anchors are best-effort near document edges.\n */\nexport interface RevealOptions {\n behavior?: ScrollBehaviorKind;\n /**\n * Target rect on the page in the VIEWER's coordinates (y-down,\n * crop-relative, unscaled points — the same `Rect` selection/search rects\n * and `CommentThreadView.contentRect` live in). Absent or `null` → the\n * whole page (null accepted so nullable sources flow in directly). A\n * zero-size rect is a point (/XYZ).\n */\n rect?: Rect | null;\n zoom?: RevealZoom;\n anchor?: RevealAnchor;\n}\n\n/** The Stage's public contract: selectors (reads) + intents (the only writers). */\nexport interface StageCapability {\n // ── selectors ──\n camera(): Camera;\n viewport(): Size;\n /**\n * The camera as a NATIVE SCROLLER — the DOM scroll vocabulary in screen px:\n * `scrollTop`/`scrollHeight`/`clientHeight` (and the x twins) mean exactly\n * what they mean on a DOM element; `scrollableX/Y` false ⇔ nothing to scroll\n * on that axis (native: no bar). Derived from the SAME travel range the pan\n * clamp uses — paged flow reads the one-item slice — so a scrollbar built on\n * it can never disagree with where panning stops. On an UNBOUNDED stage the\n * range is the union of the padded content and the current window (the Figma\n * bar): pan away and it grows, the thumb shrinking toward the edge but always\n * remaining a road back. Reference-stable until a field actually changes.\n */\n scrollMetrics(): ScrollMetrics;\n pageCount(): number;\n visiblePages(): VisiblePage[];\n /** The current page (the cursor) — valid in both flows. */\n currentPage(): number;\n /** The display indices of the current item's pages (1 page, or a spread's pages). */\n currentItemPages(): number[];\n /** The full page list with PDF labels — for page thumbnails / worksheet tabs. */\n pages(): Array<{ index: number; pon: PageObjectNumber; label: string | null }>;\n /** The laid-out box for a page by its durable pon. */\n pageRect(pon: PageObjectNumber): VisiblePage | null;\n /**\n * Screen point (this Stage's container px) → the page under it + its content\n * point, or null over a gap. The viewport-level hit-test the interaction hub\n * needs so a single pointer source can drive page-aware features (text\n * selection, annotations) AND cross-page drags.\n */\n pageAt(screen: Point): {\n pon: PageObjectNumber;\n point: Point;\n scale: number;\n /** The hit page's TOTAL display rotation (document /Rotate + view rotation). */\n rotation: PageRotation;\n /** The hit page's zoom relative to its 100% baseline (`transform.zoom`). */\n zoom: number;\n } | null;\n /**\n * Screen point → `pon`'s content space, UNCLAMPED — valid even when the point\n * is outside the page's bounds (coordinates then fall outside `[0, size]`).\n * The frame-stable projection a page-anchored gesture (annotation move/resize)\n * tracks with, where `pageAt` would re-resolve to whatever page is under the\n * cursor (what a cross-page drag like text selection wants). Null when the\n * page isn't currently laid out.\n */\n pointOnPage(pon: PageObjectNumber, screen: Point): Point | null;\n /**\n * Page space (intrinsic PDF points) → world space. Applies the page's placed\n * origin and contentScale — the transform sizing policies introduce. Compose\n * with toScreen for viewport-space overlays anchored to page content.\n */\n pageToWorld(pon: PageObjectNumber, pt: Point): Point | null;\n /**\n * Content rect on a page → this Stage viewport's screen-space AABB. Applies page\n * rotation/contentScale and the current camera. Use for upright viewport overlays\n * that need to frame a selected page region.\n */\n pageRectToScreen(pon: PageObjectNumber, rect: Rect): Rect | null;\n toScreen(world: Point): Point;\n toWorld(screen: Point): Point;\n flow(): FlowMode;\n layout(): LayoutKind;\n spread(): SpreadMode;\n sizing(): SizingMode;\n columns(): GridColumns;\n bounded(): boolean;\n padding(): number;\n gap(): Gap;\n pageFrame(): PageFrame;\n fitAlign(): Alignment;\n arrivalAlign(): ArrivalAlignment;\n zoomAlign(): AlignmentValue;\n anchorAlign(): AlignmentValue;\n direction(): Direction;\n scrollBehavior(): ScrollBehaviorKind;\n /** The lens's view rotation — see {@link StageSettings.viewRotation}. */\n viewRotation(): PageRotation;\n zoomLevel(): number;\n /** The active zoom intent: a fit-mode, or 'custom' for a fixed level. */\n zoomMode(): ZoomModeValue | 'custom';\n /** What I'm looking at + zoom intent — capture for per-page view memory. */\n viewpoint(): Viewpoint;\n /** A snapshot of all settings (handy for building/saving a customer preset). */\n settings(): StageSettings;\n viewState(): StageViewState;\n\n // ── intents ──\n setViewport(vp: Size): void;\n /** Report the device pixel ratio (web: `devicePixelRatio`). The shell calls\n * this once on mount and whenever it changes (e.g. dragging between monitors)\n * so page transforms render crisp. */\n setDevicePixelRatio(dpr: number): void;\n setCamera(c: Camera): void;\n panBy(dxScreen: number, dyScreen: number): void;\n /** `Element.scrollTo` for the camera: absolute offsets into the scroll range\n * (see {@link StageScrollToOptions}) — clamped into it, cursor-synced, zoom\n * untouched (scrolling is a pan in scroller clothing). */\n scrollTo(opts: StageScrollToOptions): void;\n /** `Element.scrollBy`: relative offsets — sugar over `scrollTo`. */\n scrollBy(opts: StageScrollToOptions): void;\n zoomAround(screenPt: Point, factor: number): void;\n /**\n * Bracket a continuous direct-manipulation gesture (touch pan / pinch).\n * While a gesture is open the camera writes stay cheap and visually calm:\n * `zoomAround` defers its zoom-intent PATCH (one per gesture instead of one\n * per event), and the camera-rest detector holds un-rested — device\n * snapping and settle-gated rendering wait for the END of the gesture, not\n * for a 150 ms hesitation inside it. Re-entrant (nesting counts). Opening a\n * gesture cancels any running tween or fling — that is how a finger\n * \"catches\" a moving page.\n *\n * `elastic` opts the gesture into RUBBER-BAND overscroll: pans past the\n * clamp stretch on the iOS resistance curve instead of stopping dead, and\n * ending the gesture (or a fling reaching an edge) springs the camera home\n * on a critically-damped curve. The clamp itself stays the untouched law of\n * REST — elasticity is a transient the gesture is allowed to hold, never a\n * state the camera can settle in. Touch contacts pass it; mouse drags and\n * wheel pans stay rigid (the desktop convention). Default false.\n */\n beginGesture(options?: { elastic?: boolean }): void;\n endGesture(): void;\n /**\n * Momentum scroll: keep panning from a release velocity (screen px/s, the\n * same sign convention as `panBy` deltas), decelerating on UIScrollView's\n * curve. Every other camera verb — including `beginGesture` from the next\n * touch — cancels it. No-op without host frames (SSR/tests without a\n * scheduler jump nowhere: momentum is presentation, not state).\n */\n fling(velocityX: number, velocityY: number): void;\n /** True while the camera is animating (navigation tween or fling) — the\n * input layer reads it to tell a \"catch\" from a tap. */\n cameraInMotion(): boolean;\n /**\n * The touch double-tap toggle: zoomed out → animate to ~2.5× the automatic\n * fit around `screenPt`; already zoomed past that → animate back to the fit\n * level, same focal point. Lands as a fixed zoom level either way.\n */\n doubleTapZoom(screenPt: Point): void;\n zoomIn(): void;\n zoomOut(): void;\n zoomTo(spec: ZoomSpec): void;\n fitWidth(): void;\n fitPage(): void;\n /** Fit the whole scene (every page) in view — the construction overview. */\n fitAll(): void;\n /** Fit width but never upscale past 100% (Adobe's \"Automatic\"). */\n automatic(): void;\n /**\n * Re-resolve the active zoom intent and re-place against the CURRENT scene,\n * preserving the anchored page-point. Call after the page geometry changes\n * underneath the lens — rotate/move/delete — so fit/pixel zoom modes\n * (`fitPage`, `pageWidth`, …) recompute against the new footprint. A no-op\n * before the first placement; for a fixed `{ level }` zoom it just re-anchors.\n * Wired automatically to the document's page-registry revision; exposed for\n * any external geometry change a host wants to react to.\n */\n refit(): void;\n /** Go to a page. Fresh arrival places by the unit rule; pass `viewpoint` to restore. */\n goToPage(pageIndex: number, opts?: GoToOptions): void;\n /**\n * The follower-UI arrival verb (thumbnail sidebars, search hits, outline\n * clicks, PDF destinations). Bare: make the page visible with minimal\n * movement — zero if it already is, cursor untouched (scrollIntoView\n * semantics; paged flow delegates to navigation since the page isn't in\n * the slice). POSITIONED (rect/zoom/anchor set — see {@link RevealOptions}):\n * place the target at the anchor, optionally re-zooming; the cursor\n * follows the camera.\n */\n reveal(pageIndex: number, opts?: RevealOptions): void;\n /** Step forward by the navigation unit (the item if it fits the viewport, else the page). */\n next(opts?: GoToOptions): void;\n /** Step backward by the navigation unit. */\n prev(opts?: GoToOptions): void;\n /**\n * This lens's identity — the stage plugin id it was registered under\n * ('stage' for the main lens; a custom id per additional lens). The surface\n * binding stamps it on every pointer sample (`PointerSample.source`) and\n * lens-scoped interaction handlers register under it, so two stages on one\n * document can never capture each other's input.\n */\n lensId(): string;\n /** Set any subset of settings at once — ONE anchor-preserving update. The way to\n * apply a customer preset: `update(myPreset)`. Writes the responsive BASE:\n * a matching rule's key wins until its rule stops matching. */\n update(patch: Partial<StageSettings>): void;\n /** Replace the responsive rules (see {@link ResponsiveRule}); re-resolves\n * immediately with the usual anchor-preserving reactions. */\n setResponsive(rules: readonly ResponsiveRule[]): void;\n /** Is the named responsive rule currently matching? Reactive: recomputed\n * whenever the box crosses a rule boundary. */\n matches(name: string): boolean;\n /** Names of all currently-matching rules, in source order. */\n activeRules(): readonly string[];\n setFlow(flow: FlowMode): void;\n setLayout(layout: LayoutKind): void;\n setSpread(spread: SpreadMode): void;\n setSizing(sizing: SizingMode): void;\n setColumns(columns: GridColumns): void;\n setBounded(bounded: boolean): void;\n setPadding(padding: number): void;\n setGap(gap: Gap): void;\n setPageFrame(pageFrame: PageFrame): void;\n setFitAlign(fitAlign: Alignment): void;\n setArrivalAlign(arrivalAlign: ArrivalAlignment): void;\n setZoomAlign(zoomAlign: AlignmentValue): void;\n setAnchorAlign(anchorAlign: AlignmentValue): void;\n setDirection(direction: Direction): void;\n /** Set the lens's view rotation to an absolute quarter-turn — see\n * {@link StageSettings.viewRotation}. An anchor-preserving reframe, like a\n * page rotation: the spot you were looking at stays put (`anchorAlign`). */\n setViewRotation(viewRotation: PageRotation): void;\n /** Rotate the view a quarter-turn from where it is (the toolbar verb) —\n * relative sugar over {@link setViewRotation}, mirroring page-edit's `rotateBy`. */\n rotateView(delta: 90 | -90): void;\n setScrollBehavior(behavior: ScrollBehaviorKind): void;\n applyViewState(view: StageViewState): void;\n /** Offer a candidate initial view; the highest-priority non-null wins at placement. */\n provideInitialView(priority: number, provider: () => StageViewState | null): void;\n /** Resolve the registered providers once (else reset). Called when the viewport is ready. */\n placeInitial(): void;\n /** Back to the start: page 0, placed by the unit rule at the current zoom intent. */\n resetView(): void;\n}\n\nexport interface StageConfig extends Partial<StageSettings> {\n /** Override the host timing seam (tests/SSR). Defaults to browser rAF. */\n scheduler?: Scheduler;\n /**\n * Container queries for the settings bag (see {@link ResponsiveRule}).\n * Defaults to `DEFAULT_RESPONSIVE` (compact containers get the thin phone\n * gutter); pass `[]` to opt out entirely.\n */\n responsive?: readonly ResponsiveRule[];\n}\n\nexport const StageToken = createCapabilityToken<StageCapability>('stage');\n"],"mappings":";;;AA0pBA,MAAa,cAAA,GAAA,eAAA,sBAAA,CAAoD,OAAO"}
@@ -0,0 +1,8 @@
1
+ import { createCapabilityToken } from "@embedpdf/core";
2
+ import { ZoomMode as ZoomMode$1 } from "@embedpdf/core-stage";
3
+ //#region src/types.ts
4
+ const StageToken = createCapabilityToken("stage");
5
+ //#endregion
6
+ export { StageToken as n, ZoomMode$1 as t };
7
+
8
+ //# sourceMappingURL=contract-CPSX8ANN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-CPSX8ANN.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["import { createCapabilityToken } from '@embedpdf/core';\nimport type { PageObjectNumber } from '@embedpdf/core';\nimport type { PageRotation, PageTransform, Rect } from '@embedpdf/core-geometry';\nimport type {\n Alignment,\n AlignmentValue,\n AlignValue,\n Anchor,\n Direction,\n Camera,\n PageBox,\n PageFrame,\n Point,\n ScrollMetrics,\n Size,\n SizingMode,\n SpreadMode,\n ZoomModeValue,\n ZoomSpec,\n} from '@embedpdf/core-stage';\n\nexport type LayoutKind = 'vertical' | 'horizontal' | 'grid';\n/** Navigation (goToPage) either tweens the camera or jumps instantly. */\nexport type ScrollBehaviorKind = 'smooth' | 'instant';\n/**\n * Presentation flow:\n * 'continuous' — the whole document is scrollable; the camera roams the full scene.\n * 'paged' — one item (page or spread) at a time: the scene is a one-item\n * slice at the cursor, and next/prev step between items.\n */\nexport type FlowMode = 'continuous' | 'paged';\n/**\n * Grid column policy:\n * 'square' — ≈√n columns (the classic canvas arrangement)\n * 'auto' — WRAPPED: as many columns as fit the viewport line at the current\n * zoom (the responsive thumbnail-sidebar behavior; re-wraps on resize)\n * number — a fixed column count\n */\nexport type GridColumns = 'square' | 'auto' | number;\n\n/**\n * Space between items — the value's shape carries the unit (like ZoomSpec):\n * number — WORLD units: the gap is part of the canvas and scales with zoom,\n * so the whole scene zooms as one rigid object (the document feel).\n * { px: n } — SCREEN px: UI-stable spacing, the same in every document at every\n * zoom (the browser-of-items feel: thumbnails, organizers).\n */\nexport type Gap = number | { px: number };\n\n/**\n * The one environmental fact a headless stage has: the box it was told about\n * (`setViewport`). Responsive rules can query nothing else — no user agent, no\n * pointer type (modality is per-event, on `PointerSample`), no window. Space,\n * not device: a narrow pane on a desktop is compact too, and each stage\n * instance resolves against ITS OWN box.\n */\nexport interface StageBox {\n width: number;\n height: number;\n /** Of the CONTAINER, not the device. A square box is 'portrait' (the CSS rule). */\n orientation: 'portrait' | 'landscape';\n}\n\n/** Declarative box query — all bounds inclusive, all fields optional (AND-ed). */\nexport interface BoxQuery {\n minWidth?: number;\n maxWidth?: number;\n minHeight?: number;\n maxHeight?: number;\n orientation?: 'portrait' | 'landscape';\n}\n\n/**\n * One `@container` block for the settings bag: when the box matches, assert\n * this settings patch. Rules evaluate in source order and ALL matching rules\n * apply, later winning per key (each key replaced whole — no deep merges).\n * Effective settings = base (config + runtime setters) ⊕ matching patches.\n *\n * Semantics, stated honestly:\n * • Runtime setters write the BASE; a matching rule wins over it. Apps\n * needing situational absolute control edit the rules (`setResponsive`).\n * • Rules assert at TRANSITIONS (box/base/rules changes), not continuously —\n * between crossings, interaction owns the state. A rule containing `zoom`\n * re-fits when the box crosses it (the rotate-an-iPad behavior) and then\n * leaves the pinch alone.\n *\n * A rule with a `name` is a queryable fact (`matches(name)`), reactive in\n * every framework; a named rule with NO settings is a pure shared breakpoint\n * the app chrome can key its own presentation off — one definition serving\n * both the layout math and the UI.\n */\nexport interface ResponsiveRule {\n name?: string;\n /** Declarative box query, or a predicate for anything the box can answer. */\n when: BoxQuery | ((box: StageBox) => boolean);\n /** The settings this situation asserts. Omit for a pure named query. */\n settings?: Partial<StageSettings>;\n}\n\n/**\n * One axis of the ARRIVAL policy — stage-core's AlignValue ('start' |\n * 'center' | 'end' | viewport fraction 0–1) plus one navigation-only word:\n * 'keep' — this axis does not move on arrival: page forward, hold your\n * pan (the two-column-paper feel; the PDF /XYZ null semantic).\n */\nexport type ArrivalAlignValue = AlignValue | 'keep';\nexport interface ArrivalAlignment {\n x: ArrivalAlignValue;\n y: ArrivalAlignValue;\n}\n\n/**\n * The Stage's orthogonal, independently-settable primitives. Every field can be set\n * on its own (setLayout, setBounded, …) or several at once via `update()`. A\n * \"preset\" is just a `Partial<StageSettings>` the app keeps and applies — no preset\n * machinery lives here.\n */\nexport interface StageSettings {\n /** Continuous scroll, or one item at a time. */\n flow: FlowMode;\n layout: LayoutKind;\n spread: SpreadMode;\n /** Page sizing: true PDF sizes, or equalize the cross axis so pages sit flush. */\n sizing: SizingMode;\n /** Grid column policy (grid layout only): 'square', 'auto' (wrapped), or a count. */\n columns: GridColumns;\n /** Clamp the camera to the content? Off = free infinite pan (plans / CAD). */\n bounded: boolean;\n /**\n * Breathing room (screen px) around the content — the one spacing concept.\n * Fit-modes inset by it, arrivals leave it as a gutter, and the clamp lets the\n * camera reveal exactly this much beyond each content edge.\n */\n padding: number;\n /**\n * Space BETWEEN items — and between the halves of a spread. A number is world\n * units (scales with zoom — the canvas feel); `{ px }` is screen px (UI-stable\n * — the thumbnail feel). See {@link Gap}.\n */\n gap: Gap;\n /**\n * Reserved chrome real estate around EACH PAGE, in SCREEN px — one thickness\n * per side. The page content is inset by these; the bands hold box-space\n * chrome (a label below, a button row above, side rails) painted by the app\n * via the adapter's `pageChrome` slot. Per page, not per item: in a spread\n * every page keeps its own flanks. Constant screen px (unaffected by zoom).\n *\n * Naming rule for this settings bag: every setting describes the STAGE itself\n * (the container) — `padding`, `gap`, `layout`, … The rare setting owned by the\n * page carries the `page` prefix (`pageFrame`; `pageWidth` in zoom).\n */\n pageFrame: PageFrame;\n /**\n * Reading direction. RTL: horizontal items advance leftward, spreads bind on the\n * right, grid rows fill right→left, and alignment 'start' on x means the RIGHT\n * edge (logical, CSS-style). Navigation is index-based and never changes.\n */\n direction: Direction;\n /**\n * The alignment family — EVERY camera move is defined by what it holds\n * fixed. Gestures (pan/pinch/wheel) hold the pointer: physics, no setting.\n * Explicit arrivals (positioned reveal, destinations, viewpoints) hold\n * whatever the call specifies. These four settings govern the rest:\n *\n * fitAlign — the standing CONSTRAINT: where content rests on an axis\n * the camera cannot travel (it fits the TRUE bounds — the\n * scene in continuous flow, the item slice in paged). The\n * clamp enforces it on every camera write — which is why a\n * fitting axis settles identically whatever arrivalAlign\n * says. center/center = document feel; y:'start' = sidebar\n * thumbnails hugging the top.\n * arrivalAlign — the landing POLICY: where navigation (goToPage, next/\n * prev, reset) puts the target — THE SAME at every zoom.\n * start/start = reading (top-left, direction-aware);\n * center/center = presentation/drawings (Drawboard feel);\n * y: 0.35 = the find-bar line; 'keep' = don't move an axis.\n * zoomAlign — the FOCAL point of a pointer-less zoom (zoomIn/zoomOut,\n * zoomTo, fit-mode switches). Pinch/ctrl+wheel always hold\n * the pointer instead — that is physics, not policy.\n * center/center = the view inflates around its middle;\n * y:'start' = the first visible line holds still.\n * anchorAlign — the viewport point that SURVIVES a reframe (viewport\n * resize, page rotation, spread/gap change): the view\n * anchor is captured there and restored there. start/start\n * = the browser scroll model (growth reveals below — a\n * container that mounts small and expands never shoves the\n * document down); center/center = canvas-style symmetric\n * resizes (the Figma feel).\n *\n * Named x values are LOGICAL (CSS-style: 'start' = reading start — the\n * right edge in RTL); fractions are physical, like screen coordinates.\n */\n fitAlign: Alignment;\n /** See {@link StageSettings.fitAlign} — where navigation lands, per axis. */\n arrivalAlign: ArrivalAlignment;\n /** See {@link StageSettings.fitAlign} — the pointer-less zoom focal point. */\n zoomAlign: AlignmentValue;\n /** See {@link StageSettings.fitAlign} — the viewport point reframes hold. */\n anchorAlign: AlignmentValue;\n /**\n * NON-PERSISTENT view rotation: a quarter-turn (clockwise) applied to how\n * EVERY page is DISPLAYED in this lens, on top of each page's own /Rotate —\n * Adobe's \"Rotate View\". A display setting like `zoom` or `layout`: per lens\n * (the main viewer can rotate while a thumbnail lens stays upright), never\n * written to the document, gone when the lens resets. The PERMANENT\n * counterpart — writing /Rotate into the PDF — is plugin-page-edit's\n * `rotateBy`/`setRotation`.\n */\n viewRotation: PageRotation;\n /** Zoom intent: a fit-mode (automatic/fit-page/fit-width/fit-all) or a fixed level. */\n zoom: ZoomSpec;\n /** Default behaviour for goToPage/next/prev. */\n scrollBehavior: ScrollBehaviorKind;\n /**\n * View pixels per PDF point — the platform's physical unit factor, folded into\n * the layout so 100% (zoom 1) is physically accurate. Web = 96/72 (1 pt = 1/72\",\n * 1 CSS px = 1/96\"); a native platform injects its own (iOS pt, Android dp). It\n * scales every page's world size (and thus `contentScale`), so the camera math,\n * `gap`/`padding`/`pageFrame` (world units), and absolute-px zoom modes\n * (`pageWidth`) are all unaffected — only the pages themselves resize.\n */\n viewUnitsPerPoint: number;\n}\n\n/**\n * A laid-out page handed to the shell.\n * - PageBox + `pon`: LAYOUT truth (world coords + identity) — the shell uses\n * `x/y/width/height` only to POSITION the page container.\n * - `transform`: PRESENTATION truth — the single bridge between PDF points,\n * view px, and device px for this page. Plugins do ALL coordinate work\n * through it (`contentToView` / `viewToContent` / `deviceWidth` / `cssMatrix`),\n * never by re-deriving `x * scale` / `* dpr`. Page-local, so it's\n * camera/pan-invariant.\n */\nexport interface VisiblePage extends PageBox {\n pon: PageObjectNumber;\n /**\n * The page's DISPLAY-box (footprint) top-left in screen px, camera-resolved and\n * snapped to the device grid. The shell positions the page container at this —\n * snapping here (not in the adapter) keeps a CSS-rotated page on the pixel grid\n * for every framework, with no hand-rounding.\n */\n screenX: number;\n screenY: number;\n transform: PageTransform;\n /**\n * The page region actually ON SCREEN, in UN-rotated page points (y-down) —\n * viewport ∩ footprint inverted through the transform (exact for\n * quarter-turns). Zero-sized when the page sits outside the viewport.\n * Visibility is the STAGE's data (it already intersects viewport × pages to\n * virtualize); adapters and demand consumers (tiling's `PageViewDemand`)\n * read it instead of re-deriving camera math per framework.\n */\n visibleRect: Rect;\n}\n\nexport interface StageState extends StageSettings {\n camera: Camera;\n /**\n * One-way render-commit latch. False while the initial viewport-driven\n * placement is unresolved; true only after its camera/settings writes have\n * completed. Page/scroll screen-space selectors refuse to answer while\n * false, so an adapter can never paint the placeholder camera at the origin.\n */\n placed: boolean;\n /**\n * Names of the responsive rules currently matching the box, in source order.\n * State (not derived) so `matches()` is reactive through the ordinary\n * selector machinery in every framework.\n */\n activeRules: readonly string[];\n /**\n * False while the ZOOM is in motion, true once it has rested (~150ms of\n * frames without a zoom write). Device-snapping of page origins is gated\n * on this: a continuous zoom and a snapped origin cannot coexist without\n * the anchor point jittering ±0.5 device px per step (the rounding lands\n * differently every frame), so pages place fractionally while zooming and\n * snap once at rest — when crispness actually matters. Pans always snap\n * (a pure translation snap has no anchor error). Transient like `camera`.\n */\n cameraResting: boolean;\n vp: Size;\n /**\n * Device pixels per view pixel (web: `window.devicePixelRatio`). Reported by\n * the shell like the viewport; feeds each page's transform so bitmaps render\n * crisp (exact device px) and boxes land on the device grid. Defaults to 1.\n */\n dpr: number;\n /**\n * The current page — transient like `camera` (NOT a setting), valid in BOTH flows.\n * Navigation sets it; in continuous flow scrolling syncs it from the camera; in\n * paged flow panning never moves it (the scene is a one-item slice at this page).\n * Stored as a page index so it survives spread/layout regrouping.\n */\n cursor: number;\n /**\n * What last drove the camera/cursor: `'programmatic'` for the arrival and\n * reveal doors (goToPage/next/prev/reset/reveal — the doors action\n * executors use), `'user'` for direct camera manipulation (wheel, drag,\n * scrollbars, embedder scroll APIs). Transient like `camera`. Consumed by\n * the page-state feed as the action engine's cascade-budget fuel: only\n * programmatic rounds burn budget, so user scrolling never starves.\n */\n motionCause: 'user' | 'programmatic';\n}\n\nexport type StageAction =\n | { type: 'CAMERA'; camera: Camera }\n | { type: 'CAMERA_REST'; resting: boolean }\n | { type: 'PLACED' }\n | { type: 'VP'; vp: Size }\n | { type: 'DPR'; dpr: number }\n | { type: 'CURSOR'; cursor: number }\n | { type: 'MOTION_CAUSE'; cause: 'user' | 'programmatic' }\n | { type: 'PATCH'; patch: Partial<StageSettings> }\n | { type: 'RESPONSIVE'; active: readonly string[] };\n\n/**\n * A page-relative view memento: \"what I'm looking at and how zoomed\". The durable\n * currency for per-page view memory (construction worksheets) — capture with\n * `viewpoint()`, restore with `goToPage(i, { viewpoint })`. Survives resizes\n * because the anchor is page-relative and fit-modes re-resolve.\n */\nexport interface Viewpoint {\n anchor: Anchor;\n zoom: ZoomSpec;\n}\n\n/** Durable, serializable view state — the unit of session persistence. */\nexport interface StageViewState extends StageSettings {\n cursor: number;\n anchor: Anchor;\n}\n\n/**\n * Host timing seam. The pure core (stage-core) never touches time; the camera tween\n * lives in this (impure) shell and asks for frames through a Scheduler. The default\n * is the browser's requestAnimationFrame; inject a fake in tests, or an instant one\n * in Node/SSR.\n */\nexport interface Scheduler {\n /** Run the callback on the next frame; returns a handle for cancellation. */\n raf(callback: (timestampMs: number) => void): number;\n /** Cancel a scheduled callback. */\n caf(handle: number): void;\n}\n\n/**\n * Options for the scroller writes — `Element.scrollTo` semantics: absolute\n * offsets (screen px) into the current scroll range (see\n * {@link StageCapability.scrollMetrics}); an omitted axis does not move.\n * `behavior` defaults to 'instant' (the DOM's 'auto'), NOT the stage's\n * `scrollBehavior` setting — that setting governs navigation verbs, and a\n * scrollbar thumb must track the pointer exactly.\n */\nexport interface StageScrollToOptions {\n left?: number;\n top?: number;\n behavior?: ScrollBehaviorKind;\n}\n\n/** Options for navigation intents. */\nexport interface GoToOptions {\n behavior?: ScrollBehaviorKind;\n /** Restore this exact viewpoint instead of fresh placement (per-page memory). */\n viewpoint?: Viewpoint;\n /** Override the landing for THIS navigation only (explicit beats default). */\n arrivalAlign?: Partial<ArrivalAlignment>;\n}\n\n/**\n * One axis of a reveal arrival — `scrollIntoView` vocabulary plus two\n * PDF-protocol necessities:\n * absent → minimal movement: scroll only if the target is off-screen\n * (CSS 'nearest'; today's reveal semantics)\n * 'keep' → this axis does not move AT ALL (PDF /XYZ null coordinate)\n * 'start' → target edge at the viewport start (plus padding)\n * 'center' → target centered\n * 'end' → target edge at the viewport end (minus padding)\n * number 0–1 → target CENTER at this viewport fraction (0.35 = \"top middle\",\n * the browser find-bar feel)\n */\nexport type RevealAnchorValue = 'keep' | 'start' | 'center' | 'end' | number;\n\nexport interface RevealAnchor {\n x?: RevealAnchorValue;\n y?: RevealAnchorValue;\n}\n\n/**\n * What happens to zoom on a reveal — always relative to the reveal's target\n * rect (the whole page when no `rect` is given):\n * 'keep' → pure pan, zoom untouched (search hits, /XYZ null zoom)\n * { level } → explicit factor (/XYZ zoom)\n * 'fit' → the rect fully visible (/FitR; /Fit, /FitB via rect=page/bbox)\n * 'fit-width' → the rect's width fills the viewport (/FitH, /FitBH)\n * 'fit-height' → the rect's height fills the viewport (/FitV, /FitBV)\n */\nexport type RevealZoom = 'keep' | 'fit' | 'fit-width' | 'fit-height' | { level: number };\n\n/**\n * Options for `reveal` — the follower-UI arrival verb (search hits, outline\n * clicks, PDF destinations, \"jump to comment\").\n *\n * With none of `rect`/`zoom`/`anchor` set, reveal keeps its original\n * semantics: minimal movement to make the page visible, cursor untouched.\n * A POSITIONED reveal (any of the three set) is \"you are now looking at\n * THIS spot\": the camera places the target per the anchor, a zoom\n * directive resolves to a concrete level (recorded as the zoom intent),\n * and the cursor follows the camera — while still clamping against the\n * normal camera bounds, so anchors are best-effort near document edges.\n */\nexport interface RevealOptions {\n behavior?: ScrollBehaviorKind;\n /**\n * Target rect on the page in the VIEWER's coordinates (y-down,\n * crop-relative, unscaled points — the same `Rect` selection/search rects\n * and `CommentThreadView.contentRect` live in). Absent or `null` → the\n * whole page (null accepted so nullable sources flow in directly). A\n * zero-size rect is a point (/XYZ).\n */\n rect?: Rect | null;\n zoom?: RevealZoom;\n anchor?: RevealAnchor;\n}\n\n/** The Stage's public contract: selectors (reads) + intents (the only writers). */\nexport interface StageCapability {\n // ── selectors ──\n camera(): Camera;\n viewport(): Size;\n /**\n * The camera as a NATIVE SCROLLER — the DOM scroll vocabulary in screen px:\n * `scrollTop`/`scrollHeight`/`clientHeight` (and the x twins) mean exactly\n * what they mean on a DOM element; `scrollableX/Y` false ⇔ nothing to scroll\n * on that axis (native: no bar). Derived from the SAME travel range the pan\n * clamp uses — paged flow reads the one-item slice — so a scrollbar built on\n * it can never disagree with where panning stops. On an UNBOUNDED stage the\n * range is the union of the padded content and the current window (the Figma\n * bar): pan away and it grows, the thumb shrinking toward the edge but always\n * remaining a road back. Reference-stable until a field actually changes.\n */\n scrollMetrics(): ScrollMetrics;\n pageCount(): number;\n visiblePages(): VisiblePage[];\n /** The current page (the cursor) — valid in both flows. */\n currentPage(): number;\n /** The display indices of the current item's pages (1 page, or a spread's pages). */\n currentItemPages(): number[];\n /** The full page list with PDF labels — for page thumbnails / worksheet tabs. */\n pages(): Array<{ index: number; pon: PageObjectNumber; label: string | null }>;\n /** The laid-out box for a page by its durable pon. */\n pageRect(pon: PageObjectNumber): VisiblePage | null;\n /**\n * Screen point (this Stage's container px) → the page under it + its content\n * point, or null over a gap. The viewport-level hit-test the interaction hub\n * needs so a single pointer source can drive page-aware features (text\n * selection, annotations) AND cross-page drags.\n */\n pageAt(screen: Point): {\n pon: PageObjectNumber;\n point: Point;\n scale: number;\n /** The hit page's TOTAL display rotation (document /Rotate + view rotation). */\n rotation: PageRotation;\n /** The hit page's zoom relative to its 100% baseline (`transform.zoom`). */\n zoom: number;\n } | null;\n /**\n * Screen point → `pon`'s content space, UNCLAMPED — valid even when the point\n * is outside the page's bounds (coordinates then fall outside `[0, size]`).\n * The frame-stable projection a page-anchored gesture (annotation move/resize)\n * tracks with, where `pageAt` would re-resolve to whatever page is under the\n * cursor (what a cross-page drag like text selection wants). Null when the\n * page isn't currently laid out.\n */\n pointOnPage(pon: PageObjectNumber, screen: Point): Point | null;\n /**\n * Page space (intrinsic PDF points) → world space. Applies the page's placed\n * origin and contentScale — the transform sizing policies introduce. Compose\n * with toScreen for viewport-space overlays anchored to page content.\n */\n pageToWorld(pon: PageObjectNumber, pt: Point): Point | null;\n /**\n * Content rect on a page → this Stage viewport's screen-space AABB. Applies page\n * rotation/contentScale and the current camera. Use for upright viewport overlays\n * that need to frame a selected page region.\n */\n pageRectToScreen(pon: PageObjectNumber, rect: Rect): Rect | null;\n toScreen(world: Point): Point;\n toWorld(screen: Point): Point;\n flow(): FlowMode;\n layout(): LayoutKind;\n spread(): SpreadMode;\n sizing(): SizingMode;\n columns(): GridColumns;\n bounded(): boolean;\n padding(): number;\n gap(): Gap;\n pageFrame(): PageFrame;\n fitAlign(): Alignment;\n arrivalAlign(): ArrivalAlignment;\n zoomAlign(): AlignmentValue;\n anchorAlign(): AlignmentValue;\n direction(): Direction;\n scrollBehavior(): ScrollBehaviorKind;\n /** The lens's view rotation — see {@link StageSettings.viewRotation}. */\n viewRotation(): PageRotation;\n zoomLevel(): number;\n /** The active zoom intent: a fit-mode, or 'custom' for a fixed level. */\n zoomMode(): ZoomModeValue | 'custom';\n /** What I'm looking at + zoom intent — capture for per-page view memory. */\n viewpoint(): Viewpoint;\n /** A snapshot of all settings (handy for building/saving a customer preset). */\n settings(): StageSettings;\n viewState(): StageViewState;\n\n // ── intents ──\n setViewport(vp: Size): void;\n /** Report the device pixel ratio (web: `devicePixelRatio`). The shell calls\n * this once on mount and whenever it changes (e.g. dragging between monitors)\n * so page transforms render crisp. */\n setDevicePixelRatio(dpr: number): void;\n setCamera(c: Camera): void;\n panBy(dxScreen: number, dyScreen: number): void;\n /** `Element.scrollTo` for the camera: absolute offsets into the scroll range\n * (see {@link StageScrollToOptions}) — clamped into it, cursor-synced, zoom\n * untouched (scrolling is a pan in scroller clothing). */\n scrollTo(opts: StageScrollToOptions): void;\n /** `Element.scrollBy`: relative offsets — sugar over `scrollTo`. */\n scrollBy(opts: StageScrollToOptions): void;\n zoomAround(screenPt: Point, factor: number): void;\n /**\n * Bracket a continuous direct-manipulation gesture (touch pan / pinch).\n * While a gesture is open the camera writes stay cheap and visually calm:\n * `zoomAround` defers its zoom-intent PATCH (one per gesture instead of one\n * per event), and the camera-rest detector holds un-rested — device\n * snapping and settle-gated rendering wait for the END of the gesture, not\n * for a 150 ms hesitation inside it. Re-entrant (nesting counts). Opening a\n * gesture cancels any running tween or fling — that is how a finger\n * \"catches\" a moving page.\n *\n * `elastic` opts the gesture into RUBBER-BAND overscroll: pans past the\n * clamp stretch on the iOS resistance curve instead of stopping dead, and\n * ending the gesture (or a fling reaching an edge) springs the camera home\n * on a critically-damped curve. The clamp itself stays the untouched law of\n * REST — elasticity is a transient the gesture is allowed to hold, never a\n * state the camera can settle in. Touch contacts pass it; mouse drags and\n * wheel pans stay rigid (the desktop convention). Default false.\n */\n beginGesture(options?: { elastic?: boolean }): void;\n endGesture(): void;\n /**\n * Momentum scroll: keep panning from a release velocity (screen px/s, the\n * same sign convention as `panBy` deltas), decelerating on UIScrollView's\n * curve. Every other camera verb — including `beginGesture` from the next\n * touch — cancels it. No-op without host frames (SSR/tests without a\n * scheduler jump nowhere: momentum is presentation, not state).\n */\n fling(velocityX: number, velocityY: number): void;\n /** True while the camera is animating (navigation tween or fling) — the\n * input layer reads it to tell a \"catch\" from a tap. */\n cameraInMotion(): boolean;\n /**\n * The touch double-tap toggle: zoomed out → animate to ~2.5× the automatic\n * fit around `screenPt`; already zoomed past that → animate back to the fit\n * level, same focal point. Lands as a fixed zoom level either way.\n */\n doubleTapZoom(screenPt: Point): void;\n zoomIn(): void;\n zoomOut(): void;\n zoomTo(spec: ZoomSpec): void;\n fitWidth(): void;\n fitPage(): void;\n /** Fit the whole scene (every page) in view — the construction overview. */\n fitAll(): void;\n /** Fit width but never upscale past 100% (Adobe's \"Automatic\"). */\n automatic(): void;\n /**\n * Re-resolve the active zoom intent and re-place against the CURRENT scene,\n * preserving the anchored page-point. Call after the page geometry changes\n * underneath the lens — rotate/move/delete — so fit/pixel zoom modes\n * (`fitPage`, `pageWidth`, …) recompute against the new footprint. A no-op\n * before the first placement; for a fixed `{ level }` zoom it just re-anchors.\n * Wired automatically to the document's page-registry revision; exposed for\n * any external geometry change a host wants to react to.\n */\n refit(): void;\n /** Go to a page. Fresh arrival places by the unit rule; pass `viewpoint` to restore. */\n goToPage(pageIndex: number, opts?: GoToOptions): void;\n /**\n * The follower-UI arrival verb (thumbnail sidebars, search hits, outline\n * clicks, PDF destinations). Bare: make the page visible with minimal\n * movement — zero if it already is, cursor untouched (scrollIntoView\n * semantics; paged flow delegates to navigation since the page isn't in\n * the slice). POSITIONED (rect/zoom/anchor set — see {@link RevealOptions}):\n * place the target at the anchor, optionally re-zooming; the cursor\n * follows the camera.\n */\n reveal(pageIndex: number, opts?: RevealOptions): void;\n /** Step forward by the navigation unit (the item if it fits the viewport, else the page). */\n next(opts?: GoToOptions): void;\n /** Step backward by the navigation unit. */\n prev(opts?: GoToOptions): void;\n /**\n * This lens's identity — the stage plugin id it was registered under\n * ('stage' for the main lens; a custom id per additional lens). The surface\n * binding stamps it on every pointer sample (`PointerSample.source`) and\n * lens-scoped interaction handlers register under it, so two stages on one\n * document can never capture each other's input.\n */\n lensId(): string;\n /** Set any subset of settings at once — ONE anchor-preserving update. The way to\n * apply a customer preset: `update(myPreset)`. Writes the responsive BASE:\n * a matching rule's key wins until its rule stops matching. */\n update(patch: Partial<StageSettings>): void;\n /** Replace the responsive rules (see {@link ResponsiveRule}); re-resolves\n * immediately with the usual anchor-preserving reactions. */\n setResponsive(rules: readonly ResponsiveRule[]): void;\n /** Is the named responsive rule currently matching? Reactive: recomputed\n * whenever the box crosses a rule boundary. */\n matches(name: string): boolean;\n /** Names of all currently-matching rules, in source order. */\n activeRules(): readonly string[];\n setFlow(flow: FlowMode): void;\n setLayout(layout: LayoutKind): void;\n setSpread(spread: SpreadMode): void;\n setSizing(sizing: SizingMode): void;\n setColumns(columns: GridColumns): void;\n setBounded(bounded: boolean): void;\n setPadding(padding: number): void;\n setGap(gap: Gap): void;\n setPageFrame(pageFrame: PageFrame): void;\n setFitAlign(fitAlign: Alignment): void;\n setArrivalAlign(arrivalAlign: ArrivalAlignment): void;\n setZoomAlign(zoomAlign: AlignmentValue): void;\n setAnchorAlign(anchorAlign: AlignmentValue): void;\n setDirection(direction: Direction): void;\n /** Set the lens's view rotation to an absolute quarter-turn — see\n * {@link StageSettings.viewRotation}. An anchor-preserving reframe, like a\n * page rotation: the spot you were looking at stays put (`anchorAlign`). */\n setViewRotation(viewRotation: PageRotation): void;\n /** Rotate the view a quarter-turn from where it is (the toolbar verb) —\n * relative sugar over {@link setViewRotation}, mirroring page-edit's `rotateBy`. */\n rotateView(delta: 90 | -90): void;\n setScrollBehavior(behavior: ScrollBehaviorKind): void;\n applyViewState(view: StageViewState): void;\n /** Offer a candidate initial view; the highest-priority non-null wins at placement. */\n provideInitialView(priority: number, provider: () => StageViewState | null): void;\n /** Resolve the registered providers once (else reset). Called when the viewport is ready. */\n placeInitial(): void;\n /** Back to the start: page 0, placed by the unit rule at the current zoom intent. */\n resetView(): void;\n}\n\nexport interface StageConfig extends Partial<StageSettings> {\n /** Override the host timing seam (tests/SSR). Defaults to browser rAF. */\n scheduler?: Scheduler;\n /**\n * Container queries for the settings bag (see {@link ResponsiveRule}).\n * Defaults to `DEFAULT_RESPONSIVE` (compact containers get the thin phone\n * gutter); pass `[]` to opt out entirely.\n */\n responsive?: readonly ResponsiveRule[];\n}\n\nexport const StageToken = createCapabilityToken<StageCapability>('stage');\n"],"mappings":";;;AA0pBA,MAAa,aAAa,sBAAuC,OAAO"}
@@ -0,0 +1,10 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_contract = require("./contract-BU33R4oL.cjs");
3
+ let _embedpdf_core_stage = require("@embedpdf/core-stage");
4
+ exports.StageToken = require_contract.StageToken;
5
+ Object.defineProperty(exports, "ZoomMode", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return _embedpdf_core_stage.ZoomMode;
9
+ }
10
+ });
@@ -0,0 +1,3 @@
1
+ import { C as Viewpoint, S as StageViewState, _ as StageConfig, a as GridColumns, b as StageState, c as RevealAnchor, d as RevealZoom, f as Scheduler, g as StageCapability, h as StageBox, i as GoToOptions, l as RevealAnchorValue, m as StageAction, n as FlowMode, o as LayoutKind, p as ScrollBehaviorKind, r as Gap, s as ResponsiveRule, t as BoxQuery, u as RevealOptions, v as StageScrollToOptions, w as VisiblePage, x as StageToken, y as StageSettings } from "./types-qdO_v-V-.cjs";
2
+ import { Align, Alignment, Direction, PageFrame, ScrollMetrics, SizingMode, SpreadMode, ZoomMode, ZoomModeValue, ZoomSpec } from "@embedpdf/core-stage";
3
+ export { type Align, type Alignment, type BoxQuery, type Direction, type FlowMode, type Gap, type GoToOptions, type GridColumns, type LayoutKind, type PageFrame, type ResponsiveRule, type RevealAnchor, type RevealAnchorValue, type RevealOptions, type RevealZoom, type Scheduler, type ScrollBehaviorKind, type ScrollMetrics, type SizingMode, type SpreadMode, type StageAction, type StageBox, type StageCapability, type StageConfig, type StageScrollToOptions, type StageSettings, type StageState, StageToken, type StageViewState, type Viewpoint, type VisiblePage, ZoomMode, type ZoomModeValue, type ZoomSpec };
@@ -0,0 +1,3 @@
1
+ import { C as Viewpoint, S as StageViewState, _ as StageConfig, a as GridColumns, b as StageState, c as RevealAnchor, d as RevealZoom, f as Scheduler, g as StageCapability, h as StageBox, i as GoToOptions, l as RevealAnchorValue, m as StageAction, n as FlowMode, o as LayoutKind, p as ScrollBehaviorKind, r as Gap, s as ResponsiveRule, t as BoxQuery, u as RevealOptions, v as StageScrollToOptions, w as VisiblePage, x as StageToken, y as StageSettings } from "./types-CdIFKQnj.js";
2
+ import { Align, Alignment, Direction, PageFrame, ScrollMetrics, SizingMode, SpreadMode, ZoomMode, ZoomModeValue, ZoomSpec } from "@embedpdf/core-stage";
3
+ export { type Align, type Alignment, type BoxQuery, type Direction, type FlowMode, type Gap, type GoToOptions, type GridColumns, type LayoutKind, type PageFrame, type ResponsiveRule, type RevealAnchor, type RevealAnchorValue, type RevealOptions, type RevealZoom, type Scheduler, type ScrollBehaviorKind, type ScrollMetrics, type SizingMode, type SpreadMode, type StageAction, type StageBox, type StageCapability, type StageConfig, type StageScrollToOptions, type StageSettings, type StageState, StageToken, type StageViewState, type Viewpoint, type VisiblePage, ZoomMode, type ZoomModeValue, type ZoomSpec };
@@ -0,0 +1,2 @@
1
+ import { n as StageToken, t as ZoomMode } from "./contract-CPSX8ANN.js";
2
+ export { StageToken, ZoomMode };
@@ -0,0 +1,147 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _embedpdf_core_geometry = require("@embedpdf/core-geometry");
3
+ //#region src/destination.ts
4
+ /**
5
+ * PDF destination → reveal: the pure translator between the PDF protocol's
6
+ * navigation vocabulary (ISO 32000-1 §12.3.2.2) and the Stage's arrival
7
+ * primitive. Outline clicks, link annotations, and `/OpenAction` all
8
+ * resolve (engine-side) to an explicit `PdfDestination`; this maps it onto
9
+ * ONE `reveal(pageIndex, options)` call — no destination-specific camera
10
+ * code exists anywhere else.
11
+ *
12
+ * The whole protocol collapses onto three knobs:
13
+ * rect — what to look at (a point for /XYZ, a rect for /FitR, the
14
+ * page for /Fit, the content bounding box for /FitB*)
15
+ * zoom — 'keep' | {level} | 'fit' | 'fit-width' | 'fit-height'
16
+ * anchor — where it lands; 'keep' encodes the spec's null-means-retain
17
+ *
18
+ * Coordinates convert PDF user space (y-up, absolute) → content space
19
+ * (y-down, crop-relative) through the SAME `pdfToContent` matrix selection
20
+ * and search use, so a destination and an overlay can never disagree.
21
+ */
22
+ /**
23
+ * Translate one explicit destination against its target page's layout.
24
+ * `bbox` is the page's content BOUNDING box in CONTENT space, for the
25
+ * `/FitB*` kinds — pass it when known; the crop box is the spec-tolerated
26
+ * fallback until the engine exposes it.
27
+ */
28
+ function destinationToReveal(dest, layout, bbox) {
29
+ const geo = (0, _embedpdf_core_geometry.pageGeometry)({
30
+ crop: layout.boxes.crop,
31
+ rotation: layout.rotation,
32
+ userUnit: layout.userUnit
33
+ }, 1);
34
+ const crop = layout.boxes.crop;
35
+ const toContent = (x, y) => (0, _embedpdf_core_geometry.applyPoint)(geo.pdfToContent, {
36
+ x,
37
+ y
38
+ });
39
+ const page = {
40
+ x: 0,
41
+ y: 0,
42
+ width: layout.size.width,
43
+ height: layout.size.height
44
+ };
45
+ const box = bbox ?? page;
46
+ const options = (() => {
47
+ switch (dest.kind) {
48
+ case "xyz": {
49
+ const hasLeft = dest.left != null;
50
+ const hasTop = dest.top != null;
51
+ const p = toContent(dest.left ?? crop.left, dest.top ?? crop.top);
52
+ return {
53
+ rect: {
54
+ x: p.x,
55
+ y: p.y,
56
+ width: 0,
57
+ height: 0
58
+ },
59
+ anchor: {
60
+ x: hasLeft ? "start" : "keep",
61
+ y: hasTop ? "start" : "keep"
62
+ },
63
+ zoom: dest.zoom != null && dest.zoom !== 0 ? { level: dest.zoom } : "keep"
64
+ };
65
+ }
66
+ case "fit": return { zoom: "fit" };
67
+ case "fitH": {
68
+ const hasTop = dest.top != null;
69
+ return {
70
+ rect: {
71
+ x: 0,
72
+ y: hasTop ? toContent(crop.left, dest.top).y : 0,
73
+ width: page.width,
74
+ height: 0
75
+ },
76
+ zoom: "fit-width",
77
+ anchor: { y: hasTop ? "start" : "keep" }
78
+ };
79
+ }
80
+ case "fitV": {
81
+ const hasLeft = dest.left != null;
82
+ return {
83
+ rect: {
84
+ x: hasLeft ? toContent(dest.left, crop.top).x : 0,
85
+ y: 0,
86
+ width: 0,
87
+ height: page.height
88
+ },
89
+ zoom: "fit-height",
90
+ anchor: { x: hasLeft ? "start" : "keep" }
91
+ };
92
+ }
93
+ case "fitR": {
94
+ const tl = toContent(dest.left, dest.top);
95
+ const br = toContent(dest.right, dest.bottom);
96
+ return {
97
+ rect: {
98
+ x: Math.min(tl.x, br.x),
99
+ y: Math.min(tl.y, br.y),
100
+ width: Math.abs(br.x - tl.x),
101
+ height: Math.abs(br.y - tl.y)
102
+ },
103
+ zoom: "fit"
104
+ };
105
+ }
106
+ case "fitB": return {
107
+ rect: box,
108
+ zoom: "fit"
109
+ };
110
+ case "fitBH": {
111
+ const hasTop = dest.top != null;
112
+ const y = hasTop ? toContent(crop.left, dest.top).y : box.y;
113
+ return {
114
+ rect: {
115
+ x: box.x,
116
+ y,
117
+ width: box.width,
118
+ height: 0
119
+ },
120
+ zoom: "fit-width",
121
+ anchor: { y: hasTop ? "start" : "keep" }
122
+ };
123
+ }
124
+ case "fitBV": {
125
+ const hasLeft = dest.left != null;
126
+ return {
127
+ rect: {
128
+ x: hasLeft ? toContent(dest.left, crop.top).x : box.x,
129
+ y: box.y,
130
+ width: 0,
131
+ height: box.height
132
+ },
133
+ zoom: "fit-height",
134
+ anchor: { x: hasLeft ? "start" : "keep" }
135
+ };
136
+ }
137
+ }
138
+ })();
139
+ return {
140
+ pageIndex: layout.index,
141
+ options
142
+ };
143
+ }
144
+ //#endregion
145
+ exports.destinationToReveal = destinationToReveal;
146
+
147
+ //# sourceMappingURL=destination.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destination.cjs","names":[],"sources":["../src/destination.ts"],"sourcesContent":["/**\n * PDF destination → reveal: the pure translator between the PDF protocol's\n * navigation vocabulary (ISO 32000-1 §12.3.2.2) and the Stage's arrival\n * primitive. Outline clicks, link annotations, and `/OpenAction` all\n * resolve (engine-side) to an explicit `PdfDestination`; this maps it onto\n * ONE `reveal(pageIndex, options)` call — no destination-specific camera\n * code exists anywhere else.\n *\n * The whole protocol collapses onto three knobs:\n * rect — what to look at (a point for /XYZ, a rect for /FitR, the\n * page for /Fit, the content bounding box for /FitB*)\n * zoom — 'keep' | {level} | 'fit' | 'fit-width' | 'fit-height'\n * anchor — where it lands; 'keep' encodes the spec's null-means-retain\n *\n * Coordinates convert PDF user space (y-up, absolute) → content space\n * (y-down, crop-relative) through the SAME `pdfToContent` matrix selection\n * and search use, so a destination and an overlay can never disagree.\n */\nimport { applyPoint, pageGeometry } from '@embedpdf/core-geometry';\nimport type { Rect } from '@embedpdf/core-geometry';\nimport type { PageLayout, PdfDestination } from '@embedpdf/core';\nimport type { RevealOptions } from './types';\n\nexport interface DestinationReveal {\n /** Display index for `reveal()` — from the layout row. */\n pageIndex: number;\n options: RevealOptions;\n}\n\n/**\n * Translate one explicit destination against its target page's layout.\n * `bbox` is the page's content BOUNDING box in CONTENT space, for the\n * `/FitB*` kinds — pass it when known; the crop box is the spec-tolerated\n * fallback until the engine exposes it.\n */\nexport function destinationToReveal(\n dest: PdfDestination,\n layout: PageLayout,\n bbox?: Rect,\n): DestinationReveal {\n const geo = pageGeometry(\n { crop: layout.boxes.crop, rotation: layout.rotation, userUnit: layout.userUnit },\n 1,\n );\n const crop = layout.boxes.crop;\n const toContent = (x: number, y: number) => applyPoint(geo.pdfToContent, { x, y });\n const page: Rect = { x: 0, y: 0, width: layout.size.width, height: layout.size.height };\n const box = bbox ?? page;\n\n const options = ((): RevealOptions => {\n switch (dest.kind) {\n case 'xyz': {\n const hasLeft = dest.left != null;\n const hasTop = dest.top != null;\n // Null axes keep the current camera value — the coordinate fed in\n // for them is inert (the 'keep' anchor never reads it).\n const p = toContent(dest.left ?? crop.left, dest.top ?? crop.top);\n return {\n rect: { x: p.x, y: p.y, width: 0, height: 0 },\n anchor: { x: hasLeft ? 'start' : 'keep', y: hasTop ? 'start' : 'keep' },\n // A /XYZ zoom of 0 means null means \"retain current\".\n zoom: dest.zoom != null && dest.zoom !== 0 ? { level: dest.zoom } : 'keep',\n };\n }\n case 'fit':\n return { zoom: 'fit' }; // whole page; slack axis centers\n case 'fitH': {\n const hasTop = dest.top != null;\n const y = hasTop ? toContent(crop.left, dest.top!).y : 0;\n return {\n rect: { x: 0, y, width: page.width, height: 0 },\n zoom: 'fit-width',\n anchor: { y: hasTop ? 'start' : 'keep' },\n };\n }\n case 'fitV': {\n const hasLeft = dest.left != null;\n const x = hasLeft ? toContent(dest.left!, crop.top).x : 0;\n return {\n rect: { x, y: 0, width: 0, height: page.height },\n zoom: 'fit-height',\n anchor: { x: hasLeft ? 'start' : 'keep' },\n };\n }\n case 'fitR': {\n const tl = toContent(dest.left, dest.top);\n const br = toContent(dest.right, dest.bottom);\n return {\n rect: {\n x: Math.min(tl.x, br.x),\n y: Math.min(tl.y, br.y),\n width: Math.abs(br.x - tl.x),\n height: Math.abs(br.y - tl.y),\n },\n zoom: 'fit',\n };\n }\n case 'fitB':\n return { rect: box, zoom: 'fit' };\n case 'fitBH': {\n const hasTop = dest.top != null;\n const y = hasTop ? toContent(crop.left, dest.top!).y : box.y;\n return {\n rect: { x: box.x, y, width: box.width, height: 0 },\n zoom: 'fit-width',\n anchor: { y: hasTop ? 'start' : 'keep' },\n };\n }\n case 'fitBV': {\n const hasLeft = dest.left != null;\n const x = hasLeft ? toContent(dest.left!, crop.top).x : box.x;\n return {\n rect: { x, y: box.y, width: 0, height: box.height },\n zoom: 'fit-height',\n anchor: { x: hasLeft ? 'start' : 'keep' },\n };\n }\n }\n })();\n\n return { pageIndex: layout.index, options };\n}\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { u as RevealOptions } from "./types-qdO_v-V-.cjs";
2
+ import { PageLayout, PdfDestination } from "@embedpdf/core";
3
+ import { Rect } from "@embedpdf/core-geometry";
4
+ //#region src/destination.d.ts
5
+ interface DestinationReveal {
6
+ /** Display index for `reveal()` — from the layout row. */
7
+ pageIndex: number;
8
+ options: RevealOptions;
9
+ }
10
+ /**
11
+ * Translate one explicit destination against its target page's layout.
12
+ * `bbox` is the page's content BOUNDING box in CONTENT space, for the
13
+ * `/FitB*` kinds — pass it when known; the crop box is the spec-tolerated
14
+ * fallback until the engine exposes it.
15
+ */
16
+ declare function destinationToReveal(dest: PdfDestination, layout: PageLayout, bbox?: Rect): DestinationReveal;
17
+ //#endregion
18
+ export { DestinationReveal, destinationToReveal };
19
+ //# sourceMappingURL=destination.d.cts.map
@@ -0,0 +1,19 @@
1
+ import { u as RevealOptions } from "./types-CdIFKQnj.js";
2
+ import { PageLayout, PdfDestination } from "@embedpdf/core";
3
+ import { Rect } from "@embedpdf/core-geometry";
4
+ //#region src/destination.d.ts
5
+ interface DestinationReveal {
6
+ /** Display index for `reveal()` — from the layout row. */
7
+ pageIndex: number;
8
+ options: RevealOptions;
9
+ }
10
+ /**
11
+ * Translate one explicit destination against its target page's layout.
12
+ * `bbox` is the page's content BOUNDING box in CONTENT space, for the
13
+ * `/FitB*` kinds — pass it when known; the crop box is the spec-tolerated
14
+ * fallback until the engine exposes it.
15
+ */
16
+ declare function destinationToReveal(dest: PdfDestination, layout: PageLayout, bbox?: Rect): DestinationReveal;
17
+ //#endregion
18
+ export { DestinationReveal, destinationToReveal };
19
+ //# sourceMappingURL=destination.d.ts.map
@@ -0,0 +1,146 @@
1
+ import { applyPoint, pageGeometry } from "@embedpdf/core-geometry";
2
+ //#region src/destination.ts
3
+ /**
4
+ * PDF destination → reveal: the pure translator between the PDF protocol's
5
+ * navigation vocabulary (ISO 32000-1 §12.3.2.2) and the Stage's arrival
6
+ * primitive. Outline clicks, link annotations, and `/OpenAction` all
7
+ * resolve (engine-side) to an explicit `PdfDestination`; this maps it onto
8
+ * ONE `reveal(pageIndex, options)` call — no destination-specific camera
9
+ * code exists anywhere else.
10
+ *
11
+ * The whole protocol collapses onto three knobs:
12
+ * rect — what to look at (a point for /XYZ, a rect for /FitR, the
13
+ * page for /Fit, the content bounding box for /FitB*)
14
+ * zoom — 'keep' | {level} | 'fit' | 'fit-width' | 'fit-height'
15
+ * anchor — where it lands; 'keep' encodes the spec's null-means-retain
16
+ *
17
+ * Coordinates convert PDF user space (y-up, absolute) → content space
18
+ * (y-down, crop-relative) through the SAME `pdfToContent` matrix selection
19
+ * and search use, so a destination and an overlay can never disagree.
20
+ */
21
+ /**
22
+ * Translate one explicit destination against its target page's layout.
23
+ * `bbox` is the page's content BOUNDING box in CONTENT space, for the
24
+ * `/FitB*` kinds — pass it when known; the crop box is the spec-tolerated
25
+ * fallback until the engine exposes it.
26
+ */
27
+ function destinationToReveal(dest, layout, bbox) {
28
+ const geo = pageGeometry({
29
+ crop: layout.boxes.crop,
30
+ rotation: layout.rotation,
31
+ userUnit: layout.userUnit
32
+ }, 1);
33
+ const crop = layout.boxes.crop;
34
+ const toContent = (x, y) => applyPoint(geo.pdfToContent, {
35
+ x,
36
+ y
37
+ });
38
+ const page = {
39
+ x: 0,
40
+ y: 0,
41
+ width: layout.size.width,
42
+ height: layout.size.height
43
+ };
44
+ const box = bbox ?? page;
45
+ const options = (() => {
46
+ switch (dest.kind) {
47
+ case "xyz": {
48
+ const hasLeft = dest.left != null;
49
+ const hasTop = dest.top != null;
50
+ const p = toContent(dest.left ?? crop.left, dest.top ?? crop.top);
51
+ return {
52
+ rect: {
53
+ x: p.x,
54
+ y: p.y,
55
+ width: 0,
56
+ height: 0
57
+ },
58
+ anchor: {
59
+ x: hasLeft ? "start" : "keep",
60
+ y: hasTop ? "start" : "keep"
61
+ },
62
+ zoom: dest.zoom != null && dest.zoom !== 0 ? { level: dest.zoom } : "keep"
63
+ };
64
+ }
65
+ case "fit": return { zoom: "fit" };
66
+ case "fitH": {
67
+ const hasTop = dest.top != null;
68
+ return {
69
+ rect: {
70
+ x: 0,
71
+ y: hasTop ? toContent(crop.left, dest.top).y : 0,
72
+ width: page.width,
73
+ height: 0
74
+ },
75
+ zoom: "fit-width",
76
+ anchor: { y: hasTop ? "start" : "keep" }
77
+ };
78
+ }
79
+ case "fitV": {
80
+ const hasLeft = dest.left != null;
81
+ return {
82
+ rect: {
83
+ x: hasLeft ? toContent(dest.left, crop.top).x : 0,
84
+ y: 0,
85
+ width: 0,
86
+ height: page.height
87
+ },
88
+ zoom: "fit-height",
89
+ anchor: { x: hasLeft ? "start" : "keep" }
90
+ };
91
+ }
92
+ case "fitR": {
93
+ const tl = toContent(dest.left, dest.top);
94
+ const br = toContent(dest.right, dest.bottom);
95
+ return {
96
+ rect: {
97
+ x: Math.min(tl.x, br.x),
98
+ y: Math.min(tl.y, br.y),
99
+ width: Math.abs(br.x - tl.x),
100
+ height: Math.abs(br.y - tl.y)
101
+ },
102
+ zoom: "fit"
103
+ };
104
+ }
105
+ case "fitB": return {
106
+ rect: box,
107
+ zoom: "fit"
108
+ };
109
+ case "fitBH": {
110
+ const hasTop = dest.top != null;
111
+ const y = hasTop ? toContent(crop.left, dest.top).y : box.y;
112
+ return {
113
+ rect: {
114
+ x: box.x,
115
+ y,
116
+ width: box.width,
117
+ height: 0
118
+ },
119
+ zoom: "fit-width",
120
+ anchor: { y: hasTop ? "start" : "keep" }
121
+ };
122
+ }
123
+ case "fitBV": {
124
+ const hasLeft = dest.left != null;
125
+ return {
126
+ rect: {
127
+ x: hasLeft ? toContent(dest.left, crop.top).x : box.x,
128
+ y: box.y,
129
+ width: 0,
130
+ height: box.height
131
+ },
132
+ zoom: "fit-height",
133
+ anchor: { x: hasLeft ? "start" : "keep" }
134
+ };
135
+ }
136
+ }
137
+ })();
138
+ return {
139
+ pageIndex: layout.index,
140
+ options
141
+ };
142
+ }
143
+ //#endregion
144
+ export { destinationToReveal };
145
+
146
+ //# sourceMappingURL=destination.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destination.js","names":[],"sources":["../src/destination.ts"],"sourcesContent":["/**\n * PDF destination → reveal: the pure translator between the PDF protocol's\n * navigation vocabulary (ISO 32000-1 §12.3.2.2) and the Stage's arrival\n * primitive. Outline clicks, link annotations, and `/OpenAction` all\n * resolve (engine-side) to an explicit `PdfDestination`; this maps it onto\n * ONE `reveal(pageIndex, options)` call — no destination-specific camera\n * code exists anywhere else.\n *\n * The whole protocol collapses onto three knobs:\n * rect — what to look at (a point for /XYZ, a rect for /FitR, the\n * page for /Fit, the content bounding box for /FitB*)\n * zoom — 'keep' | {level} | 'fit' | 'fit-width' | 'fit-height'\n * anchor — where it lands; 'keep' encodes the spec's null-means-retain\n *\n * Coordinates convert PDF user space (y-up, absolute) → content space\n * (y-down, crop-relative) through the SAME `pdfToContent` matrix selection\n * and search use, so a destination and an overlay can never disagree.\n */\nimport { applyPoint, pageGeometry } from '@embedpdf/core-geometry';\nimport type { Rect } from '@embedpdf/core-geometry';\nimport type { PageLayout, PdfDestination } from '@embedpdf/core';\nimport type { RevealOptions } from './types';\n\nexport interface DestinationReveal {\n /** Display index for `reveal()` — from the layout row. */\n pageIndex: number;\n options: RevealOptions;\n}\n\n/**\n * Translate one explicit destination against its target page's layout.\n * `bbox` is the page's content BOUNDING box in CONTENT space, for the\n * `/FitB*` kinds — pass it when known; the crop box is the spec-tolerated\n * fallback until the engine exposes it.\n */\nexport function destinationToReveal(\n dest: PdfDestination,\n layout: PageLayout,\n bbox?: Rect,\n): DestinationReveal {\n const geo = pageGeometry(\n { crop: layout.boxes.crop, rotation: layout.rotation, userUnit: layout.userUnit },\n 1,\n );\n const crop = layout.boxes.crop;\n const toContent = (x: number, y: number) => applyPoint(geo.pdfToContent, { x, y });\n const page: Rect = { x: 0, y: 0, width: layout.size.width, height: layout.size.height };\n const box = bbox ?? page;\n\n const options = ((): RevealOptions => {\n switch (dest.kind) {\n case 'xyz': {\n const hasLeft = dest.left != null;\n const hasTop = dest.top != null;\n // Null axes keep the current camera value — the coordinate fed in\n // for them is inert (the 'keep' anchor never reads it).\n const p = toContent(dest.left ?? crop.left, dest.top ?? crop.top);\n return {\n rect: { x: p.x, y: p.y, width: 0, height: 0 },\n anchor: { x: hasLeft ? 'start' : 'keep', y: hasTop ? 'start' : 'keep' },\n // A /XYZ zoom of 0 means null means \"retain current\".\n zoom: dest.zoom != null && dest.zoom !== 0 ? { level: dest.zoom } : 'keep',\n };\n }\n case 'fit':\n return { zoom: 'fit' }; // whole page; slack axis centers\n case 'fitH': {\n const hasTop = dest.top != null;\n const y = hasTop ? toContent(crop.left, dest.top!).y : 0;\n return {\n rect: { x: 0, y, width: page.width, height: 0 },\n zoom: 'fit-width',\n anchor: { y: hasTop ? 'start' : 'keep' },\n };\n }\n case 'fitV': {\n const hasLeft = dest.left != null;\n const x = hasLeft ? toContent(dest.left!, crop.top).x : 0;\n return {\n rect: { x, y: 0, width: 0, height: page.height },\n zoom: 'fit-height',\n anchor: { x: hasLeft ? 'start' : 'keep' },\n };\n }\n case 'fitR': {\n const tl = toContent(dest.left, dest.top);\n const br = toContent(dest.right, dest.bottom);\n return {\n rect: {\n x: Math.min(tl.x, br.x),\n y: Math.min(tl.y, br.y),\n width: Math.abs(br.x - tl.x),\n height: Math.abs(br.y - tl.y),\n },\n zoom: 'fit',\n };\n }\n case 'fitB':\n return { rect: box, zoom: 'fit' };\n case 'fitBH': {\n const hasTop = dest.top != null;\n const y = hasTop ? toContent(crop.left, dest.top!).y : box.y;\n return {\n rect: { x: box.x, y, width: box.width, height: 0 },\n zoom: 'fit-width',\n anchor: { y: hasTop ? 'start' : 'keep' },\n };\n }\n case 'fitBV': {\n const hasLeft = dest.left != null;\n const x = hasLeft ? toContent(dest.left!, crop.top).x : box.x;\n return {\n rect: { x, y: box.y, width: 0, height: box.height },\n zoom: 'fit-height',\n anchor: { x: hasLeft ? 'start' : 'keep' },\n };\n }\n }\n })();\n\n return { pageIndex: layout.index, options };\n}\n"],"mappings":"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"}