Skip to content

Commit d9b009c

Browse files
authored
Merge pull request #26 from DanMeon/chore/hwpx-fidelity-sync
chore: v0.8.1 — 상류 HWPX fidelity sync 흡수 (도형 shapeComment 이슈 1451)
2 parents 31807c4 + 6e0aceb commit d9b009c

9 files changed

Lines changed: 211 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@ All notable changes to this project are documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.8.1] — 2026-07-07
9+
10+
PATCH release. 상류 `edwardkim/rhwp` 의 HWPX serializer fidelity 개선을 흡수한다 — 특히 본 프로젝트가 보고한 이슈 [#1451](https://github.com/edwardkim/rhwp/issues/1451) (legacy 도형 `shapeComment` 미직렬화) 이 상류에서 해결되어, `verify_hwpx_roundtrip` 의 보존 boundary 가 도형 shapeComment 까지 실질 확대됐다. 공개 API / IR schema (`"1.1"`) 불변, 표면 신규 0. spec / ADR: [docs/roadmap/v0.8.1/hwpx-fidelity-sync.md](docs/roadmap/v0.8.1/hwpx-fidelity-sync.md) / [docs/design/v0.8.1/hwpx-fidelity-sync-research.md](docs/design/v0.8.1/hwpx-fidelity-sync-research.md).
11+
12+
### Changed
13+
14+
- `verify_hwpx_roundtrip()` 의 round-trip 무손실 범위가 legacy 도형 (polygon / ellipse 등) `shapeComment` 까지 넓어졌다 — 상류 #1451 해결을 pin 으로 흡수한 결과. `verify_hwpx_roundtrip` 시그니처·반환 타입 (`RoundtripReport`) 은 불변이고, 상류 `diff_documents` 가 비교하는 필드 집합만 넓어진 하위호환 확대다. 이전에 `table-vpos-01.hwpx` 의 도형 캡션이 round-trip 에서 소실되던 것이 이제 보존된다.
15+
16+
### Tests
17+
18+
- `tests/test_hwpx_writeback.py``test_lossy_document_reports_human_readable_differences` 재설계 — 상류 #1451 해결로 `table-vpos-01.hwpx` 가 무손실 round-trip 이 되어 자연 발생 손실 fixture 가 소멸했다. (1) 도형 shapeComment 무손실 positive (`test_shapecomment_document_roundtrips_lossless` — 손실 재발 시 red 회귀 가드) + (2) `RoundtripReport(ok=False, ...)` unit-construct 로 손실 리포트 계약 (non-empty `list[str]` + False 방향 invariant) 을 검증하는 `test_report_surfaces_differences_when_constructed_lossy` 로 교체. 손실 detection 자체는 상류 `diff_documents` 계약에 위임 (현 fixture 코퍼스 무손실).
19+
20+
### Build
21+
22+
- `external/rhwp` submodule pin `7d9aae7f` (v0.7.16 + 36) → `10f5c51e` (v0.7.17 + 68, 상류 `main` HEAD). v0.8.0 GA 기록 이후 흡수된 238 commit 을 확정. **본 binding 관점 회귀 0** — 공개 API / IR schema (`"1.1"`) / wheel 의존성 불변. 우리가 소비하는 상류 심볼 (`DocumentCore` 메서드 / `serialize_hwpx` / `diff_documents` / `svgs_to_pdf` / `RasterRenderOptions` / `LayerRasterRenderer` / `Paragraph::control_text_positions` / `utf16_pos_to_char_idx`) 시그니처 전부 불변. 검증: `cargo check` clean, `maturin develop --release` clean, `pytest -m "not slow"` 회귀 0, IR baseline byte-equal (`aift.hwp` + `table-vpos-01.hwpx`).
23+
- 흡수한 핵심 상류 개선: **legacy 도형 shapeComment 직렬화 (#1451** — 본 프로젝트 보고, commit `2d6d0cf9` + `c0f94fbb`). 이로써 `verify_hwpx_roundtrip``diff_documents` 위임 검증이 도형 shapeComment 손실을 자동 반영한다. 그 외 바탕쪽 MasterPage 직렬화 / 쪽 테두리 pageBorderFill / 묶음 개체 자식 좌표 등 serializer emit 개선이 동반되나, 상류 `diff_documents` 가 비교하지 않아 `verify_hwpx_roundtrip` 보장 범위에는 미포함 (emit ≠ verify).
24+
- 상류 이슈 문서 [docs/upstream/issue-hwpx-shapecomment-drawing-shapes.md](docs/upstream/issue-hwpx-shapecomment-drawing-shapes.md) 를 RESOLVED (in-place Frozen) 로 전환 — 본 프로젝트 보고 → 상류 머지 경로 완결.
25+
- `Cargo.toml``version` `0.8.0``0.8.1`. `pyproject.toml``dynamic = ["version"]` 으로 자동 추종.
26+
827
## [0.8.0] — 2026-06-21
928

1029
MINOR release. parse 한 `Document` 를 HWPX 로 저장했을 때 IR 의미가 보존되는지 검증하는 `Document.verify_hwpx_roundtrip()` 표면을 추가하고, 보존 boundary 를 v0.7.0 의 텍스트·문단에서 상류 `diff_documents` 가 실제 비교하는 필드 집합 (표 cell·캡션·page_break, 그림 크기·캡션, char_shape·lineseg, PageDef, 리소스·BinData count) 으로 확대한다. 직렬화·진단 모두 상류 위임 — 추가만 있고 기존 표면 보존, IR schema (`"1.1"`) 변경 0. 동시에 상류 v0.7.13 ~ v0.7.16 sync 를 흡수한다.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rhwp-python"
3-
version = "0.8.0"
3+
version = "0.8.1"
44
edition = "2021"
55
# ^ rust-version 미명시 — 상위 rhwp crate 정책(stable Rust, MSRV unclaimed) 준수.
66
# PyO3 0.28 이 Rust 1.83+ 요구하지만, 이는 README 에 문서로 안내
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
---
2+
status: Draft
3+
description: "v0.8.1 hwpx-fidelity-sync ADR — 'test_lossy' 재설계 방식 / PATCH 등급 판단 / pin 추적 브랜치 3건의 옵션 비교와 근거"
4+
target: v0.8.1
5+
last_updated: 2026-07-07
6+
---
7+
8+
# v0.8.1 hwpx-fidelity-sync — 설계 의사결정 리서치 요약
9+
10+
[v0.8.1/hwpx-fidelity-sync.md](../../roadmap/v0.8.1/hwpx-fidelity-sync.md) §결정 사항 중 외부 독자가 "왜?" 를 던질 만한 **3**건의 업계 선례·대안·실패 시나리오를 기록한다. spec 본문이 최종 결정을 기술하고, 본 문서는 그 결정의 근거를 담는다.
11+
12+
## 결정 매트릭스
13+
14+
| # | 항목 | 옵션 비교 | 채택 | 1차 근거 |
15+
|---|---|---|---|---|
16+
| 1 | `test_lossy` 재설계 | A: 새 손실 fixture 탐색 / B: xfail 대기 / C: 손실 e2e 제거 → invariant 흡수 / D: C + `RoundtripReport` unit-construct 계약 검증 | D | detection 은 상류 `diff_documents` 위임, binding 의 `ok=False` 리포트 분기는 unit 으로 가드 |
17+
| 2 | 버전 등급 | A: PATCH v0.8.1 / B: MINOR v0.9.0 | A | 공개 표면 신규 0 · breaking 0, v0.6.1 전례 동형 |
18+
| 3 | pin 추적 브랜치 | A: `main` (`10f5c51e`) / B: `devel` (`bca79ee5`) | A | `devel` 미릴리스, upstream 자체가 `main` 으로만 GA |
19+
20+
## 1. `test_lossy` 재설계
21+
22+
### 팩트
23+
24+
- `table-vpos-01.hwpx` 는 v0.8.0 시점 (pin `7d9aae7f`) 에서 polygon 2개의 `<hp:shapeComment>다각형입니다.</hp:shapeComment>``serialize_hwpx` 출력에서 소실되어 `verify_hwpx_roundtrip().ok == False` 였다 (`docs/upstream/issue-hwpx-shapecomment-drawing-shapes.md` 재현 절).
25+
- 상류 #1451 해결 (commit `2d6d0cf9` legacy 도형 `shapeComment` 직렬화 1단계, `c0f94fbb` Polygon 보존 가드 2단계) 로 현재 pin `10f5c51e` 에서 동일 문서가 `report.ok is True` / `report.differences == []` (실측: `pytest` 에서 `test_lossy_document_reports_human_readable_differences` FAILED — `RoundtripReport(ok=True, differences=[])`).
26+
- 현 fixture 코퍼스는 `aift.hwpx` (HWP5→HWPX 변환) 와 `table-vpos-01.hwpx` 둘 뿐이고 양쪽 모두 무손실 round-trip.
27+
- `verify_hwpx_roundtrip``self.inner.document()` 를 serialize → reparse → `diff_documents(원본, 재파싱)` 하는 self round-trip 이라 외부 손상 주입 지점이 없다 — 손실은 오직 상류 serializer 가 아직 못 쓰는 요소에서만 자연 발생.
28+
- 상류 `diff_documents` 가 비교하지 않는 요소 (수식 script / cell rowspan·colspan / BinData byte / 도형 raw) 는 차이가 있어도 검출되지 않으므로 손실 유도 fixture 로 부적합.
29+
30+
### 검증자 반박
31+
32+
- "손실 검출 e2e 를 없애면 verify 가 실제 손실을 잡는지 누가 보증하나?" → 상류가 `IrDifference` 를 방출하는지는 상류 회귀 테스트 (`task1392_shape_comment_loss_in_gate``ObjectComment` 게이트) 가 가드한다. 본 binding 의 변환 (`IrDifference``d.to_string()`) 은 순수 매핑이라 상류 계약이 성립하면 표면도 성립.
33+
- "상류가 회귀하면 무손실 positive 가 놓치지 않나?" → 회귀 시 `table-vpos-01.hwpx` 의 AC-1 이 `ok is True` 위반으로 red 전환되어 자동 검출된다 — negative 를 positive 로 뒤집었을 뿐 검출력은 보존.
34+
- "새 손실 fixture (A) 가 더 견고하지 않나?" → 상류가 143 HWPX 샘플 xfail 0 에 도달해 자연 손실 문서 확보가 난망하고, 확보하더라도 상류의 다음 fidelity 개선에 또 무효화되어 테스트가 상류 구현 진척에 결합된다 (동일 실패의 반복).
35+
- "xfail (B) 로 두면 의도가 보존되지 않나?" → xfail 은 "언젠가 xpass 기대" 의미인데 여기선 반대 (상류 회귀 시 손실 재발) 라 의미가 뒤집힌다. invariant 흡수가 semantics 정합.
36+
- "손실 e2e 를 지우면 binding 의 `ok=False` 분기가 아무 테스트도 안 타지 않나?" → 맞다 — 이것이 옵션 C 의 잔여 공백이다. `RoundtripReport` 는 invariant 를 model-enforce 하지 않고 (`python/rhwp/document.py` plain Pydantic) Rust 가 `ok = differences.is_empty()` (`src/document.rs:341`) 로 설정하기에만 성립하므로, 이 분기를 미검증으로 두면 리포트 계약이 무가드가 된다. 옵션 D 는 `RoundtripReport(ok=False, differences=[...])` 를 unit 으로 구성·assert 하여 non-empty str list + False 방향 invariant 를 e2e 없이 가드한다 (detection 위임은 유지, binding 표면 계약만 추가 검증).
37+
38+
### 최종 결정
39+
40+
D 채택. 손실 검출 e2e (`test_lossy...`) 를 제거하고 무손실 positive (AC-1/2) + invariant (AC-3) 로 재편하되, binding 의 `ok=False` 리포트 분기는 `RoundtripReport(ok=False, differences=[...])` unit-construct 로 계약 (AC-4) 을 가드한다. detection 자체는 상류 `diff_documents` 계약에 위임하고, 상류 회귀는 positive 테스트의 red 전환으로 자동 노출된다.
41+
42+
### 1차 소스
43+
44+
- 상류 이슈: <https://github.com/edwardkim/rhwp/issues/1451>
45+
- 상류 해결 commit: `2d6d0cf9` (legacy 도형 shapeComment 직렬화 1단계), `c0f94fbb` (Polygon 보존 가드 2단계)
46+
- upstream 이슈 초안 (재현 절 포함): `docs/upstream/issue-hwpx-shapecomment-drawing-shapes.md`
47+
48+
## 2. 버전 등급
49+
50+
### 팩트
51+
52+
- 현재 pin `10f5c51e` 에서 `cargo check` clean, `pytest -m "not slow"` 604 passed. 실패 2건은 (i) `test_version_is_package_version``.venv` 의 낡은 `rhwp_python-0.7.0.dist-info` 로 인한 로컬 메타데이터 불일치 (upstream 무관, 재설치로 해소) (ii) 본 spec 대상 `test_lossy`.
53+
- 우리 소비 심볼 (`DocumentCore` 6 메서드 / `serialize_hwpx` / `diff_documents` / `svgs_to_pdf` / `RasterRenderOptions` / `LayerRasterRenderer` / `Paragraph::control_text_positions` / `utf16_pos_to_char_idx`) 시그니처 불변.
54+
- IR schema 는 `"1.1"` 유지, `verify_hwpx_roundtrip` / `RoundtripReport` 시그니처·의미 불변, 신규 public 메서드·모듈 0.
55+
- v0.6.1 은 "상류 v0.7.11~v0.7.12 GA 흡수 + polish" 를 PATCH 로 발행 (CHANGELOG `[0.6.1]`).
56+
57+
### 검증자 반박
58+
59+
- "보존 boundary 확대는 사용자 계약 강화라 MINOR 아닌가?" → 확대는 상류 `diff_documents` 위임의 자동 반영이지 binding 표면 추가가 아니다. 사용자가 호출하는 계약 (`verify_hwpx_roundtrip` 시그니처·반환 타입·의미) 은 불변이고 보장 범위만 상류를 따라 넓어진다 — SemVer 상 하위호환 확대라 PATCH 가 정합.
60+
- "로드맵 v0.9.0 예약과 충돌하지 않나?" → v0.9.0 (HWP5 binary writeback) 은 별개 스코프이고, v0.8.1 은 그 사이 삽입되는 PATCH 라 SemVer 단조 증가를 깨지 않는다.
61+
62+
### 최종 결정
63+
64+
A 채택 (v0.8.1 PATCH). 공개 표면 신규 0 · breaking 0 · IR schema 불변이라 상류 GA 흡수 성격의 PATCH 로 발행하고, MINOR 예약 (v0.9.0) 은 보존한다.
65+
66+
### 1차 소스
67+
68+
- SemVer 2.0.0: <https://semver.org/spec/v2.0.0.html>
69+
- 전례: `CHANGELOG.md` `[0.6.1]` 섹션 (상류 GA 흡수 PATCH)
70+
71+
## 3. pin 추적 브랜치
72+
73+
### 팩트
74+
75+
- 상류 원격 상태 (`git ls-remote`): `main` = `10f5c51e` (2026-06-25), `devel` = `bca79ee5` (2026-07-07). `main..devel` = 959 commit, `devel..main` = 14 commit.
76+
- `.gitmodules``branch = main` 으로 고정. 우리 pin `10f5c51e``main` HEAD 와 동일하고 v0.7.17 태그보다 68 commit 앞선다.
77+
- `devel` 최근 활동은 내부 리팩토링 (라운드 1~4: `parse_paragraph_list` 해체, `object_ops.rs` 9839줄 하위 모듈 분해, `layout_composed` 226→146) — main→devel diff 1561 파일 / +130K / -17K.
78+
- 본 spec 이 흡수하는 fidelity 개선 (#1451 등) 은 이미 `main` HEAD (`10f5c51e`) 에 머지되어 포함됨.
79+
- 우리 소비 심볼은 `devel` 에서도 시그니처 보존 (대조 완료) — 파일 위치만 이동 (`document_core/commands/`, `queries/` 하위 분해).
80+
81+
### 검증자 반박
82+
83+
- "devel 리팩토링이 크니 미리 채택해 다음 sync 를 줄이는 게 낫지 않나?" → 미릴리스 개발 브랜치라 미완 리팩토링의 회귀를 조기 노출하고 upstream 이 아직 검증 중인 상태에 결합된다. upstream 자체도 `main` merge 로만 GA 를 낸다.
84+
- "devel 을 채택하면 fidelity 를 더 얻나?" → 본 spec 이 목표하는 fidelity 는 이미 `main` 에 있다. devel 추가분은 주로 내부 리팩토링이라 사용자 관찰 이득이 미미하고 계약은 불변.
85+
86+
### 최종 결정
87+
88+
A 채택 (`main`, pin `10f5c51e`). `.gitmodules``main` 추적 정책을 유지하고 `devel` 리팩토링은 상류 GA 시점에 흡수한다.
89+
90+
### 1차 소스
91+
92+
- `.gitmodules` (`branch = main`)
93+
- 상류 브랜치 구조: <https://github.com/edwardkim/rhwp>
94+
95+
## 참조
96+
97+
- [roadmap/v0.8.1/hwpx-fidelity-sync.md](../../roadmap/v0.8.1/hwpx-fidelity-sync.md) — 본 리서치의 결정 요약
98+
- 상류 이슈: [edwardkim/rhwp#1451](https://github.com/edwardkim/rhwp/issues/1451)

docs/roadmap/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ rhwp-python 의 버전별 로드맵 + **활성 spec 인덱스 SSOT**. 모든 spe
3636
| v0.6.0 (png-vlm-render) | Frozen | [v0.6.0/png-vlm-render.md](v0.6.0/png-vlm-render.md) | [design/v0.6.0/png-vlm-render-research.md](../design/v0.6.0/png-vlm-render-research.md) |
3737
| v0.7.0 (hwpx-writeback-baseline) | Frozen | [v0.7.0/hwpx-writeback-baseline.md](v0.7.0/hwpx-writeback-baseline.md) | [design/v0.7.0/hwpx-writeback-baseline-research.md](../design/v0.7.0/hwpx-writeback-baseline-research.md) |
3838
| v0.8.0 (hwpx-writeback-expansion) | Frozen | [v0.8.0/hwpx-writeback-expansion.md](v0.8.0/hwpx-writeback-expansion.md) | [design/v0.8.0/hwpx-writeback-expansion-research.md](../design/v0.8.0/hwpx-writeback-expansion-research.md) |
39+
| v0.8.1 (hwpx-fidelity-sync) | Draft | [v0.8.1/hwpx-fidelity-sync.md](v0.8.1/hwpx-fidelity-sync.md) | [design/v0.8.1/hwpx-fidelity-sync-research.md](../design/v0.8.1/hwpx-fidelity-sync-research.md) |
3940

4041
## 미착수 작업 계획
4142

@@ -73,6 +74,18 @@ SemVer 0.x.y 단계에서 minor 는 단조 증가. v1.0.0 은 API 안정 선언
7374
- 완전한 레이아웃 보존 (폰트 embedding 미포함 상태의 재생성) — 뷰어 차이 허용
7475
- 매크로·폼 필드·OLE 임베딩 — HWP 독자 확장 기능은 장기 과제
7576

77+
### IR 노출 확장 — 상류 파싱 정보의 미노출 갭 해소
78+
79+
2026-07-07 갭 조사 결과: 상류 `model` 은 완전한 문서 모델 (글자·문단 스타일, 문서 메타데이터, 하이퍼링크, 차트 데이터 등) 을 파싱하나 우리 IR 은 텍스트 + 기본 구조만 노출한다. 대부분 상류가 이미 파싱해 두므로 얇은 wire-through 로 메울 수 있다 (신규 파싱 구현 아님). v0.3.0 "IR 확장" 라인의 연장이며 위 writeback 라인과 독립 축. 버전 분할은 착수 시점 `/new-spec` 으로 확정.
80+
81+
우선순위 (RAG/문서이해 가치 × 노력):
82+
83+
- **티어 1 — 값싼 노출, 즉시 가치** (상류 파싱 완료, 현재 IR 이 버림): 문서 메타데이터 (제목·작성자·작성일, 현재 항상 빈값) · 글자 스타일 확장 (크기·색상·폰트, 현재 굵기/기울임/밑줄/취소선 4개만) · 문단 속성 (정렬·들여쓰기·줄간격, 현재 head_type/level 만) · 하이퍼링크 URL (현재 컨트롤 통째 미노출).
84+
- **티어 2 — 중간 노력, 큰 기능**: 차트 구조화 데이터 추출 (`series{name, values}` — 읽기 전용, 현재 SVG 렌더만) · 페이지 단위 Markdown / 레이아웃 인식 텍스트 (RAG 청킹, 현재 전체 IR 순서 `extract_text` 만) · HWP5 binary writeback (`to_hwp`, 상류 준비 완료 — 위 writeback 라인과 합류).
85+
- **티어 3 — 상황별**: 표 스타일 (제목행 반복·테두리·셀 정렬) · 그림 기하·효과·alt-text · 전체 텍스트 검색 · HTML 렌더.
86+
87+
양식개체 (체크박스/버튼) 는 위 비범위의 "폼 필드" 와 일치 — 장기 과제로 유지. 편집 commands (에디터 지향) 와 문서 생성 빌더는 "문서 이해·변환" 정체성 밖.
88+
7689
> 과거 GA 완료된 minor (v0.1.x ~ v0.3.0) 의 historical record 는 [v0.1.0/rhwp-python.md](v0.1.0/rhwp-python.md) / [v0.2.0/ir.md](v0.2.0/ir.md) / [v0.3.0/ir-expansion.md](v0.3.0/ir-expansion.md) / [v0.3.0/cli.md](v0.3.0/cli.md) 가 보유.
7790
7891
## 구현 / 검증 로그 (Frozen)

0 commit comments

Comments
 (0)