| /* poppler-page.cc: qt interface to poppler |
| * Copyright (C) 2005, Net Integration Technologies, Inc. |
| * Copyright (C) 2005, Brad Hards <bradh@frogmouth.net> |
| * Copyright (C) 2005-2022, Albert Astals Cid <aacid@kde.org> |
| * Copyright (C) 2005, Stefan Kebekus <stefan.kebekus@math.uni-koeln.de> |
| * Copyright (C) 2006-2011, Pino Toscano <pino@kde.org> |
| * Copyright (C) 2008 Carlos Garcia Campos <carlosgc@gnome.org> |
| * Copyright (C) 2009 Shawn Rutledge <shawn.t.rutledge@gmail.com> |
| * Copyright (C) 2010, 2012, Guillermo Amaral <gamaral@kdab.com> |
| * Copyright (C) 2010 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp> |
| * Copyright (C) 2010 Matthias Fauconneau <matthias.fauconneau@gmail.com> |
| * Copyright (C) 2010 Hib Eris <hib@hiberis.nl> |
| * Copyright (C) 2012 Tobias Koenig <tokoe@kdab.com> |
| * Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it> |
| * Copyright (C) 2012, 2015 Adam Reichold <adamreichold@myopera.com> |
| * Copyright (C) 2012, 2013 Thomas Freitag <Thomas.Freitag@alfa.de> |
| * Copyright (C) 2015 William Bader <williambader@hotmail.com> |
| * Copyright (C) 2016 Arseniy Lartsev <arseniy@alumni.chalmers.se> |
| * Copyright (C) 2016, Hanno Meyer-Thurow <h.mth@web.de> |
| * Copyright (C) 2017-2021, Oliver Sander <oliver.sander@tu-dresden.de> |
| * Copyright (C) 2017 Adrian Johnson <ajohnson@redneon.com> |
| * 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 |
| * Copyright (C) 2018 Intevation GmbH <intevation@intevation.de> |
| * Copyright (C) 2018, Tobias Deiminger <haxtibal@posteo.de> |
| * Copyright (C) 2018, 2021 Nelson Benítez León <nbenitezl@gmail.com> |
| * Copyright (C) 2020 Philipp Knechtges <philipp-dev@knechtges.com> |
| * Copyright (C) 2021 Hubert Figuiere <hub@figuiere.net> |
| * Copyright (C) 2021 Thomas Huxhorn <thomas.huxhorn@web.de> |
| * Copyright (C) 2023 Kevin Ottens <kevin.ottens@enioka.com>. Work sponsored by De Bortoli Wines |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2, or (at your option) |
| * any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. |
| */ |
| |
| #include <poppler-qt6.h> |
| |
| #include <QtCore/QHash> |
| #include <QtCore/QMap> |
| #include <QtCore/QVarLengthArray> |
| #include <QtGui/QImage> |
| #include <QtGui/QPainter> |
| #include <QDebug> |
| |
| #include <config.h> |
| #include <cfloat> |
| #include <poppler-config.h> |
| #include <PDFDoc.h> |
| #include <Catalog.h> |
| #include <Form.h> |
| #include <ErrorCodes.h> |
| #include <TextOutputDev.h> |
| #include <Annot.h> |
| #include <Link.h> |
| #include <QPainterOutputDev.h> |
| #include <Rendition.h> |
| #include <SplashOutputDev.h> |
| #include <splash/SplashBitmap.h> |
| |
| #include "poppler-private.h" |
| #include "poppler-page-transition-private.h" |
| #include "poppler-page-private.h" |
| #include "poppler-link-extractor-private.h" |
| #include "poppler-link-private.h" |
| #include "poppler-annotation-private.h" |
| #include "poppler-form.h" |
| #include "poppler-media.h" |
| |
| namespace Poppler { |
| |
| class TextExtractionAbortHelper |
| { |
| public: |
| TextExtractionAbortHelper(Page::ShouldAbortQueryFunc shouldAbortCallback, const QVariant &payloadA) |
| { |
| shouldAbortExtractionCallback = shouldAbortCallback; |
| payload = payloadA; |
| } |
| |
| Page::ShouldAbortQueryFunc shouldAbortExtractionCallback = nullptr; |
| QVariant payload; |
| }; |
| |
| class OutputDevCallbackHelper |
| { |
| public: |
| void setCallbacks(Page::RenderToImagePartialUpdateFunc callback, Page::ShouldRenderToImagePartialQueryFunc shouldDoCallback, Page::ShouldAbortQueryFunc shouldAbortCallback, const QVariant &payloadA) |
| { |
| partialUpdateCallback = callback; |
| shouldDoPartialUpdateCallback = shouldDoCallback; |
| shouldAbortRenderCallback = shouldAbortCallback; |
| payload = payloadA; |
| } |
| |
| Page::RenderToImagePartialUpdateFunc partialUpdateCallback = nullptr; |
| Page::ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback = nullptr; |
| Page::ShouldAbortQueryFunc shouldAbortRenderCallback = nullptr; |
| QVariant payload; |
| }; |
| |
| class Qt6SplashOutputDev : public SplashOutputDev, public OutputDevCallbackHelper |
| { |
| public: |
| Qt6SplashOutputDev(SplashColorMode colorModeA, int bitmapRowPadA, bool reverseVideoA, bool ignorePaperColorA, SplashColorPtr paperColorA, bool bitmapTopDownA, SplashThinLineMode thinLineMode, bool overprintPreviewA) |
| : SplashOutputDev(colorModeA, bitmapRowPadA, reverseVideoA, paperColorA, bitmapTopDownA, thinLineMode, overprintPreviewA), ignorePaperColor(ignorePaperColorA) |
| { |
| } |
| |
| ~Qt6SplashOutputDev() override; |
| |
| void dump() override |
| { |
| if (partialUpdateCallback && shouldDoPartialUpdateCallback && shouldDoPartialUpdateCallback(payload)) { |
| partialUpdateCallback(getXBGRImage(false /* takeImageData */), payload); |
| } |
| } |
| |
| QImage getXBGRImage(bool takeImageData) |
| { |
| SplashBitmap *b = getBitmap(); |
| |
| // If we use DeviceN8, convert to XBGR8. |
| // If requested, also transfer Splash's internal alpha channel. |
| const SplashBitmap::ConversionMode mode = ignorePaperColor ? SplashBitmap::conversionAlphaPremultiplied : SplashBitmap::conversionOpaque; |
| |
| const QImage::Format format = ignorePaperColor ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32; |
| |
| if (b->convertToXBGR(mode)) { |
| const int bw = b->getWidth(); |
| const int bh = b->getHeight(); |
| const int brs = b->getRowSize(); |
| |
| SplashColorPtr data = takeImageData ? b->takeData() : b->getDataPtr(); |
| |
| if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { |
| // Convert byte order from RGBX to XBGR. |
| for (int i = 0; i < bh; ++i) { |
| for (int j = 0; j < bw; ++j) { |
| SplashColorPtr pixel = &data[i * brs + j]; |
| |
| qSwap(pixel[0], pixel[3]); |
| qSwap(pixel[1], pixel[2]); |
| } |
| } |
| } |
| |
| if (takeImageData) { |
| // Construct a Qt image holding (and also owning) the raw bitmap data. |
| QImage i(data, bw, bh, brs, format, gfree, data); |
| if (i.isNull()) { |
| gfree(data); |
| } |
| return i; |
| } else { |
| return QImage(data, bw, bh, brs, format).copy(); |
| } |
| } |
| |
| return QImage(); |
| } |
| |
| private: |
| bool ignorePaperColor; |
| }; |
| |
| Qt6SplashOutputDev::~Qt6SplashOutputDev() = default; |
| |
| class QImageDumpingQPainterOutputDev : public QPainterOutputDev, public OutputDevCallbackHelper |
| { |
| public: |
| QImageDumpingQPainterOutputDev(QPainter *painter, QImage *i) : QPainterOutputDev(painter), image(i) { } |
| ~QImageDumpingQPainterOutputDev() override; |
| |
| void dump() override |
| { |
| if (partialUpdateCallback && shouldDoPartialUpdateCallback && shouldDoPartialUpdateCallback(payload)) { |
| partialUpdateCallback(*image, payload); |
| } |
| } |
| |
| private: |
| QImage *image; |
| }; |
| |
| QImageDumpingQPainterOutputDev::~QImageDumpingQPainterOutputDev() = default; |
| |
| std::unique_ptr<Link> PageData::convertLinkActionToLink(::LinkAction *a, const QRectF &linkArea) |
| { |
| return convertLinkActionToLink(a, parentDoc, linkArea); |
| } |
| |
| std::unique_ptr<Link> PageData::convertLinkActionToLink(::LinkAction *a, DocumentData *parentDoc, const QRectF &linkArea) |
| { |
| if (!a) { |
| return nullptr; |
| } |
| |
| std::unique_ptr<Link> popplerLink; |
| switch (a->getKind()) { |
| case actionGoTo: { |
| LinkGoTo *g = (LinkGoTo *)a; |
| const LinkDestinationData ldd(g->getDest(), g->getNamedDest(), parentDoc, false); |
| // create link: no ext file, namedDest, object pointer |
| popplerLink = std::make_unique<LinkGoto>(linkArea, QString(), LinkDestination(ldd)); |
| } break; |
| |
| case actionGoToR: { |
| LinkGoToR *g = (LinkGoToR *)a; |
| // copy link file |
| const QString fileName = UnicodeParsedString(g->getFileName()); |
| const LinkDestinationData ldd(g->getDest(), g->getNamedDest(), parentDoc, !fileName.isEmpty()); |
| // create link: fileName, namedDest, object pointer |
| popplerLink = std::make_unique<LinkGoto>(linkArea, fileName, LinkDestination(ldd)); |
| } break; |
| |
| case actionLaunch: { |
| LinkLaunch *e = (LinkLaunch *)a; |
| const GooString *p = e->getParams(); |
| popplerLink = std::make_unique<LinkExecute>(linkArea, e->getFileName()->c_str(), p ? p->c_str() : nullptr); |
| } break; |
| |
| case actionNamed: { |
| const std::string &name = ((LinkNamed *)a)->getName(); |
| if (name == "NextPage") { |
| popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::PageNext); |
| } else if (name == "PrevPage") { |
| popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::PagePrev); |
| } else if (name == "FirstPage") { |
| popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::PageFirst); |
| } else if (name == "LastPage") { |
| popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::PageLast); |
| } else if (name == "GoBack") { |
| popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::HistoryBack); |
| } else if (name == "GoForward") { |
| popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::HistoryForward); |
| } else if (name == "Quit") { |
| popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::Quit); |
| } else if (name == "GoToPage") { |
| popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::GoToPage); |
| } else if (name == "Find") { |
| popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::Find); |
| } else if (name == "FullScreen") { |
| popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::Presentation); |
| } else if (name == "Print") { |
| popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::Print); |
| } else if (name == "Close") { |
| // acroread closes the document always, doesn't care whether |
| // its presentation mode or not |
| // popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::EndPresentation); |
| popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::Close); |
| } else if (name == "SaveAs") { |
| popplerLink = std::make_unique<LinkAction>(linkArea, LinkAction::SaveAs); |
| } else { |
| qWarning() << "Unhandled action name" << name.c_str(); |
| } |
| } break; |
| |
| case actionURI: { |
| popplerLink = std::make_unique<LinkBrowse>(linkArea, ((LinkURI *)a)->getURI().c_str()); |
| } break; |
| |
| case actionSound: { |
| ::LinkSound *ls = (::LinkSound *)a; |
| popplerLink = std::make_unique<LinkSound>(linkArea, ls->getVolume(), ls->getSynchronous(), ls->getRepeat(), ls->getMix(), new SoundObject(ls->getSound())); |
| } break; |
| |
| case actionJavaScript: { |
| ::LinkJavaScript *ljs = (::LinkJavaScript *)a; |
| popplerLink = std::make_unique<LinkJavaScript>(linkArea, UnicodeParsedString(ljs->getScript())); |
| } break; |
| |
| case actionMovie: { |
| ::LinkMovie *lm = (::LinkMovie *)a; |
| |
| const QString title = (lm->hasAnnotTitle() ? UnicodeParsedString(lm->getAnnotTitle()) : QString()); |
| |
| Ref reference = Ref::INVALID(); |
| if (lm->hasAnnotRef()) { |
| reference = *lm->getAnnotRef(); |
| } |
| |
| LinkMovie::Operation operation = LinkMovie::Play; |
| switch (lm->getOperation()) { |
| case ::LinkMovie::operationTypePlay: |
| operation = LinkMovie::Play; |
| break; |
| case ::LinkMovie::operationTypePause: |
| operation = LinkMovie::Pause; |
| break; |
| case ::LinkMovie::operationTypeResume: |
| operation = LinkMovie::Resume; |
| break; |
| case ::LinkMovie::operationTypeStop: |
| operation = LinkMovie::Stop; |
| break; |
| }; |
| |
| popplerLink = std::make_unique<LinkMovie>(linkArea, operation, title, reference); |
| } break; |
| |
| case actionRendition: { |
| ::LinkRendition *lrn = (::LinkRendition *)a; |
| |
| Ref reference = Ref::INVALID(); |
| if (lrn->hasScreenAnnot()) { |
| reference = lrn->getScreenAnnot(); |
| } |
| |
| popplerLink = std::make_unique<LinkRendition>(linkArea, lrn->getMedia() ? lrn->getMedia()->copy() : nullptr, lrn->getOperation(), UnicodeParsedString(lrn->getScript()), reference); |
| } break; |
| |
| case actionOCGState: { |
| ::LinkOCGState *plocg = (::LinkOCGState *)a; |
| |
| LinkOCGStatePrivate *locgp = new LinkOCGStatePrivate(linkArea, plocg->getStateList(), plocg->getPreserveRB()); |
| popplerLink = std::make_unique<LinkOCGState>(locgp); |
| } break; |
| |
| case actionHide: { |
| ::LinkHide *lh = (::LinkHide *)a; |
| |
| LinkHidePrivate *lhp = new LinkHidePrivate(linkArea, lh->hasTargetName() ? UnicodeParsedString(lh->getTargetName()) : QString(), lh->isShowAction()); |
| popplerLink = std::make_unique<LinkHide>(lhp); |
| } break; |
| |
| case actionResetForm: |
| // Not handled in Qt6 front-end yet |
| break; |
| |
| case actionUnknown: |
| break; |
| } |
| |
| if (popplerLink) { |
| std::vector<std::unique_ptr<Link>> links; |
| for (const std::unique_ptr<::LinkAction> &nextAction : a->nextActions()) { |
| links.push_back(convertLinkActionToLink(nextAction.get(), parentDoc, linkArea)); |
| } |
| LinkPrivate::get(popplerLink.get())->nextLinks = std::move(links); |
| } |
| |
| return popplerLink; |
| } |
| |
| inline TextPage *PageData::prepareTextSearch(const QString &text, Page::Rotation rotate, QVector<Unicode> *u) |
| { |
| *u = text.toUcs4(); |
| |
| const int rotation = (int)rotate * 90; |
| |
| // fetch ourselves a textpage |
| TextOutputDev td(nullptr, true, 0, false, false); |
| parentDoc->doc->displayPage(&td, index + 1, 72, 72, rotation, false, true, false, nullptr, nullptr, nullptr, nullptr, true); |
| TextPage *textPage = td.takeText(); |
| |
| return textPage; |
| } |
| |
| 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, |
| bool sAcrossLines) |
| { |
| if (direction == Page::FromTop) { |
| return textPage->findText(u.data(), u.size(), true, true, false, false, sCase, sDiacritics, sAcrossLines, false, sWords, &sLeft, &sTop, &sRight, &sBottom, nullptr, nullptr); |
| } else if (direction == Page::NextResult) { |
| return textPage->findText(u.data(), u.size(), false, true, true, false, sCase, sDiacritics, sAcrossLines, false, sWords, &sLeft, &sTop, &sRight, &sBottom, nullptr, nullptr); |
| } else if (direction == Page::PreviousResult) { |
| return textPage->findText(u.data(), u.size(), false, true, true, false, sCase, sDiacritics, sAcrossLines, true, sWords, &sLeft, &sTop, &sRight, &sBottom, nullptr, nullptr); |
| } |
| |
| return false; |
| } |
| |
| inline QList<QRectF> PageData::performMultipleTextSearch(TextPage *textPage, QVector<Unicode> &u, bool sCase, bool sWords, bool sDiacritics, bool sAcrossLines) |
| { |
| QList<QRectF> results; |
| double sLeft = 0.0, sTop = 0.0, sRight = 0.0, sBottom = 0.0; |
| bool sIgnoredHyphen = false; |
| PDFRectangle continueMatch; |
| continueMatch.x1 = DBL_MAX; // we use this to detect valid return values |
| |
| while (textPage->findText(u.data(), u.size(), false, true, true, false, sCase, sDiacritics, sAcrossLines, false, sWords, &sLeft, &sTop, &sRight, &sBottom, &continueMatch, &sIgnoredHyphen)) { |
| QRectF result; |
| |
| result.setLeft(sLeft); |
| result.setTop(sTop); |
| result.setRight(sRight); |
| result.setBottom(sBottom); |
| |
| results.append(result); |
| |
| if (sAcrossLines && continueMatch.x1 != DBL_MAX) { |
| QRectF resultN; |
| |
| resultN.setLeft(continueMatch.x1); |
| resultN.setTop(continueMatch.y1); |
| resultN.setRight(continueMatch.x2); |
| resultN.setBottom(continueMatch.y1); |
| |
| results.append(resultN); |
| continueMatch.x1 = DBL_MAX; |
| } |
| } |
| |
| return results; |
| } |
| |
| Page::Page(DocumentData *doc, int index) |
| { |
| m_page = new PageData(); |
| m_page->index = index; |
| m_page->parentDoc = doc; |
| m_page->page = doc->doc->getPage(m_page->index + 1); |
| m_page->transition = nullptr; |
| } |
| |
| Page::~Page() |
| { |
| delete m_page->transition; |
| delete m_page; |
| } |
| |
| // Callback that filters out everything but form fields |
| static auto annotDisplayDecideCbk = [](Annot *annot, void *user_data) { |
| // Hide everything but forms |
| return (annot->getType() == Annot::typeWidget); |
| }; |
| |
| // A nullptr, but with the type of a function pointer |
| // Needed to make the ternary operator happy. |
| static bool (*nullAnnotCallBack)(Annot *annot, void *user_data) = nullptr; |
| |
| static auto shouldAbortRenderInternalCallback = [](void *user_data) { |
| OutputDevCallbackHelper *helper = reinterpret_cast<OutputDevCallbackHelper *>(user_data); |
| return helper->shouldAbortRenderCallback(helper->payload); |
| }; |
| |
| static auto shouldAbortExtractionInternalCallback = [](void *user_data) { |
| TextExtractionAbortHelper *helper = reinterpret_cast<TextExtractionAbortHelper *>(user_data); |
| return helper->shouldAbortExtractionCallback(helper->payload); |
| }; |
| |
| // A nullptr, but with the type of a function pointer |
| // Needed to make the ternary operator happy. |
| static bool (*nullAbortCallBack)(void *user_data) = nullptr; |
| |
| 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) |
| { |
| const bool savePainter = !(flags & Page::DontSaveAndRestore); |
| if (savePainter) { |
| painter->save(); |
| } |
| if (page->parentDoc->m_hints & Document::Antialiasing) { |
| painter->setRenderHint(QPainter::Antialiasing); |
| } |
| if (page->parentDoc->m_hints & Document::TextAntialiasing) { |
| painter->setRenderHint(QPainter::TextAntialiasing); |
| } |
| painter->translate(x == -1 ? 0 : -x, y == -1 ? 0 : -y); |
| |
| qpainter_output->startDoc(page->parentDoc->doc); |
| |
| const bool hideAnnotations = page->parentDoc->m_hints & Document::HideAnnotations; |
| |
| OutputDevCallbackHelper *abortHelper = qpainter_output; |
| 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, |
| abortHelper, (hideAnnotations) ? annotDisplayDecideCbk : nullAnnotCallBack, nullptr, true); |
| if (savePainter) { |
| painter->restore(); |
| } |
| return true; |
| } |
| |
| QImage Page::renderToImage(double xres, double yres, int x, int y, int w, int h, Rotation rotate) const |
| { |
| return renderToImage(xres, yres, x, y, w, h, rotate, nullptr, nullptr, QVariant()); |
| } |
| |
| QImage Page::renderToImage(double xres, double yres, int x, int y, int w, int h, Rotation rotate, RenderToImagePartialUpdateFunc partialUpdateCallback, ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback, |
| const QVariant &payload) const |
| { |
| return renderToImage(xres, yres, x, y, w, h, rotate, partialUpdateCallback, shouldDoPartialUpdateCallback, nullptr, payload); |
| } |
| |
| // Translate the text hinting settings from poppler-speak to Qt-speak |
| static QFont::HintingPreference QFontHintingFromPopplerHinting(int renderHints) |
| { |
| QFont::HintingPreference result = QFont::PreferNoHinting; |
| |
| if (renderHints & Document::TextHinting) { |
| result = (renderHints & Document::TextSlightHinting) ? QFont::PreferVerticalHinting : QFont::PreferFullHinting; |
| } |
| |
| return result; |
| } |
| |
| QImage Page::renderToImage(double xres, double yres, int xPos, int yPos, int w, int h, Rotation rotate, RenderToImagePartialUpdateFunc partialUpdateCallback, ShouldRenderToImagePartialQueryFunc shouldDoPartialUpdateCallback, |
| ShouldAbortQueryFunc shouldAbortRenderCallback, const QVariant &payload) const |
| { |
| int rotation = (int)rotate * 90; |
| QImage img; |
| switch (m_page->parentDoc->m_backend) { |
| case Poppler::Document::SplashBackend: { |
| SplashColor bgColor; |
| const bool overprintPreview = m_page->parentDoc->m_hints & Document::OverprintPreview ? true : false; |
| if (overprintPreview) { |
| unsigned char c, m, y, k; |
| |
| c = 255 - m_page->parentDoc->paperColor.blue(); |
| m = 255 - m_page->parentDoc->paperColor.red(); |
| y = 255 - m_page->parentDoc->paperColor.green(); |
| k = c; |
| if (m < k) { |
| k = m; |
| } |
| if (y < k) { |
| k = y; |
| } |
| bgColor[0] = c - k; |
| bgColor[1] = m - k; |
| bgColor[2] = y - k; |
| bgColor[3] = k; |
| for (int i = 4; i < SPOT_NCOMPS + 4; i++) { |
| bgColor[i] = 0; |
| } |
| } else { |
| bgColor[0] = m_page->parentDoc->paperColor.blue(); |
| bgColor[1] = m_page->parentDoc->paperColor.green(); |
| bgColor[2] = m_page->parentDoc->paperColor.red(); |
| } |
| |
| const SplashColorMode colorMode = overprintPreview ? splashModeDeviceN8 : splashModeXBGR8; |
| |
| SplashThinLineMode thinLineMode = splashThinLineDefault; |
| if (m_page->parentDoc->m_hints & Document::ThinLineShape) { |
| thinLineMode = splashThinLineShape; |
| } |
| if (m_page->parentDoc->m_hints & Document::ThinLineSolid) { |
| thinLineMode = splashThinLineSolid; |
| } |
| |
| const bool ignorePaperColor = m_page->parentDoc->m_hints & Document::IgnorePaperColor; |
| |
| Qt6SplashOutputDev splash_output(colorMode, 4, false, ignorePaperColor, ignorePaperColor ? nullptr : bgColor, true, thinLineMode, overprintPreview); |
| |
| splash_output.setCallbacks(partialUpdateCallback, shouldDoPartialUpdateCallback, shouldAbortRenderCallback, payload); |
| |
| splash_output.setFontAntialias(m_page->parentDoc->m_hints & Document::TextAntialiasing ? true : false); |
| splash_output.setVectorAntialias(m_page->parentDoc->m_hints & Document::Antialiasing ? true : false); |
| splash_output.setFreeTypeHinting(m_page->parentDoc->m_hints & Document::TextHinting ? true : false, m_page->parentDoc->m_hints & Document::TextSlightHinting ? true : false); |
| |
| #ifdef USE_CMS |
| splash_output.setDisplayProfile(m_page->parentDoc->m_displayProfile); |
| #endif |
| |
| splash_output.startDoc(m_page->parentDoc->doc); |
| |
| const bool hideAnnotations = m_page->parentDoc->m_hints & Document::HideAnnotations; |
| |
| OutputDevCallbackHelper *abortHelper = &splash_output; |
| 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, |
| (hideAnnotations) ? annotDisplayDecideCbk : nullAnnotCallBack, nullptr, true); |
| |
| img = splash_output.getXBGRImage(true /* takeImageData */); |
| break; |
| } |
| case Poppler::Document::QPainterBackend: { |
| QSize size = pageSize(); |
| QImage tmpimg(w == -1 ? qRound(size.width() * xres / 72.0) : w, h == -1 ? qRound(size.height() * yres / 72.0) : h, QImage::Format_ARGB32); |
| |
| QColor bgColor(m_page->parentDoc->paperColor.red(), m_page->parentDoc->paperColor.green(), m_page->parentDoc->paperColor.blue(), m_page->parentDoc->paperColor.alpha()); |
| |
| tmpimg.fill(bgColor); |
| |
| QPainter painter(&tmpimg); |
| QImageDumpingQPainterOutputDev qpainter_output(&painter, &tmpimg); |
| |
| qpainter_output.setHintingPreference(QFontHintingFromPopplerHinting(m_page->parentDoc->m_hints)); |
| |
| #ifdef USE_CMS |
| qpainter_output.setDisplayProfile(m_page->parentDoc->m_displayProfile); |
| #endif |
| |
| qpainter_output.setCallbacks(partialUpdateCallback, shouldDoPartialUpdateCallback, shouldAbortRenderCallback, payload); |
| renderToQPainter(&qpainter_output, &painter, m_page, xres, yres, xPos, yPos, w, h, rotate, DontSaveAndRestore); |
| painter.end(); |
| img = tmpimg; |
| break; |
| } |
| } |
| |
| if (shouldAbortRenderCallback && shouldAbortRenderCallback(payload)) { |
| return QImage(); |
| } |
| |
| return img; |
| } |
| |
| bool Page::renderToPainter(QPainter *painter, double xres, double yres, int x, int y, int w, int h, Rotation rotate, PainterFlags flags) const |
| { |
| if (!painter) { |
| return false; |
| } |
| |
| switch (m_page->parentDoc->m_backend) { |
| case Poppler::Document::SplashBackend: |
| return false; |
| case Poppler::Document::QPainterBackend: { |
| QImageDumpingQPainterOutputDev qpainter_output(painter, nullptr); |
| |
| qpainter_output.setHintingPreference(QFontHintingFromPopplerHinting(m_page->parentDoc->m_hints)); |
| |
| return renderToQPainter(&qpainter_output, painter, m_page, xres, yres, x, y, w, h, rotate, flags); |
| } |
| } |
| return false; |
| } |
| |
| QImage Page::thumbnail() const |
| { |
| unsigned char *data = nullptr; |
| int w = 0; |
| int h = 0; |
| int rowstride = 0; |
| bool r = m_page->page->loadThumb(&data, &w, &h, &rowstride); |
| QImage ret; |
| if (r) { |
| // first construct a temporary image with the data got, |
| // then force a copy of it so we can free the raw thumbnail data |
| ret = QImage(data, w, h, rowstride, QImage::Format_RGB888).copy(); |
| gfree(data); |
| } |
| return ret; |
| } |
| |
| QString Page::text(const QRectF &r, TextLayout textLayout) const |
| { |
| TextOutputDev *output_dev; |
| GooString *s; |
| QString result; |
| |
| const bool rawOrder = textLayout == RawOrderLayout; |
| output_dev = new TextOutputDev(nullptr, false, 0, rawOrder, false); |
| 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); |
| if (r.isNull()) { |
| const PDFRectangle *rect = m_page->page->getCropBox(); |
| s = output_dev->getText(rect->x1, rect->y1, rect->x2, rect->y2); |
| } else { |
| s = output_dev->getText(r.left(), r.top(), r.right(), r.bottom()); |
| } |
| |
| result = QString::fromUtf8(s->c_str()); |
| |
| delete output_dev; |
| delete s; |
| return result; |
| } |
| |
| QString Page::text(const QRectF &r) const |
| { |
| return text(r, PhysicalLayout); |
| } |
| |
| bool Page::search(const QString &text, double &sLeft, double &sTop, double &sRight, double &sBottom, SearchDirection direction, SearchFlags flags, Rotation rotate) const |
| { |
| const bool sCase = flags.testFlag(IgnoreCase) ? false : true; |
| const bool sWords = flags.testFlag(WholeWords) ? true : false; |
| const bool sDiacritics = flags.testFlag(IgnoreDiacritics) ? true : false; |
| const bool sAcrossLines = flags.testFlag(AcrossLines) ? true : false; |
| |
| QVector<Unicode> u; |
| TextPage *textPage = m_page->prepareTextSearch(text, rotate, &u); |
| |
| const bool found = m_page->performSingleTextSearch(textPage, u, sLeft, sTop, sRight, sBottom, direction, sCase, sWords, sDiacritics, sAcrossLines); |
| |
| textPage->decRefCnt(); |
| |
| return found; |
| } |
| |
| QList<QRectF> Page::search(const QString &text, SearchFlags flags, Rotation rotate) const |
| { |
| const bool sCase = flags.testFlag(IgnoreCase) ? false : true; |
| const bool sWords = flags.testFlag(WholeWords) ? true : false; |
| const bool sDiacritics = flags.testFlag(IgnoreDiacritics) ? true : false; |
| const bool sAcrossLines = flags.testFlag(AcrossLines) ? true : false; |
| |
| QVector<Unicode> u; |
| TextPage *textPage = m_page->prepareTextSearch(text, rotate, &u); |
| |
| QList<QRectF> results = m_page->performMultipleTextSearch(textPage, u, sCase, sWords, sDiacritics, sAcrossLines); |
| |
| textPage->decRefCnt(); |
| |
| return results; |
| } |
| |
| std::vector<std::unique_ptr<TextBox>> Page::textList(Rotation rotate) const |
| { |
| return textList(rotate, nullptr, QVariant()); |
| } |
| |
| std::vector<std::unique_ptr<TextBox>> Page::textList(Rotation rotate, ShouldAbortQueryFunc shouldAbortExtractionCallback, const QVariant &closure) const |
| { |
| std::vector<std::unique_ptr<TextBox>> output_list; |
| |
| TextOutputDev output_dev(nullptr, false, 0, false, false); |
| |
| int rotation = (int)rotate * 90; |
| |
| TextExtractionAbortHelper abortHelper(shouldAbortExtractionCallback, closure); |
| 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, |
| nullptr, nullptr, true); |
| |
| std::unique_ptr<TextWordList> word_list = output_dev.makeWordList(); |
| |
| if (shouldAbortExtractionCallback && shouldAbortExtractionCallback(closure)) { |
| return output_list; |
| } |
| |
| QHash<const TextWord *, TextBox *> wordBoxMap; |
| |
| output_list.reserve(word_list->getLength()); |
| for (int i = 0; i < word_list->getLength(); i++) { |
| TextWord *word = word_list->get(i); |
| GooString *gooWord = word->getText(); |
| QString string = QString::fromUtf8(gooWord->c_str()); |
| delete gooWord; |
| double xMin, yMin, xMax, yMax; |
| word->getBBox(&xMin, &yMin, &xMax, &yMax); |
| |
| auto text_box = std::make_unique<TextBox>(string, QRectF(xMin, yMin, xMax - xMin, yMax - yMin)); |
| text_box->m_data->hasSpaceAfter = word->hasSpaceAfter() == true; |
| text_box->m_data->charBBoxes.reserve(word->getLength()); |
| for (int j = 0; j < word->getLength(); ++j) { |
| word->getCharBBox(j, &xMin, &yMin, &xMax, &yMax); |
| text_box->m_data->charBBoxes.append(QRectF(xMin, yMin, xMax - xMin, yMax - yMin)); |
| } |
| |
| wordBoxMap.insert(word, text_box.get()); |
| |
| output_list.push_back(std::move(text_box)); |
| } |
| |
| for (int i = 0; i < word_list->getLength(); i++) { |
| TextWord *word = word_list->get(i); |
| TextBox *text_box = wordBoxMap.value(word); |
| text_box->m_data->nextWord = wordBoxMap.value(word->nextWord()); |
| } |
| |
| return output_list; |
| } |
| |
| PageTransition *Page::transition() const |
| { |
| if (!m_page->transition) { |
| Object o = m_page->page->getTrans(); |
| PageTransitionParams params; |
| params.dictObj = &o; |
| if (params.dictObj->isDict()) { |
| m_page->transition = new PageTransition(params); |
| } |
| } |
| return m_page->transition; |
| } |
| |
| std::unique_ptr<Link> Page::action(PageAction act) const |
| { |
| if (act == Page::Opening || act == Page::Closing) { |
| Object o = m_page->page->getActions(); |
| if (!o.isDict()) { |
| return nullptr; |
| } |
| Dict *dict = o.getDict(); |
| const char *key = act == Page::Opening ? "O" : "C"; |
| Object o2 = dict->lookup((char *)key); |
| std::unique_ptr<::LinkAction> lact = ::LinkAction::parseAction(&o2, m_page->parentDoc->doc->getCatalog()->getBaseURI()); |
| if (lact != nullptr) { |
| return m_page->convertLinkActionToLink(lact.get(), QRectF()); |
| } |
| } |
| return nullptr; |
| } |
| |
| QSizeF Page::pageSizeF() const |
| { |
| Page::Orientation orient = orientation(); |
| if ((Page::Landscape == orient) || (Page::Seascape == orient)) { |
| return QSizeF(m_page->page->getCropHeight(), m_page->page->getCropWidth()); |
| } else { |
| return QSizeF(m_page->page->getCropWidth(), m_page->page->getCropHeight()); |
| } |
| } |
| |
| QSize Page::pageSize() const |
| { |
| return pageSizeF().toSize(); |
| } |
| |
| Page::Orientation Page::orientation() const |
| { |
| const int rotation = m_page->page->getRotate(); |
| switch (rotation) { |
| case 90: |
| return Page::Landscape; |
| break; |
| case 180: |
| return Page::UpsideDown; |
| break; |
| case 270: |
| return Page::Seascape; |
| break; |
| default: |
| return Page::Portrait; |
| } |
| } |
| |
| void Page::defaultCTM(double *CTM, double dpiX, double dpiY, int rotate, bool upsideDown) |
| { |
| m_page->page->getDefaultCTM(CTM, dpiX, dpiY, rotate, false, upsideDown); |
| } |
| |
| std::vector<std::unique_ptr<Link>> Page::links() const |
| { |
| LinkExtractorOutputDev link_dev(m_page); |
| m_page->parentDoc->doc->processLinks(&link_dev, m_page->index + 1); |
| return link_dev.links(); |
| } |
| |
| std::vector<std::unique_ptr<Annotation>> Page::annotations() const |
| { |
| return AnnotationPrivate::findAnnotations(m_page->page, m_page->parentDoc, QSet<Annotation::SubType>()); |
| } |
| |
| std::vector<std::unique_ptr<Annotation>> Page::annotations(const QSet<Annotation::SubType> &subtypes) const |
| { |
| return AnnotationPrivate::findAnnotations(m_page->page, m_page->parentDoc, subtypes); |
| } |
| |
| void Page::addAnnotation(const Annotation *ann) |
| { |
| AnnotationPrivate::addAnnotationToPage(m_page->page, m_page->parentDoc, ann); |
| } |
| |
| void Page::removeAnnotation(const Annotation *ann) |
| { |
| AnnotationPrivate::removeAnnotationFromPage(m_page->page, ann); |
| } |
| |
| std::vector<std::unique_ptr<FormField>> Page::formFields() const |
| { |
| std::vector<std::unique_ptr<FormField>> fields; |
| ::Page *p = m_page->page; |
| const std::unique_ptr<FormPageWidgets> form = p->getFormWidgets(); |
| int formcount = form->getNumWidgets(); |
| for (int i = 0; i < formcount; ++i) { |
| ::FormWidget *fm = form->getWidget(i); |
| std::unique_ptr<FormField> ff; |
| switch (fm->getType()) { |
| case formButton: { |
| ff = std::make_unique<FormFieldButton>(m_page->parentDoc, p, static_cast<FormWidgetButton *>(fm)); |
| } break; |
| |
| case formText: { |
| ff = std::make_unique<FormFieldText>(m_page->parentDoc, p, static_cast<FormWidgetText *>(fm)); |
| } break; |
| |
| case formChoice: { |
| ff = std::make_unique<FormFieldChoice>(m_page->parentDoc, p, static_cast<FormWidgetChoice *>(fm)); |
| } break; |
| |
| case formSignature: { |
| ff = std::make_unique<FormFieldSignature>(m_page->parentDoc, p, static_cast<FormWidgetSignature *>(fm)); |
| } break; |
| |
| default:; |
| } |
| |
| if (ff) { |
| fields.push_back(std::move(ff)); |
| } |
| } |
| |
| return fields; |
| } |
| |
| double Page::duration() const |
| { |
| return m_page->page->getDuration(); |
| } |
| |
| QString Page::label() const |
| { |
| GooString goo; |
| if (!m_page->parentDoc->doc->getCatalog()->indexToLabel(m_page->index, &goo)) { |
| return QString(); |
| } |
| |
| return UnicodeParsedString(&goo); |
| } |
| |
| int Page::index() const |
| { |
| return m_page->index; |
| } |
| |
| } |