vello_api 0.0.7

Defines the public API types for Vello, providing a stable interface for CPU and Hybrid rendering implementations.
Documentation
// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

//! Turning shapes into Bézier paths without approximation.

use kurbo::{BezPath, CubicBez, Line, QuadBez, Rect, Segments, Shape, Triangle, segments};
use peniko::kurbo::{self, PathEl};

/// A generic trait for shapes that can be mapped exactly to Bézier path elements (i.e., without
/// approximation).
///
/// The methods on [`PaintScene`](crate::PaintScene) use this trait ensure that consistent behaviour
/// is maintained when Vello API is used to render content which might be rescaled.
///
/// This is implemented for [`Shape`]s from Kurbo that can be exactly mapped to Bézier path elements.
/// To convert a [`Shape`] which requires approximation (such as [`Circle`](kurbo::Circle) or
/// [`RoundedRect`](kurbo::RoundedRect)), you can use [`within`].
/// This however requires you to provide the tolerance.
/// See the docs of `within` for more details.
///
/// It is a requirement of this trait that [`Shape::path_elements`] returns the same iterator
/// as [`ExactPathElements::exact_path_elements`] for any provided tolerance value.
pub trait ExactPathElements {
    /// The iterator returned by the [`Self::exact_path_elements`] method.
    ///
    /// In many cases, this will be the same iterator as [`Shape::path_elements`]
    type ExactPathElementsIter<'iter>: Iterator<Item = PathEl> + 'iter
    where
        Self: 'iter;

    /// Returns an iterator over this shape expressed as exact [`PathEl`]s;
    /// that is, as exact Bézier path _elements_.
    ///
    /// These path elements are exact, in the sense that no approximation is required
    /// to calculate them. This is not possible for all shapes, but is possible for all
    /// finite polygons and other piecewise-cubic parametric curves. Some shapes will
    /// need to be approximated, which [`Shape::path_elements`] does instead.
    /// The [`within`] helper function allows the approximated shape to be used
    /// where an exact value is required, by providing the tolerance used.
    ///
    /// In many cases, shapes are able to iterate their elements without
    /// allocating; however creating a [`BezPath`] object always allocates.
    /// If you need an owned [`BezPath`] you can use [`BezPath::from_iter`] (or
    /// [`Iterator::collect`]).
    fn exact_path_elements(&self) -> Self::ExactPathElementsIter<'_>;

    /// Returns an iterator over this shape expressed as exact Bézier path
    /// _segments_ ([`PathSeg`]s).
    ///
    /// The allocation behaviour is the same as for [`ExactPathElements::exact_path_elements`].
    ///
    /// [`PathSeg`]: kurbo::PathSeg
    #[inline]
    fn exact_path_segments(&self) -> Segments<Self::ExactPathElementsIter<'_>> {
        segments(self.exact_path_elements())
    }
}

impl<T: ExactPathElements> ExactPathElements for &T {
    type ExactPathElementsIter<'iter>
        = T::ExactPathElementsIter<'iter>
    where
        Self: 'iter;

    #[inline]
    fn exact_path_elements(&self) -> Self::ExactPathElementsIter<'_> {
        (*self).exact_path_elements()
    }
}

/// Use an approximated shape where an [`ExactPathElements`] is required, by approximating it to
/// within the given tolerance.
///
/// *WARNING*: Unlike [`ExactPathElements`], which will produce correct renderings for any scale, this
/// approximation is only valid for a fixed range of transforms.
///
/// As the user of this function, you are responsible for determining the correct tolerance for your use case.
/// A reasonable approach might be to select a tolerance which allows scaling up ("zooming in") by 4x (for example;
/// you should evaluate the correct value yourself) and remaining within your intended tolerance bound.
/// If the user zoomed past that limit, you would then recomputing the [`Scene`](crate::Scene) from your base data representation once that is exceeded.
/// If you know that the shape will not be scaled, you can use [`UNSCALED_TOLERANCE`].
/// The resulting path will be within 1/10th of a pixel of the actual shape, which is a negligible
/// difference for rendering.
///
/// This is useful for drawing shapes such as [`Circle`](kurbo::Circle)s and [`RoundedRect`](kurbo::RoundedRect)s.
// TODO: Provide as an extension trait?
pub fn within<S: Shape>(shape: S, tolerance: f64) -> WithTolerance<S> {
    WithTolerance { shape, tolerance }
}

/// The type used to implement [`within`].
#[derive(Debug, Clone, Copy)]
pub struct WithTolerance<S: Shape> {
    /// The shape to be approximated.
    pub shape: S,
    /// The tolerance to use when approximating it.
    pub tolerance: f64,
}

impl<S: Shape> ExactPathElements for WithTolerance<S> {
    type ExactPathElementsIter<'iter>
        = S::PathElementsIter<'iter>
    where
        S: 'iter;
    fn exact_path_elements(&self) -> Self::ExactPathElementsIter<'_> {
        self.shape.path_elements(self.tolerance)
    }
}

/// The recommended tolerance for approximating shapes which won't be rescaled as Bezier paths.
///
/// This can be used as the tolerance parameter to [`within`], when you know that
/// a shape will be drawn at its natural size, without scaling or skew.
pub const UNSCALED_TOLERANCE: f64 = 0.1;

// TODO: Provide an `fn ideal_tolerance(transform: Affine) -> f64`

/// Implement `ExactPathElements` for an existing [`Shape`], which we know will not be approximated in Kurbo.
// In theory, the impl is the wrong way around; instead the `Shape` impl should be in terms of `ExactPathElements`.
macro_rules! passthrough {
    ($ty: ty) => {
        impl ExactPathElements for $ty {
            type ExactPathElementsIter<'iter>
                = <$ty as Shape>::PathElementsIter<'iter>
            where
                $ty: 'iter;

            fn exact_path_elements(&self) -> Self::ExactPathElementsIter<'_> {
                // We use a tolerance of zero here because we know this to be exact.
                self.path_elements(0.)
            }
        }
    };
}

passthrough!(BezPath);
passthrough!(CubicBez);
passthrough!(Line);
passthrough!(QuadBez);
passthrough!(Rect);
passthrough!(Triangle);