Mainpage.dox 30.8 KB
 Tobias Koenig committed May 12, 2007 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /** \mainpage Okular, the unified document viewer \section okular_overview Overview - \ref okular_history - \ref okular_design - \ref okular_generators - Website \authors Tobias König \licenses \lgpl \page okular_history Historical background  Albert Astals Cid committed May 19, 2007 17 Okular is the successor of kpdf, the PDF viewer in KDE 3.  Tobias Koenig committed May 12, 2007 18 19 20 21 22 23 24 25 26 kpdf was refactored and extended in a Google Summer of Code project to support not only viewing PDF but also other types of document, e.g. PostScript files, images and many more. \page okular_design The Design of Okular To support a wide range of document formats, Okular was designed in a modular way, so you have the following components: \li \ref Shell  Yuri Chornoivan committed Feb 07, 2019 27  \li \ref Okular::Part  Tobias Koenig committed May 12, 2007 28 29 30 31  \li \ref Okular::Document Class \li \ref Okular::Generator The shell is the application which is started by the user as standalone application and  Yuri Chornoivan committed Nov 20, 2018 32 which embeds the part. The part contains all GUI elements of Okular, for example the  Tobias Koenig committed May 12, 2007 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 content list, the bookmark manager, menus and the graphical view of the document class. The document class is an abstract presentation of the document content. It contains information about every page of the document, its size, orientation etc. But somehow the document class must retrieve these information from the various types of documents. This is the task of the Generators. Generators are plugins which are loaded at runtime and which have the knowledge about the internal structure of the different document types. They extract the needed information from the documents, convert the data into a common format and pass them to the document class. Currently Generators for the following document types are available: \li Portable Document Format (PDF) \li PostScript \li Device Independent Format (DVI) \li DeJaVu Format \li Comic Books \li Images (JPEG, PNG, GIF, and many more) \li TIFF Image Format \li FictionBook Format \li Plucker Format \li OpenDocument Text Format  Yuri Chornoivan committed Feb 07, 2019 55 56 57  \li Microsoft's CHM Format \li Microsoft's XML Document Format \li Markdown Format  Tobias Koenig committed May 12, 2007 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94  Now the questions is how can these various formats be represented in a unified way? Okular provides features like rotation, text search and extraction, zooming and many more, so how does it match with the different capabilities of the formats? \section okular_design_basics Basics of Generators Lets start with the smallest commonness of all document formats: \li they have pages (one ore more) of a given size \li pages can be represented as pictures So the first thing every Generator must support is to return the number of pages of a document. Furthermore it must be able to return the picture of a page at a requested size. For vector based document formats (e.g. PDF or PostScript) the Generators can render the page for the requested size, for static documents formats (e.g. images), the Generator must scale the content according to the requested size, so when you zoom a page in Okular, the Generators are just asked to return the page for the zoomed size. When the document class has retrieved the page pictures from the Generators, it can do further image manipulation on it, for example rotating them or applying fancy effects. \section okular_design_text_support Generators with Text support Some document formats however support more functionality than just representing a page as an image. PDF, PostScript, DVI and DeJaVu for example contains a machine readable representation of the included text. For those document formats Okular provides additional features like text search, text extraction and text selection. How is that supported by the Generators? To access the text from the documents the generators must extract it somehow and make it available to the document class. However for the text selection feature the document class must also know where the extracted text is located on the page. For a zoom factor of 100% the absolute position of the text in the document can be used, however for larger or smaller zoom factors the position must be recalculated. To make this calculation as easy as possible, the Generators return an  Yuri Chornoivan committed Nov 20, 2018 95 abstract representation (\ref Okular::TextPage) of the text which includes every character together  Tobias Koenig committed May 12, 2007 96 97 98 99 100 101 102 103 104 105 106 with its normalized position. Normalized means that the width and height of the page is 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. So when you want to know where this character is located on the page which is zoomed at 300%, you just multiply the position by 3 * page width (and page height) and get the absolute position for this zoom level. This abstract text representation also allows an easy rotation of the coordinates, so that text selection is available on rotated pages as well. \section okular_design_meta_information Meta Information  Tobias Koenig committed May 13, 2007 107 108 109 110 111 112 113 114 115 116 Most documents have additional meta information: \li Name of the author \li Date of creation \li Version number \li Table of Content \li Bookmarks \li Annotations These information can be retrieved by the generator as well and will be shown by Okular.  Tobias Koenig committed May 12, 2007 117 118 119 120 121 122  \page okular_generators How to implement a Generator The power of Okular is its extensibility by Generator plugins. This section will describe how to implement your own plugin for a new document type.  Tobias Koenig committed May 13, 2007 123 124 125  \li \ref okular_generators_basic \li \ref okular_generators_with_text \li \ref okular_generators_threaded  Tobias Koenig committed May 13, 2007 126  \li \ref okular_generators_extended  Tobias Koenig committed May 13, 2007 127   Tobias Koenig committed May 12, 2007 128 129 \section okular_generators_basic A Basic Generator  Tobias Koenig committed May 12, 2007 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 To provide a short overview and don't reimplementing an existing generator we'll work on a Generator for the Magic document format, a non existing, pure virtual format :) Lets assume we have some helper class (MagicDocument) which provides the following functionality for this document format: \li Loading a document \li Retrieving number of pages \li Returning a fixed size picture representation of a page The class API looks like this \code class MagicDocument { public: MagicDocument(); ~MagicDocument(); bool loadDocument( const QString &fileName ); int numberOfPages() const; QSize pageSize( int pageNumber ) const; QImage pictureOfPage( int pageNumber ) const; private: ... }; \endcode The methods should be self explaining, loadDocument() loads a document file and returns false on error, numberOfPages() returns the number of pages, pageSize() returns the size of the page and pictureOfPage() returns the picture representation of the page. Our first version of our Generator is a basic one which just provides page pictures to the document class.  Tobias Koenig committed May 12, 2007 168 The API of the Generator looks like the following:  Tobias Koenig committed May 12, 2007 169 170 171 172 173 174 175 176 177  \code #include "magicdocument.h" #include class MagicGenerator : public Okular::Generator { public:  Pino Toscano committed Dec 02, 2007 178  MagicGenerator( QObject *parent, const QVariantList &args );  Tobias Koenig committed May 12, 2007 179 180 181 182 183 184 185  ~MagicGenerator(); bool loadDocument( const QString &fileName, QVector &pages ); bool canGeneratePixmap() const; void generatePixmap( Okular::PixmapRequest *request );  Pino Toscano committed Dec 02, 2007 186 187 188  protected: bool doCloseDocument();  Tobias Koenig committed May 12, 2007 189 190 191 192 193  private: MagicDocument mMagicDocument; }; \endcode  Tobias Koenig committed May 12, 2007 194 The implementation of the Generator looks like this:  Tobias Koenig committed May 12, 2007 195 196 197 198 199 200  \code #include #include "magicgenerator.h"  Yuri Chornoivan committed Feb 07, 2019 201 OKULAR_EXPORT_PLUGIN(MagicGenerator, "libokularGenerator_magic.json")  Tobias Koenig committed May 12, 2007 202   Pino Toscano committed Dec 02, 2007 203 204 MagicGenerator::MagicGenerator( QObject *parent, const QVariantList &args ) : Okular::Generator( parent, args )  Tobias Koenig committed May 12, 2007 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 { } MagicGenerator::~MagicGenerator() { } bool MagicGenerator::loadDocument( const QString &fileName, QVector &pages ) { if ( !mMagicDocument.loadDocument( fileName ) ) { emit error( i18n( "Unable to load document" ), -1 ); return false; } pagesVector.resize( mMagicDocument.numberOfPages() ); for ( int i = 0; i < mMagicDocument.numberOfPages(); ++i ) { const QSize size = mMagicDocument.pageSize( i ); Okular::Page * page = new Okular::Page( i, size.width(), size.height(), Okular::Rotation0 ); pages[ i ] = page; } return true; }  Pino Toscano committed Dec 02, 2007 231 bool MagicGenerator::doCloseDocument()  Tobias Koenig committed May 12, 2007 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 { return true; } bool MagicGenerator::canGeneratePixmap() const { return true; } void MagicGenerator::generatePixmap( Okular::PixmapRequest *request ) { QImage image = mMagicDocument.pictureOfPage( request->pageNumber() ); image = image.scaled( request->width(), request->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); request->page()->setPixmap( request->id(), new QPixmap( QPixmap::fromImage( image ) ) ); signalPixmapRequestDone( request ); } \endcode  Tobias Koenig committed May 12, 2007 253 254 255 256 257 258 259 260  As you can see implementing a basic Generator is quite easy. The loadDocument() method opens the document file and extracts the number of pages. For every page in the document it adds an Okular::Page object to the pages vector which is passed in as method argument. Each page is initialized with its page number, width, height and initial rotation. These page objects will be stored in the document object and act as a container for the picture representation of the pages. This code is the same for nearly every Generator. On an failure the error() signal can be emitted to inform the user about the issue. This code is the same for nearly every Generator.  Pino Toscano committed Dec 02, 2007 261 In the doCloseDocument() method you should close the document and free all resources you have allocated in openDocument().  Tobias Koenig committed May 12, 2007 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278  Now we come to the picture creation methods. The canGeneratorPixmap() method returns whether the Generator is currently able to handle a new pixmap generation request. For a simple Generator like our one that's always the case as it works linear, however a multithreaded Generator might return false here if it is still waiting for one of its working threads to finish. In this case the document class will try to request the pixmap later again. The generatePixmap() method does the actual fetching of the picture for a page. The page number, requested width and height of the page is encapsulated in the passed Okular::PixmapRequest object. So the task of the Generator is to create a pixmap of the requested page in the requested size and then store this pixmap in the Okular::Page object which is associated with the page request. When this task is finished, the Generator has to call signalPixmapRequestDone() with the page request object as argument. This extra call is needed to allow the Generator to use signals and slots internally and create the pixmap asynchronously. So now you have the code of a working Okular Generator, the next step is to tell Okular about the new plugin. Like in other places in KDE that is done by .desktop files, which are installed to the services directory.  Yuri Chornoivan committed Feb 07, 2019 279 Every Generator needs 1 .json, 3 .desktop files, and 1 .xml file:  Tobias Koenig committed May 12, 2007 280   Yuri Chornoivan committed Feb 07, 2019 281  \li libokularGenerator_<name>.json  Tobias Koenig committed May 12, 2007 282 283  \li okularApplication_<name>.desktop \li okular<name>.desktop  Yuri Chornoivan committed Feb 07, 2019 284 285  \li org.kde.mobile.okular_<name>.desktop \li org.kde.okular-<name>.metainfo.xml  Tobias Koenig committed May 12, 2007 286 287  where <name> should be the name of the document format. So for our Magic Document Generator we  Yuri Chornoivan committed Feb 07, 2019 288 create the following 4 files:  Tobias Koenig committed May 12, 2007 289   Yuri Chornoivan committed Feb 07, 2019 290  \li libokularGenerator_magic.json  Tobias Koenig committed May 12, 2007 291 292  \li okularApplication_magic.desktop \li okularMagic.desktop  Yuri Chornoivan committed Feb 07, 2019 293 294  \li org.kde.mobile.okular_magic.desktop \li org.kde.okular-magic.metainfo.xml  Tobias Koenig committed May 12, 2007 295   Yuri Chornoivan committed Feb 07, 2019 296 where libokularGenerator_magic.json has the following content something like this  Tobias Koenig committed May 12, 2007 297 298  \verbatim  Yuri Chornoivan committed Feb 07, 2019 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 { "KPlugin": { "Authors": [ { "Email": "author@hosting.suffix", "Name": "Proud Author", } ], "Copyright": "© 2042 Proud Author", "Id": "okular_magic", "License": "GPL", "MimeTypes": [ "text/magic", "text/x-magic" ], "Name": "Magic Backend", "ServiceTypes": [ "okular/Generator" ], "Version": "0.1.0" }, "X-KDE-Priority": 1, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": true }  Tobias Koenig committed May 12, 2007 324 325 \endverbatim  Yuri Chornoivan committed Feb 07, 2019 326 The last five fields has the special meaning to Okular  Tobias Koenig committed May 12, 2007 327 328 329 330 331 332 333  \li ServiceType Must be 'okular/Generator' for all Okular Generator Plugins \li MimeType The mimetype or list of mimetypes of the supported document format(s) \li X-KDE-Priority When multiple Generators for the same mimetype exists, the one with the highest priority is used \li X-KDE-okularAPIVersion The version of the Generator Plugin API ('1' currently) \li X-KDE-okularHasInternalSettings Is 'true' when the Generator provides configuration dialogs  Yuri Chornoivan committed Feb 07, 2019 334 The first .desktop file has the following content:  Tobias Koenig committed May 12, 2007 335 336 337 338 339 340 341  \verbatim [Desktop Entry] MimeType=application/x-magic; Terminal=false Name=okular GenericName=Document Viewer  Yuri Chornoivan committed Feb 07, 2019 342 Exec=okular %U  Tobias Koenig committed May 12, 2007 343 344 345 346 347 Icon=okular Type=Application InitialPreference=7 Categories=Qt;KDE;Graphics;Viewer; NoDisplay=true  Yuri Chornoivan committed Feb 07, 2019 348 X-KDE-Keywords=Magic  Tobias Koenig committed May 12, 2007 349 350 351 352 353 \endverbatim You can use the file as it is, you just have to adapt the mimetype. This file is needed to allow Okular to handle multiple mimetypes.  Yuri Chornoivan committed Feb 07, 2019 354 The second .desktop file looks like this:  Tobias Koenig committed May 12, 2007 355 356 357 358 359  \verbatim [Desktop Entry] Icon=okular Name=okular  Yuri Chornoivan committed Feb 07, 2019 360 X-KDE-ServiceTypes=KParts/ReadOnlyPart  Albert Astals Cid committed Jul 20, 2010 361 X-KDE-Library=okularpart  Tobias Koenig committed May 12, 2007 362 Type=Service  Pino Toscano committed Jan 09, 2008 363 MimeType=application/x-magic;  Tobias Koenig committed May 12, 2007 364 365 \endverbatim  Yuri Chornoivan committed Feb 07, 2019 366 367 368 369 where \li X-KDE-Library The name of the plugin library  Tobias Koenig committed May 12, 2007 370 371 372 You can use the file as it is as well, you just have to adapt the mimetype. This file is needed to allow the Okular part to handle multiple mimetypes.  Yuri Chornoivan committed Feb 07, 2019 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 The third .desktop file contains data for the mobile version \verbatim [Desktop Entry] MimeType=application/x-magic; Name=Reader GenericName=Document viewer Comment=Viewer for various types of documents TryExec=kpackagelauncherqml -a org.kde.mobile.okular Exec=kpackagelauncherqml -a org.kde.mobile.okular %u Terminal=false Icon=okular Type=Application Categories=Qt;KDE;Graphics;Office;Viewer; InitialPreference=2 NoDisplay=true X-KDE-Keywords=Magic \endverbatim And the last .xml file has the following content \verbatim org.kde.okular-md org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 Magic Adds support for reading Magic documents application/magic https://okular.kde.org \endverbatim  Tobias Koenig committed May 12, 2007 410 411 412 413 The last piece you need for a complete Generator is a CMakeLists.txt which compiles and installs the Generator. Our CMakeLists.txt looks like the following: \verbatim  Yuri Chornoivan committed Feb 07, 2019 414 415 add_definitions(-DTRANSLATION_DOMAIN="okular_magic")  Tobias Koenig committed May 12, 2007 416 417 macro_optional_find_package(Okular)  Yuri Chornoivan committed Feb 07, 2019 418 include_directories( ${OKULAR_INCLUDE_DIR}${KF5_INCLUDE_DIR} ${QT_INCLUDES} )  Tobias Koenig committed May 12, 2007 419 420 421  ########### next target ###############  Yuri Chornoivan committed Feb 07, 2019 422 set( okularGenerator_magic_PART_SRCS generator_magic.cpp )  Tobias Koenig committed May 12, 2007 423   Yuri Chornoivan committed Feb 07, 2019 424 target_link_libraries( okularGenerator_magic PRIVATE okularcore KF5::I18n KF5::KIOCore )  Tobias Koenig committed May 12, 2007 425 426 427 428 429  install( TARGETS okularGenerator_magic DESTINATION${PLUGIN_INSTALL_DIR} ) ########### install files ###############  Yuri Chornoivan committed Feb 07, 2019 430 431 432 install( FILES okularMagic.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) install( PROGRAMS okularApplication_magic.desktop org.kde.mobile.okular_magic.desktop DESTINATION${KDE_INSTALL_APPDIR} ) install( FILES org.kde.okular-magic.metainfo.xml DESTINATION ${KDE_INSTALL_METAINFODIR} )  Tobias Koenig committed May 12, 2007 433 434 435 436 437 438 439 440 441 442 \endverbatim The macro_optional_find_package(Okular) call is required to make the${OKULAR_INCLUDE_DIR} and \${OKULAR_LIBRARIES} variables available. Now you can compile the Generator plugin and install it. After a restart of Okular the new plugin is available and you can open Magic documents. \section okular_generators_with_text A Generator with TextPage support  Tobias Koenig committed May 13, 2007 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 In this section we want to extend our Generator to support text search, text extraction and selection as well. As mentioned in \ref okular_design_text_support, the Generator must provide an Okular::TextPage object for every page which contains readable text. Since we use the helper class MagicDocument to read the data from the document we have to extend it first, so the new API looks as the following: \code class MagicDocument { public: MagicDocument(); ~MagicDocument(); bool loadDocument( const QString &fileName ); int numberOfPages() const; QSize pageSize( int pageNumber ) const; QImage pictureOfPage( int pageNumber ) const; class TextInfo { public: typedef QList List; QChar character; qreal xPos; qreal yPos; qreal width; qreal height; }; TextInfo::List textOfPage( int pageNumber ); private: ... }; \endcode MagicDocument has the new internal class TextInfo now, which contains a character and its absolute position on a page. Furthermore MagicDocument provides a method textOfPage() which returns a list of all TextInfo objects for a page. That's really an optimistic API, in reality it is sometimes quite hard to find out the position of single characters in a document format. With the extension of our helper class we can continue on extending our Generator now:  Tobias Koenig committed May 12, 2007 493 494 495 496 497 498 499 500 \code #include "magicdocument.h" #include class MagicGenerator : public Okular::Generator { public:  Pino Toscano committed Dec 02, 2007 501  MagicGenerator( QObject *parent, const QVariantList &args );  Tobias Koenig committed May 12, 2007 502 503 504 505 506 507 508  ~MagicGenerator(); bool loadDocument( const QString &fileName, QVector &pages ); bool canGeneratePixmap() const; void generatePixmap( Okular::PixmapRequest *request );  Tobias Koenig committed May 13, 2007 509 510 511  virtual bool canGenerateTextPage() const; virtual void generateTextPage( Okular::Page *page, enum Okular::GenerationType type = Okular::Synchronous );  Pino Toscano committed Dec 02, 2007 512 513 514  protected: bool doCloseDocument();  Tobias Koenig committed May 13, 2007 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535  private: MagicDocument mMagicDocument; }; \endcode We have extended the MagicGenerator class by two methods canGenerateTextPage() and generateTextPage(). The first method is equal to canGeneratePixmap(), it returns whether the Generator is currently able to handle a new text page generation request. For linear Generators that should be always the case, however when the generation is done in a separated worker thread, this method might return false. In this case the document class will try to request the text page later again. The second method will generate the Okular::TextPage object for the passed page. Depending on the capabilities of the Generator and the passed type parameter that is done synchronously or asynchronously. Let us take a look at the implementation of these methods in our MagicGenerator: \code #include ...  Pino Toscano committed Dec 02, 2007 536 537 MagicGenerator::MagicGenerator( QObject *parent, const QVariantList &args ) : Okular::Generator( parent, args )  Tobias Koenig committed May 13, 2007 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 { setFeature( TextExtraction ); } bool MagicGenerator::canGenerateTextPage() const { return true; } void MagicGenerator::generateTextPage( Okular::Page *page, enum Okular::GenerationType ) { MagicDocument::TextInfo::List characters = mMagicDocument.textOfPage( page->number() ); if ( characters.isEmpty() ) return; Okular::TextPage *textPage = new Okular::TextPage; for ( int i = 0; i < characters.count(); ++i ) { qreal left = characters[ i ].xPos / page->width(); qreal top = characters[ i ].yPos / page->height(); qreal right = (characters[ i ].xPos + characters[ i ].width) / page->width(); qreal bottom = (characters[ i ].yPos + characters[ i ].height) / page->height(); textPage->append( characters[ i ].character, new Okular::NormalizedRect( left, top, right, bottom ) ); } page->setTextPage( textPage ); } \endcode As you can see the generateTextPage method just iterates over the list of characters returned by our MagicDocument helper class and adds the character and its normalized bounding rect to the Okular::TextPage object. At the end the text page is assigned to the page. We don't pay attention to the GenerationType parameter here, if your Generator want to use threads, it should check here whether the request shall be done asynchronously or synchronously and start the generation according to that. Additionally we have to tell the Okular::Generator base class that we support text handling by setting this flag in the constructor. In this state we can now search, select and extract text from Magic documents. \section okular_generators_threaded A Generator with Thread support Sometimes it makes sense to do the generation of page pictures or text pages asynchronously to improve performance and don't blocking the user interface. This can be done in two ways, either by using signals and slots or by using threads. Both have there pros and cons:
The signal and slots approach can be achieved with a normal Generator by calling Okular::Generator::signalPixmapRequestDone() from a slot after pixmap generation has been finished. When using threads you should use a slightly different API, which hides most of the thread usage, to make implementing as easy as possible. Let's assume the pictureOfPage() and textOfPage methods in our MagicDocument helper class are thread safe, so we can use them in a multithreaded environment. So nothing prevents us from changing the MagicGenerator to use threads for better performance. The new MagicGenerator API looks like the following: \code #include "magicdocument.h" #include class MagicGenerator : public Okular::Generator { public:  Pino Toscano committed Dec 02, 2007 617  MagicGenerator( QObject *parent, const QVariantList &args );  Tobias Koenig committed May 13, 2007 618 619 620 621  ~MagicGenerator(); bool loadDocument( const QString &fileName, QVector &pages );  Pino Toscano committed Dec 02, 2007 622 623 624  protected: bool doCloseDocument();  Tobias Koenig committed May 13, 2007 625 626 627 628  virtual QImage image( Okular::PixmapRequest *request ); virtual Okular::TextPage* textPage( Okular::Page *page );  Tobias Koenig committed May 12, 2007 629 630 631 632 633  private: MagicDocument mMagicDocument; }; \endcode  Tobias Koenig committed May 13, 2007 634 635 636 637 638 639 640 As you can see the canGeneratePixmap() generatePixmap(), canGenerateTextPage() and generateTextPage() methods have been removed and replaced by the image() and textPage() methods. Before explaining why, we'll take a look at the implementation: \code  Pino Toscano committed Dec 02, 2007 641 642 MagicGenerator::MagicGenerator( QObject *parent, const QVariantList &args ) : Okular::Generator( parent, args )  Tobias Koenig committed May 13, 2007 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 { setFeature( TextExtraction ); setFeature( Threaded ); } QImage MagicGenerator::image( Okular::PixmapRequest *request ) { QImage image = mMagicDocument.pictureOfPage( request->pageNumber() ); return image.scaled( request->width(), request->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); } Okular::TextPage* textPage( Okular::Page *page ) { MagicDocument::TextInfo::List characters = mMagicDocument.textOfPage( page->number() ); if ( characters.isEmpty() ) return 0; Okular::TextPage *textPage = new Okular::TextPage; for ( int i = 0; i < characters.count(); ++i ) { qreal left = characters[ i ].xPos / page->width(); qreal top = characters[ i ].yPos / page->height(); qreal right = (characters[ i ].xPos + characters[ i ].width) / page->width(); qreal bottom = (characters[ i ].yPos + characters[ i ].height) / page->height(); textPage->append( characters[ i ].character, new Okular::NormalizedRect( left, top, right, bottom ) ); } return textPage; } \endcode So the first obviously thing is that both methods return a value instead of modifying the page directly. The reason for this is that both methods are executed in its own thread, so the code executed in them can block as long as it wants, it won't block the GUI anyway. Additionally we have to tell the Okular::Generator base class that we can handle threads by setting the flag in the constructor. With only a small change we made our MagicGenerator multithreaded now!  Tobias Koenig committed May 13, 2007 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704  \section okular_generators_extended An Extended Generator Now we want to create a new generator with some additional functionality: \li Support for document information (author, creation date etc.) \li Support for a table of content \li Support for printing the document \li Support for exporting the document as text The new Generator shall be able to handle HTML documents. We choose this format as example, because we can use QTextDocument to load, render and print a HTML page, so a lot of code can be reused. The API of our HTMLGenerator looks like the following: \code #include #include class HTMLGenerator : public Okular::Generator { public:  Pino Toscano committed Dec 02, 2007 705  HTMLGenerator( QObject *parent, const QVariantList &args );  Tobias Koenig committed May 13, 2007 706 707 708 709 710 711 712  ~HTMLGenerator(); bool loadDocument( const QString &fileName, QVector &pages ); bool canGeneratePixmap() const; void generatePixmap( Okular::PixmapRequest *request );  Friedrich W. H. Kossebau committed Sep 09, 2014 713  virtual Okular::DocumentInfo generateDocumentInfo( const QSet &keys ) const;  Tobias Koenig committed May 13, 2007 714 715 716 717 718 719 720 721 722  virtual const Okular::DocumentSynopsis* generateDocumentSynopsis(); virtual bool print( KPrinter &printer ); virtual Okular::ExportFormat::List exportFormats() const; virtual bool exportTo( const QString &fileName, const Okular::ExportFormat &format );  Pino Toscano committed Dec 02, 2007 723 724 725  protected: bool doCloseDocument();  Tobias Koenig committed May 13, 2007 726 727 728 729 730 731 732 733  private: QTextDocument *mTextDocument; Okular::DocumentInfo mDocumentInfo; Okular::DocumentSynopsis mDocumentSynopsis; }; \endcode The Generator doesn't support text search and selection, as the code would be quite complex, we'll show  Yuri Chornoivan committed Feb 07, 2019 734 how to do it in the next chapter (not yet written) anyway.  Tobias Koenig committed May 13, 2007 735 736 737  As you can see we have 5 new methods in the class:  Yuri Chornoivan committed Nov 20, 2018 738  \li generateDocumentInfo() Creates an Okular::DocumentInfo (which is in fact a QDomDocument)  Tobias Koenig committed May 13, 2007 739 740 741 742 743 744 745 746 747 748  which contains document information like author, creation time etc. \li generateDocumentSynopsis() Creates an Okular::DocumentSynopsis (which is a QDomDocument as well) which contains the table of content. \li print() Prints the document to the passed printer. \li exportFormats() Returns the supported export formats. \li exportTo() Exports the document to the given file in the given format. Now that you know what the methods are supposed to do, let's take a look at the implementation: \code  Yuri Chornoivan committed Nov 20, 2018 749 750 751 #include #include #include  Tobias Koenig committed May 13, 2007 752 753 754 755 756 757  #include #include #include "htmlgenerator.h"  Yuri Chornoivan committed Feb 07, 2019 758 #include  Pino Toscano committed Dec 02, 2007 759   Yuri Chornoivan committed Feb 07, 2019 760 OKULAR_EXPORT_PLUGIN(HTMLGenerator, "libokularGenerator_html.json")  Tobias Koenig committed May 13, 2007 761   Pino Toscano committed Dec 02, 2007 762 763 HTMLGenerator::HTMLGenerator( QObject *parent, const QVariantList &args ) : Okular::Generator( parent, args ),  Tobias Koenig committed May 13, 2007 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813  mTextDocument( 0 ) { } HTMLGenerator::~HTMLGenerator() { delete mTextDocument; } bool HTMLGenerator::loadDocument( const QString &fileName, QVector &pages ) { QFile file( fileName ); if ( !file.open( QIODevice::ReadOnly ) ) { emit error( i18n( "Unable to open file" ), -1 ); return false; } const QString data = QString::fromUtf8( file.readAll() ); file.close(); mTextDocument = new QTextDocument; mTextDocument->setHtml( data ); mTextDocument->setPageSize( QSizeF( 600, 800 ) ); pages.resize( mTextDocument->pageCount() ); for ( int i = 0; i < mTextDocument->pageCount(); ++i ) { Okular::Page * page = new Okular::Page( i, 600, 800, Okular::Rotation0 ); pages[ i ] = page; } mDocumentInfo.set( "author", "Tobias Koenig", i18n( "Author" ) ); mDocumentInfo.set( "title", "The Art of Okular Plugin Development", i18n( "Title" ) ); Okular::DocumentViewport viewport = ... // get the viewport of the chapter QDomElement item = mDocumentSynopsis.createElement( "Chapter 1" ); item.setAttribute( "Viewport", viewport.toString() ); mDocumentSynopsis.appendChild( item ); viewport = ... // get the viewport of the subchapter QDomElement childItem = mDocumentSynopsis.createElement( "SubChapter 1.1" ); childItem.setAttribute( "Viewport", viewport.toString() ); item.appendChild( childItem ); return true; }  Pino Toscano committed Dec 02, 2007 814 bool HTMLGenerator::doCloseDocument()  Tobias Koenig committed May 13, 2007 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 { delete mTextDocument; mTextDocument = 0; return true; } bool HTMLGenerator::canGeneratePixmap() const { return true; } void HTMLGenerator::generatePixmap( Okular::PixmapRequest *request ) { QPixmap *pixmap = new QPixmap( request->width(), request->height() ); pixmap->fill( Qt::white ); QPainter p; p.begin( pixmap ); qreal width = request->width(); qreal height = request->height(); p.scale( width / 600, height / 800 ); const QRect rect( 0, request->pageNumber() * 800, 600, 800 ); p.translate( QPoint( 0, request->pageNumber() * -800 ) ); d->mDocument->drawContents( &p, rect ); p.end(); request->page()->setPixmap( request->id(), pixmap ); signalPixmapRequestDone( request ); }  Friedrich W. H. Kossebau committed Sep 09, 2014 850 Okular::DocumentInfo HTMLGenerator::generateDocumentInfo( const QSet &keys ) const  Tobias Koenig committed May 13, 2007 851 {  Friedrich W. H. Kossebau committed Sep 09, 2014 852  return mDocumentInfo;  Tobias Koenig committed May 13, 2007 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 } const Okular::DocumentSynopsis* HTMLGenerator::generateDocumentSynopsis() { if ( !mDocumentSynopsis.hasChildNodes() ) return 0; else return &mDocumentSynopsis; } bool HTMLGenerator::print( KPrinter &printer ) { QPainter p( &printer ); for ( int i = 0; i < mTextDocument->pageCount(); ++i ) { if ( i != 0 ) printer.newPage(); QRect rect( 0, i * 800, 600, 800 ); p.translate( QPoint( 0, i * -800 ) ); mTextDocument->drawContents( &p, rect ); } } Okular::ExportFormat::List HTMLGenerator::exportFormats() const { return Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ); } bool HTMLGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format ) { QFile file( fileName ); if ( !fileName.open( QIODevice::WriteOnly ) ) { emit error( i18n( "Unable to open file" ), -1 ); return false; } if ( format.mimeType()->name() == QLatin1String( "text/plain" ) ) file.writeBlock( mTextDocument->toPlainText().toUtf8() ); file.close(); return true; } \endcode Let's take a closer look at the single methods. In the loadDocument() method we try to open the passed file name and read all the content into the QTextDocument object. By calling QTextDocument::setPageSize(), the whole document is divided into pages of the given size. In the next step we create Okular::Page objects for every page in the QTextDocument and fill the pages vector with them. Afterwards we fill our Okular::DocumentInfo object with data. Since extracting the HTML meta data would need a lot of code we work with static data here. [to be continued]  Tobias Koenig committed May 12, 2007 907 */