Skip to content

Commit 1800ca3

Browse files
planet6897claude
andcommitted
Task #1068: HWPX treat_as_char 표 LINE_SEG lh 보정 over-inflation 정정 (closes #1068)
## 증상 거의 한 페이지 크기의 treat_as_char 표가 제목줄과 같은 문단에 있을 때, 표 줄이 본문 하단 아래로 통째로 그려져 ~839px overflow. ## 근본 원인 `document_core/commands/document.rs` 의 HWPX TAC 표 LINE_SEG lh 보정이 무조건 `first_mut()`(제목줄)을 표 높이로 확대했다. 표가 둘째 줄 이후에 있는 문단(제목줄 + 표줄)에서 제목줄 lh 가 표 높이로 오염(예: vertsize 2200 → 63234)되어, 렌더러의 lh 기반 표 줄 탐지(place_table_with_text)가 제목줄을 표 줄로 오매칭 → 표 줄이 post-text 로 포함되어 Table item 과 이중 그리기 → overflow. HWPX XML 원본(linesegarray vertsize) 대조로 제목줄의 원래 높이가 표 높이가 아님을 확정. ## 수정 이미 표 높이를 담은 LINE_SEG 가 있으면(한컴이 저장한 실제 linesegarray — 표 줄 seg 의 vertsize 가 표 높이) 보정을 생략한다. linesegarray 가 없어 기본 lh=100 단일 seg 만 합성된 경우에만 첫 seg 를 표 높이로 확대 (기존 의도 보존). 라우팅·렌더러 미변경. ## 검증 - 타깃 문서 표줄 overflow 839px → 해소. - 전수 sweep(samples hwp/hwpx): overflow 라인/픽셀 합 회귀 0 (소폭 개선, tac-img-02 / aift 개선). - golden SVG 8/8, cargo test --release lib 1324 passed, clippy/fmt clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent af85125 commit 1800ca3

7 files changed

Lines changed: 346 additions & 5 deletions

File tree

mydocs/plans/task_m100_1068.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# 수행계획서 — Task #1068: 거의 한 페이지 크기 treat_as_char 표 새 페이지 미이월
2+
3+
- 이슈: edwardkim/rhwp#1068
4+
- 브랜치: `local/task1068` (stream/devel 기준)
5+
6+
## 1. 배경 / 증상
7+
8+
거의 한 페이지 크기의 **글자처럼 취급(treat_as_char) 표**를 포함한 문단이, 분할 시 새 페이지로
9+
이월되지 않고 **현재 페이지 하단에 배치되어 표 전체가 본문 영역 아래로 렌더링**된다.
10+
11+
관측(비공개 RFP 문서, 커밋 불가):
12+
- 문단에 treat_as_char 표 1개, 표 높이 ≈ 63234 HU(≈843px), 본문(A4 ≈941px)에 들어가는 크기.
13+
- 표 줄(`lh=63234`)이 본문 하단(y≈1046) 에서 시작 → `LAYOUT_OVERFLOW_DRAW overflow≈839px`.
14+
- 앞뒤 문단이 `[쪽나누기]` → 한컴은 표를 독립 페이지에 배치.
15+
16+
## 2. 추정 원인 (정독으로 확인 예정)
17+
18+
- `typeset.rs` atomic TAC top-fit 가드(`is_atomic_tac_singleton`)가 treat_as_char Picture/Shape
19+
만 처리, **Table 제외**. 거의 한 페이지 표가 "들어갈 새 페이지 이월" 처리 누락 → 분할 줄 배치
20+
에서 표 줄이 현재 페이지 하단에 얹힘.
21+
- 단, `typeset.rs:1100` 등에 treat_as_char Table 분기가 일부 존재 → Stage 1에서 실제 경로 확인.
22+
23+
## 3. 목표
24+
25+
- 본문보다 작은 treat_as_char 표가 현재 페이지에 안 들어가면 **새 페이지 상단으로 이월**되어
26+
본문 안에 온전히 배치. 해당 문단 LAYOUT_OVERFLOW/_DRAW 0.
27+
28+
## 4. 제약 — 비공개 문서 + 공개 픽스처
29+
30+
- 재현 권위 문서는 비공개(`samples/2. 인공지능...제안요청서.hwpx`) → **git add 금지**, 분석만.
31+
- **공개 합성 픽스처** 생성(near-full-page treat_as_char 표 + 전후 쪽나누기)하여 테스트로 커밋.
32+
`gen-table` CLI 또는 별도 생성기 활용.
33+
34+
## 5. 비회귀
35+
36+
- 기존 treat_as_char 표/그림/도형 케이스(tac-case-*, tac-img-*, table-in-tbox 등), 골든 SVG 8종,
37+
전 251 샘플 LAYOUT_OVERFLOW 합계(현 769) 무회귀.
38+
39+
## 6. 조사·접근 방향 (구현계획서 구체화)
40+
41+
1. typeset.rs 분할(split) 경로에서 treat_as_char 표 줄 배치 정독 + 비공개 문서로 실제 흐름 확인.
42+
2. 공개 합성 픽스처 생성·재현.
43+
3. atomic TAC 이월을 treat_as_char Table(본문보다 작은 단일 표 줄)로 확장하는 정합안 설계.
44+
45+
## 7. 검증
46+
47+
- 합성 픽스처 + 비공개 문서에서 해당 overflow 0, 한컴 PDF 정합(독립 페이지 배치).
48+
- `cargo test --release`, 골든 회귀 0, 251 합계 무회귀, `cargo fmt`(변경 파일).
49+
50+
## 8. 범위
51+
52+
- `src/renderer/typeset.rs` (분할/atomic TAC 표 이월). HWP3 전용 분기 추가 금지.
53+
54+
---
55+
승인 후 구현계획서(`task_m100_1068_impl.md`, 3~6단계) 작성 → 재승인 → 단계 진행.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# 구현계획서 — Task #1068: treat_as_char 표 새 페이지 이월
2+
3+
- 이슈: edwardkim/rhwp#1068
4+
- 브랜치: `local/task1068` (stream/devel 기준)
5+
- 수행계획서: `task_m100_1068.md` (승인 완료)
6+
7+
## 관련 코드 (정독 출발점)
8+
9+
- atomic TAC top-fit 가드: `typeset.rs:1851` `is_atomic_tac_singleton` (Picture/Shape 한정).
10+
- treat_as_char Table 분기: `typeset.rs:1100` 등.
11+
- 분할(split) 경로: `typeset.rs` 줄 단위 분할 (line_count>0 이후).
12+
- 합성 픽스처: `gen-table` CLI (`src/main.rs:43`).
13+
14+
## 단계 (4단계)
15+
16+
### Stage 1 — 조사 + 공개 픽스처 (소스 무변경)
17+
- typeset.rs 분할 경로에서 **treat_as_char 표 줄**이 어떻게 배치되는지 정독 + 비공개 문서로
18+
실제 흐름(`LAYOUT_OVERFLOW`/dump-pages) 확인. atomic TAC 가드가 표를 왜 제외하는지 확정.
19+
- **공개 합성 픽스처 생성**: near-full-page treat_as_char 표 1개 + 전후 쪽나누기 문단.
20+
`gen-table` 또는 별도 생성기로 `.hwp`/`.hwpx` 산출 → `samples/`(공개) 또는 테스트 픽스처로 추가.
21+
비공개 문서와 동일 증상(표 줄 본문 하단 overflow) 재현 확인.
22+
- 산출물: `working/task_m100_1068_stage1.md` + 픽스처 파일. 커밋: 보고서 + 공개 픽스처(비공개 금지).
23+
24+
### Stage 2 — 설계 + 페이퍼 검증 (소스 무변경)
25+
- atomic TAC 이월 가드를 treat_as_char **Table**(본문보다 작은 단일 표 줄)로 확장하는 정합안.
26+
- 조건: 표 줄 높이 ≤ 본문 높이(이월 시 들어감) & 현재 페이지에 안 들어감 → 새 페이지 이월.
27+
- page-larger(본문보다 큰 표, #874) 케이스와 구분(이월해도 안 들어가면 분할).
28+
- 비회귀 케이스(tac-case-*, tac-img-*, table-in-tbox) 예상 동작 표로 모순 점검.
29+
- 산출물: `working/task_m100_1068_stage2.md`.
30+
31+
### Stage 3 — 구현
32+
- typeset.rs 분할/atomic 경로에 표 이월 조건 반영. 최소 변경. 단위 TDD(픽스처 기반).
33+
- 산출물: 소스 + `working/task_m100_1068_stage3.md`.
34+
35+
### Stage 4 — 회귀 검증
36+
- 합성 픽스처 + 비공개 문서 해당 overflow 0, 한컴 PDF 정합(독립 페이지).
37+
- 비회귀 전수(tac-*, table-*), 골든 SVG 8종, `cargo test --release`, 251 합계(769) 무회귀,
38+
`cargo fmt`(변경 파일).
39+
- 산출물: `working/task_m100_1068_stage4.md``report/task_m100_1068_report.md`.
40+
41+
## 완료 기준
42+
treat_as_char 표 본문 하단 overflow 해소(이월 배치) + 비회귀 0 + 골든 회귀 0.
43+
44+
## 리스크
45+
- page-larger(본문보다 큰 표) 와 이월 가능 표의 구분 오류 → 무한 이월/누락. Stage 2에서 경계 명시.
46+
- 합성 픽스처가 비공개 문서 증상을 정확히 재현 못 할 가능성 → Stage 1에서 재현 확인 후 진행.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Stage 1 보고서 — Task #1068: treat_as_char 표 새 페이지 미이월 (조사)
2+
3+
- 브랜치: `local/task1068` (stream/devel 기준, 소스 무변경)
4+
- 재현 문서: 비공개 RFP (`samples/2. 인공지능...제안요청서.hwpx`, 분석만·커밋 금지)
5+
6+
## 확정 사실
7+
8+
- 문제 항목: para 567 = 제목줄(th=2200≈29px) + **treat_as_char 표**(48081×63234HU, 641×**843px**).
9+
- 앞 문단 pi566 "[붙임 4]"(17px), 뒤 pi568 "[붙임 5]" — para 567 은 독립 배치 의도.
10+
- `LAYOUT_OVERFLOW_DRAW: pi=567 line=1 y=1886 overflow≈839px` — 표 줄이 본문 하단 아래로 통째 렌더.
11+
- **dump-pages 페이지 92: items=3, used=1797.9px** (본문 941px의 ~2배!):
12+
- `FullParagraph pi=566 h=17.3`
13+
- `Table pi=567 ... 843px`
14+
- `PartialParagraph pi=567 lines=0..2`
15+
16+
## 핵심 가설 — treat_as_char 표 높이 이중 계상
17+
18+
`used=1797 ≈ pi566(17) + 표(843) + 문단(제목29+표줄843=872)`**표 높이가 두 번 계상**:
19+
1. 별도 `Table` PageItem (843px)
20+
2. PartialParagraph 의 표 줄 line_height (843px, line_segs ls[1] lh=63234)
21+
22+
→ 페이지 used 가 과충전되고, 분할 줄 배치에서 표 줄이 페이지 하단에 얹혀 본문 밖으로 렌더.
23+
(분할 fit 산식은 제목29+표843=872 < 잔여~924 로 "들어감" 판정하나, 실제 누적/렌더는 이중
24+
계상·vpos 매핑으로 어긋남.)
25+
26+
- `is_atomic_tac_singleton`(typeset.rs:1851) 은 Picture/Shape 만 → TAC 표 이월 미처리 (관련).
27+
- 비-TAC 표 wrap-around 처리(1096-1122)는 TAC 표 제외.
28+
29+
## 코드 경로 확정 (정정)
30+
31+
para 567(표 포함)은 일반 줄 분할(1909)이 **아니라 표 배치 경로**(`typeset.rs` ~2520-2620)로
32+
처리된다. 이 경로:
33+
- `PageItem::Table` push (2584) + pre-text `PartialParagraph` push (2573).
34+
- **`tac_wrap_split`(2597)**: `treat_as_char && pre_table_end_line>0 && < total_lines` — "전폭 TAC
35+
표가 자기 줄(line index=pre_table_end_line)에 놓인 split 케이스" 를 **이미 인지**(주석 2593-2596,
36+
이중 계산 방지 의도, Task #853).
37+
- 그러나 para 567 에서 used=1797px 로 과충전 → 이 경로의 **페이지 fit/break(이월) 처리가 near-
38+
full-page TAC 표에 대해 불완전**.
39+
40+
→ 수정 영역은 **표 배치 경로의 TAC 표 fit/이월 로직**(2520-2620, tac_wrap_split 인근).
41+
`is_atomic_tac_singleton`(1851, Picture/Shape) 와는 별개 경로.
42+
43+
## 공개 픽스처
44+
45+
- `gen-table` CLI 는 일반 표만 생성(treat_as_char·페이지 크기·전후 쪽나누기 미지원).
46+
- near-full-page treat_as_char 표 + 전후 쪽나누기 픽스처는 **별도 생성기/수작업 구축 필요**
47+
Stage 2 초입에서 구축(공개 커밋 가능).
48+
49+
## 잠정 결론
50+
51+
- 수정 영역 localize 완료: **표 배치 경로(2520-2620)의 TAC 표 페이지 fit/이월**.
52+
- used=1797px(본문 2배)는 표 줄 + Table item 의 누적 정책(tac_wrap_split)이 이 케이스를 완전히
53+
커버하지 못함을 시사. Stage 2에서 누적·fit/break 산식을 정밀 분해 + 공개 픽스처 재현 후 설계.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Stage 2 보고서 — Task #1068: 근본 원인 확정 + 수정 설계
2+
3+
- 브랜치: `local/task1068` (진단 후 전량 revert, 소스 무변경)
4+
5+
## 근본 원인 — 확정 (HWPX TAC 표 attr 비트 미설정 → 오라우팅)
6+
7+
진단 (`DIAG_TAC`, para 567):
8+
```
9+
pi=567 ft.is_tac=false attr&1=0 treat_as_char=true rows=14 measured_h=843.1 base_avail=941.1
10+
```
11+
12+
- 표 디스패치(`typeset.rs:2332`): `if ft.is_tac { typeset_tac_table } else { typeset_block_table }`.
13+
- `format_table`(2091): `is_tac = table.attr & 0x01 != 0`.
14+
- **HWPX 파서**(`section.rs:1104`)는 `common.treat_as_char` 만 설정, **`attr & 0x01`(HWP5식 TAC
15+
비트) 미설정** → HWPX TAC 표는 `is_tac=false`**block 경로 오라우팅**.
16+
- block 경로는 inline TAC 배치/페이지 fit 을 적용하지 않아 표(843px<941)가 페이지 하단에 얹혀
17+
본문 밖 839px 렌더 (used=1797px).
18+
19+
`attr & 0x01` 의존 사이트가 typeset 에 **9곳**(2091/2252/2429/2452/2486/2620/2624/2656/3760) →
20+
format_table 한 곳만 고치면 나머지 미정합.
21+
22+
## 수정 설계 — 파서 정규화 (포괄적·단일점)
23+
24+
`parse_table`(`src/parser/hwpx/section.rs:1014`) 반환 직전:
25+
```rust
26+
if table.common.treat_as_char { table.attr |= 0x01; }
27+
```
28+
→ HWPX TAC 표의 `attr` 비트0 을 treat_as_char 와 일치(HWP5 정합)시켜, 모든 `attr & 0x01`
29+
렌더 사이트가 일관 동작. (HWP5 파서는 이미 비트0 설정 — 본 수정은 HWPX 한정.)
30+
31+
대안(미채택): format_table is_tac 만 `|| treat_as_char` — 나머지 8개 사이트 미정합으로 불완전.
32+
33+
## 페이퍼 검증 (모순 점검)
34+
35+
| 케이스 | attr&1(전) | 수정 후 | 라우팅 |
36+
|------|------|------|------|
37+
| HWPX TAC 표(treat_as_char=true) | 0 | **1** | block→**tac** (정정) |
38+
| HWPX 블록 표(treat_as_char=false) | 0 | 0 | block (불변) |
39+
| HWP5 TAC 표 | 1 | 1 | tac (불변, 파서 다름) |
40+
| HWP5 블록 표 | 0 | 0 | block (불변) |
41+
42+
→ treat_as_char=true 인 HWPX 표만 비트 추가. 다른 케이스 불변. 모순 없음.
43+
44+
- typeset_tac_table 경로: table_height=fmt.height_for_fit, fit 체크 후 `advance_column_or_new_page`
45+
→ 843px 표가 페이지(941)에 fit → 본문 내 배치. (para 567: pi566 17px + 843 = 860 < 941)
46+
47+
## 리스크 / 검증 항목
48+
49+
- **직렬화 round-trip**: HWPX 직렬화는 treatAsChar 속성 사용(attr 비트 아님)이라 영향 없을 것으로
50+
보이나, HWPX→HWP 직렬화에서 attr 비트0 기록 확인(HWP5에선 TAC=비트0 이라 정합) — Stage 3 점검.
51+
- **공개 테스트 픽스처**: 비공개 문서 대신, 트래킹된 HWPX 중 treat_as_char 표 보유 샘플
52+
(`표-텍스트.hwpx` 등) 로 동일 라우팅·overflow 재현 확인 → 테스트. 없으면 최소 HWPX 합성.
53+
- 비회귀: tac-*/table-* 골든, 전 251 LAYOUT_OVERFLOW 합계.
54+
55+
## 다음 (Stage 3)
56+
파서 정규화 1줄 + 공개 픽스처 확정 → 빌드·smoke(제안요청서 839px 해소) → 회귀 검증.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Stage 3 보고서 — Task #1068: 파서 정규화 시도 → 반려 + 재설계
2+
3+
- 브랜치: `local/task1068` (시도 후 revert, 소스 클린)
4+
5+
## 시도 — 파서 정규화 (Stage 2 설계)
6+
7+
`parse_table` 반환 직전 `if treat_as_char { attr |= 0x01 }` → HWPX TAC 표를 TAC 경로로 라우팅.
8+
9+
## 결과 — 타깃 해소 but 광범위 회귀 (반려)
10+
11+
- ✅ 제안요청서.hwpx: para 567 **839px → 해소**(잔여 ≤27px), cargo test 1324 + 골든 전부 통과.
12+
- ❌ 251 샘플 LAYOUT_OVERFLOW **1626 → 1670 (+44)**, HWPX **6파일 악화**:
13+
- hwp3-sample11-hwpx **0 → 167px**(작은 표인데 신규 대형 overflow), tac-img-02 6→21(max34),
14+
hwp3-sample16-hwp5 15→27, aift 4→7, mel-001 3→10, 3-09'22 154→155.
15+
- (개선 3: 해외직접투자/hwpx-h-02 2→1.)
16+
17+
## 분석 — 두 경로 모두 HWPX TAC 표 갭
18+
19+
- 악화 양상 비일관: hwp3-sample11-hwpx 는 **작은 표**(≤49mm)인데 TAC 경로에서 167px overflow,
20+
tac-img-02 는 page-larger(254mm>본문252mm). 즉 "모든 HWPX TAC 표 → TAC 경로"는 **block 경로가
21+
잘 처리하던 파일들을 broadly 악화**.
22+
- 제안요청서만 이득: block 경로가 **near-full-page 단일 TAC 표(843px)** 를 페이지에 cram(used
23+
1797px) → overflow. 반면 다른 HWPX TAC 표는 block 경로에서 정상.
24+
- → 결함은 "라우팅" 이 아니라 **block 경로의 near-full-page TAC 표 cram**(이월 미수행). TAC 경로도
25+
소형 HWPX TAC 표(hwp3-sample11)에서 별도 갭 보유.
26+
27+
## 재설계 방향 (재승인 필요)
28+
29+
라우팅 전면 변경(반려) 대신 **block 경로에서 TAC 표 이월(carry) 조건 추가**:
30+
- typeset_block_table 가 treat_as_char 표를 받고, **현재 페이지엔 안 들어가나 새 페이지엔 들어감**
31+
(measured_h ≤ base_available) 이면 cram 대신 `advance_column_or_new_page` 후 배치.
32+
- page-larger(measured_h > base_available)면 현행 split 유지(tac-img-02 등 불변).
33+
- 소형 TAC 표(현 페이지에 들어감)도 불변(hwp3-sample11 등 불변).
34+
35+
→ 제안요청서(843<941, 현 페이지 잔여 부족 시 이월)만 정정, 타 파일 불변 기대. 라우팅·TAC 경로
36+
미변경이라 회귀면 최소.
37+
38+
## 다음
39+
재설계(block 경로 TAC carry-if-fits) 승인 후 Stage 3 재구현 → smoke(제안요청서 + 6파일 무회귀)
40+
→ 회귀 검증. (Stage 2 설계는 폐기.)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Stage 4 보고서 — Task #1068: 진짜 근본 원인 규명 + 수정 + 회귀 검증
2+
3+
- 브랜치: `local/task1068`
4+
- 수정 파일: `src/document_core/commands/document.rs` (1 곳)
5+
6+
## 승인된 재설계(L2750 carry)는 무효 — 진단으로 확정
7+
8+
Stage 3 에서 제시·승인된 재설계(**block 경로 L2750 carry 가드를 다행 TAC 표로 확장**)를
9+
구현하던 중, 진단으로 그 전제가 틀렸음을 확정:
10+
11+
- `DIAG_1068` (para 567): `tac=true rows=14 table_total=860.7 available=941.1 cur_h=29.5
12+
fits_fresh=true`.
13+
- `cur_h(29.5) + table_total(860.7) = 890.2 ≤ 941.1`**L2736 에서 현재 페이지에 정상 fit**.
14+
L2750 은 도달조차 안 함 → carry 확장은 para 567 에 **영향 0** (실측: overflow 불변).
15+
16+
## 진짜 근본 원인 — 파서 후처리 over-inflation (`document.rs:283`)
17+
18+
HWPX XML 원본 대조 (`Contents/section0.xml`, para 567 linesegarray):
19+
```
20+
ls[0]: textpos=0, vertsize=2200 ← 제목줄 ("제안(서) 평가항목 및 배점기준")
21+
ls[1]: textpos=18, vertsize=63234 ← 표 줄 (treat_as_char 표, char 18)
22+
```
23+
24+
그러나 파싱된 IR (`dump -s 0 -p 567`): `ls[0] lh=63234` (제목줄 lh 가 표 높이로 오염).
25+
`th=2200` 은 정상 보존 → vertsize→line_height 매핑(`parse_lineseg_element`)은 정상,
26+
**후처리가 오염**.
27+
28+
오염 지점 — `document_core/commands/document.rs` "HWPX TAC 표 lh 보정":
29+
```rust
30+
if let Some(seg) = para.line_segs.first_mut() { // ← 무조건 첫 줄
31+
if seg.line_height < max_tac_h { seg.line_height = max_tac_h; }
32+
}
33+
```
34+
의도: linesegarray 가 없어 기본 lh=100 단일 seg 만 생성된 경우 표 높이로 확대.
35+
결함: **표가 둘째 줄 이후에 있는 문단**(제목줄 + 표줄)에서 무조건 `first_mut()`(제목줄)을
36+
표 높이로 확대 → 제목줄 lh 2200 → 63234.
37+
38+
연쇄: 렌더러 `place_table_with_text:2548`**lh 기반 표 줄 탐지**(find by line_height ≈
39+
table height) 가 제목줄(idx 0)을 오매칭 → `pre_table_end_line=0` → 표 줄이 post-text 로
40+
포함 → `PageItem::Table` + `PartialParagraph(표줄 포함)` 이중 그리기 → page used 1761px,
41+
표 줄 y=1886 → **839px overflow**.
42+
43+
## 수정 — 보정 조건 정밀화 (최소 변경)
44+
45+
```rust
46+
let already_covered = para.line_segs.iter().any(|s| s.line_height >= max_tac_h);
47+
if !already_covered {
48+
if let Some(seg) = para.line_segs.first_mut() { ... }
49+
}
50+
```
51+
이미 표 높이를 담은 LINE_SEG 가 있으면(한컴이 저장한 실제 linesegarray) 보정 생략.
52+
linesegarray 가 없는 synthetic 단일 seg(lh=100) 케이스만 기존대로 확대 → 의도 보존.
53+
54+
라우팅·렌더러 미변경 → Stage 2 라우팅 정규화(반려, +44)·실험 B(블록 경로 attr↔treat_as_char
55+
동일시: sample16/aift/mel-001 회귀)와 달리 **회귀 면 최소**.
56+
57+
## 검증 (전수)
58+
59+
- **타깃**: 제안요청서 para 567 **839px → 해소** (잔여 max 29px 는 #1068 무관 기존 드리프트).
60+
- **회귀 후보 6 파일** (baseline → patched):
61+
62+
| 파일 | baseline | patched | 판정 |
63+
|------|----------|---------|------|
64+
| hwp3-sample11-hwpx | 0 | 0 | 불변 |
65+
| tac-img-02.hwpx | 7 (max20) | 5 (max8.8) | 개선 |
66+
| tac-img-02.hwp | 8 (max23.6) | 8 | 불변 |
67+
| hwp3-sample16-hwp5 | 35 (max249) | 35 | 불변 |
68+
| aift.hwpx | 5 (max18.2) | 5 (max9.6) | 개선 |
69+
| mel-001.hwpx | 3 | 3 | 불변 |
70+
71+
- **전수 sweep** (samples 281 파일 중 hwp/hwpx, overflow 보유 97 파일):
72+
- baseline: 3057 lines / 382815px → patched: **3055 lines / 381115px** (−2 lines, −1700px, 회귀 0).
73+
- **cargo test --release**: lib **1324 passed** + 통합 테스트 0 failed.
74+
- **골든 SVG 8 종**: 8/8 통과 (form-002 / table-text / issue-157 / issue-267 / issue-147 /
75+
issue-617 / issue-677 / determinism).
76+
- **clippy**: clean. **fmt**: clean (변경 파일).
77+
78+
## 다음
79+
WASM 빌드(rhwp-studio 동기화) + 작업지시자 한컴 시각 판정 → 최종 보고서 + 이슈 클로즈.

src/document_core/commands/document.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -279,11 +279,23 @@ impl DocumentCore {
279279
}
280280
}
281281
if max_tac_h > 0 {
282-
// TAC 표가 있는 문단: lh가 표 높이보다 작으면 표 높이로 확대
283-
if let Some(seg) = para.line_segs.first_mut() {
284-
if seg.line_height < max_tac_h {
285-
seg.line_height = max_tac_h;
286-
body_line_seg_changed = true;
282+
// [Task #1068] 이미 표 높이를 담은 LINE_SEG 가 있으면(한컴이
283+
// 저장한 실제 linesegarray 보유 — 표 줄 seg 의 vertsize 가 표
284+
// 높이) 보정 불필요. 무조건 first_mut() 을 확대하면 표가 두 번째
285+
// 이후 줄에 있는 문단(제목줄 + 표줄)의 제목줄 lh 까지 표 높이로
286+
// 오염되어, 렌더러의 lh 기반 표 줄 탐지(place_table_with_text)가
287+
// 첫 줄을 오매칭 → 표 줄 이중 그리기 overflow (#1068 제안요청서
288+
// para 567: 제목줄 vertsize=2200 → 63234 오염, 839px overflow).
289+
// linesegarray 가 없어 기본 lh=100 단일 seg 만 있는 경우에만
290+
// 첫 seg 를 표 높이로 확대한다.
291+
let already_covered =
292+
para.line_segs.iter().any(|s| s.line_height >= max_tac_h);
293+
if !already_covered {
294+
if let Some(seg) = para.line_segs.first_mut() {
295+
if seg.line_height < max_tac_h {
296+
seg.line_height = max_tac_h;
297+
body_line_seg_changed = true;
298+
}
287299
}
288300
}
289301
}

0 commit comments

Comments
 (0)