pathfinder/geometry/src/stroke.rs

148 lines
5.2 KiB
Rust
Raw Normal View History

2019-01-10 13:01:21 -05:00
// pathfinder/geometry/src/stroke.rs
//
2019-01-10 13:01:21 -05:00
// Copyright © 2019 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Utilities for converting path strokes to fills.
use lyon_path::PathEvent;
use lyon_path::iterator::PathIterator;
use segments::{Segment, SegmentIter};
#[derive(Clone, Copy, Debug)]
pub struct StrokeStyle {
pub width: f32,
}
impl StrokeStyle {
#[inline]
pub fn new(width: f32) -> StrokeStyle {
StrokeStyle {
width: width,
}
}
}
pub struct StrokeToFillIter<I> where I: PathIterator {
inner: SegmentIter<I>,
subpath: Vec<Segment>,
stack: Vec<PathEvent>,
state: StrokeToFillState,
style: StrokeStyle,
first_point_in_subpath: bool,
}
impl<I> StrokeToFillIter<I> where I: PathIterator {
#[inline]
pub fn new(inner: I, style: StrokeStyle) -> StrokeToFillIter<I> {
StrokeToFillIter {
inner: SegmentIter::new(inner),
subpath: vec![],
stack: vec![],
state: StrokeToFillState::Forward,
style: style,
first_point_in_subpath: true,
}
}
}
impl<I> Iterator for StrokeToFillIter<I> where I: PathIterator {
type Item = PathEvent;
// TODO(pcwalton): Support miter and round joins. This will probably require the inner iterator
// to be `Peekable`, I guess.
fn next(&mut self) -> Option<PathEvent> {
// If we have path events queued, return the latest.
if let Some(path_event) = self.stack.pop() {
return Some(path_event)
}
// Fetch the next segment.
let next_segment = match self.state {
StrokeToFillState::Forward => {
match self.inner.next() {
None | Some(Segment::EndSubpath(false)) => {
if self.subpath.is_empty() {
return None
}
self.state = StrokeToFillState::Backward;
return self.next()
}
Some(Segment::EndSubpath(true)) => {
if self.subpath.is_empty() {
return None
}
self.state = StrokeToFillState::Backward;
self.first_point_in_subpath = true;
return Some(PathEvent::Close)
}
Some(segment) => {
self.subpath.push(segment);
segment
}
}
}
StrokeToFillState::Backward => {
match self.subpath.pop() {
None | Some(Segment::EndSubpath(_)) => {
self.state = StrokeToFillState::Forward;
self.first_point_in_subpath = true;
return Some(PathEvent::Close)
}
Some(segment) => segment.flip(),
}
}
};
next_segment.offset(self.style.width * 0.5, |offset_segment| {
match *offset_segment {
Segment::EndSubpath(_) => unreachable!(),
Segment::Line(ref offset_segment) => {
if self.first_point_in_subpath {
self.first_point_in_subpath = false;
self.stack.push(PathEvent::MoveTo(offset_segment.from))
} else if self.stack.is_empty() {
self.stack.push(PathEvent::LineTo(offset_segment.from))
}
self.stack.push(PathEvent::LineTo(offset_segment.to))
}
Segment::Quadratic(ref offset_segment) => {
if self.first_point_in_subpath {
self.first_point_in_subpath = false;
self.stack.push(PathEvent::MoveTo(offset_segment.from))
} else if self.stack.is_empty() {
self.stack.push(PathEvent::LineTo(offset_segment.from))
}
self.stack.push(PathEvent::QuadraticTo(offset_segment.ctrl, offset_segment.to))
}
Segment::Cubic(ref offset_segment) => {
if self.first_point_in_subpath {
self.first_point_in_subpath = false;
self.stack.push(PathEvent::MoveTo(offset_segment.from))
} else if self.stack.is_empty() {
self.stack.push(PathEvent::LineTo(offset_segment.from))
}
self.stack.push(PathEvent::CubicTo(offset_segment.ctrl1,
offset_segment.ctrl2,
offset_segment.to))
}
}
});
self.stack.reverse();
return self.next()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum StrokeToFillState {
Forward,
Backward,
}