Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8f53f4f
feat: add layered paint tree scaffolding
seo-rii Apr 14, 2026
02b2acc
feat: add layered svg replay path
seo-rii Apr 14, 2026
6100537
test: add raster diff coverage for layered svg
seo-rii Apr 14, 2026
f363a6a
feat: add native skia layer renderer
seo-rii Apr 14, 2026
464c47b
test: add skia screenshot diff coverage
seo-rii Apr 14, 2026
cf3bdf6
feat: expose layer tree json for browser backends
seo-rii Apr 15, 2026
9e47b5f
feat: add browser canvaskit layer renderer
seo-rii Apr 15, 2026
1f2aedb
test: add canvaskit screenshot diff coverage
seo-rii Apr 15, 2026
119ea6a
style: format core rust modules
seo-rii Apr 15, 2026
ac6cb85
style: format wmf modules
seo-rii Apr 15, 2026
3c2cca3
chore: fix clippy warnings
seo-rii Apr 15, 2026
92b9ca6
docs: document multi-renderer backends
seo-rii Apr 15, 2026
cec7bb6
feat(studio): make canvaskit render mode configurable
seo-rii Apr 16, 2026
ae8fa43
feat(renderer): refactor layered layout pipeline
seo-rii Apr 16, 2026
b1432ba
fix(renderer): stabilize browser and skia parity
seo-rii Apr 16, 2026
3b81841
test(renderer): add tolerant visual diff thresholds
seo-rii Apr 16, 2026
f060851
test(renderer): zero tolerant diffs for passing cases
seo-rii Apr 16, 2026
cb9d066
refactor(renderer): simplify skia integration flow
seo-rii Apr 17, 2026
e311802
fix(renderer): reuse layer cache and honor layer-svg fonts
seo-rii Apr 17, 2026
aeea5d3
fix(renderer): render form controls in skia backends
seo-rii Apr 17, 2026
334c13a
fix(renderer): match skia image fill semantics
seo-rii Apr 17, 2026
882114f
fix(canvaskit): match hamchorom text parity
seo-rii Apr 17, 2026
3f0378d
fix(native-skia): add resvg runtime dependency
seo-rii Apr 17, 2026
37e4cfc
feat(layer): serialize equation and connector metadata
seo-rii Apr 17, 2026
d7e61d4
test(canvaskit): expand browser regression coverage
seo-rii Apr 17, 2026
ddc8898
feat(canvaskit): match shape and equation parity
seo-rii Apr 17, 2026
1e1859d
fix(canvaskit): restore eq-01 full-page parity
seo-rii Apr 17, 2026
6753b2a
fix: preserve shape children in canvaskit parity path
seo-rii Apr 17, 2026
5504413
docs: add layered renderer architecture guide
seo-rii Apr 17, 2026
eb55549
fix(native-skia): replay equations and tighten diff gates
seo-rii Apr 18, 2026
6f8a904
fix(canvaskit): stabilize default mode parity
seo-rii Apr 18, 2026
dc9cd68
feat: add native skia equation renderer
seo-rii Apr 19, 2026
552d8d1
test: harden skia raster screenshot comparison
seo-rii Apr 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
49 changes: 47 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ HWP 파일이 한컴과 다르게 렌더링되면 알려주세요:
### PR 전 체크리스트

```bash
cargo test # 783+ 테스트 통과
cargo clippy -- -D warnings # 린트 경고 0건
cargo test # 793+ 테스트 통과
cargo clippy --all-targets --all-features # native-skia까지 확인하려면 fontconfig/freetype 개발 패키지가 필요할 수 있음
```

두 명령이 모두 통과하는지 확인한 후 PR을 생성해주세요.
Expand Down Expand Up @@ -122,6 +122,51 @@ cargo run --bin rhwp -- dump-pages sample.hwp -p 3
cargo run --bin rhwp -- dump sample.hwp -s 0 -p 45
```

`export-svg`의 기본 출력 폴더는 `output/`입니다. 예를 들어 `sample.hwp`를 한 페이지 문서로 내보내면 `output/sample.svg`, 여러 페이지면 `output/sample_001.svg`처럼 저장됩니다. `-o`를 사용하면 다른 폴더로 보낼 수 있습니다.

### 렌더러 비교 가이드

현재 렌더러 경로는 아래처럼 나뉩니다.

- **Legacy SVG**: 기본 `cargo run --bin rhwp -- export-svg sample.hwp`
- **Layer SVG**: `RHWP_RENDER_PATH=layer-svg cargo run --bin rhwp -- export-svg sample.hwp`
- **Native Skia PNG**: 테스트 경로에서 `render_page_png_native()`로 검증되며, 현재 별도 `export-png` CLI는 없습니다
- **Browser Canvas2D / CanvasKit**: `rhwp-studio`에서 기본은 Canvas2D, `http://localhost:7700/?renderer=canvaskit`로 CanvasKit 비교
- CanvasKit 래스터 모드: `?canvaskitMode=compat`(기본, Canvas2D 유사도 우선) 또는 `?canvaskitMode=default`(CanvasKit 기본 동작)

SVG를 직접 비교하려면 보통 아래처럼 두 번 내보냅니다.

```bash
cargo run --bin rhwp -- export-svg sample.hwp -o output/legacy
RHWP_RENDER_PATH=layer-svg cargo run --bin rhwp -- export-svg sample.hwp -o output/layer
```

자동 회귀 테스트는 다음 명령을 사용합니다.

```bash
cargo test layer_svg --lib
RUSTFLAGS='-L native=target/native-libs' cargo test skia --lib --features native-skia

cd rhwp-studio
npm run e2e # 기본: host Chrome CDP 모드, CanvasKit compat/default 둘 다 실행
node e2e/text-flow.test.mjs --mode=headless && node e2e/canvaskit-render.test.mjs --mode=headless && RHWP_CANVASKIT_MODE=default node e2e/canvaskit-render.test.mjs --mode=headless
```

WSL/CI처럼 호스트 Chrome CDP가 없는 환경에서는 `npm run e2e` 대신 `--mode=headless` 명령을 사용하세요.

비교 아티팩트는 아래 위치에 남습니다.

- `output/layer-svg-diff/` — legacy SVG vs layer SVG
- `output/skia-diff/` — layer SVG vs native Skia PNG
- `output/e2e/` 및 `rhwp-studio/e2e/screenshots/` — 브라우저 Canvas2D vs CanvasKit

- `layer-svg` 비교는 현재 exact match 기준입니다. 한 픽셀이라도 diff가 생기면 테스트가 실패합니다.
- `native-skia` / `CanvasKit` 비교는 exact diff를 계속 저장하고, 별도로 채널 차이가 `8` 이하인 픽셀을 무시한 tolerant diff를 계산합니다.
- 테스트 통과 여부는 tolerant diff 기준으로 판단합니다. 현재 기준은 `native-skia`는 tolerant diff pixel `64` 이하, `CanvasKit`은 tolerant diff ratio `0.25%` 이하입니다.
- tolerant 결과는 최종 허용 예산까지 반영한 값입니다. 즉 통과한 케이스는 tolerant diff가 `0`으로 보고되고, tolerant diff 아티팩트도 남기지 않습니다. exact diff 아티팩트는 참고용으로 계속 생성될 수 있습니다.
- `CanvasKit` e2e는 기본적으로 전체 페이지를 비교합니다. `eq-01`도 다시 전체 페이지 회귀에 포함됩니다.
- 추가로 `equation`처럼 특정 op 자체를 분리해서 추적하고 싶은 기능 회귀는 해당 `layer op` bbox만 잘라서 비교합니다.

디버그 오버레이는 문단/표에 라벨을 표시합니다:
- 문단: `s{섹션}:pi={인덱스} y={좌표}`
- 표: `s{섹션}:pi={인덱스} ci={컨트롤} {행}x{열} y={좌표}`
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,17 @@ console_error_panic_hook = { version = "0.1", optional = true }

[features]
default = ["console_error_panic_hook"]
native-skia = ["dep:skia-safe"]

# PDF 내보내기 (네이티브 전용, Task #21)
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
svg2pdf = "0.13"
usvg = "0.45"
resvg = "0.45"
pdf-writer = "0.12"
subsetter = "0.2"
ttf-parser = "0.25"
skia-safe = { version = "0.93.1", optional = true, default-features = false, features = ["binary-cache", "embed-icudtl", "pdf"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3", features = [
Expand Down
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ rhwp는 Rust + WebAssembly 기반의 오픈소스 HWP/HWPX 뷰어/에디터입

- HWP 5.0 / HWPX 파서, 문단·표·수식·이미지·차트 렌더링
- 페이지네이션 (다단 분할, 표 행 분할), 머리말/꼬리말/바탕쪽/각주
- SVG 내보내기 (CLI) + Canvas 렌더링 (WASM/Web)
- 레이어 기반 SVG/Canvas2D/CanvasKit/native Skia 렌더링 경로
- 웹 에디터 + hwpctl 호환 API (30 Actions, Field API)
- 783+ 테스트
- 793+ 테스트

### v1.0.0 — 조판 엔진

Expand Down Expand Up @@ -123,10 +123,24 @@ rhwp는 Rust + WebAssembly 기반의 오픈소스 HWP/HWPX 뷰어/에디터입
- vpos-based paragraph position correction

### Output (출력)
- SVG export (CLI)
- Canvas rendering (WASM/Web)
- SVG export (CLI, legacy + layer replay)
- Canvas rendering (WASM/Web, Canvas2D + CanvasKit)
- Native Skia PNG rendering (feature-gated)
- Debug overlay (paragraph/table boundaries + indices + y-coordinates)

### Multi-Renderer Backends (멀티 렌더러 백엔드)
- `PageLayerTree` 페인트 IR를 공유하고, 백엔드별로 replay만 다르게 수행합니다.
- **Legacy SVG**: 기본 `rhwp export-svg sample.hwp`
- **Layered SVG**: `RHWP_RENDER_PATH=layer-svg rhwp export-svg sample.hwp`
- **Native Skia**: non-wasm 타깃에서 `native-skia` feature로 PNG 렌더링
- **Browser Canvas2D / CanvasKit**: `rhwp-studio` 기본값은 Canvas2D, `?renderer=canvaskit`로 CanvasKit 선택

### Renderer Regression Tests (렌더러 회귀 테스트)
- `cargo test layer_svg --lib`
- `cargo test --features native-skia skia --lib`
- `cd rhwp-studio && npm run e2e`
- diff artifact는 `output/layer-svg-diff`, `output/skia-diff`, `rhwp-studio/output/e2e`에 남습니다.

### Web Editor (웹 에디터)
- Text editing (insert, delete, undo/redo)
- Character/paragraph formatting dialogs
Expand Down Expand Up @@ -200,7 +214,8 @@ document.getElementById('viewer').innerHTML = doc.renderPageSvg(0);
```bash
cargo build # Development build
cargo build --release # Release build
cargo test # Run tests (755+ tests)
cargo test # Run tests (793+ tests)
cargo clippy --all-targets --all-features # native-skia까지 보려면 fontconfig/freetype 개발 패키지가 필요할 수 있음
```

### WASM Build
Expand All @@ -224,6 +239,8 @@ npx vite --host 0.0.0.0 --port 7700

Open `http://localhost:7700` in your browser.

브라우저 렌더러를 바꿔 비교하려면 `http://localhost:7700/?renderer=canvas2d` 또는 `http://localhost:7700/?renderer=canvaskit`를 사용하세요.

## CLI Usage

### SVG Export
Expand Down
27 changes: 22 additions & 5 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,24 @@ document.getElementById('viewer').innerHTML = doc.renderPageSvg(0);
- vpos-based paragraph position correction

### Output
- SVG export (CLI)
- Canvas rendering (WASM/Web)
- SVG export (CLI, legacy + layer replay)
- Canvas rendering (WASM/Web, Canvas2D + CanvasKit)
- Native Skia PNG rendering (feature-gated)
- Debug overlay (paragraph/table boundaries + indices + y-coordinates)

### Multi-Renderer Backends
- rhwp now shares a `PageLayerTree` paint IR and replays it through different backends.
- **Legacy SVG**: default `rhwp export-svg sample.hwp`
- **Layered SVG**: `RHWP_RENDER_PATH=layer-svg rhwp export-svg sample.hwp`
- **Native Skia**: PNG rendering on non-wasm targets behind the `native-skia` feature
- **Browser Canvas2D / CanvasKit**: `rhwp-studio` defaults to Canvas2D, and `?renderer=canvaskit` switches to CanvasKit

### Renderer Regression Tests
- `cargo test layer_svg --lib`
- `cargo test --features native-skia skia --lib`
- `cd rhwp-studio && npm run e2e`
- diff artifacts are written to `output/layer-svg-diff`, `output/skia-diff`, and `rhwp-studio/output/e2e`.

### Web Editor
- Text editing (insert, delete, undo/redo)
- Character/paragraph formatting dialogs
Expand All @@ -145,7 +159,8 @@ document.getElementById('viewer').innerHTML = doc.renderPageSvg(0);
```bash
cargo build # Development build
cargo build --release # Release build
cargo test # Run tests (783+ tests)
cargo test # Run tests (793+ tests)
cargo clippy --all-targets --all-features # native-skia may require fontconfig/freetype development libraries
```

### WASM Build
Expand All @@ -167,6 +182,8 @@ npx vite --port 7700

Open `http://localhost:7700` in your browser.

To compare browser backends directly, open `http://localhost:7700/?renderer=canvas2d` or `http://localhost:7700/?renderer=canvaskit`.

## CLI Usage

### SVG Export
Expand Down Expand Up @@ -277,7 +294,7 @@ The `mydocs/` directory (724 files, English translations in `mydocs/eng/`) conta
Most AI coding demos show simple tasks. This project demonstrates AI pair programming **at production scale**:

- **100K+ lines of Rust** — parser, renderer, pagination, editor
- **783+ tests** with zero clippy warnings
- **793+ tests** with zero clippy warnings
- **Reverse engineering** a proprietary binary format
- **Sub-pixel layout accuracy** matching commercial software
- **Full CI/CD pipeline** — from commit to npm publish to GitHub Pages
Expand All @@ -292,7 +309,7 @@ The `mydocs/` directory is not documentation about the code — it's documentati
|--|-------------|-------------|
| **Human role** | Accept AI output | Direct, review, decide |
| **Planning** | None — "just build it" | Written plan → approval → execution |
| **Quality gate** | Hope it works | 783 tests + Clippy + CI + code review |
| **Quality gate** | Hope it works | 793 tests + Clippy + CI + code review |
| **Debugging** | Ask AI to fix AI's bugs | Human diagnoses, AI implements fix |
| **Architecture** | Emergent (accidental) | Deliberate (CQRS, dependency direction) |
| **Documentation** | None | 724 files of process records |
Expand Down
Loading