1 /***************************************************************************
2 * Copyright (C) 2007 by Tobias Koenig <tokoe@kde.org> *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 ***************************************************************************/
10 #include "textdocumentgenerator.h"
11 #include "textdocumentgenerator_p.h"
13 #include <QtCore/QFile>
14 #include <QtCore/QMutex>
15 #include <QtCore/QStack>
16 #include <QtCore/QTextStream>
17 #include <QtCore/QVector>
18 #include <QtGui/QFontDatabase>
19 #include <QtGui/QImage>
20 #include <QtGui/QPainter>
21 #include <QtGui/QPrinter>
22 #if QT_VERSION >= 0x040500
23 #include <QtGui/QTextDocumentWriter>
26 #include <okular/core/action.h>
27 #include <okular/core/annotations.h>
28 #include <okular/core/page.h>
29 #include <okular/core/textpage.h>
33 using namespace Okular
;
36 * Generic Converter Implementation
38 TextDocumentConverter::TextDocumentConverter()
39 : QObject( 0 ), d_ptr( new TextDocumentConverterPrivate
)
43 TextDocumentConverter::~TextDocumentConverter()
48 DocumentViewport
TextDocumentConverter::calculateViewport( QTextDocument
*document
, const QTextBlock
&block
)
50 return Utils::calculateViewport( document
, block
);
53 TextDocumentGenerator
* TextDocumentConverter::generator() const
55 return d_ptr
->mParent
? d_ptr
->mParent
->q_func() : 0;
59 * Generic Generator Implementation
61 Okular::TextPage
* TextDocumentGeneratorPrivate::createTextPage( int pageNumber
) const
63 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
64 Q_Q( const TextDocumentGenerator
);
67 Okular::TextPage
*textPage
= new Okular::TextPage
;
71 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
72 q
->userMutex()->lock();
74 Utils::calculatePositions( mDocument
, pageNumber
, start
, end
);
77 QTextCursor
cursor( mDocument
);
78 for ( int i
= start
; i
< end
- 1; ++i
) {
79 cursor
.setPosition( i
);
80 cursor
.setPosition( i
+ 1, QTextCursor::KeepAnchor
);
82 QString text
= cursor
.selectedText();
83 if ( text
.length() == 1 ) {
85 Utils::calculateBoundingRect( mDocument
, i
, i
+ 1, rect
, pageNumber
);
86 if ( pageNumber
== -1 )
89 textPage
->append( text
, new Okular::NormalizedRect( rect
.left(), rect
.top(), rect
.right(), rect
.bottom() ) );
93 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
94 q
->userMutex()->unlock();
100 void TextDocumentGeneratorPrivate::addAction( Action
*action
, int cursorBegin
, int cursorEnd
)
105 LinkPosition position
;
106 position
.link
= action
;
107 position
.startPosition
= cursorBegin
;
108 position
.endPosition
= cursorEnd
;
110 mLinkPositions
.append( position
);
113 void TextDocumentGeneratorPrivate::addAnnotation( Annotation
*annotation
, int cursorBegin
, int cursorEnd
)
118 annotation
->setFlags( annotation
->flags() | Okular::Annotation::External
);
120 AnnotationPosition position
;
121 position
.annotation
= annotation
;
122 position
.startPosition
= cursorBegin
;
123 position
.endPosition
= cursorEnd
;
125 mAnnotationPositions
.append( position
);
128 void TextDocumentGeneratorPrivate::addTitle( int level
, const QString
&title
, const QTextBlock
&block
)
130 TitlePosition position
;
131 position
.level
= level
;
132 position
.title
= title
;
133 position
.block
= block
;
135 mTitlePositions
.append( position
);
138 void TextDocumentGeneratorPrivate::addMetaData( const QString
&key
, const QString
&value
, const QString
&title
)
140 mDocumentInfo
.set( key
, value
, title
);
143 void TextDocumentGeneratorPrivate::addMetaData( DocumentInfo::Key key
, const QString
&value
)
145 mDocumentInfo
.set( key
, value
);
148 void TextDocumentGeneratorPrivate::generateLinkInfos()
150 for ( int i
= 0; i
< mLinkPositions
.count(); ++i
) {
151 const LinkPosition
&linkPosition
= mLinkPositions
[ i
];
154 info
.link
= linkPosition
.link
;
156 Utils::calculateBoundingRect( mDocument
, linkPosition
.startPosition
, linkPosition
.endPosition
,
157 info
.boundingRect
, info
.page
);
159 if ( info
.page
>= 0 )
160 mLinkInfos
.append( info
);
164 void TextDocumentGeneratorPrivate::generateAnnotationInfos()
166 for ( int i
= 0; i
< mAnnotationPositions
.count(); ++i
) {
167 const AnnotationPosition
&annotationPosition
= mAnnotationPositions
[ i
];
170 info
.annotation
= annotationPosition
.annotation
;
172 Utils::calculateBoundingRect( mDocument
, annotationPosition
.startPosition
, annotationPosition
.endPosition
,
173 info
.boundingRect
, info
.page
);
175 if ( info
.page
>= 0 )
176 mAnnotationInfos
.append( info
);
180 void TextDocumentGeneratorPrivate::generateTitleInfos()
182 QStack
<QDomNode
> parentNodeStack
;
184 QDomNode parentNode
= mDocumentSynopsis
;
187 for ( int i
= 0; i
< mTitlePositions
.count(); ++i
)
188 level
= qMin( level
, mTitlePositions
[ i
].level
);
190 for ( int i
= 0; i
< mTitlePositions
.count(); ++i
) {
191 const TitlePosition
&position
= mTitlePositions
[ i
];
193 Okular::DocumentViewport viewport
= Utils::calculateViewport( mDocument
, position
.block
);
195 QDomElement item
= mDocumentSynopsis
.createElement( position
.title
);
196 item
.setAttribute( "Viewport", viewport
.toString() );
198 int newLevel
= position
.level
;
199 if ( newLevel
== level
) {
200 parentNode
.appendChild( item
);
201 } else if ( newLevel
> level
) {
202 parentNodeStack
.push( parentNode
);
203 parentNode
= parentNode
.lastChildElement();
204 parentNode
.appendChild( item
);
207 for ( int i
= level
; i
> newLevel
; i
-- ) {
209 parentNode
= parentNodeStack
.pop();
212 parentNode
.appendChild( item
);
217 TextDocumentGenerator::TextDocumentGenerator( TextDocumentConverter
*converter
, QObject
*parent
, const QVariantList
&args
)
218 : Okular::Generator( *new TextDocumentGeneratorPrivate( converter
), parent
, args
)
220 converter
->d_ptr
->mParent
= d_func();
222 setFeature( TextExtraction
);
223 setFeature( PrintNative
);
224 setFeature( PrintToFile
);
225 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
226 if ( QFontDatabase::supportsThreadedFontRendering() )
227 setFeature( Threaded
);
230 connect( converter
, SIGNAL( addAction( Action
*, int, int ) ),
231 this, SLOT( addAction( Action
*, int, int ) ) );
232 connect( converter
, SIGNAL( addAnnotation( Annotation
*, int, int ) ),
233 this, SLOT( addAnnotation( Annotation
*, int, int ) ) );
234 connect( converter
, SIGNAL( addTitle( int, const QString
&, const QTextBlock
& ) ),
235 this, SLOT( addTitle( int, const QString
&, const QTextBlock
& ) ) );
236 connect( converter
, SIGNAL( addMetaData( const QString
&, const QString
&, const QString
& ) ),
237 this, SLOT( addMetaData( const QString
&, const QString
&, const QString
& ) ) );
238 connect( converter
, SIGNAL( addMetaData( DocumentInfo::Key
, const QString
& ) ),
239 this, SLOT( addMetaData( DocumentInfo::Key
, const QString
& ) ) );
241 connect( converter
, SIGNAL( error( const QString
&, int ) ),
242 this, SIGNAL( error( const QString
&, int ) ) );
243 connect( converter
, SIGNAL( warning( const QString
&, int ) ),
244 this, SIGNAL( warning( const QString
&, int ) ) );
245 connect( converter
, SIGNAL( notice( const QString
&, int ) ),
246 this, SIGNAL( notice( const QString
&, int ) ) );
249 TextDocumentGenerator::~TextDocumentGenerator()
253 bool TextDocumentGenerator::loadDocument( const QString
& fileName
, QVector
<Okular::Page
*> & pagesVector
)
255 Q_D( TextDocumentGenerator
);
256 d
->mDocument
= d
->mConverter
->convert( fileName
);
260 // loading failed, cleanup all the stuff eventually gathered from the converter
261 d
->mTitlePositions
.clear();
262 Q_FOREACH ( const TextDocumentGeneratorPrivate::LinkPosition
&linkPos
, d
->mLinkPositions
)
266 d
->mLinkPositions
.clear();
267 Q_FOREACH ( const TextDocumentGeneratorPrivate::AnnotationPosition
&annPos
, d
->mAnnotationPositions
)
269 delete annPos
.annotation
;
271 d
->mAnnotationPositions
.clear();
276 d
->generateTitleInfos();
277 d
->generateLinkInfos();
278 d
->generateAnnotationInfos();
280 pagesVector
.resize( d
->mDocument
->pageCount() );
282 const QSize size
= d
->mDocument
->pageSize().toSize();
284 QVector
< QLinkedList
<Okular::ObjectRect
*> > objects( d
->mDocument
->pageCount() );
285 for ( int i
= 0; i
< d
->mLinkInfos
.count(); ++i
) {
286 const TextDocumentGeneratorPrivate::LinkInfo
&info
= d
->mLinkInfos
.at( i
);
288 const QRectF rect
= info
.boundingRect
;
289 objects
[ info
.page
].append( new Okular::ObjectRect( rect
.left(), rect
.top(), rect
.right(), rect
.bottom(), false,
290 Okular::ObjectRect::Action
, info
.link
) );
293 QVector
< QLinkedList
<Okular::Annotation
*> > annots( d
->mDocument
->pageCount() );
294 for ( int i
= 0; i
< d
->mAnnotationInfos
.count(); ++i
) {
295 const TextDocumentGeneratorPrivate::AnnotationInfo
&info
= d
->mAnnotationInfos
[ i
];
297 QRect
rect( 0, info
.page
* size
.height(), size
.width(), size
.height() );
298 info
.annotation
->setBoundingRectangle( Okular::NormalizedRect( rect
.left(), rect
.top(), rect
.right(), rect
.bottom() ) );
299 annots
[ info
.page
].append( info
.annotation
);
302 for ( int i
= 0; i
< d
->mDocument
->pageCount(); ++i
) {
303 Okular::Page
* page
= new Okular::Page( i
, size
.width(), size
.height(), Okular::Rotation0
);
304 pagesVector
[ i
] = page
;
306 if ( !objects
.at( i
).isEmpty() ) {
307 page
->setObjectRects( objects
.at( i
) );
309 QLinkedList
<Okular::Annotation
*>::ConstIterator annIt
= annots
.at( i
).begin(), annEnd
= annots
.at( i
).end();
310 for ( ; annIt
!= annEnd
; ++annIt
) {
311 page
->addAnnotation( *annIt
);
318 bool TextDocumentGenerator::doCloseDocument()
320 Q_D( TextDocumentGenerator
);
324 d
->mTitlePositions
.clear();
325 d
->mLinkPositions
.clear();
326 d
->mLinkInfos
.clear();
327 d
->mAnnotationPositions
.clear();
328 d
->mAnnotationInfos
.clear();
329 // do not use clear() for the following two, otherwise they change type
330 d
->mDocumentInfo
= Okular::DocumentInfo();
331 d
->mDocumentSynopsis
= Okular::DocumentSynopsis();
336 bool TextDocumentGenerator::canGeneratePixmap() const
338 return Generator::canGeneratePixmap();
341 void TextDocumentGenerator::generatePixmap( Okular::PixmapRequest
* request
)
343 Generator::generatePixmap( request
);
346 QImage
TextDocumentGeneratorPrivate::image( PixmapRequest
* request
)
351 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
352 Q_Q( TextDocumentGenerator
);
355 QImage
image( request
->width(), request
->height(), QImage::Format_ARGB32
);
356 image
.fill( Qt::white
);
361 qreal width
= request
->width();
362 qreal height
= request
->height();
364 const QSize size
= mDocument
->pageSize().toSize();
366 p
.scale( width
/ (qreal
)size
.width(), height
/ (qreal
)size
.height() );
369 rect
= QRect( 0, request
->pageNumber() * size
.height(), size
.width(), size
.height() );
370 p
.translate( QPoint( 0, request
->pageNumber() * size
.height() * -1 ) );
371 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
372 q
->userMutex()->lock();
374 mDocument
->drawContents( &p
, rect
);
375 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
376 q
->userMutex()->unlock();
383 Okular::TextPage
* TextDocumentGenerator::textPage( Okular::Page
* page
)
385 Q_D( TextDocumentGenerator
);
386 return d
->createTextPage( page
->number() );
389 bool TextDocumentGenerator::print( QPrinter
& printer
)
391 Q_D( TextDocumentGenerator
);
395 d
->mDocument
->print( &printer
);
400 const Okular::DocumentInfo
* TextDocumentGenerator::generateDocumentInfo()
402 Q_D( TextDocumentGenerator
);
403 return &d
->mDocumentInfo
;
406 const Okular::DocumentSynopsis
* TextDocumentGenerator::generateDocumentSynopsis()
408 Q_D( TextDocumentGenerator
);
409 if ( !d
->mDocumentSynopsis
.hasChildNodes() )
412 return &d
->mDocumentSynopsis
;
415 QVariant
TextDocumentGeneratorPrivate::metaData( const QString
&key
, const QVariant
&option
) const
418 if ( key
== "DocumentTitle" )
420 return mDocumentInfo
.get( "title" );
425 Okular::ExportFormat::List
TextDocumentGenerator::exportFormats( ) const
427 static Okular::ExportFormat::List formats
;
428 if ( formats
.isEmpty() ) {
429 formats
.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText
) );
430 formats
.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PDF
) );
431 #if QT_VERSION >= 0x040500
432 if ( QTextDocumentWriter::supportedDocumentFormats().contains( "ODF" ) ) {
433 formats
.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::OpenDocumentText
) );
435 if ( QTextDocumentWriter::supportedDocumentFormats().contains( "HTML" ) ) {
436 formats
.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::HTML
) );
444 bool TextDocumentGenerator::exportTo( const QString
&fileName
, const Okular::ExportFormat
&format
)
446 Q_D( TextDocumentGenerator
);
450 if ( format
.mimeType()->name() == QLatin1String( "application/pdf" ) ) {
451 QFile
file( fileName
);
452 if ( !file
.open( QIODevice::WriteOnly
) )
455 QPrinter
printer( QPrinter::HighResolution
);
456 printer
.setOutputFormat( QPrinter::PdfFormat
);
457 printer
.setOutputFileName( fileName
);
458 d
->mDocument
->print( &printer
);
461 } else if ( format
.mimeType()->name() == QLatin1String( "text/plain" ) ) {
462 QFile
file( fileName
);
463 if ( !file
.open( QIODevice::WriteOnly
) )
466 QTextStream
out( &file
);
467 out
<< d
->mDocument
->toPlainText();
470 #if QT_VERSION >= 0x040500
471 } else if ( format
.mimeType()->name() == QLatin1String( "application/vnd.oasis.opendocument.text" ) ) {
472 QTextDocumentWriter
odfWriter( fileName
, "odf" );
474 return odfWriter
.write( d
->mDocument
);
475 } else if ( format
.mimeType()->name() == QLatin1String( "text/html" ) ) {
476 QTextDocumentWriter
odfWriter( fileName
, "html" );
478 return odfWriter
.write( d
->mDocument
);
484 #include "textdocumentgenerator.moc"