Skip to content

feat: hwp2hwpx CLI with lossless verification gates + serializer fidelity fixes#1366

Open
idaeho wants to merge 2 commits into
edwardkim:develfrom
idaeho:port/hwp2hwpx-lossless
Open

feat: hwp2hwpx CLI with lossless verification gates + serializer fidelity fixes#1366
idaeho wants to merge 2 commits into
edwardkim:develfrom
idaeho:port/hwp2hwpx-lossless

Conversation

@idaeho

@idaeho idaeho commented Jun 10, 2026

Copy link
Copy Markdown

Closes 1365 연계 이슈: #1365

요약

한컴 미설치 환경에서의 HWP→HWPX 변환 CLI(hwp2hwpx)와 무손실 검증 게이트(--verify / --verify-pages), 그리고 그 과정에서 발견한 직렬화 정합 수정 묶음입니다. devel의 #1326 작업(머리말/꼬리말/pageHiding/pageNum/newNum/autoNum 직렬화)과 중복되는 부분은 devel 구현을 유지하고 차집합만 포팅했습니다.

변경 사항

  • CLI: rhwp hwp2hwpx <in.hwp> <out.hwpx> [--verify] [--verify-pages] — verify는 ir-diff 엔진 재사용(차이>0 → exit 3), verify-pages는 렌더 페이지 수 비교(불일치 → exit 4)
  • 표 pageBreak 한컴 의미론: 직렬화기가 CellBreak→"CELL"로 직역하던 것을 파서 매핑(CELL↔RowBreak, TABLE↔CellBreak)과 쌍으로 정정
  • 글자모양 run 경계 보존(다중 run): 슬롯 경로에 char_shapes 경계 분리 통합 (컨트롤 위치·fieldEnd 인접·트레일링 경계 포함), 머리말/꼬리말 subList·표 셀에도 적용
  • SecDef/ColDef 슬롯 위치 소비: 8유닛 마커 정합 (출력은 secPr/colPr 별도) + 갭 마커 없는 IR 대응 적응형 슬롯 선택 + 트레일링 마커 슬롯 추론 보정
  • BinData: manifest 키를 DocInfo BIN_DATA 1-based 인덱스로 매핑 (storage id 불연속 문서의 binaryItemIDRef 미등록 오류 수정)
  • secPr IR 직렬화: 용지 여백·colPr(다단)·footNotePr/endNotePr 를 템플릿 고정값 대신 IR 값으로 — 다단·미주 문서의 페이지 수 2배 어긋남 해소
  • secPr/colPr 순서 보존: 원본에서 ColumnDef가 SectionDef보다 앞서는 경우 블록 순서 교환
  • ir-diff: ir_diff() 가 차이 건수 반환, HWP5 탭 벤더 패딩(code unit 3~5, OWPML 미운반·한컴 변환기도 미보존 실측) 의미론 비교 제외

실측 (11샘플)

지표 v0.7.15 본 패치(devel 기반)
IR diff 총합 1,074 3 (9/11 샘플 0건)
렌더 페이지 수 일치 5/11 11/11
전체 테스트 2,128 통과 / 0 실패

알려진 잔여 (3건)

  • 문단 char_count raw 변형(trailing 마커 문단의 nchars — HWPX 운반 필드 부재): 1건
  • 트레일링 마커 사이 글자모양 경계 위치가 devel의 AutoNumber 슬롯 처리와 상호작용: 2건 (multi-table 샘플)

🤖 Generated with Claude Code

idaeho and others added 2 commits June 11, 2026 01:36
…lity fixes

Ported from a v0.7.15-based working branch onto devel, deduplicated against
devel's own edwardkim#1326 work (header/footer/pageHiding/pageNum/newNum/autoNum
serialization already present — this commit keeps devel's implementations).

New in this commit:
- 'rhwp hwp2hwpx <in.hwp> <out.hwpx> [--verify] [--verify-pages]' subcommand
  (--verify: IR diff gate via ir-diff engine, exit 3 on loss;
   --verify-pages: render page count gate, exit 4 on mismatch)
- Fix table pageBreak Hancom semantics (serializer wrote CellBreak->CELL,
  parser maps CELL->RowBreak; now serializer mirrors the parser: CELL<->RowBreak,
  TABLE<->CellBreak)
- Preserve char-shape run boundaries (multi-run): split runs at char_shapes
  boundaries in the slot path (controls, fieldEnd-adjacent boundaries,
  trailing boundaries), in header/footer subLists, and in table cells
- Treat SectionDef/ColumnDef as 8-unit inline slots (consume position, no
  output — secPr/colPr serialized separately) with adaptive fallback for IR
  lacking gap markers; count trailing markers in slot inference
- Map BinData manifest keys via DocInfo BIN_DATA 1-based index (fixes
  'binaryItemIDRef not registered' on documents with non-contiguous storage ids)
- Replace template page margins / colPr / footNotePr / endNotePr with IR
  values (fixes ~2x page-count divergence on multi-column and endnote-heavy
  documents)
- Preserve secPr/colPr order when ColumnDef precedes SectionDef
- ir-diff: return diff count from ir_diff(); exclude HWP5 vendor tab padding
  (code units 3-5, not representable in OWPML <hp:tab>, Hancom's own
  converter drops it too) from semantic comparison

Verification on an 11-sample suite (complex tables, nested tables, cropped/
header pictures, equations, 2-column exam docs, 4-10MB real documents):
- IR diff vs original HWP: 1074 (v0.7.15) -> 3 total, 9/11 samples at zero
- Render page count match: 11/11
- Known residuals: paragraph char_count raw mismatch (HWP5 nchars variant
  without trailing marker, not representable in HWPX), and a char-shape
  boundary position between trailing markers interacting with devel's
  AutoNumber slot handling (multi-table samples, 2 diffs)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… pairs

Follow-up to the hwp2hwpx lossless gate: with these comparator fixes the
29-pair Hancom-converted corpus (exam papers, LH press releases, hwp3->hwp5
conversions) goes from 2,126 reported diffs to 1 (an empty-paragraph raw
vpos=0 oddity), so the --verify gate now reports only real content
differences.

- char_shapes: compare referenced CharShape *content* (font names resolved
  through FACE_NAME, size, ratios/spacings, bold/italic/underline, colors)
  instead of raw table ids — Hancom rebuilds both CharShape and FaceName
  tables when saving HWPX (measured 317 vs 307 entries on the same document)
- table text_wrap: treat as equal when both sides are treat_as_char=true —
  wrap is meaningless for inline tables and Hancom normalizes the dormant
  field to TOP_AND_BOTTOM while HWP5 raw keeps a stale Square
- paragraph char_count: report only when the paragraph has substantive
  diffs (text/offsets/controls/char_shapes) — raw nchars varies by trailing
  marker convention and HWPX has no carrier field
- HWP3-conversion variant pairs (is_hwp3_variant on one side only):
  * ParaShape indent/spacing: accept the exact-2x delta introduced by the
    parser's variant normalization (parser/mod.rs Task edwardkim#1042)
  * line_segs: skip — typeset-derived values that Hancom itself recomputes
  * TabDef positions: ignore u32 negative-range sentinels from converted raw
- parser/hwpx: document that HWP5 raw paragraph margins are uniformly 2x
  (PARA_SHAPE raw hex verified; earlier per-document confusion came from
  the variant normalization above)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@edwardkim edwardkim self-requested a review June 10, 2026 23:37
@edwardkim edwardkim added the enhancement New feature or request label Jun 10, 2026
@edwardkim edwardkim added this to the v1.0.0 milestone Jun 10, 2026
@edwardkim

Copy link
Copy Markdown
Owner

안녕하세요, 김대호님. rhwp에 첫 기여를 보내주셔서 감사합니다.

이번 PR은 hwp2hwpx CLI와 --verify / --verify-pages 검증 게이트를 제안해 주신 점이 특히 좋았습니다. 한컴 미설치 환경에서 HWP → HWPX 변환을 검증 가능한 형태로 제공하는 방향은 프로젝트에 매우 의미 있는 작업이라고 생각합니다.

다만 현재 PR은 최신 devel 기준으로 바로 수용하기 어려운 상태입니다.

확인된 주요 사항은 다음과 같습니다.

그래서 번거로우시겠지만, 최신 devel에서 새로 브랜치를 만들거나 현재 브랜치를 최신 devel로 rebase한 뒤 다시 PR을 요청해 주시기를 권고드립니다. 재작업 시에는 아래 항목을 함께 반영해 주시면 리뷰가 훨씬 수월할 것 같습니다.

  1. 최신 devel 기준 conflict 해소
  2. Control::Form 직렬화와 SectionDef / ColumnDef 슬롯 소비 처리를 모두 보존
  3. cargo fmt 적용
  4. HWPX useFontSpace 파싱 복구
  5. useFontSpace="1" HWPX 파싱 회귀 테스트 추가
  6. char_shape_semantic_eq()에도 use_font_space 비교 포함
  7. 가능하다면 ir-diff 의미론 완화 항목별 테스트 또는 fixture 보강

참고로 로컬에서 작은 샘플 기준으로 hwp2hwpx 기본 변환, --verify, --verify-pages smoke test는 통과했습니다. 기능 방향 자체는 긍정적으로 보고 있습니다.

좋은 첫 기여 감사드립니다. 최신 devel 기준으로 정리된 PR을 다시 보내주시면 이어서 검토하겠습니다.

@edwardkim

Copy link
Copy Markdown
Owner

안녕하세요, 김대호님. 검토 후 진행 상황에 변화가 있어 추가로 안내드립니다.

오늘 devel에 #1379(HWPX serializer: subList 내부 컨트롤 보존)가 머지되면서, 이 PR의 일부 변경과 겹치는 부분이 생겼습니다. rebase 시 불필요한 충돌 작업을 줄이실 수 있도록 현재 기준 중복/고유 영역을 정리해 드립니다.

devel에 이미 구현되었거나 내부에서 처리 예정이라 드랍을 권장하는 부분

  • 다중 run 분할 (render_paragraph_runs, split_runs_by_char_shapes, 셀·머리말/꼬리말·각주 적용) — HWPX serializer: 문단 run 분할 보존 — char_shapes 전체를 다중 hp:run으로 출력 #1378/#1379에서 본문·셀·글상자가 동일한 render_paragraph_parts() 공유 경로로 전환되어 run 분할·컨트롤 슬롯·char_shapes 경계 보존이 함께 처리됩니다
  • 셀 lineseg IR 보존 (table.rs write_sub_list) — #1379에서 write_sub_list가 공유 경로 기반으로 전면 교체되어 lineseg IR 보존이 포함되었습니다
  • SecDef/ColDef 슬롯 소비 — #1379에서 셀·글상자 한정 colPr 인라인 방출(sub_list_depth 가드)이 들어갔습니다
  • secPr 용지 여백 IR 직렬화 — 같은 결함을 #1388로 등록했고 메인테이너 측에서 처리할 예정입니다

고유 가치가 있어 유지를 권장하는 부분

  • hwp2hwpx CLI + --verify / --verify-pages 게이트
  • BinData manifest 1-based 인덱스 매핑
  • 표 pageBreak CELL/TABLE 의미론 정정
  • footNotePr/endNotePr IR 직렬화
  • ir-diff 의미론 비교 업그레이드 (기존 코멘트의 테스트 보강 요청은 유효합니다)

기존 수정 요청 사항(fmt, useFontSpace 파싱 복구 등)은 그대로 유효합니다. 범위가 줄어드는 만큼 부담도 줄어들 것으로 기대합니다. 진행 중 궁금한 점이 있으시면 편하게 말씀해 주세요. 감사합니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants