linebender / resvg

An SVG rendering library.
Apache License 2.0
2.84k stars 229 forks source link

`abs_transform` of `use` with position regression #722

Closed Enter-tainer closed 7 months ago

Enter-tainer commented 8 months ago

The doc of usvg::Path::abs_transform :

Element’s absolute transform.

Contains all ancestors transforms excluding element’s transform.

Note that this is not the relative transform present in SVG. The SVG one would be set only on groups.

It says that element's transform is excluded. How to get it, so we can get the full absolute of an element?

To make it clear, I make a small function to print all abs_transform of all nodes in tree:

fn print_abs_ts(node: &usvg::Node) {
    if let usvg::Node::Group(g) = node {
        println!("group transform: {:?}", g.transform());
        for child in g.children() {
            print_abs_ts(child);
        }
    }
    println!("abs transform: {:?}", node.abs_transform());
}
fn main() {
    let svg = std::fs::read_to_string("test2.svg").unwrap();
    let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap();
    for node in tree.root().children() {
        print_abs_ts(node);
    }
}

For this svg(note the x=30 y=20 part):

<svg class="typst-doc" viewBox="0 0 100 100" width="100" height="100" xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:h5="http://www.w3.org/1999/xhtml">
  <g transform="translate(33, 44)">
    <rect width="100" height="100" fill="white" />
    <rect width="10" height="10" x="30" y="20" fill="black"/>
  </g>
</svg>

The output is

❯ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target\debug\usvg-bug.exe`
group transform: Transform { sx: 1.0, kx: 0.0, ky: 0.0, sy: 1.0, tx: 33.0, ty: 44.0 }
abs transform: Transform { sx: 1.0, kx: 0.0, ky: 0.0, sy: 1.0, tx: 33.0, ty: 44.0 }
abs transform: Transform { sx: 1.0, kx: 0.0, ky: 0.0, sy: 1.0, tx: 33.0, ty: 44.0 }
abs transform: Transform { sx: 1.0, kx: 0.0, ky: 0.0, sy: 1.0, tx: 33.0, ty: 44.0 }

I'd like to know how to get x="30" y="20" in usvg, because they are required to render a svg.

I'm using:

[dependencies]
usvg = { version = "0.40.0", default-features = false }
RazrFalcon commented 8 months ago

x="30" y="20" is not a transform, but a position. Those are separate concepts in SVG. But rect would be converted into path, which has a bounding box, not a position.

I would suggest using Node::abs_bounding_box instead. This way you don't have to worry about transforms at all.

Enter-tainer commented 8 months ago

Thank you for your reply! abs_bounding_box is great. One thing annoying is that I cannot generate an affine transform from it. Take this svg for example:

<svg viewBox="0 0 100 100" width="100" height="100" xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:h5="http://www.w3.org/1999/xhtml">
  <path d="M 10 10 H 90 V 90 H 10 Z" fill="transparent" stroke="black" />
</svg>

It is a 80 * 80 rect at (10, 10). And its bbox is Rect { left: 10.0, top: 10.0, right: 90.0, bottom: 90.0 }. To render it correctly, I shouldn't use left: 10, top: 10 as a translate transform, otherwise the top-left of the rect would be put at (20, 20) other than (10, 10). I wonder if it is possible to get such a transformation or there is better way to get the "abs_transform + position"(I hope this is what i need to transform each element to the correct position) of each element in an svg tree.

Enter-tainer commented 8 months ago

BTW, I'm trying to update vello_svg's usvg depedency from 0.37 to latest. It currently use abs_transform to get "abs_transform + position". But i guess api has changed since then.

RazrFalcon commented 8 months ago

You don't need to know shape/path/rect position to render it. Meaning you don't need left: 10, top: 10 at all. All you need is abs_transform.

And while API did changed, you still can use abs_transform. It should return the same value as before.

Enter-tainer commented 8 months ago

Thank you for mentioning that. I just played with some svg and i finally learned that for <path x=...> ..., the position is baked in the path and it is not a transform. They work the same in 0.40 and 0.37.

However for <use x=...>, 0.40 produce different result from 0.37.

It seems that 0.40 doesn't take the position of use into account in abs_transform.

0.37

```rust // usvg 0.37 use usvg::{NodeExt, Options, TreeParsing}; fn main() { let svg = r##" "##; let tree = usvg::Tree::from_str(&svg, &Options::default()).unwrap(); for node in tree.root.descendants() { if let usvg::NodeKind::Path(path) = &*node.borrow() { println!("path abs_transform: {:?}", node.abs_transform()); println!("path: {:?}", path.data); } } // OUTPUT: // // path abs_transform: Transform { sx: 1.0, kx: 0.0, ky: 0.0, sy: 1.0, tx: 0.0, ty: 0.0 } // path: Path { segments: "M 0 0 L 10 0 L 10 10 L 0 10 Z", bounds: Rect { left: 0.0, top: 0.0, right: 10.0, bottom: 10.0 } } // path abs_transform: Transform { sx: 1.0, kx: 0.0, ky: 0.0, sy: 1.0, tx: 20.0, ty: 0.0 } // note tx: 20.0 here!! // path: Path { segments: "M 0 0 L 10 0 L 10 10 L 0 10 Z", bounds: Rect { left: 0.0, top: 0.0, right: 10.0, bottom: 10.0 } } } ```

0.40

```rust // usvg 0.40 fn print_abs_ts(node: &usvg::Node) { if let usvg::Node::Group(g) = node { for child in g.children() { print_abs_ts(child); } } if let usvg::Node::Path(p) = node { println!("path data: {:?}", p.data()); println!("path abs transform: {:?}", node.abs_transform()); } } fn main() { let svg = r##" "##; let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).unwrap(); for node in tree.root().children() { print_abs_ts(node); } // OUTPUT // // path data: Path { segments: "M 0 0 L 10 0 L 10 10 L 0 10 Z", bounds: Rect { left: 0.0, top: 0.0, right: 10.0, bottom: 10.0 } } // path abs transform: Transform { sx: 1.0, kx: 0.0, ky: 0.0, sy: 1.0, tx: 0.0, ty: 0.0 } // path data: Path { segments: "M 0 0 L 10 0 L 10 10 L 0 10 Z", bounds: Rect { left: 0.0, top: 0.0, right: 10.0, bottom: 10.0 } } // path abs transform: Transform { sx: 1.0, kx: 0.0, ky: 0.0, sy: 1.0, tx: 0.0, ty: 0.0 } // there is no tx: 20.0 here!! } ```

RazrFalcon commented 8 months ago

I think you're confusing how SVG works internally. You're trying to map your input 1:1 to debug log you have, but it would not work this way. I would suggest using usvg CLI tool to convert your SVG first. This way you would see how usvg/resvg transforms SVG before rendering. This should solve your issues.

Enter-tainer commented 8 months ago

I would suggest using usvg CLI tool to convert your SVG first. This way you would see how usvg/resvg transforms SVG before rendering. This should solve your issues.

It is indeed solved because uses are removed.

It should return the same value as before.

But I still feel a little bit concerned about this. it seems that v0.40 produce a different abs_transform from v0.37. It is a bug or it is intended?

RazrFalcon commented 8 months ago

Hm... yes, you need node.abs_transform().pre_concat(node.transform()). From Group::abs_transform docs:

Contains all ancestors transforms excluding element's transform.

So I guess it did changed. Sorry, there were an absurd amount of changes since 0.37. Hard to track them all. Also, we mainly test only rendering and resvg renderer doesn't use abs_transform() internally.

In the end, I think it is a bug. Will take a closer look later.

Enter-tainer commented 8 months ago

Thank you for your understanding and patience! I've migrate the code to use transform only, just like what resvg did. so currently the bug don't block me anymore.

BTW, node.abs_transform().pre_concat(node.transform()) looks like what i'm initially looking for but it seems to be missing in public api. https://docs.rs/usvg/latest/usvg/enum.Node.html. There is no transform for Node and Path, only Group has this.

I'd also like to express my genuine appreciation for the resvg series project. It's really hard to believe these project are made by a single person! It's clear that a great deal of effort and passion has gone into this, and it does not go unnoticed. Thank you for your hard work; it makes a real difference to many.

RazrFalcon commented 8 months ago

Yes, transform is available only on groups. I've meant: group.abs_transform().pre_concat(group.transform()). It's not really about private API. In usvg, only Group elements can have transforms. Other nodes can't. Hard to explain why without diving deep into SVG internals.

I know that usvg API is a bit confusing. This is because it's designed mainly for resvg and is constantly changing. There are plans on making it a bit nicer, but it's not the main priority. The main goal is still correct rendering.

Thanks you for the kind words. Much appreciated.

RazrFalcon commented 7 months ago

Contains all ancestors transforms excluding element’s transform.

That statement was false. abs_transform does include element's transform. But there were also a bug in use removing which got fixed.