Files
Aislo/resources/tester/cad/table-from-drawing.test.ts
T

193 lines
5.7 KiB
TypeScript

/**
* 고른 범위 → 표 자료(`helpers/table-from-drawing.ts` · M02 83-1) 검증.
* 실행: node --experimental-strip-types resources/tester/cad/table-from-drawing.test.ts
* 견본 셋 = 표 엔티티 · 선 표(병합 · PolyLine · 끊긴 선) · 글만 + 빈 범위 · 글 없는 선 · 회전된 표.
*/
import assert from "node:assert/strict";
import {
cellValue,
tableFromDrawing,
type DrawingEntityJson,
} from "../../../B07_DesignDetail/openwebcad/src/helpers/table-from-drawing.ts";
const line = (x1: number, y1: number, x2: number, y2: number): DrawingEntityJson => ({
type: "Line",
shapeData: { startPoint: { x: x1, y: y1 }, endPoint: { x: x2, y: y2 } },
});
const text = (label: string, x: number, y: number, fontSize = 2.5): DrawingEntityJson => ({
type: "Text",
shapeData: { label, basePoint: { x, y }, options: { fontSize, textAlign: "left" } },
});
// 숫자 글
assert.equal(cellValue("1,500"), 1500, "천 단위 쉼표");
assert.equal(cellValue(" -2.75 "), -2.75, "음수 소수");
assert.equal(cellValue("12,34"), "12,34", "쉼표 자리 틀리면 글");
assert.equal(cellValue("D600"), "D600", "글 그대로");
assert.equal(cellValue("1,234,567.5"), 1234567.5);
// ① 표 엔티티 — 칸 그대로 · 병합 · 폭 · 높이
const tableEntity: DrawingEntityJson = {
type: "Table",
shapeData: {
origin: { x: 0, y: 100 },
columnWidths: [30, 20, 25],
rowHeights: [8, 6],
cells: [
[{ text: "구분", rowSpan: 2 }, { text: "수량", colSpan: 2 }, null],
[null, { text: "1,500" }, { text: "m2" }],
],
},
};
const t1 = tableFromDrawing([tableEntity, text("밖 글", 500, 500)]);
assert.ok(t1);
assert.equal(t1.kind, "table");
assert.deepEqual(t1.rows, [
["구분", "수량", ""],
["", 1500, "m2"],
]);
assert.deepEqual(t1.merges, [
{ row: 0, col: 0, rowSpan: 2, colSpan: 1 },
{ row: 0, col: 1, rowSpan: 1, colSpan: 2 },
]);
assert.deepEqual(t1.colWidths, [30, 20, 25]);
assert.deepEqual(t1.rowHeights, [8, 6]);
// ② 선 표 — 3열 × 3행 · 맨 위 줄 가운데 세로선 빠짐(병합) · 바깥 테두리는 PolyLine · 가로선 한 줄은 끊겨 둘
// x 경계 0 · 40 · 70 · 100 / y 경계 60 · 50 · 40 · 30
const outline: DrawingEntityJson = {
type: "PolyLine",
shapeData: null,
children: [
line(0, 60, 100, 60),
line(100, 60, 100, 30),
line(100, 30, 0, 30),
line(0, 30, 0, 60),
],
};
const gridEntities: DrawingEntityJson[] = [
outline,
line(0, 50, 55, 50), // 끊긴 가로선 둘(겹침 5)
line(50, 50.05, 100, 50.05), // 살짝 어긋남(허용오차 안)
line(0, 40, 100, 40),
line(40, 60, 40, 30), // 세로선 — 맨 위 줄도 지남
line(70, 50, 70, 30), // 맨 위 줄엔 없음 → (0,1)~(0,2) 병합
text("구분", 2, 53),
text("수량", 45, 53),
text("흙깎기", 2, 43),
text("1,500", 42, 43),
text("m3", 72, 43),
text("돌쌓기", 2, 33),
text("2.5", 42, 33),
text("m2", 72, 33),
text("(전면)", 2, 31), // 같은 칸 두 줄 → 「돌쌓기 (전면)」
];
const t2 = tableFromDrawing(gridEntities);
assert.ok(t2);
assert.equal(t2.kind, "grid");
assert.deepEqual(t2.rows, [
["구분", "수량", ""],
["흙깎기", 1500, "m3"],
["돌쌓기 (전면)", 2.5, "m2"],
]);
assert.deepEqual(t2.merges, [{ row: 0, col: 1, rowSpan: 1, colSpan: 2 }]);
assert.deepEqual(
t2.colWidths.map((w) => Math.round(w)),
[40, 30, 30],
);
assert.deepEqual(
t2.rowHeights.map((h) => Math.round(h)),
[10, 10, 10],
);
// 사각형 엔티티 칸 · 짧은 눈금 선은 경계로 안 침
const rectGrid = tableFromDrawing([
{
type: "Rectangle",
shapeData: {
points: [
{ x: 0, y: 0 },
{ x: 20, y: 0 },
{ x: 20, y: 10 },
{ x: 0, y: 10 },
],
},
},
{
type: "Rectangle",
shapeData: {
points: [
{ x: 20, y: 0 },
{ x: 50, y: 0 },
{ x: 50, y: 10 },
{ x: 20, y: 10 },
],
},
},
line(35, 0, 35, 1), // 눈금
text("가", 2, 4),
text("나", 22, 4),
]);
assert.ok(rectGrid);
assert.equal(rectGrid.kind, "grid");
assert.deepEqual(rectGrid.rows, [["가", "나"]]);
assert.deepEqual(rectGrid.colWidths, [20, 30]);
// ③ 글만 — 줄(y 묶음) · 칸(x 차례) · 모자란 칸은 빈 칸
const t3 = tableFromDrawing([
text("합계", 0, 10),
text("3,000", 40, 10.3),
text("비고", 80, 9.8),
text("소계", 0, 0),
text("1,000", 40, 0),
]);
assert.ok(t3);
assert.equal(t3.kind, "text");
assert.deepEqual(t3.rows, [
["합계", 3000, "비고"],
["소계", 1000, ""],
]);
assert.deepEqual(t3.merges, []);
// 빈 범위 · 글 없는 선 = null
assert.equal(tableFromDrawing([]), null, "빈 범위");
assert.equal(
tableFromDrawing([line(0, 0, 10, 0), line(0, 5, 10, 5), line(0, 0, 0, 5), line(10, 0, 10, 5)]),
null,
"글 없는 선",
);
// 회전된 표(30°) — 축 선이 없어 글 자리 폴백
const rot = (x: number, y: number): [number, number] => {
const a = Math.PI / 6;
return [x * Math.cos(a) - y * Math.sin(a), x * Math.sin(a) + y * Math.cos(a)];
};
const rline = (x1: number, y1: number, x2: number, y2: number) =>
line(...rot(x1, y1), ...rot(x2, y2));
const t4 = tableFromDrawing([
rline(0, 0, 40, 0),
rline(0, 10, 40, 10),
rline(0, 0, 0, 10),
rline(40, 0, 40, 10),
rline(20, 0, 20, 10),
text("A", ...rot(2, 4)),
text("B", ...rot(22, 4)),
]);
assert.ok(t4);
assert.equal(t4.kind, "text");
assert.equal(t4.rows.flat().filter(Boolean).length, 2, "글 둘 다 살아 있음");
// 격자 밖 글이 반 넘으면 폴백(표가 아닌 범위)
const t5 = tableFromDrawing([
line(0, 0, 10, 0),
line(0, 10, 10, 10),
line(0, 0, 0, 10),
line(10, 0, 10, 10),
text("안", 2, 4),
text("밖1", 50, 50),
text("밖2", 80, 50),
]);
assert.equal(t5?.kind, "text");
console.log("table-from-drawing: 통과");