2019-01-14 17:20:36 -05:00
|
|
|
// 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.
|
|
|
|
|
2019-03-06 21:25:08 -05:00
|
|
|
#[macro_use]
|
|
|
|
extern crate bitflags;
|
|
|
|
|
2020-02-16 18:02:37 -05:00
|
|
|
use hashbrown::HashMap;
|
2020-01-31 03:16:34 -05:00
|
|
|
use pathfinder_color::ColorU;
|
2020-02-17 17:44:48 -05:00
|
|
|
use pathfinder_content::fill::FillRule;
|
2019-06-21 13:06:19 -04:00
|
|
|
use pathfinder_content::outline::Outline;
|
|
|
|
use pathfinder_content::segment::{Segment, SegmentFlags};
|
|
|
|
use pathfinder_content::stroke::{LineCap, LineJoin, OutlineStrokeToFill, StrokeStyle};
|
2019-07-11 17:35:06 -04:00
|
|
|
use pathfinder_content::transform::Transform2FPathIter;
|
2019-06-21 13:06:19 -04:00
|
|
|
use pathfinder_geometry::line_segment::LineSegment2F;
|
|
|
|
use pathfinder_geometry::rect::RectF;
|
2019-07-11 17:35:06 -04:00
|
|
|
use pathfinder_geometry::transform2d::Transform2F;
|
2019-06-21 13:06:19 -04:00
|
|
|
use pathfinder_geometry::vector::Vector2F;
|
2019-05-14 21:09:01 -04:00
|
|
|
use pathfinder_renderer::paint::Paint;
|
2020-02-16 18:02:37 -05:00
|
|
|
use pathfinder_renderer::scene::{ClipPath, ClipPathId, DrawPath, Scene};
|
2019-03-06 21:25:08 -05:00
|
|
|
use std::fmt::{Display, Formatter, Result as FormatResult};
|
2020-02-17 17:44:48 -05:00
|
|
|
use usvg::{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, Transform as UsvgTransform};
|
|
|
|
use usvg::{Tree, Visibility};
|
2019-01-14 17:20:36 -05:00
|
|
|
|
2019-03-26 22:56:59 -04:00
|
|
|
const HAIRLINE_STROKE_WIDTH: f32 = 0.0333;
|
2019-01-14 17:20:36 -05:00
|
|
|
|
2019-03-06 21:25:08 -05:00
|
|
|
pub struct BuiltSVG {
|
|
|
|
pub scene: Scene,
|
|
|
|
pub result_flags: BuildResultFlags,
|
2020-02-16 18:02:37 -05:00
|
|
|
pub clip_paths: HashMap<String, ClipPathId>,
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
|
|
|
|
2019-03-06 21:25:08 -05:00
|
|
|
bitflags! {
|
|
|
|
// NB: If you change this, make sure to update the `Display`
|
|
|
|
// implementation as well.
|
|
|
|
pub struct BuildResultFlags: u16 {
|
|
|
|
const UNSUPPORTED_CLIP_PATH_NODE = 0x0001;
|
|
|
|
const UNSUPPORTED_DEFS_NODE = 0x0002;
|
|
|
|
const UNSUPPORTED_FILTER_NODE = 0x0004;
|
|
|
|
const UNSUPPORTED_IMAGE_NODE = 0x0008;
|
|
|
|
const UNSUPPORTED_LINEAR_GRADIENT_NODE = 0x0010;
|
|
|
|
const UNSUPPORTED_MASK_NODE = 0x0020;
|
|
|
|
const UNSUPPORTED_PATTERN_NODE = 0x0040;
|
|
|
|
const UNSUPPORTED_RADIAL_GRADIENT_NODE = 0x0080;
|
|
|
|
const UNSUPPORTED_NESTED_SVG_NODE = 0x0100;
|
|
|
|
const UNSUPPORTED_TEXT_NODE = 0x0200;
|
|
|
|
const UNSUPPORTED_LINK_PAINT = 0x0400;
|
|
|
|
const UNSUPPORTED_CLIP_PATH_ATTR = 0x0800;
|
|
|
|
const UNSUPPORTED_FILTER_ATTR = 0x1000;
|
|
|
|
const UNSUPPORTED_MASK_ATTR = 0x2000;
|
|
|
|
const UNSUPPORTED_OPACITY_ATTR = 0x4000;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BuiltSVG {
|
2019-01-14 17:20:36 -05:00
|
|
|
// TODO(pcwalton): Allow a global transform to be set.
|
2020-02-19 20:44:41 -05:00
|
|
|
#[inline]
|
|
|
|
pub fn from_tree(tree: &Tree) -> BuiltSVG {
|
|
|
|
BuiltSVG::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) -> BuiltSVG {
|
2020-02-16 18:02:37 -05:00
|
|
|
// TODO(pcwalton): Maybe have a `SVGBuilder` type to hold the clip path IDs and other
|
|
|
|
// transient data separate from `BuiltSVG`?
|
2019-03-06 21:25:08 -05:00
|
|
|
let mut built_svg = BuiltSVG {
|
2020-02-19 20:44:41 -05:00
|
|
|
scene,
|
2019-03-06 21:25:08 -05:00
|
|
|
result_flags: BuildResultFlags::empty(),
|
2020-02-16 18:02:37 -05:00
|
|
|
clip_paths: HashMap::new(),
|
2019-03-06 21:25:08 -05:00
|
|
|
};
|
2019-01-14 17:20:36 -05:00
|
|
|
|
|
|
|
let root = &tree.root();
|
|
|
|
match *root.borrow() {
|
|
|
|
NodeKind::Svg(ref svg) => {
|
2019-04-30 22:13:28 -04:00
|
|
|
built_svg.scene.set_view_box(usvg_rect_to_euclid_rect(&svg.view_box.rect));
|
2019-01-14 17:20:36 -05:00
|
|
|
for kid in root.children() {
|
2020-02-16 18:02:37 -05:00
|
|
|
built_svg.process_node(&kid, &State::new(), &mut None);
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
2020-02-19 20:44:41 -05:00
|
|
|
}
|
2019-01-14 17:20:36 -05:00
|
|
|
|
2019-03-06 21:25:08 -05:00
|
|
|
built_svg
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
|
|
|
|
2020-02-16 18:02:37 -05:00
|
|
|
fn process_node(&mut self,
|
|
|
|
node: &Node,
|
|
|
|
state: &State,
|
|
|
|
clip_outline: &mut Option<Outline>) {
|
|
|
|
let mut state = (*state).clone();
|
2019-03-06 21:25:08 -05:00
|
|
|
let node_transform = usvg_transform_to_transform_2d(&node.transform());
|
2020-02-16 18:02:37 -05:00
|
|
|
state.transform = node_transform * state.transform;
|
2019-03-06 21:25:08 -05:00
|
|
|
|
|
|
|
match *node.borrow() {
|
|
|
|
NodeKind::Group(ref group) => {
|
|
|
|
if group.filter.is_some() {
|
2019-04-29 19:58:09 -04:00
|
|
|
self.result_flags
|
|
|
|
.insert(BuildResultFlags::UNSUPPORTED_FILTER_ATTR);
|
2019-03-06 21:25:08 -05:00
|
|
|
}
|
|
|
|
if group.mask.is_some() {
|
2019-04-29 19:58:09 -04:00
|
|
|
self.result_flags
|
|
|
|
.insert(BuildResultFlags::UNSUPPORTED_MASK_ATTR);
|
2019-03-06 21:25:08 -05:00
|
|
|
}
|
2019-01-14 17:20:36 -05:00
|
|
|
|
2020-02-16 18:02:37 -05:00
|
|
|
if let Some(ref clip_path_name) = group.clip_path {
|
|
|
|
if let Some(clip_path_id) = self.clip_paths.get(clip_path_name) {
|
|
|
|
// TODO(pcwalton): Combine multiple clip paths if there's already one.
|
|
|
|
state.clip_path = Some(*clip_path_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-06 21:25:08 -05:00
|
|
|
for kid in node.children() {
|
2020-02-16 18:02:37 -05:00
|
|
|
self.process_node(&kid, &state, clip_outline)
|
2019-03-06 21:25:08 -05:00
|
|
|
}
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
2020-02-16 18:02:37 -05:00
|
|
|
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);
|
|
|
|
*clip_outline = Some(Outline::from_segments(path));
|
|
|
|
}
|
|
|
|
NodeKind::Path(ref path) if state.path_destination == PathDestination::Draw &&
|
|
|
|
path.visibility == Visibility::Visible => {
|
2019-03-06 21:25:08 -05:00
|
|
|
if let Some(ref fill) = path.fill {
|
2019-12-28 03:43:58 -05:00
|
|
|
let path = UsvgPathToSegments::new(path.data.iter().cloned());
|
2019-03-06 21:25:08 -05:00
|
|
|
let outline = Outline::from_segments(path);
|
|
|
|
|
2019-05-10 15:03:38 -04:00
|
|
|
let name = format!("Fill({})", node.id());
|
2020-02-17 17:44:48 -05:00
|
|
|
self.push_draw_path(outline,
|
|
|
|
name,
|
|
|
|
&state,
|
|
|
|
&fill.paint,
|
|
|
|
fill.opacity,
|
|
|
|
fill.rule);
|
2019-03-06 21:25:08 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(ref stroke) = path.stroke {
|
2019-05-16 13:43:43 -04:00
|
|
|
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),
|
2019-05-30 23:55:58 -04:00
|
|
|
line_join: LineJoin::from_usvg_line_join(stroke.linejoin,
|
2019-06-20 20:13:15 -04:00
|
|
|
stroke.miterlimit.value() as f32),
|
2019-05-16 13:43:43 -04:00
|
|
|
};
|
2019-03-06 21:25:08 -05:00
|
|
|
|
2019-12-28 03:43:58 -05:00
|
|
|
let path = UsvgPathToSegments::new(path.data.iter().cloned());
|
2019-03-06 21:25:08 -05:00
|
|
|
let outline = Outline::from_segments(path);
|
|
|
|
|
2019-05-29 18:15:10 -04:00
|
|
|
let mut stroke_to_fill = OutlineStrokeToFill::new(&outline, stroke_style);
|
2019-03-06 21:25:08 -05:00
|
|
|
stroke_to_fill.offset();
|
2020-03-03 14:17:09 -05:00
|
|
|
let outline = stroke_to_fill.into_outline();
|
2019-03-06 21:25:08 -05:00
|
|
|
|
2019-05-10 15:03:38 -04:00
|
|
|
let name = format!("Stroke({})", node.id());
|
2020-02-17 17:44:48 -05:00
|
|
|
self.push_draw_path(outline,
|
|
|
|
name,
|
|
|
|
&state,
|
|
|
|
&stroke.paint,
|
|
|
|
stroke.opacity,
|
|
|
|
UsvgFillRule::NonZero);
|
2019-03-06 21:25:08 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
NodeKind::Path(..) => {}
|
2020-02-16 18:02:37 -05:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(clip_outline) = clip_outline {
|
2020-02-17 17:44:48 -05:00
|
|
|
// FIXME(pcwalton): Is the winding fill rule correct to use?
|
2020-02-26 15:43:07 -05:00
|
|
|
let mut clip_path = ClipPath::new(clip_outline);
|
|
|
|
clip_path.set_name(format!("ClipPath({})", node.id()));
|
2020-02-16 18:02:37 -05:00
|
|
|
let clip_path_id = self.scene.push_clip_path(clip_path);
|
|
|
|
self.clip_paths.insert(node.id().to_owned(), clip_path_id);
|
|
|
|
}
|
2019-03-06 21:25:08 -05:00
|
|
|
}
|
2020-02-16 18:02:37 -05:00
|
|
|
NodeKind::Defs => {
|
|
|
|
// FIXME(pcwalton): This is wrong.
|
|
|
|
state.path_destination = PathDestination::Defs;
|
|
|
|
for kid in node.children() {
|
|
|
|
self.process_node(&kid, &state, clip_outline);
|
2019-03-07 14:23:44 -05:00
|
|
|
}
|
2019-03-06 21:25:08 -05:00
|
|
|
}
|
|
|
|
NodeKind::Filter(..) => {
|
2019-04-29 19:58:09 -04:00
|
|
|
self.result_flags
|
|
|
|
.insert(BuildResultFlags::UNSUPPORTED_FILTER_NODE);
|
2019-03-06 21:25:08 -05:00
|
|
|
}
|
|
|
|
NodeKind::Image(..) => {
|
2019-04-29 19:58:09 -04:00
|
|
|
self.result_flags
|
|
|
|
.insert(BuildResultFlags::UNSUPPORTED_IMAGE_NODE);
|
2019-03-06 21:25:08 -05:00
|
|
|
}
|
|
|
|
NodeKind::LinearGradient(..) => {
|
2019-04-29 19:58:09 -04:00
|
|
|
self.result_flags
|
|
|
|
.insert(BuildResultFlags::UNSUPPORTED_LINEAR_GRADIENT_NODE);
|
2019-03-06 21:25:08 -05:00
|
|
|
}
|
|
|
|
NodeKind::Mask(..) => {
|
2019-04-29 19:58:09 -04:00
|
|
|
self.result_flags
|
|
|
|
.insert(BuildResultFlags::UNSUPPORTED_MASK_NODE);
|
2019-03-06 21:25:08 -05:00
|
|
|
}
|
|
|
|
NodeKind::Pattern(..) => {
|
2019-04-29 19:58:09 -04:00
|
|
|
self.result_flags
|
|
|
|
.insert(BuildResultFlags::UNSUPPORTED_PATTERN_NODE);
|
2019-03-06 21:25:08 -05:00
|
|
|
}
|
|
|
|
NodeKind::RadialGradient(..) => {
|
2019-04-29 19:58:09 -04:00
|
|
|
self.result_flags
|
|
|
|
.insert(BuildResultFlags::UNSUPPORTED_RADIAL_GRADIENT_NODE);
|
2019-03-06 21:25:08 -05:00
|
|
|
}
|
|
|
|
NodeKind::Svg(..) => {
|
2019-04-29 19:58:09 -04:00
|
|
|
self.result_flags
|
|
|
|
.insert(BuildResultFlags::UNSUPPORTED_NESTED_SVG_NODE);
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
2019-03-06 21:25:08 -05:00
|
|
|
}
|
|
|
|
}
|
2020-02-16 18:02:37 -05:00
|
|
|
|
|
|
|
fn push_draw_path(&mut self,
|
|
|
|
outline: Outline,
|
|
|
|
name: String,
|
|
|
|
state: &State,
|
|
|
|
paint: &UsvgPaint,
|
2020-02-17 17:44:48 -05:00
|
|
|
opacity: Opacity,
|
|
|
|
fill_rule: UsvgFillRule) {
|
2020-02-26 15:43:07 -05:00
|
|
|
let style = self.scene.push_paint(&Paint::from_svg_paint(paint, &mut self.result_flags));
|
2020-02-17 17:44:48 -05:00
|
|
|
let fill_rule = FillRule::from_usvg_fill_rule(fill_rule);
|
2020-02-26 15:43:07 -05:00
|
|
|
let mut path = DrawPath::new(outline, style);
|
|
|
|
path.set_clip_path(state.clip_path);
|
|
|
|
path.set_fill_rule(fill_rule);
|
|
|
|
path.set_name(name);
|
|
|
|
path.set_opacity((opacity.value() * 255.0) as u8);
|
2020-03-03 14:17:09 -05:00
|
|
|
path.set_transform(state.transform);
|
2020-02-26 15:43:07 -05:00
|
|
|
self.scene.push_path(path);
|
2020-02-16 18:02:37 -05:00
|
|
|
}
|
2019-03-06 21:25:08 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Display for BuildResultFlags {
|
|
|
|
fn fmt(&self, formatter: &mut Formatter) -> FormatResult {
|
|
|
|
if self.is_empty() {
|
2019-04-29 19:58:09 -04:00
|
|
|
return Ok(());
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
2019-03-06 21:25:08 -05:00
|
|
|
|
|
|
|
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)?;
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
2019-03-06 21:25:08 -05:00
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
|
|
|
|
// Must match the order in `BuildResultFlags`.
|
|
|
|
static NAMES: &'static [&'static str] = &[
|
|
|
|
"<clipPath>",
|
|
|
|
"<defs>",
|
|
|
|
"<filter>",
|
|
|
|
"<image>",
|
|
|
|
"<linearGradient>",
|
|
|
|
"<mask>",
|
|
|
|
"<pattern>",
|
|
|
|
"<radialGradient>",
|
|
|
|
"nested <svg>",
|
|
|
|
"<text>",
|
|
|
|
"paint server element",
|
|
|
|
"clip-path attribute",
|
|
|
|
"filter attribute",
|
|
|
|
"mask attribute",
|
|
|
|
"opacity attribute",
|
|
|
|
];
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
trait PaintExt {
|
2020-02-26 15:43:07 -05:00
|
|
|
fn from_svg_paint(svg_paint: &UsvgPaint, result_flags: &mut BuildResultFlags) -> Self;
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl PaintExt for Paint {
|
|
|
|
#[inline]
|
2020-02-26 15:43:07 -05:00
|
|
|
fn from_svg_paint(svg_paint: &UsvgPaint, result_flags: &mut BuildResultFlags) -> Paint {
|
2020-02-05 22:59:40 -05:00
|
|
|
// TODO(pcwalton): Support gradients.
|
|
|
|
Paint::Color(match *svg_paint {
|
2020-02-26 15:43:07 -05:00
|
|
|
UsvgPaint::Color(color) => ColorU::from_svg_color(color),
|
2020-02-05 22:59:40 -05:00
|
|
|
UsvgPaint::Link(_) => {
|
|
|
|
// TODO(pcwalton)
|
|
|
|
result_flags.insert(BuildResultFlags::UNSUPPORTED_LINK_PAINT);
|
|
|
|
ColorU::black()
|
|
|
|
}
|
|
|
|
})
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-29 22:13:42 -04:00
|
|
|
fn usvg_rect_to_euclid_rect(rect: &UsvgRect) -> RectF {
|
|
|
|
RectF::new(
|
2019-06-20 20:13:15 -04:00
|
|
|
Vector2F::new(rect.x() as f32, rect.y() as f32),
|
|
|
|
Vector2F::new(rect.width() as f32, rect.height() as f32),
|
2019-01-14 17:20:36 -05:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-07-11 17:35:06 -04:00
|
|
|
fn usvg_transform_to_transform_2d(transform: &UsvgTransform) -> Transform2F {
|
|
|
|
Transform2F::row_major(
|
2019-01-14 17:20:36 -05:00
|
|
|
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,
|
2019-06-03 15:39:29 -04:00
|
|
|
first_subpath_point: Vector2F,
|
|
|
|
last_subpath_point: Vector2F,
|
2019-01-14 17:20:36 -05:00
|
|
|
just_moved: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<I> UsvgPathToSegments<I>
|
|
|
|
where
|
|
|
|
I: Iterator<Item = UsvgPathSegment>,
|
|
|
|
{
|
|
|
|
fn new(iter: I) -> UsvgPathToSegments<I> {
|
|
|
|
UsvgPathToSegments {
|
|
|
|
iter,
|
2019-06-03 15:39:29 -04:00
|
|
|
first_subpath_point: Vector2F::default(),
|
|
|
|
last_subpath_point: Vector2F::default(),
|
2019-01-14 17:20:36 -05:00
|
|
|
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 } => {
|
2019-06-03 15:39:29 -04:00
|
|
|
let to = Vector2F::new(x as f32, y as f32);
|
2019-01-14 17:20:36 -05:00
|
|
|
self.first_subpath_point = to;
|
|
|
|
self.last_subpath_point = to;
|
|
|
|
self.just_moved = true;
|
|
|
|
self.next()
|
|
|
|
}
|
|
|
|
UsvgPathSegment::LineTo { x, y } => {
|
2019-06-03 15:39:29 -04:00
|
|
|
let to = Vector2F::new(x as f32, y as f32);
|
2019-06-25 17:43:13 -04:00
|
|
|
let mut segment = Segment::line(LineSegment2F::new(self.last_subpath_point, to));
|
2019-01-14 17:20:36 -05:00
|
|
|
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,
|
|
|
|
} => {
|
2019-06-03 15:39:29 -04:00
|
|
|
let ctrl0 = Vector2F::new(x1 as f32, y1 as f32);
|
|
|
|
let ctrl1 = Vector2F::new(x2 as f32, y2 as f32);
|
|
|
|
let to = Vector2F::new(x as f32, y as f32);
|
2019-01-14 17:20:36 -05:00
|
|
|
let mut segment = Segment::cubic(
|
2019-06-25 17:43:13 -04:00
|
|
|
LineSegment2F::new(self.last_subpath_point, to),
|
|
|
|
LineSegment2F::new(ctrl0, ctrl1),
|
2019-01-14 17:20:36 -05:00
|
|
|
);
|
|
|
|
if self.just_moved {
|
|
|
|
segment.flags.insert(SegmentFlags::FIRST_IN_SUBPATH);
|
|
|
|
}
|
|
|
|
self.last_subpath_point = to;
|
|
|
|
self.just_moved = false;
|
|
|
|
Some(segment)
|
|
|
|
}
|
|
|
|
UsvgPathSegment::ClosePath => {
|
2019-06-25 17:43:13 -04:00
|
|
|
let mut segment = Segment::line(LineSegment2F::new(
|
2019-05-13 21:19:10 -04:00
|
|
|
self.last_subpath_point,
|
|
|
|
self.first_subpath_point,
|
2019-01-14 17:20:36 -05:00
|
|
|
));
|
|
|
|
segment.flags.insert(SegmentFlags::CLOSES_SUBPATH);
|
|
|
|
self.just_moved = false;
|
|
|
|
self.last_subpath_point = self.first_subpath_point;
|
|
|
|
Some(segment)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
trait ColorUExt {
|
2020-02-26 15:43:07 -05:00
|
|
|
fn from_svg_color(svg_color: SvgColor) -> Self;
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ColorUExt for ColorU {
|
|
|
|
#[inline]
|
2020-02-26 15:43:07 -05:00
|
|
|
fn from_svg_color(svg_color: SvgColor) -> ColorU {
|
|
|
|
ColorU { r: svg_color.red, g: svg_color.green, b: svg_color.blue, a: !0 }
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
|
|
|
}
|
2019-05-16 13:43:43 -04:00
|
|
|
|
|
|
|
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,
|
2019-05-30 23:55:58 -04:00
|
|
|
UsvgLineCap::Round => LineCap::Round,
|
2019-05-16 13:43:43 -04:00
|
|
|
UsvgLineCap::Square => LineCap::Square,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-05-16 14:10:15 -04:00
|
|
|
|
|
|
|
trait LineJoinExt {
|
2019-05-30 23:55:58 -04:00
|
|
|
fn from_usvg_line_join(usvg_line_join: UsvgLineJoin, miter_limit: f32) -> Self;
|
2019-05-16 14:10:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl LineJoinExt for LineJoin {
|
|
|
|
#[inline]
|
2019-05-30 23:55:58 -04:00
|
|
|
fn from_usvg_line_join(usvg_line_join: UsvgLineJoin, miter_limit: f32) -> LineJoin {
|
2019-05-16 14:10:15 -04:00
|
|
|
match usvg_line_join {
|
2019-05-30 23:55:58 -04:00
|
|
|
UsvgLineJoin::Miter => LineJoin::Miter(miter_limit),
|
|
|
|
UsvgLineJoin::Round => LineJoin::Round,
|
2019-05-16 14:10:15 -04:00
|
|
|
UsvgLineJoin::Bevel => LineJoin::Bevel,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-02-16 18:02:37 -05:00
|
|
|
|
2020-02-17 17:44:48 -05:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-16 18:02:37 -05:00
|
|
|
#[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,
|
|
|
|
}
|