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