You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
기존에는 각 renderer가 PageRenderTree를 직접 해석하는 흐름에 가까웠습니다. 앞으로는 renderer backend가 공통으로 replay할 수 있는 PageLayerTree를 중간 IR로 두고, SVG / Canvas2D / native Skia / CanvasKit / PDF export backend가 같은 layer contract를 기준으로 동작하게 합니다.
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은 당분간 canonical path가 아니라 optional variant로만 둡니다. TextRun fallback이 항상 남아 있어야 합니다.
GlyphRun foundation, font resource, native Skia guard는 같은 variant contract로 보고 한 묶음으로 검토합니다. CanvasKit guard와 GlyphOutline은 둘 다 text variant adoption 문제라 같은 후속 PR로 묶습니다.
GlyphOutline은 GlyphRun의 단순 확장이 아니라 strict-visual sidecar contract로 봅니다. 다만 reviewer 입장에서는 CanvasKit GlyphRun adoption과 같은 text variant backend phase에서 같이 보는 편이 낫습니다.
variantOps와 richer outline stroke payload는 schema migration 문제입니다. 별도 소형 PR로 빼기보다 Text IR v2 closure phase 안에서 dual-reader/single-writer 정책까지 같이 정리합니다.
cache/resource/text/image-effect 작업은 wholesale merge하지 않고 correctness -> IR contract -> guarded backend adoption -> diagnostics/performance 순서로 다시 자릅니다.
Studio/CanvasKit 쪽 helper나 E2E 변화는 backend adoption 또는 renderer sweep에 필요한 조각만 가져옵니다. editor UI/compare/history 정리는 이 시리즈에 섞지 않습니다.
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 문서 영향 여부를 같이 확인합니다.
PR #840은 GitHub 상태상 merged가 아니라 closed이지만, 메인테이너 확인 코멘트와 devel history 기준으로 반영 완료입니다.
P12 이후 devel에는 #843과 #845가 추가됐습니다.
#843은 shortcut.hwp PDF 정합성 3건을 고친 layout/text measurement 변경입니다. 앞으로 PDF/export 계획은 이 커밋 위에서 shortcut fixture를 기준점으로 다시 잡습니다.
#845는 SwiftUI native read bindings / Swift Package / XCFramework packaging을 추가했습니다. 이후 native render/export API는 Rust만 보지 않고 Swift binding 영향도 같이 봅니다.
P12 최종 범위는 guarded GlyphRun layer variant contract입니다. native Skia는 exact blob-backed typeface replay가 없으므로 GlyphRun을 선택하지 않고 TextRun fallback을 유지합니다.
후속 설계 확인 결과, P13-P17은 더 큰 리뷰 단위로 다시 묶습니다. P13은 Text IR v2 diagnostics/schema/variantOps closure, P14는 CanvasKit GlyphRun + GlyphOutline text variant adoption, P15는 image effect/cache + native object replay hardening, P16은 PDF export + native binding/API packaging, P17은 full renderer sweep/performance report로 둡니다.
추가 렌더러 실험 확인 결과, P13/P16, P14/P15, P17/P18, P19/P21은 각각 같은 reviewer context를 공유합니다. 작은 foundation-only PR을 줄이기 위해 이 묶음들을 합칩니다.
v2 closure compatibility policy도 함께 반영합니다. P13에는 line-break context completeness, v1/v2 downgrade fail-closed 규칙, variantOps dual-reader/single-writer 정책을 같이 둡니다.
P11은 이 시리즈에서 Text IR v2 개념이 처음 들어오는 지점입니다. 다만 이 단계에서 Text IR v2를 완성하거나 기존 text replay contract를 대체하지는 않습니다. P11은 기존 TextRun fallback을 유지한 채, 후속 GlyphRun/font resource/native glyph replay가 붙을 수 있는 compatibility contract를 먼저 여는 단계입니다.
Text IR v2의 핵심 목표는 아래와 같습니다.
원문 source 추적성을 보존합니다. PageLayerTree.text_sources와 TextRun.source span을 통해 backend/debug consumer가 text op가 어떤 원문 범위에서 왔는지 알 수 있어야 합니다.
기존 TextRunNode payload에 섞여 있던 layout/replay 의미를 더 명확히 나눕니다. paintStyle, projectionKind, orientation, placement, clusterBasis, clusters, legacyVisuals 같은 metadata를 통해 backend가 text placement와 compatibility fallback을 구분할 수 있게 합니다.
schema negotiation을 명시합니다. schemaMinorVersion, resourceTableMinorVersion, usedFeatures, requiredFeatures, optionalFeatures, knownFeatures로 consumer가 자신이 처리 가능한 feature와 fallback 필요 여부를 판단할 수 있어야 합니다.
special text visuals를 외부 paint op로 분리합니다. char overlap, visible control mark, tab leader, underline/strike/emphasis 같은 visual은 TextRun 내부 payload에만 묻히지 않고 PaintOp::CharOverlap, TextControlMark, TabLeader, TextDecoration으로 별도 표현합니다.
향후 exact glyph replay를 위한 자리를 만듭니다. GlyphRun은 아직 기본 경로가 아니며, font identity와 cluster mapping이 검증된 경우에만 선택 가능한 optional variant로 계획합니다.
현재 진행 상태는 아래처럼 봅니다.
P11: Text IR v2 compatibility contract 초안입니다. source table, source span, placement/cluster metadata, special visual op externalization, feature negotiation metadata를 추가합니다.
P11: 기존 SVG / Canvas / native Skia renderer는 새 special visual op를 skip합니다. 이는 새 op가 기존 TextRun fallback과 중복으로 그려지는 것을 막기 위한 guard입니다.
P11: JSON/schema field 이름과 feature taxonomy는 앞으로 consumer 구현을 붙이면서 조정될 수 있습니다. 그래서 major schema를 고정하는 완성 단계가 아니라 minor/additive contract를 여는 단계로 둡니다.
layer resource hash, skipped payload, shared font/image/SVG resource transport, cache invalidation diagnostics를 한 곳에서 봅니다.
performance metrics는 default CI gate가 아니라 report artifact로 먼저 둡니다.
Studio e2e/lifecycle/perf helper는 renderer sweep에 필요한 부분만 가져오고, editor UI/compare/history 정리는 이 renderer 분할 작업에 섞지 않습니다.
합친 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입니다.
제안 내용
멀티 렌더러를 안정적으로 지원할 수 있도록 렌더링 구조를 단계적으로 분리합니다.
기존에는 각 renderer가
PageRenderTree를 직접 해석하는 흐름에 가까웠습니다. 앞으로는 renderer backend가 공통으로 replay할 수 있는PageLayerTree를 중간 IR로 두고, SVG / Canvas2D / native Skia / CanvasKit / PDF export backend가 같은 layer contract를 기준으로 동작하게 합니다.대략적인 흐름은 아래와 같습니다.
이 이슈는 멀티 렌더러 작업을 리뷰 가능한 PR 단위로 나누어 추적하기 위한 tracking issue입니다. 기준은 단순히 변경 파일 수를 줄이는 것이 아니라, 각 PR이 독립적으로 설명 가능한 contract를 갖도록 만드는 것입니다.
분할 기준
TextRuncompatibility contract 단위로 묶습니다.TextRunfallback이 항상 남아 있어야 합니다.GlyphOutline은GlyphRun의 단순 확장이 아니라 strict-visual sidecar contract로 봅니다. 다만 reviewer 입장에서는 CanvasKit GlyphRun adoption과 같은 text variant backend phase에서 같이 보는 편이 낫습니다.variantOps와 richer outline stroke payload는 schema migration 문제입니다. 별도 소형 PR로 빼기보다 Text IR v2 closure phase 안에서 dual-reader/single-writer 정책까지 같이 정리합니다.export-png/ VLM 사용 흐름은 존중하되, VLM preset 확장이나 PNG metadata 같은 제품 요구사항은 별도 PR로 둡니다.최신 확인 결과 (2026-05-14)
edwardkim/devel은03a3f0d0(Merge local/devel: PR #845 cherry-pick) 기준입니다.249e5653, squashed feature commit은08af19b6입니다.shortcut.hwpPDF 정합성 3건을 고친 layout/text measurement 변경입니다. 앞으로 PDF/export 계획은 이 커밋 위에서 shortcut fixture를 기준점으로 다시 잡습니다.GlyphRunlayer variant contract입니다. native Skia는 exact blob-backed typeface replay가 없으므로GlyphRun을 선택하지 않고TextRunfallback을 유지합니다.variantOpsdual-reader/single-writer 정책을 같이 둡니다.Text IR v2 설계 상태
P11은 이 시리즈에서 Text IR v2 개념이 처음 들어오는 지점입니다. 다만 이 단계에서 Text IR v2를 완성하거나 기존 text replay contract를 대체하지는 않습니다. P11은 기존
TextRunfallback을 유지한 채, 후속 GlyphRun/font resource/native glyph replay가 붙을 수 있는 compatibility contract를 먼저 여는 단계입니다.Text IR v2의 핵심 목표는 아래와 같습니다.
PageLayerTree.text_sources와TextRun.sourcespan을 통해 backend/debug consumer가 text op가 어떤 원문 범위에서 왔는지 알 수 있어야 합니다.TextRunNodepayload에 섞여 있던 layout/replay 의미를 더 명확히 나눕니다.paintStyle,projectionKind,orientation,placement,clusterBasis,clusters,legacyVisuals같은 metadata를 통해 backend가 text placement와 compatibility fallback을 구분할 수 있게 합니다.schemaMinorVersion,resourceTableMinorVersion,usedFeatures,requiredFeatures,optionalFeatures,knownFeatures로 consumer가 자신이 처리 가능한 feature와 fallback 필요 여부를 판단할 수 있어야 합니다.TextRun내부 payload에만 묻히지 않고PaintOp::CharOverlap,TextControlMark,TabLeader,TextDecoration으로 별도 표현합니다.GlyphRun은 아직 기본 경로가 아니며, font identity와 cluster mapping이 검증된 경우에만 선택 가능한 optional variant로 계획합니다.현재 진행 상태는 아래처럼 봅니다.
TextRunfallback과 중복으로 그려지는 것을 막기 위한 guard입니다.TextRunfallback을 유지한 채GlyphRun을 optional sidecar variant로 추가했고, native Skia는 exact blob-backed typeface replay가 들어오기 전까지GlyphRun을 선택하지 않습니다.variantOpsdual-reader/single-writer migration을 같은 안정화 묶음으로 봅니다.GlyphRunguarded replay와GlyphOutline/outline stroke strict sidecar를 같이 보되,TextRunfallback과 deterministic fallback reason을 유지합니다.P11의 비목표도 명확히 둡니다.
GlyphRun을 canonical text path로 전환하지 않습니다.현재 진행상황
P1: 렌더러 프론트엔드/백엔드 1차 분리
PageLayerTree,LayerNode,PaintOp,LayerBuilder추가P2: Canvas 렌더링을
PageLayerTree기반으로 전환renderPageToCanvas를 layer replay 경로로 전환P3: Canvas visual diff 테스트 추가
P4: native Skia PNG raster backend 추가
native-skiafeature 추가PageLayerTree기반 Skia replay 구현P5: native Skia equation replay 추가
EquationNode.layout_box기반 replayP6: native Skia raw SVG fragment replay
RawSvgNode.svgfragment를 안전한 wrapper SVG로 감싼 뒤resvg/tiny-skia로 rasterizeP7: native Skia form control static replay
P8: Layer IR schema/resource key hardening
paint/schema.rs추가P9: native Skia text replay parity + module split
TextRunNode의 char overlap, tab leader, control mark, decoration, vertical rotation 정보를 소비하도록 보강renderer/skia/text_replay.rs로 분리P10: P9에 fold
P11: Text IR v2 compatibility contract
PageLayerTree최상위에 schema/resource table minor version과 feature negotiation metadata를 추가합니다.usedFeatures,requiredFeatures,optionalFeatures,knownFeatures로 consumer가 지원 범위를 판단할 수 있게 합니다.PageLayerTree.text_sources와 export-localTextSourceTable을 추가합니다.TextRun.sourcespan을 export해서 backend/debug consumer가 원문 source range를 추적할 수 있게 합니다.TextRun.paintStyle,projectionKind,orientation,placement,clusterBasis,clusters,legacyVisuals를 export합니다.PaintOp::CharOverlap,PaintOp::TextControlMark,PaintOp::TabLeader,PaintOp::TextDecoration을 추가합니다.docs/text-ir-v2.md에 Text IR v2 migration contract를 문서화했습니다.P12: Guarded GlyphRun variant contract
249e5653; squashed feature commit:08af19b6TextRunfallback을 유지한 상태에서GlyphRun을 optional sidecar variant로 도입했습니다.fontResources, font blob, face identity, replay eligibility 타입을 추가했습니다.TextShapeLowerer를 추가하되, 기본 lowering은TextRunfallback을 유지합니다.GlyphRunsidecar를 붙입니다.GlyphRun을 선택하지 않습니다.GlyphRunsidecar를 건너뛰고 fallback replay를 유지합니다.다음 분할 후보
P13: Text IR v2 diagnostics, schema closure, and variantOps
GlyphRun/fallback 선택 결과를 diagnostics와 schema profile로 안정화합니다.TextShapeLowerer노출 범위와 shaped measurement profile boundary를 문서화합니다.known,knownAbsent,unknown을 구분합니다. 필요한 context가 unknown이면insufficientContext로 fail closed합니다.TextShapeReport는 local/nightly migration telemetry로 보고, layer replay schema의 일부로 보지 않습니다.TextRunfallback과 same-scope v1 payload가 있을 때만 허용합니다.variantOps중 하나만 emit하는 single-writer 규칙을 둡니다.P14: Text variant backend adoption
GlyphRunguarded replay와GlyphOutlinestrict sidecar는 둘 다 text variant를 실제 backend가 어디까지 선택할 수 있는지 정하는 단계입니다.TextRun효과와 parity가 확인된 작은 subset만 gated replay로 엽니다.GlyphOutline은 genericPath가 아니라 strict-visual text variant로 추가합니다.anchorOpId, 같은 leaf의TextRunfallback,variantmetadata를 필수 조건으로 둡니다.Path와 섞지 않고 text variant diagnostics/fallback reason을 공유합니다.P15: Image, cache, and native object replay hardening
P16: PDF export backend and native API packaging
PageLayerTree에서 직접 PDF/vector recording으로 가는 경로를 검토합니다.P17: Full renderer sweep, resource transport, and performance gates
합친 phase
variantOps/schema v2 compatibility까지 합칩니다. doc-only, test-only, fallback reason-only, reader-only PR로 쪼개지 않습니다.GlyphRunguarded replay와GlyphOutlinestrict sidecar는 둘 다 text variant backend adoption 단계입니다.별도 트래킹
export-pngVLM preset 확장완료 기준
PageLayerTree기반으로 안정적으로 동작함PageLayerTreeJSON / JS export가 backend replay에 필요한 주요 정보를 안정적으로 보존함TextRunfallback이 유지되고 native Skia는 exact blob-backed typeface replay 전까지 fallback을 유지함variantOpsdual-reader/single-writer migration을 한 schema contract로 설명함GlyphRungate와GlyphOutlinestrict sidecar가TextRunfallback, deterministic fallback reason, anchor/fallback/monochrome fill-only 조건을 공유하고 genericPath로 섞이지 않음참고