mirror of
https://invent.kde.org/graphics/okular
synced 2024-08-24 18:35:37 +00:00
![Tobias Koenig](/assets/img/avatar_default.png)
in presentation mode when the flag is available. svn path=/branches/kpdf_experiments/kdegraphics/kpdf/; revision=374668
740 lines
23 KiB
C++
740 lines
23 KiB
C++
/***************************************************************************
|
|
* Copyright (C) 2004 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. *
|
|
***************************************************************************/
|
|
|
|
// qt/kde includes
|
|
#include <qfile.h>
|
|
#include <qevent.h>
|
|
#include <qimage.h>
|
|
#include <qapplication.h>
|
|
#include <klocale.h>
|
|
#include <kpassdlg.h>
|
|
#include <kprinter.h>
|
|
#include <ktempfile.h>
|
|
#include <kmessagebox.h>
|
|
#include <kdebug.h>
|
|
|
|
// xpdf includes
|
|
#include "xpdf/PSOutputDev.h"
|
|
#include "xpdf/Link.h"
|
|
#include "xpdf/ErrorCodes.h"
|
|
#include "xpdf/UnicodeMap.h"
|
|
#include "xpdf/Outline.h"
|
|
#include "goo/GList.h"
|
|
|
|
// local includes
|
|
#include "generator_pdf.h"
|
|
#include "document.h" //for PAGEVIEW_ID
|
|
#include "page.h"
|
|
#include "settings.h"
|
|
#include "QOutputDev.h"
|
|
|
|
// id for DATA_READY ThreadedGenerator Event
|
|
#define TGE_DATAREADY_ID 6969
|
|
|
|
PDFGenerator::PDFGenerator()
|
|
: pdfdoc( 0 ), kpdfOutputDev( 0 ),
|
|
docInfoDirty( true ), docSynopsisDirty( true )
|
|
{
|
|
// generate kpdfOutputDev and cache page color
|
|
reparseConfig();
|
|
// generate the pixmapGeneratorThread
|
|
generatorThread = new PDFPixmapGeneratorThread( this );
|
|
}
|
|
|
|
PDFGenerator::~PDFGenerator()
|
|
{
|
|
// first stop and delete the generator thread
|
|
if ( generatorThread )
|
|
{
|
|
generatorThread->wait();
|
|
if ( !generatorThread->isReady() )
|
|
generatorThread->endGeneration();
|
|
delete generatorThread;
|
|
}
|
|
// remove requests in queue
|
|
QValueList< PixmapRequest * >::iterator rIt = requestsQueue.begin(), rEnd = requestsQueue.end();
|
|
for ( ; rIt != rEnd; rIt++ )
|
|
delete *rIt;
|
|
requestsQueue.clear();
|
|
// remove other internal objects
|
|
docLock.lock();
|
|
delete kpdfOutputDev;
|
|
delete pdfdoc;
|
|
docLock.unlock();
|
|
}
|
|
|
|
|
|
bool PDFGenerator::loadDocument( const QString & fileName, QValueVector<KPDFPage*> & pagesVector )
|
|
{
|
|
#ifndef NDEBUG
|
|
if ( pdfdoc )
|
|
{
|
|
kdDebug() << "PDFGenerator: multiple calls to loadDocument. Check it." << endl;
|
|
return false;
|
|
}
|
|
#endif
|
|
// create PDFDoc for the given file
|
|
pdfdoc = new PDFDoc( new GString( QFile::encodeName( fileName ) ), 0, 0 );
|
|
|
|
// if the file didn't open correctly it might be encrypted, so ask for a pass
|
|
bool firstTry = true;
|
|
while ( !pdfdoc->isOk() && pdfdoc->getErrorCode() == errEncrypted )
|
|
{
|
|
QString prompt;
|
|
if ( firstTry )
|
|
prompt = i18n( "Please insert the password to read the document:" );
|
|
else
|
|
prompt = i18n( "Incorrect password. Try again:" );
|
|
firstTry = false;
|
|
|
|
QCString pwd;
|
|
if ( KPasswordDialog::getPassword( pwd, prompt ) != KPasswordDialog::Accepted )
|
|
break;
|
|
else
|
|
{
|
|
GString * pwd2 = new GString( pwd.data() );
|
|
delete pdfdoc;
|
|
pdfdoc = new PDFDoc( new GString( QFile::encodeName( fileName ) ), pwd2, pwd2 );
|
|
delete pwd2;
|
|
}
|
|
}
|
|
if ( !pdfdoc->isOk() )
|
|
{
|
|
delete pdfdoc;
|
|
pdfdoc = 0;
|
|
return false;
|
|
}
|
|
|
|
// initialize output device for rendering current pdf
|
|
kpdfOutputDev->startDoc( pdfdoc->getXRef() );
|
|
|
|
// build Pages (currentPage was set -1 by deletePages)
|
|
uint pageCount = pdfdoc->getNumPages();
|
|
pagesVector.resize( pageCount );
|
|
for ( uint i = 0; i < pageCount ; i++ )
|
|
pagesVector[i] = new KPDFPage( i, pdfdoc->getPageWidth(i+1), pdfdoc->getPageHeight(i+1), pdfdoc->getPageRotate(i+1) );
|
|
|
|
// the file has been loaded correctly
|
|
return true;
|
|
}
|
|
|
|
|
|
const DocumentInfo * PDFGenerator::documentInfo()
|
|
{
|
|
if ( docInfoDirty )
|
|
{
|
|
docLock.lock();
|
|
// compile internal structure reading properties from PDFDoc
|
|
docInfo.author = getDocumentInfo("Author");
|
|
docInfo.creationDate = getDocumentDate("CreationDate");
|
|
docInfo.modificationDate = getDocumentDate("ModDate");
|
|
docInfo.creator = getDocumentInfo("Creator");
|
|
docInfo.keywords = getDocumentInfo("Keywords");
|
|
docInfo.producer = getDocumentInfo("Producer");
|
|
docInfo.subject = getDocumentInfo("Subject");
|
|
docInfo.title = getDocumentInfo("Title");
|
|
docInfo.mimeType = "application/pdf";
|
|
docInfo.format = "PDF";
|
|
if ( pdfdoc )
|
|
{
|
|
docInfo.formatVersion = QString::number( pdfdoc->getPDFVersion() );
|
|
docInfo.encryption = pdfdoc->isEncrypted() ? i18n( "Encrypted" ) : i18n( "Unencrypted" );
|
|
docInfo.optimization = pdfdoc->isLinearized() ? i18n( "Yes" ) : i18n( "No" );
|
|
}
|
|
else
|
|
{
|
|
docInfo.formatVersion = i18n( "Unknown Version" );
|
|
docInfo.encryption = i18n( "Unknown Encryption" );
|
|
docInfo.optimization = i18n( "Unknown Optimization" );
|
|
}
|
|
docLock.unlock();
|
|
|
|
// if pdfdoc is valid then we cached good info -> don't cache them again
|
|
if ( pdfdoc )
|
|
docInfoDirty = false;
|
|
}
|
|
return &docInfo;
|
|
}
|
|
|
|
const DocumentSynopsis * PDFGenerator::documentSynopsis()
|
|
{
|
|
if ( !docSynopsisDirty )
|
|
return &docSyn;
|
|
|
|
if ( !pdfdoc )
|
|
return NULL;
|
|
|
|
Outline * outline = pdfdoc->getOutline();
|
|
if ( !outline )
|
|
return NULL;
|
|
|
|
GList * items = outline->getItems();
|
|
if ( !items || items->getLength() < 1 )
|
|
return NULL;
|
|
|
|
docSyn = DocumentSynopsis();
|
|
if ( items->getLength() > 0 )
|
|
addSynopsisChildren( &docSyn, items );
|
|
|
|
docSynopsisDirty = false;
|
|
return &docSyn;
|
|
}
|
|
|
|
void PDFGenerator::addSynopsisChildren( QDomNode * parent, GList * items )
|
|
{
|
|
int numItems = items->getLength();
|
|
for ( int i = 0; i < numItems; ++i )
|
|
{
|
|
// iterate over every object in 'items'
|
|
OutlineItem * outlineItem = (OutlineItem *)items->get( i );
|
|
|
|
// create element using outlineItem's title as tagName
|
|
QString name;
|
|
Unicode * uniChar = outlineItem->getTitle();
|
|
for ( int i = 0; i < outlineItem->getTitleLength(); ++i )
|
|
name += uniChar[ i ];
|
|
if ( name.isEmpty() )
|
|
continue;
|
|
QDomElement item = docSyn.createElement( name );
|
|
parent->appendChild( item );
|
|
|
|
// set element's attributes
|
|
if ( kpdfOutputDev )
|
|
{
|
|
KPDFLink * link = kpdfOutputDev->generateLink( outlineItem->getAction() );
|
|
if ( link && link->linkType() == KPDFLink::Goto )
|
|
{
|
|
KPDFLinkGoto * linkGoto = static_cast< KPDFLinkGoto * >( link );
|
|
item.setAttribute( "Page", linkGoto->destViewport().page );
|
|
//TODO item.setAttribute( "Position", 0 );
|
|
}
|
|
delete link;
|
|
}
|
|
|
|
// recursively descend over children
|
|
outlineItem->open();
|
|
GList * children = outlineItem->getKids();
|
|
if ( children )
|
|
addSynopsisChildren( &item, children );
|
|
}
|
|
}
|
|
|
|
|
|
bool PDFGenerator::print( KPrinter& printer )
|
|
{
|
|
KTempFile tf( QString::null, ".ps" );
|
|
PSOutputDev *psOut = new PSOutputDev(tf.name().latin1(), pdfdoc->getXRef(), pdfdoc->getCatalog(), 1, pdfdoc->getNumPages(), psModePS);
|
|
|
|
if (psOut->isOk())
|
|
{
|
|
std::list<int> pages;
|
|
|
|
if (!printer.previewOnly())
|
|
{
|
|
QValueList<int> pageList = printer.pageList();
|
|
QValueList<int>::const_iterator it;
|
|
|
|
for(it = pageList.begin(); it != pageList.end(); ++it) pages.push_back(*it);
|
|
}
|
|
else
|
|
{
|
|
for(int i = 1; i <= pdfdoc->getNumPages(); i++) pages.push_back(i);
|
|
}
|
|
|
|
docLock.lock();
|
|
pdfdoc->displayPages(psOut, pages, 72, 72, 0, globalParams->getPSCrop(), gFalse);
|
|
docLock.unlock();
|
|
|
|
// needs to be here so that the file is flushed, do not merge with the one
|
|
// in the else
|
|
delete psOut;
|
|
printer.printFiles(tf.name(), true);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
delete psOut;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void PDFGenerator::requestPixmap( PixmapRequest * request, bool asyncronous )
|
|
{
|
|
//kdDebug() << "id: " << request->id << " is requesting pixmap for page " << request->page->number() << " [" << request->width << " x " << request->height << "]." << endl;
|
|
|
|
// check if request has already been satisfied
|
|
KPDFPage * page = request->page;
|
|
if ( page->hasPixmap( request->id, request->width, request->height ) )
|
|
return;
|
|
|
|
if ( !asyncronous )
|
|
{
|
|
// compute dpi used to get an image with desired width and height
|
|
double fakeDpiX = request->width * 72.0 / page->width(),
|
|
fakeDpiY = request->height * 72.0 / page->height();
|
|
|
|
// setup kpdf 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->hasSearchPage() && (request->width == page->width()) &&
|
|
(request->height == page->height());
|
|
// generate links and image rects if rendering pages on pageview
|
|
bool genRects = request->id == PAGEVIEW_ID;
|
|
|
|
// 0. LOCK [prevents thread from concurrent access]
|
|
docLock.lock();
|
|
|
|
// 1. Set OutputDev parameters and Generate contents
|
|
kpdfOutputDev->setParams( request->width, request->height, genTextPage, genRects, genRects );
|
|
pdfdoc->displayPage( kpdfOutputDev, page->number() + 1, fakeDpiX, fakeDpiY, 0, true, genRects );
|
|
|
|
// 2. Take data from outputdev and attach it to the Page
|
|
page->setPixmap( request->id, kpdfOutputDev->takePixmap() );
|
|
if ( genTextPage )
|
|
page->setSearchPage( kpdfOutputDev->takeTextPage() );
|
|
if ( genRects )
|
|
page->setRects( kpdfOutputDev->takeRects() );
|
|
|
|
// 3. UNLOCK [re-enables shared access]
|
|
docLock.unlock();
|
|
|
|
// notify the new generation
|
|
contentsChanged( request->id, request->pageNumber );
|
|
|
|
// free memory (since we took ownership of the request)
|
|
delete request;
|
|
}
|
|
else
|
|
{
|
|
// check if an overlapping request is already stacked. if so remove
|
|
// the duplicated requests from the stack.
|
|
QValueList< PixmapRequest * >::iterator rIt = requestsQueue.begin();
|
|
QValueList< PixmapRequest * >::iterator rEnd = requestsQueue.end();
|
|
while ( rIt != rEnd )
|
|
{
|
|
const PixmapRequest * stackedRequest = *rIt;
|
|
if ( stackedRequest->id == request->id && stackedRequest->page == request->page )
|
|
{
|
|
delete stackedRequest;
|
|
rIt = requestsQueue.remove( rIt );
|
|
}
|
|
else
|
|
++rIt;
|
|
}
|
|
|
|
// queue the pixmap request at the top of the stack
|
|
requestsQueue.push_back( request );
|
|
|
|
// try to pop an item out of the stack and start generation. if
|
|
// a generation is already being done, the item just added will
|
|
// be processed in the 'event handler' calling this function.
|
|
startNewThreadedGeneration();
|
|
}
|
|
}
|
|
|
|
void PDFGenerator::requestTextPage( KPDFPage * page )
|
|
{
|
|
// build a TextPage using the lightweight KPDFTextDev generator..
|
|
KPDFTextDev td;
|
|
docLock.lock();
|
|
pdfdoc->displayPage( &td, page->number()+1, 72, 72, 0, true, false );
|
|
// ..and attach it to the page
|
|
page->setSearchPage( td.takeTextPage() );
|
|
docLock.unlock();
|
|
}
|
|
|
|
bool PDFGenerator::reparseConfig()
|
|
{
|
|
// load paper color from Settings or use the white default color
|
|
QColor color = ( (Settings::renderMode() == Settings::EnumRenderMode::Paper ) &&
|
|
Settings::changeColors() ) ? Settings::paperColor() : Qt::white;
|
|
// if paper color is changed we have to rebuild every visible pixmap in addition
|
|
// to the outputDevice. it's the 'heaviest' case, other effect are just recoloring
|
|
// over the page rendered on 'standard' white background.
|
|
if ( color != paperColor || !kpdfOutputDev )
|
|
{
|
|
paperColor = color;
|
|
SplashColor splashCol;
|
|
splashCol.rgb8 = splashMakeRGB8( paperColor.red(), paperColor.green(), paperColor.blue() );
|
|
// rebuild the output device using the new paper color
|
|
docLock.lock();
|
|
delete kpdfOutputDev;
|
|
kpdfOutputDev = new KPDFOutputDev( this, splashCol );
|
|
if ( pdfdoc )
|
|
kpdfOutputDev->startDoc( pdfdoc->getXRef() );
|
|
docLock.unlock();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
QString PDFGenerator::getMetaData( const QString &key ) const
|
|
{
|
|
if ( key == "StartFullScreen" ) {
|
|
if ( pdfdoc->getCatalog()->getPageMode() == Catalog::FullScreen )
|
|
return "yes";
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
KPDFLinkGoto::Viewport PDFGenerator::decodeLinkViewport( GString * namedDest, LinkDest * dest )
|
|
// note: this function is called when processing a page, when the MUTEX is already LOCKED
|
|
{
|
|
KPDFLinkGoto::Viewport vp;
|
|
vp.page = -1;
|
|
|
|
if ( namedDest && !dest )
|
|
dest = pdfdoc->findDest( namedDest );
|
|
|
|
if ( !dest || !dest->isOk() )
|
|
return vp;
|
|
|
|
// get destination page number
|
|
if ( !dest->isPageRef() )
|
|
vp.page = dest->getPageNum() - 1;
|
|
else
|
|
{
|
|
Ref ref = dest->getPageRef();
|
|
vp.page = pdfdoc->findPage( ref.num, ref.gen ) - 1;
|
|
}
|
|
|
|
// get destination position (fill remaining Viewport fields)
|
|
switch ( dest->getKind() )
|
|
{
|
|
case destXYZ:
|
|
/* OD -> cvtUserToDev( dest->getLeft(), dest->getTop(), &X, &Y );
|
|
if ( dest->getChangeLeft() )
|
|
make hor change
|
|
if ( dest->getChangeTop() )
|
|
make ver change
|
|
if ( dest->getChangeZoom() )
|
|
make zoom change
|
|
*/ break;
|
|
|
|
case destFit:
|
|
case destFitB:
|
|
vp.fitWidth = true;
|
|
vp.fitHeight = true;
|
|
break;
|
|
|
|
case destFitH:
|
|
case destFitBH:
|
|
// read top, fit Width
|
|
vp.fitWidth = true;
|
|
break;
|
|
|
|
case destFitV:
|
|
case destFitBV:
|
|
// read left, fit Height
|
|
vp.fitHeight = true;
|
|
break;
|
|
|
|
case destFitR:
|
|
// read and fit left,bottom,right,top
|
|
break;
|
|
}
|
|
|
|
return vp;
|
|
}
|
|
|
|
|
|
QString PDFGenerator::getDocumentInfo( const QString & data ) const
|
|
// note: this function is called by DocumentInfo gen, when the MUTEX is already LOCKED
|
|
{
|
|
// [Albert] Code adapted from pdfinfo.cc on xpdf
|
|
Object info;
|
|
if ( !pdfdoc )
|
|
return i18n( "Unknown" );
|
|
|
|
pdfdoc->getDocInfo( &info );
|
|
if ( !info.isDict() )
|
|
return i18n( "Unknown" );
|
|
|
|
QCString result;
|
|
Object obj;
|
|
GString *s1;
|
|
GBool isUnicode;
|
|
Unicode u;
|
|
char buf[8];
|
|
int i, n;
|
|
Dict *infoDict = info.getDict();
|
|
UnicodeMap *uMap = globalParams->getTextEncoding();
|
|
|
|
if ( !uMap )
|
|
return i18n( "Unknown" );
|
|
|
|
if ( infoDict->lookup( data.latin1(), &obj )->isString() )
|
|
{
|
|
s1 = obj.getString();
|
|
if ( ( s1->getChar(0) & 0xff ) == 0xfe && ( s1->getChar(1) & 0xff ) == 0xff )
|
|
{
|
|
isUnicode = gTrue;
|
|
i = 2;
|
|
}
|
|
else
|
|
{
|
|
isUnicode = gFalse;
|
|
i = 0;
|
|
}
|
|
while ( i < obj.getString()->getLength() )
|
|
{
|
|
if ( isUnicode )
|
|
{
|
|
u = ( ( s1->getChar(i) & 0xff ) << 8 ) | ( s1->getChar(i+1) & 0xff );
|
|
i += 2;
|
|
}
|
|
else
|
|
{
|
|
u = s1->getChar(i) & 0xff;
|
|
++i;
|
|
}
|
|
n = uMap->mapUnicode( u, buf, sizeof( buf ) );
|
|
result += QCString( buf, n + 1 );
|
|
}
|
|
obj.free();
|
|
return result;
|
|
}
|
|
obj.free();
|
|
return i18n( "Unknown" );
|
|
}
|
|
|
|
QString PDFGenerator::getDocumentDate( const QString & data ) const
|
|
// note: this function is called by DocumentInfo gen, when the MUTEX is already LOCKED
|
|
{
|
|
// [Albert] Code adapted from pdfinfo.cc on xpdf
|
|
Object info;
|
|
if ( !pdfdoc )
|
|
return i18n( "Unknown Date" );
|
|
|
|
pdfdoc->getDocInfo( &info );
|
|
if ( !info.isDict() )
|
|
return i18n( "Unknown Date" );
|
|
|
|
Object obj;
|
|
char *s;
|
|
int year, mon, day, hour, min, sec;
|
|
Dict *infoDict = info.getDict();
|
|
UnicodeMap *uMap = globalParams->getTextEncoding();
|
|
|
|
if ( !uMap )
|
|
return i18n( "Unknown Date" );
|
|
|
|
if ( infoDict->lookup( data.latin1(), &obj )->isString() )
|
|
{
|
|
s = obj.getString()->getCString();
|
|
if ( s[0] == 'D' && s[1] == ':' )
|
|
s += 2;
|
|
if ( sscanf( s, "%4d%2d%2d%2d%2d%2d", &year, &mon, &day, &hour, &min, &sec ) == 6 )
|
|
{
|
|
QDate d( year, mon, day ); //CHECK: it was mon-1, Jan->0 (??)
|
|
QTime t( hour, min, sec );
|
|
if ( d.isValid() && t.isValid() )
|
|
return KGlobal::locale()->formatDateTime( QDateTime(d, t), false, true );
|
|
else
|
|
return s;
|
|
}
|
|
else
|
|
return s;
|
|
fputc( '\n', stdout );
|
|
obj.free();
|
|
//return result;
|
|
}
|
|
obj.free();
|
|
return i18n( "Unknown Date" );
|
|
}
|
|
|
|
void PDFGenerator::startNewThreadedGeneration()
|
|
{
|
|
// exit if already processing a request or queue is empty
|
|
if ( !generatorThread->isReady() || requestsQueue.isEmpty() )
|
|
return;
|
|
|
|
// if any requests are already accomplished, pop them from the stack
|
|
PixmapRequest * req = requestsQueue.last();
|
|
requestsQueue.pop_back();
|
|
if ( req->page->hasPixmap( req->id, req->width, req->height ) )
|
|
return startNewThreadedGeneration();
|
|
|
|
// start generator thread with given PixmapRequest
|
|
generatorThread->startGeneration( req );
|
|
}
|
|
|
|
void PDFGenerator::customEvent( QCustomEvent * event )
|
|
{
|
|
// catch generator responses only
|
|
if ( event->type() != TGE_DATAREADY_ID )
|
|
return;
|
|
|
|
// put the requested data to the KPDFPage
|
|
const PixmapRequest * request = generatorThread->currentRequest();
|
|
int reqId = request->id,
|
|
reqPage = request->pageNumber;
|
|
|
|
QImage * outImage = generatorThread->takeImage();
|
|
TextPage * outTextPage = generatorThread->takeTextPage();
|
|
QValueList< KPDFPageRect * > outRects = generatorThread->takeRects();
|
|
|
|
// QImage -> QPixmap
|
|
// attach data to the Page
|
|
request->page->setPixmap( request->id, new QPixmap( *outImage ) );
|
|
delete outImage;
|
|
if ( outTextPage )
|
|
request->page->setSearchPage( outTextPage );
|
|
if ( !outRects.isEmpty() )
|
|
request->page->setRects( outRects );
|
|
|
|
// tell generator that contents has been taken and can unlock mutex
|
|
// note: request will be deleted so we can't access it from here on
|
|
generatorThread->endGeneration();
|
|
|
|
// notify the observer about changes in the page
|
|
contentsChanged( reqId, reqPage );
|
|
|
|
// proceed with generation
|
|
startNewThreadedGeneration();
|
|
}
|
|
|
|
|
|
|
|
/** The PDF Pixmap Generator Thread **/
|
|
|
|
struct PPGThreadPrivate
|
|
{
|
|
// reference to main objects
|
|
PDFGenerator * generator;
|
|
PixmapRequest * currentRequest;
|
|
|
|
// internal temp stored items. don't delete this.
|
|
QImage * m_image;
|
|
TextPage * m_textPage;
|
|
QValueList< KPDFPageRect * > m_rects;
|
|
};
|
|
|
|
PDFPixmapGeneratorThread::PDFPixmapGeneratorThread( PDFGenerator * gen )
|
|
: d( new PPGThreadPrivate() )
|
|
{
|
|
d->generator = gen;
|
|
d->currentRequest = 0;
|
|
}
|
|
|
|
PDFPixmapGeneratorThread::~PDFPixmapGeneratorThread()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void PDFPixmapGeneratorThread::startGeneration( PixmapRequest * request )
|
|
{
|
|
#ifndef NDEBUG
|
|
// check if a generation is already running
|
|
if ( d->currentRequest )
|
|
{
|
|
kdDebug() << "PDFPixmapGeneratorThread: requesting a pixmap "
|
|
<< "when another is being generated." << endl;
|
|
delete request;
|
|
return;
|
|
}
|
|
|
|
// check if the mutex is already held
|
|
if ( d->generator->docLock.locked() )
|
|
{
|
|
kdDebug() << "PDFPixmapGeneratorThread: requesting a pixmap "
|
|
<< "with the mutex already held." << endl;
|
|
return;
|
|
}
|
|
#endif
|
|
// set generation parameters and run thread
|
|
d->currentRequest = request;
|
|
start( QThread::LowestPriority );
|
|
}
|
|
|
|
const PixmapRequest * PDFPixmapGeneratorThread::currentRequest() const
|
|
{
|
|
return d->currentRequest;
|
|
}
|
|
|
|
bool PDFPixmapGeneratorThread::isReady() const
|
|
{
|
|
return !d->currentRequest;
|
|
}
|
|
|
|
void PDFPixmapGeneratorThread::endGeneration()
|
|
{
|
|
#ifndef NDEBUG
|
|
// check if a generation is already running
|
|
if ( !d->currentRequest )
|
|
{
|
|
kdDebug() << "PDFPixmapGeneratorThread: 'end generation' called "
|
|
<< "but generation was not started." << endl;
|
|
return;
|
|
}
|
|
#endif
|
|
// reset internal members preparing for a new generation
|
|
delete d->currentRequest;
|
|
d->currentRequest = 0;
|
|
}
|
|
|
|
QImage * PDFPixmapGeneratorThread::takeImage() const
|
|
{
|
|
return d->m_image;
|
|
}
|
|
|
|
TextPage * PDFPixmapGeneratorThread::takeTextPage() const
|
|
{
|
|
return d->m_textPage;
|
|
}
|
|
|
|
QValueList< KPDFPageRect * > PDFPixmapGeneratorThread::takeRects() const
|
|
{
|
|
return d->m_rects;
|
|
}
|
|
|
|
void PDFPixmapGeneratorThread::run()
|
|
// perform contents generation, when the MUTEX is already LOCKED
|
|
// @see PDFGenerator::requestPixmap( .. ) (and be aware to sync the code)
|
|
{
|
|
// compute dpi used to get an image with desired width and height
|
|
KPDFPage * page = d->currentRequest->page;
|
|
int width = d->currentRequest->width,
|
|
height = d->currentRequest->height;
|
|
double fakeDpiX = width * 72.0 / page->width(),
|
|
fakeDpiY = height * 72.0 / page->height();
|
|
|
|
// setup kpdf 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->hasSearchPage() &&
|
|
( width == page->width() ) &&
|
|
( height == page->height() );
|
|
|
|
// generate links and image rects if rendering pages on pageview
|
|
bool genPageRects = d->currentRequest->id == PAGEVIEW_ID;
|
|
|
|
// 0. LOCK s[tart locking XPDF thread unsafe classes]
|
|
d->generator->docLock.lock();
|
|
|
|
// 1. set OutputDev parameters and Generate contents
|
|
d->generator->kpdfOutputDev->setParams( width, height, genTextPage,
|
|
genPageRects, genPageRects, TRUE /*thread safety*/ );
|
|
d->generator->pdfdoc->displayPage( d->generator->kpdfOutputDev, page->number() + 1,
|
|
fakeDpiX, fakeDpiY, 0, true, genPageRects );
|
|
|
|
// 2. grab data from the OutputDev and store it locally
|
|
d->m_image = d->generator->kpdfOutputDev->takeImage();
|
|
d->m_textPage = d->generator->kpdfOutputDev->takeTextPage();
|
|
d->m_rects = d->generator->kpdfOutputDev->takeRects();
|
|
|
|
//d->generator->kpdfOutputDev->freeInternalBitmap();
|
|
|
|
// 3. [UNLOCK] mutex
|
|
d->generator->docLock.unlock();
|
|
|
|
// notify the GUI thread that data is pending and can be read
|
|
QApplication::postEvent( d->generator, new QCustomEvent( TGE_DATAREADY_ID ) );
|
|
}
|