Skip to main content

gfx/
path.rs

1// path -- vector paths: build, flatten (tolerance 0.2 px), fill        [src/gfx/path.s]
2//
3// Integer API in 24.8 fixed point relative to the draw origin (move_fx, line_fx, quad_fx,
4// cubic_fx, rect_fx, rrect_fx, ellipse_fx), float API in absolute target pixels (move_f,
5// line_f, quad_f, add_pt; used by the text engine).  Curves are flattened at once; fill()
6// hands the closed subpaths to the rasterizer, stroke() (stroke.rs) their outlines.
7// The origin is taken from the Dc at begin() (set_origin changes it).
8
9use alloc::vec::Vec;
10
11use super::raster::Raster;
12use super::{Dc, Surface};
13use rt::fx::{maxss, sqrt};
14
15const KAPPA_FX: i32 = 141; // 0.5523 * 256 (circle approximation by cubics)
16
17#[derive(Clone, Copy)]
18pub(crate) struct Sub {
19    pub start: usize,
20    pub count: usize,
21    pub closed: bool,
22}
23
24#[derive(Default)]
25pub struct Path {
26    pub(crate) pts: Vec<(f32, f32)>,
27    pub(crate) subs: Vec<Sub>,
28    open: bool,
29    start: (f32, f32),
30    cur: (f32, f32),
31    ox: i32,
32    oy: i32,
33    tolerance: f32,
34}
35
36/// ucomiss + je: equal or unordered
37#[inline]
38pub(crate) fn zf(a: f32, b: f32) -> bool {
39    !(a < b || a > b)
40}
41
42// clamp(ceil(sqrt(m * k)), 1, 128)
43fn seg_count(m: f32, k: f32) -> i32 {
44    let v = sqrt(m * k);
45    let mut n = rt::fx::cvtt(v);
46    if !((n as f32) >= v) {
47        n += 1;
48    }
49    n.clamp(1, 128)
50}
51
52fn vlen(x: f32, y: f32) -> f32 {
53    sqrt(x * x + y * y)
54}
55
56impl Path {
57    /// Set curve flattening tolerance in target pixels and return the old value.
58    /// Invalid values preserve the setting; 0.2 is the legacy default.
59    pub fn set_tolerance(&mut self, tolerance: f32) -> f32 {
60        let old = if self.tolerance > 0.0 { self.tolerance } else { 0.2 };
61        if tolerance.is_finite() && tolerance > 0.0 {
62            self.tolerance = tolerance.clamp(0.001, 10.0);
63        }
64        old
65    }
66
67    fn curve_factor(&self) -> f32 {
68        if self.tolerance > 0.0 { 0.2 / self.tolerance } else { 1.0 }
69    }
70    /// path_begin: an empty path; ox, oy = the draw origin used by the integer API
71    pub fn begin(&mut self, dc: &Dc) {
72        self.pts.clear();
73        self.subs.clear();
74        self.open = false;
75        self.ox = dc.ox;
76        self.oy = dc.oy;
77    }
78
79    /// an empty path, origin kept (path_begin for the float API)
80    pub fn reset(&mut self) {
81        self.pts.clear();
82        self.subs.clear();
83        self.open = false;
84    }
85
86    pub fn set_origin(&mut self, ox: i32, oy: i32) {
87        self.ox = ox;
88        self.oy = oy;
89    }
90
91    pub fn is_empty(&self) -> bool {
92        self.subs.is_empty()
93    }
94
95    /// the flattened points of every subpath (target pixels), e.g. to shear them before a
96    /// fill (the asm's g_pts / g_npts)  // paint-tools: Kritzel's italic text
97    pub fn pts_mut(&mut self) -> &mut [(f32, f32)] {
98        &mut self.pts
99    }
100
101    fn to_xy(&self, x: i32, y: i32) -> (f32, f32) {
102        (x as f32 * (1.0 / 256.0) + self.ox as f32, y as f32 * (1.0 / 256.0) + self.oy as f32)
103    }
104
105    fn new_sub(&mut self, p: (f32, f32)) {
106        self.subs.push(Sub { start: self.pts.len(), count: 0, closed: false });
107        self.open = true;
108        self.start = p;
109    }
110
111    /// path_add_pt: append to the open subpath (a new one starts at the current point
112    /// if none is open); a duplicate of the previous point is skipped
113    pub fn add_pt(&mut self, p: (f32, f32)) {
114        if !self.open {
115            if !self.subs.is_empty() {
116                let c = self.cur;
117                self.new_sub(c);
118                self.add_pt(c);
119            } else {
120                self.new_sub(p);
121            }
122        }
123        let si = self.subs.len() - 1;
124        if self.subs[si].count != 0 {
125            let last = self.pts[self.pts.len() - 1];
126            if zf(p.0, last.0) && zf(p.1, last.1) {
127                return;
128            }
129        }
130        self.pts.push(p);
131        self.subs[si].count += 1;
132        self.cur = p;
133    }
134
135    pub fn move_fx(&mut self, x: i32, y: i32) {
136        let p = self.to_xy(x, y);
137        self.new_sub(p);
138        self.add_pt(p);
139    }
140
141    pub fn move_f(&mut self, x: f32, y: f32) {
142        self.new_sub((x, y));
143        self.add_pt((x, y));
144    }
145
146    pub fn line_fx(&mut self, x: i32, y: i32) {
147        let p = self.to_xy(x, y);
148        self.add_pt(p);
149    }
150
151    pub fn line_f(&mut self, x: f32, y: f32) {
152        self.add_pt((x, y));
153    }
154
155    pub fn close(&mut self) {
156        if self.open {
157            let si = self.subs.len() - 1;
158            self.subs[si].closed = true;
159            self.open = false;
160            self.cur = self.start;
161        }
162    }
163
164    /// path_quad (24.8 fixed, origin-relative)
165    pub fn quad_fx(&mut self, cx: i32, cy: i32, x: i32, y: i32) {
166        let c = self.to_xy(cx, cy);
167        let p = self.to_xy(x, y);
168        self.quad_f(c.0, c.1, p.0, p.1);
169    }
170
171    /// path_quad_f: quadratic Bezier from the current point (float pixels)
172    pub fn quad_f(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
173        if self.subs.is_empty() {
174            self.new_sub((cx, cy));
175            self.add_pt((cx, cy));
176        }
177        let (p0x, p0y) = self.cur;
178        let mx = p0x - (cx + cx) + x;
179        let my = p0y - (cy + cy) + y;
180        let n = seg_count(vlen(mx, my), 1.25 * self.curve_factor());
181        for i in 1..=n {
182            let t = i as f32 / n as f32;
183            let u = 1.0 - t;
184            let b0 = u * u;
185            let mut b1 = t * u;
186            b1 = b1 + b1;
187            let b2 = t * t;
188            let mut px = p0x * b0 + cx * b1 + x * b2;
189            let mut py = p0y * b0 + cy * b1 + y * b2;
190            if i == n {
191                px = x;
192                py = y;
193            }
194            self.add_pt((px, py));
195        }
196    }
197
198    /// path_cubic (24.8 fixed, origin-relative)
199    pub fn cubic_fx(&mut self, c1x: i32, c1y: i32, c2x: i32, c2y: i32, x: i32, y: i32) {
200        let p1 = self.to_xy(c1x, c1y);
201        let p2 = self.to_xy(c2x, c2y);
202        let p3 = self.to_xy(x, y);
203        self.cubic_f(p1, p2, p3);
204    }
205
206    /// a cubic Bezier from the current point in float pixels (the flattening of path_cubic)
207    pub fn cubic_f(&mut self, p1: (f32, f32), p2: (f32, f32), p3: (f32, f32)) {
208        if self.subs.is_empty() {
209            self.new_sub(p1);
210            self.add_pt(p1);
211        }
212        let p0 = self.cur;
213        let m1 = vlen(p0.0 - (p1.0 + p1.0) + p2.0, p0.1 - (p1.1 + p1.1) + p2.1);
214        let m2 = vlen(p1.0 - (p2.0 + p2.0) + p3.0, p1.1 - (p2.1 + p2.1) + p3.1);
215        let n = seg_count(maxss(m2, m1), 3.75 * self.curve_factor());
216        for i in 1..=n {
217            if i == n {
218                self.add_pt(p3);
219                break;
220            }
221            let t = i as f32 / n as f32;
222            let u = 1.0 - t;
223            let uu = u * u;
224            let b0 = uu * u;
225            let b1 = uu * t * 3.0;
226            let tt = t * t;
227            let b2 = tt * u * 3.0;
228            let b3 = tt * t;
229            let px = p0.0 * b0 + p1.0 * b1 + p2.0 * b2 + p3.0 * b3;
230            let py = p0.1 * b0 + p1.1 * b1 + p2.1 * b2 + p3.1 * b3;
231            self.add_pt((px, py));
232        }
233    }
234
235    pub fn rect_fx(&mut self, x: i32, y: i32, w: i32, h: i32) {
236        let x1 = x + w;
237        let y1 = y + h;
238        self.move_fx(x, y);
239        self.line_fx(x1, y);
240        self.line_fx(x1, y1);
241        self.line_fx(x, y1);
242        self.close();
243    }
244
245    /// path_rrect: closed rounded rectangle, r clamped to min(w, h) / 2
246    pub fn rrect_fx(&mut self, x: i32, y: i32, w: i32, h: i32, r: i32) {
247        let (x0, y0, x1, y1) = (x, y, x + w, y + h);
248        let r = r.min(w.min(h) >> 1);
249        if r <= 0 {
250            self.rect_fx(x, y, w, y1 - y0);
251            return;
252        }
253        let k = (r * (256 - KAPPA_FX)) >> 8;
254        self.move_fx(x0 + r, y0);
255        self.line_fx(x1 - r, y0);
256        self.cubic_fx(x1 - k, y0, x1, y0 + k, x1, y0 + r);
257        self.line_fx(x1, y1 - r);
258        self.cubic_fx(x1, y1 - k, x1 - k, y1, x1 - r, y1);
259        self.line_fx(x0 + r, y1);
260        self.cubic_fx(x0 + k, y1, x0, y1 - k, x0, y1 - r);
261        self.line_fx(x0, y0 + r);
262        self.cubic_fx(x0, y0 + k, x0 + k, y0, x0 + r, y0);
263        self.close();
264    }
265
266    /// path_ellipse: closed ellipse from four cubic arcs
267    pub fn ellipse_fx(&mut self, cx: i32, cy: i32, rx: i32, ry: i32) {
268        let kx = (rx * KAPPA_FX) >> 8;
269        let ky = (ry * KAPPA_FX) >> 8;
270        self.move_fx(cx + rx, cy);
271        self.cubic_fx(cx + rx, cy + ky, cx + kx, cy + ry, cx, cy + ry);
272        self.cubic_fx(cx - kx, cy + ry, cx - rx, cy + ky, cx - rx, cy);
273        self.cubic_fx(cx - rx, cy - ky, cx - kx, cy - ry, cx, cy - ry);
274        self.cubic_fx(cx + kx, cy - ry, cx + rx, cy - ky, cx + rx, cy);
275        self.close();
276    }
277
278    /// the line list of path_fill: every subpath of 2+ points closed implicitly
279    pub fn fill_lines(&self, ras: &mut Raster) {
280        ras.lines_begin();
281        for s in &self.subs {
282            if s.count < 2 {
283                continue;
284            }
285            for k in 0..s.count {
286                let a = self.pts[s.start + k];
287                let b = if k + 1 < s.count { self.pts[s.start + k + 1] } else { self.pts[s.start] };
288                ras.emit_line(a.0, a.1, b.0, b.1);
289            }
290        }
291    }
292
293    /// path_fill: every subpath closed implicitly; rule 0 non-zero, 1 even-odd
294    pub fn fill(&self, ras: &mut Raster, dc: &Dc, sf: &mut Surface, rule: u32) {
295        self.fill_lines(ras);
296        ras.fill(dc, sf, rule);
297    }
298}