/* ============================================================================= * spreadsheet_figures.ts (주인 P · 534 sub6) * 그림 겹판 · 쪽 나눔 점선(요구 347-5) — 시트 `그림`(범위 닻)을 칸 위에 얹고 `인쇄.쪽나눔` 행 아래에 점선. * 격자 overlay 맨 아래 층(고름 테두리가 위) · 누름은 칸으로 통과 · 스크롤 · 확대 · 다시 그림마다 자리를 다시 셈. * 그림 주소 = 부른 쪽 `figureSrc(열쇠)` · 없으면 이름 적힌 빈 상자. 그림 옮기기 · 나눔 고치기 화면은 없음(1차). * `createSpreadsheet` 가 그림 · 쪽 나눔이 있는 문서일 때만 불러옴(크기 예산). * ========================================================================== */ import { parseRange } from "./spreadsheet_address"; import type { PartHandle, SpreadsheetContext } from "./spreadsheet_view_types"; export type FigureSrc = (열쇠: string) => string | undefined; const css = (node: HTMLElement, style: Partial) => Object.assign(node.style, style); export function attachFigures(ctx: SpreadsheetContext, figureSrc?: FigureSrc): PartHandle { const layer = document.createElement("div"); layer.className = "ss-figures"; css(layer, { position: "absolute", inset: "0", overflow: "hidden", pointerEvents: "none" }); const imgs = new Map(); // 열쇠 → 그림(다시 그려도 다시 안 받음) function draw(): void { const overlay = ctx.grid.root.querySelector(".aislo-grid__overlay"); if (!overlay) return; if (overlay.firstChild !== layer) overlay.prepend(layer); // editorSlot 상자 = 격자 뿌리 기준 → overlay 기준(머리 칸만큼 밀림) const box = (r: number, c: number) => { const b = ctx.grid.editorSlot(r, c).box; return { ...b, x: b.x - overlay.offsetLeft, y: b.y - overlay.offsetTop }; }; const sheet = ctx.sheet(); const parts: HTMLElement[] = []; for (const f of sheet.그림 ?? []) { const rg = parseRange(f.닻); if (!rg) continue; const a = box(rg.r0, rg.c0); const b = box(rg.r1, rg.c1); const frame = document.createElement("div"); frame.className = "ss-figure"; frame.dataset.key = f.열쇠; css(frame, { position: "absolute", left: `${a.x}px`, top: `${a.y}px`, width: `${Math.max(0, b.x + b.w - a.x)}px`, height: `${Math.max(0, b.y + b.h - a.y)}px`, boxSizing: "border-box", background: "#fff", }); const src = figureSrc?.(f.열쇠); if (src) { let img = imgs.get(f.열쇠); if (!img || img.getAttribute("src") !== src) { img = document.createElement("img"); img.src = src; img.alt = f.이름 ?? f.열쇠; css(img, { width: "100%", height: "100%", objectFit: "contain", display: "block" }); imgs.set(f.열쇠, img); } frame.append(img); } else { css(frame, { border: "1px dashed #888", color: "#666", font: "12px sans-serif", padding: "4px", }); frame.textContent = f.이름 ?? f.열쇠; } parts.push(frame); } const view = ctx.grid.visibleRange(); for (const k of sheet.인쇄?.쪽나눔 ?? []) { const at = box(k - 1, view.c0); const line = document.createElement("div"); line.className = "ss-pagebreak"; line.dataset.row = String(k); css(line, { position: "absolute", left: "0", right: "0", top: `${at.y + at.h - 1}px`, borderTop: "2px dashed #3b6fd8", }); parts.push(line); } layer.replaceChildren(...parts); } const onScroll = () => draw(); ctx.grid.root.addEventListener("scroll", onScroll, true); ctx.grid.root.addEventListener("aislo-grid-zoom", onScroll); draw(); return { root: null, refresh: draw, destroy() { ctx.grid.root.removeEventListener("scroll", onScroll, true); ctx.grid.root.removeEventListener("aislo-grid-zoom", onScroll); layer.remove(); }, }; }