As a part of my GSoC project, I’m working with my mentor Tobias Deiminger on implementing the FreeText typewriter annotation with post GSoC goal of click-to-type WYSIWYG editing feature in Okular to write directly on PDF page. Here we have come with the following high-level implementation ideas:
Idea 1: Dedicated annotation widgets
This is inspired by the existing FormWidget implementation. FormWidgets have a unified interface towards PageView. They scroll and scale with the viewport and let the user edit content in place. They come in different flavors, e.g. radio buttons and text input. Especially the in-place text input sounds quite like what we want for the typewriter. So let’s get a similar TypewriterWidget and making all annotations separate widgets could be a path towards better design. The typewriter can be the first doing it in real.
Idea 1.1: Custom typewriter widget rendered by the generator
With every text input keystroke, TypewriterWidget shall update the underlying annotation object. The TypewriterWidget::paintEvent shall request an up-to-date QImage with the single annotation from poppler and paint that. There is only “splash” rendering engine at work and hence the annotation is rendered by it.
PDF supports saving annotations into the document and defines the native rendering, all is done by means of poppler. Currently, in the print and PageView::paintEvent case, the generator renders annotations.
This idea probably requires something like Annotation::renderToImage to save CPU time, because Page::renderToImage says “x, y, w, h are not well-tested” and it is rather expensive. It is the best WYSIWYG approach to write directly on PDF page but implementing it might take a long time.
Idea 1.2: Use KTextEdit during user input
Here we have to relinquish WYSIWYG during user input phase. It’s more like “what you see during user input is similar to what you get when printed or page is painted”. There’s a flag “do not render this annotation by generator”. Let’s set it while user input is in progress. After finishing user input, switch to generator rendering again.
Here is the output from an experimental code:
You can see that visible differences are the downside of this idea before and after the editing. We should at least fine-tune KTextEdit to make it less notable. This way we could get rid of the position offset and have a more similar font. But FreeText ignores the font and causes the most notable difference. See https://bugs.freedesktop.org/show_bug.cgi?id=81748#c1.
There’s one more fundamental issue in the above example: It’s just different rendering engines at work. In KTextEdit the font is rendered by the Qt paint system. After you switch to poppler, the annotation is rendered by “Splash”. Both may or may not use libfreetype (see https://doc.qt.io/qt-5.10/qtgui-attribution-freetype.html and poppler/splash/SplashFTFontEngine.cc). So algorithms like for hinting and anti-aliasing may or may not differ. And poppler uses styling rules from the PDF, whereas KTextEdit carries its own set of styling rules.
Idea 2: Don’t make typewriter UI a QtWidget
Instead, we handle input events, state, and painting directly in PageView. Keep the idea that we request a QImage with a single annotation from poppler and paint that. This makes PageView a bit worse again, but the approach is in good tradition.
There’s class MouseAnnotaiton which handles selecting, moving and resizing annotations. It was not designed to work with widgets. It just draws into the PageView. We either have to redesign it or duplicate relevant events; send one to MouseAnnotaiton, and the same to TypewriterWidget. Can’t say anything about the mess it will create!