typst / svg2pdf

Converts SVG files to PDF.
Apache License 2.0
273 stars 32 forks source link

Update to usvg 0.35 + additional improvements #37

Closed LaurenzV closed 1 year ago

LaurenzV commented 1 year ago

This PR introduces a number of new changes/improvements to svg2pdf:

Update to resvg 0.35 and fontdb 0.14

The library has been updated to resvg 0.35. clap and fontdb have been bumped as well. As a result of the update of the former dependency, a couple of things had to be changed because there were quite a few breaking changes (one for example being the introduction of quad curves, which are not directly supported in PDF and thus now need to be converted by us. Another breaking change was bboxes do not include the stroke anymore, so I now just reused code from resvg to reintroduce that feature on our end).

This now means that in order to use the newest version, we also need to update to resvg 0.35 in Typst, which I think unfortunately will also require some minor but annoying changes. But I guess there is no rush.

Update in logic how usvg::Group is dealt with

Previously, we would always create a new XObject whenever we encounter a usvg::Group in the tree, which bloated the PDFs by a lot, since a lot of unnecessary overhead was introduced by this. This has now been changed. By default, everything will be written into the main page content stream (or XObject content stream when calling the convert_tree_into method), and new XObject's are only generated when needed (e.g. for blend modes). This drastically reduces the file size in many cases. It fixes #35.

This unfortunately requires us to keep track of the current transform in some locations because patterns will not be affected by transformations in the content stream, but I believe it is worth the trade-off.

Paths and fills are now rendered seperately

The reason for this is twofold.

Firstly, stroking + filling a path is rendered differently in different viewers. In Acrobat and Google Chrome, the fill is clipped of at the start of the stroke, while in other viewers it is clipped at the center of the stroke. The SVG spec requires them to be clipped at the center. I checked what Adobe Illustrator does, and by default they also always draw stroke and fills separately.

Secondly, there are a couple of cases (for example when applying a mask on a stroke or when we have stroke-and-fill instead of fill-and-stroke order) where we have to draw strokes and fills separately. In order to reduce the number of code paths that can be taken, I decided to simply draw them separately for every shape. The overhead introduced by this seems to be very minimal (although this obviously depends on the SVG) and completely outshadowed by the changes above.

Clip path improvements

Clip paths have been improved upon, and the cases where we fall back to using alpha masks have been reduced to the absolute minimum. This will also finally fix the following issue for all browsers.

Blend modes

Blend modes are now supported. Although apart from Adobe Acrobat, a lot of viewers don't seem to render them correctly.

Tests

  1. I added a new integration test from wikimedia as well as from draw.io.
  2. Previously, I scaled all of the tests by a factor of 2.5x because this is how resvg does it in its test suite and I thought it would make sense here too. However, I don't think this makes sense because in our case, whenever we make updates to the logic of svg2pdf, there end up being a lot of tests which need to be regenerated because of pixel differences. This is why I regenerated all tests to be in their original size, so that the overhead for updating the test files won't be as big anymore in the future, especially for integration tests.

Other

Apart from that, I refactored some other parts of the program (like for example gradients) to make the code easier to follow.

Results

The results can be seen below. As I did before, I generated a Typst document containing all SVG test cases and rendered them into a PDF.

Current version: current.pdf

New version: new.pdf

Note in particular how the file size dropped from 14.5MB to just 6MB! And this is only because we have a lot of very small SVG files in there, the ones that you encounter normally are usually much larger and the overall gains even bigger.

jonmmease commented 1 year ago

I've been playing with this branch and found a case where a vertical gradient is rendered upside down.

Here is the SVG (embedded and rendered by GitHub): chart

Here is a screenshot of the PDF that this branch produces (GitHub doesn't like PDF embedding): Screenshot 2023-09-03 at 7 14 55 AM

On main, the gradient is oriented correctly

jonmmease commented 1 year ago

I should add, I've gone through converting all of the SVG's produced by the Vega-Altair Python library in the gallery at https://altair-viz.github.io/gallery/index.html, and the flipped gradient is the only issue I ran into. I'm really excited about how well this already works!

LaurenzV commented 1 year ago

Hmm, that's weird... It seems to look correct in Apple Preview, but in the other ones it's indeed wrong. Will see if I can find the root cause of this.

LaurenzV commented 1 year ago

Thanks for reporting, the bug should be fixed now!

jonmmease commented 1 year ago

Thanks for reporting, the bug should be fixed now!

Fix confirmed!

laurmaedje commented 1 year ago

I assume this is ready to merge then?

LaurenzV commented 1 year ago

Yep it is!

laurmaedje commented 1 year ago

Wonderful! 🎉