use crate::outline::{ArcDirection, Contour, ContourIterFlags, Outline, PushSegmentFlags};
use crate::segment::Segment;
use pathfinder_geometry::line_segment::LineSegment2F;
use pathfinder_geometry::rect::RectF;
use pathfinder_geometry::transform2d::Transform2F;
use pathfinder_geometry::util::EPSILON;
use pathfinder_geometry::vector::{Vector2F, vec2f};
use std::f32;
const TOLERANCE: f32 = 0.01;
pub struct OutlineStrokeToFill<'a> {
input: &'a Outline,
output: Outline,
style: StrokeStyle,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct StrokeStyle {
pub line_width: f32,
pub line_cap: LineCap,
pub line_join: LineJoin,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LineCap {
Butt,
Square,
Round,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LineJoin {
Miter(f32),
Bevel,
Round,
}
impl<'a> OutlineStrokeToFill<'a> {
#[inline]
pub fn new(input: &Outline, style: StrokeStyle) -> OutlineStrokeToFill {
OutlineStrokeToFill { input, output: Outline::new(), style }
}
pub fn offset(&mut self) {
let mut new_contours = vec![];
for input in &self.input.contours {
let closed = input.closed;
let mut stroker = ContourStrokeToFill::new(input,
Contour::new(),
self.style.line_width * 0.5,
self.style.line_join);
stroker.offset_forward();
if closed {
self.push_stroked_contour(&mut new_contours, stroker, true);
stroker = ContourStrokeToFill::new(input,
Contour::new(),
self.style.line_width * 0.5,
self.style.line_join);
} else {
self.add_cap(&mut stroker.output);
}
stroker.offset_backward();
if !closed {
self.add_cap(&mut stroker.output);
}
self.push_stroked_contour(&mut new_contours, stroker, closed);
}
let mut new_bounds = None;
new_contours.iter().for_each(|contour| contour.update_bounds(&mut new_bounds));
self.output.contours = new_contours;
self.output.bounds = new_bounds.unwrap_or_else(|| RectF::default());
}
#[inline]
pub fn into_outline(self) -> Outline {
self.output
}
fn push_stroked_contour(&mut self,
new_contours: &mut Vec<Contour>,
mut stroker: ContourStrokeToFill,
closed: bool) {
if closed && stroker.output.might_need_join(self.style.line_join) {
let (p1, p0) = (stroker.output.position_of(1), stroker.output.position_of(0));
let final_segment = LineSegment2F::new(p1, p0);
stroker.output.add_join(self.style.line_width * 0.5,
self.style.line_join,
stroker.input.position_of(0),
final_segment);
}
stroker.output.closed = true;
new_contours.push(stroker.output);
}
fn add_cap(&mut self, contour: &mut Contour) {
if self.style.line_cap == LineCap::Butt || contour.len() < 2 {
return
}
let width = self.style.line_width;
let p1 = contour.position_of_last(1);
let mut p0;
let mut p0_index = contour.len() - 2;
loop {
p0 = contour.position_of(p0_index);
if (p1 - p0).square_length() > EPSILON {
break;
}
if p0_index == 0 {
return;
}
p0_index -= 1;
}
let gradient = (p1 - p0).normalize();
match self.style.line_cap {
LineCap::Butt => unreachable!(),
LineCap::Square => {
let offset = gradient * (width * 0.5);
let p2 = p1 + offset;
let p3 = p2 + gradient.yx() * vec2f(-width, width);
let p4 = p3 - offset;
contour.push_endpoint(p2);
contour.push_endpoint(p3);
contour.push_endpoint(p4);
}
LineCap::Round => {
let scale = width * 0.5;
let offset = gradient.yx() * vec2f(-1.0, 1.0);
let translation = p1 + offset * (width * 0.5);
let transform = Transform2F::from_scale(scale).translate(translation);
let chord = LineSegment2F::new(-offset, offset);
contour.push_arc_from_unit_chord(&transform, chord, ArcDirection::CW);
}
}
}
}
struct ContourStrokeToFill<'a> {
input: &'a Contour,
output: Contour,
radius: f32,
join: LineJoin,
}
impl<'a> ContourStrokeToFill<'a> {
#[inline]
fn new(input: &Contour, output: Contour, radius: f32, join: LineJoin) -> ContourStrokeToFill {
ContourStrokeToFill { input, output, radius, join }
}
fn offset_forward(&mut self) {
for (segment_index, segment) in self.input.iter(ContourIterFlags::empty()).enumerate() {
let join = if segment_index == 0 { LineJoin::Bevel } else { self.join };
segment.offset(-self.radius, join, &mut self.output);
}
}
fn offset_backward(&mut self) {
let mut segments: Vec<_> = self
.input
.iter(ContourIterFlags::empty())
.map(|segment| segment.reversed())
.collect();
segments.reverse();
for (segment_index, segment) in segments.iter().enumerate() {
let join = if segment_index == 0 { LineJoin::Bevel } else { self.join };
segment.offset(-self.radius, join, &mut self.output);
}
}
}
trait Offset {
fn offset(&self, distance: f32, join: LineJoin, contour: &mut Contour);
fn add_to_contour(&self,
distance: f32,
join: LineJoin,
join_point: Vector2F,
contour: &mut Contour);
fn offset_once(&self, distance: f32) -> Self;
fn error_is_within_tolerance(&self, other: &Segment, distance: f32) -> bool;
}
impl Offset for Segment {
fn offset(&self, distance: f32, join: LineJoin, contour: &mut Contour) {
let join_point = self.baseline.from();
if self.baseline.square_length() < TOLERANCE * TOLERANCE {
self.add_to_contour(distance, join, join_point, contour);
return;
}
let candidate = self.offset_once(distance);
if self.error_is_within_tolerance(&candidate, distance) {
candidate.add_to_contour(distance, join, join_point, contour);
return;
}
debug!("--- SPLITTING ---");
debug!("... PRE-SPLIT: {:?}", self);
let (before, after) = self.split(0.5);
debug!("... AFTER-SPLIT: {:?} {:?}", before, after);
before.offset(distance, join, contour);
after.offset(distance, join, contour);
}
fn add_to_contour(&self,
distance: f32,
join: LineJoin,
join_point: Vector2F,
contour: &mut Contour) {
if contour.might_need_join(join) {
let p3 = self.baseline.from();
let p4 = if self.is_line() {
self.baseline.to()
} else {
self.ctrl.from()
};
contour.add_join(distance, join, join_point, LineSegment2F::new(p4, p3));
}
let flags = PushSegmentFlags::UPDATE_BOUNDS | PushSegmentFlags::INCLUDE_FROM_POINT;
contour.push_segment(self, flags);
}
fn offset_once(&self, distance: f32) -> Segment {
if self.is_line() {
return Segment::line(self.baseline.offset(distance));
}
if self.is_quadratic() {
let mut segment_0 = LineSegment2F::new(self.baseline.from(), self.ctrl.from());
let mut segment_1 = LineSegment2F::new(self.ctrl.from(), self.baseline.to());
segment_0 = segment_0.offset(distance);
segment_1 = segment_1.offset(distance);
let ctrl = match segment_0.intersection_t(segment_1) {
Some(t) => segment_0.sample(t),
None => segment_0.to().lerp(segment_1.from(), 0.5),
};
let baseline = LineSegment2F::new(segment_0.from(), segment_1.to());
return Segment::quadratic(baseline, ctrl);
}
debug_assert!(self.is_cubic());
if self.baseline.from() == self.ctrl.from() {
let mut segment_0 = LineSegment2F::new(self.baseline.from(), self.ctrl.to());
let mut segment_1 = LineSegment2F::new(self.ctrl.to(), self.baseline.to());
segment_0 = segment_0.offset(distance);
segment_1 = segment_1.offset(distance);
let ctrl = match segment_0.intersection_t(segment_1) {
Some(t) => segment_0.sample(t),
None => segment_0.to().lerp(segment_1.from(), 0.5),
};
let baseline = LineSegment2F::new(segment_0.from(), segment_1.to());
let ctrl = LineSegment2F::new(segment_0.from(), ctrl);
return Segment::cubic(baseline, ctrl);
}
if self.ctrl.to() == self.baseline.to() {
let mut segment_0 = LineSegment2F::new(self.baseline.from(), self.ctrl.from());
let mut segment_1 = LineSegment2F::new(self.ctrl.from(), self.baseline.to());
segment_0 = segment_0.offset(distance);
segment_1 = segment_1.offset(distance);
let ctrl = match segment_0.intersection_t(segment_1) {
Some(t) => segment_0.sample(t),
None => segment_0.to().lerp(segment_1.from(), 0.5),
};
let baseline = LineSegment2F::new(segment_0.from(), segment_1.to());
let ctrl = LineSegment2F::new(ctrl, segment_1.to());
return Segment::cubic(baseline, ctrl);
}
let mut segment_0 = LineSegment2F::new(self.baseline.from(), self.ctrl.from());
let mut segment_1 = LineSegment2F::new(self.ctrl.from(), self.ctrl.to());
let mut segment_2 = LineSegment2F::new(self.ctrl.to(), self.baseline.to());
segment_0 = segment_0.offset(distance);
segment_1 = segment_1.offset(distance);
segment_2 = segment_2.offset(distance);
let (ctrl_0, ctrl_1) = match (
segment_0.intersection_t(segment_1),
segment_1.intersection_t(segment_2),
) {
(Some(t0), Some(t1)) => (segment_0.sample(t0), segment_1.sample(t1)),
_ => (
segment_0.to().lerp(segment_1.from(), 0.5),
segment_1.to().lerp(segment_2.from(), 0.5),
),
};
let baseline = LineSegment2F::new(segment_0.from(), segment_2.to());
let ctrl = LineSegment2F::new(ctrl_0, ctrl_1);
Segment::cubic(baseline, ctrl)
}
fn error_is_within_tolerance(&self, other: &Segment, distance: f32) -> bool {
let (mut min, mut max) = (
f32::abs(distance) - TOLERANCE,
f32::abs(distance) + TOLERANCE,
);
min = if min <= 0.0 { 0.0 } else { min * min };
max = if max <= 0.0 { 0.0 } else { max * max };
for t_num in 0..(SAMPLE_COUNT + 1) {
let t = t_num as f32 / SAMPLE_COUNT as f32;
let (this_p, other_p) = (self.sample(t), other.sample(t));
let vector = this_p - other_p;
let square_distance = vector.square_length();
debug!(
"this_p={:?} other_p={:?} vector={:?} sqdist={:?} min={:?} max={:?}",
this_p, other_p, vector, square_distance, min, max
);
if square_distance < min || square_distance > max {
return false;
}
}
return true;
const SAMPLE_COUNT: u32 = 16;
}
}
impl Contour {
fn might_need_join(&self, join: LineJoin) -> bool {
if self.len() < 2 {
false
} else {
match join {
LineJoin::Miter(_) | LineJoin::Round => true,
LineJoin::Bevel => false,
}
}
}
fn add_join(&mut self,
distance: f32,
join: LineJoin,
join_point: Vector2F,
next_tangent: LineSegment2F) {
let (p0, p1) = (self.position_of_last(2), self.position_of_last(1));
let prev_tangent = LineSegment2F::new(p0, p1);
if prev_tangent.square_length() < EPSILON || next_tangent.square_length() < EPSILON {
return;
}
match join {
LineJoin::Bevel => {}
LineJoin::Miter(miter_limit) => {
if let Some(prev_tangent_t) = prev_tangent.intersection_t(next_tangent) {
if prev_tangent_t < -EPSILON {
return;
}
let miter_endpoint = prev_tangent.sample(prev_tangent_t);
let threshold = miter_limit * distance;
if (miter_endpoint - join_point).square_length() > threshold * threshold {
return;
}
self.push_endpoint(miter_endpoint);
}
}
LineJoin::Round => {
let scale = distance.abs();
let transform = Transform2F::from_scale(scale).translate(join_point);
let chord_from = (prev_tangent.to() - join_point).normalize();
let chord_to = (next_tangent.to() - join_point).normalize();
let chord = LineSegment2F::new(chord_from, chord_to);
self.push_arc_from_unit_chord(&transform, chord, ArcDirection::CW);
}
}
}
}
impl Default for StrokeStyle {
#[inline]
fn default() -> StrokeStyle {
StrokeStyle {
line_width: 1.0,
line_cap: LineCap::default(),
line_join: LineJoin::default(),
}
}
}
impl Default for LineCap {
#[inline]
fn default() -> LineCap { LineCap::Butt }
}
impl Default for LineJoin {
#[inline]
fn default() -> LineJoin { LineJoin::Miter(10.0) }
}