Skip to content

Commit 7f537d3

Browse files
chrisryugjclaude
andcommitted
fix(render): 저장 표 높이가 안쪽 행 경계면 아래 여백 넘침 여유를 주지 않는다
쪽 바닥을 넘는 표가 아래 여백 안에서 끝나면 렌더는 표를 가르지 않고 한 쪽에 둔다(넘침 여유). 한컴이 행 사이에서 갈라 첫 쪽 조각만 sz 에 적은 표도 이 여유에 걸려, 다음 쪽에 찍혀야 할 마지막 행이 첫 쪽 아래 여백에 그려졌다. 저장 표 높이(sz)가 안쪽 행 경계(±2)와 같으면 넘침 여유를 주지 않는다. 근거 — 넘침 여유가 걸리는 표 122문서 추적: sz 가 안쪽 행 경계와 정확히 같은 표 3개(조달청 직제 시행규칙 별표 sz 71,649 = 26행 경계, 마지막 행 2,334 가 같은 판 한컴 PDF 다음 쪽 / 채용 공고 sz 28,347 = 2행 경계, 셋째 행이 다음 쪽 머리 행 아래 / 마스킹 결재문서). 나머지는 sz = 전체 높이(여유 규칙이 맞음). 실측(한컴 PDF 짝 렌더, 짝 문서 전체 목록 대조): - 쪽 수 1,422 → 1,423/1,431(조달청 직제 시행규칙 별표 1 → 2쪽), 새로 어긋난 문서 0 - 글 조각 86,080/90,433·그림 3,872/3,976 그대로(문서별 오르내림 0) - 테스트 4,305(통과 4,297·건너뜀 8; 새 검사: 세 행 표 sz 2,000(= 둘째 행까지)이면 마지막 행이 다음 쪽 — 옛 코드는 같은 쪽으로 실패, sz 5,000 이면 한 쪽), 쪽 경계 10/10, reflow 게이트 59/59, 왕복 PASS Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SK9LfgkTPmDtFJVSU4GmZy
1 parent d2e8411 commit 7f537d3

3 files changed

Lines changed: 32 additions & 0 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- COM 폴백(`extractTextViaCom`)과 벤치 `bench/hangul-com-pdf.ps1` 의 `RegisterModule` 첫 인수를 한컴 오토메이션의 모듈 종류 `'FilePathCheckDLL'` 로 바로잡는다. 종전 `'FilePathCheckerModule'` 은 보안 승인 모듈(DLL)을 `HKCU\Software\HNC\HwpAutomation\Modules` 에 등록해 둔 환경에서도 늘 `False` 라 접근 확인 창 억제가 걸리지 않았다(한글 2022 실측). `%TEMP%` 복사 우회는 그대로다.
1313
- 칸 높이를 잴 때, 줄 위에 두면 칸 안 앞 문단과 겹칠 위아래 배치 개체는 줄을 밀지 않은 것으로 보고 줄 자리에서 잰다(렌더). 종전엔 개체 높이만큼 줄 위에서 시작한다고 봐, 앞 문단 끝에 저장된 빈 문단 줄의 표가 차지하는 높이를 그만큼 덜 셌다 — 칸 안 8,408 표가 다음 쪽으로 넘어가야 할 별표가 한 쪽.
14+
- 저장 표 높이(`sz`)가 안쪽 행 경계(±2)와 같은 표 — 한컴이 행 사이에서 갈라 첫 쪽 조각만 적은 표 — 는 아래 여백으로 넘쳐 한 쪽에 남기지 않는다(렌더). 종전엔 표 전체가 아래 여백 안에서 끝나면 가르지 않아, 마지막 행이 다음 쪽에 찍히는 27행 별표를 한 쪽에 그렸다.
1415

1516
## [4.22.13] - 2026-10-18
1617

‎src/render/table-pages.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ export function planTableRows(tbl: Element, cells: CellModel[], rowH: number[],
9292
// 한컴이 칸 줄을 쪽에서 갈라 저장한 표(칸 안 줄 vertpos 가 되돌아감)는 아래 여백으로 넘치지 않는다 — 저장본이 이미 다음 쪽 조각을 적었다
9393
// (한컴 PDF: 아래 여백 안에서 끝날 서식 표가 칸 줄 되돌아간 자리부터 2쪽)
9494
if (slack > 0 && ty + rowY[nRows] > pageBottom && ty + rowY[nRows] <= pageBottom + slack && cells.some(c => cellLineBoxes(c).some(b => b.part > 0))) slack = 0
95+
// 저장 표 높이(sz)가 안쪽 행 경계와 같아도 — 한컴이 첫 쪽 조각만 sz 에 적었다(행 사이에서 가른 표) — 넘침 여유를 주지 않는다
96+
// (같은 판이 저장·인쇄한 한컴 PDF: sz 71,649 = 26행 경계인 27행 표의 마지막 행이 다음 쪽)
97+
if (slack > 0 && ty + rowY[nRows] > pageBottom && ty + rowY[nRows] <= pageBottom + slack) {
98+
const declared = num(findChildByLocalName(tbl, "sz"), "height")
99+
if (rowY.some((v, k) => k > 0 && k < nRows && Math.abs(v - declared) <= 2)) slack = 0
100+
}
95101
if (BODY_H <= 0 || (ty + rowY[nRows] <= pageBottom + slack) && !stackOver) return plain()
96102
// 글자처럼 취급하는 표는 쪽에서 가르지 않는다 — 쪽 맨 위에 있으면 그 쪽에 잘리고(한컴 PDF: 용지를 6567 넘는 인라인 서식 표도 1쪽),
97103
// 아니면 표째 다음 쪽 맨 위로(한컴 PDF: 앞 표 아래 49105 에 저장된 인라인 표가 다음 쪽에 찍힌다)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { describe, it } from "node:test"
2+
import assert from "node:assert/strict"
3+
import { createXmlParser } from "../src/hwpx/parser-shared.js"
4+
5+
const hp = 'xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph"'
6+
const seg = (th: number) => `<hp:lineseg textpos="0" vertpos="0" vertsize="${th}" textheight="${th}" baseline="${th * 0.85}" spacing="600" horzpos="0" horzsize="9000"/>`
7+
const row = (r: number, h: number, th = 1000) => `<hp:tr><hp:tc><hp:subList vertAlign="TOP"><hp:p><hp:run><hp:t>가</hp:t></hp:run><hp:linesegarray>${seg(th)}</hp:linesegarray></hp:p></hp:subList>` +
8+
`<hp:cellAddr colAddr="0" rowAddr="${r}"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="10000" height="${h}"/><hp:cellMargin left="0" right="0" top="0" bottom="0"/></hp:tc></hp:tr>`
9+
// 세 행(1000·1000·3000) 표 — 표 위 2000, 본문 0~6000, 아래 여백 넘침 여유 2000(표 바닥 7000 은 여유 안).
10+
// 마지막 행의 줄(높이 2500)은 남은 2000 에 안 든다
11+
const lastRowPage = async (declared: number) => {
12+
const { planTableRows } = await import("../src/render/table-pages.js")
13+
const { collectCells, measureTableRowHeights } = await import("../src/render/para-model.js")
14+
const t = createXmlParser().parseFromString(`<hp:tbl ${hp} pageBreak="CELL" rowCnt="3" colCnt="1"><hp:sz width="10000" height="${declared}"/>${row(0, 1000)}${row(1, 1000)}${row(2, 3000, 2500)}</hp:tbl>`, "text/xml").documentElement as unknown as Element
15+
return planTableRows(t, collectCells(t), measureTableRowHeights(t), 2000, 0, 6000, 2000)[2][0].page
16+
}
17+
18+
describe("render: 저장 표 높이가 첫 쪽 조각인 표와 아래 여백 넘침 여유", () => {
19+
it("sz 가 안쪽 행 경계(2000 = 둘째 행까지)면 한컴이 가른 표 — 넘침 여유 없이 마지막 행이 다음 쪽", async () => {
20+
assert.equal(await lastRowPage(2000), 1)
21+
})
22+
it("sz 가 표 전체 높이(5000)면 종전대로 아래 여백 안에서 한 쪽", async () => {
23+
assert.equal(await lastRowPage(5000), 0)
24+
})
25+
})

0 commit comments

Comments
 (0)