Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
25c370d
Task #877 Stage 1: HWP3 파서 allocation sanity check (방어성 가드)
jangster77 May 13, 2026
a2ea6f5
Task #877 Stage 2: HWP3 special char alignment 정합 (ch=5/6/7/8)
jangster77 May 13, 2026
51467a8
Task #877 Stage 3: HWP3 사적 인코딩 Ⅰ~Ⅹ 로마숫자 매핑 추가
jangster77 May 13, 2026
9e038d2
Task #877 Stage 3 v2: HWP3 drawing line_style=0 묵시적 실선 보강
jangster77 May 13, 2026
44145ab
Task #877 Stage 3 v3: HWP3 빈 페이지 2 회귀 — drawing object TAC line_spaci…
jangster77 May 13, 2026
948f917
Task #877 Stage 3 v3 보고서 갱신 — 시각 차이 4건 중 3건 수정 완료
jangster77 May 13, 2026
2839dba
Task #877 Stage 3 v4: HWP3 PUA 글머리 0x3366 매핑 추가
jangster77 May 13, 2026
d744141
Task #877 Stage 4: HWP3 outline 2단계 글머리 ◦ 자동 prefix (휴리스틱)
jangster77 May 13, 2026
6b155f4
Task #877 Stage 4: HWP3 image WMF/EMF magic detection 추가
jangster77 May 13, 2026
349278e
Task #877 Stage 4 보고서 — ◦ 글머리 휴리스틱 + WMF magic detection
jangster77 May 13, 2026
6b381fc
Task #877 Stage 4: 점선 (LineType 2~7) 가시성 최소 1.0 px 보강
jangster77 May 13, 2026
de6746a
Task #877 Stage 4: PUA 글머리 → ○ + picture ref_pos=0 위치 정합
jangster77 May 13, 2026
078f5f4
Task #877 Stage 4 분석: HWP3 paragraph border_fill 휴리스틱 도입 불필요
jangster77 May 13, 2026
60fee6c
Task #877 Stage 4: HWP3 drawing fill_color high flag 투명 처리
jangster77 May 13, 2026
1dc0c4f
Task #877 Stage 4: HWP3 drawing fill_color high flag → 흰색 대체
jangster77 May 13, 2026
cf43cb2
Task #877 Stage 4: HWP3 drawing Fill.alpha 한컴 convention (0=불투명)
jangster77 May 13, 2026
d4a72ee
Task #877 최종 결과 보고서 + 5/14 orders 갱신
jangster77 May 13, 2026
3fca3bd
Task #877 Stage 4: HWP3 doc_info 페이지 외곽선 (border_type) IR 변환
jangster77 May 13, 2026
c958e26
Task #877 Stage 4: ◦ 글머리 휴리스틱 ls=145 확장 (페이지 16 본문)
jangster77 May 13, 2026
c8a53f8
Task #877 최종 보고서 갱신 — 19 commits 누적 + Page border + ls=145
jangster77 May 14, 2026
efbcd42
Task #877 Stage 4: nested text_box paragraph 1단계 ○ 글머리 휴리스틱
jangster77 May 14, 2026
13d50ff
Task #877 Stage 4: treat_as_char picture wrap=Square → TopAndBottom 정합
jangster77 May 14, 2026
98dda4f
Task #877 잔존 문제 분석 + 보고서/orders 갱신 (rebase 후)
jangster77 May 14, 2026
12593e4
Task #894: 수행 계획서 작성
jangster77 May 14, 2026
b25aa45
Task #894: 구현 계획서 작성 (Stage 1~3)
jangster77 May 14, 2026
e9138c7
Task #894: 계획서에 항목 D (CLAUDE.md c2955b5) scope 추가 반영
jangster77 May 14, 2026
2415911
Task #894 Stage 1 진단 보고서 — HWPX 페이지 수 72→62 정합
jangster77 May 14, 2026
f454cd0
Task #894 Stage 1: HWPX self-closing run 의 charPrIDRef 처리 추가
jangster77 May 14, 2026
1d62de3
Task #894 Stage 1 진단 보고서 갱신 — Fix 1 적용 + 추가 후보 발견
jangster77 May 14, 2026
29befbc
Task #894 Stage 1 진단 갱신 — 옵션 (ii) 종합 진단 결론
jangster77 May 14, 2026
d756168
Task #894 Stage 1: ROOT CAUSE 명확 확정 — HWPX lineseg vpos 0 reset 누락
jangster77 May 14, 2026
685987e
Task #894: Stage 1 별도 task #895 분리 — Fix 1 만 본 task 유지
jangster77 May 14, 2026
90137d6
Task #894 Stage 2: control_text_positions fallback 강화 — text marker 스캔
jangster77 May 14, 2026
8b31b17
Task #894 Stage 2 완료 보고서 — paragraph multi-line picture 중복 emit 해소
jangster77 May 14, 2026
2e7078d
Task #894 Stage 3: HWP3 page border 좌표 기준 paper_based 로 정합
jangster77 May 14, 2026
df335b2
Task #894 Stage 3 완료 보고서 — HWP3 page border paper_based 정합
jangster77 May 14, 2026
ce8d3ce
Task #894 최종 결과 보고서 + orders 갱신
jangster77 May 14, 2026
21bd5c2
Task #896: 수행 계획서 작성
jangster77 May 14, 2026
66c2c6b
Task #896: 구현 계획서 작성 (Stage 1~5) + scope 확정 반영
jangster77 May 14, 2026
0eaa1d6
Task #896 Stage 1+2: apply_bullet_fixup_single 에 dash skip 추가
jangster77 May 14, 2026
0ef4887
Task #896 Stage 3+4: WMF font name 인코딩 정정 + 한국어 fallback chain
jangster77 May 14, 2026
b2b4bd6
Task #896: 최종 보고서 — Stage 1+2 + Stage 3+4 (Stage 7 drop, #902 분리)
jangster77 May 14, 2026
4e4e031
Merge branch 'devel' into local/task896
jangster77 May 14, 2026
304abd6
Merge upstream/devel into local/task896: PR #905 conflict 해결
jangster77 May 15, 2026
1d55505
Merge remote-tracking branch 'origin/local/task896' into local/task896
jangster77 May 15, 2026
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
9 changes: 9 additions & 0 deletions mydocs/orders/20260514.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# 오늘 할일 - 2026년 5월 14일

## M100 — v1.0.0 조판 엔진

| Issue | 타스크 | 상태 | 비고 |
|------|--------|------|------|
| **#877** | hwp3-sample16.hwp WASM 로드 실패 — RawVec capacity overflow + paragraph alignment + 시각 정합 | **완료 (PR #890 머지 대기)** | 브랜치 `local/task877_v2` (22 commits). 핵심 근본 원인: HWP3 drawing Fill.alpha 한컴 convention. 한컴 viewer 정합. 잔존 3건은 #894 통합 task 에서 처리 후 #895/#896 분리. |
| **#894** | Task #877 잔존 통합 (HWPX 페이지 수 / paragraph multi-line picture / HWP5 페이지 수 → HWP3 외곽선 좌표) | **완료** | 브랜치 `local/task894` (base `local/devel @ c2955b5`). Stage 2 (paragraph multi-line picture 중복 image 3→1) + Stage 3 (HWP3 page border paper_based, 페이지 번호 외곽선 안) 완전 해소. Stage 1 (HWPX self-closing run charPrIDRef) 정확성 보강. cargo test --all-targets 1355 passed. 최종 보고서: `mydocs/report/task_m100_894_report.md`. |
| **#895** | HWPX 변환본 lineseg vpos 페이지 break 0 reset 누락 (Task #894 Stage 1 분리) | **등록 (별도 task 권장)** | 한컴 HWPX 변환기가 lineseg vpos 를 누적값으로 저장 → typeset 의 vpos-reset trigger (cv==0 && pv>5000) 발동 실패 → 페이지 break point 마다 1 페이지 누적 inflate (sample16-hwp5.hwpx 72/62). Fix α/β/γ 후보 모두 광범위 영향 + 회귀 점검 자료 부족. |
| **#896** | sample16 페이지 18 추가 시각 — ◦ x 좌표 + WMF 텍스트 (Task #894 Stage 4 분리) | **등록 (별도 task 권장)** | 차이 1 (paragraph 397~399 ◦ x 좌표 시프트): raw HWP3 char_shapes 의 empty char + space 분할 구조 → paragraph_layout 첫 빈 char_shape 처리 차이. 차이 2 (WMF 그림 안 텍스트 겹침): WMF converter 영역. 모두 본 task scope 외 별도 영역. |

## M100 — v1.0.0 조판 엔진 (PR 처리 — 5/14 사이클)

| Issue | 타스크 | 상태 | 비고 |
Expand Down
64 changes: 64 additions & 0 deletions mydocs/plans/task_m100_877.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Task #877 수행 계획서

**제목**: hwp3-sample16.hwp WASM 로드 실패 — RawVec capacity overflow (HWP3 picture record alignment)

**이슈**: https://github.com/edwardkim/rhwp/issues/877
**브랜치**: `local/task877` (분기: `local/devel`)
**마일스톤**: v1.0.0 (M100)

## 배경

`samples/hwp3-sample16.hwp` (2.9 MB, 64쪽 RFP 문서) 를 rhwp-studio 에서 열면 panic:

```
panicked at library/alloc/src/raw_vec/mod.rs:28:5: capacity overflow
[main] 파일 로드 실패: RuntimeError: unreachable
```

한컴오피스에서는 정상 오픈. 네이티브 CLI 에서는 graceful Err 반환.

## 원인 분석 (이슈 #877 의 probe 결과)

### 1차 panic 지점
[src/parser/hwp3/records.rs:413](../../src/parser/hwp3/records.rs#L413) — `Hwp3AdditionalInfoBlock::read`:
```rust
let length = reader.read_u32::<LittleEndian>()?;
let mut data = vec![0u8; length as usize]; // length = 0xDC000000 (~3.69 GB) → WASM panic
```

### 상위 원인
- decompressed body = 16.2 MB (64쪽 분량) 인데 paragraph 1개만 인식
- 첫 picture (`ch=11`) 처리 후 cursor misalign → 16.18 MB 가 통째로 `additional_info_blocks` 영역으로 잘못 진입
- 첫 additional_info_block 의 length 가 garbage (0xDC000000) 로 읽혀 거대 할당 시도

### 환경별 차이
- 32-bit WASM: 메모리 한계 초과 → `RawVec::capacity_overflow` panic → `unreachable` trap
- 64-bit 네이티브: 가상메모리 할당 통과 → `read_exact` EOF → graceful Err

## 목표

1. **방어성 (Stage 1)**: 모든 HWP3 length-prefixed 할당 지점에 sanity check 도입 → panic → Err 변환. WASM/네이티브 모두 graceful fail.
2. **WASM panic hook (Stage 2)**: `console_error_panic_hook` 등록 (이미 부분 동작 — 메시지 노출은 되나 stack 이 wasm function index 만). Rust source map 강화 검토.
3. **근본 원인 (Stage 3)**: HWP3 picture (`ch=11`) byte alignment 정합. sample16 의 첫 picture 가 정확히 어디까지 stream 을 소비해야 하는지 spec 재확인 + 수정 → 64쪽 전체 파싱 성공.

## 제약 / 비목표

- 수정은 `src/parser/hwp3/` 영역에 한정 (CLAUDE.md HWP3 파서 규칙).
- 렌더링 품질 (예: picture 미리보기) 은 본 task 범위 밖. 본 task 는 **로드 성공 + 64쪽 인식** 까지가 목표.
- 다른 HWP3 sample (sample01~14) 의 회귀 없음 확인 필수.

## 검증 계획

1. `cargo test --release` 전체 테스트 통과
2. `cargo run --release --bin rhwp -- dump samples/hwp3-sample16.hwp` 성공
3. `cargo run --release --bin rhwp -- dump-pages samples/hwp3-sample16.hwp -p 0` 64쪽 인식
4. `cargo run --release --bin rhwp -- export-svg samples/hwp3-sample16.hwp` 64개 SVG 출력
5. 다른 HWP3 sample (sample01/10/11/13/14 등) export-svg 회귀 없음
6. rhwp-studio WASM 빌드 후 sample16 로드 + 페이지 표시 시각 확인

## 참고 파일

- 이슈: [GitHub #877](https://github.com/edwardkim/rhwp/issues/877)
- 샘플: `samples/hwp3-sample16.hwp` (미커밋 신규)
- 비교 샘플: `samples/hwp3-sample16-hwp5.hwp`, `samples/hwp3-sample16-hwp5.hwpx`
- 코드: [src/parser/hwp3/records.rs:405-417](../../src/parser/hwp3/records.rs#L405-L417), [src/parser/hwp3/mod.rs:929-1128](../../src/parser/hwp3/mod.rs#L929-L1128)
178 changes: 178 additions & 0 deletions mydocs/plans/task_m100_877_impl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Task #877 구현 계획서

**관련 수행 계획서**: [task_m100_877.md](task_m100_877.md)

## 단계 구성 (3 stage)

---

## Stage 1 — 방어성 가드 (allocation sanity check)

### 목적
HWP3 파서에서 외부 입력으로 받은 `length` 값으로 직접 `vec![0u8; length as usize]` / `Vec::with_capacity(length)` 를 하는 모든 지점에 sanity check 도입.
- 32-bit WASM 의 `RawVec` capacity overflow panic → graceful `Hwp3Error::IoError` 변환
- 64-bit 네이티브의 거대 가상메모리 할당 시도 차단 → 빠른 graceful fail

### 작업 내용

1. **취약 지점 식별** (`src/parser/hwp3/` grep):
- [records.rs:413](../../src/parser/hwp3/records.rs#L413) `Hwp3AdditionalInfoBlock::read` — `length: u32`
- [records.rs:392](../../src/parser/hwp3/records.rs#L392) `Hwp3InfoBlock::read` — `length: u16` (16-bit 이므로 ≤64KB, panic 위험 낮음)
- [mod.rs:932](../../src/parser/hwp3/mod.rs#L932) picture `ext_buf` — `n_ext: u32`
- [mod.rs:1120](../../src/parser/hwp3/mod.rs#L1120) `ch=29` (`< 1000000` 검증 기존 존재 — 동일 패턴 표준화)
- drawing.rs, ole.rs 등 다른 `read_exact` 호출 위치 정밀 grep

2. **공통 helper 도입** (예: `src/parser/hwp3/util.rs` 또는 `mod.rs` 내부):
```rust
/// stream 의 남은 길이를 초과하는 length 요청 시 Err.
/// HWP3 binary 파싱에서 garbage length 로 인한 거대 vec! 할당을 방지.
fn read_sized_buf<R: Read + Seek>(
reader: &mut R,
length: usize,
max_allowed: usize,
) -> Result<Vec<u8>, io::Error> {
if length > max_allowed {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("HWP3 size overflow: requested={length}, max_allowed={max_allowed}"),
));
}
let mut buf = vec![0u8; length];
reader.read_exact(&mut buf)?;
Ok(buf)
}
```
- `max_allowed` 는 호출 측에서 stream 남은 길이 또는 spec 상 최댓값 전달
- `Cursor<&[u8]>` 사용처에서는 `get_ref().len() - position()` 으로 산출
- 일반 `R: Read` 사용처 (drawing.rs 등) 에서는 호출 측에서 적정 상한 전달

3. **호출 측 수정**:
- `Hwp3AdditionalInfoBlock::read`: `max_allowed = body_data.len() - cursor.position()` (호출자가 전달)
- picture `ext_buf`: `max_allowed` = body 잔여 또는 spec 권장 (예: 100 MB 상한)
- `ch=29` 의 기존 `< 1000000` 검증 → 새 helper 로 통합

4. **단위 테스트**: garbage length 입력 시 panic 없이 `Err` 반환 검증.

### 산출물
- 코드: `src/parser/hwp3/` 내 가드 적용
- 보고서: `mydocs/working/task_m100_877_stage1.md`

### 검증
- `cargo test --release` 통과
- sample16 dump 시 panic 없이 graceful Err (또는 부분 파싱 후 Err) 반환
- 다른 HWP3 sample 회귀 없음

---

## Stage 2 — HWP3 picture (ch=11) byte alignment 정합 (근본 원인)

### 목적
sample16 가 1개 paragraph 만 인식되고 나머지 16.18 MB 가 잘못 해석되는 alignment 버그 수정 → 64쪽 전체 파싱 성공.

### 작업 내용

1. **현 picture (ch=11) 처리 흐름 정리** ([mod.rs:852-1053](../../src/parser/hwp3/mod.rs#L852-L1053)):
- 6 byte 헤더 (u32 + u16) 소비
- 348 byte `info_buf`
- `n_ext` (info_buf[0..4]) 만큼 `ext_buf` 추가 소비
- `pic_type == 3` 일 때 `parse_drawing_object_tree(ext_buf)` (drawing.rs 진입)
- `caption_paras = parse_paragraph_list(body_cursor, ...)` **재귀 호출**
- 호출자 (text body 루프) 는 `i += 3` (헤더 4 hchar 소비)

2. **sample16 picture 실제 byte 구조 분석**:
- decompressed body offset 15078~ 의 bytes 정밀 dump (probe binary 작성)
- `info_buf` 348 byte 의 모든 필드 추출 + 의미 매핑 (HWP3 spec hwp30_spec.pdf 또는 한컴 변환본 `hwp3-sample16-hwp5.hwp` IR 비교)
- 정확한 picture record 끝 위치 = ?
- 같은 sample16 가 처음 1개 paragraph 만 cc=5 인 이유 검증 (실제로 표지가 매우 작은 paragraph + 큰 picture 인 경우 vs. parser 가 일부 byte 를 빠뜨리는 경우)

3. **HWP5 변환본 (`samples/hwp3-sample16-hwp5.hwp`) IR 비교**:
- `rhwp dump samples/hwp3-sample16-hwp5.hwp` 출력 → 한컴이 변환한 paragraph 구조
- 첫 paragraph 의 picture record 가 HWP5 에서 어떻게 표현되는지 확인
- HWP3 → HWP5 변환 spec offset 매핑 추정

4. **alignment 수정**:
- Picture record 끝나는 정확한 offset 산출 로직 수정
- `caption_paras` 재귀 호출 시 진입 시점이 caption 영역인지 명확화
- 가능한 원인 후보:
- (a) `info_buf` 크기 (348) 가 sample16 에서는 다를 가능성 (variant 1.7 vs 1.5 등 HWP3 minor version)
- (b) `ext_buf` 무조건 read 대신 `pic_type == 3` 일 때만 read
- (c) `caption_paras` 재귀 호출 진입 조건 (caption 이 있을 때만)
- (d) 다른 미처리 sub-record (예: pic_type 별 별도 데이터 블록)

5. **회귀 방지**: 기존 HWP3 sample (sample01/10/11/13/14) 의 picture 처리 동일성 유지.

### 산출물
- 코드: `src/parser/hwp3/mod.rs` picture (`ch=11`) 부분
- 분석 문서: `mydocs/tech/hwp3_picture_record_alignment.md` (byte 구조 정리)
- 보고서: `mydocs/working/task_m100_877_stage2.md`

### 검증
- `cargo run --bin rhwp -- dump samples/hwp3-sample16.hwp` 성공 (64쪽 인식)
- `cargo run --bin rhwp -- dump-pages samples/hwp3-sample16.hwp -p 0` 정상
- 다른 HWP3 sample 회귀 없음 (sample01/10/11/13/14 dump 비교)

---

## Stage 3 — WASM panic hook + 통합 회귀 테스트

### 목적
- WASM 환경에서 향후 미식별 panic 발생 시 진단 가능하도록 panic hook 정비
- sample16 회귀 방지를 위한 통합 테스트 추가

### 작업 내용

1. **WASM panic hook 점검**:
- 현재 console 로그에 `panicked at library/alloc/src/raw_vec/mod.rs:28:5: capacity overflow` 메시지는 노출 — `console_error_panic_hook` 또는 유사 hook 이 이미 동작 중인지 확인
- `src/wasm_api.rs` 초기화 부에서 panic hook 설정 코드 확인 + 누락 시 추가
- 빌드 옵션 (debug-info, source map) 검토 — wasm function index 만 노출되는 stack 을 source 라인으로 매핑하는 방법 조사 (debug build 비용 vs. release 성능)
- 결정: 매핑 비용이 크면 panic hook 만 강화하고 source map 은 별도 task 로 미룸

2. **try_reserve 패턴 검토**:
- Stage 1 의 helper 외에 `Vec::try_reserve` / `Vec::try_with_capacity_in` 같은 alloc API 적용 가치 검토
- 현재 stable Rust 에서 `Vec::with_capacity` 의 panic 가능성 vs. `try_reserve_exact` 의 fallible 패턴
- 보수적 결정: 본 task 에서는 helper 함수 + length 검증만 하고 `try_reserve` 도입은 별도 RFC 로

3. **통합 회귀 테스트 추가**:
- `tests/issue_877.rs` (또는 `tests/hwp3_sample16.rs`) 신설
- sample16 로딩 → `DocumentCore::from_bytes()` panic 없이 성공
- paragraph 개수 / 페이지 수가 1 보다 큰지 (Stage 2 후 64쪽 인식 검증)
- 다른 HWP3 sample (sample14 등) 의 회귀 없음 sanity check

4. **rhwp-studio 시각 확인**:
- Docker 로 WASM 빌드 (`docker compose --env-file .env.docker run --rm wasm`)
- rhwp-studio 에서 sample16 로드 → 페이지 표시 확인
- 스크린샷 캡처 → 보고서 첨부

### 산출물
- 코드: `src/wasm_api.rs` panic hook (필요 시), `tests/issue_877.rs`
- 보고서: `mydocs/working/task_m100_877_stage3.md`

### 검증
- `cargo test --release tests::issue_877` 통과
- `cargo test --release` 전체 통과
- WASM 빌드 → rhwp-studio sample16 로드 시각 확인

---

## 최종 산출물

- 코드 수정: `src/parser/hwp3/` (records.rs, mod.rs), `src/wasm_api.rs`, `tests/issue_877.rs`
- 문서:
- 수행 계획서: `mydocs/plans/task_m100_877.md`
- 구현 계획서: `mydocs/plans/task_m100_877_impl.md`
- Stage 1/2/3 보고서: `mydocs/working/task_m100_877_stage{1,2,3}.md`
- 분석 문서: `mydocs/tech/hwp3_picture_record_alignment.md`
- 최종 보고서: `mydocs/report/task_m100_877_report.md`
- 신규 sample git 등록: `samples/hwp3-sample16.hwp` (및 `-hwp5.hwp` / `-hwp5.hwpx` 변환본)

## 위험 / 미해결 가능성

- **Stage 2 가 가장 불확실**: HWP3 spec 의 picture record 정확한 layout 이 sample16 의 variant 와 일치하지 않을 가능성. 분석 후 본 task 범위 내에서 해결 불가 판단 시 Stage 1 (방어성) 만 머지하고 alignment 는 별도 task 로 분기.
- HWP3 spec 문서 부재 시 한컴 변환본 (`hwp3-sample16-hwp5.hwp`) IR 비교 + 다른 HWP3 sample 실측으로 reverse-engineer.

## 진행 순서

1. Stage 1 시작 → 완료 후 Stage 1 보고서 + 승인 요청
2. Stage 2 시작 → 분석 후 (alignment 가능 / 불가능) 결정 → Stage 2 보고서 + 승인 요청
3. Stage 3 시작 → 완료 후 Stage 3 보고서 + 승인 요청
4. 최종 결과 보고서 + orders 갱신 → 승인 요청 → 머지
Loading
Loading