1use crate::path_builder::{Path, PathBuilder, PathOp};
5use crate::{Point, Vector};
6
7#[derive(Clone, PartialEq, Debug)]
8pub struct StrokeStyle {
9 pub width: f32,
10 pub cap: LineCap,
11 pub join: LineJoin,
12 pub miter_limit: f32,
13 pub dash_array: Vec<f32>,
14 pub dash_offset: f32,
15}
16
17impl Default for StrokeStyle {
18 fn default() -> Self {
19 StrokeStyle {
20 width: 1.,
21 cap: LineCap::Butt,
22 join: LineJoin::Miter,
23 miter_limit: 10.,
24 dash_array: Vec::new(),
25 dash_offset: 0.,
26 }
27 }
28}
29
30#[derive(Clone, Copy, PartialEq, Debug)]
31pub enum LineCap {
32 Round,
33 Square,
34 Butt,
35}
36
37#[derive(Clone, Copy, PartialEq, Debug)]
38pub enum LineJoin {
39 Round,
40 Miter,
41 Bevel,
42}
43
44fn compute_normal(p0: Point, p1: Point) -> Option<Vector> {
45 let ux = p1.x - p0.x;
46 let uy = p1.y - p0.y;
47
48 let ulen = ux.hypot(uy);
52 if ulen == 0. {
53 return None;
54 }
55 Some(Vector::new(-uy / ulen, ux / ulen))
57}
58
59fn flip(v: Vector) -> Vector {
60 Vector::new(-v.x, -v.y)
61}
62
63fn arc_segment(path: &mut PathBuilder, xc: f32, yc: f32, radius: f32, a: Vector, b: Vector) {
87 let r_sin_a = radius * a.y;
88 let r_cos_a = radius * a.x;
89 let r_sin_b = radius * b.y;
90 let r_cos_b = radius * b.x;
91
92 let mut mid = a + b;
94 mid /= mid.length();
95
96 let mid2 = a + mid;
99
100 let h = (4. / 3.) * dot(perp(a), mid2) / dot(a, mid2);
101
102 path.cubic_to(
103 xc + r_cos_a - h * r_sin_a,
104 yc + r_sin_a + h * r_cos_a,
105 xc + r_cos_b + h * r_sin_b,
106 yc + r_sin_b - h * r_cos_b,
107 xc + r_cos_b,
108 yc + r_sin_b,
109 );
110}
111
112fn bisect(a: Vector, b: Vector) -> Vector {
114 let mut mid;
115 if dot(a, b) >= 0. {
116 mid = a + b;
119 } else {
120 mid = flip(a) + b;
123 mid = perp(mid);
124 }
125
126 let mid_len = mid.x * mid.x + mid.y * mid.y;
130 let len = mid_len.sqrt();
131 return mid / len;
132}
133
134fn arc(path: &mut PathBuilder, xc: f32, yc: f32, radius: f32, a: Vector, b: Vector) {
135 let mid_v = bisect(a, b);
137
138 arc_segment(path, xc, yc, radius, a, mid_v);
140 arc_segment(path, xc, yc, radius, mid_v, b);
141}
142
143fn join_round(path: &mut PathBuilder, center: Point, a: Vector, b: Vector, radius: f32) {
144 arc(path, center.x, center.y, radius, a, b);
150}
151
152fn cap_line(dest: &mut PathBuilder, style: &StrokeStyle, pt: Point, normal: Vector) {
153 let offset = style.width / 2.;
154 match style.cap {
155 LineCap::Butt => { }
156 LineCap::Round => {
157 dest.move_to(pt.x + normal.x * offset, pt.y + normal.y * offset);
158 arc(dest, pt.x, pt.y, offset, normal, flip(normal));
159 dest.line_to(pt.x, pt.y);
160 dest.close();
161 }
162 LineCap::Square => {
163 let v = Vector::new(normal.y, -normal.x);
165 let end = pt + v * offset;
166 dest.move_to(pt.x + normal.x * offset, pt.y + normal.y * offset);
167 dest.line_to(end.x + normal.x * offset, end.y + normal.y * offset);
168 dest.line_to(end.x + -normal.x * offset, end.y + -normal.y * offset);
169 dest.line_to(pt.x - normal.x * offset, pt.y - normal.y * offset);
170 dest.line_to(pt.x, pt.y);
171 dest.close();
172 }
173 }
174}
175
176fn bevel(
177 dest: &mut PathBuilder,
178 style: &StrokeStyle,
179 pt: Point,
180 s1_normal: Vector,
181 s2_normal: Vector,
182) {
183 let offset = style.width / 2.;
184 dest.move_to(pt.x + s1_normal.x * offset, pt.y + s1_normal.y * offset);
185 dest.line_to(pt.x + s2_normal.x * offset, pt.y + s2_normal.y * offset);
186 dest.line_to(pt.x, pt.y);
187 dest.close();
188}
189
190fn swap(a: Vector) -> Vector {
193 Vector::new(a.y, -a.x)
195}
196
197fn unperp(a: Vector) -> Vector {
198 swap(a)
199}
200
201fn perp(v: Vector) -> Vector {
203 Vector::new(-v.y, v.x)
204}
205
206fn dot(a: Vector, b: Vector) -> f32 {
207 a.x * b.x + a.y * b.y
208}
209
210fn line_intersection(a: Point, a_perp: Vector, b: Point, b_perp: Vector) -> Option<Point> {
215 let a_parallel = unperp(a_perp);
216 let c = b - a;
217 let denom = dot(b_perp, a_parallel);
218 if denom == 0.0 {
219 return None;
220 }
221
222 let t = dot(b_perp, c) / denom;
223
224 let intersection = Point::new(a.x + t * (a_parallel.x), a.y + t * (a_parallel.y));
225
226 Some(intersection)
227}
228
229fn is_interior_angle(a: Vector, b: Vector) -> bool {
230 dot(perp(a), b) > 0. || a == b }
234
235fn join_line(
236 dest: &mut PathBuilder,
237 style: &StrokeStyle,
238 pt: Point,
239 mut s1_normal: Vector,
240 mut s2_normal: Vector,
241) {
242 if is_interior_angle(s1_normal, s2_normal) {
243 s2_normal = flip(s2_normal);
244 s1_normal = flip(s1_normal);
245 std::mem::swap(&mut s1_normal, &mut s2_normal);
246 }
247
248 let offset = style.width / 2.;
251 match style.join {
252 LineJoin::Round => {
253 dest.move_to(pt.x + s1_normal.x * offset, pt.y + s1_normal.y * offset);
254 join_round(dest, pt, s1_normal, s2_normal, offset);
255 dest.line_to(pt.x, pt.y);
256 dest.close();
257 }
258 LineJoin::Miter => {
259 let in_dot_out = -s1_normal.x * s2_normal.x + -s1_normal.y * s2_normal.y;
260 if 2. <= style.miter_limit * style.miter_limit * (1. - in_dot_out) {
261 let start = pt + s1_normal * offset;
262 let end = pt + s2_normal * offset;
263 if let Some(intersection) = line_intersection(start, s1_normal, end, s2_normal) {
264 dest.move_to(pt.x + s1_normal.x * offset, pt.y + s1_normal.y * offset);
266 dest.line_to(intersection.x, intersection.y);
267 dest.line_to(pt.x + s2_normal.x * offset, pt.y + s2_normal.y * offset);
268 dest.line_to(pt.x, pt.y);
269 dest.close();
270 }
271 } else {
272 bevel(dest, style, pt, s1_normal, s2_normal);
273 }
274 }
275 LineJoin::Bevel => {
276 bevel(dest, style, pt, s1_normal, s2_normal);
277 }
278 }
279}
280
281pub fn stroke_to_path(path: &Path, style: &StrokeStyle) -> Path {
282 let mut stroked_path = PathBuilder::new();
283
284 if style.width <= 0. {
285 return stroked_path.finish();
286 }
287
288 let mut cur_pt = None;
289 let mut last_normal = Vector::zero();
290 let half_width = style.width / 2.;
291 let mut start_point = None;
292 for op in &path.ops {
293 match *op {
294 PathOp::MoveTo(pt) => {
295 if let (Some(cur_pt), Some((point, normal))) = (cur_pt, start_point) {
296 cap_line(&mut stroked_path, style, cur_pt, last_normal);
298 cap_line(&mut stroked_path, style, point, flip(normal));
300 }
301 start_point = None;
302 cur_pt = Some(pt);
303 }
304 PathOp::LineTo(pt) => {
305 if cur_pt.is_none() {
306 start_point = None;
307 } else if let Some(cur_pt) = cur_pt {
308 if let Some(normal) = compute_normal(cur_pt, pt) {
309 if start_point.is_none() {
310 start_point = Some((cur_pt, normal));
311 } else {
312 join_line(&mut stroked_path, style, cur_pt, last_normal, normal);
313 }
314
315 stroked_path.move_to(
316 cur_pt.x + normal.x * half_width,
317 cur_pt.y + normal.y * half_width,
318 );
319 stroked_path.line_to(pt.x + normal.x * half_width, pt.y + normal.y * half_width);
320 stroked_path.line_to(pt.x, pt.y);
324 stroked_path.line_to(pt.x + -normal.x * half_width, pt.y + -normal.y * half_width);
325 stroked_path.line_to(
326 cur_pt.x - normal.x * half_width,
327 cur_pt.y - normal.y * half_width,
328 );
329 stroked_path.line_to(cur_pt.x, cur_pt.y);
330
331 stroked_path.close();
332
333 last_normal = normal;
334
335 }
336 }
337 cur_pt = Some(pt);
338
339 }
340 PathOp::Close => {
341 if let (Some(cur_pt), Some((end_point, start_normal))) = (cur_pt, start_point) {
342 if let Some(normal) = compute_normal(cur_pt, end_point) {
343 join_line(&mut stroked_path, style, cur_pt, last_normal, normal);
344
345 stroked_path.move_to(
347 cur_pt.x + normal.x * half_width,
348 cur_pt.y + normal.y * half_width,
349 );
350 stroked_path.line_to(
351 end_point.x + normal.x * half_width,
352 end_point.y + normal.y * half_width,
353 );
354 stroked_path.line_to(
355 end_point.x,
356 end_point.y,
357 );
358 stroked_path.line_to(
359 end_point.x + -normal.x * half_width,
360 end_point.y + -normal.y * half_width,
361 );
362 stroked_path.line_to(
363 cur_pt.x - normal.x * half_width,
364 cur_pt.y - normal.y * half_width,
365 );
366 stroked_path.line_to(
367 cur_pt.x,
368 cur_pt.y,
369 );
370 stroked_path.close();
371
372 join_line(&mut stroked_path, style, end_point, normal, start_normal);
373 } else {
374 join_line(&mut stroked_path, style, end_point, last_normal, start_normal);
375 }
376 }
377 cur_pt = start_point.map(|x| x.0);
378 start_point = None;
379 }
380 PathOp::QuadTo(..) => panic!("Only flat paths handled"),
381 PathOp::CubicTo(..) => panic!("Only flat paths handled"),
382 }
383 }
384 if let (Some(cur_pt), Some((point, normal))) = (cur_pt, start_point) {
385 cap_line(&mut stroked_path, style, cur_pt, last_normal);
387 cap_line(&mut stroked_path, style, point, flip(normal));
389 }
390 stroked_path.finish()
391}