compile
[kdegraphics.git] / okular / generators / poppler / generator_pdf.cpp
blob16083b65e9692ad87eca0efd9cd8570155ddfe1c
1 /***************************************************************************
2 * Copyright (C) 2004-2008 by Albert Astals Cid <tsdgeos@terra.es> *
3 * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 ***************************************************************************/
11 #include "generator_pdf.h"
13 // qt/kde includes
14 #include <qcheckbox.h>
15 #include <qcolor.h>
16 #include <qfile.h>
17 #include <qimage.h>
18 #include <qlayout.h>
19 #include <qmutex.h>
20 #include <qregexp.h>
21 #include <qtextstream.h>
22 #include <QtGui/QPrinter>
24 #include <kaboutdata.h>
25 #include <klocale.h>
26 #include <kmessagebox.h>
27 #include <kpassworddialog.h>
28 #include <kwallet.h>
29 #include <ktemporaryfile.h>
30 #include <kdebug.h>
31 #include <kglobal.h>
33 #include <okular/core/action.h>
34 #include <okular/core/page.h>
35 #include <okular/core/annotations.h>
36 #include <okular/core/movie.h>
37 #include <okular/core/pagetransition.h>
38 #include <okular/core/sound.h>
39 #include <okular/core/sourcereference.h>
40 #include <okular/core/textpage.h>
41 #include <okular/core/fileprinter.h>
42 #include <okular/core/utils.h>
44 #include <config-okular-poppler.h>
46 #include "formfields.h"
47 #include "popplerembeddedfile.h"
49 #ifdef HAVE_POPPLER_0_9
50 Q_DECLARE_METATYPE(Poppler::FontInfo)
51 #endif
53 static const int PDFDebug = 4710;
55 class PDFOptionsPage : public QWidget
57 public:
58 PDFOptionsPage()
60 setWindowTitle( i18n( "PDF Options" ) );
61 QVBoxLayout *layout = new QVBoxLayout(this);
62 m_forceRaster = new QCheckBox(i18n("Force rasterization"), this);
63 m_forceRaster->setToolTip(i18n("Rasterize into an image before printing"));
64 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."));
65 layout->addWidget(m_forceRaster);
66 layout->addStretch(1);
69 bool printForceRaster()
71 return m_forceRaster->isChecked();
74 void setPrintForceRaster( bool forceRaster )
76 m_forceRaster->setChecked( forceRaster );
79 private:
80 QCheckBox *m_forceRaster;
84 static void fillViewportFromLinkDestination( Okular::DocumentViewport &viewport, const Poppler::LinkDestination &destination )
86 viewport.pageNumber = destination.pageNumber() - 1;
88 if (!viewport.isValid()) return;
90 // get destination position
91 // TODO add other attributes to the viewport (taken from link)
92 // switch ( destination->getKind() )
93 // {
94 // case destXYZ:
95 if (destination.isChangeLeft() || destination.isChangeTop())
97 // TODO remember to change this if we implement DPI and/or rotation
98 double left, top;
99 left = destination.left();
100 top = destination.top();
102 viewport.rePos.normalizedX = left;
103 viewport.rePos.normalizedY = top;
104 viewport.rePos.enabled = true;
105 viewport.rePos.pos = Okular::DocumentViewport::TopLeft;
107 /* TODO
108 if ( dest->getChangeZoom() )
109 make zoom change*/
110 /* break;
112 default:
113 // implement the others cases
114 break;*/
115 // }
118 Okular::Sound* createSoundFromPopplerSound( const Poppler::SoundObject *popplerSound )
120 Okular::Sound *sound = popplerSound->soundType() == Poppler::SoundObject::Embedded ? new Okular::Sound( popplerSound->data() ) : new Okular::Sound( popplerSound->url() );
121 sound->setSamplingRate( popplerSound->samplingRate() );
122 sound->setChannels( popplerSound->channels() );
123 sound->setBitsPerSample( popplerSound->bitsPerSample() );
124 switch ( popplerSound->soundEncoding() )
126 case Poppler::SoundObject::Raw:
127 sound->setSoundEncoding( Okular::Sound::Raw );
128 break;
129 case Poppler::SoundObject::Signed:
130 sound->setSoundEncoding( Okular::Sound::Signed );
131 break;
132 case Poppler::SoundObject::muLaw:
133 sound->setSoundEncoding( Okular::Sound::muLaw );
134 break;
135 case Poppler::SoundObject::ALaw:
136 sound->setSoundEncoding( Okular::Sound::ALaw );
137 break;
139 return sound;
142 #ifdef HAVE_POPPLER_0_9
143 Okular::Movie* createMovieFromPopplerMovie( const Poppler::MovieObject *popplerMovie )
145 Okular::Movie *movie = new Okular::Movie( popplerMovie->url() );
146 movie->setSize( popplerMovie->size() );
147 movie->setRotation( (Okular::Rotation)( popplerMovie->rotation() / 90 ) );
148 movie->setShowControls( popplerMovie->showControls() );
149 movie->setPlayMode( (Okular::Movie::PlayMode)popplerMovie->playMode() );
150 return movie;
152 #endif
154 Okular::Action* createLinkFromPopplerLink(const Poppler::Link *popplerLink)
156 Okular::Action *link = 0;
157 const Poppler::LinkGoto *popplerLinkGoto;
158 const Poppler::LinkExecute *popplerLinkExecute;
159 const Poppler::LinkBrowse *popplerLinkBrowse;
160 const Poppler::LinkAction *popplerLinkAction;
161 const Poppler::LinkSound *popplerLinkSound;
162 #ifdef HAVE_POPPLER_0_9
163 const Poppler::LinkJavaScript *popplerLinkJS;
164 #endif
165 Okular::DocumentViewport viewport;
167 switch(popplerLink->linkType())
169 case Poppler::Link::None:
170 break;
172 case Poppler::Link::Goto:
173 popplerLinkGoto = static_cast<const Poppler::LinkGoto *>(popplerLink);
174 fillViewportFromLinkDestination( viewport, popplerLinkGoto->destination() );
175 link = new Okular::GotoAction(popplerLinkGoto->fileName(), viewport);
176 break;
178 case Poppler::Link::Execute:
179 popplerLinkExecute = static_cast<const Poppler::LinkExecute *>(popplerLink);
180 link = new Okular::ExecuteAction( popplerLinkExecute->fileName(), popplerLinkExecute->parameters() );
181 break;
183 case Poppler::Link::Browse:
184 popplerLinkBrowse = static_cast<const Poppler::LinkBrowse *>(popplerLink);
185 link = new Okular::BrowseAction( popplerLinkBrowse->url() );
186 break;
188 case Poppler::Link::Action:
189 popplerLinkAction = static_cast<const Poppler::LinkAction *>(popplerLink);
190 link = new Okular::DocumentAction( (Okular::DocumentAction::DocumentActionType)popplerLinkAction->actionType() );
191 break;
193 case Poppler::Link::Sound:
195 popplerLinkSound = static_cast<const Poppler::LinkSound *>(popplerLink);
196 Poppler::SoundObject *popplerSound = popplerLinkSound->sound();
197 Okular::Sound *sound = createSoundFromPopplerSound( popplerSound );
198 link = new Okular::SoundAction( popplerLinkSound->volume(), popplerLinkSound->synchronous(), popplerLinkSound->repeat(), popplerLinkSound->mix(), sound );
200 break;
202 #ifdef HAVE_POPPLER_0_9
203 case Poppler::Link::JavaScript:
205 popplerLinkJS = static_cast<const Poppler::LinkJavaScript *>(popplerLink);
206 link = new Okular::ScriptAction( Okular::JavaScript, popplerLinkJS->script() );
208 break;
209 #endif
211 case Poppler::Link::Movie:
212 // not implemented
213 break;
216 return link;
219 static QLinkedList<Okular::ObjectRect*> generateLinks( const QList<Poppler::Link*> &popplerLinks )
221 QLinkedList<Okular::ObjectRect*> links;
222 foreach(const Poppler::Link *popplerLink, popplerLinks)
224 QRectF linkArea = popplerLink->linkArea();
225 double nl = linkArea.left(),
226 nt = linkArea.top(),
227 nr = linkArea.right(),
228 nb = linkArea.bottom();
229 // create the rect using normalized coords and attach the Okular::Link to it
230 Okular::ObjectRect * rect = new Okular::ObjectRect( nl, nt, nr, nb, false, Okular::ObjectRect::Action, createLinkFromPopplerLink(popplerLink) );
231 // add the ObjectRect to the container
232 links.push_front( rect );
234 qDeleteAll(popplerLinks);
235 return links;
238 extern Okular::Annotation* createAnnotationFromPopplerAnnotation( Poppler::Annotation *ann, bool * doDelete );
240 /** NOTES on threading:
241 * internal: thread race prevention is done via the 'docLock' mutex. the
242 * mutex is needed only because we have the asynchronous thread; else
243 * the operations are all within the 'gui' thread, scheduled by the
244 * Qt scheduler and no mutex is needed.
245 * external: dangerous operations are all locked via mutex internally, and the
246 * only needed external thing is the 'canGeneratePixmap' method
247 * that tells if the generator is free (since we don't want an
248 * internal queue to store PixmapRequests). A generatedPixmap call
249 * without the 'ready' flag set, results in undefined behavior.
250 * So, as example, printing while generating a pixmap asynchronously is safe,
251 * it might only block the gui thread by 1) waiting for the mutex to unlock
252 * in async thread and 2) doing the 'heavy' print operation.
255 static KAboutData createAboutData()
257 KAboutData aboutData(
258 "okular_poppler",
259 "okular_poppler",
260 ki18n( "PDF Backend" ),
261 "0.2",
262 ki18n( "A PDF file renderer" ),
263 KAboutData::License_GPL,
264 ki18n( "© 2005-2008 Albert Astals Cid" )
266 aboutData.addAuthor( ki18n( "Albert Astals Cid" ), KLocalizedString(), "aacid@kde.org" );
267 return aboutData;
270 OKULAR_EXPORT_PLUGIN(PDFGenerator, createAboutData())
272 PDFGenerator::PDFGenerator( QObject *parent, const QVariantList &args )
273 : Generator( parent, args ), pdfdoc( 0 ), ready( true ),
274 pixmapRequest( 0 ), docInfoDirty( true ), docSynopsisDirty( true ),
275 docEmbeddedFilesDirty( true ), nextFontPage( 0 )
277 setFeature( TextExtraction );
278 setFeature( FontInfo );
279 setFeature( PrintPostscript );
280 if ( Okular::FilePrinter::ps2pdfAvailable() )
281 setFeature( PrintToFile );
282 setFeature( ReadRawData );
283 // generate the pixmapGeneratorThread
284 generatorThread = new PDFPixmapGeneratorThread( this );
285 connect(generatorThread, SIGNAL(finished()), this, SLOT(threadFinished()), Qt::QueuedConnection);
288 PDFGenerator::~PDFGenerator()
290 // stop and delete the generator thread
291 if ( generatorThread )
293 generatorThread->wait();
294 delete generatorThread;
297 delete pdfOptionsPage;
300 //BEGIN Generator inherited functions
301 bool PDFGenerator::loadDocument( const QString & filePath, QVector<Okular::Page*> & pagesVector )
303 #ifndef NDEBUG
304 if ( pdfdoc )
306 kDebug(PDFDebug) << "PDFGenerator: multiple calls to loadDocument. Check it.";
307 return false;
309 #endif
310 // create PDFDoc for the given file
311 pdfdoc = Poppler::Document::load( filePath, 0, 0 );
312 bool success = init(pagesVector, filePath.section('/', -1, -1));
313 if (success && QFile::exists(filePath + QLatin1String( "sync" )))
315 loadPdfSync(filePath, pagesVector);
317 return success;
320 bool PDFGenerator::loadDocumentFromData( const QByteArray & fileData, QVector<Okular::Page*> & pagesVector )
322 #ifndef NDEBUG
323 if ( pdfdoc )
325 kDebug(PDFDebug) << "PDFGenerator: multiple calls to loadDocument. Check it.";
326 return false;
328 #endif
329 // create PDFDoc for the given file
330 pdfdoc = Poppler::Document::loadFromData( fileData, 0, 0 );
331 return init(pagesVector, QString());
334 bool PDFGenerator::init(QVector<Okular::Page*> & pagesVector, const QString &walletKey)
336 // if the file didn't open correctly it might be encrypted, so ask for a pass
337 bool firstInput = true;
338 bool triedWallet = false;
339 KWallet::Wallet * wallet = 0;
340 bool keep = true;
341 while ( pdfdoc && pdfdoc->isLocked() )
343 QString password;
345 // 1.A. try to retrieve the first password from the kde wallet system
346 if ( !triedWallet && !walletKey.isNull() )
348 QString walletName = KWallet::Wallet::NetworkWallet();
349 WId parentwid = 0;
350 if ( document() && document()->widget() )
351 parentwid = document()->widget()->effectiveWinId();
352 wallet = KWallet::Wallet::openWallet( walletName, parentwid );
353 if ( wallet )
355 // use the KPdf folder (and create if missing)
356 if ( !wallet->hasFolder( "KPdf" ) )
357 wallet->createFolder( "KPdf" );
358 wallet->setFolder( "KPdf" );
360 // look for the pass in that folder
361 QString retrievedPass;
362 if ( !wallet->readPassword( walletKey, retrievedPass ) )
363 password = retrievedPass;
365 triedWallet = true;
368 // 1.B. if not retrieved, ask the password using the kde password dialog
369 if ( password.isNull() )
371 QString prompt;
372 if ( firstInput )
373 prompt = i18n( "Please insert the password to read the document:" );
374 else
375 prompt = i18n( "Incorrect password. Try again:" );
376 firstInput = false;
378 // if the user presses cancel, abort opening
379 KPasswordDialog dlg( document()->widget(), wallet ? KPasswordDialog::ShowKeepPassword : KPasswordDialog::KPasswordDialogFlags() );
380 dlg.setCaption( i18n( "Document Password" ) );
381 dlg.setPrompt( prompt );
382 if( !dlg.exec() )
383 break;
384 password = dlg.password();
385 if ( wallet )
386 keep = dlg.keepPassword();
389 // 2. reopen the document using the password
390 pdfdoc->unlock( password.toLatin1(), password.toLatin1() );
392 // 3. if the password is correct and the user chose to remember it, store it to the wallet
393 if ( !pdfdoc->isLocked() && wallet && /*safety check*/ wallet->isOpen() && keep )
395 wallet->writePassword( walletKey, password );
398 if ( !pdfdoc || pdfdoc->isLocked() )
400 delete pdfdoc;
401 pdfdoc = 0;
402 return false;
405 // build Pages (currentPage was set -1 by deletePages)
406 uint pageCount = pdfdoc->numPages();
407 pagesVector.resize(pageCount);
408 rectsGenerated.fill(false, pageCount);
410 loadPages(pagesVector, 0, false);
412 // update the configuration
413 reparseConfig();
415 // the file has been loaded correctly
416 return true;
419 bool PDFGenerator::doCloseDocument()
421 // remove internal objects
422 userMutex()->lock();
423 delete pdfdoc;
424 pdfdoc = 0;
425 userMutex()->unlock();
426 docInfoDirty = true;
427 docSynopsisDirty = true;
428 docSyn.clear();
429 docEmbeddedFilesDirty = true;
430 qDeleteAll(docEmbeddedFiles);
431 docEmbeddedFiles.clear();
432 nextFontPage = 0;
434 return true;
437 void PDFGenerator::loadPages(QVector<Okular::Page*> &pagesVector, int rotation, bool clear)
439 // TODO XPDF 3.01 check
440 int count=pagesVector.count(),w=0,h=0;
441 for ( int i = 0; i < count ; i++ )
443 // get xpdf page
444 Poppler::Page * p = pdfdoc->page( i );
445 QSize pSize = p->pageSize();
446 w = pSize.width();
447 h = pSize.height();
448 Okular::Rotation orientation = Okular::Rotation0;
449 switch (p->orientation())
451 case Poppler::Page::Landscape: orientation = Okular::Rotation90; break;
452 case Poppler::Page::UpsideDown: orientation = Okular::Rotation180; break;
453 case Poppler::Page::Seascape: orientation = Okular::Rotation270; break;
454 case Poppler::Page::Portrait: orientation = Okular::Rotation0; break;
456 if (rotation % 2 == 1)
457 qSwap(w,h);
458 // init a Okular::page, add transition and annotation information
459 Okular::Page * page = new Okular::Page( i, w, h, orientation );
460 addTransition( p, page );
461 if ( true ) //TODO real check
462 addAnnotations( p, page );
463 Poppler::Link * tmplink = p->action( Poppler::Page::Opening );
464 if ( tmplink )
466 page->setPageAction( Okular::Page::Opening, createLinkFromPopplerLink( tmplink ) );
467 delete tmplink;
469 tmplink = p->action( Poppler::Page::Closing );
470 if ( tmplink )
472 page->setPageAction( Okular::Page::Closing, createLinkFromPopplerLink( tmplink ) );
473 delete tmplink;
475 page->setDuration( p->duration() );
476 page->setLabel( p->label() );
478 addFormFields( p, page );
479 // kWarning(PDFDebug).nospace() << page->width() << "x" << page->height();
481 #ifdef PDFGENERATOR_DEBUG
482 kDebug(PDFDebug) << "load page" << i << "with rotation" << rotation << "and orientation" << orientation;
483 #endif
484 delete p;
486 if (clear && pagesVector[i])
487 delete pagesVector[i];
488 // set the Okular::page at the right position in document's pages vector
489 pagesVector[i] = page;
493 const Okular::DocumentInfo * PDFGenerator::generateDocumentInfo()
495 if ( docInfoDirty )
497 userMutex()->lock();
499 docInfo.set( Okular::DocumentInfo::MimeType, "application/pdf" );
501 if ( pdfdoc )
503 // compile internal structure reading properties from PDFDoc
504 docInfo.set( Okular::DocumentInfo::Title, pdfdoc->info("Title") );
505 docInfo.set( Okular::DocumentInfo::Subject, pdfdoc->info("Subject") );
506 docInfo.set( Okular::DocumentInfo::Author, pdfdoc->info("Author") );
507 docInfo.set( Okular::DocumentInfo::Keywords, pdfdoc->info("Keywords") );
508 docInfo.set( Okular::DocumentInfo::Creator, pdfdoc->info("Creator") );
509 docInfo.set( Okular::DocumentInfo::Producer, pdfdoc->info("Producer") );
510 docInfo.set( Okular::DocumentInfo::CreationDate,
511 KGlobal::locale()->formatDateTime( pdfdoc->date("CreationDate"), KLocale::LongDate, true ) );
512 docInfo.set( Okular::DocumentInfo::ModificationDate,
513 KGlobal::locale()->formatDateTime( pdfdoc->date("ModDate"), KLocale::LongDate, true ) );
515 docInfo.set( "format", i18nc( "PDF v. <version>", "PDF v. %1",
516 pdfdoc->pdfVersion() ), i18n( "Format" ) );
517 docInfo.set( "encryption", pdfdoc->isEncrypted() ? i18n( "Encrypted" ) : i18n( "Unencrypted" ),
518 i18n("Security") );
519 docInfo.set( "optimization", pdfdoc->isLinearized() ? i18n( "Yes" ) : i18n( "No" ),
520 i18n("Optimized") );
522 docInfo.set( Okular::DocumentInfo::Pages, QString::number( pdfdoc->numPages() ) );
524 else
526 // TODO not sure one can reach here, check and if it is not possible, remove the code
527 docInfo.set( Okular::DocumentInfo::Title, i18n("Unknown") );
528 docInfo.set( Okular::DocumentInfo::Subject, i18n("Unknown") );
529 docInfo.set( Okular::DocumentInfo::Author, i18n("Unknown") );
530 docInfo.set( Okular::DocumentInfo::Keywords, i18n("Unknown") );
531 docInfo.set( Okular::DocumentInfo::Creator, i18n("Unknown") );
532 docInfo.set( Okular::DocumentInfo::Producer, i18n("Unknown") );
533 docInfo.set( Okular::DocumentInfo::CreationDate, i18n("Unknown Date") );
534 docInfo.set( Okular::DocumentInfo::ModificationDate, i18n("Unknown Date") );
536 docInfo.set( "format", "PDF", i18n( "Format" ) );
537 docInfo.set( "encryption", i18n( "Unknown Encryption" ), i18n( "Security" ) );
538 docInfo.set( "optimization", i18n( "Unknown Optimization" ), i18n( "Optimized" ) );
540 docInfo.set( Okular::DocumentInfo::Pages, i18n("Unknown") );
542 userMutex()->unlock();
544 // if pdfdoc is valid then we cached good info -> don't cache them again
545 if ( pdfdoc )
546 docInfoDirty = false;
548 return &docInfo;
551 const Okular::DocumentSynopsis * PDFGenerator::generateDocumentSynopsis()
553 if ( !docSynopsisDirty )
554 return &docSyn;
556 if ( !pdfdoc )
557 return NULL;
559 userMutex()->lock();
560 QDomDocument *toc = pdfdoc->toc();
561 userMutex()->unlock();
562 if ( !toc )
563 return NULL;
565 addSynopsisChildren(toc, &docSyn);
566 delete toc;
568 docSynopsisDirty = false;
569 return &docSyn;
572 static Okular::FontInfo::FontType convertPopplerFontInfoTypeToOkularFontInfoType( Poppler::FontInfo::Type type )
574 switch ( type )
576 case Poppler::FontInfo::Type1:
577 return Okular::FontInfo::Type1;
578 break;
579 case Poppler::FontInfo::Type1C:
580 return Okular::FontInfo::Type1C;
581 break;
582 case Poppler::FontInfo::Type3:
583 return Okular::FontInfo::Type3;
584 break;
585 case Poppler::FontInfo::TrueType:
586 return Okular::FontInfo::TrueType;
587 break;
588 case Poppler::FontInfo::CIDType0:
589 return Okular::FontInfo::CIDType0;
590 break;
591 case Poppler::FontInfo::CIDType0C:
592 return Okular::FontInfo::CIDType0C;
593 break;
594 case Poppler::FontInfo::CIDTrueType:
595 return Okular::FontInfo::CIDTrueType;
596 break;
597 case Poppler::FontInfo::Type1COT:
598 return Okular::FontInfo::Type1COT;
599 break;
600 case Poppler::FontInfo::TrueTypeOT:
601 return Okular::FontInfo::TrueTypeOT;
602 break;
603 case Poppler::FontInfo::CIDType0COT:
604 return Okular::FontInfo::CIDType0COT;
605 break;
606 case Poppler::FontInfo::CIDTrueTypeOT:
607 return Okular::FontInfo::CIDTrueTypeOT;
608 break;
609 case Poppler::FontInfo::unknown:
610 default: ;
612 return Okular::FontInfo::Unknown;
615 static Okular::FontInfo::EmbedType embedTypeForPopplerFontInfo( const Poppler::FontInfo &fi )
617 Okular::FontInfo::EmbedType ret = Okular::FontInfo::NotEmbedded;
618 if ( fi.isEmbedded() )
620 if ( fi.isSubset() )
622 ret = Okular::FontInfo::EmbeddedSubset;
624 else
626 ret = Okular::FontInfo::FullyEmbedded;
629 return ret;
632 Okular::FontInfo::List PDFGenerator::fontsForPage( int page )
634 Okular::FontInfo::List list;
636 if ( page != nextFontPage )
637 return list;
639 QList<Poppler::FontInfo> fonts;
640 userMutex()->lock();
641 pdfdoc->scanForFonts( 1, &fonts );
642 userMutex()->unlock();
644 foreach (const Poppler::FontInfo &font, fonts)
646 Okular::FontInfo of;
647 of.setName( font.name() );
648 of.setType( convertPopplerFontInfoTypeToOkularFontInfoType( font.type() ) );
649 of.setEmbedType( embedTypeForPopplerFontInfo( font) );
650 of.setFile( font.file() );
651 #ifdef HAVE_POPPLER_0_9
652 of.setCanBeExtracted( of.embedType() != Okular::FontInfo::NotEmbedded );
654 QVariant nativeId;
655 nativeId.setValue( font );
656 of.setNativeId( nativeId );
657 #endif
659 list.append( of );
662 ++nextFontPage;
664 return list;
667 const QList<Okular::EmbeddedFile*> *PDFGenerator::embeddedFiles() const
669 if (docEmbeddedFilesDirty)
671 userMutex()->lock();
672 const QList<Poppler::EmbeddedFile*> &popplerFiles = pdfdoc->embeddedFiles();
673 foreach(Poppler::EmbeddedFile* pef, popplerFiles)
675 docEmbeddedFiles.append(new PDFEmbeddedFile(pef));
677 userMutex()->unlock();
679 docEmbeddedFilesDirty = false;
682 return &docEmbeddedFiles;
685 bool PDFGenerator::isAllowed( Okular::Permission permission ) const
687 bool b = true;
688 switch ( permission )
690 case Okular::AllowModify:
691 b = pdfdoc->okToChange();
692 break;
693 case Okular::AllowCopy:
694 b = pdfdoc->okToCopy();
695 break;
696 case Okular::AllowPrint:
697 b = pdfdoc->okToPrint();
698 break;
699 case Okular::AllowNotes:
700 b = pdfdoc->okToAddNotes();
701 break;
702 case Okular::AllowFillForms:
703 b = pdfdoc->okToFillForm();
704 break;
705 default: ;
707 return b;
710 bool PDFGenerator::canGeneratePixmap() const
712 return ready;
715 void PDFGenerator::generatePixmap( Okular::PixmapRequest * request )
717 #ifndef NDEBUG
718 if ( !ready )
719 kDebug(PDFDebug) << "calling generatePixmap() when not in READY state!";
720 #endif
721 // update busy state (not really needed here, because the flag needs to
722 // be set only to prevent asking a pixmap while the thread is running)
723 ready = false;
725 // debug requests to this (xpdf) generator
726 //kDebug(PDFDebug) << "id: " << request->id << " is requesting " << (request->async ? "ASYNC" : "sync") << " pixmap for page " << request->page->number() << " [" << request->width << " x " << request->height << "].";
728 /** asynchronous requests (generation in PDFPixmapGeneratorThread::run() **/
729 if ( request->asynchronous() )
731 // start the generation into the thread
732 generatorThread->startGeneration( request );
733 return;
736 /** synchronous request: in-place generation **/
737 // compute dpi used to get an image with desired width and height
738 Okular::Page * page = request->page();
740 double pageWidth = page->width(),
741 pageHeight = page->height();
743 if ( page->rotation() % 2 )
744 qSwap( pageWidth, pageHeight );
746 double fakeDpiX = request->width() * 72.0 / pageWidth,
747 fakeDpiY = request->height() * 72.0 / pageHeight;
749 // setup Okular:: output device: text page is generated only if we are at 72dpi.
750 // since we can pre-generate the TextPage at the right res.. why not?
751 bool genTextPage = !page->hasTextPage() && (request->width() == page->width()) &&
752 (request->height() == page->height());
753 // generate links rects only the first time
754 bool genObjectRects = !rectsGenerated.at( page->number() );
756 // 0. LOCK [waits for the thread end]
757 userMutex()->lock();
759 // 1. Set OutputDev parameters and Generate contents
760 // note: thread safety is set on 'false' for the GUI (this) thread
761 Poppler::Page *p = pdfdoc->page(page->number());
763 // 2. Take data from outputdev and attach it to the Page
765 QImage img( p->renderToImage(fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0 ) );
766 if ( !page->isBoundingBoxKnown() )
767 updatePageBoundingBox( page->number(), Okular::Utils::imageBoundingBox( &img ) );
769 page->setPixmap( request->id(), new QPixmap( QPixmap::fromImage( img ) ) );
772 if ( genObjectRects )
774 // TODO previously we extracted Image type rects too, but that needed porting to poppler
775 // and as we are not doing anything with Image type rects i did not port it, have a look at
776 // dead gp_outputdev.cpp on image extraction
777 page->setObjectRects( generateLinks(p->links()) );
778 rectsGenerated[ request->page()->number() ] = true;
781 // 3. UNLOCK [re-enables shared access]
782 userMutex()->unlock();
783 if ( genTextPage )
785 #ifdef HAVE_POPPLER_0_7
786 QList<Poppler::TextBox*> textList = p->textList();
787 #else
788 QList<Poppler::TextBox*> textList = p->textList((Poppler::Page::Rotation)request->page()->orientation());
789 #endif
790 Okular::TextPage *tp = abstractTextPage(textList, page->height(), page->width(), request->page()->orientation());
791 page->setTextPage( tp );
792 qDeleteAll(textList);
794 // notify the new generation
795 signalTextGenerationDone( page, tp );
797 delete p;
799 // update ready state
800 ready = true;
802 // notify the new generation
803 signalPixmapRequestDone( request );
806 Okular::TextPage* PDFGenerator::textPage( Okular::Page *page )
808 kDebug(PDFDebug) << "calling" ;
809 // build a TextList...
810 Poppler::Page *pp = pdfdoc->page( page->number() );
811 userMutex()->lock();
812 #ifdef HAVE_POPPLER_0_7
813 QList<Poppler::TextBox*> textList = pp->textList();
814 #else
815 QList<Poppler::TextBox*> textList = pp->textList((Poppler::Page::Rotation)page->orientation());
816 #endif
817 userMutex()->unlock();
818 delete pp;
820 #ifdef HAVE_POPPLER_0_7
821 const double pageWidth = page->width();
822 const double pageHeight = page->height();
823 #else
824 const double pageWidth = ( page->rotation() % 2 ? page->height() : page->width() );
825 const double pageHeight = ( page->rotation() % 2 ? page->width() : page->height() );
826 #endif
828 Okular::TextPage *tp = abstractTextPage(textList, pageHeight, pageWidth, (Poppler::Page::Rotation)page->orientation());
829 qDeleteAll(textList);
830 return tp;
833 void PDFGenerator::requestFontData(const Okular::FontInfo &font, QByteArray *data)
835 #ifdef HAVE_POPPLER_0_9
836 Poppler::FontInfo fi = font.nativeId().value<Poppler::FontInfo>();
837 *data = pdfdoc->fontData(fi);
838 #else
839 Q_UNUSED( font )
840 Q_UNUSED( data )
841 #endif
844 bool PDFGenerator::print( QPrinter& printer )
846 // Get the real page size to pass to the ps generator
847 QPrinter dummy( QPrinter::PrinterResolution );
848 dummy.setFullPage( true );
849 dummy.setOrientation( printer.orientation() );
850 dummy.setPageSize( printer.pageSize() );
851 dummy.setPaperSize( printer.paperSize( QPrinter::Millimeter ), QPrinter::Millimeter );
852 int width = dummy.width();
853 int height = dummy.height();
855 // Create the tempfile to send to FilePrinter, which will manage the deletion
856 KTemporaryFile tf;
857 tf.setSuffix( ".ps" );
858 if ( !tf.open() )
859 return false;
860 QString tempfilename = tf.fileName();
862 // Generate the list of pages to be printed as selected in the print dialog
863 QList<int> pageList = Okular::FilePrinter::pageList( printer, pdfdoc->numPages(),
864 document()->bookmarkedPageList() );
866 // TODO rotation
868 #ifdef HAVE_POPPLER_0_7
869 tf.setAutoRemove(false);
870 #else
871 tf.close();
872 #endif
874 QString pstitle = metaData(QLatin1String("Title"), QVariant()).toString();
875 if ( pstitle.trimmed().isEmpty() )
877 pstitle = document()->currentDocument().fileName();
880 bool forceRasterize = false;
881 if ( pdfOptionsPage )
883 forceRasterize = pdfOptionsPage->printForceRaster();
886 Poppler::PSConverter *psConverter = pdfdoc->psConverter();
888 #ifdef HAVE_POPPLER_0_7
889 psConverter->setOutputDevice(&tf);
890 #else
891 psConverter->setOutputFileName(tempfilename);
892 #endif
894 psConverter->setPageList(pageList);
895 psConverter->setPaperWidth(width);
896 psConverter->setPaperHeight(height);
897 psConverter->setRightMargin(0);
898 psConverter->setBottomMargin(0);
899 psConverter->setLeftMargin(0);
900 psConverter->setTopMargin(0);
901 psConverter->setStrictMargins(false);
902 psConverter->setForceRasterize(forceRasterize);
903 psConverter->setTitle(pstitle);
905 userMutex()->lock();
906 if (psConverter->convert())
908 userMutex()->unlock();
909 delete psConverter;
910 #ifdef HAVE_POPPLER_0_7
911 tf.close();
912 #endif
913 int ret = Okular::FilePrinter::printFile( printer, tempfilename,
914 Okular::FilePrinter::SystemDeletesFiles,
915 Okular::FilePrinter::ApplicationSelectsPages,
916 document()->bookmarkedPageRange() );
917 if ( ret >= 0 ) return true;
919 else
921 delete psConverter;
922 userMutex()->unlock();
923 return false;
926 tf.close();
928 return false;
931 QVariant PDFGenerator::metaData( const QString & key, const QVariant & option ) const
933 if ( key == "StartFullScreen" )
935 // asking for the 'start in fullscreen mode' (pdf property)
936 if ( pdfdoc->pageMode() == Poppler::Document::FullScreen )
937 return true;
939 else if ( key == "NamedViewport" && !option.toString().isEmpty() )
941 // asking for the page related to a 'named link destination'. the
942 // option is the link name. @see addSynopsisChildren.
943 Okular::DocumentViewport viewport;
944 userMutex()->lock();
945 Poppler::LinkDestination *ld = pdfdoc->linkDestination( option.toString() );
946 userMutex()->unlock();
947 if ( ld )
949 fillViewportFromLinkDestination( viewport, *ld );
951 delete ld;
952 if ( viewport.pageNumber >= 0 )
953 return viewport.toString();
955 else if ( key == "DocumentTitle" )
957 userMutex()->lock();
958 QString title = pdfdoc->info( "Title" );
959 userMutex()->unlock();
960 return title;
962 else if ( key == "OpenTOC" )
964 if ( pdfdoc->pageMode() == Poppler::Document::UseOutlines )
965 return true;
967 #ifdef HAVE_POPPLER_0_9
968 else if ( key == "DocumentScripts" && option.toString() == "JavaScript" )
970 return pdfdoc->scripts();
972 #endif
973 return QVariant();
976 bool PDFGenerator::reparseConfig()
978 if ( !pdfdoc )
979 return false;
981 bool somethingchanged = false;
982 // load paper color
983 QColor color = documentMetaData( "PaperColor", true ).value< QColor >();
984 // if paper color is changed we have to rebuild every visible pixmap in addition
985 // to the outputDevice. it's the 'heaviest' case, other effect are just recoloring
986 // over the page rendered on 'standard' white background.
987 if ( color != pdfdoc->paperColor() )
989 userMutex()->lock();
990 pdfdoc->setPaperColor(color);
991 userMutex()->unlock();
992 somethingchanged = true;
994 bool aaChanged = setAAOptions();
995 somethingchanged = somethingchanged || aaChanged;
996 return somethingchanged;
999 void PDFGenerator::addPages( KConfigDialog * )
1003 bool PDFGenerator::setAAOptions()
1005 bool changed = false;
1006 static Poppler::Document::RenderHints oldhints = 0;
1007 #define SET_HINT(hintname, hintdefvalue, hintflag) \
1009 bool newhint = documentMetaData(hintname, hintdefvalue).toBool(); \
1010 if (newhint != (oldhints & hintflag)) \
1012 pdfdoc->setRenderHint(hintflag, newhint); \
1013 if (newhint) \
1014 oldhints |= hintflag; \
1015 else \
1016 oldhints &= ~(int)hintflag; \
1017 changed = true; \
1020 SET_HINT("GraphicsAntialias", true, Poppler::Document::Antialiasing)
1021 SET_HINT("TextAntialias", true, Poppler::Document::TextAntialiasing)
1022 #undef SET_HINT
1023 return changed;
1026 Okular::ExportFormat::List PDFGenerator::exportFormats() const
1028 static Okular::ExportFormat::List formats;
1029 if ( formats.isEmpty() ) {
1030 formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ) );
1033 return formats;
1036 bool PDFGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format )
1038 if ( format.mimeType()->name() == QLatin1String( "text/plain" ) ) {
1039 QFile f( fileName );
1040 if ( !f.open( QIODevice::WriteOnly ) )
1041 return false;
1043 QTextStream ts( &f );
1044 int num = document()->pages();
1045 for ( int i = 0; i < num; ++i )
1047 userMutex()->lock();
1048 Poppler::Page *pp = pdfdoc->page(i);
1049 QString text = pp->text(QRect());
1050 userMutex()->unlock();
1051 ts << text;
1052 delete pp;
1054 f.close();
1056 return true;
1059 return false;
1062 //END Generator inherited functions
1064 inline void append (Okular::TextPage* ktp,
1065 const QString &s, double l, double b, double r, double t)
1067 // kWarning(PDFDebug).nospace() << "text: " << s << " at (" << l << "," << t << ")x(" << r <<","<<b<<")";
1068 ktp->append( s ,
1069 new Okular::NormalizedRect(
1077 Okular::TextPage * PDFGenerator::abstractTextPage(const QList<Poppler::TextBox*> &text, double height, double width,int rot)
1079 Okular::TextPage* ktp=new Okular::TextPage;
1080 Poppler::TextBox *next;
1081 kWarning(PDFDebug) << "getting text page in generator pdf - rotation:" << rot;
1082 int charCount=0;
1083 int j;
1084 QString s;
1085 #ifdef HAVE_POPPLER_0_7
1086 foreach (Poppler::TextBox *word, text)
1088 charCount=word->text().length();
1089 next=word->nextWord();
1090 for (j = 0; j < charCount; j++)
1092 s = word->text().at(j);
1093 QRectF charBBox = word->charBoundingBox(j);
1094 append(ktp, (j==charCount-1 && !next ) ? (s + '\n') : s,
1095 charBBox.left()/width,
1096 charBBox.bottom()/height,
1097 charBBox.right()/width,
1098 charBBox.top()/height);
1101 if ( word->hasSpaceAfter() && next )
1103 // TODO Check with a document with vertical text
1104 // probably won't work and we will need to do comparisons
1105 // between wordBBox and nextWordBBox to see if they are
1106 // vertically or horizontally aligned
1107 QRectF wordBBox = word->boundingBox();
1108 QRectF nextWordBBox = next->boundingBox();
1109 append(ktp, " ",
1110 wordBBox.right()/width,
1111 wordBBox.bottom()/height,
1112 nextWordBBox.left()/width,
1113 wordBBox.top()/height);
1116 #else
1117 Okular::NormalizedRect * wordRect = new Okular::NormalizedRect;
1119 rot = rot % 4;
1121 foreach (Poppler::TextBox *word, text)
1123 wordRect->left = word->boundingBox().left();
1124 wordRect->bottom = word->boundingBox().bottom();
1125 wordRect->right = word->boundingBox().right();
1126 wordRect->top = word->boundingBox().top();
1127 charCount=word->text().length();
1128 next=word->nextWord();
1129 switch (rot)
1131 case 0:
1132 // 0 degrees, normal word boundaries are top and bottom
1133 // only x boundaries change the order of letters is normal not reversed
1134 for (j = 0; j < charCount; j++)
1136 s = word->text().at(j);
1137 append(ktp, (j==charCount-1 && !next ) ? (s + '\n') : s,
1138 // this letters boundary
1139 word->edge(j)/width,
1140 wordRect->bottom/height,
1141 // next letters boundary
1142 word->edge(j+1)/width,
1143 wordRect->top/height);
1146 if ( word->hasSpaceAfter() && next )
1147 append(ktp, " ",
1148 // this letters boundary
1149 word->edge(charCount)/width,
1150 wordRect->bottom/height,
1151 // next letters boundary
1152 next->edge(0)/width,
1153 wordRect->top/height);
1154 break;
1156 case 1:
1157 // 90 degrees, x boundaries not changed
1158 // y ones change, the order of letters is normal not reversed
1159 for (j=0;j<charCount;j++)
1161 s=word->text().at(j);
1162 append(ktp, (j==charCount-1 && !next ) ? (s + '\n') : s,
1163 wordRect->left/width,
1164 word->edge(j)/height,
1165 wordRect->right/width,
1166 word->edge(j+1)/height);
1169 if ( word->hasSpaceAfter() && next )
1170 append(ktp, " ",
1171 // this letters boundary
1172 wordRect->left/width,
1173 word->edge(charCount)/height,
1174 // next letters boundary
1175 wordRect->right/width,
1176 next->edge(0)/height);
1177 break;
1179 case 2:
1180 // same as case 0 but reversed order of letters
1181 for (j=0;j<charCount;j++)
1183 s=word->text().at(j);
1184 append(ktp, (j==charCount-1 && !next ) ? (s + '\n') : s,
1185 word->edge(j+1)/width,
1186 wordRect->bottom/height,
1187 word->edge(j)/width,
1188 wordRect->top/height);
1192 if ( word->hasSpaceAfter() && next )
1193 append(ktp, " ",
1194 // this letters boundary
1195 next->edge(0)/width,
1196 wordRect->bottom/height,
1197 // next letters boundary
1198 word->edge(charCount)/width,
1199 wordRect->top/height);
1201 break;
1203 case 3:
1204 for (j=0;j<charCount;j++)
1206 s=word->text().at(j);
1207 append(ktp, (j==charCount-1 && !next ) ? (s + '\n') : s,
1208 wordRect->left/width,
1209 word->edge(j+1)/height,
1210 wordRect->right/width,
1211 word->edge(j)/height);
1214 if ( word->hasSpaceAfter() && next )
1215 append(ktp, " ",
1216 // this letters boundary
1217 wordRect->left/width,
1218 next->edge(0)/height,
1219 // next letters boundary
1220 wordRect->right/width,
1221 word->edge(charCount)/height);
1222 break;
1225 delete wordRect;
1226 #endif
1227 return ktp;
1230 void PDFGenerator::addSynopsisChildren( QDomNode * parent, QDomNode * parentDestination )
1232 // keep track of the current listViewItem
1233 QDomNode n = parent->firstChild();
1234 while( !n.isNull() )
1236 // convert the node to an element (sure it is)
1237 QDomElement e = n.toElement();
1239 // The name is the same
1240 QDomElement item = docSyn.createElement( e.tagName() );
1241 parentDestination->appendChild(item);
1243 if (!e.attribute("ExternalFileName").isNull()) item.setAttribute("ExternalFileName", e.attribute("ExternalFileName"));
1244 if (!e.attribute("DestinationName").isNull()) item.setAttribute("ViewportName", e.attribute("DestinationName"));
1245 if (!e.attribute("Destination").isNull())
1247 Okular::DocumentViewport vp;
1248 fillViewportFromLinkDestination( vp, Poppler::LinkDestination(e.attribute("Destination")) );
1249 item.setAttribute( "Viewport", vp.toString() );
1251 if (!e.attribute("Open").isNull()) item.setAttribute("Open", e.attribute("Open"));
1252 if (!e.attribute("DestinationURI").isNull()) item.setAttribute("URL", e.attribute("DestinationURI"));
1254 // descend recursively and advance to the next node
1255 if ( e.hasChildNodes() ) addSynopsisChildren( &n, & item );
1256 n = n.nextSibling();
1260 void PDFGenerator::addAnnotations( Poppler::Page * popplerPage, Okular::Page * page )
1262 QList<Poppler::Annotation*> popplerAnnotations = popplerPage->annotations();
1263 foreach(Poppler::Annotation *a, popplerAnnotations)
1265 a->window.width = (int)(page->width() * a->window.width);
1266 a->window.height = (int)(page->height() * a->window.height);
1267 //a->window.width = a->window.width < 200 ? 200 : a->window.width;
1268 // a->window.height = a->window.height < 120 ? 120 : a->window.height;
1269 // resize annotation's geometry to an icon
1270 // TODO okular geom.right = geom.left + 22.0 / page->width();
1271 // TODO okular geom.bottom = geom.top + 22.0 / page->height();
1273 QString szanno;
1274 QTextStream(&szanno)<<"PopplerAnnotation={author:"<<a->author
1275 <<", contents:"<<a->contents
1276 <<", uniqueName:"<<a->uniqueName
1277 <<", modifyDate:"<<a->modifyDate.toString("hh:mm:ss, dd.MM.yyyy")
1278 <<", creationDate:"<<a->creationDate.toString("hh:mm:ss, dd.MM.yyyy")
1279 <<", flags:"<<a->flags
1280 <<", boundary:"<<a->boundary.left()<<","<<a->boundary.top()<<","<<a->boundary.right()<<","<<a->boundary.bottom()
1281 <<", style.color:"<<a->style.color.name()
1282 <<", style.opacity:"<<a->style.opacity
1283 <<", style.width:"<<a->style.width
1284 <<", style.LineStyle:"<<a->style.style
1285 <<", style.xyCorners:"<<a->style.xCorners<<","<<a->style.yCorners
1286 <<", style.marks:"<<a->style.marks
1287 <<", style.spaces:"<<a->style.spaces
1288 <<", style.LineEffect:"<<a->style.effect
1289 <<", style.effectIntensity:"<<a->style.effectIntensity
1290 <<", window.flags:"<<a->window.flags
1291 <<", window.topLeft:"<<(a->window.topLeft.x())
1292 <<","<<(a->window.topLeft.y())
1293 <<", window.width,height:"<<a->window.width<<","<<a->window.height
1294 <<", window.title:"<<a->window.title
1295 <<", window.summary:"<<a->window.summary
1296 <<", window.text:"<<a->window.text;
1297 kDebug(PDFDebug) << "astario: " << szanno; */
1298 //TODO add annotations after poppler write feather is full suported
1299 bool doDelete = true;
1300 Okular::Annotation * newann = createAnnotationFromPopplerAnnotation( a, &doDelete );
1301 if (newann)
1303 // the Contents field has lines separated by \r
1304 QString contents = newann->contents();
1305 contents.replace( QLatin1Char( '\r' ), QLatin1Char( '\n' ) );
1306 newann->setContents( contents );
1307 // explicitly mark as external
1308 newann->setFlags( newann->flags() | Okular::Annotation::External );
1309 page->addAnnotation(newann);
1311 if ( doDelete )
1312 delete a;
1316 void PDFGenerator::addTransition( Poppler::Page * pdfPage, Okular::Page * page )
1317 // called on opening when MUTEX is not used
1319 Poppler::PageTransition *pdfTransition = pdfPage->transition();
1320 if ( !pdfTransition || pdfTransition->type() == Poppler::PageTransition::Replace )
1321 return;
1323 Okular::PageTransition *transition = new Okular::PageTransition();
1324 switch ( pdfTransition->type() ) {
1325 case Poppler::PageTransition::Replace:
1326 // won't get here, added to avoid warning
1327 break;
1328 case Poppler::PageTransition::Split:
1329 transition->setType( Okular::PageTransition::Split );
1330 break;
1331 case Poppler::PageTransition::Blinds:
1332 transition->setType( Okular::PageTransition::Blinds );
1333 break;
1334 case Poppler::PageTransition::Box:
1335 transition->setType( Okular::PageTransition::Box );
1336 break;
1337 case Poppler::PageTransition::Wipe:
1338 transition->setType( Okular::PageTransition::Wipe );
1339 break;
1340 case Poppler::PageTransition::Dissolve:
1341 transition->setType( Okular::PageTransition::Dissolve );
1342 break;
1343 case Poppler::PageTransition::Glitter:
1344 transition->setType( Okular::PageTransition::Glitter );
1345 break;
1346 case Poppler::PageTransition::Fly:
1347 transition->setType( Okular::PageTransition::Fly );
1348 break;
1349 case Poppler::PageTransition::Push:
1350 transition->setType( Okular::PageTransition::Push );
1351 break;
1352 case Poppler::PageTransition::Cover:
1353 transition->setType( Okular::PageTransition::Cover );
1354 break;
1355 case Poppler::PageTransition::Uncover:
1356 transition->setType( Okular::PageTransition::Uncover );
1357 break;
1358 case Poppler::PageTransition::Fade:
1359 transition->setType( Okular::PageTransition::Fade );
1360 break;
1363 transition->setDuration( pdfTransition->duration() );
1365 switch ( pdfTransition->alignment() ) {
1366 case Poppler::PageTransition::Horizontal:
1367 transition->setAlignment( Okular::PageTransition::Horizontal );
1368 break;
1369 case Poppler::PageTransition::Vertical:
1370 transition->setAlignment( Okular::PageTransition::Vertical );
1371 break;
1374 switch ( pdfTransition->direction() ) {
1375 case Poppler::PageTransition::Inward:
1376 transition->setDirection( Okular::PageTransition::Inward );
1377 break;
1378 case Poppler::PageTransition::Outward:
1379 transition->setDirection( Okular::PageTransition::Outward );
1380 break;
1383 transition->setAngle( pdfTransition->angle() );
1384 transition->setScale( pdfTransition->scale() );
1385 transition->setIsRectangular( pdfTransition->isRectangular() );
1387 page->setTransition( transition );
1390 void PDFGenerator::addFormFields( Poppler::Page * popplerPage, Okular::Page * page )
1392 QList<Poppler::FormField*> popplerFormFields = popplerPage->formFields();
1393 QLinkedList<Okular::FormField*> okularFormFields;
1394 foreach( Poppler::FormField *f, popplerFormFields )
1396 Okular::FormField * of = 0;
1397 switch ( f->type() )
1399 #ifdef HAVE_POPPLER_0_7
1400 case Poppler::FormField::FormButton:
1401 of = new PopplerFormFieldButton( static_cast<Poppler::FormFieldButton*>( f ) );
1402 break;
1403 #endif
1404 case Poppler::FormField::FormText:
1405 of = new PopplerFormFieldText( static_cast<Poppler::FormFieldText*>( f ) );
1406 break;
1407 case Poppler::FormField::FormChoice:
1408 of = new PopplerFormFieldChoice( static_cast<Poppler::FormFieldChoice*>( f ) );
1409 break;
1410 default: ;
1412 if ( of )
1413 // form field created, good - it will take care of the Poppler::FormField
1414 okularFormFields.append( of );
1415 else
1416 // no form field available - delete the Poppler::FormField
1417 delete f;
1419 if ( !okularFormFields.isEmpty() )
1420 page->setFormFields( okularFormFields );
1423 struct pdfsyncpoint
1425 QString file;
1426 qlonglong x;
1427 qlonglong y;
1428 int row;
1429 int column;
1430 int page;
1433 void PDFGenerator::loadPdfSync( const QString & filePath, QVector<Okular::Page*> & pagesVector )
1435 QFile f( filePath + QLatin1String( "sync" ) );
1436 if ( !f.open( QIODevice::ReadOnly ) )
1437 return;
1439 QTextStream ts( &f );
1440 // first row: core name of the pdf output - we skip it
1441 ts.readLine();
1442 // second row: version string, in the form 'Version %u'
1443 QString versionstr = ts.readLine();
1444 QRegExp versionre( "Version (\\d+)" );
1445 versionre.setCaseSensitivity( Qt::CaseInsensitive );
1446 if ( !versionre.exactMatch( versionstr ) )
1447 return;
1449 QHash<int, pdfsyncpoint> points;
1450 QString currentfile;
1451 int currentpage = -1;
1452 QRegExp newfilere( "\\(\\s*([^\\s]+)" );
1453 QRegExp linere( "l\\s+(\\d+)\\s+(\\d+)(\\s+(\\d+))?" );
1454 QRegExp pagere( "s\\s+(\\d+)" );
1455 QRegExp locre( "p\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)" );
1456 QRegExp locstarre( "p\\*\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)" );
1458 QString line;
1459 while ( !ts.atEnd() )
1461 line = ts.readLine();
1462 if ( line.startsWith( QLatin1Char( 'l' ) ) && linere.exactMatch( line ) )
1464 int id = linere.cap( 1 ).toInt();
1465 QHash<int, pdfsyncpoint>::const_iterator it = points.constFind( id );
1466 if ( it == points.constEnd() )
1468 pdfsyncpoint pt;
1469 pt.x = 0;
1470 pt.y = 0;
1471 pt.row = linere.cap( 2 ).toInt();
1472 pt.column = 0; // TODO
1473 pt.page = -1;
1474 pt.file = currentfile;
1475 points[ id ] = pt;
1478 else if ( line.startsWith( QLatin1Char( 's' ) ) && pagere.exactMatch( line ) )
1480 currentpage = pagere.cap( 1 ).toInt() - 1;
1482 else if ( line.startsWith( QLatin1String( "p*" ) ) && locstarre.exactMatch( line ) )
1484 // TODO
1485 kDebug(PDFDebug) << "PdfSync: 'p*' line ignored";
1487 else if ( line.startsWith( QLatin1Char( 'p' ) ) && locre.exactMatch( line ) )
1489 int id = locre.cap( 1 ).toInt();
1490 QHash<int, pdfsyncpoint>::iterator it = points.find( id );
1491 if ( it != points.end() )
1493 it->x = locre.cap( 2 ).toInt();
1494 it->y = locre.cap( 3 ).toInt();
1495 it->page = currentpage;
1498 else if ( line.startsWith( QLatin1Char( '(' ) ) && newfilere.exactMatch( line ) )
1500 QString newfile = newfilere.cap( 1 );
1501 if ( currentfile.isEmpty() )
1503 currentfile = newfile;
1505 else
1506 kDebug(PDFDebug) << "PdfSync: more than one file level:" << newfile;
1508 else if ( line == QLatin1String( ")" ) )
1510 if ( !currentfile.isEmpty() )
1512 currentfile.clear();
1514 else
1515 kDebug(PDFDebug) << "PdfSync: going one level down:" << currentfile;
1517 else
1518 kDebug(PDFDebug).nospace() << "PdfSync: unknown line format: '" << line << "'";
1522 QVector< QLinkedList< Okular::SourceRefObjectRect * > > refRects( pagesVector.size() );
1523 foreach ( const pdfsyncpoint& pt, points )
1525 // drop pdfsync points not completely valid
1526 if ( pt.page < 0 || pt.page >= pagesVector.size() )
1527 continue;
1529 // maginc numbers for TeX's RSU's (Ridiculously Small Units) conversion to pixels
1530 Okular::NormalizedPoint p(
1531 ( pt.x * 72.0 ) / ( 72.27 * 65536.0 * pagesVector[pt.page]->width() ),
1532 ( pt.y * 72.0 ) / ( 72.27 * 65536.0 * pagesVector[pt.page]->height() )
1534 QString file;
1535 if ( !pt.file.isEmpty() )
1537 file = pt.file;
1538 int dotpos = file.lastIndexOf( QLatin1Char( '.' ) );
1539 QString ext;
1540 if ( dotpos == -1 )
1541 file += QString::fromLatin1( ".tex" );
1543 else
1545 file = filePath;
1547 Okular::SourceReference * sourceRef = new Okular::SourceReference( file, pt.row, pt.column );
1548 refRects[ pt.page ].append( new Okular::SourceRefObjectRect( p, sourceRef ) );
1550 for ( int i = 0; i < refRects.size(); ++i )
1551 if ( !refRects.at(i).isEmpty() )
1552 pagesVector[i]->setSourceReferences( refRects.at(i) );
1555 QWidget* PDFGenerator::printConfigurationWidget() const
1557 if ( !pdfOptionsPage )
1559 const_cast<PDFGenerator*>(this)->pdfOptionsPage = new PDFOptionsPage();
1561 return pdfOptionsPage;
1564 bool PDFGenerator::supportsOption( SaveOption option ) const
1566 switch ( option )
1568 #ifdef HAVE_POPPLER_0_7
1569 case SaveChanges:
1570 return true;
1571 #endif
1572 default: ;
1574 return false;
1577 bool PDFGenerator::save( const QString &fileName, SaveOptions options )
1579 #ifdef HAVE_POPPLER_0_7
1580 Poppler::PDFConverter *pdfConv = pdfdoc->pdfConverter();
1582 pdfConv->setOutputFileName( fileName );
1583 if ( options & SaveChanges )
1584 pdfConv->setPDFOptions( pdfConv->pdfOptions() | Poppler::PDFConverter::WithChanges );
1586 QMutexLocker locker( userMutex() );
1587 bool success = pdfConv->convert();
1588 delete pdfConv;
1589 return success;
1590 #else
1591 Q_UNUSED( fileName )
1592 Q_UNUSED( options )
1593 return false;
1594 #endif
1597 void PDFGenerator::threadFinished()
1599 #if 0
1600 // check if thread is running (has to be stopped now)
1601 if ( generatorThread->running() )
1603 // if so, wait for effective thread termination
1604 if ( !generatorThread->wait( 9999 /*10s timeout*/ ) )
1606 kWarning(PDFDebug) << "PDFGenerator: thread sent 'data available' "
1607 << "signal but had problems ending.";
1608 return;
1611 #endif
1613 // 1. the mutex must be unlocked now
1614 bool isLocked = true;
1615 if (userMutex()->tryLock()) {
1616 userMutex()->unlock();
1617 isLocked = false;
1619 if ( isLocked )
1621 kWarning(PDFDebug) << "PDFGenerator: 'data available' but mutex still "
1622 << "held. Recovering.";
1623 // synchronize GUI thread (must not happen)
1624 userMutex()->lock();
1625 userMutex()->unlock();
1628 // 2. put thread's generated data into the Okular::Page
1629 Okular::PixmapRequest * request = generatorThread->request();
1630 QImage * outImage = generatorThread->takeImage();
1631 QList<Poppler::TextBox*> outText = generatorThread->takeText();
1632 QLinkedList< Okular::ObjectRect * > outRects = generatorThread->takeObjectRects();
1634 if ( !request->page()->isBoundingBoxKnown() )
1635 updatePageBoundingBox( request->page()->number(), Okular::Utils::imageBoundingBox( outImage ) );
1636 request->page()->setPixmap( request->id(), new QPixmap( QPixmap::fromImage( *outImage ) ) );
1637 delete outImage;
1638 if ( !outText.isEmpty() )
1640 Okular::TextPage *tp = abstractTextPage( outText, request->page()->height(),
1641 request->page()->width(),request->page()->orientation());
1642 request->page()->setTextPage( tp );
1643 qDeleteAll(outText);
1645 // notify the new generation
1646 signalTextGenerationDone( request->page(), tp );
1648 bool genObjectRects = !rectsGenerated.at( request->page()->number() );
1649 if (genObjectRects)
1651 request->page()->setObjectRects( outRects );
1652 rectsGenerated[ request->page()->number() ] = true;
1654 else
1655 qDeleteAll( outRects );
1657 // 3. tell generator that data has been taken
1658 generatorThread->endGeneration();
1660 // update ready state
1661 ready = true;
1662 // notify the new generation
1663 signalPixmapRequestDone( request );
1668 /** The PDF Pixmap Generator Thread **/
1670 struct PPGThreadPrivate
1672 // reference to main objects
1673 PDFGenerator * generator;
1674 Okular::PixmapRequest * currentRequest;
1676 // internal temp stored items. don't delete this.
1677 QImage * m_image;
1678 QList<Poppler::TextBox*> m_textList;
1679 QLinkedList< Okular::ObjectRect * > m_rects;
1680 bool m_rectsTaken;
1683 PDFPixmapGeneratorThread::PDFPixmapGeneratorThread( PDFGenerator * gen )
1684 : QThread(), d( new PPGThreadPrivate() )
1686 d->generator = gen;
1687 d->currentRequest = 0;
1688 d->m_image = 0;
1689 d->m_rectsTaken = true;
1692 PDFPixmapGeneratorThread::~PDFPixmapGeneratorThread()
1694 // delete internal objects if the class is deleted before the gui thread
1695 // takes the data
1696 delete d->m_image;
1697 qDeleteAll(d->m_textList);
1698 if ( !d->m_rectsTaken && d->m_rects.count() )
1700 qDeleteAll(d->m_rects);
1702 delete d->currentRequest;
1703 // delete internal storage structure
1704 delete d;
1707 void PDFPixmapGeneratorThread::startGeneration( Okular::PixmapRequest * request )
1709 #ifndef NDEBUG
1710 // check if a generation is already running
1711 if ( d->currentRequest )
1713 kDebug(PDFDebug) << "PDFPixmapGeneratorThread: requesting a pixmap "
1714 << "when another is being generated.";
1715 delete request;
1716 return;
1719 // check if the mutex is already held
1720 bool isLocked = true;
1721 if (d->generator->userMutex()->tryLock()) {
1722 d->generator->userMutex()->unlock();
1723 isLocked = false;
1725 if ( isLocked )
1727 kDebug(PDFDebug) << "PDFPixmapGeneratorThread: requesting a pixmap "
1728 << "with the mutex already held.";
1729 delete request;
1730 return;
1732 #endif
1733 // set generation parameters and run thread
1734 d->currentRequest = request;
1735 start( QThread::InheritPriority );
1738 void PDFPixmapGeneratorThread::endGeneration()
1740 #ifndef NDEBUG
1741 // check if a generation is already running
1742 if ( !d->currentRequest )
1744 kDebug(PDFDebug) << "PDFPixmapGeneratorThread: 'end generation' called "
1745 << "but generation was not started.";
1746 return;
1748 #endif
1749 // reset internal members preparing for a new generation
1750 d->currentRequest = 0;
1753 Okular::PixmapRequest *PDFPixmapGeneratorThread::request() const
1755 return d->currentRequest;
1758 QImage * PDFPixmapGeneratorThread::takeImage() const
1760 QImage * img = d->m_image;
1761 d->m_image = 0;
1762 return img;
1765 QList<Poppler::TextBox*> PDFPixmapGeneratorThread::takeText()
1767 QList<Poppler::TextBox*> tl = d->m_textList;
1768 d->m_textList.clear();
1769 return tl;
1772 QLinkedList< Okular::ObjectRect * > PDFPixmapGeneratorThread::takeObjectRects() const
1774 d->m_rectsTaken = true;
1775 QLinkedList< Okular::ObjectRect * > newrects = d->m_rects;
1776 d->m_rects.clear();
1777 return newrects;
1780 void PDFPixmapGeneratorThread::run()
1781 // perform contents generation, when the MUTEX is already LOCKED
1782 // @see PDFGenerator::generatePixmap( .. ) (and be aware to sync the code)
1784 // compute dpi used to get an image with desired width and height
1785 Okular::Page * page = d->currentRequest->page();
1786 int width = d->currentRequest->width(),
1787 height = d->currentRequest->height();
1788 double pageWidth = page->width(),
1789 pageHeight = page->height();
1791 if ( page->rotation() % 2 )
1792 qSwap( pageWidth, pageHeight );
1794 // setup Okular:: output device: text page is generated only if we are at 72dpi.
1795 // since we can pre-generate the TextPage at the right res.. why not?
1796 bool genTextPage = !page->hasTextPage() &&
1797 ( width == page->width() ) &&
1798 ( height == page->height() );
1800 // generate links rects only the first time
1801 bool genObjectRects = !d->generator->rectsGenerated.at( page->number() );
1803 // 0. LOCK s[tart locking XPDF thread unsafe classes]
1804 d->generator->userMutex()->lock();
1806 // 1. set OutputDev parameters and Generate contents
1807 Poppler::Page *pp = d->generator->pdfdoc->page( page->number() );
1808 const QSizeF &pageSizeF = pp->pageSizeF();
1810 double fakeDpiX = width * 72.0 / pageSizeF.width(),
1811 fakeDpiY = height * 72.0 / pageSizeF.height();
1813 // 2. grab data from the OutputDev and store it locally (note takeIMAGE)
1814 #ifndef NDEBUG
1815 if ( d->m_image )
1816 kDebug(PDFDebug) << "PDFPixmapGeneratorThread: previous image not taken";
1817 if ( !d->m_textList.isEmpty() )
1818 kDebug(PDFDebug) << "PDFPixmapGeneratorThread: previous text not taken";
1819 #endif
1820 d->m_image = new QImage( pp->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0 ) );
1822 if ( genObjectRects )
1824 d->m_rects = generateLinks(pp->links());
1826 else d->m_rectsTaken = false;
1828 if ( genTextPage )
1830 #ifdef HAVE_POPPLER_0_7
1831 d->m_textList = pp->textList();
1832 #else
1833 d->m_textList = pp->textList((Poppler::Page::Rotation)d->currentRequest->page()->orientation());
1834 #endif
1836 delete pp;
1838 // 3. [UNLOCK] mutex
1839 d->generator->userMutex()->unlock();
1841 // by ending the thread notifies the GUI thread that data is pending and can be read
1844 #include "generator_pdf.moc"
1846 /* kate: replace-tabs on; indent-width 4; */