Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions mydocs/plans/task_m100_1068.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# 수행계획서 — Task #1068: 거의 한 페이지 크기 treat_as_char 표 새 페이지 미이월

- 이슈: edwardkim/rhwp#1068
- 브랜치: `local/task1068` (stream/devel 기준)

## 1. 배경 / 증상

거의 한 페이지 크기의 **글자처럼 취급(treat_as_char) 표**를 포함한 문단이, 분할 시 새 페이지로
이월되지 않고 **현재 페이지 하단에 배치되어 표 전체가 본문 영역 아래로 렌더링**된다.

관측(비공개 RFP 문서, 커밋 불가):
- 문단에 treat_as_char 표 1개, 표 높이 ≈ 63234 HU(≈843px), 본문(A4 ≈941px)에 들어가는 크기.
- 표 줄(`lh=63234`)이 본문 하단(y≈1046) 에서 시작 → `LAYOUT_OVERFLOW_DRAW overflow≈839px`.
- 앞뒤 문단이 `[쪽나누기]` → 한컴은 표를 독립 페이지에 배치.

## 2. 추정 원인 (정독으로 확인 예정)

- `typeset.rs` atomic TAC top-fit 가드(`is_atomic_tac_singleton`)가 treat_as_char Picture/Shape
만 처리, **Table 제외**. 거의 한 페이지 표가 "들어갈 새 페이지 이월" 처리 누락 → 분할 줄 배치
에서 표 줄이 현재 페이지 하단에 얹힘.
- 단, `typeset.rs:1100` 등에 treat_as_char Table 분기가 일부 존재 → Stage 1에서 실제 경로 확인.

## 3. 목표

- 본문보다 작은 treat_as_char 표가 현재 페이지에 안 들어가면 **새 페이지 상단으로 이월**되어
본문 안에 온전히 배치. 해당 문단 LAYOUT_OVERFLOW/_DRAW 0.

## 4. 제약 — 비공개 문서 + 공개 픽스처

- 재현 권위 문서는 비공개(`samples/2. 인공지능...제안요청서.hwpx`) → **git add 금지**, 분석만.
- **공개 합성 픽스처** 생성(near-full-page treat_as_char 표 + 전후 쪽나누기)하여 테스트로 커밋.
`gen-table` CLI 또는 별도 생성기 활용.

## 5. 비회귀

- 기존 treat_as_char 표/그림/도형 케이스(tac-case-*, tac-img-*, table-in-tbox 등), 골든 SVG 8종,
전 251 샘플 LAYOUT_OVERFLOW 합계(현 769) 무회귀.

## 6. 조사·접근 방향 (구현계획서 구체화)

1. typeset.rs 분할(split) 경로에서 treat_as_char 표 줄 배치 정독 + 비공개 문서로 실제 흐름 확인.
2. 공개 합성 픽스처 생성·재현.
3. atomic TAC 이월을 treat_as_char Table(본문보다 작은 단일 표 줄)로 확장하는 정합안 설계.

## 7. 검증

- 합성 픽스처 + 비공개 문서에서 해당 overflow 0, 한컴 PDF 정합(독립 페이지 배치).
- `cargo test --release`, 골든 회귀 0, 251 합계 무회귀, `cargo fmt`(변경 파일).

## 8. 범위

- `src/renderer/typeset.rs` (분할/atomic TAC 표 이월). HWP3 전용 분기 추가 금지.

---
승인 후 구현계획서(`task_m100_1068_impl.md`, 3~6단계) 작성 → 재승인 → 단계 진행.
46 changes: 46 additions & 0 deletions mydocs/plans/task_m100_1068_impl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# 구현계획서 — Task #1068: treat_as_char 표 새 페이지 이월

- 이슈: edwardkim/rhwp#1068
- 브랜치: `local/task1068` (stream/devel 기준)
- 수행계획서: `task_m100_1068.md` (승인 완료)

## 관련 코드 (정독 출발점)

- atomic TAC top-fit 가드: `typeset.rs:1851` `is_atomic_tac_singleton` (Picture/Shape 한정).
- treat_as_char Table 분기: `typeset.rs:1100` 등.
- 분할(split) 경로: `typeset.rs` 줄 단위 분할 (line_count>0 이후).
- 합성 픽스처: `gen-table` CLI (`src/main.rs:43`).

## 단계 (4단계)

### Stage 1 — 조사 + 공개 픽스처 (소스 무변경)
- typeset.rs 분할 경로에서 **treat_as_char 표 줄**이 어떻게 배치되는지 정독 + 비공개 문서로
실제 흐름(`LAYOUT_OVERFLOW`/dump-pages) 확인. atomic TAC 가드가 표를 왜 제외하는지 확정.
- **공개 합성 픽스처 생성**: near-full-page treat_as_char 표 1개 + 전후 쪽나누기 문단.
`gen-table` 또는 별도 생성기로 `.hwp`/`.hwpx` 산출 → `samples/`(공개) 또는 테스트 픽스처로 추가.
비공개 문서와 동일 증상(표 줄 본문 하단 overflow) 재현 확인.
- 산출물: `working/task_m100_1068_stage1.md` + 픽스처 파일. 커밋: 보고서 + 공개 픽스처(비공개 금지).

### Stage 2 — 설계 + 페이퍼 검증 (소스 무변경)
- atomic TAC 이월 가드를 treat_as_char **Table**(본문보다 작은 단일 표 줄)로 확장하는 정합안.
- 조건: 표 줄 높이 ≤ 본문 높이(이월 시 들어감) & 현재 페이지에 안 들어감 → 새 페이지 이월.
- page-larger(본문보다 큰 표, #874) 케이스와 구분(이월해도 안 들어가면 분할).
- 비회귀 케이스(tac-case-*, tac-img-*, table-in-tbox) 예상 동작 표로 모순 점검.
- 산출물: `working/task_m100_1068_stage2.md`.

### Stage 3 — 구현
- typeset.rs 분할/atomic 경로에 표 이월 조건 반영. 최소 변경. 단위 TDD(픽스처 기반).
- 산출물: 소스 + `working/task_m100_1068_stage3.md`.

### Stage 4 — 회귀 검증
- 합성 픽스처 + 비공개 문서 해당 overflow 0, 한컴 PDF 정합(독립 페이지).
- 비회귀 전수(tac-*, table-*), 골든 SVG 8종, `cargo test --release`, 251 합계(769) 무회귀,
`cargo fmt`(변경 파일).
- 산출물: `working/task_m100_1068_stage4.md` → `report/task_m100_1068_report.md`.

## 완료 기준
treat_as_char 표 본문 하단 overflow 해소(이월 배치) + 비회귀 0 + 골든 회귀 0.

## 리스크
- page-larger(본문보다 큰 표) 와 이월 가능 표의 구분 오류 → 무한 이월/누락. Stage 2에서 경계 명시.
- 합성 픽스처가 비공개 문서 증상을 정확히 재현 못 할 가능성 → Stage 1에서 재현 확인 후 진행.
53 changes: 53 additions & 0 deletions mydocs/working/task_m100_1068_stage1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Stage 1 보고서 — Task #1068: treat_as_char 표 새 페이지 미이월 (조사)

- 브랜치: `local/task1068` (stream/devel 기준, 소스 무변경)
- 재현 문서: 비공개 RFP (`samples/2. 인공지능...제안요청서.hwpx`, 분석만·커밋 금지)

## 확정 사실

- 문제 항목: para 567 = 제목줄(th=2200≈29px) + **treat_as_char 표**(48081×63234HU, 641×**843px**).
- 앞 문단 pi566 "[붙임 4]"(17px), 뒤 pi568 "[붙임 5]" — para 567 은 독립 배치 의도.
- `LAYOUT_OVERFLOW_DRAW: pi=567 line=1 y=1886 overflow≈839px` — 표 줄이 본문 하단 아래로 통째 렌더.
- **dump-pages 페이지 92: items=3, used=1797.9px** (본문 941px의 ~2배!):
- `FullParagraph pi=566 h=17.3`
- `Table pi=567 ... 843px`
- `PartialParagraph pi=567 lines=0..2`

## 핵심 가설 — treat_as_char 표 높이 이중 계상

`used=1797 ≈ pi566(17) + 표(843) + 문단(제목29+표줄843=872)` → **표 높이가 두 번 계상**:
1. 별도 `Table` PageItem (843px)
2. PartialParagraph 의 표 줄 line_height (843px, line_segs ls[1] lh=63234)

→ 페이지 used 가 과충전되고, 분할 줄 배치에서 표 줄이 페이지 하단에 얹혀 본문 밖으로 렌더.
(분할 fit 산식은 제목29+표843=872 < 잔여~924 로 "들어감" 판정하나, 실제 누적/렌더는 이중
계상·vpos 매핑으로 어긋남.)

- `is_atomic_tac_singleton`(typeset.rs:1851) 은 Picture/Shape 만 → TAC 표 이월 미처리 (관련).
- 비-TAC 표 wrap-around 처리(1096-1122)는 TAC 표 제외.

## 코드 경로 확정 (정정)

para 567(표 포함)은 일반 줄 분할(1909)이 **아니라 표 배치 경로**(`typeset.rs` ~2520-2620)로
처리된다. 이 경로:
- `PageItem::Table` push (2584) + pre-text `PartialParagraph` push (2573).
- **`tac_wrap_split`(2597)**: `treat_as_char && pre_table_end_line>0 && < total_lines` — "전폭 TAC
표가 자기 줄(line index=pre_table_end_line)에 놓인 split 케이스" 를 **이미 인지**(주석 2593-2596,
이중 계산 방지 의도, Task #853).
- 그러나 para 567 에서 used=1797px 로 과충전 → 이 경로의 **페이지 fit/break(이월) 처리가 near-
full-page TAC 표에 대해 불완전**.

→ 수정 영역은 **표 배치 경로의 TAC 표 fit/이월 로직**(2520-2620, tac_wrap_split 인근).
`is_atomic_tac_singleton`(1851, Picture/Shape) 와는 별개 경로.

## 공개 픽스처

- `gen-table` CLI 는 일반 표만 생성(treat_as_char·페이지 크기·전후 쪽나누기 미지원).
- near-full-page treat_as_char 표 + 전후 쪽나누기 픽스처는 **별도 생성기/수작업 구축 필요** →
Stage 2 초입에서 구축(공개 커밋 가능).

## 잠정 결론

- 수정 영역 localize 완료: **표 배치 경로(2520-2620)의 TAC 표 페이지 fit/이월**.
- used=1797px(본문 2배)는 표 줄 + Table item 의 누적 정책(tac_wrap_split)이 이 케이스를 완전히
커버하지 못함을 시사. Stage 2에서 누적·fit/break 산식을 정밀 분해 + 공개 픽스처 재현 후 설계.
56 changes: 56 additions & 0 deletions mydocs/working/task_m100_1068_stage2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Stage 2 보고서 — Task #1068: 근본 원인 확정 + 수정 설계

- 브랜치: `local/task1068` (진단 후 전량 revert, 소스 무변경)

## 근본 원인 — 확정 (HWPX TAC 표 attr 비트 미설정 → 오라우팅)

진단 (`DIAG_TAC`, para 567):
```
pi=567 ft.is_tac=false attr&1=0 treat_as_char=true rows=14 measured_h=843.1 base_avail=941.1
```

- 표 디스패치(`typeset.rs:2332`): `if ft.is_tac { typeset_tac_table } else { typeset_block_table }`.
- `format_table`(2091): `is_tac = table.attr & 0x01 != 0`.
- **HWPX 파서**(`section.rs:1104`)는 `common.treat_as_char` 만 설정, **`attr & 0x01`(HWP5식 TAC
비트) 미설정** → HWPX TAC 표는 `is_tac=false` → **block 경로 오라우팅**.
- block 경로는 inline TAC 배치/페이지 fit 을 적용하지 않아 표(843px<941)가 페이지 하단에 얹혀
본문 밖 839px 렌더 (used=1797px).

`attr & 0x01` 의존 사이트가 typeset 에 **9곳**(2091/2252/2429/2452/2486/2620/2624/2656/3760) →
format_table 한 곳만 고치면 나머지 미정합.

## 수정 설계 — 파서 정규화 (포괄적·단일점)

`parse_table`(`src/parser/hwpx/section.rs:1014`) 반환 직전:
```rust
if table.common.treat_as_char { table.attr |= 0x01; }
```
→ HWPX TAC 표의 `attr` 비트0 을 treat_as_char 와 일치(HWP5 정합)시켜, 모든 `attr & 0x01`
렌더 사이트가 일관 동작. (HWP5 파서는 이미 비트0 설정 — 본 수정은 HWPX 한정.)

대안(미채택): format_table is_tac 만 `|| treat_as_char` — 나머지 8개 사이트 미정합으로 불완전.

## 페이퍼 검증 (모순 점검)

| 케이스 | attr&1(전) | 수정 후 | 라우팅 |
|------|------|------|------|
| HWPX TAC 표(treat_as_char=true) | 0 | **1** | block→**tac** (정정) |
| HWPX 블록 표(treat_as_char=false) | 0 | 0 | block (불변) |
| HWP5 TAC 표 | 1 | 1 | tac (불변, 파서 다름) |
| HWP5 블록 표 | 0 | 0 | block (불변) |

→ treat_as_char=true 인 HWPX 표만 비트 추가. 다른 케이스 불변. 모순 없음.

- typeset_tac_table 경로: table_height=fmt.height_for_fit, fit 체크 후 `advance_column_or_new_page`
→ 843px 표가 페이지(941)에 fit → 본문 내 배치. (para 567: pi566 17px + 843 = 860 < 941)

## 리스크 / 검증 항목

- **직렬화 round-trip**: HWPX 직렬화는 treatAsChar 속성 사용(attr 비트 아님)이라 영향 없을 것으로
보이나, HWPX→HWP 직렬화에서 attr 비트0 기록 확인(HWP5에선 TAC=비트0 이라 정합) — Stage 3 점검.
- **공개 테스트 픽스처**: 비공개 문서 대신, 트래킹된 HWPX 중 treat_as_char 표 보유 샘플
(`표-텍스트.hwpx` 등) 로 동일 라우팅·overflow 재현 확인 → 테스트. 없으면 최소 HWPX 합성.
- 비회귀: tac-*/table-* 골든, 전 251 LAYOUT_OVERFLOW 합계.

## 다음 (Stage 3)
파서 정규화 1줄 + 공개 픽스처 확정 → 빌드·smoke(제안요청서 839px 해소) → 회귀 검증.
40 changes: 40 additions & 0 deletions mydocs/working/task_m100_1068_stage3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Stage 3 보고서 — Task #1068: 파서 정규화 시도 → 반려 + 재설계

- 브랜치: `local/task1068` (시도 후 revert, 소스 클린)

## 시도 — 파서 정규화 (Stage 2 설계)

`parse_table` 반환 직전 `if treat_as_char { attr |= 0x01 }` → HWPX TAC 표를 TAC 경로로 라우팅.

## 결과 — 타깃 해소 but 광범위 회귀 (반려)

- ✅ 제안요청서.hwpx: para 567 **839px → 해소**(잔여 ≤27px), cargo test 1324 + 골든 전부 통과.
- ❌ 251 샘플 LAYOUT_OVERFLOW **1626 → 1670 (+44)**, HWPX **6파일 악화**:
- hwp3-sample11-hwpx **0 → 167px**(작은 표인데 신규 대형 overflow), tac-img-02 6→21(max34),
hwp3-sample16-hwp5 15→27, aift 4→7, mel-001 3→10, 3-09'22 154→155.
- (개선 3: 해외직접투자/hwpx-h-02 2→1.)

## 분석 — 두 경로 모두 HWPX TAC 표 갭

- 악화 양상 비일관: hwp3-sample11-hwpx 는 **작은 표**(≤49mm)인데 TAC 경로에서 167px overflow,
tac-img-02 는 page-larger(254mm>본문252mm). 즉 "모든 HWPX TAC 표 → TAC 경로"는 **block 경로가
잘 처리하던 파일들을 broadly 악화**.
- 제안요청서만 이득: block 경로가 **near-full-page 단일 TAC 표(843px)** 를 페이지에 cram(used
1797px) → overflow. 반면 다른 HWPX TAC 표는 block 경로에서 정상.
- → 결함은 "라우팅" 이 아니라 **block 경로의 near-full-page TAC 표 cram**(이월 미수행). TAC 경로도
소형 HWPX TAC 표(hwp3-sample11)에서 별도 갭 보유.

## 재설계 방향 (재승인 필요)

라우팅 전면 변경(반려) 대신 **block 경로에서 TAC 표 이월(carry) 조건 추가**:
- typeset_block_table 가 treat_as_char 표를 받고, **현재 페이지엔 안 들어가나 새 페이지엔 들어감**
(measured_h ≤ base_available) 이면 cram 대신 `advance_column_or_new_page` 후 배치.
- page-larger(measured_h > base_available)면 현행 split 유지(tac-img-02 등 불변).
- 소형 TAC 표(현 페이지에 들어감)도 불변(hwp3-sample11 등 불변).

→ 제안요청서(843<941, 현 페이지 잔여 부족 시 이월)만 정정, 타 파일 불변 기대. 라우팅·TAC 경로
미변경이라 회귀면 최소.

## 다음
재설계(block 경로 TAC carry-if-fits) 승인 후 Stage 3 재구현 → smoke(제안요청서 + 6파일 무회귀)
→ 회귀 검증. (Stage 2 설계는 폐기.)
79 changes: 79 additions & 0 deletions mydocs/working/task_m100_1068_stage4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Stage 4 보고서 — Task #1068: 진짜 근본 원인 규명 + 수정 + 회귀 검증

- 브랜치: `local/task1068`
- 수정 파일: `src/document_core/commands/document.rs` (1 곳)

## 승인된 재설계(L2750 carry)는 무효 — 진단으로 확정

Stage 3 에서 제시·승인된 재설계(**block 경로 L2750 carry 가드를 다행 TAC 표로 확장**)를
구현하던 중, 진단으로 그 전제가 틀렸음을 확정:

- `DIAG_1068` (para 567): `tac=true rows=14 table_total=860.7 available=941.1 cur_h=29.5
fits_fresh=true`.
- `cur_h(29.5) + table_total(860.7) = 890.2 ≤ 941.1` → **L2736 에서 현재 페이지에 정상 fit**.
L2750 은 도달조차 안 함 → carry 확장은 para 567 에 **영향 0** (실측: overflow 불변).

## 진짜 근본 원인 — 파서 후처리 over-inflation (`document.rs:283`)

HWPX XML 원본 대조 (`Contents/section0.xml`, para 567 linesegarray):
```
ls[0]: textpos=0, vertsize=2200 ← 제목줄 ("제안(서) 평가항목 및 배점기준")
ls[1]: textpos=18, vertsize=63234 ← 표 줄 (treat_as_char 표, char 18)
```

그러나 파싱된 IR (`dump -s 0 -p 567`): `ls[0] lh=63234` (제목줄 lh 가 표 높이로 오염).
`th=2200` 은 정상 보존 → vertsize→line_height 매핑(`parse_lineseg_element`)은 정상,
**후처리가 오염**.

오염 지점 — `document_core/commands/document.rs` "HWPX TAC 표 lh 보정":
```rust
if let Some(seg) = para.line_segs.first_mut() { // ← 무조건 첫 줄
if seg.line_height < max_tac_h { seg.line_height = max_tac_h; }
}
```
의도: linesegarray 가 없어 기본 lh=100 단일 seg 만 생성된 경우 표 높이로 확대.
결함: **표가 둘째 줄 이후에 있는 문단**(제목줄 + 표줄)에서 무조건 `first_mut()`(제목줄)을
표 높이로 확대 → 제목줄 lh 2200 → 63234.

연쇄: 렌더러 `place_table_with_text:2548` 의 **lh 기반 표 줄 탐지**(find by line_height ≈
table height) 가 제목줄(idx 0)을 오매칭 → `pre_table_end_line=0` → 표 줄이 post-text 로
포함 → `PageItem::Table` + `PartialParagraph(표줄 포함)` 이중 그리기 → page used 1761px,
표 줄 y=1886 → **839px overflow**.

## 수정 — 보정 조건 정밀화 (최소 변경)

```rust
let already_covered = para.line_segs.iter().any(|s| s.line_height >= max_tac_h);
if !already_covered {
if let Some(seg) = para.line_segs.first_mut() { ... }
}
```
이미 표 높이를 담은 LINE_SEG 가 있으면(한컴이 저장한 실제 linesegarray) 보정 생략.
linesegarray 가 없는 synthetic 단일 seg(lh=100) 케이스만 기존대로 확대 → 의도 보존.

라우팅·렌더러 미변경 → Stage 2 라우팅 정규화(반려, +44)·실험 B(블록 경로 attr↔treat_as_char
동일시: sample16/aift/mel-001 회귀)와 달리 **회귀 면 최소**.

## 검증 (전수)

- **타깃**: 제안요청서 para 567 **839px → 해소** (잔여 max 29px 는 #1068 무관 기존 드리프트).
- **회귀 후보 6 파일** (baseline → patched):

| 파일 | baseline | patched | 판정 |
|------|----------|---------|------|
| hwp3-sample11-hwpx | 0 | 0 | 불변 |
| tac-img-02.hwpx | 7 (max20) | 5 (max8.8) | 개선 |
| tac-img-02.hwp | 8 (max23.6) | 8 | 불변 |
| hwp3-sample16-hwp5 | 35 (max249) | 35 | 불변 |
| aift.hwpx | 5 (max18.2) | 5 (max9.6) | 개선 |
| mel-001.hwpx | 3 | 3 | 불변 |

- **전수 sweep** (samples 281 파일 중 hwp/hwpx, overflow 보유 97 파일):
- baseline: 3057 lines / 382815px → patched: **3055 lines / 381115px** (−2 lines, −1700px, 회귀 0).
- **cargo test --release**: lib **1324 passed** + 통합 테스트 0 failed.
- **골든 SVG 8 종**: 8/8 통과 (form-002 / table-text / issue-157 / issue-267 / issue-147 /
issue-617 / issue-677 / determinism).
- **clippy**: clean. **fmt**: clean (변경 파일).

## 다음
WASM 빌드(rhwp-studio 동기화) + 작업지시자 한컴 시각 판정 → 최종 보고서 + 이슈 클로즈.
22 changes: 17 additions & 5 deletions src/document_core/commands/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,11 +279,23 @@ impl DocumentCore {
}
}
if max_tac_h > 0 {
// TAC 표가 있는 문단: lh가 표 높이보다 작으면 표 높이로 확대
if let Some(seg) = para.line_segs.first_mut() {
if seg.line_height < max_tac_h {
seg.line_height = max_tac_h;
body_line_seg_changed = true;
// [Task #1068] 이미 표 높이를 담은 LINE_SEG 가 있으면(한컴이
// 저장한 실제 linesegarray 보유 — 표 줄 seg 의 vertsize 가 표
// 높이) 보정 불필요. 무조건 first_mut() 을 확대하면 표가 두 번째
// 이후 줄에 있는 문단(제목줄 + 표줄)의 제목줄 lh 까지 표 높이로
// 오염되어, 렌더러의 lh 기반 표 줄 탐지(place_table_with_text)가
// 첫 줄을 오매칭 → 표 줄 이중 그리기 overflow (#1068 제안요청서
// para 567: 제목줄 vertsize=2200 → 63234 오염, 839px overflow).
// linesegarray 가 없어 기본 lh=100 단일 seg 만 있는 경우에만
// 첫 seg 를 표 높이로 확대한다.
let already_covered =
para.line_segs.iter().any(|s| s.line_height >= max_tac_h);
if !already_covered {
if let Some(seg) = para.line_segs.first_mut() {
if seg.line_height < max_tac_h {
seg.line_height = max_tac_h;
body_line_seg_changed = true;
}
}
}
}
Expand Down
Loading