compile
[kdegraphics.git] / okular / Mainpage.dox
blobf9a6dc553cd70fcf46db13a3f16c3ee04157ff62
1 /**
2 \mainpage Okular, the unified document viewer
4 \section okular_overview Overview
6 - \ref okular_history
7 - \ref okular_design
8 - \ref okular_generators
9 - <a href="http://www.okular.org">Website</a>
11 \authors Tobias König <tokoe@kde.org>
13 \licenses \lgpl
15 \page okular_history Historical background
17 Okular is the successor of <a href="http://kpdf.kde.org">kpdf</a>, the PDF viewer in KDE 3.
18 kpdf was refactored and extended in a Google Summer of Code project to support not only
19 viewing PDF but also other types of document, e.g. PostScript files, images and many more.
21 \page okular_design The Design of Okular
23 To support a wide range of document formats, Okular was designed in a modular way, so you
24 have the following components:
26   \li \ref Shell
27   \li \ref Part
28   \li \ref Okular::Document Class
29   \li \ref Okular::Generator
31 The shell is the application which is started by the user as standalone application and
32 which embedds the part. The part contains all GUI elements of Okular, for example the
33 content list, the bookmark manager, menus and the graphical view of the document class.
34 The document class is an abstract presentation of the document content. It contains information
35 about every page of the document, its size, orientation etc.
37 But somehow the document class must retrieve these information from the various types of documents.
38 This is the task of the Generators. Generators are plugins which are loaded at runtime and which
39 have the knowledge about the internal structure of the different document types.
40 They extract the needed information from the documents, convert the data into a common format and
41 pass them to the document class.
43 Currently Generators for the following document types are available:
45   \li Portable Document Format (PDF)
46   \li PostScript
47   \li Device Independent Format (DVI)
48   \li DeJaVu Format
49   \li Comic Books
50   \li Images (JPEG, PNG, GIF, and many more)
51   \li TIFF Image Format
52   \li FictionBook Format
53   \li Plucker Format
54   \li OpenDocument Text Format
55   \li Microsofts CHM Format
56   \li Microsofts XML Document Format
58 Now the questions is how can these various formats be represented in a unified way?
59 Okular provides features like rotation, text search and extraction, zooming and many more, so how
60 does it match with the different capabilities of the formats?
62 \section okular_design_basics Basics of Generators
64 Lets start with the smallest commonness of all document formats:
66   \li they have pages (one ore more) of a given size
67   \li pages can be represented as pictures
69 So the first thing every Generator must support is to return the number of pages of a document.
70 Furthermore it must be able to return the picture of a page at a requested size.
72 For vector based document formats (e.g. PDF or PostScript) the Generators can render the page for
73 the requested size, for static documents formats (e.g. images), the Generator must scale the
74 content according to the requested size, so when you zoom a page in Okular, the Generators are
75 just asked to return the page for the zoomed size.
77 When the document class has retrieved the page pictures from the Generators, it can do further
78 image manipulation on it, for example rotating them or applying fancy effects.
80 \section okular_design_text_support Generators with Text support
82 Some document formats however support more functionality than just representing a page as an image.
83 PDF, PostScript, DVI and DeJaVu for example contains a machine readable representation of the
84 included text. For those document formats Okular provides additional features like text search,
85 text extraction and text selection.
87 How is that supported by the Generators?
89 To access the text from the documents the generators must extract it somehow and make it available
90 to the document class. However for the text selection feature the document class must also know <em>where</em>
91 the extracted text is located on the page. For a zoom factor of 100% the absolute position of
92 the text in the document can be used, however for larger or smaller zoom factors the position
93 must be recalculated. To make this calculation as easy as possible, the Generators return an
94 abstract represtentation (\ref Okular::TextPage) of the text which includes every character together
95 with its <em>normalized</em> position. Normalized means that the width and height of the page is
96 in the range of 0 to 1, so a character in the middle of the page is at x=0.5 and y=0.5.
98 So when you want to know where this character is located on the page which is zoomed at 300%, you just
99 multiply the position by 3 * page width (and page height) and get the absolute position for this zoom level.
101 This abstract text representation also allows an easy rotation of the coordinates, so that text selection
102 is available on rotated pages as well.
104 \section okular_design_meta_information Meta Information
106 Most documents have additional meta information:
108   \li Name of the author
109   \li Date of creation
110   \li Version number
111   \li Table of Content
112   \li Bookmarks
113   \li Annotations
115 These information can be retrieved by the generator as well and will be shown by Okular.
117 \page okular_generators How to implement a Generator
119 The power of Okular is its extensibility by Generator plugins. This section will describe how to
120 implement your own plugin for a new document type.
122   \li \ref okular_generators_basic
123   \li \ref okular_generators_with_text
124   \li \ref okular_generators_threaded
125   \li \ref okular_generators_extended
127 \section okular_generators_basic A Basic Generator
129 To provide a short overview and don't reimplementing an existing generator we'll work on a Generator
130 for the Magic document format, a non existing, pure virtual format :)
132 Lets assume we have some helper class (MagicDocument) which provides the following functionality for this
133 document format:
135  \li Loading a document
136  \li Retrieving number of pages
137  \li Returning a fixed size picture representation of a page
139 The class API looks like this
141 \code
142 class MagicDocument
144     public:
145         MagicDocument();
146         ~MagicDocument();
148         bool loadDocument( const QString &fileName );
150         int numberOfPages() const;
152         QSize pageSize( int pageNumber ) const;
154         QImage pictureOfPage( int pageNumber ) const;
156     private:
157         ...
159 \endcode
161 The methods should be self explaining, loadDocument() loads a document file and returns false on error,
162 numberOfPages() returns the number of pages, pageSize() returns the size of the page and pictureOfPage()
163 returns the picture representation of the page.
165 Our first version of our Generator is a basic one which just provides page pictures to the document class.
167 The API of the Generator looks like the following:
169 \code
170 #include "magicdocument.h"
172 #include <okular/core/generator.h>
174 class MagicGenerator : public Okular::Generator
176     public:
177         MagicGenerator( QObject *parent, const QVariantList &args );
178         ~MagicGenerator();
180         bool loadDocument( const QString &fileName, QVector<Okular::Page*> &pages );
182         bool canGeneratePixmap() const;
183         void generatePixmap( Okular::PixmapRequest *request );
185     protected:
186         bool doCloseDocument();
188     private:
189         MagicDocument mMagicDocument;
191 \endcode
193 The implementation of the Generator looks like this:
195 \code
196 #include <okular/core/page.h>
198 #include "magicgenerator.h"
200 static KAboutData createAboutData()
202     KAboutData aboutData(...);
203     // fill the about data
204     return aboutData;
207 OKULAR_EXPORT_PLUGIN(MagicGenerator, createAboutData())
209 MagicGenerator::MagicGenerator( QObject *parent, const QVariantList &args )
210     : Okular::Generator( parent, args )
214 MagicGenerator::~MagicGenerator()
218 bool MagicGenerator::loadDocument( const QString &fileName, QVector<Okular::Page*> &pages )
220     if ( !mMagicDocument.loadDocument( fileName ) ) {
221         emit error( i18n( "Unable to load document" ), -1 );
222         return false;
223     }
225     pagesVector.resize( mMagicDocument.numberOfPages() );
227     for ( int i = 0; i < mMagicDocument.numberOfPages(); ++i ) {
228       const QSize size = mMagicDocument.pageSize( i );
230       Okular::Page * page = new Okular::Page( i, size.width(), size.height(), Okular::Rotation0 );
231       pages[ i ] = page;
232     }
234     return true;
237 bool MagicGenerator::doCloseDocument()
239     return true;
242 bool MagicGenerator::canGeneratePixmap() const
244     return true;
247 void MagicGenerator::generatePixmap( Okular::PixmapRequest *request )
249     QImage image = mMagicDocument.pictureOfPage( request->pageNumber() );
251     image = image.scaled( request->width(), request->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
253     request->page()->setPixmap( request->id(), new QPixmap( QPixmap::fromImage( image ) ) );
255     signalPixmapRequestDone( request );
258 \endcode
260 As you can see implementing a basic Generator is quite easy. The loadDocument() method opens the document file
261 and extracts the number of pages. For every page in the document it adds an Okular::Page object to the pages vector
262 which is passed in as method argument. Each page is initialized with its page number, width, height and initial rotation.
263 These page objects will be stored in the document object and act as a container for the picture representation
264 of the pages. This code is the same for nearly every Generator. On an failure the error() signal can be emitted
265 to inform the user about the issue. This code is the same for nearly every Generator.
267 In the doCloseDocument() method you should close the document and free all resources you have allocated in openDocument().
269 Now we come to the picture creation methods. The canGeneratorPixmap() method returns whether the Generator is currently
270 able to handle a new pixmap generation request. For a simple Generator like our one that's always the case as it works
271 linear, however a multithreaded Generator might return <em>false</em> here if it is still waiting for one of its working
272 threads to finish. In this case the document class will try to request the pixmap later again.
274 The generatePixmap() method does the actual fetching of the picture for a page. The page number, requested width and
275 height of the page is encapsulated in the passed Okular::PixmapRequest object.
276 So the task of the Generator is to create a pixmap of the requested page in the requested size and then store this
277 pixmap in the Okular::Page object which is associated with the page request.
278 When this task is finished, the Generator has to call signalPixmapRequestDone() with the page request object
279 as argument. This extra call is needed to allow the Generator to use signals and slots internally and create the
280 pixmap asynchronously.
282 So now you have the code of a working Okular Generator, the next step is to tell Okular about the new plugin.
283 Like in other places in KDE that is done by .desktop files, which are installed to the services directory.
285 Every Generator needs 3 .desktop files:
287   \li libokularGenerator_&lt;name&gt;.desktop
288   \li okularApplication_&lt;name&gt;.desktop
289   \li okular&lt;name&gt;.desktop
291 where &lt;name&gt; should be the name of the document format. So for our Magic Document Generator we
292 create the following 3 files:
294   \li libokularGenerator_magic.desktop
295   \li okularApplication_magic.desktop
296   \li okularMagic.desktop
298 with the following content:
300 \verbatim
301 [Desktop Entry]
302 Encoding=UTF-8
303 Type=Service
304 Name=Magic Document
305 Comment=Magic Document backend for okular
306 ServiceTypes=okular/Generator
307 MimeType=application/x-magic;
308 X-KDE-Library=okularGenerator_magic
309 X-KDE-Priority=1
310 X-KDE-okularAPIVersion=1
311 X-KDE-okularHasInternalSettings=false
312 \endverbatim
314 The first 6 fields are standard .desktop entries, the fields afterwards have a special meaning to Okular
316   \li <b>ServiceType</b>  Must be 'okular/Generator' for all Okular Generator Plugins
317   \li <b>MimeType</b> The mimetype or list of mimetypes of the supported document format(s)
318   \li <b>X-KDE-Library</b> The name of the plugin library
319   \li <b>X-KDE-Priority</b> When multiple Generators for the same mimetype exists, the one with the highest priority is used
320   \li <b>X-KDE-okularAPIVersion</b> The version of the Generator Plugin API ('1' currently)
321   \li <b>X-KDE-okularHasInternalSettings</b> Is 'true' when the Generator provides configuration dialogs
323 The second .desktop file has the following content:
325 \verbatim
326 [Desktop Entry]
327 Encoding=UTF-8
328 MimeType=application/x-magic;
329 Terminal=false
330 Name=okular
331 GenericName=Document Viewer
332 Exec=okular %U %i -caption "%c"
333 Icon=okular
334 Type=Application
335 InitialPreference=7
336 Categories=Qt;KDE;Graphics;Viewer;
337 NoDisplay=true
338 \endverbatim
340 You can use the file as it is, you just have to adapt the mimetype. This file is needed to allow Okular
341 to handle multiple mimetypes.
343 The third .desktop file looks like this:
345 \verbatim
346 [Desktop Entry]
347 Encoding=UTF-8
348 Icon=okular
349 Name=okular
350 ServiceTypes=KParts/ReadOnlyPart
351 X-KDE-Library=libokularpart
352 Type=Service
353 MimeType=application/x-magic;
354 \endverbatim
356 You can use the file as it is as well, you just have to adapt the mimetype. This file is needed to allow
357 the Okular part to handle multiple mimetypes.
359 The last piece you need for a complete Generator is a CMakeLists.txt which compiles and installs the
360 Generator. Our CMakeLists.txt looks like the following:
362 \verbatim
363 macro_optional_find_package(Okular)
365 include_directories( ${OKULAR_INCLUDE_DIR} ${KDE4_INCLUDE_DIR} ${QT_INCLUDES} )
367 ########### next target ###############
369 set( okularGenerator_magic_SRCS generator_magic.cpp )
371 kde4_add_plugin( okularGenerator_magic ${okularGenerator_magic_SRCS} )
373 target_link_libraries( okularGenerator_magic ${OKULAR_LIBRARIES} ${KDE4_KDEUI_LIBS} )
375 install( TARGETS okularGenerator_magic DESTINATION ${PLUGIN_INSTALL_DIR} )
377 ########### install files ###############
379 install( FILES libokularGenerator_magic.desktop okularMagic.desktop DESTINATION ${SERVICES_INSTALL_DIR} )
380 install( FILES okularApplication_magic.desktop DESTINATION ${XDG_APPS_DIR} )
381 \endverbatim
383 The macro_optional_find_package(Okular) call is required to make the ${OKULAR_INCLUDE_DIR} and ${OKULAR_LIBRARIES}
384 variables available.
386 Now you can compile the Generator plugin and install it. After a restart of Okular the new plugin is available
387 and you can open Magic documents.
389 \section okular_generators_with_text A Generator with TextPage support
391 In this section we want to extend our Generator to support text search, text extraction and selection
392 as well. As mentioned in \ref okular_design_text_support, the Generator must provide an Okular::TextPage
393 object for every page which contains readable text.
395 Since we use the helper class MagicDocument to read the data from the document we have to extend it first,
396 so the new API looks as the following:
398 \code
399 class MagicDocument
401     public:
402         MagicDocument();
403         ~MagicDocument();
405         bool loadDocument( const QString &fileName );
407         int numberOfPages() const;
409         QSize pageSize( int pageNumber ) const;
411         QImage pictureOfPage( int pageNumber ) const;
413         class TextInfo
414         {
415             public:
416                 typedef QList<TextInfo> List;
418                 QChar character;
419                 qreal xPos;
420                 qreal yPos;
421                 qreal width;
422                 qreal height;
423         };
425         TextInfo::List textOfPage( int pageNumber );
427     private:
428         ...
430 \endcode
432 MagicDocument has the new internal class TextInfo now, which contains a character and
433 its absolute position on a page. Furthermore MagicDocument provides a method textOfPage()
434 which returns a list of all TextInfo objects for a page.
436 That's really an optimistic API, in reality it is sometimes quite hard to find out
437 the position of single characters in a document format.
439 With the extension of our helper class we can continue on extending our Generator now:
441 \code
442 #include "magicdocument.h"
444 #include <okular/core/generator.h>
446 class MagicGenerator : public Okular::Generator
448     public:
449         MagicGenerator( QObject *parent, const QVariantList &args );
450         ~MagicGenerator();
452         bool loadDocument( const QString &fileName, QVector<Okular::Page*> &pages );
454         bool canGeneratePixmap() const;
455         void generatePixmap( Okular::PixmapRequest *request );
457         virtual bool canGenerateTextPage() const;
458         virtual void generateTextPage( Okular::Page *page, enum Okular::GenerationType type = Okular::Synchronous );
460     protected:
461         bool doCloseDocument();
463     private:
464         MagicDocument mMagicDocument;
466 \endcode
468 We have extended the MagicGenerator class by two methods canGenerateTextPage() and generateTextPage().
469 The first method is equal to canGeneratePixmap(), it returns whether the Generator is currently able to
470 handle a new text page generation request. For linear Generators that should be always the case, however
471 when the generation is done in a separated worker thread, this method might return <em>false</em>.
472 In this case the document class will try to request the text page later again.
474 The second method will generate the Okular::TextPage object for the passed page. Depending on the capabilities
475 of the Generator and the passed <em>type</em> parameter that is done synchronously or asynchronously.
477 Let us take a look at the implementation of these methods in our MagicGenerator:
479 \code
480 #include <okular/core/textpage.h>
484 MagicGenerator::MagicGenerator( QObject *parent, const QVariantList &args )
485     : Okular::Generator( parent, args )
487     setFeature( TextExtraction );
490 bool MagicGenerator::canGenerateTextPage() const
492     return true;
495 void MagicGenerator::generateTextPage( Okular::Page *page, enum Okular::GenerationType )
497     MagicDocument::TextInfo::List characters = mMagicDocument.textOfPage( page->number() );
498     if ( characters.isEmpty() )
499         return;
501     Okular::TextPage *textPage = new Okular::TextPage;
502     for ( int i = 0; i < characters.count(); ++i ) {
503         qreal left = characters[ i ].xPos / page->width();
504         qreal top = characters[ i ].yPos / page->height();
505         qreal right = (characters[ i ].xPos + characters[ i ].width) / page->width();
506         qreal bottom = (characters[ i ].yPos + characters[ i ].height) / page->height();
508         textPage->append( characters[ i ].character,
509                           new Okular::NormalizedRect( left, top, right, bottom ) );
510     }
512     page->setTextPage( textPage );
514 \endcode
516 As you can see the generateTextPage method just iterates over the list of characters returned
517 by our MagicDocument helper class and adds the character and its normalized bounding rect to
518 the Okular::TextPage object. At the end the text page is assigned to the page. We don't pay
519 attention to the GenerationType parameter here, if your Generator want to use threads, it should
520 check here whether the request shall be done asynchronously or synchronously and start the generation
521 according to that. Additionally we have to tell the Okular::Generator base class that we support
522 text handling by setting this flag in the constructor.
524 In this state we can now search, select and extract text from Magic documents.
526 \section okular_generators_threaded A Generator with Thread support
528 Sometimes it makes sense to do the generation of page pictures or text pages asynchronously to
529 improve performance and don't blocking the user interface. This can be done in two ways, either
530 by using signals and slots or by using threads. Both have there pros and cons:
532   <ul>
533     <li><b>Signals and Slots</b></li>
534     <ul>
535     <li>Pro: Can be used with backend libraries which are not thread safe</li>
536     <li>Con: Sometime difficult to implement</li>
537     </ul>
538     <li><b>Threads</b></li>
539     <ul>
540     <li>Pro: Easy to implement as you can make synchronous calls to the backend libraries</li>
541     <li>Con: Backend libraries must be thread safe and you must prevent race conditions by using mutexes</li>
542     </ul>
543   </ul>
545 The signal and slots approach can be achieved with a normal Generator by calling Okular::Generator::signalPixmapRequestDone()
546 from a slot after pixmap generation has been finished.
548 When using threads you should use a slightly different API, which hides most of the thread usage, to make
549 implementing as easy as possible.
551 Let's assume the pictureOfPage() and textOfPage methods in our MagicDocument helper class are thread safe,
552 so we can use them in a multithreaded environment.
553 So nothing prevents us from changing the MagicGenerator to use threads for better performance.
555 The new MagicGenerator API looks like the following:
557 \code
558 #include "magicdocument.h"
560 #include <okular/core/generator.h>
562 class MagicGenerator : public Okular::Generator
564     public:
565         MagicGenerator( QObject *parent, const QVariantList &args );
566         ~MagicGenerator();
568         bool loadDocument( const QString &fileName, QVector<Okular::Page*> &pages );
570     protected:
571         bool doCloseDocument();
573         virtual QImage image( Okular::PixmapRequest *request );
575         virtual Okular::TextPage* textPage( Okular::Page *page );
577     private:
578         MagicDocument mMagicDocument;
580 \endcode
582 As you can see the canGeneratePixmap() generatePixmap(), canGenerateTextPage() and generateTextPage() methods have
583 been removed and replaced by the image() and textPage() methods.
585 Before explaining why, we'll take a look at the implementation:
587 \code
589 MagicGenerator::MagicGenerator( QObject *parent, const QVariantList &args )
590     : Okular::Generator( parent, args )
592     setFeature( TextExtraction );
593     setFeature( Threaded );
596 QImage MagicGenerator::image( Okular::PixmapRequest *request )
598     QImage image = mMagicDocument.pictureOfPage( request->pageNumber() );
600     return image.scaled( request->width(), request->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
603 Okular::TextPage* textPage( Okular::Page *page )
605     MagicDocument::TextInfo::List characters = mMagicDocument.textOfPage( page->number() );
606     if ( characters.isEmpty() )
607         return 0;
609     Okular::TextPage *textPage = new Okular::TextPage;
610     for ( int i = 0; i < characters.count(); ++i ) {
611         qreal left = characters[ i ].xPos / page->width();
612         qreal top = characters[ i ].yPos / page->height();
613         qreal right = (characters[ i ].xPos + characters[ i ].width) / page->width();
614         qreal bottom = (characters[ i ].yPos + characters[ i ].height) / page->height();
616         textPage->append( characters[ i ].character,
617                           new Okular::NormalizedRect( left, top, right, bottom ) );
618     }
620     return textPage;
622 \endcode
624 So the first obviously thing is that both methods return a value instead of modifying the page directly.
625 The reason for this is that both methods are executed in its own thread, so the code executed in them can
626 block as long as it wants, it won't block the GUI anyway. Additionally we have to tell the Okular::Generator
627 base class that we can handle threads by setting the flag in the constructor.
629 With only a small change we made our MagicGenerator multithreaded now!
631 \section okular_generators_extended An Extended Generator
633 Now we want to create a new generator with some additional functionality:
635   \li Support for document information (author, creation date etc.)
636   \li Support for a table of content
637   \li Support for printing the document
638   \li Support for exporting the document as text
640 The new Generator shall be able to handle HTML documents. We choose this format as example, because
641 we can use QTextDocument to load, render and print a HTML page, so a lot of code can be reused.
643 The API of our HTMLGenerator looks like the following:
645 \code
646 #include <QtGui/QTextDocument>
648 #include <okular/core/generator.h>
650 class HTMLGenerator : public Okular::Generator
652     public:
653         HTMLGenerator( QObject *parent, const QVariantList &args );
654         ~HTMLGenerator();
656         bool loadDocument( const QString &fileName, QVector<Okular::Page*> &pages );
658         bool canGeneratePixmap() const;
659         void generatePixmap( Okular::PixmapRequest *request );
661         virtual const Okular::DocumentInfo* generateDocumentInfo();
663         virtual const Okular::DocumentSynopsis* generateDocumentSynopsis();
665         virtual bool print( KPrinter &printer );
667         virtual Okular::ExportFormat::List exportFormats() const;
669         virtual bool exportTo( const QString &fileName, const Okular::ExportFormat &format );
671     protected:
672         bool doCloseDocument();
674     private:
675         QTextDocument *mTextDocument;
676         Okular::DocumentInfo mDocumentInfo;
677         Okular::DocumentSynopsis mDocumentSynopsis;
679 \endcode
681 The Generator doesn't support text search and selection, as the code would be quite complex, we'll show
682 how to do it in the next chapter \ref okular_generators_textdocument anyway.
684 As you can see we have 5 new methods in the class:
686   \li <b>generateDocumentInfo()</b> Creates an Okular::DocumentInfo (which is infact a QDomDocument)
687       which contains document information like author, creation time etc.
688   \li <b>generateDocumentSynopsis()</b> Creates an Okular::DocumentSynopsis (which is a QDomDocument as well)
689       which contains the table of content.
690   \li <b>print()</b> Prints the document to the passed printer.
691   \li <b>exportFormats()</b> Returns the supported export formats.
692   \li <b>exportTo()</b> Exports the document to the given file in the given format.
694 Now that you know what the methods are supposed to do, let's take a look at the implementation:
696 \code
697 #include <QtCore/QFile>
698 #include <QtGui/QAbstractTextDocumentLayout>
700 #include <kprinter.h>
702 #include <okular/core/document.h>
703 #include <okular/core/page.h>
705 #include "htmlgenerator.h"
707 static KAboutData createAboutData()
709     KAboutData aboutData(...);
710     // fill the about data
711     return aboutData;
714 OKULAR_EXPORT_PLUGIN(HTMLGenerator, createAboutData())
716 HTMLGenerator::HTMLGenerator( QObject *parent, const QVariantList &args )
717     : Okular::Generator( parent, args ),
718       mTextDocument( 0 )
722 HTMLGenerator::~HTMLGenerator()
724     delete mTextDocument;
727 bool HTMLGenerator::loadDocument( const QString &fileName, QVector<Okular::Page*> &pages )
729     QFile file( fileName );
730     if ( !file.open( QIODevice::ReadOnly ) ) {
731         emit error( i18n( "Unable to open file" ), -1 );
732         return false;
733     }
735     const QString data = QString::fromUtf8( file.readAll() );
737     file.close();
739     mTextDocument = new QTextDocument;
740     mTextDocument->setHtml( data );
741     mTextDocument->setPageSize( QSizeF( 600, 800 ) );
743     pages.resize( mTextDocument->pageCount() );
745     for ( int i = 0; i < mTextDocument->pageCount(); ++i ) {
746       Okular::Page * page = new Okular::Page( i, 600, 800, Okular::Rotation0 );
747       pages[ i ] = page;
748     }
750     mDocumentInfo.set( "author", "Tobias Koenig", i18n( "Author" ) );
751     mDocumentInfo.set( "title", "The Art of Okular Plugin Development", i18n( "Title" ) );
753     Okular::DocumentViewport viewport = ... // get the viewport of the chapter
755     QDomElement item = mDocumentSynopsis.createElement( "Chapter 1" );
756     item.setAttribute( "Viewport", viewport.toString() );
757     mDocumentSynopsis.appendChild( item );
759     viewport = ... // get the viewport of the subchapter
761     QDomElement childItem = mDocumentSynopsis.createElement( "SubChapter 1.1" );
762     childItem.setAttribute( "Viewport", viewport.toString() );
763     item.appendChild( childItem );
765     return true;
768 bool HTMLGenerator::doCloseDocument()
770     delete mTextDocument;
771     mTextDocument = 0;
773     return true;
776 bool HTMLGenerator::canGeneratePixmap() const
778     return true;
781 void HTMLGenerator::generatePixmap( Okular::PixmapRequest *request )
783     QPixmap *pixmap = new QPixmap( request->width(), request->height() );
784     pixmap->fill( Qt::white );
786     QPainter p;
787     p.begin( pixmap );
789     qreal width = request->width();
790     qreal height = request->height();
792     p.scale( width / 600, height / 800 );
794     const QRect rect( 0, request->pageNumber() * 800, 600, 800 );
795     p.translate( QPoint( 0, request->pageNumber() * -800 ) );
796     d->mDocument->drawContents( &p, rect );
797     p.end();
799     request->page()->setPixmap( request->id(), pixmap );
801     signalPixmapRequestDone( request );
804 const Okular::DocumentInfo* HTMLGenerator::generateDocumentInfo()
806     return &mDocumentInfo;
809 const Okular::DocumentSynopsis* HTMLGenerator::generateDocumentSynopsis()
811     if ( !mDocumentSynopsis.hasChildNodes() )
812         return 0;
813     else
814         return &mDocumentSynopsis;
817 bool HTMLGenerator::print( KPrinter &printer )
819     QPainter p( &printer );
821     for ( int i = 0; i < mTextDocument->pageCount(); ++i ) {
822         if ( i != 0 )
823             printer.newPage();
825         QRect rect( 0, i * 800, 600, 800 );
826         p.translate( QPoint( 0, i * -800 ) );
827         mTextDocument->drawContents( &p, rect );
828     }
831 Okular::ExportFormat::List HTMLGenerator::exportFormats() const
833     return Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText );
836 bool HTMLGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format )
838     QFile file( fileName );
839     if ( !fileName.open( QIODevice::WriteOnly ) ) {
840         emit error( i18n( "Unable to open file" ), -1 );
841         return false;
842     }
844     if ( format.mimeType()->name() == QLatin1String( "text/plain" ) )
845         file.writeBlock( mTextDocument->toPlainText().toUtf8() );
847     file.close();
849     return true;
851 \endcode
853 Let's take a closer look at the single methods. In the loadDocument() method we try to open the
854 passed file name and read all the content into the QTextDocument object. By calling
855 QTextDocument::setPageSize(), the whole document is divided into pages of the given size.
856 In the next step we create Okular::Page objects for every page in the QTextDocument and fill
857 the pages vector with them.
859 Afterwards we fill our Okular::DocumentInfo object with data. Since extracting the HTML meta data
860 would need a lot of code we work with static data here. [to be continued]
863 // DOXYGEN_EXCLUDE = conf generators shell ui
864 // DOXYGEN_FILE_PATTERNS = core/*.h *.dox