nanachi/
path_builder.rs

1//! PathBuilder
2
3use crate::{
4    models::{Arc, Cubic, Ellipse, Line, Quad},
5    path::{Path, PathItem},
6    point::Point,
7};
8use std::f64::consts::PI;
9
10/// Path builder.
11#[derive(Debug, Clone)]
12pub struct PathBuilder {
13    items: Vec<PathItem>,
14    last_pos: Option<Point>,
15    path_start: Option<Point>,
16}
17
18impl PathBuilder {
19    /// Create [`PathBuilder`].
20    pub fn new() -> PathBuilder {
21        PathBuilder {
22            items: vec![],
23            last_pos: None,
24            path_start: None,
25        }
26    }
27
28    fn push(&mut self, pi: PathItem) {
29        if !pi.is_zero() {
30            self.items.push(pi);
31        }
32    }
33
34    fn set_pos(&mut self, p: Point) {
35        if self.path_start == None {
36            self.path_start = Some(p);
37        }
38        self.last_pos = Some(p);
39    }
40
41    /// Set current position.
42    pub fn move_to(&mut self, x: f64, y: f64) {
43        match self.items.last() {
44            Some(PathItem::CloseAndJump) | Some(PathItem::Jump) | None => {}
45            Some(_) => {
46                self.push(PathItem::Jump);
47                self.path_start = None;
48            }
49        }
50        self.set_pos(Point(x, y));
51    }
52
53    /// Add a segment that from current position to specified position. And then set the end position to current position.
54    pub fn line_to(&mut self, x: f64, y: f64) {
55        let p = Point(x, y);
56        if let Some(last_pos) = self.last_pos {
57            self.push(PathItem::Line(Line(last_pos, p)));
58        }
59        self.set_pos(p);
60    }
61
62    /// Add an arc.
63    pub fn arc(&mut self, x: f64, y: f64, radius: f64, angle1: f64, angle2: f64) {
64        let center = Point(x, y);
65        let arc = PathItem::Arc(Arc {
66            center,
67            radius,
68            angle1,
69            angle2,
70        });
71        if let Some(last_pos) = self.last_pos {
72            let left_point = arc.left_point();
73            if last_pos != left_point {
74                self.push(PathItem::Line(Line(last_pos, left_point)));
75            }
76        }
77        self.set_pos(arc.right_point());
78        self.push(arc);
79    }
80
81    /// Add an ellipse.
82    pub fn ellipse(
83        &mut self,
84        x: f64,
85        y: f64,
86        radius_x: f64,
87        radius_y: f64,
88        rotation: f64,
89        angle1: f64,
90        angle2: f64,
91    ) {
92        let radius_x = radius_x.abs();
93        let radius_y = radius_y.abs();
94        let center = Point(x, y);
95        let ellipse = PathItem::Ellipse(Ellipse {
96            center,
97            radius_x,
98            radius_y,
99            rotation,
100            angle1,
101            angle2,
102        });
103        if let Some(last_pos) = self.last_pos {
104            let left_point = ellipse.left_point();
105            if last_pos != left_point {
106                self.push(PathItem::Line(Line(last_pos, left_point)));
107            }
108        }
109        self.set_pos(ellipse.right_point());
110        self.push(ellipse);
111    }
112
113    /// Add an endpoint-parameterized ellipse.
114    pub fn ellipse_from_endpoint(
115        &mut self,
116        radius_x: f64,
117        radius_y: f64,
118        rotation: f64,
119        large: bool,
120        clockwise: bool,
121        x: f64,
122        y: f64,
123    ) {
124        let start = self.last_pos.unwrap_or_else(|| panic!("PathBuilder::move_to() is required before ellipse_from_endpoint"));
125        let end = Point(x, y);
126        if radius_x == 0.0 || radius_y == 0.0 {
127            self.set_pos(end);
128            self.push(PathItem::Line(Line(start, end)));
129            return;
130        }
131        let mut radius_x = radius_x.abs();
132        let mut radius_y = radius_y.abs();
133        let p = (start - end).rotate(-rotation) / 2.0;
134        {
135            let s = (p.0 / radius_x).powi(2) + (p.1 / radius_y).powi(2);
136            if 1.0 < s {
137                radius_x *= s.sqrt();
138                radius_y *= s.sqrt();
139            }
140        }
141        let (rx2, ry2) = (radius_x.powi(2), radius_y.powi(2));
142        let mut a = ((rx2 * ry2 - rx2 * p.1.powi(2) - ry2 * p.0.powi(2)) / (rx2 * p.1.powi(2) + ry2 * p.0.powi(2))).sqrt();
143        if large == clockwise {
144            a = -a;
145        }
146        let q = Point(radius_x * p.1 / radius_y, -radius_y * p.0 / radius_x) * a;
147        let center = q.rotate(rotation) + (start + end) / 2.0;
148        let a1 = Point((p.0 - q.0) / radius_x, (p.1 - q.1) / radius_y);
149        let mut angle1 = (a1.0 / a1.norm()).acos().copysign(a1.1);
150        let a2 = Point(-(p.0 + q.0) / radius_x, -(p.1 + q.1) / radius_y);
151        let mut angle2 = (a2.0 / a2.norm()).acos().copysign(a2.1);
152        if clockwise && angle2 < angle1 {
153            angle2 += PI * 2.0;
154        }
155        if !clockwise && angle1 < angle2 {
156            angle1 += PI * 2.0;
157        }
158        let ellipse = PathItem::Ellipse(Ellipse {
159            center,
160            radius_x,
161            radius_y,
162            rotation,
163            angle1,
164            angle2,
165        });
166        self.set_pos(end);
167        self.push(ellipse);
168    }
169
170    /// Add a quadratic bezier curve.
171    pub fn quad(&mut self, control_x: f64, control_y: f64, x: f64, y: f64) {
172        if let Some(last_pos) = self.last_pos {
173            let quad = PathItem::Quad(Quad {
174                start: last_pos,
175                end: Point(x, y),
176                control1: Point(control_x, control_y),
177            });
178            let left_point = quad.left_point();
179            if last_pos != left_point {
180                self.push(PathItem::Line(Line(last_pos, left_point)));
181            }
182            self.set_pos(quad.right_point());
183            self.push(quad);
184        } else {
185            panic!("PathBuilder::move_to() is required before quad");
186        }
187    }
188
189    /// Add a cubic bezier curve.
190    pub fn cubic(
191        &mut self,
192        control_x1: f64,
193        control_y1: f64,
194        control_x2: f64,
195        control_y2: f64,
196        x: f64,
197        y: f64,
198    ) {
199        if let Some(last_pos) = self.last_pos {
200            let cubic = PathItem::Cubic(Cubic {
201                start: last_pos,
202                end: Point(x, y),
203                control1: Point(control_x1, control_y1),
204                control2: Point(control_x2, control_y2),
205            });
206            let left_point = cubic.left_point();
207            if last_pos != left_point {
208                self.push(PathItem::Line(Line(last_pos, left_point)));
209            }
210            self.set_pos(cubic.right_point());
211            self.push(cubic);
212        } else {
213            panic!("PathBuilder::move_to() is required before cubic");
214        }
215    }
216
217    /// Close the path.
218    pub fn close(&mut self) {
219        if let Some(p) = self.path_start {
220            self.line_to(p.0, p.1);
221            self.push(PathItem::CloseAndJump);
222            self.path_start = None;
223            self.last_pos = None;
224        }
225    }
226
227    /// Return the built Path.
228    pub fn end(&mut self) -> Path {
229        let mut items = Vec::new();
230        std::mem::swap(&mut items, &mut self.items);
231        Path(items)
232    }
233
234    /// Return current position.
235    pub fn current_pos(&self) -> Option<(f64, f64)> {
236        self.last_pos.map(|p| p.into())
237    }
238}