1use thiserror::Error;
16
17use crate::geometry::{Point, Rect};
18
19const MAX_FLATTEN_DEPTH: u32 = 12;
21pub(crate) const FLATTEN_TOLERANCE: f32 = 0.05;
23const ARC_MAX_ANGLE_STEP: f32 = std::f32::consts::PI / 16.0;
25const SUBSAMPLES: usize = 4;
27
28#[derive(Debug, Clone, PartialEq, Eq, Error)]
30pub enum SvgPathError {
31 #[error("unexpected byte {byte:?} at offset {offset}")]
33 UnexpectedByte {
34 byte: char,
36 offset: usize,
38 },
39 #[error("expected a number at offset {offset}")]
41 ExpectedNumber {
42 offset: usize,
44 },
45 #[error("expected an arc flag (0 or 1) at offset {offset}")]
47 ExpectedFlag {
48 offset: usize,
50 },
51 #[error("path data must start with a moveto (M/m) command")]
53 MissingMoveTo,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
58pub enum PathFillRule {
59 #[default]
61 NonZero,
62 EvenOdd,
64}
65
66#[derive(Debug, Clone)]
68pub struct VectorPath {
69 subpaths: Vec<Vec<Point>>,
70 fill_rule: PathFillRule,
71 bounds: Rect,
72}
73
74impl VectorPath {
75 pub fn parse(d: &str) -> Result<Self, SvgPathError> {
77 let subpaths = parse_path_data(d)?;
78 Ok(Self::from_subpaths(subpaths, PathFillRule::NonZero))
79 }
80
81 pub fn parse_with_fill_rule(d: &str, fill_rule: PathFillRule) -> Result<Self, SvgPathError> {
83 let subpaths = parse_path_data(d)?;
84 Ok(Self::from_subpaths(subpaths, fill_rule))
85 }
86
87 pub(crate) fn from_subpaths(subpaths: Vec<Vec<Point>>, fill_rule: PathFillRule) -> Self {
88 let mut min = Point::new(f32::INFINITY, f32::INFINITY);
89 let mut max = Point::new(f32::NEG_INFINITY, f32::NEG_INFINITY);
90 for point in subpaths.iter().flatten() {
91 min.x = min.x.min(point.x);
92 min.y = min.y.min(point.y);
93 max.x = max.x.max(point.x);
94 max.y = max.y.max(point.y);
95 }
96 let bounds = if min.x.is_finite() {
97 Rect {
98 x: min.x,
99 y: min.y,
100 width: (max.x - min.x).max(0.0),
101 height: (max.y - min.y).max(0.0),
102 }
103 } else {
104 Rect {
105 x: 0.0,
106 y: 0.0,
107 width: 0.0,
108 height: 0.0,
109 }
110 };
111 Self {
112 subpaths,
113 fill_rule,
114 bounds,
115 }
116 }
117
118 pub fn scaled(&self, factor: f32) -> Self {
121 let subpaths = self
122 .subpaths
123 .iter()
124 .map(|subpath| {
125 subpath
126 .iter()
127 .map(|point| Point::new(point.x * factor, point.y * factor))
128 .collect()
129 })
130 .collect();
131 Self::from_subpaths(subpaths, self.fill_rule)
132 }
133
134 pub fn translated(&self, dx: f32, dy: f32) -> Self {
136 let subpaths = self
137 .subpaths
138 .iter()
139 .map(|subpath| {
140 subpath
141 .iter()
142 .map(|point| Point::new(point.x + dx, point.y + dy))
143 .collect()
144 })
145 .collect();
146 Self::from_subpaths(subpaths, self.fill_rule)
147 }
148
149 pub fn fill_rule(&self) -> PathFillRule {
151 self.fill_rule
152 }
153
154 pub fn bounds(&self) -> Rect {
156 self.bounds
157 }
158
159 pub fn is_empty(&self) -> bool {
161 !self.subpaths.iter().any(|subpath| subpath.len() >= 3)
162 }
163
164 pub fn subpaths(&self) -> &[Vec<Point>] {
166 &self.subpaths
167 }
168
169 pub fn coverage_mask(
173 &self,
174 width: usize,
175 height: usize,
176 origin: Point,
177 scale: f32,
178 opacity: f32,
179 ) -> Vec<u8> {
180 let mut mask = vec![0u8; width * height];
181 if width == 0 || height == 0 || scale <= 0.0 {
182 return mask;
183 }
184 let edges = self.scanline_edges(origin, scale);
185 if edges.is_empty() {
186 return mask;
187 }
188 let mut scanner = EdgeScanner::new(&edges);
189 let mut row_coverage = vec![0.0f32; width];
190 let subsample_weight = 1.0 / SUBSAMPLES as f32;
191 for (row, mask_row) in mask.chunks_exact_mut(width).enumerate() {
192 let mut touched = width..0;
194 for sub in 0..SUBSAMPLES {
195 let sample_y = row as f32 + (sub as f32 + 0.5) * subsample_weight;
196 let crossings = scanner.crossings_at(sample_y);
197 self.fill_rule.for_each_span(crossings, |x0, x1| {
198 if let Some(span) =
199 accumulate_span(&mut row_coverage, x0, x1, subsample_weight, width)
200 {
201 touched.start = touched.start.min(span.start);
202 touched.end = touched.end.max(span.end);
203 }
204 });
205 }
206 if let (Some(mask_span), Some(coverage)) = (
207 mask_row.get_mut(touched.clone()),
208 row_coverage.get_mut(touched),
209 ) {
210 write_row_coverage(mask_span, coverage, opacity);
211 }
212 }
213 mask
214 }
215
216 fn scanline_edges(&self, origin: Point, scale: f32) -> Vec<Edge> {
219 let map = |p: &Point| Point::new((p.x - origin.x) * scale, (p.y - origin.y) * scale);
220 let closed = self.subpaths.iter().filter(|subpath| subpath.len() >= 3);
221 closed
222 .flat_map(|subpath| {
223 let next = subpath.iter().skip(1).chain(subpath.first());
224 subpath.iter().zip(next).filter_map(move |(a, b)| {
225 let (a, b) = (map(a), map(b));
226 match a.y.total_cmp(&b.y) {
227 std::cmp::Ordering::Less => Some(Edge::new(a, b, 1)),
228 std::cmp::Ordering::Greater => Some(Edge::new(b, a, -1)),
229 std::cmp::Ordering::Equal => None,
230 }
231 })
232 })
233 .collect()
234 }
235}
236
237struct Edge {
240 top: Point,
241 bottom: Point,
242 delta: Point,
244 winding: i32,
245}
246
247impl Edge {
248 fn new(top: Point, bottom: Point, winding: i32) -> Self {
249 Self {
250 top,
251 bottom,
252 delta: Point::new(bottom.x - top.x, bottom.y - top.y),
253 winding,
254 }
255 }
256}
257
258struct EdgeScanner<'a> {
262 edges: Vec<(f32, &'a Edge)>,
264 next: usize,
265 active: Vec<(f32, &'a Edge)>,
267}
268
269impl<'a> EdgeScanner<'a> {
270 fn new(edges: &'a [Edge]) -> Self {
271 let mut edges: Vec<_> = edges.iter().map(|edge| (edge.top.y, edge)).collect();
272 sort_by_position(&mut edges);
273 Self {
274 edges,
275 next: 0,
276 active: Vec::new(),
277 }
278 }
279
280 fn crossings_at(&mut self, sample_y: f32) -> &[(f32, &'a Edge)] {
283 while let Some(&(_, edge)) = self
284 .edges
285 .get(self.next)
286 .filter(|(top, _)| *top <= sample_y)
287 {
288 self.active.push((0.0, edge));
289 self.next += 1;
290 }
291 self.active.retain(|(_, edge)| sample_y < edge.bottom.y);
292 for (x, edge) in &mut self.active {
293 let t = (sample_y - edge.top.y) / edge.delta.y;
294 *x = edge.top.x + t * edge.delta.x;
295 }
296 sort_by_position(&mut self.active);
300 &self.active
301 }
302}
303
304fn sort_by_position(edges: &mut [(f32, &Edge)]) {
308 edges.sort_unstable_by(|(a, _), (b, _)| a.total_cmp(b));
309}
310
311impl PathFillRule {
312 fn for_each_span(self, crossings: &[(f32, &Edge)], mut span: impl FnMut(f32, f32)) {
315 let mut winding = 0i32;
316 let mut span_start = 0.0f32;
317 for &(x, edge) in crossings {
318 let was_inside = self.contains(winding);
319 winding += edge.winding;
320 match (was_inside, self.contains(winding)) {
321 (false, true) => span_start = x,
322 (true, false) => span(span_start, x),
323 _ => {}
324 }
325 }
326 }
327
328 pub(crate) fn contains(self, winding: i32) -> bool {
330 match self {
331 Self::NonZero => winding != 0,
332 Self::EvenOdd => winding % 2 != 0,
333 }
334 }
335}
336
337fn write_row_coverage(mask_row: &mut [u8], row_coverage: &mut [f32], opacity: f32) {
340 let full = |coverage: f32| (coverage.min(1.0) * 255.0 + 0.5) as u8;
341 let pixels = mask_row.iter_mut().zip(row_coverage.iter());
342 if (opacity - 1.0).abs() <= f32::EPSILON {
345 for (value, &coverage) in pixels {
346 *value = full(coverage);
347 }
348 } else {
349 for (value, &coverage) in pixels {
350 *value = (opacity * f32::from(full(coverage)) + 0.5) as u8;
351 }
352 }
353 row_coverage.fill(0.0);
354}
355
356fn accumulate_span(
360 row_coverage: &mut [f32],
361 x0: f32,
362 x1: f32,
363 weight: f32,
364 width: usize,
365) -> Option<std::ops::Range<usize>> {
366 let x0 = x0.max(0.0);
367 let x1 = x1.min(width as f32);
368 if x1 <= x0 {
369 return None;
370 }
371
372 let first = x0.floor() as usize;
375 let last = (x1.ceil() as usize).min(width);
376 let partial = |pixel: usize| {
377 let pixel_start = pixel as f32;
378 (x1.min(pixel_start + 1.0) - x0.max(pixel_start)).max(0.0) * weight
379 };
380 match row_coverage.get_mut(first..last)? {
381 [] => {}
382 [only] => *only += partial(first),
383 [head, interior @ .., tail] => {
384 *head += partial(first);
385 for coverage in interior {
386 *coverage += weight;
387 }
388 *tail += partial(last - 1);
389 }
390 }
391 Some(first..last)
392}
393
394struct PathLexer<'a> {
395 bytes: &'a [u8],
396 pos: usize,
397}
398
399impl<'a> PathLexer<'a> {
400 fn new(d: &'a str) -> Self {
401 Self {
402 bytes: d.as_bytes(),
403 pos: 0,
404 }
405 }
406
407 fn skip_separators(&mut self) {
408 while self.pos < self.bytes.len() {
409 match self.bytes[self.pos] {
410 b' ' | b'\t' | b'\r' | b'\n' | b',' => self.pos += 1,
411 _ => break,
412 }
413 }
414 }
415
416 fn peek(&mut self) -> Option<u8> {
417 self.skip_separators();
418 self.bytes.get(self.pos).copied()
419 }
420
421 fn at_number(&mut self) -> bool {
422 matches!(self.peek(), Some(b'0'..=b'9' | b'.' | b'-' | b'+'))
423 }
424
425 fn next_command(&mut self) -> Option<u8> {
426 let byte = self.peek()?;
427 if byte.is_ascii_alphabetic() {
428 self.pos += 1;
429 Some(byte)
430 } else {
431 None
432 }
433 }
434
435 fn next_number(&mut self) -> Result<f32, SvgPathError> {
436 self.skip_separators();
437 let start = self.pos;
438 let bytes = self.bytes;
439 let mut pos = self.pos;
440
441 if pos < bytes.len() && (bytes[pos] == b'+' || bytes[pos] == b'-') {
442 pos += 1;
443 }
444 let int_digits = Self::eat_digits(bytes, &mut pos);
445 let mut frac_digits = 0;
446 if pos < bytes.len() && bytes[pos] == b'.' {
447 pos += 1;
448 frac_digits = Self::eat_digits(bytes, &mut pos);
449 }
450 if int_digits == 0 && frac_digits == 0 {
451 return Err(SvgPathError::ExpectedNumber { offset: start });
452 }
453 if pos < bytes.len() && (bytes[pos] == b'e' || bytes[pos] == b'E') {
454 let mut exp_pos = pos + 1;
455 if exp_pos < bytes.len() && (bytes[exp_pos] == b'+' || bytes[exp_pos] == b'-') {
456 exp_pos += 1;
457 }
458 if Self::eat_digits(bytes, &mut exp_pos) > 0 {
459 pos = exp_pos;
460 }
461 }
462
463 let text = std::str::from_utf8(&bytes[start..pos])
464 .map_err(|_| SvgPathError::ExpectedNumber { offset: start })?;
465 let value = text
466 .parse::<f32>()
467 .map_err(|_| SvgPathError::ExpectedNumber { offset: start })?;
468 self.pos = pos;
469 Ok(value)
470 }
471
472 fn eat_digits(bytes: &[u8], pos: &mut usize) -> usize {
473 let start = *pos;
474 while *pos < bytes.len() && bytes[*pos].is_ascii_digit() {
475 *pos += 1;
476 }
477 *pos - start
478 }
479
480 fn next_flag(&mut self) -> Result<bool, SvgPathError> {
481 self.skip_separators();
482 match self.bytes.get(self.pos) {
483 Some(b'0') => {
484 self.pos += 1;
485 Ok(false)
486 }
487 Some(b'1') => {
488 self.pos += 1;
489 Ok(true)
490 }
491 _ => Err(SvgPathError::ExpectedFlag { offset: self.pos }),
492 }
493 }
494
495 fn at_end(&mut self) -> bool {
496 self.peek().is_none()
497 }
498}
499
500struct PathBuilder {
501 subpaths: Vec<Vec<Point>>,
502 current: Vec<Point>,
503 position: Point,
504 subpath_start: Point,
505 last_cubic_control: Option<Point>,
506 last_quad_control: Option<Point>,
507}
508
509impl PathBuilder {
510 fn new() -> Self {
511 Self {
512 subpaths: Vec::new(),
513 current: Vec::new(),
514 position: Point::ZERO,
515 subpath_start: Point::ZERO,
516 last_cubic_control: None,
517 last_quad_control: None,
518 }
519 }
520
521 fn flush_subpath(&mut self) {
522 if self.current.len() >= 2 {
523 self.subpaths.push(std::mem::take(&mut self.current));
524 } else {
525 self.current.clear();
526 }
527 }
528
529 fn move_to(&mut self, point: Point) {
530 self.flush_subpath();
531 self.position = point;
532 self.subpath_start = point;
533 self.current.push(point);
534 }
535
536 fn line_to(&mut self, point: Point) {
537 self.begin_segment();
538 self.current.push(point);
539 self.position = point;
540 }
541
542 fn begin_segment(&mut self) -> Point {
545 if self.current.is_empty() {
546 self.current.push(self.position);
547 }
548 self.position
549 }
550
551 fn close(&mut self) {
552 self.position = self.subpath_start;
553 self.flush_subpath();
554 self.current.push(self.subpath_start);
555 }
556
557 fn finish(mut self) -> Vec<Vec<Point>> {
558 self.flush_subpath();
559 self.subpaths
560 }
561}
562
563fn parse_path_data(d: &str) -> Result<Vec<Vec<Point>>, SvgPathError> {
564 let mut lexer = PathLexer::new(d);
565 let mut builder = PathBuilder::new();
566 let mut command: Option<u8> = None;
567 let mut seen_moveto = false;
568
569 loop {
570 if lexer.at_end() {
571 break;
572 }
573
574 if let Some(next) = lexer.next_command() {
575 command = Some(next);
576 } else if command.is_none() || !lexer.at_number() {
577 let offset = lexer.pos;
578 let byte = lexer.bytes.get(offset).copied().unwrap_or(b'?') as char;
579 return Err(SvgPathError::UnexpectedByte { byte, offset });
580 }
581
582 let Some(cmd) = command else {
583 return Err(SvgPathError::MissingMoveTo);
584 };
585 if !seen_moveto && !matches!(cmd, b'M' | b'm') {
586 return Err(SvgPathError::MissingMoveTo);
587 }
588 let relative = cmd.is_ascii_lowercase();
589 let pos = builder.position;
590 let rel = |value: Point| {
591 if relative {
592 Point::new(pos.x + value.x, pos.y + value.y)
593 } else {
594 value
595 }
596 };
597
598 match cmd.to_ascii_uppercase() {
599 b'M' => {
600 let point = rel(read_point(&mut lexer)?);
601 builder.move_to(point);
602 seen_moveto = true;
603 builder.last_cubic_control = None;
604 builder.last_quad_control = None;
605 command = Some(if relative { b'l' } else { b'L' });
606 }
607 b'L' => {
608 let point = rel(read_point(&mut lexer)?);
609 builder.line_to(point);
610 builder.last_cubic_control = None;
611 builder.last_quad_control = None;
612 }
613 b'H' => {
614 let x = lexer.next_number()?;
615 let x = if relative { pos.x + x } else { x };
616 builder.line_to(Point::new(x, pos.y));
617 builder.last_cubic_control = None;
618 builder.last_quad_control = None;
619 }
620 b'V' => {
621 let y = lexer.next_number()?;
622 let y = if relative { pos.y + y } else { y };
623 builder.line_to(Point::new(pos.x, y));
624 builder.last_cubic_control = None;
625 builder.last_quad_control = None;
626 }
627 b'C' => {
628 let c1 = rel(read_point(&mut lexer)?);
629 let c2 = rel(read_point(&mut lexer)?);
630 let end = rel(read_point(&mut lexer)?);
631 emit_cubic(&mut builder, c1, c2, end);
632 }
633 b'S' => {
634 let c1 = match builder.last_cubic_control {
635 Some(control) => reflect(pos, control),
636 None => pos,
637 };
638 let c2 = rel(read_point(&mut lexer)?);
639 let end = rel(read_point(&mut lexer)?);
640 emit_cubic(&mut builder, c1, c2, end);
641 }
642 b'Q' => {
643 let control = rel(read_point(&mut lexer)?);
644 let end = rel(read_point(&mut lexer)?);
645 emit_quad(&mut builder, control, end);
646 }
647 b'T' => {
648 let control = match builder.last_quad_control {
649 Some(control) => reflect(pos, control),
650 None => pos,
651 };
652 let end = rel(read_point(&mut lexer)?);
653 emit_quad(&mut builder, control, end);
654 }
655 b'A' => {
656 let rx = lexer.next_number()?;
657 let ry = lexer.next_number()?;
658 let x_rotation_deg = lexer.next_number()?;
659 let large_arc = lexer.next_flag()?;
660 let sweep = lexer.next_flag()?;
661 let end = rel(read_point(&mut lexer)?);
662 emit_arc(&mut builder, rx, ry, x_rotation_deg, large_arc, sweep, end);
663 builder.last_cubic_control = None;
664 builder.last_quad_control = None;
665 }
666 b'Z' => {
667 builder.close();
668 builder.last_cubic_control = None;
669 builder.last_quad_control = None;
670 command = None;
671 }
672 other => {
673 return Err(SvgPathError::UnexpectedByte {
674 byte: other as char,
675 offset: lexer.pos.saturating_sub(1),
676 });
677 }
678 }
679 }
680
681 if !seen_moveto {
682 return Err(SvgPathError::MissingMoveTo);
683 }
684 Ok(builder.finish())
685}
686
687fn read_point(lexer: &mut PathLexer<'_>) -> Result<Point, SvgPathError> {
688 let x = lexer.next_number()?;
689 let y = lexer.next_number()?;
690 Ok(Point::new(x, y))
691}
692
693fn reflect(origin: Point, point: Point) -> Point {
694 Point::new(2.0 * origin.x - point.x, 2.0 * origin.y - point.y)
695}
696
697fn emit_cubic(builder: &mut PathBuilder, c1: Point, c2: Point, end: Point) {
698 let start = builder.begin_segment();
699 flatten_cubic_into(&mut builder.current, start, c1, c2, end);
700 builder.position = end;
701 builder.last_cubic_control = Some(c2);
702 builder.last_quad_control = None;
703}
704
705fn emit_quad(builder: &mut PathBuilder, control: Point, end: Point) {
706 let start = builder.begin_segment();
707 let (c1, c2) = quad_as_cubic(start, control, end);
708 flatten_cubic_into(&mut builder.current, start, c1, c2, end);
709 builder.position = end;
710 builder.last_quad_control = Some(control);
711 builder.last_cubic_control = None;
712}
713
714pub(crate) fn quad_as_cubic(start: Point, control: Point, end: Point) -> (Point, Point) {
717 (
718 Point::new(
719 start.x + 2.0 / 3.0 * (control.x - start.x),
720 start.y + 2.0 / 3.0 * (control.y - start.y),
721 ),
722 Point::new(
723 end.x + 2.0 / 3.0 * (control.x - end.x),
724 end.y + 2.0 / 3.0 * (control.y - end.y),
725 ),
726 )
727}
728
729pub(crate) fn flatten_cubic_into(
733 points: &mut Vec<Point>,
734 p0: Point,
735 p1: Point,
736 p2: Point,
737 p3: Point,
738) {
739 flatten_cubic(points, p0, p1, p2, p3, 0);
740}
741
742fn flatten_cubic(points: &mut Vec<Point>, p0: Point, p1: Point, p2: Point, p3: Point, depth: u32) {
743 if depth >= MAX_FLATTEN_DEPTH || cubic_is_flat(p0, p1, p2, p3) {
744 points.push(p3);
745 return;
746 }
747
748 let mid = |a: Point, b: Point| Point::new((a.x + b.x) * 0.5, (a.y + b.y) * 0.5);
749 let p01 = mid(p0, p1);
750 let p12 = mid(p1, p2);
751 let p23 = mid(p2, p3);
752 let p012 = mid(p01, p12);
753 let p123 = mid(p12, p23);
754 let p0123 = mid(p012, p123);
755
756 flatten_cubic(points, p0, p01, p012, p0123, depth + 1);
757 flatten_cubic(points, p0123, p123, p23, p3, depth + 1);
758}
759
760fn cubic_is_flat(p0: Point, p1: Point, p2: Point, p3: Point) -> bool {
762 let d1 = point_to_chord_distance_squared(p1, p0, p3);
763 let d2 = point_to_chord_distance_squared(p2, p0, p3);
764 let tolerance = FLATTEN_TOLERANCE * FLATTEN_TOLERANCE;
765 d1 <= tolerance && d2 <= tolerance
766}
767
768fn point_to_chord_distance_squared(point: Point, a: Point, b: Point) -> f32 {
769 let ab = Point::new(b.x - a.x, b.y - a.y);
770 let ap = Point::new(point.x - a.x, point.y - a.y);
771 let ab_len_sq = ab.x * ab.x + ab.y * ab.y;
772 if ab_len_sq <= f32::EPSILON {
773 return ap.x * ap.x + ap.y * ap.y;
774 }
775 let cross = ab.x * ap.y - ab.y * ap.x;
776 cross * cross / ab_len_sq
777}
778
779fn emit_arc(
782 builder: &mut PathBuilder,
783 rx: f32,
784 ry: f32,
785 x_rotation_deg: f32,
786 large_arc: bool,
787 sweep: bool,
788 end: Point,
789) {
790 let start = builder.position;
791 if (start.x - end.x).abs() <= f32::EPSILON && (start.y - end.y).abs() <= f32::EPSILON {
792 return;
793 }
794 let mut rx = rx.abs();
795 let mut ry = ry.abs();
796 if rx <= f32::EPSILON || ry <= f32::EPSILON {
797 builder.line_to(end);
798 return;
799 }
800
801 let phi = x_rotation_deg.to_radians();
802 let (sin_phi, cos_phi) = phi.sin_cos();
803
804 let dx2 = (start.x - end.x) * 0.5;
805 let dy2 = (start.y - end.y) * 0.5;
806 let x1p = cos_phi * dx2 + sin_phi * dy2;
807 let y1p = -sin_phi * dx2 + cos_phi * dy2;
808
809 let lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);
810 if lambda > 1.0 {
811 let scale = lambda.sqrt();
812 rx *= scale;
813 ry *= scale;
814 }
815
816 let rx_sq = rx * rx;
817 let ry_sq = ry * ry;
818 let numerator = (rx_sq * ry_sq - rx_sq * y1p * y1p - ry_sq * x1p * x1p).max(0.0);
819 let denominator = rx_sq * y1p * y1p + ry_sq * x1p * x1p;
820 let mut coefficient = if denominator <= f32::EPSILON {
821 0.0
822 } else {
823 (numerator / denominator).sqrt()
824 };
825 if large_arc == sweep {
826 coefficient = -coefficient;
827 }
828 let cxp = coefficient * rx * y1p / ry;
829 let cyp = -coefficient * ry * x1p / rx;
830
831 let cx = cos_phi * cxp - sin_phi * cyp + (start.x + end.x) * 0.5;
832 let cy = sin_phi * cxp + cos_phi * cyp + (start.y + end.y) * 0.5;
833
834 let angle_of = |x: f32, y: f32| y.atan2(x);
835 let theta1 = angle_of((x1p - cxp) / rx, (y1p - cyp) / ry);
836 let theta2 = angle_of((-x1p - cxp) / rx, (-y1p - cyp) / ry);
837 let two_pi = std::f32::consts::TAU;
838 let mut delta = theta2 - theta1;
839 if sweep {
840 if delta < 0.0 {
841 delta += two_pi;
842 }
843 } else if delta > 0.0 {
844 delta -= two_pi;
845 }
846
847 let segments = ((delta.abs() / ARC_MAX_ANGLE_STEP).ceil() as usize).max(2);
848 for i in 1..=segments {
849 let theta = theta1 + delta * (i as f32 / segments as f32);
850 let (sin_theta, cos_theta) = theta.sin_cos();
851 let x = cos_phi * rx * cos_theta - sin_phi * ry * sin_theta + cx;
852 let y = sin_phi * rx * cos_theta + cos_phi * ry * sin_theta + cy;
853 builder.line_to(Point::new(x, y));
854 }
855 builder.line_to(end);
856 builder.position = end;
857}
858
859#[cfg(test)]
860#[path = "tests/vector_path_tests.rs"]
861mod tests;