/*! This crate provides a high performance, low level 2D rasterization library with support for rendering paths of various styles into alpha or subpixel masks. Broadly speaking, support is provided for the following: - 256x anti-aliased rasterization (8-bit alpha or 32-bit RGBA subpixel alpha) - Pixel perfect hit testing with customizable coverage threshold - Non-zero and even-odd fills - Stroking with the standard set of joins and caps (separate start and end caps are possible) - Numerically stable dashing for smooth dash offset animation - Vertex traversal for marker placement - Stepped distance traversal for animation or text-on-path support - Abstract representation of path data that imposes no policy on storage While this crate is general purpose, in the interest of interoperability and familiarity, the feature set was chosen specifically to accommodate the requirements of the [SVG path specification](https://www.w3.org/TR/SVG/paths.html). Furthermore, the rasterized masks are nearly identical to those generated by Skia (sans slight AA differences) and as such, should yield images that are equivalent to those produced by modern web browsers. # Rendering Due to the large configuration space for styling and rendering paths, the builder pattern is used pervasively. The [`Mask`] struct is the builder used for rasterization. For example, to render a simple triangle into a 64x64 8-bit alpha mask: ```rust use zeno::{Mask, PathData}; // The target buffer that will contain the mask let mut mask = [0u8; 64 * 64]; // Create a new mask with some path data Mask::new("M 8,56 32,8 56,56 Z") // Choose an explicit size for the target .size(64, 64) // Finally, render the path into the target .render_into(&mut mask, None); ``` Note that, in this case, the path itself is supplied as a string in SVG path data format. This crate provides several different kinds of path data by default along with support for custom implementations. See the [`PathData`] trait for more detail. The previous example did not provide a style, so a non-zero [`Fill`] was chosen by default. Let's render the same path with a 4 pixel wide stroke and a round line join: ```rust use zeno::{Join, Mask, PathData, Stroke}; let mut mask = [0u8; 64 * 64]; Mask::new("M 8,56 32,8 56,56 Z") .size(64, 64) .style(Stroke::new(4.0).join(Join::Round)) .render_into(&mut mask, None); ``` Or to make it a bit more dashing: ```rust use zeno::{Cap, Join, Mask, PathData, Stroke}; let mut mask = [0u8; 64 * 64]; Mask::new("M 8,56 32,8 56,56 Z") .style( Stroke::new(4.0) .join(Join::Round) .cap(Cap::Round) // dash accepts a slice of dash lengths and an initial dash offset .dash(&[10.0, 12.0, 0.0], 0.0), ) .size(64, 64) .render_into(&mut mask, None); ``` See the [`Stroke`] builder struct for all available options. So far, we've generated our masks into fixed buffers with explicit sizes. It is often the case that it is preferred to ignore all empty space and render a path into a tightly bound mask of dynamic size. This can be done by eliding the call for the size method: ```rust use zeno::{Mask, PathData}; // Dynamic buffer that will contain the mask let mut mask = Vec::new(); let placement = Mask::new("M 8,56 32,8 56,56 Z") // Insert an inspect call here to access the computed dimensions .inspect(|format, width, height| { // Make sure our buffer is the correct size mask.resize(format.buffer_size(width, height), 0); }) .render_into(&mut mask, None); ``` The call to size has been replaced with a call to inspect which injects a closure into the call chain giving us the opportunity to extend our buffer to the appropriate size. Note also that the render method has a return value that has been captured here. This [`Placement`] struct describes the dimensions of the resulting mask along with an offset that should be applied during composition to compensate for the removal of any empty space. Finally, it is possible to render without a target buffer, in which case the rasterizer will allocate and return a new `Vec` containing the mask: ```rust use zeno::{Mask, PathData}; // mask is a Vec let (mask, placement) = Mask::new("M 8,56 32,8 56,56 Z") // Calling render() instead of render_into() will allocate a buffer // for you that is returned along with the placement .render(); ``` Both [`Mask`] and [`Stroke`] offer large sets of options for fine-grained control of styling and rasterization including offsets, scaling, transformations, formats, coordinate spaces and more. See their respective documentation for more detail. # Hit testing Hit testing is the process of determining if a point is within the region that would be painted by the path. A typical use case is to determine if a user's cursor is hovering over a particular path. The process generally follows the same form as rendering: ```rust use zeno::{HitTest, PathData}; // A 20x10 region with the right half covered by the path let hit_test = HitTest::new("M10,0 10,10 20,10 20,0 Z"); assert_eq!(hit_test.test([15, 5]), true); assert_eq!(hit_test.test([5, 5]), false); ``` Due to the fact that paths are anti-aliased, the hit test builder offers a threshold option that determines how much "coverage" is required for a hit test to pass at a particular point. ```rust use zeno::{HitTest, PathData}; let mut hit_test = HitTest::new("M2.5,0 2.5,2 5,2 5,0 Z"); // Require full coverage for a successful hit test hit_test.threshold(255); assert_eq!(hit_test.test([2, 0]), false); // Succeed for any non-zero coverage hit_test.threshold(0); assert_eq!(hit_test.test([2, 0]), true); ``` See the [`HitTest`] type for more detail. # Path building While SVG paths are a reasonable choice for static storage, there sometimes arise cases where paths must be built dynamically at runtime: ```rust use zeno::{Command, Mask, PathBuilder, PathData}; // Create a vector to store the path commands let mut path: Vec = Vec::new(); // Construct the path with chained method calls path.move_to([8, 56]).line_to([32, 8]).line_to([56, 56]).close(); // Ensure it is equal to the equivalent SVG path assert!((&path).commands().eq("M 8,56 32,8 56,56 Z".commands())); // &Vec is also valid path data Mask::new(&path).render(); // ... ``` Here, a vector of [`Command`]s is used to store the path data and the [`PathBuilder`] trait provides the extension methods necessary for building a path. Beyond the four basic path commands, the path builder trait also provides arcs (and position relative versions of all previous commands) along with rectangles, round rectangles, ellipses and circles: ```rust use zeno::{Angle, ArcSize, ArcSweep, Command, PathBuilder, PathData}; let mut path: Vec = Vec::new(); path.move_to([1, 2]).rel_arc_to( 8.0, 4.0, Angle::from_degrees(30.0), ArcSize::Small, ArcSweep::Positive, [10, 4], ); assert!((&path).commands().eq("M1,2 a8,4,30,0,1,10,4".commands())); ``` Along with incremental building of paths, path builder can also be used as a "sink" for capturing the result of the application of a style and transform to some path data. For example, it is possible to store the output of a stroke style to avoid the cost of stroke evaluation for future rendering or hit test operations with the use of the [`apply`] function: ```rust use zeno::{apply, Cap, Command, PathBuilder, PathData, Stroke}; let mut stroke: Vec = Vec::new(); apply("L10,0", Stroke::new(4.0).cap(Cap::Round), None, &mut stroke); ``` [`PathBuilder`] is only implemented for `Vec` by default, but custom implementations are possible to support capturing and building paths into other data structures. # Traversal Path traversal involves incremental evaluation of a path by some metric. This crate currently provides two methods of traversal. The [`Vertices`] iterator yields a variant of the [`Vertex`] enum at the beginning and end of each subpath and between each path command. Each variant provides all the geometric information necessary to place SVG style markers. The [`Walk`] type is an iterator-like type that allows for stepping along the path by arbitrary distances. Each step yields the position on the path at the next distance along with a vector describing the left-ward direction from the path at that point. This is useful for animating objects along a path, or for rendering text attached to a path. # Transient memory allocations The algorithms in this crate make a concerted effort to avoid dynamic allocations where possible, but paths of significant size or complexity may cause spills into temporary heap memory. Specifically, stroke evaluation and rasterization may cause heap allocations. To amortize the cost of these, the appropriately named [`Scratch`] struct is available. This type contains internal heap allocated storage and provides replacement methods for functions that may allocate. In addition, the [`Mask::with_scratch`] and [`HitTest::with_scratch`] constructors are provided which take a scratch instance as an argument and redirect all transient allocations to the reusable storage. */ #![cfg_attr(not(feature = "std"), no_std)] #[cfg(not(any(feature = "std", feature = "libm")))] compile_error! { "Either the std or libm feature must be enabled" } extern crate alloc; mod command; mod geometry; #[cfg(feature = "eval")] mod hit_test; #[cfg(feature = "eval")] mod mask; mod path_builder; mod path_data; #[cfg(feature = "eval")] mod raster; #[cfg(feature = "eval")] mod scratch; mod segment; #[cfg(feature = "eval")] mod stroke; mod style; mod svg_parser; #[cfg(feature = "eval")] mod traversal; pub use command::{Command, Verb}; pub use geometry::{Angle, Bounds, Origin, Placement, Point, Transform, Vector}; #[cfg(feature = "eval")] pub use hit_test::HitTest; #[cfg(feature = "eval")] pub use mask::{Format, Mask}; pub use path_builder::{ArcSize, ArcSweep, PathBuilder}; #[cfg(feature = "eval")] pub use path_data::{apply, bounds}; pub use path_data::{length, PathData}; #[cfg(feature = "eval")] pub use scratch::Scratch; pub use style::*; pub use svg_parser::validate_svg; #[cfg(feature = "eval")] pub use traversal::{Vertex, Vertices, Walk}; macro_rules! define_f32_ext { ($($fpname:ident($($argname:ident: $argty:ty),*) -> $ret:ty => $libmname:ident;)*) => { /// An extension trait defining floating point operations. #[allow(dead_code)] trait F32Ext { $( fn $fpname(self, $($argname:$argty),*) -> $ret; )* } #[cfg(feature = "std")] impl F32Ext for f32 { $( fn $fpname(self, $($argname:$argty),*) -> $ret { // This intrinsic is natively defined in libstd. f32::$fpname(self, $($argname),*) } )* } #[cfg(all(not(feature = "std"), feature = "libm"))] impl F32Ext for f32 { $( fn $fpname(self, $($argname:$argty),*) -> $ret { // Use the libm version of this intrinsic. <$ret>::libm_cvt(libm::$libmname( self.into(), $(($argname).into()),* ) as _) } )* } } } define_f32_ext! { abs() -> f32 => fabs; acos() -> f32 => acos; atan2(x:f32) -> f32 => atan2; ceil() -> f32 => ceil; cos() -> f32 => cos; floor() -> f32 => floor; sin_cos() -> (f32, f32) => sincos; sqrt() -> f32 => sqrt; powf(x:f32) -> f32 => powf; powi(x:i32) -> f32 => pow; tan() -> f32 => tan; } #[cfg(all(not(feature = "std"), feature = "libm"))] trait LibmCvt { type Input; fn libm_cvt(input: Self::Input) -> Self; } #[cfg(all(not(feature = "std"), feature = "libm"))] impl LibmCvt for f32 { type Input = f64; fn libm_cvt(input: f64) -> f32 { input as f32 } } #[cfg(all(not(feature = "std"), feature = "libm"))] impl LibmCvt for (f32, f32) { type Input = (f64, f64); fn libm_cvt((a, b): (f64, f64)) -> (f32, f32) { (a as f32, b as f32) } } // Prep for no_std support when core supports FP intrinsics. mod lib { pub use alloc::vec::Vec; }