ultisuite-client/lib/drive/docs-graphic-position.ts
R3D347HR4Y 303b2b1074
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
wow
2026-06-11 01:22:40 +02:00

101 lines
3.1 KiB
TypeScript

import { DOCS_PAGE_GAP_PX } from "./docs-page-layout-constants.ts"
import {
type DocsGraphicAttrs,
parseGraphicAttrs,
} from "./docs-graphic-types.ts"
export type PageCoords = {
pageIndex: number
pageX: number
pageY: number
}
/** Graphics rendered on the page overlay layer (not clipped by prose). */
export function usesPageLayer(attrs: DocsGraphicAttrs | Record<string, unknown>): boolean {
const parsed = "graphicType" in attrs ? (attrs as DocsGraphicAttrs) : parseGraphicAttrs(attrs)
return (
parsed.wrap === "behind" ||
parsed.wrap === "in-front" ||
parsed.positionMode === "fixed-on-page"
)
}
export type PageLayerSlot = "behind" | "front"
/** Which page overlay slot should host this graphic. */
export function pageLayerSlot(attrs: DocsGraphicAttrs): PageLayerSlot | null {
if (!usesPageLayer(attrs)) return null
if (attrs.wrap === "behind") return "behind"
return "front"
}
export function pageLayerElementId(slot: PageLayerSlot): string {
return slot === "behind"
? "docs-page-graphic-layer-behind"
: "docs-page-graphic-layer-front"
}
export function pageTopPx(pageIndex: number, pageHeight: number): number {
return pageIndex * (pageHeight + DOCS_PAGE_GAP_PX)
}
export function pageIndexFromStackY(stackY: number, pageHeight: number): number {
if (stackY < 0) return 0
const stride = pageHeight + DOCS_PAGE_GAP_PX
return Math.max(0, Math.floor(stackY / stride))
}
export function stackYFromPageCoords(pageIndex: number, pageY: number, pageHeight: number): number {
return pageTopPx(pageIndex, pageHeight) + pageY
}
/** Convert DOM bbox (viewport px) to page-stack coordinates. */
export function bboxToPageCoords(
rect: DOMRect,
stackRect: DOMRect,
scale: number,
pageHeight: number
): PageCoords {
const stackX = (rect.left - stackRect.left) / scale
const stackY = (rect.top - stackRect.top) / scale
const pageIndex = pageIndexFromStackY(stackY, pageHeight)
const pageY = stackY - pageTopPx(pageIndex, pageHeight)
return {
pageIndex,
pageX: Math.round(stackX),
pageY: Math.round(Math.max(0, pageY)),
}
}
/**
* Re-resolve page index after a drag so pageY stays within the page the
* graphic visually sits on (a drag may cross page boundaries).
*/
export function normalizePageCoords(
pageIndex: number,
pageY: number,
pageHeight: number
): { pageIndex: number; pageY: number } {
if (pageHeight <= 0) {
return { pageIndex: Math.max(0, pageIndex), pageY: Math.round(pageY) }
}
const stackY = stackYFromPageCoords(Math.max(0, pageIndex), pageY, pageHeight)
const nextIndex = pageIndexFromStackY(stackY, pageHeight)
const rawY = stackY - pageTopPx(nextIndex, pageHeight)
// Clamp into the page band (a drop in the inter-page gap snaps to the page bottom).
const nextY = Math.min(Math.max(0, Math.round(rawY)), pageHeight)
return { pageIndex: nextIndex, pageY: nextY }
}
export function pageCoordsToStackStyle(
pageIndex: number,
pageX: number,
pageY: number,
pageHeight: number
): { left: number; top: number } {
return {
left: pageX,
top: stackYFromPageCoords(pageIndex, pageY, pageHeight),
}
}