Implement dashing for line segments (no curves yet)

This commit is contained in:
Patrick Walton 2019-06-03 14:16:58 -07:00
parent a1b0df0a42
commit 1a42bbf4d3
4 changed files with 169 additions and 14 deletions

View File

@ -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<f32>) {
// 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<FontCollection>,
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<f32>,
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,
}
}

120
geometry/src/dash.rs Normal file
View File

@ -0,0 +1,120 @@
// pathfinder/geometry/src/dash.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// 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.
//! 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(&current_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
}
}

View File

@ -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;

View File

@ -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)]