1use alloc::{string::String, vec::Vec};
8use azul_css::props::basic::{SvgCubicCurve, SvgPoint, SvgQuadraticCurve};
9
10use crate::svg::{SvgLine, SvgMultiPolygon, SvgPath, SvgPathElement, SvgPathElementVec, SvgPathVec};
11
12const KAPPA: f32 = 0.5522847498;
14
15const POINT_EPSILON: f32 = 1e-6;
17
18const CLOSEPATH_EPSILON: f32 = 0.001;
20
21const ZERO_LENGTH_EPSILON: f32 = 1e-10;
23
24const ARC_SPLIT_FUDGE: f32 = 0.001;
26
27#[derive(Debug, Clone, PartialEq)]
29pub enum SvgPathParseError {
30 EmptyPath,
32 UnexpectedChar { pos: usize, ch: char },
34 ExpectedNumber { pos: usize },
36 InvalidArcFlag { pos: usize },
38}
39
40impl core::fmt::Display for SvgPathParseError {
42 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
43 match self {
44 Self::EmptyPath => write!(f, "empty path"),
45 Self::UnexpectedChar { pos, ch } => {
46 write!(f, "unexpected char '{}' at byte {}", ch, pos)
47 }
48 Self::ExpectedNumber { pos } => write!(f, "expected number at byte {}", pos),
49 Self::InvalidArcFlag { pos } => write!(f, "invalid arc flag at byte {}", pos),
50 }
51 }
52}
53
54struct PathParser<'a> {
56 input: &'a [u8],
57 pos: usize,
58 current: SvgPoint,
59 subpath_start: SvgPoint,
60 last_control: Option<SvgPoint>,
61 last_command: u8,
62}
63
64impl<'a> PathParser<'a> {
65 fn new(input: &'a [u8]) -> Self {
66 Self {
67 input,
68 pos: 0,
69 current: SvgPoint { x: 0.0, y: 0.0 },
70 subpath_start: SvgPoint { x: 0.0, y: 0.0 },
71 last_control: None,
72 last_command: 0,
73 }
74 }
75
76 fn at_end(&self) -> bool {
77 self.pos >= self.input.len()
78 }
79
80 fn peek(&self) -> Option<u8> {
81 self.input.get(self.pos).copied()
82 }
83
84 fn skip_whitespace_and_commas(&mut self) {
85 while let Some(&b) = self.input.get(self.pos) {
86 if b == b' ' || b == b'\t' || b == b'\n' || b == b'\r' || b == b',' {
87 self.pos += 1;
88 } else {
89 break;
90 }
91 }
92 }
93
94 fn skip_whitespace(&mut self) {
95 while let Some(&b) = self.input.get(self.pos) {
96 if b == b' ' || b == b'\t' || b == b'\n' || b == b'\r' {
97 self.pos += 1;
98 } else {
99 break;
100 }
101 }
102 }
103
104 fn has_number(&self) -> bool {
106 match self.input.get(self.pos) {
107 Some(b'+') | Some(b'-') | Some(b'.') => true,
108 Some(b) if b.is_ascii_digit() => true,
109 _ => false,
110 }
111 }
112
113 fn parse_number(&mut self) -> Result<f32, SvgPathParseError> {
114 self.skip_whitespace_and_commas();
115 let start = self.pos;
116
117 if let Some(&b) = self.input.get(self.pos) {
119 if b == b'+' || b == b'-' {
120 self.pos += 1;
121 }
122 }
123
124 let mut has_digits = false;
125
126 while let Some(&b) = self.input.get(self.pos) {
128 if b.is_ascii_digit() {
129 self.pos += 1;
130 has_digits = true;
131 } else {
132 break;
133 }
134 }
135
136 if let Some(&b'.') = self.input.get(self.pos) {
138 self.pos += 1;
139 while let Some(&b) = self.input.get(self.pos) {
140 if b.is_ascii_digit() {
141 self.pos += 1;
142 has_digits = true;
143 } else {
144 break;
145 }
146 }
147 }
148
149 if !has_digits {
150 return Err(SvgPathParseError::ExpectedNumber { pos: start });
151 }
152
153 if let Some(&b) = self.input.get(self.pos) {
155 if b == b'e' || b == b'E' {
156 self.pos += 1;
157 if let Some(&b) = self.input.get(self.pos) {
158 if b == b'+' || b == b'-' {
159 self.pos += 1;
160 }
161 }
162 while let Some(&b) = self.input.get(self.pos) {
163 if b.is_ascii_digit() {
164 self.pos += 1;
165 } else {
166 break;
167 }
168 }
169 }
170 }
171
172 let s = core::str::from_utf8(&self.input[start..self.pos])
173 .map_err(|_| SvgPathParseError::ExpectedNumber { pos: start })?;
174 s.parse::<f32>()
175 .map_err(|_| SvgPathParseError::ExpectedNumber { pos: start })
176 }
177
178 fn parse_flag(&mut self) -> Result<bool, SvgPathParseError> {
179 self.skip_whitespace_and_commas();
180 match self.input.get(self.pos) {
181 Some(b'0') => {
182 self.pos += 1;
183 Ok(false)
184 }
185 Some(b'1') => {
186 self.pos += 1;
187 Ok(true)
188 }
189 _ => Err(SvgPathParseError::InvalidArcFlag { pos: self.pos }),
190 }
191 }
192
193 fn parse_coordinate_pair(&mut self) -> Result<(f32, f32), SvgPathParseError> {
194 let x = self.parse_number()?;
195 let y = self.parse_number()?;
196 Ok((x, y))
197 }
198
199 fn make_absolute(&self, x: f32, y: f32, relative: bool) -> SvgPoint {
200 if relative {
201 SvgPoint {
202 x: self.current.x + x,
203 y: self.current.y + y,
204 }
205 } else {
206 SvgPoint { x, y }
207 }
208 }
209
210 fn handle_line_to(&mut self, relative: bool, elements: &mut Vec<SvgPathElement>) -> Result<(), SvgPathParseError> {
211 let (x, y) = self.parse_coordinate_pair()?;
212 let end = self.make_absolute(x, y, relative);
213 elements.push(SvgPathElement::Line(SvgLine { start: self.current, end }));
214 self.current = end;
215 self.last_control = None;
216 Ok(())
217 }
218
219 fn handle_horizontal_to(&mut self, relative: bool, elements: &mut Vec<SvgPathElement>) -> Result<(), SvgPathParseError> {
220 let x = self.parse_number()?;
221 let abs_x = if relative { self.current.x + x } else { x };
222 let end = SvgPoint { x: abs_x, y: self.current.y };
223 elements.push(SvgPathElement::Line(SvgLine { start: self.current, end }));
224 self.current = end;
225 self.last_control = None;
226 Ok(())
227 }
228
229 fn handle_vertical_to(&mut self, relative: bool, elements: &mut Vec<SvgPathElement>) -> Result<(), SvgPathParseError> {
230 let y = self.parse_number()?;
231 let abs_y = if relative { self.current.y + y } else { y };
232 let end = SvgPoint { x: self.current.x, y: abs_y };
233 elements.push(SvgPathElement::Line(SvgLine { start: self.current, end }));
234 self.current = end;
235 self.last_control = None;
236 Ok(())
237 }
238
239 fn handle_cubic_to(&mut self, relative: bool, elements: &mut Vec<SvgPathElement>) -> Result<(), SvgPathParseError> {
240 let (c1x, c1y) = self.parse_coordinate_pair()?;
241 let (c2x, c2y) = self.parse_coordinate_pair()?;
242 let (ex, ey) = self.parse_coordinate_pair()?;
243 let ctrl_1 = self.make_absolute(c1x, c1y, relative);
244 let ctrl_2 = self.make_absolute(c2x, c2y, relative);
245 let end = self.make_absolute(ex, ey, relative);
246 elements.push(SvgPathElement::CubicCurve(SvgCubicCurve {
247 start: self.current, ctrl_1, ctrl_2, end,
248 }));
249 self.last_control = Some(ctrl_2);
250 self.current = end;
251 Ok(())
252 }
253
254 fn handle_smooth_cubic_to(&mut self, relative: bool, elements: &mut Vec<SvgPathElement>) -> Result<(), SvgPathParseError> {
255 let ctrl_1 = match self.last_control {
256 Some(lc) if matches!(self.last_command.to_ascii_uppercase(), b'C' | b'S') => {
257 SvgPoint {
258 x: 2.0 * self.current.x - lc.x,
259 y: 2.0 * self.current.y - lc.y,
260 }
261 }
262 _ => self.current,
263 };
264 let (c2x, c2y) = self.parse_coordinate_pair()?;
265 let (ex, ey) = self.parse_coordinate_pair()?;
266 let ctrl_2 = self.make_absolute(c2x, c2y, relative);
267 let end = self.make_absolute(ex, ey, relative);
268 elements.push(SvgPathElement::CubicCurve(SvgCubicCurve {
269 start: self.current, ctrl_1, ctrl_2, end,
270 }));
271 self.last_control = Some(ctrl_2);
272 self.current = end;
273 Ok(())
274 }
275
276 fn handle_quadratic_to(&mut self, relative: bool, elements: &mut Vec<SvgPathElement>) -> Result<(), SvgPathParseError> {
277 let (cx, cy) = self.parse_coordinate_pair()?;
278 let (ex, ey) = self.parse_coordinate_pair()?;
279 let ctrl = self.make_absolute(cx, cy, relative);
280 let end = self.make_absolute(ex, ey, relative);
281 elements.push(SvgPathElement::QuadraticCurve(SvgQuadraticCurve {
282 start: self.current, ctrl, end,
283 }));
284 self.last_control = Some(ctrl);
285 self.current = end;
286 Ok(())
287 }
288
289 fn handle_smooth_quadratic_to(&mut self, relative: bool, elements: &mut Vec<SvgPathElement>) -> Result<(), SvgPathParseError> {
290 let ctrl = match self.last_control {
291 Some(lc) if matches!(self.last_command.to_ascii_uppercase(), b'Q' | b'T') => {
292 SvgPoint {
293 x: 2.0 * self.current.x - lc.x,
294 y: 2.0 * self.current.y - lc.y,
295 }
296 }
297 _ => self.current,
298 };
299 let (ex, ey) = self.parse_coordinate_pair()?;
300 let end = self.make_absolute(ex, ey, relative);
301 elements.push(SvgPathElement::QuadraticCurve(SvgQuadraticCurve {
302 start: self.current, ctrl, end,
303 }));
304 self.last_control = Some(ctrl);
305 self.current = end;
306 Ok(())
307 }
308
309 fn handle_arc_to(&mut self, relative: bool, elements: &mut Vec<SvgPathElement>) -> Result<(), SvgPathParseError> {
310 let rx = self.parse_number()?.abs();
311 let ry = self.parse_number()?.abs();
312 let x_rotation = self.parse_number()?;
313 let large_arc = self.parse_flag()?;
314 let sweep = self.parse_flag()?;
315 let (ex, ey) = self.parse_coordinate_pair()?;
316 let end = self.make_absolute(ex, ey, relative);
317 arc_to_cubics(self.current, end, rx, ry, x_rotation, large_arc, sweep, elements);
318 self.current = end;
319 self.last_control = None;
320 Ok(())
321 }
322}
323
324#[must_use]
329pub fn parse_svg_path_d(d: &str) -> Result<SvgMultiPolygon, SvgPathParseError> {
330 let d = d.trim();
331 if d.is_empty() {
332 return Err(SvgPathParseError::EmptyPath);
333 }
334
335 let mut parser = PathParser::new(d.as_bytes());
336 let mut rings: Vec<SvgPath> = Vec::new();
337 let mut current_elements: Vec<SvgPathElement> = Vec::new();
338
339 parser.skip_whitespace();
340
341 while !parser.at_end() {
342 parser.skip_whitespace_and_commas();
343 if parser.at_end() {
344 break;
345 }
346
347 let b = parser.peek().unwrap();
348
349 let cmd = if b.is_ascii_alphabetic() {
351 parser.pos += 1;
352 b
353 } else if parser.last_command != 0 {
354 match parser.last_command {
356 b'M' => b'L',
357 b'm' => b'l',
358 other => other,
359 }
360 } else {
361 return Err(SvgPathParseError::UnexpectedChar {
362 pos: parser.pos,
363 ch: b as char,
364 });
365 };
366
367 let relative = cmd.is_ascii_lowercase();
368 let cmd_upper = cmd.to_ascii_uppercase();
369
370 match cmd_upper {
371 b'M' => {
372 if !current_elements.is_empty() {
374 rings.push(SvgPath {
375 items: SvgPathElementVec::from_vec(core::mem::take(&mut current_elements)),
376 });
377 }
378 let (x, y) = parser.parse_coordinate_pair()?;
379 let pt = parser.make_absolute(x, y, relative);
380 parser.current = pt;
381 parser.subpath_start = pt;
382 parser.last_control = None;
383 parser.last_command = cmd;
384 }
385 b'L' => {
386 parser.handle_line_to(relative, &mut current_elements)?;
387 parser.last_command = cmd;
388 }
389 b'H' => {
390 parser.handle_horizontal_to(relative, &mut current_elements)?;
391 parser.last_command = cmd;
392 }
393 b'V' => {
394 parser.handle_vertical_to(relative, &mut current_elements)?;
395 parser.last_command = cmd;
396 }
397 b'C' => {
398 parser.handle_cubic_to(relative, &mut current_elements)?;
399 parser.last_command = cmd;
400 }
401 b'S' => {
402 parser.handle_smooth_cubic_to(relative, &mut current_elements)?;
403 parser.last_command = cmd;
404 }
405 b'Q' => {
406 parser.handle_quadratic_to(relative, &mut current_elements)?;
407 parser.last_command = cmd;
408 }
409 b'T' => {
410 parser.handle_smooth_quadratic_to(relative, &mut current_elements)?;
411 parser.last_command = cmd;
412 }
413 b'A' => {
414 parser.handle_arc_to(relative, &mut current_elements)?;
415 parser.last_command = cmd;
416 }
417 b'Z' => {
418 let dx = parser.current.x - parser.subpath_start.x;
420 let dy = parser.current.y - parser.subpath_start.y;
421 if dx * dx + dy * dy > CLOSEPATH_EPSILON * CLOSEPATH_EPSILON {
422 current_elements.push(SvgPathElement::Line(SvgLine {
423 start: parser.current,
424 end: parser.subpath_start,
425 }));
426 }
427 parser.current = parser.subpath_start;
428 parser.last_control = None;
429 parser.last_command = cmd;
430
431 if !current_elements.is_empty() {
433 rings.push(SvgPath {
434 items: SvgPathElementVec::from_vec(core::mem::take(&mut current_elements)),
435 });
436 }
437 }
438 _ => {
439 return Err(SvgPathParseError::UnexpectedChar {
440 pos: parser.pos - 1,
441 ch: cmd as char,
442 });
443 }
444 }
445
446 if cmd_upper != b'M' && cmd_upper != b'Z' {
449 loop {
450 parser.skip_whitespace_and_commas();
451 if parser.at_end() {
452 break;
453 }
454 let next = parser.peek().unwrap();
455 if next.is_ascii_alphabetic() {
456 break; }
458 if !parser.has_number() {
459 break;
460 }
461
462 match cmd_upper {
464 b'L' => parser.handle_line_to(relative, &mut current_elements)?,
465 b'H' => parser.handle_horizontal_to(relative, &mut current_elements)?,
466 b'V' => parser.handle_vertical_to(relative, &mut current_elements)?,
467 b'C' => parser.handle_cubic_to(relative, &mut current_elements)?,
468 b'S' => parser.handle_smooth_cubic_to(relative, &mut current_elements)?,
469 b'Q' => parser.handle_quadratic_to(relative, &mut current_elements)?,
470 b'T' => parser.handle_smooth_quadratic_to(relative, &mut current_elements)?,
471 b'A' => parser.handle_arc_to(relative, &mut current_elements)?,
472 _ => break,
473 }
474 }
475 }
476 }
477
478 if !current_elements.is_empty() {
480 rings.push(SvgPath {
481 items: SvgPathElementVec::from_vec(current_elements),
482 });
483 }
484
485 Ok(SvgMultiPolygon {
486 rings: SvgPathVec::from_vec(rings),
487 })
488}
489
490fn arc_to_cubics(
494 start: SvgPoint,
495 end: SvgPoint,
496 mut rx: f32,
497 mut ry: f32,
498 x_rotation_deg: f32,
499 large_arc: bool,
500 sweep: bool,
501 out: &mut Vec<SvgPathElement>,
502) {
503 if (start.x - end.x).abs() < POINT_EPSILON && (start.y - end.y).abs() < POINT_EPSILON {
505 return;
506 }
507 if rx < POINT_EPSILON || ry < POINT_EPSILON {
508 out.push(SvgPathElement::Line(SvgLine { start, end }));
509 return;
510 }
511
512 let phi = x_rotation_deg.to_radians();
513 let cos_phi = phi.cos();
514 let sin_phi = phi.sin();
515
516 let dx = (start.x - end.x) / 2.0;
518 let dy = (start.y - end.y) / 2.0;
519 let x1p = cos_phi * dx + sin_phi * dy;
520 let y1p = -sin_phi * dx + cos_phi * dy;
521
522 let x1p2 = x1p * x1p;
524 let y1p2 = y1p * y1p;
525 let mut rx2 = rx * rx;
526 let mut ry2 = ry * ry;
527
528 let lambda = x1p2 / rx2 + y1p2 / ry2;
529 if lambda > 1.0 {
530 let sqrt_lambda = lambda.sqrt();
531 rx *= sqrt_lambda;
532 ry *= sqrt_lambda;
533 rx2 = rx * rx;
534 ry2 = ry * ry;
535 }
536
537 let num = (rx2 * ry2 - rx2 * y1p2 - ry2 * x1p2).max(0.0);
538 let den = rx2 * y1p2 + ry2 * x1p2;
539 let sq = if den > 0.0 {
540 (num / den).sqrt()
541 } else {
542 0.0
543 };
544
545 let sign = if large_arc == sweep { -1.0 } else { 1.0 };
546 let cxp = sign * sq * (rx * y1p / ry);
547 let cyp = sign * sq * -(ry * x1p / rx);
548
549 let mx = (start.x + end.x) / 2.0;
551 let my = (start.y + end.y) / 2.0;
552 let cx = cos_phi * cxp - sin_phi * cyp + mx;
553 let cy = sin_phi * cxp + cos_phi * cyp + my;
554
555 let theta1 = angle_between(1.0, 0.0, (x1p - cxp) / rx, (y1p - cyp) / ry);
557 let mut dtheta = angle_between(
558 (x1p - cxp) / rx,
559 (y1p - cyp) / ry,
560 (-x1p - cxp) / rx,
561 (-y1p - cyp) / ry,
562 );
563
564 if !sweep && dtheta > 0.0 {
565 dtheta -= core::f32::consts::TAU;
566 } else if sweep && dtheta < 0.0 {
567 dtheta += core::f32::consts::TAU;
568 }
569
570 let n_segs = (dtheta.abs() / (core::f32::consts::FRAC_PI_2 + ARC_SPLIT_FUDGE)).ceil() as usize;
572 let n_segs = n_segs.max(1);
573 let seg_angle = dtheta / n_segs as f32;
574
575 let mut prev = start;
576 for i in 0..n_segs {
577 let t1 = theta1 + seg_angle * i as f32;
578 let t2 = theta1 + seg_angle * (i + 1) as f32;
579
580 let (c1, c2, ep) =
581 arc_segment_to_cubic(cx, cy, rx, ry, cos_phi, sin_phi, t1, t2);
582
583 let seg_end = if i + 1 == n_segs { end } else { ep };
584 out.push(SvgPathElement::CubicCurve(SvgCubicCurve {
585 start: prev,
586 ctrl_1: c1,
587 ctrl_2: c2,
588 end: seg_end,
589 }));
590 prev = seg_end;
591 }
592}
593
594fn angle_between(ux: f32, uy: f32, vx: f32, vy: f32) -> f32 {
596 let dot = ux * vx + uy * vy;
597 let len = ((ux * ux + uy * uy) * (vx * vx + vy * vy)).sqrt();
598 if len < ZERO_LENGTH_EPSILON {
599 return 0.0;
600 }
601 let cos_val = (dot / len).clamp(-1.0, 1.0);
602 let angle = cos_val.acos();
603 if ux * vy - uy * vx < 0.0 {
604 -angle
605 } else {
606 angle
607 }
608}
609
610fn arc_segment_to_cubic(
612 cx: f32,
613 cy: f32,
614 rx: f32,
615 ry: f32,
616 cos_phi: f32,
617 sin_phi: f32,
618 theta1: f32,
619 theta2: f32,
620) -> (SvgPoint, SvgPoint, SvgPoint) {
621 let alpha = 4.0 / 3.0 * ((theta2 - theta1) / 4.0).tan();
622
623 let cos1 = theta1.cos();
624 let sin1 = theta1.sin();
625 let cos2 = theta2.cos();
626 let sin2 = theta2.sin();
627
628 let dx1 = rx * (cos1 - alpha * sin1);
630 let dy1 = ry * (sin1 + alpha * cos1);
631 let dx2 = rx * (cos2 + alpha * sin2);
633 let dy2 = ry * (sin2 - alpha * cos2);
634 let dx3 = rx * cos2;
636 let dy3 = ry * sin2;
637
638 let c1 = SvgPoint {
639 x: cos_phi * dx1 - sin_phi * dy1 + cx,
640 y: sin_phi * dx1 + cos_phi * dy1 + cy,
641 };
642 let c2 = SvgPoint {
643 x: cos_phi * dx2 - sin_phi * dy2 + cx,
644 y: sin_phi * dx2 + cos_phi * dy2 + cy,
645 };
646 let ep = SvgPoint {
647 x: cos_phi * dx3 - sin_phi * dy3 + cx,
648 y: sin_phi * dx3 + cos_phi * dy3 + cy,
649 };
650
651 (c1, c2, ep)
652}
653
654#[must_use]
658pub fn svg_circle_to_paths(cx: f32, cy: f32, r: f32) -> SvgPath {
659 let k = r * KAPPA;
660
661 let elements = vec![
662 SvgPathElement::CubicCurve(SvgCubicCurve {
664 start: SvgPoint { x: cx, y: cy - r },
665 ctrl_1: SvgPoint {
666 x: cx + k,
667 y: cy - r,
668 },
669 ctrl_2: SvgPoint {
670 x: cx + r,
671 y: cy - k,
672 },
673 end: SvgPoint { x: cx + r, y: cy },
674 }),
675 SvgPathElement::CubicCurve(SvgCubicCurve {
677 start: SvgPoint { x: cx + r, y: cy },
678 ctrl_1: SvgPoint {
679 x: cx + r,
680 y: cy + k,
681 },
682 ctrl_2: SvgPoint {
683 x: cx + k,
684 y: cy + r,
685 },
686 end: SvgPoint { x: cx, y: cy + r },
687 }),
688 SvgPathElement::CubicCurve(SvgCubicCurve {
690 start: SvgPoint { x: cx, y: cy + r },
691 ctrl_1: SvgPoint {
692 x: cx - k,
693 y: cy + r,
694 },
695 ctrl_2: SvgPoint {
696 x: cx - r,
697 y: cy + k,
698 },
699 end: SvgPoint { x: cx - r, y: cy },
700 }),
701 SvgPathElement::CubicCurve(SvgCubicCurve {
703 start: SvgPoint { x: cx - r, y: cy },
704 ctrl_1: SvgPoint {
705 x: cx - r,
706 y: cy - k,
707 },
708 ctrl_2: SvgPoint {
709 x: cx - k,
710 y: cy - r,
711 },
712 end: SvgPoint { x: cx, y: cy - r },
713 }),
714 ];
715
716 SvgPath {
717 items: SvgPathElementVec::from_vec(elements),
718 }
719}
720
721#[must_use]
726pub fn svg_rect_to_path(x: f32, y: f32, w: f32, h: f32, rx: f32, ry: f32) -> SvgPath {
727 let rx = rx.min(w / 2.0);
728 let ry = ry.min(h / 2.0);
729
730 if rx < CLOSEPATH_EPSILON && ry < CLOSEPATH_EPSILON {
731 let tl = SvgPoint { x, y };
733 let tr = SvgPoint { x: x + w, y };
734 let br = SvgPoint { x: x + w, y: y + h };
735 let bl = SvgPoint { x, y: y + h };
736
737 let elements = vec![
738 SvgPathElement::Line(SvgLine { start: tl, end: tr }),
739 SvgPathElement::Line(SvgLine { start: tr, end: br }),
740 SvgPathElement::Line(SvgLine {
741 start: br,
742 end: bl,
743 }),
744 SvgPathElement::Line(SvgLine { start: bl, end: tl }),
745 ];
746
747 return SvgPath {
748 items: SvgPathElementVec::from_vec(elements),
749 };
750 }
751
752 let kx = rx * KAPPA;
754 let ky = ry * KAPPA;
755
756 let mut elements = Vec::with_capacity(8);
757
758 elements.push(SvgPathElement::Line(SvgLine {
760 start: SvgPoint { x: x + rx, y },
761 end: SvgPoint { x: x + w - rx, y },
762 }));
763 elements.push(SvgPathElement::CubicCurve(SvgCubicCurve {
765 start: SvgPoint { x: x + w - rx, y },
766 ctrl_1: SvgPoint {
767 x: x + w - rx + kx,
768 y,
769 },
770 ctrl_2: SvgPoint {
771 x: x + w,
772 y: y + ry - ky,
773 },
774 end: SvgPoint {
775 x: x + w,
776 y: y + ry,
777 },
778 }));
779 elements.push(SvgPathElement::Line(SvgLine {
781 start: SvgPoint {
782 x: x + w,
783 y: y + ry,
784 },
785 end: SvgPoint {
786 x: x + w,
787 y: y + h - ry,
788 },
789 }));
790 elements.push(SvgPathElement::CubicCurve(SvgCubicCurve {
792 start: SvgPoint {
793 x: x + w,
794 y: y + h - ry,
795 },
796 ctrl_1: SvgPoint {
797 x: x + w,
798 y: y + h - ry + ky,
799 },
800 ctrl_2: SvgPoint {
801 x: x + w - rx + kx,
802 y: y + h,
803 },
804 end: SvgPoint {
805 x: x + w - rx,
806 y: y + h,
807 },
808 }));
809 elements.push(SvgPathElement::Line(SvgLine {
811 start: SvgPoint {
812 x: x + w - rx,
813 y: y + h,
814 },
815 end: SvgPoint { x: x + rx, y: y + h },
816 }));
817 elements.push(SvgPathElement::CubicCurve(SvgCubicCurve {
819 start: SvgPoint { x: x + rx, y: y + h },
820 ctrl_1: SvgPoint {
821 x: x + rx - kx,
822 y: y + h,
823 },
824 ctrl_2: SvgPoint {
825 x,
826 y: y + h - ry + ky,
827 },
828 end: SvgPoint { x, y: y + h - ry },
829 }));
830 elements.push(SvgPathElement::Line(SvgLine {
832 start: SvgPoint { x, y: y + h - ry },
833 end: SvgPoint { x, y: y + ry },
834 }));
835 elements.push(SvgPathElement::CubicCurve(SvgCubicCurve {
837 start: SvgPoint { x, y: y + ry },
838 ctrl_1: SvgPoint {
839 x,
840 y: y + ry - ky,
841 },
842 ctrl_2: SvgPoint {
843 x: x + rx - kx,
844 y,
845 },
846 end: SvgPoint { x: x + rx, y },
847 }));
848
849 SvgPath {
850 items: SvgPathElementVec::from_vec(elements),
851 }
852}