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_<name>.json 0282 \li okularApplication_<name>.desktop 0283 \li okular<name>.desktop 0284 \li org.kde.mobile.okular_<name>.desktop 0285 \li org.kde.okular-<name>.metainfo.xml 0286 0287 where <name> 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 */