Skip to content

Commit

Permalink
Fix absolute transform propagation during use resolving.
Browse files Browse the repository at this point in the history
Closes #722
  • Loading branch information
YevheniiReizner committed Mar 31, 2024
1 parent 6bae69b commit 09338a1
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 4 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
This changelog also contains important changes in dependencies.

## [Unreleased]
### Fixed
- Absolute transform propagation during `use` resolving.
- `Node::abs_transform` documentation. The current element's transform _is_ included.

## [0.40.0] - 2024-02-17
### Added
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions crates/usvg/src/parser/use_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ fn convert_children(
is_context_element: bool,
parent: &mut Group,
) {
// Temporarily adjust absolute transform so `convert_group` would account for `transform`.
let old_abs_transform = parent.abs_transform;
parent.abs_transform = parent.abs_transform.pre_concat(transform);

let required = !transform.is_identity();
if let Some(mut g) =
converter::convert_group(node, state, required, cache, parent, &|cache, g| {
Expand All @@ -260,6 +264,8 @@ fn convert_children(
g.transform = transform;
parent.children.push(Node::Group(Box::new(g)));
}

parent.abs_transform = old_abs_transform;
}

fn get_clip_rect(
Expand Down
6 changes: 3 additions & 3 deletions crates/usvg/src/tree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1050,7 +1050,7 @@ impl Group {

/// Element's absolute transform.
///
/// Contains all ancestors transforms excluding element's transform.
/// Contains all ancestors transforms including group's transform.
///
/// Note that subroots, like clipPaths, masks and patterns, have their own root transform,
/// which isn't affected by the node that references this subroot.
Expand Down Expand Up @@ -1359,7 +1359,7 @@ impl Path {

/// Element's absolute transform.
///
/// Contains all ancestors transforms excluding element's transform.
/// Contains all ancestors transforms including elements's transform.
///
/// Note that this is not the relative transform present in SVG.
/// The SVG one would be set only on groups.
Expand Down Expand Up @@ -1495,7 +1495,7 @@ impl Image {

/// Element's absolute transform.
///
/// Contains all ancestors transforms excluding element's transform.
/// Contains all ancestors transforms including elements's transform.
///
/// Note that this is not the relative transform present in SVG.
/// The SVG one would be set only on groups.
Expand Down
2 changes: 1 addition & 1 deletion crates/usvg/src/tree/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ impl Text {

/// Element's absolute transform.
///
/// Contains all ancestors transforms excluding element's transform.
/// Contains all ancestors transforms including elements's transform.
///
/// Note that this is not the relative transform present in SVG.
/// The SVG one would be set only on groups.
Expand Down
112 changes: 112 additions & 0 deletions crates/usvg/tests/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,115 @@ fn tree_is_send_and_sync() {
fn ensure_send_and_sync<T: Send + Sync>() {}
ensure_send_and_sync::<usvg::Tree>();
}

#[test]
fn path_transform() {
let svg = "
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'>
<path transform='translate(10)' d='M 0 0 L 10 10'/>
</svg>
";

let fontdb = usvg::fontdb::Database::new();
let tree = usvg::Tree::from_str(&svg, &usvg::Options::default(), &fontdb).unwrap();
assert_eq!(tree.root().children().len(), 1);

let group_node = &tree.root().children()[0];
assert!(matches!(group_node, usvg::Node::Group(_)));
assert_eq!(group_node.abs_transform(), usvg::Transform::from_translate(10.0, 0.0));

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

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

#[test]
fn path_transform_nested() {
let svg = "
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'>
<g transform='translate(20)'>
<path transform='translate(10)' d='M 0 0 L 10 10'/>
</g>
</svg>
";

let fontdb = usvg::fontdb::Database::new();
let tree = usvg::Tree::from_str(&svg, &usvg::Options::default(), &fontdb).unwrap();
assert_eq!(tree.root().children().len(), 1);

let group_node1 = &tree.root().children()[0];
assert!(matches!(group_node1, usvg::Node::Group(_)));
assert_eq!(group_node1.abs_transform(), usvg::Transform::from_translate(20.0, 0.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.abs_transform(), usvg::Transform::from_translate(30.0, 0.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(30.0, 0.0));
}

#[test]
fn path_transform_in_symbol() {
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='visible'>
<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">
// <g id="use1">
// <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>
// </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::from_translate(20.0, 0.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(20.0, 0.0));
}

0 comments on commit 09338a1

Please sign in to comment.