Skip to content

멀티 렌더러 지원 트래킹 이슈 #536

@seo-rii

Description

@seo-rii

제안 내용

멀티 렌더러를 안정적으로 지원할 수 있도록 렌더링 구조를 단계적으로 분리합니다.

기존에는 각 renderer가 PageRenderTree를 직접 해석하는 흐름에 가까웠습니다. 이 시리즈에서는 renderer backend가 공통으로 replay할 수 있는 PageLayerTree를 중간 IR로 두고, SVG / Canvas2D / native Skia / CanvasKit / PDF export backend가 같은 layer contract를 기준으로 동작하게 합니다.

Document / Layout
-> PageRenderTree
-> PageLayerTree
-> SVG / Canvas2D / CanvasKit / native Skia / PDF export backend

이 이슈는 멀티 렌더러 작업을 리뷰 가능한 PR 단위로 나누어 추적하기 위한 tracking issue입니다. 기준은 단순히 변경 파일 수를 줄이는 것이 아니라, 각 PR이 독립적으로 설명 가능한 contract를 갖도록 만드는 것입니다.

분할 기준

  • 기본 동작을 바로 바꾸지 않는 구조 변경은 먼저 넣습니다.
  • public rendering path가 바뀌는 PR은 별도로 둡니다.
  • path 전환 뒤에는 visual/pixel diff 같은 검증 pipeline을 따로 붙입니다.
  • native Skia backend는 Canvas 전환과 섞지 않고 별도 backend PR로 둡니다.
  • native Skia 안에서도 equation, raw SVG, form control, text, image effect처럼 실패 양상이 다른 leaf replay는 독립적으로 검토합니다.
  • foundation만 추가하고 consumer가 없는 phase는 가능하면 다음 consumer 작업과 합칩니다.
  • Text IR v2는 source table, placement/cluster metadata, special visual externalization을 각각 쪼개지 않고 TextRun compatibility contract 단위로 묶습니다.
  • GlyphRun/GlyphOutline은 당분간 canonical path가 아니라 optional text variant로만 둡니다. TextRun fallback이 항상 남아 있어야 합니다.
  • text variant adoption은 writer contract, diagnostics, backend selection gate를 먼저 만들고, 실제 backend replay는 별도 phase에서 엽니다.
  • CanvasKit은 Canvas2D-assisted preview가 아니라 native Skia로 이어지는 독립 replay backend로 키웁니다. canvaskitMode=default는 direct replay 우선이며 Canvas2D overlay로 조용히 숨기지 않습니다. canvaskitMode=compat도 overlay fallback이 아니라 더 보수적인 CanvasKit direct replay policy입니다.
  • CanvasKit overlay 제거는 mode/diagnostics foundation -> raster image direct replay -> equation/formObject -> TextRun effects -> GlyphRun/GlyphOutline gates 순서로 진행합니다. 지원하지 않는 기능은 Canvas2D paint로 덮지 않고 TextRun fallback, deterministic reject reason, backend diagnostics로 드러냅니다. raster output은 fuzzy 허용, semantic decision은 exact 기준으로 둡니다.
  • CanvasKit direct replay가 기존 Canvas2D와 의도적으로 달라지는 경우는 Skia strict replay improvement로 fixture/diagnostics에 명시하고, compatibility mismatch와 구분합니다.
  • PDF export는 PNG raster export와 다른 출력 계약으로 보고 별도 PR로 둡니다. 다만 SVG->PDF MVP와 direct/vector hardening은 같은 PDF backend contract 안에서 같이 봅니다.
  • native binding/Swift package는 별도 사용자 표면입니다. native render/export API가 추가될 때마다 Swift/XCFramework/FFI 문서 영향 여부를 같이 확인합니다.

최신 확인 결과 (2026-06-10)

계획 업데이트 (2026-06-10)

CanvasKit 방향은 overlay fallback을 줄이는 수준이 아니라, browser CanvasKit과 future native Skia가 같은 replay contract를 공유할 수 있도록 overlay-free direct replay를 기본 전제로 둡니다. P16-P21이 devel에 반영되었으므로 이후 계획은 “초기 renderer 도입”이 아니라 “direct replay coverage를 어떤 contract 단위로 넓히고 검증할지”가 기준입니다.

  • canvaskitMode=default: direct CanvasKit replay 우선.
  • canvaskitMode=compat: Canvas2D overlay fallback이 아니라 conservative CanvasKit direct replay policy.
  • unsupported case: Canvas2D paint로 숨기지 않고 TextRun fallback, deterministic reject reason, backend diagnostics 중 하나로 처리.
  • canvaskitSurface=auto|webgpu|webgl|software: render mode와 별도인 diagnostics axis. browser baseline, native-vs-CanvasKit parity report, aggregate Markdown report에 요청 surface와 fallback reason을 보존합니다.
  • P17은 bridge/schema/payload gate hardening으로 완료했습니다. latest devel layout fixes와 섞지 않고, compat direct replay reporting, bridge fallback safety, schema metadata sync, deterministic reject reason 기준을 먼저 닫았습니다.
  • P18은 image replay coverage로 완료했습니다. crop/fill-mode/original-size/transform/cache-key/direct diagnostics는 닫았고, effect/filter와 broader object replay는 후속으로 넘깁니다.
  • P19는 text/glyph advanced payload gate와 COLRv1 supported replay subset으로 완료했습니다. PR #1117은 maintainer-side cherry-pick 수용 후 closed 되었고, ColorLayers, BitmapGlyph, SvgGlyph, GlyphOutline richer payload의 validator/reject reason과 COLRv1 solid/linear/radial/sweep CanvasKit direct replay subset이 devel에 들어갔습니다. composite/blend/clip reuse/unreachable graph/malformed root/unordered stop은 deterministic reject로 유지합니다.
  • P20은 glyph payload resource corpus와 font construction proof로 완료했습니다. PR #1159에서 static vector glyph/resource payload fixture, payloadResourceKey, cache-key/resource identity, face index/variation/exact typeface construction failure diagnostics를 묶어서 GlyphRun/GlyphOutline replay eligibility 전제를 단단히 했습니다.
  • P21은 renderer sweep / replay-plane ordering / surface axis를 합친 baseline infrastructure phase로 완료했습니다. PR #1312에서 SVG, Canvas2D, CanvasKit, native Skia를 작은 representative manifest/report에서 비교하는 report-first 파이프라인, Full Renderer Sweep manual workflow, browser/native baseline report, surface axis diagnostics를 정리했습니다. Large corpus 확장과 PDF artifact 수집은 P21에 넣지 않았습니다.
  • P22는 WebCanvas direct PaintOp replay와 layer option metadata closure로 완료했습니다. Canvas public path를 다시 바꾸지 않고, WebCanvas layer replay가 leaf payload를 임시 legacy node로 되돌리는 부분을 줄였고 buildOptions/debugOptions/outputOptions contract를 명확히 했습니다.
  • P23은 PDF export와 native render/export API packaging입니다. PR #1359에서는 먼저 SVG-derived PDF export를 DocumentCore native API로 올리고 CLI export-pdf가 같은 API를 쓰도록 정리합니다. render-diff CI에는 report-only PDF visual diff를 추가해 export-pdf raster와 browser Canvas 출력의 차이를 summary/artifact로 남깁니다. Direct/vector PageLayerTree -> PDF, PDF visual diff hard gate, Swift/XCFramework/FFI packaging 변경은 후속으로 남깁니다.
  • P24는 strict BitmapGlyph/SvgGlyph producer-output corpus widening입니다. 기존 handcrafted payload fixture를 더 늘리는 게 아니라, 실제 lowering/resource path를 통과한 one-strike bitmap payload와 sanitized static vector payload를 corpus로 추가합니다.
  • P25는 exact font replay corpus widening입니다. native Skia variation/TTC proof는 real variable-font / real collection corpus와 digest-pinned positive/negative controls로 넓히고, CanvasKit variation/TTC는 exact construction proof가 생길 때까지 conservative fallback을 유지합니다.
  • P26은 authority-gated v2 follow-up입니다. 추가 COLRv1 blend/composite/clip primitive, shapedModern width input/line breaking, cross-scope variants, public MixedPerGlyph writer emission은 concrete document/use case와 fallback/reject policy가 준비될 때까지 보류합니다.
  • direct replay가 Canvas2D와 의도적으로 달라지는 경우는 Skia strict replay improvement로 fixture/diagnostics에 명시하고, compatibility mismatch와 구분합니다.
  • crossScopeVariants, MixedPerGlyph, shapedModern layout mutation은 schema vocabulary와 validator gate는 유지하되, 실제 writer emission은 use case / corpus / backend semantics가 준비될 때까지 보류합니다.

Text IR v2 설계 상태

Text IR v2는 기존 TextRun fallback을 유지한 채, backend가 더 엄격한 text variant를 선택할 수 있는 compatibility contract를 단계적으로 여는 작업입니다.

  • P11: source table, source span, placement/cluster metadata, special visual op externalization, feature negotiation metadata를 추가했습니다.
  • P12: GlyphRun optional sidecar와 font/blob/face identity contract를 추가했습니다. native Skia와 Canvas2D/layered SVG는 fallback을 유지합니다.
  • P13: textV2 diagnostics, validation issue, line-break risk telemetry, fallback-free profile guard를 schema minor 1.10으로 정리했습니다.
  • P14: GlyphOutline strict sidecar와 backend text variant selection diagnostics를 schema minor 1.11로 추가했습니다. 기존 renderers는 TextRun fallback을 유지했습니다.
  • P19: advanced GlyphOutline payload vocabulary와 COLRv1 bounded graph contract를 schema minor 1.14로 추가했습니다. CanvasKit은 solid/linear/radial/full-circle sweep gradient subset만 direct replay하고, 나머지는 deterministic reject와 TextRun fallback을 유지합니다.
  • P20: payloadResourceKey, resource table minor 1.4, native font-construction proof diagnostics를 추가했습니다. exact glyph-id replay는 proof가 있는 native cases로만 제한하고, CanvasKit variation/TTC는 conservative fallback을 유지합니다.

비목표도 유지합니다.

  • GlyphRun/GlyphOutline을 canonical text path로 전환하지 않습니다.
  • font blob / font face resource table을 완성했다고 보지 않습니다.
  • native Skia glyph id replay나 CanvasKit glyph replay를 기본 경로로 넣지 않습니다.
  • public render path를 바꾸지 않습니다.
  • Text IR v2 schema를 최종 안정화했다고 선언하지 않습니다.

P16 이후 보존/이동한 수정 방향

P16이 devel에 반영되었으므로, 이후 phase는 CanvasKit direct replay renderer를 더 크게 만드는 방식이 아니라 contract 단위로 안정성을 넓히는 방식으로 갑니다.

  • CanvasKit direct replay 방향은 유지합니다.
    • default mode에서는 unsupported op를 Canvas2D overlay로 조용히 가리지 않습니다.
    • compat mode도 Canvas2D overlay fallback이 아니라 conservative direct replay mode입니다.
    • 필요한 경우 더 보수적인 clip padding / sampling / variant selection / cache policy를 선택하고, unsupported case는 deterministic fallback/reject diagnostics로 남깁니다.
  • P17은 다음 hardening을 완료했습니다.
    • replay bridge fallback safety
    • layer schema metadata sync
    • compat direct replay reporting
    • CanvasKit payload replay gates and reject reasons
  • P18은 image replay coverage expansion으로 완료했습니다.
    • crop / fill-mode / original-size / transform replay
    • image cache key payload fingerprint
    • direct image diagnostics and replay-plan detail
    • effect / brightness / contrast는 direct pixel effect가 아니라 diagnostics로 노출
  • P19는 text/glyph advanced payload gate와 COLRv1 supported replay subset으로 완료했습니다. 관련 PR은 #1117입니다.
    • GlyphRun/GlyphOutline은 canonical path가 아니라 gated text variant입니다.
    • ColorLayers, BitmapGlyph, SvgGlyph payload validator/reject reason을 고정했습니다.
    • COLRv1 solid/linear/radial/sweep graph는 CanvasKit direct replay subset으로 열었고, composite/blend/clip reuse처럼 아직 replay하지 않을 payload는 deterministic reject로 남겼습니다.
    • GlyphOutline은 generic Path처럼 섞지 않고 text variant gate로만 다룹니다.
    • Canvas2D/CanvasKit GlyphOutline replay branch parity를 payloadKind, colorFormat, COLRv1 graph node.kind 기준으로 검증했습니다.
  • P20은 glyph payload resource corpus와 font construction proof로 완료했습니다. 관련 PR은 #1159입니다.
    • static vector glyph/resource payload fixture로 resource identity와 cache key를 검증합니다.
    • payloadResourceKey로 color/bitmap/SVG glyph sidecar resource/cache identity를 분리했습니다.
    • face index, variation, exact typeface construction 실패를 backend별 silent fallback이 아니라 diagnostics/reject matrix로 확인합니다.
  • P21은 sweep/report이면서 replay-plane ordering baseline으로 완료했습니다. 관련 PR은 #1312입니다.
    • SVG / Canvas2D / native Skia / CanvasKit을 작은 representative fixture 기준으로 비교할 수 있는 runner와 report를 정리했습니다.
    • replay-plane subtree detection, z-order, empty-plane skip, static cache plane key가 backend마다 갈라지지 않게 고정했습니다.
    • WebGPU/software surface axis와 performance metrics는 default CI gate가 아니라 artifact/report로 먼저 두었습니다.
  • P22는 WebCanvas boundary cleanup으로 완료했습니다. 관련 PR은 #1346입니다.
    • WebCanvas layer replay가 image/page-background/shape/text/equation/footnote/form payload를 직접 소비하도록 legacy wrapper adapter를 줄였습니다.
    • layer buildOptions, debugOptions, outputOptions는 build-time metadata, debug display metadata, replay semantics를 구분해서 내보냅니다.
    • public Canvas route를 다시 바꾸지 않고, 이미 layer 기반인 route의 내부 adapter risk를 줄였습니다.
  • P23은 PDF export와 native API packaging입니다. 관련 PR은 #1359입니다.
    • SVG-derived PDF export를 DocumentCore native API와 CLI가 공유하는 표면으로 올립니다.
    • render-diff CI에 report-only PDF visual diff를 추가해 export-pdf raster와 browser Canvas 출력의 차이를 JSON/Markdown summary와 artifact로 남깁니다.
    • direct/vector hardening, PDF visual diff hard gate, Swift/XCFramework/FFI packaging 변경은 후속으로 남깁니다.
  • P24/P25는 strict glyph/font proof를 넓히는 phase입니다.
    • P24는 strict BitmapGlyph/SvgGlyph producer-output corpus를 추가합니다.
    • P25는 native variation/TTC real corpus와 CanvasKit exact construction proof gate를 다룹니다.
  • P26은 authority-gated follow-up입니다.
    • COLRv1 추가 primitive, shapedModern, cross-scope variants, public MixedPerGlyph는 concrete corpus/use case가 생길 때까지 writer emission을 열지 않습니다.
  • 최신 devel에는 layout/render/security 수정이 계속 들어오고 있으므로, 후속 renderer PR은 항상 최신 devel 위에서 시작합니다. broad layout fix나 extension fetch security fix는 renderer backend PR에 섞지 않고 이미 devel에 들어간 correctness/security fix를 전제로 삼습니다.

현재 진행상황

P22까지 devel에 반영되었습니다. 현재 next action은 P23입니다. P23 PR #1359에서는 PDF export를 native DocumentCore API와 CLI가 공유하는 표면으로 올리고, report-only PDF visual diff를 render-diff CI에 추가합니다. Direct/vector PDF backend와 PDF visual diff hard gate는 후속으로 분리합니다.

  • P1: 렌더러 프론트엔드/백엔드 1차 분리

  • P2: Canvas 렌더링을 PageLayerTree 기반으로 전환

  • P3: Canvas visual diff 테스트 추가

  • P4: native Skia PNG raster backend 추가

    • native-skia feature 추가
    • PageLayerTree 기반 Skia replay 구현
    • native-only PNG export API 추가
    • raster guard / image replay / fallback replay / native Skia smoke test 추가
    • 관련 PR: render: add native Skia PNG raster backend #599
  • P5: native Skia equation replay 추가

  • P6: native Skia raw SVG fragment replay

  • P7: native Skia form control static replay

    • push button / checkbox / radio button / combo box / edit field 정적 drawing
    • PNG/export 경로에서 form object가 placeholder로만 보이지 않도록 보강
    • 관련 PR: Skia form control static replay (P7) #740
  • P8: Layer IR schema/resource key hardening

  • P9: native Skia text replay parity + module split

    • native Skia text replay가 기존 TextRunNode의 char overlap, tab leader, control mark, decoration, vertical rotation 정보를 소비하도록 보강
    • 커진 text replay 구현을 renderer/skia/text_replay.rs로 분리
    • 관련 PR: render: improve Skia text replay parity #769
  • P10: P9에 fold

    • 별도 PR로 열기에는 순수 리팩터링에 가까워서 P9에 포함했습니다.
  • P11: Text IR v2 compatibility contract

  • P12: Guarded GlyphRun variant contract

    • TextRun fallback을 유지한 상태에서 GlyphRun optional sidecar variant 도입
    • font blob, face identity, replay eligibility 타입 추가
    • 관련 PR: render: add guarded GlyphRun layer variant contract #840 (cherry-pick/squash 후 closed)
    • devel merge commit: 249e5653; squashed feature commit: 08af19b6
  • P13: Text IR v2 diagnostics/schema closure

  • P14: GlyphOutline text variant adoption + backend selection diagnostics

    • PaintOp::GlyphOutline strict sidecar contract 추가
    • layer schema minor 1.11
    • text.glyphOutline / text.glyphOutline.strictSidecar JSON feature metadata 추가
    • analyze_text_variant_selection backend selection diagnostics API 추가
    • 기존 renderers는 GlyphOutline을 직접 그리지 않고 TextRun fallback을 유지
    • 관련 PR: render: adopt guarded text variants for backend replay #916 (closed 후 devel cherry-pick)
    • devel commit: 586d914a
  • P15: CanvasKit replay policy diagnostics

    • getCanvasKitReplayPlan(page, mode) diagnostics-only API 추가
    • CanvasKit default / compat mode contract와 hidden overlay inventory 추가
    • default는 hidden Canvas2D overlay를 금지하고, compat는 transition overlay를 명시적으로 보고하는 P15 기준 policy plan입니다.
    • 이후 P16에서는 compat도 Canvas2D overlay fallback이 아니라 conservative direct replay policy로 구현합니다.
    • 관련 PR: render: add CanvasKit replay policy diagnostics #925 (closed 후 devel cherry-pick)
    • devel commit: e94b1d01
  • P16: Browser CanvasKit direct renderer and overlay-free mode split

    • Studio에 browser CanvasKit backend를 추가했습니다.
    • PageLayerTree를 직접 replay하며 Canvas2D overlay pass를 backend contract로 들여오지 않습니다.
    • render-backend.ts에서 canvas2d / canvaskit backend, renderer=skia alias, canvaskitMode=default|compat, canvaskitSurface=auto|webgpu|webgl|software, renderProfile resolver를 정리했습니다.
    • URL 기반 renderer opt-in은 localStorage에 고정하지 않고, invalid renderer 값은 diagnostic/warning으로 드러냅니다.
    • default mode는 native-preparation direct replay입니다.
    • compat mode는 conservative direct replay입니다. clip padding, sampling, variant selection, cache policy는 보수적으로 고를 수 있지만 Canvas2D paint를 덮는 fallback pass는 쓰지 않습니다.
    • getPageLayerTreeObject(page, profile) bridge는 JSON parse/shape validation을 수행하고, legacy empty layer shape는 안전한 empty PageLayerTree로 낮춥니다.
    • renderer diagnostics는 unsupported op, surface fallback reason, per-page render failure를 남깁니다.
    • 관련 PR: render: add browser CanvasKit direct renderer #996 (closed 후 devel 반영)
    • devel merge/report commit: 3d4a9c34; feature commit: 933c056e; follow-up docs commit: 20482412
  • P17: CanvasKit direct replay contract hardening

    • compat mode가 Canvas2D overlay fallback이 아니라 direct replay contract로 보고되도록 native/WASM/Studio diagnostics를 정리했습니다.
    • CanvasKit replay mode policy를 DIRECT_ONLY 계약으로 모아 hidden overlay 허용 여부와 direct replay required 여부를 한 곳에서 설명합니다.
    • Studio bridge fallback도 hiddenCanvas2dOverlayAllowed=false, directReplayRequired=true contract를 유지합니다.
    • layer schema constant export와 LAYER_TREE_SCHEMA 동기화 테스트를 보강했습니다.
    • CanvasKit renderer / Studio fallback regression test로 Canvas2D overlay replay가 다시 들어오지 않게 막았습니다.
    • 관련 PR: render: harden CanvasKit direct replay contract #1057 (closed 후 devel cherry-pick)
    • devel commits: dbfd7563, 06047e53; review/report commit: 2b1cdb40
  • P18: CanvasKit image replay coverage expansion

    • 관련 PR: render: expand CanvasKit image replay coverage #1096
    • CanvasKit image replay가 layer JSON의 crop, originalSize, fillMode, transform payload를 소비하도록 확장합니다.
    • crop source rect는 기존 SVG replay와 같은 HWPUNIT 75 HU/px 기준으로 계산합니다.
    • image cache key에 base64 payload fingerprint를 포함해 같은 imageRef의 다른 payload 충돌을 막습니다.
    • getCanvasKitReplayPlan은 simple image를 direct로 보고, image effect / brightness / contrast는 아직 diagnostics/detail로 남깁니다.
    • non-goal: grayscale/blackWhite/pattern8x8 pixel filter, RawSvg/WMF/OLE object replay, TextRun/glyph replay.
    • devel merge commit: 6cbd25ea; 처리 기록 commit: 17f6ad84
  • P19: Text/glyph advanced payload gates + COLRv1 supported replay subset

    • 관련 PR: render: gate advanced glyph outline payloads #1117 (maintainer-side cherry-pick 수용 후 closed)
    • devel 반영 커밋: c54e80d0, 3945a937, 225e8229, 4d1938e7, 90f6fa84, 606505bd
    • layer schema minor 1.14text.glyphOutline.advancedPayloads feature metadata를 추가했습니다.
    • ColorLayers, BitmapGlyph, SvgGlyph, richer GlyphOutline payload family를 writer-ready로 선언하지 않고 gate vocabulary와 diagnostics로 먼저 노출했습니다.
    • COLRv1 solid/linear/radial/sweep graph만 CanvasKit direct replay subset으로 열고, composite/blend/clip reuse/unreachable graph/malformed root/unordered stop은 deterministic reject로 유지했습니다.
    • Canvas2D/CanvasKit branch parity, fallback/reject reason, schema/README/docs contract를 함께 검증했습니다.
    • non-goal 유지: arbitrary font construction, canonical text path 전환, native/SVG full color glyph replay.
  • P20: Glyph payload resource corpus and font construction proof

    • 관련 PR: render: add glyph payload resource proof diagnostics #1159
    • devel merge commit: 0f1c22ba
    • layer schema minor 1.15, resource table minor 1.4
    • payloadResourceKey로 color/bitmap/SVG glyph sidecar resource/cache identity를 분리했습니다.
    • ResourceArena font blob ref lookup과 native Skia glyph-run replay proof diagnostics를 추가했습니다.
    • native Skia exact glyph-id replay는 계속 닫아 두고 missing blob bytes, replay eligibility, face index, variation, typeface construction blocker를 분리했습니다.
  • P21: Renderer sweep, replay-plane ordering, surface axis, and report artifacts

    • 관련 PR: render: add renderer baseline sweep reports #1312
    • devel merge commit: 785f5ef7
    • 작은 representative manifest와 scripts/renderer_baseline.py report pipeline을 추가했습니다.
    • browser Canvas2D/CanvasKit baseline, native Skia vs CanvasKit parity report, CanvasKit surface axis/performance diagnostics를 정리했습니다.
    • Full Renderer Sweep manual workflow를 추가했습니다.
    • replay-plane subtree detection/helper를 CanvasKit/native Skia/WebCanvas 경로에서 공유하도록 정리했습니다.
    • non-goal 유지: large corpus expansion, PDF artifact collection, default CI gate.
  • P22: WebCanvas direct PaintOp replay and layer option metadata closure

    • 관련 PR: render: reduce WebCanvas layer adapter and split option metadata #1346
    • devel merge commit: 2bc411f4
    • WebCanvas layer replay에서 core PaintOp leaf payload를 직접 소비하도록 legacy wrapper adapter를 줄였습니다.
    • layer JSON의 buildOptions, debugOptions, outputOptions metadata를 분리하고 legacy outputOptions mirror를 compatibility field로 유지했습니다.
    • public Canvas route는 그대로 유지했고, 내부 adapter risk cleanup으로 닫았습니다.

다음 분할 후보

  • P23: PDF export and native render/export API packaging

    • 관련 PR: render: expose native PDF export API #1359
    • PDF는 PNG raster export와 다른 출력 계약입니다. P21에서는 PDF artifact를 baseline report에 넣지 않았으므로, export API 안정화는 별도 phase로 봅니다.
    • 이번 PR은 native-only SVG-derived PDF export MVP를 DocumentCore::render_page_pdf_native, render_pages_pdf_native, render_document_pdf_native API로 올리고, CLI export-pdf가 같은 API를 쓰게 합니다.
    • 단일/다중 페이지, page range, output path handling, error reporting은 focused test와 CLI smoke로 확인합니다.
    • render-diff CI에는 report-only PDF visual diff를 추가합니다. export-pdf 결과를 pdftoppm으로 rasterize하고 browser Canvas 출력과 비교해 JSON/Markdown summary와 artifacts를 남깁니다.
    • 이후 PageLayerTree에서 직접 PDF/vector recording으로 가는 경로를 검토합니다.
    • Swift/XCFramework/FFI packaging 변경과 PDF visual diff hard gate는 후속으로 둡니다.
    • unsupported object는 기존 fallback을 유지합니다.
  • P24: Strict BitmapGlyph/SvgGlyph producer-output corpus widening

    • handcrafted strict payload fixture를 더 늘리는 대신 실제 lowering/resource path를 통과한 producer-output fixture를 추가합니다.
    • BitmapGlyph는 one producer-selected strike, deterministic alpha/scaling/filtering, no strict backendDefault, resource bytes in cache key, colorSpaceDefaulted diagnostics를 유지합니다.
    • SvgGlyphVectorResourceId, required viewBox, securityMode=staticSanitized, script/animation/external/interactivity hard false, no raw SVG-in-font replay를 유지합니다.
    • strictVisual without valid payload는 hard reject하고 compatibility export는 TextRun/GlyphRun fallback을 유지합니다.
  • P25: Exact font replay corpus widening

    • native Skia variation replay는 real variable-font corpus와 digest-pinned positive/negative controls로 넓힙니다.
    • native Skia TTC/OTC replay는 real collection fixture, face-index positive/negative, wrong-face/high-index/ambiguous metadata, digest mismatch를 corpus로 검증합니다.
    • CanvasKit variation/TTC는 public API path가 exact variation tuple / exact collection face construction을 증명할 때까지 variationUnsupported / faceIndexUnsupported fallback을 유지합니다.
    • public u32 glyph id range guard는 backend enablement 뒤에도 유지합니다.
  • P26: Guarded v2 authority follow-ups

    • COLRv1 추가 blend/composite/clip primitive는 concrete document가 있고 glyph-payload-local semantics를 유지할 때만 엽니다.
    • shapedModern width input/line breaking은 representative HWP corpus, width-delta distribution, fallback split, cluster mapping, vertical metrics, table/cell review 전까지 report-only로 둡니다.
    • cross-scope variants는 same-scope fallback이 부족한 concrete use case가 생길 때까지 vocabulary/diagnostics만 유지합니다.
    • public MixedPerGlyph writer emission은 cluster/grapheme orientation, GlyphTransformRun, transform replay, vertical fixtures, backend fallback/reject policy가 안정될 때까지 homogeneous run split을 기본으로 둡니다.

합친 phase

  • P10은 P9에 합쳤습니다. 순수 리팩터링이라 별도 리뷰 단위로 두면 같은 text replay 코드를 두 번 보게 됩니다.
  • 기존 P11/P12/P13은 P11로 합쳤습니다. source table, placement/cluster metadata, special visuals externalization은 모두 TextRun v2 compatibility contract를 완성하는 한 묶음입니다.
  • 기존 P14/P15/P16은 P12로 합쳤습니다. font resource table만 따로 넣으면 consumer가 없는 foundation PR이 되고, native Skia guard는 같은 GlyphRun variant contract입니다.
  • P13은 diagnostics/shape profile/schema closeout을 합쳐 devel에 반영했습니다. variantOps/schema v2 compatibility는 실제 writer 전환 없이 후속 text variant adoption에서 다룹니다.
  • 실제 P14가 GlyphOutline/Text variant adoption으로 반영됐고 P15가 diagnostics-only policy로 반영됐으므로, browser CanvasKit 구현은 P16부터 overlay-free direct renderer로 시작합니다.
  • image geometry/cache-key direct replay는 P18에서 완료했고, effect/filter와 broader object replay는 후속 direct replay coverage로 남깁니다.
  • advanced glyph payload gate는 P19로 완료했습니다. PR #1117은 payload validator, branch parity test, deterministic reject reason을 먼저 닫고, COLRv1 solid/linear/radial/sweep CanvasKit subset만 같은 gate 아래에서 열었습니다.
  • COLRv1 solid/linear/radial/sweep color glyph replay subset은 P19에 합쳤습니다. gate vocabulary만 두면 phase가 너무 작아지고, CanvasKit direct replay consumer가 같은 PR에 있어야 validator의 의미가 분명하기 때문입니다.
  • glyph payload resource corpus와 font construction proof는 P20으로 완료했습니다. resource identity/cache key와 exact typeface construction failure는 실제 glyph replay eligibility의 전제라 sweep보다 먼저 검증했습니다.
  • replay-plane ordering, 작은 renderer baseline manifest, surface-axis diagnostics, performance report는 P21로 완료했습니다. Large corpus manifest expansion과 PDF artifact collection은 P21에서 분리했습니다.
  • WebCanvas direct PaintOp replay와 layer option metadata는 P22에 합쳤습니다. 둘 다 public path 전환이 아니라 layer boundary cleanup이고, 같은 JSON/JS/Studio type contract를 확인해야 했기 때문입니다.
  • PDF export backend와 native render/export API packaging은 P23으로 이동했습니다. P21에서 PDF artifact collection은 분리했고, P23 PR #1359는 SVG-derived PDF export API surface를 안정화하면서 report-only PDF visual diff artifacts를 render-diff CI에 추가합니다.
  • strict BitmapGlyphSvgGlyph producer-output corpus widening은 P24에 묶습니다. 둘 다 strict payload resource family hardening이고, shared resource/cache/validator contract를 함께 확인해야 합니다.
  • variation font와 TTC/OTC exact replay corpus widening은 P25에 묶습니다. 둘 다 exact font construction proof와 digest/face/axis negative controls가 핵심입니다.
  • shapedModern, cross-scope variants, public MixedPerGlyph, 추가 COLRv1 primitive는 P26 authority-gated follow-up으로 유지합니다. concrete corpus/use case 없이 별도 구현 phase로 열지 않습니다.

별도 트래킹

완료 기준

  • public Canvas 렌더링이 PageLayerTree 기반으로 안정적으로 동작함
  • Canvas legacy vs layer Canvas visual diff pipeline이 CI에서 동작함
  • native Skia backend가 feature-gated 상태로 추가되고 기본 PNG 렌더링 테스트를 통과함
  • native Skia PNG 경로에서 equation replay가 동작함
  • native Skia PNG 경로에서 raw SVG fragment replay가 동작함
  • native Skia PNG 경로에서 form control static replay가 동작함
  • PageLayerTree JSON / JS export가 backend replay에 필요한 주요 정보를 안정적으로 보존함
  • Text IR v2 compatibility contract가 source table, placement/cluster metadata, special visual ops를 함께 보존함
  • GlyphRun foundation이 들어가되 TextRun fallback이 유지되고 native Skia는 exact blob-backed typeface replay 전까지 fallback을 유지함
  • Text IR v2 diagnostics/schema closure가 fallback reason, feature negotiation, shaped profile boundary, line-break risk telemetry, fallback-free strict guard를 additive textV2 diagnostics contract로 설명함
  • GlyphOutline strict sidecar와 backend text variant selection diagnostics가 들어가되 TextRun fallback이 유지됨
  • CanvasKit replay policy diagnostics가 devel에 들어가고 hidden overlay inventory가 public API로 노출됨
  • Browser CanvasKit renderer가 default/compat 모두 hidden Canvas2D overlay 없이 direct replay backend로 동작함
  • CanvasKit direct replay hardening이 bridge fallback, schema sync, direct compat reporting, payload gates, deterministic reject reason을 갖춤
  • CanvasKit image replay가 crop/fill-mode/original-size/transform/cache-key를 직접 소비하고, effect/filter gap을 deterministic diagnostics로 남김
  • Text/glyph advanced payload gates가 schema minor, feature metadata, Canvas2D/CanvasKit branch parity, deterministic reject reason을 갖추고 COLRv1 solid/linear/radial/sweep subset 외 payload replay를 닫아 둠
  • Canvas2D/CanvasKit GlyphOutline replay branch parity가 payloadKind, colorFormat, COLRv1 graph node 기준으로 검증됨
  • COLRv1 solid/linear/radial/sweep graph가 CanvasKit direct replay subset과 deterministic reject matrix를 가짐
  • glyph payload resource corpus와 font construction proof fixture가 resource identity, cache key, exact typeface construction failure를 검증함
  • 작은 renderer baseline manifest가 SVG / Canvas2D / native Skia / CanvasKit을 같은 IR 기준으로 report-first 비교함
  • replay-plane ordering, z-order, empty-plane skip, static cache plane key가 backend 공통 helper/fixture로 검증됨
  • 주요 fixture에 대한 visual regression 테스트와 performance report가 artifact workflow에서 동작함
  • WebGPU/software surface axis와 browser/native renderer baseline report가 artifact workflow에서 동작함
  • WebCanvas layer replay가 core leaf PaintOp를 임시 legacy wrapper 없이 직접 소비함
  • layer buildOptions / debugOptions / outputOptions metadata가 JSON/JS/Studio 타입과 backend replay semantics에서 일관됨
  • CanvasKit direct replay coverage가 page background/vector, equation/formObject, TextRun effects 순서의 Canvas2D-vs-CanvasKit fixture와 diagnostics를 갖춤
  • CanvasKit GlyphRun gate와 GlyphOutline strict sidecar가 TextRun fallback, deterministic fallback reason, anchor/fallback/monochrome fill-only 조건을 공유하고 generic Path로 섞이지 않음
  • strict BitmapGlyph producer-output corpus가 one-strike resource, deterministic scaling/filtering, color-space default diagnostics, cache-key contract를 검증함
  • strict SvgGlyph producer-output corpus가 sanitized static vector resource, required viewBox, hard-false safety flags, no raw SVG replay contract를 검증함
  • native variation/TTC real corpus와 CanvasKit exact-construction proof gate가 backend divergence를 deterministic diagnostics로 설명함
  • Skia image effect/cache/resource diagnostics와 HWP3 picture / WMF / EMF / OLE native object replay가 fallback을 보존하면서 동작함
  • PDF export backend가 단일/다중 페이지, page range, page metadata, vector/raster fallback policy를 안정적으로 처리하고 native binding packaging 기준에서도 설명 가능함
  • shapedModern, cross-scope variants, public MixedPerGlyph, 추가 COLRv1 primitive가 concrete corpus/use case 없이 writer emission으로 열리지 않음

참고

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions