import type { CSSProperties } from "react" import { type DocsGraphicAttrs, parseGraphicAttrs, } from "./docs-graphic-types.ts" export type DocsGraphicLayoutStyle = { wrapper: CSSProperties inner: CSSProperties content: CSSProperties behindText: boolean inFrontText: boolean } export function computeGraphicLayoutStyle( raw: Record | DocsGraphicAttrs ): DocsGraphicLayoutStyle { const attrs = "graphicType" in raw && typeof raw.graphicType === "string" ? (raw as DocsGraphicAttrs) : parseGraphicAttrs(raw) const width = attrs.width const height = attrs.height const rotation = attrs.rotationDeg const transformParts = rotation ? [`rotate(${rotation}deg)`] : [] const behindText = attrs.wrap === "behind" const inFrontText = attrs.wrap === "in-front" const isAbsolute = attrs.placement === "absolute" || behindText || inFrontText const wrapper: CSSProperties = { width: isAbsolute ? undefined : width, height: isAbsolute ? undefined : height, maxWidth: "100%", } const inner: CSSProperties = { width, height, transform: transformParts.length ? transformParts.join(" ") : undefined, transformOrigin: "center center", position: isAbsolute ? "absolute" : "relative", left: isAbsolute ? attrs.x : undefined, top: isAbsolute ? attrs.y : undefined, zIndex: behindText ? 0 : inFrontText ? 20 : attrs.zIndex || undefined, pointerEvents: behindText ? "none" : "auto", opacity: attrs.opacity < 1 ? attrs.opacity : undefined, boxShadow: attrs.shadow || undefined, } if (attrs.placement === "inline" || attrs.wrap === "inline") { inner.display = "inline-block" inner.verticalAlign = "baseline" } else if (attrs.wrap === "top-bottom") { inner.display = "block" inner.clear = "both" inner.marginBlock = "8px" if (attrs.floatSide === "center") { inner.marginInline = "auto" } } else if (attrs.wrap === "square" || attrs.wrap === "tight" || attrs.wrap === "through") { inner.display = "block" inner.float = attrs.floatSide === "right" ? "right" : "left" inner.marginInlineStart = attrs.floatSide === "right" ? "12px" : undefined inner.marginInlineEnd = attrs.floatSide === "right" ? undefined : "12px" inner.marginBlock = "4px" if (attrs.wrap === "tight") { inner.shapeOutside = "margin-box" inner.marginInlineStart = attrs.floatSide === "right" ? "6px" : undefined inner.marginInlineEnd = attrs.floatSide === "right" ? undefined : "6px" } if (attrs.wrap === "through") { inner.opacity = 0.85 inner.mixBlendMode = "multiply" } } else if (attrs.placement === "block") { inner.display = "block" inner.marginBlock = "8px" if (attrs.floatSide === "center") { inner.marginInline = "auto" } } const content: CSSProperties = { width: "100%", height: "100%", overflow: "hidden", } return { wrapper, inner, content, behindText, inFrontText } } export const RESIZE_HANDLES = [ "nw", "n", "ne", "e", "se", "s", "sw", "w", ] as const export type ResizeHandle = (typeof RESIZE_HANDLES)[number] export function resizeWithHandle( handle: ResizeHandle, startWidth: number, startHeight: number, dx: number, dy: number, minSize = 24, lockAspect = false ): { width: number; height: number; xOffset: number; yOffset: number } { let width = startWidth let height = startHeight let xOffset = 0 let yOffset = 0 if (lockAspect) { const aspect = startWidth / Math.max(startHeight, 1) if (Math.abs(dx) >= Math.abs(dy)) { width = startWidth + (handle.includes("w") ? -dx : handle.includes("e") ? dx : dx) height = width / aspect } else { height = startHeight + (handle.includes("n") ? -dy : handle.includes("s") ? dy : dy) width = height * aspect } if (handle.includes("w")) xOffset = startWidth - width if (handle.includes("n")) yOffset = startHeight - height } else { if (handle.includes("e")) width = startWidth + dx if (handle.includes("w")) { width = startWidth - dx xOffset = dx } if (handle.includes("s")) height = startHeight + dy if (handle.includes("n")) { height = startHeight - dy yOffset = dy } } width = Math.max(minSize, width) height = Math.max(minSize, height) if (handle.includes("w") && width === minSize) xOffset = startWidth - minSize if (handle.includes("n") && height === minSize) yOffset = startHeight - minSize return { width: Math.round(width), height: Math.round(height), xOffset, yOffset } }