1use crate::{
4 models::{Arc, Cubic, Ellipse, Line, Quad},
5 path::{Path, PathItem},
6 point::Point,
7};
8use std::f64::consts::PI;
9
10#[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 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 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 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 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 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 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 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 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 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 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 pub fn current_pos(&self) -> Option<(f64, f64)> {
236 self.last_pos.map(|p| p.into())
237 }
238}