pathfinder/svg/src/lib.rs

559 lines
20 KiB
Rust

// pathfinder/svg/src/lib.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.
//! Converts a subset of SVG to a Pathfinder scene.
#[macro_use]
extern crate bitflags;
use hashbrown::HashMap;
use pathfinder_color::ColorU;
use pathfinder_content::dash::OutlineDash;
use pathfinder_content::fill::FillRule;
use pathfinder_content::gradient::{ColorStop, Gradient, GradientWrap};
use pathfinder_content::outline::Outline;
use pathfinder_content::segment::{Segment, SegmentFlags};
use pathfinder_content::stroke::{LineCap, LineJoin, OutlineStrokeToFill, StrokeStyle};
use pathfinder_content::transform::Transform2FPathIter;
use pathfinder_geometry::line_segment::LineSegment2F;
use pathfinder_geometry::rect::RectF;
use pathfinder_geometry::transform2d::Transform2F;
use pathfinder_geometry::vector::{Vector2F, vec2f};
use pathfinder_renderer::paint::Paint;
use pathfinder_renderer::scene::{ClipPath, ClipPathId, DrawPath, Scene};
use pathfinder_simd::default::F32x2;
use std::fmt::{Display, Formatter, Result as FormatResult};
use usvg::{BaseGradient, Color as SvgColor, FillRule as UsvgFillRule, LineCap as UsvgLineCap};
use usvg::{LineJoin as UsvgLineJoin, Node, NodeExt, NodeKind, Opacity, Paint as UsvgPaint};
use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, SpreadMethod, Stop};
use usvg::{Transform as UsvgTransform, Tree, Visibility};
const HAIRLINE_STROKE_WIDTH: f32 = 0.0333;
pub struct SVGScene {
pub scene: Scene,
pub result_flags: BuildResultFlags,
pub clip_paths: HashMap<String, Outline>,
gradients: HashMap<String, GradientInfo>,
}
bitflags! {
// NB: If you change this, make sure to update the `Display`
// implementation as well.
pub struct BuildResultFlags: u16 {
const UNSUPPORTED_FILTER_NODE = 0x0001;
const UNSUPPORTED_IMAGE_NODE = 0x0002;
const UNSUPPORTED_MASK_NODE = 0x0004;
const UNSUPPORTED_PATTERN_NODE = 0x0008;
const UNSUPPORTED_MULTIPLE_CLIP_PATHS = 0x0010;
const UNSUPPORTED_LINK_PAINT = 0x0020;
const UNSUPPORTED_FILTER_ATTR = 0x0040;
const UNSUPPORTED_MASK_ATTR = 0x0080;
}
}
impl SVGScene {
// TODO(pcwalton): Allow a global transform to be set.
#[inline]
pub fn from_tree(tree: &Tree) -> SVGScene {
SVGScene::from_tree_and_scene(tree, Scene::new())
}
// TODO(pcwalton): Allow a global transform to be set.
pub fn from_tree_and_scene(tree: &Tree, scene: Scene) -> SVGScene {
// TODO(pcwalton): Maybe have a `SVGBuilder` type to hold the clip path IDs and other
// transient data separate from `SVGScene`?
let mut built_svg = SVGScene {
scene,
result_flags: BuildResultFlags::empty(),
clip_paths: HashMap::new(),
gradients: HashMap::new(),
};
let root = &tree.root();
match *root.borrow() {
NodeKind::Svg(ref svg) => {
built_svg.scene.set_view_box(usvg_rect_to_euclid_rect(&svg.view_box.rect));
for kid in root.children() {
built_svg.process_node(&kid, &State::new(), &mut None);
}
}
_ => unreachable!(),
}
built_svg
}
fn process_node(&mut self,
node: &Node,
state: &State,
clip_outline: &mut Option<Outline>) {
let mut state = (*state).clone();
let node_transform = usvg_transform_to_transform_2d(&node.transform());
state.transform = state.transform * node_transform;
match *node.borrow() {
NodeKind::Group(ref group) => {
if group.filter.is_some() {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_FILTER_ATTR);
}
if group.mask.is_some() {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_MASK_ATTR);
}
if let Some(ref clip_path_name) = group.clip_path {
if let Some(clip_outline) = self.clip_paths.get(clip_path_name) {
let transformed_outline = clip_outline.clone().transformed(&state.transform);
let mut clip_path = ClipPath::new(transformed_outline);
clip_path.set_clip_path(state.clip_path);
clip_path.set_name(format!("ClipPath({})", clip_path_name));
let clip_path_id = self.scene.push_clip_path(clip_path);
state.clip_path = Some(clip_path_id);
}
}
for kid in node.children() {
self.process_node(&kid, &state, clip_outline)
}
}
NodeKind::Path(ref path) if state.path_destination == PathDestination::Clip => {
// TODO(pcwalton): Multiple clip paths.
let path = UsvgPathToSegments::new(path.data.iter().cloned());
let path = Transform2FPathIter::new(path, &state.transform);
if clip_outline.is_some() {
self.result_flags.insert(BuildResultFlags::UNSUPPORTED_MULTIPLE_CLIP_PATHS);
}
*clip_outline = Some(Outline::from_segments(path));
}
NodeKind::Path(ref path) if state.path_destination == PathDestination::Draw &&
path.visibility == Visibility::Visible => {
if let Some(ref fill) = path.fill {
let path = UsvgPathToSegments::new(path.data.iter().cloned());
let outline = Outline::from_segments(path);
let name = format!("Fill({})", node.id());
self.push_draw_path(outline,
name,
&state,
&fill.paint,
fill.opacity,
fill.rule);
}
if let Some(ref stroke) = path.stroke {
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),
line_join: LineJoin::from_usvg_line_join(stroke.linejoin,
stroke.miterlimit.value() as f32),
};
let path = UsvgPathToSegments::new(path.data.iter().cloned());
let mut outline = Outline::from_segments(path);
if let Some(ref dash_array) = stroke.dasharray {
let dash_array: Vec<f32> = dash_array.iter().map(|&x| x as f32).collect();
let mut dash = OutlineDash::new(&outline, &dash_array, stroke.dashoffset);
dash.dash();
outline = dash.into_outline();
}
let mut stroke_to_fill = OutlineStrokeToFill::new(&outline, stroke_style);
stroke_to_fill.offset();
let outline = stroke_to_fill.into_outline();
let name = format!("Stroke({})", node.id());
self.push_draw_path(outline,
name,
&state,
&stroke.paint,
stroke.opacity,
UsvgFillRule::NonZero);
}
}
NodeKind::Path(..) => {}
NodeKind::ClipPath(_) => {
let mut clip_outline = None;
state.path_destination = PathDestination::Clip;
for kid in node.children() {
self.process_node(&kid, &state, &mut clip_outline);
}
self.clip_paths.insert(node.id().to_owned(), clip_outline.unwrap());
}
NodeKind::Defs => {
// FIXME(pcwalton): This is wrong.
state.path_destination = PathDestination::Defs;
for kid in node.children() {
self.process_node(&kid, &state, clip_outline);
}
}
NodeKind::LinearGradient(ref svg_linear_gradient) => {
let from = vec2f(svg_linear_gradient.x1 as f32, svg_linear_gradient.y1 as f32);
let to = vec2f(svg_linear_gradient.x2 as f32, svg_linear_gradient.y2 as f32);
let gradient = Gradient::linear_from_points(from, to);
self.add_gradient(gradient,
svg_linear_gradient.id.clone(),
&svg_linear_gradient.base)
}
NodeKind::RadialGradient(ref svg_radial_gradient) => {
let from = vec2f(svg_radial_gradient.fx as f32, svg_radial_gradient.fy as f32);
let to = vec2f(svg_radial_gradient.cx as f32, svg_radial_gradient.cy as f32);
let radii = F32x2::new(0.0, svg_radial_gradient.r.value() as f32);
let gradient = Gradient::radial(LineSegment2F::new(from, to), radii);
self.add_gradient(gradient,
svg_radial_gradient.id.clone(),
&svg_radial_gradient.base)
}
NodeKind::Filter(..) => {
self.result_flags
.insert(BuildResultFlags::UNSUPPORTED_FILTER_NODE);
}
NodeKind::Image(..) => {
self.result_flags
.insert(BuildResultFlags::UNSUPPORTED_IMAGE_NODE);
}
NodeKind::Mask(..) => {
self.result_flags
.insert(BuildResultFlags::UNSUPPORTED_MASK_NODE);
}
NodeKind::Pattern(..) => {
self.result_flags
.insert(BuildResultFlags::UNSUPPORTED_PATTERN_NODE);
}
NodeKind::Svg(..) => unreachable!(),
}
}
fn add_gradient(&mut self,
mut gradient: Gradient,
id: String,
usvg_base_gradient: &BaseGradient) {
for stop in &usvg_base_gradient.stops {
let mut stop = ColorStop::from_usvg_stop(stop);
if usvg_base_gradient.spread_method == SpreadMethod::Reflect {
stop.offset *= 0.5;
}
gradient.add(stop);
}
// Reflect gradient if necessary.
if usvg_base_gradient.spread_method == SpreadMethod::Reflect {
for stop in &usvg_base_gradient.stops {
let mut stop = ColorStop::from_usvg_stop(stop);
if usvg_base_gradient.spread_method == SpreadMethod::Reflect {
stop.offset = 1.0 - stop.offset * 0.5;
}
gradient.add(stop);
}
}
match usvg_base_gradient.spread_method {
SpreadMethod::Pad => {}
SpreadMethod::Reflect | SpreadMethod::Repeat => gradient.wrap = GradientWrap::Repeat,
}
let transform = usvg_transform_to_transform_2d(&usvg_base_gradient.transform);
// TODO(pcwalton): What should we do with `gradientUnits`?
self.gradients.insert(id, GradientInfo { gradient, transform });
}
fn push_draw_path(&mut self,
mut outline: Outline,
name: String,
state: &State,
paint: &UsvgPaint,
opacity: Opacity,
fill_rule: UsvgFillRule) {
outline.transform(&state.transform);
let paint = Paint::from_svg_paint(paint,
&state.transform,
opacity,
&self.gradients,
&mut self.result_flags);
let style = self.scene.push_paint(&paint);
let fill_rule = FillRule::from_usvg_fill_rule(fill_rule);
let mut path = DrawPath::new(outline, style);
path.set_clip_path(state.clip_path);
path.set_fill_rule(fill_rule);
path.set_name(name);
self.scene.push_draw_path(path);
}
}
impl Display for BuildResultFlags {
fn fmt(&self, formatter: &mut Formatter) -> FormatResult {
if self.is_empty() {
return Ok(());
}
let mut first = true;
for (bit, name) in NAMES.iter().enumerate() {
if (self.bits() >> bit) & 1 == 0 {
continue;
}
if !first {
formatter.write_str(", ")?;
} else {
first = false;
}
formatter.write_str(name)?;
}
return Ok(());
// Must match the order in `BuildResultFlags`.
static NAMES: &'static [&'static str] = &[
"<filter>",
"<image>",
"<mask>",
"<pattern>",
"multiple clip paths",
"non-color paint",
"filter attribute",
"mask attribute",
];
}
}
trait PaintExt {
fn from_svg_paint(svg_paint: &UsvgPaint,
transform: &Transform2F,
opacity: Opacity,
gradients: &HashMap<String, GradientInfo>,
result_flags: &mut BuildResultFlags)
-> Self;
}
impl PaintExt for Paint {
#[inline]
fn from_svg_paint(svg_paint: &UsvgPaint,
transform: &Transform2F,
opacity: Opacity,
gradients: &HashMap<String, GradientInfo>,
result_flags: &mut BuildResultFlags)
-> Paint {
let mut paint;
match *svg_paint {
UsvgPaint::Color(color) => paint = Paint::from_color(ColorU::from_svg_color(color)),
UsvgPaint::Link(ref id) => {
match gradients.get(id) {
Some(ref gradient_info) => {
paint = Paint::from_gradient(gradient_info.gradient.clone());
paint.apply_transform(&(*transform * gradient_info.transform));
}
None => {
// TODO(pcwalton)
result_flags.insert(BuildResultFlags::UNSUPPORTED_LINK_PAINT);
paint = Paint::from_color(ColorU::black());
}
}
}
}
let mut base_color = paint.base_color().to_f32();
base_color.set_a(base_color.a() * opacity.value() as f32);
paint.set_base_color(base_color.to_u8());
paint
}
}
fn usvg_rect_to_euclid_rect(rect: &UsvgRect) -> RectF {
RectF::new(vec2f(rect.x() as f32, rect.y() as f32),
vec2f(rect.width() as f32, rect.height() as f32))
}
fn usvg_transform_to_transform_2d(transform: &UsvgTransform) -> Transform2F {
Transform2F::row_major(transform.a as f32, transform.c as f32, transform.e as f32,
transform.b as f32, transform.d as f32, transform.f as f32)
}
struct UsvgPathToSegments<I>
where
I: Iterator<Item = UsvgPathSegment>,
{
iter: I,
first_subpath_point: Vector2F,
last_subpath_point: Vector2F,
just_moved: bool,
}
impl<I> UsvgPathToSegments<I>
where
I: Iterator<Item = UsvgPathSegment>,
{
fn new(iter: I) -> UsvgPathToSegments<I> {
UsvgPathToSegments {
iter,
first_subpath_point: Vector2F::zero(),
last_subpath_point: Vector2F::zero(),
just_moved: false,
}
}
}
impl<I> Iterator for UsvgPathToSegments<I>
where
I: Iterator<Item = UsvgPathSegment>,
{
type Item = Segment;
fn next(&mut self) -> Option<Segment> {
match self.iter.next()? {
UsvgPathSegment::MoveTo { x, y } => {
let to = vec2f(x as f32, y as f32);
self.first_subpath_point = to;
self.last_subpath_point = to;
self.just_moved = true;
self.next()
}
UsvgPathSegment::LineTo { x, y } => {
let to = vec2f(x as f32, y as f32);
let mut segment = Segment::line(LineSegment2F::new(self.last_subpath_point, to));
if self.just_moved {
segment.flags.insert(SegmentFlags::FIRST_IN_SUBPATH);
}
self.last_subpath_point = to;
self.just_moved = false;
Some(segment)
}
UsvgPathSegment::CurveTo {
x1,
y1,
x2,
y2,
x,
y,
} => {
let ctrl0 = vec2f(x1 as f32, y1 as f32);
let ctrl1 = vec2f(x2 as f32, y2 as f32);
let to = vec2f(x as f32, y as f32);
let mut segment = Segment::cubic(LineSegment2F::new(self.last_subpath_point, to),
LineSegment2F::new(ctrl0, ctrl1));
if self.just_moved {
segment.flags.insert(SegmentFlags::FIRST_IN_SUBPATH);
}
self.last_subpath_point = to;
self.just_moved = false;
Some(segment)
}
UsvgPathSegment::ClosePath => {
let mut segment = Segment::line(LineSegment2F::new(
self.last_subpath_point,
self.first_subpath_point,
));
segment.flags.insert(SegmentFlags::CLOSES_SUBPATH);
self.just_moved = false;
self.last_subpath_point = self.first_subpath_point;
Some(segment)
}
}
}
}
trait ColorUExt {
fn from_svg_color(svg_color: SvgColor) -> Self;
}
impl ColorUExt for ColorU {
#[inline]
fn from_svg_color(svg_color: SvgColor) -> ColorU {
ColorU { r: svg_color.red, g: svg_color.green, b: svg_color.blue, a: !0 }
}
}
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 => LineCap::Round,
UsvgLineCap::Square => LineCap::Square,
}
}
}
trait LineJoinExt {
fn from_usvg_line_join(usvg_line_join: UsvgLineJoin, miter_limit: f32) -> Self;
}
impl LineJoinExt for LineJoin {
#[inline]
fn from_usvg_line_join(usvg_line_join: UsvgLineJoin, miter_limit: f32) -> LineJoin {
match usvg_line_join {
UsvgLineJoin::Miter => LineJoin::Miter(miter_limit),
UsvgLineJoin::Round => LineJoin::Round,
UsvgLineJoin::Bevel => LineJoin::Bevel,
}
}
}
trait FillRuleExt {
fn from_usvg_fill_rule(usvg_fill_rule: UsvgFillRule) -> Self;
}
impl FillRuleExt for FillRule {
#[inline]
fn from_usvg_fill_rule(usvg_fill_rule: UsvgFillRule) -> FillRule {
match usvg_fill_rule {
UsvgFillRule::NonZero => FillRule::Winding,
UsvgFillRule::EvenOdd => FillRule::EvenOdd,
}
}
}
trait ColorStopExt {
fn from_usvg_stop(usvg_stop: &Stop) -> Self;
}
impl ColorStopExt for ColorStop {
fn from_usvg_stop(usvg_stop: &Stop) -> ColorStop {
let mut color = ColorU::from_svg_color(usvg_stop.color);
color.a = (usvg_stop.opacity.value() * 255.0) as u8;
ColorStop::new(color, usvg_stop.offset.value() as f32)
}
}
#[derive(Clone)]
struct State {
// Where paths are being appended to.
path_destination: PathDestination,
// The current transform.
transform: Transform2F,
// The current clip path in effect.
clip_path: Option<ClipPathId>,
}
impl State {
fn new() -> State {
State {
path_destination: PathDestination::Draw,
transform: Transform2F::default(),
clip_path: None,
}
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
enum PathDestination {
Draw,
Defs,
Clip,
}
struct GradientInfo {
gradient: Gradient,
transform: Transform2F,
}