1use std::{
2 cell::{RefCell, RefMut},
3 f32::consts::PI,
4 slice,
5};
6
7use crate::geometry::{Position, Transform2D, Vector};
8#[cfg(feature = "textlayout")]
9use rustybuzz::ttf_parser;
10
11mod cache;
12pub use cache::{Convexity, PathCache};
13
14const KAPPA90: f32 = 0.552_284_8; #[inline]
18fn add3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
19 std::array::from_fn(|i| a[i] + b[i])
20}
21
22#[inline]
23fn sub3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
24 std::array::from_fn(|i| a[i] - b[i])
25}
26
27#[inline]
28fn scale3(v: [f32; 3], s: f32) -> [f32; 3] {
29 v.map(|c| c * s)
30}
31
32#[inline]
33fn dot3(a: [f32; 3], b: [f32; 3]) -> f32 {
34 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
35}
36
37fn cross3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
38 [
39 a[1] * b[2] - a[2] * b[1],
40 a[2] * b[0] - a[0] * b[2],
41 a[0] * b[1] - a[1] * b[0],
42 ]
43}
44
45fn normalize3(v: [f32; 3]) -> [f32; 3] {
46 let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
47 if len > 1e-6 {
48 v.map(|c| c / len)
49 } else {
50 [0.0, 0.0, 1.0]
51 }
52}
53
54fn build_basis(n: &[f32; 3]) -> ([f32; 3], [f32; 3]) {
55 let t = if n[0].abs() < 0.9 {
56 [1.0, 0.0, 0.0]
57 } else {
58 [0.0, 1.0, 0.0]
59 };
60 let u = normalize3(cross3(t, *n));
61 let v = cross3(*n, u);
62 (u, v)
63}
64
65#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Default)]
69pub enum Solidity {
70 #[default]
72 Solid = 1,
73 Hole = 2,
75}
76
77#[derive(Copy, Clone, Debug, Eq, PartialEq)]
78#[repr(u8)]
79#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
80pub enum PackedVerb {
81 MoveTo,
82 LineTo,
83 BezierTo,
84 Solid,
85 Hole,
86 Close,
87}
88
89#[derive(Copy, Clone, Debug)]
92pub enum Verb<const N: usize = 2> {
93 MoveTo([f32; N]),
95 LineTo([f32; N]),
98 BezierTo([f32; N], [f32; N], [f32; N]),
101 Solid,
103 Hole,
105 Close,
107}
108
109impl<const N: usize> Verb<N> {
110 fn num_coordinates(&self) -> usize {
111 match *self {
112 Self::MoveTo(..) => 1,
113 Self::LineTo(..) => 1,
114 Self::BezierTo(..) => 3,
115 Self::Solid => 0,
116 Self::Hole => 0,
117 Self::Close => 0,
118 }
119 }
120
121 fn from_packed(packed: &PackedVerb, coords: &[[f32; N]]) -> Self {
122 match *packed {
123 PackedVerb::MoveTo => Self::MoveTo(coords[0]),
124 PackedVerb::LineTo => Self::LineTo(coords[0]),
125 PackedVerb::BezierTo => Self::BezierTo(coords[0], coords[1], coords[2]),
126 PackedVerb::Solid => Self::Solid,
127 PackedVerb::Hole => Self::Hole,
128 PackedVerb::Close => Self::Close,
129 }
130 }
131}
132
133#[derive(Clone, Debug)]
156#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
157pub struct Path<const N: usize = 2> {
158 verbs: Vec<PackedVerb>,
159 coords: Vec<[f32; N]>,
160 last_pos: [f32; N],
161 dist_tol: f32,
162 #[cfg_attr(feature = "serde", serde(skip))]
163 pub(crate) cache: RefCell<Option<(u64, PathCache)>>,
164}
165
166impl<const N: usize> Default for Path<N> {
167 fn default() -> Self {
168 Self {
169 verbs: Vec::new(),
170 coords: Vec::new(),
171 last_pos: [0.0; N],
172 dist_tol: 0.01,
173 cache: RefCell::new(None),
174 }
175 }
176}
177
178impl<const N: usize> Path<N> {
179 pub fn new() -> Self {
181 Self::default()
182 }
183
184 pub fn size(&self) -> usize {
186 std::mem::size_of::<PackedVerb>() * self.verbs.len() + std::mem::size_of::<[f32; N]>() * self.coords.len()
187 }
188
189 pub fn is_empty(&self) -> bool {
191 self.verbs.is_empty()
192 }
193
194 pub fn set_distance_tolerance(&mut self, value: f32) {
196 self.dist_tol = value;
197 }
198
199 pub fn verbs(&self) -> PathIter<'_, N> {
201 PathIter {
202 verbs: self.verbs.iter(),
203 coords: &self.coords,
204 }
205 }
206
207 pub fn move_to(&mut self, pos: impl Into<[f32; N]>) {
209 let pos = pos.into();
210 self.append(&[PackedVerb::MoveTo], &[pos]);
211 }
212
213 pub fn line_to(&mut self, pos: impl Into<[f32; N]>) {
215 let pos = pos.into();
216 self.append(&[PackedVerb::LineTo], &[pos]);
217 }
218
219 pub fn bezier_to(
221 &mut self,
222 control1: impl Into<[f32; N]>,
223 control2: impl Into<[f32; N]>,
224 pos: impl Into<[f32; N]>,
225 ) {
226 self.append(&[PackedVerb::BezierTo], &[control1.into(), control2.into(), pos.into()]);
227 }
228
229 pub fn quad_to(&mut self, control: impl Into<[f32; N]>, pos: impl Into<[f32; N]>) {
231 let control = control.into();
232 let pos = pos.into();
233 let pos0 = self.last_pos;
234 let pos1 = std::array::from_fn(|i| pos0[i] + (control[i] - pos0[i]) * (2.0 / 3.0));
235 let pos2 = std::array::from_fn(|i| pos[i] + (control[i] - pos[i]) * (2.0 / 3.0));
236 self.append(&[PackedVerb::BezierTo], &[pos1, pos2, pos]);
237 }
238
239 pub fn close(&mut self) {
241 self.append(&[PackedVerb::Close], &[]);
242 }
243
244 pub fn solidity(&mut self, solidity: Solidity) {
246 match solidity {
247 Solidity::Solid => self.append(&[PackedVerb::Solid], &[]),
248 Solidity::Hole => self.append(&[PackedVerb::Hole], &[]),
249 }
250 }
251
252 pub fn map<const M: usize>(&self, f: impl Fn([f32; N]) -> [f32; M]) -> Path<M> {
266 Path {
267 verbs: self.verbs.clone(),
268 coords: self.coords.iter().map(|c| f(*c)).collect(),
269 last_pos: f(self.last_pos),
270 dist_tol: self.dist_tol,
271 cache: RefCell::new(None),
272 }
273 }
274
275 fn append(&mut self, verbs: &[PackedVerb], coords: &[[f32; N]]) {
276 if !coords.is_empty() {
277 self.last_pos = coords[coords.len() - 1];
278 }
279
280 self.verbs.extend_from_slice(verbs);
281 self.coords.extend_from_slice(coords);
282 }
283}
284
285impl Path<2> {
286 pub(crate) fn cache<'a>(&'a self, transform: &Transform2D, tess_tol: f32, dist_tol: f32) -> RefMut<'a, PathCache> {
287 let key = transform.cache_key();
288
289 let needs_rebuild = if let Some((transform_cache_key, _cache)) = &*self.cache.borrow() {
290 key != *transform_cache_key
291 } else {
292 true
293 };
294
295 if needs_rebuild {
296 let path_cache = PathCache::new(self.verbs(), transform, tess_tol, dist_tol);
297 *self.cache.borrow_mut() = Some((key, path_cache));
298 }
299
300 RefMut::map(self.cache.borrow_mut(), |cache| &mut cache.as_mut().unwrap().1)
301 }
302
303 pub fn arc(&mut self, center: impl Into<[f32; 2]>, r: f32, a0: f32, a1: f32, dir: Solidity) {
307 let [cx, cy] = center.into();
308 let cpos = Position { x: cx, y: cy };
309
310 let mut da = a1 - a0;
311
312 if dir == Solidity::Hole {
313 if da.abs() >= PI * 2.0 {
314 da = PI * 2.0;
315 } else {
316 while da < 0.0 {
317 da += PI * 2.0
318 }
319 }
320 } else if da.abs() >= PI * 2.0 {
321 da = -PI * 2.0;
322 } else {
323 while da > 0.0 {
324 da -= PI * 2.0
325 }
326 }
327
328 let ndivs = ((da.abs() / (PI * 0.5) + 0.5) as i32).clamp(1, 5);
330 let hda = (da / ndivs as f32) / 2.0;
331 let mut kappa = (4.0 / 3.0 * (1.0 - hda.cos()) / hda.sin()).abs();
332
333 let mut commands = Vec::with_capacity(ndivs as usize);
334
335 if dir == Solidity::Solid {
336 kappa = -kappa;
337 }
338
339 let (mut ppos, mut ptanpos) = (Position { x: 0.0, y: 0.0 }, Vector::zero());
340
341 let mut pos_coords: Vec<Position> = Vec::with_capacity(ndivs as usize);
342
343 for i in 0..=ndivs {
344 let a = a0 + da * (i as f32 / ndivs as f32);
345 let dpos = Vector::from_angle(a);
346 let pos = cpos + dpos * r;
347 let tanpos = -dpos.orthogonal() * r * kappa;
348
349 if i == 0 {
350 let first_move = if self.verbs.is_empty() {
351 PackedVerb::MoveTo
352 } else {
353 PackedVerb::LineTo
354 };
355
356 commands.push(first_move);
357 pos_coords.push(pos);
358 } else {
359 commands.push(PackedVerb::BezierTo);
360 pos_coords.extend_from_slice(&[ppos + ptanpos, pos - tanpos, pos]);
361 }
362
363 ppos = pos;
364 ptanpos = tanpos;
365 }
366
367 let coords: Vec<[f32; 2]> = pos_coords.into_iter().map(Into::into).collect();
368 self.append(&commands, &coords);
369 }
370
371 pub fn arc_to(&mut self, pos1: impl Into<[f32; 2]>, pos2: impl Into<[f32; 2]>, radius: f32) {
373 if self.verbs.is_empty() {
374 return;
375 }
376
377 let pos0: Position = self.last_pos.into();
378 let pos1: Position = pos1.into().into();
379 let pos2: Position = pos2.into().into();
380
381 if Position::equals(pos0, pos1, self.dist_tol)
382 || Position::equals(pos1, pos2, self.dist_tol)
383 || Position::segment_distance(pos1, pos0, pos2) < self.dist_tol * self.dist_tol
384 || radius < self.dist_tol
385 {
386 self.line_to(<[f32; 2]>::from(pos1));
387 return;
388 }
389
390 let mut dpos0 = pos0 - pos1;
391 let mut dpos1 = pos2 - pos1;
392
393 dpos0.normalize();
394 dpos1.normalize();
395
396 let a = dpos0.dot(dpos1).acos();
397 let d = radius / (a / 2.0).tan();
398
399 if d > 10000.0 {
400 self.line_to(<[f32; 2]>::from(pos1));
401 return;
402 }
403
404 let (cpos, a0, a1, dir);
405
406 if dpos0.cross(dpos1) > 0.0 {
407 cpos = pos1 + dpos0 * d + dpos0.orthogonal() * radius;
408 a0 = dpos0.angle();
409 a1 = (-dpos1).angle();
410 dir = Solidity::Hole;
411 } else {
412 cpos = pos1 + dpos0 * d - dpos0.orthogonal() * radius;
413 a0 = (-dpos0).angle();
414 a1 = dpos1.angle();
415 dir = Solidity::Solid;
416 }
417
418 self.arc(<[f32; 2]>::from(cpos), radius, a0 + PI / 2.0, a1 + PI / 2.0, dir);
419 }
420
421 pub fn rect(&mut self, pos: impl Into<[f32; 2]>, size: impl Into<[f32; 2]>) {
423 let [x, y] = pos.into();
424 let [w, h] = size.into();
425 self.append(
426 &[
427 PackedVerb::MoveTo,
428 PackedVerb::LineTo,
429 PackedVerb::LineTo,
430 PackedVerb::LineTo,
431 PackedVerb::Close,
432 ],
433 &[[x, y], [x, y + h], [x + w, y + h], [x + w, y]],
434 );
435 }
436
437 pub fn rounded_rect(&mut self, pos: impl Into<[f32; 2]>, size: impl Into<[f32; 2]>, r: f32) {
439 let pos = pos.into();
440 let size = size.into();
441 self.rounded_rect_varying(pos, size, r, r, r, r);
442 }
443
444 pub fn rounded_rect_varying(
446 &mut self,
447 pos: impl Into<[f32; 2]>,
448 size: impl Into<[f32; 2]>,
449 rad_top_left: f32,
450 rad_top_right: f32,
451 rad_bottom_right: f32,
452 rad_bottom_left: f32,
453 ) {
454 let [x, y] = pos.into();
455 let [w, h] = size.into();
456
457 if rad_top_left < 0.1 && rad_top_right < 0.1 && rad_bottom_right < 0.1 && rad_bottom_left < 0.1 {
458 self.rect([x, y], [w, h]);
459 } else {
460 let halfw = w.abs() * 0.5;
461 let halfh = h.abs() * 0.5;
462
463 let rx_bl = rad_bottom_left.min(halfw) * w.signum();
464 let ry_bl = rad_bottom_left.min(halfh) * h.signum();
465
466 let rx_br = rad_bottom_right.min(halfw) * w.signum();
467 let ry_br = rad_bottom_right.min(halfh) * h.signum();
468
469 let rx_tr = rad_top_right.min(halfw) * w.signum();
470 let ry_tr = rad_top_right.min(halfh) * h.signum();
471
472 let rx_tl = rad_top_left.min(halfw) * w.signum();
473 let ry_tl = rad_top_left.min(halfh) * h.signum();
474
475 self.append(
476 &[
477 PackedVerb::MoveTo,
478 PackedVerb::LineTo,
479 PackedVerb::BezierTo,
480 PackedVerb::LineTo,
481 PackedVerb::BezierTo,
482 PackedVerb::LineTo,
483 PackedVerb::BezierTo,
484 PackedVerb::LineTo,
485 PackedVerb::BezierTo,
486 PackedVerb::Close,
487 ],
488 &[
489 [x, y + ry_tl],
490 [x, y + h - ry_bl],
491 [x, y + h - ry_bl * (1.0 - KAPPA90)],
492 [x + rx_bl * (1.0 - KAPPA90), y + h],
493 [x + rx_bl, y + h],
494 [x + w - rx_br, y + h],
495 [x + w - rx_br * (1.0 - KAPPA90), y + h],
496 [x + w, y + h - ry_br * (1.0 - KAPPA90)],
497 [x + w, y + h - ry_br],
498 [x + w, y + ry_tr],
499 [x + w, y + ry_tr * (1.0 - KAPPA90)],
500 [x + w - rx_tr * (1.0 - KAPPA90), y],
501 [x + w - rx_tr, y],
502 [x + rx_tl, y],
503 [x + rx_tl * (1.0 - KAPPA90), y],
504 [x, y + ry_tl * (1.0 - KAPPA90)],
505 [x, y + ry_tl],
506 ],
507 );
508 }
509 }
510
511 pub fn ellipse(&mut self, center: impl Into<[f32; 2]>, radii: impl Into<[f32; 2]>) {
513 let [cx, cy] = center.into();
514 let [rx, ry] = radii.into();
515 let kx = rx * KAPPA90;
516 let ky = ry * KAPPA90;
517 self.append(
518 &[
519 PackedVerb::MoveTo,
520 PackedVerb::BezierTo,
521 PackedVerb::BezierTo,
522 PackedVerb::BezierTo,
523 PackedVerb::BezierTo,
524 PackedVerb::Close,
525 ],
526 &[
527 [cx - rx, cy],
528 [cx - rx, cy + ky],
529 [cx - kx, cy + ry],
530 [cx, cy + ry],
531 [cx + kx, cy + ry],
532 [cx + rx, cy + ky],
533 [cx + rx, cy],
534 [cx + rx, cy - ky],
535 [cx + kx, cy - ry],
536 [cx, cy - ry],
537 [cx - kx, cy - ry],
538 [cx - rx, cy - ky],
539 [cx - rx, cy],
540 ],
541 );
542 }
543
544 pub fn circle(&mut self, center: impl Into<[f32; 2]>, r: f32) {
546 let center = center.into();
547 self.ellipse(center, [r, r]);
548 }
549}
550
551pub trait PathExt3d {
556 fn circle(&mut self, center: impl Into<[f32; 3]>, normal: impl Into<[f32; 3]>, r: f32);
558
559 fn ellipse(&mut self, center: impl Into<[f32; 3]>, normal: impl Into<[f32; 3]>, radii: impl Into<[f32; 2]>);
562
563 fn arc(
567 &mut self,
568 center: impl Into<[f32; 3]>,
569 normal: impl Into<[f32; 3]>,
570 r: f32,
571 a0: f32,
572 a1: f32,
573 dir: Solidity,
574 );
575
576 fn arc_to(&mut self, pos1: impl Into<[f32; 3]>, pos2: impl Into<[f32; 3]>, radius: f32);
579}
580
581impl PathExt3d for Path<3> {
582 fn circle(&mut self, center: impl Into<[f32; 3]>, normal: impl Into<[f32; 3]>, r: f32) {
583 self.ellipse(center, normal, [r, r]);
584 }
585
586 fn ellipse(&mut self, center: impl Into<[f32; 3]>, normal: impl Into<[f32; 3]>, radii: impl Into<[f32; 2]>) {
587 let center = center.into();
588 let [rx, ry] = radii.into();
589 let normal = normalize3(normal.into());
590 let (u, v) = build_basis(&normal);
591
592 let kx = rx * KAPPA90;
593 let ky = ry * KAPPA90;
594
595 let p = |xu: f32, yv: f32| -> [f32; 3] { add3(center, add3(scale3(u, xu), scale3(v, yv))) };
596
597 self.append(
598 &[
599 PackedVerb::MoveTo,
600 PackedVerb::BezierTo,
601 PackedVerb::BezierTo,
602 PackedVerb::BezierTo,
603 PackedVerb::BezierTo,
604 PackedVerb::Close,
605 ],
606 &[
607 p(-rx, 0.0),
608 p(-rx, ky),
609 p(-kx, ry),
610 p(0.0, ry),
611 p(kx, ry),
612 p(rx, ky),
613 p(rx, 0.0),
614 p(rx, -ky),
615 p(kx, -ry),
616 p(0.0, -ry),
617 p(-kx, -ry),
618 p(-rx, -ky),
619 p(-rx, 0.0),
620 ],
621 );
622 }
623
624 fn arc(
625 &mut self,
626 center: impl Into<[f32; 3]>,
627 normal: impl Into<[f32; 3]>,
628 r: f32,
629 a0: f32,
630 a1: f32,
631 dir: Solidity,
632 ) {
633 let center = center.into();
634 let normal = normalize3(normal.into());
635 let (u, v) = build_basis(&normal);
636
637 let mut da = a1 - a0;
638
639 if dir == Solidity::Hole {
640 if da.abs() >= PI * 2.0 {
641 da = PI * 2.0;
642 } else {
643 while da < 0.0 {
644 da += PI * 2.0
645 }
646 }
647 } else if da.abs() >= PI * 2.0 {
648 da = -PI * 2.0;
649 } else {
650 while da > 0.0 {
651 da -= PI * 2.0
652 }
653 }
654
655 let ndivs = ((da.abs() / (PI * 0.5) + 0.5) as i32).clamp(1, 5);
656 let hda = (da / ndivs as f32) / 2.0;
657 let mut kappa = (4.0 / 3.0 * (1.0 - hda.cos()) / hda.sin()).abs();
658
659 if dir == Solidity::Solid {
660 kappa = -kappa;
661 }
662
663 let mut commands = Vec::with_capacity(ndivs as usize);
664 let mut pos_coords: Vec<[f32; 3]> = Vec::with_capacity(ndivs as usize * 3 + 1);
665 let (mut ppos, mut ptanpos) = ([0.0; 3], [0.0; 3]);
666
667 for i in 0..=ndivs {
668 let a = a0 + da * (i as f32 / ndivs as f32);
669 let dpos = add3(scale3(u, a.cos()), scale3(v, a.sin()));
670 let pos = add3(center, scale3(dpos, r));
671 let tanpos = scale3(cross3(normal, dpos), r * kappa);
672
673 if i == 0 {
674 let first_move = if self.verbs.is_empty() {
675 PackedVerb::MoveTo
676 } else {
677 PackedVerb::LineTo
678 };
679 commands.push(first_move);
680 pos_coords.push(pos);
681 } else {
682 commands.push(PackedVerb::BezierTo);
683 pos_coords.extend_from_slice(&[add3(ppos, ptanpos), sub3(pos, tanpos), pos]);
684 }
685
686 ppos = pos;
687 ptanpos = tanpos;
688 }
689
690 self.append(&commands, &pos_coords);
691 }
692
693 fn arc_to(&mut self, pos1: impl Into<[f32; 3]>, pos2: impl Into<[f32; 3]>, radius: f32) {
694 if self.verbs.is_empty() {
695 return;
696 }
697
698 let pos0 = self.last_pos;
699 let pos1 = pos1.into();
700 let pos2 = pos2.into();
701
702 let d0 = sub3(pos0, pos1);
703 let d1 = sub3(pos2, pos1);
704
705 if dot3(d0, d0) < self.dist_tol * self.dist_tol || dot3(d1, d1) < self.dist_tol * self.dist_tol {
706 self.line_to(pos1);
707 return;
708 }
709
710 let raw_normal = cross3(d0, d1);
711 if dot3(raw_normal, raw_normal) < 1e-12 {
712 self.line_to(pos1);
713 return;
714 }
715
716 let normal = normalize3(raw_normal);
717 let (u, v) = build_basis(&normal);
718
719 let to_2d = |p: [f32; 3]| -> Position {
720 let d = sub3(p, pos1);
721 Position {
722 x: dot3(d, u),
723 y: dot3(d, v),
724 }
725 };
726
727 let p0 = to_2d(pos0);
728 let p1 = to_2d(pos1);
729 let p2 = to_2d(pos2);
730
731 if Position::equals(p0, p1, self.dist_tol)
732 || Position::equals(p1, p2, self.dist_tol)
733 || Position::segment_distance(p1, p0, p2) < self.dist_tol * self.dist_tol
734 || radius < self.dist_tol
735 {
736 self.line_to(pos1);
737 return;
738 }
739
740 let mut dpos0 = p0 - p1;
741 let mut dpos1 = p2 - p1;
742
743 dpos0.normalize();
744 dpos1.normalize();
745
746 let a = dpos0.dot(dpos1).acos();
747 let d = radius / (a / 2.0).tan();
748
749 if d > 10000.0 {
750 self.line_to(pos1);
751 return;
752 }
753
754 let (cpos, a0, a1, dir);
755
756 if dpos0.cross(dpos1) > 0.0 {
757 cpos = p1 + dpos0 * d + dpos0.orthogonal() * radius;
758 a0 = dpos0.angle();
759 a1 = (-dpos1).angle();
760 dir = Solidity::Hole;
761 } else {
762 cpos = p1 + dpos0 * d - dpos0.orthogonal() * radius;
763 a0 = (-dpos0).angle();
764 a1 = dpos1.angle();
765 dir = Solidity::Solid;
766 }
767
768 let center3 = add3(pos1, add3(scale3(u, cpos.x), scale3(v, cpos.y)));
769
770 self.arc(center3, normal, radius, a0 + PI / 2.0, a1 + PI / 2.0, dir);
771 }
772}
773
774#[derive(Debug)]
776pub struct PathIter<'a, const N: usize = 2> {
777 verbs: slice::Iter<'a, PackedVerb>,
778 coords: &'a [[f32; N]],
779}
780
781impl<const N: usize> Iterator for PathIter<'_, N> {
782 type Item = Verb<N>;
783
784 fn next(&mut self) -> Option<Self::Item> {
785 if let Some(verb) = self.verbs.next() {
786 let verb = Verb::from_packed(verb, self.coords);
787 let num_coords = verb.num_coordinates();
788 self.coords = &self.coords[num_coords..];
789 Some(verb)
790 } else {
791 None
792 }
793 }
794}
795
796#[cfg(feature = "textlayout")]
797impl ttf_parser::OutlineBuilder for Path {
798 fn move_to(&mut self, x: f32, y: f32) {
799 self.move_to([x, y]);
800 }
801
802 fn line_to(&mut self, x: f32, y: f32) {
803 self.line_to([x, y]);
804 }
805
806 fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
807 self.quad_to([x1, y1], [x, y]);
808 }
809
810 fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
811 self.bezier_to([x1, y1], [x2, y2], [x, y]);
812 }
813
814 fn close(&mut self) {
815 self.close();
816 }
817}