Warning, /graphics/okular/Mainpage.dox is written in an unsupported language. File is not indexed.

0001 /**
0002 \mainpage Okular, the unified document viewer
0003 
0004 \section okular_overview Overview
0005 
0006 - \ref okular_history
0007 - \ref okular_design
0008 - \ref okular_generators
0009 - <a href="http://www.okular.org">Website</a>
0010 
0011 \authors Tobias König <tokoe@kde.org>
0012 
0013 \licenses \lgpl
0014 
0015 \page okular_history Historical background
0016 
0017 Okular is the successor of <a href="http://kpdf.kde.org">kpdf</a>, the PDF viewer in KDE 3.
0018 kpdf was refactored and extended in a Google Summer of Code project to support not only
0019 viewing PDF but also other types of document, e.g. PostScript files, images and many more.
0020 
0021 \page okular_design The Design of Okular
0022 
0023 To support a wide range of document formats, Okular was designed in a modular way, so you
0024 have the following components:
0025 
0026   \li \ref Shell
0027   \li \ref Okular::Part
0028   \li \ref Okular::Document Class
0029   \li \ref Okular::Generator
0030 
0031 The shell is the application which is started by the user as standalone application and
0032 which embeds the part. The part contains all GUI elements of Okular, for example the
0033 content list, the bookmark manager, menus and the graphical view of the document class.
0034 The document class is an abstract presentation of the document content. It contains information
0035 about every page of the document, its size, orientation etc.
0036 
0037 But somehow the document class must retrieve these information from the various types of documents.
0038 This is the task of the Generators. Generators are plugins which are loaded at runtime and which
0039 have the knowledge about the internal structure of the different document types.
0040 They extract the needed information from the documents, convert the data into a common format and
0041 pass them to the document class.
0042 
0043 Currently Generators for the following document types are available:
0044 
0045   \li Portable Document Format (PDF)
0046   \li PostScript
0047   \li Device Independent Format (DVI)
0048   \li DeJaVu Format
0049   \li Comic Books
0050   \li Images (JPEG, PNG, GIF, and many more)
0051   \li TIFF Image Format
0052   \li FictionBook Format
0053   \li Plucker Format
0054   \li OpenDocument Text Format
0055   \li Microsoft's CHM Format
0056   \li Microsoft's XML Document Format
0057   \li Markdown Format
0058 
0059 Now the questions is how can these various formats be represented in a unified way?
0060 Okular provides features like rotation, text search and extraction, zooming and many more, so how
0061 does it match with the different capabilities of the formats?
0062 
0063 \section okular_design_basics Basics of Generators
0064 
0065 Lets start with the smallest commonness of all document formats:
0066 
0067   \li they have pages (one ore more) of a given size
0068   \li pages can be represented as pictures
0069 
0070 So the first thing every Generator must support is to return the number of pages of a document.
0071 Furthermore it must be able to return the picture of a page at a requested size.
0072 
0073 For vector based document formats (e.g. PDF or PostScript) the Generators can render the page for
0074 the requested size, for static documents formats (e.g. images), the Generator must scale the
0075 content according to the requested size, so when you zoom a page in Okular, the Generators are
0076 just asked to return the page for the zoomed size.
0077 
0078 When the document class has retrieved the page pictures from the Generators, it can do further
0079 image manipulation on it, for example rotating them or applying fancy effects.
0080 
0081 \section okular_design_text_support Generators with Text support
0082 
0083 Some document formats however support more functionality than just representing a page as an image.
0084 PDF, PostScript, DVI and DeJaVu for example contains a machine readable representation of the
0085 included text. For those document formats Okular provides additional features like text search,
0086 text extraction and text selection.
0087 
0088 How is that supported by the Generators?
0089 
0090 To access the text from the documents the generators must extract it somehow and make it available
0091 to the document class. However for the text selection feature the document class must also know <em>where</em>
0092 the extracted text is located on the page. For a zoom factor of 100% the absolute position of
0093 the text in the document can be used, however for larger or smaller zoom factors the position
0094 must be recalculated. To make this calculation as easy as possible, the Generators return an
0095 abstract representation (\ref Okular::TextPage) of the text which includes every character together
0096 with its <em>normalized</em> position. Normalized means that the width and height of the page is
0097 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.
0098 
0099 So when you want to know where this character is located on the page which is zoomed at 300%, you just
0100 multiply the position by 3 * page width (and page height) and get the absolute position for this zoom level.
0101 
0102 This abstract text representation also allows an easy rotation of the coordinates, so that text selection
0103 is available on rotated pages as well.
0104 
0105 \section okular_design_meta_information Meta Information
0106 
0107 Most documents have additional meta information:
0108 
0109   \li Name of the author
0110   \li Date of creation
0111   \li Version number
0112   \li Table of Content
0113   \li Bookmarks
0114   \li Annotations
0115 
0116 These information can be retrieved by the generator as well and will be shown by Okular.
0117 
0118 \page okular_generators How to implement a Generator
0119 
0120 The power of Okular is its extensibility by Generator plugins. This section will describe how to
0121 implement your own plugin for a new document type.
0122 
0123   \li \ref okular_generators_basic
0124   \li \ref okular_generators_with_text
0125   \li \ref okular_generators_threaded
0126   \li \ref okular_generators_extended
0127 
0128 \section okular_generators_basic A Basic Generator
0129 
0130 To provide a short overview and don't reimplementing an existing generator we'll work on a Generator
0131 for the Magic document format, a non existing, pure virtual format :)
0132 
0133 Lets assume we have some helper class (MagicDocument) which provides the following functionality for this
0134 document format:
0135 
0136  \li Loading a document
0137  \li Retrieving number of pages
0138  \li Returning a fixed size picture representation of a page
0139 
0140 The class API looks like this
0141 
0142 \code
0143 class MagicDocument
0144 {
0145     public:
0146         MagicDocument();
0147         ~MagicDocument();
0148 
0149         bool loadDocument( const QString &fileName );
0150 
0151         int numberOfPages() const;
0152 
0153         QSize pageSize( int pageNumber ) const;
0154 
0155         QImage pictureOfPage( int pageNumber ) const;
0156 
0157     private:
0158         ...
0159 };
0160 \endcode
0161 
0162 The methods should be self explaining, loadDocument() loads a document file and returns false on error,
0163 numberOfPages() returns the number of pages, pageSize() returns the size of the page and pictureOfPage()
0164 returns the picture representation of the page.
0165 
0166 Our first version of our Generator is a basic one which just provides page pictures to the document class.
0167 
0168 The API of the Generator looks like the following:
0169 
0170 \code
0171 #include "magicdocument.h"
0172 
0173 #include <okular/core/generator.h>
0174 
0175 class MagicGenerator : public Okular::Generator
0176 {
0177     public:
0178         MagicGenerator( QObject *parent, const QVariantList &args );
0179         ~MagicGenerator();
0180 
0181         bool loadDocument( const QString &fileName, QVector<Okular::Page*> &pages );
0182 
0183         bool canGeneratePixmap() const;
0184         void generatePixmap( Okular::PixmapRequest *request );
0185 
0186     protected:
0187         bool doCloseDocument();
0188 
0189     private:
0190         MagicDocument mMagicDocument;
0191 };
0192 \endcode
0193 
0194 The implementation of the Generator looks like this:
0195 
0196 \code
0197 #include <okular/core/page.h>
0198 
0199 #include "magicgenerator.h"
0200 
0201 OKULAR_EXPORT_PLUGIN(MagicGenerator, "libokularGenerator_magic.json")
0202 
0203 MagicGenerator::MagicGenerator( QObject *parent, const QVariantList &args )
0204     : Okular::Generator( parent, args )
0205 {
0206 }
0207 
0208 MagicGenerator::~MagicGenerator()
0209 {
0210 }
0211 
0212 bool MagicGenerator::loadDocument( const QString &fileName, QVector<Okular::Page*> &pages )
0213 {
0214     if ( !mMagicDocument.loadDocument( fileName ) ) {
0215         emit error( i18n( "Unable to load document" ), -1 );
0216         return false;
0217     }
0218 
0219     pagesVector.resize( mMagicDocument.numberOfPages() );
0220 
0221     for ( int i = 0; i < mMagicDocument.numberOfPages(); ++i ) {
0222       const QSize size = mMagicDocument.pageSize( i );
0223 
0224       Okular::Page * page = new Okular::Page( i, size.width(), size.height(), Okular::Rotation0 );
0225       pages[ i ] = page;
0226     }
0227 
0228     return true;
0229 }
0230 
0231 bool MagicGenerator::doCloseDocument()
0232 {
0233     return true;
0234 }
0235 
0236 bool MagicGenerator::canGeneratePixmap() const
0237 {
0238     return true;
0239 }
0240 
0241 void MagicGenerator::generatePixmap( Okular::PixmapRequest *request )
0242 {
0243     QImage image = mMagicDocument.pictureOfPage( request->pageNumber() );
0244 
0245     image = image.scaled( request->width(), request->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
0246 
0247     request->page()->setPixmap( request->id(), new QPixmap( QPixmap::fromImage( image ) ) );
0248 
0249     signalPixmapRequestDone( request );
0250 }
0251 
0252 \endcode
0253 
0254 As you can see implementing a basic Generator is quite easy. The loadDocument() method opens the document file
0255 and extracts the number of pages. For every page in the document it adds an Okular::Page object to the pages vector
0256 which is passed in as method argument. Each page is initialized with its page number, width, height and initial rotation.
0257 These page objects will be stored in the document object and act as a container for the picture representation
0258 of the pages. This code is the same for nearly every Generator. On an failure the error() signal can be emitted
0259 to inform the user about the issue. This code is the same for nearly every Generator.
0260 
0261 In the doCloseDocument() method you should close the document and free all resources you have allocated in openDocument().
0262 
0263 Now we come to the picture creation methods. The canGeneratorPixmap() method returns whether the Generator is currently
0264 able to handle a new pixmap generation request. For a simple Generator like our one that's always the case as it works
0265 linear, however a multithreaded Generator might return <em>false</em> here if it is still waiting for one of its working
0266 threads to finish. In this case the document class will try to request the pixmap later again.
0267 
0268 The generatePixmap() method does the actual fetching of the picture for a page. The page number, requested width and
0269 height of the page is encapsulated in the passed Okular::PixmapRequest object.
0270 So the task of the Generator is to create a pixmap of the requested page in the requested size and then store this
0271 pixmap in the Okular::Page object which is associated with the page request.
0272 When this task is finished, the Generator has to call signalPixmapRequestDone() with the page request object
0273 as argument. This extra call is needed to allow the Generator to use signals and slots internally and create the
0274 pixmap asynchronously.
0275 
0276 So now you have the code of a working Okular Generator, the next step is to tell Okular about the new plugin.
0277 Like in other places in KDE that is done by .desktop files, which are installed to the services directory.
0278 
0279 Every Generator needs 1 .json, 3 .desktop files, and 1 .xml file:
0280 
0281   \li libokularGenerator_&lt;name&gt;.json
0282   \li okularApplication_&lt;name&gt;.desktop
0283   \li okular&lt;name&gt;.desktop
0284   \li org.kde.mobile.okular_&lt;name&gt;.desktop
0285   \li org.kde.okular-&lt;name&gt;.metainfo.xml
0286 
0287 where &lt;name&gt; should be the name of the document format. So for our Magic Document Generator we
0288 create the following 4 files:
0289 
0290   \li libokularGenerator_magic.json
0291   \li okularApplication_magic.desktop
0292   \li okularMagic.desktop
0293   \li org.kde.mobile.okular_magic.desktop
0294   \li org.kde.okular-magic.metainfo.xml
0295 
0296 where libokularGenerator_magic.json has the following content something like this
0297 
0298 \verbatim
0299 {
0300     "KPlugin": {
0301         "Authors": [
0302             {
0303                 "Email": "author@hosting.suffix",
0304                 "Name": "Proud Author",
0305             }
0306         ],
0307         "Copyright": "© 2042 Proud Author",
0308         "Id": "okular_magic",
0309         "License": "GPL",
0310         "MimeTypes": [
0311             "text/magic",
0312             "text/x-magic"
0313         ],
0314         "Name": "Magic Backend",
0315         "Version": "0.1.0"
0316     },
0317     "X-KDE-Priority": 1,
0318     "X-KDE-okularAPIVersion": 1,
0319     "X-KDE-okularHasInternalSettings": true
0320 }
0321 \endverbatim
0322 
0323 The last five fields has the special meaning to Okular
0324 
0325   \li <b>MimeType</b> The mimetype or list of mimetypes of the supported document format(s)
0326   \li <b>X-KDE-Priority</b> When multiple Generators for the same mimetype exists, the one with the highest priority is used
0327   \li <b>X-KDE-okularAPIVersion</b> The version of the Generator Plugin API ('1' currently)
0328   \li <b>X-KDE-okularHasInternalSettings</b> Is 'true' when the Generator provides configuration dialogs
0329 
0330 The first .desktop file has the following content:
0331 
0332 \verbatim
0333 [Desktop Entry]
0334 MimeType=application/x-magic;
0335 Terminal=false
0336 Name=okular
0337 GenericName=Document Viewer
0338 Exec=okular %U
0339 Icon=okular
0340 Type=Application
0341 InitialPreference=7
0342 Categories=Qt;KDE;Graphics;Viewer;
0343 NoDisplay=true
0344 X-KDE-Keywords=Magic
0345 \endverbatim
0346 
0347 You can use the file as it is, you just have to adapt the mimetype. This file is needed to allow Okular
0348 to handle multiple mimetypes.
0349 
0350 The second .desktop file looks like this:
0351 
0352 \verbatim
0353 [Desktop Entry]
0354 Icon=okular
0355 Name=okular
0356 X-KDE-ServiceTypes=KParts/ReadOnlyPart
0357 X-KDE-Library=okularpart
0358 Type=Service
0359 MimeType=application/x-magic;
0360 \endverbatim
0361 
0362 where
0363 
0364   \li <b>X-KDE-Library</b> The name of the plugin library
0365 
0366 You can use the file as it is as well, you just have to adapt the mimetype. This file is needed to allow
0367 the Okular part to handle multiple mimetypes.
0368 
0369 The third .desktop file contains data for the mobile version
0370 
0371 \verbatim
0372 [Desktop Entry]
0373 MimeType=application/x-magic;
0374 Name=Reader
0375 GenericName=Document viewer
0376 Comment=Viewer for various types of documents
0377 TryExec=kpackagelauncherqml -a org.kde.mobile.okular
0378 Exec=kpackagelauncherqml -a org.kde.mobile.okular %u
0379 Terminal=false
0380 Icon=okular
0381 Type=Application
0382 Categories=Qt;KDE;Graphics;Office;Viewer;
0383 InitialPreference=2
0384 NoDisplay=true
0385 X-KDE-Keywords=Magic
0386 \endverbatim
0387 
0388 And the last .xml file has the following content
0389 
0390 \verbatim
0391 <?xml version="1.0" encoding="utf-8"?>
0392 <component type="addon">
0393   <id>org.kde.okular-md</id>
0394   <extends>org.kde.okular.desktop</extends>
0395   <metadata_license>CC0-1.0</metadata_license>
0396   <project_license>GPL-2.0+ and GFDL-1.3</project_license>
0397   <name>Magic</name>
0398   <summary>Adds support for reading Magic documents</summary>
0399   <mimetypes>
0400     <mimetype>application/magic</mimetype>
0401   </mimetypes>
0402   <url type="homepage">https://okular.kde.org</url>
0403 </component>
0404 \endverbatim
0405 
0406 The last piece you need for a complete Generator is a CMakeLists.txt which compiles and installs the
0407 Generator. Our CMakeLists.txt looks like the following:
0408 
0409 \verbatim
0410 add_definitions(-DTRANSLATION_DOMAIN="okular_magic")
0411 
0412 macro_optional_find_package(Okular)
0413 
0414 include_directories( ${OKULAR_INCLUDE_DIR} ${KF6_INCLUDE_DIR} ${QT_INCLUDES} )
0415 
0416 ########### next target ###############
0417 
0418 set( okularGenerator_magic_PART_SRCS generator_magic.cpp )
0419 
0420 target_link_libraries( okularGenerator_magic PRIVATE okularcore KF6::I18n KF6::KIOCore )
0421 
0422 install( TARGETS okularGenerator_magic DESTINATION ${PLUGIN_INSTALL_DIR} )
0423 
0424 ########### install files ###############
0425 
0426 install( FILES okularMagic.desktop  DESTINATION  ${KDE_INSTALL_KSERVICES5DIR} )
0427 install( PROGRAMS okularApplication_magic.desktop org.kde.mobile.okular_magic.desktop DESTINATION ${KDE_INSTALL_APPDIR} )
0428 install( FILES org.kde.okular-magic.metainfo.xml DESTINATION ${KDE_INSTALL_METAINFODIR} )
0429 \endverbatim
0430 
0431 The macro_optional_find_package(Okular) call is required to make the ${OKULAR_INCLUDE_DIR} and ${OKULAR_LIBRARIES}
0432 variables available.
0433 
0434 Now you can compile the Generator plugin and install it. After a restart of Okular the new plugin is available
0435 and you can open Magic documents.
0436 
0437 \section okular_generators_with_text A Generator with TextPage support
0438 
0439 In this section we want to extend our Generator to support text search, text extraction and selection
0440 as well. As mentioned in \ref okular_design_text_support, the Generator must provide an Okular::TextPage
0441 object for every page which contains readable text.
0442 
0443 Since we use the helper class MagicDocument to read the data from the document we have to extend it first,
0444 so the new API looks as the following:
0445 
0446 \code
0447 class MagicDocument
0448 {
0449     public:
0450         MagicDocument();
0451         ~MagicDocument();
0452 
0453         bool loadDocument( const QString &fileName );
0454 
0455         int numberOfPages() const;
0456 
0457         QSize pageSize( int pageNumber ) const;
0458 
0459         QImage pictureOfPage( int pageNumber ) const;
0460 
0461         class TextInfo
0462         {
0463             public:
0464                 typedef QList<TextInfo> List;
0465 
0466                 QChar character;
0467                 qreal xPos;
0468                 qreal yPos;
0469                 qreal width;
0470                 qreal height;
0471         };
0472 
0473         TextInfo::List textOfPage( int pageNumber );
0474 
0475     private:
0476         ...
0477 };
0478 \endcode
0479 
0480 MagicDocument has the new internal class TextInfo now, which contains a character and
0481 its absolute position on a page. Furthermore MagicDocument provides a method textOfPage()
0482 which returns a list of all TextInfo objects for a page.
0483 
0484 That's really an optimistic API, in reality it is sometimes quite hard to find out
0485 the position of single characters in a document format.
0486 
0487 With the extension of our helper class we can continue on extending our Generator now:
0488 
0489 \code
0490 #include "magicdocument.h"
0491 
0492 #include <okular/core/generator.h>
0493 
0494 class MagicGenerator : public Okular::Generator
0495 {
0496     public:
0497         MagicGenerator( QObject *parent, const QVariantList &args );
0498         ~MagicGenerator();
0499 
0500         bool loadDocument( const QString &fileName, QVector<Okular::Page*> &pages );
0501 
0502         bool canGeneratePixmap() const;
0503         void generatePixmap( Okular::PixmapRequest *request );
0504 
0505         virtual bool canGenerateTextPage() const;
0506         virtual void generateTextPage( Okular::Page *page, enum Okular::GenerationType type = Okular::Synchronous );
0507 
0508     protected:
0509         bool doCloseDocument();
0510 
0511     private:
0512         MagicDocument mMagicDocument;
0513 };
0514 \endcode
0515 
0516 We have extended the MagicGenerator class by two methods canGenerateTextPage() and generateTextPage().
0517 The first method is equal to canGeneratePixmap(), it returns whether the Generator is currently able to
0518 handle a new text page generation request. For linear Generators that should be always the case, however
0519 when the generation is done in a separated worker thread, this method might return <em>false</em>.
0520 In this case the document class will try to request the text page later again.
0521 
0522 The second method will generate the Okular::TextPage object for the passed page. Depending on the capabilities
0523 of the Generator and the passed <em>type</em> parameter that is done synchronously or asynchronously.
0524 
0525 Let us take a look at the implementation of these methods in our MagicGenerator:
0526 
0527 \code
0528 #include <okular/core/textpage.h>
0529 
0530 ...
0531 
0532 MagicGenerator::MagicGenerator( QObject *parent, const QVariantList &args )
0533     : Okular::Generator( parent, args )
0534 {
0535     setFeature( TextExtraction );
0536 }
0537 
0538 bool MagicGenerator::canGenerateTextPage() const
0539 {
0540     return true;
0541 }
0542 
0543 void MagicGenerator::generateTextPage( Okular::Page *page, enum Okular::GenerationType )
0544 {
0545     MagicDocument::TextInfo::List characters = mMagicDocument.textOfPage( page->number() );
0546     if ( characters.isEmpty() )
0547         return;
0548 
0549     Okular::TextPage *textPage = new Okular::TextPage;
0550     for ( int i = 0; i < characters.count(); ++i ) {
0551         qreal left = characters[ i ].xPos / page->width();
0552         qreal top = characters[ i ].yPos / page->height();
0553         qreal right = (characters[ i ].xPos + characters[ i ].width) / page->width();
0554         qreal bottom = (characters[ i ].yPos + characters[ i ].height) / page->height();
0555 
0556         textPage->append( characters[ i ].character,
0557                           new Okular::NormalizedRect( left, top, right, bottom ) );
0558     }
0559 
0560     page->setTextPage( textPage );
0561 }
0562 \endcode
0563 
0564 As you can see the generateTextPage method just iterates over the list of characters returned
0565 by our MagicDocument helper class and adds the character and its normalized bounding rect to
0566 the Okular::TextPage object. At the end the text page is assigned to the page. We don't pay
0567 attention to the GenerationType parameter here, if your Generator want to use threads, it should
0568 check here whether the request shall be done asynchronously or synchronously and start the generation
0569 according to that. Additionally we have to tell the Okular::Generator base class that we support
0570 text handling by setting this flag in the constructor.
0571 
0572 In this state we can now search, select and extract text from Magic documents.
0573 
0574 \section okular_generators_threaded A Generator with Thread support
0575 
0576 Sometimes it makes sense to do the generation of page pictures or text pages asynchronously to
0577 improve performance and don't blocking the user interface. This can be done in two ways, either
0578 by using signals and slots or by using threads. Both have there pros and cons:
0579 
0580   <ul>
0581     <li><b>Signals and Slots</b></li>
0582     <ul>
0583     <li>Pro: Can be used with backend libraries which are not thread safe</li>
0584     <li>Con: Sometime difficult to implement</li>
0585     </ul>
0586     <li><b>Threads</b></li>
0587     <ul>
0588     <li>Pro: Easy to implement as you can make synchronous calls to the backend libraries</li>
0589     <li>Con: Backend libraries must be thread safe and you must prevent race conditions by using mutexes</li>
0590     </ul>
0591   </ul>
0592 
0593 The signal and slots approach can be achieved with a normal Generator by calling Okular::Generator::signalPixmapRequestDone()
0594 from a slot after pixmap generation has been finished.
0595 
0596 When using threads you should use a slightly different API, which hides most of the thread usage, to make
0597 implementing as easy as possible.
0598 
0599 Let's assume the pictureOfPage() and textOfPage methods in our MagicDocument helper class are thread safe,
0600 so we can use them in a multithreaded environment.
0601 So nothing prevents us from changing the MagicGenerator to use threads for better performance.
0602 
0603 The new MagicGenerator API looks like the following:
0604 
0605 \code
0606 #include "magicdocument.h"
0607 
0608 #include <okular/core/generator.h>
0609 
0610 class MagicGenerator : public Okular::Generator
0611 {
0612     public:
0613         MagicGenerator( QObject *parent, const QVariantList &args );
0614         ~MagicGenerator();
0615 
0616         bool loadDocument( const QString &fileName, QVector<Okular::Page*> &pages );
0617 
0618     protected:
0619         bool doCloseDocument();
0620 
0621         virtual QImage image( Okular::PixmapRequest *request );
0622 
0623         virtual Okular::TextPage* textPage( Okular::Page *page );
0624 
0625     private:
0626         MagicDocument mMagicDocument;
0627 };
0628 \endcode
0629 
0630 As you can see the canGeneratePixmap() generatePixmap(), canGenerateTextPage() and generateTextPage() methods have
0631 been removed and replaced by the image() and textPage() methods.
0632 
0633 Before explaining why, we'll take a look at the implementation:
0634 
0635 \code
0636 
0637 MagicGenerator::MagicGenerator( QObject *parent, const QVariantList &args )
0638     : Okular::Generator( parent, args )
0639 {
0640     setFeature( TextExtraction );
0641     setFeature( Threaded );
0642 }
0643 
0644 QImage MagicGenerator::image( Okular::PixmapRequest *request )
0645 {
0646     QImage image = mMagicDocument.pictureOfPage( request->pageNumber() );
0647 
0648     return image.scaled( request->width(), request->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
0649 }
0650 
0651 Okular::TextPage* textPage( Okular::Page *page )
0652 {
0653     MagicDocument::TextInfo::List characters = mMagicDocument.textOfPage( page->number() );
0654     if ( characters.isEmpty() )
0655         return 0;
0656 
0657     Okular::TextPage *textPage = new Okular::TextPage;
0658     for ( int i = 0; i < characters.count(); ++i ) {
0659         qreal left = characters[ i ].xPos / page->width();
0660         qreal top = characters[ i ].yPos / page->height();
0661         qreal right = (characters[ i ].xPos + characters[ i ].width) / page->width();
0662         qreal bottom = (characters[ i ].yPos + characters[ i ].height) / page->height();
0663 
0664         textPage->append( characters[ i ].character,
0665                           new Okular::NormalizedRect( left, top, right, bottom ) );
0666     }
0667 
0668     return textPage;
0669 }
0670 \endcode
0671 
0672 So the first obviously thing is that both methods return a value instead of modifying the page directly.
0673 The reason for this is that both methods are executed in its own thread, so the code executed in them can
0674 block as long as it wants, it won't block the GUI anyway. Additionally we have to tell the Okular::Generator
0675 base class that we can handle threads by setting the flag in the constructor.
0676 
0677 With only a small change we made our MagicGenerator multithreaded now!
0678 
0679 \section okular_generators_extended An Extended Generator
0680 
0681 Now we want to create a new generator with some additional functionality:
0682 
0683   \li Support for document information (author, creation date etc.)
0684   \li Support for a table of content
0685   \li Support for printing the document
0686   \li Support for exporting the document as text
0687 
0688 The new Generator shall be able to handle HTML documents. We choose this format as example, because
0689 we can use QTextDocument to load, render and print a HTML page, so a lot of code can be reused.
0690 
0691 The API of our HTMLGenerator looks like the following:
0692 
0693 \code
0694 #include <QtGui/QTextDocument>
0695 
0696 #include <okular/core/generator.h>
0697 
0698 class HTMLGenerator : public Okular::Generator
0699 {
0700     public:
0701         HTMLGenerator( QObject *parent, const QVariantList &args );
0702         ~HTMLGenerator();
0703 
0704         bool loadDocument( const QString &fileName, QVector<Okular::Page*> &pages );
0705 
0706         bool canGeneratePixmap() const;
0707         void generatePixmap( Okular::PixmapRequest *request );
0708 
0709         virtual Okular::DocumentInfo generateDocumentInfo( const QSet<Okular::DocumentInfo::Key> &keys ) const;
0710 
0711         virtual const Okular::DocumentSynopsis* generateDocumentSynopsis();
0712 
0713         virtual bool print( KPrinter &printer );
0714 
0715         virtual Okular::ExportFormat::List exportFormats() const;
0716 
0717         virtual bool exportTo( const QString &fileName, const Okular::ExportFormat &format );
0718 
0719     protected:
0720         bool doCloseDocument();
0721 
0722     private:
0723         QTextDocument *mTextDocument;
0724         Okular::DocumentInfo mDocumentInfo;
0725         Okular::DocumentSynopsis mDocumentSynopsis;
0726 };
0727 \endcode
0728 
0729 The Generator doesn't support text search and selection, as the code would be quite complex, we'll show
0730 how to do it in the next chapter (not yet written) anyway.
0731 
0732 As you can see we have 5 new methods in the class:
0733 
0734   \li <b>generateDocumentInfo()</b> Creates an Okular::DocumentInfo (which is in fact a QDomDocument)
0735       which contains document information like author, creation time etc.
0736   \li <b>generateDocumentSynopsis()</b> Creates an Okular::DocumentSynopsis (which is a QDomDocument as well)
0737       which contains the table of content.
0738   \li <b>print()</b> Prints the document to the passed printer.
0739   \li <b>exportFormats()</b> Returns the supported export formats.
0740   \li <b>exportTo()</b> Exports the document to the given file in the given format.
0741 
0742 Now that you know what the methods are supposed to do, let's take a look at the implementation:
0743 
0744 \code
0745 #include <QFile>
0746 #include <QAbstractTextDocumentLayout>
0747 #include <QPrinter>
0748 
0749 #include <okular/core/document.h>
0750 #include <okular/core/page.h>
0751 
0752 #include "htmlgenerator.h"
0753 
0754 #include <KLocalizedString>
0755 
0756 OKULAR_EXPORT_PLUGIN(HTMLGenerator, "libokularGenerator_html.json")
0757 
0758 HTMLGenerator::HTMLGenerator( QObject *parent, const QVariantList &args )
0759     : Okular::Generator( parent, args ),
0760       mTextDocument( 0 )
0761 {
0762 }
0763 
0764 HTMLGenerator::~HTMLGenerator()
0765 {
0766     delete mTextDocument;
0767 }
0768 
0769 bool HTMLGenerator::loadDocument( const QString &fileName, QVector<Okular::Page*> &pages )
0770 {
0771     QFile file( fileName );
0772     if ( !file.open( QIODevice::ReadOnly ) ) {
0773         emit error( i18n( "Unable to open file" ), -1 );
0774         return false;
0775     }
0776 
0777     const QString data = QString::fromUtf8( file.readAll() );
0778 
0779     file.close();
0780 
0781     mTextDocument = new QTextDocument;
0782     mTextDocument->setHtml( data );
0783     mTextDocument->setPageSize( QSizeF( 600, 800 ) );
0784 
0785     pages.resize( mTextDocument->pageCount() );
0786 
0787     for ( int i = 0; i < mTextDocument->pageCount(); ++i ) {
0788       Okular::Page * page = new Okular::Page( i, 600, 800, Okular::Rotation0 );
0789       pages[ i ] = page;
0790     }
0791 
0792     mDocumentInfo.set( "author", "Tobias Koenig", i18n( "Author" ) );
0793     mDocumentInfo.set( "title", "The Art of Okular Plugin Development", i18n( "Title" ) );
0794 
0795     Okular::DocumentViewport viewport = ... // get the viewport of the chapter
0796 
0797     QDomElement item = mDocumentSynopsis.createElement( "Chapter 1" );
0798     item.setAttribute( "Viewport", viewport.toString() );
0799     mDocumentSynopsis.appendChild( item );
0800 
0801     viewport = ... // get the viewport of the subchapter
0802 
0803     QDomElement childItem = mDocumentSynopsis.createElement( "SubChapter 1.1" );
0804     childItem.setAttribute( "Viewport", viewport.toString() );
0805     item.appendChild( childItem );
0806 
0807     return true;
0808 }
0809 
0810 bool HTMLGenerator::doCloseDocument()
0811 {
0812     delete mTextDocument;
0813     mTextDocument = 0;
0814 
0815     return true;
0816 }
0817 
0818 bool HTMLGenerator::canGeneratePixmap() const
0819 {
0820     return true;
0821 }
0822 
0823 void HTMLGenerator::generatePixmap( Okular::PixmapRequest *request )
0824 {
0825     QPixmap *pixmap = new QPixmap( request->width(), request->height() );
0826     pixmap->fill( Qt::white );
0827 
0828     QPainter p;
0829     p.begin( pixmap );
0830 
0831     qreal width = request->width();
0832     qreal height = request->height();
0833 
0834     p.scale( width / 600, height / 800 );
0835 
0836     const QRect rect( 0, request->pageNumber() * 800, 600, 800 );
0837     p.translate( QPoint( 0, request->pageNumber() * -800 ) );
0838     d->mDocument->drawContents( &p, rect );
0839     p.end();
0840 
0841     request->page()->setPixmap( request->id(), pixmap );
0842 
0843     signalPixmapRequestDone( request );
0844 }
0845 
0846 Okular::DocumentInfo HTMLGenerator::generateDocumentInfo( const QSet<Okular::DocumentInfo::Key> &keys ) const
0847 {
0848     return mDocumentInfo;
0849 }
0850 
0851 const Okular::DocumentSynopsis* HTMLGenerator::generateDocumentSynopsis()
0852 {
0853     if ( !mDocumentSynopsis.hasChildNodes() )
0854         return 0;
0855     else
0856         return &mDocumentSynopsis;
0857 }
0858 
0859 bool HTMLGenerator::print( KPrinter &printer )
0860 {
0861     QPainter p( &printer );
0862 
0863     for ( int i = 0; i < mTextDocument->pageCount(); ++i ) {
0864         if ( i != 0 )
0865             printer.newPage();
0866 
0867         QRect rect( 0, i * 800, 600, 800 );
0868         p.translate( QPoint( 0, i * -800 ) );
0869         mTextDocument->drawContents( &p, rect );
0870     }
0871 }
0872 
0873 Okular::ExportFormat::List HTMLGenerator::exportFormats() const
0874 {
0875     return Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText );
0876 }
0877 
0878 bool HTMLGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format )
0879 {
0880     QFile file( fileName );
0881     if ( !fileName.open( QIODevice::WriteOnly ) ) {
0882         emit error( i18n( "Unable to open file" ), -1 );
0883         return false;
0884     }
0885 
0886     if ( format.mimeType()->name() == QLatin1String( "text/plain" ) )
0887         file.writeBlock( mTextDocument->toPlainText().toUtf8() );
0888 
0889     file.close();
0890 
0891     return true;
0892 }
0893 \endcode
0894 
0895 Let's take a closer look at the single methods. In the loadDocument() method we try to open the
0896 passed file name and read all the content into the QTextDocument object. By calling
0897 QTextDocument::setPageSize(), the whole document is divided into pages of the given size.
0898 In the next step we create Okular::Page objects for every page in the QTextDocument and fill
0899 the pages vector with them.
0900 
0901 Afterwards we fill our Okular::DocumentInfo object with data. Since extracting the HTML meta data
0902 would need a lot of code we work with static data here. [to be continued]
0903 */