Spaces:
Running
Running
/* poppler-page.cc: qt interface to poppler | |
* Copyright (C) 2005, Net Integration Technologies, Inc. | |
* Copyright (C) 2005, Brad Hards <[email protected]> | |
* Copyright (C) 2005-2022, Albert Astals Cid <[email protected]> | |
* Copyright (C) 2005, Stefan Kebekus <[email protected]> | |
* Copyright (C) 2006-2011, Pino Toscano <[email protected]> | |
* Copyright (C) 2008 Carlos Garcia Campos <[email protected]> | |
* Copyright (C) 2009 Shawn Rutledge <[email protected]> | |
* Copyright (C) 2010, 2012, Guillermo Amaral <[email protected]> | |
* Copyright (C) 2010 Suzuki Toshiya <[email protected]> | |
* Copyright (C) 2010 Matthias Fauconneau <[email protected]> | |
* Copyright (C) 2010 Hib Eris <[email protected]> | |
* Copyright (C) 2012 Tobias Koenig <[email protected]> | |
* Copyright (C) 2012 Fabio D'Urso <[email protected]> | |
* Copyright (C) 2012, 2015 Adam Reichold <[email protected]> | |
* Copyright (C) 2012, 2013 Thomas Freitag <[email protected]> | |
* Copyright (C) 2015 William Bader <[email protected]> | |
* Copyright (C) 2016 Arseniy Lartsev <[email protected]> | |
* Copyright (C) 2016, Hanno Meyer-Thurow <[email protected]> | |
* Copyright (C) 2017-2020, Oliver Sander <[email protected]> | |
* Copyright (C) 2017 Adrian Johnson <[email protected]> | |
* Copyright (C) 2017, 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <[email protected]>. Work sponsored by the LiMux project of the city of Munich | |
* Copyright (C) 2018 Intevation GmbH <[email protected]> | |
* Copyright (C) 2018, Tobias Deiminger <[email protected]> | |
* Copyright (C) 2018, 2021 Nelson Benítez León <[email protected]> | |
* Copyright (C) 2020 Oliver Sander <[email protected]> | |
* Copyright (C) 2020 Philipp Knechtges <[email protected]> | |
* Copyright (C) 2021 Hubert Figuiere <[email protected]> | |
* Copyright (C) 2021 Thomas Huxhorn <[email protected]> | |
* Copyright (C) 2023 Kevin Ottens <[email protected]>. Work sponsored by De Bortoli Wines | |
* Copyright (C) 2024 Stefan Brüns <[email protected]> | |
* | |
* 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. | |
*/ | |
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, e->getFileName()->c_str(), 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() : nullptr, 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: | |
// Not handled in Qt5 front-end yet | |
break; | |
case actionUnknown: | |
break; | |
} | |
if (popplerLink) { | |
QVector<Link *> 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<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; | |
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); | |
splash_output.setDisplayProfile(m_page->parentDoc->m_displayProfile); | |
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)); | |
qpainter_output.setDisplayProfile(m_page->parentDoc->m_displayProfile); | |
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<Unicode> 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<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, SearchMode caseSensitive, Rotation rotate) const | |
{ | |
const bool sCase = caseSensitive == Page::CaseSensitive ? true : false; | |
QVector<Unicode> u; | |
TextPage *textPage = m_page->prepareTextSearch(text, rotate, &u); | |
const QList<QRectF> results = m_page->performMultipleTextSearch(textPage, u, sCase, false, false, false); | |
textPage->decRefCnt(); | |
return results; | |
} | |
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); | |
const QList<QRectF> results = m_page->performMultipleTextSearch(textPage, u, sCase, sWords, sDiacritics, sAcrossLines); | |
textPage->decRefCnt(); | |
return results; | |
} | |
QList<TextBox *> Page::textList(Rotation rotate) const | |
{ | |
return textList(rotate, nullptr, QVariant()); | |
} | |
QList<TextBox *> Page::textList(Rotation rotate, ShouldAbortQueryFunc shouldAbortExtractionCallback, const QVariant &closure) const | |
{ | |
QList<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); | |
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<Link *> Page::links() const | |
{ | |
LinkExtractorOutputDev link_dev(m_page); | |
m_page->parentDoc->doc->processLinks(&link_dev, m_page->index + 1); | |
QList<Link *> popplerLinks = link_dev.links(); | |
return popplerLinks; | |
} | |
QList<Annotation *> Page::annotations() const | |
{ | |
return AnnotationPrivate::findAnnotations(m_page->page, m_page->parentDoc, QSet<Annotation::SubType>()); | |
} | |
QList<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); | |
} | |
QList<FormField *> Page::formFields() const | |
{ | |
QList<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); | |
FormField *ff = nullptr; | |
switch (fm->getType()) { | |
case formButton: { | |
ff = new FormFieldButton(m_page->parentDoc, p, static_cast<FormWidgetButton *>(fm)); | |
} break; | |
case formText: { | |
ff = new FormFieldText(m_page->parentDoc, p, static_cast<FormWidgetText *>(fm)); | |
} break; | |
case formChoice: { | |
ff = new FormFieldChoice(m_page->parentDoc, p, static_cast<FormWidgetChoice *>(fm)); | |
} break; | |
case formSignature: { | |
ff = new FormFieldSignature(m_page->parentDoc, p, static_cast<FormWidgetSignature *>(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; | |
} | |
} | |