diff --git a/A00_Common/spreadsheet/spreadsheet_commands.ts b/A00_Common/spreadsheet/spreadsheet_commands.ts index 0527c5d91..80b7a9d28 100644 --- a/A00_Common/spreadsheet/spreadsheet_commands.ts +++ b/A00_Common/spreadsheet/spreadsheet_commands.ts @@ -321,6 +321,22 @@ function insertDelete( }); if (s.조건부서식) s.조건부서식 = remapRanges(s.조건부서식); if (s.유효성) s.유효성 = remapRanges(s.유효성); + // 그림 닻 · 쪽 나눔 · 머리 행도 따라감(요구 347-5) — 다 지워진 그림 · 나눔 행은 뺌 + if (s.그림) { + s.그림 = s.그림.flatMap((f) => { + const next = parseRange(f.닻) && shiftRange(parseRange(f.닻)!); + return next ? [{ ...f, 닻: rangeToA1(next) }] : []; + }); + if (!s.그림.length) delete s.그림; + } + const print = rows ? s.인쇄 : undefined; + if (print?.쪽나눔) + print.쪽나눔 = print.쪽나눔.flatMap((k) => span(k - 1) ?? []).map((i) => i + 1); + if (print?.머리행) { + const sp = shiftSpan(print.머리행[0] - 1, print.머리행[1] - 1, insert, at, n, max); + if (sp) print.머리행 = [sp[0] + 1, sp[1] + 1]; + else delete print.머리행; + } const axis = rows ? "행" : "열"; const table = s[axis]; diff --git a/A00_Common/spreadsheet/spreadsheet_sample.json b/A00_Common/spreadsheet/spreadsheet_sample.json index 9d078d35f..a501fc0c1 100644 --- a/A00_Common/spreadsheet/spreadsheet_sample.json +++ b/A00_Common/spreadsheet/spreadsheet_sample.json @@ -70,7 +70,16 @@ }, "행": { "1": { "높이": 20 } }, "틀고정": { "행": 3 }, - "보기": { "눈금선": false } + "보기": { "눈금선": false }, + "인쇄": { + "용지": "A4", + "방향": "portrait", + "맞춤": { "폭": 1, "높이": 0 }, + "여백": { "위": 15, "아래": 15, "왼": 10, "오른": 10 }, + "머리행": [3, 3], + "쪽나눔": [4] + }, + "그림": [{ "열쇠": "fig/sample.png", "닻": "B8:F12", "이름": "단면 그림" }] }, { "id": "s2", diff --git a/A00_Common/spreadsheet/spreadsheet_types.ts b/A00_Common/spreadsheet/spreadsheet_types.ts index 502794912..75d193d3b 100644 --- a/A00_Common/spreadsheet/spreadsheet_types.ts +++ b/A00_Common/spreadsheet/spreadsheet_types.ts @@ -10,7 +10,8 @@ * · M02 만 `import()` 로 늦게 불러옴 — 다른 페이지 · `A00_Common/main.ts` 가 정적 import 금지. * · 엔진 파일(A · B)은 DOM 을 모름 — 화면 즉시 풀이와 [저장] 때 서버 Node 풀이가 같은 TS(CLAUDE.md 5장 ②). * · 보이는 칸만 그림 · 파일마다 700 줄 안쪽(예산 넘기 전에 쪼갬) · 화면 글자는 그 파일 안에 둠. - * · xlsx 가져오기 · 내보내기 · 인쇄 · PDF 는 만들지 않음(단추도 없음). xlsx 는 시험(F)만 읽음. + * · xlsx 가져오기 · 인쇄 단추는 없음. 내보내기(요구 347-5) = 서버가 시트 `인쇄` · `그림` 으로 — + * 인쇄 HTML(`spreadsheet_print_html.ts` · Node 진입 print) → PDF · xlsx = `common_util_workbook_xlsx.py`. * * ── 크기 예산 (vite build · 압축(gzip) 뒤 · M02 면적산출 · 재료산출을 열 때만 받는 조각) ── * 엔진 A+B 48 KB · 화면 C+D+E 72 KB · css 8 KB · 합 128 KB 안쪽. 재는 곳 = F(빌드 조각 크기 시험). @@ -67,7 +68,10 @@ * 3 spreadsheet_autosum.ts 자동 합계(Alt+= · Σ, 주인 sub_laptop_4 · 안 이어짐) ~90 * 3 spreadsheet_sheet_extra.ts 시트 숨김 · 보이기 · 탭 색(주인 sub_laptop_4 · 계약 더함 · 안 이어짐) ~150 * 3 spreadsheet_stage3.css 위 여섯 화면 스타일 한 벌 ~140 - * F common_util/common_util_spreadsheet_node.ts Node 진입(재계산 · 폴더 밖) ~40 + * P spreadsheet_figures.ts 그림 겹판 · 쪽 나눔 점선(늦게 불러옴 · 534 sub6) ~150 + * P spreadsheet_print_html.ts 통합문서 → 인쇄 HTML(DOM 없음 · 서버 Node print · 534 sub6) ~250 + * P common_util/common_util_workbook_xlsx.py 통합문서 → xlsx(openpyxl · 534 sub6) ~200 + * F common_util/common_util_spreadsheet_node.ts Node 진입(재계산 · 인쇄 HTML · 폴더 밖) ~40 * F common_util/common_util_spreadsheet.py 파이썬 껍데기(common_util_node_bundle) ~60 * F package.json `build:spreadsheet` · M02 `basis` 종류(Store · Layers) · 옛 산출근거 옮김 · * M02_MasterTemplete_Structure.ts 끼우기 · resources/tester/test_spreadsheet_*.py(실무 2,699 칸 대조 · 크기) @@ -198,6 +202,35 @@ export interface Sheet { /** 알약 연결(`spreadsheet_links.ts` · sub7 계약 더함) — 아래 알약(입력값 · 자재)이 가리키는 이 시트 칸. * 행열 넣기 · 지우기에 따라감(지운 칸이면 빠짐) · 바꿈은 `시트통째` 로(되돌리기 짝). */ 연결?: CellLink[]; + /** 인쇄 설정(요구 347-5) — 인쇄 HTML · PDF · xlsx 가 씀 · 화면은 쪽 나눔 점선만. */ + 인쇄?: SheetPrint; + /** 그림(요구 347-5) — 칸 범위에 얹음 · 화면 겹판 · 인쇄 HTML `` · xlsx 그림. */ + 그림?: SheetFigure[]; +} + +/** 시트 인쇄 설정 — 없는 값은 A4 · 세로 · 100% · 여백 10 mm. 시트 경계 = 늘 새 쪽. */ +export interface SheetPrint { + 용지?: "A4" | "A3"; + 방향?: "portrait" | "landscape"; + /** 배율 %(10~400) — `맞춤` 이 있으면 안 씀. */ + 배율?: number; + /** 쪽 수에 맞춤(엑셀 fitToWidth · fitToHeight · 0 = 제한 없음) — 인쇄 HTML 은 폭만 맞춤. */ + 맞춤?: { 폭?: number; 높이?: number }; + /** mm */ + 여백?: { 위?: number; 아래?: number; 왼?: number; 오른?: number }; + /** 쪽마다 되풀이할 머리 행 [첫 행, 끝 행](1 부터 · 엑셀 print_title_rows). 행열 넣기에 따라감. */ + 머리행?: [number, number]; + /** 이 행 **아래** 에서 쪽을 끊음(1 부터 · 엑셀 row_breaks 와 같음) · 오름차순. 행 넣기 · 지우기에 따라감(지운 행은 빠짐). */ + 쪽나눔?: number[]; +} + +/** 그림 한 장 — 범위를 채움(엑셀 두 칸 닻). 그림 자체는 문서에 안 넣음 — 부른 쪽이 `열쇠` 로 풂. */ +export interface SheetFigure { + /** 그림 열쇠(E05 = `fig/<측점 키>.png` 등) — 화면 `figureSrc` · 인쇄 · xlsx 가 주소 · 바이트로 바꿈. */ + 열쇠: string; + /** 칸 범위 A1(`B5:H20`) — 행열 넣기 · 지우기에 따라감(범위가 다 지워지면 그림도 빠짐). */ + 닻: string; + 이름?: string; } /** 자재DB 한 줄 — 재료산출 알약. */ diff --git a/resources/tester/spreadsheet/helper_spreadsheet_engine.cjs b/resources/tester/spreadsheet/helper_spreadsheet_engine.cjs index a2a05c41c..47e1a750a 100644 --- a/resources/tester/spreadsheet/helper_spreadsheet_engine.cjs +++ b/resources/tester/spreadsheet/helper_spreadsheet_engine.cjs @@ -529,6 +529,22 @@ section("commands", () => { ["A1:H1", "C3:F3", "C5:F5", "C6:F6", "A7:G7"], ]); }); + // 그림 닻 · 쪽 나눔 · 머리 행(요구 347-5)이 행열 넣기 · 지우기에 따라감 + roundTrip("행넣기 인쇄 · 그림", { 종류: "행넣기", 시트: "s1", at: 3, 수: 2 }, (b) => { + eq("쪽나눔 · 머리행 · 그림 밀림", [s1(b).인쇄.쪽나눔, s1(b).인쇄.머리행, s1(b).그림[0].닻], [[6], [3, 3], "B10:F14"]); + }); + roundTrip("행넣기 머리행 위", { 종류: "행넣기", 시트: "s1", at: 0, 수: 1 }, (b) => { + eq("머리행 밀림", s1(b).인쇄.머리행, [4, 4]); + }); + roundTrip("행지우기 인쇄 · 그림", { 종류: "행지우기", 시트: "s1", at: 3, 수: 1 }, (b) => { + eq("나눔 행 지움 · 그림 올라감", [s1(b).인쇄.쪽나눔, s1(b).그림[0].닻], [[], "B7:F11"]); + }); + roundTrip("행지우기 그림 통째", { 종류: "행지우기", 시트: "s1", at: 7, 수: 5 }, (b) => { + eq("그림 빠짐", s1(b).그림, undefined); + }); + roundTrip("열넣기 그림", { 종류: "열넣기", 시트: "s1", at: 0, 수: 1 }, (b) => { + eq("그림 열 밀림 · 쪽나눔 그대로", [s1(b).그림[0].닻, s1(b).인쇄.쪽나눔], ["C8:G12", [4]]); + }); roundTrip("행넣기 틀고정 위", { 종류: "행넣기", 시트: "s1", at: 0, 수: 2 }, (b) => { eq("틀고정 · 행 높이 밀림", [s1(b).틀고정, s1(b).행], [{ 행: 5 }, { 3: { 높이: 20 } }]); });