diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs index c3a0f033..527d7f4b 100644 --- a/canvas/src/lib.rs +++ b/canvas/src/lib.rs @@ -19,6 +19,7 @@ use pathfinder_geometry::basic::vector::Vector2F; use pathfinder_geometry::basic::rect::RectF; use pathfinder_geometry::basic::transform2d::Transform2DF; use pathfinder_geometry::color::ColorU; +use pathfinder_geometry::dash::OutlineDash; use pathfinder_geometry::outline::{ArcDirection, Contour, Outline}; use pathfinder_geometry::stroke::{LineCap, LineJoin as StrokeLineJoin}; use pathfinder_geometry::stroke::{OutlineStrokeToFill, StrokeStyle}; @@ -151,13 +152,15 @@ impl CanvasRenderingContext2D { } #[inline] - pub fn set_fill_style(&mut self, new_fill_style: FillStyle) { - self.current_state.fill_paint = new_fill_style.to_paint(); - } + pub fn set_line_dash(&mut self, mut new_line_dash: Vec) { + // Duplicate and concatenate if an odd number of dashes are present. + if new_line_dash.len() % 2 == 1 { + let mut real_line_dash = new_line_dash.clone(); + real_line_dash.extend(new_line_dash.into_iter()); + new_line_dash = real_line_dash; + } - #[inline] - pub fn set_stroke_style(&mut self, new_stroke_style: FillStyle) { - self.current_state.stroke_paint = new_stroke_style.to_paint(); + self.current_state.line_dash = new_line_dash } // Text styles @@ -172,6 +175,18 @@ impl CanvasRenderingContext2D { self.current_state.text_align = new_text_align; } + // Fill and stroke styles + + #[inline] + pub fn set_fill_style(&mut self, new_fill_style: FillStyle) { + self.current_state.fill_paint = new_fill_style.to_paint(); + } + + #[inline] + pub fn set_stroke_style(&mut self, new_stroke_style: FillStyle) { + self.current_state.stroke_paint = new_stroke_style.to_paint(); + } + // Drawing paths #[inline] @@ -193,10 +208,17 @@ impl CanvasRenderingContext2D { let mut stroke_style = self.current_state.resolve_stroke_style(); stroke_style.line_width = f32::max(stroke_style.line_width, HAIRLINE_STROKE_WIDTH); - let outline = path.into_outline(); + let mut outline = path.into_outline(); + if !self.current_state.line_dash.is_empty() { + let mut dash = OutlineDash::new(&outline, &self.current_state.line_dash); + dash.dash(); + outline = dash.into_outline(); + } + let mut stroke_to_fill = OutlineStrokeToFill::new(&outline, stroke_style); stroke_to_fill.offset(); - let mut outline = stroke_to_fill.into_outline(); + outline = stroke_to_fill.into_outline(); + outline.transform(&self.current_state.transform); self.scene.push_path(PathObject::new(outline, paint_id, String::new())) } @@ -250,13 +272,14 @@ struct State { transform: Transform2DF, font_collection: Arc, font_size: f32, - text_align: TextAlign, - fill_paint: Paint, - stroke_paint: Paint, line_width: f32, line_cap: LineCap, line_join: LineJoin, miter_limit: f32, + line_dash: Vec, + fill_paint: Paint, + stroke_paint: Paint, + text_align: TextAlign, global_alpha: f32, } @@ -266,13 +289,14 @@ impl State { transform: Transform2DF::default(), font_collection: default_font_collection, font_size: DEFAULT_FONT_SIZE, - text_align: TextAlign::Left, - fill_paint: Paint { color: ColorU::black() }, - stroke_paint: Paint { color: ColorU::black() }, line_width: 1.0, line_cap: LineCap::Butt, line_join: LineJoin::Miter, miter_limit: 10.0, + line_dash: vec![], + fill_paint: Paint { color: ColorU::black() }, + stroke_paint: Paint { color: ColorU::black() }, + text_align: TextAlign::Left, global_alpha: 1.0, } } diff --git a/geometry/src/dash.rs b/geometry/src/dash.rs new file mode 100644 index 00000000..0029ec92 --- /dev/null +++ b/geometry/src/dash.rs @@ -0,0 +1,120 @@ +// pathfinder/geometry/src/dash.rs +// +// Copyright © 2019 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Line dashing support. + +use crate::outline::{Contour, Outline, PushSegmentFlags}; +use std::mem; + +pub struct OutlineDash<'a> { + input: &'a Outline, + output: Outline, + state: DashState<'a>, +} + +impl<'a> OutlineDash<'a> { + #[inline] + pub fn new(input: &'a Outline, dashes: &'a [f32]) -> OutlineDash<'a> { + OutlineDash { input, output: Outline::new(), state: DashState::new(dashes) } + } + + pub fn dash(&mut self) { + for contour in &self.input.contours { + ContourDash::new(contour, &mut self.output, &mut self.state).dash() + } + } + + pub fn into_outline(mut self) -> Outline { + if self.state.is_on() { + self.output.push_contour(self.state.output); + } + self.output + } +} + +struct ContourDash<'a, 'b, 'c> { + input: &'a Contour, + output: &'b mut Outline, + state: &'c mut DashState<'a>, +} + +impl<'a, 'b, 'c> ContourDash<'a, 'b, 'c> { + fn new(input: &'a Contour, output: &'b mut Outline, state: &'c mut DashState<'a>) + -> ContourDash<'a, 'b, 'c> { + ContourDash { input, output, state } + } + + fn dash(&mut self) { + let (mut iterator, mut queued_segment) = (self.input.iter(), None); + loop { + if queued_segment.is_none() { + match iterator.next() { + None => break, + Some(segment) => queued_segment = Some(segment), + } + } + + let mut current_segment = queued_segment.take().unwrap(); + let mut distance = self.state.distance_left; + + let t = current_segment.time_for_distance(distance); + if t < 1.0 { + let (prev_segment, next_segment) = current_segment.split(t); + current_segment = prev_segment; + queued_segment = Some(next_segment); + } else { + distance = current_segment.arc_length(); + } + + if self.state.is_on() { + self.state.output.push_segment(¤t_segment, PushSegmentFlags::empty()); + } + + self.state.distance_left -= distance; + if self.state.distance_left < EPSILON { + if self.state.is_on() { + self.output.push_contour(mem::replace(&mut self.state.output, Contour::new())); + } + + self.state.current_dash_index += 1; + if self.state.current_dash_index == self.state.dashes.len() { + self.state.current_dash_index = 0; + } + + self.state.distance_left = self.state.dashes[self.state.current_dash_index]; + } + } + + const EPSILON: f32 = 0.0001; + } +} + +struct DashState<'a> { + output: Contour, + dashes: &'a [f32], + current_dash_index: usize, + distance_left: f32, +} + +impl<'a> DashState<'a> { + fn new(dashes: &'a [f32]) -> DashState<'a> { + DashState { + output: Contour::new(), + dashes, + current_dash_index: 0, + distance_left: dashes[0], + } + } + + #[inline] + fn is_on(&self) -> bool { + self.current_dash_index % 2 == 0 + } +} diff --git a/geometry/src/lib.rs b/geometry/src/lib.rs index 9379e471..8445b7ae 100644 --- a/geometry/src/lib.rs +++ b/geometry/src/lib.rs @@ -20,6 +20,7 @@ extern crate log; pub mod basic; pub mod clip; pub mod color; +pub mod dash; pub mod orientation; pub mod outline; pub mod segment; diff --git a/geometry/src/segment.rs b/geometry/src/segment.rs index c1e8559d..2cd8adf4 100644 --- a/geometry/src/segment.rs +++ b/geometry/src/segment.rs @@ -223,6 +223,16 @@ impl Segment { flags: self.flags, } } + + pub fn arc_length(&self) -> f32 { + // FIXME(pcwalton) + self.baseline.vector().length() + } + + pub fn time_for_distance(&self, distance: f32) -> f32 { + // FIXME(pcwalton) + distance / self.arc_length() + } } #[derive(Clone, Copy, Debug, PartialEq)]