Skip to content

Commit

Permalink
Fix absolute transform propagation during nested svg resolving.
Browse files Browse the repository at this point in the history
Closes #724
  • Loading branch information
RazrFalcon committed Apr 3, 2024
1 parent c6a4129 commit bf476d7
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This changelog also contains important changes in dependencies.
## [Unreleased]
### Fixed
- Absolute transform propagation during `use` resolving.
- Absolute transform propagation during nested `svg` resolving.
- `Node::abs_transform` documentation. The current element's transform _is_ included.

## [0.40.0] - 2024-02-17
Expand Down
2 changes: 2 additions & 0 deletions crates/usvg/src/parser/use_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ pub(crate) fn convert(

if let Some(clip_rect) = get_clip_rect(node, child, &use_state) {
let mut g = clip_element(node, clip_rect, orig_ts, &use_state, cache);
g.abs_transform = parent.abs_transform;

// Make group for `use`.
if let Some(mut g2) =
Expand Down Expand Up @@ -178,6 +179,7 @@ pub(crate) fn convert_svg(

if let Some(clip_rect) = get_clip_rect(node, node, state) {
let mut g = clip_element(node, clip_rect, orig_ts, state, cache);
g.abs_transform = parent.abs_transform;
convert_children(node, new_ts, &new_state, cache, false, &mut g);
g.calculate_bounding_boxes();
parent.children.push(Node::Group(Box::new(g)));
Expand Down
121 changes: 120 additions & 1 deletion crates/usvg/tests/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ fn path_transform_nested() {
}

#[test]
fn path_transform_in_symbol() {
fn path_transform_in_symbol_no_clip() {
let svg = "
<svg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>
<defs>
Expand Down Expand Up @@ -210,3 +210,122 @@ fn path_transform_in_symbol() {
assert!(matches!(path, usvg::Node::Path(_)));
assert_eq!(path.abs_transform(), usvg::Transform::from_translate(20.0, 0.0));
}

#[test]
fn path_transform_in_symbol_with_clip() {
let svg = "
<svg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>
<defs>
<symbol id='symbol1' overflow='hidden'>
<rect id='rect1' x='0' y='0' width='10' height='10'/>
</symbol>
</defs>
<use id='use1' xlink:href='#symbol1' x='20'/>
</svg>
";

// Will be parsed as:
// <svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
// <defs>
// <clipPath id="clipPath1">
// <path fill="#000000" stroke="none" d="M 20 0 L 120 0 L 120 100 L 20 100 Z"/>
// </clipPath>
// </defs>
// <g id="use1" clip-path="url(#clipPath1)">
// <g>
// <g transform="matrix(1 0 0 1 20 0)">
// <path fill="#000000" stroke="none" d="M 0 0 L 10 0 L 10 10 L 0 10 Z"/>
// </g>
// </g>
// </g>
// </svg>

let fontdb = usvg::fontdb::Database::new();
let tree = usvg::Tree::from_str(&svg, &usvg::Options::default(), &fontdb).unwrap();

let group_node1 = &tree.root().children()[0];
assert!(matches!(group_node1, usvg::Node::Group(_)));
assert_eq!(group_node1.id(), "use1");
assert_eq!(group_node1.abs_transform(), usvg::Transform::default());

let group1 = match group_node1 {
usvg::Node::Group(ref g) => g,
_ => unreachable!(),
};

let group_node2 = &group1.children()[0];
assert!(matches!(group_node2, usvg::Node::Group(_)));
assert_eq!(group_node2.abs_transform(), usvg::Transform::default());

let group2 = match group_node2 {
usvg::Node::Group(ref g) => g,
_ => unreachable!(),
};

let group_node3 = &group2.children()[0];
assert!(matches!(group_node3, usvg::Node::Group(_)));
assert_eq!(group_node3.abs_transform(), usvg::Transform::from_translate(20.0, 0.0));

let group3 = match group_node3 {
usvg::Node::Group(ref g) => g,
_ => unreachable!(),
};

let path = &group3.children()[0];
assert!(matches!(path, usvg::Node::Path(_)));
assert_eq!(path.abs_transform(), usvg::Transform::from_translate(20.0, 0.0));
}

#[test]
fn path_transform_in_svg() {
let svg = "
<svg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>
<g id='g1' transform='translate(100 150)'>
<svg id='svg1' width='100' height='50'>
<rect id='rect1' width='10' height='10'/>
</svg>
</g>
</svg>
";

// Will be parsed as:
// <svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
// <defs>
// <clipPath id="clipPath1">
// <path fill="#000000" stroke="none" d="M 0 0 L 100 0 L 100 50 L 0 50 Z"/>
// </clipPath>
// </defs>
// <g id="g1" transform="matrix(1 0 0 1 100 150)">
// <g id="svg1" clip-path="url(#clipPath1)">
// <path id="rect1" fill="#000000" stroke="none" d="M 0 0 L 10 0 L 10 10 L 0 10 Z"/>
// </g>
// </g>
// </svg>

let fontdb = usvg::fontdb::Database::new();
let tree = usvg::Tree::from_str(&svg, &usvg::Options::default(), &fontdb).unwrap();

let group_node1 = &tree.root().children()[0];
assert!(matches!(group_node1, usvg::Node::Group(_)));
assert_eq!(group_node1.id(), "g1");
assert_eq!(group_node1.abs_transform(), usvg::Transform::from_translate(100.0, 150.0));

let group1 = match group_node1 {
usvg::Node::Group(ref g) => g,
_ => unreachable!(),
};

let group_node2 = &group1.children()[0];
assert!(matches!(group_node2, usvg::Node::Group(_)));
assert_eq!(group_node2.id(), "svg1");
assert_eq!(group_node2.abs_transform(), usvg::Transform::from_translate(100.0, 150.0));

let group2 = match group_node2 {
usvg::Node::Group(ref g) => g,
_ => unreachable!(),
};

let path = &group2.children()[0];
assert!(matches!(path, usvg::Node::Path(_)));
assert_eq!(path.abs_transform(), usvg::Transform::from_translate(100.0, 150.0));
}

0 comments on commit bf476d7

Please sign in to comment.