/* ============================================================================= * B08_Quantity_UI_StructureFigure.ts * 구조물도 한 장의 **상단 그림** — 서버가 낸 모양 목록(m, 위가 +y)을 SVG 로 그림 (PLAN 12장). * * ⚠ 읽기 전용 — 치수를 셈하지 않음(서버 `B08_Quantity_Engine_StructureFigure` 한 벌). CAD 편집은 뒤. * ⚠ 못 그리는 장은 빈칸 대신 **까닭**을 같은 자리에 띄움. * ⚠ 한 장 = 그림 6 : 표 4(실무 비율) — 그림 칸 높이를 화면 높이의 60% 로 둠. * ========================================================================== */ import { el } from "./B08_Quantity_UI_StructureSheet_Formula"; export type FigureShape = | { kind: "path"; points: [number, number][]; role: "wall" | "guide"; dash: boolean } | { kind: "text"; text: string; at: [number, number]; align: "left" | "center" | "right"; size: number; }; const SVG_NS = "http://www.w3.org/2000/svg"; const ANCHOR = { left: "start", center: "middle", right: "end" } as const; export const FIGURE_CSS = ` .b08-sheet__figure { height: 60vh; min-height: 16rem; border: 1px solid var(--color-border); background: var(--color-surface); } .b08-sheet__figure svg { display: block; width: 100%; height: 100%; } .b08-sheet__figure--none { height: auto; min-height: 0; padding: 6px 10px; } .b08-fig__wall, .b08-fig__guide { fill: none; vector-effect: non-scaling-stroke; } .b08-fig__wall { stroke: var(--color-text); stroke-width: 2; } .b08-fig__guide { stroke: var(--color-text-secondary, #888); stroke-width: 1; } .b08-fig__guide.is-dash { stroke-dasharray: 6 4; } .b08-sheet__figure text { fill: var(--color-text); } `; /** 그림 칸 — 모양이 없으면 까닭 한 줄. */ export function figureSection( figure: FigureShape[] | null | undefined, reason: string | null | undefined, ): HTMLElement { const box = el("div", "b08-sheet__figure"); if (!figure?.length) { box.classList.add("b08-sheet__figure--none"); box.append(el("p", "b08-quantity__message", (reason ?? "그림 없음").replace(/\*\*/g, ""))); return box; } const svg = document.createElementNS(SVG_NS, "svg"); const group = document.createElementNS(SVG_NS, "g"); for (const shape of figure) { if (shape.kind === "text") { const text = document.createElementNS(SVG_NS, "text"); text.setAttribute("x", String(shape.at[0])); text.setAttribute("y", String(-shape.at[1])); text.setAttribute("font-size", String(shape.size)); text.setAttribute("text-anchor", ANCHOR[shape.align]); text.setAttribute("dominant-baseline", "middle"); text.textContent = shape.text; group.append(text); continue; } const line = document.createElementNS(SVG_NS, "polyline"); line.setAttribute("points", shape.points.map(([x, y]) => `${x},${-y}`).join(" ")); line.setAttribute("class", `b08-fig__${shape.role}${shape.dash ? " is-dash" : ""}`); group.append(line); } svg.append(group); box.append(svg); // 보이는 틀은 **그려진 뒤 실제 글자 폭**으로 — 글자 폭을 서버가 짐작하지 않게. requestAnimationFrame(() => { if (!svg.isConnected) return; const b = group.getBBox(); const pad = Math.max(b.width, b.height) * 0.04; svg.setAttribute( "viewBox", `${b.x - pad} ${b.y - pad} ${b.width + pad * 2} ${b.height + pad * 2}`, ); }); return box; }