개요
`samples/hwp3-sample16-hwp5.hwp` 페이지 19 부터 paragraph 들이 시각적으로 겹침 (overlap). 페이지 18 까지는 정상.
동일 원본의 HWP3 (`samples/hwp3-sample16.hwp`) 는 모든 페이지 정상 렌더.
정밀 진단 (2026-05-18)
패턴 확정
| Paragraph 타입 |
line_segs 보유 |
시각 결과 |
| ` ...` (PUA bullet, long text) |
없음 ✗ |
겹침 |
| ` ◦ ...` (regular bullet) |
있음 ✓ |
정상 |
| 일반 헤딩 |
있음 ✓ |
정상 |
페이지 19 가 시작점인 이유
- 페이지 18 까지: 박스 본문 + 일반 paragraph + 다이어그램 (모두 line_segs 보유)
- 페이지 19 부터: ` ...` paragraph 시작 — line_segs 없음
겹치는 paragraph (확인됨): pi=400, 403, 407, 410, 417, 424, 426, 431, 437439, 442446, 449~452, 460, ...
HWP3 vs HWP5 IR 비교
| Paragraph |
HWP3 line_segs |
HWP5 line_segs |
| pi=443 (cc=165) |
`ls[0] lh=1300 ls=780` ✓ |
누락 ✗ |
| pi=444 (cc=134) |
`ls[0]` ✓ |
누락 ✗ |
| pi=445 (cc=156) |
`ls[0]` ✓ |
누락 ✗ |
| pi=446 (cc=198) |
(확인 필요) |
누락 ✗ |
| pi=400 (cc=52) |
`ls[0]` ✓ |
누락 ✗ |
| pi=403 (cc=48) |
`ls[0]` ✓ |
누락 ✗ |
| pi=407 (cc=89) |
(확인 필요) |
누락 ✗ |
→ Hancom HWP3 → HWP5 변환 시 일부 paragraph 의 `PARA_LINE_SEG` 레코드 누락.
Root cause
src/renderer/composer.rs:301-326 의 fallback:
```rust
if para.line_segs.is_empty() {
// 단일 ComposedLine 생성
return vec![ComposedLine {
line_height: 400, // 400 HU = 5.33 px (너무 작음!)
baseline_distance: 320,
...
}];
}
```
→ 165 char 짜리 텍스트가 line_height=5.33px 의 단일 라인 으로 처리.
→ paragraph_layout 의 text reflow 가 wrap line 들을 생성하지만 모든 wrapped 라인이 같은 y 좌표 에 그려짐 → 시각적 겹침.
재현
```bash
./target/release/rhwp export-svg samples/hwp3-sample16-hwp5.hwp -o /tmp/h5/
./target/release/rhwp export-svg samples/hwp3-sample16.hwp -o /tmp/h3/
/tmp/h5/hwp3-sample16-hwp5_019.svg 부터 겹침 발생
/tmp/h3/ 는 모든 페이지 정상
./target/release/rhwp dump samples/hwp3-sample16-hwp5.hwp -s 0 -p 400
→ ls[0] 라인 없음 (line_segs.is_empty())
./target/release/rhwp dump samples/hwp3-sample16.hwp -s 0 -p 400
→ ls[0]: ... lh=1300 ls=780 (정상)
```
수정 방향 후보
| 후보 |
설명 |
위치 |
Risk |
| G1 |
`compose_lines` 의 빈 line_segs case 에서 ParaShape `line_spacing` + max font_size 로 line_height 동적 계산 |
composer.rs:301 |
중 |
| G2 |
HWP5 parser 가 PARA_LINE_SEG 누락 paragraph 감지 후 합성 (parse 시점) |
body_text.rs |
큼 |
| G3 |
별도 `reflow_line_segs()` 호출 — layout 진입 전 합성 |
(신규) |
큼 |
G1 이 가장 좁고 정합도 높음.
scope
- 1차 검증 파일: `samples/hwp3-sample16-hwp5.hwp` (page 19~62)
- 영향 모듈: `src/renderer/composer.rs` (compose_lines fallback)
- 회귀 검증: 240 sample 페이지 수 + 시각 (다른 HWP5 sample 에서 line_segs 누락 paragraph 영향)
관련
개요
`samples/hwp3-sample16-hwp5.hwp` 페이지 19 부터 paragraph 들이 시각적으로 겹침 (overlap). 페이지 18 까지는 정상.
동일 원본의 HWP3 (`samples/hwp3-sample16.hwp`) 는 모든 페이지 정상 렌더.
정밀 진단 (2026-05-18)
패턴 확정
페이지 19 가 시작점인 이유
겹치는 paragraph (확인됨): pi=400, 403, 407, 410, 417, 424, 426, 431, 437
439, 442446, 449~452, 460, ...HWP3 vs HWP5 IR 비교
→ Hancom HWP3 → HWP5 변환 시 일부 paragraph 의 `PARA_LINE_SEG` 레코드 누락.
Root cause
src/renderer/composer.rs:301-326 의 fallback:
```rust
if para.line_segs.is_empty() {
// 단일 ComposedLine 생성
return vec![ComposedLine {
line_height: 400, // 400 HU = 5.33 px (너무 작음!)
baseline_distance: 320,
...
}];
}
```
→ 165 char 짜리 텍스트가 line_height=5.33px 의 단일 라인 으로 처리.
→ paragraph_layout 의 text reflow 가 wrap line 들을 생성하지만 모든 wrapped 라인이 같은 y 좌표 에 그려짐 → 시각적 겹침.
재현
```bash
./target/release/rhwp export-svg samples/hwp3-sample16-hwp5.hwp -o /tmp/h5/
./target/release/rhwp export-svg samples/hwp3-sample16.hwp -o /tmp/h3/
/tmp/h5/hwp3-sample16-hwp5_019.svg 부터 겹침 발생
/tmp/h3/ 는 모든 페이지 정상
./target/release/rhwp dump samples/hwp3-sample16-hwp5.hwp -s 0 -p 400
→ ls[0] 라인 없음 (line_segs.is_empty())
./target/release/rhwp dump samples/hwp3-sample16.hwp -s 0 -p 400
→ ls[0]: ... lh=1300 ls=780 (정상)
```
수정 방향 후보
G1 이 가장 좁고 정합도 높음.
scope
관련