summaryrefslogtreecommitdiffstats
path: root/kpdf/core
diff options
context:
space:
mode:
Diffstat (limited to 'kpdf/core')
-rw-r--r--kpdf/core/Makefile.am13
-rw-r--r--kpdf/core/document.cpp1634
-rw-r--r--kpdf/core/document.h225
-rw-r--r--kpdf/core/generator.h115
-rw-r--r--kpdf/core/generator_kimgio/Makefile.am6
-rw-r--r--kpdf/core/generator_kimgio/generator_kimgio.cpp72
-rw-r--r--kpdf/core/generator_kimgio/generator_kimgio.h43
-rw-r--r--kpdf/core/generator_pdf/Makefile.am10
-rw-r--r--kpdf/core/generator_pdf/generator_pdf.cpp1258
-rw-r--r--kpdf/core/generator_pdf/generator_pdf.h150
-rw-r--r--kpdf/core/generator_pdf/gp_outputdev.cpp397
-rw-r--r--kpdf/core/generator_pdf/gp_outputdev.h90
-rw-r--r--kpdf/core/link.cpp65
-rw-r--r--kpdf/core/link.h118
-rw-r--r--kpdf/core/observer.h58
-rw-r--r--kpdf/core/page.cpp327
-rw-r--r--kpdf/core/page.h149
-rw-r--r--kpdf/core/pagetransition.cpp28
-rw-r--r--kpdf/core/pagetransition.h86
19 files changed, 4844 insertions, 0 deletions
diff --git a/kpdf/core/Makefile.am b/kpdf/core/Makefile.am
new file mode 100644
index 00000000..74c77485
--- /dev/null
+++ b/kpdf/core/Makefile.am
@@ -0,0 +1,13 @@
+SUBDIRS = generator_pdf generator_kimgio
+
+INCLUDES = -I$(srcdir)/generator_pdf -I$(srcdir)/.. -I$(srcdir)/../xpdf -I$(srcdir)/../xpdf/goo -I$(top_builddir)/kpdf $(all_includes)
+
+METASOURCES = AUTO
+
+libkpdfcore_la_LIBADD = ./generator_pdf/libgeneratorpdf.la ./generator_kimgio/libgeneratorkimgio.la
+libkpdfcore_la_SOURCES = document.cpp link.cpp page.cpp pagetransition.cpp
+
+noinst_LTLIBRARIES = libkpdfcore.la
+
+document.lo: ../conf/settings.h
+page.lo: ../conf/settings.h
diff --git a/kpdf/core/document.cpp b/kpdf/core/document.cpp
new file mode 100644
index 00000000..25e19d9a
--- /dev/null
+++ b/kpdf/core/document.cpp
@@ -0,0 +1,1634 @@
+/***************************************************************************
+ * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> *
+ * Copyright (C) 2004-2005 by Albert Astals Cid <tsdgeos@terra.es> *
+ * *
+ * 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/system includes
+#include <qdir.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qimage.h>
+#include <qtextstream.h>
+#include <qvaluevector.h>
+#include <qtimer.h>
+#include <qmap.h>
+#include <kdebug.h>
+#include <kimageio.h>
+#include <klocale.h>
+#include <kfinddialog.h>
+#include <kmessagebox.h>
+#include <kapplication.h>
+#include <kuserprofile.h>
+#include <krun.h>
+#include <kstandarddirs.h>
+
+// local includes
+#include "document.h"
+#include "observer.h"
+#include "page.h"
+#include "link.h"
+#include "generator_pdf/generator_pdf.h" // PDF generator
+#include "generator_kimgio/generator_kimgio.h" // KIMGIO generator
+#include "conf/settings.h"
+
+// structures used internally by KPDFDocument for local variables storage
+class AllocatedPixmap;
+class RunningSearch;
+class KPDFDocumentPrivate
+{
+ public:
+ // find descriptors, mapped by ID (we handle multiple searches)
+ QMap< int, RunningSearch * > searches;
+ int m_lastSearchID;
+
+ // needed because for remote documents docFileName is a local file and
+ // we want the remote url when the document refers to relativeNames
+ KURL url;
+
+ // cached stuff
+ QString docFileName;
+ QString xmlFileName;
+
+ // a list of the mimetypes qimage can understand
+ QStringList kimgioMimes;
+
+ // viewport stuff
+ QValueList< DocumentViewport > viewportHistory;
+ QValueList< DocumentViewport >::iterator viewportIterator;
+ DocumentViewport nextDocumentViewport; // see KPDFLink::Goto for an explanation
+
+ // observers / requests / allocator stuff
+ QMap< int, DocumentObserver * > observers;
+ QValueList< PixmapRequest * > pixmapRequestsStack;
+ QValueList< AllocatedPixmap * > allocatedPixmapsFifo;
+ int allocatedPixmapsTotalMemory;
+
+ // timers (memory checking / info saver)
+ QTimer * memCheckTimer;
+ QTimer * saveBookmarksTimer;
+};
+
+struct AllocatedPixmap
+{
+ // owner of the page
+ int id;
+ int page;
+ int memory;
+ // public constructor: initialize data
+ AllocatedPixmap( int i, int p, int m ) : id( i ), page( p ), memory( m ) {};
+};
+
+struct RunningSearch
+{
+ // store search properties
+ int continueOnPage;
+ NormalizedRect continueOnMatch;
+ QValueList< int > highlightedPages;
+
+ // fields related to previous searches (used for 'continueSearch')
+ QString cachedString;
+ KPDFDocument::SearchType cachedType;
+ bool cachedCaseSensitive;
+ bool cachedViewportMove;
+ bool cachedNoDialogs;
+ QColor cachedColor;
+};
+
+#define foreachObserver( cmd ) {\
+ QMap< int, DocumentObserver * >::iterator it=d->observers.begin(), end=d->observers.end();\
+ for ( ; it != end ; ++ it ) { (*it)-> cmd ; } }
+
+
+/** KPDFDocument **/
+
+KPDFDocument::KPDFDocument(QWidget *widget)
+ : QObject(widget), generator( 0 ), d( new KPDFDocumentPrivate )
+{
+ d->allocatedPixmapsTotalMemory = 0;
+ d->memCheckTimer = 0;
+ d->saveBookmarksTimer = 0;
+ d->m_lastSearchID = -1;
+ KImageIO::registerFormats();
+ QStringList list = QImage::inputFormatList();
+ QStringList::Iterator it = list.begin();
+ while( it != list.end() )
+ {
+ d->kimgioMimes << KMimeType::findByPath(QString("foo.%1").arg(*it), 0, true)->name();
+ ++it;
+ }
+}
+
+KPDFDocument::~KPDFDocument()
+{
+ // delete generator, pages, and related stuff
+ closeDocument();
+
+ // delete the private structure
+ delete d;
+}
+
+
+bool KPDFDocument::openDocument( const QString & docFile, const KURL & url, const KMimeType::Ptr &mime )
+{
+ // docFile is always local so we can use QFile on it
+ QFile fileReadTest( docFile );
+ if ( !fileReadTest.open( IO_ReadOnly ) )
+ {
+ d->docFileName = QString::null;
+ return false;
+ }
+ // determine the related "xml document-info" filename
+ d->url = url;
+ d->docFileName = docFile;
+ QString fn = docFile.contains('/') ? docFile.section('/', -1, -1) : docFile;
+ fn = "kpdf/" + QString::number(fileReadTest.size()) + "." + fn + ".xml";
+ fileReadTest.close();
+ d->xmlFileName = locateLocal( "data", fn );
+
+ // create the generator based on the file's mimetype
+ if ( (*mime).is( "application/pdf" ) )
+ generator = new PDFGenerator( this );
+// else if ( mimeName == "application/postscript" )
+// kdError() << "PS generator not available" << endl;
+ else
+ {
+ QStringList::Iterator it = d->kimgioMimes.begin();
+ while( it != d->kimgioMimes.end() )
+ {
+ kdDebug() << *it << endl;
+ if ( (*mime).is( *it ) )
+ {
+ generator = new KIMGIOGenerator( this );
+ break;
+ }
+ ++it;
+ }
+ if ( it == d->kimgioMimes.end() )
+ {
+ kdWarning() << "Unknown mimetype '" << mime->name() << "'." << endl;
+ return false;
+ }
+ }
+
+ // 1. load Document (and set busy cursor while loading)
+ QApplication::setOverrideCursor( waitCursor );
+ bool openOk = generator->loadDocument( docFile, pages_vector );
+ QApplication::restoreOverrideCursor();
+ if ( !openOk || pages_vector.size() <= 0 )
+ {
+ delete generator;
+ generator = 0;
+ return openOk;
+ }
+
+ // 2. load Additional Data (our bookmarks and metadata) about the document
+ loadDocumentInfo();
+
+ // 3. setup observers inernal lists and data
+ foreachObserver( notifySetup( pages_vector, true ) );
+
+ // 4. set initial page (restoring the page saved in xml if loaded)
+ DocumentViewport loadedViewport = (*d->viewportIterator);
+ if ( loadedViewport.pageNumber != -1 )
+ (*d->viewportIterator) = DocumentViewport();
+ else
+ loadedViewport.pageNumber = 0;
+ setViewport( loadedViewport );
+
+ // start bookmark saver timer
+ if ( !d->saveBookmarksTimer )
+ {
+ d->saveBookmarksTimer = new QTimer( this );
+ connect( d->saveBookmarksTimer, SIGNAL( timeout() ), this, SLOT( saveDocumentInfo() ) );
+ }
+ d->saveBookmarksTimer->start( 5 * 60 * 1000 );
+
+ // start memory check timer
+ if ( !d->memCheckTimer )
+ {
+ d->memCheckTimer = new QTimer( this );
+ connect( d->memCheckTimer, SIGNAL( timeout() ), this, SLOT( slotTimedMemoryCheck() ) );
+ }
+ d->memCheckTimer->start( 2000 );
+
+ if (d->nextDocumentViewport.pageNumber != -1)
+ {
+ setViewport(d->nextDocumentViewport);
+ d->nextDocumentViewport = DocumentViewport();
+ }
+
+ return true;
+}
+
+void KPDFDocument::closeDocument()
+{
+ // save document info if a document is still opened
+ if ( generator && pages_vector.size() > 0 )
+ saveDocumentInfo();
+
+ // stop timers
+ if ( d->memCheckTimer )
+ d->memCheckTimer->stop();
+ if ( d->saveBookmarksTimer )
+ d->saveBookmarksTimer->stop();
+
+ // delete contents generator
+ delete generator;
+ generator = 0;
+
+ d->url = KURL();
+
+ // remove requests left in queue
+ QValueList< PixmapRequest * >::iterator sIt = d->pixmapRequestsStack.begin();
+ QValueList< PixmapRequest * >::iterator sEnd = d->pixmapRequestsStack.end();
+ for ( ; sIt != sEnd; ++sIt )
+ delete *sIt;
+ d->pixmapRequestsStack.clear();
+
+ // send an empty list to observers (to free their data)
+ foreachObserver( notifySetup( QValueVector< KPDFPage * >(), true ) );
+
+ // delete pages and clear 'pages_vector' container
+ QValueVector< KPDFPage * >::iterator pIt = pages_vector.begin();
+ QValueVector< KPDFPage * >::iterator pEnd = pages_vector.end();
+ for ( ; pIt != pEnd; ++pIt )
+ delete *pIt;
+ pages_vector.clear();
+
+ // clear 'memory allocation' descriptors
+ QValueList< AllocatedPixmap * >::iterator aIt = d->allocatedPixmapsFifo.begin();
+ QValueList< AllocatedPixmap * >::iterator aEnd = d->allocatedPixmapsFifo.end();
+ for ( ; aIt != aEnd; ++aIt )
+ delete *aIt;
+ d->allocatedPixmapsFifo.clear();
+
+ // clear 'running searches' descriptors
+ QMap< int, RunningSearch * >::iterator rIt = d->searches.begin();
+ QMap< int, RunningSearch * >::iterator rEnd = d->searches.end();
+ for ( ; rIt != rEnd; ++rIt )
+ delete *rIt;
+ d->searches.clear();
+
+ // reset internal variables
+ d->viewportHistory.clear();
+ d->viewportHistory.append( DocumentViewport() );
+ d->viewportIterator = d->viewportHistory.begin();
+ d->allocatedPixmapsTotalMemory = 0;
+}
+
+
+void KPDFDocument::addObserver( DocumentObserver * pObserver )
+{
+ // keep the pointer to the observer in a map
+ d->observers[ pObserver->observerId() ] = pObserver;
+
+ // if the observer is added while a document is already opened, tell it
+ if ( !pages_vector.isEmpty() )
+ {
+ pObserver->notifySetup( pages_vector, true );
+ pObserver->notifyViewportChanged( false /*disables smoothMove*/ );
+ }
+}
+
+void KPDFDocument::removeObserver( DocumentObserver * pObserver )
+{
+ // remove observer from the map. it won't receive notifications anymore
+ if ( d->observers.contains( pObserver->observerId() ) )
+ {
+ // free observer's pixmap data
+ int observerId = pObserver->observerId();
+ QValueVector<KPDFPage*>::iterator it = pages_vector.begin(), end = pages_vector.end();
+ for ( ; it != end; ++it )
+ (*it)->deletePixmap( observerId );
+
+ // [MEM] free observer's allocation descriptors
+ QValueList< AllocatedPixmap * >::iterator aIt = d->allocatedPixmapsFifo.begin();
+ QValueList< AllocatedPixmap * >::iterator aEnd = d->allocatedPixmapsFifo.end();
+ while ( aIt != aEnd )
+ {
+ AllocatedPixmap * p = *aIt;
+ if ( p->id == observerId )
+ {
+ aIt = d->allocatedPixmapsFifo.remove( aIt );
+ delete p;
+ }
+ else
+ ++aIt;
+ }
+
+ // delete observer entry from the map
+ d->observers.remove( observerId );
+ }
+}
+
+void KPDFDocument::reparseConfig()
+{
+ // reparse generator config and if something changed clear KPDFPages
+ if ( generator && generator->reparseConfig() )
+ {
+ // invalidate pixmaps
+ QValueVector<KPDFPage*>::iterator it = pages_vector.begin(), end = pages_vector.end();
+ for ( ; it != end; ++it )
+ (*it)->deletePixmapsAndRects();
+
+ // [MEM] remove allocation descriptors
+ QValueList< AllocatedPixmap * >::iterator aIt = d->allocatedPixmapsFifo.begin();
+ QValueList< AllocatedPixmap * >::iterator aEnd = d->allocatedPixmapsFifo.end();
+ for ( ; aIt != aEnd; ++aIt )
+ delete *aIt;
+ d->allocatedPixmapsFifo.clear();
+ d->allocatedPixmapsTotalMemory = 0;
+
+ // send reload signals to observers
+ foreachObserver( notifyContentsCleared( DocumentObserver::Pixmap ) );
+ }
+
+ // free memory if in 'low' profile
+ if ( KpdfSettings::memoryLevel() == KpdfSettings::EnumMemoryLevel::Low &&
+ !d->allocatedPixmapsFifo.isEmpty() && !pages_vector.isEmpty() )
+ cleanupPixmapMemory();
+}
+
+
+QWidget *KPDFDocument::widget() const
+{
+ return static_cast<QWidget*>(parent());
+}
+
+bool KPDFDocument::isOpened() const
+{
+ return generator;
+}
+
+const DocumentInfo * KPDFDocument::documentInfo() const
+{
+ return generator ? generator->generateDocumentInfo() : NULL;
+}
+
+const DocumentSynopsis * KPDFDocument::documentSynopsis() const
+{
+ return generator ? generator->generateDocumentSynopsis() : NULL;
+}
+
+const KPDFPage * KPDFDocument::page( uint n ) const
+{
+ return ( n < pages_vector.count() ) ? pages_vector[n] : 0;
+}
+
+const DocumentViewport & KPDFDocument::viewport() const
+{
+ return (*d->viewportIterator);
+}
+
+uint KPDFDocument::currentPage() const
+{
+ return (*d->viewportIterator).pageNumber;
+}
+
+uint KPDFDocument::pages() const
+{
+ return pages_vector.size();
+}
+
+KURL KPDFDocument::currentDocument() const
+{
+ return d->url;
+}
+
+bool KPDFDocument::isAllowed( int flags ) const
+{
+ return generator ? generator->isAllowed( flags ) : false;
+}
+
+bool KPDFDocument::historyAtBegin() const
+{
+ return d->viewportIterator == d->viewportHistory.begin();
+}
+
+bool KPDFDocument::historyAtEnd() const
+{
+ return d->viewportIterator == --(d->viewportHistory.end());
+}
+
+QString KPDFDocument::getMetaData( const QString & key, const QString & option ) const
+{
+ return generator ? generator->getMetaData( key, option ) : QString();
+}
+
+bool KPDFDocument::supportsSearching() const
+{
+ return generator ? generator->supportsSearching() : false;
+}
+
+bool KPDFDocument::hasFonts() const
+{
+ return generator ? generator->hasFonts() : false;
+}
+
+void KPDFDocument::putFontInfo(KListView *list)
+{
+ if (generator) generator->putFontInfo(list);
+}
+
+void KPDFDocument::requestPixmaps( const QValueList< PixmapRequest * > & requests )
+{
+ if ( !generator )
+ {
+ // delete requests..
+ QValueList< PixmapRequest * >::const_iterator rIt = requests.begin(), rEnd = requests.end();
+ for ( ; rIt != rEnd; ++rIt )
+ delete *rIt;
+ // ..and return
+ return;
+ }
+
+ // 1. [CLEAN STACK] remove previous requests of requesterID
+ int requesterID = requests.first()->id;
+ QValueList< PixmapRequest * >::iterator sIt = d->pixmapRequestsStack.begin(), sEnd = d->pixmapRequestsStack.end();
+ while ( sIt != sEnd )
+ {
+ if ( (*sIt)->id == requesterID )
+ {
+ // delete request and remove it from stack
+ delete *sIt;
+ sIt = d->pixmapRequestsStack.remove( sIt );
+ }
+ else
+ ++sIt;
+ }
+
+ // 2. [ADD TO STACK] add requests to stack
+ bool threadingDisabled = !KpdfSettings::enableThreading();
+ QValueList< PixmapRequest * >::const_iterator rIt = requests.begin(), rEnd = requests.end();
+ for ( ; rIt != rEnd; ++rIt )
+ {
+ // set the 'page field' (see PixmapRequest) and check if it is valid
+ PixmapRequest * request = *rIt;
+ if ( !(request->page = pages_vector[ request->pageNumber ]) )
+ {
+ // skip requests referencing an invalid page (must not happen)
+ delete request;
+ continue;
+ }
+
+ if ( !request->async )
+ request->priority = 0;
+
+ if ( request->async && threadingDisabled )
+ request->async = false;
+
+ // add request to the 'stack' at the right place
+ if ( !request->priority )
+ // add priority zero requests to the top of the stack
+ d->pixmapRequestsStack.append( request );
+ else
+ {
+ // insert in stack sorted by priority
+ sIt = d->pixmapRequestsStack.begin();
+ sEnd = d->pixmapRequestsStack.end();
+ while ( sIt != sEnd && (*sIt)->priority > request->priority )
+ ++sIt;
+ d->pixmapRequestsStack.insert( sIt, request );
+ }
+ }
+
+ // 3. [START FIRST GENERATION] if generator is ready, start a new generation,
+ // or else (if gen is running) it will be started when the new contents will
+ //come from generator (in requestDone())
+ if ( generator->canGeneratePixmap() )
+ sendGeneratorRequest();
+}
+
+void KPDFDocument::requestTextPage( uint page )
+{
+ KPDFPage * kp = pages_vector[ page ];
+ if ( !generator || !kp )
+ return;
+
+ // Memory management for TextPages
+
+ generator->generateSyncTextPage( kp );
+}
+/* REFERENCE IMPLEMENTATION: better calling setViewport from other code
+void KPDFDocument::setNextPage()
+{
+ // advance page and set viewport on observers
+ if ( (*d->viewportIterator).pageNumber < (int)pages_vector.count() - 1 )
+ setViewport( DocumentViewport( (*d->viewportIterator).pageNumber + 1 ) );
+}
+
+void KPDFDocument::setPrevPage()
+{
+ // go to previous page and set viewport on observers
+ if ( (*d->viewportIterator).pageNumber > 0 )
+ setViewport( DocumentViewport( (*d->viewportIterator).pageNumber - 1 ) );
+}
+*/
+void KPDFDocument::setViewportPage( int page, int excludeId, bool smoothMove )
+{
+ // clamp page in range [0 ... numPages-1]
+ if ( page < 0 )
+ page = 0;
+ else if ( page > (int)pages_vector.count() )
+ page = pages_vector.count() - 1;
+
+ // make a viewport from the page and broadcast it
+ setViewport( DocumentViewport( page ), excludeId, smoothMove );
+}
+
+void KPDFDocument::setViewport( const DocumentViewport & viewport, int excludeId, bool smoothMove )
+{
+ // if already broadcasted, don't redo it
+ DocumentViewport & oldViewport = *d->viewportIterator;
+ if ( viewport == oldViewport )
+ kdDebug() << "setViewport with the same viewport." << endl;
+
+ // set internal viewport taking care of history
+ if ( oldViewport.pageNumber == viewport.pageNumber || oldViewport.pageNumber == -1 )
+ {
+ // if page is unchanged save the viewport at current position in queue
+ oldViewport = viewport;
+ }
+ else
+ {
+ // remove elements after viewportIterator in queue
+ d->viewportHistory.erase( ++d->viewportIterator, d->viewportHistory.end() );
+
+ // keep the list to a reasonable size by removing head when needed
+ if ( d->viewportHistory.count() >= 100 )
+ d->viewportHistory.pop_front();
+
+ // add the item at the end of the queue
+ d->viewportIterator = d->viewportHistory.append( viewport );
+ }
+
+ // notify change to all other (different from id) observers
+ QMap< int, DocumentObserver * >::iterator it = d->observers.begin(), end = d->observers.end();
+ for ( ; it != end ; ++ it )
+ if ( it.key() != excludeId )
+ (*it)->notifyViewportChanged( smoothMove );
+
+ // [MEM] raise position of currently viewed page in allocation queue
+ if ( d->allocatedPixmapsFifo.count() > 1 )
+ {
+ const int page = viewport.pageNumber;
+ QValueList< AllocatedPixmap * > viewportPixmaps;
+ QValueList< AllocatedPixmap * >::iterator aIt = d->allocatedPixmapsFifo.begin();
+ QValueList< AllocatedPixmap * >::iterator aEnd = d->allocatedPixmapsFifo.end();
+ while ( aIt != aEnd )
+ {
+ if ( (*aIt)->page == page )
+ {
+ viewportPixmaps.append( *aIt );
+ aIt = d->allocatedPixmapsFifo.remove( aIt );
+ continue;
+ }
+ ++aIt;
+ }
+ if ( !viewportPixmaps.isEmpty() )
+ d->allocatedPixmapsFifo += viewportPixmaps;
+ }
+}
+
+void KPDFDocument::setPrevViewport()
+// restore viewport from the history
+{
+ if ( d->viewportIterator != d->viewportHistory.begin() )
+ {
+ // restore previous viewport and notify it to observers
+ --d->viewportIterator;
+ foreachObserver( notifyViewportChanged( true ) );
+ }
+}
+
+void KPDFDocument::setNextViewport()
+// restore next viewport from the history
+{
+ QValueList< DocumentViewport >::iterator nextIterator = d->viewportIterator;
+ ++nextIterator;
+ if ( nextIterator != d->viewportHistory.end() )
+ {
+ // restore next viewport and notify it to observers
+ ++d->viewportIterator;
+ foreachObserver( notifyViewportChanged( true ) );
+ }
+}
+
+void KPDFDocument::setNextDocumentViewport( const DocumentViewport & viewport )
+{
+ d->nextDocumentViewport = viewport;
+}
+
+
+bool KPDFDocument::searchText( int searchID, const QString & text, bool fromStart, bool caseSensitive,
+ SearchType type, bool moveViewport, const QColor & color, bool noDialogs )
+{
+ // don't perform searches on empty docs
+ if ( !generator || pages_vector.isEmpty() )
+ return false;
+
+ // if searchID search not recorded, create new descriptor and init params
+ if ( !d->searches.contains( searchID ) )
+ {
+ RunningSearch * search = new RunningSearch();
+ search->continueOnPage = -1;
+ d->searches[ searchID ] = search;
+ }
+ if (d->m_lastSearchID != searchID)
+ {
+ resetSearch(d->m_lastSearchID);
+ }
+ d->m_lastSearchID = searchID;
+ RunningSearch * s = d->searches[ searchID ];
+
+ // update search stucture
+ bool newText = text != s->cachedString;
+ s->cachedString = text;
+ s->cachedType = type;
+ s->cachedCaseSensitive = caseSensitive;
+ s->cachedViewportMove = moveViewport;
+ s->cachedNoDialogs = noDialogs;
+ s->cachedColor = color;
+
+ // global data for search
+ bool foundAMatch = false;
+ QValueList< int > pagesToNotify;
+
+ // remove highlights from pages and queue them for notifying changes
+ pagesToNotify += s->highlightedPages;
+ QValueList< int >::iterator it = s->highlightedPages.begin(), end = s->highlightedPages.end();
+ for ( ; it != end; ++it )
+ pages_vector[ *it ]->deleteHighlights( searchID );
+ s->highlightedPages.clear();
+
+ // set hourglass cursor
+ QApplication::setOverrideCursor( waitCursor );
+
+ // 1. ALLDOC - proces all document marking pages
+ if ( type == AllDoc )
+ {
+ // search and highlight text on all pages
+ QValueVector< KPDFPage * >::iterator it = pages_vector.begin(), end = pages_vector.end();
+ for ( ; it != end; ++it )
+ {
+ // get page (from the first to the last)
+ KPDFPage * page = *it;
+ int pageNumber = page->number();
+
+ // request search page if needed
+ if ( !page->hasSearchPage() )
+ requestTextPage( pageNumber );
+
+ // loop on a page adding highlights for all found items
+ bool addedHighlights = false;
+ NormalizedRect * lastMatch = 0;
+ while ( 1 )
+ {
+ if ( lastMatch )
+ lastMatch = page->findText( text, caseSensitive, lastMatch );
+ else
+ lastMatch = page->findText( text, caseSensitive );
+
+ if ( !lastMatch )
+ break;
+
+ // add highligh rect to the page
+ page->setHighlight( searchID, lastMatch, color );
+ addedHighlights = true;
+ }
+
+ // if added highlights, udpate internals and queue page for notify
+ if ( addedHighlights )
+ {
+ foundAMatch = true;
+ s->highlightedPages.append( pageNumber );
+ if ( !pagesToNotify.contains( pageNumber ) )
+ pagesToNotify.append( pageNumber );
+ }
+ }
+
+ // reset cursor to previous shape
+ QApplication::restoreOverrideCursor();
+
+ // send page lists if found anything new
+ //if ( foundAMatch ) ?maybe?
+ foreachObserver( notifySetup( pages_vector, false ) );
+ }
+ // 2. NEXTMATCH - find next matching item (or start from top)
+ else if ( type == NextMatch )
+ {
+ // find out from where to start/resume search from
+ int viewportPage = (*d->viewportIterator).pageNumber;
+ int currentPage = fromStart ? 0 : ((s->continueOnPage != -1) ? s->continueOnPage : viewportPage);
+ KPDFPage * lastPage = fromStart ? 0 : pages_vector[ currentPage ];
+
+ // continue checking last SearchPage first (if it is the current page)
+ NormalizedRect * match = 0;
+ if ( lastPage && lastPage->number() == s->continueOnPage )
+ {
+ if ( newText )
+ match = lastPage->findText( text, caseSensitive );
+ else
+ match = lastPage->findText( text, caseSensitive, &s->continueOnMatch );
+ if ( !match )
+ currentPage++;
+ }
+
+ // if no match found, loop through the whole doc, starting from currentPage
+ if ( !match )
+ {
+ const int pageCount = pages_vector.count();
+ for ( int i = 0; i < pageCount; i++ )
+ {
+ if ( currentPage >= pageCount )
+ {
+ if ( noDialogs || KMessageBox::questionYesNo(widget(), i18n("End of document reached.\nContinue from the beginning?"), QString::null, KStdGuiItem::cont(), KStdGuiItem::cancel()) == KMessageBox::Yes )
+ currentPage = 0;
+ else
+ break;
+ }
+ // get page
+ KPDFPage * page = pages_vector[ currentPage ];
+ // request search page if needed
+ if ( !page->hasSearchPage() )
+ requestTextPage( page->number() );
+ // if found a match on the current page, end the loop
+ if ( (match = page->findText( text, caseSensitive )) )
+ break;
+ currentPage++;
+ }
+ }
+
+ // reset cursor to previous shape
+ QApplication::restoreOverrideCursor();
+
+ // if a match has been found..
+ if ( match )
+ {
+ // update the RunningSearch structure adding this match..
+ foundAMatch = true;
+ s->continueOnPage = currentPage;
+ s->continueOnMatch = *match;
+ s->highlightedPages.append( currentPage );
+ // ..add highlight to the page..
+ pages_vector[ currentPage ]->setHighlight( searchID, match, color );
+
+ // ..queue page for notifying changes..
+ if ( !pagesToNotify.contains( currentPage ) )
+ pagesToNotify.append( currentPage );
+
+ // ..move the viewport to show the searched word centered
+ if ( moveViewport )
+ {
+ DocumentViewport searchViewport( currentPage );
+ searchViewport.rePos.enabled = true;
+ searchViewport.rePos.normalizedX = (match->left + match->right) / 2.0;
+ searchViewport.rePos.normalizedY = (match->top + match->bottom) / 2.0;
+ setViewport( searchViewport, -1, true );
+ }
+ }
+ else if ( !noDialogs )
+ KMessageBox::information( widget(), i18n("No matches found for '%1'.").arg( text ) );
+ }
+ // 3. PREVMATCH //TODO
+ else if ( type == PrevMatch )
+ {
+ }
+ // 4. GOOGLE* - process all document marking pages
+ else if ( type == GoogleAll || type == GoogleAny )
+ {
+ // search and highlight every word in 'text' on all pages
+ bool matchAll = type == GoogleAll;
+ QStringList words = QStringList::split( " ", text );
+ int wordsCount = words.count(),
+ hueStep = (wordsCount > 1) ? (60 / (wordsCount - 1)) : 60,
+ baseHue, baseSat, baseVal;
+ color.getHsv( &baseHue, &baseSat, &baseVal );
+ QValueVector< KPDFPage * >::iterator it = pages_vector.begin(), end = pages_vector.end();
+ for ( ; it != end; ++it )
+ {
+ // get page (from the first to the last)
+ KPDFPage * page = *it;
+ int pageNumber = page->number();
+
+ // request search page if needed
+ if ( !page->hasSearchPage() )
+ requestTextPage( pageNumber );
+
+ // loop on a page adding highlights for all found items
+ bool allMatched = wordsCount > 0,
+ anyMatched = false;
+ for ( int w = 0; w < wordsCount; w++ )
+ {
+ QString word = words[ w ];
+ int newHue = baseHue - w * hueStep;
+ if ( newHue < 0 )
+ newHue += 360;
+ QColor wordColor = QColor( newHue, baseSat, baseVal, QColor::Hsv );
+ NormalizedRect * lastMatch = 0;
+ // add all highlights for current word
+ bool wordMatched = false;
+ while ( 1 )
+ {
+ if ( lastMatch )
+ lastMatch = page->findText( word, caseSensitive, lastMatch );
+ else
+ lastMatch = page->findText( word, caseSensitive );
+
+ if ( !lastMatch )
+ break;
+
+ // add highligh rect to the page
+ page->setHighlight( searchID, lastMatch, wordColor );
+ wordMatched = true;
+ }
+ allMatched = allMatched && wordMatched;
+ anyMatched = anyMatched || wordMatched;
+ }
+
+ // if not all words are present in page, remove partial highlights
+ if ( !allMatched && matchAll )
+ page->deleteHighlights( searchID );
+
+ // if page contains all words, udpate internals and queue page for notify
+ if ( (allMatched && matchAll) || (anyMatched && !matchAll) )
+ {
+ foundAMatch = true;
+ s->highlightedPages.append( pageNumber );
+ if ( !pagesToNotify.contains( pageNumber ) )
+ pagesToNotify.append( pageNumber );
+ }
+ }
+
+ // reset cursor to previous shape
+ QApplication::restoreOverrideCursor();
+
+ // send page lists to update observers (since some filter on bookmarks)
+ foreachObserver( notifySetup( pages_vector, false ) );
+ }
+
+ // notify observers about highlights changes
+ QValueList< int >::iterator nIt = pagesToNotify.begin(), nEnd = pagesToNotify.end();
+ for ( ; nIt != nEnd; ++nIt )
+ foreachObserver( notifyPageChanged( *nIt, DocumentObserver::Highlights ) );
+
+ // return if search has found one or more matches
+ return foundAMatch;
+}
+
+bool KPDFDocument::continueSearch( int searchID )
+{
+ // check if searchID is present in runningSearches
+ if ( !d->searches.contains( searchID ) )
+ return false;
+
+ // start search with cached parameters from last search by searchID
+ RunningSearch * p = d->searches[ searchID ];
+ return searchText( searchID, p->cachedString, false, p->cachedCaseSensitive,
+ p->cachedType, p->cachedViewportMove, p->cachedColor,
+ p->cachedNoDialogs );
+}
+
+void KPDFDocument::resetSearch( int searchID )
+{
+ // check if searchID is present in runningSearches
+ if ( !d->searches.contains( searchID ) )
+ return;
+
+ // get previous parameters for search
+ RunningSearch * s = d->searches[ searchID ];
+
+ // unhighlight pages and inform observers about that
+ QValueList< int >::iterator it = s->highlightedPages.begin(), end = s->highlightedPages.end();
+ for ( ; it != end; ++it )
+ {
+ int pageNumber = *it;
+ pages_vector[ pageNumber ]->deleteHighlights( searchID );
+ foreachObserver( notifyPageChanged( pageNumber, DocumentObserver::Highlights ) );
+ }
+
+ // send the setup signal too (to update views that filter on matches)
+ foreachObserver( notifySetup( pages_vector, false ) );
+
+ // remove serch from the runningSearches list and delete it
+ d->searches.remove( searchID );
+ delete s;
+}
+
+bool KPDFDocument::continueLastSearch()
+{
+ return continueSearch( d->m_lastSearchID );
+}
+
+
+void KPDFDocument::toggleBookmark( int n )
+{
+ KPDFPage * page = ( n < (int)pages_vector.count() ) ? pages_vector[ n ] : 0;
+ if ( page )
+ {
+ page->setBookmark( !page->hasBookmark() );
+ foreachObserver( notifyPageChanged( n, DocumentObserver::Bookmark ) );
+ }
+}
+
+void KPDFDocument::processLink( const KPDFLink * link )
+{
+ if ( !link )
+ return;
+
+ switch( link->linkType() )
+ {
+ case KPDFLink::Goto: {
+ const KPDFLinkGoto * go = static_cast< const KPDFLinkGoto * >( link );
+ d->nextDocumentViewport = go->destViewport();
+
+ // Explanation of why d->nextDocumentViewport is needed
+ // all openRelativeFile does is launch a signal telling we
+ // want to open another URL, the problem is that when the file is
+ // non local, the loading is done assynchronously so you can't
+ // do a setViewport after the if as it was because you are doing the setViewport
+ // on the old file and when the new arrives there is no setViewport for it and
+ // it does not show anything
+
+ // first open filename if link is pointing outside this document
+ if ( go->isExternal() && !openRelativeFile( go->fileName() ) )
+ {
+ kdWarning() << "Link: Error opening '" << go->fileName() << "'." << endl;
+ return;
+ }
+ else
+ {
+ if (d->nextDocumentViewport.pageNumber == -1) return;
+ setViewport( d->nextDocumentViewport, -1, true );
+ d->nextDocumentViewport = DocumentViewport();
+ }
+
+ } break;
+
+ case KPDFLink::Execute: {
+ const KPDFLinkExecute * exe = static_cast< const KPDFLinkExecute * >( link );
+ QString fileName = exe->fileName();
+ if ( fileName.endsWith( ".pdf" ) || fileName.endsWith( ".PDF" ) )
+ {
+ openRelativeFile( fileName );
+ return;
+ }
+
+ // Albert: the only pdf i have that has that kind of link don't define
+ // an application and use the fileName as the file to open
+ fileName = giveAbsolutePath( fileName );
+ KMimeType::Ptr mime = KMimeType::findByPath( fileName );
+ // Check executables
+ if ( KRun::isExecutableFile( fileName, mime->name() ) )
+ {
+ // Don't have any pdf that uses this code path, just a guess on how it should work
+ if ( !exe->parameters().isEmpty() )
+ {
+ fileName = giveAbsolutePath( exe->parameters() );
+ mime = KMimeType::findByPath( fileName );
+ if ( KRun::isExecutableFile( fileName, mime->name() ) )
+ {
+ // this case is a link pointing to an executable with a parameter
+ // that also is an executable, possibly a hand-crafted pdf
+ KMessageBox::information( widget(), i18n("The pdf file is trying to execute an external application and for your safety kpdf does not allow that.") );
+ return;
+ }
+ }
+ else
+ {
+ // this case is a link pointing to an executable with no parameters
+ // core developers find unacceptable executing it even after asking the user
+ KMessageBox::information( widget(), i18n("The pdf file is trying to execute an external application and for your safety kpdf does not allow that.") );
+ return;
+ }
+ }
+
+ KService::Ptr ptr = KServiceTypeProfile::preferredService( mime->name(), "Application" );
+ if ( ptr )
+ {
+ KURL::List lst;
+ lst.append( fileName );
+ KRun::run( *ptr, lst );
+ }
+ else
+ KMessageBox::information( widget(), i18n( "No application found for opening file of mimetype %1." ).arg( mime->name() ) );
+ } break;
+
+ case KPDFLink::Action: {
+ const KPDFLinkAction * action = static_cast< const KPDFLinkAction * >( link );
+ switch( action->actionType() )
+ {
+ case KPDFLinkAction::PageFirst:
+ setViewportPage( 0 );
+ break;
+ case KPDFLinkAction::PagePrev:
+ if ( (*d->viewportIterator).pageNumber > 0 )
+ setViewportPage( (*d->viewportIterator).pageNumber - 1 );
+ break;
+ case KPDFLinkAction::PageNext:
+ if ( (*d->viewportIterator).pageNumber < (int)pages_vector.count() - 1 )
+ setViewportPage( (*d->viewportIterator).pageNumber + 1 );
+ break;
+ case KPDFLinkAction::PageLast:
+ setViewportPage( pages_vector.count() - 1 );
+ break;
+ case KPDFLinkAction::HistoryBack:
+ setPrevViewport();
+ break;
+ case KPDFLinkAction::HistoryForward:
+ setNextViewport();
+ break;
+ case KPDFLinkAction::Quit:
+ emit quit();
+ break;
+ case KPDFLinkAction::Presentation:
+ emit linkPresentation();
+ break;
+ case KPDFLinkAction::EndPresentation:
+ emit linkEndPresentation();
+ break;
+ case KPDFLinkAction::Find:
+ emit linkFind();
+ break;
+ case KPDFLinkAction::GoToPage:
+ emit linkGoToPage();
+ break;
+ case KPDFLinkAction::Close:
+ emit close();
+ break;
+ }
+ } break;
+
+ case KPDFLink::Browse: {
+ const KPDFLinkBrowse * browse = static_cast< const KPDFLinkBrowse * >( link );
+ // if the url is a mailto one, invoke mailer
+ if ( browse->url().startsWith( "mailto:", false ) )
+ kapp->invokeMailer( browse->url() );
+ else
+ {
+ QString url = browse->url();
+
+ // fix for #100366, documents with relative links that are the form of http:foo.pdf
+ if (url.find("http:") == 0 && url.find("http://") == -1 && url.right(4) == ".pdf")
+ {
+ openRelativeFile(url.mid(5));
+ return;
+ }
+
+ // Albert: this is not a leak!
+ new KRun(url);
+ }
+ } break;
+
+ case KPDFLink::Movie:
+ //const KPDFLinkMovie * browse = static_cast< const KPDFLinkMovie * >( link );
+ // TODO this (Movie link)
+ break;
+ }
+}
+
+bool KPDFDocument::print( KPrinter &printer )
+{
+ return generator ? generator->print( printer ) : false;
+}
+
+void KPDFDocument::requestDone( PixmapRequest * req )
+{
+#ifndef NDEBUG
+ if ( !generator->canGeneratePixmap() )
+ kdDebug() << "requestDone with generator not in READY state." << endl;
+#endif
+
+ // [MEM] 1.1 find and remove a previous entry for the same page and id
+ QValueList< AllocatedPixmap * >::iterator aIt = d->allocatedPixmapsFifo.begin();
+ QValueList< AllocatedPixmap * >::iterator aEnd = d->allocatedPixmapsFifo.end();
+ for ( ; aIt != aEnd; ++aIt )
+ if ( (*aIt)->page == req->pageNumber && (*aIt)->id == req->id )
+ {
+ AllocatedPixmap * p = *aIt;
+ d->allocatedPixmapsFifo.remove( aIt );
+ d->allocatedPixmapsTotalMemory -= p->memory;
+ delete p;
+ break;
+ }
+
+ if ( d->observers.contains( req->id ) )
+ {
+ // [MEM] 1.2 append memory allocation descriptor to the FIFO
+ int memoryBytes = 4 * req->width * req->height;
+ AllocatedPixmap * memoryPage = new AllocatedPixmap( req->id, req->pageNumber, memoryBytes );
+ d->allocatedPixmapsFifo.append( memoryPage );
+ d->allocatedPixmapsTotalMemory += memoryBytes;
+
+ // 2. notify an observer that its pixmap changed
+ d->observers[ req->id ]->notifyPageChanged( req->pageNumber, DocumentObserver::Pixmap );
+ }
+#ifndef NDEBUG
+ else
+ kdWarning() << "Receiving a done request for the defunct observer " << req->id << endl;
+#endif
+
+ // 3. delete request
+ delete req;
+
+ // 4. start a new generation if some is pending
+ if ( !d->pixmapRequestsStack.isEmpty() )
+ sendGeneratorRequest();
+}
+
+void KPDFDocument::sendGeneratorRequest()
+{
+ // find a request
+ PixmapRequest * request = 0;
+ while ( !d->pixmapRequestsStack.isEmpty() && !request )
+ {
+ PixmapRequest * r = d->pixmapRequestsStack.last();
+ d->pixmapRequestsStack.pop_back();
+ // request only if page isn't already present
+ if ( !r->page->hasPixmap( r->id, r->width, r->height ) )
+ request = r;
+ else
+ delete r;
+ }
+
+ // if no request found (or already generated), return
+ if ( !request )
+ return;
+
+ // [MEM] preventive memory freeing
+ int pixmapBytes = 4 * request->width * request->height;
+ if ( pixmapBytes > (1024 * 1024) )
+ cleanupPixmapMemory( pixmapBytes );
+
+ // submit the request to the generator
+ generator->generatePixmap( request );
+}
+
+void KPDFDocument::cleanupPixmapMemory( int /*sure? bytesOffset*/ )
+{
+ // [MEM] choose memory parameters based on configuration profile
+ int clipValue = -1;
+ int memoryToFree = -1;
+ switch ( KpdfSettings::memoryLevel() )
+ {
+ case KpdfSettings::EnumMemoryLevel::Low:
+ memoryToFree = d->allocatedPixmapsTotalMemory;
+ break;
+
+ case KpdfSettings::EnumMemoryLevel::Normal:
+ memoryToFree = d->allocatedPixmapsTotalMemory - getTotalMemory() / 3;
+ clipValue = (d->allocatedPixmapsTotalMemory - getFreeMemory()) / 2;
+ break;
+
+ case KpdfSettings::EnumMemoryLevel::Aggressive:
+ clipValue = (d->allocatedPixmapsTotalMemory - getFreeMemory()) / 2;
+ break;
+ }
+
+ if ( clipValue > memoryToFree )
+ memoryToFree = clipValue;
+
+ if ( memoryToFree > 0 )
+ {
+ // [MEM] free memory starting from older pixmaps
+ int pagesFreed = 0;
+ QValueList< AllocatedPixmap * >::iterator pIt = d->allocatedPixmapsFifo.begin();
+ QValueList< AllocatedPixmap * >::iterator pEnd = d->allocatedPixmapsFifo.end();
+ while ( (pIt != pEnd) && (memoryToFree > 0) )
+ {
+ AllocatedPixmap * p = *pIt;
+ if ( d->observers[ p->id ]->canUnloadPixmap( p->page ) )
+ {
+ // update internal variables
+ pIt = d->allocatedPixmapsFifo.remove( pIt );
+ d->allocatedPixmapsTotalMemory -= p->memory;
+ memoryToFree -= p->memory;
+ pagesFreed++;
+ // delete pixmap
+ pages_vector[ p->page ]->deletePixmap( p->id );
+ // delete allocation descriptor
+ delete p;
+ } else
+ ++pIt;
+ }
+ //p--rintf("freeMemory A:[%d -%d = %d] \n", d->allocatedPixmapsFifo.count() + pagesFreed, pagesFreed, d->allocatedPixmapsFifo.count() );
+ }
+}
+
+int KPDFDocument::getTotalMemory()
+{
+ static int cachedValue = 0;
+ if ( cachedValue )
+ return cachedValue;
+
+#ifdef __linux__
+ // if /proc/meminfo doesn't exist, return 128MB
+ QFile memFile( "/proc/meminfo" );
+ if ( !memFile.open( IO_ReadOnly ) )
+ return (cachedValue = 134217728);
+
+ // read /proc/meminfo and sum up the contents of 'MemFree', 'Buffers'
+ // and 'Cached' fields. consider swapped memory as used memory.
+ QTextStream readStream( &memFile );
+ while ( !readStream.atEnd() )
+ {
+ QString entry = readStream.readLine();
+ if ( entry.startsWith( "MemTotal:" ) )
+ return (cachedValue = (1024 * entry.section( ' ', -2, -2 ).toInt()));
+ }
+#endif
+ return (cachedValue = 134217728);
+}
+
+int KPDFDocument::getFreeMemory()
+{
+ static QTime lastUpdate = QTime::currentTime();
+ static int cachedValue = 0;
+
+ if ( lastUpdate.secsTo( QTime::currentTime() ) <= 2 )
+ return cachedValue;
+
+#ifdef __linux__
+ // if /proc/meminfo doesn't exist, return MEMORY FULL
+ QFile memFile( "/proc/meminfo" );
+ if ( !memFile.open( IO_ReadOnly ) )
+ return 0;
+
+ // read /proc/meminfo and sum up the contents of 'MemFree', 'Buffers'
+ // and 'Cached' fields. consider swapped memory as used memory.
+ int memoryFree = 0;
+ QString entry;
+ QTextStream readStream( &memFile );
+ while ( !readStream.atEnd() )
+ {
+ entry = readStream.readLine();
+ if ( entry.startsWith( "MemFree:" ) ||
+ entry.startsWith( "Buffers:" ) ||
+ entry.startsWith( "Cached:" ) ||
+ entry.startsWith( "SwapFree:" ) )
+ memoryFree += entry.section( ' ', -2, -2 ).toInt();
+ if ( entry.startsWith( "SwapTotal:" ) )
+ memoryFree -= entry.section( ' ', -2, -2 ).toInt();
+ }
+ memFile.close();
+
+ lastUpdate = QTime::currentTime();
+
+ return ( cachedValue = ( 1024 * memoryFree ) );
+#else
+ // tell the memory is full.. will act as in LOW profile
+ return 0;
+#endif
+}
+
+void KPDFDocument::loadDocumentInfo()
+// note: load data and stores it internally (document or pages). observers
+// are still uninitialized at this point so don't access them
+{
+ //kdDebug() << "Using '" << d->xmlFileName << "' as document info file." << endl;
+ QFile infoFile( d->xmlFileName );
+ if ( !infoFile.exists() || !infoFile.open( IO_ReadOnly ) )
+ return;
+
+ // Load DOM from XML file
+ QDomDocument doc( "documentInfo" );
+ if ( !doc.setContent( &infoFile ) )
+ {
+ kdDebug() << "Could not set content" << endl;
+ infoFile.close();
+ return;
+ }
+ infoFile.close();
+
+ QDomElement root = doc.documentElement();
+ if ( root.tagName() != "documentInfo" )
+ return;
+
+ // Parse the DOM tree
+ QDomNode topLevelNode = root.firstChild();
+ while ( topLevelNode.isElement() )
+ {
+ QString catName = topLevelNode.toElement().tagName();
+
+ // Get bookmarks list from DOM
+ if ( catName == "bookmarkList" )
+ {
+ QDomNode n = topLevelNode.firstChild();
+ QDomElement e;
+ int pageNumber;
+ bool ok;
+ while ( n.isElement() )
+ {
+ e = n.toElement();
+ if (e.tagName() == "page")
+ {
+ pageNumber = e.text().toInt(&ok);
+ if ( ok && pageNumber >= 0 && pageNumber < (int)pages_vector.count() )
+ pages_vector[ pageNumber ]->setBookmark( true );
+ }
+ n = n.nextSibling();
+ }
+ } // </bookmarkList>
+ // Get 'general info' from the DOM
+ else if ( catName == "generalInfo" )
+ {
+ QDomNode infoNode = topLevelNode.firstChild();
+ while ( infoNode.isElement() )
+ {
+ QDomElement infoElement = infoNode.toElement();
+
+ // compatibility: [pre-3.4 viewport storage] @remove after 3.4 relase
+ if ( infoElement.tagName() == "activePage" )
+ {
+ if ( infoElement.hasAttribute( "viewport" ) )
+ *d->viewportIterator = DocumentViewport( infoElement.attribute( "viewport" ) );
+ }
+
+ // restore viewports history
+ if ( infoElement.tagName() == "history" )
+ {
+ // clear history
+ d->viewportHistory.clear();
+ // append old viewports
+ QDomNode historyNode = infoNode.firstChild();
+ while ( historyNode.isElement() )
+ {
+ QDomElement historyElement = historyNode.toElement();
+ if ( historyElement.hasAttribute( "viewport" ) )
+ {
+ QString vpString = historyElement.attribute( "viewport" );
+ d->viewportIterator = d->viewportHistory.append(
+ DocumentViewport( vpString ) );
+ }
+ historyNode = historyNode.nextSibling();
+ }
+ // consistancy check
+ if ( d->viewportHistory.isEmpty() )
+ d->viewportIterator = d->viewportHistory.append( DocumentViewport() );
+ }
+ infoNode = infoNode.nextSibling();
+ }
+ } // </generalInfo>
+ topLevelNode = topLevelNode.nextSibling();
+ } // </documentInfo>
+}
+
+QString KPDFDocument::giveAbsolutePath( const QString & fileName )
+{
+ if ( !d->url.isValid() )
+ return QString::null;
+
+ return d->url.upURL().url() + fileName;
+}
+
+bool KPDFDocument::openRelativeFile( const QString & fileName )
+{
+ QString absFileName = giveAbsolutePath( fileName );
+ if ( absFileName.isNull() )
+ return false;
+
+ kdDebug() << "openDocument: '" << absFileName << "'" << endl;
+
+ emit openURL( absFileName );
+ return true;
+}
+
+
+void KPDFDocument::saveDocumentInfo() const
+{
+ if ( d->docFileName.isNull() )
+ return;
+
+ QFile infoFile( d->xmlFileName );
+ if (infoFile.open( IO_WriteOnly | IO_Truncate) )
+ {
+ // Create DOM
+ QDomDocument doc( "documentInfo" );
+ QDomElement root = doc.createElement( "documentInfo" );
+ doc.appendChild( root );
+
+ // Add bookmark list to DOM
+ QDomElement bookmarkList = doc.createElement( "bookmarkList" );
+ root.appendChild( bookmarkList );
+
+ for ( uint i = 0; i < pages_vector.count() ; i++ )
+ {
+ if ( pages_vector[i]->hasBookmark() )
+ {
+ QDomElement page = doc.createElement( "page" );
+ page.appendChild( doc.createTextNode( QString::number(i) ) );
+
+ bookmarkList.appendChild( page );
+ }
+ }
+
+ // Add general info to DOM
+ QDomElement generalInfo = doc.createElement( "generalInfo" );
+ root.appendChild( generalInfo );
+
+ // <general info><history> ... </history> saves history up to 10 viewports
+ QValueList< DocumentViewport >::iterator backIterator = d->viewportIterator;
+ if ( backIterator != d->viewportHistory.end() )
+ {
+ // go back up to 10 steps from the current viewportIterator
+ int backSteps = 10;
+ while ( backSteps-- && backIterator != d->viewportHistory.begin() )
+ --backIterator;
+
+ // create history root node
+ QDomElement historyNode = doc.createElement( "history" );
+ generalInfo.appendChild( historyNode );
+
+ // add old[backIterator] and present[viewportIterator] items
+ QValueList< DocumentViewport >::iterator endIt = d->viewportIterator;
+ ++endIt;
+ while ( backIterator != endIt )
+ {
+ QString name = (backIterator == d->viewportIterator) ? "current" : "oldPage";
+ QDomElement historyEntry = doc.createElement( name );
+ historyEntry.setAttribute( "viewport", (*backIterator).toString() );
+ historyNode.appendChild( historyEntry );
+ ++backIterator;
+ }
+ }
+
+ // Save DOM to XML file
+ QString xml = doc.toString();
+ QTextStream os( &infoFile );
+ os << xml;
+ }
+ infoFile.close();
+}
+
+void KPDFDocument::slotTimedMemoryCheck()
+{
+ // [MEM] clean memory (for 'free mem dependant' profiles only)
+ if ( KpdfSettings::memoryLevel() != KpdfSettings::EnumMemoryLevel::Low &&
+ d->allocatedPixmapsTotalMemory > 1024*1024 )
+ cleanupPixmapMemory();
+}
+
+
+/** DocumentViewport **/
+
+DocumentViewport::DocumentViewport( int n )
+ : pageNumber( n )
+{
+ // default settings
+ rePos.enabled = false;
+ rePos.normalizedX = 0.5;
+ rePos.normalizedY = 0.0;
+ rePos.pos = Center;
+ autoFit.enabled = false;
+ autoFit.width = false;
+ autoFit.height = false;
+}
+
+DocumentViewport::DocumentViewport( const QString & xmlDesc )
+ : pageNumber( -1 )
+{
+ // default settings (maybe overridden below)
+ rePos.enabled = false;
+ rePos.normalizedX = 0.5;
+ rePos.normalizedY = 0.0;
+ rePos.pos = Center;
+ autoFit.enabled = false;
+ autoFit.width = false;
+ autoFit.height = false;
+
+ // check for string presence
+ if ( xmlDesc.isEmpty() )
+ return;
+
+ // decode the string
+ bool ok;
+ int field = 0;
+ QString token = xmlDesc.section( ';', field, field );
+ while ( !token.isEmpty() )
+ {
+ // decode the current token
+ if ( field == 0 )
+ {
+ pageNumber = token.toInt( &ok );
+ if ( !ok )
+ return;
+ }
+ else if ( token.startsWith( "C1" ) )
+ {
+ rePos.enabled = true;
+ rePos.normalizedX = token.section( ':', 1, 1 ).toDouble();
+ rePos.normalizedY = token.section( ':', 2, 2 ).toDouble();
+ rePos.pos = Center;
+ }
+ else if ( token.startsWith( "C2" ) )
+ {
+ rePos.enabled = true;
+ rePos.normalizedX = token.section( ':', 1, 1 ).toDouble();
+ rePos.normalizedY = token.section( ':', 2, 2 ).toDouble();
+ if (token.section( ':', 3, 3 ).toInt() == 1) rePos.pos = Center;
+ else rePos.pos = TopLeft;
+ }
+ else if ( token.startsWith( "AF1" ) )
+ {
+ autoFit.enabled = true;
+ autoFit.width = token.section( ':', 1, 1 ) == "T";
+ autoFit.height = token.section( ':', 2, 2 ) == "T";
+ }
+ // proceed tokenizing string
+ field++;
+ token = xmlDesc.section( ';', field, field );
+ }
+}
+
+QString DocumentViewport::toString() const
+{
+ // start string with page number
+ QString s = QString::number( pageNumber );
+ // if has center coordinates, save them on string
+ if ( rePos.enabled )
+ s += QString( ";C2:" ) + QString::number( rePos.normalizedX ) +
+ ':' + QString::number( rePos.normalizedY ) +
+ ':' + QString::number( rePos.pos );
+ // if has autofit enabled, save its state on string
+ if ( autoFit.enabled )
+ s += QString( ";AF1:" ) + (autoFit.width ? "T" : "F") +
+ ':' + (autoFit.height ? "T" : "F");
+ return s;
+}
+
+bool DocumentViewport::operator==( const DocumentViewport & vp ) const
+{
+ bool equal = ( pageNumber == vp.pageNumber ) &&
+ ( rePos.enabled == vp.rePos.enabled ) &&
+ ( autoFit.enabled == vp.autoFit.enabled );
+ if ( !equal )
+ return false;
+ if ( rePos.enabled &&
+ (( rePos.normalizedX != vp.rePos.normalizedX) ||
+ ( rePos.normalizedY != vp.rePos.normalizedY ) || rePos.pos != vp.rePos.pos) )
+ return false;
+ if ( autoFit.enabled &&
+ (( autoFit.width != vp.autoFit.width ) ||
+ ( autoFit.height != vp.autoFit.height )) )
+ return false;
+ return true;
+}
+
+
+/** DocumentInfo **/
+
+DocumentInfo::DocumentInfo()
+ : QDomDocument( "DocumentInformation" )
+{
+ QDomElement docElement = createElement( "DocumentInfo" );
+ appendChild( docElement );
+}
+
+void DocumentInfo::set( const QString &key, const QString &value,
+ const QString &title )
+{
+ QDomElement docElement = documentElement();
+ QDomElement element;
+
+ // check whether key already exists
+ QDomNodeList list = docElement.elementsByTagName( key );
+ if ( list.count() > 0 )
+ element = list.item( 0 ).toElement();
+ else
+ element = createElement( key );
+
+ element.setAttribute( "value", value );
+ element.setAttribute( "title", title );
+
+ if ( list.count() == 0 )
+ docElement.appendChild( element );
+}
+
+QString DocumentInfo::get( const QString &key ) const
+{
+ QDomElement docElement = documentElement();
+ QDomElement element;
+
+ // check whether key already exists
+ QDomNodeList list = docElement.elementsByTagName( key );
+ if ( list.count() > 0 )
+ return list.item( 0 ).toElement().attribute( "value" );
+ else
+ return QString();
+}
+
+
+/** DocumentSynopsis **/
+
+DocumentSynopsis::DocumentSynopsis()
+ : QDomDocument( "DocumentSynopsis" )
+{
+ // void implementation, only subclassed for naming
+}
+
+#include "document.moc"
diff --git a/kpdf/core/document.h b/kpdf/core/document.h
new file mode 100644
index 00000000..8e4c1d8e
--- /dev/null
+++ b/kpdf/core/document.h
@@ -0,0 +1,225 @@
+/***************************************************************************
+ * Copyright (C) 2004 by Enrico Ros <eros.kde@email.it> *
+ * Copyright (C) 2004-2005 by Albert Astals Cid <tsdgeos@terra.es> *
+ * *
+ * 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. *
+ ***************************************************************************/
+
+#ifndef _KPDF_DOCUMENT_H_
+#define _KPDF_DOCUMENT_H_
+
+#include <qobject.h>
+#include <qvaluevector.h>
+#include <qstring.h>
+#include <qdom.h>
+
+#include <kmimetype.h>
+
+class KPDFPage;
+class KPDFLink;
+class DocumentObserver;
+class DocumentViewport;
+class DocumentInfo;
+class DocumentSynopsis;
+class Generator;
+class PixmapRequest;
+class KListView;
+class KPrinter;
+class KURL;
+
+/**
+ * @short The Document. Heart of everything. Actions take place here.
+ *
+ * The Document is the main object in KPDF. All views query the Document to
+ * get data/properties or even for accessing pages (in a 'const' way).
+ *
+ * It is designed to keep it detached from the document type (pdf, ps, you
+ * name it..) so whenever you want to get some data, it asks its internals
+ * generator to do the job and return results in a format-indepedent way.
+ *
+ * Apart from the generator (the currently running one) the document stores
+ * all the Pages ('KPDFPage' class) of the current document in a vector and
+ * notifies all the registered DocumentObservers when some content changes.
+ *
+ * For a better understanding of hieracies @see README.internals.png
+ * @see DocumentObserver, KPDFPage
+ */
+class KPDFDocument : public QObject
+{
+ Q_OBJECT
+ public:
+ KPDFDocument( QWidget *widget );
+ ~KPDFDocument();
+
+ // document handling
+ bool openDocument( const QString & docFile, const KURL & url, const KMimeType::Ptr &mime );
+ void closeDocument();
+
+ // misc methods
+ void addObserver( DocumentObserver * pObserver );
+ void removeObserver( DocumentObserver * pObserver );
+ void reparseConfig();
+
+ // enum definitions
+ enum Permission { AllowModify = 1, AllowCopy = 2, AllowPrint = 4, AllowNotes = 8 };
+
+ // returns the widget where the document is shown
+ QWidget *widget() const;
+
+ // query methods (const ones)
+ bool isOpened() const;
+ const DocumentInfo * documentInfo() const;
+ const DocumentSynopsis * documentSynopsis() const;
+ const KPDFPage * page( uint page ) const;
+ const DocumentViewport & viewport() const;
+ uint currentPage() const;
+ uint pages() const;
+ KURL currentDocument() const;
+ bool isAllowed( int /*Document::Permisison(s)*/ ) const;
+ bool historyAtBegin() const;
+ bool historyAtEnd() const;
+ QString getMetaData( const QString & key, const QString & option = QString() ) const;
+ bool supportsSearching() const;
+ bool hasFonts() const;
+ void putFontInfo(KListView *list);
+
+ // perform actions on document / pages
+ void setViewportPage( int page, int excludeId = -1, bool smoothMove = false );
+ void setViewport( const DocumentViewport & viewport, int excludeId = -1, bool smoothMove = false );
+ void setPrevViewport();
+ void setNextViewport();
+ void setNextDocumentViewport( const DocumentViewport & viewport );
+ void requestPixmaps( const QValueList< PixmapRequest * > & requests );
+ void requestTextPage( uint page );
+
+ enum SearchType { NextMatch, PrevMatch, AllDoc, GoogleAll, GoogleAny };
+ bool searchText( int searchID, const QString & text, bool fromStart, bool caseSensitive,
+ SearchType type, bool moveViewport, const QColor & color, bool noDialogs = false );
+ bool continueSearch( int searchID );
+ void resetSearch( int searchID );
+ bool continueLastSearch();
+ void toggleBookmark( int page );
+ void processLink( const KPDFLink * link );
+ bool print( KPrinter &printer );
+
+ // notifications sent by generator
+ void requestDone( PixmapRequest * request );
+
+ signals:
+ void close();
+ void quit();
+ void linkFind();
+ void linkGoToPage();
+ void openURL(const KURL &url);
+ void linkPresentation();
+ void linkEndPresentation();
+
+ private:
+ void sendGeneratorRequest();
+ // memory management related functions
+ void cleanupPixmapMemory( int bytesOffset = 0 );
+ int getTotalMemory();
+ int getFreeMemory();
+ // more private functions
+ void loadDocumentInfo();
+ QString giveAbsolutePath( const QString & fileName );
+ bool openRelativeFile( const QString & fileName );
+
+ Generator * generator;
+ QValueVector< KPDFPage * > pages_vector;
+ class KPDFDocumentPrivate * d;
+
+ private slots:
+ void saveDocumentInfo() const;
+ void slotTimedMemoryCheck();
+};
+
+
+/**
+ * @short A view on the document.
+ *
+ * The Viewport structure is the 'current view' over the document. Contained
+ * data is broadcasted between observers to syncronize their viewports to get
+ * the 'I scroll one view and others scroll too' views.
+ */
+class DocumentViewport
+{
+ public:
+ /** data fields **/
+ // the page nearest the center of the viewport
+ int pageNumber;
+
+ // enum definitions
+ enum Position { Center = 1, TopLeft = 2};
+
+ // if reCenter.enabled, this contains the viewport center
+ struct {
+ bool enabled;
+ double normalizedX;
+ double normalizedY;
+ Position pos;
+ } rePos;
+
+ // if autoFit.enabled, page must be autofitted in the viewport
+ struct {
+ bool enabled;
+ bool width;
+ bool height;
+ } autoFit;
+
+ /** class methods **/
+ // allowed constructors, don't use others
+ DocumentViewport( int pageNumber = -1 );
+ DocumentViewport( const QString & xmlDesc );
+ QString toString() const;
+ bool operator==( const DocumentViewport & vp ) const;
+};
+
+/**
+ * @short A dom tree containing informations about the document.
+ *
+ * The Info structure can be filled in by generators to display metadata
+ * about the currently opened file.
+ */
+class DocumentInfo : public QDomDocument
+{
+ public:
+ DocumentInfo();
+
+ /**
+ * Sets a value for a special key. The title should be an i18n'ed
+ * string, since it's used in the document information dialog.
+ */
+ void set( const QString &key, const QString &value,
+ const QString &title = QString() );
+
+ /**
+ * Returns the value for a given key or an empty string when the
+ * key doesn't exist.
+ */
+ QString get( const QString &key ) const;
+};
+
+/**
+ * @short A Dom tree that describes the Table of Contents.
+ *
+ * The Synopsis (TOC or Table Of Contents for friends) is represented via
+ * a dom tree where each nod has an internal name (displayed in the listview)
+ * and one or more attributes.
+ *
+ * In the tree the tag name is the 'screen' name of the entry. A tag can have
+ * attributes. Here follows the list of tag attributes with meaning:
+ * - Viewport: A string description of the referred viewport
+ * - ViewportName: A 'named reference' to the viewport that must be converted
+ * using getMetaData( "NamedViewport", *viewport_name* )
+ */
+class DocumentSynopsis : public QDomDocument
+{
+ public:
+ DocumentSynopsis();
+};
+
+#endif
diff --git a/kpdf/core/generator.h b/kpdf/core/generator.h
new file mode 100644
index 00000000..ca0ea015
--- /dev/null
+++ b/kpdf/core/generator.h
@@ -0,0 +1,115 @@
+/***************************************************************************
+ * 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. *
+ ***************************************************************************/
+
+#ifndef _KPDF_GENERATOR_H_
+#define _KPDF_GENERATOR_H_
+
+#include <qobject.h>
+#include <qvaluevector.h>
+#include <qstring.h>
+#include "core/document.h"
+class KListView;
+class KPrinter;
+class KPDFPage;
+class KPDFLink;
+class PixmapRequest;
+
+/* Note: on contents generation and asyncronous queries.
+ * Many observers may want to request data syncronously or asyncronously.
+ * - Sync requests. These should be done in-place.
+ * - Async request must be done in real background. That usually means a
+ * thread, such as QThread derived classes.
+ * Once contents are available, they must be immediately stored in the
+ * KPDFPage they refer to, and a signal is emitted as soon as storing
+ * (even for sync or async queries) has been done.
+ */
+
+/**
+ * @short [Abstract Class] The information generator.
+ *
+ * Most of class members are pure virtuals and they must be implemented to
+ * provide a class that builds contents (graphics and text).
+ *
+ * Generation/query is requested by the 'KPDFDocument' class only, and that
+ * class stores the resulting data into 'KPDFPage's. The data will then be
+ * displayed by the GUI components (pageView, thumbnailList, etc..).
+ */
+class Generator : public QObject
+{
+ public:
+ /** virtual methods to reimplement **/
+ // load a document and fill up the pagesVector
+ virtual bool loadDocument( const QString & fileName, QValueVector< KPDFPage * > & pagesVector ) = 0;
+
+ // Document description and Table of contents
+ virtual const DocumentInfo * generateDocumentInfo() { return 0L; }
+ virtual const DocumentSynopsis * generateDocumentSynopsis() { return 0L; }
+
+ // DRM handling
+ virtual bool isAllowed( int /*Document::Permisison(s)*/ ) { return true; }
+
+ // page contents generation
+ virtual bool canGeneratePixmap() = 0;
+ virtual void generatePixmap( PixmapRequest * request ) = 0;
+ virtual void generateSyncTextPage( KPDFPage * page ) = 0;
+
+ // capability querying
+ virtual bool supportsSearching() const = 0;
+ virtual bool hasFonts() const = 0;
+
+ // font related
+ virtual void putFontInfo(KListView *list) = 0;
+
+ // print document using already configured kprinter
+ virtual bool print( KPrinter& /*printer*/ ) { return false; }
+ // access meta data of the generator
+ virtual QString getMetaData( const QString &/*key*/, const QString &/*option*/ ) { return QString(); }
+ // tell generator to re-parse configuration and return true if something changed
+ virtual bool reparseConfig() { return false; }
+
+ /** 'signals' to send events the KPDFDocument **/
+ // tell the document that the job has been completed
+ void signalRequestDone( PixmapRequest * request ) { m_document->requestDone( request ); }
+
+ /** constructor: takes the Document as a parameter **/
+ Generator( KPDFDocument * doc ) : m_document( doc ) {};
+
+ protected:
+ KPDFDocument * m_document;
+
+ private:
+ Generator();
+};
+
+/**
+ * @short Describes a pixmap type request.
+ */
+struct PixmapRequest
+{
+ PixmapRequest( int rId, int n, int w, int h, int p, bool a = false )
+ : id( rId ), pageNumber( n ), width( w ), height( h ),
+ priority( p ), async( a ), page( 0 ) {};
+
+ // observer id
+ int id;
+ // page number and size
+ int pageNumber;
+ int width;
+ int height;
+ // asyncronous request priority (less is better, 0 is max)
+ int priority;
+ // generate the pixmap in a thread and notify observer when done
+ bool async;
+
+ // this field is set by the Docuemnt prior passing the
+ // request to the generator
+ KPDFPage * page;
+};
+
+#endif
diff --git a/kpdf/core/generator_kimgio/Makefile.am b/kpdf/core/generator_kimgio/Makefile.am
new file mode 100644
index 00000000..5a93221d
--- /dev/null
+++ b/kpdf/core/generator_kimgio/Makefile.am
@@ -0,0 +1,6 @@
+INCLUDES = -I$(srcdir)/../../ $(all_includes)
+
+libgeneratorkimgio_la_LDFLAGS = $(all_libraries)
+libgeneratorkimgio_la_SOURCES = generator_kimgio.cpp
+
+noinst_LTLIBRARIES = libgeneratorkimgio.la
diff --git a/kpdf/core/generator_kimgio/generator_kimgio.cpp b/kpdf/core/generator_kimgio/generator_kimgio.cpp
new file mode 100644
index 00000000..bb754686
--- /dev/null
+++ b/kpdf/core/generator_kimgio/generator_kimgio.cpp
@@ -0,0 +1,72 @@
+/***************************************************************************
+ * Copyright (C) 2005 by Albert Astals Cid <tsdgeos@terra.es> *
+ * *
+ * 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 <qpainter.h>
+#include <qpixmap.h>
+#include <kprinter.h>
+
+#include "core/page.h"
+#include "generator_kimgio.h"
+
+KIMGIOGenerator::KIMGIOGenerator( KPDFDocument * document ) : Generator( document )
+{
+}
+
+KIMGIOGenerator::~KIMGIOGenerator()
+{
+ delete m_pix;
+}
+
+bool KIMGIOGenerator::loadDocument( const QString & fileName, QValueVector<KPDFPage*> & pagesVector )
+{
+ m_pix = new QPixmap(fileName);
+
+ pagesVector.resize( 1 );
+
+ KPDFPage * page = new KPDFPage( 0, m_pix->width(), m_pix->height(), 0 );
+ pagesVector[0] = page;
+
+ return true;
+}
+
+bool KIMGIOGenerator::canGeneratePixmap()
+{
+ return true;
+}
+
+void KIMGIOGenerator::generatePixmap( PixmapRequest * request )
+{
+ QPixmap *p = new QPixmap(*m_pix);
+ request->page->setPixmap(request->id, p);
+}
+
+void KIMGIOGenerator::generateSyncTextPage( KPDFPage * )
+{
+}
+
+bool KIMGIOGenerator::supportsSearching() const
+{
+ return false;
+}
+
+bool KIMGIOGenerator::hasFonts() const
+{
+ return false;
+}
+
+void KIMGIOGenerator::putFontInfo( KListView * )
+{
+}
+
+bool KIMGIOGenerator::print( KPrinter& printer )
+{
+ QPainter p(&printer);
+ p.drawPixmap(0, 0, *m_pix);
+ return true;
+}
diff --git a/kpdf/core/generator_kimgio/generator_kimgio.h b/kpdf/core/generator_kimgio/generator_kimgio.h
new file mode 100644
index 00000000..bda411f2
--- /dev/null
+++ b/kpdf/core/generator_kimgio/generator_kimgio.h
@@ -0,0 +1,43 @@
+/***************************************************************************
+ * Copyright (C) 2005 by Albert Astals Cid <tsdgeos@terra.es> *
+ * *
+ * 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. *
+ ***************************************************************************/
+
+#ifndef _KPDF_GENERATOR_PNG_H_
+#define _KPDF_GENERATOR_PNG_H_
+
+#include "core/generator.h"
+
+class KIMGIOGenerator : public Generator
+{
+ public:
+ KIMGIOGenerator( KPDFDocument * document );
+ virtual ~KIMGIOGenerator();
+
+ // [INHERITED] load a document and fill up the pagesVector
+ bool loadDocument( const QString & fileName, QValueVector<KPDFPage*> & pagesVector );
+
+ // [INHERITED] perform actions on document / pages
+ bool canGeneratePixmap();
+ void generatePixmap( PixmapRequest * request );
+ void generateSyncTextPage( KPDFPage * page );
+
+ // [INHERITED] capability querying
+ bool supportsSearching() const;
+ bool hasFonts() const;
+
+ // font related
+ void putFontInfo(KListView *list);
+
+ // [INHERITED] print document using already configured kprinter
+ bool print( KPrinter& printer );
+
+ private:
+ QPixmap *m_pix;
+};
+
+#endif
diff --git a/kpdf/core/generator_pdf/Makefile.am b/kpdf/core/generator_pdf/Makefile.am
new file mode 100644
index 00000000..700740b7
--- /dev/null
+++ b/kpdf/core/generator_pdf/Makefile.am
@@ -0,0 +1,10 @@
+INCLUDES = -I$(srcdir)/../.. -I$(srcdir)/../../xpdf -I$(srcdir)/../../xpdf/goo -I$(srcdir)/../../xpdf/splash -I$(top_builddir)/kpdf $(all_includes)
+
+libgeneratorpdf_la_LDFLAGS = $(all_libraries)
+libgeneratorpdf_la_SOURCES = generator_pdf.cpp gp_outputdev.cpp
+
+noinst_LTLIBRARIES = libgeneratorpdf.la
+
+KDE_OPTIONS = nofinal
+
+generator_pdf.lo: ../../conf/settings.h
diff --git a/kpdf/core/generator_pdf/generator_pdf.cpp b/kpdf/core/generator_pdf/generator_pdf.cpp
new file mode 100644
index 00000000..7ad34152
--- /dev/null
+++ b/kpdf/core/generator_pdf/generator_pdf.cpp
@@ -0,0 +1,1258 @@
+/***************************************************************************
+ * 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 <qpaintdevicemetrics.h>
+#include <qregexp.h>
+#include <qvariant.h>
+#include <kapplication.h>
+#include <klistview.h>
+#include <klocale.h>
+#include <kpassdlg.h>
+#include <kwallet.h>
+#include <kprinter.h>
+#include <ktempfile.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+
+// xpdf includes
+#include "xpdf/Object.h"
+#include "xpdf/Dict.h"
+#include "xpdf/Annot.h"
+#include "xpdf/PSOutputDev.h"
+#include "xpdf/TextOutputDev.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 "gp_outputdev.h"
+#include "core/observer.h" //for PAGEVIEW_ID
+#include "core/page.h"
+#include "core/pagetransition.h"
+#include "conf/settings.h"
+
+#include <stdlib.h>
+
+#include <config.h>
+
+// id for DATA_READY PDFPixmapGeneratorThread Event
+#define TGE_DATAREADY_ID 6969
+
+/** NOTES on threading:
+ * internal: thread race prevention is done via the 'docLock' mutex. the
+ * mutex is needed only because we have the asyncronous 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 asyncronously 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.
+ */
+
+PDFGenerator::PDFGenerator( KPDFDocument * doc )
+ : Generator( doc ), pdfdoc( 0 ), kpdfOutputDev( 0 ), ready( true ),
+ pixmapRequest( 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();
+ delete generatorThread;
+ }
+ // remove other internal objects
+ docLock.lock();
+ delete kpdfOutputDev;
+ delete pdfdoc;
+ docLock.unlock();
+}
+
+
+//BEGIN Generator inherited functions
+bool PDFGenerator::loadDocument( const QString & filePath, 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( filePath ) ), 0, 0 );
+
+ // 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;
+ int keep = 1;
+ while ( !pdfdoc->isOk() && pdfdoc->getErrorCode() == errEncrypted )
+ {
+ QCString password;
+
+ // 1.A. try to retrieve the first password from the kde wallet system
+ if ( !triedWallet )
+ {
+ QString walletName = KWallet::Wallet::NetworkWallet();
+ wallet = KWallet::Wallet::openWallet( walletName );
+ 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( filePath.section('/', -1, -1), retrievedPass ) )
+ password = retrievedPass.local8Bit();
+ }
+ 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
+ if ( KPasswordDialog::getPassword( password, prompt, wallet ? &keep : 0 ) != KPasswordDialog::Accepted )
+ break;
+ }
+
+ // 2. reopen the document using the password
+ GString * pwd2 = new GString( QString::fromLocal8Bit(password.data()).latin1() );
+ delete pdfdoc;
+ pdfdoc = new PDFDoc( new GString( QFile::encodeName( filePath ) ), pwd2, pwd2 );
+ delete pwd2;
+
+ // 3. if the password is correct and the user chose to remember it, store it to the wallet
+ if ( pdfdoc->isOk() && wallet && /*safety check*/ wallet->isOpen() && keep > 0 )
+ {
+ QString goodPass = QString::fromLocal8Bit( password.data() );
+ wallet->writePassword( filePath.section('/', -1, -1), goodPass );
+ }
+ }
+ if ( !pdfdoc->isOk() )
+ {
+ delete pdfdoc;
+ pdfdoc = 0;
+ return false;
+ }
+
+ // initialize output device for rendering current pdf
+ 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++ )
+ {
+ KPDFPage * page = new KPDFPage( i, pdfdoc->getPageCropWidth(i+1),
+ pdfdoc->getPageCropHeight(i+1),
+ pdfdoc->getPageRotate(i+1) );
+ addTransition( i, page );
+ pagesVector[i] = page;
+ }
+
+ // the file has been loaded correctly
+ return true;
+}
+
+
+const DocumentInfo * PDFGenerator::generateDocumentInfo()
+{
+ if ( docInfoDirty )
+ {
+ docLock.lock();
+ // compile internal structure reading properties from PDFDoc
+ docInfo.set( "title", getDocumentInfo("Title"), i18n("Title") );
+ docInfo.set( "subject", getDocumentInfo("Subject"), i18n("Subject") );
+ docInfo.set( "author", getDocumentInfo("Author"), i18n("Author") );
+ docInfo.set( "keywords", getDocumentInfo("Keywords"), i18n("Keywords") );
+ docInfo.set( "creator", getDocumentInfo("Creator"), i18n("Creator") );
+ docInfo.set( "producer", getDocumentInfo("Producer"), i18n("Producer") );
+ docInfo.set( "creationDate", getDocumentDate("CreationDate"), i18n("Created") );
+ docInfo.set( "modificationDate", getDocumentDate("ModDate"), i18n("Modified") );
+ docInfo.set( "mimeType", "application/pdf" );
+ if ( pdfdoc )
+ {
+ docInfo.set( "format", i18n( "PDF v. <version>", "PDF v. %1" )
+ .arg( QString::number( pdfdoc->getPDFVersion() ) ), 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( "pages", QString::number( pdfdoc->getCatalog()->getNumPages() ), i18n("Pages") );
+ }
+ else
+ {
+ docInfo.set( "format", "PDF", i18n( "Format" ) );
+ docInfo.set( "encryption", i18n( "Unknown Encryption" ), i18n( "Security" ) );
+ docInfo.set( "optimization", i18n( "Unknown Optimization" ), i18n( "Optimized" ) );
+ }
+ 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::generateDocumentSynopsis()
+{
+ 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;
+
+ docLock.lock();
+ docSyn = DocumentSynopsis();
+ if ( items->getLength() > 0 )
+ addSynopsisChildren( &docSyn, items );
+ docLock.unlock();
+
+ docSynopsisDirty = false;
+ return &docSyn;
+}
+
+bool PDFGenerator::isAllowed( int permissions )
+{
+#if !KPDF_FORCE_DRM
+ if (kapp->authorize("skip_drm") && !KpdfSettings::obeyDRM()) return true;
+#endif
+
+ bool b = true;
+ if (permissions & KPDFDocument::AllowModify) b = b && pdfdoc->okToChange();
+ if (permissions & KPDFDocument::AllowCopy) b = b && pdfdoc->okToCopy();
+ if (permissions & KPDFDocument::AllowPrint) b = b && pdfdoc->okToPrint();
+ if (permissions & KPDFDocument::AllowNotes) b = b && pdfdoc->okToAddNotes();
+ return b;
+}
+
+bool PDFGenerator::canGeneratePixmap()
+{
+ return ready;
+}
+
+void PDFGenerator::generatePixmap( PixmapRequest * request )
+{
+#ifndef NDEBUG
+ if ( !ready )
+ kdDebug() << "calling generatePixmap() when not in READY state!" << endl;
+#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
+ //kdDebug() << "id: " << request->id << " is requesting " << (request->async ? "ASYNC" : "sync") << " pixmap for page " << request->page->number() << " [" << request->width << " x " << request->height << "]." << endl;
+
+ /** asyncronous requests (generation in PDFPixmapGeneratorThread::run() **/
+ if ( request->async )
+ {
+ // start the generation into the thread
+ generatorThread->startGeneration( request );
+ return;
+ }
+
+ /** syncronous request: in-place generation **/
+ // compute dpi used to get an image with desired width and height
+ KPDFPage * page = request->page;
+ 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 genObjectRects = request->id & (PAGEVIEW_ID | PRESENTATION_ID);
+
+ // 0. LOCK [waits for the thread end]
+ docLock.lock();
+
+ // 1. Set OutputDev parameters and Generate contents
+ // note: thread safety is set on 'false' for the GUI (this) thread
+ kpdfOutputDev->setParams( request->width, request->height, genObjectRects, genObjectRects, false );
+ pdfdoc->displayPage( kpdfOutputDev, page->number() + 1, fakeDpiX, fakeDpiY, 0, false, true, false );
+ if ( genObjectRects )
+ pdfdoc->processLinks( kpdfOutputDev, page->number() + 1 );
+
+ // 2. Take data from outputdev and attach it to the Page
+ page->setPixmap( request->id, kpdfOutputDev->takePixmap() );
+ if ( genObjectRects )
+ page->setObjectRects( kpdfOutputDev->takeObjectRects() );
+
+ // 3. UNLOCK [re-enables shared access]
+ docLock.unlock();
+
+ if ( genTextPage )
+ generateSyncTextPage( page );
+
+ // update ready state
+ ready = true;
+
+ // notify the new generation
+ signalRequestDone( request );
+}
+
+void PDFGenerator::generateSyncTextPage( KPDFPage * page )
+{
+ // build a TextPage...
+ TextOutputDev td(NULL, gTrue, gFalse, gFalse);
+ docLock.lock();
+ pdfdoc->displayPage( &td, page->number()+1, 72, 72, 0, false, true, false );
+ // ..and attach it to the page
+ page->setSearchPage( td.takeText() );
+ docLock.unlock();
+}
+
+bool PDFGenerator::supportsSearching() const
+{
+ return true;
+}
+
+bool PDFGenerator::hasFonts() const
+{
+ return true;
+}
+
+void PDFGenerator::putFontInfo(KListView *list)
+{
+ Page *page;
+ Dict *resDict;
+ Annots *annots;
+ Object obj1, obj2;
+ int pg, i;
+
+ Ref *fonts;
+ int fontsLen;
+ int fontsSize;
+
+ list->addColumn(i18n("Name"));
+ list->addColumn(i18n("Type"));
+ list->addColumn(i18n("Embedded"));
+ list->addColumn(i18n("File"));
+
+ docLock.lock();
+
+ fonts = NULL;
+ fontsLen = fontsSize = 0;
+ QValueVector<Ref> visitedXObjects;
+ for (pg = 1; pg <= pdfdoc->getNumPages(); ++pg)
+ {
+ page = pdfdoc->getCatalog()->getPage(pg);
+ if ((resDict = page->getResourceDict()))
+ {
+ scanFonts(resDict, list, &fonts, fontsLen, fontsSize, &visitedXObjects);
+ }
+ annots = new Annots(pdfdoc->getXRef(), pdfdoc->getCatalog(), page->getAnnots(&obj1));
+ obj1.free();
+ for (i = 0; i < annots->getNumAnnots(); ++i)
+ {
+ if (annots->getAnnot(i)->getAppearance(&obj1)->isStream())
+ {
+ obj1.streamGetDict()->lookup("Resources", &obj2);
+ if (obj2.isDict())
+ {
+ scanFonts(obj2.getDict(), list, &fonts, fontsLen, fontsSize, &visitedXObjects);
+ }
+ obj2.free();
+ }
+ obj1.free();
+ }
+ delete annots;
+ }
+ gfree(fonts);
+
+ docLock.unlock();
+}
+
+bool PDFGenerator::print( KPrinter& printer )
+{
+ // PageSize is a CUPS artificially created setting
+ QString ps = printer.option("PageSize");
+ int paperWidth, paperHeight;
+ int marginTop, marginLeft, marginRight, marginBottom;
+ marginTop = (int)printer.option("kde-margin-top").toDouble();
+ marginLeft = (int)printer.option("kde-margin-left").toDouble();
+ marginRight = (int)printer.option("kde-margin-right").toDouble();
+ marginBottom = (int)printer.option("kde-margin-bottom").toDouble();
+ bool forceRasterize = printer.option("kde-kpdf-forceRaster").toInt();
+
+ if (ps.find(QRegExp("w\\d+h\\d+")) == 0)
+ {
+ // size not supported by Qt, CUPS gives us the size as wWIDTHhHEIGHT, at least on the printers i tester
+ // remove the w
+ ps = ps.mid(1);
+ int hPos = ps.find("h");
+ paperWidth = ps.left(hPos).toInt();
+ paperHeight = ps.mid(hPos+1).toInt();
+ }
+ else
+ {
+ // size is supported by Qt, we get either the pageSize name or nothing because the CUPS driver
+ // does not do any translation, then use KPrinter::pageSize to get the page size
+ KPrinter::PageSize qtPageSize;
+ if (!ps.isEmpty()) qtPageSize = pageNameToPageSize(ps);
+ else qtPageSize = printer.pageSize();
+
+ QPrinter dummy(QPrinter::PrinterResolution);
+ dummy.setFullPage(true);
+ dummy.setPageSize((QPrinter::PageSize)qtPageSize);
+
+ QPaintDeviceMetrics metrics(&dummy);
+ paperWidth = metrics.width();
+ paperHeight = metrics.height();
+ }
+
+ KTempFile tf( QString::null, ".ps" );
+ globalParams->setPSPaperWidth(paperWidth);
+ globalParams->setPSPaperHeight(paperHeight);
+ QString pstitle = getDocumentInfo("Title", true);
+ if ( pstitle.isEmpty() )
+ {
+ pstitle = m_document->currentDocument().fileName( false );
+ }
+ // this looks non-unicode-safe and it is. anything other than ASCII is not specified
+ // and some printers actually stop printing when they encounter non-ASCII characters in the
+ // Postscript %%Title tag
+ QCString pstitle8Bit = pstitle.latin1();
+ const char* pstitlechar;
+ if (!pstitle.isEmpty())
+ {
+ pstitlechar = pstitle8Bit.data();
+ for (unsigned char* p = (unsigned char*) pstitle8Bit.data(); *p; ++p)
+ if (*p >= 0x80)
+ *p = '?';
+
+ printer.setDocName(pstitle);
+ }
+ else
+ {
+ pstitlechar = 0;
+ }
+ PSOutputDev *psOut = new PSOutputDev(const_cast<char*>(tf.name().latin1()), const_cast<char*>(pstitlechar), pdfdoc->getXRef(), pdfdoc->getCatalog(), 1, pdfdoc->getNumPages(), psModePS, marginLeft, marginBottom, paperWidth - marginRight, paperHeight - marginTop, forceRasterize);
+
+ if (psOut->isOk())
+ {
+ double xScale = ((double)paperWidth - (double)marginLeft - (double)marginRight) / (double)paperWidth;
+ double yScale = ((double)paperHeight - (double)marginBottom - (double)marginTop) / (double)paperHeight;
+
+ if ( abs((int)(xScale * 100) - (int)(yScale * 100)) > 5 ) {
+ int result = KMessageBox::questionYesNo(m_document->widget(),
+ i18n("The margins you specified change the page aspect ratio. Do you want to print with the aspect ratio changed or do you want the margins to be adapted so that the aspect ratio is preserved?"),
+ i18n("Aspect ratio change"),
+ i18n("Print with specified margins"),
+ i18n("Print adapting margins to keep aspect ratio"),
+ "kpdfStrictlyObeyMargins");
+ if (result == KMessageBox::Yes) psOut->setScale(xScale, yScale);
+ }
+
+ QValueList<int> pageList;
+
+ if (!printer.previewOnly())
+ {
+ pageList = printer.pageList();
+ }
+ else
+ {
+ for(int i = 1; i <= pdfdoc->getNumPages(); i++) pageList.append(i);
+ }
+
+ QValueList<int>::const_iterator pIt = pageList.begin(), pEnd = pageList.end();
+ docLock.lock();
+ for ( ; pIt != pEnd; ++pIt )
+ {
+ pdfdoc->displayPage(psOut, *pIt, 72, 72, 0, false, globalParams->getPSCrop(), gTrue);
+ }
+ 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;
+ }
+}
+
+static GString *QStringToGString(const QString &s) {
+ int len = s.length();
+ char *cstring = (char *)gmallocn(s.length(), sizeof(char));
+ for (int i = 0; i < len; ++i)
+ cstring[i] = s.at(i).unicode();
+ return new GString(cstring, len);
+}
+
+static QString unicodeToQString(Unicode* u, int len) {
+ QString ret;
+ ret.setLength(len);
+ QChar* qch = (QChar*) ret.unicode();
+ for (;len;--len)
+ *qch++ = (QChar) *u++;
+ return ret;
+}
+
+static QString UnicodeParsedString(GString *s1) {
+ GBool isUnicode;
+ int i;
+ Unicode u;
+ QString result;
+ if ( ( s1->getChar(0) & 0xff ) == 0xfe && ( s1->getChar(1) & 0xff ) == 0xff )
+ {
+ isUnicode = gTrue;
+ i = 2;
+ }
+ else
+ {
+ isUnicode = gFalse;
+ i = 0;
+ }
+ while ( i < s1->getLength() )
+ {
+ if ( isUnicode )
+ {
+ u = ( ( s1->getChar(i) & 0xff ) << 8 ) | ( s1->getChar(i+1) & 0xff );
+ i += 2;
+ }
+ else
+ {
+ u = s1->getChar(i) & 0xff;
+ ++i;
+ }
+ result += unicodeToQString( &u, 1 );
+ }
+ return result;
+}
+
+QString PDFGenerator::getMetaData( const QString & key, const QString & option )
+{
+ if ( key == "StartFullScreen" )
+ {
+ // asking for the 'start in fullscreen mode' (pdf property)
+ if ( pdfdoc->getCatalog()->getPageMode() == Catalog::FullScreen )
+ return "yes";
+ }
+ else if ( key == "NamedViewport" && !option.isEmpty() )
+ {
+ // asking for the page related to a 'named link destination'. the
+ // option is the link name. @see addSynopsisChildren.
+ DocumentViewport viewport;
+ GString * namedDest = QStringToGString(option);
+ docLock.lock();
+ LinkDest * destination = pdfdoc->findDest( namedDest );
+ if ( destination )
+ {
+ fillViewportFromLink( viewport, destination );
+ }
+ docLock.unlock();
+ delete namedDest;
+ if ( viewport.pageNumber >= 0 )
+ return viewport.toString();
+ }
+ else if ( key == "OpenTOC" )
+ {
+ if ( pdfdoc->getCatalog()->getPageMode() == Catalog::UseOutlines )
+ return "yes";
+ }
+ return QString();
+}
+
+bool PDFGenerator::reparseConfig()
+{
+ // load paper color from Settings or use the white default color
+ QColor color = ( (KpdfSettings::renderMode() == KpdfSettings::EnumRenderMode::Paper ) &&
+ KpdfSettings::changeColors() ) ? KpdfSettings::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[0] = paperColor.red();
+ splashCol[1] = paperColor.green();
+ splashCol[2] = paperColor.blue();
+ // rebuild the output device using the new paper color and initialize it
+ docLock.lock();
+ delete kpdfOutputDev;
+ kpdfOutputDev = new KPDFOutputDev( splashCol );
+ if ( pdfdoc )
+ kpdfOutputDev->initDevice( pdfdoc );
+ docLock.unlock();
+ return true;
+ }
+ return false;
+}
+//END Generator inherited functions
+
+void PDFGenerator::scanFonts(Dict *resDict, KListView *list, Ref **fonts, int &fontsLen, int &fontsSize, QValueVector<Ref> *visitedXObjects)
+{
+ Object obj1, obj2, xObjDict, xObj, xObj2, resObj;
+ Ref r;
+ GfxFontDict *gfxFontDict;
+ GfxFont *font;
+ int i;
+
+ // scan the fonts in this resource dictionary
+ gfxFontDict = NULL;
+ resDict->lookupNF("Font", &obj1);
+ if (obj1.isRef())
+ {
+ obj1.fetch(pdfdoc->getXRef(), &obj2);
+ if (obj2.isDict())
+ {
+ r = obj1.getRef();
+ gfxFontDict = new GfxFontDict(pdfdoc->getXRef(), &r, obj2.getDict());
+ }
+ obj2.free();
+ }
+ else if (obj1.isDict())
+ {
+ gfxFontDict = new GfxFontDict(pdfdoc->getXRef(), NULL, obj1.getDict());
+ }
+ if (gfxFontDict)
+ {
+ for (i = 0; i < gfxFontDict->getNumFonts(); ++i)
+ {
+ if ((font = gfxFontDict->getFont(i))) scanFont(font, list, fonts, fontsLen, fontsSize);
+ }
+ delete gfxFontDict;
+ }
+ obj1.free();
+
+ // recursively scan any resource dictionaries in objects in this
+ // resource dictionary
+ resDict->lookup("XObject", &xObjDict);
+ if (xObjDict.isDict()) {
+ for (i = 0; i < xObjDict.dictGetLength(); ++i) {
+ xObjDict.dictGetValNF(i, &xObj);
+ if (xObj.isRef()) {
+ bool alreadySeen = false;
+ // check for an already-seen XObject
+ for (int k = 0; k < visitedXObjects->count(); ++k) {
+ if (xObj.getRef().num == visitedXObjects->at(k).num &&
+ xObj.getRef().gen == visitedXObjects->at(k).gen) {
+ alreadySeen = true;
+ }
+ }
+
+ if (alreadySeen) {
+ xObj.free();
+ continue;
+ }
+
+ visitedXObjects->append(xObj.getRef());
+ }
+
+ xObj.fetch(pdfdoc->getXRef(), &xObj2);
+
+ if (xObj2.isStream()) {
+ xObj2.streamGetDict()->lookup("Resources", &resObj);
+ if (resObj.isDict() && resObj.getDict() != resDict) {
+ scanFonts(resObj.getDict(), list, fonts, fontsLen, fontsSize, visitedXObjects);
+ }
+ resObj.free();
+ }
+ xObj.free();
+ xObj2.free();
+ }
+ }
+ xObjDict.free();
+}
+
+void PDFGenerator::scanFont(GfxFont *font, KListView *list, Ref **fonts, int &fontsLen, int &fontsSize)
+{
+ Ref fontRef, embRef;
+ Object fontObj, toUnicodeObj;
+ GString *name;
+ GBool emb;
+ int i;
+
+ QString fontTypeNames[12] = {
+ i18n("unknown"),
+ i18n("Type 1"),
+ i18n("Type 1C"),
+ i18n("OT means OpenType", "Type 1C (OT)"),
+ i18n("Type 3"),
+ i18n("TrueType"),
+ i18n("OT means OpenType", "TrueType (OT)"),
+ i18n("CID Type 0"),
+ i18n("CID Type 0C"),
+ i18n("OT means OpenType", "CID Type 0C (OT)"),
+ i18n("CID TrueType"),
+ i18n("OT means OpenType", "CID TrueType (OT)")
+ };
+
+ fontRef = *font->getID();
+
+ // check for an already-seen font
+ for (i = 0; i < fontsLen; ++i)
+ {
+ if (fontRef.num == (*fonts)[i].num && fontRef.gen == (*fonts)[i].gen)
+ {
+ return;
+ }
+ }
+
+ // font name
+ name = font->getOrigName();
+
+ // check for an embedded font
+ if (font->getType() == fontType3) emb = gTrue;
+ else emb = font->getEmbeddedFontID(&embRef);
+
+ QString sName, sEmb, sPath;
+ if (name)
+ {
+ sName = name->getCString();
+ if (!emb)
+ {
+ DisplayFontParam *dfp = globalParams->getDisplayFont(name);
+ if (dfp)
+ {
+ if (dfp -> kind == displayFontT1) sPath = dfp->t1.fileName->getCString();
+ else sPath = dfp->tt.fileName->getCString();
+ }
+ else sPath = i18n("-");
+ }
+ else sPath = i18n("-");
+ }
+ else
+ {
+ sName = i18n("[none]");
+ sPath = i18n("-");
+ }
+ sEmb = emb ? i18n("Yes") : i18n("No");
+ new KListViewItem(list, sName, fontTypeNames[font->getType()], sEmb, sPath);
+
+ // add this font to the list
+ if (fontsLen == fontsSize)
+ {
+ fontsSize += 32;
+ *fonts = (Ref *)grealloc(*fonts, fontsSize * sizeof(Ref));
+ }
+ (*fonts)[fontsLen++] = *font->getID();
+}
+
+QString PDFGenerator::getDocumentInfo( const QString & data, bool canReturnNull ) const
+// note: MUTEX is LOCKED while calling this
+{
+ // [Albert] Code adapted from pdfinfo.cc on xpdf
+ Object info;
+ if ( !pdfdoc )
+ return canReturnNull ? QString::null : i18n( "Unknown" );
+
+ pdfdoc->getDocInfo( &info );
+ if ( !info.isDict() )
+ return canReturnNull ? QString::null : i18n( "Unknown" );
+
+ Object obj;
+ Dict *infoDict = info.getDict();
+
+ if ( infoDict->lookup( (char*)data.latin1(), &obj )->isString() )
+ {
+ QString result = UnicodeParsedString(obj.getString());
+ obj.free();
+ info.free();
+ return result;
+ }
+ obj.free();
+ info.free();
+ return canReturnNull ? QString::null : i18n( "Unknown" );
+}
+
+QString PDFGenerator::getDocumentDate( const QString & data ) const
+// note: MUTEX is LOCKED while calling this
+{
+ // [Albert] Code adapted from pdfinfo.cc on xpdf
+ if ( !pdfdoc )
+ return i18n( "Unknown Date" );
+
+ Object info;
+ pdfdoc->getDocInfo( &info );
+ if ( !info.isDict() )
+ return i18n( "Unknown Date" );
+
+ Object obj;
+ int year, mon, day, hour, min, sec;
+ Dict *infoDict = info.getDict();
+ UnicodeMap *uMap = globalParams->getTextEncoding();
+ QString result;
+
+ if ( !uMap )
+ return i18n( "Unknown Date" );
+
+ if ( infoDict->lookup( (char*)data.latin1(), &obj )->isString() )
+ {
+ QString s = UnicodeParsedString(obj.getString());
+ if ( s[0] == 'D' && s[1] == ':' )
+ s = s.mid(2);
+
+ if ( !s.isEmpty() && sscanf( s.latin1(), "%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() )
+ result = KGlobal::locale()->formatDateTime( QDateTime(d, t), false, true );
+ else
+ result = s;
+ }
+ else
+ result = s;
+ }
+ else
+ result = i18n( "Unknown Date" );
+ obj.free();
+ info.free();
+ return result;
+}
+
+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 );
+
+ // 1. create element using outlineItem's title as tagName
+ QString name;
+ Unicode * uniChar = outlineItem->getTitle();
+ int titleLength = outlineItem->getTitleLength();
+ name = unicodeToQString(uniChar, titleLength);
+ if ( name.isEmpty() )
+ continue;
+ QDomElement item = docSyn.createElement( name );
+ parent->appendChild( item );
+
+ // 2. find the page the link refers to
+ LinkAction * a = outlineItem->getAction();
+ if ( a && ( a->getKind() == actionGoTo || a->getKind() == actionGoToR ) )
+ {
+ // page number is contained/referenced in a LinkGoTo
+ LinkGoTo * g = static_cast< LinkGoTo * >( a );
+ LinkDest * destination = g->getDest();
+ if ( !destination && g->getNamedDest() )
+ {
+ // no 'destination' but an internal 'named reference'. we could
+ // get the destination for the page now, but it's VERY time consuming,
+ // so better storing the reference and provide the viewport as metadata
+ // on demand
+ GString *s = g->getNamedDest();
+ QChar *charArray = new QChar[s->getLength()];
+ for (int i = 0; i < s->getLength(); ++i) charArray[i] = QChar(s->getCString()[i]);
+ QString option(charArray, s->getLength());
+ item.setAttribute( "ViewportName", option );
+ delete[] charArray;
+ }
+ else if ( destination && destination->isOk() )
+ {
+ DocumentViewport vp;
+ fillViewportFromLink( vp, destination );
+ item.setAttribute( "Viewport", vp.toString() );
+ }
+ if ( a->getKind() == actionGoToR )
+ {
+ LinkGoToR * g2 = static_cast< LinkGoToR * >( a );
+ item.setAttribute( "ExternalFileName", g2->getFileName()->getCString() );
+ }
+ }
+
+ item.setAttribute( "Open", QVariant( (bool)outlineItem->isOpen() ).toString() );
+
+ // 3. recursively descend over children
+ outlineItem->open();
+ GList * children = outlineItem->getKids();
+ if ( children )
+ addSynopsisChildren( &item, children );
+ }
+}
+
+void PDFGenerator::fillViewportFromLink( DocumentViewport &viewport, LinkDest *destination )
+{
+ if ( !destination->isPageRef() )
+ viewport.pageNumber = destination->getPageNum() - 1;
+ else
+ {
+ Ref ref = destination->getPageRef();
+ viewport.pageNumber = pdfdoc->findPage( ref.num, ref.gen ) - 1;
+ }
+
+ if (viewport.pageNumber < 0) return;
+ if (viewport.pageNumber >= pdfdoc->getNumPages()) return;
+
+ // get destination position
+ // TODO add other attributes to the viewport (taken from link)
+// switch ( destination->getKind() )
+// {
+// case destXYZ:
+ if (destination->getChangeLeft() || destination->getChangeTop())
+ {
+ double CTM[6];
+ Page *page = pdfdoc->getCatalog()->getPage( viewport.pageNumber + 1 );
+ // TODO remember to change this if we implement DPI and/or rotation
+ page->getDefaultCTM(CTM, 72.0, 72.0, 0, gFalse, gTrue);
+
+ int left, top;
+ // this is OutputDev::cvtUserToDev
+ left = (int)(CTM[0] * destination->getLeft() + CTM[2] * destination->getTop() + CTM[4] + 0.5);
+ top = (int)(CTM[1] * destination->getLeft() + CTM[3] * destination->getTop() + CTM[5] + 0.5);
+
+ viewport.rePos.normalizedX = (double)left / (double)page->getCropWidth();
+ viewport.rePos.normalizedY = (double)top / (double)page->getCropHeight();
+ viewport.rePos.enabled = true;
+ viewport.rePos.pos = DocumentViewport::TopLeft;
+ }
+ /* TODO
+ if ( dest->getChangeZoom() )
+ make zoom change*/
+/* break;
+
+ default:
+ // implement the others cases
+ break;*/
+// }
+}
+
+void PDFGenerator::addTransition( int pageNumber, KPDFPage * page )
+{
+ Page *pdfPage = pdfdoc->getCatalog()->getPage( pageNumber + 1 );
+ if ( !pdfPage )
+ return;
+
+ PageTransition *pdfTransition = pdfPage->getTransition();
+ if ( !pdfTransition || pdfTransition->getType() == PageTransition::Replace )
+ return;
+
+ KPDFPageTransition *transition = new KPDFPageTransition();
+ switch ( pdfTransition->getType() ) {
+ case PageTransition::Replace:
+ // won't get here, added to avoid warning
+ break;
+ case PageTransition::Split:
+ transition->setType( KPDFPageTransition::Split );
+ break;
+ case PageTransition::Blinds:
+ transition->setType( KPDFPageTransition::Blinds );
+ break;
+ case PageTransition::Box:
+ transition->setType( KPDFPageTransition::Box );
+ break;
+ case PageTransition::Wipe:
+ transition->setType( KPDFPageTransition::Wipe );
+ break;
+ case PageTransition::Dissolve:
+ transition->setType( KPDFPageTransition::Dissolve );
+ break;
+ case PageTransition::Glitter:
+ transition->setType( KPDFPageTransition::Glitter );
+ break;
+ case PageTransition::Fly:
+ transition->setType( KPDFPageTransition::Fly );
+ break;
+ case PageTransition::Push:
+ transition->setType( KPDFPageTransition::Push );
+ break;
+ case PageTransition::Cover:
+ transition->setType( KPDFPageTransition::Cover );
+ break;
+ case PageTransition::Uncover:
+ transition->setType( KPDFPageTransition::Uncover );
+ break;
+ case PageTransition::Fade:
+ transition->setType( KPDFPageTransition::Fade );
+ break;
+ }
+
+ transition->setDuration( pdfTransition->getDuration() );
+
+ switch ( pdfTransition->getAlignment() ) {
+ case PageTransition::Horizontal:
+ transition->setAlignment( KPDFPageTransition::Horizontal );
+ break;
+ case PageTransition::Vertical:
+ transition->setAlignment( KPDFPageTransition::Vertical );
+ break;
+ }
+
+ switch ( pdfTransition->getDirection() ) {
+ case PageTransition::Inward:
+ transition->setDirection( KPDFPageTransition::Inward );
+ break;
+ case PageTransition::Outward:
+ transition->setDirection( KPDFPageTransition::Outward );
+ break;
+ }
+
+ transition->setAngle( pdfTransition->getAngle() );
+ transition->setScale( pdfTransition->getScale() );
+ transition->setIsRectangular( pdfTransition->isRectangular() == gTrue );
+
+ page->setTransition( transition );
+}
+
+
+
+void PDFGenerator::customEvent( QCustomEvent * event )
+{
+ // catch generator 'ready events' only
+ if ( event->type() != TGE_DATAREADY_ID )
+ return;
+
+#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
+
+ // 1. 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();
+ }
+
+ // 2. put thread's generated data into the KPDFPage
+ PixmapRequest * request = static_cast< PixmapRequest * >( event->data() );
+ QImage * outImage = generatorThread->takeImage();
+ TextPage * outTextPage = generatorThread->takeTextPage();
+ QValueList< ObjectRect * > outRects = generatorThread->takeObjectRects();
+
+ request->page->setPixmap( request->id, new QPixmap( *outImage ) );
+ delete outImage;
+ if ( outTextPage )
+ request->page->setSearchPage( outTextPage );
+ if ( !outRects.isEmpty() )
+ request->page->setObjectRects( outRects );
+
+ // 3. tell generator that data has been taken
+ generatorThread->endGeneration();
+
+ // update ready state
+ ready = true;
+ // notify the new generation
+ signalRequestDone( request );
+}
+
+
+
+/** 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< ObjectRect * > m_rects;
+ bool m_rectsTaken;
+};
+
+PDFPixmapGeneratorThread::PDFPixmapGeneratorThread( PDFGenerator * gen )
+ : d( new PPGThreadPrivate() )
+{
+ d->generator = gen;
+ d->currentRequest = 0;
+ d->m_image = 0;
+ d->m_textPage = 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;
+ delete d->m_textPage;
+ if ( !d->m_rectsTaken && d->m_rects.count() )
+ {
+ QValueList< ObjectRect * >::iterator it = d->m_rects.begin(), end = d->m_rects.end();
+ for ( ; it != end; ++it )
+ delete *it;
+ }
+ delete d->currentRequest;
+ // delete internal storage structure
+ 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;
+ 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 )
+ {
+ kdDebug() << "PDFPixmapGeneratorThread: 'end generation' called "
+ << "but generation was not started." << endl;
+ return;
+ }
+#endif
+ // reset internal members preparing for a new generation
+ d->currentRequest = 0;
+}
+
+QImage * PDFPixmapGeneratorThread::takeImage() const
+{
+ QImage * img = d->m_image;
+ d->m_image = 0;
+ return img;
+}
+
+TextPage * PDFPixmapGeneratorThread::takeTextPage() const
+{
+ TextPage * tp = d->m_textPage;
+ d->m_textPage = 0;
+ return tp;
+}
+
+QValueList< ObjectRect * > PDFPixmapGeneratorThread::takeObjectRects() const
+{
+ d->m_rectsTaken = true;
+ return d->m_rects;
+}
+
+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
+ 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 genObjectRects = d->currentRequest->id & (PAGEVIEW_ID | PRESENTATION_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,
+ genObjectRects, genObjectRects, TRUE /*thread safety*/ );
+ d->generator->pdfdoc->displayPage( d->generator->kpdfOutputDev, page->number() + 1,
+ fakeDpiX, fakeDpiY, 0, false, true, false );
+ if ( genObjectRects )
+ d->generator->pdfdoc->processLinks( d->generator->kpdfOutputDev, page->number() + 1 );
+
+ // 2. grab data from the OutputDev and store it locally (note takeIMAGE)
+#ifndef NDEBUG
+ if ( d->m_image )
+ kdDebug() << "PDFPixmapGeneratorThread: previous image not taken" << endl;
+ if ( d->m_textPage )
+ kdDebug() << "PDFPixmapGeneratorThread: previous textpage not taken" << endl;
+#endif
+ d->m_image = d->generator->kpdfOutputDev->takeImage();
+ d->m_rects = d->generator->kpdfOutputDev->takeObjectRects();
+ d->m_rectsTaken = false;
+
+ if ( genTextPage )
+ {
+ TextOutputDev td(NULL, gTrue, gFalse, gFalse);
+ d->generator->pdfdoc->displayPage( &td, page->number()+1, 72, 72, 0, false, true, false );
+ // ..and attach it to the page
+ d->m_textPage = td.takeText();
+ }
+
+ // 3. [UNLOCK] mutex
+ d->generator->docLock.unlock();
+
+ // notify the GUI thread that data is pending and can be read
+ QCustomEvent * readyEvent = new QCustomEvent( TGE_DATAREADY_ID );
+ readyEvent->setData( d->currentRequest );
+ QApplication::postEvent( d->generator, readyEvent );
+}
diff --git a/kpdf/core/generator_pdf/generator_pdf.h b/kpdf/core/generator_pdf/generator_pdf.h
new file mode 100644
index 00000000..ca267b18
--- /dev/null
+++ b/kpdf/core/generator_pdf/generator_pdf.h
@@ -0,0 +1,150 @@
+/***************************************************************************
+ * 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. *
+ ***************************************************************************/
+
+#ifndef _KPDF_GENERATOR_PDF_H_
+#define _KPDF_GENERATOR_PDF_H_
+
+#include <qmutex.h>
+#include <qcolor.h>
+#include <qstring.h>
+#include <qthread.h>
+#include "core/generator.h"
+#include "core/document.h"
+#include "core/link.h"
+
+class Dict;
+class GfxFont;
+class LinkDest;
+class Ref;
+class PDFDoc;
+class GList;
+class TextPage;
+
+class ObjectRect;
+class KPDFOutputDev;
+class PDFPixmapGeneratorThread;
+
+/**
+ * @short A generator that builds contents from a PDF document.
+ *
+ * All Generator features are supported and implented by this one.
+ * Internally this holds a reference to xpdf's core objects and provides
+ * contents generation using the PDFDoc object and a couple of OutputDevices
+ * called KPDFOutputDev and KPDFTextDev (both defined in gp_outputdev.h).
+ *
+ * For generating page contents we tell PDFDoc to render a page and grab
+ * contents from out OutputDevs when rendering finishes.
+ *
+ * Background asyncronous contents providing is done via a QThread inherited
+ * class defined at the bottom of the file.
+ */
+class PDFGenerator : public Generator
+{
+ public:
+ PDFGenerator( KPDFDocument * document );
+ virtual ~PDFGenerator();
+
+ // [INHERITED] load a document and fill up the pagesVector
+ bool loadDocument( const QString & fileName, QValueVector<KPDFPage*> & pagesVector );
+
+ // [INHERITED] document informations
+ const DocumentInfo * generateDocumentInfo();
+ const DocumentSynopsis * generateDocumentSynopsis();
+
+ // [INHERITED] document informations
+ bool isAllowed( int permissions );
+
+ // [INHERITED] perform actions on document / pages
+ bool canGeneratePixmap();
+ void generatePixmap( PixmapRequest * request );
+ void generateSyncTextPage( KPDFPage * page );
+
+ // [INHERITED] capability querying
+ bool supportsSearching() const;
+ bool hasFonts() const;
+
+ // [INHERITED] font related
+ void putFontInfo(KListView *list);
+
+ // [INHERITED] print page using an already configured kprinter
+ bool print( KPrinter& printer );
+
+ // [INHERITED] reply to some metadata requests
+ QString getMetaData( const QString & key, const QString & option );
+
+ // [INHERITED] reparse configuration
+ bool reparseConfig();
+
+ private:
+ // friend class to access private document related variables
+ friend class PDFPixmapGeneratorThread;
+
+ void scanFonts(Dict *resDict, KListView *list, Ref **fonts, int &fontsLen, int &fontsSize, QValueVector<Ref> *visitedXObjects);
+ void scanFont(GfxFont *font, KListView *list, Ref **fonts, int &fontsLen, int &fontsSize);
+
+ void fillViewportFromLink( DocumentViewport &viewport, LinkDest *destination );
+
+ // private functions for accessing document informations via PDFDoc
+ QString getDocumentInfo( const QString & data, bool canReturnNull = false ) const;
+ QString getDocumentDate( const QString & data ) const;
+ // private function for creating the document synopsis hieracy
+ void addSynopsisChildren( QDomNode * parent, GList * items );
+ // private function for creating the transition information
+ void addTransition( int pageNumber, KPDFPage * page );
+ // (async related) receive data from the generator thread
+ void customEvent( QCustomEvent * );
+
+ // xpdf dependant stuff
+ QMutex docLock;
+ PDFDoc * pdfdoc;
+ KPDFOutputDev * kpdfOutputDev;
+ QColor paperColor;
+
+ // asyncronous generation related stuff
+ PDFPixmapGeneratorThread * generatorThread;
+
+ // misc variables for document info and synopsis caching
+ bool ready;
+ PixmapRequest * pixmapRequest;
+ bool docInfoDirty;
+ DocumentInfo docInfo;
+ bool docSynopsisDirty;
+ DocumentSynopsis docSyn;
+};
+
+
+/**
+ * @short A thread that builds contents for PDFGenerator in the background.
+ *
+ */
+class PDFPixmapGeneratorThread : public QThread
+{
+ public:
+ PDFPixmapGeneratorThread( PDFGenerator * generator );
+ ~PDFPixmapGeneratorThread();
+
+ // set the request to the thread (it will be reparented)
+ void startGeneration( PixmapRequest * request );
+ // end generation
+ void endGeneration();
+
+ // methods for getting contents from the GUI thread
+ QImage * takeImage() const;
+ TextPage * takeTextPage() const;
+ QValueList< ObjectRect * > takeObjectRects() const;
+
+ private:
+ // can't be called from the outside (but from startGeneration)
+ void run();
+
+ class PPGThreadPrivate * d;
+};
+
+#endif
diff --git a/kpdf/core/generator_pdf/gp_outputdev.cpp b/kpdf/core/generator_pdf/gp_outputdev.cpp
new file mode 100644
index 00000000..c55ccd7c
--- /dev/null
+++ b/kpdf/core/generator_pdf/gp_outputdev.cpp
@@ -0,0 +1,397 @@
+/***************************************************************************
+ * Copyright (C) 2003-2004 by Christophe Devriese *
+ * <Christophe.Devriese@student.kuleuven.ac.be> *
+ * Copyright (C) 2003 by Andy Goossens <andygoossens@telenet.be> *
+ * Copyright (C) 2003 by Scott Wheeler <wheeler@kde.org> *
+ * Copyright (C) 2003 by Ingo Klöcker <kloecker@kde.org> *
+ * Copyright (C) 2003 by Will Andrews <will@csociety.org> *
+ * Copyright (C) 2004 by Dominique Devriese <devriese@kde.org> *
+ * Copyright (C) 2004 by Waldo Bastian <bastian@kde.org> *
+ * Copyright (C) 2004 by Albert Astals Cid <tsdgeos@terra.es> *
+ * *
+ * 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. *
+ ***************************************************************************/
+
+#ifdef __GNUC__
+#pragma implementation
+#endif
+
+#include <kdebug.h>
+#include <qpixmap.h>
+#include <qimage.h>
+
+#include "gp_outputdev.h"
+#include "generator_pdf.h"
+#include "core/document.h" // for DocumentViewport
+#include "core/page.h"
+#include "core/link.h"
+#include "xpdf/Link.h"
+#include "xpdf/GfxState.h"
+#include "xpdf/TextOutputDev.h"
+#include "splash/SplashBitmap.h"
+
+//NOTE: XPDF/Splash *implementation dependant* code is marked with '###'
+
+/** KPDFOutputDev implementation **/
+
+KPDFOutputDev::KPDFOutputDev( SplashColor paperColor )
+ : SplashOutputDev( splashModeRGB8, 4, false, paperColor ),
+ m_doc( 0 ), m_pixmap( 0 ), m_image( 0 )
+{
+}
+
+KPDFOutputDev::~KPDFOutputDev()
+{
+ clear();
+}
+
+void KPDFOutputDev::initDevice( PDFDoc * pdfDoc )
+{
+ m_doc = pdfDoc;
+ startDoc( pdfDoc->getXRef() );
+}
+
+void KPDFOutputDev::setParams( int width, int height, bool genL, bool genI, bool safe )
+{
+ clear();
+
+ m_pixmapWidth = width;
+ m_pixmapHeight = height;
+
+ m_qtThreadSafety = safe;
+ m_generateLinks = genL;
+ m_generateImages = genI;
+}
+
+QPixmap * KPDFOutputDev::takePixmap()
+{
+ QPixmap * pix = m_pixmap;
+ m_pixmap = 0;
+ return pix;
+}
+
+QImage * KPDFOutputDev::takeImage()
+{
+ QImage * img = m_image;
+ m_image = 0;
+ return img;
+}
+
+QValueList< ObjectRect * > KPDFOutputDev::takeObjectRects()
+{
+ if ( m_rects.isEmpty() )
+ return m_rects;
+ QValueList< ObjectRect * > rectsCopy( m_rects );
+ m_rects.clear();
+ return rectsCopy;
+}
+
+//BEGIN - OutputDev hooked calls
+void KPDFOutputDev::endPage()
+{
+ SplashOutputDev::endPage();
+
+ int bh = getBitmap()->getHeight(),
+ bw = getBitmap()->getWidth();
+ // TODO The below loop can be avoided if using the code that is commented here and
+ // we change splashModeRGB8 to splashModeARGB8 the problem is that then bug101800.pdf
+ // does not work
+/* SplashColorPtr dataPtr = getBitmap()->getDataPtr();
+ // construct a qimage SHARING the raw bitmap data in memory
+ QImage * img = new QImage( dataPtr, bw, bh, 32, 0, 0, QImage::IgnoreEndian );*/
+ QImage * img = new QImage( bw, bh, 32 );
+ SplashColorPtr pixel = new Guchar[4];
+ for (int i = 0; i < bw; i++)
+ {
+ for (int j = 0; j < bh; j++)
+ {
+ getBitmap()->getPixel(i, j, pixel);
+ img->setPixel( i, j, qRgb( pixel[0], pixel[1], pixel[2] ) );
+ }
+ }
+ delete [] pixel;
+
+ // use the QImage or convert it immediately to QPixmap for better
+ // handling and memory unloading
+ if ( m_qtThreadSafety )
+ {
+ delete m_image;
+ // it may happen (in fact it doesn't) that we need a rescaling
+ if ( bw != m_pixmapWidth && bh != m_pixmapHeight )
+ m_image = new QImage( img->smoothScale( m_pixmapWidth, m_pixmapHeight ) );
+ else
+ // dereference image from the xpdf memory
+ m_image = new QImage( img->copy() );
+ }
+ else
+ {
+ delete m_pixmap;
+ // it may happen (in fact it doesn't) that we need a rescaling
+ if ( bw != m_pixmapWidth || bh != m_pixmapHeight )
+ m_pixmap = new QPixmap( img->smoothScale( m_pixmapWidth, m_pixmapHeight ) );
+ else
+ m_pixmap = new QPixmap( *img );
+ }
+
+ // destroy the shared descriptor and (###) unload underlying xpdf bitmap
+ delete img;
+ SplashOutputDev::startPage( 0, NULL );
+}
+
+void KPDFOutputDev::processLink( Link * link, Catalog * catalog )
+{
+ if ( !link->isOk() )
+ return;
+
+ if ( m_generateLinks )
+ {
+ // create the link descriptor
+ KPDFLink * l = generateLink( link->getAction() );
+ if ( l )
+ {
+ // create the page rect representing the link
+ double x1, y1, x2, y2;
+ link->getRect( &x1, &y1, &x2, &y2 );
+ int left, top, right, bottom;
+ cvtUserToDev( x1, y1, &left, &top );
+ cvtUserToDev( x2, y2, &right, &bottom );
+ double nl = (double)left / (double)m_pixmapWidth,
+ nt = (double)top / (double)m_pixmapHeight,
+ nr = (double)right / (double)m_pixmapWidth,
+ nb = (double)bottom / (double)m_pixmapHeight;
+ // create the rect using normalized coords and attach the KPDFLink to it
+ ObjectRect * rect = new ObjectRect( nl, nt, nr, nb, ObjectRect::Link, l );
+ // add the ObjectRect to the vector container
+ m_rects.push_front( rect );
+ }
+ }
+ SplashOutputDev::processLink( link, catalog );
+}
+
+void KPDFOutputDev::drawImage( GfxState *state, Object *ref, Stream *str,
+ int _width, int _height, GfxImageColorMap *colorMap, int *maskColors, GBool inlineImg )
+{
+ if ( m_generateImages )
+ {
+ // find out image rect from the Coord Transform Matrix
+ double * ctm = state->getCTM();
+ int left = (int)ctm[4],
+ top = (int)ctm[5],
+ width = (int)ctm[0],
+ height = (int)ctm[3];
+ // normalize width
+ if ( width < 0 )
+ {
+ width = -width;
+ left -= width;
+ }
+ // normalize height
+ if ( height < 0 )
+ {
+ height = -height;
+ top -= height;
+ }
+ if ( width > 10 && height > 10 )
+ {
+ // build a descriptor for the image rect
+ double nl = (double)left / (double)m_pixmapWidth,
+ nt = (double)top / (double)m_pixmapHeight,
+ nr = (double)(left + width) / (double)m_pixmapWidth,
+ nb = (double)(top + height) / (double)m_pixmapHeight;
+ // create the rect using normalized coords and set it of KPDFImage type
+ ObjectRect * rect = new ObjectRect( nl, nt, nr, nb, ObjectRect::Image, 0 );
+ // add the ObjectRect to the vector container
+ m_rects.push_back( rect );
+ }
+ }
+ SplashOutputDev::drawImage( state, ref, str, _width, _height, colorMap, maskColors, inlineImg );
+}
+//END - OutputDev hooked calls
+
+//BEGIN - private helpers
+void KPDFOutputDev::clear()
+{
+ // delete rects
+ if ( m_rects.count() )
+ {
+ QValueList< ObjectRect * >::iterator it = m_rects.begin(), end = m_rects.end();
+ for ( ; it != end; ++it )
+ delete *it;
+ m_rects.clear();
+ }
+ // delete pixmap
+ if ( m_pixmap )
+ {
+ delete m_pixmap;
+ m_pixmap = 0;
+ }
+ // delete image
+ if ( m_image )
+ {
+ delete m_image;
+ m_image = 0;
+ }
+}
+
+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 if ( !strcmp( name, "Close" ) )
+ link = new KPDFLinkAction( KPDFLinkAction::Close );
+ else
+ kdDebug() << "Unknown named action: '" << name << "'" << endl;
+ }
+ break;
+
+ case actionURI:
+ link = new KPDFLinkBrowse( ((LinkURI *)a)->getURI()->getCString() );
+ break;
+
+ case actionMovie:
+/* { TODO this (Movie link)
+ 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;
+}
+
+DocumentViewport KPDFOutputDev::decodeViewport( GString * namedDest, LinkDest * dest )
+// note: this function is called when processing a page, when the MUTEX is already LOCKED
+{
+ DocumentViewport vp( -1 );
+ bool deleteDest = false;
+
+ if ( namedDest && !dest )
+ {
+ deleteDest = true;
+ dest = m_doc->findDest( namedDest );
+ }
+
+ if ( !dest || !dest->isOk() )
+ {
+ if (deleteDest) delete dest;
+ return vp;
+ }
+
+ // get destination page number
+ if ( !dest->isPageRef() )
+ vp.pageNumber = dest->getPageNum() - 1;
+ else
+ {
+ Ref ref = dest->getPageRef();
+ vp.pageNumber = m_doc->findPage( ref.num, ref.gen ) - 1;
+ }
+
+ // get destination position
+ // TODO add other attributes to the viewport (taken from link)
+ switch ( dest->getKind() )
+ {
+ case destXYZ:
+ if (dest->getChangeLeft() || dest->getChangeTop())
+ {
+ int left, top;
+ cvtUserToDev( dest->getLeft(), dest->getTop(), &left, &top );
+ vp.rePos.normalizedX = (double)left / (double)m_pixmapWidth;
+ vp.rePos.normalizedY = (double)top / (double)m_pixmapHeight;
+ vp.rePos.enabled = true;
+ vp.rePos.pos = DocumentViewport::TopLeft;
+ }
+ /* TODO
+ 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;
+ }
+
+ if (deleteDest) delete dest;
+ return vp;
+}
+//END - private helpers
+
diff --git a/kpdf/core/generator_pdf/gp_outputdev.h b/kpdf/core/generator_pdf/gp_outputdev.h
new file mode 100644
index 00000000..e08724e0
--- /dev/null
+++ b/kpdf/core/generator_pdf/gp_outputdev.h
@@ -0,0 +1,90 @@
+/***************************************************************************
+ * Copyright (C) 2003-2004 by Christophe Devriese *
+ * <Christophe.Devriese@student.kuleuven.ac.be> *
+ * Copyright (C) 2003 by Helio Chissini de Castro *
+ * <helio@conectiva.com.br> *
+ * Copyright (C) 2004 by Dominique Devriese <devriese@kde.org> *
+ * Copyright (C) 2004 by Albert Astals Cid <tsdgeos@terra.es> *
+ * *
+ * 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. *
+ ***************************************************************************/
+
+#ifndef KPDFOUTPUTDEV_H
+#define KPDFOUTPUTDEV_H
+
+#ifdef __GNUC__
+#pragma interface
+#endif
+
+#include <qvaluelist.h>
+#include "xpdf/PDFDoc.h" // for 'Object'
+#include "xpdf/SplashOutputDev.h"
+
+class QPixmap;
+class KPDFLink;
+class ObjectRect;
+class DocumentViewport;
+
+/**
+ * @short A SplashOutputDev renderer that grabs text and links.
+ *
+ * This output device:
+ * - renders the page using SplashOutputDev (its parent)
+ * - harvests text into a textPage (for searching text)
+ * - harvests links and collects them
+ * - collects images and collects them
+ */
+class KPDFOutputDev : public SplashOutputDev
+{
+ public:
+ KPDFOutputDev( SplashColor paperColor );
+ virtual ~KPDFOutputDev();
+
+ // 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 decodeLinks, bool decodeImages, bool qtThreadSafety = false );
+
+ // takes pointers out of the class (so deletion it's up to others)
+ QPixmap * takePixmap();
+ QImage * takeImage();
+ QValueList< ObjectRect * > takeObjectRects();
+
+ /** inherited from OutputDev */
+ // End a page.
+ virtual void endPage();
+ //----- link borders
+ virtual void processLink(Link *link, Catalog *catalog);
+ //----- image drawing
+ virtual void drawImage(GfxState *state, Object *ref, Stream *str, int width, int height,
+ GfxImageColorMap *colorMap, int *maskColors, GBool inlineImg);
+
+ private:
+ // delete all interal objects and data
+ void clear();
+ // generate a valid KPDFLink subclass (or null) from a xpdf's LinkAction
+ KPDFLink * generateLink( LinkAction * a );
+ // fills up a Viewport structure out of a given LinkGoto link
+ DocumentViewport decodeViewport( GString *, class LinkDest * );
+
+ // generator switches and parameters
+ bool m_qtThreadSafety;
+ bool m_generateLinks;
+ bool m_generateImages;
+ int m_pixmapWidth;
+ int m_pixmapHeight;
+
+ // Internal objects
+ PDFDoc * m_doc;
+ QPixmap * m_pixmap;
+ QImage * m_image;
+ QValueList< ObjectRect * > m_rects; // objectRects (links/images)
+};
+
+#endif
diff --git a/kpdf/core/link.cpp b/kpdf/core/link.cpp
new file mode 100644
index 00000000..d8e47a49
--- /dev/null
+++ b/kpdf/core/link.cpp
@@ -0,0 +1,65 @@
+/***************************************************************************
+ * 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. *
+ ***************************************************************************/
+
+// local includes
+#include "link.h"
+
+#include <klocale.h>
+
+KPDFLink::~KPDFLink()
+{
+}
+
+QString KPDFLinkGoto::linkTip() const
+{
+ return m_extFileName.isEmpty() ? ( m_vp.pageNumber != -1 ? i18n( "Go to page %1" ).arg( m_vp.pageNumber + 1 ) : QString::null ) : i18n("Open external file");
+}
+
+QString KPDFLinkExecute::linkTip() const
+{
+ return i18n( "Execute '%1'..." ).arg( m_fileName );
+}
+
+QString KPDFLinkBrowse::linkTip() const
+{
+ return m_url;
+}
+
+QString KPDFLinkAction::linkTip() const
+{
+ switch ( m_type )
+ {
+ case PageFirst:
+ return i18n( "First Page" );
+ case PagePrev:
+ return i18n( "Previous Page" );
+ case PageNext:
+ return i18n( "Next Page" );
+ case PageLast:
+ return i18n( "Last Page" );
+ case HistoryBack:
+ return i18n( "Back" );
+ case HistoryForward:
+ return i18n( "Forward" );
+ case Quit:
+ return i18n( "Quit" );
+ case Presentation:
+ return i18n( "Start Presentation" );
+ case EndPresentation:
+ return i18n( "End Presentation" );
+ case Find:
+ return i18n( "Find..." );
+ case GoToPage:
+ return i18n( "Go To Page..." );
+ case Close:
+ default: ;
+ }
+
+ return QString::null;
+}
diff --git a/kpdf/core/link.h b/kpdf/core/link.h
new file mode 100644
index 00000000..10420c50
--- /dev/null
+++ b/kpdf/core/link.h
@@ -0,0 +1,118 @@
+/***************************************************************************
+ * 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. *
+ ***************************************************************************/
+
+#ifndef _KPDF_LINK_H_
+#define _KPDF_LINK_H_
+
+#include <qstring.h>
+#include <qrect.h>
+#include "core/document.h" // for DocumentViewport
+
+/**
+ * @short Encapsulates data that describes a link.
+ *
+ * This is the base class for links. It makes mandatory for inherited
+ * widgets to reimplement the 'linkType' method and return the type of
+ * the link described by the reimplemented class.
+ */
+class KPDFLink
+{
+ public:
+ // get link type (inherited classes mustreturn an unique identifier)
+ enum LinkType { Goto, Execute, Browse, Action, Movie };
+ virtual LinkType linkType() const = 0;
+ virtual QString linkTip() const { return QString::null; }
+
+ // virtual destructor (remove warnings)
+ virtual ~KPDFLink();
+};
+
+
+/** Goto: a viewport and maybe a reference to an external filename **/
+class KPDFLinkGoto : public KPDFLink
+{
+ public:
+ // query for goto parameters
+ bool isExternal() const { return !m_extFileName.isEmpty(); }
+ const QString & fileName() const { return m_extFileName; }
+ const DocumentViewport & destViewport() const { return m_vp; }
+
+ // create a KPDFLink_Goto
+ KPDFLinkGoto( QString extFileName, const DocumentViewport & vp ) { m_extFileName = extFileName; m_vp = vp; }
+ LinkType linkType() const { return Goto; }
+ QString linkTip() const;
+
+ private:
+ QString m_extFileName;
+ DocumentViewport m_vp;
+};
+
+/** Execute: filename and parameters to execute **/
+class KPDFLinkExecute : public KPDFLink
+{
+ public:
+ // query for filename / parameters
+ const QString & fileName() const { return m_fileName; }
+ const QString & parameters() const { return m_parameters; }
+
+ // create a KPDFLink_Execute
+ KPDFLinkExecute( const QString & file, const QString & params ) { m_fileName = file; m_parameters = params; }
+ LinkType linkType() const { return Execute; }
+ QString linkTip() const;
+
+ private:
+ QString m_fileName;
+ QString m_parameters;
+};
+
+/** Browse: an URL to open, ranging from 'http://' to 'mailto:' etc.. **/
+class KPDFLinkBrowse : public KPDFLink
+{
+ public:
+ // query for URL
+ const QString & url() const { return m_url; }
+
+ // create a KPDFLink_Browse
+ KPDFLinkBrowse( const QString &url ) { m_url = url; }
+ LinkType linkType() const { return Browse; }
+ QString linkTip() const;
+
+ private:
+ QString m_url;
+};
+
+/** Action: contains an action to perform on document / kpdf **/
+class KPDFLinkAction : public KPDFLink
+{
+ public:
+ // define types of actions
+ enum ActionType { PageFirst, PagePrev, PageNext, PageLast, HistoryBack, HistoryForward, Quit, Presentation, EndPresentation, Find, GoToPage, Close };
+
+ // query for action type
+ ActionType actionType() const { return m_type; }
+
+ // create a KPDFLink_Action
+ KPDFLinkAction( enum ActionType actionType ) { m_type = actionType; }
+ LinkType linkType() const { return Action; }
+ QString linkTip() const;
+
+ private:
+ ActionType m_type;
+};
+
+/** Movie: Not yet defined -> think renaming to 'Media' link **/
+class KPDFLinkMovie : public KPDFLink
+// TODO this (Movie link)
+{
+ public:
+ KPDFLinkMovie() {};
+ LinkType linkType() const { return Movie; }
+};
+
+#endif
diff --git a/kpdf/core/observer.h b/kpdf/core/observer.h
new file mode 100644
index 00000000..28f07bf5
--- /dev/null
+++ b/kpdf/core/observer.h
@@ -0,0 +1,58 @@
+/***************************************************************************
+ * Copyright (C) 2005 by Enrico Ros <eros.kde@email.it> *
+ * Copyright (C) 2005 by Albert Astals Cid <tsdgeos@terra.es> *
+ * *
+ * 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. *
+ ***************************************************************************/
+
+#ifndef _KPDF_DOCUMENTOBSERVER_H_
+#define _KPDF_DOCUMENTOBSERVER_H_
+
+#include <qvaluevector.h>
+#include <qrect.h>
+
+/** IDs for observers. Globally defined here. **/
+#define PRESENTATION_ID 1
+#define PART_ID 2
+#define PAGEVIEW_ID 3
+#define THUMBNAILS_ID 4
+#define TOC_ID 5
+#define MINIBAR_ID 6
+
+/** PRIORITIES for requests. Globally defined here. **/
+#define PAGEVIEW_PRIO 1
+#define PAGEVIEW_PRELOAD_PRIO 4
+#define THUMBNAILS_PRIO 2
+#define THUMBNAILS_PRELOAD_PRIO 5
+#define PRESENTATION_PRIO 0
+#define PRESENTATION_PRELOAD_PRIO 3
+
+class KPDFPage;
+
+/**
+ * @short Base class for objects being notified when something changes.
+ *
+ * Inherit this class and call KPDFDocument->addObserver( yourClass ) to get
+ * notified of asyncronous events (new pixmap generated, or changed, etc..).
+ */
+class DocumentObserver
+{
+ public:
+ // you must give each observer a unique ID (used for notifications)
+ virtual uint observerId() const = 0;
+
+ // commands from the Document to all observers
+ enum ChangedFlags { Pixmap = 1, Bookmark = 2, Highlights = 4 };
+ virtual void notifySetup( const QValueVector< KPDFPage * > & /*pages*/, bool /*documentChanged*/ ) {};
+ virtual void notifyViewportChanged( bool /*smoothMove*/ ) {};
+ virtual void notifyPageChanged( int /*pageNumber*/, int /*changedFlags*/ ) {};
+ virtual void notifyContentsCleared( int /*changedFlags*/ ) {};
+
+ // queries to observers
+ virtual bool canUnloadPixmap( int /*pageNum*/ ) { return true; }
+};
+
+#endif
diff --git a/kpdf/core/page.cpp b/kpdf/core/page.cpp
new file mode 100644
index 00000000..e6a847a8
--- /dev/null
+++ b/kpdf/core/page.cpp
@@ -0,0 +1,327 @@
+/***************************************************************************
+ * 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 <qpixmap.h>
+#include <qstring.h>
+#include <qmap.h>
+#include <kdebug.h>
+
+// local includes
+#include "page.h"
+#include "pagetransition.h"
+#include "link.h"
+#include "conf/settings.h"
+#include "xpdf/TextOutputDev.h"
+
+
+/** class KPDFPage **/
+
+KPDFPage::KPDFPage( uint page, float w, float h, int r )
+ : m_number( page ), m_rotation( r ), m_width( w ), m_height( h ),
+ m_bookmarked( false ), m_text( 0 ), m_transition( 0 )
+{
+ // if landscape swap width <-> height (rotate 90deg CCW)
+ if ( r == 90 || r == 270 )
+ {
+ m_width = h;
+ m_height = w;
+ }
+ // avoid Division-By-Zero problems in the program
+ if ( m_width <= 0 )
+ m_width = 1;
+ if ( m_height <= 0 )
+ m_height = 1;
+}
+
+KPDFPage::~KPDFPage()
+{
+ deletePixmapsAndRects();
+ deleteHighlights();
+ delete m_text;
+ delete m_transition;
+}
+
+
+bool KPDFPage::hasPixmap( int id, int width, int height ) const
+{
+ if ( !m_pixmaps.contains( id ) )
+ return false;
+ if ( width == -1 || height == -1 )
+ return true;
+ QPixmap * p = m_pixmaps[ id ];
+ return p ? ( p->width() == width && p->height() == height ) : false;
+}
+
+bool KPDFPage::hasSearchPage() const
+{
+ return m_text != 0;
+}
+
+bool KPDFPage::hasBookmark() const
+{
+ return m_bookmarked;
+}
+
+bool KPDFPage::hasObjectRect( double x, double y ) const
+{
+ if ( m_rects.count() < 1 )
+ return false;
+ QValueList< ObjectRect * >::const_iterator it = m_rects.begin(), end = m_rects.end();
+ for ( ; it != end; ++it )
+ if ( (*it)->contains( x, y ) )
+ return true;
+ return false;
+}
+
+bool KPDFPage::hasHighlights( int s_id ) const
+{
+ // simple case: have no highlights
+ if ( m_highlights.isEmpty() )
+ return false;
+ // simple case: we have highlights and no id to match
+ if ( s_id == -1 )
+ return true;
+ // iterate on the highlights list to find an entry by id
+ QValueList< HighlightRect * >::const_iterator it = m_highlights.begin(), end = m_highlights.end();
+ for ( ; it != end; ++it )
+ if ( (*it)->s_id == s_id )
+ return true;
+ return false;
+}
+
+bool KPDFPage::hasTransition() const
+{
+ return m_transition != 0;
+}
+
+
+NormalizedRect * KPDFPage::findText( const QString & text, bool strictCase, NormalizedRect * lastRect ) const
+{
+ if ( text.isEmpty() )
+ return 0;
+
+ // create a xpf's Unicode (unsigned int) array for the given text
+ const QChar * str = text.unicode();
+ int len = text.length();
+ QMemArray<Unicode> u(len);
+ for (int i = 0; i < len; ++i)
+ u[i] = str[i].unicode();
+
+ // find out the direction of search
+ enum SearchDir { FromTop, NextMatch, PrevMatch } dir = lastRect ? NextMatch : FromTop;
+ double sLeft, sTop, sRight, sBottom;
+ if ( dir == NextMatch )
+ {
+ sLeft = lastRect->left * m_width;
+ sTop = lastRect->top * m_height;
+ sRight = lastRect->right * m_width;
+ sBottom = lastRect->bottom * m_height;
+ }
+
+ // this loop is only for 'bad case' matches
+ bool found = false;
+ while ( !found )
+ {
+ if ( dir == FromTop )
+ found = m_text->findText( const_cast<Unicode*>(static_cast<const Unicode*>(u)), len, gTrue, gTrue, gFalse, gFalse, strictCase, gFalse, &sLeft, &sTop, &sRight, &sBottom );
+ else if ( dir == NextMatch )
+ found = m_text->findText( const_cast<Unicode*>(static_cast<const Unicode*>(u)), len, gFalse, gTrue, gTrue, gFalse, strictCase, gFalse, &sLeft, &sTop, &sRight, &sBottom );
+ else if ( dir == PrevMatch )
+ // FIXME: this doesn't work as expected (luckily backward search isn't yet used)
+ // TODO: check if the new xpdf 3.01 code is able of searching backwards
+ found = m_text->findText( const_cast<Unicode*>(static_cast<const Unicode*>(u)), len, gTrue, gFalse, gFalse, gTrue, strictCase, gTrue, &sLeft, &sTop, &sRight, &sBottom );
+
+ // if not found (even in case unsensitive search), terminate
+ if ( !found )
+ break;
+ }
+
+ // if the page was found, return a new normalizedRect
+ if ( found )
+ return new NormalizedRect( sLeft / m_width, sTop / m_height, sRight / m_width, sBottom / m_height );
+ return 0;
+}
+
+const QString KPDFPage::getText( const NormalizedRect & rect ) const
+{
+ if ( !m_text )
+ return QString::null;
+ int left = (int)( rect.left * m_width ),
+ top = (int)( rect.top * m_height ),
+ right = (int)( rect.right * m_width ),
+ bottom = (int)( rect.bottom * m_height );
+ GString * text = m_text->getText( left, top, right, bottom );
+ QString result = QString::fromUtf8( text->getCString() );
+ delete text;
+ return result;
+}
+
+const ObjectRect * KPDFPage::hasObject( ObjectRect::ObjectType type, double x, double y ) const
+{
+ QValueList< ObjectRect * >::const_iterator it = m_rects.begin(), end = m_rects.end();
+ for ( ; it != end; ++it )
+ if ( (*it)->contains( x, y ) )
+ if ((*it)->objectType() == type) return *it;
+ return 0;
+}
+
+const KPDFPageTransition * KPDFPage::getTransition() const
+{
+ return m_transition;
+}
+
+
+void KPDFPage::setPixmap( int id, QPixmap * pixmap )
+{
+ if ( m_pixmaps.contains( id ) )
+ delete m_pixmaps[id];
+ m_pixmaps[id] = pixmap;
+}
+
+void KPDFPage::setSearchPage( TextPage * tp )
+{
+ delete m_text;
+ m_text = tp;
+}
+
+void KPDFPage::setBookmark( bool state )
+{
+ m_bookmarked = state;
+}
+
+void KPDFPage::setObjectRects( const QValueList< ObjectRect * > rects )
+{
+ QValueList< ObjectRect * >::iterator it = m_rects.begin(), end = m_rects.end();
+ for ( ; it != end; ++it )
+ delete *it;
+ m_rects = rects;
+}
+
+void KPDFPage::setHighlight( int s_id, NormalizedRect * &rect, const QColor & color )
+{
+ // create a HighlightRect descriptor taking values from params
+ HighlightRect * hr = new HighlightRect();
+ hr->s_id = s_id;
+ hr->color = color;
+ hr->left = rect->left;
+ hr->top = rect->top;
+ hr->right = rect->right;
+ hr->bottom = rect->bottom;
+ // append the HighlightRect to the list
+ m_highlights.append( hr );
+ // delete old object and change reference
+ delete rect;
+ rect = hr;
+}
+
+void KPDFPage::setTransition( KPDFPageTransition * transition )
+{
+ delete m_transition;
+ m_transition = transition;
+}
+
+void KPDFPage::deletePixmap( int id )
+{
+ if ( m_pixmaps.contains( id ) )
+ {
+ delete m_pixmaps[ id ];
+ m_pixmaps.remove( id );
+ }
+}
+
+void KPDFPage::deletePixmapsAndRects()
+{
+ // delete all stored pixmaps
+ QMap<int,QPixmap *>::iterator it = m_pixmaps.begin(), end = m_pixmaps.end();
+ for ( ; it != end; ++it )
+ delete *it;
+ m_pixmaps.clear();
+ // delete ObjectRects
+ QValueList< ObjectRect * >::iterator rIt = m_rects.begin(), rEnd = m_rects.end();
+ for ( ; rIt != rEnd; ++rIt )
+ delete *rIt;
+ m_rects.clear();
+}
+
+void KPDFPage::deleteHighlights( int s_id )
+{
+ // delete highlights by ID
+ QValueList< HighlightRect * >::iterator it = m_highlights.begin(), end = m_highlights.end();
+ while ( it != end )
+ {
+ HighlightRect * highlight = *it;
+ if ( s_id == -1 || highlight->s_id == s_id )
+ {
+ it = m_highlights.remove( it );
+ delete highlight;
+ }
+ else
+ ++it;
+ }
+}
+
+
+/** class NormalizedRect **/
+
+NormalizedRect::NormalizedRect()
+ : left( 0.0 ), top( 0.0 ), right( 0.0 ), bottom( 0.0 ) {}
+
+NormalizedRect::NormalizedRect( double l, double t, double r, double b )
+ // note: check for swapping coords?
+ : left( l ), top( t ), right( r ), bottom( b ) {}
+
+NormalizedRect::NormalizedRect( const QRect & r, double xScale, double yScale )
+ : left( (double)r.left() / xScale ), top( (double)r.top() / yScale ),
+ right( (double)r.right() / xScale ), bottom( (double)r.bottom() / yScale ) {}
+
+bool NormalizedRect::contains( double x, double y ) const
+{
+ return x >= left && x <= right && y >= top && y <= bottom;
+}
+
+bool NormalizedRect::intersects( const NormalizedRect & r ) const
+{
+ return (r.left < right) && (r.right > left) && (r.top < bottom) && (r.bottom > top);
+}
+
+bool NormalizedRect::intersects( double l, double t, double r, double b ) const
+{
+ return (l < right) && (r > left) && (t < bottom) && (b > top);
+}
+
+QRect NormalizedRect::geometry( int xScale, int yScale ) const
+{
+ int l = (int)( left * xScale ),
+ t = (int)( top * yScale ),
+ r = (int)( right * xScale ),
+ b = (int)( bottom * yScale );
+ return QRect( l, t, r - l + 1, b - t + 1 );
+}
+
+
+/** class ObjectRect **/
+
+ObjectRect::ObjectRect( double l, double t, double r, double b, ObjectType type, void * pnt )
+ // assign coordinates swapping them if negative width or height
+ : NormalizedRect( r > l ? l : r, b > t ? t : b, r > l ? r : l, b > t ? b : t ),
+ m_objectType( type ), m_pointer( pnt )
+{
+}
+
+ObjectRect::~ObjectRect()
+{
+ if ( !m_pointer )
+ return;
+
+ if ( m_objectType == Link )
+ delete static_cast<KPDFLink*>( m_pointer );
+ else
+ kdDebug() << "Object deletion not implemented for type '" << m_objectType << "' ." << endl;
+}
diff --git a/kpdf/core/page.h b/kpdf/core/page.h
new file mode 100644
index 00000000..ebd6e522
--- /dev/null
+++ b/kpdf/core/page.h
@@ -0,0 +1,149 @@
+/***************************************************************************
+ * 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. *
+ ***************************************************************************/
+
+#ifndef _KPDF_PAGE_H_
+#define _KPDF_PAGE_H_
+
+#include <qmap.h>
+#include <qvaluelist.h>
+
+class QPixmap;
+class QRect;
+class TextPage;
+class KPDFPageTransition;
+class HighlightRect;
+class Annotation;
+
+/**
+ * @short A rect in normalized [0,1] coordinates.
+ */
+class NormalizedRect
+{
+ public:
+ double left, top, right, bottom;
+
+ NormalizedRect();
+ NormalizedRect( double l, double t, double r, double b );
+ NormalizedRect( const QRect & r, double xScale, double yScale );
+
+ bool contains( double x, double y ) const;
+ bool intersects( const NormalizedRect & normRect ) const;
+ bool intersects( double l, double t, double r, double b ) const;
+
+ QRect geometry( int xScale, int yScale ) const;
+};
+
+/**
+ * @short NormalizedRect that contains a reference to an object.
+ *
+ * These rects contains a pointer to a kpdf object (such as a link or something
+ * like that). The pointer is read and stored as 'void pointer' so cast is
+ * performed by accessors based on the value returned by objectType(). Objects
+ * are reparented to this class.
+ *
+ * Type / Class correspondency tab:
+ * - Link : class KPDFLink : description of a link
+ * - Image : class KPDFImage : description of an image (n/a)
+ */
+class ObjectRect : public NormalizedRect
+{
+ public:
+ // definition of the types of storable objects
+ enum ObjectType { Link, Image };
+
+ // default constructor: initialize all parameters
+ ObjectRect( double l, double t, double r, double b, ObjectType typ, void * obj );
+ ~ObjectRect();
+
+ // query type and get a const pointer to the stored object
+ inline ObjectType objectType() const { return m_objectType; }
+ inline const void * pointer() const { return m_pointer; }
+
+ private:
+ ObjectType m_objectType;
+ void * m_pointer;
+};
+
+/**
+ * @short Collector for all the data belonging to a page.
+ *
+ * The KPDFPage class contains pixmaps (referenced using observers id as key),
+ * a search page (a class used internally for retrieving text), rect classes
+ * (that describe links or other active areas in the current page) and more.
+ *
+ * All coordinates are normalized to the page, so {x,y} are valid in [0,1]
+ * range as long as NormalizedRect components.
+ *
+ * Note: The class takes ownership of all objects.
+ */
+class KPDFPage
+{
+ public:
+ KPDFPage( uint number, float width, float height, int rotation );
+ ~KPDFPage();
+
+ // query properties (const read-only methods)
+ inline int number() const { return m_number; }
+ inline int rotation() const { return m_rotation; }
+ inline float width() const { return m_width; }
+ inline float height() const { return m_height; }
+ inline float ratio() const { return m_height / m_width; }
+ bool hasPixmap( int p_id, int width = -1, int height = -1 ) const;
+ bool hasSearchPage() const;
+ bool hasBookmark() const;
+ bool hasObjectRect( double x, double y ) const;
+ bool hasHighlights( int s_id = -1 ) const;
+ //bool hasAnnotation( double x, double y ) const;
+ bool hasTransition() const;
+
+ NormalizedRect * findText( const QString & text, bool keepCase, NormalizedRect * last = 0 ) const;
+ const QString getText( const NormalizedRect & rect ) const;
+ const ObjectRect * hasObject( ObjectRect::ObjectType type, double x, double y ) const;
+ //const Annotation * getAnnotation( double x, double y ) const;
+ const KPDFPageTransition * getTransition() const;
+
+ // operations: set/delete contents (by KPDFDocument)
+ void setPixmap( int p_id, QPixmap * pixmap );
+ void setSearchPage( TextPage * text );
+ void setBookmark( bool state );
+ void setObjectRects( const QValueList< ObjectRect * > rects );
+ void setHighlight( int s_id, NormalizedRect * &r, const QColor & color );
+ //void setAnnotation( Annotation * annotation );
+ void setTransition( KPDFPageTransition * transition );
+ void deletePixmap( int p_id );
+ void deletePixmapsAndRects();
+ void deleteHighlights( int s_id = -1 );
+
+ private:
+ friend class PagePainter;
+ int m_number, m_rotation;
+ float m_width, m_height;
+ bool m_bookmarked;
+
+ QMap< int, QPixmap * > m_pixmaps;
+ TextPage * m_text;
+ QValueList< ObjectRect * > m_rects;
+ QValueList< HighlightRect * > m_highlights;
+ //QValueList< Annotation * > m_annotations;
+ KPDFPageTransition * m_transition;
+};
+
+
+/**
+ * Internal Storage: normalized colored highlight owned by id
+ */
+struct HighlightRect : public NormalizedRect
+{
+ // searchID of the highlight owner
+ int s_id;
+ // color of the highlight
+ QColor color;
+};
+
+#endif
diff --git a/kpdf/core/pagetransition.cpp b/kpdf/core/pagetransition.cpp
new file mode 100644
index 00000000..cdf04a7f
--- /dev/null
+++ b/kpdf/core/pagetransition.cpp
@@ -0,0 +1,28 @@
+/***************************************************************************
+ * Copyright (C) 2005 by Tobias Koenig <tokoe@kde.org> *
+ * *
+ * 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. *
+ ***************************************************************************/
+
+// local includes
+#include "pagetransition.h"
+
+/** class KPDFPageTransition **/
+
+KPDFPageTransition::KPDFPageTransition( Type type )
+ : m_type( type ),
+ m_duration( 1 ),
+ m_alignment( Horizontal ),
+ m_direction( Inward ),
+ m_angle( 0 ),
+ m_scale( 1.0 ),
+ m_rectangular( false )
+{
+}
+
+KPDFPageTransition::~KPDFPageTransition()
+{
+}
diff --git a/kpdf/core/pagetransition.h b/kpdf/core/pagetransition.h
new file mode 100644
index 00000000..70792355
--- /dev/null
+++ b/kpdf/core/pagetransition.h
@@ -0,0 +1,86 @@
+/***************************************************************************
+ * Copyright (C) 2005 by Tobias Koenig <tokoe@kde.org> *
+ * *
+ * 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. *
+ ***************************************************************************/
+
+#ifndef _KPDF_PAGE_TRANSITION_H_
+#define _KPDF_PAGE_TRANSITION_H_
+
+/**
+ * @short Information object for the transition effect of a page.
+ */
+class KPDFPageTransition
+{
+ public:
+ enum Type {
+ Replace,
+ Split,
+ Blinds,
+ Box,
+ Wipe,
+ Dissolve,
+ Glitter,
+ Fly,
+ Push,
+ Cover,
+ Uncover,
+ Fade
+ };
+
+ enum Alignment {
+ Horizontal,
+ Vertical
+ };
+
+ enum Direction {
+ Inward,
+ Outward
+ };
+
+ KPDFPageTransition( Type type = Replace );
+ ~KPDFPageTransition();
+
+ // Get type of the transition.
+ inline Type type() const { return m_type; }
+
+ // Get duration of the transition in seconds.
+ inline int duration() const { return m_duration; }
+
+ // Get dimension in which the transition effect occurs.
+ inline Alignment alignment() const { return m_alignment; }
+
+ // Get direction of motion of the transition effect.
+ inline Direction direction() const { return m_direction; }
+
+ // Get direction in which the transition effect moves.
+ inline int angle() const { return m_angle; }
+
+ // Get starting or ending scale. (Fly only)
+ inline double scale() const { return m_scale; }
+
+ // Returns true if the area to be flown is rectangular and opaque. (Fly only)
+ inline bool isRectangular() const { return m_rectangular; }
+
+ inline void setType( Type type ) { m_type = type; }
+ inline void setDuration( int duration ) { m_duration = duration; }
+ inline void setAlignment( Alignment alignment ) { m_alignment = alignment; }
+ inline void setDirection( Direction direction ) { m_direction = direction; }
+ inline void setAngle( int angle ) { m_angle = angle; }
+ inline void setScale( double scale ) { m_scale = scale; }
+ inline void setIsRectangular( bool rectangular ) { m_rectangular = rectangular; }
+
+ private:
+ Type m_type;
+ int m_duration;
+ Alignment m_alignment;
+ Direction m_direction;
+ int m_angle;
+ double m_scale;
+ bool m_rectangular;
+};
+
+#endif