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::basic::transform2d::Transform2DF32;
|
||||||
use pathfinder_geometry::color::ColorU;
|
use pathfinder_geometry::color::ColorU;
|
||||||
use pathfinder_geometry::outline::{Contour, Outline};
|
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::paint::Paint;
|
||||||
use pathfinder_renderer::scene::{PathObject, Scene};
|
use pathfinder_renderer::scene::{PathObject, Scene};
|
||||||
use pathfinder_text::{SceneExt, TextRenderMode};
|
use pathfinder_text::{SceneExt, TextRenderMode};
|
||||||
|
@ -132,6 +132,11 @@ impl CanvasRenderingContext2D {
|
||||||
self.current_state.stroke_style.line_cap = new_line_cap
|
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]
|
#[inline]
|
||||||
pub fn set_fill_style(&mut self, new_fill_style: FillStyle) {
|
pub fn set_fill_style(&mut self, new_fill_style: FillStyle) {
|
||||||
self.current_state.fill_paint = new_fill_style.to_paint();
|
self.current_state.fill_paint = new_fill_style.to_paint();
|
||||||
|
|
|
@ -28,6 +28,7 @@ pub struct OutlineStrokeToFill {
|
||||||
pub struct StrokeStyle {
|
pub struct StrokeStyle {
|
||||||
pub line_width: f32,
|
pub line_width: f32,
|
||||||
pub line_cap: LineCap,
|
pub line_cap: LineCap,
|
||||||
|
pub line_join: LineJoin,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
@ -36,6 +37,12 @@ pub enum LineCap {
|
||||||
Square,
|
Square,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum LineJoin {
|
||||||
|
Miter,
|
||||||
|
Bevel,
|
||||||
|
}
|
||||||
|
|
||||||
impl OutlineStrokeToFill {
|
impl OutlineStrokeToFill {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(outline: Outline, style: StrokeStyle) -> OutlineStrokeToFill {
|
pub fn new(outline: Outline, style: StrokeStyle) -> OutlineStrokeToFill {
|
||||||
|
@ -48,21 +55,26 @@ impl OutlineStrokeToFill {
|
||||||
let closed = input.closed;
|
let closed = input.closed;
|
||||||
let mut stroker = ContourStrokeToFill::new(input,
|
let mut stroker = ContourStrokeToFill::new(input,
|
||||||
Contour::new(),
|
Contour::new(),
|
||||||
self.style.line_width * 0.5);
|
self.style.line_width * 0.5,
|
||||||
|
self.style.line_join);
|
||||||
|
|
||||||
stroker.offset_forward();
|
stroker.offset_forward();
|
||||||
if closed {
|
if closed {
|
||||||
|
// TODO(pcwalton): Line join.
|
||||||
stroker.output.closed = true;
|
stroker.output.closed = true;
|
||||||
new_contours.push(stroker.output);
|
new_contours.push(stroker.output);
|
||||||
stroker = ContourStrokeToFill::new(stroker.input,
|
stroker = ContourStrokeToFill::new(stroker.input,
|
||||||
Contour::new(),
|
Contour::new(),
|
||||||
self.style.line_width * 0.5);
|
self.style.line_width * 0.5,
|
||||||
|
self.style.line_join);
|
||||||
} else {
|
} else {
|
||||||
self.add_cap(&mut stroker.output);
|
self.add_cap(&mut stroker.output);
|
||||||
}
|
}
|
||||||
|
|
||||||
stroker.offset_backward();
|
stroker.offset_backward();
|
||||||
if !closed {
|
if closed {
|
||||||
|
// TODO(pcwalton): Line join.
|
||||||
|
} else {
|
||||||
self.add_cap(&mut stroker.output);
|
self.add_cap(&mut stroker.output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,21 +113,18 @@ struct ContourStrokeToFill {
|
||||||
input: Contour,
|
input: Contour,
|
||||||
output: Contour,
|
output: Contour,
|
||||||
radius: f32,
|
radius: f32,
|
||||||
|
join: LineJoin,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContourStrokeToFill {
|
impl ContourStrokeToFill {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn new(input: Contour, output: Contour, radius: f32) -> ContourStrokeToFill {
|
fn new(input: Contour, output: Contour, radius: f32, join: LineJoin) -> ContourStrokeToFill {
|
||||||
ContourStrokeToFill {
|
ContourStrokeToFill { input, output, radius, join }
|
||||||
input,
|
|
||||||
output,
|
|
||||||
radius,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn offset_forward(&mut self) {
|
fn offset_forward(&mut self) {
|
||||||
for segment in self.input.iter() {
|
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();
|
.collect();
|
||||||
segments.reverse();
|
segments.reverse();
|
||||||
for segment in &segments {
|
for segment in &segments {
|
||||||
segment.offset(self.radius, &mut self.output);
|
segment.offset(self.radius, self.join, &mut self.output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Offset {
|
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 offset_once(&self, distance: f32) -> Self;
|
||||||
fn error_is_within_tolerance(&self, other: &Segment, distance: f32) -> bool;
|
fn error_is_within_tolerance(&self, other: &Segment, distance: f32) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Offset for Segment {
|
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 {
|
if self.baseline.square_length() < TOLERANCE * TOLERANCE {
|
||||||
contour.push_full_segment(self, true);
|
self.add_to_contour(join, contour);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let candidate = self.offset_once(distance);
|
let candidate = self.offset_once(distance);
|
||||||
if self.error_is_within_tolerance(&candidate, distance) {
|
if self.error_is_within_tolerance(&candidate, distance) {
|
||||||
contour.push_full_segment(&candidate, true);
|
candidate.add_to_contour(join, contour);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,8 +165,33 @@ impl Offset for Segment {
|
||||||
debug!("... PRE-SPLIT: {:?}", self);
|
debug!("... PRE-SPLIT: {:?}", self);
|
||||||
let (before, after) = self.split(0.5);
|
let (before, after) = self.split(0.5);
|
||||||
debug!("... AFTER-SPLIT: {:?} {:?}", before, after);
|
debug!("... AFTER-SPLIT: {:?} {:?}", before, after);
|
||||||
before.offset(distance, contour);
|
before.offset(distance, join, contour);
|
||||||
after.offset(distance, 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 {
|
fn offset_once(&self, distance: f32) -> Segment {
|
||||||
|
@ -260,7 +295,11 @@ impl Offset for Segment {
|
||||||
impl Default for StrokeStyle {
|
impl Default for StrokeStyle {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn default() -> StrokeStyle {
|
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]
|
#[inline]
|
||||||
fn default() -> LineCap { LineCap::Butt }
|
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::color::ColorU;
|
||||||
use pathfinder_geometry::outline::Outline;
|
use pathfinder_geometry::outline::Outline;
|
||||||
use pathfinder_geometry::segment::{Segment, SegmentFlags};
|
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::paint::Paint;
|
||||||
use pathfinder_renderer::scene::{PathObject, Scene};
|
use pathfinder_renderer::scene::{PathObject, Scene};
|
||||||
use std::fmt::{Display, Formatter, Result as FormatResult};
|
use std::fmt::{Display, Formatter, Result as FormatResult};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use usvg::{Color as SvgColor, LineCap as UsvgLineCap, Node, NodeExt, NodeKind, Opacity};
|
use usvg::{Color as SvgColor, LineCap as UsvgLineCap, LineJoin as UsvgLineJoin, Node, NodeExt};
|
||||||
use usvg::{Paint as UsvgPaint, PathSegment as UsvgPathSegment, Rect as UsvgRect};
|
use usvg::{NodeKind, Opacity, Paint as UsvgPaint, PathSegment as UsvgPathSegment};
|
||||||
use usvg::{Transform as UsvgTransform, Tree, Visibility};
|
use usvg::{Rect as UsvgRect, Transform as UsvgTransform, Tree, Visibility};
|
||||||
|
|
||||||
const HAIRLINE_STROKE_WIDTH: f32 = 0.0333;
|
const HAIRLINE_STROKE_WIDTH: f32 = 0.0333;
|
||||||
|
|
||||||
|
@ -139,6 +139,7 @@ impl BuiltSVG {
|
||||||
let stroke_style = StrokeStyle {
|
let stroke_style = StrokeStyle {
|
||||||
line_width: f32::max(stroke.width.value() as f32, HAIRLINE_STROKE_WIDTH),
|
line_width: f32::max(stroke.width.value() as f32, HAIRLINE_STROKE_WIDTH),
|
||||||
line_cap: LineCap::from_usvg_line_cap(stroke.linecap),
|
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());
|
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