/*************************************************************************** * Copyright (C) 2004-2006 by Albert Astals Cid * * Copyright (C) 2004 by Enrico Ros * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "generator_pdf.h" // qt/kde includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_POPPLER_0_6 #include "formfields.h" #endif static const int PDFDebug = 4710; class PDFEmbeddedFile : public Okular::EmbeddedFile { public: PDFEmbeddedFile(Poppler::EmbeddedFile *f) : ef(f) { } QString name() const { return ef->name(); } QString description() const { return ef->description(); } QByteArray data() const { return ef->data(); } int size() const { #ifndef HAVE_POPPLER_0_6 return -1; #else int s = ef->size(); return s <= 0 ? -1 : s; #endif } QDateTime modificationDate() const { return ef->modDate(); } QDateTime creationDate() const { return ef->createDate(); } private: Poppler::EmbeddedFile *ef; }; class PDFOptionsPage : public QWidget { public: PDFOptionsPage() { setWindowTitle( i18n( "PDF Options" ) ); QVBoxLayout *layout = new QVBoxLayout(this); m_forceRaster = new QCheckBox(i18n("Force rasterization"), this); m_forceRaster->setToolTip(i18n("Rasterize into an image before printing")); 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.")); layout->addWidget(m_forceRaster); layout->addStretch(1); } bool printForceRaster() { return m_forceRaster->isChecked(); } void setPrintForceRaster( bool forceRaster ) { m_forceRaster->setChecked( forceRaster ); } private: QCheckBox *m_forceRaster; }; static void fillViewportFromLinkDestination( Okular::DocumentViewport &viewport, const Poppler::LinkDestination &destination, const Poppler::Document *pdfdoc ) { #ifdef HAVE_POPPLER_0_6 Q_UNUSED( pdfdoc ) #endif viewport.pageNumber = destination.pageNumber() - 1; if (!viewport.isValid()) return; // get destination position // TODO add other attributes to the viewport (taken from link) // switch ( destination->getKind() ) // { // case destXYZ: if (destination.isChangeLeft() || destination.isChangeTop()) { // TODO remember to change this if we implement DPI and/or rotation double left, top; left = destination.left(); top = destination.top(); #ifndef HAVE_POPPLER_0_6 Poppler::Page *page = pdfdoc->page( viewport.pageNumber ); QSize pageSize = page->pageSize(); delete page; viewport.rePos.normalizedX = (double)left / (double)pageSize.width(); viewport.rePos.normalizedY = (double)top / (double)pageSize.height(); #else viewport.rePos.normalizedX = left; viewport.rePos.normalizedY = top; #endif viewport.rePos.enabled = true; viewport.rePos.pos = Okular::DocumentViewport::TopLeft; } /* TODO if ( dest->getChangeZoom() ) make zoom change*/ /* break; default: // implement the others cases break;*/ // } } static Okular::Action* createLinkFromPopplerLink(const Poppler::Link *popplerLink, const Poppler::Document *pdfdoc) { Okular::Action *link = 0; const Poppler::LinkGoto *popplerLinkGoto; const Poppler::LinkExecute *popplerLinkExecute; const Poppler::LinkBrowse *popplerLinkBrowse; const Poppler::LinkAction *popplerLinkAction; #ifdef HAVE_POPPLER_0_6 const Poppler::LinkSound *popplerLinkSound; #endif Okular::DocumentViewport viewport; switch(popplerLink->linkType()) { case Poppler::Link::None: break; case Poppler::Link::Goto: popplerLinkGoto = static_cast(popplerLink); fillViewportFromLinkDestination( viewport, popplerLinkGoto->destination(), pdfdoc ); link = new Okular::GotoAction(popplerLinkGoto->fileName(), viewport); break; case Poppler::Link::Execute: popplerLinkExecute = static_cast(popplerLink); link = new Okular::ExecuteAction( popplerLinkExecute->fileName(), popplerLinkExecute->parameters() ); break; case Poppler::Link::Browse: popplerLinkBrowse = static_cast(popplerLink); link = new Okular::BrowseAction( popplerLinkBrowse->url() ); break; case Poppler::Link::Action: popplerLinkAction = static_cast(popplerLink); link = new Okular::DocumentAction( (Okular::DocumentAction::DocumentActionType)popplerLinkAction->actionType() ); break; #ifdef HAVE_POPPLER_0_6 case Poppler::Link::Sound: { popplerLinkSound = static_cast(popplerLink); Poppler::SoundObject *popplerSound = popplerLinkSound->sound(); Okular::Sound *sound = popplerSound->soundType() == Poppler::SoundObject::Embedded ? new Okular::Sound( popplerSound->data() ) : new Okular::Sound( popplerSound->url() ); sound->setSamplingRate( popplerSound->samplingRate() ); sound->setChannels( popplerSound->channels() ); sound->setBitsPerSample( popplerSound->bitsPerSample() ); switch ( popplerSound->soundEncoding() ) { case Poppler::SoundObject::Raw: sound->setSoundEncoding( Okular::Sound::Raw ); break; case Poppler::SoundObject::Signed: sound->setSoundEncoding( Okular::Sound::Signed ); break; case Poppler::SoundObject::muLaw: sound->setSoundEncoding( Okular::Sound::muLaw ); break; case Poppler::SoundObject::ALaw: sound->setSoundEncoding( Okular::Sound::ALaw ); break; } link = new Okular::SoundAction( popplerLinkSound->volume(), popplerLinkSound->synchronous(), popplerLinkSound->repeat(), popplerLinkSound->mix(), sound ); } break; #endif case Poppler::Link::Movie: // not implemented break; } return link; } static QLinkedList generateLinks( const QList &popplerLinks, int width, int height, const Poppler::Document *pdfdoc ) { #ifdef HAVE_POPPLER_0_6 Q_UNUSED( width ) Q_UNUSED( height ) #endif QLinkedList links; foreach(const Poppler::Link *popplerLink, popplerLinks) { QRectF linkArea = popplerLink->linkArea(); #ifdef HAVE_POPPLER_0_6 double nl = linkArea.left(), nt = linkArea.top(), nr = linkArea.right(), nb = linkArea.bottom(); #else double nl = linkArea.left() / (double)width, nt = linkArea.top() / (double)height, nr = linkArea.right() / (double)width, nb = linkArea.bottom() / (double)height; #endif // create the rect using normalized coords and attach the Okular::Link to it Okular::ObjectRect * rect = new Okular::ObjectRect( nl, nt, nr, nb, false, Okular::ObjectRect::Action, createLinkFromPopplerLink(popplerLink, pdfdoc) ); // add the ObjectRect to the container links.push_front( rect ); } qDeleteAll(popplerLinks); return links; } /** NOTES on threading: * internal: thread race prevention is done via the 'docLock' mutex. the * mutex is needed only because we have the asynchronous thread; else * the operations are all within the 'gui' thread, scheduled by the * Qt scheduler and no mutex is needed. * external: dangerous operations are all locked via mutex internally, and the * only needed external thing is the 'canGeneratePixmap' method * that tells if the generator is free (since we don't want an * internal queue to store PixmapRequests). A generatedPixmap call * without the 'ready' flag set, results in undefined behavior. * So, as example, printing while generating a pixmap asynchronously is safe, * it might only block the gui thread by 1) waiting for the mutex to unlock * in async thread and 2) doing the 'heavy' print operation. */ static KAboutData createAboutData() { KAboutData aboutData( "okular_poppler", "okular_poppler", ki18n( "PDF Backend" ), "0.1", ki18n( "A PDF file renderer" ), KAboutData::License_GPL, ki18n( "© 2005-2008 Albert Astals Cid" ) ); aboutData.addAuthor( ki18n( "Albert Astals Cid" ), KLocalizedString(), "aacid@kde.org" ); return aboutData; } OKULAR_EXPORT_PLUGIN(PDFGenerator, createAboutData()) PDFGenerator::PDFGenerator( QObject *parent, const QVariantList &args ) : Generator( parent, args ), pdfdoc( 0 ), ready( true ), pixmapRequest( 0 ), docInfoDirty( true ), docSynopsisDirty( true ), docEmbeddedFilesDirty( true ), nextFontPage( 0 ), pdfOptionsPage( 0 ) { setFeature( TextExtraction ); setFeature( FontInfo ); setFeature( PrintPostscript ); #ifdef HAVE_POPPLER_0_6 setFeature( ReadRawData ); pdfOptionsPage = new PDFOptionsPage(); #endif // update the configuration reparseConfig(); // generate the pixmapGeneratorThread generatorThread = new PDFPixmapGeneratorThread( this ); connect(generatorThread, SIGNAL(finished()), this, SLOT(threadFinished()), Qt::QueuedConnection); } PDFGenerator::~PDFGenerator() { // stop and delete the generator thread if ( generatorThread ) { generatorThread->wait(); delete generatorThread; } delete pdfOptionsPage; } //BEGIN Generator inherited functions bool PDFGenerator::loadDocument( const QString & filePath, QVector & pagesVector ) { #ifndef NDEBUG if ( pdfdoc ) { kDebug(PDFDebug) << "PDFGenerator: multiple calls to loadDocument. Check it."; return false; } #endif // create PDFDoc for the given file pdfdoc = Poppler::Document::load( filePath, 0, 0 ); bool success = init(pagesVector, filePath.section('/', -1, -1)); if (success && QFile::exists(filePath + QLatin1String( "sync" ))) { loadPdfSync(filePath, pagesVector); } return success; } bool PDFGenerator::loadDocumentFromData( const QByteArray & fileData, QVector & pagesVector ) { #ifdef HAVE_POPPLER_0_6 #ifndef NDEBUG if ( pdfdoc ) { kDebug(PDFDebug) << "PDFGenerator: multiple calls to loadDocument. Check it."; return false; } #endif // create PDFDoc for the given file pdfdoc = Poppler::Document::loadFromData( fileData, 0, 0 ); return init(pagesVector, QString()); #else Q_UNUSED(fileData) Q_UNUSED(pagesVector) return false; #endif } bool PDFGenerator::init(QVector & pagesVector, const QString &walletKey) { // if the file didn't open correctly it might be encrypted, so ask for a pass bool firstInput = true; bool triedWallet = false; KWallet::Wallet * wallet = 0; bool keep = true; while ( pdfdoc && pdfdoc->isLocked() ) { QString password; // 1.A. try to retrieve the first password from the kde wallet system if ( !triedWallet && !walletKey.isNull() ) { QString walletName = KWallet::Wallet::NetworkWallet(); WId parentwid = 0; if ( document() && document()->widget() ) parentwid = document()->widget()->winId(); wallet = KWallet::Wallet::openWallet( walletName, parentwid ); if ( wallet ) { // use the KPdf folder (and create if missing) if ( !wallet->hasFolder( "KPdf" ) ) wallet->createFolder( "KPdf" ); wallet->setFolder( "KPdf" ); // look for the pass in that folder QString retrievedPass; if ( !wallet->readPassword( walletKey, retrievedPass ) ) password = retrievedPass; } triedWallet = true; } // 1.B. if not retrieved, ask the password using the kde password dialog if ( password.isNull() ) { QString prompt; if ( firstInput ) prompt = i18n( "Please insert the password to read the document:" ); else prompt = i18n( "Incorrect password. Try again:" ); firstInput = false; // if the user presses cancel, abort opening KPasswordDialog dlg( 0, wallet ? KPasswordDialog::ShowKeepPassword : KPasswordDialog::KPasswordDialogFlags() ); dlg.setCaption( i18n( "Document Password" ) ); dlg.setPrompt( prompt ); if( !dlg.exec() ) break; password = dlg.password(); if ( wallet ) keep = dlg.keepPassword(); } // 2. reopen the document using the password pdfdoc->unlock( password.toLatin1(), password.toLatin1() ); // 3. if the password is correct and the user chose to remember it, store it to the wallet if ( !pdfdoc->isLocked() && wallet && /*safety check*/ wallet->isOpen() && keep ) { wallet->writePassword( walletKey, password ); } } if ( !pdfdoc || pdfdoc->isLocked() ) { delete pdfdoc; pdfdoc = 0; return false; } // build Pages (currentPage was set -1 by deletePages) uint pageCount = pdfdoc->numPages(); pagesVector.resize(pageCount); rectsGenerated.fill(false, pageCount); loadPages(pagesVector, 0, false); setAAOptions(); // the file has been loaded correctly return true; } bool PDFGenerator::doCloseDocument() { // remove internal objects userMutex()->lock(); delete pdfdoc; pdfdoc = 0; userMutex()->unlock(); docInfoDirty = true; docSynopsisDirty = true; docSyn.clear(); docEmbeddedFilesDirty = true; qDeleteAll(docEmbeddedFiles); docEmbeddedFiles.clear(); nextFontPage = 0; return true; } void PDFGenerator::loadPages(QVector &pagesVector, int rotation, bool clear) { // TODO XPDF 3.01 check int count=pagesVector.count(),w=0,h=0; for ( int i = 0; i < count ; i++ ) { // get xpdf page Poppler::Page * p = pdfdoc->page( i ); QSize pSize = p->pageSize(); w = pSize.width(); h = pSize.height(); Okular::Rotation orientation = Okular::Rotation0; switch (p->orientation()) { case Poppler::Page::Landscape: orientation = Okular::Rotation90; break; case Poppler::Page::UpsideDown: orientation = Okular::Rotation180; break; case Poppler::Page::Seascape: orientation = Okular::Rotation270; break; case Poppler::Page::Portrait: orientation = Okular::Rotation0; break; } if (rotation % 2 == 1) qSwap(w,h); // init a Okular::page, add transition and annotation information Okular::Page * page = new Okular::Page( i, w, h, orientation ); addTransition( p, page ); if ( true ) //TODO real check addAnnotations( p, page ); #ifdef HAVE_POPPLER_0_6 Poppler::Link * tmplink = p->action( Poppler::Page::Opening ); if ( tmplink ) { page->setPageAction( Okular::Page::Opening, createLinkFromPopplerLink( tmplink, pdfdoc ) ); delete tmplink; } tmplink = p->action( Poppler::Page::Closing ); if ( tmplink ) { page->setPageAction( Okular::Page::Closing, createLinkFromPopplerLink( tmplink, pdfdoc ) ); delete tmplink; } page->setDuration( p->duration() ); page->setLabel( p->label() ); addFormFields( p, page ); #endif // kWarning(PDFDebug).nospace() << page->width() << "x" << page->height(); #ifdef PDFGENERATOR_DEBUG kDebug(PDFDebug) << "load page" << i << "with rotation" << rotation << "and orientation" << orientation; #endif delete p; if (clear && pagesVector[i]) delete pagesVector[i]; // set the Okular::page at the right position in document's pages vector pagesVector[i] = page; } } const Okular::DocumentInfo * PDFGenerator::generateDocumentInfo() { if ( docInfoDirty ) { userMutex()->lock(); docInfo.set( Okular::DocumentInfo::MimeType, "application/pdf" ); if ( pdfdoc ) { // compile internal structure reading properties from PDFDoc docInfo.set( Okular::DocumentInfo::Title, pdfdoc->info("Title") ); docInfo.set( Okular::DocumentInfo::Subject, pdfdoc->info("Subject") ); docInfo.set( Okular::DocumentInfo::Author, pdfdoc->info("Author") ); docInfo.set( Okular::DocumentInfo::Keywords, pdfdoc->info("Keywords") ); docInfo.set( Okular::DocumentInfo::Creator, pdfdoc->info("Creator") ); docInfo.set( Okular::DocumentInfo::Producer, pdfdoc->info("Producer") ); docInfo.set( Okular::DocumentInfo::CreationDate, KGlobal::locale()->formatDateTime( pdfdoc->date("CreationDate"), KLocale::LongDate, true ) ); docInfo.set( Okular::DocumentInfo::ModificationDate, KGlobal::locale()->formatDateTime( pdfdoc->date("ModDate"), KLocale::LongDate, true ) ); docInfo.set( "format", i18nc( "PDF v. ", "PDF v. %1", pdfdoc->pdfVersion() ), i18n( "Format" ) ); docInfo.set( "encryption", pdfdoc->isEncrypted() ? i18n( "Encrypted" ) : i18n( "Unencrypted" ), i18n("Security") ); docInfo.set( "optimization", pdfdoc->isLinearized() ? i18n( "Yes" ) : i18n( "No" ), i18n("Optimized") ); docInfo.set( Okular::DocumentInfo::Pages, QString::number( pdfdoc->numPages() ) ); } else { // TODO not sure one can reach here, check and if it is not possible, remove the code docInfo.set( Okular::DocumentInfo::Title, i18n("Unknown") ); docInfo.set( Okular::DocumentInfo::Subject, i18n("Unknown") ); docInfo.set( Okular::DocumentInfo::Author, i18n("Unknown") ); docInfo.set( Okular::DocumentInfo::Keywords, i18n("Unknown") ); docInfo.set( Okular::DocumentInfo::Creator, i18n("Unknown") ); docInfo.set( Okular::DocumentInfo::Producer, i18n("Unknown") ); docInfo.set( Okular::DocumentInfo::CreationDate, i18n("Unknown Date") ); docInfo.set( Okular::DocumentInfo::ModificationDate, i18n("Unknown Date") ); docInfo.set( "format", "PDF", i18n( "Format" ) ); docInfo.set( "encryption", i18n( "Unknown Encryption" ), i18n( "Security" ) ); docInfo.set( "optimization", i18n( "Unknown Optimization" ), i18n( "Optimized" ) ); docInfo.set( Okular::DocumentInfo::Pages, i18n("Unknown") ); } userMutex()->unlock(); // if pdfdoc is valid then we cached good info -> don't cache them again if ( pdfdoc ) docInfoDirty = false; } return &docInfo; } const Okular::DocumentSynopsis * PDFGenerator::generateDocumentSynopsis() { if ( !docSynopsisDirty ) return &docSyn; if ( !pdfdoc ) return NULL; userMutex()->lock(); QDomDocument *toc = pdfdoc->toc(); userMutex()->unlock(); if ( !toc ) return NULL; addSynopsisChildren(toc, &docSyn); delete toc; docSynopsisDirty = false; return &docSyn; } static Okular::FontInfo::FontType convertPopplerFontInfoTypeToOkularFontInfoType( Poppler::FontInfo::Type type ) { switch ( type ) { case Poppler::FontInfo::Type1: return Okular::FontInfo::Type1; break; case Poppler::FontInfo::Type1C: return Okular::FontInfo::Type1C; break; case Poppler::FontInfo::Type3: return Okular::FontInfo::Type3; break; case Poppler::FontInfo::TrueType: return Okular::FontInfo::TrueType; break; case Poppler::FontInfo::CIDType0: return Okular::FontInfo::CIDType0; break; case Poppler::FontInfo::CIDType0C: return Okular::FontInfo::CIDType0C; break; case Poppler::FontInfo::CIDTrueType: return Okular::FontInfo::CIDTrueType; break; #ifdef HAVE_POPPLER_0_6 case Poppler::FontInfo::Type1COT: return Okular::FontInfo::Type1COT; break; case Poppler::FontInfo::TrueTypeOT: return Okular::FontInfo::TrueTypeOT; break; case Poppler::FontInfo::CIDType0COT: return Okular::FontInfo::CIDType0COT; break; case Poppler::FontInfo::CIDTrueTypeOT: return Okular::FontInfo::CIDTrueTypeOT; break; #endif case Poppler::FontInfo::unknown: default: ; } return Okular::FontInfo::Unknown; } static Okular::FontInfo::EmbedType embedTypeForPopplerFontInfo( const Poppler::FontInfo &fi ) { Okular::FontInfo::EmbedType ret = Okular::FontInfo::NotEmbedded; if ( fi.isEmbedded() ) { if ( fi.isSubset() ) { ret = Okular::FontInfo::EmbeddedSubset; } else { ret = Okular::FontInfo::FullyEmbedded; } } return ret; } Okular::FontInfo::List PDFGenerator::fontsForPage( int page ) { Okular::FontInfo::List list; if ( page != nextFontPage ) return list; QList fonts; userMutex()->lock(); pdfdoc->scanForFonts( 1, &fonts ); userMutex()->unlock(); foreach (const Poppler::FontInfo &font, fonts) { Okular::FontInfo of; of.setName( font.name() ); of.setType( convertPopplerFontInfoTypeToOkularFontInfoType( font.type() ) ); of.setEmbedType( embedTypeForPopplerFontInfo( font) ); of.setFile( font.file() ); list.append( of ); } ++nextFontPage; return list; } const QList *PDFGenerator::embeddedFiles() const { if (docEmbeddedFilesDirty) { userMutex()->lock(); const QList &popplerFiles = pdfdoc->embeddedFiles(); foreach(Poppler::EmbeddedFile* pef, popplerFiles) { docEmbeddedFiles.append(new PDFEmbeddedFile(pef)); } userMutex()->unlock(); docEmbeddedFilesDirty = false; } return &docEmbeddedFiles; } bool PDFGenerator::isAllowed( Okular::Permission permission ) const { bool b = true; switch ( permission ) { case Okular::AllowModify: b = pdfdoc->okToChange(); break; case Okular::AllowCopy: b = pdfdoc->okToCopy(); break; case Okular::AllowPrint: b = pdfdoc->okToPrint(); break; case Okular::AllowNotes: b = pdfdoc->okToAddNotes(); break; case Okular::AllowFillForms: b = pdfdoc->okToFillForm(); break; default: ; } return b; } bool PDFGenerator::canGeneratePixmap() const { return ready; } void PDFGenerator::generatePixmap( Okular::PixmapRequest * request ) { #ifndef NDEBUG if ( !ready ) kDebug(PDFDebug) << "calling generatePixmap() when not in READY state!"; #endif // update busy state (not really needed here, because the flag needs to // be set only to prevent asking a pixmap while the thread is running) ready = false; // debug requests to this (xpdf) generator //kDebug(PDFDebug) << "id: " << request->id << " is requesting " << (request->async ? "ASYNC" : "sync") << " pixmap for page " << request->page->number() << " [" << request->width << " x " << request->height << "]."; /** asynchronous requests (generation in PDFPixmapGeneratorThread::run() **/ if ( request->asynchronous() ) { // start the generation into the thread generatorThread->startGeneration( request ); return; } /** synchronous request: in-place generation **/ // compute dpi used to get an image with desired width and height Okular::Page * page = request->page(); double pageWidth = page->width(), pageHeight = page->height(); if ( page->rotation() % 2 ) qSwap( pageWidth, pageHeight ); double fakeDpiX = request->width() * 72.0 / pageWidth, fakeDpiY = request->height() * 72.0 / pageHeight; // setup Okular:: output device: text page is generated only if we are at 72dpi. // since we can pre-generate the TextPage at the right res.. why not? bool genTextPage = !page->hasTextPage() && (request->width() == page->width()) && (request->height() == page->height()); // generate links rects only the first time bool genObjectRects = !rectsGenerated.at( page->number() ); // 0. LOCK [waits for the thread end] userMutex()->lock(); // 1. Set OutputDev parameters and Generate contents // note: thread safety is set on 'false' for the GUI (this) thread Poppler::Page *p = pdfdoc->page(page->number()); // 2. Take data from outputdev and attach it to the Page #ifdef HAVE_POPPLER_0_6 page->setPixmap( request->id(), new QPixmap( QPixmap::fromImage( p->renderToImage(fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0 ) ) ) ); #else page->setPixmap( request->id(), p->splashRenderToPixmap(fakeDpiX, fakeDpiY, -1, -1, request->width(), request->height(), genObjectRects, Poppler::Page::Rotate0 ) ); #endif if ( genObjectRects ) { // TODO previously we extracted Image type rects too, but that needed porting to poppler // and as we are not doing anything with Image type rects i did not port it, have a look at // dead gp_outputdev.cpp on image extraction page->setObjectRects( generateLinks(p->links(), request->width(), request->height(), pdfdoc) ); rectsGenerated[ request->page()->number() ] = true; } // 3. UNLOCK [re-enables shared access] userMutex()->unlock(); if ( genTextPage ) { QList textList = p->textList((Poppler::Page::Rotation)request->page()->orientation()); page->setTextPage( abstractTextPage(textList, page->height(), page->width(), request->page()->orientation()) ); qDeleteAll(textList); } delete p; // update ready state ready = true; // notify the new generation signalPixmapRequestDone( request ); } Okular::TextPage* PDFGenerator::textPage( Okular::Page *page ) { kDebug(PDFDebug) << "calling" ; // build a TextList... Poppler::Page *pp = pdfdoc->page( page->number() ); userMutex()->lock(); QList textList = pp->textList((Poppler::Page::Rotation)page->orientation()); userMutex()->unlock(); delete pp; const double pageWidth = ( page->rotation() % 2 ? page->height() : page->width() ); const double pageHeight = ( page->rotation() % 2 ? page->width() : page->height() ); Okular::TextPage *tp = abstractTextPage(textList, pageHeight, pageWidth, (Poppler::Page::Rotation)page->orientation()); qDeleteAll(textList); return tp; } bool PDFGenerator::print( QPrinter& printer ) { // Get the real page size to pass to the ps generator QPrinter dummy( QPrinter::PrinterResolution ); dummy.setFullPage( true ); dummy.setOrientation( printer.orientation() ); int width = dummy.width(); int height = dummy.height(); // Create the tempfile to send to FilePrinter, which will manage the deletion KTemporaryFile tf; tf.setSuffix( ".ps" ); if ( !tf.open() ) return false; QString tempfilename = tf.fileName(); // Generate the list of pages to be printed as selected in the print dialog QList pageList = Okular::FilePrinter::pageList( printer, pdfdoc->numPages(), document()->bookmarkedPageList() ); // TODO rotation #ifdef HAVE_POPPLER_0_6 #if HAVE_POPPLER_0_7 tf.setAutoRemove(false); #else tf.close(); #endif QString pstitle = metaData(QLatin1String("Title"), QVariant()).toString(); if ( pstitle.trimmed().isEmpty() ) { pstitle = document()->currentDocument().fileName(); } bool forceRasterize = pdfOptionsPage->printForceRaster(); Poppler::PSConverter *psConverter = pdfdoc->psConverter(); #if HAVE_POPPLER_0_7 psConverter->setOutputDevice(&tf); #else psConverter->setOutputFileName(tempfilename); #endif psConverter->setPageList(pageList); psConverter->setPaperWidth(width); psConverter->setPaperHeight(height); psConverter->setRightMargin(0); psConverter->setBottomMargin(0); psConverter->setLeftMargin(0); psConverter->setTopMargin(0); psConverter->setStrictMargins(false); psConverter->setForceRasterize(forceRasterize); psConverter->setTitle(pstitle); userMutex()->lock(); if (psConverter->convert()) { userMutex()->unlock(); delete psConverter; int ret = Okular::FilePrinter::printFile( printer, tempfilename, Okular::FilePrinter::SystemDeletesFiles, Okular::FilePrinter::ApplicationSelectsPages, document()->bookmarkedPageRange() ); if ( ret >= 0 ) return true; } else { delete psConverter; userMutex()->unlock(); return false; } #else // Not HAVE_POPPLER_0_6 userMutex()->lock(); if ( pdfdoc->print( tempfilename, pageList, 72, 72, 0, width, height ) ) { userMutex()->unlock(); tf.setAutoRemove( false ); int ret = Okular::FilePrinter::printFile( printer, tempfilename, Okular::FilePrinter::SystemDeletesFiles, Okular::FilePrinter::ApplicationSelectsPages, document()->bookmarkedPageRange() ); if ( ret >= 0 ) return true; } else { userMutex()->unlock(); return false; } #endif // HAVE_POPPLER_0_6 tf.close(); return false; } QVariant PDFGenerator::metaData( const QString & key, const QVariant & option ) const { if ( key == "StartFullScreen" ) { // asking for the 'start in fullscreen mode' (pdf property) if ( pdfdoc->pageMode() == Poppler::Document::FullScreen ) return true; } else if ( key == "NamedViewport" && !option.toString().isEmpty() ) { // asking for the page related to a 'named link destination'. the // option is the link name. @see addSynopsisChildren. Okular::DocumentViewport viewport; userMutex()->lock(); Poppler::LinkDestination *ld = pdfdoc->linkDestination( option.toString() ); userMutex()->unlock(); if ( ld ) { fillViewportFromLinkDestination( viewport, *ld, pdfdoc ); } delete ld; if ( viewport.pageNumber >= 0 ) return viewport.toString(); } else if ( key == "DocumentTitle" ) { userMutex()->lock(); QString title = pdfdoc->info( "Title" ); userMutex()->unlock(); return title; } else if ( key == "OpenTOC" ) { if ( pdfdoc->pageMode() == Poppler::Document::UseOutlines ) return true; } return QVariant(); } bool PDFGenerator::reparseConfig() { if ( !pdfdoc ) return false; bool somethingchanged = false; // load paper color QColor color = documentMetaData( "PaperColor", true ).value< QColor >(); // if paper color is changed we have to rebuild every visible pixmap in addition // to the outputDevice. it's the 'heaviest' case, other effect are just recoloring // over the page rendered on 'standard' white background. if ( color != pdfdoc->paperColor() ) { userMutex()->lock(); pdfdoc->setPaperColor(color); userMutex()->unlock(); somethingchanged = true; } bool aaChanged = setAAOptions(); somethingchanged = somethingchanged || aaChanged; return somethingchanged; } void PDFGenerator::addPages( KConfigDialog * ) { } bool PDFGenerator::setAAOptions() { bool changed = false; #ifdef HAVE_POPPLER_0_6 static Poppler::Document::RenderHints oldhints = 0; #define SET_HINT(hintname, hintdefvalue, hintflag) \ { \ bool newhint = documentMetaData(hintname, hintdefvalue).toBool(); \ if (newhint != (oldhints & hintflag)) \ { \ pdfdoc->setRenderHint(hintflag, newhint); \ if (newhint) \ oldhints |= hintflag; \ else \ oldhints &= ~(int)hintflag; \ changed = true; \ } \ } SET_HINT("GraphicsAntialias", true, Poppler::Document::Antialiasing) SET_HINT("TextAntialias", true, Poppler::Document::TextAntialiasing) #undef SET_HINT #endif return changed; } Okular::ExportFormat::List PDFGenerator::exportFormats() const { static Okular::ExportFormat::List formats; if ( formats.isEmpty() ) { formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ) ); } return formats; } bool PDFGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format ) { if ( format.mimeType()->name() == QLatin1String( "text/plain" ) ) { QFile f( fileName ); if ( !f.open( QIODevice::WriteOnly ) ) return false; QTextStream ts( &f ); int num = document()->pages(); for ( int i = 0; i < num; ++i ) { userMutex()->lock(); Poppler::Page *pp = pdfdoc->page(i); QString text = pp->text(QRect()); userMutex()->unlock(); ts << text; delete pp; } f.close(); return true; } return false; } //END Generator inherited functions inline void append (Okular::TextPage* ktp, const QString &s, double l, double b, double r, double t) { // kWarning(PDFDebug).nospace() << "text: " << s << " at (" << l << "," << t << ")x(" << r <<","<append( s , new Okular::NormalizedRect( l, t, r, b )); } Okular::TextPage * PDFGenerator::abstractTextPage(const QList &text, double height, double width,int rot) { Okular::TextPage* ktp=new Okular::TextPage; Poppler::TextBox *next; kWarning(PDFDebug) << "getting text page in generator pdf - rotation:" << rot; int charCount=0; int j; QString s; Okular::NormalizedRect * wordRect = new Okular::NormalizedRect; rot = rot % 4; foreach (Poppler::TextBox *word, text) { wordRect->left = word->boundingBox().left(); wordRect->bottom = word->boundingBox().bottom(); wordRect->right = word->boundingBox().right(); wordRect->top = word->boundingBox().top(); charCount=word->text().length(); next=word->nextWord(); switch (rot) { case 0: // 0 degrees, normal word boundaries are top and bottom // only x boundaries change the order of letters is normal not reversed for (j = 0; j < charCount; j++) { s = word->text().at(j); append(ktp, (j==charCount-1 && !next ) ? (s + '\n') : s, // this letters boundary word->edge(j)/width, wordRect->bottom/height, // next letters boundary word->edge(j+1)/width, wordRect->top/height); } if ( word->hasSpaceAfter() && next ) append(ktp, " ", // this letters boundary word->edge(charCount)/width, wordRect->bottom/height, // next letters boundary next->edge(0)/width, wordRect->top/height); break; case 1: // 90 degrees, x boundaries not changed // y ones change, the order of letters is normal not reversed for (j=0;jtext().at(j); append(ktp, (j==charCount-1 && !next ) ? (s + '\n') : s, wordRect->left/width, word->edge(j)/height, wordRect->right/width, word->edge(j+1)/height); } if ( word->hasSpaceAfter() && next ) append(ktp, " ", // this letters boundary wordRect->left/width, word->edge(charCount)/height, // next letters boundary wordRect->right/width, next->edge(0)/height); break; case 2: // same as case 0 but reversed order of letters for (j=0;jtext().at(j); append(ktp, (j==charCount-1 && !next ) ? (s + '\n') : s, word->edge(j+1)/width, wordRect->bottom/height, word->edge(j)/width, wordRect->top/height); } if ( word->hasSpaceAfter() && next ) append(ktp, " ", // this letters boundary next->edge(0)/width, wordRect->bottom/height, // next letters boundary word->edge(charCount)/width, wordRect->top/height); break; case 3: for (j=0;jtext().at(j); append(ktp, (j==charCount-1 && !next ) ? (s + '\n') : s, wordRect->left/width, word->edge(j+1)/height, wordRect->right/width, word->edge(j)/height); } if ( word->hasSpaceAfter() && next ) append(ktp, " ", // this letters boundary wordRect->left/width, next->edge(0)/height, // next letters boundary wordRect->right/width, word->edge(charCount)/height); break; } } delete wordRect; return ktp; } void PDFGenerator::addSynopsisChildren( QDomNode * parent, QDomNode * parentDestination ) { // keep track of the current listViewItem QDomNode n = parent->firstChild(); while( !n.isNull() ) { // convert the node to an element (sure it is) QDomElement e = n.toElement(); // The name is the same QDomElement item = docSyn.createElement( e.tagName() ); parentDestination->appendChild(item); if (!e.attribute("ExternalFileName").isNull()) item.setAttribute("ExternalFileName", e.attribute("ExternalFileName")); if (!e.attribute("DestinationName").isNull()) item.setAttribute("ViewportName", e.attribute("DestinationName")); if (!e.attribute("Destination").isNull()) { Okular::DocumentViewport vp; fillViewportFromLinkDestination( vp, Poppler::LinkDestination(e.attribute("Destination")), pdfdoc ); item.setAttribute( "Viewport", vp.toString() ); } if (!e.attribute("Open").isNull()) item.setAttribute("Open", e.attribute("Open")); // descend recursively and advance to the next node if ( e.hasChildNodes() ) addSynopsisChildren( &n, & item ); n = n.nextSibling(); } } void PDFGenerator::addAnnotations( Poppler::Page * popplerPage, Okular::Page * page ) { QList popplerAnnotations = popplerPage->annotations(); foreach(Poppler::Annotation *a, popplerAnnotations) { a->window.width = (int)(page->width() * a->window.width); a->window.height = (int)(page->height() * a->window.height); //a->window.width = a->window.width < 200 ? 200 : a->window.width; // a->window.height = a->window.height < 120 ? 120 : a->window.height; // resize annotation's geometry to an icon // TODO okular geom.right = geom.left + 22.0 / page->width(); // TODO okular geom.bottom = geom.top + 22.0 / page->height(); /* QString szanno; QTextStream(&szanno)<<"PopplerAnnotation={author:"<author <<", contents:"<contents <<", uniqueName:"<uniqueName <<", modifyDate:"<modifyDate.toString("hh:mm:ss, dd.MM.yyyy") <<", creationDate:"<creationDate.toString("hh:mm:ss, dd.MM.yyyy") <<", flags:"<flags <<", boundary:"<boundary.left()<<","<boundary.top()<<","<boundary.right()<<","<boundary.bottom() <<", style.color:"<style.color.name() <<", style.opacity:"<style.opacity <<", style.width:"<style.width <<", style.LineStyle:"<style.style <<", style.xyCorners:"<style.xCorners<<","<style.yCorners <<", style.marks:"<style.marks <<", style.spaces:"<style.spaces <<", style.LineEffect:"<style.effect <<", style.effectIntensity:"<style.effectIntensity <<", window.flags:"<window.flags <<", window.topLeft:"<<(a->window.topLeft.x()) <<","<<(a->window.topLeft.y()) <<", window.width,height:"<window.width<<","<window.height <<", window.title:"<window.title <<", window.summary:"<window.summary <<", window.text:"<window.text; kDebug(PDFDebug) << "astario: " << szanno; */ // this is uber ugly but i don't know a better way to do it without introducing a poppler::annotation dependency on core //TODO add annotations after poppler write feather is full suported QDomDocument doc; QDomElement root = doc.createElement("root"); doc.appendChild(root); Poppler::AnnotationUtils::storeAnnotation(a, root, doc); Okular::Annotation * newann = Okular::AnnotationUtils::createAnnotation(root); if (newann) { // the Contents field has lines separated by \r QString contents = newann->contents(); contents.replace( QLatin1Char( '\r' ), QLatin1Char( '\n' ) ); newann->setContents( contents ); // explicitly mark as external newann->setFlags( newann->flags() | Okular::Annotation::External ); page->addAnnotation(newann); } } qDeleteAll(popplerAnnotations); } void PDFGenerator::addTransition( Poppler::Page * pdfPage, Okular::Page * page ) // called on opening when MUTEX is not used { Poppler::PageTransition *pdfTransition = pdfPage->transition(); if ( !pdfTransition || pdfTransition->type() == Poppler::PageTransition::Replace ) return; Okular::PageTransition *transition = new Okular::PageTransition(); switch ( pdfTransition->type() ) { case Poppler::PageTransition::Replace: // won't get here, added to avoid warning break; case Poppler::PageTransition::Split: transition->setType( Okular::PageTransition::Split ); break; case Poppler::PageTransition::Blinds: transition->setType( Okular::PageTransition::Blinds ); break; case Poppler::PageTransition::Box: transition->setType( Okular::PageTransition::Box ); break; case Poppler::PageTransition::Wipe: transition->setType( Okular::PageTransition::Wipe ); break; case Poppler::PageTransition::Dissolve: transition->setType( Okular::PageTransition::Dissolve ); break; case Poppler::PageTransition::Glitter: transition->setType( Okular::PageTransition::Glitter ); break; case Poppler::PageTransition::Fly: transition->setType( Okular::PageTransition::Fly ); break; case Poppler::PageTransition::Push: transition->setType( Okular::PageTransition::Push ); break; case Poppler::PageTransition::Cover: transition->setType( Okular::PageTransition::Cover ); break; case Poppler::PageTransition::Uncover: transition->setType( Okular::PageTransition::Uncover ); break; case Poppler::PageTransition::Fade: transition->setType( Okular::PageTransition::Fade ); break; } transition->setDuration( pdfTransition->duration() ); switch ( pdfTransition->alignment() ) { case Poppler::PageTransition::Horizontal: transition->setAlignment( Okular::PageTransition::Horizontal ); break; case Poppler::PageTransition::Vertical: transition->setAlignment( Okular::PageTransition::Vertical ); break; } switch ( pdfTransition->direction() ) { case Poppler::PageTransition::Inward: transition->setDirection( Okular::PageTransition::Inward ); break; case Poppler::PageTransition::Outward: transition->setDirection( Okular::PageTransition::Outward ); break; } transition->setAngle( pdfTransition->angle() ); transition->setScale( pdfTransition->scale() ); transition->setIsRectangular( pdfTransition->isRectangular() ); page->setTransition( transition ); } void PDFGenerator::addFormFields( Poppler::Page * popplerPage, Okular::Page * page ) { #ifdef HAVE_POPPLER_0_6 QList popplerFormFields = popplerPage->formFields(); QLinkedList okularFormFields; foreach( Poppler::FormField *f, popplerFormFields ) { Okular::FormField * of = 0; switch ( f->type() ) { #ifdef HAVE_POPPLER_0_7 case Poppler::FormField::FormButton: of = new PopplerFormFieldButton( static_cast( f ) ); break; #endif case Poppler::FormField::FormText: of = new PopplerFormFieldText( static_cast( f ) ); break; case Poppler::FormField::FormChoice: of = new PopplerFormFieldChoice( static_cast( f ) ); break; default: ; } if ( of ) // form field created, good - it will take care of the Poppler::FormField okularFormFields.append( of ); else // no form field available - delete the Poppler::FormField delete f; } if ( !okularFormFields.isEmpty() ) page->setFormFields( okularFormFields ); #else Q_UNUSED( popplerPage ) Q_UNUSED( page ) #endif } struct pdfsyncpoint { QString file; qlonglong x; qlonglong y; int row; int column; int page; }; void PDFGenerator::loadPdfSync( const QString & filePath, QVector & pagesVector ) { QFile f( filePath + QLatin1String( "sync" ) ); if ( !f.open( QIODevice::ReadOnly ) ) return; QTextStream ts( &f ); // first row: core name of the pdf output - we skip it ts.readLine(); // second row: version string, in the form 'Version %u' QString versionstr = ts.readLine(); QRegExp versionre( "Version (\\d+)" ); versionre.setCaseSensitivity( Qt::CaseInsensitive ); if ( !versionre.exactMatch( versionstr ) ) return; QHash points; QString currentfile; int currentpage = -1; QRegExp newfilere( "\\(\\s*([^\\s]+)" ); QRegExp linere( "l\\s+(\\d+)\\s+(\\d+)(\\s+(\\d+))?" ); QRegExp pagere( "s\\s+(\\d+)" ); QRegExp locre( "p\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)" ); QRegExp locstarre( "p\\*\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)" ); QString line; while ( !ts.atEnd() ) { line = ts.readLine(); if ( line.startsWith( QLatin1Char( 'l' ) ) && linere.exactMatch( line ) ) { int id = linere.cap( 1 ).toInt(); QHash::const_iterator it = points.find( id ); if ( it == points.end() ) { pdfsyncpoint pt; pt.x = 0; pt.y = 0; pt.row = linere.cap( 2 ).toInt(); pt.column = 0; // TODO pt.page = -1; pt.file = currentfile; points[ id ] = pt; } } else if ( line.startsWith( QLatin1Char( 's' ) ) && pagere.exactMatch( line ) ) { currentpage = pagere.cap( 1 ).toInt() - 1; } else if ( line.startsWith( QLatin1String( "p*" ) ) && locstarre.exactMatch( line ) ) { // TODO kDebug(PDFDebug) << "PdfSync: 'p*' line ignored"; } else if ( line.startsWith( QLatin1Char( 'p' ) ) && locre.exactMatch( line ) ) { int id = locre.cap( 1 ).toInt(); QHash::iterator it = points.find( id ); if ( it != points.end() ) { it->x = locre.cap( 2 ).toInt(); it->y = locre.cap( 3 ).toInt(); it->page = currentpage; } } else if ( line.startsWith( QLatin1Char( '(' ) ) && newfilere.exactMatch( line ) ) { QString newfile = newfilere.cap( 1 ); if ( currentfile.isEmpty() ) { currentfile = newfile; } else kDebug(PDFDebug) << "PdfSync: more than one file level:" << newfile; } else if ( line == QLatin1String( ")" ) ) { if ( !currentfile.isEmpty() ) { currentfile.clear(); } else kDebug(PDFDebug) << "PdfSync: going one level down:" << currentfile; } else kDebug(PDFDebug).nospace() << "PdfSync: unknown line format: '" << line << "'"; } QVector< QLinkedList< Okular::SourceRefObjectRect * > > refRects( pagesVector.size() ); foreach ( const pdfsyncpoint& pt, points ) { // drop pdfsync points not completely valid if ( pt.page < 0 || pt.page >= pagesVector.size() ) continue; // maginc numbers for TeX's RSU's (Ridiculously Small Units) conversion to pixels Okular::NormalizedPoint p( ( pt.x * 72.0 ) / ( 72.27 * 65536.0 * pagesVector[pt.page]->width() ), ( pt.y * 72.0 ) / ( 72.27 * 65536.0 * pagesVector[pt.page]->height() ) ); QString file; if ( !pt.file.isEmpty() ) { file = pt.file; int dotpos = file.lastIndexOf( QLatin1Char( '.' ) ); QString ext; if ( dotpos == -1 ) file += QString::fromLatin1( ".tex" ); } else { file = filePath; } Okular::SourceReference * sourceRef = new Okular::SourceReference( file, pt.row, pt.column ); refRects[ pt.page ].append( new Okular::SourceRefObjectRect( p, sourceRef ) ); } for ( int i = 0; i < refRects.size(); ++i ) if ( !refRects.at(i).isEmpty() ) pagesVector[i]->setSourceReferences( refRects.at(i) ); } QWidget* PDFGenerator::printConfigurationWidget() const { return pdfOptionsPage; } bool PDFGenerator::supportsOption( SaveOption option ) const { switch ( option ) { #if HAVE_POPPLER_0_7 case SaveChanges: return true; #endif default: ; } return false; } bool PDFGenerator::save( const QString &fileName, SaveOptions options ) { #if HAVE_POPPLER_0_7 Poppler::PDFConverter *pdfConv = pdfdoc->pdfConverter(); pdfConv->setOutputFileName( fileName ); if ( options & SaveChanges ) pdfConv->setPDFOptions( pdfConv->pdfOptions() | Poppler::PDFConverter::WithChanges ); QMutexLocker locker( userMutex() ); return pdfConv->convert(); #else Q_UNUSED( fileName ) Q_UNUSED( options ) return false; #endif } void PDFGenerator::threadFinished() { #if 0 // check if thread is running (has to be stopped now) if ( generatorThread->running() ) { // if so, wait for effective thread termination if ( !generatorThread->wait( 9999 /*10s timeout*/ ) ) { kWarning(PDFDebug) << "PDFGenerator: thread sent 'data available' " << "signal but had problems ending."; return; } } #endif // 1. the mutex must be unlocked now bool isLocked = true; if (userMutex()->tryLock()) { userMutex()->unlock(); isLocked = false; } if ( isLocked ) { kWarning(PDFDebug) << "PDFGenerator: 'data available' but mutex still " << "held. Recovering."; // synchronize GUI thread (must not happen) userMutex()->lock(); userMutex()->unlock(); } // 2. put thread's generated data into the Okular::Page Okular::PixmapRequest * request = generatorThread->request(); QImage * outImage = generatorThread->takeImage(); QList outText = generatorThread->takeText(); QLinkedList< Okular::ObjectRect * > outRects = generatorThread->takeObjectRects(); request->page()->setPixmap( request->id(), new QPixmap( QPixmap::fromImage( *outImage ) ) ); delete outImage; if ( !outText.isEmpty() ) { request->page()->setTextPage( abstractTextPage( outText , request->page()->height(), request->page()->width(),request->page()->orientation())); qDeleteAll(outText); } bool genObjectRects = !rectsGenerated.at( request->page()->number() ); if (genObjectRects) { request->page()->setObjectRects( outRects ); rectsGenerated[ request->page()->number() ] = true; } else qDeleteAll( outRects ); // 3. tell generator that data has been taken generatorThread->endGeneration(); // update ready state ready = true; // notify the new generation signalPixmapRequestDone( request ); } /** The PDF Pixmap Generator Thread **/ struct PPGThreadPrivate { // reference to main objects PDFGenerator * generator; Okular::PixmapRequest * currentRequest; // internal temp stored items. don't delete this. QImage * m_image; QList m_textList; QLinkedList< Okular::ObjectRect * > m_rects; bool m_rectsTaken; }; PDFPixmapGeneratorThread::PDFPixmapGeneratorThread( PDFGenerator * gen ) : QThread(), d( new PPGThreadPrivate() ) { d->generator = gen; d->currentRequest = 0; d->m_image = 0; d->m_rectsTaken = true; } PDFPixmapGeneratorThread::~PDFPixmapGeneratorThread() { // delete internal objects if the class is deleted before the gui thread // takes the data delete d->m_image; qDeleteAll(d->m_textList); if ( !d->m_rectsTaken && d->m_rects.count() ) { qDeleteAll(d->m_rects); } delete d->currentRequest; // delete internal storage structure delete d; } void PDFPixmapGeneratorThread::startGeneration( Okular::PixmapRequest * request ) { #ifndef NDEBUG // check if a generation is already running if ( d->currentRequest ) { kDebug(PDFDebug) << "PDFPixmapGeneratorThread: requesting a pixmap " << "when another is being generated."; delete request; return; } // check if the mutex is already held bool isLocked = true; if (d->generator->userMutex()->tryLock()) { d->generator->userMutex()->unlock(); isLocked = false; } if ( isLocked ) { kDebug(PDFDebug) << "PDFPixmapGeneratorThread: requesting a pixmap " << "with the mutex already held."; delete request; return; } #endif // set generation parameters and run thread d->currentRequest = request; start( QThread::InheritPriority ); } void PDFPixmapGeneratorThread::endGeneration() { #ifndef NDEBUG // check if a generation is already running if ( !d->currentRequest ) { kDebug(PDFDebug) << "PDFPixmapGeneratorThread: 'end generation' called " << "but generation was not started."; return; } #endif // reset internal members preparing for a new generation d->currentRequest = 0; } Okular::PixmapRequest *PDFPixmapGeneratorThread::request() const { return d->currentRequest; } QImage * PDFPixmapGeneratorThread::takeImage() const { QImage * img = d->m_image; d->m_image = 0; return img; } QList PDFPixmapGeneratorThread::takeText() { QList tl = d->m_textList; d->m_textList.clear(); return tl; } QLinkedList< Okular::ObjectRect * > PDFPixmapGeneratorThread::takeObjectRects() const { d->m_rectsTaken = true; QLinkedList< Okular::ObjectRect * > newrects = d->m_rects; d->m_rects.clear(); return newrects; } void PDFPixmapGeneratorThread::run() // perform contents generation, when the MUTEX is already LOCKED // @see PDFGenerator::generatePixmap( .. ) (and be aware to sync the code) { // compute dpi used to get an image with desired width and height Okular::Page * page = d->currentRequest->page(); int width = d->currentRequest->width(), height = d->currentRequest->height(); double pageWidth = page->width(), pageHeight = page->height(); if ( page->rotation() % 2 ) qSwap( pageWidth, pageHeight ); // setup Okular:: output device: text page is generated only if we are at 72dpi. // since we can pre-generate the TextPage at the right res.. why not? bool genTextPage = !page->hasTextPage() && ( width == page->width() ) && ( height == page->height() ); // generate links rects only the first time bool genObjectRects = !d->generator->rectsGenerated.at( page->number() ); // 0. LOCK s[tart locking XPDF thread unsafe classes] d->generator->userMutex()->lock(); // 1. set OutputDev parameters and Generate contents Poppler::Page *pp = d->generator->pdfdoc->page( page->number() ); const QSizeF &pageSizeF = pp->pageSizeF(); double fakeDpiX = width * 72.0 / pageSizeF.width(), fakeDpiY = height * 72.0 / pageSizeF.height(); // 2. grab data from the OutputDev and store it locally (note takeIMAGE) #ifndef NDEBUG if ( d->m_image ) kDebug(PDFDebug) << "PDFPixmapGeneratorThread: previous image not taken"; if ( !d->m_textList.isEmpty() ) kDebug(PDFDebug) << "PDFPixmapGeneratorThread: previous text not taken"; #endif #ifdef HAVE_POPPLER_0_6 d->m_image = new QImage( pp->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0 ) ); #else d->m_image = new QImage( pp->splashRenderToImage( fakeDpiX, fakeDpiY, -1, -1, width, height, genObjectRects, Poppler::Page::Rotate0 ) ); #endif if ( genObjectRects ) { d->m_rects = generateLinks(pp->links(), width, height, d->generator->pdfdoc); } else d->m_rectsTaken = false; if ( genTextPage ) { d->m_textList = pp->textList((Poppler::Page::Rotation)d->currentRequest->page()->orientation()); } delete pp; // 3. [UNLOCK] mutex d->generator->userMutex()->unlock(); // by ending the thread notifies the GUI thread that data is pending and can be read } #include "generator_pdf.moc"