Commit 1051d4b9 authored by Enrico Ros's avatar Enrico Ros

FixCrash 3/3: More decoupling between PDFGenerator and KPDFOutputDevice

classes (moved code between classes). Threading stuff fixed. Add console
debug output if something threading related goes wrong.
BUG: 96274

svn path=/trunk/kdegraphics/kpdf/; revision=376087
parent e06c3f4c
......@@ -554,26 +554,29 @@ void KPDFDocument::mCleanupMemory( int observerId )
if ( clipValue > memoryToFree )
memoryToFree = clipValue;
if ( memoryToFree <= 0 )
return;
// free memory. remove older data until we free enough memory
int freed = 0;
if ( memoryToFree > 0 )
QMap< int, int >::iterator it = obs->pageMemory.begin(), end = obs->pageMemory.end();
while ( (it != end) && (memoryToFree > 0) )
{
QMap< int, int >::iterator it = obs->pageMemory.begin(), end = obs->pageMemory.end();
while ( (it != end) && (memoryToFree > 0) )
int pageNumber = it.key();
if ( obs->observer->canUnloadPixmap( pageNumber ) )
{
int freeNumber = it.key();
if ( obs->observer->canUnloadPixmap( freeNumber ) )
{
// update mem stats
memoryToFree -= it.data();
obs->totalMemory -= it.data();
obs->pageMemory.remove( it );
// delete pixmap
pages_vector[ freeNumber ]->deletePixmap( observerId );
freed++;
}
// copy iterator to avoid invalidation on map->remove( it )
QMap< int, int >::iterator i( it );
++it;
// update mem stats
memoryToFree -= i.data();
obs->totalMemory -= i.data();
obs->pageMemory.remove( i );
// delete pixmap
pages_vector[ pageNumber ]->deletePixmap( observerId );
freed++;
} else
++it;
}
}
//kdDebug() << "Id:" << observerId << " [" << obs->totalMemory << "kB] Removed " << freed << " pages. " << obs->pageMemory.count() << " pages kept in memory." << endl;
}
......
......@@ -114,12 +114,13 @@ bool PDFGenerator::loadDocument( const QString & fileName, QValueVector<KPDFPage
}
// initialize output device for rendering current pdf
kpdfOutputDev->startDoc( pdfdoc->getXRef() );
kpdfOutputDev->initDevice( pdfdoc );
// build Pages (currentPage was set -1 by deletePages)
uint pageCount = pdfdoc->getNumPages();
pagesVector.resize( pageCount );
for ( uint i = 0; i < pageCount ; i++ ) {
for ( uint i = 0; i < pageCount ; i++ )
{
KPDFPage * page = new KPDFPage( i, pdfdoc->getPageWidth(i+1),
pdfdoc->getPageHeight(i+1),
pdfdoc->getPageRotate(i+1) );
......@@ -466,12 +467,12 @@ bool PDFGenerator::reparseConfig()
paperColor = color;
SplashColor splashCol;
splashCol.rgb8 = splashMakeRGB8( paperColor.red(), paperColor.green(), paperColor.blue() );
// rebuild the output device using the new paper color
// rebuild the output device using the new paper color and initialize it
docLock.lock();
delete kpdfOutputDev;
kpdfOutputDev = new KPDFOutputDev( this, splashCol );
kpdfOutputDev = new KPDFOutputDev( splashCol );
if ( pdfdoc )
kpdfOutputDev->startDoc( pdfdoc->getXRef() );
kpdfOutputDev->initDevice( pdfdoc );
docLock.unlock();
return true;
}
......@@ -512,69 +513,8 @@ QString PDFGenerator::getMetaData( const QString & key, const QString & option )
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
// note: MUTEX is LOCKED while calling this
{
// [Albert] Code adapted from pdfinfo.cc on xpdf
Object info;
......@@ -634,13 +574,13 @@ QString PDFGenerator::getDocumentInfo( const QString & data ) const
}
QString PDFGenerator::getDocumentDate( const QString & data ) const
// note: this function is called by DocumentInfo gen, when the MUTEX is already LOCKED
// note: MUTEX is LOCKED while calling this
{
// [Albert] Code adapted from pdfinfo.cc on xpdf
Object info;
if ( !pdfdoc )
return i18n( "Unknown Date" );
Object info;
pdfdoc->getDocInfo( &info );
if ( !info.isDict() )
return i18n( "Unknown Date" );
......@@ -688,7 +628,11 @@ void PDFGenerator::startNewThreadedGeneration()
PixmapRequest * req = requestsQueue.last();
requestsQueue.pop_back();
if ( req->page->hasPixmap( req->id, req->width, req->height ) )
return startNewThreadedGeneration();
{
delete req;
startNewThreadedGeneration();
return;
}
// start generator thread with given PixmapRequest
generatorThread->startGeneration( req );
......@@ -696,11 +640,35 @@ void PDFGenerator::startNewThreadedGeneration()
void PDFGenerator::customEvent( QCustomEvent * event )
{
// catch generator responses only
// catch generator 'ready events' only
if ( event->type() != TGE_DATAREADY_ID )
return;
// put the requested data to the KPDFPage
#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*/ ) )
{
kdWarning() << "PDFGenerator: thread sent 'data available' "
<< "signal but had problems ending." << endl;
return;
}
}
#endif
// the mutex must be unlocked now
if ( docLock.locked() )
{
kdWarning() << "PDFGenerator: 'data available' but mutex still "
<< "held. Recovering." << endl;
// syncronize GUI thread (must not happen)
docLock.lock();
docLock.unlock();
}
// put thread's generated data into the KPDFPage
const PixmapRequest * request = generatorThread->currentRequest();
int reqId = request->id,
reqPage = request->pageNumber;
......@@ -718,7 +686,7 @@ void PDFGenerator::customEvent( QCustomEvent * event )
if ( !outRects.isEmpty() )
request->page->setRects( outRects );
// tell generator that contents has been taken and can unlock mutex
// tell generator that generation is ended and to free its data
// note: request will be deleted so we can't access it from here on
generatorThread->endGeneration();
......@@ -774,12 +742,15 @@ void PDFPixmapGeneratorThread::startGeneration( PixmapRequest * request )
{
kdDebug() << "PDFPixmapGeneratorThread: requesting a pixmap "
<< "with the mutex already held." << endl;
delete request;
return;
}
#endif
// set generation parameters and run thread
d->currentRequest = request;
start( QThread::LowestPriority );
// TODO: use a different priority for different types of requests
// TODO: embed priority in requests
start( request->id == PAGEVIEW_ID ? QThread::InheritPriority : QThread::LowPriority );
}
const PixmapRequest * PDFPixmapGeneratorThread::currentRequest() const
......@@ -837,8 +808,8 @@ void PDFPixmapGeneratorThread::run()
// 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() );
( width == page->width() ) &&
( height == page->height() );
// generate links and image rects if rendering pages on pageview
bool genPageRects = d->currentRequest->id == PAGEVIEW_ID;
......@@ -857,8 +828,6 @@ void PDFPixmapGeneratorThread::run()
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();
......
......@@ -63,9 +63,6 @@ class PDFGenerator : public Generator
QString getMetaData( const QString & key, const QString & option );
// used by the KPDFOutputDev child
KPDFLinkGoto::Viewport decodeLinkViewport( class GString * namedDest, class LinkDest * dest );
private:
// friend class to access private document related variables
friend class PDFPixmapGeneratorThread;
......
......@@ -34,10 +34,11 @@
//NOTE: XPDF/Splash *implementation dependant* code is marked with '###'
//BEGIN KPDFOutputDev
KPDFOutputDev::KPDFOutputDev( PDFGenerator * parent, SplashColor paperColor )
/** KPDFOutputDev implementation **/
KPDFOutputDev::KPDFOutputDev( SplashColor paperColor )
: SplashOutputDev( splashModeRGB8, false, paperColor ),
m_pixmap( 0 ), m_image( 0 ), m_generator( parent ), m_text( 0 )
m_doc( 0 ), m_pixmap( 0 ), m_image( 0 ), m_text( 0 )
{
}
......@@ -46,6 +47,12 @@ KPDFOutputDev::~KPDFOutputDev()
clear();
}
void KPDFOutputDev::initDevice( PDFDoc * pdfDoc )
{
m_doc = pdfDoc;
startDoc( pdfDoc->getXRef() );
}
void KPDFOutputDev::setParams( int width, int height, bool genT, bool genL, bool genI, bool safe )
{
clear();
......@@ -62,89 +69,6 @@ void KPDFOutputDev::setParams( int width, int height, bool genT, bool genL, bool
m_text = new TextPage( gFalse );
}
KPDFLink * KPDFOutputDev::generateLink( LinkAction * a )
{
KPDFLink * link = NULL;
if ( a ) switch ( a->getKind() )
{
case actionGoTo:
{
LinkGoTo * g = (LinkGoTo *) a;
// ceate link: no ext file, namedDest, object pointer
link = new KPDFLinkGoto( QString::null, m_generator->decodeLinkViewport( g->getNamedDest(), g->getDest() ) );
}
break;
case actionGoToR:
{
LinkGoToR * g = (LinkGoToR *) a;
// copy link file
const char * fileName = g->getFileName()->getCString();
// ceate link: fileName, namedDest, object pointer
link = new KPDFLinkGoto( (QString)fileName, m_generator->decodeLinkViewport( g->getNamedDest(), g->getDest() ) );
}
break;
case actionLaunch:
{
LinkLaunch * e = (LinkLaunch *)a;
GString * p = e->getParams();
link = new KPDFLinkExecute( e->getFileName()->getCString(), p ? p->getCString() : 0 );
}
break;
case actionNamed:
{
const char * name = ((LinkNamed *)a)->getName()->getCString();
if ( !strcmp( name, "NextPage" ) )
link = new KPDFLinkAction( KPDFLinkAction::PageNext );
else if ( !strcmp( name, "PrevPage" ) )
link = new KPDFLinkAction( KPDFLinkAction::PagePrev );
else if ( !strcmp( name, "FirstPage" ) )
link = new KPDFLinkAction( KPDFLinkAction::PageFirst );
else if ( !strcmp( name, "LastPage" ) )
link = new KPDFLinkAction( KPDFLinkAction::PageLast );
else if ( !strcmp( name, "GoBack" ) )
link = new KPDFLinkAction( KPDFLinkAction::HistoryBack );
else if ( !strcmp( name, "GoForward" ) )
link = new KPDFLinkAction( KPDFLinkAction::HistoryForward );
else if ( !strcmp( name, "Quit" ) )
link = new KPDFLinkAction( KPDFLinkAction::Quit );
else if ( !strcmp( name, "GoToPage" ) )
link = new KPDFLinkAction( KPDFLinkAction::GoToPage );
else if ( !strcmp( name, "Find" ) )
link = new KPDFLinkAction( KPDFLinkAction::Find );
else
kdDebug() << "Unknown named action: '" << name << "'" << endl;
}
break;
case actionURI:
link = new KPDFLinkBrowse( ((LinkURI *)a)->getURI()->getCString() );
break;
case actionMovie:
/* { TODO this
m_type = Movie;
LinkMovie * m = (LinkMovie *) a;
// copy Movie parameters (2 IDs and a const char *)
Ref * r = m->getAnnotRef();
m_refNum = r->num;
m_refGen = r->gen;
copyString( m_uri, m->getTitle()->getCString() );
}
*/ break;
case actionUnknown:
kdDebug() << "Unknown link." << endl;
break;
}
// link may be zero at that point
return link;
}
QPixmap * KPDFOutputDev::takePixmap()
{
QPixmap * pix = m_pixmap;
......@@ -175,7 +99,7 @@ QValueList< KPDFPageRect * > KPDFOutputDev::takeRects()
return rectsCopy;
}
//BEGIN - OutputDev hooked calls
void KPDFOutputDev::startPage( int pageNum, GfxState *state )
{
if ( m_generateText )
......@@ -217,7 +141,7 @@ void KPDFOutputDev::endPage()
m_pixmap = new QPixmap( *img );
}
// destroy the shared descriptor and ### unload underlying xpdf bitmap
// destroy the shared descriptor and (###) unload underlying xpdf bitmap
delete img;
SplashOutputDev::startPage( 0, NULL );
}
......@@ -303,7 +227,9 @@ void KPDFOutputDev::drawImage( GfxState *state, Object *ref, Stream *str,
}
SplashOutputDev::drawImage( state, ref, str, _width, _height, colorMap, maskColors, inlineImg );
}
//END - OutputDev hooked calls
//BEGIN - private helpers
void KPDFOutputDev::clear()
{
// delete rects
......@@ -333,10 +259,154 @@ void KPDFOutputDev::clear()
m_text = 0;
}
}
//END KPDFOutputDev
KPDFLink * KPDFOutputDev::generateLink( LinkAction * a )
// note: this function is called when processing a page, when the MUTEX is already LOCKED
{
KPDFLink * link = NULL;
if ( a ) switch ( a->getKind() )
{
case actionGoTo:
{
LinkGoTo * g = (LinkGoTo *) a;
// ceate link: no ext file, namedDest, object pointer
link = new KPDFLinkGoto( QString::null, decodeViewport( g->getNamedDest(), g->getDest() ) );
}
break;
case actionGoToR:
{
LinkGoToR * g = (LinkGoToR *) a;
// copy link file
const char * fileName = g->getFileName()->getCString();
// ceate link: fileName, namedDest, object pointer
link = new KPDFLinkGoto( (QString)fileName, decodeViewport( g->getNamedDest(), g->getDest() ) );
}
break;
case actionLaunch:
{
LinkLaunch * e = (LinkLaunch *)a;
GString * p = e->getParams();
link = new KPDFLinkExecute( e->getFileName()->getCString(), p ? p->getCString() : 0 );
}
break;
case actionNamed:
{
const char * name = ((LinkNamed *)a)->getName()->getCString();
if ( !strcmp( name, "NextPage" ) )
link = new KPDFLinkAction( KPDFLinkAction::PageNext );
else if ( !strcmp( name, "PrevPage" ) )
link = new KPDFLinkAction( KPDFLinkAction::PagePrev );
else if ( !strcmp( name, "FirstPage" ) )
link = new KPDFLinkAction( KPDFLinkAction::PageFirst );
else if ( !strcmp( name, "LastPage" ) )
link = new KPDFLinkAction( KPDFLinkAction::PageLast );
else if ( !strcmp( name, "GoBack" ) )
link = new KPDFLinkAction( KPDFLinkAction::HistoryBack );
else if ( !strcmp( name, "GoForward" ) )
link = new KPDFLinkAction( KPDFLinkAction::HistoryForward );
else if ( !strcmp( name, "Quit" ) )
link = new KPDFLinkAction( KPDFLinkAction::Quit );
else if ( !strcmp( name, "GoToPage" ) )
link = new KPDFLinkAction( KPDFLinkAction::GoToPage );
else if ( !strcmp( name, "Find" ) )
link = new KPDFLinkAction( KPDFLinkAction::Find );
else
kdDebug() << "Unknown named action: '" << name << "'" << endl;
}
break;
case actionURI:
link = new KPDFLinkBrowse( ((LinkURI *)a)->getURI()->getCString() );
break;
case actionMovie:
/* { TODO this
m_type = Movie;
LinkMovie * m = (LinkMovie *) a;
// copy Movie parameters (2 IDs and a const char *)
Ref * r = m->getAnnotRef();
m_refNum = r->num;
m_refGen = r->gen;
copyString( m_uri, m->getTitle()->getCString() );
}
*/ break;
case actionUnknown:
kdDebug() << "Unknown link." << endl;
break;
}
// link may be zero at that point
return link;
}
KPDFLinkGoto::Viewport KPDFOutputDev::decodeViewport( 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 = m_doc->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 = m_doc->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;
}
//END - private helpers
/** KPDFTextDev implementation **/
//BEGIN KPDFTextDev
KPDFTextDev::KPDFTextDev()
{
m_text = new TextPage( gFalse );
......@@ -376,4 +446,3 @@ void KPDFTextDev::drawChar( GfxState *state, double x, double y, double dx, doub
{
m_text->addChar( state, x, y, dx, dy, code, u, uLen );
}
//END KPDFTextDev
......@@ -22,11 +22,10 @@
#include <qvaluelist.h>
#include "xpdf/PDFDoc.h" // for 'Object'
#include "xpdf/SplashOutputDev.h"
#include "core/link.h"
class QPixmap;
class TextPage;
class PDFGenerator;
class KPDFLink;
class KPDFPageRect;
/**
......@@ -41,17 +40,17 @@ class KPDFPageRect;
class KPDFOutputDev : public SplashOutputDev
{
public:
KPDFOutputDev( PDFGenerator * parent, SplashColor paperColor );
KPDFOutputDev( SplashColor paperColor );
virtual ~KPDFOutputDev();
// to be called before PDFDoc->displayPage( thisclass, .. )
// @param qtThreadSafety: use a slow QImage and conversions (for threads only)
// initialize device -> attach device to PDFDoc
void initDevice( class PDFDoc * pdfDoc );
// set parameters before rendering *each* page
// @param qtThreadSafety: duplicate memory buffer (slow but safe)
void setParams( int pixmapWidth, int pixmapHeight, bool generateTextPage,
bool decodeLinks, bool decodeImages, bool qtThreadSafety = false );
// generate a valid KPDFLink subclass (or null) from a xpdf's LinkAction
KPDFLink * generateLink( LinkAction * );
// takes pointers out of the class (so deletion it's up to others)
QPixmap * takePixmap();
QImage * takeImage();
......@@ -77,6 +76,10 @@ class KPDFOutputDev : public SplashOutputDev
private:
// delete all interal objects and data