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

source code of poppler/qt6/src/poppler-page.cc