1use kurbo::{BezPath, CubicBez, ParamCurve, PathEl, Point, QuadBez};
18
19pub const MAX_DEVICE: f64 = 65_536.0;
22
23const MARGIN: f64 = 64.0;
27
28const FLAT: f64 = 1.0 / 64.0;
30
31const MAX_DEPTH: u32 = 64;
33
34#[must_use]
39pub fn project_far(path: &BezPath, width: u32, height: u32) -> BezPath {
40 let bounds = kurbo::Shape::bounding_box(path);
41 let (w, h) = (f64::from(width), f64::from(height));
42 let finite = bounds.x0.is_finite()
43 && bounds.y0.is_finite()
44 && bounds.x1.is_finite()
45 && bounds.y1.is_finite();
46 if !finite {
47 return BezPath::new();
48 }
49 if bounds.x0 >= -MAX_DEVICE
50 && bounds.y0 >= -MAX_DEVICE
51 && bounds.x1 <= w + MAX_DEVICE
52 && bounds.y1 <= h + MAX_DEVICE
53 {
54 return path.clone();
55 }
56 let mut clip = Clip {
57 left: -MARGIN,
58 top: -MARGIN,
59 right: w + MARGIN,
60 bottom: h + MARGIN,
61 out: BezPath::new(),
62 at: Point::ZERO,
63 start: Point::ZERO,
64 open: false,
65 };
66 for element in path.elements() {
67 match *element {
68 PathEl::MoveTo(p) => {
69 clip.close();
70 clip.move_to(p);
71 }
72 PathEl::LineTo(p) => clip.line(p),
73 PathEl::QuadTo(p1, p2) => clip.quad(QuadBez::new(clip.at, p1, p2), 0),
74 PathEl::CurveTo(p1, p2, p3) => clip.cubic(CubicBez::new(clip.at, p1, p2, p3), 0),
75 PathEl::ClosePath => clip.close(),
76 }
77 }
78 clip.close();
79 clip.out
80}
81
82struct Clip {
83 left: f64,
84 top: f64,
85 right: f64,
86 bottom: f64,
87 out: BezPath,
88 at: Point,
90 start: Point,
92 open: bool,
94}
95
96enum Side {
97 Inside,
98 Outside,
99 Across,
100}
101
102impl Clip {
103 fn project(&self, p: Point) -> Point {
104 Point::new(
105 p.x.clamp(self.left, self.right),
106 p.y.clamp(self.top, self.bottom),
107 )
108 }
109
110 fn inside(&self, p: Point) -> bool {
111 (self.left..=self.right).contains(&p.x) && (self.top..=self.bottom).contains(&p.y)
112 }
113
114 fn move_to(&mut self, p: Point) {
115 let projected = self.project(p);
116 self.out.move_to(projected);
117 self.at = p;
118 self.start = p;
119 self.open = true;
120 }
121
122 fn line_to(&mut self, p: Point) {
123 if !self.open {
124 self.move_to(self.at);
125 }
126 let projected = self.project(p);
127 self.out.line_to(projected);
128 self.at = p;
129 }
130
131 fn close(&mut self) {
134 if self.open {
135 self.line(self.start);
136 self.out.close_path();
137 self.open = false;
138 }
139 self.at = self.start;
140 }
141
142 fn line(&mut self, to: Point) {
145 let from = self.at;
146 let mut cuts = [0.0_f64; 4];
147 let mut count = 0;
148 for (a, b, edge) in [
149 (from.x, to.x, self.left),
150 (from.x, to.x, self.right),
151 (from.y, to.y, self.top),
152 (from.y, to.y, self.bottom),
153 ] {
154 if (a < edge) != (b < edge) {
155 let t = (edge - a) / (b - a);
156 if t > 0.0 && t < 1.0 {
157 cuts[count] = t;
158 count += 1;
159 }
160 }
161 }
162 let cuts = &mut cuts[..count];
163 cuts.sort_by(f64::total_cmp);
164 for t in cuts.iter() {
165 self.line_to(from.lerp(to, *t));
166 }
167 self.line_to(to);
168 }
169
170 fn side(&self, points: &[Point]) -> Side {
172 if points.iter().all(|p| self.inside(*p)) {
173 Side::Inside
174 } else if points.iter().all(|p| p.x <= self.left)
175 || points.iter().all(|p| p.x >= self.right)
176 || points.iter().all(|p| p.y <= self.top)
177 || points.iter().all(|p| p.y >= self.bottom)
178 {
179 Side::Outside
180 } else {
181 Side::Across
182 }
183 }
184
185 fn small(points: &[Point]) -> bool {
186 let first = points[0];
187 points
188 .iter()
189 .all(|p| (p.x - first.x).abs() < FLAT && (p.y - first.y).abs() < FLAT)
190 }
191
192 fn quad(&mut self, quad: QuadBez, depth: u32) {
193 let points = [quad.p0, quad.p1, quad.p2];
194 match self.side(&points) {
195 Side::Inside => {
196 if !self.open {
197 self.move_to(self.at);
198 }
199 self.out.quad_to(quad.p1, quad.p2);
200 self.at = quad.p2;
201 }
202 Side::Across if depth < MAX_DEPTH && !Self::small(&points) => {
203 let (a, b) = quad.subdivide();
204 self.quad(a, depth + 1);
205 self.quad(b, depth + 1);
206 }
207 Side::Outside | Side::Across => self.line(quad.p2),
208 }
209 }
210
211 fn cubic(&mut self, cubic: CubicBez, depth: u32) {
212 let points = [cubic.p0, cubic.p1, cubic.p2, cubic.p3];
213 match self.side(&points) {
214 Side::Inside => {
215 if !self.open {
216 self.move_to(self.at);
217 }
218 self.out.curve_to(cubic.p1, cubic.p2, cubic.p3);
219 self.at = cubic.p3;
220 }
221 Side::Across if depth < MAX_DEPTH && !Self::small(&points) => {
222 let (a, b) = cubic.subdivide();
223 self.cubic(a, depth + 1);
224 self.cubic(b, depth + 1);
225 }
226 Side::Outside | Side::Across => self.line(cubic.p3),
227 }
228 }
229}