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 TextDocumentUtils::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 TextDocumentUtils::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 TextDocumentUtils::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 TextDocumentUtils::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 TextDocumentUtils::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
< QPair
<int,QDomNode
> > parentNodeStack
;
184 QDomNode parentNode
= mDocumentSynopsis
;
186 parentNodeStack
.push( qMakePair( 0, parentNode
) );
188 for ( int i
= 0; i
< mTitlePositions
.count(); ++i
) {
189 const TitlePosition
&position
= mTitlePositions
[ i
];
191 Okular::DocumentViewport viewport
= TextDocumentUtils::calculateViewport( mDocument
, position
.block
);
193 QDomElement item
= mDocumentSynopsis
.createElement( position
.title
);
194 item
.setAttribute( "Viewport", viewport
.toString() );
196 int headingLevel
= position
.level
;
198 // we need a parent, which has to be at a higher heading level than this heading level
199 // so we just work through the stack
200 while ( ! parentNodeStack
.isEmpty() ) {
201 int parentLevel
= parentNodeStack
.top().first
;
202 if ( parentLevel
< headingLevel
) {
203 // this is OK as a parent
204 parentNode
= parentNodeStack
.top().second
;
207 // we'll need to be further into the stack
208 parentNodeStack
.pop();
211 parentNode
.appendChild( item
);
212 parentNodeStack
.push( qMakePair( headingLevel
, QDomNode(item
) ) );
216 TextDocumentGenerator::TextDocumentGenerator( TextDocumentConverter
*converter
, QObject
*parent
, const QVariantList
&args
)
217 : Okular::Generator( *new TextDocumentGeneratorPrivate( converter
), parent
, args
)
219 converter
->d_ptr
->mParent
= d_func();
221 setFeature( TextExtraction
);
222 setFeature( PrintNative
);
223 setFeature( PrintToFile
);
224 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
225 if ( QFontDatabase::supportsThreadedFontRendering() )
226 setFeature( Threaded
);
229 connect( converter
, SIGNAL( addAction( Action
*, int, int ) ),
230 this, SLOT( addAction( Action
*, int, int ) ) );
231 connect( converter
, SIGNAL( addAnnotation( Annotation
*, int, int ) ),
232 this, SLOT( addAnnotation( Annotation
*, int, int ) ) );
233 connect( converter
, SIGNAL( addTitle( int, const QString
&, const QTextBlock
& ) ),
234 this, SLOT( addTitle( int, const QString
&, const QTextBlock
& ) ) );
235 connect( converter
, SIGNAL( addMetaData( const QString
&, const QString
&, const QString
& ) ),
236 this, SLOT( addMetaData( const QString
&, const QString
&, const QString
& ) ) );
237 connect( converter
, SIGNAL( addMetaData( DocumentInfo::Key
, const QString
& ) ),
238 this, SLOT( addMetaData( DocumentInfo::Key
, const QString
& ) ) );
240 connect( converter
, SIGNAL( error( const QString
&, int ) ),
241 this, SIGNAL( error( const QString
&, int ) ) );
242 connect( converter
, SIGNAL( warning( const QString
&, int ) ),
243 this, SIGNAL( warning( const QString
&, int ) ) );
244 connect( converter
, SIGNAL( notice( const QString
&, int ) ),
245 this, SIGNAL( notice( const QString
&, int ) ) );
248 TextDocumentGenerator::~TextDocumentGenerator()
252 bool TextDocumentGenerator::loadDocument( const QString
& fileName
, QVector
<Okular::Page
*> & pagesVector
)
254 Q_D( TextDocumentGenerator
);
255 d
->mDocument
= d
->mConverter
->convert( fileName
);
259 // loading failed, cleanup all the stuff eventually gathered from the converter
260 d
->mTitlePositions
.clear();
261 Q_FOREACH ( const TextDocumentGeneratorPrivate::LinkPosition
&linkPos
, d
->mLinkPositions
)
265 d
->mLinkPositions
.clear();
266 Q_FOREACH ( const TextDocumentGeneratorPrivate::AnnotationPosition
&annPos
, d
->mAnnotationPositions
)
268 delete annPos
.annotation
;
270 d
->mAnnotationPositions
.clear();
275 d
->generateTitleInfos();
276 d
->generateLinkInfos();
277 d
->generateAnnotationInfos();
279 pagesVector
.resize( d
->mDocument
->pageCount() );
281 const QSize size
= d
->mDocument
->pageSize().toSize();
283 QVector
< QLinkedList
<Okular::ObjectRect
*> > objects( d
->mDocument
->pageCount() );
284 for ( int i
= 0; i
< d
->mLinkInfos
.count(); ++i
) {
285 const TextDocumentGeneratorPrivate::LinkInfo
&info
= d
->mLinkInfos
.at( i
);
287 const QRectF rect
= info
.boundingRect
;
288 objects
[ info
.page
].append( new Okular::ObjectRect( rect
.left(), rect
.top(), rect
.right(), rect
.bottom(), false,
289 Okular::ObjectRect::Action
, info
.link
) );
292 QVector
< QLinkedList
<Okular::Annotation
*> > annots( d
->mDocument
->pageCount() );
293 for ( int i
= 0; i
< d
->mAnnotationInfos
.count(); ++i
) {
294 const TextDocumentGeneratorPrivate::AnnotationInfo
&info
= d
->mAnnotationInfos
[ i
];
296 QRect
rect( 0, info
.page
* size
.height(), size
.width(), size
.height() );
297 info
.annotation
->setBoundingRectangle( Okular::NormalizedRect( rect
.left(), rect
.top(), rect
.right(), rect
.bottom() ) );
298 annots
[ info
.page
].append( info
.annotation
);
301 for ( int i
= 0; i
< d
->mDocument
->pageCount(); ++i
) {
302 Okular::Page
* page
= new Okular::Page( i
, size
.width(), size
.height(), Okular::Rotation0
);
303 pagesVector
[ i
] = page
;
305 if ( !objects
.at( i
).isEmpty() ) {
306 page
->setObjectRects( objects
.at( i
) );
308 QLinkedList
<Okular::Annotation
*>::ConstIterator annIt
= annots
.at( i
).begin(), annEnd
= annots
.at( i
).end();
309 for ( ; annIt
!= annEnd
; ++annIt
) {
310 page
->addAnnotation( *annIt
);
317 bool TextDocumentGenerator::doCloseDocument()
319 Q_D( TextDocumentGenerator
);
323 d
->mTitlePositions
.clear();
324 d
->mLinkPositions
.clear();
325 d
->mLinkInfos
.clear();
326 d
->mAnnotationPositions
.clear();
327 d
->mAnnotationInfos
.clear();
328 // do not use clear() for the following two, otherwise they change type
329 d
->mDocumentInfo
= Okular::DocumentInfo();
330 d
->mDocumentSynopsis
= Okular::DocumentSynopsis();
335 bool TextDocumentGenerator::canGeneratePixmap() const
337 return Generator::canGeneratePixmap();
340 void TextDocumentGenerator::generatePixmap( Okular::PixmapRequest
* request
)
342 Generator::generatePixmap( request
);
345 QImage
TextDocumentGeneratorPrivate::image( PixmapRequest
* request
)
350 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
351 Q_Q( TextDocumentGenerator
);
354 QImage
image( request
->width(), request
->height(), QImage::Format_ARGB32
);
355 image
.fill( Qt::white
);
360 qreal width
= request
->width();
361 qreal height
= request
->height();
363 const QSize size
= mDocument
->pageSize().toSize();
365 p
.scale( width
/ (qreal
)size
.width(), height
/ (qreal
)size
.height() );
368 rect
= QRect( 0, request
->pageNumber() * size
.height(), size
.width(), size
.height() );
369 p
.translate( QPoint( 0, request
->pageNumber() * size
.height() * -1 ) );
370 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
371 q
->userMutex()->lock();
373 mDocument
->drawContents( &p
, rect
);
374 #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING
375 q
->userMutex()->unlock();
382 Okular::TextPage
* TextDocumentGenerator::textPage( Okular::Page
* page
)
384 Q_D( TextDocumentGenerator
);
385 return d
->createTextPage( page
->number() );
388 bool TextDocumentGenerator::print( QPrinter
& printer
)
390 Q_D( TextDocumentGenerator
);
394 d
->mDocument
->print( &printer
);
399 const Okular::DocumentInfo
* TextDocumentGenerator::generateDocumentInfo()
401 Q_D( TextDocumentGenerator
);
402 return &d
->mDocumentInfo
;
405 const Okular::DocumentSynopsis
* TextDocumentGenerator::generateDocumentSynopsis()
407 Q_D( TextDocumentGenerator
);
408 if ( !d
->mDocumentSynopsis
.hasChildNodes() )
411 return &d
->mDocumentSynopsis
;
414 QVariant
TextDocumentGeneratorPrivate::metaData( const QString
&key
, const QVariant
&option
) const
417 if ( key
== "DocumentTitle" )
419 return mDocumentInfo
.get( "title" );
424 Okular::ExportFormat::List
TextDocumentGenerator::exportFormats( ) const
426 static Okular::ExportFormat::List formats
;
427 if ( formats
.isEmpty() ) {
428 formats
.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText
) );
429 formats
.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PDF
) );
430 #if QT_VERSION >= 0x040500
431 if ( QTextDocumentWriter::supportedDocumentFormats().contains( "ODF" ) ) {
432 formats
.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::OpenDocumentText
) );
434 if ( QTextDocumentWriter::supportedDocumentFormats().contains( "HTML" ) ) {
435 formats
.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::HTML
) );
443 bool TextDocumentGenerator::exportTo( const QString
&fileName
, const Okular::ExportFormat
&format
)
445 Q_D( TextDocumentGenerator
);
449 if ( format
.mimeType()->name() == QLatin1String( "application/pdf" ) ) {
450 QFile
file( fileName
);
451 if ( !file
.open( QIODevice::WriteOnly
) )
454 QPrinter
printer( QPrinter::HighResolution
);
455 printer
.setOutputFormat( QPrinter::PdfFormat
);
456 printer
.setOutputFileName( fileName
);
457 d
->mDocument
->print( &printer
);
460 } else if ( format
.mimeType()->name() == QLatin1String( "text/plain" ) ) {
461 QFile
file( fileName
);
462 if ( !file
.open( QIODevice::WriteOnly
) )
465 QTextStream
out( &file
);
466 out
<< d
->mDocument
->toPlainText();
469 #if QT_VERSION >= 0x040500
470 } else if ( format
.mimeType()->name() == QLatin1String( "application/vnd.oasis.opendocument.text" ) ) {
471 QTextDocumentWriter
odfWriter( fileName
, "odf" );
473 return odfWriter
.write( d
->mDocument
);
474 } else if ( format
.mimeType()->name() == QLatin1String( "text/html" ) ) {
475 QTextDocumentWriter
odfWriter( fileName
, "html" );
477 return odfWriter
.write( d
->mDocument
);
483 #include "textdocumentgenerator.moc"