summaryrefslogtreecommitdiff
path: root/src/export
diff options
context:
space:
mode:
authorBirk Tjelmeland <git@birktj.no>2023-04-13 16:05:56 +0200
committerGitHub <noreply@github.com>2023-04-13 16:05:56 +0200
commitd1cd814ef8149cbac6e59c81e074aa59c930eed3 (patch)
tree02b9a8afed4d121b34d89669452f91cda19df8e2 /src/export
parent46ce9c94e3f615751989d3cba5aa1599e0ba5913 (diff)
Add support for more complex strokes (#505)
Diffstat (limited to 'src/export')
-rw-r--r--src/export/pdf/page.rs60
-rw-r--r--src/export/render.rs54
2 files changed, 106 insertions, 8 deletions
diff --git a/src/export/pdf/page.rs b/src/export/pdf/page.rs
index 636d42c7..d6ead124 100644
--- a/src/export/pdf/page.rs
+++ b/src/export/pdf/page.rs
@@ -1,5 +1,7 @@
use ecow::eco_format;
-use pdf_writer::types::{ActionType, AnnotationType, ColorSpaceOperand};
+use pdf_writer::types::{
+ ActionType, AnnotationType, ColorSpaceOperand, LineCapStyle, LineJoinStyle,
+};
use pdf_writer::writers::ColorSpace;
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str};
@@ -7,8 +9,8 @@ use super::{deflate, AbsExt, EmExt, PdfContext, RefExt, D65_GRAY, SRGB};
use crate::doc::{Destination, Frame, FrameItem, GroupItem, Meta, TextItem};
use crate::font::Font;
use crate::geom::{
- self, Abs, Color, Em, Geometry, Numeric, Paint, Point, Ratio, Shape, Size, Stroke,
- Transform,
+ self, Abs, Color, Em, Geometry, LineCap, LineJoin, Numeric, Paint, Point, Ratio,
+ Shape, Size, Stroke, Transform,
};
use crate::image::Image;
@@ -250,8 +252,17 @@ impl PageContext<'_, '_> {
fn set_stroke(&mut self, stroke: &Stroke) {
if self.state.stroke.as_ref() != Some(stroke) {
+ let Stroke {
+ paint,
+ thickness,
+ line_cap,
+ line_join,
+ dash_pattern,
+ miter_limit,
+ } = stroke;
+
let f = |c| c as f32 / 255.0;
- let Paint::Solid(color) = stroke.paint;
+ let Paint::Solid(color) = paint;
match color {
Color::Luma(c) => {
self.set_stroke_color_space(D65_GRAY);
@@ -267,7 +278,26 @@ impl PageContext<'_, '_> {
}
}
- self.content.set_line_width(stroke.thickness.to_f32());
+ self.content.set_line_width(thickness.to_f32());
+ if self.state.stroke.as_ref().map(|s| &s.line_cap) != Some(line_cap) {
+ self.content.set_line_cap(line_cap.into());
+ }
+ if self.state.stroke.as_ref().map(|s| &s.line_join) != Some(line_join) {
+ self.content.set_line_join(line_join.into());
+ }
+ if self.state.stroke.as_ref().map(|s| &s.dash_pattern) != Some(dash_pattern) {
+ if let Some(pattern) = dash_pattern {
+ self.content.set_dash_pattern(
+ pattern.array.iter().map(|l| l.to_f32()),
+ pattern.phase.to_f32(),
+ );
+ } else {
+ self.content.set_dash_pattern([], 0.0);
+ }
+ }
+ if self.state.stroke.as_ref().map(|s| &s.miter_limit) != Some(miter_limit) {
+ self.content.set_miter_limit(miter_limit.0 as f32);
+ }
self.state.stroke = Some(stroke.clone());
}
}
@@ -486,3 +516,23 @@ fn write_link(ctx: &mut PageContext, pos: Point, dest: &Destination, size: Size)
ctx.links.push((dest.clone(), rect));
}
+
+impl From<&LineCap> for LineCapStyle {
+ fn from(line_cap: &LineCap) -> Self {
+ match line_cap {
+ LineCap::Butt => LineCapStyle::ButtCap,
+ LineCap::Round => LineCapStyle::RoundCap,
+ LineCap::Square => LineCapStyle::ProjectingSquareCap,
+ }
+ }
+}
+
+impl From<&LineJoin> for LineJoinStyle {
+ fn from(line_join: &LineJoin) -> Self {
+ match line_join {
+ LineJoin::Miter => LineJoinStyle::MiterJoin,
+ LineJoin::Round => LineJoinStyle::RoundJoin,
+ LineJoin::Bevel => LineJoinStyle::BevelJoin,
+ }
+ }
+}
diff --git a/src/export/render.rs b/src/export/render.rs
index 8cee3aa6..f3c72ba0 100644
--- a/src/export/render.rs
+++ b/src/export/render.rs
@@ -11,7 +11,8 @@ use usvg::{FitTo, NodeExt};
use crate::doc::{Frame, FrameItem, GroupItem, Meta, TextItem};
use crate::geom::{
- self, Abs, Color, Geometry, Paint, PathItem, Shape, Size, Stroke, Transform,
+ self, Abs, Color, Geometry, LineCap, LineJoin, Paint, PathItem, Shape, Size, Stroke,
+ Transform,
};
use crate::image::{DecodedImage, Image};
@@ -392,9 +393,36 @@ fn render_shape(
canvas.fill_path(&path, &paint, rule, ts, mask);
}
- if let Some(Stroke { paint, thickness }) = &shape.stroke {
+ if let Some(Stroke {
+ paint,
+ thickness,
+ line_cap,
+ line_join,
+ dash_pattern,
+ miter_limit,
+ }) = &shape.stroke
+ {
+ let dash = dash_pattern.as_ref().and_then(|pattern| {
+ // tiny-skia only allows dash patterns with an even number of elements,
+ // while pdf allows any number.
+ let len = if pattern.array.len() % 2 == 1 {
+ pattern.array.len() * 2
+ } else {
+ pattern.array.len()
+ };
+ let dash_array =
+ pattern.array.iter().map(|l| l.to_f32()).cycle().take(len).collect();
+
+ sk::StrokeDash::new(dash_array, pattern.phase.to_f32())
+ });
let paint = paint.into();
- let stroke = sk::Stroke { width: thickness.to_f32(), ..Default::default() };
+ let stroke = sk::Stroke {
+ width: thickness.to_f32(),
+ line_cap: line_cap.into(),
+ line_join: line_join.into(),
+ dash,
+ miter_limit: miter_limit.0 as f32,
+ };
canvas.stroke_path(&path, &paint, &stroke, ts, mask);
}
@@ -525,6 +553,26 @@ impl From<Color> for sk::Color {
}
}
+impl From<&LineCap> for sk::LineCap {
+ fn from(line_cap: &LineCap) -> Self {
+ match line_cap {
+ LineCap::Butt => sk::LineCap::Butt,
+ LineCap::Round => sk::LineCap::Round,
+ LineCap::Square => sk::LineCap::Square,
+ }
+ }
+}
+
+impl From<&LineJoin> for sk::LineJoin {
+ fn from(line_join: &LineJoin) -> Self {
+ match line_join {
+ LineJoin::Miter => sk::LineJoin::Miter,
+ LineJoin::Round => sk::LineJoin::Round,
+ LineJoin::Bevel => sk::LineJoin::Bevel,
+ }
+ }
+}
+
/// Allows to build tiny-skia paths from glyph outlines.
struct WrappedPathBuilder(sk::PathBuilder);