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 @@
#pragma implementation
#endif
#include <kdebug.h>
#include "SplashBitmap.h"
#include "TextOutputDev.h"
#include "QOutputDev.h"
// NOTE: XPDF/Splash implementation dependant code will be marked with '###'
//------------------------------------------------------------------------
// QOutputDev
// KPDFOutputDev
//------------------------------------------------------------------------
QOutputDev::QOutputDev(SplashColor paperColor)
: SplashOutputDev(splashModeRGB8, false, paperColor), m_image(0)
KPDFOutputDev::KPDFOutputDev(SplashColor paperColor)
: 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;
}
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);
SplashOutputDev::drawChar(state, x, y, dx, dy, originX, originY, code, u, uLen);
m_pixmapWidth = width;
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);
return SplashOutputDev::beginType3Char(state, x, y, dx, dy, code, u, uLen);
QPixmap * pix = m_pixmap;
m_pixmap = 0;
return pix;
}
void QOutputDev::clear()
TextPage * KPDFOutputDev::takeTextPage()
{
startDoc(NULL);
startPage(0, NULL);
TextPage * text = m_text;
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);
m_text->startPage(state);
if ( m_text )
m_text->startPage(state);
}
void QOutputDev::endPage()
void KPDFOutputDev::endPage()
{
SplashColorPtr dataPtr;
int bh, bw;
SplashOutputDev::endPage();
m_text->coalesce(gTrue);
bh = getBitmap()->getHeight();
bw = getBitmap()->getWidth();
dataPtr = getBitmap()->getDataPtr();
m_image = QImage((uchar*)dataPtr.rgb8, bw, bh, 32, 0, 0, QImage::IgnoreEndian);
m_image.setAlphaBuffer( false );
// TODO HACK: unload memory used by bitmap
//SplashOutputDev::startPage(pageNum, state (with pix size={0,0}) );
if ( m_text )
m_text->coalesce(gTrue);
// create a QPixmap from page data
delete m_pixmap;
int bh = getBitmap()->getHeight(),
bw = getBitmap()->getWidth();
SplashColorPtr dataPtr = getBitmap()->getDataPtr();
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);
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 @@
* (at your option) any later version. *
***************************************************************************/
#ifndef QOUTPUTDEV_H
#define QOUTPUTDEV_H
#ifndef KPDFOUTPUTDEV_H
#define KPDFOUTPUTDEV_H
#ifdef __GNUC__
#pragma interface
......@@ -21,43 +21,55 @@
#include <qimage.h>
#include "XRef.h"
#include "SplashOutputDev.h"
#include "Link.h"
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:
// Constructor
QOutputDev(SplashColor paperColor);
// Destructor.
virtual ~QOutputDev();
// Start a page.
virtual void startPage(int pageNum, GfxState *state);
// End a page.
virtual void endPage();
//----- update text state
virtual void updateFont(GfxState *state);
//----- text drawing
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);
// Clear out the document (used when displaying an empty window).
void clear();
bool find(Unicode *s, int len, GBool startAtTop, GBool stopAtBottom, GBool startAtLast, GBool stopAtLast, double *xMin, double *yMin, double *xMax, double *yMax);
const QImage &getImage() const;
private:
TextPage *m_text; // text from the current page
QImage m_image; // the image where the page is drawn
public:
KPDFOutputDev( SplashColor paperColor );
virtual ~KPDFOutputDev();
// to be called before PDFDoc->displayPage( thisclass, .. )
void setParams( int pixmapWidth, int pixmapHeight, bool generateText );
// takes pointers out of the class (so deletion it's up to others)
QPixmap * takePixmap();
TextPage * takeTextPage();
/** inherited from OutputDev */
// Start a page.
virtual void startPage(int pageNum, GfxState *state);
// End a page.
virtual void endPage();
//----- link borders
virtual void drawLink(Link *link, Catalog *catalog);
//----- update text state
virtual void updateFont(GfxState *state);
//----- text drawing
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);
private:
// the pixmap where the page is drawn (generated on every execution)
int m_pageNum;
int m_pixmapWidth;
int m_pixmapHeight;
QPixmap * m_pixmap;
// text page generated on demand
TextPage * m_text;
};
#endif
Personal Albert's list
-> make links functional
-> make links functional (done in HEAD)
More items
-> multiple pages per view
-> continous mode
-> screen editing: framework
-> 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
-> implementing Document / Page using AACid's thread as the generation thread
-> improve thumbnail generation to make it non-gui blocking
-> multiple pages per view
-> centering pages in the view
-> outline bottom and right edges (of pages)
Porting / In progress on the branch (first item comes first):
-> porting Albert's link following
-> porting Albert's search
-> implementing async document generator using Albert's thread as the generation thread
-> better zoom handling
Done
-> merge lots of kpdf_part and part (centralview) code (to simplify/clenup)
-> maybe: use local instead of X memory for thumbnails (..)
Done (sorted by inv.time)
-> smart handling of pixmap using an Observer ID (thumbnails are gone, only pixmaps now)
-> 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 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 @@
#include <qfile.h>
#include <qmutex.h>
#include <qvaluevector.h>
#include <qmap.h>
#include <kdebug.h>
// local includes
......@@ -35,18 +36,18 @@ public:
// document related
QMutex docLock;
PDFDoc * pdfdoc;
QOutputDev * splashOutputDevice;
KPDFOutputDev * kpdfOutputDev;
int currentPage;
float currentPosition;
QValueVector< KPDFPage* > pages;
// observers related (note: won't delete oservers)
QValueList< KPDFDocumentObserver* > observers;
QMap< int, KPDFDocumentObserver* > observers;
};
#define foreachObserver( cmd ) {\
QValueList<KPDFDocumentObserver*>::iterator it = d->observers.begin();\
QValueList<KPDFDocumentObserver*>::iterator end = d->observers.end();\
QMap<int,KPDFDocumentObserver*>::iterator it = d->observers.begin();\
QMap<int,KPDFDocumentObserver*>::iterator end = d->observers.end();\
for ( ; it != end ; ++ it ) { (*it)-> cmd ; } }
/*
......@@ -60,13 +61,13 @@ KPDFDocument::KPDFDocument()
d->currentPosition = 0;
SplashColor paperColor;
paperColor.rgb8 = splashMakeRGB8( 0xff, 0xff, 0xff );
d->splashOutputDevice = new QOutputDev( paperColor );
d->kpdfOutputDev = new KPDFOutputDev( paperColor );
}
KPDFDocument::~KPDFDocument()
{
close();
delete d->splashOutputDevice;
delete d->kpdfOutputDev;
delete d;
}
......@@ -94,7 +95,7 @@ bool KPDFDocument::openFile( const QString & docFile )
errors::clear();
// 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)
uint pageCount = d->pdfdoc->getNumPages();
......@@ -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 )
{
slotSetCurrentPagePosition( page, 0.0 );
......@@ -261,93 +300,16 @@ void KPDFDocument::slotFind( bool /*nextMatch*/, const QString & /*text*/ )
void KPDFDocument::slotGoToLink( /* QString anchor */ )
{
}
//END slots
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()
void KPDFDocument::sendFilteredPageList( bool forceEmpty )
{
// make up a value list of the pages [1,2,3..]
uint pageCount = d->pages.count();
QValueList<int> pagesList;
for ( uint i = 0; i < pageCount ; i++ )
pagesList.push_back( i );
if ( !forceEmpty )
for ( uint i = 0; i < pageCount ; i++ )
pagesList.push_back( i );
// send the list to observers
foreachObserver( pageSetup( pagesList ) );
......@@ -359,8 +321,7 @@ void KPDFDocument::deletePages()
return;
// broadcast an empty page list to observers
QValueList<int> pagesList;
foreachObserver( pageSetup( pagesList ) );
sendFilteredPageList( true );
// delete pages and clear container
for ( uint i = 0; i < d->pages.count() ; i++ )
......
......@@ -18,23 +18,26 @@ class KPDFPage;
/**
* @short Base class for objects being notified when something changes.
*
* Inherit this class and call KPDFDocument->addObserver( obsClass ) to get
* notified of asyncronous events (a new thumbnail has arrived, a pixmap has
* changed, and other events).
* Inherit this class and call KPDFDocument->addObserver( obsClass ) to get notified
* of asyncronous events (a new pixmap has arrived, changed, etc... ).
*/
class KPDFDocumentObserver
{
public:
// you must give each observer a unique ID (used for notifications)
virtual uint observerId() = 0;
// monitor changes in pixmaps (generation thread complete)
virtual void notifyThumbnailChanged( int /*pageNumber*/ ) {};
virtual void notifyPixmapChanged( int /*pageNumber*/ ) {};
// commands from the Document to observers
// commands from the Document to all observers
virtual void pageSetup( const QValueList<int> & /*pages*/ ) {};
virtual void pageSetCurrent( int /*pageNumber*/, float /*position*/ ) {};
virtual void pageSetHilight( int /*x*/, int /*y*/, int /*width*/, int /*height*/ ) {};
//virtual void pageSetHilight( int /*x*/, int /*y*/, int /*width*/, int /*height*/ ) {};
};
#define PAGEWIDGET_ID 1
#define THUMBNAILS_ID 2
/**
* @short The information container. Actions (like open,find) take place here.
......@@ -63,8 +66,7 @@ public:
// observers related methods
void addObserver( KPDFDocumentObserver * pObserver );
void requestPixmap( uint page, int width, int height, bool syncronous = false );
void requestThumbnail( uint page, int width, int height, bool syncronous = false );
void requestPixmap( int id, uint page, int width, int height, bool syncronous = false );
public slots:
// document commands via slots
......@@ -78,7 +80,7 @@ signals:
void pageChanged();
private:
void sendFilteredPageList();
void sendFilteredPageList( bool forceEmpty = false );
void deletePages();
class KPDFDocumentPrivate * d;
......
......@@ -115,7 +115,7 @@ void PageWidget::pageSetup( const QValueList<int> & pages )
m_page = 0;
if ( pages.count() < 1 )
return slotUpdateView();
return;
// populate internal vector with the list of pages and update
QValueList<int>::const_iterator pageIt = pages.begin();
......@@ -258,7 +258,7 @@ void PageWidget::viewportResizeEvent( QResizeEvent * )
}
m_delayTimer->start( 400, true );
// recalc coordinates
slotUpdateView( false );
//slotUpdateView( false );
}
void PageWidget::keyPressEvent( QKeyEvent * e )
......@@ -324,7 +324,7 @@ void PageWidget::dropEvent( QDropEvent * ev )
emit urlDropped( lst.first() );
}
void PageWidget::drawContents ( QPainter *p, int clipx, int clipy, int clipw, int cliph )
void PageWidget::drawContents( QPainter *p, int clipx, int clipy, int clipw, int cliph )
{
QColor bc( paletteBackgroundColor() /*KGlobalSettings::calculateAlternateBackgroundColor( KGlobalSettings::baseColor() )*/ );
if ( m_page )
......@@ -339,7 +339,7 @@ void PageWidget::drawContents ( QPainter *p, int clipx, int clipy, int clipw, in
p->translate( m_pageRect.left(), m_pageRect.top() );
QRect translatedPageClip( pageClip );
translatedPageClip.moveBy( -m_pageRect.left(), -m_pageRect.top() );
m_page->drawPixmap( p, translatedPageClip, m_pageRect.width(), m_pageRect.height() );
m_page->drawPixmap( PAGEWIDGET_ID, p, translatedPageClip, m_pageRect.width(), m_pageRect.height() );
p->restore();
}
......@@ -499,7 +499,7 @@ void PageWidget::slotUpdateView( bool repaint )
viewH = QMAX( viewport()->height(), pageH );
m_pageRect.setRect( (viewW - pageW) / 2, (viewH - pageH) / 2, pageW, pageH );
resizeContents( viewW, viewH );
m_document->requestPixmap( m_page->number(), pageW, pageH, true );
m_document->requestPixmap( PAGEWIDGET_ID, m_page->number(), pageW, pageH, true );
}
if ( repaint )
viewport()->update();
......
......@@ -34,6 +34,7 @@ public:
PageWidget( QWidget *parent, KPDFDocument *document );
// create actions that interact with this widget
uint observerId() { return PAGEWIDGET_ID; }
void setupActions( KActionCollection * collection, KConfigGroup * config );
void saveSettings( KConfigGroup * config );
......
......@@ -9,94 +9,96 @@
// qt includes