257 lines
8.4 KiB
Rust
257 lines
8.4 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.
|
||
|
|
||
|
use euclid::{Point2D, Rect, Size2D};
|
||
|
use lyon_path::iterator::PathIter;
|
||
|
use pathfinder_geometry::line_segment::LineSegmentF32;
|
||
|
use pathfinder_geometry::monotonic::MonotonicConversionIter;
|
||
|
use pathfinder_geometry::outline::Outline;
|
||
|
use pathfinder_geometry::point::Point2DF32;
|
||
|
use pathfinder_geometry::segment::{PathEventsToSegments, Segment};
|
||
|
use pathfinder_geometry::segment::{SegmentFlags, SegmentsToPathEvents};
|
||
|
use pathfinder_geometry::stroke::{StrokeStyle, StrokeToFillIter};
|
||
|
use pathfinder_geometry::transform::{Transform2DF32, Transform2DF32PathIter};
|
||
|
use pathfinder_renderer::paint::{ColorU, Paint};
|
||
|
use pathfinder_renderer::scene::{PathObject, PathObjectKind, Scene};
|
||
|
use std::mem;
|
||
|
use usvg::{Color as SvgColor, Node, NodeExt, NodeKind, Paint as UsvgPaint};
|
||
|
use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, Transform as UsvgTransform, Tree};
|
||
|
|
||
|
const HAIRLINE_STROKE_WIDTH: f32 = 0.1;
|
||
|
|
||
|
pub trait SceneExt {
|
||
|
fn from_tree(tree: Tree) -> Self;
|
||
|
}
|
||
|
|
||
|
impl SceneExt for Scene {
|
||
|
// TODO(pcwalton): Allow a global transform to be set.
|
||
|
fn from_tree(tree: Tree) -> Scene {
|
||
|
let global_transform = Transform2DF32::default();
|
||
|
|
||
|
let mut scene = Scene::new();
|
||
|
|
||
|
let root = &tree.root();
|
||
|
match *root.borrow() {
|
||
|
NodeKind::Svg(ref svg) => {
|
||
|
scene.view_box = usvg_rect_to_euclid_rect(&svg.view_box.rect);
|
||
|
for kid in root.children() {
|
||
|
process_node(&mut scene, &kid, &global_transform);
|
||
|
}
|
||
|
}
|
||
|
_ => unreachable!(),
|
||
|
};
|
||
|
|
||
|
// FIXME(pcwalton): This is needed to avoid stack exhaustion in debug builds when
|
||
|
// recursively dropping reference counts on very large SVGs. :(
|
||
|
mem::forget(tree);
|
||
|
|
||
|
scene
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn process_node(scene: &mut Scene, node: &Node, transform: &Transform2DF32) {
|
||
|
let node_transform = usvg_transform_to_transform_2d(&node.transform());
|
||
|
let transform = transform.pre_mul(&node_transform);
|
||
|
|
||
|
match *node.borrow() {
|
||
|
NodeKind::Group(_) => {
|
||
|
for kid in node.children() {
|
||
|
process_node(scene, &kid, &transform)
|
||
|
}
|
||
|
}
|
||
|
NodeKind::Path(ref path) => {
|
||
|
if let Some(ref fill) = path.fill {
|
||
|
let style = scene.push_paint(&Paint::from_svg_paint(&fill.paint));
|
||
|
|
||
|
let path = UsvgPathToSegments::new(path.segments.iter().cloned());
|
||
|
let path = Transform2DF32PathIter::new(path, &transform);
|
||
|
let path = MonotonicConversionIter::new(path);
|
||
|
let outline = Outline::from_segments(path);
|
||
|
|
||
|
scene.bounds = scene.bounds.union(outline.bounds());
|
||
|
scene.objects.push(PathObject::new(
|
||
|
outline,
|
||
|
style,
|
||
|
node.id().to_string(),
|
||
|
PathObjectKind::Fill,
|
||
|
));
|
||
|
}
|
||
|
|
||
|
if let Some(ref stroke) = path.stroke {
|
||
|
let style = scene.push_paint(&Paint::from_svg_paint(&stroke.paint));
|
||
|
let stroke_width =
|
||
|
f32::max(stroke.width.value() as f32, HAIRLINE_STROKE_WIDTH);
|
||
|
|
||
|
let path = UsvgPathToSegments::new(path.segments.iter().cloned());
|
||
|
let path = SegmentsToPathEvents::new(path);
|
||
|
let path = PathIter::new(path);
|
||
|
let path = StrokeToFillIter::new(path, StrokeStyle::new(stroke_width));
|
||
|
let path = PathEventsToSegments::new(path);
|
||
|
let path = Transform2DF32PathIter::new(path, &transform);
|
||
|
let path = MonotonicConversionIter::new(path);
|
||
|
let outline = Outline::from_segments(path);
|
||
|
|
||
|
scene.bounds = scene.bounds.union(outline.bounds());
|
||
|
scene.objects.push(PathObject::new(
|
||
|
outline,
|
||
|
style,
|
||
|
node.id().to_string(),
|
||
|
PathObjectKind::Stroke,
|
||
|
));
|
||
|
}
|
||
|
}
|
||
|
_ => {
|
||
|
// TODO(pcwalton): Handle these by punting to WebRender.
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
trait PaintExt {
|
||
|
fn from_svg_paint(svg_paint: &UsvgPaint) -> Self;
|
||
|
}
|
||
|
|
||
|
impl PaintExt for Paint {
|
||
|
#[inline]
|
||
|
fn from_svg_paint(svg_paint: &UsvgPaint) -> Paint {
|
||
|
Paint {
|
||
|
color: match *svg_paint {
|
||
|
UsvgPaint::Color(color) => ColorU::from_svg_color(color),
|
||
|
UsvgPaint::Link(_) => {
|
||
|
// TODO(pcwalton)
|
||
|
ColorU::black()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn usvg_rect_to_euclid_rect(rect: &UsvgRect) -> Rect<f32> {
|
||
|
Rect::new(
|
||
|
Point2D::new(rect.x, rect.y),
|
||
|
Size2D::new(rect.width, rect.height),
|
||
|
)
|
||
|
.to_f32()
|
||
|
}
|
||
|
|
||
|
fn usvg_transform_to_transform_2d(transform: &UsvgTransform) -> Transform2DF32 {
|
||
|
Transform2DF32::row_major(
|
||
|
transform.a as f32,
|
||
|
transform.b as f32,
|
||
|
transform.c as f32,
|
||
|
transform.d as f32,
|
||
|
transform.e as f32,
|
||
|
transform.f as f32,
|
||
|
)
|
||
|
}
|
||
|
|
||
|
struct UsvgPathToSegments<I>
|
||
|
where
|
||
|
I: Iterator<Item = UsvgPathSegment>,
|
||
|
{
|
||
|
iter: I,
|
||
|
first_subpath_point: Point2DF32,
|
||
|
last_subpath_point: Point2DF32,
|
||
|
just_moved: bool,
|
||
|
}
|
||
|
|
||
|
impl<I> UsvgPathToSegments<I>
|
||
|
where
|
||
|
I: Iterator<Item = UsvgPathSegment>,
|
||
|
{
|
||
|
fn new(iter: I) -> UsvgPathToSegments<I> {
|
||
|
UsvgPathToSegments {
|
||
|
iter,
|
||
|
first_subpath_point: Point2DF32::default(),
|
||
|
last_subpath_point: Point2DF32::default(),
|
||
|
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 = Point2DF32::new(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 = Point2DF32::new(x as f32, y as f32);
|
||
|
let mut segment =
|
||
|
Segment::line(&LineSegmentF32::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 = Point2DF32::new(x1 as f32, y1 as f32);
|
||
|
let ctrl1 = Point2DF32::new(x2 as f32, y2 as f32);
|
||
|
let to = Point2DF32::new(x as f32, y as f32);
|
||
|
let mut segment = Segment::cubic(
|
||
|
&LineSegmentF32::new(&self.last_subpath_point, &to),
|
||
|
&LineSegmentF32::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(&LineSegmentF32::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: 255,
|
||
|
}
|
||
|
}
|
||
|
}
|