fschutt / printpdf

An easy-to-use library for writing PDF in Rust
https://docs.rs/printpdf/
MIT License
777 stars 95 forks source link

Add the SMask of an image as an independent object. #172

Closed rodrigorc closed 3 months ago

rodrigorc commented 3 months ago

This PR fixes #166, I think.

The problem was that the SMask was being streamed directly into the parent image dictionary, something like:

9 0 obj
<</Type/XObject/Subtype/Image /SMask <</Type/XObject/Subtype/Image>>stream...endstream>>stream...endstream
endobj

But that is not allowed by the PDF specification, although some parsers seem to be able to read just fine. Others will ignore the whole XObject.

The proper way would be to dump the SMask first, and then the Image with a ref:

8 0 obj
<</Type/XObject/Subtype/Image>>stream...endstream
endobj
9 0 obj
<</Type/XObject/Subtype/Image /SMask 8 0 R>>stream...endstream
endobj

I'm not fully satisfied by my code but it seems to work fine. I would like the ImageXObject to be able to generate up to two streams, but I didn't find a way to make it work.

Dumping the stream for the smask early, from Image::add_to_layer does the work, but it feels a bit hacky. The main issue is that of "locality", that is the masks of all images will be dumped at the very beginning of the file, instead of next to the image they modify. But hey, it works.

Also, I think that ImageXObject::{try_from, from_dynamic_image} would be better as members of Image, but I don't know how you would feel about such a change in a public interface. I changed only the return value to make the SMask separate, because an ImageXObject simply can't contain another ImageXObject.

Then, I removed the SMask type, that was never actually fully used, and that matte member of type Vec<i64> that made two unneeded conversions of the mask data.

And finally, since I was already editing that function, I moved a piece of duplicated code into preprocess_image_with_alpha().