Implement dashing for line segments (no curves yet)
This commit is contained in:
parent
a1b0df0a42
commit
1a42bbf4d3
|
@ -19,6 +19,7 @@ use pathfinder_geometry::basic::vector::Vector2F;
|
||||||
use pathfinder_geometry::basic::rect::RectF;
|
use pathfinder_geometry::basic::rect::RectF;
|
||||||
use pathfinder_geometry::basic::transform2d::Transform2DF;
|
use pathfinder_geometry::basic::transform2d::Transform2DF;
|
||||||
use pathfinder_geometry::color::ColorU;
|
use pathfinder_geometry::color::ColorU;
|
||||||
|
use pathfinder_geometry::dash::OutlineDash;
|
||||||
use pathfinder_geometry::outline::{ArcDirection, Contour, Outline};
|
use pathfinder_geometry::outline::{ArcDirection, Contour, Outline};
|
||||||
use pathfinder_geometry::stroke::{LineCap, LineJoin as StrokeLineJoin};
|
use pathfinder_geometry::stroke::{LineCap, LineJoin as StrokeLineJoin};
|
||||||
use pathfinder_geometry::stroke::{OutlineStrokeToFill, StrokeStyle};
|
use pathfinder_geometry::stroke::{OutlineStrokeToFill, StrokeStyle};
|
||||||
|
@ -151,13 +152,15 @@ impl CanvasRenderingContext2D {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_fill_style(&mut self, new_fill_style: FillStyle) {
|
pub fn set_line_dash(&mut self, mut new_line_dash: Vec<f32>) {
|
||||||
self.current_state.fill_paint = new_fill_style.to_paint();
|
// 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]
|
self.current_state.line_dash = new_line_dash
|
||||||
pub fn set_stroke_style(&mut self, new_stroke_style: FillStyle) {
|
|
||||||
self.current_state.stroke_paint = new_stroke_style.to_paint();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text styles
|
// Text styles
|
||||||
|
@ -172,6 +175,18 @@ impl CanvasRenderingContext2D {
|
||||||
self.current_state.text_align = new_text_align;
|
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
|
// Drawing paths
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -193,10 +208,17 @@ impl CanvasRenderingContext2D {
|
||||||
let mut stroke_style = self.current_state.resolve_stroke_style();
|
let mut stroke_style = self.current_state.resolve_stroke_style();
|
||||||
stroke_style.line_width = f32::max(stroke_style.line_width, HAIRLINE_STROKE_WIDTH);
|
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);
|
let mut stroke_to_fill = OutlineStrokeToFill::new(&outline, stroke_style);
|
||||||
stroke_to_fill.offset();
|
stroke_to_fill.offset();
|
||||||
let mut outline = stroke_to_fill.into_outline();
|
outline = stroke_to_fill.into_outline();
|
||||||
|
|
||||||
outline.transform(&self.current_state.transform);
|
outline.transform(&self.current_state.transform);
|
||||||
self.scene.push_path(PathObject::new(outline, paint_id, String::new()))
|
self.scene.push_path(PathObject::new(outline, paint_id, String::new()))
|
||||||
}
|
}
|
||||||
|
@ -250,13 +272,14 @@ struct State {
|
||||||
transform: Transform2DF,
|
transform: Transform2DF,
|
||||||
font_collection: Arc<FontCollection>,
|
font_collection: Arc<FontCollection>,
|
||||||
font_size: f32,
|
font_size: f32,
|
||||||
text_align: TextAlign,
|
|
||||||
fill_paint: Paint,
|
|
||||||
stroke_paint: Paint,
|
|
||||||
line_width: f32,
|
line_width: f32,
|
||||||
line_cap: LineCap,
|
line_cap: LineCap,
|
||||||
line_join: LineJoin,
|
line_join: LineJoin,
|
||||||
miter_limit: f32,
|
miter_limit: f32,
|
||||||
|
line_dash: Vec<f32>,
|
||||||
|
fill_paint: Paint,
|
||||||
|
stroke_paint: Paint,
|
||||||
|
text_align: TextAlign,
|
||||||
global_alpha: f32,
|
global_alpha: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,13 +289,14 @@ impl State {
|
||||||
transform: Transform2DF::default(),
|
transform: Transform2DF::default(),
|
||||||
font_collection: default_font_collection,
|
font_collection: default_font_collection,
|
||||||
font_size: DEFAULT_FONT_SIZE,
|
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_width: 1.0,
|
||||||
line_cap: LineCap::Butt,
|
line_cap: LineCap::Butt,
|
||||||
line_join: LineJoin::Miter,
|
line_join: LineJoin::Miter,
|
||||||
miter_limit: 10.0,
|
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,
|
global_alpha: 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(¤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
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ extern crate log;
|
||||||
pub mod basic;
|
pub mod basic;
|
||||||
pub mod clip;
|
pub mod clip;
|
||||||
pub mod color;
|
pub mod color;
|
||||||
|
pub mod dash;
|
||||||
pub mod orientation;
|
pub mod orientation;
|
||||||
pub mod outline;
|
pub mod outline;
|
||||||
pub mod segment;
|
pub mod segment;
|
||||||
|
|
|
@ -223,6 +223,16 @@ impl Segment {
|
||||||
flags: self.flags,
|
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)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
|
Loading…
Reference in New Issue