diff --git a/Mainpage.dox b/Mainpage.dox index a23f208ff..7204a0700 100644 --- a/Mainpage.dox +++ b/Mainpage.dox @@ -1,858 +1,907 @@ /** \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 Okular is the successor of kpdf, the PDF viewer in KDE 3. 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 - \li \ref Part + \li \ref Okular::Part \li \ref Okular::Document Class \li \ref Okular::Generator The shell is the application which is started by the user as standalone application and which embeds the part. The part contains all GUI elements of Okular, for example the 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 - \li Microsofts CHM Format - \li Microsofts XML Document Format + \li Microsoft's CHM Format + \li Microsoft's XML Document Format + \li Markdown Format 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 abstract representation (\ref Okular::TextPage) of the text which includes every character together 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 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. \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. \li \ref okular_generators_basic \li \ref okular_generators_with_text \li \ref okular_generators_threaded \li \ref okular_generators_extended \section okular_generators_basic A Basic Generator 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. The API of the Generator looks like the following: \code #include "magicdocument.h" #include class MagicGenerator : public Okular::Generator { public: MagicGenerator( QObject *parent, const QVariantList &args ); ~MagicGenerator(); bool loadDocument( const QString &fileName, QVector &pages ); bool canGeneratePixmap() const; void generatePixmap( Okular::PixmapRequest *request ); protected: bool doCloseDocument(); private: MagicDocument mMagicDocument; }; \endcode The implementation of the Generator looks like this: \code #include #include "magicgenerator.h" -static KAboutData createAboutData() -{ - KAboutData aboutData(...); - // fill the about data - return aboutData; -} - -OKULAR_EXPORT_PLUGIN(MagicGenerator, createAboutData()) +OKULAR_EXPORT_PLUGIN(MagicGenerator, "libokularGenerator_magic.json") MagicGenerator::MagicGenerator( QObject *parent, const QVariantList &args ) : Okular::Generator( parent, args ) { } 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; } bool MagicGenerator::doCloseDocument() { 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 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. In the doCloseDocument() method you should close the document and free all resources you have allocated in openDocument(). 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. -Every Generator needs 3 .desktop files: +Every Generator needs 1 .json, 3 .desktop files, and 1 .xml file: - \li libokularGenerator_<name>.desktop + \li libokularGenerator_<name>.json \li okularApplication_<name>.desktop \li okular<name>.desktop + \li org.kde.mobile.okular_<name>.desktop + \li org.kde.okular-<name>.metainfo.xml where <name> should be the name of the document format. So for our Magic Document Generator we -create the following 3 files: +create the following 4 files: - \li libokularGenerator_magic.desktop + \li libokularGenerator_magic.json \li okularApplication_magic.desktop \li okularMagic.desktop + \li org.kde.mobile.okular_magic.desktop + \li org.kde.okular-magic.metainfo.xml -with the following content: +where libokularGenerator_magic.json has the following content something like this \verbatim -[Desktop Entry] -Type=Service -Name=Magic Document -Comment=Magic Document backend for okular -ServiceTypes=okular/Generator -MimeType=application/x-magic; -X-KDE-Library=okularGenerator_magic -X-KDE-Priority=1 -X-KDE-okularAPIVersion=1 -X-KDE-okularHasInternalSettings=false +{ + "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 +} \endverbatim -The first 6 fields are standard .desktop entries, the fields afterwards have a special meaning to Okular +The last five fields has the special meaning to Okular \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-Library The name of the plugin library \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 -The second .desktop file has the following content: +The first .desktop file has the following content: \verbatim [Desktop Entry] MimeType=application/x-magic; Terminal=false Name=okular GenericName=Document Viewer -Exec=okular %U %i +Exec=okular %U Icon=okular Type=Application InitialPreference=7 Categories=Qt;KDE;Graphics;Viewer; NoDisplay=true +X-KDE-Keywords=Magic \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. -The third .desktop file looks like this: +The second .desktop file looks like this: \verbatim [Desktop Entry] Icon=okular Name=okular -ServiceTypes=KParts/ReadOnlyPart +X-KDE-ServiceTypes=KParts/ReadOnlyPart X-KDE-Library=okularpart Type=Service MimeType=application/x-magic; \endverbatim +where + + \li X-KDE-Library The name of the plugin library + 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. +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 + 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 +add_definitions(-DTRANSLATION_DOMAIN="okular_magic") + macro_optional_find_package(Okular) -include_directories( ${OKULAR_INCLUDE_DIR} ${KDE4_INCLUDE_DIR} ${QT_INCLUDES} ) +include_directories( ${OKULAR_INCLUDE_DIR} ${KF5_INCLUDE_DIR} ${QT_INCLUDES} ) ########### next target ############### -set( okularGenerator_magic_SRCS generator_magic.cpp ) - -kde4_add_plugin( okularGenerator_magic ${okularGenerator_magic_SRCS} ) +set( okularGenerator_magic_PART_SRCS generator_magic.cpp ) -target_link_libraries( okularGenerator_magic ${OKULAR_LIBRARIES} ${KDE4_KDEUI_LIBS} ) +target_link_libraries( okularGenerator_magic PRIVATE okularcore KF5::I18n KF5::KIOCore ) install( TARGETS okularGenerator_magic DESTINATION ${PLUGIN_INSTALL_DIR} ) ########### install files ############### -install( FILES libokularGenerator_magic.desktop okularMagic.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) -install( FILES okularApplication_magic.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) +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} ) \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 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: \code #include "magicdocument.h" #include class MagicGenerator : public Okular::Generator { public: MagicGenerator( QObject *parent, const QVariantList &args ); ~MagicGenerator(); bool loadDocument( const QString &fileName, QVector &pages ); bool canGeneratePixmap() const; void generatePixmap( Okular::PixmapRequest *request ); virtual bool canGenerateTextPage() const; virtual void generateTextPage( Okular::Page *page, enum Okular::GenerationType type = Okular::Synchronous ); protected: bool doCloseDocument(); 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 ... MagicGenerator::MagicGenerator( QObject *parent, const QVariantList &args ) : Okular::Generator( parent, args ) { 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:
  • Signals and Slots
    • Pro: Can be used with backend libraries which are not thread safe
    • Con: Sometime difficult to implement
  • Threads
    • Pro: Easy to implement as you can make synchronous calls to the backend libraries
    • Con: Backend libraries must be thread safe and you must prevent race conditions by using mutexes
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: MagicGenerator( QObject *parent, const QVariantList &args ); ~MagicGenerator(); bool loadDocument( const QString &fileName, QVector &pages ); protected: bool doCloseDocument(); virtual QImage image( Okular::PixmapRequest *request ); virtual Okular::TextPage* textPage( Okular::Page *page ); private: MagicDocument mMagicDocument; }; \endcode 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 MagicGenerator::MagicGenerator( QObject *parent, const QVariantList &args ) : Okular::Generator( parent, args ) { 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! \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: HTMLGenerator( QObject *parent, const QVariantList &args ); ~HTMLGenerator(); bool loadDocument( const QString &fileName, QVector &pages ); bool canGeneratePixmap() const; void generatePixmap( Okular::PixmapRequest *request ); virtual Okular::DocumentInfo generateDocumentInfo( const QSet &keys ) const; 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 ); protected: bool doCloseDocument(); 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 -how to do it in the next chapter \ref okular_generators_textdocument anyway. +how to do it in the next chapter (not yet written) anyway. As you can see we have 5 new methods in the class: \li generateDocumentInfo() Creates an Okular::DocumentInfo (which is in fact a QDomDocument) 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 #include #include - #include #include #include #include "htmlgenerator.h" -static KAboutData createAboutData() -{ - KAboutData aboutData(...); - // fill the about data - return aboutData; -} +#include -OKULAR_EXPORT_PLUGIN(HTMLGenerator, createAboutData()) +OKULAR_EXPORT_PLUGIN(HTMLGenerator, "libokularGenerator_html.json") HTMLGenerator::HTMLGenerator( QObject *parent, const QVariantList &args ) : Okular::Generator( parent, args ), 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; } bool HTMLGenerator::doCloseDocument() { 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 ); } Okular::DocumentInfo HTMLGenerator::generateDocumentInfo( const QSet &keys ) const { return mDocumentInfo; } 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] */ diff --git a/core/generator.h b/core/generator.h index 819557dc5..de28aba31 100644 --- a/core/generator.h +++ b/core/generator.h @@ -1,824 +1,824 @@ /*************************************************************************** * Copyright (C) 2004-5 by Enrico Ros * * Copyright (C) 2005 by Piotr Szymanski * * Copyright (C) 2008 by Albert Astals Cid * * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group * * company, info@kdab.com. Work sponsored by the * * LiMux project of the city of Munich * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #ifndef _OKULAR_GENERATOR_H_ #define _OKULAR_GENERATOR_H_ #include "okularcore_export.h" #include "document.h" #include "fontinfo.h" #include "global.h" #include "pagesize.h" #include #include #include #include #include #include #include #include #include #define OKULAR_EXPORT_PLUGIN(classname, json ) \ static_assert(json[0] != '\0', "arg2 must be a string literal"); \ K_PLUGIN_CLASS_WITH_JSON(classname, json) class QByteArray; class QMutex; class QPrinter; class QPrintDialog; class QIcon; namespace Okular { class BackendOpaqueAction; class DocumentFonts; class DocumentInfo; class DocumentObserver; class DocumentSynopsis; class EmbeddedFile; class ExportFormatPrivate; class FontInfo; class GeneratorPrivate; class Page; class PixmapRequest; class PixmapRequestPrivate; class TextPage; class TextRequest; class TextRequestPrivate; class NormalizedRect; class SourceReference; /* Note: on contents generation and asynchronous queries. * Many observers may want to request data synchronously or asynchronously. * - Sync requests. These should be done in-place. * - Async request must be done in real background. That usually means a * thread, such as QThread derived classes. * Once contents are available, they must be immediately stored in the * Page they refer to, and a signal is emitted as soon as storing * (even for sync or async queries) has been done. */ /** * @short Defines an entry for the export menu * * This class encapsulates information about an export format. * Every Generator can support 0 or more export formats which can be * queried with @ref Generator::exportFormats(). */ class OKULARCORE_EXPORT ExportFormat { public: typedef QList List; /** * Creates an empty export format. * * @see isNull() */ ExportFormat(); /** * Creates a new export format. * * @param description The i18n'ed description of the format. * @param mimeType The supported mime type of the format. */ ExportFormat( const QString &description, const QMimeType &mimeType ); /** * Creates a new export format. * * @param icon The icon used in the GUI for this format. * @param description The i18n'ed description of the format. * @param mimeType The supported mime type of the format. */ ExportFormat( const QIcon &icon, const QString &description, const QMimeType &mimeType ); /** * Destroys the export format. */ ~ExportFormat(); /** * @internal */ ExportFormat( const ExportFormat &other ); /** * @internal */ ExportFormat& operator=( const ExportFormat &other ); /** * Returns the description of the format. */ QString description() const; /** * Returns the mime type of the format. */ QMimeType mimeType() const; /** * Returns the icon for GUI representations of the format. */ QIcon icon() const; /** * Returns whether the export format is null/valid. * * An ExportFormat is null if the mimetype is not valid or the * description is empty, or both. */ bool isNull() const; /** * Type of standard export format. */ enum StandardExportFormat { PlainText, ///< Plain text PDF, ///< PDF, aka Portable Document Format OpenDocumentText, ///< OpenDocument Text format @since 0.8 (KDE 4.2) HTML ///< OpenDocument Text format @since 0.8 (KDE 4.2) }; /** * Builds a standard format for the specified @p type . */ static ExportFormat standardFormat( StandardExportFormat type ); bool operator==( const ExportFormat &other ) const; bool operator!=( const ExportFormat &other ) const; private: /// @cond PRIVATE friend class ExportFormatPrivate; /// @endcond QSharedDataPointer d; }; /** * @short [Abstract Class] The information generator. * * Most of class members are virtuals and some of them pure virtual. The pure * virtuals provide the minimal functionalities for a Generator, that is being * able to generate QPixmap for the Page 's of the Document. * * Implementing the other functions will make the Generator able to provide * more contents and/or functionalities (like text extraction). * * Generation/query is requested by the Document class only, and that * class stores the resulting data into Page s. The data will then be * displayed by the GUI components (PageView, ThumbnailList, etc..). * * @see PrintInterface, ConfigInterface, GuiInterface */ class OKULARCORE_EXPORT Generator : public QObject { /// @cond PRIVATE friend class PixmapGenerationThread; friend class TextPageGenerationThread; /// @endcond Q_OBJECT public: /** * Describe the possible optional features that a Generator can * provide. */ enum GeneratorFeature { - Threaded, + Threaded, ///< Whether the Generator supports asynchronous generation of pictures or text pages TextExtraction, ///< Whether the Generator can extract text from the document in the form of TextPage's ReadRawData, ///< Whether the Generator can read a document directly from its raw data. FontInfo, ///< Whether the Generator can provide information about the fonts used in the document PageSizes, ///< Whether the Generator can change the size of the document pages. PrintNative, ///< Whether the Generator supports native cross-platform printing (QPainter-based). PrintPostscript, ///< Whether the Generator supports postscript-based file printing. PrintToFile, ///< Whether the Generator supports export to PDF & PS through the Print Dialog TiledRendering, ///< Whether the Generator can render tiles @since 0.16 (KDE 4.10) SwapBackingFile, ///< Whether the Generator can hot-swap the file it's reading from @since 1.3 SupportsCancelling ///< Whether the Generator can cancel requests @since 1.4 }; /** * Creates a new generator. */ explicit Generator(QObject* parent = nullptr, const QVariantList& args = QVariantList()); /** * Destroys the generator. */ virtual ~Generator(); /** * Loads the document with the given @p fileName and fills the * @p pagesVector with the parsed pages. * * @note If you implement the WithPassword variants you don't need to implement this one * * @returns true on success, false otherwise. */ virtual bool loadDocument( const QString & fileName, QVector< Page * > & pagesVector ); /** * Loads the document from the raw data @p fileData and fills the * @p pagesVector with the parsed pages. * * @note If you implement the WithPassword variants you don't need to implement this one * * @note the Generator has to have the feature @ref ReadRawData enabled * * @returns true on success, false otherwise. */ virtual bool loadDocumentFromData( const QByteArray & fileData, QVector< Page * > & pagesVector ); /** * Loads the document with the given @p fileName and @p password and fills the * @p pagesVector with the parsed pages. * * @note Do not implement this if your format doesn't support passwords, it'll cleanly call loadDocument() * * @since 0.20 (KDE 4.14) * * @returns a LoadResult defining the result of the operation */ virtual Document::OpenResult loadDocumentWithPassword( const QString & fileName, QVector< Page * > & pagesVector, const QString &password ); /** * Loads the document from the raw data @p fileData and @p password and fills the * @p pagesVector with the parsed pages. * * @note Do not implement this if your format doesn't support passwords, it'll cleanly call loadDocumentFromData() * * @note the Generator has to have the feature @ref ReadRawData enabled * * @since 0.20 (KDE 4.14) * * @returns a LoadResult defining the result of the operation */ virtual Document::OpenResult loadDocumentFromDataWithPassword( const QByteArray & fileData, QVector< Page * > & pagesVector, const QString &password ); /** * Describes the result of an swap file operation. * * @since 1.3 */ enum SwapBackingFileResult { SwapBackingFileError, //< The document could not be swapped SwapBackingFileNoOp, //< The document was swapped and nothing needs to be done SwapBackingFileReloadInternalData //< The document was swapped and internal data (forms, annotations, etc) needs to be reloaded }; /** * Changes the path of the file we are reading from. The new path must * point to a copy of the same document. * * @note the Generator has to have the feature @ref SwapBackingFile enabled * * @since 1.3 */ virtual SwapBackingFileResult swapBackingFile( const QString & newFileName, QVector & newPagesVector ); /** * This method is called when the document is closed and not used * any longer. * * @returns true on success, false otherwise. */ bool closeDocument(); /** * This method returns whether the generator is ready to * handle a new pixmap request. */ virtual bool canGeneratePixmap() const; /** * This method can be called to trigger the generation of * a new pixmap as described by @p request. */ virtual void generatePixmap( PixmapRequest * request ); /** * This method returns whether the generator is ready to * handle a new text page request. */ virtual bool canGenerateTextPage() const; /** * This method can be called to trigger the generation of * a text page for the given @p page. * * The generation is done in the calling thread. * * @see TextPage */ void generateTextPage( Page * page ); /** * Returns the general information object of the document. * * Changed signature in okular version 0.21 */ virtual DocumentInfo generateDocumentInfo( const QSet &keys ) const; /** * Returns the 'table of content' object of the document or 0 if * no table of content is available. */ virtual const DocumentSynopsis * generateDocumentSynopsis(); /** * Returns the 'list of embedded fonts' object of the specified \p page * of the document. * * \param page a page of the document, starting from 0 - -1 indicates all * the other fonts */ virtual FontInfo::List fontsForPage( int page ); /** * Returns the 'list of embedded files' object of the document or 0 if * no list of embedded files is available. */ virtual const QList * embeddedFiles() const; /** * This enum identifies the metric of the page size. */ enum PageSizeMetric { None, ///< The page size is not defined in a physical metric. Points, ///< The page size is given in 1/72 inches. Pixels ///< The page size is given in screen pixels @since 0.19 (KDE 4.13) }; /** * This method returns the metric of the page size. Default is @ref None. */ virtual PageSizeMetric pagesSizeMetric() const; /** * Returns whether the given @p action is allowed in the document. - * @see @ref Permission + * @see @ref Okular::Permission */ virtual bool isAllowed( Permission action ) const; /** * This method is called when the orientation has been changed by the user. */ virtual void rotationChanged( Rotation orientation, Rotation oldOrientation ); /** * Returns the list of supported page sizes. */ virtual PageSize::List pageSizes() const; /** * This method is called when the page size has been changed by the user. */ virtual void pageSizeChanged( const PageSize &pageSize, const PageSize &oldPageSize ); /** * This method is called to print the document to the given @p printer. */ virtual bool print( QPrinter &printer ); /** * Possible print errors * @since 0.11 (KDE 4.5) */ enum PrintError { NoPrintError, ///< There was no print error UnknownPrintError, TemporaryFileOpenPrintError, FileConversionPrintError, PrintingProcessCrashPrintError, PrintingProcessStartPrintError, PrintToFilePrintError, InvalidPrinterStatePrintError, UnableToFindFilePrintError, NoFileToPrintError, NoBinaryToPrintError, InvalidPageSizePrintError ///< @since 0.18.2 (KDE 4.12.2) }; /** * This method returns the meta data of the given @p key with the given @p option * of the document. */ virtual QVariant metaData( const QString &key, const QVariant &option ) const; /** * Returns the list of additional supported export formats. */ virtual ExportFormat::List exportFormats() const; /** * This method is called to export the document in the given @p format and save it * under the given @p fileName. The format must be one of the supported export formats. */ virtual bool exportTo( const QString &fileName, const ExportFormat &format ); /** * This method is called to know which wallet data should be used for the given file name. * Unless you have very special requirements to where wallet data should be stored you * don't need to reimplement this method. */ virtual void walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const; /** * Query for the specified @p feature. */ bool hasFeature( GeneratorFeature feature ) const; /** * Update DPI of the generator * * @since 0.19 (KDE 4.13) */ void setDPI(const QSizeF &dpi); /** * Returns the 'layers model' object of the document or NULL if * layers model is not available. * * @since 0.24 */ virtual QAbstractItemModel * layersModel() const; /** * Calls the backend to execute an BackendOpaqueAction */ virtual void opaqueAction( const BackendOpaqueAction *action ); Q_SIGNALS: /** * This signal should be emitted whenever an error occurred in the generator. * * @param message The message which should be shown to the user. * @param duration The time that the message should be shown to the user. */ void error( const QString &message, int duration ); /** * This signal should be emitted whenever the user should be warned. * * @param message The message which should be shown to the user. * @param duration The time that the message should be shown to the user. */ void warning( const QString &message, int duration ); /** * This signal should be emitted whenever the user should be noticed. * * @param message The message which should be shown to the user. * @param duration The time that the message should be shown to the user. */ void notice( const QString &message, int duration ); protected: /** * This method must be called when the pixmap request triggered by generatePixmap() * has been finished. */ void signalPixmapRequestDone( PixmapRequest * request ); /** * This method must be called when a text generation has been finished. */ void signalTextGenerationDone( Page *page, TextPage *textPage ); /** * This method is called when the document is closed and not used * any longer. * * @returns true on success, false otherwise. */ virtual bool doCloseDocument() = 0; /** * Returns the image of the page as specified in * the passed pixmap @p request. * * Must return a null image if the request was cancelled and the generator supports cancelling * * @warning this method may be executed in its own separated thread if the * @ref Threaded is enabled! */ virtual QImage image( PixmapRequest *page ); /** * Returns the text page for the given @p request. * * Must return a null pointer if the request was cancelled and the generator supports cancelling * * @warning this method may be executed in its own separated thread if the * @ref Threaded is enabled! * * @since 1.4 */ virtual TextPage* textPage( TextRequest *request ); /** * Returns a pointer to the document. */ const Document * document() const; /** * Toggle the @p feature . */ void setFeature( GeneratorFeature feature, bool on = true ); /** * Internal document setting */ enum DocumentMetaDataKey { PaperColorMetaData, ///< Returns (QColor) the paper color if set in Settings or the default color (white) if option is true (otherwise returns a non initialized QColor) TextAntialiasMetaData, ///< Returns (bool) text antialias from Settings (option is not used) GraphicsAntialiasMetaData, ///< Returns (bool)graphic antialias from Settings (option is not used) TextHintingMetaData ///< Returns (bool)text hinting from Settings (option is not used) }; /** * Request a meta data of the Document, if available, like an internal * setting. * * @since 1.1 */ QVariant documentMetaData( const DocumentMetaDataKey key, const QVariant &option = QVariant() ) const; /** * Request a meta data of the Document, if available, like an internal * setting. */ OKULARCORE_DEPRECATED QVariant documentMetaData( const QString &key, const QVariant &option = QVariant() ) const; /** * Return the pointer to a mutex the generator can use freely. */ QMutex* userMutex() const; /** * Set the bounding box of a page after the page has already been handed * to the Document. Call this instead of Page::setBoundingBox() to ensure * that all observers are notified. * * @since 0.7 (KDE 4.1) */ void updatePageBoundingBox( int page, const NormalizedRect & boundingBox ); /** * Returns DPI, previously set via setDPI() * @since 0.19 (KDE 4.13) */ QSizeF dpi() const; protected Q_SLOTS: /** * Gets the font data for the given font * * @since 0.8 (KDE 4.1) */ void requestFontData(const Okular::FontInfo &font, QByteArray *data); /** * Returns the last print error in case print() failed * @since 0.11 (KDE 4.5) */ Okular::Generator::PrintError printError() const; /** * This method can be called to trigger a partial pixmap update for the given request * Make sure you call it in a way it's executed in the main thread. * @since 1.3 */ void signalPartialPixmapRequest( Okular::PixmapRequest *request, const QImage &image ); protected: /// @cond PRIVATE Generator(GeneratorPrivate &dd, QObject *parent, const QVariantList &args); Q_DECLARE_PRIVATE( Generator ) GeneratorPrivate *d_ptr; friend class Document; friend class DocumentPrivate; /// @endcond PRIVATE private: Q_DISABLE_COPY( Generator ) Q_PRIVATE_SLOT( d_func(), void pixmapGenerationFinished() ) Q_PRIVATE_SLOT( d_func(), void textpageGenerationFinished() ) }; /** * @short Describes a pixmap type request. */ class OKULARCORE_EXPORT PixmapRequest { friend class Document; friend class DocumentPrivate; public: enum PixmapRequestFeature { NoFeature = 0, Asynchronous = 1, Preload = 2 }; Q_DECLARE_FLAGS( PixmapRequestFeatures, PixmapRequestFeature ) /** * Creates a new pixmap request. * * @param observer The observer. * @param pageNumber The page number. * @param width The width of the page. * @param height The height of the page. * @param priority The priority of the request. * @param features The features of generation. */ PixmapRequest( DocumentObserver *observer, int pageNumber, int width, int height, int priority, PixmapRequestFeatures features ); /** * Destroys the pixmap request. */ ~PixmapRequest(); /** * Returns the observer of the request. */ DocumentObserver *observer() const; /** * Returns the page number of the request. */ int pageNumber() const; /** * Returns the page width of the requested pixmap. */ int width() const; /** * Returns the page height of the requested pixmap. */ int height() const; /** * Returns the priority (less it better, 0 is maximum) of the * request. */ int priority() const; /** * Returns whether the generation should be done synchronous or * asynchronous. * * If asynchronous, the pixmap is created in a thread and the observer * is notified when the job is done. */ bool asynchronous() const; /** * Returns whether the generation request is for a page that is not important * i.e. it's just for speeding up future rendering */ bool preload() const; /** * Returns a pointer to the page where the pixmap shall be generated for. */ Page *page() const; /** * Sets whether the generator should render only the given normalized * rect or the entire page * * @since 0.16 (KDE 4.10) */ void setTile( bool tile ); /** * Returns whether the generator should render just the region given by * normalizedRect() or the entire page. * * @since 0.16 (KDE 4.10) */ bool isTile() const; /** * Sets the region of the page to request. * * @since 0.16 (KDE 4.10) */ void setNormalizedRect( const NormalizedRect &rect ); /** * Returns the normalized region of the page to request. * * @since 0.16 (KDE 4.10) */ const NormalizedRect& normalizedRect() const; /** * Sets whether the request should report back updates if possible * * @since 1.3 */ void setPartialUpdatesWanted(bool partialUpdatesWanted); /** * Should the request report back updates if possible? * * @since 1.3 */ bool partialUpdatesWanted() const; /** * Should the request be aborted if possible? * * @since 1.4 */ bool shouldAbortRender() const; private: Q_DISABLE_COPY( PixmapRequest ) friend class PixmapRequestPrivate; PixmapRequestPrivate* const d; }; /** * @short Describes a text request. * * @since 1.4 */ class OKULARCORE_EXPORT TextRequest { public: /** * Creates a new text request. */ explicit TextRequest( Page *page ); TextRequest(); /** * Destroys the pixmap request. */ ~TextRequest(); /** * Returns a pointer to the page where the pixmap shall be generated for. */ Page *page() const; /** * Should the request be aborted if possible? */ bool shouldAbortExtraction() const; private: Q_DISABLE_COPY( TextRequest ) friend TextRequestPrivate; TextRequestPrivate* const d; }; } Q_DECLARE_METATYPE(Okular::Generator::PrintError) Q_DECLARE_METATYPE(Okular::PixmapRequest*) #define OkularGeneratorInterface_iid "org.kde.okular.Generator" Q_DECLARE_INTERFACE(Okular::Generator, OkularGeneratorInterface_iid) #ifndef QT_NO_DEBUG_STREAM OKULARCORE_EXPORT QDebug operator<<( QDebug str, const Okular::PixmapRequest &req ); #endif #endif /* kate: replace-tabs on; indent-width 4; */ diff --git a/core/global.h b/core/global.h index 93d76fe2f..038b1fc06 100644 --- a/core/global.h +++ b/core/global.h @@ -1,83 +1,89 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #ifndef OKULAR_GLOBAL_H #define OKULAR_GLOBAL_H #include +/** + * \namespace Okular global.h + * + * \brief The documentation to the global Okular namespace. + */ + namespace Okular { /** * Describes the DRM capabilities. */ enum Permission { AllowModify = 1, ///< Allows to modify the document AllowCopy = 2, ///< Allows to copy the document AllowPrint = 4, ///< Allows to print the document AllowNotes = 8, ///< Allows to add annotations to the document AllowFillForms = 16 ///< Allows to fill the forms in the document }; Q_DECLARE_FLAGS( Permissions, Permission ) /** * Describes the direction of searching. */ enum SearchDirection { FromTop, ///< Searching from top of the page, next result is to be found, there was no earlier search result. FromBottom, ///< Searching from bottom of the page, next result is to be found, there was no earlier search result. NextResult, ///< Searching for the next result on the page, earlier result should be located so we search from the last result not from the beginning of the page. PreviousResult ///< Searching for the previous result on the page, earlier result should be located so we search from the last result not from the beginning of the page. }; /** * A rotation. */ enum Rotation { Rotation0 = 0, ///< Not rotated. Rotation90 = 1, ///< Rotated 90 degrees clockwise. Rotation180 = 2, ///< Rotated 180 degrees clockwise. Rotation270 = 3 ///< Rotated 2700 degrees clockwise. }; /** * Describes the type of generation of objects */ enum GenerationType { Synchronous, ///< Will create the object in a synchronous way Asynchronous ///< Will create the object in an asynchronous way }; /** * The side(s) to be considered when merging areas. */ enum MergeSide { MergeRight = 0, ///< Merge only if the right side of the first area intersect. MergeBottom = 1, ///< Merge only if the bottom side of the first area intersect. MergeLeft = 2, ///< Merge only if the left side of the first area intersect. MergeTop = 3, ///< Merge only if the top side of the first area intersect. MergeAll = 4 ///< Merge if the areas intersects, no matter which side(s). }; /** * Describes the possible script types. */ enum ScriptType { JavaScript = 0 ///< JavaScript code }; } #endif