Implement interior miter line joins (not yet endpoints)
This commit is contained in:
parent
272b63a017
commit
e282eb57d5
|
@ -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();
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue