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 | |
80 | namespace Poppler { |
81 | |
82 | class |
83 | { |
84 | public: |
85 | (Page::ShouldAbortQueryFunc shouldAbortCallback, const QVariant &payloadA) |
86 | { |
87 | shouldAbortExtractionCallback = shouldAbortCallback; |
88 | payload = payloadA; |
89 | } |
90 | |
91 | Page::ShouldAbortQueryFunc = nullptr; |
92 | QVariant ; |
93 | }; |
94 | |
95 | class OutputDevCallbackHelper |
96 | { |
97 | public: |
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 | |
112 | class Qt6SplashOutputDev : public SplashOutputDev, public OutputDevCallbackHelper |
113 | { |
114 | public: |
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 | |
173 | private: |
174 | bool ignorePaperColor; |
175 | }; |
176 | |
177 | Qt6SplashOutputDev::~Qt6SplashOutputDev() = default; |
178 | |
179 | class QImageDumpingQPainterOutputDev : public QPainterOutputDev, public OutputDevCallbackHelper |
180 | { |
181 | public: |
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 | |
192 | private: |
193 | QImage *image; |
194 | }; |
195 | |
196 | QImageDumpingQPainterOutputDev::~QImageDumpingQPainterOutputDev() = default; |
197 | |
198 | std::unique_ptr<Link> PageData::convertLinkActionToLink(::LinkAction *a, const QRectF &linkArea) |
199 | { |
200 | return convertLinkActionToLink(a, parentDoc, linkArea); |
201 | } |
202 | |
203 | std::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 | |
356 | inline 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 | |
370 | inline bool PageData::(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 | |
384 | inline QList<QRectF> PageData::(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 | |
418 | Page::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 | |
427 | Page::~Page() |
428 | { |
429 | delete m_page->transition; |
430 | delete m_page; |
431 | } |
432 | |
433 | // Callback that filters out everything but form fields |
434 | static 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. |
441 | static bool (*nullAnnotCallBack)(Annot *annot, void *user_data) = nullptr; |
442 | |
443 | static auto shouldAbortRenderInternalCallback = [](void *user_data) { |
444 | OutputDevCallbackHelper *helper = reinterpret_cast<OutputDevCallbackHelper *>(user_data); |
445 | return helper->shouldAbortRenderCallback(helper->payload); |
446 | }; |
447 | |
448 | static auto = [](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. |
455 | static bool (*nullAbortCallBack)(void *user_data) = nullptr; |
456 | |
457 | 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) |
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 | |
484 | QImage 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 | |
489 | QImage 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 |
496 | static 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 | |
507 | QImage 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 | |
609 | bool 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 | |
629 | QImage 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 | |
646 | QString 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 | |
673 | QString Page::text(const QRectF &r) const |
674 | { |
675 | return text(r, textLayout: PhysicalLayout); |
676 | } |
677 | |
678 | bool 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 | |
695 | QList<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 | |
712 | std::vector<std::unique_ptr<TextBox>> Page::textList(Rotation rotate) const |
713 | { |
714 | return textList(rotate, shouldAbortExtractionCallback: nullptr, closure: QVariant()); |
715 | } |
716 | |
717 | std::vector<std::unique_ptr<TextBox>> Page::textList(Rotation rotate, ShouldAbortQueryFunc , 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 | |
768 | PageTransition *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 | |
781 | std::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 | |
799 | QSizeF 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 | |
809 | QSize Page::pageSize() const |
810 | { |
811 | return pageSizeF().toSize(); |
812 | } |
813 | |
814 | Page::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 | |
832 | void 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 | |
837 | std::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 | |
844 | std::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 | |
849 | std::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 | |
854 | void Page::addAnnotation(const Annotation *ann) |
855 | { |
856 | AnnotationPrivate::addAnnotationToPage(pdfPage: m_page->page, doc: m_page->parentDoc, ann); |
857 | } |
858 | |
859 | void Page::removeAnnotation(const Annotation *ann) |
860 | { |
861 | AnnotationPrivate::removeAnnotationFromPage(pdfPage: m_page->page, ann); |
862 | } |
863 | |
864 | std::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 | |
901 | double Page::duration() const |
902 | { |
903 | return m_page->page->getDuration(); |
904 | } |
905 | |
906 | QString 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 | |
916 | int Page::index() const |
917 | { |
918 | return m_page->index; |
919 | } |
920 | |
921 | } |
922 | |