diff --git a/core/textdocumentgenerator.cpp b/core/textdocumentgenerator.cpp index 32ba7cbb2..568e810d8 100644 --- a/core/textdocumentgenerator.cpp +++ b/core/textdocumentgenerator.cpp @@ -1,586 +1,586 @@ /*************************************************************************** * 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. * ***************************************************************************/ #include "textdocumentgenerator.h" #include "textdocumentgenerator_p.h" #include #include #include #include #include #include #include #include #include #include #include "action.h" #include "annotations.h" #include "document_p.h" #include "page.h" #include "textpage.h" #include "document.h" #include using namespace Okular; /** * Generic Converter Implementation */ TextDocumentConverter::TextDocumentConverter() : QObject( nullptr ), d_ptr( new TextDocumentConverterPrivate ) { } TextDocumentConverter::~TextDocumentConverter() { delete d_ptr; } QTextDocument *TextDocumentConverter::convert( const QString & ) { return nullptr; } Document::OpenResult TextDocumentConverter::convertWithPassword( const QString &fileName, const QString & ) { QTextDocument *doc = convert( fileName ); setDocument( doc ); return doc != nullptr ? Document::OpenSuccess : Document::OpenError; } QTextDocument *TextDocumentConverter::document() { return d_ptr->mDocument; } void TextDocumentConverter::setDocument( QTextDocument *document ) { d_ptr->mDocument = document; } DocumentViewport TextDocumentConverter::calculateViewport( QTextDocument *document, const QTextBlock &block ) { return TextDocumentUtils::calculateViewport( document, block ); } TextDocumentGenerator* TextDocumentConverter::generator() const { return d_ptr->mParent ? d_ptr->mParent->q_func() : nullptr; } /** * Generic Generator Implementation */ Okular::TextPage* TextDocumentGeneratorPrivate::createTextPage( int pageNumber ) const { #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING Q_Q( const TextDocumentGenerator ); #endif Okular::TextPage *textPage = new Okular::TextPage; int start, end; #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->userMutex()->lock(); #endif TextDocumentUtils::calculatePositions( mDocument, pageNumber, start, end ); { QTextCursor cursor( mDocument ); for ( int i = start; i < end - 1; ++i ) { cursor.setPosition( i ); cursor.setPosition( i + 1, QTextCursor::KeepAnchor ); QString text = cursor.selectedText(); if ( text.length() == 1 ) { QRectF rect; TextDocumentUtils::calculateBoundingRect( mDocument, i, i + 1, rect, pageNumber ); if ( pageNumber == -1 ) text = QStringLiteral("\n"); textPage->append( text, new Okular::NormalizedRect( rect.left(), rect.top(), rect.right(), rect.bottom() ) ); } } } #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->userMutex()->unlock(); #endif return textPage; } void TextDocumentGeneratorPrivate::addAction( Action *action, int cursorBegin, int cursorEnd ) { if ( !action ) return; LinkPosition position; position.link = action; position.startPosition = cursorBegin; position.endPosition = cursorEnd; mLinkPositions.append( position ); } void TextDocumentGeneratorPrivate::addAnnotation( Annotation *annotation, int cursorBegin, int cursorEnd ) { if ( !annotation ) return; annotation->setFlags( annotation->flags() | Okular::Annotation::External ); AnnotationPosition position; position.annotation = annotation; position.startPosition = cursorBegin; position.endPosition = cursorEnd; mAnnotationPositions.append( position ); } void TextDocumentGeneratorPrivate::addTitle( int level, const QString &title, const QTextBlock &block ) { TitlePosition position; position.level = level; position.title = title; position.block = block; mTitlePositions.append( position ); } void TextDocumentGeneratorPrivate::addMetaData( const QString &key, const QString &value, const QString &title ) { mDocumentInfo.set( key, value, title ); } void TextDocumentGeneratorPrivate::addMetaData( DocumentInfo::Key key, const QString &value ) { mDocumentInfo.set( key, value ); } QList TextDocumentGeneratorPrivate::generateLinkInfos() const { QList result; for ( int i = 0; i < mLinkPositions.count(); ++i ) { const LinkPosition &linkPosition = mLinkPositions[ i ]; const QVector rects = TextDocumentUtils::calculateBoundingRects( mDocument, linkPosition.startPosition, linkPosition.endPosition ); for ( int i = 0; i < rects.count(); ++i) { const QRectF &rect = rects[ i ]; LinkInfo info; info.link = linkPosition.link; info.ownsLink = i == 0; info.page = std::floor( rect.y() ); info.boundingRect = QRectF( rect.x(), rect.y() - info.page, rect.width(), rect.height() ); result.append( info ); } } return result; } QList TextDocumentGeneratorPrivate::generateAnnotationInfos() const { QList result; for ( int i = 0; i < mAnnotationPositions.count(); ++i ) { const AnnotationPosition &annotationPosition = mAnnotationPositions[ i ]; AnnotationInfo info; info.annotation = annotationPosition.annotation; TextDocumentUtils::calculateBoundingRect( mDocument, annotationPosition.startPosition, annotationPosition.endPosition, info.boundingRect, info.page ); if ( info.page >= 0 ) result.append( info ); } return result; } void TextDocumentGeneratorPrivate::generateTitleInfos() { QStack< QPair > parentNodeStack; QDomNode parentNode = mDocumentSynopsis; parentNodeStack.push( qMakePair( 0, parentNode ) ); for ( int i = 0; i < mTitlePositions.count(); ++i ) { const TitlePosition &position = mTitlePositions[ i ]; Okular::DocumentViewport viewport = TextDocumentUtils::calculateViewport( mDocument, position.block ); QDomElement item = mDocumentSynopsis.createElement( position.title ); item.setAttribute( QStringLiteral("Viewport"), viewport.toString() ); int headingLevel = position.level; // we need a parent, which has to be at a higher heading level than this heading level // so we just work through the stack while ( ! parentNodeStack.isEmpty() ) { int parentLevel = parentNodeStack.top().first; if ( parentLevel < headingLevel ) { // this is OK as a parent parentNode = parentNodeStack.top().second; break; } else { // we'll need to be further into the stack parentNodeStack.pop(); } } parentNode.appendChild( item ); parentNodeStack.push( qMakePair( headingLevel, QDomNode(item) ) ); } } void TextDocumentGeneratorPrivate::initializeGenerator() { Q_Q( TextDocumentGenerator ); mConverter->d_ptr->mParent = q->d_func(); if ( mGeneralSettings ) { mFont = mGeneralSettings->font(); } q->setFeature( Generator::TextExtraction ); q->setFeature( Generator::PrintNative ); q->setFeature( Generator::PrintToFile ); #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->setFeature( Generator::Threaded ); #endif QObject::connect( mConverter, SIGNAL(addAction(Action*,int,int)), q, SLOT(addAction(Action*,int,int)) ); QObject::connect( mConverter, SIGNAL(addAnnotation(Annotation*,int,int)), q, SLOT(addAnnotation(Annotation*,int,int)) ); QObject::connect( mConverter, SIGNAL(addTitle(int,QString,QTextBlock)), q, SLOT(addTitle(int,QString,QTextBlock)) ); QObject::connect( mConverter, SIGNAL(addMetaData(QString,QString,QString)), q, SLOT(addMetaData(QString,QString,QString)) ); QObject::connect( mConverter, SIGNAL(addMetaData(DocumentInfo::Key,QString)), q, SLOT(addMetaData(DocumentInfo::Key,QString)) ); QObject::connect( mConverter, &TextDocumentConverter::error, q, &Generator::error ); QObject::connect( mConverter, &TextDocumentConverter::warning, q, &Generator::warning ); QObject::connect( mConverter, &TextDocumentConverter::notice, q, &Generator::notice ); } TextDocumentGenerator::TextDocumentGenerator(TextDocumentConverter *converter, const QString& configName , QObject *parent, const QVariantList &args) : Okular::Generator( *new TextDocumentGeneratorPrivate( converter ), parent, args ) { Q_D( TextDocumentGenerator ); d->mGeneralSettings = new TextDocumentSettings( configName, this ); d->initializeGenerator(); } TextDocumentGenerator::~TextDocumentGenerator() { } Document::OpenResult TextDocumentGenerator::loadDocumentWithPassword( const QString & fileName, QVector & pagesVector, const QString &password ) { Q_D( TextDocumentGenerator ); const Document::OpenResult openResult = d->mConverter->convertWithPassword( fileName, password ); if ( openResult != Document::OpenSuccess ) { d->mDocument = nullptr; // loading failed, cleanup all the stuff eventually gathered from the converter d->mTitlePositions.clear(); Q_FOREACH ( const TextDocumentGeneratorPrivate::LinkPosition &linkPos, d->mLinkPositions ) { delete linkPos.link; } d->mLinkPositions.clear(); Q_FOREACH ( const TextDocumentGeneratorPrivate::AnnotationPosition &annPos, d->mAnnotationPositions ) { delete annPos.annotation; } d->mAnnotationPositions.clear(); return openResult; } d->mDocument = d->mConverter->document(); d->generateTitleInfos(); const QList linkInfos = d->generateLinkInfos(); const QList annotationInfos = d->generateAnnotationInfos(); pagesVector.resize( d->mDocument->pageCount() ); const QSize size = d->mDocument->pageSize().toSize(); QVector< QLinkedList > objects( d->mDocument->pageCount() ); for ( const TextDocumentGeneratorPrivate::LinkInfo &info : linkInfos ) { // in case that the converter report bogus link info data, do not assert here - if ( info.page >= objects.count() ) + if ( info.page < 0 || info.page >= objects.count() ) continue; const QRectF rect = info.boundingRect; if ( info.ownsLink ) { objects[ info.page ].append( new Okular::ObjectRect( rect.left(), rect.top(), rect.right(), rect.bottom(), false, Okular::ObjectRect::Action, info.link ) ); } else { objects[ info.page ].append( new Okular::NonOwningObjectRect( rect.left(), rect.top(), rect.right(), rect.bottom(), false, Okular::ObjectRect::Action, info.link ) ); } } QVector< QLinkedList > annots( d->mDocument->pageCount() ); for ( const TextDocumentGeneratorPrivate::AnnotationInfo &info : annotationInfos ) { annots[ info.page ].append( info.annotation ); } for ( int i = 0; i < d->mDocument->pageCount(); ++i ) { Okular::Page * page = new Okular::Page( i, size.width(), size.height(), Okular::Rotation0 ); pagesVector[ i ] = page; if ( !objects.at( i ).isEmpty() ) { page->setObjectRects( objects.at( i ) ); } QLinkedList::ConstIterator annIt = annots.at( i ).begin(), annEnd = annots.at( i ).end(); for ( ; annIt != annEnd; ++annIt ) { page->addAnnotation( *annIt ); } } return openResult; } bool TextDocumentGenerator::doCloseDocument() { Q_D( TextDocumentGenerator ); delete d->mDocument; d->mDocument = nullptr; d->mTitlePositions.clear(); d->mLinkPositions.clear(); d->mAnnotationPositions.clear(); // do not use clear() for the following two, otherwise they change type d->mDocumentInfo = Okular::DocumentInfo(); d->mDocumentSynopsis = Okular::DocumentSynopsis(); return true; } bool TextDocumentGenerator::canGeneratePixmap() const { return Generator::canGeneratePixmap(); } void TextDocumentGenerator::generatePixmap( Okular::PixmapRequest * request ) { Generator::generatePixmap( request ); } QImage TextDocumentGeneratorPrivate::image( PixmapRequest * request ) { if ( !mDocument ) return QImage(); #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING Q_Q( TextDocumentGenerator ); #endif QImage image( request->width(), request->height(), QImage::Format_ARGB32 ); image.fill( Qt::white ); QPainter p; p.begin( &image ); qreal width = request->width(); qreal height = request->height(); const QSize size = mDocument->pageSize().toSize(); p.scale( width / (qreal)size.width(), height / (qreal)size.height() ); QRect rect; rect = QRect( 0, request->pageNumber() * size.height(), size.width(), size.height() ); p.translate( QPoint( 0, request->pageNumber() * size.height() * -1 ) ); p.setClipRect( rect ); #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->userMutex()->lock(); #endif QAbstractTextDocumentLayout::PaintContext context; context.palette.setColor( QPalette::Text, Qt::black ); // FIXME Fix Qt, this doesn't work, we have horrible hacks // in the generators that return html, remove that code // if Qt ever gets fixed // context.palette.setColor( QPalette::Link, Qt::blue ); context.clip = rect; mDocument->setDefaultFont( mFont ); mDocument->documentLayout()->draw( &p, context ); #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->userMutex()->unlock(); #endif p.end(); return image; } Okular::TextPage* TextDocumentGenerator::textPage( Okular::TextRequest * request ) { Q_D( TextDocumentGenerator ); return d->createTextPage( request->page()->number() ); } bool TextDocumentGenerator::print( QPrinter& printer ) { Q_D( TextDocumentGenerator ); if ( !d->mDocument ) return false; d->mDocument->print( &printer ); return true; } Okular::DocumentInfo TextDocumentGenerator::generateDocumentInfo( const QSet & /*keys*/ ) const { Q_D( const TextDocumentGenerator ); return d->mDocumentInfo; } const Okular::DocumentSynopsis* TextDocumentGenerator::generateDocumentSynopsis() { Q_D( TextDocumentGenerator ); if ( !d->mDocumentSynopsis.hasChildNodes() ) return nullptr; else return &d->mDocumentSynopsis; } QVariant TextDocumentGeneratorPrivate::metaData( const QString &key, const QVariant &option ) const { Q_UNUSED( option ) if ( key == QLatin1String("DocumentTitle") ) { return mDocumentInfo.get( DocumentInfo::Title ); } return QVariant(); } Okular::ExportFormat::List TextDocumentGenerator::exportFormats( ) const { static Okular::ExportFormat::List formats; if ( formats.isEmpty() ) { formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ) ); formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PDF ) ); if ( QTextDocumentWriter::supportedDocumentFormats().contains( "ODF" ) ) { formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::OpenDocumentText ) ); } if ( QTextDocumentWriter::supportedDocumentFormats().contains( "HTML" ) ) { formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::HTML ) ); } } return formats; } bool TextDocumentGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format ) { Q_D( TextDocumentGenerator ); if ( !d->mDocument ) return false; if ( format.mimeType().name() == QLatin1String( "application/pdf" ) ) { QFile file( fileName ); if ( !file.open( QIODevice::WriteOnly ) ) return false; QPrinter printer( QPrinter::HighResolution ); printer.setOutputFormat( QPrinter::PdfFormat ); printer.setOutputFileName( fileName ); d->mDocument->print( &printer ); return true; } else if ( format.mimeType().name() == QLatin1String( "text/plain" ) ) { QFile file( fileName ); if ( !file.open( QIODevice::WriteOnly ) ) return false; QTextStream out( &file ); out << d->mDocument->toPlainText(); return true; } else if ( format.mimeType().name() == QLatin1String( "application/vnd.oasis.opendocument.text" ) ) { QTextDocumentWriter odfWriter( fileName, "odf" ); return odfWriter.write( d->mDocument ); } else if ( format.mimeType().name() == QLatin1String( "text/html" ) ) { QTextDocumentWriter odfWriter( fileName, "html" ); return odfWriter.write( d->mDocument ); } return false; } bool TextDocumentGenerator::reparseConfig() { Q_D( TextDocumentGenerator ); const QFont newFont = d->mGeneralSettings->font(); if ( newFont != d->mFont ) { d->mFont = newFont; return true; } return false; } void TextDocumentGenerator::addPages( KConfigDialog* /*dlg*/ ) { qCWarning(OkularCoreDebug) << "You forgot to reimplement addPages in your TextDocumentGenerator"; return; } TextDocumentSettings* TextDocumentGenerator::generalSettings() { Q_D( TextDocumentGenerator ); return d->mGeneralSettings; } TextDocumentConverter* TextDocumentGenerator::converter() { Q_D( TextDocumentGenerator ); return d->mConverter; } void TextDocumentGenerator::setTextDocument( QTextDocument *textDocument ) { Q_D( TextDocumentGenerator ); d->mDocument = textDocument; Q_FOREACH (Page *p, d->m_document->m_pagesVector ) { p->setTextPage( nullptr ); } } #include "moc_textdocumentgenerator.cpp"