forked from KDE/okular
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Mainpage.dox
907 lines (673 loc) · 30.8 KB
/
Mainpage.dox
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
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
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
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
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
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
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
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
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
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
814
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
850
851
852
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
907
/**
\mainpage Okular, the unified document viewer
\section okular_overview Overview
- \ref okular_history
- \ref okular_design
- \ref okular_generators
- <a href="http://www.okular.org">Website</a>
\authors Tobias König <[email protected]>
\licenses \lgpl
\page okular_history Historical background
Okular is the successor of <a href="http://kpdf.kde.org">kpdf</a>, 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 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 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 <em>where</em>
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 <em>normalized</em> 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 <okular/core/generator.h>
class MagicGenerator : public Okular::Generator
{
public:
MagicGenerator( QObject *parent, const QVariantList &args );
~MagicGenerator();
bool loadDocument( const QString &fileName, QVector<Okular::Page*> &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 <okular/core/page.h>
#include "magicgenerator.h"
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<Okular::Page*> &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 <em>false</em> 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 1 .json, 3 .desktop files, and 1 .xml file:
\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 4 files:
\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
where libokularGenerator_magic.json has the following content something like this
\verbatim
{
"KPlugin": {
"Authors": [
{
"Email": "[email protected]",
"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 last five fields has the special meaning to Okular
\li <b>ServiceType</b> Must be 'okular/Generator' for all Okular Generator Plugins
\li <b>MimeType</b> The mimetype or list of mimetypes of the supported document format(s)
\li <b>X-KDE-Priority</b> When multiple Generators for the same mimetype exists, the one with the highest priority is used
\li <b>X-KDE-okularAPIVersion</b> The version of the Generator Plugin API ('1' currently)
\li <b>X-KDE-okularHasInternalSettings</b> Is 'true' when the Generator provides configuration dialogs
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
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 second .desktop file looks like this:
\verbatim
[Desktop Entry]
Icon=okular
Name=okular
X-KDE-ServiceTypes=KParts/ReadOnlyPart
X-KDE-Library=okularpart
Type=Service
MimeType=application/x-magic;
\endverbatim
where
\li <b>X-KDE-Library</b> 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
<?xml version="1.0" encoding="utf-8"?>
<component type="addon">
<id>org.kde.okular-md</id>
<extends>org.kde.okular.desktop</extends>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-2.0+ and GFDL-1.3</project_license>
<name>Magic</name>
<summary>Adds support for reading Magic documents</summary>
<mimetypes>
<mimetype>application/magic</mimetype>
</mimetypes>
<url type="homepage">https://okular.kde.org</url>
</component>
\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} ${KF5_INCLUDE_DIR} ${QT_INCLUDES} )
########### next target ###############
set( okularGenerator_magic_PART_SRCS generator_magic.cpp )
target_link_libraries( okularGenerator_magic PRIVATE okularcore KF5::I18n KF5::KIOCore )
install( TARGETS okularGenerator_magic DESTINATION ${PLUGIN_INSTALL_DIR} )
########### install files ###############
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<TextInfo> 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 <okular/core/generator.h>
class MagicGenerator : public Okular::Generator
{
public:
MagicGenerator( QObject *parent, const QVariantList &args );
~MagicGenerator();
bool loadDocument( const QString &fileName, QVector<Okular::Page*> &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 <em>false</em>.
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 <em>type</em> parameter that is done synchronously or asynchronously.
Let us take a look at the implementation of these methods in our MagicGenerator:
\code
#include <okular/core/textpage.h>
...
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:
<ul>
<li><b>Signals and Slots</b></li>
<ul>
<li>Pro: Can be used with backend libraries which are not thread safe</li>
<li>Con: Sometime difficult to implement</li>
</ul>
<li><b>Threads</b></li>
<ul>
<li>Pro: Easy to implement as you can make synchronous calls to the backend libraries</li>
<li>Con: Backend libraries must be thread safe and you must prevent race conditions by using mutexes</li>
</ul>
</ul>
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 <okular/core/generator.h>
class MagicGenerator : public Okular::Generator
{
public:
MagicGenerator( QObject *parent, const QVariantList &args );
~MagicGenerator();
bool loadDocument( const QString &fileName, QVector<Okular::Page*> &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 <QtGui/QTextDocument>
#include <okular/core/generator.h>
class HTMLGenerator : public Okular::Generator
{
public:
HTMLGenerator( QObject *parent, const QVariantList &args );
~HTMLGenerator();
bool loadDocument( const QString &fileName, QVector<Okular::Page*> &pages );
bool canGeneratePixmap() const;
void generatePixmap( Okular::PixmapRequest *request );
virtual Okular::DocumentInfo generateDocumentInfo( const QSet<Okular::DocumentInfo::Key> &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 (not yet written) anyway.
As you can see we have 5 new methods in the class:
\li <b>generateDocumentInfo()</b> Creates an Okular::DocumentInfo (which is in fact a QDomDocument)
which contains document information like author, creation time etc.
\li <b>generateDocumentSynopsis()</b> Creates an Okular::DocumentSynopsis (which is a QDomDocument as well)
which contains the table of content.
\li <b>print()</b> Prints the document to the passed printer.
\li <b>exportFormats()</b> Returns the supported export formats.
\li <b>exportTo()</b> 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 <QFile>
#include <QAbstractTextDocumentLayout>
#include <QPrinter>
#include <okular/core/document.h>
#include <okular/core/page.h>
#include "htmlgenerator.h"
#include <KLocalizedString>
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<Okular::Page*> &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<Okular::DocumentInfo::Key> &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]
*/