Closed LaurenzV closed 1 year ago
Hey!
Thank you for your PR, it looks spectacular, especially your write-up! Bug number 1 was an oversight, you went for the simplest fix, great! This should not have any unintended side effects. Similarly, the ordering of transform application and child rendering was one of my fumbles and looks a lot nicer now.
We did not use usvg
's transform because we did not want to depend on their Transform
type to remain stable across crate updates. However, their transform type had the neccessary impls and our didn't so a good choice to just use something that works. I have looked at the type, and within memory, it's also really just 6 f64
s, just in a nicer package.
Overall, I think that svg2pdf
's way of handling transforms is too impromptu and we are missing the right abstraction. It is too easy to forget them or to apply them in a wrong order. I wrote this crate in a bit of a rush π€. Similarly, we should probably have reference images to compare the rendered PDFs against, but at the moment, changes have to be tested by manually inspecting the PDFs.
Your code to print out the usvg
tree looks really useful! If you want to, you could add that as a utility function for debugging in another PR!
As it stands, this PR LGTM, thank you!
Hey, so I did some digging into #9, and I believe I found out a fix for it (I'm relatively new to Rust and everything related to PDFs though, so I can't guarantee that some of the changes have some side effects, but I hope not. π
So, essentially I fixed three different problems, the first one is not directly related to the example mentioned in the issue, but I noticed it while messing around with it.
Transformation for clip paths
So, firstly what I noticed is that transformations on clip path objects are completely ignored sometimes. Consider this svg file for example:
Which is supposed to look like this:
But it currently is rendered like this:
The green rectangle should be shown in the bottom right corner of the black one because the clip path is translated by (100, 100),
To understand why this is the case, I created a small function that prints out all of the nodes of the tree including their level, and for this example it looks like this (I cut off the unrelevant parts)
So as you can see, the transform is stored in the
ClipPath
object instead of thePath
itself (which makes sense), but this is a "problem" because in the code that creates the clip paths:The
path
variable in line 3 contains the data for the clip path (including the transformations), but it is never used! Instead, we just iterate through all of the children and then draw them with their transformations, but we ignore the transformation of the clip path itself. I copied over the code from how group handles this:And this seems to fix the problem for the green square. I also checked the other test svgs and they look the same, so this change shouldn't break anything else. However, this doesn't actually solve the problem with group transformations outlined above.
Group transformation in the wrong order
Consider this example next (adapted from the clip path example above:
How it is supposed to look:
How it actually looks:
As you can see, the transform is simply not applied to the line. And the reason for this is in the
render
method forGroup
objects:As you can see, we first render the child contents and only then do we apply the transformations to the context, meaning that the children won't be affected anymore. I put
child_content
afterold
and this seemed to fix it. However, this only fixes it for the case where x = y, so if we try this:We still get a wrong result:
And the reason this is the case becomes clear when we look at the tree again:
So as you can see, the transformation is stored correctly in the group object, but the problem is that because of the fix we implemented for the first problem, the
apply_clip_path
method will now override the transformation from the group because of the way thetransform()
method is currently implemented. I might be wrong about this, but I think it should instead be implemented by chaining the transformations together instead of replacing them? This is why I rewroteCoordToPdf
implementation to use aTransform
object internally instead of a matrix. I'm not sure if there was a particular reason why an array of length 6 was chosen to store it instead of aTransform
object, so let me know if this should be changed. But anyway, this change fixes the output for me, and the other ones seem to work just like before (unfortunately it still doesn't fix #17, but I haven't had the chance to look into that one yet).And as I mentioned, I'm relatively new to all of this and this is my first PR, so I'd appreciate if someone could look over it to make sure the changes are fine and don't have any side effects, so that it doesn't break anything else. π Also, I didn't quite get what the best way to test all of this is, do you currently just run the test and then look over the generated PDFs manually to make sure everything looks okay?