Commit 727d5dd5 authored by Enrico Ros's avatar Enrico Ros

More refactoring. Big internal changes but nothing visible.. apart from a

memory reduction on the displayed page. OutputDev under big changes.
Now DocumentObservers have an unique ID so they can queue requests to
documents and they get their data stored in Page(s). No more Pixmaps or
Thumbnails requests, every observer can request a custom-sized pixmap for
a given page. That makes room for new observers (like a cool perspective
book like page viewer or stacked viewer or what your fantasy suggests :-).

svn path=/branches/kpdf_experiments/kdegraphics/kpdf/; revision=346792
parent 8ac5c9e9
...@@ -19,80 +19,121 @@ ...@@ -19,80 +19,121 @@
#pragma implementation #pragma implementation
#endif #endif
#include <kdebug.h>
#include "SplashBitmap.h" #include "SplashBitmap.h"
#include "TextOutputDev.h" #include "TextOutputDev.h"
#include "QOutputDev.h" #include "QOutputDev.h"
// NOTE: XPDF/Splash implementation dependant code will be marked with '###'
//------------------------------------------------------------------------ //------------------------------------------------------------------------
// QOutputDev // KPDFOutputDev
//------------------------------------------------------------------------ //------------------------------------------------------------------------
QOutputDev::QOutputDev(SplashColor paperColor) KPDFOutputDev::KPDFOutputDev(SplashColor paperColor)
: SplashOutputDev(splashModeRGB8, false, paperColor), m_image(0) : SplashOutputDev(splashModeRGB8, false, paperColor),
m_pixmapWidth( -1 ), m_pixmapHeight( -1 ), m_pixmap( 0 ), m_text( 0 )
{ {
// create text object
m_text = new TextPage ( gFalse );
} }
QOutputDev::~QOutputDev ( ) KPDFOutputDev::~KPDFOutputDev()
{ {
delete m_pixmap;
delete m_text; delete m_text;
} }
void QOutputDev::drawChar(GfxState *state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, Unicode *u, int uLen) void KPDFOutputDev::setParams( int width, int height, bool generateText )
{ {
m_text->addChar(state, x, y, dx, dy, code, u, uLen); m_pixmapWidth = width;
SplashOutputDev::drawChar(state, x, y, dx, dy, originX, originY, code, u, uLen); m_pixmapHeight = height;
if ( m_pixmap )
{
delete m_pixmap;
m_pixmap = 0;
}
delete m_text;
m_text = generateText ? new TextPage( gFalse ) : 0;
} }
GBool QOutputDev::beginType3Char(GfxState *state, double x, double y, double dx, double dy, CharCode code, Unicode *u, int uLen) QPixmap * KPDFOutputDev::takePixmap()
{ {
m_text->addChar(state, x, y, dx, dy, code, u, uLen); QPixmap * pix = m_pixmap;
return SplashOutputDev::beginType3Char(state, x, y, dx, dy, code, u, uLen); m_pixmap = 0;
return pix;
} }
void QOutputDev::clear() TextPage * KPDFOutputDev::takeTextPage()
{ {
startDoc(NULL); TextPage * text = m_text;
startPage(0, NULL); m_text = 0;
return text;
} }
void QOutputDev::startPage(int pageNum, GfxState *state)
void KPDFOutputDev::startPage(int pageNum, GfxState *state)
{ {
m_pageNum = pageNum;
SplashOutputDev::startPage(pageNum, state); SplashOutputDev::startPage(pageNum, state);
m_text->startPage(state); if ( m_text )
m_text->startPage(state);
} }
void QOutputDev::endPage() void KPDFOutputDev::endPage()
{ {
SplashColorPtr dataPtr;
int bh, bw;
SplashOutputDev::endPage(); SplashOutputDev::endPage();
m_text->coalesce(gTrue); if ( m_text )
bh = getBitmap()->getHeight(); m_text->coalesce(gTrue);
bw = getBitmap()->getWidth();
dataPtr = getBitmap()->getDataPtr(); // create a QPixmap from page data
m_image = QImage((uchar*)dataPtr.rgb8, bw, bh, 32, 0, 0, QImage::IgnoreEndian); delete m_pixmap;
m_image.setAlphaBuffer( false ); int bh = getBitmap()->getHeight(),
bw = getBitmap()->getWidth();
// TODO HACK: unload memory used by bitmap SplashColorPtr dataPtr = getBitmap()->getDataPtr();
//SplashOutputDev::startPage(pageNum, state (with pix size={0,0}) ); QImage * img = new QImage((uchar*)dataPtr.rgb8, bw, bh, 32, 0, 0, QImage::IgnoreEndian);
if ( bw != m_pixmapWidth || bh != m_pixmapHeight )
{
// it may happen (in fact it doesn't) that we need rescaling
kdWarning() << "Pixmap at page '" << m_pageNum << "' needed rescale." << endl;
m_pixmap = new QPixmap( img->smoothScale( m_pixmapWidth, m_pixmapHeight ) );
}
else
m_pixmap = new QPixmap( *img );
delete img;
// ### hack: unload memory used by bitmap
SplashOutputDev::startPage(0, NULL);
} }
void QOutputDev::updateFont(GfxState *state) void KPDFOutputDev::drawLink(Link * /*l*/, Catalog */*catalog*/)
{
/* double x1,y1, x2,y2;
l->getRect( &x1,&y1, &x2,&y2 );
LinkAction * a = l->getAction();
pri NOWARN ntf("LINK %x ok:%d t:%d rect:[%f,%f,%f,%f] \n", (uint)l, (int)l->isOk(),
(int)a->getKind(), x1,y2, x2-x1, y2-y1 );
*/}
void KPDFOutputDev::updateFont(GfxState *state)
{ {
SplashOutputDev::updateFont(state); SplashOutputDev::updateFont(state);
m_text->updateFont(state); if ( m_text )
m_text->updateFont(state);
} }
bool QOutputDev::find(Unicode *s, int len, GBool startAtTop, GBool stopAtBottom, GBool startAtLast, GBool stopAtLast, double *xMin, double *yMin, double *xMax, double *yMax) void KPDFOutputDev::drawChar(GfxState *state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, Unicode *u, int uLen)
{ {
return m_text -> findText(s, len, startAtTop, stopAtBottom, startAtLast, stopAtLast, xMin, yMin, xMax, yMax); if ( m_text )
m_text->addChar(state, x, y, dx, dy, code, u, uLen);
SplashOutputDev::drawChar(state, x, y, dx, dy, originX, originY, code, u, uLen);
} }
const QImage &QOutputDev::getImage() const GBool KPDFOutputDev::beginType3Char(GfxState *state, double x, double y, double dx, double dy, CharCode code, Unicode *u, int uLen)
{ {
return m_image; if ( m_text )
m_text->addChar(state, x, y, dx, dy, code, u, uLen);
return SplashOutputDev::beginType3Char(state, x, y, dx, dy, code, u, uLen);
} }
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
* (at your option) any later version. * * (at your option) any later version. *
***************************************************************************/ ***************************************************************************/
#ifndef QOUTPUTDEV_H #ifndef KPDFOUTPUTDEV_H
#define QOUTPUTDEV_H #define KPDFOUTPUTDEV_H
#ifdef __GNUC__ #ifdef __GNUC__
#pragma interface #pragma interface
...@@ -21,43 +21,55 @@ ...@@ -21,43 +21,55 @@
#include <qimage.h> #include <qimage.h>
#include "XRef.h"
#include "SplashOutputDev.h" #include "SplashOutputDev.h"
#include "Link.h"
class TextPage; class TextPage;
class KPDFPage;
class QOutputDev : public SplashOutputDev /**
* @short A SplashOutputDev rendered that grab text and links.
*
* This output device:
* - renders the page using SplashOutputDev (its parent)
* - harvests text into a textPage (for searching text)
* - harvests links and set them to a KPDFPage
*/
class KPDFOutputDev : public SplashOutputDev
{ {
public: public:
// Constructor KPDFOutputDev( SplashColor paperColor );
QOutputDev(SplashColor paperColor); virtual ~KPDFOutputDev();
// Destructor. // to be called before PDFDoc->displayPage( thisclass, .. )
virtual ~QOutputDev(); void setParams( int pixmapWidth, int pixmapHeight, bool generateText );
// Start a page. // takes pointers out of the class (so deletion it's up to others)
virtual void startPage(int pageNum, GfxState *state); QPixmap * takePixmap();
TextPage * takeTextPage();
// End a page.
virtual void endPage(); /** inherited from OutputDev */
// Start a page.
//----- update text state virtual void startPage(int pageNum, GfxState *state);
virtual void updateFont(GfxState *state); // End a page.
virtual void endPage();
//----- text drawing //----- link borders
virtual void drawChar(GfxState *state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, Unicode *u, int uLen); virtual void drawLink(Link *link, Catalog *catalog);
virtual GBool beginType3Char(GfxState *state, double x, double y, double dx, double dy, CharCode code, Unicode *u, int uLen); //----- update text state
virtual void updateFont(GfxState *state);
// Clear out the document (used when displaying an empty window). //----- text drawing
void clear(); virtual void drawChar(GfxState *state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, Unicode *u, int uLen);
virtual GBool beginType3Char(GfxState *state, double x, double y, double dx, double dy, CharCode code, Unicode *u, int uLen);
bool find(Unicode *s, int len, GBool startAtTop, GBool stopAtBottom, GBool startAtLast, GBool stopAtLast, double *xMin, double *yMin, double *xMax, double *yMax);
private:
const QImage &getImage() const; // the pixmap where the page is drawn (generated on every execution)
int m_pageNum;
private: int m_pixmapWidth;
TextPage *m_text; // text from the current page int m_pixmapHeight;
QImage m_image; // the image where the page is drawn QPixmap * m_pixmap;
// text page generated on demand
TextPage * m_text;
}; };
#endif #endif
Personal Albert's list Personal Albert's list
-> make links functional -> make links functional (done in HEAD)
More items More items
-> multiple pages per view
-> continous mode
-> screen editing: framework -> screen editing: framework
-> screen editing: tools -> screen editing: tools
-> export all text in plain_text/html
-> extract(export?) images
-> implement history (mainly for actionNamed)
-> new icons (contest at kde-look that will end in 2004-Oct-01)
Porting / In progress Porting / In progress on the branch (first item comes first):
-> implementing Document / Page using AACid's thread as the generation thread -> porting Albert's link following
-> improve thumbnail generation to make it non-gui blocking -> porting Albert's search
-> multiple pages per view -> implementing async document generator using Albert's thread as the generation thread
-> centering pages in the view -> better zoom handling
-> outline bottom and right edges (of pages)
Done Done (sorted by inv.time)
-> merge lots of kpdf_part and part (centralview) code (to simplify/clenup) -> smart handling of pixmap using an Observer ID (thumbnails are gone, only pixmaps now)
-> maybe: use local instead of X memory for thumbnails (..) -> some toolbar/menu changes
-> outline bottom and right edges (of pages)
-> centering pages in the view
-> kpdf output at 100% has exactly the same size as acroread now
-> qsplitter layouting
-> zooming works as expected (and added 'fit to page' too)
-> new go to page dialog
-> previews sorted by visible areas (prioritize items where the scrollbar is) -> previews sorted by visible areas (prioritize items where the scrollbar is)
-> previews speedup: 50-100%
-> use local instead of X memory for thumbnails (..)
-> merge lots of kpdf_part and part (centralview) code (to simplify/clenup)
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <qfile.h> #include <qfile.h>
#include <qmutex.h> #include <qmutex.h>
#include <qvaluevector.h> #include <qvaluevector.h>
#include <qmap.h>
#include <kdebug.h> #include <kdebug.h>
// local includes // local includes
...@@ -35,18 +36,18 @@ public: ...@@ -35,18 +36,18 @@ public:
// document related // document related
QMutex docLock; QMutex docLock;
PDFDoc * pdfdoc; PDFDoc * pdfdoc;
QOutputDev * splashOutputDevice; KPDFOutputDev * kpdfOutputDev;
int currentPage; int currentPage;
float currentPosition; float currentPosition;
QValueVector< KPDFPage* > pages; QValueVector< KPDFPage* > pages;
// observers related (note: won't delete oservers) // observers related (note: won't delete oservers)
QValueList< KPDFDocumentObserver* > observers; QMap< int, KPDFDocumentObserver* > observers;
}; };
#define foreachObserver( cmd ) {\ #define foreachObserver( cmd ) {\
QValueList<KPDFDocumentObserver*>::iterator it = d->observers.begin();\ QMap<int,KPDFDocumentObserver*>::iterator it = d->observers.begin();\
QValueList<KPDFDocumentObserver*>::iterator end = d->observers.end();\ QMap<int,KPDFDocumentObserver*>::iterator end = d->observers.end();\
for ( ; it != end ; ++ it ) { (*it)-> cmd ; } } for ( ; it != end ; ++ it ) { (*it)-> cmd ; } }
/* /*
...@@ -60,13 +61,13 @@ KPDFDocument::KPDFDocument() ...@@ -60,13 +61,13 @@ KPDFDocument::KPDFDocument()
d->currentPosition = 0; d->currentPosition = 0;
SplashColor paperColor; SplashColor paperColor;
paperColor.rgb8 = splashMakeRGB8( 0xff, 0xff, 0xff ); paperColor.rgb8 = splashMakeRGB8( 0xff, 0xff, 0xff );
d->splashOutputDevice = new QOutputDev( paperColor ); d->kpdfOutputDev = new KPDFOutputDev( paperColor );
} }
KPDFDocument::~KPDFDocument() KPDFDocument::~KPDFDocument()
{ {
close(); close();
delete d->splashOutputDevice; delete d->kpdfOutputDev;
delete d; delete d;
} }
...@@ -94,7 +95,7 @@ bool KPDFDocument::openFile( const QString & docFile ) ...@@ -94,7 +95,7 @@ bool KPDFDocument::openFile( const QString & docFile )
errors::clear(); errors::clear();
// initialize output device for rendering current pdf // initialize output device for rendering current pdf
d->splashOutputDevice->startDoc( d->pdfdoc->getXRef() ); d->kpdfOutputDev->startDoc( d->pdfdoc->getXRef() );
// build Pages (currentPage was set -1 by deletePages) // build Pages (currentPage was set -1 by deletePages)
uint pageCount = d->pdfdoc->getNumPages(); uint pageCount = d->pdfdoc->getNumPages();
...@@ -146,6 +147,44 @@ const KPDFPage * KPDFDocument::page( uint n ) const ...@@ -146,6 +147,44 @@ const KPDFPage * KPDFDocument::page( uint n ) const
} }
void KPDFDocument::addObserver( KPDFDocumentObserver * pObserver )
{
d->observers[ pObserver->observerId() ] = pObserver;
}
void KPDFDocument::requestPixmap( int id, uint page, int width, int height, bool syn )
{
KPDFPage * kp = d->pages[page];
if ( !d->pdfdoc || !kp || kp->width() < 1 || kp->height() < 1 )
return;
if ( syn )
{
// in-place Pixmap generation for syncronous requests
if ( !kp->hasPixmap( id, width, height ) )
{
// set KPDFPage pointer to outputdevice for links/text harvesting
d->kpdfOutputDev->setParams( width, height, false );
// compute dpi used to get an image with desired width and height
double fakeDpiX = width * 72.0 / kp->width(),
fakeDpiY = height * 72.0 / kp->height();
d->docLock.lock();
d->pdfdoc->displayPage( d->kpdfOutputDev, page + 1, fakeDpiX, fakeDpiY, 0, true, true );
d->docLock.unlock();
kp->setPixmap( id, d->kpdfOutputDev->takePixmap() );
d->observers[id]->notifyPixmapChanged( page );
}
}
else
{
//TODO asyncronous events queuing
}
}
// BEGIN slots
void KPDFDocument::slotSetCurrentPage( int page ) void KPDFDocument::slotSetCurrentPage( int page )
{ {
slotSetCurrentPagePosition( page, 0.0 ); slotSetCurrentPagePosition( page, 0.0 );
...@@ -261,93 +300,16 @@ void KPDFDocument::slotFind( bool /*nextMatch*/, const QString & /*text*/ ) ...@@ -261,93 +300,16 @@ void KPDFDocument::slotFind( bool /*nextMatch*/, const QString & /*text*/ )
void KPDFDocument::slotGoToLink( /* QString anchor */ ) void KPDFDocument::slotGoToLink( /* QString anchor */ )
{ {
} }
//END slots
void KPDFDocument::sendFilteredPageList( bool forceEmpty )
void KPDFDocument::addObserver( KPDFDocumentObserver * pObserver )
{
d->observers.push_back( pObserver );
}
void KPDFDocument::requestPixmap( uint page, int width, int height, bool syn )
{
KPDFPage * kp = d->pages[page];
if ( !d->pdfdoc || !kp || kp->width() < 1 || kp->height() < 1 )
return;
if ( syn )
{
// in-place Pixmap generation for syncronous requests
if ( !kp->hasPixmap( width, height ) )
{
// compute dpi used to get an image with desired width and height
double fakeDpiX = width * 72.0 / kp->width(),
fakeDpiY = height * 72.0 / kp->height();
d->docLock.lock();
d->pdfdoc->displayPage( d->splashOutputDevice, page + 1, fakeDpiX, fakeDpiY, 0, true, false );
d->docLock.unlock();
// it may happen (in fact it doesn't) that we need rescaling
if ( d->splashOutputDevice->getImage().size() != QSize( width, height ) )
{
kdWarning() << "Pixmap for page '" << page << "' needed rescale." << endl;
kp->setPixmap( d->splashOutputDevice->getImage().smoothScale( width, height ) );
}
else
kp->setPixmap( d->splashOutputDevice->getImage() );
foreachObserver( notifyPixmapChanged( page ) );
}
}
else
{
//TODO asyncronous events queuing
}
}
void KPDFDocument::requestThumbnail( uint page, int width, int height, bool syn )
{
KPDFPage * kp = d->pages[page];
if ( !d->pdfdoc || !kp || kp->width() < 1 || kp->height() < 1 )
return;
if ( syn )
{
// in-place Thumbnail generation for syncronous requests
if ( !kp->hasThumbnail( width, height ) )
{
// compute dpi used to get an image with desired width and height
double fakeDpiX = width * 72.0 / kp->width(),
fakeDpiY = height * 72.0 / kp->height();
d->docLock.lock();
d->pdfdoc->displayPage( d->splashOutputDevice, page + 1, fakeDpiX, fakeDpiY, 0, true, false );
d->docLock.unlock();
// it may happen (in fact it doesn't) that we need rescaling
if ( d->splashOutputDevice->getImage().size() != QSize( width, height ) )
{
kdWarning() << "Thumbnail for page '" << page << "' needed rescale." << endl;
kp->setThumbnail( d->splashOutputDevice->getImage().smoothScale( width, height ) );
}
else
kp->setThumbnail( d->splashOutputDevice->getImage() );
foreachObserver( notifyThumbnailChanged( page ) );
}
}
else
{
//TODO asyncronous events queuing
}
}
void KPDFDocument::sendFilteredPageList()
{ {
// make up a value list of the pages [1,2,3..] // make up a value list of the pages [1,2,3..]
uint pageCount = d->pages.count(); uint pageCount = d->pages.count();
QValueList<int> pagesList; QValueList<int> pagesList;
for ( uint i = 0; i < pageCount ; i++ ) if ( !forceEmpty )
pagesList.push_back( i ); for ( uint i = 0; i < pageCount ; i++ )
pagesList.push_back( i );
// send the list to observers // send the list to observers
foreachObserver( pageSetup( pagesList ) ); foreachObserver( pageSetup( pagesList ) );
...@@ -359,8 +321,7 @@ void KPDFDocument::deletePages() ...@@ -359,8 +321,7 @@ void KPDFDocument::deletePages()
return; return;
// broadcast an empty page list to observers // broadcast an empty page list to observers
QValueList<int> pagesList; sendFilteredPageList( true );
foreachObserver( pageSetup( pagesList ) );
// delete pages and clear container // delete pages and clear container
for ( uint i = 0; i < d->pages.count() ; i++ ) for ( uint i = 0; i < d->pages.count() ; i++ )
......
...@@ -18,23 +18,26 @@ class KPDFPage; ...@@ -18,23 +18,26 @@ class KPDFPage;
/** /**
* @short Base class for objects being notified when something changes. * @short Base class for objects being notified when something changes.
* *
* Inherit this class and call KPDFDocument->addObserver( obsClass ) to get * Inherit this class and call KPDFDocument->addObserver( obsClass ) to get notified
* notified of asyncronous events (a new thumbnail has arrived, a pixmap has * of asyncronous events (a new pixmap has arrived, changed, etc... ).
* changed, and other events).
*/ */
class KPDFDocumentObserver class KPDFDocumentObserver
{ {
public: public:
// you must give each observer a unique ID (used for notifications)
virtual uint observerId() = 0;
// monitor changes in pixmaps (generation thread complete) // monitor changes in pixmaps (generation thread complete)
virtual void notifyThumbnailChanged( int /*pageNumber*/ ) {};
virtual void notifyPixmapChanged( int /*pageNumber*/ ) {}; virtual void notifyPixmapChanged( int /*pageNumber*/ ) {};