Skip to main content

i_slint_core/graphics/
path.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4/*!
5This module contains path related types and functions for the run-time library.
6*/
7
8use crate::debug_log;
9use crate::items::{ImageFit, PathEvent};
10use crate::lengths::{LogicalPx, LogicalVector};
11#[cfg(feature = "rtti")]
12use crate::rtti::*;
13use const_field_offset::FieldOffsets;
14use euclid::Point2D;
15use i_slint_core_macros::*;
16
17#[repr(C)]
18#[derive(FieldOffsets, Default, SlintElement, Clone, Debug, PartialEq)]
19#[pin]
20/// PathMoveTo describes the event of setting the cursor on the path to use as starting
21/// point for sub-sequent events, such as `LineTo`. Moving the cursor also implicitly closes
22/// sub-paths and therefore beings a new sub-path.
23pub struct PathMoveTo {
24    #[rtti_field]
25    /// The x coordinate where the current position should be.
26    pub x: f32,
27    #[rtti_field]
28    /// The y coordinate where the current position should be.
29    pub y: f32,
30}
31
32#[repr(C)]
33#[derive(FieldOffsets, Default, SlintElement, Clone, Debug, PartialEq)]
34#[pin]
35/// PathLineTo describes the event of moving the cursor on the path to the specified location
36/// along a straight line.
37pub struct PathLineTo {
38    #[rtti_field]
39    /// The x coordinate where the line should go to.
40    pub x: f32,
41    #[rtti_field]
42    /// The y coordinate where the line should go to.
43    pub y: f32,
44}
45
46#[repr(C)]
47#[derive(FieldOffsets, Default, SlintElement, Clone, Debug, PartialEq)]
48#[pin]
49/// PathArcTo describes the event of moving the cursor on the path across an arc to the specified
50/// x/y coordinates, with the specified x/y radius and additional properties.
51pub struct PathArcTo {
52    #[rtti_field]
53    /// The x coordinate where the arc should end up.
54    pub x: f32,
55    #[rtti_field]
56    /// The y coordinate where the arc should end up.
57    pub y: f32,
58    #[rtti_field]
59    /// The radius on the x-axis of the arc.
60    pub radius_x: f32,
61    #[rtti_field]
62    /// The radius on the y-axis of the arc.
63    pub radius_y: f32,
64    #[rtti_field]
65    /// The rotation along the x-axis of the arc in degrees.
66    pub x_rotation: f32,
67    #[rtti_field]
68    /// large_arc indicates whether to take the long or the shorter path to complete the arc.
69    pub large_arc: bool,
70    #[rtti_field]
71    /// sweep indicates the direction of the arc. If true, a clockwise direction is chosen,
72    /// otherwise counter-clockwise.
73    pub sweep: bool,
74}
75
76#[repr(C)]
77#[derive(FieldOffsets, Default, SlintElement, Clone, Debug, PartialEq)]
78#[pin]
79/// PathCubicTo describes a smooth Bézier curve from the path's current position
80/// to the specified x/y location, using two control points.
81pub struct PathCubicTo {
82    #[rtti_field]
83    /// The x coordinate of the curve's end point.
84    pub x: f32,
85    #[rtti_field]
86    /// The y coordinate of the curve's end point.
87    pub y: f32,
88    #[rtti_field]
89    /// The x coordinate of the curve's first control point.
90    pub control_1_x: f32,
91    #[rtti_field]
92    /// The y coordinate of the curve's first control point.
93    pub control_1_y: f32,
94    #[rtti_field]
95    /// The x coordinate of the curve's second control point.
96    pub control_2_x: f32,
97    #[rtti_field]
98    /// The y coordinate of the curve's second control point.
99    pub control_2_y: f32,
100}
101
102#[repr(C)]
103#[derive(FieldOffsets, Default, SlintElement, Clone, Debug, PartialEq)]
104#[pin]
105/// PathCubicTo describes a smooth Bézier curve from the path's current position
106/// to the specified x/y location, using one control points.
107pub struct PathQuadraticTo {
108    #[rtti_field]
109    /// The x coordinate of the curve's end point.
110    pub x: f32,
111    #[rtti_field]
112    /// The y coordinate of the curve's end point.
113    pub y: f32,
114    #[rtti_field]
115    /// The x coordinate of the curve's control point.
116    pub control_x: f32,
117    #[rtti_field]
118    /// The y coordinate of the curve's control point.
119    pub control_y: f32,
120}
121
122#[repr(C)]
123#[derive(Clone, Debug, PartialEq, derive_more::From)]
124/// PathElement describes a single element on a path, such as move-to, line-to, etc.
125pub enum PathElement {
126    /// The MoveTo variant sets the current position on the path.
127    MoveTo(PathMoveTo),
128    /// The LineTo variant describes a line.
129    LineTo(PathLineTo),
130    /// The PathArcTo variant describes an arc.
131    ArcTo(PathArcTo),
132    /// The CubicTo variant describes a Bézier curve with two control points.
133    CubicTo(PathCubicTo),
134    /// The QuadraticTo variant describes a Bézier curve with one control point.
135    QuadraticTo(PathQuadraticTo),
136    /// Indicates that the path should be closed now by connecting to the starting point.
137    Close,
138}
139
140struct ToLyonPathEventIterator<'a> {
141    events_it: core::slice::Iter<'a, PathEvent>,
142    coordinates_it: core::slice::Iter<'a, lyon_path::math::Point>,
143    first: Option<&'a lyon_path::math::Point>,
144    last: Option<&'a lyon_path::math::Point>,
145}
146
147impl Iterator for ToLyonPathEventIterator<'_> {
148    type Item = lyon_path::Event<lyon_path::math::Point, lyon_path::math::Point>;
149    fn next(&mut self) -> Option<Self::Item> {
150        use lyon_path::Event;
151
152        self.events_it.next().map(|event| match event {
153            PathEvent::Begin => Event::Begin { at: *self.coordinates_it.next().unwrap() },
154            PathEvent::Line => Event::Line {
155                from: *self.coordinates_it.next().unwrap(),
156                to: *self.coordinates_it.next().unwrap(),
157            },
158            PathEvent::Quadratic => Event::Quadratic {
159                from: *self.coordinates_it.next().unwrap(),
160                ctrl: *self.coordinates_it.next().unwrap(),
161                to: *self.coordinates_it.next().unwrap(),
162            },
163            PathEvent::Cubic => Event::Cubic {
164                from: *self.coordinates_it.next().unwrap(),
165                ctrl1: *self.coordinates_it.next().unwrap(),
166                ctrl2: *self.coordinates_it.next().unwrap(),
167                to: *self.coordinates_it.next().unwrap(),
168            },
169            PathEvent::EndOpen => {
170                Event::End { first: *self.first.unwrap(), last: *self.last.unwrap(), close: false }
171            }
172            PathEvent::EndClosed => {
173                Event::End { first: *self.first.unwrap(), last: *self.last.unwrap(), close: true }
174            }
175        })
176    }
177
178    fn size_hint(&self) -> (usize, Option<usize>) {
179        self.events_it.size_hint()
180    }
181}
182
183impl ExactSizeIterator for ToLyonPathEventIterator<'_> {}
184
185struct TransformedLyonPathIterator<EventIt> {
186    it: EventIt,
187    transform: lyon_path::math::Transform,
188}
189
190impl<EventIt: Iterator<Item = lyon_path::Event<lyon_path::math::Point, lyon_path::math::Point>>>
191    Iterator for TransformedLyonPathIterator<EventIt>
192{
193    type Item = lyon_path::Event<lyon_path::math::Point, lyon_path::math::Point>;
194    fn next(&mut self) -> Option<Self::Item> {
195        self.it.next().map(|ev| ev.transformed(&self.transform))
196    }
197
198    fn size_hint(&self) -> (usize, Option<usize>) {
199        self.it.size_hint()
200    }
201}
202
203impl<EventIt: Iterator<Item = lyon_path::Event<lyon_path::math::Point, lyon_path::math::Point>>>
204    ExactSizeIterator for TransformedLyonPathIterator<EventIt>
205{
206}
207
208/// The two sources of lyon path events a `PathDataIterator` can iterate over, unified into
209/// one type so that `PathDataIterator::iter()` doesn't need to box its return value.
210enum LyonPathEventIterator<'a> {
211    FromPath(lyon_path::path::Iter<'a>),
212    FromEvents(ToLyonPathEventIterator<'a>),
213}
214
215impl Iterator for LyonPathEventIterator<'_> {
216    type Item = lyon_path::Event<lyon_path::math::Point, lyon_path::math::Point>;
217    fn next(&mut self) -> Option<Self::Item> {
218        match self {
219            Self::FromPath(it) => it.next(),
220            Self::FromEvents(it) => it.next(),
221        }
222    }
223
224    fn size_hint(&self) -> (usize, Option<usize>) {
225        match self {
226            Self::FromPath(it) => it.size_hint(),
227            Self::FromEvents(it) => it.size_hint(),
228        }
229    }
230}
231
232/// PathDataIterator is a data structure that acts as starting point for iterating
233/// through the low-level events of a path. If the path was constructed from said
234/// events, then it is a very thin abstraction. If the path was created from higher-level
235/// elements, then an intermediate lyon path is required/built.
236pub struct PathDataIterator {
237    it: LyonPathIteratorVariant,
238    transform: lyon_path::math::Transform,
239}
240
241enum LyonPathIteratorVariant {
242    FromPath(lyon_path::Path),
243    FromEvents(crate::SharedVector<PathEvent>, crate::SharedVector<lyon_path::math::Point>),
244}
245
246impl PathDataIterator {
247    /// Create a new iterator for path traversal.
248    pub fn iter(
249        &self,
250    ) -> impl Iterator<Item = lyon_path::Event<lyon_path::math::Point, lyon_path::math::Point>> + '_
251    {
252        TransformedLyonPathIterator {
253            it: match &self.it {
254                LyonPathIteratorVariant::FromPath(path) => {
255                    LyonPathEventIterator::FromPath(path.iter())
256                }
257                LyonPathIteratorVariant::FromEvents(events, coordinates) => {
258                    LyonPathEventIterator::FromEvents(ToLyonPathEventIterator {
259                        events_it: events.iter(),
260                        coordinates_it: coordinates.iter(),
261                        first: coordinates.first(),
262                        last: coordinates.last(),
263                    })
264                }
265            },
266            transform: self.transform,
267        }
268    }
269
270    /// Applies a transformation on the elements this iterator provides that tries to fit everything
271    /// into the specified width/height, respecting the provided viewbox. If no viewbox is specified,
272    /// the bounding rectangle of the path is used.
273    pub fn fit(
274        &mut self,
275        width: f32,
276        height: f32,
277        viewbox: Option<lyon_path::math::Box2D>,
278        style: ImageFit,
279    ) {
280        if width > 0. || height > 0. {
281            let fit_style = match style {
282                ImageFit::Contain => lyon_algorithms::fit::FitStyle::Min,
283                ImageFit::Cover => lyon_algorithms::fit::FitStyle::Max,
284                ImageFit::Fill => lyon_algorithms::fit::FitStyle::Stretch,
285                ImageFit::Preserve => return,
286            };
287            let viewbox =
288                viewbox.unwrap_or_else(|| lyon_algorithms::aabb::bounding_box(self.iter()));
289            self.transform = lyon_algorithms::fit::fit_box(
290                &viewbox,
291                &lyon_path::math::Box2D::from_size(lyon_path::math::Size::new(width, height)),
292                fit_style,
293            );
294        }
295    }
296
297    fn to_lyon_path(&self) -> lyon_path::Path {
298        match &self.it {
299            LyonPathIteratorVariant::FromPath(path) => path.clone().transformed(&self.transform),
300            LyonPathIteratorVariant::FromEvents(..) => self.iter().collect(),
301        }
302    }
303
304    /// Builds the lyon path together with its length measurements
305    pub fn to_fitted_path(&self, offset: LogicalVector) -> FittedPath {
306        use lyon_algorithms::measure::PathMeasurements;
307
308        let path = self.to_lyon_path();
309        // the number of path segments is proportional to 1/sqrt(tolerance)
310        // Tolerance is the distance between the curve and the approximation
311        // in the paths element coordinates (without the offset applied yet)
312        let measurements = PathMeasurements::from_path(&path, 1e-3);
313        FittedPath { path, offset, measurements }
314    }
315}
316
317/// A path and measurements so they don't need to be recalculated every call to sample
318pub struct FittedPath {
319    path: lyon_path::Path,
320    offset: LogicalVector,
321    measurements: lyon_algorithms::measure::PathMeasurements,
322}
323
324impl FittedPath {
325    /// Samples the fitted path at a given `t`. Returns None if the path length is 0
326    pub fn sample_at(&self, t: f32) -> Option<(Point2D<f32, LogicalPx>, f32)> {
327        use lyon_algorithms::measure::SampleType;
328
329        let mut rem = t.rem_euclid(1.);
330        if rem == 0.0 && t != 0.0 {
331            // This makes the path end at the end and not the start
332            rem = 1.0;
333        }
334        if self.measurements.length() <= 0. {
335            return None;
336        }
337        let mut sampler = self.measurements.create_sampler(&self.path, SampleType::Normalized);
338        let sample = sampler.sample(rem);
339        let pos = Point2D::new(sample.position().x, sample.position().y);
340        let angle = sample.tangent().angle_from_x_axis().to_degrees();
341        Some((pos + self.offset, angle))
342    }
343}
344
345#[repr(C)]
346#[derive(Clone, Debug, PartialEq)]
347/// PathData represents a path described by either high-level elements or low-level
348/// events and coordinates.
349#[derive(Default)]
350pub enum PathData {
351    /// None is the variant when the path is empty.
352    #[default]
353    None,
354    /// The Elements variant is used to make a Path from shared arrays of elements.
355    Elements(crate::SharedVector<PathElement>),
356    /// The Events variant describes the path as a series of low-level events and
357    /// associated coordinates.
358    Events(crate::SharedVector<PathEvent>, crate::SharedVector<lyon_path::math::Point>),
359    /// The Commands variant describes the path as a series of SVG encoded path commands.
360    Commands(crate::SharedString),
361}
362
363impl PathData {
364    /// This function returns an iterator that allows traversing the path by means of lyon events.
365    pub fn iter(self) -> Option<PathDataIterator> {
366        PathDataIterator {
367            it: match self {
368                PathData::None => return None,
369                PathData::Elements(elements) => LyonPathIteratorVariant::FromPath(
370                    PathData::build_path(elements.as_slice().iter()),
371                ),
372                PathData::Events(events, coordinates) => {
373                    LyonPathIteratorVariant::FromEvents(events, coordinates)
374                }
375                PathData::Commands(commands) => {
376                    let mut builder = lyon_path::Path::builder();
377                    let mut parser = lyon_extra::parser::PathParser::new();
378                    match parser.parse(
379                        &lyon_extra::parser::ParserOptions::DEFAULT,
380                        &mut lyon_extra::parser::Source::new(commands.chars()),
381                        &mut builder,
382                    ) {
383                        Ok(()) => LyonPathIteratorVariant::FromPath(builder.build()),
384                        Err(e) => {
385                            debug_log!("Error while parsing path commands '{commands}': {e:?}");
386                            LyonPathIteratorVariant::FromPath(Default::default())
387                        }
388                    }
389                }
390            },
391            transform: Default::default(),
392        }
393        .into()
394    }
395
396    fn build_path(element_it: core::slice::Iter<PathElement>) -> lyon_path::Path {
397        use lyon_geom::SvgArc;
398        use lyon_path::ArcFlags;
399        use lyon_path::math::{Angle, Point, Vector};
400        use lyon_path::traits::SvgPathBuilder;
401
402        let mut path_builder = lyon_path::Path::builder().with_svg();
403        for element in element_it {
404            match element {
405                PathElement::MoveTo(PathMoveTo { x, y }) => {
406                    path_builder.move_to(Point::new(*x, *y));
407                }
408                PathElement::LineTo(PathLineTo { x, y }) => {
409                    path_builder.line_to(Point::new(*x, *y));
410                }
411                PathElement::ArcTo(PathArcTo {
412                    x,
413                    y,
414                    radius_x,
415                    radius_y,
416                    x_rotation,
417                    large_arc,
418                    sweep,
419                }) => {
420                    let radii = Vector::new(*radius_x, *radius_y);
421                    let x_rotation = Angle::degrees(*x_rotation);
422                    let flags = ArcFlags { large_arc: *large_arc, sweep: *sweep };
423                    let to = Point::new(*x, *y);
424
425                    let svg_arc = SvgArc {
426                        from: path_builder.current_position(),
427                        radii,
428                        x_rotation,
429                        flags,
430                        to,
431                    };
432
433                    if svg_arc.is_straight_line() {
434                        path_builder.line_to(to);
435                    } else {
436                        path_builder.arc_to(radii, x_rotation, flags, to)
437                    }
438                }
439                PathElement::CubicTo(PathCubicTo {
440                    x,
441                    y,
442                    control_1_x,
443                    control_1_y,
444                    control_2_x,
445                    control_2_y,
446                }) => {
447                    path_builder.cubic_bezier_to(
448                        Point::new(*control_1_x, *control_1_y),
449                        Point::new(*control_2_x, *control_2_y),
450                        Point::new(*x, *y),
451                    );
452                }
453                PathElement::QuadraticTo(PathQuadraticTo { x, y, control_x, control_y }) => {
454                    path_builder.quadratic_bezier_to(
455                        Point::new(*control_x, *control_y),
456                        Point::new(*x, *y),
457                    );
458                }
459                PathElement::Close => path_builder.close(),
460            }
461        }
462
463        path_builder.build()
464    }
465}
466
467#[cfg(test)]
468mod tests {
469    use super::*;
470    use crate::lengths::LogicalLength;
471    use alloc::vec;
472    use alloc::vec::Vec;
473    use lyon_path::math::{Point, Transform};
474
475    // Two equivalent representations of the same L-shaped path (Begin, Line, Line, EndOpen):
476    // one built from high-level elements, one from low-level events/coordinates. Used to check
477    // that `PathDataIterator::to_lyon_path` applies `self.transform` the same way regardless of
478    // which `LyonPathIteratorVariant` backs it.
479    fn elements_path() -> PathData {
480        PathData::Elements(
481            [
482                PathElement::MoveTo(PathMoveTo { x: 0., y: 0. }),
483                PathElement::LineTo(PathLineTo { x: 10., y: 0. }),
484                PathElement::LineTo(PathLineTo { x: 10., y: 10. }),
485            ]
486            .as_slice()
487            .into(),
488        )
489    }
490
491    fn events_path() -> PathData {
492        let events: crate::SharedVector<PathEvent> =
493            [PathEvent::Begin, PathEvent::Line, PathEvent::Line, PathEvent::EndOpen]
494                .as_slice()
495                .into();
496        let coordinates: crate::SharedVector<Point> = [
497            Point::new(0., 0.),
498            Point::new(0., 0.),
499            Point::new(10., 0.),
500            Point::new(10., 0.),
501            Point::new(10., 10.),
502        ]
503        .as_slice()
504        .into();
505        PathData::Events(events, coordinates)
506    }
507
508    fn line_path() -> PathData {
509        PathData::Elements(
510            [
511                PathElement::MoveTo(PathMoveTo { x: 0., y: 0. }),
512                PathElement::LineTo(PathLineTo { x: 10., y: 0. }),
513            ]
514            .as_slice()
515            .into(),
516        )
517    }
518
519    fn points_of(path: &lyon_path::Path) -> Vec<Point> {
520        path.iter()
521            .flat_map(|ev| match ev {
522                lyon_path::Event::Begin { at } => vec![at],
523                lyon_path::Event::Line { from, to } => vec![from, to],
524                lyon_path::Event::Quadratic { from, ctrl, to } => vec![from, ctrl, to],
525                lyon_path::Event::Cubic { from, ctrl1, ctrl2, to } => vec![from, ctrl1, ctrl2, to],
526                lyon_path::Event::End { .. } => vec![],
527            })
528            .collect()
529    }
530
531    #[test]
532    fn to_lyon_path_applies_transform_for_elements_variant() {
533        let mut it = elements_path().iter().unwrap();
534        it.transform = Transform::translation(5., 7.);
535        assert_eq!(
536            points_of(&it.to_lyon_path()),
537            vec![
538                Point::new(5., 7.),
539                Point::new(5., 7.),
540                Point::new(15., 7.),
541                Point::new(15., 7.),
542                Point::new(15., 17.),
543            ]
544        );
545    }
546
547    #[test]
548    fn to_lyon_path_applies_transform_for_events_variant() {
549        let mut it = events_path().iter().unwrap();
550        it.transform = Transform::translation(5., 7.);
551        assert_eq!(
552            points_of(&it.to_lyon_path()),
553            vec![
554                Point::new(5., 7.),
555                Point::new(5., 7.),
556                Point::new(15., 7.),
557                Point::new(15., 7.),
558                Point::new(15., 17.),
559            ]
560        );
561    }
562
563    #[test]
564    fn to_lyon_path_transform_stays_consistent_across_variants_after_fit() {
565        let mut elements_it = elements_path().iter().unwrap();
566        let mut events_it = events_path().iter().unwrap();
567
568        // fit() derives a scale+translate transform from the (untransformed) bounding box;
569        // both variants describe the same geometry so they must end up with the same transform.
570        elements_it.fit(100., 50., None, ImageFit::Contain);
571        events_it.fit(100., 50., None, ImageFit::Contain);
572
573        assert_ne!(elements_it.transform, Transform::identity());
574        assert_eq!(elements_it.transform, events_it.transform);
575        assert_eq!(points_of(&elements_it.to_lyon_path()), points_of(&events_it.to_lyon_path()));
576    }
577
578    #[test]
579    fn fitted_path_sample_at_adds_offset_to_position() {
580        let it = line_path().iter().unwrap();
581        let offset = LogicalVector::from_lengths(LogicalLength::new(3.), LogicalLength::new(4.));
582        let fitted = it.to_fitted_path(offset);
583
584        let (start, _) = fitted.sample_at(0.0).unwrap();
585        assert_eq!(start, Point2D::new(3., 4.));
586
587        let (end, _) = fitted.sample_at(1.0).unwrap();
588        assert_eq!(end, Point2D::new(13., 4.));
589    }
590
591    #[test]
592    fn fitted_path_sample_at_wraps_percent_for_open_path() {
593        let it = line_path().iter().unwrap();
594        let fitted = it.to_fitted_path(LogicalVector::default());
595
596        // 1.0 is a special case (sampled directly, not wrapped) and must land on the actual
597        // end of the path, not back at the start the way rem_euclid(1.0, 1.0) == 0.0 would.
598        let (end, _) = fitted.sample_at(1.0).unwrap();
599        assert_eq!(end, Point2D::new(10., 0.));
600
601        let (mid, _) = fitted.sample_at(0.5).unwrap();
602        assert_eq!(mid, Point2D::new(5., 0.));
603
604        // Percentages outside of [0, 1) wrap around via rem_euclid.
605        let (wrapped_up, _) = fitted.sample_at(1.5).unwrap();
606        assert_eq!(wrapped_up, mid);
607        let (wrapped_down, _) = fitted.sample_at(-0.5).unwrap();
608        assert_eq!(wrapped_down, mid);
609    }
610
611    #[test]
612    fn fitted_path_sample_at_returns_none_for_zero_length_path() {
613        let elements = PathData::Elements(
614            [PathElement::MoveTo(PathMoveTo { x: 0., y: 0. })].as_slice().into(),
615        );
616        let it = elements.iter().unwrap();
617        let fitted = it.to_fitted_path(LogicalVector::default());
618
619        assert!(fitted.sample_at(0.0).is_none());
620        assert!(fitted.sample_at(0.5).is_none());
621    }
622}
623
624#[cfg(not(target_arch = "wasm32"))]
625pub(crate) mod ffi {
626    #![allow(unsafe_code)]
627
628    use super::super::*;
629    use super::*;
630    use core::ffi::c_void;
631
632    #[unsafe(no_mangle)]
633    /// This function is used for the low-level C++ interface to allocate the backing vector for a shared path element array.
634    pub unsafe extern "C" fn slint_new_path_elements(
635        out: *mut c_void,
636        first_element: *const PathElement,
637        count: usize,
638    ) {
639        let arr =
640            crate::SharedVector::from(unsafe { core::slice::from_raw_parts(first_element, count) });
641        unsafe { core::ptr::write(out as *mut crate::SharedVector<PathElement>, arr) };
642    }
643
644    #[unsafe(no_mangle)]
645    /// This function is used for the low-level C++ interface to allocate the backing vector for a shared path event array.
646    pub unsafe extern "C" fn slint_new_path_events(
647        out_events: *mut c_void,
648        out_coordinates: *mut c_void,
649        first_event: *const PathEvent,
650        event_count: usize,
651        first_coordinate: *const Point,
652        coordinate_count: usize,
653    ) {
654        let events = crate::SharedVector::from(unsafe {
655            core::slice::from_raw_parts(first_event, event_count)
656        });
657        unsafe { core::ptr::write(out_events as *mut crate::SharedVector<PathEvent>, events) };
658        let coordinates = crate::SharedVector::from(unsafe {
659            core::slice::from_raw_parts(first_coordinate, coordinate_count)
660        });
661        unsafe {
662            core::ptr::write(out_coordinates as *mut crate::SharedVector<Point>, coordinates)
663        };
664    }
665}