Implement square line caps
This commit is contained in:
parent
1b9cfa98a9
commit
272b63a017
|
@ -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::OutlineStrokeToFill;
|
use pathfinder_geometry::stroke::{LineCap, 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};
|
||||||
|
@ -115,7 +115,7 @@ impl CanvasRenderingContext2D {
|
||||||
&TextStyle { size: self.current_state.font_size },
|
&TextStyle { size: self.current_state.font_size },
|
||||||
&self.current_state.font_collection,
|
&self.current_state.font_collection,
|
||||||
&transform,
|
&transform,
|
||||||
TextRenderMode::Stroke(self.current_state.line_width),
|
TextRenderMode::Stroke(self.current_state.stroke_style),
|
||||||
HintingOptions::None,
|
HintingOptions::None,
|
||||||
paint_id));
|
paint_id));
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,12 @@ impl CanvasRenderingContext2D {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_line_width(&mut self, new_line_width: f32) {
|
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]
|
#[inline]
|
||||||
|
@ -162,8 +167,10 @@ impl CanvasRenderingContext2D {
|
||||||
let paint = self.current_state.resolve_paint(self.current_state.stroke_paint);
|
let paint = self.current_state.resolve_paint(self.current_state.stroke_paint);
|
||||||
let paint_id = self.scene.push_paint(&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_style = self.current_state.stroke_style;
|
||||||
let mut stroke_to_fill = OutlineStrokeToFill::new(path.into_outline(), stroke_width);
|
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.offset();
|
||||||
stroke_to_fill.outline.transform(&self.current_state.transform);
|
stroke_to_fill.outline.transform(&self.current_state.transform);
|
||||||
self.scene.push_path(PathObject::new(stroke_to_fill.outline, paint_id, String::new()))
|
self.scene.push_path(PathObject::new(stroke_to_fill.outline, paint_id, String::new()))
|
||||||
|
@ -220,7 +227,7 @@ pub struct State {
|
||||||
font_size: f32,
|
font_size: f32,
|
||||||
fill_paint: Paint,
|
fill_paint: Paint,
|
||||||
stroke_paint: Paint,
|
stroke_paint: Paint,
|
||||||
line_width: f32,
|
stroke_style: StrokeStyle,
|
||||||
global_alpha: f32,
|
global_alpha: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,7 +239,7 @@ impl State {
|
||||||
font_size: DEFAULT_FONT_SIZE,
|
font_size: DEFAULT_FONT_SIZE,
|
||||||
fill_paint: Paint { color: ColorU::black() },
|
fill_paint: Paint { color: ColorU::black() },
|
||||||
stroke_paint: Paint { color: ColorU::black() },
|
stroke_paint: Paint { color: ColorU::black() },
|
||||||
line_width: 1.0,
|
stroke_style: StrokeStyle::default(),
|
||||||
global_alpha: 1.0,
|
global_alpha: 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -267,6 +267,11 @@ impl Contour {
|
||||||
self.points[index as usize]
|
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]
|
#[inline]
|
||||||
pub(crate) fn last_position(&self) -> Option<Point2DF32> {
|
pub(crate) fn last_position(&self) -> Option<Point2DF32> {
|
||||||
self.points.last().cloned()
|
self.points.last().cloned()
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
//! Utilities for converting path strokes to fills.
|
//! Utilities for converting path strokes to fills.
|
||||||
|
|
||||||
use crate::basic::line_segment::LineSegmentF32;
|
use crate::basic::line_segment::LineSegmentF32;
|
||||||
|
use crate::basic::point::Point2DF32;
|
||||||
use crate::basic::rect::RectF32;
|
use crate::basic::rect::RectF32;
|
||||||
use crate::outline::{Contour, Outline};
|
use crate::outline::{Contour, Outline};
|
||||||
use crate::segment::Segment;
|
use crate::segment::Segment;
|
||||||
|
@ -20,35 +21,52 @@ const TOLERANCE: f32 = 0.01;
|
||||||
|
|
||||||
pub struct OutlineStrokeToFill {
|
pub struct OutlineStrokeToFill {
|
||||||
pub outline: Outline,
|
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 {
|
impl OutlineStrokeToFill {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(outline: Outline, stroke_width: f32) -> OutlineStrokeToFill {
|
pub fn new(outline: Outline, style: StrokeStyle) -> OutlineStrokeToFill {
|
||||||
OutlineStrokeToFill {
|
OutlineStrokeToFill { outline, style }
|
||||||
outline,
|
|
||||||
stroke_width,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn offset(&mut self) {
|
pub fn offset(&mut self) {
|
||||||
let mut new_contours = vec![];
|
let mut new_contours = vec![];
|
||||||
for input in mem::replace(&mut self.outline.contours, vec![]) {
|
for input in mem::replace(&mut self.outline.contours, vec![]) {
|
||||||
|
let closed = input.closed;
|
||||||
let mut stroker = ContourStrokeToFill::new(input,
|
let mut stroker = ContourStrokeToFill::new(input,
|
||||||
Contour::new(),
|
Contour::new(),
|
||||||
self.stroke_width * 0.5);
|
self.style.line_width * 0.5);
|
||||||
|
|
||||||
stroker.offset_forward();
|
stroker.offset_forward();
|
||||||
stroker.output.closed = stroker.input.closed;
|
if closed {
|
||||||
if stroker.input.closed {
|
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.stroke_width * 0.5);
|
self.style.line_width * 0.5);
|
||||||
|
} else {
|
||||||
|
self.add_cap(&mut stroker.output);
|
||||||
}
|
}
|
||||||
|
|
||||||
stroker.offset_backward();
|
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);
|
new_contours.push(stroker.output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +76,25 @@ impl OutlineStrokeToFill {
|
||||||
self.outline.contours = new_contours;
|
self.outline.contours = new_contours;
|
||||||
self.outline.bounds = new_bounds.unwrap_or_else(|| RectF32::default());
|
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 {
|
struct ContourStrokeToFill {
|
||||||
|
@ -219,3 +256,15 @@ impl Offset for Segment {
|
||||||
const SAMPLE_COUNT: u32 = 16;
|
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 }
|
||||||
|
}
|
||||||
|
|
|
@ -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::OutlineStrokeToFill;
|
use pathfinder_geometry::stroke::{LineCap, 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, Node, NodeExt, NodeKind, Opacity, Paint as UsvgPaint};
|
use usvg::{Color as SvgColor, LineCap as UsvgLineCap, Node, NodeExt, NodeKind, Opacity};
|
||||||
use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, Transform as UsvgTransform};
|
use usvg::{Paint as UsvgPaint, PathSegment as UsvgPathSegment, Rect as UsvgRect};
|
||||||
use usvg::{Tree, Visibility};
|
use usvg::{Transform as UsvgTransform, Tree, Visibility};
|
||||||
|
|
||||||
const HAIRLINE_STROKE_WIDTH: f32 = 0.0333;
|
const HAIRLINE_STROKE_WIDTH: f32 = 0.0333;
|
||||||
|
|
||||||
|
@ -135,12 +135,16 @@ impl BuiltSVG {
|
||||||
stroke.opacity,
|
stroke.opacity,
|
||||||
&mut self.result_flags,
|
&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 path = UsvgPathToSegments::new(path.segments.iter().cloned());
|
||||||
let outline = Outline::from_segments(path);
|
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();
|
stroke_to_fill.offset();
|
||||||
let mut outline = stroke_to_fill.outline;
|
let mut outline = stroke_to_fill.outline;
|
||||||
outline.transform(&transform);
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ use lyon_path::builder::{FlatPathBuilder, PathBuilder};
|
||||||
use pathfinder_geometry::basic::point::Point2DF32;
|
use pathfinder_geometry::basic::point::Point2DF32;
|
||||||
use pathfinder_geometry::basic::transform2d::Transform2DF32;
|
use pathfinder_geometry::basic::transform2d::Transform2DF32;
|
||||||
use pathfinder_geometry::outline::{Contour, Outline};
|
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::paint::PaintId;
|
||||||
use pathfinder_renderer::scene::{PathObject, Scene};
|
use pathfinder_renderer::scene::{PathObject, Scene};
|
||||||
use skribo::{FontCollection, Layout, TextStyle};
|
use skribo::{FontCollection, Layout, TextStyle};
|
||||||
|
@ -69,8 +69,8 @@ impl SceneExt for Scene {
|
||||||
font.outline(glyph_id, hinting_options, &mut outline_builder)?;
|
font.outline(glyph_id, hinting_options, &mut outline_builder)?;
|
||||||
let mut outline = outline_builder.build();
|
let mut outline = outline_builder.build();
|
||||||
|
|
||||||
if let TextRenderMode::Stroke(stroke_width) = render_mode {
|
if let TextRenderMode::Stroke(stroke_style) = render_mode {
|
||||||
let mut stroke_to_fill = OutlineStrokeToFill::new(outline, stroke_width);
|
let mut stroke_to_fill = OutlineStrokeToFill::new(outline, stroke_style);
|
||||||
stroke_to_fill.offset();
|
stroke_to_fill.offset();
|
||||||
outline = stroke_to_fill.outline;
|
outline = stroke_to_fill.outline;
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ impl SceneExt for Scene {
|
||||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
pub enum TextRenderMode {
|
pub enum TextRenderMode {
|
||||||
Fill,
|
Fill,
|
||||||
Stroke(f32),
|
Stroke(StrokeStyle),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct OutlinePathBuilder {
|
struct OutlinePathBuilder {
|
||||||
|
|
Loading…
Reference in New Issue