2019-01-10 13:01:21 -05:00
|
|
|
// pathfinder/geometry/src/stroke.rs
|
2017-09-08 16:09:00 -04:00
|
|
|
//
|
2019-01-10 13:01:21 -05:00
|
|
|
// Copyright © 2019 The Pathfinder Project Developers.
|
2017-09-08 16:09:00 -04:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
2018-01-05 15:35:01 -05:00
|
|
|
//! Utilities for converting path strokes to fills.
|
|
|
|
|
2019-02-20 15:41:43 -05:00
|
|
|
use crate::basic::line_segment::LineSegmentF32;
|
|
|
|
use crate::basic::rect::RectF32;
|
|
|
|
use crate::outline::{Contour, Outline};
|
2019-05-09 15:29:39 -04:00
|
|
|
use crate::segment::Segment;
|
2019-02-20 15:41:43 -05:00
|
|
|
use std::mem;
|
2017-09-08 19:49:15 -04:00
|
|
|
|
2019-02-20 21:27:18 -05:00
|
|
|
const TOLERANCE: f32 = 0.01;
|
|
|
|
|
2019-02-20 15:41:43 -05:00
|
|
|
pub struct OutlineStrokeToFill {
|
|
|
|
pub outline: Outline,
|
2019-02-21 23:19:42 -05:00
|
|
|
pub stroke_width: f32,
|
2019-02-20 15:41:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl OutlineStrokeToFill {
|
|
|
|
#[inline]
|
2019-02-21 23:19:42 -05:00
|
|
|
pub fn new(outline: Outline, stroke_width: f32) -> OutlineStrokeToFill {
|
2019-04-29 19:46:35 -04:00
|
|
|
OutlineStrokeToFill {
|
|
|
|
outline,
|
|
|
|
stroke_width,
|
|
|
|
}
|
2019-02-20 15:41:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn offset(&mut self) {
|
|
|
|
let mut new_bounds = None;
|
|
|
|
for contour in &mut self.outline.contours {
|
|
|
|
let input = mem::replace(contour, Contour::new());
|
|
|
|
let mut contour_stroke_to_fill =
|
2019-02-21 23:19:42 -05:00
|
|
|
ContourStrokeToFill::new(input, Contour::new(), self.stroke_width * 0.5);
|
2019-02-20 15:41:43 -05:00
|
|
|
contour_stroke_to_fill.offset_forward();
|
|
|
|
contour_stroke_to_fill.offset_backward();
|
|
|
|
*contour = contour_stroke_to_fill.output;
|
|
|
|
contour.update_bounds(&mut new_bounds);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.outline.bounds = new_bounds.unwrap_or_else(|| RectF32::default());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ContourStrokeToFill {
|
|
|
|
input: Contour,
|
|
|
|
output: Contour,
|
|
|
|
radius: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ContourStrokeToFill {
|
|
|
|
#[inline]
|
|
|
|
fn new(input: Contour, output: Contour, radius: f32) -> ContourStrokeToFill {
|
2019-04-29 19:46:35 -04:00
|
|
|
ContourStrokeToFill {
|
|
|
|
input,
|
|
|
|
output,
|
|
|
|
radius,
|
|
|
|
}
|
2019-02-20 15:41:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
fn offset_forward(&mut self) {
|
2019-02-20 19:21:58 -05:00
|
|
|
for segment in self.input.iter() {
|
|
|
|
segment.offset(self.radius, &mut self.output);
|
|
|
|
}
|
|
|
|
}
|
2019-02-20 15:41:43 -05:00
|
|
|
|
2019-02-20 19:21:58 -05:00
|
|
|
fn offset_backward(&mut self) {
|
|
|
|
// FIXME(pcwalton)
|
2019-04-29 19:46:35 -04:00
|
|
|
let mut segments: Vec<_> = self
|
|
|
|
.input
|
|
|
|
.iter()
|
|
|
|
.map(|segment| segment.reversed())
|
|
|
|
.collect();
|
2019-02-20 19:21:58 -05:00
|
|
|
segments.reverse();
|
|
|
|
for segment in &segments {
|
|
|
|
segment.offset(self.radius, &mut self.output);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-02-20 15:41:43 -05:00
|
|
|
|
2019-02-20 19:21:58 -05:00
|
|
|
trait Offset {
|
|
|
|
fn offset(&self, distance: f32, contour: &mut Contour);
|
2019-02-20 21:27:18 -05:00
|
|
|
fn offset_once(&self, distance: f32) -> Self;
|
2019-05-09 15:29:39 -04:00
|
|
|
fn error_is_within_tolerance(&self, other: &Segment, distance: f32) -> bool;
|
2019-02-20 19:21:58 -05:00
|
|
|
}
|
2019-02-20 15:41:43 -05:00
|
|
|
|
2019-05-09 15:29:39 -04:00
|
|
|
impl Offset for Segment {
|
2019-02-20 19:21:58 -05:00
|
|
|
fn offset(&self, distance: f32, contour: &mut Contour) {
|
2019-02-20 21:27:18 -05:00
|
|
|
if self.baseline.square_length() < TOLERANCE * TOLERANCE {
|
|
|
|
contour.push_full_segment(self, true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let candidate = self.offset_once(distance);
|
|
|
|
if self.error_is_within_tolerance(&candidate, distance) {
|
|
|
|
contour.push_full_segment(&candidate, true);
|
2019-02-20 19:21:58 -05:00
|
|
|
return;
|
|
|
|
}
|
2019-02-20 15:41:43 -05:00
|
|
|
|
2019-04-29 19:43:24 -04:00
|
|
|
debug!("--- SPLITTING ---");
|
|
|
|
debug!("... PRE-SPLIT: {:?}", self);
|
2019-02-20 21:27:18 -05:00
|
|
|
let (before, after) = self.split(0.5);
|
2019-04-29 19:43:24 -04:00
|
|
|
debug!("... AFTER-SPLIT: {:?} {:?}", before, after);
|
2019-02-20 21:27:18 -05:00
|
|
|
before.offset(distance, contour);
|
|
|
|
after.offset(distance, contour);
|
|
|
|
}
|
|
|
|
|
2019-05-09 15:29:39 -04:00
|
|
|
fn offset_once(&self, distance: f32) -> Segment {
|
2019-02-20 21:27:18 -05:00
|
|
|
if self.is_line() {
|
2019-05-09 15:29:39 -04:00
|
|
|
return Segment::line(&self.baseline.offset(distance));
|
2019-02-20 21:27:18 -05:00
|
|
|
}
|
|
|
|
|
2019-02-20 19:21:58 -05:00
|
|
|
if self.is_quadratic() {
|
2019-05-13 15:17:49 -04:00
|
|
|
let mut segment_0 = LineSegmentF32::new(self.baseline.from(), self.ctrl.from());
|
|
|
|
let mut segment_1 = LineSegmentF32::new(self.ctrl.from(), self.baseline.to());
|
2019-02-20 19:21:58 -05:00
|
|
|
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),
|
|
|
|
};
|
2019-05-13 15:17:49 -04:00
|
|
|
let baseline = LineSegmentF32::new(segment_0.from(), segment_1.to());
|
|
|
|
return Segment::quadratic(&baseline, ctrl);
|
2019-02-20 15:41:43 -05:00
|
|
|
}
|
|
|
|
|
2019-02-20 19:21:58 -05:00
|
|
|
debug_assert!(self.is_cubic());
|
2019-02-20 15:41:43 -05:00
|
|
|
|
2019-02-20 19:21:58 -05:00
|
|
|
if self.baseline.from() == self.ctrl.from() {
|
2019-05-13 15:17:49 -04:00
|
|
|
let mut segment_0 = LineSegmentF32::new(self.baseline.from(), self.ctrl.to());
|
|
|
|
let mut segment_1 = LineSegmentF32::new(self.ctrl.to(), self.baseline.to());
|
2019-02-20 19:21:58 -05:00
|
|
|
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),
|
|
|
|
};
|
2019-05-13 15:17:49 -04:00
|
|
|
let baseline = LineSegmentF32::new(segment_0.from(), segment_1.to());
|
|
|
|
let ctrl = LineSegmentF32::new(segment_0.from(), ctrl);
|
2019-05-09 15:29:39 -04:00
|
|
|
return Segment::cubic(&baseline, &ctrl);
|
2019-02-20 19:21:58 -05:00
|
|
|
}
|
2019-02-20 15:41:43 -05:00
|
|
|
|
2019-02-20 19:21:58 -05:00
|
|
|
if self.ctrl.to() == self.baseline.to() {
|
2019-05-13 15:17:49 -04:00
|
|
|
let mut segment_0 = LineSegmentF32::new(self.baseline.from(), self.ctrl.from());
|
|
|
|
let mut segment_1 = LineSegmentF32::new(self.ctrl.from(), self.baseline.to());
|
2019-02-20 19:21:58 -05:00
|
|
|
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),
|
|
|
|
};
|
2019-05-13 15:17:49 -04:00
|
|
|
let baseline = LineSegmentF32::new(segment_0.from(), segment_1.to());
|
|
|
|
let ctrl = LineSegmentF32::new(ctrl, segment_1.to());
|
2019-05-09 15:29:39 -04:00
|
|
|
return Segment::cubic(&baseline, &ctrl);
|
2019-02-20 19:21:58 -05:00
|
|
|
}
|
2019-02-20 15:41:43 -05:00
|
|
|
|
2019-05-13 15:17:49 -04:00
|
|
|
let mut segment_0 = LineSegmentF32::new(self.baseline.from(), self.ctrl.from());
|
|
|
|
let mut segment_1 = LineSegmentF32::new(self.ctrl.from(), self.ctrl.to());
|
|
|
|
let mut segment_2 = LineSegmentF32::new(self.ctrl.to(), self.baseline.to());
|
2019-02-20 19:21:58 -05:00
|
|
|
segment_0 = segment_0.offset(distance);
|
|
|
|
segment_1 = segment_1.offset(distance);
|
|
|
|
segment_2 = segment_2.offset(distance);
|
2019-04-29 19:46:35 -04:00
|
|
|
let (ctrl_0, ctrl_1) = match (
|
|
|
|
segment_0.intersection_t(&segment_1),
|
|
|
|
segment_1.intersection_t(&segment_2),
|
|
|
|
) {
|
2019-02-20 19:21:58 -05:00
|
|
|
(Some(t0), Some(t1)) => (segment_0.sample(t0), segment_1.sample(t1)),
|
2019-04-29 19:46:35 -04:00
|
|
|
_ => (
|
|
|
|
segment_0.to().lerp(segment_1.from(), 0.5),
|
|
|
|
segment_1.to().lerp(segment_2.from(), 0.5),
|
|
|
|
),
|
2019-02-20 19:21:58 -05:00
|
|
|
};
|
2019-05-13 15:17:49 -04:00
|
|
|
let baseline = LineSegmentF32::new(segment_0.from(), segment_2.to());
|
|
|
|
let ctrl = LineSegmentF32::new(ctrl_0, ctrl_1);
|
2019-05-09 15:29:39 -04:00
|
|
|
Segment::cubic(&baseline, &ctrl)
|
2019-02-20 21:27:18 -05:00
|
|
|
}
|
|
|
|
|
2019-05-09 15:29:39 -04:00
|
|
|
fn error_is_within_tolerance(&self, other: &Segment, distance: f32) -> bool {
|
2019-04-29 19:46:35 -04:00
|
|
|
let (mut min, mut max) = (
|
|
|
|
f32::abs(distance) - TOLERANCE,
|
|
|
|
f32::abs(distance) + TOLERANCE,
|
|
|
|
);
|
2019-02-20 21:27:18 -05:00
|
|
|
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;
|
|
|
|
// FIXME(pcwalton): Use signed distance!
|
|
|
|
let (this_p, other_p) = (self.sample(t), other.sample(t));
|
|
|
|
let vector = this_p - other_p;
|
|
|
|
let square_distance = vector.square_length();
|
2019-04-29 19:46:35 -04:00
|
|
|
debug!(
|
|
|
|
"this_p={:?} other_p={:?} vector={:?} sqdist={:?} min={:?} max={:?}",
|
|
|
|
this_p, other_p, vector, square_distance, min, max
|
|
|
|
);
|
2019-02-20 21:27:18 -05:00
|
|
|
if square_distance < min || square_distance > max {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
const SAMPLE_COUNT: u32 = 16;
|
2019-02-20 15:41:43 -05:00
|
|
|
}
|
|
|
|
}
|