Adding Custom Stamps Support To Okular
Google Summer of Code is almost finished, and it’s time for me to wrap up and present my work done so far during this period. It has been an awesome, and fruitful journey, I really learned a lot and I enjoyed every bit of it. In this blog post, I’d like to present all of the work done, the challenging parts, as well as the future work that I’m planning to do. Before I continue to representing the work, I’d like to thank Albert for helping me through this period and for his great efforts and continuous support, it’s much appreciated.
The code didn’t get merged yet, it’s still in the reviewing process. I’ll be modifying anything that should be modified according to Albert’s helpful comments. However, both MRs (Poppler: Improve Poppler Support For Custom Image Annotations, as well as Okular: Adds support for the new custom stamps icons APIs) hopefully should be ready to get in and if we needed to modify them, they’ll be minor stuff.
- Poppler: Improve Poppler Support For Custom Image Annotations
- Poppler: Support the standard stamp annotations icon
- Okular: Adds support for the new custom stamp icons APIs
KDE Status Report
A new helper class has been added to the Poppler side, the function of this class is to create a new PDF object (Image XObject) that gets added to the document. This way, we can easily reference the created object in our custom stamp annotation. This new helper class is really simple, it takes the important attributes of the image like the width, height, color space, etc. as well as the actual image data. The new helper class supports creating Image XObjects with transparency as well. This can be done by first creating the soft mask for the image (which will represent the transparency part or the alpha component) as if it was a normal image created (instantiate a new object from the helper class) then passing this newly created object to the target image that you’d like to create.
This involved modifying the Annot class in Poppler to support the new helper class, by adding new methods to the stamp subclass (the AnnotStamp class) which will get the helper object and reference the created image and build its appearance out of it. As well as, modifying the Qt5 Frontend, by adding new APIs to support the new Poppler functionality.This involved adding a new API that takes in a QImage and converts the QImage to the corresponding Image XObject that Poppler can understand.
We’ve managed to make Poppler itself generate default AP streams for most of default standard stamps (approved, final, expired, etc.). However, there are some of them that might need to more work so that they are fully supported. Adding the stamps default stream was done using a tool called cairosvg to convert Okular SVGs to PDFs, then uncompressing the document to extract the data stream and integrate it with our AnnotStamp class implementation.
Most of the work done on Okular Side was on the PDF Generator. Okular has multiple generators that are used to talk to the corresponding library. One of those generator is the PDF Generator, which talks directly to the Qt5 Frontend of Poppler. Mainly, we modified the current behavior to support calling the new APIs. There were some challenges in this particular part that I’m going to talk about in detail below. However, all of the modifications were only related to stamp anontations. We needed first to disable Okular’s rendering for stamps and only rely on Poppler’s rendering, and whenever a new Stamp gets added we load its QImage and pass it to the Qt5 API. This way we’re sure that any new stamps get added by Okular will have AP streams and other PDF viewers will be able to understand and render them.
I think that the most challenging part in Poppler was getting the data in a correct way from the QImages while converting them to Image XObject (object that Poppler can understand). This part took me some time and lots of testing to make sure that everything is sorted.
Another challenge, is what I’m currently facing. As mentioned above, most of the default stamps are working correctly except for two or three. We’re currently looking into that and hopefully everything gets sorted as well soon.
We faced two major challenges while working on Okular, the first one was loading the images (especially the SVG images) to scale. This was solved by moving the function that loads the image to AnnotationUtils class in Okular’s core library, as well as always getting the correct requested dimensions to be able to load the image with them using QSvgRenderer.
The second major challenge was handling the “delete then undo” operation, this resulted in losing the AP stream of the Stamp and loading the stamp again but with the default Draft icon. Albert suggested a brilliant solution which was saving the annotation appearance of the stamp using a wrapper class in the Qt5 Frontend, then load it again upon the undo operation. We agreed that we can use a map that contains the annotation itself and its corresponding appearance, then upon re-adding that annotation again, we’ll detect that it has existed before and therefore its a “delete then undo” operation, so we’ll load the appearance again.
Everything should be ported to Qt6 wrapper, and I think that we’ll need to work more on optimizing and reducing the created document size, by compressing the converted QImage data.
I’m planning on adding tests for the new APIs added to the Poppler and Qt5 sides.
There’s another idea suggested related to the way we retrieve the stamp annotation appearance (currently we store them upon loading the file), so we thought it might be better to store the appearances on their removal and check if they’ve existed before upon re-adding them. This way we’ll only store the appearances we need.
It has been an awesome experience and I really enjoyed SoC this year. I learned a lot of things related to PDF and how it gets rendered, as well as enhancing my C++ knowledge. As I mentioned before, I didn’t imagine myself working on something as huge as Poppler, so it makes me so proud and glad that I got accepted this summer to work on this awesome project. I’m really thankful to Albert, without him and his continuous support and motivation I wouldn’t be able to reach this progress.
I’ll leave you now with a small video showing an overview of the work, hope you enjoyed the blog.