add strokes

This commit is contained in:
Patrick Walton 2018-12-14 14:21:35 -08:00
parent 316a130143
commit 8f5eb1ca60
4 changed files with 191 additions and 102 deletions

4
Cargo.lock generated
View File

@ -990,7 +990,7 @@ name = "pathfinder_path_utils"
version = "0.2.0"
dependencies = [
"arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"lyon_path 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lyon_path 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1496,6 +1496,8 @@ dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.18.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lyon_algorithms 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lyon_path 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pathfinder_path_utils 0.2.0",
"quick-xml 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)",
"quickcheck 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -5,6 +5,6 @@ authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[dependencies]
arrayvec = "0.4"
lyon_path = "0.12"
lyon_path = "0.11"
serde = "1.0"
serde_derive = "1.0"

View File

@ -8,9 +8,13 @@ edition = "2018"
bitflags = "1.0"
euclid = "0.18"
lyon_algorithms = "0.11"
lyon_path = "0.11"
quick-xml = "0.12"
svgtypes = "0.2"
[dependencies.pathfinder_path_utils]
path = "../../path-utils"
[dev-dependencies]
quickcheck = "0.7"
rand = "0.5"

View File

@ -18,6 +18,9 @@ extern crate rand;
use euclid::{Point2D, Rect, Transform2D, Vector2D};
use lyon_algorithms::geom::{CubicBezierSegment, LineSegment, QuadraticBezierSegment};
use lyon_path::PathEvent;
use lyon_path::iterator::PathIter;
use pathfinder_path_utils::stroke::{StrokeStyle, StrokeToFillIter};
use quick_xml::Reader;
use quick_xml::events::Event;
use std::cmp::Ordering;
@ -75,6 +78,7 @@ fn main() {
struct Scene {
objects: Vec<PathObject>,
styles: Vec<ComputedStyle>,
//bounds: Rect<f32>,
}
#[derive(Debug)]
@ -115,10 +119,7 @@ impl Scene {
}
let value = reader.decode(&attribute.value);
let style = scene.ensure_style(&mut style, &mut group_styles);
let path_parser = PathParser::from(&*value);
let outline =
Outline::from_svg_path_segments(path_parser, scene.get_style(style));
scene.objects.push(PathObject::new(outline, style));
scene.push_svg_path(&*value, style);
}
}
Ok(Event::Start(ref event)) if event.name() == b"g" => {
@ -215,6 +216,27 @@ impl Scene {
// TODO(pcwalton)
}
}
fn push_svg_path(&mut self, value: &str, style: StyleId) {
if self.get_style(style).stroke_width > 0.0 {
let computed_style = self.get_style(style);
let mut path_parser = PathParser::from(&*value);
let path = SvgPathToPathEvents::new(&mut path_parser);
let path = PathIter::new(path);
let path = StrokeToFillIter::new(path, StrokeStyle::new(computed_style.stroke_width));
let outline = Outline::from_path_events(path, computed_style);
self.objects.push(PathObject::new(outline, style));
}
if self.get_style(style).fill_color.is_some() {
let computed_style = self.get_style(style);
let mut path_parser = PathParser::from(&*value);
let path = SvgPathToPathEvents::new(&mut path_parser);
let outline = Outline::from_path_events(path, computed_style);
self.objects.push(PathObject::new(outline, style));
}
}
}
impl PathObject {
@ -255,62 +277,30 @@ impl Outline {
}
}
fn from_svg_path_segments<I>(segments: I, style: &ComputedStyle) -> Outline
where I: Iterator<Item = SvgPathSegment> {
fn from_path_events<I>(path_events: I, style: &ComputedStyle) -> Outline
where I: Iterator<Item = PathEvent> {
let mut outline = Outline::new();
let mut current_contour = Contour::new();
let mut bounding_points = None;
let (mut first_point_in_path, mut last_ctrl_point, mut last_point) = (None, None, None);
for segment in segments {
match segment {
SvgPathSegment::MoveTo { abs, x, y } => {
for path_event in path_events {
match path_event {
PathEvent::MoveTo(to) => {
if !current_contour.is_empty() {
outline.contours.push(mem::replace(&mut current_contour, Contour::new()))
}
let to = compute_point(x, y, abs, &last_point);
first_point_in_path = Some(to);
last_point = Some(to);
last_ctrl_point = None;
current_contour.push_transformed_point(&to,
PointFlags::empty(),
&style.transform,
&mut bounding_points);
}
SvgPathSegment::LineTo { abs, x, y } => {
let to = compute_point(x, y, abs, &last_point);
last_point = Some(to);
last_ctrl_point = None;
PathEvent::LineTo(to) => {
current_contour.push_transformed_point(&to,
PointFlags::empty(),
&style.transform,
&mut bounding_points);
}
SvgPathSegment::HorizontalLineTo { abs, x } => {
let to = Point2D::new(compute_point(x, 0.0, abs, &last_point).x,
last_point.unwrap_or(Point2D::zero()).y);
last_point = Some(to);
last_ctrl_point = None;
current_contour.push_transformed_point(&to,
PointFlags::empty(),
&style.transform,
&mut bounding_points);
}
SvgPathSegment::VerticalLineTo { abs, y } => {
let to = Point2D::new(last_point.unwrap_or(Point2D::zero()).x,
compute_point(0.0, y, abs, &last_point).y);
last_point = Some(to);
last_ctrl_point = None;
current_contour.push_transformed_point(&to,
PointFlags::empty(),
&style.transform,
&mut bounding_points);
}
SvgPathSegment::Quadratic { abs, x1, y1, x, y } => {
let ctrl = compute_point(x1, y1, abs, &last_point);
last_ctrl_point = Some(ctrl);
let to = compute_point(x, y, abs, &last_point);
last_point = Some(to);
PathEvent::QuadraticTo(ctrl, to) => {
current_contour.push_transformed_point(&ctrl,
PointFlags::CONTROL_POINT_0,
&style.transform,
@ -320,28 +310,7 @@ impl Outline {
&style.transform,
&mut bounding_points);
}
SvgPathSegment::SmoothQuadratic { abs, x, y } => {
let ctrl = last_point.unwrap_or(Point2D::zero()) +
(last_point.unwrap_or(Point2D::zero()) -
last_ctrl_point.unwrap_or(Point2D::zero()));
last_ctrl_point = Some(ctrl);
let to = compute_point(x, y, abs, &last_point);
last_point = Some(to);
current_contour.push_transformed_point(&ctrl,
PointFlags::CONTROL_POINT_0,
&style.transform,
&mut bounding_points);
current_contour.push_transformed_point(&to,
PointFlags::empty(),
&style.transform,
&mut bounding_points);
}
SvgPathSegment::CurveTo { abs, x1, y1, x2, y2, x, y } => {
let ctrl0 = compute_point(x1, y1, abs, &last_point);
let ctrl1 = compute_point(x2, y2, abs, &last_point);
last_ctrl_point = Some(ctrl1);
let to = compute_point(x, y, abs, &last_point);
last_point = Some(to);
PathEvent::CubicTo(ctrl0, ctrl1, to) => {
current_contour.push_transformed_point(&ctrl0,
PointFlags::CONTROL_POINT_0,
&style.transform,
@ -355,35 +324,12 @@ impl Outline {
&style.transform,
&mut bounding_points);
}
SvgPathSegment::SmoothCurveTo { abs, x2, y2, x, y } => {
let ctrl0 = last_point.unwrap_or(Point2D::zero()) +
(last_point.unwrap_or(Point2D::zero()) -
last_ctrl_point.unwrap_or(Point2D::zero()));
let ctrl1 = compute_point(x2, y2, abs, &last_point);
last_ctrl_point = Some(ctrl1);
let to = compute_point(x, y, abs, &last_point);
last_point = Some(to);
current_contour.push_transformed_point(&ctrl0,
PointFlags::CONTROL_POINT_0,
&style.transform,
&mut bounding_points);
current_contour.push_transformed_point(&ctrl1,
PointFlags::CONTROL_POINT_1,
&style.transform,
&mut bounding_points);
current_contour.push_transformed_point(&to,
PointFlags::empty(),
&style.transform,
&mut bounding_points);
}
SvgPathSegment::ClosePath { abs: _ } => {
PathEvent::Close => {
if !current_contour.is_empty() {
outline.contours.push(mem::replace(&mut current_contour, Contour::new()));
last_point = first_point_in_path;
last_ctrl_point = None;
}
}
SvgPathSegment::EllipticalArc { .. } => unimplemented!("arcs"),
PathEvent::Arc(..) => unimplemented!("arcs"),
}
}
if !current_contour.is_empty() {
@ -394,16 +340,12 @@ impl Outline {
outline.bounds = Rect::from_points([upper_left, lower_right].into_iter())
}
return outline;
outline
}
fn compute_point(x: f64, y: f64, abs: bool, last_point: &Option<Point2D<f32>>)
-> Point2D<f32> {
let point = Point2D::new(x, y).to_f32();
match *last_point {
Some(last_point) if !abs => last_point + point.to_vector(),
_ => point,
}
}
#[inline]
fn iter(&self) -> OutlineIter {
OutlineIter { outline: self, index: PointIndex::default() }
}
fn segment_after(&self, endpoint_index: PointIndex) -> Segment {
@ -485,12 +427,56 @@ impl Contour {
}
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Default)]
struct PointIndex {
contour_index: usize,
point_index: usize,
}
struct OutlineIter<'a> {
outline: &'a Outline,
index: PointIndex,
}
impl<'a> Iterator for OutlineIter<'a> {
type Item = PathEvent;
fn next(&mut self) -> Option<PathEvent> {
if self.index.contour_index == self.outline.contours.len() {
return None
}
let contour = &self.outline.contours[self.index.contour_index];
if self.index.point_index == contour.points.len() {
self.index.contour_index += 1;
self.index.point_index = 0;
return Some(PathEvent::Close)
}
let point0_index = self.index.point_index;
let point0 = contour.points[point0_index];
self.index.point_index += 1;
if point0_index == 0 {
return Some(PathEvent::MoveTo(point0))
}
if contour.point_is_endpoint(point0_index) {
return Some(PathEvent::LineTo(point0))
}
let point1_index = self.index.point_index;
let point1 = contour.points[point1_index];
self.index.point_index += 1;
if contour.point_is_endpoint(point1_index) {
return Some(PathEvent::QuadraticTo(point0, point1))
}
let point2_index = self.index.point_index;
let point2 = contour.points[point2_index];
self.index.point_index += 1;
debug_assert!(contour.point_is_endpoint(point2_index));
Some(PathEvent::CubicTo(point0, point1, point2))
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct Segment {
from: Point2D<f32>,
@ -885,6 +871,103 @@ impl IntervalRange {
}
}
// SVG stuff
struct SvgPathToPathEvents<'a, I> where I: Iterator<Item = SvgPathSegment> {
iter: &'a mut I,
last_endpoint: Option<Point2D<f32>>,
last_ctrl_point: Option<Point2D<f32>>,
}
impl<'a, I> SvgPathToPathEvents<'a, I> where I: Iterator<Item = SvgPathSegment> {
fn new(iter: &'a mut I) -> SvgPathToPathEvents<'a, I> {
SvgPathToPathEvents { iter, last_endpoint: None, last_ctrl_point: None }
}
}
impl<'a, I> Iterator for SvgPathToPathEvents<'a, I> where I: Iterator<Item = SvgPathSegment> {
type Item = PathEvent;
fn next(&mut self) -> Option<PathEvent> {
return match self.iter.next() {
None => None,
Some(SvgPathSegment::MoveTo { abs, x, y }) => {
let to = compute_point(x, y, abs, &self.last_endpoint);
self.last_endpoint = Some(to);
self.last_ctrl_point = None;
Some(PathEvent::MoveTo(to))
}
Some(SvgPathSegment::LineTo { abs, x, y }) => {
let to = compute_point(x, y, abs, &self.last_endpoint);
self.last_endpoint = Some(to);
self.last_ctrl_point = None;
Some(PathEvent::LineTo(to))
}
Some(SvgPathSegment::HorizontalLineTo { abs, x }) => {
let to = compute_point(x, 0.0, abs, &self.last_endpoint);
self.last_endpoint = Some(to);
self.last_ctrl_point = None;
Some(PathEvent::LineTo(to))
}
Some(SvgPathSegment::VerticalLineTo { abs, y }) => {
let to = compute_point(0.0, y, abs, &self.last_endpoint);
self.last_endpoint = Some(to);
self.last_ctrl_point = None;
Some(PathEvent::LineTo(to))
}
Some(SvgPathSegment::Quadratic { abs, x1, y1, x, y }) => {
let ctrl = compute_point(x1, y1, abs, &self.last_endpoint);
self.last_ctrl_point = Some(ctrl);
let to = compute_point(x, y, abs, &self.last_endpoint);
self.last_endpoint = Some(to);
Some(PathEvent::QuadraticTo(ctrl, to))
}
Some(SvgPathSegment::SmoothQuadratic { abs, x, y }) => {
let ctrl = self.last_endpoint.unwrap_or(Point2D::zero()) +
(self.last_endpoint.unwrap_or(Point2D::zero()) -
self.last_ctrl_point.unwrap_or(Point2D::zero()));
self.last_ctrl_point = Some(ctrl);
let to = compute_point(x, y, abs, &self.last_endpoint);
self.last_endpoint = Some(to);
Some(PathEvent::QuadraticTo(ctrl, to))
}
Some(SvgPathSegment::CurveTo { abs, x1, y1, x2, y2, x, y }) => {
let ctrl0 = compute_point(x1, y1, abs, &self.last_endpoint);
let ctrl1 = compute_point(x2, y2, abs, &self.last_endpoint);
self.last_ctrl_point = Some(ctrl1);
let to = compute_point(x, y, abs, &self.last_endpoint);
self.last_endpoint = Some(to);
Some(PathEvent::CubicTo(ctrl0, ctrl1, to))
}
Some(SvgPathSegment::SmoothCurveTo { abs, x2, y2, x, y }) => {
let ctrl0 = self.last_endpoint.unwrap_or(Point2D::zero()) +
(self.last_endpoint.unwrap_or(Point2D::zero()) -
self.last_ctrl_point.unwrap_or(Point2D::zero()));
let ctrl1 = compute_point(x2, y2, abs, &self.last_endpoint);
self.last_ctrl_point = Some(ctrl1);
let to = compute_point(x, y, abs, &self.last_endpoint);
self.last_endpoint = Some(to);
Some(PathEvent::CubicTo(ctrl0, ctrl1, to))
}
Some(SvgPathSegment::ClosePath { abs: _ }) => {
Some(PathEvent::Close)
}
Some(SvgPathSegment::EllipticalArc { .. }) => unimplemented!("arcs"),
};
fn compute_point(x: f64, y: f64, abs: bool, last_endpoint: &Option<Point2D<f32>>)
-> Point2D<f32> {
let point = Point2D::new(x, y).to_f32();
match *last_endpoint {
Some(last_endpoint) if !abs => last_endpoint + point.to_vector(),
_ => point,
}
}
}
}
// Testing
#[cfg(test)]
mod test {
use crate::{IntervalRange, Intervals};