/* poppler-page.cc: qt interface to poppler * Copyright (C) 2005, Net Integration Technologies, Inc. * Copyright (C) 2005, Brad Hards * Copyright (C) 2005-2022, 2024, Albert Astals Cid * Copyright (C) 2005, Stefan Kebekus * Copyright (C) 2006-2011, Pino Toscano * Copyright (C) 2008 Carlos Garcia Campos * Copyright (C) 2009 Shawn Rutledge * Copyright (C) 2010, 2012, Guillermo Amaral * Copyright (C) 2010 Suzuki Toshiya * Copyright (C) 2010 Matthias Fauconneau * Copyright (C) 2010 Hib Eris * Copyright (C) 2012 Tobias Koenig * Copyright (C) 2012 Fabio D'Urso * Copyright (C) 2012, 2015 Adam Reichold * Copyright (C) 2012, 2013 Thomas Freitag * Copyright (C) 2015 William Bader * Copyright (C) 2016 Arseniy Lartsev * Copyright (C) 2016, Hanno Meyer-Thurow * Copyright (C) 2017-2020, Oliver Sander * Copyright (C) 2017 Adrian Johnson * Copyright (C) 2017, 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, . Work sponsored by the LiMux project of the city of Munich * Copyright (C) 2018 Intevation GmbH * Copyright (C) 2018, Tobias Deiminger * Copyright (C) 2018, 2021 Nelson Benítez León * Copyright (C) 2020 Oliver Sander * Copyright (C) 2020 Philipp Knechtges * Copyright (C) 2021 Hubert Figuiere * Copyright (C) 2021 Thomas Huxhorn * Copyright (C) 2023 Kevin Ottens . Work sponsored by De Bortoli Wines * Copyright (C) 2024 Stefan Brüns * Copyright (C) 2024 Pratham Gandhi * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 Qt5SplashOutputDev : public SplashOutputDev, public OutputDevCallbackHelper { public: Qt5SplashOutputDev(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) { } ~Qt5SplashOutputDev() 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; }; Qt5SplashOutputDev::~Qt5SplashOutputDev() = 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; Link *PageData::convertLinkActionToLink(::LinkAction *a, const QRectF &linkArea) { return convertLinkActionToLink(a, parentDoc, linkArea); } Link *PageData::convertLinkActionToLink(::LinkAction *a, DocumentData *parentDoc, const QRectF &linkArea) { if (!a) { return nullptr; } Link *popplerLink = nullptr; 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 = new 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 = new LinkGoto(linkArea, fileName, LinkDestination(ldd)); } break; case actionLaunch: { LinkLaunch *e = (LinkLaunch *)a; const GooString *p = e->getParams(); popplerLink = new LinkExecute(linkArea, UnicodeParsedString(e->getFileName()), p ? p->c_str() : nullptr); } break; case actionNamed: { const std::string &name = ((LinkNamed *)a)->getName(); if (name == "NextPage") { popplerLink = new LinkAction(linkArea, LinkAction::PageNext); } else if (name == "PrevPage") { popplerLink = new LinkAction(linkArea, LinkAction::PagePrev); } else if (name == "FirstPage") { popplerLink = new LinkAction(linkArea, LinkAction::PageFirst); } else if (name == "LastPage") { popplerLink = new LinkAction(linkArea, LinkAction::PageLast); } else if (name == "GoBack") { popplerLink = new LinkAction(linkArea, LinkAction::HistoryBack); } else if (name == "GoForward") { popplerLink = new LinkAction(linkArea, LinkAction::HistoryForward); } else if (name == "Quit") { popplerLink = new LinkAction(linkArea, LinkAction::Quit); } else if (name == "GoToPage") { popplerLink = new LinkAction(linkArea, LinkAction::GoToPage); } else if (name == "Find") { popplerLink = new LinkAction(linkArea, LinkAction::Find); } else if (name == "FullScreen") { popplerLink = new LinkAction(linkArea, LinkAction::Presentation); } else if (name == "Print") { popplerLink = new LinkAction(linkArea, LinkAction::Print); } else if (name == "Close") { // acroread closes the document always, doesnt care whether // its presentation mode or not // popplerLink = new LinkAction( linkArea, LinkAction::EndPresentation ); popplerLink = new LinkAction(linkArea, LinkAction::Close); } else if (name == "SaveAs") { popplerLink = new LinkAction(linkArea, LinkAction::SaveAs); } else { qWarning() << "Unhandled action name" << name.c_str(); } } break; case actionURI: { popplerLink = new LinkBrowse(linkArea, ((LinkURI *)a)->getURI().c_str()); } break; case actionSound: { ::LinkSound *ls = (::LinkSound *)a; popplerLink = new LinkSound(linkArea, ls->getVolume(), ls->getSynchronous(), ls->getRepeat(), ls->getMix(), new SoundObject(ls->getSound())); } break; case actionJavaScript: { ::LinkJavaScript *ljs = (::LinkJavaScript *)a; popplerLink = new 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 = new LinkMovie(linkArea, operation, title, reference); } break; case actionRendition: { ::LinkRendition *lrn = (::LinkRendition *)a; Ref reference = Ref::INVALID(); if (lrn->hasScreenAnnot()) { reference = lrn->getScreenAnnot(); } popplerLink = new LinkRendition(linkArea, lrn->getMedia() ? lrn->getMedia()->copy() : std::unique_ptr<::MediaRendition> {}, lrn->getOperation(), UnicodeParsedString(lrn->getScript()), reference); } break; case actionOCGState: { ::LinkOCGState *plocg = (::LinkOCGState *)a; LinkOCGStatePrivate *locgp = new LinkOCGStatePrivate(linkArea, plocg->getStateList(), plocg->getPreserveRB()); popplerLink = new LinkOCGState(locgp); } break; case actionHide: { ::LinkHide *lh = (::LinkHide *)a; LinkHidePrivate *lhp = new LinkHidePrivate(linkArea, lh->hasTargetName() ? UnicodeParsedString(lh->getTargetName()) : QString(), lh->isShowAction()); popplerLink = new LinkHide(lhp); } break; case actionResetForm: { ::LinkResetForm *lrf = (::LinkResetForm *)a; std::vector stdStringFields = lrf->getFields(); QStringList qStringFields; for (const std::string &str : stdStringFields) { qStringFields << QString::fromStdString(str); } LinkResetFormPrivate *lrfp = new LinkResetFormPrivate(linkArea, qStringFields, lrf->getExclude()); popplerLink = new LinkResetForm(lrfp); } break; case actionSubmitForm: { ::LinkSubmitForm *lsf = (::LinkSubmitForm *)a; const std::vector &stdStringFields = lsf->getFields(); QVector fieldIds; fieldIds.reserve(stdStringFields.size()); Form *form = parentDoc->doc->getCatalog()->getForm(); for (const std::string &fieldStr : stdStringFields) { ::FormField *field = form->findFieldByFullyQualifiedNameOrRef(fieldStr); if (!field->getNoExport()) { int numWidgets = field->getNumWidgets(); for (int i = 0; i < numWidgets; i++) { ::FormWidget *widget = field->getWidget(i); if (widget) { fieldIds.append(field->getWidget(i)->getID()); } } } } QString qStringUrl = QString::fromStdString(lsf->getUrl()); LinkSubmitForm::SubmitFormFlags qFlags = static_cast(lsf->getFlags()); LinkSubmitFormPrivate *lsfp = new LinkSubmitFormPrivate(linkArea, fieldIds, qStringUrl, qFlags); popplerLink = new LinkSubmitForm(lsfp); } break; case actionUnknown: break; } if (popplerLink) { QVector links; for (const std::unique_ptr<::LinkAction> &nextAction : a->nextActions()) { links << convertLinkActionToLink(nextAction.get(), parentDoc, linkArea); } LinkPrivate::get(popplerLink)->nextLinks = links; } return popplerLink; } inline TextPage *PageData::prepareTextSearch(const QString &text, Page::Rotation rotate, QVector *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 &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 PageData::performMultipleTextSearch(TextPage *textPage, QVector &u, bool sCase, bool sWords, bool sDiacritics, bool sAcrossLines) { QList 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(user_data); return helper->shouldAbortRenderCallback(helper->payload); }; static auto shouldAbortExtractionInternalCallback = [](void *user_data) { TextExtractionAbortHelper *helper = reinterpret_cast(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; Qt5SplashOutputDev 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(); if (orientation() == Orientation::Portrait || orientation() == Orientation::UpsideDown) { s = output_dev->getText(rect->x1, rect->y1, rect->x2, rect->y2); } else { s = output_dev->getText(rect->y1, rect->x1, rect->y2, rect->x2); } } 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, SearchMode caseSensitive, Rotation rotate) const { const bool sCase = caseSensitive == Page::CaseSensitive ? true : false; QVector u; TextPage *textPage = m_page->prepareTextSearch(text, rotate, &u); const bool found = m_page->performSingleTextSearch(textPage, u, sLeft, sTop, sRight, sBottom, direction, sCase, false, false, false); textPage->decRefCnt(); return found; } 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 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 Page::search(const QString &text, SearchMode caseSensitive, Rotation rotate) const { const bool sCase = caseSensitive == Page::CaseSensitive ? true : false; QVector u; TextPage *textPage = m_page->prepareTextSearch(text, rotate, &u); const QList results = m_page->performMultipleTextSearch(textPage, u, sCase, false, false, false); textPage->decRefCnt(); return results; } QList 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 u; TextPage *textPage = m_page->prepareTextSearch(text, rotate, &u); const QList results = m_page->performMultipleTextSearch(textPage, u, sCase, sWords, sDiacritics, sAcrossLines); textPage->decRefCnt(); return results; } QList Page::textList(Rotation rotate) const { return textList(rotate, nullptr, QVariant()); } QList Page::textList(Rotation rotate, ShouldAbortQueryFunc shouldAbortExtractionCallback, const QVariant &closure) const { QList 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 word_list = output_dev.makeWordList(); if (shouldAbortExtractionCallback && shouldAbortExtractionCallback(closure)) { return output_list; } QHash 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); TextBox *text_box = new 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); output_list.append(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; } 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()); Link *popplerLink = nullptr; if (lact != nullptr) { popplerLink = m_page->convertLinkActionToLink(lact.get(), QRectF()); } return popplerLink; } 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); } QList Page::links() const { LinkExtractorOutputDev link_dev(m_page); m_page->parentDoc->doc->processLinks(&link_dev, m_page->index + 1); QList popplerLinks = link_dev.links(); return popplerLinks; } QList Page::annotations() const { return AnnotationPrivate::findAnnotations(m_page->page, m_page->parentDoc, QSet()); } QList Page::annotations(const QSet &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); } QList Page::formFields() const { QList fields; ::Page *p = m_page->page; const std::unique_ptr form = p->getFormWidgets(); int formcount = form->getNumWidgets(); for (int i = 0; i < formcount; ++i) { ::FormWidget *fm = form->getWidget(i); FormField *ff = nullptr; switch (fm->getType()) { case formButton: { ff = new FormFieldButton(m_page->parentDoc, p, static_cast(fm)); } break; case formText: { ff = new FormFieldText(m_page->parentDoc, p, static_cast(fm)); } break; case formChoice: { ff = new FormFieldChoice(m_page->parentDoc, p, static_cast(fm)); } break; case formSignature: { ff = new FormFieldSignature(m_page->parentDoc, p, static_cast(fm)); } break; default:; } if (ff) { fields.append(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; } }