-
Notifications
You must be signed in to change notification settings - Fork 56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improved gradient support #175
Conversation
- Fixes transformations that were incorrectly being performed in PDF space. - Support transparent gradients with `stop-opacity`. - Support repeated and reflected gradients using `spreadMethod`.
Hi @tonymarklove, thank you for this excellent PR! I'm sorry it's taken me such a long time to get to it. I'll review it over the next few days. |
@mogest no problem. I know it's a big change! Hopefully it makes sense once you take a look. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks once again @tonymarklove, this is both extensive and beautiful and will make a lot of prawn-svg users very happy!
Functionality looks perfect. Just have a few comments on the code below; if you're short on time then please send back to me and I'm happy to make the minor tweaks.
Thank you so much! I'll do a gem release in the next couple of days. |
Looks like there's a regression here. I was looking at mojavelinux's test case on #153 which was working in the latest published gem but only displays a gradient on the last image with the new main branch. I think it's that the cache key has to also include the position on the screen. Something about the gradient also including coordinates inside it? I can't remember, it's been too long since I worked with this stuff! I'll have a look over the next few days anyway; if the answer is obvious to you @tonymarklove then let me know otherwise I will grind it out. :) |
Quick check before bedtime confirms that replacing the SHA1 digest with a random key for each invocation of GradientRenderer#draw results in the regression being fixed. Don't know why yet tho. |
@mogest yes, I think you are right. Because we pull in the position using |
At that point, am I right in thinking that we might as well just make a random/globally-incrementing key for every new gradient paint? If you have to be painting the same size, in the same area, with everything else the same to share the pattern, I can't imagine why anyone would want to do that twice? |
Yes I think that makes sense. I considered doing that from the start but I didn't want to stray too far from the existing implementation in Prawn. But as you say, I don't think it's really possible to reuse a gradient so the cache key isn't doing anything useful. |
OK great! I've added ccbbdf3 and that seems to fix the regression. |
Overview
This pull request does three main things:
stop-opacity
.spreadMethod
.Intro
Following up from my fairly small previous PR, I was doing a lot of investigation into the rendering of gradients. I found that when SVGs contained complex gradient transforms, there were some bugs in the prawn-svg implementation.
It turned out that Prawn makes a bunch of assumptions about how gradients and gradient matrices are handled, so the easiest way forward was to reimplement Prawns gradient handling. Once I had done that, it made sense to support transparency and repeated gradients as a way of justifying the new implementation.
I'm aware this has ended up being a rather large PR, so feel free to ask questions if anything doesn't make sense.
The Visuals
I created an example SVG to stress test the various gradient options. Here is what it looks like in Chrome:
But here is what it looks like rendered to PDF with the latest release of prawn-svg:
With the code in this PR, it now renders like this, which should be very close to the Chrome example:
Technical Details
Performing transforms in SVG-space
The bugs that I was seeing with gradient transforms, which show up as white boxes in the image above, were mostly due to a mismatch between PDF-space and SVG-space, where the origins are at bottom-left and top-left respectively.
I have switched this round so that the gradient transforms are read from the SVG in SVG-space. They are finally combined with an
svg_to_pdf_matrix
which transforms the final coordinates back into PDF page-space. This combined gradient matrix can be directly inserted into the PDF as the pattern matrix, and everything works out correctly.AdditionalGradientTransforms
is no longer neededBecause of the above gradient matrix, we no longer need to monkey-patch the
gradient_coordinates
method inside Prawn, andAdditionalGradientTransforms
can be removed.Unclamping
objectBoundingBox
When
gradientUnits="objectBoundingBox"
is set in the SVG, the coordinate system for the provided attributes is relative to the element's bounding box. Previously this was being clamped to a range of 0-1, but there is no reason this needs to be the case. It is valid to make a gradient larger than the bounding box, so I have removed that restriction with the newpercentage_or_proportion
method.Prawn::SVG::GradientRenderer
Prawn::SVG::GradientRenderer
is where the core of the gradient generation logic sits. This should really go into Prawn itself, but I didn't want to have to submit pull requests to both projects at once to get it working.I will also try to get a PR opened for Prawn with these changes. If that gets accepted
GradientRenderer
can probably be removed from prawn-svg entirely.Because I decided to wrap the
GradientRenderer
in a class, rather than mix it in as aPrawn::Document.extensions
module, I did need to call private methods withsend
in three places. Hopefully that seems reasonable, for the added encapsulation gained.Integration tests
I have checked all impacted samples generated by the integration tests. Due to the new gradient generation code, any PDFs with a gradient will get a modified MD5 hash now.
I have checked all of these by hand, and they either look identical or improved by this PR.