81 lines
3.4 KiB
TypeScript
81 lines
3.4 KiB
TypeScript
/* =============================================================================
|
|
* 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;
|
|
}
|