summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRazrFalcon <razrfalcon@gmail.com>2018-12-24 20:00:02 +0200
committerRazrFalcon <razrfalcon@gmail.com>2018-12-24 20:00:02 +0200
commit78683370cd41ae10dcc426f2c3237e2bb55e9820 (patch)
tree71ea77490f66f9d095aba91e53cd5d2dd059e369
parent07b5191683f18be693a08f66334568f098ea5429 (diff)
Added marker support.
-rw-r--r--CHANGELOG.md9
-rw-r--r--Cargo.lock12
-rw-r--r--README.md2
-rw-r--r--docs/backend_requirements.md2
-rw-r--r--docs/unsupported.md5
-rw-r--r--src/backend_cairo/clippath.rs7
-rw-r--r--src/backend_cairo/marker.rs94
-rw-r--r--src/backend_cairo/mod.rs3
-rw-r--r--src/backend_cairo/path.rs4
-rw-r--r--src/backend_cairo/stroke.rs2
-rw-r--r--src/backend_qt/clippath.rs7
-rw-r--r--src/backend_qt/marker.rs93
-rw-r--r--src/backend_qt/mod.rs3
-rw-r--r--src/backend_qt/path.rs4
-rw-r--r--src/backend_qt/pattern.rs1
-rw-r--r--src/backend_qt/stroke.rs2
-rw-r--r--src/backend_utils/marker.rs282
-rw-r--r--src/backend_utils/mod.rs1
-rw-r--r--testing_tools/regression/allow-cairo.txt74
-rw-r--r--testing_tools/regression/allow-qt.txt74
-rw-r--r--usvg/CHANGELOG.md2
-rw-r--r--usvg/Cargo.toml2
-rw-r--r--usvg/docs/usvg_spec.adoc9
-rw-r--r--usvg/src/convert/marker.rs84
-rw-r--r--usvg/src/convert/mod.rs13
-rw-r--r--usvg/src/convert/path.rs41
-rw-r--r--usvg/src/convert/stroke.rs2
-rw-r--r--usvg/src/geom.rs7
-rw-r--r--usvg/src/preproc/fix_recursive_links.rs33
-rw-r--r--usvg/src/preproc/mod.rs4
-rw-r--r--usvg/src/preproc/prepare_marker.rs35
-rw-r--r--usvg/src/preproc/resolve_style_attrs.rs6
-rw-r--r--usvg/src/tree/attributes.rs80
-rw-r--r--usvg/src/tree/convert.rs59
-rw-r--r--usvg/src/tree/nodes.rs42
-rw-r--r--usvg/testing_tools/allow.csv5
-rw-r--r--usvg/testing_tools/cache.csv89
37 files changed, 1114 insertions, 80 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab9a61d..0069dd8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,17 +8,18 @@ This changelog also contains important changes in dependencies.
## [Unreleased]
### Added
-- (resvg) Partial `baseline-shift` support.
-- (resvg) `letter-spacing` support.
+- Added marker support.
+- Partial `baseline-shift` support.
+- `letter-spacing` support.
- (qt-backend) `word-spacing` support.
Does not work on the cairo backend.
-- (resvg) Keep invisible shapes during *export by ID*.
- Required for a proper bbox resolving.
### Fixed
- (usvg) `offset` attribute resolving inside the `stop` element.
- (usvg) Ungrouping of groups with non-inheritable attributes.
- (usvg) `rotate` attribute resolving.
+- (usvg) Paths without stroke and fill will no longer be removed.
+ Required for a proper bbox resolving.
- (svgdom) `stroke-miterlimit` attribute parsing.
- (svgdom) `length` and `number` attribute types parsing.
- (svgdom) `offset` attribute parsing.
diff --git a/Cargo.lock b/Cargo.lock
index 8d8a5e8..da7c63e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -547,19 +547,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "svgdom"
version = "0.15.0"
-source = "git+https://github.com/RazrFalcon/svgdom?rev=f53af72#f53af72283fbc6fdfc3ff00a3238f3e4b3e053c1"
+source = "git+https://github.com/RazrFalcon/svgdom?rev=6ff5579#6ff557934d84be85f25223c626057df8ebb9004c"
dependencies = [
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"roxmltree 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"simplecss 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "svgtypes 0.3.0 (git+https://github.com/RazrFalcon/svgtypes?rev=3034692)",
+ "svgtypes 0.3.0 (git+https://github.com/RazrFalcon/svgtypes?rev=63fc6da)",
]
[[package]]
name = "svgtypes"
version = "0.3.0"
-source = "git+https://github.com/RazrFalcon/svgtypes?rev=3034692#3034692f4ef2dc31f6da62d23c3d363029aeeaca"
+source = "git+https://github.com/RazrFalcon/svgtypes?rev=63fc6da#63fc6da5b1cb226b056344db546510ef8b923923"
dependencies = [
"float-cmp 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"phf 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -605,7 +605,7 @@ dependencies = [
"lyon_geom 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rctree 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "svgdom 0.15.0 (git+https://github.com/RazrFalcon/svgdom?rev=f53af72)",
+ "svgdom 0.15.0 (git+https://github.com/RazrFalcon/svgdom?rev=6ff5579)",
"unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -699,8 +699,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum simplecss 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "135685097a85a64067df36e28a243e94a94f76d829087ce0be34eeb014260c0e"
"checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
"checksum slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5f9776d6b986f77b35c6cf846c11ad986ff128fe0b2b63a3628e3755e8d3102d"
-"checksum svgdom 0.15.0 (git+https://github.com/RazrFalcon/svgdom?rev=f53af72)" = "<none>"
-"checksum svgtypes 0.3.0 (git+https://github.com/RazrFalcon/svgtypes?rev=3034692)" = "<none>"
+"checksum svgdom 0.15.0 (git+https://github.com/RazrFalcon/svgdom?rev=6ff5579)" = "<none>"
+"checksum svgtypes 0.3.0 (git+https://github.com/RazrFalcon/svgtypes?rev=63fc6da)" = "<none>"
"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741"
"checksum time 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "847da467bf0db05882a9e2375934a8a55cffdc9db0d128af1518200260ba1f6c"
"checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1"
diff --git a/README.md b/README.md
index 2545050..172d7ae 100644
--- a/README.md
+++ b/README.md
@@ -63,7 +63,7 @@ of the SVG Tiny 1.2 subset. In simple terms - it correctly renders only primitiv
*resvg* is aiming to support only the [static](http://www.w3.org/TR/SVG11/feature#SVG-static)
SVG subset. E.g. no `a`, `script`, `view`, `cursor` elements, no events and no animations.
-Also, `marker`, `textPath` and
+Also, `textPath` and
[embedded fonts](https://www.w3.org/TR/SVG11/feature#Font) are not yet implemented.
A full list can be found [here](docs/unsupported.md).
diff --git a/docs/backend_requirements.md b/docs/backend_requirements.md
index 8919018..8f02165 100644
--- a/docs/backend_requirements.md
+++ b/docs/backend_requirements.md
@@ -49,6 +49,8 @@ List of features required from the 2D graphics library to implement a backend fo
- Stretch
- Variant: *normal*, *small cap*
- Size
+ - Letter spacing
+ - Word spacing
- Font metrics:
- Text bounding box
- Ascent/baseline
diff --git a/docs/unsupported.md b/docs/unsupported.md
index 3868e89..cd1d0a9 100644
--- a/docs/unsupported.md
+++ b/docs/unsupported.md
@@ -26,7 +26,6 @@
- `altGlyphItem`
- `glyphRef`
- `color-profile`
-- `marker`
- `textPath`
- `use` with a reference to an external SVG
@@ -49,10 +48,6 @@
with `BackgroundImage`, `BackgroundAlpha`, `FillPaint`, `StrokePaint`
- `image-rendering`
- `kerning`
-- `lighting-color`
-- `marker-start`
-- `marker-mid`
-- `marker-end`
- `shape-rendering`
- `text-rendering`
- `unicode-bidi`
diff --git a/src/backend_cairo/clippath.rs b/src/backend_cairo/clippath.rs
index 574dc37..374435a 100644
--- a/src/backend_cairo/clippath.rs
+++ b/src/backend_cairo/clippath.rs
@@ -46,7 +46,7 @@ pub fn apply(
match *node.borrow() {
usvg::NodeKind::Path(ref p) => {
- path::draw(&node.tree(), p, opt, &clip_cr);
+ path::draw(&node.tree(), p, opt, layers, &clip_cr);
}
usvg::NodeKind::Text(ref text) => {
text::draw(&node.tree(), text, opt, &clip_cr);
@@ -103,7 +103,7 @@ fn clip_group(
clip_cr.paint();
clip_cr.set_matrix(cr.get_matrix());
- draw_group_child(&node, opt, &clip_cr);
+ draw_group_child(&node, opt, layers, &clip_cr);
apply(clip_node, cp, opt, bbox, layers, &clip_cr);
@@ -120,6 +120,7 @@ fn clip_group(
fn draw_group_child(
node: &usvg::Node,
opt: &Options,
+ layers: &mut CairoLayers,
cr: &cairo::Context,
) {
if let Some(child) = node.first_child() {
@@ -127,7 +128,7 @@ fn draw_group_child(
match *child.borrow() {
usvg::NodeKind::Path(ref path_node) => {
- path::draw(&child.tree(), path_node, opt, cr);
+ path::draw(&child.tree(), path_node, opt, layers, cr);
}
usvg::NodeKind::Text(ref text) => {
text::draw(&child.tree(), text, opt, cr);
diff --git a/src/backend_cairo/marker.rs b/src/backend_cairo/marker.rs
new file mode 100644
index 0000000..755cac1
--- /dev/null
+++ b/src/backend_cairo/marker.rs
@@ -0,0 +1,94 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// external
+use cairo;
+
+// self
+use super::prelude::*;
+use backend_utils::marker::*;
+
+
+pub fn apply(
+ tree: &usvg::Tree,
+ path: &usvg::Path,
+ opt: &Options,
+ layers: &mut CairoLayers,
+ cr: &cairo::Context,
+) {
+ let mut draw_marker = |id: &Option<String>, kind: MarkerKind| {
+ if let Some(ref id) = id {
+ if let Some(node) = tree.defs_by_id(id) {
+ if let usvg::NodeKind::Marker(ref marker) = *node.borrow() {
+ _apply(path, marker, &node, kind, opt, layers, cr);
+ }
+ }
+ }
+ };
+
+ draw_marker(&path.marker.start, MarkerKind::Start);
+ draw_marker(&path.marker.mid, MarkerKind::Middle);
+ draw_marker(&path.marker.end, MarkerKind::End);
+}
+
+fn _apply(
+ path: &usvg::Path,
+ marker: &usvg::Marker,
+ marker_node: &usvg::Node,
+ marker_kind: MarkerKind,
+ opt: &Options,
+ layers: &mut CairoLayers,
+ cr: &cairo::Context,
+) {
+ let stroke_scale = try_opt!(stroke_scale(marker, path), ());
+
+ let r = marker.rect;
+ debug_assert!(r.is_valid());
+
+ let draw_marker = |x: f64, y: f64, idx: usize| {
+ let old_ts = cr.get_matrix();
+ cr.translate(x, y);
+
+ let angle = match marker.orientation {
+ usvg::MarkerOrientation::Auto => calc_vertex_angle(&path.segments, idx),
+ usvg::MarkerOrientation::Angle(angle) => angle,
+ };
+
+ if !angle.is_fuzzy_zero() {
+ let ts = usvg::Transform::new_rotate(angle);
+ cr.transform(ts.to_native());
+ }
+
+ if let Some(vbox) = marker.view_box {
+ let size = Size::new(r.width * stroke_scale, r.height * stroke_scale);
+ let ts = utils::view_box_to_transform(vbox.rect, vbox.aspect, size);
+ cr.transform(ts.to_native());
+
+ cr.translate(vbox.rect.x, vbox.rect.y);
+ } else {
+ cr.scale(stroke_scale, stroke_scale);
+ }
+
+ cr.translate(-r.x, -r.y);
+
+ match marker.overflow {
+ usvg::Overflow::Hidden | usvg::Overflow::Scroll => {
+ if let Some(vbox) = marker.view_box {
+ cr.rectangle(vbox.rect.x, vbox.rect.y, vbox.rect.width, vbox.rect.height);
+ } else {
+ cr.rectangle(0.0, 0.0, r.width, r.height);
+ }
+ cr.clip();
+ }
+ _ => {}
+ }
+
+ super::render_group(marker_node, opt, layers, cr);
+
+ cr.set_matrix(old_ts);
+ cr.reset_clip();
+ };
+
+ draw_markers(&path.segments, marker_kind, draw_marker);
+}
diff --git a/src/backend_cairo/mod.rs b/src/backend_cairo/mod.rs
index 832caae..5d326d4 100644
--- a/src/backend_cairo/mod.rs
+++ b/src/backend_cairo/mod.rs
@@ -37,6 +37,7 @@ mod fill;
mod filter;
mod gradient;
mod image;
+mod marker;
mod mask;
mod path;
mod pattern;
@@ -264,7 +265,7 @@ fn render_node(
Some(render_group(node, opt, layers, cr))
}
usvg::NodeKind::Path(ref path) => {
- Some(path::draw(&node.tree(), path, opt, cr))
+ Some(path::draw(&node.tree(), path, opt, layers, cr))
}
usvg::NodeKind::Text(ref text) => {
Some(text::draw(&node.tree(), text, opt, cr))
diff --git a/src/backend_cairo/path.rs b/src/backend_cairo/path.rs
index 98eecb9..27a8727 100644
--- a/src/backend_cairo/path.rs
+++ b/src/backend_cairo/path.rs
@@ -10,6 +10,7 @@ use super::prelude::*;
use super::{
fill,
stroke,
+ marker,
};
@@ -17,6 +18,7 @@ pub fn draw(
tree: &usvg::Tree,
path: &usvg::Path,
opt: &Options,
+ layers: &mut CairoLayers,
cr: &cairo::Context,
) -> Rect {
let mut is_square_cap = false;
@@ -42,6 +44,8 @@ pub fn draw(
cr.fill();
}
+ marker::apply(tree, path, opt, layers, cr);
+
bbox
}
diff --git a/src/backend_cairo/stroke.rs b/src/backend_cairo/stroke.rs
index a0efac8..d30d41d 100644
--- a/src/backend_cairo/stroke.rs
+++ b/src/backend_cairo/stroke.rs
@@ -59,7 +59,7 @@ pub fn apply(
cr.set_line_join(linejoin);
match stroke.dasharray {
- Some(ref list) => cr.set_dash(list, stroke.dashoffset),
+ Some(ref list) => cr.set_dash(list, stroke.dashoffset as f64),
None => cr.set_dash(&[], 0.0),
}
diff --git a/src/backend_qt/clippath.rs b/src/backend_qt/clippath.rs
index eba05bf..ef1219a 100644
--- a/src/backend_qt/clippath.rs
+++ b/src/backend_qt/clippath.rs
@@ -41,7 +41,7 @@ pub fn apply(
match *node.borrow() {
usvg::NodeKind::Path(ref path_node) => {
- path::draw(&node.tree(), path_node, opt, &mut clip_p);
+ path::draw(&node.tree(), path_node, opt, layers, &mut clip_p);
}
usvg::NodeKind::Text(ref text) => {
text::draw(&node.tree(), text, opt, &mut clip_p);
@@ -90,7 +90,7 @@ fn clip_group(
let mut clip_p = qt::Painter::new(&mut clip_img);
clip_p.set_transform(&p.get_transform());
- draw_group_child(&node, opt, &mut clip_p);
+ draw_group_child(&node, opt, layers, &mut clip_p);
apply(clip_node, cp, opt, bbox, layers, &mut clip_p);
clip_p.end();
@@ -106,6 +106,7 @@ fn clip_group(
fn draw_group_child(
node: &usvg::Node,
opt: &Options,
+ layers: &mut QtLayers,
p: &mut qt::Painter,
) {
if let Some(child) = node.first_child() {
@@ -113,7 +114,7 @@ fn draw_group_child(
match *child.borrow() {
usvg::NodeKind::Path(ref path_node) => {
- path::draw(&child.tree(), path_node, opt, p);
+ path::draw(&child.tree(), path_node, opt, layers, p);
}
usvg::NodeKind::Text(ref text) => {
text::draw(&child.tree(), text, opt, p);
diff --git a/src/backend_qt/marker.rs b/src/backend_qt/marker.rs
new file mode 100644
index 0000000..694f432
--- /dev/null
+++ b/src/backend_qt/marker.rs
@@ -0,0 +1,93 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// external
+use qt;
+
+// self
+use super::prelude::*;
+use backend_utils::marker::*;
+
+
+pub fn apply(
+ tree: &usvg::Tree,
+ path: &usvg::Path,
+ opt: &Options,
+ layers: &mut QtLayers,
+ p: &mut qt::Painter,
+) {
+ let mut draw_marker = |id: &Option<String>, kind: MarkerKind| {
+ if let Some(ref id) = id {
+ if let Some(node) = tree.defs_by_id(id) {
+ if let usvg::NodeKind::Marker(ref marker) = *node.borrow() {
+ _apply(path, marker, &node, kind, opt, layers, p);
+ }
+ }
+ }
+ };
+
+ draw_marker(&path.marker.start, MarkerKind::Start);
+ draw_marker(&path.marker.mid, MarkerKind::Middle);
+ draw_marker(&path.marker.end, MarkerKind::End);
+}
+
+fn _apply(
+ path: &usvg::Path,
+ marker: &usvg::Marker,
+ marker_node: &usvg::Node,
+ marker_kind: MarkerKind,
+ opt: &Options,
+ layers: &mut QtLayers,
+ p: &mut qt::Painter,
+) {
+ let stroke_scale = try_opt!(stroke_scale(marker, path), ());
+
+ let r = marker.rect;
+ debug_assert!(r.is_valid());
+
+ let draw_marker = |x: f64, y: f64, idx: usize| {
+ let old_ts = p.get_transform();
+ p.translate(x, y);
+
+ let angle = match marker.orientation {
+ usvg::MarkerOrientation::Auto => calc_vertex_angle(&path.segments, idx),
+ usvg::MarkerOrientation::Angle(angle) => angle,
+ };
+
+ if !angle.is_fuzzy_zero() {
+ let ts = usvg::Transform::new_rotate(angle);
+ p.apply_transform(&ts.to_native());
+ }
+
+ if let Some(vbox) = marker.view_box {
+ let size = Size::new(r.width * stroke_scale, r.height * stroke_scale);
+ let ts = utils::view_box_to_transform(vbox.rect, vbox.aspect, size);
+ p.apply_transform(&ts.to_native());
+
+ p.translate(vbox.rect.x, vbox.rect.y);
+ } else {
+ p.scale(stroke_scale, stroke_scale);
+ }
+
+ p.translate(-r.x, -r.y);
+
+ match marker.overflow {
+ usvg::Overflow::Hidden | usvg::Overflow::Scroll => {
+ if let Some(vbox) = marker.view_box {
+ p.set_clip_rect(vbox.rect.x, vbox.rect.y, vbox.rect.width, vbox.rect.height);
+ } else {
+ p.set_clip_rect(0.0, 0.0, r.width, r.height);
+ }
+ }
+ _ => {}
+ }
+
+ super::render_group(marker_node, opt, layers, p);
+
+ p.set_transform(&old_ts);
+ p.reset_clip_path();
+ };
+
+ draw_markers(&path.segments, marker_kind, draw_marker);
+}
diff --git a/src/backend_qt/mod.rs b/src/backend_qt/mod.rs
index 27302ed..b4d8b94 100644
--- a/src/backend_qt/mod.rs
+++ b/src/backend_qt/mod.rs
@@ -33,6 +33,7 @@ mod fill;
mod filter;
mod gradient;
mod image;
+mod marker;
mod mask;
mod path;
mod pattern;
@@ -221,7 +222,7 @@ fn render_node(
Some(render_group(node, opt, layers, p))
}
usvg::NodeKind::Path(ref path) => {
- Some(path::draw(&node.tree(), path, opt, p))
+ Some(path::draw(&node.tree(), path, opt, layers, p))
}
usvg::NodeKind::Text(ref text) => {
Some(text::draw(&node.tree(), text, opt, p))
diff --git a/src/backend_qt/path.rs b/src/backend_qt/path.rs
index dc3dad3..ef47cf4 100644
--- a/src/backend_qt/path.rs
+++ b/src/backend_qt/path.rs
@@ -10,6 +10,7 @@ use super::prelude::*;
use super::{
fill,
stroke,
+ marker,
};
@@ -17,6 +18,7 @@ pub fn draw(
tree: &usvg::Tree,
path: &usvg::Path,
opt: &Options,
+ layers: &mut QtLayers,
p: &mut qt::Painter,
) -> Rect {
let mut p_path = qt::PainterPath::new();
@@ -40,6 +42,8 @@ pub fn draw(
p.draw_path(&p_path);
+ marker::apply(tree, path, opt, layers, p);
+
bbox
}
diff --git a/src/backend_qt/pattern.rs b/src/backend_qt/pattern.rs
index e177024..ff30b8d 100644
--- a/src/backend_qt/pattern.rs
+++ b/src/backend_qt/pattern.rs
@@ -23,6 +23,7 @@ pub fn apply(
pattern.rect
};
+ // TODO: wrong
let global_ts = usvg::Transform::from_native(&global_ts);
let (sx, sy) = global_ts.get_scale();
// Only integer scaling is allowed.
diff --git a/src/backend_qt/stroke.rs b/src/backend_qt/stroke.rs
index ab19d0b..07e94fe 100644
--- a/src/backend_qt/stroke.rs
+++ b/src/backend_qt/stroke.rs
@@ -71,7 +71,7 @@ pub fn apply(
pen.set_width(stroke.width.value());
if let Some(ref list) = stroke.dasharray {
- pen.set_dash_offset(stroke.dashoffset);
+ pen.set_dash_offset(stroke.dashoffset as f64);
pen.set_dash_array(list);
}
diff --git a/src/backend_utils/marker.rs b/src/backend_utils/marker.rs
new file mode 100644
index 0000000..da09139
--- /dev/null
+++ b/src/backend_utils/marker.rs
@@ -0,0 +1,282 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// external
+use usvg::PathSegment as Segment;
+
+// self
+use super::prelude::*;
+
+
+pub enum MarkerKind {
+ Start,
+ Middle,
+ End,
+}
+
+pub fn stroke_scale(marker: &usvg::Marker, path: &usvg::Path) -> Option<f64> {
+ match marker.units {
+ usvg::MarkerUnits::StrokeWidth => {
+ match path.marker.stroke {
+ Some(sw) => Some(sw.value()),
+ None => None,
+ }
+ }
+ usvg::MarkerUnits::UserSpaceOnUse => Some(1.0),
+ }
+}
+
+pub fn draw_markers<P>(segments: &[Segment], kind: MarkerKind, mut draw_marker: P)
+ where P: FnMut(f64, f64, usize)
+{
+ match kind {
+ MarkerKind::Start => {
+ if let Some(Segment::MoveTo { x, y }) = segments.first() {
+ draw_marker(*x, *y, 0);
+ }
+ }
+ MarkerKind::Middle => {
+ let total = segments.len() - 1;
+ let mut i = 1;
+ while i < total {
+ let (x, y) = match segments[i] {
+ Segment::MoveTo { x, y } => (x, y),
+ Segment::LineTo { x, y } => (x, y),
+ Segment::CurveTo { x, y, .. } => (x, y),
+ _ => {
+ i += 1;
+ continue
+ }
+ };
+
+ draw_marker(x, y, i);
+
+ i += 1;
+ }
+ }
+ MarkerKind::End => {
+ let idx = segments.len() - 1;
+ match segments.last() {
+ Some(Segment::LineTo { x, y }) => {
+ draw_marker(*x, *y, idx);
+ }
+ Some(Segment::CurveTo { x, y, .. }) => {
+ draw_marker(*x, *y, idx);
+ }
+ Some(Segment::ClosePath) => {
+ let (x, y) = get_subpath_start(segments, idx);
+ draw_marker(x, y, idx);
+ }
+ _ => {}
+ }
+ }
+ }
+}
+
+pub fn calc_vertex_angle(segments: &[Segment], idx: usize) -> f64 {
+ if idx == 0 {
+ // First segment.
+
+ debug_assert!(segments.len() > 1);
+
+ let seg1 = segments[0];
+ let seg2 = segments[1];
+
+ match (seg1, seg2) {
+ (Segment::MoveTo { x: mx, y: my }, Segment::LineTo { x, y }) => {
+ calc_line_angle(mx, my, x, y)
+ }
+ (Segment::MoveTo { x: mx, y: my }, Segment::CurveTo { x1, y1, x, y, .. }) => {
+ if mx.fuzzy_eq(&x1) && my.fuzzy_eq(&y1) {
+ calc_line_angle(mx, my, x, y)
+ } else {
+ calc_line_angle(mx, my, x1, y1)
+ }
+ }
+ _ => 0.0,
+ }
+ } else if idx == segments.len() - 1 {
+ // Last segment.
+
+ let seg1 = segments[idx - 1];
+ let seg2 = segments[idx];
+
+ match (seg1, seg2) {
+ (_, Segment::MoveTo { .. }) => 0.0, // unreachable
+ (_, Segment::LineTo { x, y }) => {
+ let (px, py) = get_prev_vertex(segments, idx);
+ calc_line_angle(px, py, x, y)
+ }
+ (_, Segment::CurveTo { x2, y2, x, y, .. }) => {
+ if x2.fuzzy_eq(&x) && y2.fuzzy_eq(&y) {
+ let (px, py) = get_prev_vertex(segments, idx);
+ calc_line_angle(px, py, x, y)
+ } else {
+ calc_line_angle(x2, y2, x, y)
+ }
+ }
+ (Segment::LineTo { x, y }, Segment::ClosePath) => {
+ let (nx, ny) = get_subpath_start(segments, idx);
+ calc_line_angle(x, y, nx, ny)
+ }
+ (Segment::CurveTo { x2, y2, x, y, .. }, Segment::ClosePath) => {
+ let (px, py) = get_prev_vertex(segments, idx);
+ let (nx, ny) = get_subpath_start(segments, idx);
+ calc_curves_angle(
+ px, py, x2, y2,
+ x, y,
+ nx, ny, nx, ny,
+ )
+ }
+ (_, Segment::ClosePath) => 0.0,
+ }
+ } else {
+ // Middle segments.
+
+ let seg1 = segments[idx];
+ let seg2 = segments[idx + 1];
+
+ // Not sure if there is a better way.
+ match (seg1, seg2) {
+ (Segment::MoveTo { x: mx, y: my }, Segment::LineTo { x, y }) => {
+ calc_line_angle(mx, my, x, y)
+ }
+ (Segment::MoveTo { x: mx, y: my }, Segment::CurveTo { x1, y1, .. }) => {
+ calc_line_angle(mx, my, x1, y1)
+ }
+ (Segment::LineTo { x: x1, y: y1 }, Segment::LineTo { x: x2, y: y2 }) => {
+ let (px, py) = get_prev_vertex(segments, idx);
+ calc_angle(px, py, x1, y1,
+ x1, y1, x2, y2)
+ }
+ (Segment::CurveTo { x2: c1_x2, y2: c1_y2, x, y, .. },
+ Segment::CurveTo { x1: c2_x1, y1: c2_y1, x: nx, y: ny, .. }) => {
+ let (px, py) = get_prev_vertex(segments, idx);
+ calc_curves_angle(
+ px, py, c1_x2, c1_y2,
+ x, y,
+ c2_x1, c2_y1, nx, ny,
+ )
+ }
+ (Segment::LineTo { x, y },
+ Segment::CurveTo { x1, y1, x: nx, y: ny, .. }) => {
+ let (px, py) = get_prev_vertex(segments, idx);
+ calc_curves_angle(
+ px, py, px, py,
+ x, y,
+ x1, y1, nx, ny,
+ )
+ }
+ (Segment::CurveTo { x2, y2, x, y, .. },
+ Segment::LineTo { x: nx, y: ny }) => {
+ let (px, py) = get_prev_vertex(segments, idx);
+ calc_curves_angle(
+ px, py, x2, y2,
+ x, y,
+ nx, ny, nx, ny,
+ )
+ }
+ (Segment::LineTo { x, y }, Segment::MoveTo { .. }) => {
+ let (px, py) = get_prev_vertex(segments, idx);
+ calc_line_angle(px, py, x, y)
+ }
+ (Segment::CurveTo { x2, y2, x, y, .. }, Segment::MoveTo { .. }) => {
+ if x.fuzzy_eq(&x2) && y.fuzzy_eq(&y2) {
+ let (px, py) = get_prev_vertex(segments, idx);
+ calc_line_angle(px, py, x, y)
+ } else {
+ calc_line_angle(x2, y2, x, y)
+ }
+ }
+ (Segment::LineTo { x, y }, Segment::ClosePath) => {
+ let (px, py) = get_prev_vertex(segments, idx);
+ let (nx, ny) = get_subpath_start(segments, idx);
+ calc_angle(px, py, x, y,
+ x, y, nx, ny)
+ }
+ (_, Segment::ClosePath) => {
+ let (px, py) = get_prev_vertex(segments, idx);
+ let (nx, ny) = get_subpath_start(segments, idx);
+ calc_line_angle(px, py, nx, ny)
+ }
+ (_, Segment::MoveTo { .. }) |
+ (Segment::ClosePath, _) => {
+ 0.0
+ }
+ }
+ }
+}
+
+fn calc_line_angle(
+ x1: f64, y1: f64,
+ x2: f64, y2: f64,
+) -> f64 {
+ calc_angle(x1, y1, x2, y2, x1, y1, x2, y2)
+}
+
+fn calc_curves_angle(
+ px: f64, py: f64, // previous vertex
+ cx1: f64, cy1: f64, // previous control point
+ x: f64, y: f64, // current vertex
+ cx2: f64, cy2: f64, // next control point
+ nx: f64, ny: f64, // next vertex
+) -> f64 {
+ if cx1.fuzzy_eq(&x) && cy1.fuzzy_eq(&y) {
+ calc_angle(px, py, x, y, x, y, cx2, cy2)
+ } else if x.fuzzy_eq(&cx2) && y.fuzzy_eq(&cy2) {
+ calc_angle(cx1, cy1, x, y, x, y, nx, ny)
+ } else {
+ calc_angle(cx1, cy1, x, y, x, y, cx2, cy2)
+ }
+}
+
+fn calc_angle(
+ x1: f64, y1: f64,
+ x2: f64, y2: f64,
+ x3: f64, y3: f64,
+ x4: f64, y4: f64,
+) -> f64 {
+ use std::f64::consts::*;
+
+ fn normalize(rad: f64) -> f64 {
+ let v = rad % (PI * 2.0);
+ if v < 0.0 { v + PI * 2.0 } else { v }
+ }
+
+ fn vector_angle(vx: f64, vy: f64) -> f64 {
+ let rad = vy.atan2(vx);
+ if rad.is_nan() { 0.0 } else { normalize(rad) }
+ }
+
+ let in_a = vector_angle(x2 - x1, y2 - y1);
+ let out_a = vector_angle(x4 - x3, y4 - y3);
+ let d = (out_a - in_a) * 0.5;
+
+ let mut angle = in_a + d;
+ if FRAC_PI_2 < d.abs() {
+ angle -= PI;
+ }
+
+ normalize(angle) * 180.0 / PI
+}
+
+fn get_subpath_start(segments: &[Segment], idx: usize) -> (f64, f64) {
+ let offset = segments.len() - idx;
+ for seg in segments.iter().rev().skip(offset) {
+ if let Segment::MoveTo { x, y } = seg {
+ return (*x, *y);
+ }
+ }
+
+ return (0.0, 0.0)
+}
+
+fn get_prev_vertex(segments: &[Segment], idx: usize) -> (f64, f64) {
+ match segments[idx - 1] {
+ Segment::MoveTo { x, y } => (x, y),
+ Segment::LineTo { x, y } => (x, y),
+ Segment::CurveTo { x, y, .. } => (x, y),
+ Segment::ClosePath => get_subpath_start(segments, idx),
+ }
+}
diff --git a/src/backend_utils/mod.rs b/src/backend_utils/mod.rs
index d16f7e4..acdf593 100644
--- a/src/backend_utils/mod.rs
+++ b/src/backend_utils/mod.rs
@@ -4,6 +4,7 @@
pub mod filter;
pub mod image;
+pub mod marker;
pub mod mask;
pub mod text;
diff --git a/testing_tools/regression/allow-cairo.txt b/testing_tools/regression/allow-cairo.txt
index 77edfdc..2fdfb87 100644
--- a/testing_tools/regression/allow-cairo.txt
+++ b/testing_tools/regression/allow-cairo.txt
@@ -1,12 +1,62 @@
-a-letter-spacing-001.svg
-a-letter-spacing-002.svg
-a-letter-spacing-003.svg
-a-letter-spacing-004.svg
-a-letter-spacing-005.svg
-a-letter-spacing-006.svg
-a-word-spacing-001.svg
-a-word-spacing-002.svg
-a-word-spacing-003.svg
-a-word-spacing-004.svg
-a-word-spacing-005.svg
-a-word-spacing-006.svg
+a-marker-end-001.svg
+a-marker-mid-001.svg
+a-marker-start-001.svg
+a-overflow-001.svg
+a-overflow-002.svg
+a-overflow-003.svg
+e-marker-001.svg
+e-marker-002.svg
+e-marker-003.svg
+e-marker-004.svg
+e-marker-005.svg
+e-marker-006.svg
+e-marker-007.svg
+e-marker-008.svg
+e-marker-009.svg
+e-marker-010.svg
+e-marker-011.svg
+e-marker-012.svg
+e-marker-013.svg
+e-marker-014.svg
+e-marker-015.svg
+e-marker-016.svg
+e-marker-017.svg
+e-marker-018.svg
+e-marker-019.svg
+e-marker-020.svg
+e-marker-021.svg
+e-marker-022.svg
+e-marker-023.svg
+e-marker-024.svg
+e-marker-025.svg
+e-marker-026.svg
+e-marker-027.svg
+e-marker-028.svg
+e-marker-029.svg
+e-marker-030.svg
+e-marker-031.svg
+e-marker-032.svg
+e-marker-033.svg
+e-marker-034.svg
+e-marker-035.svg
+e-marker-036.svg
+e-marker-037.svg
+e-marker-038.svg
+e-marker-039.svg
+e-marker-040.svg
+e-marker-041.svg
+e-marker-042.svg
+e-marker-043.svg
+e-marker-044.svg
+e-marker-045.svg
+e-marker-046.svg
+e-marker-047.svg
+e-marker-048.svg
+e-marker-049.svg
+e-marker-050.svg
+e-marker-051.svg
+e-marker-052.svg
+e-marker-053.svg
+e-marker-054.svg
+e-marker-055.svg
+e-marker-056.svg
diff --git a/testing_tools/regression/allow-qt.txt b/testing_tools/regression/allow-qt.txt
index 77edfdc..2fdfb87 100644
--- a/testing_tools/regression/allow-qt.txt
+++ b/testing_tools/regression/allow-qt.txt
@@ -1,12 +1,62 @@
-a-letter-spacing-001.svg
-a-letter-spacing-002.svg
-a-letter-spacing-003.svg
-a-letter-spacing-004.svg
-a-letter-spacing-005.svg
-a-letter-spacing-006.svg
-a-word-spacing-001.svg
-a-word-spacing-002.svg
-a-word-spacing-003.svg
-a-word-spacing-004.svg
-a-word-spacing-005.svg
-a-word-spacing-006.svg
+a-marker-end-001.svg
+a-marker-mid-001.svg
+a-marker-start-001.svg
+a-overflow-001.svg
+a-overflow-002.svg
+a-overflow-003.svg
+e-marker-001.svg
+e-marker-002.svg
+e-marker-003.svg
+e-marker-004.svg
+e-marker-005.svg
+e-marker-006.svg
+e-marker-007.svg
+e-marker-008.svg
+e-marker-009.svg
+e-marker-010.svg
+e-marker-011.svg
+e-marker-012.svg
+e-marker-013.svg
+e-marker-014.svg
+e-marker-015.svg
+e-marker-016.svg
+e-marker-017.svg
+e-marker-018.svg
+e-marker-019.svg
+e-marker-020.svg
+e-marker-021.svg
+e-marker-022.svg
+e-marker-023.svg
+e-marker-024.svg
+e-marker-025.svg
+e-marker-026.svg
+e-marker-027.svg
+e-marker-028.svg
+e-marker-029.svg
+e-marker-030.svg
+e-marker-031.svg
+e-marker-032.svg
+e-marker-033.svg
+e-marker-034.svg
+e-marker-035.svg
+e-marker-036.svg
+e-marker-037.svg
+e-marker-038.svg
+e-marker-039.svg
+e-marker-040.svg
+e-marker-041.svg
+e-marker-042.svg
+e-marker-043.svg
+e-marker-044.svg
+e-marker-045.svg
+e-marker-046.svg
+e-marker-047.svg
+e-marker-048.svg
+e-marker-049.svg
+e-marker-050.svg
+e-marker-051.svg
+e-marker-052.svg
+e-marker-053.svg
+e-marker-054.svg
+e-marker-055.svg
+e-marker-056.svg
diff --git a/usvg/CHANGELOG.md b/usvg/CHANGELOG.md
index 15b5193..734436a 100644
--- a/usvg/CHANGELOG.md
+++ b/usvg/CHANGELOG.md
@@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
### Added
+- Added marker support.
- Implement `FuzzyEq` for `Rect`, `Size` and `Point`.
- `StrokeMiterlimit` and `FontSize` wrappers for `f64`.
- `letter-spacing` and `word-spacing` support.
@@ -18,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
have an input has it's own `filter_input` field now.
- Rename `filter_input` fields into `input`.
- Filter primitives inputs and results will be resolved now.
+- `Stroke::dashoffset` is `f32` and not `f64` now.
### Fixed
- `offset` attribute resolving inside the `stop` element.
diff --git a/usvg/Cargo.toml b/usvg/Cargo.toml
index 52f5d11..180b983 100644
--- a/usvg/Cargo.toml
+++ b/usvg/Cargo.toml
@@ -18,7 +18,7 @@ libflate = "0.1"
log = "0.4"
lyon_geom = "0.12"
rctree = "0.2.1"
-svgdom = { git = "https://github.com/RazrFalcon/svgdom", rev = "f53af72" }
+svgdom = { git = "https://github.com/RazrFalcon/svgdom", rev = "6ff5579" }
#svgdom = { path = "../../svgdom" }
unicode-segmentation = "1.2.1"
diff --git a/usvg/docs/usvg_spec.adoc b/usvg/docs/usvg_spec.adoc
index 96d80bc..44d7d11 100644
--- a/usvg/docs/usvg_spec.adoc
+++ b/usvg/docs/usvg_spec.adoc
@@ -2,6 +2,7 @@
:toc:
:1H: #
+:star: *
== Elements
@@ -104,7 +105,7 @@ A group can be empty when it has `filter` attribute.
Children: `g`, `path`, `text` and `image`.
-Attributes: `id`, `transform`, `opacity`, `clip-path` and `mask`.
+Attributes: `id`, `transform`, `opacity`, `clip-path`, `mask` and `filter`.
* `id` is optional but never empty.
@@ -116,9 +117,13 @@ MoveTo, LineTo, CurveTo and ClosePath segments.
Attributes: `id`, <<fill_attrs, filling>>, <<stroke_attrs,stroking>>,
`clip-rule` (when inside the `clipPath`), `clip-path` (when inside the `clipPath`),
-`visibility` and `transform`.
+`visibility`, `marker-start`, `marker-mid`, `marker-end` and `transform`.
* `id` is optional but never empty.
+* `marker-{star}` attributes will be set only on paths that were originally
+ `path`, `line`, `polyline` or `polygon`.
+* If a path contains an ArcTo segment and a marker - it will be rendered incorrectly,
+ because `usvg` will convert ArcTo into series of CurveTo's.
=== text
diff --git a/usvg/src/convert/marker.rs b/usvg/src/convert/marker.rs
new file mode 100644
index 0000000..0d3f433
--- /dev/null
+++ b/usvg/src/convert/marker.rs
@@ -0,0 +1,84 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// external
+use svgdom;
+
+// self
+use tree;
+use super::prelude::*;
+
+
+pub fn convert(
+ node: &svgdom::Node,
+ tree: &mut tree::Tree,
+) -> Option<tree::Node> {
+ let ref attrs = node.attributes();
+
+ let rect = convert_rect(attrs);
+ if !rect.is_valid() {
+ warn!("Marker '{}' has an invalid size. Skipped.", node.id());
+ return None;
+ }
+
+ let view_box = node.get_viewbox().map(|vb|
+ tree::ViewBox {
+ rect: vb,
+ aspect: super::convert_aspect(attrs),
+ }
+ );
+
+ Some(tree.append_to_defs(tree::NodeKind::Marker(tree::Marker {
+ id: node.id().clone(),
+ units: convert_units(attrs),
+ rect,
+ view_box,
+ orientation: convert_orientation(attrs),
+ overflow: convert_overflow(attrs),
+ })))
+}
+
+fn convert_rect(attrs: &svgdom::Attributes) -> Rect {
+ (
+ attrs.get_number_or(AId::RefX, 0.0),
+ attrs.get_number_or(AId::RefY, 0.0),
+ attrs.get_number_or(AId::MarkerWidth, 3.0),
+ attrs.get_number_or(AId::MarkerHeight, 3.0),
+ ).into()
+}
+
+fn convert_units(attrs: &svgdom::Attributes) -> tree::MarkerUnits {
+ match attrs.get_str(AId::MarkerUnits) {
+ Some("userSpaceOnUse") => tree::MarkerUnits::UserSpaceOnUse,
+ _ => tree::MarkerUnits::StrokeWidth,
+ }
+}
+
+fn convert_overflow(attrs: &svgdom::Attributes) -> tree::Overflow {
+ match attrs.get_str(AId::Overflow) {
+ Some("visible") => tree::Overflow::Visible,
+ Some("hidden") => tree::Overflow::Hidden,
+ Some("scroll") => tree::Overflow::Scroll,
+ Some("auto") => tree::Overflow::Auto,
+ _ => tree::Overflow::Hidden,
+ }
+}
+
+fn convert_orientation(attrs: &svgdom::Attributes) -> tree::MarkerOrientation {
+ match attrs.get_value(AId::Orient) {
+ Some(AValue::Angle(angle)) => {
+ let a = match angle.unit {
+ svgdom::AngleUnit::Degrees => angle.num,
+ svgdom::AngleUnit::Gradians => angle.num * 180.0 / 200.0,
+ svgdom::AngleUnit::Radians => angle.num * 180.0 / std::f64::consts::PI,
+ };
+
+ tree::MarkerOrientation::Angle(a)
+ }
+ Some(AValue::String(s)) if s == "auto" => {
+ tree::MarkerOrientation::Auto
+ }
+ _ => tree::MarkerOrientation::Angle(0.0),
+ }
+}
diff --git a/usvg/src/convert/mod.rs b/usvg/src/convert/mod.rs
index c37ba8e..5a07eba 100644
--- a/usvg/src/convert/mod.rs
+++ b/usvg/src/convert/mod.rs
@@ -33,6 +33,7 @@ mod fill;
mod filter;
mod gradient;
mod image;
+mod marker;
mod mask;
mod path;
mod pattern;
@@ -154,6 +155,11 @@ fn convert_ref_nodes(
later_nodes.push((node, new_node));
}
}
+ EId::Marker => {
+ if let Some(new_node) = marker::convert(&node, tree) {
+ later_nodes.push((node, new_node));
+ }
+ }
EId::Filter => {
filter::convert(&node, opt, tree);
}
@@ -185,6 +191,13 @@ fn convert_ref_nodes(
warn!("Mask '{}' has no children. Skipped.", node.id());
new_node.detach();
}
+ } else if node.is_tag_name(EId::Marker) {
+ convert_nodes(&node, &mut new_node, opt, tree);
+
+ if !new_node.has_children() {
+ warn!("Marker '{}' has no children. Skipped.", node.id());
+ new_node.detach();
+ }
} else if node.is_tag_name(EId::Pattern) {
convert_nodes(&node, &mut new_node.clone(), opt, tree);
diff --git a/usvg/src/convert/path.rs b/usvg/src/convert/path.rs
index 6104db3..4841fd8 100644
--- a/usvg/src/convert/path.rs
+++ b/usvg/src/convert/path.rs
@@ -36,24 +36,26 @@ pub fn convert(
let transform = attrs.get_transform(AId::Transform).unwrap_or_default();
let mut visibility = super::convert_visibility(&attrs);
- // Shapes without a bbox cannot be filled,
- // and if there is no stroke than there is nothing to render.
- if !has_bbox && stroke.is_none() {
- return;
- }
-
// If a path doesn't have a fill or a stroke than it's invisible.
// By setting `visibility` to `hidden` we are disabling the rendering of this path.
if fill.is_none() && stroke.is_none() {
visibility = tree::Visibility::Hidden
}
+ let marker = Box::new(tree::PathMarker {
+ start: conv_marker(AId::MarkerStart, node, tree),
+ mid: conv_marker(AId::MarkerMid, node, tree),
+ end: conv_marker(AId::MarkerEnd, node, tree),
+ stroke: conv_stroke_width(&attrs),
+ });
+
parent.append_kind(tree::NodeKind::Path(tree::Path {
id: node.id().clone(),
transform,
visibility,
fill,
stroke,
+ marker,
segments: d,
}));
}
@@ -303,3 +305,30 @@ fn has_bbox(segments: &[tree::PathSegment]) -> bool {
false
}
+
+fn conv_marker(
+ aid: AId,
+ node: &svgdom::Node,
+ tree: &tree::Tree,
+) -> Option<String> {
+ let attrs = node.attributes();
+ if let Some(&AValue::FuncLink(ref link)) = attrs.get_type(aid) {
+ if link.is_tag_name(EId::Marker) {
+ if let Some(node) = tree.defs_by_id(&link.id()) {
+ return Some(node.id().to_string());
+ }
+ }
+ }
+
+ None
+}
+
+fn conv_stroke_width(attrs: &svgdom::Attributes) -> Option<tree::StrokeWidth> {
+ let width = attrs.get_number_or(AId::StrokeWidth, 1.0);
+
+ if !(width > 0.0) {
+ return None;
+ }
+
+ Some(tree::StrokeWidth::new(width))
+}
diff --git a/usvg/src/convert/stroke.rs b/usvg/src/convert/stroke.rs
index eb6dc98..12b41ab 100644
--- a/usvg/src/convert/stroke.rs
+++ b/usvg/src/convert/stroke.rs
@@ -15,7 +15,7 @@ pub fn convert(
attrs: &svgdom::Attributes,
has_bbox: bool,
) -> Option<tree::Stroke> {
- let dashoffset = attrs.get_number_or(AId::StrokeDashoffset, 0.0);
+ let dashoffset = attrs.get_number_or(AId::StrokeDashoffset, 0.0) as f32;
let miterlimit = attrs.get_number_or(AId::StrokeMiterlimit, 4.0);
let opacity = attrs.get_number_or(AId::StrokeOpacity, 1.0).into();
let width = attrs.get_number_or(AId::StrokeWidth, 1.0);
diff --git a/usvg/src/geom.rs b/usvg/src/geom.rs
index 6ef4b6f..a0ecdbc 100644
--- a/usvg/src/geom.rs
+++ b/usvg/src/geom.rs
@@ -192,7 +192,7 @@ impl Rect {
self.y + self.height
}
- /// Checks that rect contains a point.
+ /// Checks that the rect contains a point.
pub fn contains(&self, p: Point) -> bool {
if p.x < self.x || p.x > self.x + self.width - 1.0 {
return false;
@@ -204,6 +204,11 @@ impl Rect {
true
}
+
+ /// Checks that the rect has a valid size.
+ pub fn is_valid(&self) -> bool {
+ self.width > 0.0 && self.height > 0.0
+ }
}
impl FuzzyEq for Rect {
diff --git a/usvg/src/preproc/fix_recursive_links.rs b/usvg/src/preproc/fix_recursive_links.rs
index c78319b..a86063e 100644
--- a/usvg/src/preproc/fix_recursive_links.rs
+++ b/usvg/src/preproc/fix_recursive_links.rs
@@ -7,6 +7,7 @@ use super::prelude::*;
pub fn fix_recursive_links(doc: &Document) {
fix_pattern(doc);
+ fix_marker(doc);
fix_func_iri(doc, EId::ClipPath, AId::ClipPath);
fix_func_iri(doc, EId::Mask, AId::Mask);
fix_func_iri(doc, EId::Filter, AId::Filter);
@@ -43,6 +44,38 @@ fn fix_pattern(doc: &Document) {
}
}
+fn fix_marker(doc: &Document) {
+ for marker_node in doc.root().descendants().filter(|n| n.is_tag_name(EId::Marker)) {
+ for mut node in marker_node.descendants() {
+ let mut check_attr = |aid: AId| {
+ let av = node.attributes().get_value(aid).cloned();
+ if let Some(AValue::FuncLink(link)) = av {
+ if link == marker_node {
+ // If a marker child has a link to the marker itself
+ // then we have to remove it.
+ // Otherwise we will get endless loop/recursion and stack overflow.
+ node.remove_attribute(aid);
+ } else {
+ // Check that linked node children doesn't link this marker.
+ for node2 in link.descendants() {
+ let av2 = node2.attributes().get_value(aid).cloned();
+ if let Some(AValue::FuncLink(link2)) = av2 {
+ if link2 == marker_node {
+ node.remove_attribute(aid);
+ }
+ }
+ }
+ }
+ }
+ };
+
+ check_attr(AId::MarkerStart);
+ check_attr(AId::MarkerMid);
+ check_attr(AId::MarkerEnd);
+ }
+ }
+}
+
fn fix_func_iri(doc: &Document, eid: EId, aid: AId) {
for node in doc.root().descendants().filter(|n| n.is_tag_name(eid)) {
for mut child in node.descendants() {
diff --git a/usvg/src/preproc/mod.rs b/usvg/src/preproc/mod.rs
index 7757d76..e43e103 100644
--- a/usvg/src/preproc/mod.rs
+++ b/usvg/src/preproc/mod.rs
@@ -42,6 +42,7 @@ mod rm_non_svg_data;
mod rm_unused_defs;
mod ungroup_a;
mod ungroup_groups;
+mod prepare_marker;
use self::conv_units::*;
@@ -74,6 +75,7 @@ use self::rm_non_svg_data::*;
use self::rm_unused_defs::*;
use self::ungroup_a::*;
use self::ungroup_groups::*;
+use self::prepare_marker::*;
mod prelude {
@@ -178,6 +180,8 @@ pub fn prepare_doc(doc: &mut svgdom::Document, opt: &Options) {
prepare_text_decoration(doc);
resolve_style_attributes(doc, opt);
+ rm_marker_attributes(doc);
+
// Should be done only after style resolving.
remove_invalid_gradients(doc);
diff --git a/usvg/src/preproc/prepare_marker.rs b/usvg/src/preproc/prepare_marker.rs
new file mode 100644
index 0000000..1278919
--- /dev/null
+++ b/usvg/src/preproc/prepare_marker.rs
@@ -0,0 +1,35 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use super::prelude::*;
+
+
+/// Removes marker attributes from unsupported elements.
+///
+/// `marker-*` attributes can only be set on `path`, `line`, `polyline` and `polygon`.
+///
+/// Also, `marker-*` attributes cannot be set on shapes inside the `clipPath`.
+pub fn rm_marker_attributes(doc: &Document) {
+ for mut node in doc.root().descendants() {
+ let is_valid_elem =
+ node.is_tag_name(EId::Path)
+ || node.is_tag_name(EId::Line)
+ || node.is_tag_name(EId::Polyline)
+ || node.is_tag_name(EId::Polygon);
+
+ if !is_valid_elem {
+ node.remove_attribute(AId::MarkerStart);
+ node.remove_attribute(AId::MarkerMid);
+ node.remove_attribute(AId::MarkerEnd);
+ }
+ }
+
+ for node in doc.root().descendants().filter(|n| n.is_tag_name(EId::ClipPath)) {
+ for mut child in node.descendants() {
+ child.remove_attribute(AId::MarkerStart);
+ child.remove_attribute(AId::MarkerMid);
+ child.remove_attribute(AId::MarkerEnd);
+ }
+ }
+}
diff --git a/usvg/src/preproc/resolve_style_attrs.rs b/usvg/src/preproc/resolve_style_attrs.rs
index d1bbac8..7ff0e10 100644
--- a/usvg/src/preproc/resolve_style_attrs.rs
+++ b/usvg/src/preproc/resolve_style_attrs.rs
@@ -38,6 +38,12 @@ fn resolve_inherit(parent: &Node, opt: &Options) {
resolve(&mut node, AId::StrokeWidth);
}
+ if node.is_shape() {
+ resolve(&mut node, AId::MarkerStart);
+ resolve(&mut node, AId::MarkerMid);
+ resolve(&mut node, AId::MarkerEnd);
+ }
+
if node.is_graphic() && node.parent().unwrap().is_tag_name(EId::ClipPath) {
resolve(&mut node, AId::ClipRule);
}
diff --git a/usvg/src/tree/attributes.rs b/usvg/src/tree/attributes.rs
index c38971c..516bc76 100644
--- a/usvg/src/tree/attributes.rs
+++ b/usvg/src/tree/attributes.rs
@@ -57,8 +57,6 @@ pub enum FillRule {
/// An element units.
-///
-/// `*Units` attribute in the SVG.
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Units {
@@ -67,6 +65,26 @@ pub enum Units {
}
+/// A marker units.
+#[allow(missing_docs)]
+#[derive(Clone, Copy, PartialEq, Debug)]
+pub enum MarkerUnits {
+ StrokeWidth,
+ UserSpaceOnUse,
+}
+
+
+/// A marker orientation.
+#[derive(Clone, Copy, Debug)]
+pub enum MarkerOrientation {
+ /// Requires an automatic rotation.
+ Auto,
+
+ /// A rotation angle in degrees.
+ Angle(f64),
+}
+
+
/// A spread method.
///
/// `spreadMethod` attribute in the SVG.
@@ -91,6 +109,30 @@ pub enum Visibility {
}
+/// An overflow property.
+///
+/// `overflow` attribute in the SVG.
+#[allow(missing_docs)]
+#[derive(Clone, Copy, PartialEq, Debug)]
+pub enum Overflow {
+ Visible,
+ Hidden,
+ Scroll,
+ Auto,
+}
+
+impl ToString for Overflow {
+ fn to_string(&self) -> String {
+ match self {
+ Overflow::Visible => "visible",
+ Overflow::Hidden => "hidden",
+ Overflow::Scroll => "scroll",
+ Overflow::Auto => "auto",
+ }.to_string()
+ }
+}
+
+
/// A text decoration style.
///
/// Defines the style of the line that should be rendered.
@@ -263,7 +305,7 @@ impl Default for Fill {
pub struct Stroke {
pub paint: Paint,
pub dasharray: Option<NumberList>,
- pub dashoffset: f64,
+ pub dashoffset: f32,
pub miterlimit: StrokeMiterlimit,
pub opacity: Opacity,
pub width: StrokeWidth,
@@ -327,8 +369,8 @@ pub struct ViewBox {
/// A path absolute segment.
///
-/// Unlike the SVG spec can contain only `M`, `L`, `C` and `Z` segments.
-/// All other segments will be converted to this one.
+/// Unlike the SVG spec, can contain only `M`, `L`, `C` and `Z` segments.
+/// All other segments will be converted into this one.
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)]
pub enum PathSegment {
@@ -510,3 +552,31 @@ pub enum BaselineShift {
Percent(f64),
Number(f64),
}
+
+
+/// A path marker properties.
+#[derive(Clone, Debug)]
+pub struct PathMarker {
+ /// Start marker.
+ ///
+ /// `marker-start` in SVG.
+ pub start: Option<String>,
+
+ /// Middle marker
+ ///
+ /// `marker-mid` in SVG.
+ pub mid: Option<String>,
+
+ /// End marker
+ ///
+ /// `marker-end` in SVG.
+ pub end: Option<String>,
+
+ /// Marker stroke.
+ ///
+ /// This value contains a copy of the `stroke-width` value.
+ /// `usvg` will set `Path::stroke` to `None` if a path doesn't have a stroke,
+ /// but marker rendering still relies on the `stroke-width` value, even when `stroke=none`.
+ /// So we have to store it separately.
+ pub stroke: Option<StrokeWidth>,
+}
diff --git a/usvg/src/tree/convert.rs b/usvg/src/tree/convert.rs
index 9de1431..28e13c8 100644
--- a/usvg/src/tree/convert.rs
+++ b/usvg/src/tree/convert.rs
@@ -123,6 +123,38 @@ fn conv_defs(
conv_transform(AId::PatternTransform, &pattern.transform, &mut pattern_elem);
later_nodes.push((n.clone(), pattern_elem.clone()));
}
+ NodeKind::Marker(ref marker) => {
+ let mut marker_elem = new_doc.create_element(EId::Marker);
+ defs.append(marker_elem.clone());
+
+ marker_elem.set_id(marker.id.clone());
+
+ marker_elem.set_attribute((AId::MarkerUnits,
+ match marker.units {
+ MarkerUnits::UserSpaceOnUse => "userSpaceOnUse",
+ MarkerUnits::StrokeWidth => "strokeWidth",
+ }
+ ));
+
+ marker_elem.set_attribute((AId::RefX, marker.rect.x));
+ marker_elem.set_attribute((AId::RefY, marker.rect.y));
+ marker_elem.set_attribute((AId::MarkerWidth, marker.rect.width));
+ marker_elem.set_attribute((AId::MarkerHeight, marker.rect.height));
+
+ if let Some(vbox) = marker.view_box {
+ conv_viewbox(&vbox, &mut marker_elem);
+ }
+
+ let orientation: AValue = match marker.orientation {
+ MarkerOrientation::Auto => "auto".into(),
+ MarkerOrientation::Angle(a) => a.into(),
+ };
+ marker_elem.set_attribute((AId::Orient, orientation));
+
+ marker_elem.set_attribute((AId::Overflow, marker.overflow.to_string()));
+
+ later_nodes.push((n.clone(), marker_elem.clone()));
+ }
NodeKind::Filter(ref filter) => {
let mut filter_elem = new_doc.create_element(EId::Filter);
defs.append(filter_elem.clone());
@@ -302,6 +334,31 @@ fn conv_elements(
conv_fill(tree, &p.fill, defs, parent, &mut path_elem);
conv_stroke(tree, &p.stroke, defs, &mut path_elem);
+
+ if let Some(ref id) = p.marker.start {
+ conv_link(tree, defs, AId::MarkerStart, id, &mut path_elem);
+ }
+
+ if let Some(ref id) = p.marker.mid {
+ conv_link(tree, defs, AId::MarkerMid, id, &mut path_elem);
+ }
+
+ if let Some(ref id) = p.marker.end {
+ conv_link(tree, defs, AId::MarkerEnd, id, &mut path_elem);
+ }
+
+ // Set `stroke-width` if path has a marker.
+ // Even if `stroke` is not set, the `stroke-width` attribute
+ // will still affect the marker rendering.
+ let has_marker = p.marker.start.is_some()
+ || p.marker.mid.is_some()
+ || p.marker.end.is_some();
+
+ if p.stroke.is_none() && has_marker {
+ if let Some(sw) = p.marker.stroke {
+ path_elem.set_attribute((AId::StrokeWidth, sw.value()));
+ }
+ }
}
NodeKind::Text(ref text) => {
let mut text_elem = new_doc.create_element(EId::Text);
@@ -503,7 +560,7 @@ fn conv_stroke(
}
node.set_attribute((AId::StrokeOpacity, stroke.opacity.value()));
- node.set_attribute((AId::StrokeDashoffset, stroke.dashoffset));
+ node.set_attribute((AId::StrokeDashoffset, stroke.dashoffset as f64));
node.set_attribute((AId::StrokeMiterlimit, stroke.miterlimit.value()));
node.set_attribute((AId::StrokeWidth, stroke.width.value()));
diff --git a/usvg/src/tree/nodes.rs b/usvg/src/tree/nodes.rs
index 8f6e4ac..d4f8af9 100644
--- a/usvg/src/tree/nodes.rs
+++ b/usvg/src/tree/nodes.rs
@@ -20,6 +20,7 @@ pub enum NodeKind {
ClipPath(ClipPath),
Mask(Mask),
Pattern(Pattern),
+ Marker(Marker),
Filter(Filter),
Path(Path),
Text(Text),
@@ -41,6 +42,7 @@ impl NodeKind {
NodeKind::ClipPath(ref e) => e.id.as_str(),
NodeKind::Mask(ref e) => e.id.as_str(),
NodeKind::Pattern(ref e) => e.id.as_str(),
+ NodeKind::Marker(ref e) => e.id.as_str(),
NodeKind::Filter(ref e) => e.id.as_str(),
NodeKind::Path(ref e) => e.id.as_str(),
NodeKind::Text(ref e) => e.id.as_str(),
@@ -62,6 +64,7 @@ impl NodeKind {
NodeKind::ClipPath(ref e) => e.transform,
NodeKind::Mask(_) => Transform::default(),
NodeKind::Pattern(ref e) => e.transform,
+ NodeKind::Marker(_) => Transform::default(),
NodeKind::Filter(_) => Transform::default(),
NodeKind::Path(ref e) => e.transform,
NodeKind::Text(ref e) => e.transform,
@@ -113,6 +116,9 @@ pub struct Path {
/// Stroke style.
pub stroke: Option<Stroke>,
+ /// Marker.
+ pub marker: Box<PathMarker>,
+
/// Segments list.
///
/// All segments are in absolute coordinates.
@@ -485,6 +491,42 @@ pub struct Pattern {
}
+/// A marker element.
+///
+/// `marker` element in SVG.
+#[derive(Clone, Debug)]
+pub struct Marker {
+ /// Element's ID.
+ ///
+ /// Taken from the SVG itself.
+ /// Can't be empty.
+ pub id: String,
+
+ /// Coordinate system units.
+ ///
+ /// `markerUnits` in SVG.
+ pub units: MarkerUnits,
+
+ /// Marker rectangle.
+ ///
+ /// `refX`, `refY`, `markerWidth` and `markerHeight` in SVG.
+ pub rect: Rect,
+
+ /// Marker viewbox.
+ pub view_box: Option<ViewBox>,
+
+ /// Marker orientation.
+ ///
+ /// `orient` in SVG.
+ pub orientation: MarkerOrientation,
+
+ /// Marker overflow.
+ ///
+ /// `overflow` in SVG.
+ pub overflow: Overflow,
+}
+
+
/// A filter element.
///
/// `filter` element in the SVG.
diff --git a/usvg/testing_tools/allow.csv b/usvg/testing_tools/allow.csv
index 677e80b..1869858 100644
--- a/usvg/testing_tools/allow.csv
+++ b/usvg/testing_tools/allow.csv
@@ -20,6 +20,8 @@ a-letter-spacing-005,2171,chrome bug
a-word-spacing-005,730,chrome bug
a-opacity-001,580
a-opacity-002,137
+a-overflow-001,299
+a-overflow-003,299
a-stroke-dasharray-012,244
a-stroke-width-004,960,chrome bug
a-systemLanguage-001,25604,chrome bug
@@ -86,6 +88,9 @@ e-image-028,22690,bug #13
e-image-029,24795,bug #13
e-image-030,18864,bug #13
e-image-031,18864,bug #13
+e-marker-015,846
+e-marker-030,704
+e-marker-035,1155,bug
e-mask-015,38
e-mask-017,22344,bug #14
e-mask-020,12804,bug
diff --git a/usvg/testing_tools/cache.csv b/usvg/testing_tools/cache.csv
index 3deb23d..2681c2b 100644
--- a/usvg/testing_tools/cache.csv
+++ b/usvg/testing_tools/cache.csv
@@ -126,12 +126,12 @@ a-stroke-006,cdb0324b
a-stroke-007,35ba0648
a-stroke-008,bf48479d
a-stroke-009,e9fc55d3
-a-stroke-010,22e1c426
+a-stroke-010,765346d4
a-stroke-011,3e34be29
-a-stroke-012,01e50923
+a-stroke-012,4a0cc7cb
a-stroke-013,1e292886
-a-stroke-014,aa0f5699
-a-stroke-015,aa0f5699
+a-stroke-014,7b219da8
+a-stroke-015,47644d90
a-stroke-dasharray-001,6f36c050
a-stroke-dasharray-002,bd2f3486
a-stroke-dasharray-003,bd2f3486
@@ -146,7 +146,7 @@ a-stroke-dasharray-011,c0ffdc35
a-stroke-dasharray-012,018df324
a-stroke-dashoffset-001,bd2f3486
a-stroke-dashoffset-002,21be0853
-a-stroke-dashoffset-003,109e1fc8
+a-stroke-dashoffset-003,52db6329
a-stroke-dashoffset-004,dbce2c47
a-stroke-dashoffset-005,2365f1b2
a-stroke-dashoffset-006,64093725
@@ -546,12 +546,12 @@ e-path-030,19efd46c
e-path-031,2cb8c8b6
e-path-032,7f149c64
e-path-033,405d6486
-e-path-034,2739396f
-e-path-035,84a79cbb
+e-path-034,ef582b10
+e-path-035,fd0d0d04
e-path-036,bde418bf
-e-path-037,050496f5
-e-path-038,a8d5218b
-e-path-039,050496f5
+e-path-037,46015de7
+e-path-038,daacdeed
+e-path-039,46015de7
e-path-040,69b6f2ed
e-path-041,69b6f2ed
e-path-042,6fa3fd25
@@ -565,9 +565,9 @@ e-pattern-005,c7498dda
e-pattern-006,030f0964
e-pattern-007,9f4e924c
e-pattern-008,fae06cc3
-e-pattern-009,6c78ecb7
-e-pattern-010,494eec45
-e-pattern-011,f52b6448
+e-pattern-009,e5617a38
+e-pattern-010,ca8d710d
+e-pattern-011,938e478d
e-pattern-012,ed4e93e6
e-pattern-013,ed4e93e6
e-pattern-014,745d0dd7
@@ -864,3 +864,66 @@ a-word-spacing-003,b12e5267
a-word-spacing-004,7ffef6bd
a-word-spacing-005,7c3f6f89
a-word-spacing-006,ec89c95f
+a-marker-end-001,83157013
+a-marker-mid-001,b0968f0f
+a-marker-start-001,aaf419ba
+a-overflow-001,fcb11a00
+a-overflow-002,2e6595da
+a-overflow-003,637a1b8b
+e-clipPath-038,bcf9789e
+e-marker-001,685c8215
+e-marker-002,ac86ce31
+e-marker-003,2602ed72
+e-marker-004,ac86ce31
+e-marker-005,139b75d6
+e-marker-006,6a958e47
+e-marker-007,650df3d2
+e-marker-008,4bc7d273
+e-marker-009,972c821f
+e-marker-010,1eae734a
+e-marker-011,8e98192b
+e-marker-012,4cb167b4
+e-marker-013,e528c1e3
+e-marker-014,0c04cc45
+e-marker-015,a8afae9f
+e-marker-016,b8249769
+e-marker-017,559cc288
+e-marker-018,a11e3b08
+e-marker-019,1aed08b9
+e-marker-020,dc2a311c
+e-marker-021,dc2a311c
+e-marker-022,a7ec8a6e
+e-marker-023,8e98192b
+e-marker-024,8e98192b
+e-marker-025,8e98192b
+e-marker-026,8e98192b
+e-marker-027,8e98192b
+e-marker-028,dc2a311c
+e-marker-029,dc2a311c
+e-marker-030,ac427a97
+e-marker-031,178b4f7f
+e-marker-032,ec96b09d
+e-marker-033,08ee7de2
+e-marker-034,e59f062d
+e-marker-035,bfdd5d9d
+e-marker-036,15c61b20
+e-marker-037,1e5caea2
+e-marker-038,ee9b5611
+e-marker-039,81d1c0f6
+e-marker-040,35629b1d
+e-marker-041,bc3ebb35
+e-marker-042,74464750
+e-marker-043,020752cd
+e-marker-044,bb3232f1
+e-marker-045,bf6a8252
+e-marker-046,afe1f6c5
+e-marker-047,4aac76a6
+e-marker-048,4085e7c8
+e-marker-049,d99e4f75
+e-marker-050,ec050c14
+e-marker-051,5ddafdc5
+e-marker-052,d4f6f60b
+e-marker-053,83702e9d
+e-marker-054,fda46a82
+e-marker-055,d7c0f54a
+e-marker-056,b8f5cf2e