render: route Canvas rendering through PageLayerTree#456
Conversation
- src/renderer/layout/utils.rs::find_bin_data 의 c.id == bin_data_id 가드 제거 (c.id 는 storage_id, bin_data_id 는 인덱스 — 가드가 정상 케이스를 거짓 실패시킴) - 1-indexed 인덱스 매칭 우선, 범위 밖일 때만 id 직접 검색 (HWPX 차트 sparse id 보존) - 단위 테스트 5개: zero / 인덱스 매칭 (storage_id 다른 케이스, hwpspec.hwp 패턴) / 일반 케이스 / 차트 sparse id / 범위 밖 - cargo test --lib renderer::layout::utils::: 5 passed - cargo build --lib + clippy 통과 - 수행/구현 계획서 + Stage 1 보고서 추가 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
차트 회귀 우려 점검: - HWPX 차트 sparse id (60001+) 는 항상 bin_data_content.len() 보다 큼 → 인덱스 범위 밖 → fallback id 검색 → 정확 매칭 - HWPX 일반 그림 (id=인덱스+1, 항상 일치) 은 인덱스 매칭으로 동일 결과 - 차트 회귀 위험 0 신규 단위 테스트 2개: - find_bin_data_hwpx_realistic_layout_with_chart: 일반 BinData 1~3 + 차트 60001/60002 의 실제 push 패턴 모사, 양쪽 모두 정상 매칭 검증 - find_bin_data_hwp_hwpspec_page_bg_pattern: hwpspec.hwp 의 14개 BinData 모사, bin_data_id=1 이 storage_id=12 (BIN000C.png) 매칭 검증 검증: - 단위 테스트 7/7 passed - 전체 lib test: 1023 passed (1016 → +7) - svg_snapshot: 6/6 passed (다른 샘플 무회귀) - samples/hwpspec.hwp 1 페이지 SVG 재생성: * 정정 전: <image width="16" height="13"> (강제 stretch) * 정정 후: <image width="793.72" height="1121.56"> (정상 PNG, 1137 bytes 의 BIN000C) * 파일 크기 42 KB → 260 KB Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 자동 검증 종합: lib 1023 passed, svg_snapshot 6/6, clippy warning 0건, WASM 빌드 통과 - 트러블슈팅 문서 갱신 (bin_data_id_index_mapping.md): * 2026-04-20 회귀 origin (Task edwardkim#195) 이력 추가 * 2026-04-28 재정정 (가드 제거 + sparse id 분기) * 추가 교훈 — 가드/fallback 추가 시 트러블슈팅 정독, PR 검토 절차 강화 - 최종 보고서 + 오늘할일 갱신 - 별개 결함 (페이지 20 이미지 이중 출력) 은 별도 이슈로 처리 예정 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- mydocs/pr/pr_456_review.md (P2 cherry-pick 검토, SVG 100% byte 동일) 검증: 1075 passed (+5 Canvas parity test) + svg_snapshot 6/6 + issue_418 1/1 + clippy 0 WASM: 4,206,022 bytes (+19,741, paint 모듈 Canvas replay 추가) 광범위 byte 비교: 305/305 byte 동일 (SVG legacy 경로 0 영향) ✅ 본질 (PR #419 의 P2): - Canvas 렌더 경로를 PageLayerTree replay 로 전환 - legacy 경로는 renderPageCanvasLegacy 로 보존 (fallback) - LayerBuilder leaf children 보존 정정 - Canvas parity test 추가 (CI 통합) 시각 판정: 통합 검증 (PR #454 + #457 + #461 + #456 머지 후 작업지시자 직접)
…ry-pick @seo-rii 6 commits)
|
@seo-rii 님 PR 감사드립니다. 메인테이너가 cherry-pick 으로 devel 에 적용 완료했습니다. PR #419 (P1) 에서 만들어주신 PageLayerTree generation API 를 Canvas 렌더 경로의 default 로 전환한 P2 — 외부 backend 도입 토대가 정확히 구축되었습니다. 처리작성자 attribution 보존 본질 6 commits 분리 cherry-pick (PR #419 commits + Task #416 commits 11 개는 이미 devel 흡수):
devel 머지 commit: 검증
광범위 byte 비교 — 매우 깔끔10 샘플 / 305 페이지 SVG 비교: 305/305 byte 단위 동일 (100%) ✅ → SVG legacy 경로 영향 0. PR 본문 명시 ("기존 SVG 기본 출력은 여전히 legacy 경로를 사용") 정확 검증. 본 PR 의 좋은 점
통합 시각 검증 정책작업지시자 결정으로 PR #454 + #457 + #461 + #456 모두 머지된 현재 상태에서 통합 시각 검증 진행 예정. 본 PR (#456) 의 Canvas 경로 전환은 SVG byte 단위 동일이라 위험 매우 낮음 — 통합 검증의 핵심은:
후속 P3 안내작성자 본문 명시:
→ P3 도 같은 P1/P2 패턴 (분리 PR + 작은 단위 회전) 으로 진행 환영합니다. P1/P2 의 정확한 분리 + Canvas parity test 추가 + LayerBuilder 본질 정정 모두 좋은 작업이었습니다. 감사합니다. |
…Canvas→LayerTree 반영 (Task edwardkim#460 기반) - upstream/devel 46 commits 병합 (PR edwardkim#446 set_field fix ~ PR edwardkim#456 Canvas→LayerTree) - 소스 파일 자동 병합 성공 (layout.rs, paragraph_layout.rs, engine.rs) - orders/20260430.md add/add 충돌만 수동 해소 (Task edwardkim#460 섹션 + PR edwardkim#450 섹션 통합) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…7 commits) 본 PR 은 PR #456 (PageLayerTree replay 전환 P2) 후속 P3 검증 레이어: - rhwp-studio E2E (canvas-render-diff.test.mjs) - GitHub Actions Render Diff workflow - legacy Canvas vs PageLayerTree replay Canvas 픽셀 diff 자동 검증 7 commits 분리 cherry-pick (test + diagnostics + docs + CI runner + 보안 hardening 3건). 변경 영역: JS E2E + CI workflow + Vite 설정 + 문서 (Rust 변경 0). 검증: cargo test --lib 1102 passed, svg_snapshot 6/6, issue_418 1/1, issue_501 PASS, clippy 0건. 작업지시자 정책: ios/devel 처럼 skia 쪽 렌더러도 별도 브랜치로 운영하여 위험도 낮추는 방법 고려 중. 본 검증 인프라가 향후 별도 backend 실험 시 backend 별 회귀 검출 도구로 활용 가능. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
본질: PR #599 (P4 PNG raster backend) + PR #626 (P5 equation replay) 후속의 P6 단계. native Skia 경로 영역 의 RawSvg leaf 영역 placeholder fallback 영역 → 실제 raster (resvg + tiny-skia 영역) 정합. 기존 (renderer.rs:763): PaintOp::RawSvg { bbox, .. } => draw_placeholder(*bbox, "svg") 정정: rasterize_svg_fragment_to_png + draw_image_bytes 영역 재사용 영역. 신규 함수 (image_conv.rs +82 LOC): - draw_svg_fragment(canvas, fragment, x, y, w, h, sampling) -> bool - rasterize_svg_fragment_to_png(fragment, w, h) -> Option<Vec<u8>> - svg_parse_options() -> usvg::Options<'static> renderer.rs:760+ (line +88/-6): - PaintOp::RawSvg { bbox, raw } 영역 의 draw_svg_fragment 호출 - invalid SVG 영역 fallback placeholder 영역 보존 보안 가드 (영향 좁힘): - MAX_SVG_FRAGMENT_BYTES = 4 MB (fragment 크기 가드) - MAX_SVG_RASTER_PIXELS = 67M (8192x8192 영역 raster 가드) - resolve_string = Box::new(|_, _| None) (external href 차단 — file:// / http:// / https:// 등) - resolve_data = usvg 기본 data: URI resolver (data: URI 만 허용) - resources_dir = None (디렉터리 자동 탐색 차단) - Wrapper SVG: <svg xmlns="..." width="..." height="..." viewBox="...">{fragment}</svg> 의존성 (Cargo.toml): - native-skia feature 영역 의 dep:resvg 추가 - resvg = { version = "0.45", optional = true } 회귀 가드 테스트 (2건 신규): - renders_raw_svg_fragment_as_colored_ink: green rect 100+ green 픽셀 검증 - raw_svg_replay_does_not_load_external_file_hrefs: 외부 file href 영역 red 0 픽셀 검증 (보안 가드 작동 입증) 영향 범위: - native Skia PNG/VLM 경로 영역 의 차트/OLE/내장 SVG 영역 fragment 영역 실제 렌더링 - WASM/browser SVG / CanvasKit / form replay 영역 무영향 (별건) - 다른 PaintOp 영역 (Image, Equation, Path, Text 등) 무영향 비목표 명시 (PR 본문): - browser/WASM SVG replay / CanvasKit raw SVG replay - full SVG security policy 설계 - network/file resource loading - animated SVG / SVG filter 전체 parity - form native replay / VLM preset 확장 (#613) - PNG DPI metadata (#614) 검증: - cargo test --release: lib 1173 + 통합 ALL GREEN, failed 0 - cargo test --release --features native-skia skia --lib: 24/24 PASS (신규 2건 회귀 가드 + 기존 22건) - cargo clippy --release --features native-skia --lib -- -D warnings: 통과 - 광범위 sweep 7 fixture / 170 페이지 / 회귀 0 (Skia 영역 무관 영역, native-skia 미사용 영역 sweep 정합 확정) @seo-rii 7번째 사이클 PR (Skia 영역 트래킹 #536 영역 의 단계적 진전 영역). PR #165 (skia 도입) → #419 (PageLayerTree) → #456 (Canvas 라우팅) → #498 (visual diff) → #599 (P4 PNG) → #626 (P5 equation) → #720 (P6 raw SVG). refs #536 Co-Authored-By: seorii <me@seorii.page> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
변경 요약
#165 에서 한 번에 들어갔던 렌더링 구조 변경을 P1 / P2로 나눠서 올리는 두 번째 단계입니다.
P1에서는
PageRenderTree이후 단계에PageLayerTree를 만들고, 이를 Rust native / WASM에서 확인할 수 있게 했습니다. 이번 PR은 그 다음 단계로, 기존 Canvas 렌더링 경로가PageRenderTree를 직접 소비하지 않고PageLayerTree를 통해 replay되도록 전환합니다.목적은 새 renderer를 추가하는 것이 아니라, 이미 있는 Canvas renderer도 P1에서 만든 frontend/backend boundary를 타게 만드는 것입니다. 이렇게 하면 이후 CanvasKit, Skia, ThorVG 같은 renderer를 붙일 때도 모두 같은
PageLayerTree입력을 기준으로 검증할 수 있습니다.포함한 것
PageLayerTreereplay 경로 추가WebCanvasRenderer에PageLayerTreereplay 경로 추가renderPageCanvasrenderPageToCanvasrenderPageCanvasLegacyrenderPageToCanvasLegacyLayerBuilder가 leaf node의 child를 보존하도록 수정이번 PR에서 하지 않는 것
PageLayerTreereplay 입력으로 옮기는 단계입니다.기존 SVG 기본 출력은 여전히 legacy 경로를 사용합니다. 이 PR에서 사용자-visible하게 바뀌는 부분은 public Canvas API가
PageLayerTree를 거쳐 replay된다는 점입니다.관련 이슈
#364 #419 #165
테스트
cargo test canvas_layer_tree_matches_legacy --lib통과cargo check --lib통과cargo check --target wasm32-unknown-unknown --lib통과PageLayerTree기반으로 빌드되는지 확인스크린샷
이 PR은 Canvas replay 경로 전환과 parity test 추가가 중심이라 별도 스크린샷은 첨부하지 않았습니다.
pixel diff / visual regression 결과는 후속 P3 PR에서 다룹니다.