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; */