Skip to content

render: introduce PageLayerTree generation API#419

Closed
seo-rii wants to merge 9 commits intoedwardkim:develfrom
seo-rii:render-p1
Closed

render: introduce PageLayerTree generation API#419
seo-rii wants to merge 9 commits intoedwardkim:develfrom
seo-rii:render-p1

Conversation

@seo-rii
Copy link
Copy Markdown
Contributor

@seo-rii seo-rii commented Apr 28, 2026

변경 요약

#165 에서 한 번에 넣었던 렌더링 구조 변경을 먼저 작게 나눠서 올립니다.

이번 PR은 Skia / CanvasKit / ThorVG 같은 신규 backend를 붙이는 PR이 아니라, PageRenderTree 이후 단계에 PageLayerTree를 만들고 이를 Rust native / WASM에서 확인할 수 있게 하는 1단계입니다.

Closes #364

포함한 것

  • paint 모듈 추가
    • PageLayerTree
    • LayerNode
    • PaintOp
    • LayerBuilder
    • ResourceArena
  • Rust native API 추가
    • DocumentCore::build_page_layer_tree(page_num)
    • DocumentCore::get_page_layer_tree_native(page_num)
  • WASM API 추가
    • getPageLayerTree(pageNum)
  • JSON export 추가
    • schemaVersion: 1
    • resourceTableVersion: 1
    • unit: "px"
    • coordinateSystem: "page-top-left"
  • RawSvg / Placeholder가 lowering 과정에서 빠지지 않도록 PaintOp에 명시적으로 추가
  • 기존 SVG renderer를 재사용하는 opt-in transition adapter 추가
    • RHWP_RENDER_PATH=layer-svg
    • 이 경로는 새 SVG backend가 아니라 PageLayerTree -> 임시 PageRenderTree -> 기존 SvgRenderer 검증용 adapter입니다.

Schema outline

  • top-level: schemaVersion, resourceTableVersion, unit, coordinateSystem, profile, pageWidth, pageHeight, root
  • layer node kind: group, clipRect, leaf
  • paint op type: pageBackground, textRun, footnoteMarker, line, rectangle, ellipse, path, image, equation, formObject, placeholder, rawSvg
  • text: 현재는 glyph run이 아니라 string + style + positions 기반입니다. 한글/폰트 fallback/italic/형광펜 계열 정보를 보존할 수 있도록 style payload를 포함했습니다.
  • shape/path: fill/stroke/pattern/gradient/opacity/shadow/lineStyle/connectorEndpoints를 JSON에 포함합니다.
  • transform: 현재는 2D matrix가 아니라 rotation, horzFlip, vertFlip 형태입니다.

Schema 변경은 가능한 한 additive하게 유지하고, JSON shape가 호환되지 않게 바뀌면 schemaVersion을 올리는 정책으로 가져가겠습니다.

이번 PR에서 하지 않는 것

  • Skia backend 추가 안 함
  • CanvasKit backend 추가 안 함
  • ThorVG backend 추가 안 함
  • C ABI 추가 안 함
  • Canvas public path 전환 안 함
  • ResourceArena의 binary resource interning 구현 안 함
  • renderer 간 pixel-perfect parity 보장 안 함

Canvas와 기존 SVG 기본 출력은 여전히 legacy PageRenderTree 경로를 사용합니다. layer-svg는 opt-in 검증 경로입니다.

관련

관련 이슈

closes #364

테스트

  • cargo test 통과
  • cargo clippy -- -D warnings 통과
  • 관련 샘플 파일로 SVG 내보내기 확인
  • 웹(WASM) 렌더링 확인 (해당하는 경우)

스크린샷

변경 전후 비교가 필요한 경우 첨부해주세요.

edwardkim added a commit that referenced this pull request Apr 29, 2026
- mydocs/pr/pr_419_review.md (옵션 A: 본질 8 commits 분리 cherry-pick)
- mydocs/pr/pr_419_report.md (cherry-pick 머지 결정 + 4단계 검증 정리)
- mydocs/pr/pr_419_fidelity_analysis.md (작업지시자 요청 피델리티 영향 분석)
- mydocs/orders/20260426.md (PR #419 완료 행 추가)

검증: 1062 passed + svg_snapshot 6/6 + issue_418 1/1 + clippy 0 + WASM 4,184,496 bytes
피델리티: 10 샘플 309 페이지 SVG 100% byte 단위 동일, 기존 렌더러 5 파일 변경 0 라인
@edwardkim
Copy link
Copy Markdown
Owner

@seo-rii 님 PR 감사드립니다. 메인테이너가 cherry-pick 으로 devel 에 적용 완료했습니다.

처리

PR 의 9 commits 중 merge commit 1개 (devel into render-p1) 제외하고 본질 8 commits 분리 cherry-pick — 작성자 attribution 보존:

  • 51ce84f (← 6f973d6) feat: add layered paint tree scaffolding
  • 194ee10 (← fc3572e) feat: add layered svg replay path
  • 575b0da (← cd523f4) feat: expose layer tree json for browser backends
  • ac68c30 (← 646cb33) docs: document multi-renderer backends
  • 42dc3db (← 0e17be0) fix: tighten layer tree replay contract
  • 0647802 (← f4d2385) fix: preserve layer node metadata in json
  • 11f2ac8 (← a8749f3) fix: align layer tree api with issue 364
  • e2015fe (← a27896b) fix: carry layer output options

devel 머지 commit: 092cdaa

검증

피델리티 영향 분석

작업지시자 요청으로 별도 분석 진행 — mydocs/pr/pr_419_fidelity_analysis.md 에 4단계 증거 정리:

  1. 정적 코드 분석: 기존 5 렌더러 파일 (svg.rs, canvas.rs, web_canvas.rs, render_tree.rs, layout.rs) 변경 0 라인. paint 미의존.
  2. wasm_api.rs 본질 변경: 메서드 251 → 252 (get_page_layer_tree 1개만 신규, 나머지는 rustfmt).
  3. 환경변수 가드: layer-svg 경로는 RHWP_RENDER_PATH=layer-svg 명시 시만 활성화. 기본 동작은 legacy 그대로.
  4. 광범위 byte 단위 비교: 10 샘플 / 309 페이지 SVG 모두 devel baseline 과 byte 단위 동일 (100%).

→ 작성자 본문 주장 ("Canvas 와 기존 SVG 기본 출력은 여전히 legacy PageRenderTree 경로 사용") 모두 사실 확정.

메인테이너 안내 7항목 정확 대응

이슈 #364 의 안내 7항목 모두 정확 반영하신 점 확인했습니다 — schema outline / schemaVersion 정책 / 변환 정확성 (paint::builder 단위 테스트 8건) / PR 범위 좁히기 / surface 우선순위 (Rust+WASM 만, C ABI 후속) / 회귀 테스트.

향후 backend 추가 (Skia/CanvasKit 등) PR 도 같은 방향으로 이어지길 기대합니다. 이슈 #364 도 함께 close 됩니다. 감사합니다.

@edwardkim edwardkim closed this Apr 29, 2026
edwardkim added a commit that referenced this pull request Apr 30, 2026
- 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 머지 후 작업지시자 직접)
edwardkim added a commit that referenced this pull request May 9, 2026
본질: 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants