Implement interior miter line joins (not yet endpoints)

This commit is contained in:
Patrick Walton 2019-05-16 11:10:15 -07:00
parent 272b63a017
commit e282eb57d5
3 changed files with 91 additions and 23 deletions

View File

@ -19,7 +19,7 @@ use pathfinder_geometry::basic::rect::RectF32;
use pathfinder_geometry::basic::transform2d::Transform2DF32;
use pathfinder_geometry::color::ColorU;
use pathfinder_geometry::outline::{Contour, Outline};
use pathfinder_geometry::stroke::{LineCap, OutlineStrokeToFill, StrokeStyle};
use pathfinder_geometry::stroke::{LineCap, LineJoin, OutlineStrokeToFill, StrokeStyle};
use pathfinder_renderer::paint::Paint;
use pathfinder_renderer::scene::{PathObject, Scene};
use pathfinder_text::{SceneExt, TextRenderMode};
@ -132,6 +132,11 @@ impl CanvasRenderingContext2D {
self.current_state.stroke_style.line_cap = new_line_cap
}
#[inline]
pub fn set_line_join(&mut self, new_line_join: LineJoin) {
self.current_state.stroke_style.line_join = new_line_join
}
#[inline]
pub fn set_fill_style(&mut self, new_fill_style: FillStyle) {
self.current_state.fill_paint = new_fill_style.to_paint();

View File

@ -28,6 +28,7 @@ pub struct OutlineStrokeToFill {
pub struct StrokeStyle {
pub line_width: f32,
pub line_cap: LineCap,
pub line_join: LineJoin,
}
#[derive(Clone, Copy, Debug, PartialEq)]
@ -36,6 +37,12 @@ pub enum LineCap {
Square,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LineJoin {
Miter,
Bevel,
}
impl OutlineStrokeToFill {
#[inline]
pub fn new(outline: Outline, style: StrokeStyle) -> OutlineStrokeToFill {
@ -48,21 +55,26 @@ impl OutlineStrokeToFill {
let closed = input.closed;
let mut stroker = ContourStrokeToFill::new(input,
Contour::new(),
self.style.line_width * 0.5);
self.style.line_width * 0.5,
self.style.line_join);
stroker.offset_forward();
if closed {
// TODO(pcwalton): Line join.
stroker.output.closed = true;
new_contours.push(stroker.output);
stroker = ContourStrokeToFill::new(stroker.input,
Contour::new(),
self.style.line_width * 0.5);
self.style.line_width * 0.5,
self.style.line_join);
} else {
self.add_cap(&mut stroker.output);
}
stroker.offset_backward();
if !closed {
if closed {
// TODO(pcwalton): Line join.
} else {
self.add_cap(&mut stroker.output);
}
@ -101,21 +113,18 @@ struct ContourStrokeToFill {
input: Contour,
output: Contour,
radius: f32,
join: LineJoin,
}
impl ContourStrokeToFill {
#[inline]
fn new(input: Contour, output: Contour, radius: f32) -> ContourStrokeToFill {
ContourStrokeToFill {
input,
output,
radius,
}
fn new(input: Contour, output: Contour, radius: f32, join: LineJoin) -> ContourStrokeToFill {
ContourStrokeToFill { input, output, radius, join }
}
fn offset_forward(&mut self) {
for segment in self.input.iter() {
segment.offset(self.radius, &mut self.output);
segment.offset(self.radius, self.join, &mut self.output);
}
}
@ -127,27 +136,28 @@ impl ContourStrokeToFill {
.collect();
segments.reverse();
for segment in &segments {
segment.offset(self.radius, &mut self.output);
segment.offset(self.radius, self.join, &mut self.output);
}
}
}
trait Offset {
fn offset(&self, distance: f32, contour: &mut Contour);
fn offset(&self, distance: f32, join: LineJoin, contour: &mut Contour);
fn add_to_contour(&self, join: LineJoin, 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, contour: &mut Contour) {
fn offset(&self, distance: f32, join: LineJoin, contour: &mut Contour) {
if self.baseline.square_length() < TOLERANCE * TOLERANCE {
contour.push_full_segment(self, true);
self.add_to_contour(join, contour);
return;
}
let candidate = self.offset_once(distance);
if self.error_is_within_tolerance(&candidate, distance) {
contour.push_full_segment(&candidate, true);
candidate.add_to_contour(join, contour);
return;
}
@ -155,8 +165,33 @@ impl Offset for Segment {
debug!("... PRE-SPLIT: {:?}", self);
let (before, after) = self.split(0.5);
debug!("... AFTER-SPLIT: {:?} {:?}", before, after);
before.offset(distance, contour);
after.offset(distance, contour);
before.offset(distance, join, contour);
after.offset(distance, join, contour);
}
fn add_to_contour(&self, join: LineJoin, contour: &mut Contour) {
// Add join.
// TODO(pcwalton): Miter limit.
if join == LineJoin::Miter && contour.len() >= 2 {
let (p0, p1) = (contour.position_of_last(2), contour.position_of_last(1));
let p3 = self.baseline.from();
let p4 = if self.is_line() {
self.baseline.to()
} else {
// NB: If you change the representation of quadratic curves, you will need to
// change this.
self.ctrl.from()
};
let prev_tangent = LineSegmentF32::new(p0, p1);
let next_tangent = LineSegmentF32::new(p4, p3);
if let Some(prev_tangent_t) = prev_tangent.intersection_t(&next_tangent) {
contour.push_endpoint(prev_tangent.sample(prev_tangent_t));
}
}
// Push segment.
contour.push_full_segment(self, true);
}
fn offset_once(&self, distance: f32) -> Segment {
@ -260,7 +295,11 @@ impl Offset for Segment {
impl Default for StrokeStyle {
#[inline]
fn default() -> StrokeStyle {
StrokeStyle { line_width: 1.0, line_cap: LineCap::default() }
StrokeStyle {
line_width: 1.0,
line_cap: LineCap::default(),
line_join: LineJoin::default(),
}
}
}
@ -268,3 +307,8 @@ impl Default for LineCap {
#[inline]
fn default() -> LineCap { LineCap::Butt }
}
impl Default for LineJoin {
#[inline]
fn default() -> LineJoin { LineJoin::Miter }
}

View File

@ -20,14 +20,14 @@ use pathfinder_geometry::basic::transform2d::{Transform2DF32, Transform2DF32Path
use pathfinder_geometry::color::ColorU;
use pathfinder_geometry::outline::Outline;
use pathfinder_geometry::segment::{Segment, SegmentFlags};
use pathfinder_geometry::stroke::{LineCap, OutlineStrokeToFill, StrokeStyle};
use pathfinder_geometry::stroke::{LineCap, LineJoin, OutlineStrokeToFill, StrokeStyle};
use pathfinder_renderer::paint::Paint;
use pathfinder_renderer::scene::{PathObject, Scene};
use std::fmt::{Display, Formatter, Result as FormatResult};
use std::mem;
use usvg::{Color as SvgColor, LineCap as UsvgLineCap, Node, NodeExt, NodeKind, Opacity};
use usvg::{Paint as UsvgPaint, PathSegment as UsvgPathSegment, Rect as UsvgRect};
use usvg::{Transform as UsvgTransform, Tree, Visibility};
use usvg::{Color as SvgColor, LineCap as UsvgLineCap, LineJoin as UsvgLineJoin, Node, NodeExt};
use usvg::{NodeKind, Opacity, Paint as UsvgPaint, PathSegment as UsvgPathSegment};
use usvg::{Rect as UsvgRect, Transform as UsvgTransform, Tree, Visibility};
const HAIRLINE_STROKE_WIDTH: f32 = 0.0333;
@ -139,6 +139,7 @@ impl BuiltSVG {
let stroke_style = StrokeStyle {
line_width: f32::max(stroke.width.value() as f32, HAIRLINE_STROKE_WIDTH),
line_cap: LineCap::from_usvg_line_cap(stroke.linecap),
line_join: LineJoin::from_usvg_line_join(stroke.linejoin),
};
let path = UsvgPathToSegments::new(path.segments.iter().cloned());
@ -400,3 +401,21 @@ impl LineCapExt for LineCap {
}
}
}
trait LineJoinExt {
fn from_usvg_line_join(usvg_line_join: UsvgLineJoin) -> Self;
}
impl LineJoinExt for LineJoin {
#[inline]
fn from_usvg_line_join(usvg_line_join: UsvgLineJoin) -> LineJoin {
match usvg_line_join {
UsvgLineJoin::Miter => LineJoin::Miter,
UsvgLineJoin::Round => {
// TODO(pcwalton)
LineJoin::Miter
}
UsvgLineJoin::Bevel => LineJoin::Bevel,
}
}
}