1 /* poppler-page.cc: qt interface to poppler
2  * Copyright (C) 2005, Net Integration Technologies, Inc.
3  * Copyright (C) 2005, Brad Hards <bradh@frogmouth.net>
4  * Copyright (C) 2005-2021, Albert Astals Cid <aacid@kde.org>
5  * Copyright (C) 2005, Stefan Kebekus <stefan.kebekus@math.uni-koeln.de>
6  * Copyright (C) 2006-2011, Pino Toscano <pino@kde.org>
7  * Copyright (C) 2008 Carlos Garcia Campos <carlosgc@gnome.org>
8  * Copyright (C) 2009 Shawn Rutledge <shawn.t.rutledge@gmail.com>
9  * Copyright (C) 2010, 2012, Guillermo Amaral <gamaral@kdab.com>
10  * Copyright (C) 2010 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
11  * Copyright (C) 2010 Matthias Fauconneau <matthias.fauconneau@gmail.com>
12  * Copyright (C) 2010 Hib Eris <hib@hiberis.nl>
13  * Copyright (C) 2012 Tobias Koenig <tokoe@kdab.com>
14  * Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
15  * Copyright (C) 2012, 2015 Adam Reichold <adamreichold@myopera.com>
16  * Copyright (C) 2012, 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
17  * Copyright (C) 2015 William Bader <williambader@hotmail.com>
18  * Copyright (C) 2016 Arseniy Lartsev <arseniy@alumni.chalmers.se>
19  * Copyright (C) 2016, Hanno Meyer-Thurow <h.mth@web.de>
20  * Copyright (C) 2017-2021, Oliver Sander <oliver.sander@tu-dresden.de>
21  * Copyright (C) 2017 Adrian Johnson <ajohnson@redneon.com>
22  * Copyright (C) 2017, 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
23  * Copyright (C) 2018 Intevation GmbH <intevation@intevation.de>
24  * Copyright (C) 2018, Tobias Deiminger <haxtibal@posteo.de>
25  * Copyright (C) 2018, 2021 Nelson Benítez León <nbenitezl@gmail.com>
26  * Copyright (C) 2020 Philipp Knechtges <philipp-dev@knechtges.com>
27  * Copyright (C) 2021 Hubert Figuiere <hub@figuiere.net>
28  * Copyright (C) 2021 Thomas Huxhorn <thomas.huxhorn@web.de>
29  *
30  * This program is free software; you can redistribute it and/or modify
31  * it under the terms of the GNU General Public License as published by
32  * the Free Software Foundation; either version 2, or (at your option)
33  * any later version.
34  *
35  * This program is distributed in the hope that it will be useful,
36  * but WITHOUT ANY WARRANTY; without even the implied warranty of
37  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
38  * GNU General Public License for more details.
39  *
40  * You should have received a copy of the GNU General Public License
41  * along with this program; if not, write to the Free Software
42  * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
43  */
44 
45 #include <poppler-qt6.h>
46 
47 #include <QtCore/QHash>
48 #include <QtCore/QMap>
49 #include <QtCore/QVarLengthArray>
50 #include <QtGui/QImage>
51 #include <QtGui/QPainter>
52 
53 #include <config.h>
54 #include <cfloat>
55 #include <poppler-config.h>
56 #include <PDFDoc.h>
57 #include <Catalog.h>
58 #include <Form.h>
59 #include <ErrorCodes.h>
60 #include <TextOutputDev.h>
61 #include <Annot.h>
62 #include <Link.h>
63 #include <QPainterOutputDev.h>
64 #include <Rendition.h>
65 #include <SplashOutputDev.h>
66 #include <splash/SplashBitmap.h>
67 
68 #include "poppler-private.h"
69 #include "poppler-page-transition-private.h"
70 #include "poppler-page-private.h"
71 #include "poppler-link-extractor-private.h"
72 #include "poppler-link-private.h"
73 #include "poppler-annotation-private.h"
74 #include "poppler-form.h"
75 #include "poppler-media.h"
76 
77 namespace Poppler {
78 
79 class TextExtractionAbortHelper
80 {
81 public:
TextExtractionAbortHelper(Page::ShouldAbortQueryFunc shouldAbortCallback,const QVariant & payloadA)82     TextExtractionAbortHelper(Page::ShouldAbortQueryFunc shouldAbortCallback, const QVariant &payloadA)
83     {
84         shouldAbortExtractionCallback = shouldAbortCallback;
85         payload = payloadA;
86     }
87 
88     Page::ShouldAbortQueryFunc shouldAbortExtractionCallback = nullptr;
89     QVariant payload;
90 };
91 
92 class OutputDevCallbackHelper
93 {
94 public:
setCallbacks(Page::RenderToImagePartialUpdateFunc callback,Page::ShouldRenderToImagePartialQueryFunc shouldDoCallback,Page::ShouldAbortQueryFunc shouldAbortCallback,const QVariant & payloadA)95     void setCallbacks(Page::RenderToImagePartialUpdateFunc callback, Page::ShouldRenderToImagePartialQueryFunc shouldDoCallback, Page::ShouldAbortQueryFunc shouldAbortCallback, const QVariant &payloadA)
96     {
97         partialUpdateCallback = callback;
98         shouldDoPartialUpdateCallback = shouldDoCallback;
99         shouldAbortRenderCallback = shouldAbortCallback;
100         payload = payloadA;
101     }
102 
103     Page::RenderToImagePartialUpdateFunc partialUpdateCallback = nullptr;
104     Page::ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback = nullptr;
105     Page::ShouldAbortQueryFunc shouldAbortRenderCallback = nullptr;
106     QVariant payload;
107 };
108 
109 class Qt6SplashOutputDev : public SplashOutputDev, public OutputDevCallbackHelper
110 {
111 public:
Qt6SplashOutputDev(SplashColorMode colorModeA,int bitmapRowPadA,bool reverseVideoA,bool ignorePaperColorA,SplashColorPtr paperColorA,bool bitmapTopDownA,SplashThinLineMode thinLineMode,bool overprintPreviewA)112     Qt6SplashOutputDev(SplashColorMode colorModeA, int bitmapRowPadA, bool reverseVideoA, bool ignorePaperColorA, SplashColorPtr paperColorA, bool bitmapTopDownA, SplashThinLineMode thinLineMode, bool overprintPreviewA)
113         : SplashOutputDev(colorModeA, bitmapRowPadA, reverseVideoA, paperColorA, bitmapTopDownA, thinLineMode, overprintPreviewA), ignorePaperColor(ignorePaperColorA)
114     {
115     }
116 
117     ~Qt6SplashOutputDev() override;
118 
dump()119     void dump() override
120     {
121         if (partialUpdateCallback && shouldDoPartialUpdateCallback && shouldDoPartialUpdateCallback(payload)) {
122             partialUpdateCallback(getXBGRImage(false /* takeImageData */), payload);
123         }
124     }
125 
getXBGRImage(bool takeImageData)126     QImage getXBGRImage(bool takeImageData)
127     {
128         SplashBitmap *b = getBitmap();
129 
130         const int bw = b->getWidth();
131         const int bh = b->getHeight();
132         const int brs = b->getRowSize();
133 
134         // If we use DeviceN8, convert to XBGR8.
135         // If requested, also transfer Splash's internal alpha channel.
136         const SplashBitmap::ConversionMode mode = ignorePaperColor ? SplashBitmap::conversionAlphaPremultiplied : SplashBitmap::conversionOpaque;
137 
138         const QImage::Format format = ignorePaperColor ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32;
139 
140         if (b->convertToXBGR(mode)) {
141             SplashColorPtr data = takeImageData ? b->takeData() : b->getDataPtr();
142 
143             if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
144                 // Convert byte order from RGBX to XBGR.
145                 for (int i = 0; i < bh; ++i) {
146                     for (int j = 0; j < bw; ++j) {
147                         SplashColorPtr pixel = &data[i * brs + j];
148 
149                         qSwap(pixel[0], pixel[3]);
150                         qSwap(pixel[1], pixel[2]);
151                     }
152                 }
153             }
154 
155             if (takeImageData) {
156                 // Construct a Qt image holding (and also owning) the raw bitmap data.
157                 QImage i(data, bw, bh, brs, format, gfree, data);
158                 if (i.isNull()) {
159                     gfree(data);
160                 }
161                 return i;
162             } else {
163                 return QImage(data, bw, bh, brs, format).copy();
164             }
165         }
166 
167         return QImage();
168     }
169 
170 private:
171     bool ignorePaperColor;
172 };
173 
174 Qt6SplashOutputDev::~Qt6SplashOutputDev() = default;
175 
176 class QImageDumpingQPainterOutputDev : public QPainterOutputDev, public OutputDevCallbackHelper
177 {
178 public:
QImageDumpingQPainterOutputDev(QPainter * painter,QImage * i)179     QImageDumpingQPainterOutputDev(QPainter *painter, QImage *i) : QPainterOutputDev(painter), image(i) { }
180     ~QImageDumpingQPainterOutputDev() override;
181 
dump()182     void dump() override
183     {
184         if (partialUpdateCallback && shouldDoPartialUpdateCallback && shouldDoPartialUpdateCallback(payload)) {
185             partialUpdateCallback(*image, payload);
186         }
187     }
188 
189 private:
190     QImage *image;
191 };
192 
193 QImageDumpingQPainterOutputDev::~QImageDumpingQPainterOutputDev() = default;
194 
convertLinkActionToLink(::LinkAction * a,const QRectF & linkArea)195 std::unique_ptr<Link> PageData::convertLinkActionToLink(::LinkAction *a, const QRectF &linkArea)
196 {
197     return convertLinkActionToLink(a, parentDoc, linkArea);
198 }
199 
convertLinkActionToLink(::LinkAction * a,DocumentData * parentDoc,const QRectF & linkArea)200 std::unique_ptr<Link> PageData::convertLinkActionToLink(::LinkAction *a, DocumentData *parentDoc, const QRectF &linkArea)
201 {
202     if (!a)
203         return nullptr;
204 
205     std::unique_ptr<Link> popplerLink;
206     switch (a->getKind()) {
207     case actionGoTo: {
208         LinkGoTo *g = (LinkGoTo *)a;
209         const LinkDestinationData ldd(g->getDest(), g->getNamedDest(), parentDoc, false);
210         // create link: no ext file, namedDest, object pointer
211         popplerLink = std::make_unique<LinkGoto>(linkArea, QString(), LinkDestination(ldd));
212     } break;
213 
214     case actionGoToR: {
215         LinkGoToR *g = (LinkGoToR *)a;
216         // copy link file
217         const QString fileName = UnicodeParsedString(g->getFileName());
218         const LinkDestinationData ldd(g->getDest(), g->getNamedDest(), parentDoc, !fileName.isEmpty());
219         // create link: fileName, namedDest, object pointer
220         popplerLink = std::make_unique<LinkGoto>(linkArea, fileName, LinkDestination(ldd));
221     } break;
222 
223     case actionLaunch: {
224         LinkLaunch *e = (LinkLaunch *)a;
225         const GooString *p = e->getParams();
226         popplerLink = std::make_unique<LinkExecute>(linkArea, e->getFileName()->c_str(), p ? p->c_str() : nullptr);
227     } break;
228 
229     case actionNamed: {
230         const std::string &name = ((LinkNamed *)a)->getName();
231         if (name == "NextPage")
232             popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::PageNext);
233         else if (name == "PrevPage")
234             popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::PagePrev);
235         else if (name == "FirstPage")
236             popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::PageFirst);
237         else if (name == "LastPage")
238             popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::PageLast);
239         else if (name == "GoBack")
240             popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::HistoryBack);
241         else if (name == "GoForward")
242             popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::HistoryForward);
243         else if (name == "Quit")
244             popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::Quit);
245         else if (name == "GoToPage")
246             popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::GoToPage);
247         else if (name == "Find")
248             popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::Find);
249         else if (name == "FullScreen")
250             popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::Presentation);
251         else if (name == "Print")
252             popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::Print);
253         else if (name == "Close") {
254             // acroread closes the document always, doesn't care whether
255             // its presentation mode or not
256             // popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::EndPresentation);
257             popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::Close);
258         } else {
259             // TODO
260         }
261     } break;
262 
263     case actionURI: {
264         popplerLink = std::make_unique<LinkBrowse>(linkArea, ((LinkURI *)a)->getURI().c_str());
265     } break;
266 
267     case actionSound: {
268         ::LinkSound *ls = (::LinkSound *)a;
269         popplerLink = std::make_unique<LinkSound>(linkArea, ls->getVolume(), ls->getSynchronous(), ls->getRepeat(), ls->getMix(), new SoundObject(ls->getSound()));
270     } break;
271 
272     case actionJavaScript: {
273         ::LinkJavaScript *ljs = (::LinkJavaScript *)a;
274         popplerLink = std::make_unique<LinkJavaScript>(linkArea, UnicodeParsedString(ljs->getScript()));
275     } break;
276 
277     case actionMovie: {
278         ::LinkMovie *lm = (::LinkMovie *)a;
279 
280         const QString title = (lm->hasAnnotTitle() ? UnicodeParsedString(lm->getAnnotTitle()) : QString());
281 
282         Ref reference = Ref::INVALID();
283         if (lm->hasAnnotRef())
284             reference = *lm->getAnnotRef();
285 
286         LinkMovie::Operation operation = LinkMovie::Play;
287         switch (lm->getOperation()) {
288         case ::LinkMovie::operationTypePlay:
289             operation = LinkMovie::Play;
290             break;
291         case ::LinkMovie::operationTypePause:
292             operation = LinkMovie::Pause;
293             break;
294         case ::LinkMovie::operationTypeResume:
295             operation = LinkMovie::Resume;
296             break;
297         case ::LinkMovie::operationTypeStop:
298             operation = LinkMovie::Stop;
299             break;
300         };
301 
302         popplerLink = std::make_unique<LinkMovie>(linkArea, operation, title, reference);
303     } break;
304 
305     case actionRendition: {
306         ::LinkRendition *lrn = (::LinkRendition *)a;
307 
308         Ref reference = Ref::INVALID();
309         if (lrn->hasScreenAnnot())
310             reference = lrn->getScreenAnnot();
311 
312         popplerLink = std::make_unique<LinkRendition>(linkArea, lrn->getMedia() ? lrn->getMedia()->copy() : nullptr, lrn->getOperation(), UnicodeParsedString(lrn->getScript()), reference);
313     } break;
314 
315     case actionOCGState: {
316         ::LinkOCGState *plocg = (::LinkOCGState *)a;
317 
318         LinkOCGStatePrivate *locgp = new LinkOCGStatePrivate(linkArea, plocg->getStateList(), plocg->getPreserveRB());
319         popplerLink = std::make_unique<LinkOCGState>(locgp);
320     } break;
321 
322     case actionHide: {
323         ::LinkHide *lh = (::LinkHide *)a;
324 
325         LinkHidePrivate *lhp = new LinkHidePrivate(linkArea, lh->hasTargetName() ? UnicodeParsedString(lh->getTargetName()) : QString(), lh->isShowAction());
326         popplerLink = std::make_unique<LinkHide>(lhp);
327     } break;
328 
329     case actionResetForm:
330         // Not handled in Qt6 front-end yet
331         break;
332 
333     case actionUnknown:
334         break;
335     }
336 
337     if (popplerLink) {
338         std::vector<std::unique_ptr<Link>> links;
339         for (const std::unique_ptr<::LinkAction> &nextAction : a->nextActions()) {
340             links.push_back(convertLinkActionToLink(nextAction.get(), parentDoc, linkArea));
341         }
342         LinkPrivate::get(popplerLink.get())->nextLinks = std::move(links);
343     }
344 
345     return popplerLink;
346 }
347 
prepareTextSearch(const QString & text,Page::Rotation rotate,QVector<Unicode> * u)348 inline TextPage *PageData::prepareTextSearch(const QString &text, Page::Rotation rotate, QVector<Unicode> *u)
349 {
350     *u = text.toUcs4();
351 
352     const int rotation = (int)rotate * 90;
353 
354     // fetch ourselves a textpage
355     TextOutputDev td(nullptr, true, 0, false, false);
356     parentDoc->doc->displayPage(&td, index + 1, 72, 72, rotation, false, true, false, nullptr, nullptr, nullptr, nullptr, true);
357     TextPage *textPage = td.takeText();
358 
359     return textPage;
360 }
361 
performSingleTextSearch(TextPage * textPage,QVector<Unicode> & u,double & sLeft,double & sTop,double & sRight,double & sBottom,Page::SearchDirection direction,bool sCase,bool sWords,bool sDiacritics,bool sAcrossLines)362 inline bool PageData::performSingleTextSearch(TextPage *textPage, QVector<Unicode> &u, double &sLeft, double &sTop, double &sRight, double &sBottom, Page::SearchDirection direction, bool sCase, bool sWords, bool sDiacritics,
363                                               bool sAcrossLines)
364 {
365     if (direction == Page::FromTop)
366         return textPage->findText(u.data(), u.size(), true, true, false, false, sCase, sDiacritics, sAcrossLines, false, sWords, &sLeft, &sTop, &sRight, &sBottom, nullptr, nullptr);
367     else if (direction == Page::NextResult)
368         return textPage->findText(u.data(), u.size(), false, true, true, false, sCase, sDiacritics, sAcrossLines, false, sWords, &sLeft, &sTop, &sRight, &sBottom, nullptr, nullptr);
369     else if (direction == Page::PreviousResult)
370         return textPage->findText(u.data(), u.size(), false, true, true, false, sCase, sDiacritics, sAcrossLines, true, sWords, &sLeft, &sTop, &sRight, &sBottom, nullptr, nullptr);
371 
372     return false;
373 }
374 
performMultipleTextSearch(TextPage * textPage,QVector<Unicode> & u,bool sCase,bool sWords,bool sDiacritics,bool sAcrossLines)375 inline QList<QRectF> PageData::performMultipleTextSearch(TextPage *textPage, QVector<Unicode> &u, bool sCase, bool sWords, bool sDiacritics, bool sAcrossLines)
376 {
377     QList<QRectF> results;
378     double sLeft = 0.0, sTop = 0.0, sRight = 0.0, sBottom = 0.0;
379     bool sIgnoredHyphen = false;
380     PDFRectangle continueMatch;
381     continueMatch.x1 = DBL_MAX; // we use this to detect valid return values
382 
383     while (textPage->findText(u.data(), u.size(), false, true, true, false, sCase, sDiacritics, sAcrossLines, false, sWords, &sLeft, &sTop, &sRight, &sBottom, &continueMatch, &sIgnoredHyphen)) {
384         QRectF result;
385 
386         result.setLeft(sLeft);
387         result.setTop(sTop);
388         result.setRight(sRight);
389         result.setBottom(sBottom);
390 
391         results.append(result);
392 
393         if (sAcrossLines && continueMatch.x1 != DBL_MAX) {
394             QRectF resultN;
395 
396             resultN.setLeft(continueMatch.x1);
397             resultN.setTop(continueMatch.y1);
398             resultN.setRight(continueMatch.x2);
399             resultN.setBottom(continueMatch.y1);
400 
401             results.append(resultN);
402             continueMatch.x1 = DBL_MAX;
403         }
404     }
405 
406     return results;
407 }
408 
Page(DocumentData * doc,int index)409 Page::Page(DocumentData *doc, int index)
410 {
411     m_page = new PageData();
412     m_page->index = index;
413     m_page->parentDoc = doc;
414     m_page->page = doc->doc->getPage(m_page->index + 1);
415     m_page->transition = nullptr;
416 }
417 
~Page()418 Page::~Page()
419 {
420     delete m_page->transition;
421     delete m_page;
422 }
423 
424 // Callback that filters out everything but form fields
__anonad0e744c0102(Annot *annot, void *user_data) 425 static auto annotDisplayDecideCbk = [](Annot *annot, void *user_data) {
426     // Hide everything but forms
427     return (annot->getType() == Annot::typeWidget);
428 };
429 
430 // A nullptr, but with the type of a function pointer
431 // Needed to make the ternary operator happy.
432 static bool (*nullAnnotCallBack)(Annot *annot, void *user_data) = nullptr;
433 
__anonad0e744c0202(void *user_data) 434 static auto shouldAbortRenderInternalCallback = [](void *user_data) {
435     OutputDevCallbackHelper *helper = reinterpret_cast<OutputDevCallbackHelper *>(user_data);
436     return helper->shouldAbortRenderCallback(helper->payload);
437 };
438 
__anonad0e744c0302(void *user_data) 439 static auto shouldAbortExtractionInternalCallback = [](void *user_data) {
440     TextExtractionAbortHelper *helper = reinterpret_cast<TextExtractionAbortHelper *>(user_data);
441     return helper->shouldAbortExtractionCallback(helper->payload);
442 };
443 
444 // A nullptr, but with the type of a function pointer
445 // Needed to make the ternary operator happy.
446 static bool (*nullAbortCallBack)(void *user_data) = nullptr;
447 
renderToQPainter(QImageDumpingQPainterOutputDev * qpainter_output,QPainter * painter,PageData * page,double xres,double yres,int x,int y,int w,int h,Page::Rotation rotate,Page::PainterFlags flags)448 static bool renderToQPainter(QImageDumpingQPainterOutputDev *qpainter_output, QPainter *painter, PageData *page, double xres, double yres, int x, int y, int w, int h, Page::Rotation rotate, Page::PainterFlags flags)
449 {
450     const bool savePainter = !(flags & Page::DontSaveAndRestore);
451     if (savePainter)
452         painter->save();
453     if (page->parentDoc->m_hints & Document::Antialiasing)
454         painter->setRenderHint(QPainter::Antialiasing);
455     if (page->parentDoc->m_hints & Document::TextAntialiasing)
456         painter->setRenderHint(QPainter::TextAntialiasing);
457     painter->translate(x == -1 ? 0 : -x, y == -1 ? 0 : -y);
458 
459     qpainter_output->startDoc(page->parentDoc->doc);
460 
461     const bool hideAnnotations = page->parentDoc->m_hints & Document::HideAnnotations;
462 
463     OutputDevCallbackHelper *abortHelper = qpainter_output;
464     page->parentDoc->doc->displayPageSlice(qpainter_output, page->index + 1, xres, yres, (int)rotate * 90, false, true, false, x, y, w, h, abortHelper->shouldAbortRenderCallback ? shouldAbortRenderInternalCallback : nullAbortCallBack,
465                                            abortHelper, (hideAnnotations) ? annotDisplayDecideCbk : nullAnnotCallBack, nullptr, true);
466     if (savePainter)
467         painter->restore();
468     return true;
469 }
470 
renderToImage(double xres,double yres,int x,int y,int w,int h,Rotation rotate) const471 QImage Page::renderToImage(double xres, double yres, int x, int y, int w, int h, Rotation rotate) const
472 {
473     return renderToImage(xres, yres, x, y, w, h, rotate, nullptr, nullptr, QVariant());
474 }
475 
renderToImage(double xres,double yres,int x,int y,int w,int h,Rotation rotate,RenderToImagePartialUpdateFunc partialUpdateCallback,ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback,const QVariant & payload) const476 QImage Page::renderToImage(double xres, double yres, int x, int y, int w, int h, Rotation rotate, RenderToImagePartialUpdateFunc partialUpdateCallback, ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback,
477                            const QVariant &payload) const
478 {
479     return renderToImage(xres, yres, x, y, w, h, rotate, partialUpdateCallback, shouldDoPartialUpdateCallback, nullptr, payload);
480 }
481 
482 // Translate the text hinting settings from poppler-speak to Qt-speak
QFontHintingFromPopplerHinting(int renderHints)483 static QFont::HintingPreference QFontHintingFromPopplerHinting(int renderHints)
484 {
485     QFont::HintingPreference result = QFont::PreferNoHinting;
486 
487     if (renderHints & Document::TextHinting) {
488         result = (renderHints & Document::TextSlightHinting) ? QFont::PreferVerticalHinting : QFont::PreferFullHinting;
489     }
490 
491     return result;
492 }
493 
renderToImage(double xres,double yres,int xPos,int yPos,int w,int h,Rotation rotate,RenderToImagePartialUpdateFunc partialUpdateCallback,ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback,ShouldAbortQueryFunc shouldAbortRenderCallback,const QVariant & payload) const494 QImage Page::renderToImage(double xres, double yres, int xPos, int yPos, int w, int h, Rotation rotate, RenderToImagePartialUpdateFunc partialUpdateCallback, ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback,
495                            ShouldAbortQueryFunc shouldAbortRenderCallback, const QVariant &payload) const
496 {
497     int rotation = (int)rotate * 90;
498     QImage img;
499     switch (m_page->parentDoc->m_backend) {
500     case Poppler::Document::SplashBackend: {
501         SplashColor bgColor;
502         const bool overprintPreview = m_page->parentDoc->m_hints & Document::OverprintPreview ? true : false;
503         if (overprintPreview) {
504             unsigned char c, m, y, k;
505 
506             c = 255 - m_page->parentDoc->paperColor.blue();
507             m = 255 - m_page->parentDoc->paperColor.red();
508             y = 255 - m_page->parentDoc->paperColor.green();
509             k = c;
510             if (m < k) {
511                 k = m;
512             }
513             if (y < k) {
514                 k = y;
515             }
516             bgColor[0] = c - k;
517             bgColor[1] = m - k;
518             bgColor[2] = y - k;
519             bgColor[3] = k;
520             for (int i = 4; i < SPOT_NCOMPS + 4; i++) {
521                 bgColor[i] = 0;
522             }
523         } else {
524             bgColor[0] = m_page->parentDoc->paperColor.blue();
525             bgColor[1] = m_page->parentDoc->paperColor.green();
526             bgColor[2] = m_page->parentDoc->paperColor.red();
527         }
528 
529         const SplashColorMode colorMode = overprintPreview ? splashModeDeviceN8 : splashModeXBGR8;
530 
531         SplashThinLineMode thinLineMode = splashThinLineDefault;
532         if (m_page->parentDoc->m_hints & Document::ThinLineShape)
533             thinLineMode = splashThinLineShape;
534         if (m_page->parentDoc->m_hints & Document::ThinLineSolid)
535             thinLineMode = splashThinLineSolid;
536 
537         const bool ignorePaperColor = m_page->parentDoc->m_hints & Document::IgnorePaperColor;
538 
539         Qt6SplashOutputDev splash_output(colorMode, 4, false, ignorePaperColor, ignorePaperColor ? nullptr : bgColor, true, thinLineMode, overprintPreview);
540 
541         splash_output.setCallbacks(partialUpdateCallback, shouldDoPartialUpdateCallback, shouldAbortRenderCallback, payload);
542 
543         splash_output.setFontAntialias(m_page->parentDoc->m_hints & Document::TextAntialiasing ? true : false);
544         splash_output.setVectorAntialias(m_page->parentDoc->m_hints & Document::Antialiasing ? true : false);
545         splash_output.setFreeTypeHinting(m_page->parentDoc->m_hints & Document::TextHinting ? true : false, m_page->parentDoc->m_hints & Document::TextSlightHinting ? true : false);
546 
547 #ifdef USE_CMS
548         splash_output.setDisplayProfile(m_page->parentDoc->m_displayProfile);
549 #endif
550 
551         splash_output.startDoc(m_page->parentDoc->doc);
552 
553         const bool hideAnnotations = m_page->parentDoc->m_hints & Document::HideAnnotations;
554 
555         OutputDevCallbackHelper *abortHelper = &splash_output;
556         m_page->parentDoc->doc->displayPageSlice(&splash_output, m_page->index + 1, xres, yres, rotation, false, true, false, xPos, yPos, w, h, shouldAbortRenderCallback ? shouldAbortRenderInternalCallback : nullAbortCallBack, abortHelper,
557                                                  (hideAnnotations) ? annotDisplayDecideCbk : nullAnnotCallBack, nullptr, true);
558 
559         img = splash_output.getXBGRImage(true /* takeImageData */);
560         break;
561     }
562     case Poppler::Document::QPainterBackend: {
563         QSize size = pageSize();
564         QImage tmpimg(w == -1 ? qRound(size.width() * xres / 72.0) : w, h == -1 ? qRound(size.height() * yres / 72.0) : h, QImage::Format_ARGB32);
565 
566         QColor bgColor(m_page->parentDoc->paperColor.red(), m_page->parentDoc->paperColor.green(), m_page->parentDoc->paperColor.blue(), m_page->parentDoc->paperColor.alpha());
567 
568         tmpimg.fill(bgColor);
569 
570         QPainter painter(&tmpimg);
571         QImageDumpingQPainterOutputDev qpainter_output(&painter, &tmpimg);
572 
573         qpainter_output.setHintingPreference(QFontHintingFromPopplerHinting(m_page->parentDoc->m_hints));
574 
575 #ifdef USE_CMS
576         qpainter_output.setDisplayProfile(m_page->parentDoc->m_displayProfile);
577 #endif
578 
579         qpainter_output.setCallbacks(partialUpdateCallback, shouldDoPartialUpdateCallback, shouldAbortRenderCallback, payload);
580         renderToQPainter(&qpainter_output, &painter, m_page, xres, yres, xPos, yPos, w, h, rotate, DontSaveAndRestore);
581         painter.end();
582         img = tmpimg;
583         break;
584     }
585     }
586 
587     if (shouldAbortRenderCallback && shouldAbortRenderCallback(payload))
588         return QImage();
589 
590     return img;
591 }
592 
renderToPainter(QPainter * painter,double xres,double yres,int x,int y,int w,int h,Rotation rotate,PainterFlags flags) const593 bool Page::renderToPainter(QPainter *painter, double xres, double yres, int x, int y, int w, int h, Rotation rotate, PainterFlags flags) const
594 {
595     if (!painter)
596         return false;
597 
598     switch (m_page->parentDoc->m_backend) {
599     case Poppler::Document::SplashBackend:
600         return false;
601     case Poppler::Document::QPainterBackend: {
602         QImageDumpingQPainterOutputDev qpainter_output(painter, nullptr);
603 
604         qpainter_output.setHintingPreference(QFontHintingFromPopplerHinting(m_page->parentDoc->m_hints));
605 
606         return renderToQPainter(&qpainter_output, painter, m_page, xres, yres, x, y, w, h, rotate, flags);
607     }
608     }
609     return false;
610 }
611 
thumbnail() const612 QImage Page::thumbnail() const
613 {
614     unsigned char *data = nullptr;
615     int w = 0;
616     int h = 0;
617     int rowstride = 0;
618     bool r = m_page->page->loadThumb(&data, &w, &h, &rowstride);
619     QImage ret;
620     if (r) {
621         // first construct a temporary image with the data got,
622         // then force a copy of it so we can free the raw thumbnail data
623         ret = QImage(data, w, h, rowstride, QImage::Format_RGB888).copy();
624         gfree(data);
625     }
626     return ret;
627 }
628 
text(const QRectF & r,TextLayout textLayout) const629 QString Page::text(const QRectF &r, TextLayout textLayout) const
630 {
631     TextOutputDev *output_dev;
632     GooString *s;
633     QString result;
634 
635     const bool rawOrder = textLayout == RawOrderLayout;
636     output_dev = new TextOutputDev(nullptr, false, 0, rawOrder, false);
637     m_page->parentDoc->doc->displayPageSlice(output_dev, m_page->index + 1, 72, 72, 0, false, true, false, -1, -1, -1, -1, nullptr, nullptr, nullptr, nullptr, true);
638     if (r.isNull()) {
639         const PDFRectangle *rect = m_page->page->getCropBox();
640         s = output_dev->getText(rect->x1, rect->y1, rect->x2, rect->y2);
641     } else {
642         s = output_dev->getText(r.left(), r.top(), r.right(), r.bottom());
643     }
644 
645     result = QString::fromUtf8(s->c_str());
646 
647     delete output_dev;
648     delete s;
649     return result;
650 }
651 
text(const QRectF & r) const652 QString Page::text(const QRectF &r) const
653 {
654     return text(r, PhysicalLayout);
655 }
656 
search(const QString & text,double & sLeft,double & sTop,double & sRight,double & sBottom,SearchDirection direction,SearchFlags flags,Rotation rotate) const657 bool Page::search(const QString &text, double &sLeft, double &sTop, double &sRight, double &sBottom, SearchDirection direction, SearchFlags flags, Rotation rotate) const
658 {
659     const bool sCase = flags.testFlag(IgnoreCase) ? false : true;
660     const bool sWords = flags.testFlag(WholeWords) ? true : false;
661     const bool sDiacritics = flags.testFlag(IgnoreDiacritics) ? true : false;
662     const bool sAcrossLines = flags.testFlag(AcrossLines) ? true : false;
663 
664     QVector<Unicode> u;
665     TextPage *textPage = m_page->prepareTextSearch(text, rotate, &u);
666 
667     const bool found = m_page->performSingleTextSearch(textPage, u, sLeft, sTop, sRight, sBottom, direction, sCase, sWords, sDiacritics, sAcrossLines);
668 
669     textPage->decRefCnt();
670 
671     return found;
672 }
673 
search(const QString & text,SearchFlags flags,Rotation rotate) const674 QList<QRectF> Page::search(const QString &text, SearchFlags flags, Rotation rotate) const
675 {
676     const bool sCase = flags.testFlag(IgnoreCase) ? false : true;
677     const bool sWords = flags.testFlag(WholeWords) ? true : false;
678     const bool sDiacritics = flags.testFlag(IgnoreDiacritics) ? true : false;
679     const bool sAcrossLines = flags.testFlag(AcrossLines) ? true : false;
680 
681     QVector<Unicode> u;
682     TextPage *textPage = m_page->prepareTextSearch(text, rotate, &u);
683 
684     QList<QRectF> results = m_page->performMultipleTextSearch(textPage, u, sCase, sWords, sDiacritics, sAcrossLines);
685 
686     textPage->decRefCnt();
687 
688     return results;
689 }
690 
textList(Rotation rotate) const691 std::vector<std::unique_ptr<TextBox>> Page::textList(Rotation rotate) const
692 {
693     return textList(rotate, nullptr, QVariant());
694 }
695 
textList(Rotation rotate,ShouldAbortQueryFunc shouldAbortExtractionCallback,const QVariant & closure) const696 std::vector<std::unique_ptr<TextBox>> Page::textList(Rotation rotate, ShouldAbortQueryFunc shouldAbortExtractionCallback, const QVariant &closure) const
697 {
698     std::vector<std::unique_ptr<TextBox>> output_list;
699 
700     TextOutputDev output_dev(nullptr, false, 0, false, false);
701 
702     int rotation = (int)rotate * 90;
703 
704     TextExtractionAbortHelper abortHelper(shouldAbortExtractionCallback, closure);
705     m_page->parentDoc->doc->displayPageSlice(&output_dev, m_page->index + 1, 72, 72, rotation, false, false, false, -1, -1, -1, -1, shouldAbortExtractionCallback ? shouldAbortExtractionInternalCallback : nullAbortCallBack, &abortHelper,
706                                              nullptr, nullptr, true);
707 
708     std::unique_ptr<TextWordList> word_list = output_dev.makeWordList();
709 
710     if (shouldAbortExtractionCallback && shouldAbortExtractionCallback(closure)) {
711         return output_list;
712     }
713 
714     QHash<const TextWord *, TextBox *> wordBoxMap;
715 
716     output_list.reserve(word_list->getLength());
717     for (int i = 0; i < word_list->getLength(); i++) {
718         TextWord *word = word_list->get(i);
719         GooString *gooWord = word->getText();
720         QString string = QString::fromUtf8(gooWord->c_str());
721         delete gooWord;
722         double xMin, yMin, xMax, yMax;
723         word->getBBox(&xMin, &yMin, &xMax, &yMax);
724 
725         auto text_box = std::make_unique<TextBox>(string, QRectF(xMin, yMin, xMax - xMin, yMax - yMin));
726         text_box->m_data->hasSpaceAfter = word->hasSpaceAfter() == true;
727         text_box->m_data->charBBoxes.reserve(word->getLength());
728         for (int j = 0; j < word->getLength(); ++j) {
729             word->getCharBBox(j, &xMin, &yMin, &xMax, &yMax);
730             text_box->m_data->charBBoxes.append(QRectF(xMin, yMin, xMax - xMin, yMax - yMin));
731         }
732 
733         wordBoxMap.insert(word, text_box.get());
734 
735         output_list.push_back(std::move(text_box));
736     }
737 
738     for (int i = 0; i < word_list->getLength(); i++) {
739         TextWord *word = word_list->get(i);
740         TextBox *text_box = wordBoxMap.value(word);
741         text_box->m_data->nextWord = wordBoxMap.value(word->nextWord());
742     }
743 
744     return output_list;
745 }
746 
transition() const747 PageTransition *Page::transition() const
748 {
749     if (!m_page->transition) {
750         Object o = m_page->page->getTrans();
751         PageTransitionParams params;
752         params.dictObj = &o;
753         if (params.dictObj->isDict())
754             m_page->transition = new PageTransition(params);
755     }
756     return m_page->transition;
757 }
758 
action(PageAction act) const759 std::unique_ptr<Link> Page::action(PageAction act) const
760 {
761     if (act == Page::Opening || act == Page::Closing) {
762         Object o = m_page->page->getActions();
763         if (!o.isDict()) {
764             return nullptr;
765         }
766         Dict *dict = o.getDict();
767         const char *key = act == Page::Opening ? "O" : "C";
768         Object o2 = dict->lookup((char *)key);
769         std::unique_ptr<::LinkAction> lact = ::LinkAction::parseAction(&o2, m_page->parentDoc->doc->getCatalog()->getBaseURI());
770         if (lact != nullptr) {
771             return m_page->convertLinkActionToLink(lact.get(), QRectF());
772         }
773     }
774     return nullptr;
775 }
776 
pageSizeF() const777 QSizeF Page::pageSizeF() const
778 {
779     Page::Orientation orient = orientation();
780     if ((Page::Landscape == orient) || (Page::Seascape == orient)) {
781         return QSizeF(m_page->page->getCropHeight(), m_page->page->getCropWidth());
782     } else {
783         return QSizeF(m_page->page->getCropWidth(), m_page->page->getCropHeight());
784     }
785 }
786 
pageSize() const787 QSize Page::pageSize() const
788 {
789     return pageSizeF().toSize();
790 }
791 
orientation() const792 Page::Orientation Page::orientation() const
793 {
794     const int rotation = m_page->page->getRotate();
795     switch (rotation) {
796     case 90:
797         return Page::Landscape;
798         break;
799     case 180:
800         return Page::UpsideDown;
801         break;
802     case 270:
803         return Page::Seascape;
804         break;
805     default:
806         return Page::Portrait;
807     }
808 }
809 
defaultCTM(double * CTM,double dpiX,double dpiY,int rotate,bool upsideDown)810 void Page::defaultCTM(double *CTM, double dpiX, double dpiY, int rotate, bool upsideDown)
811 {
812     m_page->page->getDefaultCTM(CTM, dpiX, dpiY, rotate, false, upsideDown);
813 }
814 
links() const815 std::vector<std::unique_ptr<Link>> Page::links() const
816 {
817     LinkExtractorOutputDev link_dev(m_page);
818     m_page->parentDoc->doc->processLinks(&link_dev, m_page->index + 1);
819     return link_dev.links();
820 }
821 
annotations() const822 std::vector<std::unique_ptr<Annotation>> Page::annotations() const
823 {
824     return AnnotationPrivate::findAnnotations(m_page->page, m_page->parentDoc, QSet<Annotation::SubType>());
825 }
826 
annotations(const QSet<Annotation::SubType> & subtypes) const827 std::vector<std::unique_ptr<Annotation>> Page::annotations(const QSet<Annotation::SubType> &subtypes) const
828 {
829     return AnnotationPrivate::findAnnotations(m_page->page, m_page->parentDoc, subtypes);
830 }
831 
addAnnotation(const Annotation * ann)832 void Page::addAnnotation(const Annotation *ann)
833 {
834     AnnotationPrivate::addAnnotationToPage(m_page->page, m_page->parentDoc, ann);
835 }
836 
removeAnnotation(const Annotation * ann)837 void Page::removeAnnotation(const Annotation *ann)
838 {
839     AnnotationPrivate::removeAnnotationFromPage(m_page->page, ann);
840 }
841 
formFields() const842 std::vector<std::unique_ptr<FormField>> Page::formFields() const
843 {
844     std::vector<std::unique_ptr<FormField>> fields;
845     ::Page *p = m_page->page;
846     const std::unique_ptr<FormPageWidgets> form = p->getFormWidgets();
847     int formcount = form->getNumWidgets();
848     for (int i = 0; i < formcount; ++i) {
849         ::FormWidget *fm = form->getWidget(i);
850         std::unique_ptr<FormField> ff;
851         switch (fm->getType()) {
852         case formButton: {
853             ff = std::make_unique<FormFieldButton>(m_page->parentDoc, p, static_cast<FormWidgetButton *>(fm));
854         } break;
855 
856         case formText: {
857             ff = std::make_unique<FormFieldText>(m_page->parentDoc, p, static_cast<FormWidgetText *>(fm));
858         } break;
859 
860         case formChoice: {
861             ff = std::make_unique<FormFieldChoice>(m_page->parentDoc, p, static_cast<FormWidgetChoice *>(fm));
862         } break;
863 
864         case formSignature: {
865             ff = std::make_unique<FormFieldSignature>(m_page->parentDoc, p, static_cast<FormWidgetSignature *>(fm));
866         } break;
867 
868         default:;
869         }
870 
871         if (ff)
872             fields.push_back(std::move(ff));
873     }
874 
875     return fields;
876 }
877 
duration() const878 double Page::duration() const
879 {
880     return m_page->page->getDuration();
881 }
882 
label() const883 QString Page::label() const
884 {
885     GooString goo;
886     if (!m_page->parentDoc->doc->getCatalog()->indexToLabel(m_page->index, &goo))
887         return QString();
888 
889     return UnicodeParsedString(&goo);
890 }
891 
index() const892 int Page::index() const
893 {
894     return m_page->index;
895 }
896 
897 }
898