[librsvg: 6/9] Document the rest of path_builder.rs
- From: Federico Mena Quintero <federico src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [librsvg: 6/9] Document the rest of path_builder.rs
- Date: Sat, 29 May 2021 01:04:34 +0000 (UTC)
commit e8e144329896d24c84f0404b3b0c5c7fc090d192
Author: Federico Mena Quintero <federico gnome org>
Date: Fri May 28 17:06:45 2021 -0500
Document the rest of path_builder.rs
src/path_builder.rs | 85 +++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 72 insertions(+), 13 deletions(-)
---
diff --git a/src/path_builder.rs b/src/path_builder.rs
index 099de4c4..4f091e44 100644
--- a/src/path_builder.rs
+++ b/src/path_builder.rs
@@ -1,4 +1,22 @@
//! Representation of Bézier paths.
+//!
+//! Path data can consume a significant amount of memory in complex SVG documents. This
+//! module deals with this as follows:
+//!
+//! * The path parser pushes commands into a [`PathBuilder`](struct.PathBuilder.html). This is a
+//! mutable, temporary storage for path data.
+//!
+//! * Then, the `PathBuilder` gets turned into a long-term, immutable [`Path`](struct.Path.html) that
+//! has a more compact representation.
+//!
+//! The code tries to reduce work in the allocator, by using a `TinyVec` with space for at
+//! least 32 commands on the stack for `PathBuilder`; most paths in SVGs in the wild have
+//! fewer than 32 commands, and larger ones will spill to the heap.
+//!
+//! See these blog posts for details and profiles:
+//!
+//! * [Compact representation for path
data](https://people.gnome.org/~federico/blog/reducing-memory-consumption-in-librsvg-4.html)
+//! * [Reducing slack space and allocator
work](https://people.gnome.org/~federico/blog/reducing-memory-consumption-in-librsvg-3.html)
use tinyvec::TinyVec;
@@ -9,15 +27,18 @@ use std::slice;
use crate::float_eq_cairo::ApproxEqCairo;
use crate::util::clamp;
+/// Whether an arc's sweep should be >= 180 degrees, or smaller.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct LargeArc(pub bool);
+/// Angular direction in which an arc is drawn.
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Sweep {
Negative,
Positive,
}
+/// "c" command for paths; describes a cubic Bézier segment.
#[derive(Debug, Copy, Clone, PartialEq, Default)]
pub struct CubicBezierCurve {
/// The (x, y) coordinates of the first control point.
@@ -29,6 +50,7 @@ pub struct CubicBezierCurve {
}
impl CubicBezierCurve {
+ /// Consumes 6 coordinates and creates a curve segment.
fn from_coords(coords: &mut slice::Iter<'_, f64>) -> CubicBezierCurve {
let pt1 = take_two(coords);
let pt2 = take_two(coords);
@@ -37,6 +59,7 @@ impl CubicBezierCurve {
CubicBezierCurve { pt1, pt2, to }
}
+ /// Pushes 6 coordinates to `coords` and returns `PackedCommand::CurveTo`.
fn to_packed_and_coords(&self, coords: &mut Vec<f64>) -> PackedCommand {
coords.push(self.pt1.0);
coords.push(self.pt1.1);
@@ -48,6 +71,11 @@ impl CubicBezierCurve {
}
}
+/// Conversion from endpoint parameterization to center parameterization.
+///
+/// SVG path data specifies elliptical arcs in terms of their endpoints, but
+/// they are easier to process if they are converted to a center parameterization.
+///
/// When attempting to compute the center parameterization of the arc,
/// out of range parameters may see an arc omitted or treated as a line.
pub enum ArcParameterization {
@@ -68,6 +96,7 @@ pub enum ArcParameterization {
Omit,
}
+/// "a" command for paths; describes an elliptical arc in terms of its endpoints.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct EllipticalArc {
/// The (x-axis, y-axis) radii for the ellipse.
@@ -91,8 +120,8 @@ impl EllipticalArc {
///
/// Radii may be adjusted if there is no solution.
///
- /// See Appendix F.6 Elliptical arc implementation notes.
- /// http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
+ /// See section B.2.4. Conversion from endpoint to center parameterization
+ /// https://www.w3.org/TR/SVG2/implnote.html#ArcConversionEndpointToCenter
pub(crate) fn center_parameterization(self) -> ArcParameterization {
let Self {
r: (mut rx, mut ry),
@@ -209,6 +238,7 @@ impl EllipticalArc {
}
}
+ /// Consumes 7 coordinates and creates an arc segment.
fn from_coords(
large_arc: LargeArc,
sweep: Sweep,
@@ -229,6 +259,7 @@ impl EllipticalArc {
}
}
+ /// Pushes 7 coordinates to `coords` and returns one of `PackedCommand::Arc*`.
fn to_packed_and_coords(&self, coords: &mut Vec<f64>) -> PackedCommand {
coords.push(self.r.0);
coords.push(self.r.1);
@@ -291,6 +322,9 @@ pub(crate) fn arc_segment(
}
}
+/// Long-form version of a single path command.
+///
+/// This is returned from iterators on paths and subpaths.
#[derive(Clone, Debug, PartialEq)]
pub enum PathCommand {
MoveTo(f64, f64),
@@ -300,13 +334,16 @@ pub enum PathCommand {
ClosePath,
}
+// This is just so we can use TinyVec, whose type parameter requires T: Default.
+// There is no actual default for path commands in the SVG spec; this is just our
+// implementation detail.
enum_default!(
PathCommand,
PathCommand::CurveTo(CubicBezierCurve::default())
);
impl PathCommand {
- // Returns the number of coordinate values that this command will generate in a `Path`.
+ /// Returns the number of coordinate values that this command will generate in a `Path`.
fn num_coordinates(&self) -> usize {
match *self {
PathCommand::MoveTo(..) => 2,
@@ -317,6 +354,7 @@ impl PathCommand {
}
}
+ /// Pushes a command's coordinates to `coords` and returns the corresponding `PackedCommand`.
fn to_packed(&self, coords: &mut Vec<f64>) -> PackedCommand {
match *self {
PathCommand::MoveTo(x, y) => {
@@ -339,6 +377,7 @@ impl PathCommand {
}
}
+ /// Consumes a packed command's coordinates from the `coords` iterator and returns the rehydrated
`PathCommand`.
fn from_packed(packed: PackedCommand, coords: &mut slice::Iter<'_, f64>) -> PathCommand {
match packed {
PackedCommand::MoveTo => {
@@ -386,8 +425,8 @@ impl PathCommand {
/// Constructs a path out of commands.
///
-/// When you are finished constructing a path builder, turn it into
-/// a `Path` with `into_path`.
+/// When you are finished constructing a path builder, turn it into a `Path` with
+/// `into_path`. You can then iterate on that `Path`'s commands with its methods.
#[derive(Default)]
pub struct PathBuilder {
path_commands: TinyVec<[PathCommand; 32]>,
@@ -397,15 +436,17 @@ pub struct PathBuilder {
///
/// This is constructed from a `PathBuilder` once it is finished. You
/// can get an iterator for the path's commands with the `iter`
-/// function.
+/// method, or an iterator for its subpaths (subsequences of commands that
+/// start with a MoveTo) with the `iter_subpath` method.
///
-/// Most `PathCommand` variants only have a few coordinates, but `PathCommand::Arc`
-/// has two extra booleans. We separate the commands from their coordinates so
-/// we can have two dense arrays: one with a compact representation of commands,
-/// and another with a linear list of the coordinates for each command.
+/// The variants in `PathCommand` have different sizes, so a simple array of `PathCommand`
+/// would have a lot of slack space. We reduce this to a minimum by separating the
+/// commands from their coordinates. Then, we can have two dense arrays: one with a compact
+/// representation of commands, and another with a linear list of the coordinates for each
+/// command.
///
-/// Each `PathCommand` knows how many coordinates it ought to produce, with
-/// its `num_coordinates` method.
+/// Both `PathCommand` and `PackedCommand` know how many coordinates they ought to
+/// produce, with their `num_coordinates` methods.
///
/// This struct implements `Default`, and it yields an empty path.
#[derive(Default)]
@@ -415,6 +456,12 @@ pub struct Path {
}
/// Packed version of a `PathCommand`, used in `Path`.
+///
+/// MoveTo/LineTo/CurveTo have only pairs of coordinates, while ClosePath has no coordinates,
+/// and EllipticalArc has a bunch of coordinates plus two flags. Here we represent the flags
+/// as four variants.
+///
+/// This is `repr(u8)` to keep it as small as possible.
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
enum PackedCommand {
@@ -445,6 +492,7 @@ impl PackedCommand {
}
impl PathBuilder {
+ /// Consumes the `PathBuilder` and returns a compact, immutable representation as a `Path`.
pub fn into_path(self) -> Path {
let num_coords = self
.path_commands
@@ -465,14 +513,17 @@ impl PathBuilder {
}
}
+ /// Adds a MoveTo command to the path.
pub fn move_to(&mut self, x: f64, y: f64) {
self.path_commands.push(PathCommand::MoveTo(x, y));
}
+ /// Adds a LineTo command to the path.
pub fn line_to(&mut self, x: f64, y: f64) {
self.path_commands.push(PathCommand::LineTo(x, y));
}
+ /// Adds a CurveTo command to the path.
pub fn curve_to(&mut self, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) {
let curve = CubicBezierCurve {
pt1: (x2, y2),
@@ -482,6 +533,7 @@ impl PathBuilder {
self.path_commands.push(PathCommand::CurveTo(curve));
}
+ /// Adds an EllipticalArc command to the path.
pub fn arc(
&mut self,
x1: f64,
@@ -505,12 +557,13 @@ impl PathBuilder {
self.path_commands.push(PathCommand::Arc(arc));
}
+ /// Adds a ClosePath command to the path.
pub fn close_path(&mut self) {
self.path_commands.push(PathCommand::ClosePath);
}
}
-/// An iterator over `SubPath` from a Path.
+/// An iterator over the subpaths of a `Path`.
pub struct SubPathIter<'a> {
path: &'a Path,
commands_start: usize,
@@ -523,12 +576,14 @@ pub struct SubPath<'a> {
coords: &'a [f64],
}
+/// An iterator over the commands/coordinates of a subpath.
pub struct SubPathCommandsIter<'a> {
commands_iter: slice::Iter<'a, PackedCommand>,
coords_iter: slice::Iter<'a, f64>,
}
impl<'a> SubPath<'a> {
+ /// Returns an iterator over the subpath's commands.
pub fn iter_commands(&self) -> SubPathCommandsIter<'_> {
SubPathCommandsIter {
commands_iter: self.commands.iter(),
@@ -536,6 +591,7 @@ impl<'a> SubPath<'a> {
}
}
+ /// Each subpath starts with a MoveTo; this returns its `(x, y)` coordinates.
pub fn origin(&self) -> (f64, f64) {
let first = *self.commands.first().unwrap();
assert!(matches!(first, PackedCommand::MoveTo));
@@ -547,6 +603,7 @@ impl<'a> SubPath<'a> {
}
}
+ /// Returns whether the length of a subpath is approximately zero.
pub fn is_zero_length(&self) -> bool {
let (cur_x, cur_y) = self.origin();
@@ -645,6 +702,7 @@ impl Path {
}
}
+ /// Get an iterator over a path's commands.
pub fn iter(&self) -> impl Iterator<Item = PathCommand> + '_ {
let commands = self.commands.iter();
let mut coords = self.coords.iter();
@@ -652,6 +710,7 @@ impl Path {
commands.map(move |cmd| PathCommand::from_packed(*cmd, &mut coords))
}
+ /// Returns whether there are no commands in the path.
pub fn is_empty(&self) -> bool {
self.commands.is_empty()
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]