zeno/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
/*!
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.Mask.html) 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.PathData.html) trait for more detail.
The previous example did not provide a style, so a non-zero
[Fill](enum.Fill.html) 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](struct.Stroke.html) 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.Placement.html) 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<u8>` containing the mask:
```rust
use zeno::{Mask, PathData};
// mask is a Vec<u8>
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](struct.Mask.html) and [Stroke](struct.Stroke.html) 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](struct.HitTest.html) 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<Command> = 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<Command> is also valid path data
Mask::new(&path).render(); // ...
```
Here, a vector of [Command](enum.Command.html)s is used to store the path data
and the [PathBuilder](trait.PathBuilder.html) 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<Command> = 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](fn.apply.html) function:
```rust
use zeno::{apply, Cap, Command, PathBuilder, PathData, Stroke};
let mut stroke: Vec<Command> = Vec::new();
apply("L10,0", Stroke::new(4.0).cap(Cap::Round), None, &mut stroke);
```
[PathBuilder](struct.PathBuilder.html) is only implemented for `Vec<Command>`
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](struct.Vertices.html) iterator yields a variant of the
[Vertex](enum.Vertex.html) 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](struct.Walk.html) 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.Scratch.html) 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](struct.Mask.html#method.with_scratch) and
[HitTest::with_scratch](struct.HitTest.html#method.with_scratch)
constructors are provided which take a scratch instance as an argument and
redirect all transient allocations to the reusable storage.
*/
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};
// Prep for no_std support when core supports FP intrinsics.
mod lib {
pub use std::vec::Vec;
}