Skip to main content

vello_common/
flatten.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Flattening filled and stroked paths.
5
6use crate::flatten_simd::{Callback, LinePathEl};
7use crate::kurbo::{self, Affine, PathEl, Stroke, StrokeCtx, StrokeOpts};
8use alloc::vec::Vec;
9use fearless_simd::{Level, Simd, dispatch};
10use log::warn;
11
12pub use crate::flatten_simd::FlattenCtx;
13
14/// The flattening tolerance.
15const TOL: f64 = 0.25;
16pub(crate) const TOL_2: f64 = TOL * TOL;
17
18/// A point.
19#[derive(Clone, Copy, Debug, PartialEq)]
20pub struct Point {
21    /// The x coordinate of the point.
22    pub x: f32,
23    /// The y coordinate of the point.
24    pub y: f32,
25}
26
27impl Point {
28    /// The point `(0, 0)`.
29    pub const ZERO: Self = Self::new(0., 0.);
30
31    /// Create a new point.
32    pub const fn new(x: f32, y: f32) -> Self {
33        Self { x, y }
34    }
35}
36
37impl core::ops::Add for Point {
38    type Output = Self;
39
40    fn add(self, rhs: Self) -> Self {
41        Self::new(self.x + rhs.x, self.y + rhs.y)
42    }
43}
44
45impl core::ops::Sub for Point {
46    type Output = Self;
47
48    fn sub(self, rhs: Self) -> Self {
49        Self::new(self.x - rhs.x, self.y - rhs.y)
50    }
51}
52
53impl core::ops::Mul<f32> for Point {
54    type Output = Self;
55
56    fn mul(self, rhs: f32) -> Self {
57        Self::new(self.x * rhs, self.y * rhs)
58    }
59}
60
61/// A line.
62#[derive(Clone, Copy, Debug)]
63pub struct Line {
64    /// The start point of the line.
65    pub p0: Point,
66    /// The end point of the line.
67    pub p1: Point,
68}
69
70impl Line {
71    /// Create a new line.
72    pub fn new(p0: Point, p1: Point) -> Self {
73        Self { p0, p1 }
74    }
75}
76
77/// Flatten a filled bezier path into line segments.
78pub fn fill(
79    level: Level,
80    path: impl IntoIterator<Item = PathEl>,
81    affine: Affine,
82    line_buf: &mut Vec<Line>,
83    ctx: &mut FlattenCtx,
84) {
85    dispatch!(level, simd => fill_impl(simd, path, affine, line_buf, ctx));
86}
87
88/// Flatten a filled bezier path into line segments.
89#[inline(always)]
90pub fn fill_impl<S: Simd>(
91    simd: S,
92    path: impl IntoIterator<Item = PathEl>,
93    affine: Affine,
94    line_buf: &mut Vec<Line>,
95    flatten_ctx: &mut FlattenCtx,
96) {
97    line_buf.clear();
98    let iter = path.into_iter().map(
99        #[inline(always)]
100        |el| affine * el,
101    );
102
103    let mut lb = FlattenerCallback {
104        line_buf,
105        start: Point::ZERO,
106        p0: Point::ZERO,
107        is_nan: false,
108    };
109
110    crate::flatten_simd::flatten(simd, iter, TOL, &mut lb, flatten_ctx);
111
112    // A path that contains NaN is ill-defined, so ignore it.
113    if lb.is_nan {
114        warn!("A path contains NaN, ignoring it.");
115
116        line_buf.clear();
117    }
118}
119/// Flatten a stroked bezier path into line segments.
120pub fn stroke(
121    level: Level,
122    path: impl IntoIterator<Item = PathEl>,
123    style: &Stroke,
124    affine: Affine,
125    line_buf: &mut Vec<Line>,
126    flatten_ctx: &mut FlattenCtx,
127    stroke_ctx: &mut StrokeCtx,
128) {
129    // TODO: Temporary hack to ensure that strokes are scaled properly by the transform.
130    let tolerance = TOL
131        / affine.as_coeffs()[0]
132            .abs()
133            .max(affine.as_coeffs()[3].abs())
134            .max(1.);
135
136    expand_stroke(path, style, tolerance, stroke_ctx);
137    fill(level, stroke_ctx.output(), affine, line_buf, flatten_ctx);
138}
139
140/// Expand a stroked path to a filled path.
141pub fn expand_stroke(
142    path: impl IntoIterator<Item = PathEl>,
143    style: &Stroke,
144    tolerance: f64,
145    stroke_ctx: &mut StrokeCtx,
146) {
147    kurbo::stroke_with(path, style, &StrokeOpts::default(), tolerance, stroke_ctx);
148}
149
150struct FlattenerCallback<'a> {
151    line_buf: &'a mut Vec<Line>,
152    start: Point,
153    p0: Point,
154    is_nan: bool,
155}
156
157impl Callback for FlattenerCallback<'_> {
158    #[inline(always)]
159    fn callback(&mut self, el: LinePathEl) {
160        match el {
161            LinePathEl::MoveTo(p) => {
162                self.is_nan |= p.is_nan();
163
164                self.start = Point::new(p.x as f32, p.y as f32);
165                self.p0 = self.start;
166            }
167            LinePathEl::LineTo(p) => {
168                self.is_nan |= p.is_nan();
169
170                let p = Point::new(p.x as f32, p.y as f32);
171                self.line_buf.push(Line::new(self.p0, p));
172                self.p0 = p;
173            }
174        }
175    }
176}