From 6eb5cf2ecde2646e26798afe6976564ea3261f61 Mon Sep 17 00:00:00 2001 From: Yevhenii Reizner Date: Sun, 3 Dec 2023 18:32:03 +0200 Subject: [PATCH] Optimize `usvg_tree::NodeExt::abs_transform`. --- CHANGELOG.md | 2 + crates/usvg-parser/src/converter.rs | 2 + crates/usvg-text-layout/src/lib.rs | 1 + crates/usvg-tree/src/lib.rs | 67 +++++++++++++++++++++-------- 4 files changed, 55 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 285a9e95c..d3544a595 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ This changelog also contains important changes in dependencies. ### Added - `usvg_tree::Text::flattened` that will contain an flattened/outlined text. - `usvg_tree::Text::bounding_box`. Will be set only after text flattening. +- Optimize `usvg_tree::NodeExt::abs_transform` by storing absolute transforms in the tree + instead of calculating them each time. ### Changed - `usvg_tree::Text::positions` was replaced with `usvg_tree::Text::dx` and `usvg_tree::Text::dy`.
diff --git a/crates/usvg-parser/src/converter.rs b/crates/usvg-parser/src/converter.rs index b5a85b171..7fc503d1b 100644 --- a/crates/usvg-parser/src/converter.rs +++ b/crates/usvg-parser/src/converter.rs @@ -176,6 +176,7 @@ pub(crate) fn convert_doc(svg_doc: &svgtree::Document, opt: &Options) -> Result< convert_children(svg_doc.root(), &state, &mut cache, &mut tree.root); remove_empty_groups(&mut tree); + tree.calculate_abs_transforms(); if restore_viewbox { calculate_svg_bbox(&mut tree); @@ -541,6 +542,7 @@ pub(crate) fn convert_group( let g = parent.append_kind(NodeKind::Group(Group { id, transform, + abs_transform: Transform::identity(), opacity, blend_mode, isolate, diff --git a/crates/usvg-text-layout/src/lib.rs b/crates/usvg-text-layout/src/lib.rs index fd5278dba..e991e7e21 100644 --- a/crates/usvg-text-layout/src/lib.rs +++ b/crates/usvg-text-layout/src/lib.rs @@ -44,6 +44,7 @@ pub trait TreeTextToPath { impl TreeTextToPath for usvg_tree::Tree { fn convert_text(&mut self, fontdb: &fontdb::Database) { convert_text(self.root.clone(), fontdb); + self.calculate_abs_transforms(); } } diff --git a/crates/usvg-tree/src/lib.rs b/crates/usvg-tree/src/lib.rs index 91f27872c..f744460b4 100644 --- a/crates/usvg-tree/src/lib.rs +++ b/crates/usvg-tree/src/lib.rs @@ -743,9 +743,19 @@ pub struct Group { /// Can be empty. pub id: String, - /// Element transform. + /// Element's transform. pub transform: Transform, + /// Element's absolute transform. + /// + /// Contains all ancestors transforms. + /// Will be set automatically by the parser or can be recalculated manually using + /// [`Tree::calculate_abs_transforms`]. + /// + /// Note that subroots, like clipPaths, masks and patterns, have their own root transform, + /// which isn't affected by the node that references this subroot. + pub abs_transform: Transform, + /// Group opacity. /// /// After the group is rendered we should combine @@ -777,6 +787,7 @@ impl Default for Group { Group { id: String::new(), transform: Transform::default(), + abs_transform: Transform::default(), opacity: Opacity::ONE, blend_mode: BlendMode::Normal, isolate: false, @@ -999,6 +1010,16 @@ impl Tree { pub fn filters)>(&self, mut f: F) { loop_over_filters(&self.root, &mut f) } + + /// Calculates absolute transforms for all nodes in the tree. + /// + /// As of now, sets [`Group::abs_transform`]. + /// + /// Automatically called by the parser + /// and ideally should be called manually after each tree modification. + pub fn calculate_abs_transforms(&mut self) { + calculate_abs_transform(&self.root, Transform::identity()); + } } fn has_text_nodes(root: &Node) -> bool { @@ -1203,17 +1224,17 @@ pub trait NodeExt { /// /// If a current node doesn't support transformation - a default /// transform will be returned. + /// + /// This method is cheap, since an absolute transform is already stored in + /// [`Group::abs_transform`]. fn abs_transform(&self) -> Transform; /// Appends `kind` as a node child. - /// - /// Shorthand for `Node::append(Node::new(Box::new(kind)))`. fn append_kind(&self, kind: NodeKind) -> Node; /// Calculates node's absolute bounding box. /// - /// Always returns `None` for `NodeKind::Text` since we cannot calculate its bbox - /// without converting it into paths first. + /// Returns `None` for `NodeKind::Text` unless it was flattened already. fn calculate_bbox(&self) -> Option; /// Calls a closure for each subroot this `Node` has. @@ -1248,19 +1269,13 @@ impl NodeExt for Node { } fn abs_transform(&self) -> Transform { - let mut ts_list = Vec::new(); - for p in self.ancestors() { - if let NodeKind::Group(ref group) = *p.borrow() { - ts_list.push(group.transform); - } - } - - let mut abs_ts = Transform::default(); - for ts in ts_list.iter().rev() { - abs_ts = abs_ts.pre_concat(*ts); + if let NodeKind::Group(ref g) = *self.borrow() { + g.abs_transform + } else { + // Only groups can have a transform, therefore for paths, images and text + // we simply use the parent transform. + self.parent().map(|n| n.abs_transform()).unwrap_or_default() } - - abs_ts } #[inline] @@ -1314,3 +1329,21 @@ fn calc_node_bbox(node: &Node, ts: Transform) -> Option { } } } + +// TODO: test somehow +fn calculate_abs_transform(node: &Node, ts: Transform) { + if matches!(*node.borrow(), NodeKind::Group(_)) { + let mut abs_ts = ts; + if let NodeKind::Group(ref mut group) = *node.borrow_mut() { + group.abs_transform = ts.pre_concat(group.transform); + abs_ts = group.abs_transform; + } + + for child in node.children() { + calculate_abs_transform(&child, abs_ts); + } + } + + // Yes, subroots are not affected by the node's transform. + node.subroots(|root| calculate_abs_transform(&root, Transform::identity())); +}