1 /***************************************************************************
2 * Copyright (C) 2004-2008 by Albert Astals Cid <tsdgeos@terra.es> *
3 * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> *
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"
14 #include <qcheckbox.h>
21 #include <qtextstream.h>
22 #include <QtGui/QPrinter>
24 #include <kaboutdata.h>
26 #include <kmessagebox.h>
27 #include <kpassworddialog.h>
29 #include <ktemporaryfile.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
)
53 static const int PDFDebug
= 4710;
55 class PDFOptionsPage
: public QWidget
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
);
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() )
95 if (destination
.isChangeLeft() || destination
.isChangeTop())
97 // TODO remember to change this if we implement DPI and/or rotation
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
;
108 if ( dest->getChangeZoom() )
113 // implement the others cases
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
);
129 case Poppler::SoundObject::Signed
:
130 sound
->setSoundEncoding( Okular::Sound::Signed
);
132 case Poppler::SoundObject::muLaw
:
133 sound
->setSoundEncoding( Okular::Sound::muLaw
);
135 case Poppler::SoundObject::ALaw
:
136 sound
->setSoundEncoding( Okular::Sound::ALaw
);
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() );
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
;
165 Okular::DocumentViewport viewport
;
167 switch(popplerLink
->linkType())
169 case Poppler::Link::None
:
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
);
178 case Poppler::Link::Execute
:
179 popplerLinkExecute
= static_cast<const Poppler::LinkExecute
*>(popplerLink
);
180 link
= new Okular::ExecuteAction( popplerLinkExecute
->fileName(), popplerLinkExecute
->parameters() );
183 case Poppler::Link::Browse
:
184 popplerLinkBrowse
= static_cast<const Poppler::LinkBrowse
*>(popplerLink
);
185 link
= new Okular::BrowseAction( popplerLinkBrowse
->url() );
188 case Poppler::Link::Action
:
189 popplerLinkAction
= static_cast<const Poppler::LinkAction
*>(popplerLink
);
190 link
= new Okular::DocumentAction( (Okular::DocumentAction::DocumentActionType
)popplerLinkAction
->actionType() );
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
);
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() );
211 case Poppler::Link::Movie
:
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(),
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
);
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(
260 ki18n( "PDF Backend" ),
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" );
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
)
306 kDebug(PDFDebug
) << "PDFGenerator: multiple calls to loadDocument. Check it.";
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
);
320 bool PDFGenerator::loadDocumentFromData( const QByteArray
& fileData
, QVector
<Okular::Page
*> & pagesVector
)
325 kDebug(PDFDebug
) << "PDFGenerator: multiple calls to loadDocument. Check it.";
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;
341 while ( pdfdoc
&& pdfdoc
->isLocked() )
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();
350 if ( document() && document()->widget() )
351 parentwid
= document()->widget()->effectiveWinId();
352 wallet
= KWallet::Wallet::openWallet( walletName
, parentwid
);
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
;
368 // 1.B. if not retrieved, ask the password using the kde password dialog
369 if ( password
.isNull() )
373 prompt
= i18n( "Please insert the password to read the document:" );
375 prompt
= i18n( "Incorrect password. Try again:" );
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
);
384 password
= dlg
.password();
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() )
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
415 // the file has been loaded correctly
419 bool PDFGenerator::doCloseDocument()
421 // remove internal objects
425 userMutex()->unlock();
427 docSynopsisDirty
= true;
429 docEmbeddedFilesDirty
= true;
430 qDeleteAll(docEmbeddedFiles
);
431 docEmbeddedFiles
.clear();
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
++ )
444 Poppler::Page
* p
= pdfdoc
->page( i
);
445 QSize pSize
= p
->pageSize();
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)
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
);
466 page
->setPageAction( Okular::Page::Opening
, createLinkFromPopplerLink( tmplink
) );
469 tmplink
= p
->action( Poppler::Page::Closing
);
472 page
->setPageAction( Okular::Page::Closing
, createLinkFromPopplerLink( 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
;
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()
499 docInfo
.set( Okular::DocumentInfo::MimeType
, "application/pdf" );
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" ),
519 docInfo
.set( "optimization", pdfdoc
->isLinearized() ? i18n( "Yes" ) : i18n( "No" ),
522 docInfo
.set( Okular::DocumentInfo::Pages
, QString::number( pdfdoc
->numPages() ) );
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
546 docInfoDirty
= false;
551 const Okular::DocumentSynopsis
* PDFGenerator::generateDocumentSynopsis()
553 if ( !docSynopsisDirty
)
560 QDomDocument
*toc
= pdfdoc
->toc();
561 userMutex()->unlock();
565 addSynopsisChildren(toc
, &docSyn
);
568 docSynopsisDirty
= false;
572 static Okular::FontInfo::FontType
convertPopplerFontInfoTypeToOkularFontInfoType( Poppler::FontInfo::Type type
)
576 case Poppler::FontInfo::Type1
:
577 return Okular::FontInfo::Type1
;
579 case Poppler::FontInfo::Type1C
:
580 return Okular::FontInfo::Type1C
;
582 case Poppler::FontInfo::Type3
:
583 return Okular::FontInfo::Type3
;
585 case Poppler::FontInfo::TrueType
:
586 return Okular::FontInfo::TrueType
;
588 case Poppler::FontInfo::CIDType0
:
589 return Okular::FontInfo::CIDType0
;
591 case Poppler::FontInfo::CIDType0C
:
592 return Okular::FontInfo::CIDType0C
;
594 case Poppler::FontInfo::CIDTrueType
:
595 return Okular::FontInfo::CIDTrueType
;
597 case Poppler::FontInfo::Type1COT
:
598 return Okular::FontInfo::Type1COT
;
600 case Poppler::FontInfo::TrueTypeOT
:
601 return Okular::FontInfo::TrueTypeOT
;
603 case Poppler::FontInfo::CIDType0COT
:
604 return Okular::FontInfo::CIDType0COT
;
606 case Poppler::FontInfo::CIDTrueTypeOT
:
607 return Okular::FontInfo::CIDTrueTypeOT
;
609 case Poppler::FontInfo::unknown
:
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() )
622 ret
= Okular::FontInfo::EmbeddedSubset
;
626 ret
= Okular::FontInfo::FullyEmbedded
;
632 Okular::FontInfo::List
PDFGenerator::fontsForPage( int page
)
634 Okular::FontInfo::List list
;
636 if ( page
!= nextFontPage
)
639 QList
<Poppler::FontInfo
> fonts
;
641 pdfdoc
->scanForFonts( 1, &fonts
);
642 userMutex()->unlock();
644 foreach (const Poppler::FontInfo
&font
, fonts
)
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
);
655 nativeId
.setValue( font
);
656 of
.setNativeId( nativeId
);
667 const QList
<Okular::EmbeddedFile
*> *PDFGenerator::embeddedFiles() const
669 if (docEmbeddedFilesDirty
)
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
688 switch ( permission
)
690 case Okular::AllowModify
:
691 b
= pdfdoc
->okToChange();
693 case Okular::AllowCopy
:
694 b
= pdfdoc
->okToCopy();
696 case Okular::AllowPrint
:
697 b
= pdfdoc
->okToPrint();
699 case Okular::AllowNotes
:
700 b
= pdfdoc
->okToAddNotes();
702 case Okular::AllowFillForms
:
703 b
= pdfdoc
->okToFillForm();
710 bool PDFGenerator::canGeneratePixmap() const
715 void PDFGenerator::generatePixmap( Okular::PixmapRequest
* request
)
719 kDebug(PDFDebug
) << "calling generatePixmap() when not in READY state!";
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)
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
);
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]
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();
785 #ifdef HAVE_POPPLER_0_7
786 QList
<Poppler::TextBox
*> textList
= p
->textList();
788 QList
<Poppler::TextBox
*> textList
= p
->textList((Poppler::Page::Rotation
)request
->page()->orientation());
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
);
799 // update ready state
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() );
812 #ifdef HAVE_POPPLER_0_7
813 QList
<Poppler::TextBox
*> textList
= pp
->textList();
815 QList
<Poppler::TextBox
*> textList
= pp
->textList((Poppler::Page::Rotation
)page
->orientation());
817 userMutex()->unlock();
820 #ifdef HAVE_POPPLER_0_7
821 const double pageWidth
= page
->width();
822 const double pageHeight
= page
->height();
824 const double pageWidth
= ( page
->rotation() % 2 ? page
->height() : page
->width() );
825 const double pageHeight
= ( page
->rotation() % 2 ? page
->width() : page
->height() );
828 Okular::TextPage
*tp
= abstractTextPage(textList
, pageHeight
, pageWidth
, (Poppler::Page::Rotation
)page
->orientation());
829 qDeleteAll(textList
);
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
);
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
857 tf
.setSuffix( ".ps" );
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() );
868 #ifdef HAVE_POPPLER_0_7
869 tf
.setAutoRemove(false);
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
);
891 psConverter
->setOutputFileName(tempfilename
);
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
);
906 if (psConverter
->convert())
908 userMutex()->unlock();
910 #ifdef HAVE_POPPLER_0_7
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;
922 userMutex()->unlock();
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
)
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
;
945 Poppler::LinkDestination
*ld
= pdfdoc
->linkDestination( option
.toString() );
946 userMutex()->unlock();
949 fillViewportFromLinkDestination( viewport
, *ld
);
952 if ( viewport
.pageNumber
>= 0 )
953 return viewport
.toString();
955 else if ( key
== "DocumentTitle" )
958 QString title
= pdfdoc
->info( "Title" );
959 userMutex()->unlock();
962 else if ( key
== "OpenTOC" )
964 if ( pdfdoc
->pageMode() == Poppler::Document::UseOutlines
)
967 #ifdef HAVE_POPPLER_0_9
968 else if ( key
== "DocumentScripts" && option
.toString() == "JavaScript" )
970 return pdfdoc
->scripts();
976 bool PDFGenerator::reparseConfig()
981 bool somethingchanged
= false;
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() )
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); \
1014 oldhints |= hintflag; \
1016 oldhints &= ~(int)hintflag; \
1020 SET_HINT("GraphicsAntialias", true, Poppler::Document::Antialiasing
)
1021 SET_HINT("TextAntialias", true, Poppler::Document::TextAntialiasing
)
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
) );
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
) )
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();
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<<")";
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
;
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();
1110 wordBBox
.right()/width
,
1111 wordBBox
.bottom()/height
,
1112 nextWordBBox
.left()/width
,
1113 wordBBox
.top()/height
);
1117 Okular::NormalizedRect
* wordRect
= new Okular::NormalizedRect
;
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();
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
)
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
);
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
)
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
);
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
)
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
);
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
)
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
);
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();
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
);
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
);
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
)
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
1328 case Poppler::PageTransition::Split
:
1329 transition
->setType( Okular::PageTransition::Split
);
1331 case Poppler::PageTransition::Blinds
:
1332 transition
->setType( Okular::PageTransition::Blinds
);
1334 case Poppler::PageTransition::Box
:
1335 transition
->setType( Okular::PageTransition::Box
);
1337 case Poppler::PageTransition::Wipe
:
1338 transition
->setType( Okular::PageTransition::Wipe
);
1340 case Poppler::PageTransition::Dissolve
:
1341 transition
->setType( Okular::PageTransition::Dissolve
);
1343 case Poppler::PageTransition::Glitter
:
1344 transition
->setType( Okular::PageTransition::Glitter
);
1346 case Poppler::PageTransition::Fly
:
1347 transition
->setType( Okular::PageTransition::Fly
);
1349 case Poppler::PageTransition::Push
:
1350 transition
->setType( Okular::PageTransition::Push
);
1352 case Poppler::PageTransition::Cover
:
1353 transition
->setType( Okular::PageTransition::Cover
);
1355 case Poppler::PageTransition::Uncover
:
1356 transition
->setType( Okular::PageTransition::Uncover
);
1358 case Poppler::PageTransition::Fade
:
1359 transition
->setType( Okular::PageTransition::Fade
);
1363 transition
->setDuration( pdfTransition
->duration() );
1365 switch ( pdfTransition
->alignment() ) {
1366 case Poppler::PageTransition::Horizontal
:
1367 transition
->setAlignment( Okular::PageTransition::Horizontal
);
1369 case Poppler::PageTransition::Vertical
:
1370 transition
->setAlignment( Okular::PageTransition::Vertical
);
1374 switch ( pdfTransition
->direction() ) {
1375 case Poppler::PageTransition::Inward
:
1376 transition
->setDirection( Okular::PageTransition::Inward
);
1378 case Poppler::PageTransition::Outward
:
1379 transition
->setDirection( Okular::PageTransition::Outward
);
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
) );
1404 case Poppler::FormField::FormText
:
1405 of
= new PopplerFormFieldText( static_cast<Poppler::FormFieldText
*>( f
) );
1407 case Poppler::FormField::FormChoice
:
1408 of
= new PopplerFormFieldChoice( static_cast<Poppler::FormFieldChoice
*>( f
) );
1413 // form field created, good - it will take care of the Poppler::FormField
1414 okularFormFields
.append( of
);
1416 // no form field available - delete the Poppler::FormField
1419 if ( !okularFormFields
.isEmpty() )
1420 page
->setFormFields( okularFormFields
);
1433 void PDFGenerator::loadPdfSync( const QString
& filePath
, QVector
<Okular::Page
*> & pagesVector
)
1435 QFile
f( filePath
+ QLatin1String( "sync" ) );
1436 if ( !f
.open( QIODevice::ReadOnly
) )
1439 QTextStream
ts( &f
);
1440 // first row: core name of the pdf output - we skip it
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
) )
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+)" );
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() )
1471 pt
.row
= linere
.cap( 2 ).toInt();
1472 pt
.column
= 0; // TODO
1474 pt
.file
= currentfile
;
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
) )
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
;
1506 kDebug(PDFDebug
) << "PdfSync: more than one file level:" << newfile
;
1508 else if ( line
== QLatin1String( ")" ) )
1510 if ( !currentfile
.isEmpty() )
1512 currentfile
.clear();
1515 kDebug(PDFDebug
) << "PdfSync: going one level down:" << currentfile
;
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() )
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() )
1535 if ( !pt
.file
.isEmpty() )
1538 int dotpos
= file
.lastIndexOf( QLatin1Char( '.' ) );
1541 file
+= QString::fromLatin1( ".tex" );
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
1568 #ifdef HAVE_POPPLER_0_7
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();
1591 Q_UNUSED( fileName
)
1597 void PDFGenerator::threadFinished()
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.";
1613 // 1. the mutex must be unlocked now
1614 bool isLocked
= true;
1615 if (userMutex()->tryLock()) {
1616 userMutex()->unlock();
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
) ) );
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() );
1651 request
->page()->setObjectRects( outRects
);
1652 rectsGenerated
[ request
->page()->number() ] = true;
1655 qDeleteAll( outRects
);
1657 // 3. tell generator that data has been taken
1658 generatorThread
->endGeneration();
1660 // update ready state
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.
1678 QList
<Poppler::TextBox
*> m_textList
;
1679 QLinkedList
< Okular::ObjectRect
* > m_rects
;
1683 PDFPixmapGeneratorThread::PDFPixmapGeneratorThread( PDFGenerator
* gen
)
1684 : QThread(), d( new PPGThreadPrivate() )
1687 d
->currentRequest
= 0;
1689 d
->m_rectsTaken
= true;
1692 PDFPixmapGeneratorThread::~PDFPixmapGeneratorThread()
1694 // delete internal objects if the class is deleted before the gui thread
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
1707 void PDFPixmapGeneratorThread::startGeneration( Okular::PixmapRequest
* request
)
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.";
1719 // check if the mutex is already held
1720 bool isLocked
= true;
1721 if (d
->generator
->userMutex()->tryLock()) {
1722 d
->generator
->userMutex()->unlock();
1727 kDebug(PDFDebug
) << "PDFPixmapGeneratorThread: requesting a pixmap "
1728 << "with the mutex already held.";
1733 // set generation parameters and run thread
1734 d
->currentRequest
= request
;
1735 start( QThread::InheritPriority
);
1738 void PDFPixmapGeneratorThread::endGeneration()
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.";
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
;
1765 QList
<Poppler::TextBox
*> PDFPixmapGeneratorThread::takeText()
1767 QList
<Poppler::TextBox
*> tl
= d
->m_textList
;
1768 d
->m_textList
.clear();
1772 QLinkedList
< Okular::ObjectRect
* > PDFPixmapGeneratorThread::takeObjectRects() const
1774 d
->m_rectsTaken
= true;
1775 QLinkedList
< Okular::ObjectRect
* > newrects
= d
->m_rects
;
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)
1816 kDebug(PDFDebug
) << "PDFPixmapGeneratorThread: previous image not taken";
1817 if ( !d
->m_textList
.isEmpty() )
1818 kDebug(PDFDebug
) << "PDFPixmapGeneratorThread: previous text not taken";
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;
1830 #ifdef HAVE_POPPLER_0_7
1831 d
->m_textList
= pp
->textList();
1833 d
->m_textList
= pp
->textList((Poppler::Page::Rotation
)d
->currentRequest
->page()->orientation());
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; */