File indexing completed on 2024-05-12 16:06:47

0001 /*
0002     SPDX-FileCopyrightText: 2004-2008 Albert Astals Cid <aacid@kde.org>
0003     SPDX-FileCopyrightText: 2004 Enrico Ros <eros.kde@email.it>
0004     SPDX-FileCopyrightText: 2012 Guillermo A. Amaral B. <gamaral@kde.org>
0005     SPDX-FileCopyrightText: 2019 Oliver Sander <oliver.sander@tu-dresden.de>
0006 
0007     Work sponsored by the LiMux project of the city of Munich:
0008     SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com>
0009 
0010     SPDX-License-Identifier: GPL-2.0-or-later
0011 */
0012 
0013 #include <memory>
0014 
0015 #include "generator_pdf.h"
0016 
0017 // qt/kde includes
0018 #include <QCheckBox>
0019 #include <QColor>
0020 #include <QComboBox>
0021 #include <QDebug>
0022 #include <QDir>
0023 #include <QFile>
0024 #include <QImage>
0025 #include <QImageReader>
0026 #include <QLayout>
0027 #include <QMutex>
0028 #include <QPainter>
0029 #include <QPrinter>
0030 #include <QStack>
0031 #include <QTemporaryFile>
0032 #include <QTextStream>
0033 #include <QTimeZone>
0034 #include <QTimer>
0035 
0036 #include <KAboutData>
0037 #include <KConfigDialog>
0038 #include <KLocalizedString>
0039 
0040 #include <core/action.h>
0041 #include <core/annotations.h>
0042 #include <core/fileprinter.h>
0043 #include <core/movie.h>
0044 #include <core/page.h>
0045 #include <core/pagetransition.h>
0046 #include <core/signatureutils.h>
0047 #include <core/sound.h>
0048 #include <core/sourcereference.h>
0049 #include <core/textpage.h>
0050 #include <core/utils.h>
0051 
0052 #include "pdfsettings.h"
0053 
0054 #include <poppler-media.h>
0055 #include <poppler-version.h>
0056 
0057 #include "annots.h"
0058 #include "debug_pdf.h"
0059 #include "formfields.h"
0060 #include "imagescaling.h"
0061 #include "pdfsettingswidget.h"
0062 #include "pdfsignatureutils.h"
0063 #include "popplerembeddedfile.h"
0064 
0065 #include <functional>
0066 
0067 Q_DECLARE_METATYPE(Poppler::Annotation *)
0068 Q_DECLARE_METATYPE(Poppler::FontInfo)
0069 Q_DECLARE_METATYPE(const Poppler::LinkMovie *)
0070 Q_DECLARE_METATYPE(const Poppler::LinkRendition *)
0071 Q_DECLARE_METATYPE(const Poppler::LinkOCGState *)
0072 
0073 #define POPPLER_VERSION_MACRO ((POPPLER_VERSION_MAJOR << 16) | (POPPLER_VERSION_MINOR << 8) | (POPPLER_VERSION_MICRO))
0074 
0075 static const int defaultPageWidth = 595;
0076 static const int defaultPageHeight = 842;
0077 
0078 class PDFOptionsPage : public Okular::PrintOptionsWidget
0079 {
0080     Q_OBJECT
0081 
0082 public:
0083     enum ScaleMode { FitToPrintableArea, FitToPage, None };
0084     Q_ENUM(ScaleMode)
0085 
0086     PDFOptionsPage()
0087     {
0088         setWindowTitle(i18n("PDF Options"));
0089         QVBoxLayout *layout = new QVBoxLayout(this);
0090         m_printAnnots = new QCheckBox(i18n("Print annotations"), this);
0091         m_printAnnots->setToolTip(i18n("Include annotations in the printed document"));
0092         m_printAnnots->setWhatsThis(i18n("Includes annotations in the printed document. You can disable this if you want to print the original unannotated document."));
0093         layout->addWidget(m_printAnnots);
0094         m_forceRaster = new QCheckBox(i18n("Force rasterization"), this);
0095         m_forceRaster->setToolTip(i18n("Rasterize into an image before printing"));
0096         m_forceRaster->setWhatsThis(i18n("Forces the rasterization of each page into an image before printing it. This usually gives somewhat worse results, but is useful when printing documents that appear to print incorrectly."));
0097         layout->addWidget(m_forceRaster);
0098 
0099         QWidget *formWidget = new QWidget(this);
0100         QFormLayout *printBackendLayout = new QFormLayout(formWidget);
0101 
0102         m_scaleMode = new QComboBox;
0103         m_scaleMode->insertItem(FitToPrintableArea, i18n("Fit to printable area"), FitToPrintableArea);
0104         m_scaleMode->insertItem(FitToPage, i18n("Fit to full page"), FitToPage);
0105         m_scaleMode->insertItem(None, i18n("None; print original size"), None);
0106         m_scaleMode->setToolTip(i18n("Scaling mode for the printed pages"));
0107         printBackendLayout->addRow(i18n("Scale mode:"), m_scaleMode);
0108 
0109         // Set scale mode to users default
0110         m_scaleMode->setCurrentIndex(PDFSettings::printScaleMode());
0111 
0112         // Set rasterizing if scale mode is 1 or 2
0113         if (m_scaleMode->currentIndex() != 0) {
0114             m_forceRaster->setCheckState(Qt::Checked);
0115         }
0116 
0117         // If the user selects a scaling mode that requires the use of the
0118         // "Force rasterization" feature, enable it automatically so they don't
0119         // have to 1) know this and 2) do it manually
0120         connect(m_scaleMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [=](int index) { m_forceRaster->setChecked(index != 0); });
0121 
0122         layout->addWidget(formWidget);
0123 
0124         layout->addStretch(1);
0125 
0126         setPrintAnnots(true); // Default value
0127     }
0128 
0129     bool ignorePrintMargins() const override
0130     {
0131         return scaleMode() == FitToPage;
0132     }
0133 
0134     bool printAnnots()
0135     {
0136         return m_printAnnots->isChecked();
0137     }
0138 
0139     void setPrintAnnots(bool printAnnots)
0140     {
0141         m_printAnnots->setChecked(printAnnots);
0142     }
0143 
0144     bool printForceRaster()
0145     {
0146         return m_forceRaster->isChecked();
0147     }
0148 
0149     void setPrintForceRaster(bool forceRaster)
0150     {
0151         m_forceRaster->setChecked(forceRaster);
0152     }
0153 
0154     ScaleMode scaleMode() const
0155     {
0156         return m_scaleMode->currentData().value<ScaleMode>();
0157     }
0158 
0159 private:
0160     QCheckBox *m_printAnnots;
0161     QCheckBox *m_forceRaster;
0162     QComboBox *m_scaleMode;
0163 };
0164 
0165 static void fillViewportFromLinkDestination(Okular::DocumentViewport &viewport, const Poppler::LinkDestination &destination)
0166 {
0167     viewport.pageNumber = destination.pageNumber() - 1;
0168 
0169     if (!viewport.isValid()) {
0170         return;
0171     }
0172 
0173     // get destination position
0174     // TODO add other attributes to the viewport (taken from link)
0175     //     switch ( destination->getKind() )
0176     //     {
0177     //         case destXYZ:
0178     if (destination.isChangeLeft() || destination.isChangeTop()) {
0179         // TODO remember to change this if we implement DPI and/or rotation
0180         double left, top;
0181         left = destination.left();
0182         top = destination.top();
0183 
0184         viewport.rePos.normalizedX = left;
0185         viewport.rePos.normalizedY = top;
0186         viewport.rePos.enabled = true;
0187         viewport.rePos.pos = Okular::DocumentViewport::TopLeft;
0188     }
0189     /* TODO
0190     if ( dest->getChangeZoom() )
0191         make zoom change*/
0192     /*        break;
0193 
0194             default:
0195                 // implement the others cases
0196             break;*/
0197     //     }
0198 }
0199 
0200 Okular::Sound *createSoundFromPopplerSound(const Poppler::SoundObject *popplerSound)
0201 {
0202     Okular::Sound *sound = popplerSound->soundType() == Poppler::SoundObject::Embedded ? new Okular::Sound(popplerSound->data()) : new Okular::Sound(popplerSound->url());
0203     sound->setSamplingRate(popplerSound->samplingRate());
0204     sound->setChannels(popplerSound->channels());
0205     sound->setBitsPerSample(popplerSound->bitsPerSample());
0206     switch (popplerSound->soundEncoding()) {
0207     case Poppler::SoundObject::Raw:
0208         sound->setSoundEncoding(Okular::Sound::Raw);
0209         break;
0210     case Poppler::SoundObject::Signed:
0211         sound->setSoundEncoding(Okular::Sound::Signed);
0212         break;
0213     case Poppler::SoundObject::muLaw:
0214         sound->setSoundEncoding(Okular::Sound::muLaw);
0215         break;
0216     case Poppler::SoundObject::ALaw:
0217         sound->setSoundEncoding(Okular::Sound::ALaw);
0218         break;
0219     }
0220     return sound;
0221 }
0222 
0223 Okular::Movie *createMovieFromPopplerMovie(const Poppler::MovieObject *popplerMovie)
0224 {
0225     Okular::Movie *movie = new Okular::Movie(popplerMovie->url());
0226     movie->setSize(popplerMovie->size());
0227     movie->setRotation((Okular::Rotation)(popplerMovie->rotation() / 90));
0228     movie->setShowControls(popplerMovie->showControls());
0229     movie->setPlayMode((Okular::Movie::PlayMode)popplerMovie->playMode());
0230     movie->setAutoPlay(false); // will be triggered by external MovieAnnotation
0231     movie->setStartPaused(false);
0232     movie->setShowPosterImage(popplerMovie->showPosterImage());
0233     movie->setPosterImage(popplerMovie->posterImage());
0234     return movie;
0235 }
0236 
0237 Okular::Movie *createMovieFromPopplerScreen(const Poppler::LinkRendition *popplerScreen)
0238 {
0239     Poppler::MediaRendition *rendition = popplerScreen->rendition();
0240     Okular::Movie *movie = nullptr;
0241     if (rendition->isEmbedded()) {
0242         movie = new Okular::Movie(rendition->fileName(), rendition->data());
0243     } else {
0244         movie = new Okular::Movie(rendition->fileName());
0245     }
0246     movie->setSize(rendition->size());
0247     movie->setShowControls(rendition->showControls());
0248     if (rendition->repeatCount() == 0) {
0249         movie->setPlayMode(Okular::Movie::PlayRepeat);
0250     } else {
0251         movie->setPlayMode(Okular::Movie::PlayLimited);
0252         movie->setPlayRepetitions(rendition->repeatCount());
0253     }
0254     /**
0255      * Warning: Confusing flag name from PDF spec. Described as:
0256      * > If true, the media should automatically play when activated.
0257      * > If false, the media should be initially paused when activated
0258      * To set autoplay, page actions are used.
0259      */
0260     movie->setStartPaused(!rendition->autoPlay());
0261     return movie;
0262 }
0263 
0264 QPair<Okular::Movie *, Okular::EmbeddedFile *> createMovieFromPopplerRichMedia(const Poppler::RichMediaAnnotation *popplerRichMedia)
0265 {
0266     const QPair<Okular::Movie *, Okular::EmbeddedFile *> emptyResult(nullptr, nullptr);
0267 
0268     /**
0269      * To convert a Flash/Video based RichMedia annotation to a movie, we search for the first
0270      * Flash/Video richmedia instance and parse the flashVars parameter for the 'source' identifier.
0271      * That identifier is then used to find the associated embedded file through the assets
0272      * mapping.
0273      */
0274     const Poppler::RichMediaAnnotation::Content *content = popplerRichMedia->content();
0275     if (!content) {
0276         return emptyResult;
0277     }
0278 
0279     const QList<Poppler::RichMediaAnnotation::Configuration *> configurations = content->configurations();
0280     if (configurations.isEmpty()) {
0281         return emptyResult;
0282     }
0283 
0284     const Poppler::RichMediaAnnotation::Configuration *configuration = configurations[0];
0285 
0286     const QList<Poppler::RichMediaAnnotation::Instance *> instances = configuration->instances();
0287     if (instances.isEmpty()) {
0288         return emptyResult;
0289     }
0290 
0291     const Poppler::RichMediaAnnotation::Instance *instance = instances[0];
0292 
0293     if ((instance->type() != Poppler::RichMediaAnnotation::Instance::TypeFlash) && (instance->type() != Poppler::RichMediaAnnotation::Instance::TypeVideo)) {
0294         return emptyResult;
0295     }
0296 
0297     const Poppler::RichMediaAnnotation::Params *params = instance->params();
0298     if (!params) {
0299         return emptyResult;
0300     }
0301 
0302     QString sourceId;
0303     bool playbackLoops = false;
0304 
0305     const QStringList flashVars = params->flashVars().split(QLatin1Char('&'));
0306     for (const QString &flashVar : flashVars) {
0307         const int pos = flashVar.indexOf(QLatin1Char('='));
0308         if (pos == -1) {
0309             continue;
0310         }
0311 
0312         const QString key = flashVar.left(pos);
0313         const QString value = flashVar.mid(pos + 1);
0314 
0315         if (key == QLatin1String("source")) {
0316             sourceId = value;
0317         } else if (key == QLatin1String("loop")) {
0318             playbackLoops = (value == QLatin1String("true") ? true : false);
0319         }
0320     }
0321 
0322     if (sourceId.isEmpty()) {
0323         return emptyResult;
0324     }
0325 
0326     const QList<Poppler::RichMediaAnnotation::Asset *> assets = content->assets();
0327     if (assets.isEmpty()) {
0328         return emptyResult;
0329     }
0330 
0331     Poppler::RichMediaAnnotation::Asset *matchingAsset = nullptr;
0332     for (Poppler::RichMediaAnnotation::Asset *asset : assets) {
0333         if (asset->name() == sourceId) {
0334             matchingAsset = asset;
0335             break;
0336         }
0337     }
0338 
0339     if (!matchingAsset) {
0340         return emptyResult;
0341     }
0342 
0343     Poppler::EmbeddedFile *embeddedFile = matchingAsset->embeddedFile();
0344     if (!embeddedFile) {
0345         return emptyResult;
0346     }
0347 
0348     Okular::EmbeddedFile *pdfEmbeddedFile = new PDFEmbeddedFile(embeddedFile);
0349 
0350     Okular::Movie *movie = new Okular::Movie(embeddedFile->name(), embeddedFile->data());
0351     movie->setPlayMode(playbackLoops ? Okular::Movie::PlayRepeat : Okular::Movie::PlayLimited);
0352 
0353     if (popplerRichMedia && popplerRichMedia->settings() && popplerRichMedia->settings()->activation()) {
0354         if (popplerRichMedia->settings()->activation()->condition() == Poppler::RichMediaAnnotation::Activation::PageOpened ||
0355             popplerRichMedia->settings()->activation()->condition() == Poppler::RichMediaAnnotation::Activation::PageVisible) {
0356             movie->setAutoPlay(true);
0357         } else {
0358             movie->setAutoPlay(false);
0359         }
0360 
0361     } else {
0362         movie->setAutoPlay(false);
0363     }
0364 
0365     return qMakePair(movie, pdfEmbeddedFile);
0366 }
0367 
0368 static Okular::DocumentAction::DocumentActionType popplerToOkular(Poppler::LinkAction::ActionType pat)
0369 {
0370     switch (pat) {
0371     case Poppler::LinkAction::PageFirst:
0372         return Okular::DocumentAction::PageFirst;
0373     case Poppler::LinkAction::PagePrev:
0374         return Okular::DocumentAction::PagePrev;
0375     case Poppler::LinkAction::PageNext:
0376         return Okular::DocumentAction::PageNext;
0377     case Poppler::LinkAction::PageLast:
0378         return Okular::DocumentAction::PageLast;
0379     case Poppler::LinkAction::HistoryBack:
0380         return Okular::DocumentAction::HistoryBack;
0381     case Poppler::LinkAction::HistoryForward:
0382         return Okular::DocumentAction::HistoryForward;
0383     case Poppler::LinkAction::Quit:
0384         return Okular::DocumentAction::Quit;
0385     case Poppler::LinkAction::Presentation:
0386         return Okular::DocumentAction::Presentation;
0387     case Poppler::LinkAction::EndPresentation:
0388         return Okular::DocumentAction::EndPresentation;
0389     case Poppler::LinkAction::Find:
0390         return Okular::DocumentAction::Find;
0391     case Poppler::LinkAction::GoToPage:
0392         return Okular::DocumentAction::GoToPage;
0393     case Poppler::LinkAction::Close:
0394         return Okular::DocumentAction::Close;
0395     case Poppler::LinkAction::Print:
0396         return Okular::DocumentAction::Print;
0397 #if POPPLER_VERSION_MACRO >= QT_VERSION_CHECK(22, 04, 0)
0398     case Poppler::LinkAction::SaveAs:
0399         return Okular::DocumentAction::SaveAs;
0400 #endif
0401     }
0402 
0403     qWarning() << "Unsupported Poppler::LinkAction::ActionType" << pat;
0404     // TODO When we can use C++17 make this function return an optional
0405     //      for now it's not super important since at the time of writing
0406     //      okular DocumentAction supports all that poppler ActionType supports
0407     return Okular::DocumentAction::PageFirst;
0408 }
0409 
0410 /**
0411  * Note: the function will take ownership of the popplerLink object.
0412  */
0413 Okular::Action *createLinkFromPopplerLink(const Poppler::Link *popplerLink, bool deletePopplerLink)
0414 {
0415     if (!popplerLink) {
0416         return nullptr;
0417     }
0418 
0419     Okular::Action *link = nullptr;
0420     const Poppler::LinkGoto *popplerLinkGoto;
0421     const Poppler::LinkExecute *popplerLinkExecute;
0422     const Poppler::LinkBrowse *popplerLinkBrowse;
0423     const Poppler::LinkAction *popplerLinkAction;
0424     const Poppler::LinkSound *popplerLinkSound;
0425     const Poppler::LinkJavaScript *popplerLinkJS;
0426     const Poppler::LinkMovie *popplerLinkMovie;
0427     const Poppler::LinkRendition *popplerLinkRendition;
0428     Okular::DocumentViewport viewport;
0429 
0430     switch (popplerLink->linkType()) {
0431     case Poppler::Link::None:
0432         break;
0433 
0434     case Poppler::Link::Goto: {
0435         popplerLinkGoto = static_cast<const Poppler::LinkGoto *>(popplerLink);
0436         const Poppler::LinkDestination dest = popplerLinkGoto->destination();
0437         const QString destName = dest.destinationName();
0438         if (destName.isEmpty()) {
0439             fillViewportFromLinkDestination(viewport, dest);
0440             link = new Okular::GotoAction(popplerLinkGoto->fileName(), viewport);
0441         } else {
0442             link = new Okular::GotoAction(popplerLinkGoto->fileName(), destName);
0443         }
0444     } break;
0445 
0446     case Poppler::Link::Execute:
0447         popplerLinkExecute = static_cast<const Poppler::LinkExecute *>(popplerLink);
0448         link = new Okular::ExecuteAction(popplerLinkExecute->fileName(), popplerLinkExecute->parameters());
0449         break;
0450 
0451     case Poppler::Link::Browse:
0452         popplerLinkBrowse = static_cast<const Poppler::LinkBrowse *>(popplerLink);
0453         link = new Okular::BrowseAction(QUrl(popplerLinkBrowse->url()));
0454         break;
0455 
0456     case Poppler::Link::Action:
0457         popplerLinkAction = static_cast<const Poppler::LinkAction *>(popplerLink);
0458         link = new Okular::DocumentAction(popplerToOkular(popplerLinkAction->actionType()));
0459         break;
0460 
0461     case Poppler::Link::Sound: {
0462         popplerLinkSound = static_cast<const Poppler::LinkSound *>(popplerLink);
0463         Poppler::SoundObject *popplerSound = popplerLinkSound->sound();
0464         Okular::Sound *sound = createSoundFromPopplerSound(popplerSound);
0465         link = new Okular::SoundAction(popplerLinkSound->volume(), popplerLinkSound->synchronous(), popplerLinkSound->repeat(), popplerLinkSound->mix(), sound);
0466     } break;
0467 
0468     case Poppler::Link::JavaScript: {
0469         popplerLinkJS = static_cast<const Poppler::LinkJavaScript *>(popplerLink);
0470         link = new Okular::ScriptAction(Okular::JavaScript, popplerLinkJS->script());
0471     } break;
0472 
0473     case Poppler::Link::Rendition: {
0474         if (!deletePopplerLink) {
0475             // If links should not be deleted it probably means that they
0476             // are part of a nextActions chain. There is no support
0477             // to resolveMediaLinkReferences on nextActions. It would also
0478             // be necessary to ensure that resolveMediaLinkReferences does
0479             // not delete the Links which are part of a nextActions list
0480             // to avoid a double deletion.
0481             qCDebug(OkularPdfDebug) << "parsing rendition link without deletion is not supported. Action chain might be broken.";
0482             break;
0483         }
0484         deletePopplerLink = false; // we'll delete it inside resolveMediaLinkReferences() after we have resolved all references
0485 
0486         popplerLinkRendition = static_cast<const Poppler::LinkRendition *>(popplerLink);
0487 
0488         Okular::RenditionAction::OperationType operation = Okular::RenditionAction::None;
0489         switch (popplerLinkRendition->action()) {
0490         case Poppler::LinkRendition::NoRendition:
0491             operation = Okular::RenditionAction::None;
0492             break;
0493         case Poppler::LinkRendition::PlayRendition:
0494             operation = Okular::RenditionAction::Play;
0495             break;
0496         case Poppler::LinkRendition::StopRendition:
0497             operation = Okular::RenditionAction::Stop;
0498             break;
0499         case Poppler::LinkRendition::PauseRendition:
0500             operation = Okular::RenditionAction::Pause;
0501             break;
0502         case Poppler::LinkRendition::ResumeRendition:
0503             operation = Okular::RenditionAction::Resume;
0504             break;
0505         };
0506 
0507         Okular::Movie *movie = nullptr;
0508         if (popplerLinkRendition->rendition()) {
0509             movie = createMovieFromPopplerScreen(popplerLinkRendition);
0510         }
0511 
0512         Okular::RenditionAction *renditionAction = new Okular::RenditionAction(operation, movie, Okular::JavaScript, popplerLinkRendition->script());
0513         renditionAction->setNativeId(QVariant::fromValue(popplerLinkRendition));
0514         link = renditionAction;
0515     } break;
0516 
0517     case Poppler::Link::Movie: {
0518         if (!deletePopplerLink) {
0519             // See comment above in Link::Rendition
0520             qCDebug(OkularPdfDebug) << "parsing movie link without deletion is not supported. Action chain might be broken.";
0521             break;
0522         }
0523         deletePopplerLink = false; // we'll delete it inside resolveMediaLinkReferences() after we have resolved all references
0524 
0525         popplerLinkMovie = static_cast<const Poppler::LinkMovie *>(popplerLink);
0526 
0527         Okular::MovieAction::OperationType operation = Okular::MovieAction::Play;
0528         switch (popplerLinkMovie->operation()) {
0529         case Poppler::LinkMovie::Play:
0530             operation = Okular::MovieAction::Play;
0531             break;
0532         case Poppler::LinkMovie::Stop:
0533             operation = Okular::MovieAction::Stop;
0534             break;
0535         case Poppler::LinkMovie::Pause:
0536             operation = Okular::MovieAction::Pause;
0537             break;
0538         case Poppler::LinkMovie::Resume:
0539             operation = Okular::MovieAction::Resume;
0540             break;
0541         };
0542 
0543         Okular::MovieAction *movieAction = new Okular::MovieAction(operation);
0544         movieAction->setNativeId(QVariant::fromValue(popplerLinkMovie));
0545         link = movieAction;
0546     } break;
0547 
0548     case Poppler::Link::Hide: {
0549         const Poppler::LinkHide *l = static_cast<const Poppler::LinkHide *>(popplerLink);
0550         QStringList scripts;
0551         const QVector<QString> targets = l->targets();
0552         for (const QString &target : targets) {
0553             scripts << QStringLiteral("getField(\"%1\").hidden = %2;").arg(target).arg(l->isShowAction() ? QLatin1String("false") : QLatin1String("true"));
0554         }
0555         link = new Okular::ScriptAction(Okular::JavaScript, scripts.join(QLatin1Char('\n')));
0556     } break;
0557 
0558     case Poppler::Link::OCGState:
0559         link = new Okular::BackendOpaqueAction();
0560         link->setNativeId(QVariant::fromValue(static_cast<const Poppler::LinkOCGState *>(popplerLink)));
0561         deletePopplerLink = false;
0562         break;
0563     }
0564 
0565     if (link) {
0566         QVector<Okular::Action *> nextActions;
0567         const QVector<Poppler::Link *> nextLinks = popplerLink->nextLinks();
0568         for (const Poppler::Link *nl : nextLinks) {
0569             nextActions << createLinkFromPopplerLink(nl, false);
0570         }
0571         link->setNextActions(nextActions);
0572     }
0573 
0574     if (deletePopplerLink) {
0575         delete popplerLink;
0576     }
0577 
0578     return link;
0579 }
0580 
0581 /**
0582  * Note: the function will take ownership of the popplerLink objects.
0583  */
0584 static QList<Okular::ObjectRect *> generateLinks(std::vector<std::unique_ptr<Poppler::Link>> &&popplerLinks)
0585 {
0586     QList<Okular::ObjectRect *> links;
0587     for (const auto &popplerLink : popplerLinks) {
0588         QRectF linkArea = popplerLink->linkArea();
0589         double nl = linkArea.left(), nt = linkArea.top(), nr = linkArea.right(), nb = linkArea.bottom();
0590         // create the rect using normalized coords and attach the Okular::Link to it
0591         Okular::ObjectRect *rect = new Okular::ObjectRect(nl, nt, nr, nb, false, Okular::ObjectRect::Action, createLinkFromPopplerLink(popplerLink.get(), false));
0592         // add the ObjectRect to the container
0593         links.push_front(rect);
0594     }
0595     return links;
0596 }
0597 
0598 /** NOTES on threading:
0599  * internal: thread race prevention is done via the 'docLock' mutex. the
0600  *           mutex is needed only because we have the asynchronous thread; else
0601  *           the operations are all within the 'gui' thread, scheduled by the
0602  *           Qt scheduler and no mutex is needed.
0603  * external: dangerous operations are all locked via mutex internally, and the
0604  *           only needed external thing is the 'canGeneratePixmap' method
0605  *           that tells if the generator is free (since we don't want an
0606  *           internal queue to store PixmapRequests). A generatedPixmap call
0607  *           without the 'ready' flag set, results in undefined behavior.
0608  * So, as example, printing while generating a pixmap asynchronously is safe,
0609  * it might only block the gui thread by 1) waiting for the mutex to unlock
0610  * in async thread and 2) doing the 'heavy' print operation.
0611  */
0612 
0613 OKULAR_EXPORT_PLUGIN(PDFGenerator, "libokularGenerator_poppler.json")
0614 
0615 static void PDFGeneratorPopplerDebugFunction(const QString &message, const QVariant &closure)
0616 {
0617     Q_UNUSED(closure);
0618     qCDebug(OkularPdfDebug) << "[Poppler]" << message;
0619 }
0620 
0621 PDFGenerator::PDFGenerator(QObject *parent, const QVariantList &args)
0622     : Generator(parent, args)
0623     , pdfdoc(nullptr)
0624     , docSynopsisDirty(true)
0625     , xrefReconstructed(false)
0626     , docEmbeddedFilesDirty(true)
0627     , nextFontPage(0)
0628     , annotProxy(nullptr)
0629     , certStore(nullptr)
0630 {
0631     setFeature(Threaded);
0632     setFeature(TextExtraction);
0633     setFeature(FontInfo);
0634 #ifdef Q_OS_WIN32
0635     setFeature(PrintNative);
0636 #else
0637     setFeature(PrintPostscript);
0638 #endif
0639     if (Okular::FilePrinter::ps2pdfAvailable()) {
0640         setFeature(PrintToFile);
0641     }
0642     setFeature(ReadRawData);
0643     setFeature(TiledRendering);
0644     setFeature(SwapBackingFile);
0645     setFeature(SupportsCancelling);
0646 
0647     // You only need to do it once not for each of the documents but it is cheap enough
0648     // so doing it all the time won't hurt either
0649     Poppler::setDebugErrorFunction(PDFGeneratorPopplerDebugFunction, QVariant());
0650     if (!PDFSettings::useDefaultCertDB()) {
0651         Poppler::setNSSDir(QUrl(PDFSettings::dBCertificatePath()).toLocalFile());
0652     }
0653 #if POPPLER_VERSION_MACRO >= QT_VERSION_CHECK(23, 06, 0)
0654     auto activeBackend = PDFSettingsWidget::settingStringToPopplerEnum(PDFSettings::signatureBackend());
0655     if (activeBackend) {
0656         Poppler::setActiveCryptoSignBackend(activeBackend.value());
0657     }
0658 #endif
0659 }
0660 
0661 PDFGenerator::~PDFGenerator()
0662 {
0663     delete pdfOptionsPage;
0664     delete certStore;
0665 }
0666 
0667 // BEGIN Generator inherited functions
0668 Okular::Document::OpenResult PDFGenerator::loadDocumentWithPassword(const QString &filePath, QVector<Okular::Page *> &pagesVector, const QString &password)
0669 {
0670 #ifndef NDEBUG
0671     if (pdfdoc) {
0672         qCDebug(OkularPdfDebug) << "PDFGenerator: multiple calls to loadDocument. Check it.";
0673         return Okular::Document::OpenError;
0674     }
0675 #endif
0676     // create PDFDoc for the given file
0677     pdfdoc = Poppler::Document::load(filePath, nullptr, nullptr);
0678     return init(pagesVector, password);
0679 }
0680 
0681 Okular::Document::OpenResult PDFGenerator::loadDocumentFromDataWithPassword(const QByteArray &fileData, QVector<Okular::Page *> &pagesVector, const QString &password)
0682 {
0683 #ifndef NDEBUG
0684     if (pdfdoc) {
0685         qCDebug(OkularPdfDebug) << "PDFGenerator: multiple calls to loadDocument. Check it.";
0686         return Okular::Document::OpenError;
0687     }
0688 #endif
0689     // create PDFDoc for the given file
0690     pdfdoc = Poppler::Document::loadFromData(fileData, nullptr, nullptr);
0691     return init(pagesVector, password);
0692 }
0693 
0694 Okular::Document::OpenResult PDFGenerator::init(QVector<Okular::Page *> &pagesVector, const QString &password)
0695 {
0696     if (!pdfdoc) {
0697         return Okular::Document::OpenError;
0698     }
0699 
0700     if (pdfdoc->isLocked()) {
0701         pdfdoc->unlock(password.toLatin1(), password.toLatin1());
0702 
0703         if (pdfdoc->isLocked()) {
0704             pdfdoc->unlock(password.toUtf8(), password.toUtf8());
0705 
0706             if (pdfdoc->isLocked()) {
0707                 pdfdoc.reset();
0708                 return Okular::Document::OpenNeedsPassword;
0709             }
0710         }
0711     }
0712 
0713     xrefReconstructed = false;
0714     if (pdfdoc->xrefWasReconstructed()) {
0715         xrefReconstructionHandler();
0716     } else {
0717         std::function<void()> cb = std::bind(&PDFGenerator::xrefReconstructionHandler, this);
0718         pdfdoc->setXRefReconstructedCallback(cb);
0719     }
0720 
0721     // build Pages (currentPage was set -1 by deletePages)
0722     int pageCount = pdfdoc->numPages();
0723     if (pageCount < 0) {
0724         pdfdoc.reset();
0725         return Okular::Document::OpenError;
0726     }
0727     pagesVector.resize(pageCount);
0728     rectsGenerated.fill(false, pageCount);
0729 
0730     annotationsOnOpenHash.clear();
0731 
0732     loadPages(pagesVector, 0, false);
0733 
0734     // update the configuration
0735     reparseConfig();
0736 
0737     // create annotation proxy
0738     annotProxy = new PopplerAnnotationProxy(pdfdoc.get(), userMutex(), &annotationsOnOpenHash);
0739 
0740     // the file has been loaded correctly
0741     return Okular::Document::OpenSuccess;
0742 }
0743 
0744 PDFGenerator::SwapBackingFileResult PDFGenerator::swapBackingFile(QString const &newFileName, QVector<Okular::Page *> &newPagesVector)
0745 {
0746     const QBitArray oldRectsGenerated = rectsGenerated;
0747 
0748     doCloseDocument();
0749     auto openResult = loadDocumentWithPassword(newFileName, newPagesVector, QString());
0750     if (openResult != Okular::Document::OpenSuccess) {
0751         return SwapBackingFileError;
0752     }
0753 
0754     // Recreate links if needed since they are done on image() and image() is not called when swapping the file
0755     // since the page is already rendered
0756     if (oldRectsGenerated.count() == rectsGenerated.count()) {
0757         for (int i = 0; i < oldRectsGenerated.count(); ++i) {
0758             if (oldRectsGenerated[i]) {
0759                 Okular::Page *page = newPagesVector[i];
0760 
0761                 std::unique_ptr<Poppler::Page> pp = pdfdoc->page(i);
0762                 if (pp) {
0763                     page->setObjectRects(generateLinks(pp->links()));
0764                     rectsGenerated[i] = true;
0765                     resolveMediaLinkReferences(page);
0766                 }
0767             }
0768         }
0769     }
0770 
0771     return SwapBackingFileReloadInternalData;
0772 }
0773 
0774 bool PDFGenerator::doCloseDocument()
0775 {
0776     // remove internal objects
0777     userMutex()->lock();
0778     delete annotProxy;
0779     annotProxy = nullptr;
0780     pdfdoc = nullptr;
0781     userMutex()->unlock();
0782     docSynopsisDirty = true;
0783     docSyn.clear();
0784     docEmbeddedFilesDirty = true;
0785     qDeleteAll(docEmbeddedFiles);
0786     docEmbeddedFiles.clear();
0787     nextFontPage = 0;
0788     rectsGenerated.clear();
0789 
0790     return true;
0791 }
0792 
0793 void PDFGenerator::loadPages(QVector<Okular::Page *> &pagesVector, int rotation, bool clear)
0794 {
0795     // TODO XPDF 3.01 check
0796     const int count = pagesVector.count();
0797     double w = 0, h = 0;
0798     for (int i = 0; i < count; i++) {
0799         // get xpdf page
0800         std::unique_ptr<Poppler::Page> p = pdfdoc->page(i);
0801         Okular::Page *page;
0802         if (p) {
0803             const QSizeF pSize = p->pageSizeF();
0804             w = pSize.width() / 72.0 * dpi().width();
0805             h = pSize.height() / 72.0 * dpi().height();
0806             Okular::Rotation orientation = Okular::Rotation0;
0807             switch (p->orientation()) {
0808             case Poppler::Page::Landscape:
0809                 orientation = Okular::Rotation90;
0810                 break;
0811             case Poppler::Page::UpsideDown:
0812                 orientation = Okular::Rotation180;
0813                 break;
0814             case Poppler::Page::Seascape:
0815                 orientation = Okular::Rotation270;
0816                 break;
0817             case Poppler::Page::Portrait:
0818                 orientation = Okular::Rotation0;
0819                 break;
0820             }
0821             if (rotation % 2 == 1) {
0822                 qSwap(w, h);
0823             }
0824             // init a Okular::page, add transition and annotation information
0825             page = new Okular::Page(i, w, h, orientation);
0826             addTransition(p.get(), page);
0827             if (true) { // TODO real check
0828                 addAnnotations(p.get(), page);
0829             }
0830             std::unique_ptr<Poppler::Link> tmplink = p->action(Poppler::Page::Opening);
0831             if (tmplink) {
0832                 page->setPageAction(Okular::Page::Opening, createLinkFromPopplerLink(tmplink.get(), false));
0833             }
0834             tmplink = p->action(Poppler::Page::Closing);
0835             if (tmplink) {
0836                 page->setPageAction(Okular::Page::Closing, createLinkFromPopplerLink(tmplink.get(), false));
0837             }
0838             page->setDuration(p->duration());
0839             page->setLabel(p->label());
0840 
0841             QList<Okular::FormField *> okularFormFields;
0842             if (i > 0) { // for page 0 we handle the form fields at the end
0843                 okularFormFields = getFormFields(p.get());
0844             }
0845             if (!okularFormFields.isEmpty()) {
0846                 page->setFormFields(okularFormFields);
0847             }
0848             // qWarning(PDFDebug).nospace() << page->width() << "x" << page->height();
0849 
0850 #ifdef PDFGENERATOR_DEBUG
0851             qCDebug(OkularPdfDebug) << "load page" << i << "with rotation" << rotation << "and orientation" << orientation;
0852 #endif
0853             if (clear && pagesVector[i]) {
0854                 delete pagesVector[i];
0855             }
0856         } else {
0857             page = new Okular::Page(i, defaultPageWidth, defaultPageHeight, Okular::Rotation0);
0858         }
0859         // set the Okular::page at the right position in document's pages vector
0860         pagesVector[i] = page;
0861     }
0862 
0863     // Once we've added the signatures to all pages except page 0, we add all the missing signatures there
0864     // we do that because there's signatures that don't belong to any page, but okular needs a page<->signature mapping
0865     if (count > 0) {
0866         std::vector<std::unique_ptr<Poppler::FormFieldSignature>> allSignatures = pdfdoc->signatures();
0867         std::unique_ptr<Poppler::Page> page0(pdfdoc->page(0));
0868         QList<Okular::FormField *> page0FormFields = getFormFields(page0.get());
0869 
0870         for (auto &s : allSignatures) {
0871             bool createSignature = true;
0872             const QString fullyQualifiedName = s->fullyQualifiedName();
0873             auto compareSignatureByFullyQualifiedName = [&fullyQualifiedName](const Okular::FormField *off) { return off->fullyQualifiedName() == fullyQualifiedName; };
0874 
0875             // See if the signature is in one of the already loaded page (i.e. 1 to end)
0876             for (Okular::Page *p : std::as_const(pagesVector)) {
0877                 const QList<Okular::FormField *> pageFormFields = p->formFields();
0878                 if (std::find_if(pageFormFields.begin(), pageFormFields.end(), compareSignatureByFullyQualifiedName) != pageFormFields.end()) {
0879                     createSignature = false;
0880                     break;
0881                 }
0882             }
0883             // See if the signature is in page 0
0884             if (createSignature && std::find_if(page0FormFields.constBegin(), page0FormFields.constEnd(), compareSignatureByFullyQualifiedName) != page0FormFields.constEnd()) {
0885                 createSignature = false;
0886             }
0887             // Otherwise it's a page-less signature, add it to page 0
0888             if (createSignature) {
0889                 Okular::FormField *of = new PopplerFormFieldSignature(std::move(s));
0890                 page0FormFields.append(of);
0891             }
0892         }
0893 
0894         if (!page0FormFields.isEmpty()) {
0895             pagesVector[0]->setFormFields(page0FormFields);
0896         }
0897     }
0898 }
0899 
0900 Okular::DocumentInfo PDFGenerator::generateDocumentInfo(const QSet<Okular::DocumentInfo::Key> &keys) const
0901 {
0902     Okular::DocumentInfo docInfo;
0903     docInfo.set(Okular::DocumentInfo::MimeType, QStringLiteral("application/pdf"));
0904 
0905     userMutex()->lock();
0906 
0907     if (pdfdoc) {
0908         // compile internal structure reading properties from PDFDoc
0909         if (keys.contains(Okular::DocumentInfo::Title)) {
0910             docInfo.set(Okular::DocumentInfo::Title, pdfdoc->info(QStringLiteral("Title")));
0911         }
0912         if (keys.contains(Okular::DocumentInfo::Subject)) {
0913             docInfo.set(Okular::DocumentInfo::Subject, pdfdoc->info(QStringLiteral("Subject")));
0914         }
0915         if (keys.contains(Okular::DocumentInfo::Author)) {
0916             docInfo.set(Okular::DocumentInfo::Author, pdfdoc->info(QStringLiteral("Author")));
0917         }
0918         if (keys.contains(Okular::DocumentInfo::Keywords)) {
0919             docInfo.set(Okular::DocumentInfo::Keywords, pdfdoc->info(QStringLiteral("Keywords")));
0920         }
0921         if (keys.contains(Okular::DocumentInfo::Creator)) {
0922             docInfo.set(Okular::DocumentInfo::Creator, pdfdoc->info(QStringLiteral("Creator")));
0923         }
0924         if (keys.contains(Okular::DocumentInfo::Producer)) {
0925             docInfo.set(Okular::DocumentInfo::Producer, pdfdoc->info(QStringLiteral("Producer")));
0926         }
0927         if (keys.contains(Okular::DocumentInfo::CreationDate)) {
0928             docInfo.set(Okular::DocumentInfo::CreationDate, QLocale().toString(pdfdoc->date(QStringLiteral("CreationDate")), QLocale::LongFormat));
0929         }
0930         if (keys.contains(Okular::DocumentInfo::ModificationDate)) {
0931             docInfo.set(Okular::DocumentInfo::ModificationDate, QLocale().toString(pdfdoc->date(QStringLiteral("ModDate")), QLocale::LongFormat));
0932         }
0933         if (keys.contains(Okular::DocumentInfo::CustomKeys)) {
0934             int major, minor;
0935             auto version = pdfdoc->getPdfVersion();
0936             major = version.major;
0937             minor = version.minor;
0938             docInfo.set(QStringLiteral("format"), i18nc("PDF v. <version>", "PDF v. %1.%2", major, minor), i18n("Format"));
0939             docInfo.set(QStringLiteral("encryption"), pdfdoc->isEncrypted() ? i18n("Encrypted") : i18n("Unencrypted"), i18n("Security"));
0940             docInfo.set(QStringLiteral("optimization"), pdfdoc->isLinearized() ? i18n("Yes") : i18n("No"), i18n("Optimized"));
0941         }
0942 
0943         docInfo.set(Okular::DocumentInfo::Pages, QString::number(pdfdoc->numPages()));
0944     }
0945     userMutex()->unlock();
0946 
0947     return docInfo;
0948 }
0949 
0950 const Okular::DocumentSynopsis *PDFGenerator::generateDocumentSynopsis()
0951 {
0952     if (!docSynopsisDirty) {
0953         return &docSyn;
0954     }
0955 
0956     if (!pdfdoc) {
0957         return nullptr;
0958     }
0959 
0960     userMutex()->lock();
0961     const QVector<Poppler::OutlineItem> outline = pdfdoc->outline();
0962     userMutex()->unlock();
0963 
0964     if (outline.isEmpty()) {
0965         return nullptr;
0966     }
0967 
0968     addSynopsisChildren(outline, &docSyn);
0969 
0970     docSynopsisDirty = false;
0971     return &docSyn;
0972 }
0973 
0974 static Okular::FontInfo::FontType convertPopplerFontInfoTypeToOkularFontInfoType(Poppler::FontInfo::Type type)
0975 {
0976     switch (type) {
0977     case Poppler::FontInfo::Type1:
0978         return Okular::FontInfo::Type1;
0979         break;
0980     case Poppler::FontInfo::Type1C:
0981         return Okular::FontInfo::Type1C;
0982         break;
0983     case Poppler::FontInfo::Type3:
0984         return Okular::FontInfo::Type3;
0985         break;
0986     case Poppler::FontInfo::TrueType:
0987         return Okular::FontInfo::TrueType;
0988         break;
0989     case Poppler::FontInfo::CIDType0:
0990         return Okular::FontInfo::CIDType0;
0991         break;
0992     case Poppler::FontInfo::CIDType0C:
0993         return Okular::FontInfo::CIDType0C;
0994         break;
0995     case Poppler::FontInfo::CIDTrueType:
0996         return Okular::FontInfo::CIDTrueType;
0997         break;
0998     case Poppler::FontInfo::Type1COT:
0999         return Okular::FontInfo::Type1COT;
1000         break;
1001     case Poppler::FontInfo::TrueTypeOT:
1002         return Okular::FontInfo::TrueTypeOT;
1003         break;
1004     case Poppler::FontInfo::CIDType0COT:
1005         return Okular::FontInfo::CIDType0COT;
1006         break;
1007     case Poppler::FontInfo::CIDTrueTypeOT:
1008         return Okular::FontInfo::CIDTrueTypeOT;
1009         break;
1010     case Poppler::FontInfo::unknown:
1011     default:;
1012     }
1013     return Okular::FontInfo::Unknown;
1014 }
1015 
1016 static Okular::FontInfo::EmbedType embedTypeForPopplerFontInfo(const Poppler::FontInfo &fi)
1017 {
1018     Okular::FontInfo::EmbedType ret = Okular::FontInfo::NotEmbedded;
1019     if (fi.isEmbedded()) {
1020         if (fi.isSubset()) {
1021             ret = Okular::FontInfo::EmbeddedSubset;
1022         } else {
1023             ret = Okular::FontInfo::FullyEmbedded;
1024         }
1025     }
1026     return ret;
1027 }
1028 
1029 Okular::FontInfo::List PDFGenerator::fontsForPage(int page)
1030 {
1031     Okular::FontInfo::List list;
1032 
1033     if (page != nextFontPage) {
1034         return list;
1035     }
1036 
1037     QList<Poppler::FontInfo> fonts;
1038     userMutex()->lock();
1039 
1040     {
1041         std::unique_ptr<Poppler::FontIterator> it = pdfdoc->newFontIterator(page);
1042         if (it->hasNext()) {
1043             fonts = it->next();
1044         }
1045     }
1046     userMutex()->unlock();
1047 
1048     for (const Poppler::FontInfo &font : std::as_const(fonts)) {
1049         Okular::FontInfo of;
1050         of.setName(font.name());
1051         of.setSubstituteName(font.substituteName());
1052         of.setType(convertPopplerFontInfoTypeToOkularFontInfoType(font.type()));
1053         of.setEmbedType(embedTypeForPopplerFontInfo(font));
1054         of.setFile(font.file());
1055         of.setCanBeExtracted(of.embedType() != Okular::FontInfo::NotEmbedded);
1056 
1057         QVariant nativeId;
1058         nativeId.setValue(font);
1059         of.setNativeId(nativeId);
1060 
1061         list.append(of);
1062     }
1063 
1064     ++nextFontPage;
1065 
1066     return list;
1067 }
1068 
1069 const QList<Okular::EmbeddedFile *> *PDFGenerator::embeddedFiles() const
1070 {
1071     if (docEmbeddedFilesDirty) {
1072         userMutex()->lock();
1073         const QList<Poppler::EmbeddedFile *> &popplerFiles = pdfdoc->embeddedFiles();
1074         for (Poppler::EmbeddedFile *pef : popplerFiles) {
1075             docEmbeddedFiles.append(new PDFEmbeddedFile(pef));
1076         }
1077         userMutex()->unlock();
1078 
1079         docEmbeddedFilesDirty = false;
1080     }
1081 
1082     return &docEmbeddedFiles;
1083 }
1084 
1085 QAbstractItemModel *PDFGenerator::layersModel() const
1086 {
1087     return pdfdoc->hasOptionalContent() ? pdfdoc->optionalContentModel() : nullptr;
1088 }
1089 
1090 void PDFGenerator::opaqueAction(const Okular::BackendOpaqueAction *action)
1091 {
1092     const Poppler::LinkOCGState *popplerLink = action->nativeId().value<const Poppler::LinkOCGState *>();
1093     pdfdoc->optionalContentModel()->applyLink(const_cast<Poppler::LinkOCGState *>(popplerLink));
1094 }
1095 
1096 void PDFGenerator::freeOpaqueActionContents(const Okular::BackendOpaqueAction &action)
1097 {
1098     delete action.nativeId().value<const Poppler::LinkOCGState *>();
1099 }
1100 
1101 bool PDFGenerator::isAllowed(Okular::Permission permission) const
1102 {
1103     bool b = true;
1104     switch (permission) {
1105     case Okular::AllowModify:
1106         b = pdfdoc->okToChange();
1107         break;
1108     case Okular::AllowCopy:
1109         b = pdfdoc->okToCopy();
1110         break;
1111     case Okular::AllowPrint:
1112         b = pdfdoc->okToPrint();
1113         break;
1114     case Okular::AllowNotes:
1115         b = pdfdoc->okToAddNotes();
1116         break;
1117     case Okular::AllowFillForms:
1118         b = pdfdoc->okToFillForm();
1119         break;
1120     default:;
1121     }
1122     return b;
1123 }
1124 
1125 struct RenderImagePayload {
1126     RenderImagePayload(PDFGenerator *g, Okular::PixmapRequest *r)
1127         : generator(g)
1128         , request(r)
1129     {
1130         // Don't report partial updates for the first 500 ms
1131         timer.setInterval(500);
1132         timer.setSingleShot(true);
1133         timer.start();
1134     }
1135 
1136     PDFGenerator *generator;
1137     Okular::PixmapRequest *request;
1138     QTimer timer;
1139 };
1140 Q_DECLARE_METATYPE(RenderImagePayload *)
1141 
1142 static bool shouldDoPartialUpdateCallback(const QVariant &vPayload)
1143 {
1144     auto payload = vPayload.value<RenderImagePayload *>();
1145 
1146     // Since the timer lives in a thread without an event loop we need to stop it ourselves
1147     // when the remaining time has reached 0
1148     if (payload->timer.isActive() && payload->timer.remainingTime() == 0) {
1149         payload->timer.stop();
1150     }
1151 
1152     return !payload->timer.isActive();
1153 }
1154 
1155 static void partialUpdateCallback(const QImage &image, const QVariant &vPayload)
1156 {
1157     auto payload = vPayload.value<RenderImagePayload *>();
1158     // clang-format off
1159     // Otherwise the Okular::PixmapRequest* gets turned into Okular::PixmapRequest * that is not normalized and is slightly slower
1160     QMetaObject::invokeMethod(payload->generator, "signalPartialPixmapRequest", Qt::QueuedConnection, Q_ARG(Okular::PixmapRequest*, payload->request), Q_ARG(QImage, image));
1161     // clang-format on
1162 }
1163 
1164 static bool shouldAbortRenderCallback(const QVariant &vPayload)
1165 {
1166     auto payload = vPayload.value<RenderImagePayload *>();
1167     return payload->request->shouldAbortRender();
1168 }
1169 
1170 QImage PDFGenerator::image(Okular::PixmapRequest *request)
1171 {
1172     // debug requests to this (xpdf) generator
1173     // qCDebug(OkularPdfDebug) << "id: " << request->id << " is requesting " << (request->async ? "ASYNC" : "sync") <<  " pixmap for page " << request->page->number() << " [" << request->width << " x " << request->height << "].";
1174 
1175     // compute dpi used to get an image with desired width and height
1176     Okular::Page *page = request->page();
1177 
1178     double pageWidth = page->width(), pageHeight = page->height();
1179 
1180     if (page->rotation() % 2) {
1181         qSwap(pageWidth, pageHeight);
1182     }
1183 
1184     qreal fakeDpiX = request->width() / pageWidth * dpi().width();
1185     qreal fakeDpiY = request->height() / pageHeight * dpi().height();
1186 
1187     // generate links rects only the first time
1188     bool genObjectRects = !rectsGenerated.at(page->number());
1189 
1190     // 0. LOCK [waits for the thread end]
1191     userMutex()->lock();
1192 
1193     if (request->shouldAbortRender()) {
1194         userMutex()->unlock();
1195         return QImage();
1196     }
1197 
1198     // 1. Set OutputDev parameters and Generate contents
1199     // note: thread safety is set on 'false' for the GUI (this) thread
1200     std::unique_ptr<Poppler::Page> p = pdfdoc->page(page->number());
1201 
1202     // 2. Take data from outputdev and attach it to the Page
1203     QImage img;
1204     if (p) {
1205         if (request->isTile()) {
1206             const QRect rect = request->normalizedRect().geometry(request->width(), request->height());
1207             if (request->partialUpdatesWanted()) {
1208                 RenderImagePayload payload(this, request);
1209                 img = p->renderToImage(
1210                     fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0, partialUpdateCallback, shouldDoPartialUpdateCallback, shouldAbortRenderCallback, QVariant::fromValue(&payload));
1211             } else {
1212                 RenderImagePayload payload(this, request);
1213                 img = p->renderToImage(fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0, nullptr, nullptr, shouldAbortRenderCallback, QVariant::fromValue(&payload));
1214             }
1215         } else {
1216             if (request->partialUpdatesWanted()) {
1217                 RenderImagePayload payload(this, request);
1218                 img = p->renderToImage(fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0, partialUpdateCallback, shouldDoPartialUpdateCallback, shouldAbortRenderCallback, QVariant::fromValue(&payload));
1219             } else {
1220                 RenderImagePayload payload(this, request);
1221                 img = p->renderToImage(fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0, nullptr, nullptr, shouldAbortRenderCallback, QVariant::fromValue(&payload));
1222             }
1223         }
1224     } else {
1225         img = QImage(request->width(), request->height(), QImage::Format_Mono);
1226         img.fill(Qt::white);
1227     }
1228 
1229     if (p && genObjectRects) {
1230         // TODO previously we extracted Image type rects too, but that needed porting to poppler
1231         // and as we are not doing anything with Image type rects i did not port it, have a look at
1232         // dead gp_outputdev.cpp on image extraction
1233         page->setObjectRects(generateLinks(p->links()));
1234         rectsGenerated[request->page()->number()] = true;
1235 
1236         resolveMediaLinkReferences(page);
1237     }
1238 
1239     // 3. UNLOCK [re-enables shared access]
1240     userMutex()->unlock();
1241 
1242     return img;
1243 }
1244 
1245 template<typename PopplerLinkType, typename OkularLinkType, typename PopplerAnnotationType, typename OkularAnnotationType>
1246 void resolveMediaLinks(Okular::Action *action, enum Okular::Annotation::SubType subType, QHash<Okular::Annotation *, Poppler::Annotation *> &annotationsHash)
1247 {
1248     OkularLinkType *okularAction = static_cast<OkularLinkType *>(action);
1249 
1250     const PopplerLinkType *popplerLink = action->nativeId().value<const PopplerLinkType *>();
1251 
1252     QHashIterator<Okular::Annotation *, Poppler::Annotation *> it(annotationsHash);
1253     while (it.hasNext()) {
1254         it.next();
1255 
1256         if (it.key()->subType() == subType) {
1257             const PopplerAnnotationType *popplerAnnotation = static_cast<const PopplerAnnotationType *>(it.value());
1258 
1259             if (popplerLink->isReferencedAnnotation(popplerAnnotation)) {
1260                 okularAction->setAnnotation(static_cast<OkularAnnotationType *>(it.key()));
1261                 okularAction->setNativeId(QVariant());
1262                 delete popplerLink; // delete the associated Poppler::LinkMovie object, it's not needed anymore
1263                 break;
1264             }
1265         }
1266     }
1267 }
1268 
1269 void PDFGenerator::resolveMediaLinkReference(Okular::Action *action)
1270 {
1271     if (!action) {
1272         return;
1273     }
1274 
1275     if ((action->actionType() != Okular::Action::Movie) && (action->actionType() != Okular::Action::Rendition)) {
1276         return;
1277     }
1278 
1279     resolveMediaLinks<Poppler::LinkMovie, Okular::MovieAction, Poppler::MovieAnnotation, Okular::MovieAnnotation>(action, Okular::Annotation::AMovie, annotationsOnOpenHash);
1280     resolveMediaLinks<Poppler::LinkRendition, Okular::RenditionAction, Poppler::ScreenAnnotation, Okular::ScreenAnnotation>(action, Okular::Annotation::AScreen, annotationsOnOpenHash);
1281 }
1282 
1283 void PDFGenerator::resolveMediaLinkReferences(Okular::Page *page)
1284 {
1285     resolveMediaLinkReference(const_cast<Okular::Action *>(page->pageAction(Okular::Page::Opening)));
1286     resolveMediaLinkReference(const_cast<Okular::Action *>(page->pageAction(Okular::Page::Closing)));
1287 
1288     const QList<Okular::Annotation *> annotations = page->annotations();
1289     for (Okular::Annotation *annotation : annotations) {
1290         if (annotation->subType() == Okular::Annotation::AScreen) {
1291             Okular::ScreenAnnotation *screenAnnotation = static_cast<Okular::ScreenAnnotation *>(annotation);
1292             resolveMediaLinkReference(screenAnnotation->additionalAction(Okular::Annotation::PageOpening));
1293             resolveMediaLinkReference(screenAnnotation->additionalAction(Okular::Annotation::PageClosing));
1294         }
1295 
1296         if (annotation->subType() == Okular::Annotation::AWidget) {
1297             Okular::WidgetAnnotation *widgetAnnotation = static_cast<Okular::WidgetAnnotation *>(annotation);
1298             resolveMediaLinkReference(widgetAnnotation->additionalAction(Okular::Annotation::PageOpening));
1299             resolveMediaLinkReference(widgetAnnotation->additionalAction(Okular::Annotation::PageClosing));
1300         }
1301     }
1302 
1303     const QList<Okular::FormField *> fields = page->formFields();
1304     for (Okular::FormField *field : fields) {
1305         resolveMediaLinkReference(field->activationAction());
1306     }
1307 }
1308 
1309 struct TextExtractionPayload {
1310     explicit TextExtractionPayload(Okular::TextRequest *r)
1311         : request(r)
1312     {
1313     }
1314 
1315     Okular::TextRequest *request;
1316 };
1317 Q_DECLARE_METATYPE(TextExtractionPayload *)
1318 
1319 static bool shouldAbortTextExtractionCallback(const QVariant &vPayload)
1320 {
1321     auto payload = vPayload.value<TextExtractionPayload *>();
1322     return payload->request->shouldAbortExtraction();
1323 }
1324 
1325 Okular::TextPage *PDFGenerator::textPage(Okular::TextRequest *request)
1326 {
1327     const Okular::Page *page = request->page();
1328 #ifdef PDFGENERATOR_DEBUG
1329     qCDebug(OkularPdfDebug) << "page" << page->number();
1330 #endif
1331     // build a TextList...
1332     std::vector<std::unique_ptr<Poppler::TextBox>> textList;
1333     double pageWidth, pageHeight;
1334     userMutex()->lock();
1335     if (request->shouldAbortExtraction()) {
1336         userMutex()->unlock();
1337         return nullptr;
1338     }
1339     std::unique_ptr<Poppler::Page> pp = pdfdoc->page(page->number());
1340     if (pp) {
1341         TextExtractionPayload payload(request);
1342         textList = pp->textList(Poppler::Page::Rotate0, shouldAbortTextExtractionCallback, QVariant::fromValue(&payload));
1343         const QSizeF s = pp->pageSizeF();
1344         pageWidth = s.width();
1345         pageHeight = s.height();
1346     } else {
1347         pageWidth = defaultPageWidth;
1348         pageHeight = defaultPageHeight;
1349     }
1350     userMutex()->unlock();
1351 
1352     if (textList.empty() && request->shouldAbortExtraction()) {
1353         return nullptr;
1354     }
1355 
1356     Okular::TextPage *tp = abstractTextPage(textList, pageHeight, pageWidth, (Poppler::Page::Rotation)page->orientation());
1357     return tp;
1358 }
1359 
1360 QByteArray PDFGenerator::requestFontData(const Okular::FontInfo &font)
1361 {
1362     Poppler::FontInfo fi = font.nativeId().value<Poppler::FontInfo>();
1363     return pdfdoc->fontData(fi);
1364 }
1365 
1366 void PDFGenerator::okularToPoppler(const Okular::NewSignatureData &oData, Poppler::PDFConverter::NewSignatureData *pData)
1367 {
1368     pData->setCertNickname(oData.certNickname());
1369     pData->setPassword(oData.password());
1370     pData->setPage(oData.page());
1371     const QString datetime = QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd hh:mm:ss t"));
1372     pData->setSignatureText(i18n("Signed by: %1\n\nDate: %2", oData.certSubjectCommonName(), datetime));
1373     pData->setSignatureLeftText(oData.certSubjectCommonName());
1374     const Okular::NormalizedRect bRect = oData.boundingRectangle();
1375     pData->setBoundingRectangle({bRect.left, bRect.top, bRect.width(), bRect.height()});
1376     pData->setFontColor(Qt::black);
1377     pData->setBorderColor(Qt::black);
1378     pData->setReason(oData.reason());
1379     pData->setLocation(oData.location());
1380     pData->setDocumentOwnerPassword(oData.documentPassword().toLatin1());
1381     pData->setDocumentUserPassword(oData.documentPassword().toLatin1());
1382 }
1383 
1384 #define DUMMY_QPRINTER_COPY
1385 Okular::Document::PrintError PDFGenerator::print(QPrinter &printer)
1386 {
1387     bool printAnnots = true;
1388     bool forceRasterize = false;
1389     PDFOptionsPage::ScaleMode scaleMode = PDFOptionsPage::FitToPrintableArea;
1390 
1391     if (pdfOptionsPage) {
1392         printAnnots = pdfOptionsPage->printAnnots();
1393         forceRasterize = pdfOptionsPage->printForceRaster();
1394         scaleMode = pdfOptionsPage->scaleMode();
1395     }
1396 
1397     const auto overprintPreviewEnabled = PDFSettings::overprintPreviewEnabled();
1398 
1399 #ifdef Q_OS_WIN
1400     // Windows can only print by rasterization, because that is
1401     // currently the only way Okular implements printing without using UNIX-specific
1402     // tools like 'lpr'.
1403     forceRasterize = true;
1404 #endif
1405 
1406     if (forceRasterize) {
1407         pdfdoc->setRenderHint(Poppler::Document::HideAnnotations, !printAnnots);
1408         pdfdoc->setRenderHint(Poppler::Document::OverprintPreview, overprintPreviewEnabled);
1409 
1410         if (pdfOptionsPage) {
1411             // If requested, scale to full page instead of the printable area
1412             printer.setFullPage(pdfOptionsPage->ignorePrintMargins());
1413         }
1414 
1415         QPainter painter;
1416         painter.begin(&printer);
1417 
1418         QList<int> pageList = Okular::FilePrinter::pageList(printer, pdfdoc->numPages(), document()->currentPage() + 1, document()->bookmarkedPageList());
1419         for (int i = 0; i < pageList.count(); ++i) {
1420             if (i != 0) {
1421                 printer.newPage();
1422             }
1423 
1424             const int page = pageList.at(i) - 1;
1425             userMutex()->lock();
1426             std::unique_ptr<Poppler::Page> pp(pdfdoc->page(page));
1427             if (pp) {
1428                 QSizeF pageSize = pp->pageSizeF();      // Unit is 'points' (i.e., 1/72th of an inch)
1429                 QRect painterWindow = painter.window(); // Unit is 'QPrinter::DevicePixel'
1430 
1431                 // Default: no scaling at all, but we need to go from DevicePixel units to 'points'
1432                 // Warning: We compute the horizontal scaling, and later assume that the vertical scaling will be the same.
1433                 double scaling = printer.paperRect(QPrinter::DevicePixel).width() / printer.paperRect(QPrinter::Point).width();
1434 
1435                 if (scaleMode != PDFOptionsPage::None) {
1436                     // Get the two scaling factors needed to fit the page onto paper horizontally or vertically
1437                     auto horizontalScaling = painterWindow.width() / pageSize.width();
1438                     auto verticalScaling = painterWindow.height() / pageSize.height();
1439 
1440                     // We use the smaller of the two for both directions, to keep the aspect ratio
1441                     scaling = std::min(horizontalScaling, verticalScaling);
1442                 }
1443 
1444 #ifdef Q_OS_WIN
1445                 QImage img = pp->renderToImage(printer.physicalDpiX(), printer.physicalDpiY());
1446 #else
1447                 // UNIX: Same resolution as the postscript rasterizer; see discussion at https://git.reviewboard.kde.org/r/130218/
1448                 QImage img = pp->renderToImage(300, 300);
1449 #endif
1450                 painter.drawImage(QRectF(QPointF(0, 0), scaling * pp->pageSizeF()), img);
1451             }
1452             userMutex()->unlock();
1453         }
1454         painter.end();
1455         return Okular::Document::NoPrintError;
1456     }
1457 
1458 #ifdef DUMMY_QPRINTER_COPY
1459     // Get the real page size to pass to the ps generator
1460     QPrinter dummy(QPrinter::PrinterResolution);
1461     dummy.setFullPage(true);
1462     dummy.setPageOrientation(printer.pageLayout().orientation());
1463     dummy.setPageSize(printer.pageLayout().pageSize());
1464     int width = dummy.width();
1465     int height = dummy.height();
1466 #else
1467     int width = printer.width();
1468     int height = printer.height();
1469 #endif
1470 
1471     if (width <= 0 || height <= 0) {
1472         return Okular::Document::InvalidPageSizePrintError;
1473     }
1474 
1475     // Create the tempfile to send to FilePrinter, which will manage the deletion
1476     QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps"));
1477     if (!tf.open()) {
1478         return Okular::Document::TemporaryFileOpenPrintError;
1479     }
1480     QString tempfilename = tf.fileName();
1481 
1482     // Generate the list of pages to be printed as selected in the print dialog
1483     QList<int> pageList = Okular::FilePrinter::pageList(printer, pdfdoc->numPages(), document()->currentPage() + 1, document()->bookmarkedPageList());
1484 
1485     // TODO rotation
1486 
1487     tf.setAutoRemove(false);
1488 
1489     QString pstitle = metaData(QStringLiteral("Title"), QVariant()).toString();
1490     if (pstitle.trimmed().isEmpty()) {
1491         pstitle = document()->currentDocument().fileName();
1492     }
1493 
1494     std::unique_ptr<Poppler::PSConverter> psConverter = pdfdoc->psConverter();
1495 
1496     psConverter->setOutputDevice(&tf);
1497 
1498     psConverter->setPageList(pageList);
1499     psConverter->setPaperWidth(width);
1500     psConverter->setPaperHeight(height);
1501     psConverter->setRightMargin(0);
1502     psConverter->setBottomMargin(0);
1503     psConverter->setLeftMargin(0);
1504     psConverter->setTopMargin(0);
1505     psConverter->setStrictMargins(false);
1506     psConverter->setForceRasterize(forceRasterize);
1507     psConverter->setTitle(pstitle);
1508 
1509 #if POPPLER_VERSION_MACRO >= QT_VERSION_CHECK(23, 9, 0)
1510     const auto isPdfOutput = printer.outputFormat() == QPrinter::PdfFormat;
1511     psConverter->setForceOverprintPreview(!isPdfOutput && overprintPreviewEnabled);
1512 #endif
1513 
1514     if (!printAnnots) {
1515         psConverter->setPSOptions(psConverter->psOptions() | Poppler::PSConverter::HideAnnotations);
1516     }
1517 
1518     userMutex()->lock();
1519     if (psConverter->convert()) {
1520         userMutex()->unlock();
1521         tf.close();
1522 
1523         const Okular::FilePrinter::ScaleMode filePrinterScaleMode = (scaleMode == PDFOptionsPage::None) ? Okular::FilePrinter::ScaleMode::NoScaling : Okular::FilePrinter::ScaleMode::FitToPrintArea;
1524 
1525         return Okular::FilePrinter::printFile(printer, tempfilename, document()->orientation(), Okular::FilePrinter::SystemDeletesFiles, Okular::FilePrinter::ApplicationSelectsPages, document()->bookmarkedPageRange(), filePrinterScaleMode);
1526     } else {
1527         userMutex()->unlock();
1528 
1529         tf.close();
1530 
1531         return Okular::Document::FileConversionPrintError;
1532     }
1533 }
1534 
1535 QVariant PDFGenerator::metaData(const QString &key, const QVariant &option) const
1536 {
1537     if (key == QLatin1String("StartFullScreen")) {
1538         QMutexLocker ml(userMutex());
1539         // asking for the 'start in fullscreen mode' (pdf property)
1540         if (pdfdoc->pageMode() == Poppler::Document::FullScreen) {
1541             return true;
1542         }
1543     } else if (key == QLatin1String("NamedViewport") && !option.toString().isEmpty()) {
1544         Okular::DocumentViewport viewport;
1545         QString optionString = option.toString();
1546 
1547         // asking for the page related to a 'named link destination'. the
1548         // option is the link name. @see addSynopsisChildren.
1549         userMutex()->lock();
1550         std::unique_ptr<Poppler::LinkDestination> ld = pdfdoc->linkDestination(optionString);
1551         userMutex()->unlock();
1552         if (ld) {
1553             fillViewportFromLinkDestination(viewport, *ld);
1554         }
1555         if (viewport.pageNumber >= 0) {
1556             return viewport.toString();
1557         }
1558     } else if (key == QLatin1String("DocumentTitle")) {
1559         userMutex()->lock();
1560         QString title = pdfdoc->info(QStringLiteral("Title"));
1561         userMutex()->unlock();
1562         return title;
1563     } else if (key == QLatin1String("OpenTOC")) {
1564         QMutexLocker ml(userMutex());
1565         if (pdfdoc->pageMode() == Poppler::Document::UseOutlines) {
1566             return true;
1567         }
1568     } else if (key == QLatin1String("DocumentScripts") && option.toString() == QLatin1String("JavaScript")) {
1569         QMutexLocker ml(userMutex());
1570         return pdfdoc->scripts();
1571     } else if (key == QLatin1String("HasUnsupportedXfaForm")) {
1572         QMutexLocker ml(userMutex());
1573         return pdfdoc->formType() == Poppler::Document::XfaForm;
1574     } else if (key == QLatin1String("FormCalculateOrder")) {
1575         QMutexLocker ml(userMutex());
1576         return QVariant::fromValue<QVector<int>>(pdfdoc->formCalculateOrder());
1577     } else if (key == QLatin1String("GeneratorExtraDescription")) {
1578         if (Poppler::Version::string() == QStringLiteral(POPPLER_VERSION)) {
1579             return i18n("Using Poppler %1", Poppler::Version::string());
1580         } else {
1581             return i18n("Using Poppler %1\n\nBuilt against Poppler %2", Poppler::Version::string(), QStringLiteral(POPPLER_VERSION));
1582         }
1583     } else if (key == QLatin1String("DocumentHasPassword")) {
1584         return pdfdoc->isEncrypted() ? QStringLiteral("yes") : QStringLiteral("no");
1585     }
1586     return QVariant();
1587 }
1588 
1589 bool PDFGenerator::reparseConfig()
1590 {
1591     if (!pdfdoc) {
1592         return false;
1593     }
1594 
1595     bool somethingchanged = false;
1596     // load paper color
1597     QColor color = documentMetaData(PaperColorMetaData, true).value<QColor>();
1598     // if paper color is changed we have to rebuild every visible pixmap in addition
1599     // to the outputDevice. it's the 'heaviest' case, other effect are just recoloring
1600     // over the page rendered on 'standard' white background.
1601     if (color != pdfdoc->paperColor()) {
1602         userMutex()->lock();
1603         pdfdoc->setPaperColor(color);
1604         userMutex()->unlock();
1605         somethingchanged = true;
1606     }
1607     bool aaChanged = setDocumentRenderHints();
1608     somethingchanged = somethingchanged || aaChanged;
1609     return somethingchanged;
1610 }
1611 
1612 void PDFGenerator::addPages(KConfigDialog *dlg)
1613 {
1614     PDFSettingsWidget *w = new PDFSettingsWidget(dlg);
1615 
1616     dlg->addPage(w, PDFSettings::self(), i18n("PDF"), QStringLiteral("application-pdf"), i18n("PDF Backend Configuration"));
1617 }
1618 
1619 bool PDFGenerator::setDocumentRenderHints()
1620 {
1621     bool changed = false;
1622     const Poppler::Document::RenderHints oldhints = pdfdoc->renderHints();
1623 #define SET_HINT(hintname, hintdefvalue, hintflag)                                                                                                                                                                                             \
1624     {                                                                                                                                                                                                                                          \
1625         bool newhint = documentMetaData(hintname, hintdefvalue).toBool();                                                                                                                                                                      \
1626         if (newhint != oldhints.testFlag(hintflag)) {                                                                                                                                                                                          \
1627             pdfdoc->setRenderHint(hintflag, newhint);                                                                                                                                                                                          \
1628             changed = true;                                                                                                                                                                                                                    \
1629         }                                                                                                                                                                                                                                      \
1630     }
1631     SET_HINT(GraphicsAntialiasMetaData, true, Poppler::Document::Antialiasing)
1632     SET_HINT(TextAntialiasMetaData, true, Poppler::Document::TextAntialiasing)
1633     SET_HINT(TextHintingMetaData, false, Poppler::Document::TextHinting)
1634 #undef SET_HINT
1635     // load thin line mode
1636     const int thinLineMode = PDFSettings::enhanceThinLines();
1637 #if POPPLER_VERSION_MACRO >= QT_VERSION_CHECK(23, 07, 0)
1638     const bool enableOverprintPreview = PDFSettings::overprintPreviewEnabled();
1639 #endif
1640     const bool enableThinLineSolid = thinLineMode == PDFSettings::EnumEnhanceThinLines::Solid;
1641     const bool enableShapeLineSolid = thinLineMode == PDFSettings::EnumEnhanceThinLines::Shape;
1642 
1643 #if POPPLER_VERSION_MACRO >= QT_VERSION_CHECK(23, 07, 0)
1644     const bool overprintPreviewWasEnabled = (oldhints & Poppler::Document::OverprintPreview) == Poppler::Document::OverprintPreview;
1645 #endif
1646     const bool thinLineSolidWasEnabled = (oldhints & Poppler::Document::ThinLineSolid) == Poppler::Document::ThinLineSolid;
1647     const bool thinLineShapeWasEnabled = (oldhints & Poppler::Document::ThinLineShape) == Poppler::Document::ThinLineShape;
1648 
1649 #if POPPLER_VERSION_MACRO >= QT_VERSION_CHECK(23, 07, 0)
1650     if (enableOverprintPreview != overprintPreviewWasEnabled) {
1651         pdfdoc->setRenderHint(Poppler::Document::OverprintPreview, enableOverprintPreview);
1652         changed = true;
1653     }
1654 #endif
1655     if (enableThinLineSolid != thinLineSolidWasEnabled) {
1656         pdfdoc->setRenderHint(Poppler::Document::ThinLineSolid, enableThinLineSolid);
1657         changed = true;
1658     }
1659     if (enableShapeLineSolid != thinLineShapeWasEnabled) {
1660         pdfdoc->setRenderHint(Poppler::Document::ThinLineShape, enableShapeLineSolid);
1661         changed = true;
1662     }
1663     return changed;
1664 }
1665 
1666 Okular::ExportFormat::List PDFGenerator::exportFormats() const
1667 {
1668     static Okular::ExportFormat::List formats;
1669     if (formats.isEmpty()) {
1670         formats.append(Okular::ExportFormat::standardFormat(Okular::ExportFormat::PlainText));
1671     }
1672 
1673     return formats;
1674 }
1675 
1676 bool PDFGenerator::exportTo(const QString &fileName, const Okular::ExportFormat &format)
1677 {
1678     if (format.mimeType().inherits(QStringLiteral("text/plain"))) {
1679         QFile f(fileName);
1680         if (!f.open(QIODevice::WriteOnly)) {
1681             return false;
1682         }
1683 
1684         QTextStream ts(&f);
1685         int num = document()->pages();
1686         for (int i = 0; i < num; ++i) {
1687             QString text;
1688             userMutex()->lock();
1689             std::unique_ptr<Poppler::Page> pp = pdfdoc->page(i);
1690             if (pp) {
1691                 text = pp->text(QRect()).normalized(QString::NormalizationForm_KC);
1692             }
1693             userMutex()->unlock();
1694             ts << text;
1695         }
1696         f.close();
1697 
1698         return true;
1699     }
1700 
1701     return false;
1702 }
1703 
1704 // END Generator inherited functions
1705 
1706 inline void append(Okular::TextPage *ktp, const QString &s, double l, double b, double r, double t)
1707 {
1708     //    qWarning(PDFDebug).nospace() << "text: " << s << " at (" << l << "," << t << ")x(" << r <<","<<b<<")";
1709     ktp->append(s, Okular::NormalizedRect(l, t, r, b));
1710 }
1711 
1712 Okular::TextPage *PDFGenerator::abstractTextPage(const std::vector<std::unique_ptr<Poppler::TextBox>> &text, double height, double width, int rot)
1713 {
1714     Q_UNUSED(rot);
1715     Okular::TextPage *ktp = new Okular::TextPage;
1716     Poppler::TextBox *next;
1717 #ifdef PDFGENERATOR_DEBUG
1718     qCDebug(OkularPdfDebug) << "getting text page in generator pdf - rotation:" << rot;
1719 #endif
1720     QString s;
1721     bool addChar;
1722     for (const auto &word : text) {
1723         const int qstringCharCount = word->text().length();
1724         next = word->nextWord();
1725         int textBoxChar = 0;
1726         for (int j = 0; j < qstringCharCount; j++) {
1727             const QChar c = word->text().at(j);
1728             if (c.isHighSurrogate()) {
1729                 s = c;
1730                 addChar = false;
1731             } else if (c.isLowSurrogate()) {
1732                 s += c;
1733                 addChar = true;
1734             } else {
1735                 s = c;
1736                 addChar = true;
1737             }
1738 
1739             if (addChar) {
1740                 QRectF charBBox = word->charBoundingBox(textBoxChar);
1741                 append(ktp, (j == qstringCharCount - 1 && !next) ? (s + QLatin1Char('\n')) : s, charBBox.left() / width, charBBox.bottom() / height, charBBox.right() / width, charBBox.top() / height);
1742                 textBoxChar++;
1743             }
1744         }
1745 
1746         if (word->hasSpaceAfter() && next) {
1747             // TODO Check with a document with vertical text
1748             // probably won't work and we will need to do comparisons
1749             // between wordBBox and nextWordBBox to see if they are
1750             // vertically or horizontally aligned
1751             QRectF wordBBox = word->boundingBox();
1752             QRectF nextWordBBox = next->boundingBox();
1753             append(ktp, QStringLiteral(" "), wordBBox.right() / width, wordBBox.bottom() / height, nextWordBBox.left() / width, wordBBox.top() / height);
1754         }
1755     }
1756     return ktp;
1757 }
1758 
1759 void PDFGenerator::addSynopsisChildren(const QVector<Poppler::OutlineItem> &outlineItems, QDomNode *parentDestination)
1760 {
1761     for (const Poppler::OutlineItem &outlineItem : outlineItems) {
1762         QDomElement item = docSyn.createElement(outlineItem.name());
1763         parentDestination->appendChild(item);
1764 
1765         item.setAttribute(QStringLiteral("ExternalFileName"), outlineItem.externalFileName());
1766         const QSharedPointer<const Poppler::LinkDestination> outlineDestination = outlineItem.destination();
1767         if (outlineDestination) {
1768             const QString destinationName = outlineDestination->destinationName();
1769             if (!destinationName.isEmpty()) {
1770                 item.setAttribute(QStringLiteral("ViewportName"), destinationName);
1771             } else {
1772                 Okular::DocumentViewport vp;
1773                 fillViewportFromLinkDestination(vp, *outlineDestination);
1774                 item.setAttribute(QStringLiteral("Viewport"), vp.toString());
1775             }
1776         }
1777         item.setAttribute(QStringLiteral("Open"), outlineItem.isOpen());
1778         item.setAttribute(QStringLiteral("URL"), outlineItem.uri());
1779 
1780         if (outlineItem.hasChildren()) {
1781             addSynopsisChildren(outlineItem.children(), &item);
1782         }
1783     }
1784 }
1785 
1786 void PDFGenerator::addAnnotations(Poppler::Page *popplerPage, Okular::Page *page)
1787 {
1788     QSet<Poppler::Annotation::SubType> subtypes;
1789     subtypes << Poppler::Annotation::AFileAttachment << Poppler::Annotation::ASound << Poppler::Annotation::AMovie << Poppler::Annotation::AWidget << Poppler::Annotation::AScreen << Poppler::Annotation::AText << Poppler::Annotation::ALine
1790              << Poppler::Annotation::AGeom << Poppler::Annotation::AHighlight << Poppler::Annotation::AInk << Poppler::Annotation::AStamp << Poppler::Annotation::ACaret;
1791 
1792     std::vector<std::unique_ptr<Poppler::Annotation>> popplerAnnotations = popplerPage->annotations(subtypes);
1793 
1794     for (auto &a : popplerAnnotations) {
1795         bool doDelete = true;
1796         Okular::Annotation *newann = createAnnotationFromPopplerAnnotation(a.get(), *popplerPage, &doDelete);
1797         if (newann) {
1798             page->addAnnotation(newann);
1799 
1800             if (a->subType() == Poppler::Annotation::AScreen) {
1801                 Poppler::ScreenAnnotation *annotScreen = static_cast<Poppler::ScreenAnnotation *>(a.get());
1802                 Okular::ScreenAnnotation *screenAnnotation = static_cast<Okular::ScreenAnnotation *>(newann);
1803 
1804                 // The activation action
1805                 Poppler::Link *actionLink = annotScreen->action();
1806                 if (actionLink) {
1807                     screenAnnotation->setAction(createLinkFromPopplerLink(actionLink, true /*The function doesn't delete this kind*/));
1808                     /* The actionLink is still owned by the poppler annotation, but createLinkFromPopplerLink specialcases this and
1809                      * insists on on being passed 'true' to tell them we know what we are doing
1810                      * At some point in the future, there is probably stuff to be cleaned up here */
1811                 }
1812 
1813                 // The additional actions
1814                 std::unique_ptr<Poppler::Link> pageOpeningLink = annotScreen->additionalAction(Poppler::Annotation::PageOpeningAction);
1815                 if (pageOpeningLink) {
1816                     screenAnnotation->setAdditionalAction(Okular::Annotation::PageOpening, createLinkFromPopplerLink(pageOpeningLink.get(), false));
1817                 }
1818 
1819                 std::unique_ptr<Poppler::Link> pageClosingLink = annotScreen->additionalAction(Poppler::Annotation::PageClosingAction);
1820                 if (pageClosingLink) {
1821                     screenAnnotation->setAdditionalAction(Okular::Annotation::PageClosing, createLinkFromPopplerLink(pageClosingLink.get(), false));
1822                 }
1823             }
1824 
1825             if (a->subType() == Poppler::Annotation::AWidget) {
1826                 Poppler::WidgetAnnotation *annotWidget = static_cast<Poppler::WidgetAnnotation *>(a.get());
1827                 Okular::WidgetAnnotation *widgetAnnotation = static_cast<Okular::WidgetAnnotation *>(newann);
1828 
1829                 // The additional actions
1830                 std::unique_ptr<Poppler::Link> pageOpeningLink = annotWidget->additionalAction(Poppler::Annotation::PageOpeningAction);
1831                 if (pageOpeningLink) {
1832                     widgetAnnotation->setAdditionalAction(Okular::Annotation::PageOpening, createLinkFromPopplerLink(pageOpeningLink.get(), false));
1833                 }
1834 
1835                 std::unique_ptr<Poppler::Link> pageClosingLink = annotWidget->additionalAction(Poppler::Annotation::PageClosingAction);
1836                 if (pageClosingLink) {
1837                     widgetAnnotation->setAdditionalAction(Okular::Annotation::PageClosing, createLinkFromPopplerLink(pageClosingLink.get(), false));
1838                 }
1839             }
1840 
1841             if (!doDelete) {
1842                 annotationsOnOpenHash.insert(newann, a.release()); // investigate
1843             }
1844         }
1845     }
1846 }
1847 
1848 void PDFGenerator::addTransition(Poppler::Page *pdfPage, Okular::Page *page)
1849 // called on opening when MUTEX is not used
1850 {
1851     Poppler::PageTransition *pdfTransition = pdfPage->transition();
1852     if (!pdfTransition || pdfTransition->type() == Poppler::PageTransition::Replace) {
1853         return;
1854     }
1855 
1856     Okular::PageTransition *transition = new Okular::PageTransition();
1857     switch (pdfTransition->type()) {
1858     case Poppler::PageTransition::Replace:
1859         // won't get here, added to avoid warning
1860         break;
1861     case Poppler::PageTransition::Split:
1862         transition->setType(Okular::PageTransition::Split);
1863         break;
1864     case Poppler::PageTransition::Blinds:
1865         transition->setType(Okular::PageTransition::Blinds);
1866         break;
1867     case Poppler::PageTransition::Box:
1868         transition->setType(Okular::PageTransition::Box);
1869         break;
1870     case Poppler::PageTransition::Wipe:
1871         transition->setType(Okular::PageTransition::Wipe);
1872         break;
1873     case Poppler::PageTransition::Dissolve:
1874         transition->setType(Okular::PageTransition::Dissolve);
1875         break;
1876     case Poppler::PageTransition::Glitter:
1877         transition->setType(Okular::PageTransition::Glitter);
1878         break;
1879     case Poppler::PageTransition::Fly:
1880         transition->setType(Okular::PageTransition::Fly);
1881         break;
1882     case Poppler::PageTransition::Push:
1883         transition->setType(Okular::PageTransition::Push);
1884         break;
1885     case Poppler::PageTransition::Cover:
1886         transition->setType(Okular::PageTransition::Cover);
1887         break;
1888     case Poppler::PageTransition::Uncover:
1889         transition->setType(Okular::PageTransition::Uncover);
1890         break;
1891     case Poppler::PageTransition::Fade:
1892         transition->setType(Okular::PageTransition::Fade);
1893         break;
1894     }
1895 
1896     transition->setDuration(pdfTransition->durationReal());
1897 
1898     switch (pdfTransition->alignment()) {
1899     case Poppler::PageTransition::Horizontal:
1900         transition->setAlignment(Okular::PageTransition::Horizontal);
1901         break;
1902     case Poppler::PageTransition::Vertical:
1903         transition->setAlignment(Okular::PageTransition::Vertical);
1904         break;
1905     }
1906 
1907     switch (pdfTransition->direction()) {
1908     case Poppler::PageTransition::Inward:
1909         transition->setDirection(Okular::PageTransition::Inward);
1910         break;
1911     case Poppler::PageTransition::Outward:
1912         transition->setDirection(Okular::PageTransition::Outward);
1913         break;
1914     }
1915 
1916     transition->setAngle(pdfTransition->angle());
1917     transition->setScale(pdfTransition->scale());
1918     transition->setIsRectangular(pdfTransition->isRectangular());
1919 
1920     page->setTransition(transition);
1921 }
1922 
1923 QList<Okular::FormField *> PDFGenerator::getFormFields(Poppler::Page *popplerPage)
1924 {
1925     if (!popplerPage) {
1926         return {};
1927     }
1928 
1929     std::vector<std::unique_ptr<Poppler::FormField>> popplerFormFields = popplerPage->formFields();
1930     QList<Okular::FormField *> okularFormFields;
1931     for (auto &f : popplerFormFields) {
1932         Okular::FormField *of = nullptr;
1933         switch (f->type()) {
1934         case Poppler::FormField::FormButton:
1935             of = new PopplerFormFieldButton(std::unique_ptr<Poppler::FormFieldButton>(static_cast<Poppler::FormFieldButton *>(f.release())));
1936             break;
1937         case Poppler::FormField::FormText:
1938             of = new PopplerFormFieldText(std::unique_ptr<Poppler::FormFieldText>(static_cast<Poppler::FormFieldText *>(f.release())));
1939             break;
1940         case Poppler::FormField::FormChoice:
1941             of = new PopplerFormFieldChoice(std::unique_ptr<Poppler::FormFieldChoice>(static_cast<Poppler::FormFieldChoice *>(f.release())));
1942             break;
1943         case Poppler::FormField::FormSignature: {
1944             of = new PopplerFormFieldSignature(std::unique_ptr<Poppler::FormFieldSignature>(static_cast<Poppler::FormFieldSignature *>(f.release())));
1945             break;
1946         }
1947         default:;
1948         }
1949         if (of) {
1950             // form field created, good - it will take care of the Poppler::FormField
1951             okularFormFields.append(of);
1952         }
1953     }
1954 
1955     return okularFormFields;
1956 }
1957 
1958 Okular::PrintOptionsWidget *PDFGenerator::printConfigurationWidget() const
1959 {
1960     if (!pdfOptionsPage) {
1961         const_cast<PDFGenerator *>(this)->pdfOptionsPage = new PDFOptionsPage();
1962     }
1963     return pdfOptionsPage;
1964 }
1965 
1966 bool PDFGenerator::supportsOption(SaveOption option) const
1967 {
1968     switch (option) {
1969     case SaveChanges: {
1970         return true;
1971     }
1972     default:;
1973     }
1974     return false;
1975 }
1976 
1977 bool PDFGenerator::save(const QString &fileName, SaveOptions options, QString *errorText)
1978 {
1979     Q_UNUSED(errorText);
1980     std::unique_ptr<Poppler::PDFConverter> pdfConv = pdfdoc->pdfConverter();
1981 
1982     pdfConv->setOutputFileName(fileName);
1983     if (options & SaveChanges) {
1984         pdfConv->setPDFOptions(pdfConv->pdfOptions() | Poppler::PDFConverter::WithChanges);
1985     }
1986 
1987     QMutexLocker locker(userMutex());
1988 
1989     QHashIterator<Okular::Annotation *, Poppler::Annotation *> it(annotationsOnOpenHash);
1990     while (it.hasNext()) {
1991         it.next();
1992 
1993         if (it.value()->uniqueName().isEmpty()) {
1994             it.value()->setUniqueName(it.key()->uniqueName());
1995         }
1996     }
1997 
1998     bool success = pdfConv->convert();
1999     if (!success) {
2000         switch (pdfConv->lastError()) {
2001         case Poppler::BaseConverter::NotSupportedInputFileError:
2002             // This can only happen with Poppler before 0.22 which did not have qt5 version
2003             break;
2004 
2005         case Poppler::BaseConverter::NoError:
2006         case Poppler::BaseConverter::FileLockedError:
2007             // we can't get here
2008             break;
2009 
2010         case Poppler::BaseConverter::OpenOutputError:
2011             // the default text message is good for this case
2012             break;
2013         }
2014     }
2015     return success;
2016 }
2017 
2018 Okular::AnnotationProxy *PDFGenerator::annotationProxy() const
2019 {
2020     return annotProxy;
2021 }
2022 
2023 bool PDFGenerator::canSign() const
2024 {
2025 #if POPPLER_VERSION_MACRO >= QT_VERSION_CHECK(23, 06, 0)
2026     return !Poppler::availableCryptoSignBackends().empty();
2027 #else
2028     return Poppler::hasNSSSupport();
2029 #endif
2030 }
2031 
2032 bool PDFGenerator::sign(const Okular::NewSignatureData &oData, const QString &rFilename)
2033 {
2034     // We need a temporary file to pass a prepared image to poppler
2035     QTemporaryFile timg(QFileInfo(rFilename).absolutePath() + QLatin1String("/okular_XXXXXX.png"));
2036     timg.setAutoRemove(true);
2037     if (!timg.open()) {
2038         return false;
2039     }
2040 
2041     // save to tmp file - poppler doesn't like overwriting in-place
2042     QTemporaryFile tf(QFileInfo(rFilename).absolutePath() + QLatin1String("/okular_XXXXXX.pdf"));
2043     tf.setAutoRemove(false);
2044     if (!tf.open()) {
2045         return false;
2046     }
2047     std::unique_ptr<Poppler::PDFConverter> converter(pdfdoc->pdfConverter());
2048     converter->setOutputFileName(tf.fileName());
2049     converter->setPDFOptions(converter->pdfOptions() | Poppler::PDFConverter::WithChanges);
2050 
2051     Poppler::PDFConverter::NewSignatureData pData;
2052     okularToPoppler(oData, &pData);
2053     if (!oData.backgroundImagePath().isEmpty() && QFile::exists(oData.backgroundImagePath())) {
2054         // width and height for target image
2055         const Okular::NormalizedRect bRect = oData.boundingRectangle();
2056         // 2 is an experimental decided upon fudge factor to compensate for the fact that pageSize is in points
2057         // but most of this ends up working in pixels anyway
2058         double width = pdfdoc->page(oData.page())->pageSizeF().width() * bRect.width() * 2;
2059         double height = pdfdoc->page(oData.page())->pageSizeF().height() * bRect.height() * 2;
2060 
2061         QImageReader reader(oData.backgroundImagePath());
2062         QSize imageSize = reader.size();
2063         if (!reader.size().isNull()) {
2064             reader.setScaledSize(imageSize.scaled(width, height, Qt::KeepAspectRatio));
2065         }
2066         auto input = reader.read();
2067         if (!input.isNull()) {
2068             auto scaled = imagescaling::scaleAndFitCanvas(input, QSize(width, height));
2069             bool success = scaled.save(timg.fileName(), "png");
2070             if (success) {
2071                 pData.setImagePath(timg.fileName());
2072                 pData.setBackgroundColor(Qt::white);
2073             }
2074         }
2075     }
2076     if (!converter->sign(pData)) {
2077         tf.remove();
2078         return false;
2079     }
2080 
2081     // now copy over old file
2082     QFile::remove(rFilename);
2083     if (!tf.rename(rFilename)) {
2084         return false;
2085     }
2086 
2087     return true;
2088 }
2089 
2090 Okular::CertificateStore *PDFGenerator::certificateStore() const
2091 {
2092     if (!certStore) {
2093         certStore = new PopplerCertificateStore();
2094     }
2095 
2096     return certStore;
2097 }
2098 
2099 void PDFGenerator::xrefReconstructionHandler()
2100 {
2101     if (!xrefReconstructed) {
2102         qCDebug(OkularPdfDebug) << "XRef Table of the document has been reconstructed";
2103         xrefReconstructed = true;
2104         Q_EMIT warning(i18n("Some errors were found in the document, Okular might not be able to show the content correctly"), 5000);
2105     }
2106 }
2107 
2108 #include "generator_pdf.moc"
2109 
2110 Q_LOGGING_CATEGORY(OkularPdfDebug, "org.kde.okular.generators.pdf", QtWarningMsg)
2111 
2112 /* kate: replace-tabs on; indent-width 4; */