Auto merge of #392 - pcwalton:docs-outline, r=pcwalton

Document the `pathfinder_content::outline` module
This commit is contained in:
bors-servo 2020-07-14 15:08:45 -04:00 committed by GitHub
commit f62f85354f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 159 additions and 29 deletions

View File

@ -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;

View File

@ -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<Contour>,
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<Vector2F>,
@ -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<I>(segments: I) -> Outline
where
I: Iterator<Item = Segment>,
{
pub fn from_segments<I>(segments: I) -> Outline where I: Iterator<Item = Segment> {
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<Contour> {
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<Contour> {
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<Vector2F> {
self.points.first().cloned()
}
/// Returns the position of the last point in this subpath.
#[inline]
pub fn last_position(&self) -> Option<Vector2F> {
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)
}
/// 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;
}
}

View File

@ -221,6 +221,7 @@ impl Scene {
self.epoch.next();
}
#[allow(deprecated)]
pub(crate) fn apply_render_options(&self,
original_outline: &Outline,
options: &PreparedBuildOptions)