Implement square line caps

This commit is contained in:
Patrick Walton 2019-05-16 10:43:43 -07:00
parent 1b9cfa98a9
commit 272b63a017
5 changed files with 112 additions and 29 deletions

View File

@ -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::OutlineStrokeToFill;
use pathfinder_geometry::stroke::{LineCap, OutlineStrokeToFill, StrokeStyle};
use pathfinder_renderer::paint::Paint;
use pathfinder_renderer::scene::{PathObject, Scene};
use pathfinder_text::{SceneExt, TextRenderMode};
@ -115,7 +115,7 @@ impl CanvasRenderingContext2D {
&TextStyle { size: self.current_state.font_size },
&self.current_state.font_collection,
&transform,
TextRenderMode::Stroke(self.current_state.line_width),
TextRenderMode::Stroke(self.current_state.stroke_style),
HintingOptions::None,
paint_id));
}
@ -124,7 +124,12 @@ impl CanvasRenderingContext2D {
#[inline]
pub fn set_line_width(&mut self, new_line_width: f32) {
self.current_state.line_width = new_line_width
self.current_state.stroke_style.line_width = new_line_width
}
#[inline]
pub fn set_line_cap(&mut self, new_line_cap: LineCap) {
self.current_state.stroke_style.line_cap = new_line_cap
}
#[inline]
@ -162,8 +167,10 @@ impl CanvasRenderingContext2D {
let paint = self.current_state.resolve_paint(self.current_state.stroke_paint);
let paint_id = self.scene.push_paint(&paint);
let stroke_width = f32::max(self.current_state.line_width, HAIRLINE_STROKE_WIDTH);
let mut stroke_to_fill = OutlineStrokeToFill::new(path.into_outline(), stroke_width);
let mut stroke_style = self.current_state.stroke_style;
stroke_style.line_width = f32::max(stroke_style.line_width, HAIRLINE_STROKE_WIDTH);
let mut stroke_to_fill = OutlineStrokeToFill::new(path.into_outline(), stroke_style);
stroke_to_fill.offset();
stroke_to_fill.outline.transform(&self.current_state.transform);
self.scene.push_path(PathObject::new(stroke_to_fill.outline, paint_id, String::new()))
@ -220,7 +227,7 @@ pub struct State {
font_size: f32,
fill_paint: Paint,
stroke_paint: Paint,
line_width: f32,
stroke_style: StrokeStyle,
global_alpha: f32,
}
@ -232,7 +239,7 @@ impl State {
font_size: DEFAULT_FONT_SIZE,
fill_paint: Paint { color: ColorU::black() },
stroke_paint: Paint { color: ColorU::black() },
line_width: 1.0,
stroke_style: StrokeStyle::default(),
global_alpha: 1.0,
}
}

View File

@ -267,6 +267,11 @@ impl Contour {
self.points[index as usize]
}
#[inline]
pub(crate) fn position_of_last(&self, index: u32) -> Point2DF32 {
self.points[self.points.len() - index as usize]
}
#[inline]
pub(crate) fn last_position(&self) -> Option<Point2DF32> {
self.points.last().cloned()

View File

@ -11,6 +11,7 @@
//! Utilities for converting path strokes to fills.
use crate::basic::line_segment::LineSegmentF32;
use crate::basic::point::Point2DF32;
use crate::basic::rect::RectF32;
use crate::outline::{Contour, Outline};
use crate::segment::Segment;
@ -20,35 +21,52 @@ const TOLERANCE: f32 = 0.01;
pub struct OutlineStrokeToFill {
pub outline: Outline,
pub stroke_width: f32,
pub style: StrokeStyle,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct StrokeStyle {
pub line_width: f32,
pub line_cap: LineCap,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LineCap {
Butt,
Square,
}
impl OutlineStrokeToFill {
#[inline]
pub fn new(outline: Outline, stroke_width: f32) -> OutlineStrokeToFill {
OutlineStrokeToFill {
outline,
stroke_width,
}
pub fn new(outline: Outline, style: StrokeStyle) -> OutlineStrokeToFill {
OutlineStrokeToFill { outline, style }
}
#[inline]
pub fn offset(&mut self) {
let mut new_contours = vec![];
for input in mem::replace(&mut self.outline.contours, vec![]) {
let closed = input.closed;
let mut stroker = ContourStrokeToFill::new(input,
Contour::new(),
self.stroke_width * 0.5);
self.style.line_width * 0.5);
stroker.offset_forward();
stroker.output.closed = stroker.input.closed;
if stroker.input.closed {
if closed {
stroker.output.closed = true;
new_contours.push(stroker.output);
stroker = ContourStrokeToFill::new(stroker.input,
Contour::new(),
self.stroke_width * 0.5);
self.style.line_width * 0.5);
} else {
self.add_cap(&mut stroker.output);
}
stroker.offset_backward();
stroker.output.closed = stroker.input.closed;
if !closed {
self.add_cap(&mut stroker.output);
}
stroker.output.closed = true;
new_contours.push(stroker.output);
}
@ -58,6 +76,25 @@ impl OutlineStrokeToFill {
self.outline.contours = new_contours;
self.outline.bounds = new_bounds.unwrap_or_else(|| RectF32::default());
}
pub fn add_cap(&mut self, contour: &mut Contour) {
if self.style.line_cap == LineCap::Butt || contour.len() < 2 {
return
}
let width = self.style.line_width;
let (p0, p1) = (contour.position_of_last(2), contour.position_of_last(1));
let gradient = (p1 - p0).normalize();
let offset = gradient.scale(width * 0.5);
let p2 = p1 + offset;
let p3 = p2 + gradient.yx().scale_xy(Point2DF32::new(width, -width));
let p4 = p3 - offset;
contour.push_endpoint(p2);
contour.push_endpoint(p3);
contour.push_endpoint(p4);
}
}
struct ContourStrokeToFill {
@ -219,3 +256,15 @@ impl Offset for Segment {
const SAMPLE_COUNT: u32 = 16;
}
}
impl Default for StrokeStyle {
#[inline]
fn default() -> StrokeStyle {
StrokeStyle { line_width: 1.0, line_cap: LineCap::default() }
}
}
impl Default for LineCap {
#[inline]
fn default() -> LineCap { LineCap::Butt }
}

View File

@ -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::OutlineStrokeToFill;
use pathfinder_geometry::stroke::{LineCap, 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, Node, NodeExt, NodeKind, Opacity, Paint as UsvgPaint};
use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, Transform as UsvgTransform};
use usvg::{Tree, Visibility};
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};
const HAIRLINE_STROKE_WIDTH: f32 = 0.0333;
@ -135,12 +135,16 @@ impl BuiltSVG {
stroke.opacity,
&mut self.result_flags,
));
let stroke_width = f32::max(stroke.width.value() as f32, HAIRLINE_STROKE_WIDTH);
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),
};
let path = UsvgPathToSegments::new(path.segments.iter().cloned());
let outline = Outline::from_segments(path);
let mut stroke_to_fill = OutlineStrokeToFill::new(outline, stroke_width);
let mut stroke_to_fill = OutlineStrokeToFill::new(outline, stroke_style);
stroke_to_fill.offset();
let mut outline = stroke_to_fill.outline;
outline.transform(&transform);
@ -378,3 +382,21 @@ impl ColorUExt for ColorU {
}
}
}
trait LineCapExt {
fn from_usvg_line_cap(usvg_line_cap: UsvgLineCap) -> Self;
}
impl LineCapExt for LineCap {
#[inline]
fn from_usvg_line_cap(usvg_line_cap: UsvgLineCap) -> LineCap {
match usvg_line_cap {
UsvgLineCap::Butt => LineCap::Butt,
UsvgLineCap::Round => {
// TODO(pcwalton)
LineCap::Square
}
UsvgLineCap::Square => LineCap::Square,
}
}
}

View File

@ -16,7 +16,7 @@ use lyon_path::builder::{FlatPathBuilder, PathBuilder};
use pathfinder_geometry::basic::point::Point2DF32;
use pathfinder_geometry::basic::transform2d::Transform2DF32;
use pathfinder_geometry::outline::{Contour, Outline};
use pathfinder_geometry::stroke::OutlineStrokeToFill;
use pathfinder_geometry::stroke::{OutlineStrokeToFill, StrokeStyle};
use pathfinder_renderer::paint::PaintId;
use pathfinder_renderer::scene::{PathObject, Scene};
use skribo::{FontCollection, Layout, TextStyle};
@ -69,8 +69,8 @@ impl SceneExt for Scene {
font.outline(glyph_id, hinting_options, &mut outline_builder)?;
let mut outline = outline_builder.build();
if let TextRenderMode::Stroke(stroke_width) = render_mode {
let mut stroke_to_fill = OutlineStrokeToFill::new(outline, stroke_width);
if let TextRenderMode::Stroke(stroke_style) = render_mode {
let mut stroke_to_fill = OutlineStrokeToFill::new(outline, stroke_style);
stroke_to_fill.offset();
outline = stroke_to_fill.outline;
}
@ -123,7 +123,7 @@ impl SceneExt for Scene {
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum TextRenderMode {
Fill,
Stroke(f32),
Stroke(StrokeStyle),
}
struct OutlinePathBuilder {