ProtoPie renderer — renderer-pathops 개발 문서 원문 텍스트 추출본 / 2026-09-14 # renderer-pathops Skia `SkPathOps` boolean engine 의 Rust port. **Math foundation 만**. 본 crate 는 stroke V2 의 17-piece 분해 (`claudedocs/skpathops_algorithm_audit.md` § 4.5) 중 **P1a (Math types / point / bounds)** 의 산출물. 이후 piece (P1b–P4c) 가 본 crate 의 module 을 채워나간다. **시각 변경 0** (현재 어떤 frame 경로에도 연결 안 됨). ## Scope (현재 piece = P1a) | Module | Skia origin | 역할 | |--------|-------------|------| | `epsilon` | `SkPathOpsTypes.h:303–321` | `FLT_EPSILON*`, `DBL_EPSILON*`, `ROUGH_EPSILON` 등 tolerance 상수 | | `predicates` | `SkPathOpsTypes.h:323–547` | `approximately_*`, `precisely_*`, `roughly_*`, `between` | | `ulps` | `SkPathOpsTypes.cpp:18–201` | `almost_equal_ulps`, `ulps_distance`, ULPs ε = {2, 8, 16, 256} | | `point` | `SkPathOpsPoint.h:19–279` | `DPoint`, `DVector` + cross/dot/length/normalize | | `bounds` | `SkPathOpsRect.h:21–82`, `SkPathOpsBounds.h:14–63` | `DRect` (f64) + `Bounds` (f32, segment용) | | `verb` | `SkPathOpsTypes.h:553–605` | `Verb` enum + `points_to_verb`, `d_interp`, `d_sign`, `d_side`, `pin_t` | | `line` (P1b.1) | `SkPathOpsLine.h/cpp` | `DLine` + `pt_at_t` / `exact_point` / `near_point` / `near_ray` / `*H`/`*V` | | `quad` (P1b.1) | `SkPathOpsQuad.h/cpp` (자기완결 부분) | `DQuad` + `DQuadPair` + `pt_at_t` / `dxdy_at_t` / `subdivide` / `chop_at` / `roots_real` / `roots_valid_t` / `find_extrema` / `set_abc` / `monotonic_in_*` / `align` / `other_pts` / `collapsed` / `controls_inside` / `flip` | | `conic` (P1b.2) | `SkPathOpsConic.h/cpp` (자기완결 부분) | `DConic` (`pts: DQuad, weight: f32` — SkScalar parity) + Quad delegate (collapsed/controls_inside/flip/monotonic_in_*/align/other_pts) + rational `pt_at_t` / `dxdy_at_t` / `subdivide` / `subdivide_with_endpoints` / `find_extrema` | | `cubic` (P1b.3) | `SkPathOpsCubic.h/cpp` (자기완결 부분) | `DCubic` + `DCubicPair` + `SearchAxis` enum + Cardano `roots_real` (3 real / 1 real / double-root branches) + `roots_valid_t` (±0.00005 endpoint snap) + `coefficients` (power basis) + `pt_at_t` / `dxdy_at_t` / `subdivide(t1,t2)` (4-T linear system) / `subdivide_with_endpoints` / `chop_at(t)` (t=0.5 fast path) + `find_extrema` (2 roots) / `find_inflections` (2 roots) / `find_max_curvature` (3 roots) / `monotonic_in_*` / `controls_inside` / `collapsed` / `ends_are_extrema_in_x_or_y` / `align` / `other_pts` / `calc_precision` / `to_float_points` / `binary_search` / `search_roots` / `top` | | `curve` (P1b.4) | `SkPathOpsCurve.h/cpp` (자기완결 부분) + `SkPathOpsTCurve.h` | `DCurve` enum (Line/Quad/Conic/Cubic — Skia `SkDCurve` union 의 Rust 대체) + dispatch methods (`pt_at_t` / `dxdy_at_t` / `subdivide` / `is_vertical` / `get` / `point_count` / `verb`) + `DCurveSweep` (`curve` + sweep vectors + flags) + `set_curve_hull_sweep(verb)` (sector 정렬용, cubic degenerate case 의 `pts[1] = pts[3]` mutation 포함) | ## 후속 piece 가 추가할 module (audit § 4.5) | Piece | 예상 module | |-------|------------| | P1c | `reduce_order`, `cubic_hull` (포함 `convexHull` / `hullIntersects`), `cubic_to_quads` (`toQuad`), `tight_bounds` (포함 `setBounds(curve, sub, …)`), `line_parameters` (`isLinear`) | | P1d | `line_intersection`, `quad_line_intersection`, `cubic_line_intersection`, `conic_line_intersection`, `intersections` | | P1e | `t_sect` (cubic-cubic) | | P2a–P2f | `segment`, `span`, `contour`, `angle`, `coincidence`, `edge_builder` (Skia structure layer 의 idiomatic Rust 재설계) | | P3a–P3c | `winding`, `as_winding`, `bridge_op`, `path_writer` | | P4a–P4c | `command_interop`, `stroker_align_refactor`, figma sign-off | ### Deferred methods (P1b.* 에서 의도적으로 누락 → P1c/P1d/P1e 가 채움) | 메소드 | 의존 piece | |--------|-----------| | `DQuad::is_linear` | P1c (`SkLineParameters`) | | `DQuad::hull_intersects(&DQuad/&DConic/&DCubic)` | P1c (cross-curve, convexHull) | | `DQuad::subdivide(a, c, t1, t2)` | P1d (`SkIntersections`) | | `DQuad::horizontal_intersect` / `DQuad::vertical_intersect` | P1d (`SkDQuadLineIntersection`) | | `DConic::is_linear` | P1c (Quad delegate, `SkLineParameters`) | | `DConic::hull_intersects(&DCubic)` | P1c (cross-curve) | | `DCubic::is_linear` | P1c (`SkLineParameters`) | | `DCubic::hull_intersects(&DCubic/&DQuad/&DConic/&[DPoint])` | P1c (`SkOpCubicHull::convexHull`) | | `DCubic::convex_hull` | P1c (`SkOpCubicHull.cpp`) | | `DCubic::to_quad` | P1c (`SkDCubicToQuads.cpp`) | | `DCubic::complex_break` | 후속 (외부 `SkClassifyCubic` 의존) | | `DCubic::horizontal_intersect` / `DCubic::vertical_intersect` | P1d (`SkDCubicLineIntersection`) | | `DCurve::near_point` | P1d (`SkIntersections`) | | `DCurve::set_*_bounds` | P1c (`SkDRect::setBounds(curve, sub, …)`) | | `DCurve::intersect_ray` / `intercept_h` / `intercept_v` | P1d | | `SkTCurve` trait (virtual dispatch base) | P1e (TSect 가 진짜 필요할 때) | | `SkOpCurve` (단정밀도 SkPoint 4개 storage) | P2 (segment 가 사용) | 본 crate 가 P4b 단계에서 `renderer-tess` 의 stroker 와 연결될 때 stroke align 의 4 색 figma sign-off endgoal 에 도달한다 (= 17-piece plan 의 유일한 progress checkpoint). ## Hard rules - **f64 precision 유지**. Skia math layer 는 `double` 전제. `f32` narrowing 은 Skia 가 명시적으로 narrow 하는 곳에서만 (`asSkPoint`, `Bounds::add_dpoint` 등). - **Skia 와 bit-equivalence**. `FLT_EPSILON_SQRT` 처럼 pre-computed 상수는 literal 그대로 보존. ULPs 비교는 `f32::to_bits()` 기반 (safe). - **`#![forbid(unsafe_code)]`** 유지. ULPs 계산은 `f32::to_bits()` 로 unsafe 없이 가능. - **WASM 호환** — `std::thread` / blocking I/O 금지. 본 crate 는 순수 math, 의존성 0. - **명명 convention**: Skia 의 `SkD*` prefix 는 `D*` 로 (예: `SkDPoint` → `DPoint`), camelCase function 은 snake_case 로 (`AlmostEqualUlps` → `almost_equal_ulps`). - **Predicate 인용**: 모든 `pub fn` 의 `///` doc comment 첫 줄에 Skia path:line 명시. - **Test 추가 시**: ULPs / approximate-equal 비교는 절대 `assert_eq!(f64, f64)` 금지. ε 기반 tolerance check. ## Cross-crate impact - **현재**: 없음. 본 crate 는 어떤 다른 crate 가 사용 안 함. - **P4a 이후**: `renderer-tess` 가 stroke align boolean op 시 본 crate 호출. 그때까지 본 crate 변경은 다른 crate 에 영향 없음. ## Related docs - `claudedocs/skpathops_algorithm_audit.md` — 정독 audit + 17 piece 분해 + 작업량 측정. - `claudedocs/skpathops_angle_coincidence_segment_spec.md` — piece B spec (Angle / Coincidence / Segment). - `claudedocs/stroke_reference_audit_2026_05_25.md` — Skia/kurbo vs 우리 stroker diff. - `claudedocs/stroke_boolean_engine_options_audit.md` — 4 옵션 (flo_curves / SkPathOps / 자체 / shader) 비교, Hybrid B+C 선택 근거. - `.agents/claudedocs/references/skia_pathops/` — 본 port 의 1:1 source (55 파일, BSD-3-Clause, fetch 2026-05-25). - `~/.claude/projects/-Users-sookie-pj-protopie-renderer/memory/project_stroke_align_mask_continuation.md` — stroke V2 작업 SOT.