vg/
path.rs

1use std::f32::consts::PI;
2use std::slice;
3
4use crate::geometry::{
5    self,
6    Transform2D,
7};
8
9mod cache;
10pub use cache::{
11    Convexity,
12    PathCache,
13};
14
15// Length proportional to radius of a cubic bezier handle for 90deg arcs.
16const KAPPA90: f32 = 0.552_284_8; // 0.552_284_7493 for f64
17
18/// Used to specify Solid/Hole when adding shapes to a path.
19#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd)]
20pub enum Solidity {
21    /// Solid shape
22    Solid = 1,
23    /// Hole shape
24    Hole = 2,
25}
26
27impl Default for Solidity {
28    fn default() -> Self {
29        Self::Solid
30    }
31}
32
33#[derive(Copy, Clone, Debug, Eq, PartialEq)]
34#[repr(u8)]
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
36pub enum PackedVerb {
37    MoveTo,
38    LineTo,
39    BezierTo,
40    Solid,
41    Hole,
42    Close,
43}
44
45#[derive(Copy, Clone, Debug)]
46pub enum Verb {
47    MoveTo(f32, f32),
48    LineTo(f32, f32),
49    BezierTo(f32, f32, f32, f32, f32, f32),
50    Solid,
51    Hole,
52    Close,
53}
54
55impl Verb {
56    fn num_coordinates(&self) -> usize {
57        match *self {
58            Self::MoveTo(..) => 2,
59            Self::LineTo(..) => 2,
60            Self::BezierTo(..) => 6,
61            Self::Solid => 0,
62            Self::Hole => 0,
63            Self::Close => 0,
64        }
65    }
66
67    fn from_packed(packed: &PackedVerb, coords: &[f32]) -> Self {
68        match *packed {
69            PackedVerb::MoveTo => Self::MoveTo(coords[0], coords[1]),
70            PackedVerb::LineTo => Self::LineTo(coords[0], coords[1]),
71            PackedVerb::BezierTo => Self::BezierTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]),
72            PackedVerb::Solid => Self::Solid,
73            PackedVerb::Hole => Self::Hole,
74            PackedVerb::Close => Self::Close,
75        }
76    }
77}
78
79/// A collection of verbs describing one or more contours.
80#[derive(Default, Clone, Debug)]
81#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
82pub struct Path {
83    verbs: Vec<PackedVerb>,
84    coords: Vec<f32>,
85    lastx: f32,
86    lasty: f32,
87    dist_tol: f32,
88    #[cfg_attr(feature = "serde", serde(skip))]
89    pub(crate) cache: Option<(u64, PathCache)>,
90}
91
92impl Path {
93    /// Create an empty path
94    pub fn new() -> Self {
95        Self {
96            dist_tol: 0.01,
97            ..Default::default()
98        }
99    }
100
101    /// Memory usage in bytes
102    pub fn size(&self) -> usize {
103        std::mem::size_of::<PackedVerb>() * self.verbs.len() + std::mem::size_of::<f32>() * self.coords.len()
104    }
105
106    /// Check the verbs is empty
107    pub fn is_empty(&self) -> bool {
108        self.verbs.is_empty()
109    }
110
111    /// Set the distanse tolerance
112    pub fn set_distance_tolerance(&mut self, value: f32) {
113        self.dist_tol = value;
114    }
115
116    /// Retrieve PathIter with current verbs and coords
117    pub fn verbs(&self) -> PathIter<'_> {
118        PathIter {
119            verbs: self.verbs.iter(),
120            coords: &self.coords,
121        }
122    }
123
124    pub(crate) fn cache<'a>(&'a mut self, transform: &Transform2D, tess_tol: f32, dist_tol: f32) -> &'a mut PathCache {
125        // The path cache saves a flattened and transformed version of the path. If client code calls
126        // (fill|stroke)_path repeatedly with the same Path under the same transform circumstances then it will be
127        // retrieved from cache. I'm not sure if transform.cache_key() is actually good enough for this
128        // and if it will produce the correct cache keys under different float edge cases.
129
130        let key = transform.cache_key();
131
132        // this shouldn't need a bool once non lexic lifetimes are stable
133        let mut needs_rebuild = true;
134
135        if let Some((transform_cache_key, _cache)) = self.cache.as_ref() {
136            needs_rebuild = key != *transform_cache_key;
137        }
138
139        if needs_rebuild {
140            let path_cache = PathCache::new(self.verbs(), transform, tess_tol, dist_tol);
141            self.cache = Some((key, path_cache));
142        }
143
144        &mut self.cache.as_mut().unwrap().1
145    }
146
147    // Path funcs
148
149    /// Starts new sub-path with specified point as first point.
150    pub fn move_to(&mut self, x: f32, y: f32) {
151        self.append(&[PackedVerb::MoveTo], &[x, y]);
152    }
153
154    /// Adds line segment from the last point in the path to the specified point.
155    pub fn line_to(&mut self, x: f32, y: f32) {
156        self.append(&[PackedVerb::LineTo], &[x, y]);
157    }
158
159    /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
160    pub fn bezier_to(&mut self, c1x: f32, c1y: f32, c2x: f32, c2y: f32, x: f32, y: f32) {
161        self.append(&[PackedVerb::BezierTo], &[c1x, c1y, c2x, c2y, x, y]);
162    }
163
164    /// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
165    pub fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
166        let x0 = self.lastx;
167        let y0 = self.lasty;
168
169        self.append(
170            &[PackedVerb::BezierTo],
171            &[
172                x0 + 2.0 / 3.0 * (cx - x0),
173                y0 + 2.0 / 3.0 * (cy - y0),
174                x + 2.0 / 3.0 * (cx - x),
175                y + 2.0 / 3.0 * (cy - y),
176                x,
177                y,
178            ],
179        );
180    }
181
182    /// Closes current sub-path with a line segment.
183    pub fn close(&mut self) {
184        self.append(&[PackedVerb::Close], &[]);
185    }
186
187    /// Sets the current sub-path winding, see Solidity
188    pub fn solidity(&mut self, solidity: Solidity) {
189        match solidity {
190            Solidity::Solid => self.append(&[PackedVerb::Solid], &[]),
191            Solidity::Hole => self.append(&[PackedVerb::Hole], &[]),
192        }
193    }
194
195    /// Creates new circle arc shaped sub-path. The arc center is at cx,cy, the arc radius is r,
196    /// and the arc is drawn from angle a0 to a1, and swept in direction dir (Winding)
197    /// Angles are specified in radians.
198    pub fn arc(&mut self, cx: f32, cy: f32, r: f32, a0: f32, a1: f32, dir: Solidity) {
199        let mut da = a1 - a0;
200
201        if dir == Solidity::Hole {
202            if da.abs() >= PI * 2.0 {
203                da = PI * 2.0;
204            } else {
205                while da < 0.0 {
206                    da += PI * 2.0
207                }
208            }
209        } else if da.abs() >= PI * 2.0 {
210            da = -PI * 2.0;
211        } else {
212            while da > 0.0 {
213                da -= PI * 2.0
214            }
215        }
216
217        // Split arc into max 90 degree segments.
218        let ndivs = ((da.abs() / (PI * 0.5) + 0.5) as i32).min(5).max(1);
219        let hda = (da / ndivs as f32) / 2.0;
220        let mut kappa = (4.0 / 3.0 * (1.0 - hda.cos()) / hda.sin()).abs();
221
222        let mut commands = Vec::with_capacity(ndivs as usize);
223        let mut coords = Vec::with_capacity(ndivs as usize);
224
225        if dir == Solidity::Solid {
226            kappa = -kappa;
227        }
228
229        let (mut px, mut py, mut ptanx, mut ptany) = (0f32, 0f32, 0f32, 0f32);
230
231        for i in 0..=ndivs {
232            let a = a0 + da * (i as f32 / ndivs as f32);
233            let dx = a.cos();
234            let dy = a.sin();
235            let x = cx + dx * r;
236            let y = cy + dy * r;
237            let tanx = -dy * r * kappa;
238            let tany = dx * r * kappa;
239
240            if i == 0 {
241                let first_move = if !self.verbs.is_empty() {
242                    PackedVerb::LineTo
243                } else {
244                    PackedVerb::MoveTo
245                };
246
247                commands.push(first_move);
248                coords.extend_from_slice(&[x, y]);
249            } else {
250                commands.push(PackedVerb::BezierTo);
251                coords.extend_from_slice(&[px + ptanx, py + ptany, x - tanx, y - tany, x, y]);
252            }
253
254            px = x;
255            py = y;
256            ptanx = tanx;
257            ptany = tany;
258        }
259
260        self.append(&commands, &coords);
261    }
262
263    /// Adds an arc segment at the corner defined by the last path point, and two specified points.
264    pub fn arc_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, radius: f32) {
265        if self.verbs.is_empty() {
266            return;
267        }
268
269        let x0 = self.lastx;
270        let y0 = self.lasty;
271
272        // Handle degenerate cases.
273        if geometry::pt_equals(x0, y0, x1, y1, self.dist_tol)
274            || geometry::pt_equals(x1, y1, x2, y2, self.dist_tol)
275            || geometry::dist_pt_segment(x1, y1, x0, y0, x2, y2) < self.dist_tol * self.dist_tol
276            || radius < self.dist_tol
277        {
278            self.line_to(x1, y1);
279        }
280
281        let mut dx0 = x0 - x1;
282        let mut dy0 = y0 - y1;
283        let mut dx1 = x2 - x1;
284        let mut dy1 = y2 - y1;
285
286        geometry::normalize(&mut dx0, &mut dy0);
287        geometry::normalize(&mut dx1, &mut dy1);
288
289        let a = (dx0 * dx1 + dy0 * dy1).acos();
290        let d = radius / (a / 2.0).tan();
291
292        if d > 10000.0 {
293            return self.line_to(x1, y1);
294        }
295
296        let (cx, cy, a0, a1, dir);
297
298        if geometry::cross(dx0, dy0, dx1, dy1) > 0.0 {
299            cx = x1 + dx0 * d + dy0 * radius;
300            cy = y1 + dy0 * d + -dx0 * radius;
301            a0 = dx0.atan2(-dy0);
302            a1 = -dx1.atan2(dy1);
303            dir = Solidity::Hole;
304        } else {
305            cx = x1 + dx0 * d + -dy0 * radius;
306            cy = y1 + dy0 * d + dx0 * radius;
307            a0 = -dx0.atan2(dy0);
308            a1 = dx1.atan2(-dy1);
309            dir = Solidity::Solid;
310        }
311
312        self.arc(cx, cy, radius, a0, a1, dir);
313    }
314
315    /// Creates new rectangle shaped sub-path.
316    pub fn rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
317        self.append(
318            &[
319                PackedVerb::MoveTo,
320                PackedVerb::LineTo,
321                PackedVerb::LineTo,
322                PackedVerb::LineTo,
323                PackedVerb::Close,
324            ],
325            &[x, y, x, y + h, x + w, y + h, x + w, y],
326        );
327    }
328
329    /// Creates new rounded rectangle shaped sub-path.
330    pub fn rounded_rect(&mut self, x: f32, y: f32, w: f32, h: f32, r: f32) {
331        self.rounded_rect_varying(x, y, w, h, r, r, r, r);
332    }
333
334    /// Creates new rounded rectangle shaped sub-path with varying radii for each corner.
335    pub fn rounded_rect_varying(
336        &mut self,
337        x: f32,
338        y: f32,
339        w: f32,
340        h: f32,
341        rad_top_left: f32,
342        rad_top_right: f32,
343        rad_bottom_right: f32,
344        rad_bottom_left: f32,
345    ) {
346        if rad_top_left < 0.1 && rad_top_right < 0.1 && rad_bottom_right < 0.1 && rad_bottom_left < 0.1 {
347            self.rect(x, y, w, h);
348        } else {
349            let halfw = w.abs() * 0.5;
350            let halfh = h.abs() * 0.5;
351
352            let rx_bl = rad_bottom_left.min(halfw) * w.signum();
353            let ry_bl = rad_bottom_left.min(halfh) * h.signum();
354
355            let rx_br = rad_bottom_right.min(halfw) * w.signum();
356            let ry_br = rad_bottom_right.min(halfh) * h.signum();
357
358            let rx_tr = rad_top_right.min(halfw) * w.signum();
359            let ry_tr = rad_top_right.min(halfh) * h.signum();
360
361            let rx_tl = rad_top_left.min(halfw) * w.signum();
362            let ry_tl = rad_top_left.min(halfh) * h.signum();
363
364            self.append(
365                &[
366                    PackedVerb::MoveTo,
367                    PackedVerb::LineTo,
368                    PackedVerb::BezierTo,
369                    PackedVerb::LineTo,
370                    PackedVerb::BezierTo,
371                    PackedVerb::LineTo,
372                    PackedVerb::BezierTo,
373                    PackedVerb::LineTo,
374                    PackedVerb::BezierTo,
375                    PackedVerb::Close,
376                ],
377                &[
378                    x,
379                    y + ry_tl,
380                    x,
381                    y + h - ry_bl,
382                    //
383                    x,
384                    y + h - ry_bl * (1.0 - KAPPA90),
385                    x + rx_bl * (1.0 - KAPPA90),
386                    y + h,
387                    x + rx_bl,
388                    y + h,
389                    //
390                    x + w - rx_br,
391                    y + h,
392                    //
393                    x + w - rx_br * (1.0 - KAPPA90),
394                    y + h,
395                    x + w,
396                    y + h - ry_br * (1.0 - KAPPA90),
397                    x + w,
398                    y + h - ry_br,
399                    //
400                    x + w,
401                    y + ry_tr,
402                    //
403                    x + w,
404                    y + ry_tr * (1.0 - KAPPA90),
405                    x + w - rx_tr * (1.0 - KAPPA90),
406                    y,
407                    x + w - rx_tr,
408                    y,
409                    //
410                    x + rx_tl,
411                    y,
412                    //
413                    x + rx_tl * (1.0 - KAPPA90),
414                    y,
415                    x,
416                    y + ry_tl * (1.0 - KAPPA90),
417                    x,
418                    y + ry_tl,
419                ],
420            );
421        }
422    }
423
424    /// Creates new ellipse shaped sub-path.
425    pub fn ellipse(&mut self, cx: f32, cy: f32, rx: f32, ry: f32) {
426        self.append(
427            &[
428                PackedVerb::MoveTo,
429                PackedVerb::BezierTo,
430                PackedVerb::BezierTo,
431                PackedVerb::BezierTo,
432                PackedVerb::BezierTo,
433                PackedVerb::Close,
434            ],
435            &[
436                cx - rx,
437                cy,
438                cx - rx,
439                cy + ry * KAPPA90,
440                cx - rx * KAPPA90,
441                cy + ry,
442                cx,
443                cy + ry,
444                cx + rx * KAPPA90,
445                cy + ry,
446                cx + rx,
447                cy + ry * KAPPA90,
448                cx + rx,
449                cy,
450                cx + rx,
451                cy - ry * KAPPA90,
452                cx + rx * KAPPA90,
453                cy - ry,
454                cx,
455                cy - ry,
456                cx - rx * KAPPA90,
457                cy - ry,
458                cx - rx,
459                cy - ry * KAPPA90,
460                cx - rx,
461                cy,
462            ],
463        );
464    }
465
466    /// Creates new circle shaped sub-path.
467    pub fn circle(&mut self, cx: f32, cy: f32, r: f32) {
468        self.ellipse(cx, cy, r, r);
469    }
470
471    /// Appends a slice of verbs to the path
472    fn append(&mut self, verbs: &[PackedVerb], coords: &[f32]) {
473        if coords.len() > 1 {
474            self.lastx = coords[coords.len() - 2];
475            self.lasty = coords[coords.len() - 1];
476        }
477
478        self.verbs.extend_from_slice(verbs);
479        self.coords.extend_from_slice(coords);
480    }
481}
482
483pub struct PathIter<'a> {
484    verbs: slice::Iter<'a, PackedVerb>,
485    coords: &'a [f32],
486}
487
488impl<'a> Iterator for PathIter<'a> {
489    type Item = Verb;
490
491    fn next(&mut self) -> Option<Self::Item> {
492        if let Some(verb) = self.verbs.next() {
493            let verb = Verb::from_packed(verb, self.coords);
494            let num_coords = verb.num_coordinates();
495            self.coords = &self.coords[num_coords..];
496            Some(verb)
497        } else {
498            None
499        }
500    }
501}
502
503impl ttf_parser::OutlineBuilder for Path {
504    fn move_to(&mut self, x: f32, y: f32) {
505        self.move_to(x, y);
506    }
507
508    fn line_to(&mut self, x: f32, y: f32) {
509        self.line_to(x, y);
510    }
511
512    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
513        self.quad_to(x1, y1, x, y);
514    }
515
516    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
517        self.bezier_to(x1, y1, x2, y2, x, y);
518    }
519
520    fn close(&mut self) {
521        self.close();
522    }
523}