From e2e3ec7e214acd93a1c10bb5e8c111cbc466cfff Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 14 Jul 2020 12:05:12 -0700 Subject: [PATCH] Document the `pathfinder_content::outline` module --- content/src/lib.rs | 4 +- content/src/outline.rs | 183 +++++++++++++++++++++++++++++++++++------ renderer/src/scene.rs | 1 + 3 files changed, 159 insertions(+), 29 deletions(-) diff --git a/content/src/lib.rs b/content/src/lib.rs index db751ec1..19ae5680 100644 --- a/content/src/lib.rs +++ b/content/src/lib.rs @@ -8,9 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! Pathfinder's representation of a vector scene. -//! -//! This module also contains various path utilities. +//! Components of a vector scene, and various path utilities. #[macro_use] extern crate bitflags; diff --git a/content/src/outline.rs b/content/src/outline.rs index 798e786b..589d3cd6 100644 --- a/content/src/outline.rs +++ b/content/src/outline.rs @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! A compressed in-memory representation of paths. +//! A compressed in-memory representation of a vector path. use crate::clip::{self, ContourPolygonClipper}; use crate::dilation::ContourDilator; @@ -25,12 +25,20 @@ use std::f32::consts::PI; use std::fmt::{self, Debug, Formatter}; use std::mem; +/// A vector path to be filled. Outlines (a.k.a. paths) consist of *contours* (a.k.a. subpaths), +/// which can be filled according to a fill rule. +/// +/// The names "outline" and "contour" come from the TrueType specification. They were chosen to +/// avoid conflicting with the Rust use of "path" for filesystem paths. #[derive(Clone)] pub struct Outline { pub(crate) contours: Vec, pub(crate) bounds: RectF, } +/// An individual subpath, consisting of a series of endpoints and/or control points. Contours can +/// be either open (first and last points disconnected) or closed (first point implicitly joined to +/// last point with a line). #[derive(Clone)] pub struct Contour { pub(crate) points: Vec, @@ -40,20 +48,29 @@ pub struct Contour { } bitflags! { + /// Flags that each point can have, indicating whether it is on-curve or whether it's a control + /// point. pub struct PointFlags: u8 { + /// This point is the first control point of a cubic Bézier curve or the only control point + /// of a quadratic Bézier curve. const CONTROL_POINT_0 = 0x01; + /// This point is the second point of a quadratic Bézier curve. const CONTROL_POINT_1 = 0x02; } } bitflags! { - pub struct PushSegmentFlags: u8 { + // Flags specifying what actions to take when pushing a segment onto a contour. + pub(crate) struct PushSegmentFlags: u8 { + /// The bounds should be updated. const UPDATE_BOUNDS = 0x01; + /// The "from" point of the segme const INCLUDE_FROM_POINT = 0x02; } } impl Outline { + /// Creates a new empty outline with no contours. #[inline] pub fn new() -> Outline { Outline { @@ -71,11 +88,9 @@ impl Outline { } } + /// Creates a new outline from a list of segments. #[inline] - pub fn from_segments(segments: I) -> Outline - where - I: Iterator, - { + pub fn from_segments(segments: I) -> Outline where I: Iterator { let mut outline = Outline::new(); let mut current_contour = Contour::new(); @@ -120,6 +135,7 @@ impl Outline { outline } + /// Creates a new outline that represents a single axis-aligned rectangle. #[inline] pub fn from_rect(rect: RectF) -> Outline { let mut outline = Outline::new(); @@ -127,6 +143,7 @@ impl Outline { outline } + /// Creates a new outline that represents a rounded rectangle. #[inline] pub fn from_rect_rounded(rect: RectF, radius: Vector2F) -> Outline { let mut outline = Outline::new(); @@ -134,16 +151,19 @@ impl Outline { outline } + /// Returns the dimensions of an axis-aligned box that encloses the entire outline. #[inline] pub fn bounds(&self) -> RectF { self.bounds } + /// Returns a list of the subpaths in this path. #[inline] pub fn contours(&self) -> &[Contour] { &self.contours } + /// Destroys this outline and returns a list of its subpaths. #[inline] pub fn into_contours(self) -> Vec { self.contours @@ -156,6 +176,7 @@ impl Outline { self.bounds = RectF::default(); } + /// Adds a new subpath to this outline. pub fn push_contour(&mut self, contour: Contour) { if contour.is_empty() { return; @@ -170,6 +191,7 @@ impl Outline { self.contours.push(contour); } + /// Removes the last subpath from this outline and returns it. pub fn pop_contour(&mut self) -> Option { let last_contour = self.contours.pop(); @@ -182,6 +204,7 @@ impl Outline { last_contour } + /// Applies an affine transform to this outline and all its subpaths. pub fn transform(&mut self, transform: &Transform2F) { if transform.is_identity() { return; @@ -195,11 +218,16 @@ impl Outline { self.bounds = new_bounds.unwrap_or_else(|| RectF::default()); } + /// Applies an affine transform to this outline and all its subpaths, consuming this outline + /// instead of mutating it. pub fn transformed(mut self, transform: &Transform2F) -> Outline { self.transform(transform); self } + /// Applies a perspective transform to this outline. + #[deprecated] + #[allow(deprecated)] pub fn apply_perspective(&mut self, perspective: &Perspective) { let mut new_bounds = None; for contour in &mut self.contours { @@ -209,6 +237,9 @@ impl Outline { self.bounds = new_bounds.unwrap_or_else(|| RectF::default()); } + /// Thickens the outline by the given amount. + /// + /// This is implemented by pushing vectors out along their normals. pub fn dilate(&mut self, amount: Vector2F) { let orientation = Orientation::from_outline(self); self.contours @@ -217,6 +248,10 @@ impl Outline { self.bounds = self.bounds.dilate(amount); } + /// Returns true if this outline is obviously completely outside the closed polygon with the + /// given vertices, via a quick check. + /// + /// Even if the outline is outside the polygon, this might return false. pub fn is_outside_polygon(&self, clip_polygon: &[Vector2F]) -> bool { clip::rect_is_outside_polygon(self.bounds, clip_polygon) } @@ -225,6 +260,9 @@ impl Outline { clip::rect_is_inside_polygon(self.bounds, clip_polygon) } + /// Clips this outline against the given closed polygon with the given vertices. + /// + /// This is implemented with Sutherland-Hodgman clipping. pub fn clip_against_polygon(&mut self, clip_polygon: &[Vector2F]) { // Quick check. if self.is_inside_polygon(clip_polygon) { @@ -236,11 +274,13 @@ impl Outline { } } + /// Marks all contours as closed. #[inline] pub fn close_all_contours(&mut self) { self.contours.iter_mut().for_each(|contour| contour.close()); } + /// Returns true if this outline has no points. #[inline] pub fn is_empty(&self) -> bool { self.contours.iter().all(Contour::is_empty) @@ -252,7 +292,7 @@ impl Outline { self.contours.len() } - /// Appends the contours in another outline to this one. + /// Appends the contours of another outline to this one. pub fn push_outline(&mut self, other: Outline) { if other.is_empty() { return; @@ -281,6 +321,7 @@ impl Debug for Outline { } impl Contour { + /// Creates a new empty unclosed subpath. #[inline] pub fn new() -> Contour { Contour { @@ -291,6 +332,8 @@ impl Contour { } } + /// Creates a new empty unclosed subpath with space preallocated for the given number of + /// points. #[inline] pub fn with_capacity(length: usize) -> Contour { Contour { @@ -301,6 +344,7 @@ impl Contour { } } + /// Creates a closed subpath representing the given axis-aligned rectangle. #[inline] pub fn from_rect(rect: RectF) -> Contour { let mut contour = Contour::with_capacity(4); @@ -313,6 +357,7 @@ impl Contour { contour } + /// Creates a closed subpath representing the given axis-aligned rounded rectangle. #[inline] pub fn from_rect_rounded(rect: RectF, radius: Vector2F) -> Contour { use std::f32::consts::SQRT_2; @@ -397,7 +442,8 @@ impl Contour { ) } - /// restore self to the state of Contour::new(), but keep the points buffer allocated + /// Restores this contour to the state of `Contour::new()` but keeps the points buffer + /// allocated. #[inline] pub fn clear(&mut self) { self.points.clear(); @@ -406,6 +452,7 @@ impl Contour { self.closed = false; } + /// Returns an iterator over the segments in this contour. #[inline] pub fn iter(&self, flags: ContourIterFlags) -> ContourIter { ContourIter { @@ -415,36 +462,46 @@ impl Contour { } } + /// Returns true if this contour has no points. #[inline] pub fn is_empty(&self) -> bool { self.points.is_empty() } + /// Returns the number of points (including on-curve and control points) in this contour. #[inline] pub fn len(&self) -> u32 { self.points.len() as u32 } + /// Returns the dimensions of an axis-aligned rectangle that encloses this contour. #[inline] pub fn bounds(&self) -> RectF { self.bounds } + /// Returns true if this contour is closed. #[inline] pub fn is_closed(&self) -> bool { self.closed } + /// Returns the position of the point (which can be an on-curve point or a control point) with + /// the given index. + /// + /// Panics if the index is out of bounds. #[inline] pub fn position_of(&self, index: u32) -> Vector2F { self.points[index as usize] } + /// Returns the position of the first point in this subpath. #[inline] pub fn first_position(&self) -> Option { self.points.first().cloned() } + /// Returns the position of the last point in this subpath. #[inline] pub fn last_position(&self) -> Option { self.points.last().cloned() @@ -455,29 +512,39 @@ impl Contour { self.points[self.points.len() - index as usize] } + /// Returns a set of flags that describes the type of the point with the given index. + /// + /// Panics if the index is out of range. #[inline] pub fn flags_of(&self, index: u32) -> PointFlags { self.flags[index as usize] } + /// Adds a new on-curve point at the given position to this contour. #[inline] - pub fn push_endpoint(&mut self, point: Vector2F) { - self.push_point(point, PointFlags::empty(), true); + pub fn push_endpoint(&mut self, to: Vector2F) { + self.push_point(to, PointFlags::empty(), true); } + /// Adds a new quadratic Bézier curve to the given on-curve position and control point to this + /// contour. #[inline] - pub fn push_quadratic(&mut self, ctrl: Vector2F, point: Vector2F) { + pub fn push_quadratic(&mut self, ctrl: Vector2F, to: Vector2F) { self.push_point(ctrl, PointFlags::CONTROL_POINT_0, true); - self.push_point(point, PointFlags::empty(), true); + self.push_point(to, PointFlags::empty(), true); } + /// Adds a new cubic Bézier curve to the given on-curve position and control points to this + /// contour. #[inline] - pub fn push_cubic(&mut self, ctrl0: Vector2F, ctrl1: Vector2F, point: Vector2F) { + pub fn push_cubic(&mut self, ctrl0: Vector2F, ctrl1: Vector2F, to: Vector2F) { self.push_point(ctrl0, PointFlags::CONTROL_POINT_0, true); self.push_point(ctrl1, PointFlags::CONTROL_POINT_1, true); - self.push_point(point, PointFlags::empty(), true); + self.push_point(to, PointFlags::empty(), true); } + /// Marks this contour as closed, which results in an implicit line from the end back to the + /// starting point. #[inline] pub fn close(&mut self) { self.closed = true; @@ -526,6 +593,19 @@ impl Contour { self.push_point(segment.baseline.to(), PointFlags::empty(), update_bounds); } + /// Adds Bézier curves approximating a possibly-transformed unit arc to this contour. + /// + /// Arguments: + /// + /// * `transform`: An affine transform to apply to the unit arc. This can be used to reposition + /// and resize the arc. + /// + /// * `start_angle`: The starting angle in radians. 0 represents the +x axis. + /// + /// * `end_angle`: The ending angle in radians. 0 represents the +x axis. + /// + /// * `direction`: Whether the arc should be drawn clockwise or counterclockwise from the +x + /// axis. pub fn push_arc(&mut self, transform: &Transform2F, start_angle: f32, @@ -540,6 +620,8 @@ impl Contour { } } + /// Given the endpoints of a unit arc, adds Bézier curves to approximate that arc to the + /// current contour. The given transform is applied to the resulting arc. pub fn push_arc_from_unit_chord(&mut self, transform: &Transform2F, mut chord: LineSegment2F, @@ -590,12 +672,17 @@ impl Contour { const EPSILON: f32 = 0.001; } - /// Push a SVG arc + /// Adds an arc specified in SVG form to the current contour. /// - /// Draws an ellipse section with radii given in `radius` rotated by `x_axis_rotation` to 'to' in the given `direction`. - /// If `large_arc` is true, draws an arc bigger than a 180°, otherwise smaller than 180°. - /// note that `x_axis_rotation` is in radians. - pub fn push_svg_arc(&mut self, radius: Vector2F, x_axis_rotation: f32, large_arc: bool, direction: ArcDirection, to: Vector2F) { + /// Draws an ellipse section with radii given by `radius` rotated by `x_axis_rotation` in + /// radians to `to` in the given direction. If `large_arc` is true, draws an arc bigger than + /// π radians, otherwise smaller than π radians. + pub fn push_svg_arc(&mut self, + radius: Vector2F, + x_axis_rotation: f32, + large_arc: bool, + direction: ArcDirection, + to: Vector2F) { let r = radius; let p = to; let last = self.last_position().unwrap_or_default(); @@ -648,6 +735,9 @@ impl Contour { } } + /// Adds an unit circle to this contour, transformed with the given transform. + /// + /// Non-uniform scales can be used to transform this circle into an ellipse. pub fn push_ellipse(&mut self, transform: &Transform2F) { let segment = Segment::quarter_circle_arc(); let mut rotation; @@ -664,6 +754,11 @@ impl Contour { PushSegmentFlags::UPDATE_BOUNDS); } + /// Returns the segment starting at the point with the given index. + /// + /// The index must represent an on-curve point. + /// + /// Panics if `point_index` is out of range. #[inline] pub fn segment_after(&self, point_index: u32) -> Segment { debug_assert!(self.point_is_endpoint(point_index)); @@ -694,6 +789,10 @@ impl Contour { segment } + /// Returns a line segment from the point with the given index to the next point, whether + /// on-curve or off-curve. + /// + /// Panics if `prev_point_index` is not in range. #[inline] pub fn hull_segment_after(&self, prev_point_index: u32) -> LineSegment2F { let next_point_index = self.next_point_index_of(prev_point_index); @@ -703,12 +802,14 @@ impl Contour { ) } + /// Returns true if the given point is on-curve or false if it is off-curve. #[inline] pub fn point_is_endpoint(&self, point_index: u32) -> bool { !self.flags[point_index as usize] - .intersects(PointFlags::CONTROL_POINT_0 | PointFlags::CONTROL_POINT_1) + .intersects(PointFlags::CONTROL_POINT_0 | PointFlags::CONTROL_POINT_1) } + /// Returns `point_index + addend` modulo the number of points in this contour. #[inline] pub fn add_to_point_index(&self, point_index: u32, addend: u32) -> u32 { let (index, limit) = (point_index + addend, self.len()); @@ -719,12 +820,10 @@ impl Contour { } } - #[inline] - pub fn point_is_logically_above(&self, a: u32, b: u32) -> bool { - let (a_y, b_y) = (self.points[a as usize].y(), self.points[b as usize].y()); - a_y < b_y || (a_y == b_y && a < b) - } - + /// Returns the first on-curve point strictly before the point with the given index. + /// + /// This takes closed paths into account, so the returned index might be greater than + /// `point_index`. #[inline] pub fn prev_endpoint_index_of(&self, mut point_index: u32) -> u32 { loop { @@ -735,6 +834,10 @@ impl Contour { } } + /// Returns the first on-curve point strictly after the point with the given index. + /// + /// This takes closed paths into account, so the returned index might be less than + /// `point_index`. #[inline] pub fn next_endpoint_index_of(&self, mut point_index: u32) -> u32 { loop { @@ -745,6 +848,10 @@ impl Contour { } } + /// Returns the index of the point before the given `point_index`. + /// + /// If the index of the first point is passed in, then this returns the index of the last + /// point. #[inline] pub fn prev_point_index_of(&self, point_index: u32) -> u32 { if point_index == 0 { @@ -754,6 +861,10 @@ impl Contour { } } + /// Returns the index of the point after the given `point_index`. + /// + /// If the index of the last point is passed in, then this returns the index of the first + /// point. #[inline] pub fn next_point_index_of(&self, point_index: u32) -> u32 { if point_index == self.len() - 1 { @@ -763,6 +874,7 @@ impl Contour { } } + /// Applies the given affine transform to this subpath. pub fn transform(&mut self, transform: &Transform2F) { if transform.is_identity() { return; @@ -774,12 +886,16 @@ impl Contour { } } + /// Applies the given affine transform to this contour, returning a new contour instead of + /// mutating this one. #[inline] pub fn transformed(mut self, transform: &Transform2F) -> Contour { self.transform(transform); self } + /// Applies a perspective transform to this subpath. + #[deprecated] pub fn apply_perspective(&mut self, perspective: &Perspective) { for (point_index, point) in self.points.iter_mut().enumerate() { *point = *perspective * *point; @@ -787,6 +903,8 @@ impl Contour { } } + /// Thickens the outline by the given amount. The `orientation` parameter specifies the winding + /// of the path (clockwise or counterclockwise) and is necessary to avoid flipped normals. pub fn dilate(&mut self, amount: Vector2F, orientation: Orientation) { ContourDilator::new(self, amount, orientation).dilate(); self.bounds = self.bounds.dilate(amount); @@ -858,10 +976,14 @@ impl Debug for Contour { } } +/// The index of a point within an outline, either on-curve or off-curve. +/// +/// This packs a contour index with a point index into a single 32-bit value. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct PointIndex(u32); impl PointIndex { + /// Packs a contour index and the index of a point within that contour into a single value. #[inline] pub fn new(contour: u32, point: u32) -> PointIndex { debug_assert!(contour <= 0xfff); @@ -869,17 +991,20 @@ impl PointIndex { PointIndex((contour << 20) | point) } + /// Extracts the index of the contour and returns it. #[inline] pub fn contour(self) -> u32 { self.0 >> 20 } + /// Extracts the index of the point within that contour and returns it. #[inline] pub fn point(self) -> u32 { self.0 & 0x000f_ffff } } +/// Iterates over all Bézier segments within a contour. pub struct ContourIter<'a> { contour: &'a Contour, index: u32, @@ -933,14 +1058,20 @@ impl<'a> Iterator for ContourIter<'a> { } } +/// The direction of an arc: clockwise or counterclockwise. #[derive(Clone, Copy, Debug, PartialEq)] pub enum ArcDirection { + /// Clockwise, starting from the +x axis. CW, + /// Counterclockwise, starting from the +x axis. CCW, } bitflags! { + /// Flags that control the behavior of `Contour::iter()`. pub struct ContourIterFlags: u8 { + /// Set to true to avoid iterating over the implicit line segment that joins the last point + /// to the first point for closed contours. const IGNORE_CLOSE_SEGMENT = 1; } } diff --git a/renderer/src/scene.rs b/renderer/src/scene.rs index 335ea787..75cfef75 100644 --- a/renderer/src/scene.rs +++ b/renderer/src/scene.rs @@ -221,6 +221,7 @@ impl Scene { self.epoch.next(); } + #[allow(deprecated)] pub(crate) fn apply_render_options(&self, original_outline: &Outline, options: &PreparedBuildOptions)