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