diff --git a/core/generator.cpp b/core/generator.cpp index 7d70049cd..047915a97 100644 --- a/core/generator.cpp +++ b/core/generator.cpp @@ -1,700 +1,702 @@ /*************************************************************************** * Copyright (C) 2005 by Piotr Szymanski * * Copyright (C) 2008 by Albert Astals Cid * * * * 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 "generator.h" #include "generator_p.h" #include "observer.h" #include #include #include #include #include #include #include #include #include "document.h" #include "document_p.h" #include "page.h" #include "page_p.h" #include "textpage.h" #include "utils.h" using namespace Okular; GeneratorPrivate::GeneratorPrivate() : m_document( 0 ), mPixmapGenerationThread( 0 ), mTextPageGenerationThread( 0 ), m_mutex( 0 ), m_threadsMutex( 0 ), mPixmapReady( true ), mTextPageReady( true ), m_closing( false ), m_closingLoop( 0 ), m_dpi(72.0, 72.0) { } GeneratorPrivate::~GeneratorPrivate() { if ( mPixmapGenerationThread ) mPixmapGenerationThread->wait(); delete mPixmapGenerationThread; if ( mTextPageGenerationThread ) mTextPageGenerationThread->wait(); delete mTextPageGenerationThread; delete m_mutex; delete m_threadsMutex; } PixmapGenerationThread* GeneratorPrivate::pixmapGenerationThread() { if ( mPixmapGenerationThread ) return mPixmapGenerationThread; Q_Q( Generator ); mPixmapGenerationThread = new PixmapGenerationThread( q ); QObject::connect( mPixmapGenerationThread, SIGNAL(finished()), q, SLOT(pixmapGenerationFinished()), Qt::QueuedConnection ); return mPixmapGenerationThread; } TextPageGenerationThread* GeneratorPrivate::textPageGenerationThread() { if ( mTextPageGenerationThread ) return mTextPageGenerationThread; Q_Q( Generator ); mTextPageGenerationThread = new TextPageGenerationThread( q ); QObject::connect( mTextPageGenerationThread, SIGNAL(finished()), q, SLOT(textpageGenerationFinished()), Qt::QueuedConnection ); return mTextPageGenerationThread; } void GeneratorPrivate::pixmapGenerationFinished() { Q_Q( Generator ); PixmapRequest *request = mPixmapGenerationThread->request(); mPixmapGenerationThread->endGeneration(); QMutexLocker locker( threadsLock() ); mPixmapReady = true; if ( m_closing ) { delete request; if ( mTextPageReady ) { locker.unlock(); m_closingLoop->quit(); } return; } const QImage& img = mPixmapGenerationThread->image(); request->page()->setPixmap( request->observer(), new QPixmap( QPixmap::fromImage( img ) ), request->normalizedRect() ); const int pageNumber = request->page()->number(); if ( mPixmapGenerationThread->calcBoundingBox() ) q->updatePageBoundingBox( pageNumber, mPixmapGenerationThread->boundingBox() ); q->signalPixmapRequestDone( request ); } void GeneratorPrivate::textpageGenerationFinished() { Q_Q( Generator ); Page *page = mTextPageGenerationThread->page(); mTextPageGenerationThread->endGeneration(); QMutexLocker locker( threadsLock() ); mTextPageReady = true; if ( m_closing ) { delete mTextPageGenerationThread->textPage(); if ( mPixmapReady ) { locker.unlock(); m_closingLoop->quit(); } return; } if ( mTextPageGenerationThread->textPage() ) { TextPage *tp = mTextPageGenerationThread->textPage(); page->setTextPage( tp ); q->signalTextGenerationDone( page, tp ); } } QMutex* GeneratorPrivate::threadsLock() { if ( !m_threadsMutex ) m_threadsMutex = new QMutex(); return m_threadsMutex; } QVariant GeneratorPrivate::metaData( const QString &, const QVariant & ) const { return QVariant(); } QImage GeneratorPrivate::image( PixmapRequest * ) { return QImage(); } Generator::Generator(QObject* parent, const QVariantList&) : QObject(parent) , d_ptr( new GeneratorPrivate() ) { d_ptr->q_ptr = this; } Generator::Generator(GeneratorPrivate &dd, QObject *parent, const QVariantList &args) : QObject(parent), d_ptr(&dd) { d_ptr->q_ptr = this; Q_UNUSED(args) } Generator::~Generator() { delete d_ptr; } bool Generator::loadDocument( const QString & fileName, QVector< Page * > & pagesVector ) { Q_UNUSED(fileName); Q_UNUSED(pagesVector); return false; } bool Generator::loadDocumentFromData( const QByteArray &, QVector< Page * > & ) { return false; } Document::OpenResult Generator::loadDocumentWithPassword( const QString & fileName, QVector< Page * > & pagesVector, const QString & ) { return loadDocument( fileName, pagesVector ) ? Document::OpenSuccess : Document::OpenError; } Document::OpenResult Generator::loadDocumentFromDataWithPassword( const QByteArray & fileData, QVector< Page * > & pagesVector, const QString & ) { return loadDocumentFromData( fileData, pagesVector ) ? Document::OpenSuccess : Document::OpenError; } bool Generator::closeDocument() { Q_D( Generator ); d->m_closing = true; d->threadsLock()->lock(); if ( !( d->mPixmapReady && d->mTextPageReady ) ) { QEventLoop loop; d->m_closingLoop = &loop; d->threadsLock()->unlock(); loop.exec(); d->m_closingLoop = 0; } else { d->threadsLock()->unlock(); } bool ret = doCloseDocument(); d->m_closing = false; return ret; } bool Generator::canGeneratePixmap() const { Q_D( const Generator ); return d->mPixmapReady; } void Generator::generatePixmap( PixmapRequest *request ) { Q_D( Generator ); d->mPixmapReady = false; const bool calcBoundingBox = !request->isTile() && !request->page()->isBoundingBoxKnown(); if ( request->asynchronous() && hasFeature( Threaded ) ) { d->pixmapGenerationThread()->startGeneration( request, calcBoundingBox ); /** * We create the text page for every page that is visible to the * user, so he can use the text extraction tools without a delay. */ if ( hasFeature( TextExtraction ) && !request->page()->hasTextPage() && canGenerateTextPage() && !d->m_closing ) { d->mTextPageReady = false; d->textPageGenerationThread()->startGeneration( request->page() ); } return; } const QImage& img = image( request ); request->page()->setPixmap( request->observer(), new QPixmap( QPixmap::fromImage( img ) ), request->normalizedRect() ); const int pageNumber = request->page()->number(); d->mPixmapReady = true; signalPixmapRequestDone( request ); if ( calcBoundingBox ) updatePageBoundingBox( pageNumber, Utils::imageBoundingBox( &img ) ); } bool Generator::canGenerateTextPage() const { Q_D( const Generator ); return d->mTextPageReady; } void Generator::generateTextPage( Page *page ) { TextPage *tp = textPage( page ); page->setTextPage( tp ); signalTextGenerationDone( page, tp ); } QImage Generator::image( PixmapRequest *request ) { Q_D( Generator ); return d->image( request ); } TextPage* Generator::textPage( Page* ) { return 0; } DocumentInfo Generator::generateDocumentInfo(const QSet &keys) const { Q_UNUSED(keys); return DocumentInfo(); } const DocumentSynopsis * Generator::generateDocumentSynopsis() { return 0; } FontInfo::List Generator::fontsForPage( int ) { return FontInfo::List(); } const QList * Generator::embeddedFiles() const { return 0; } Generator::PageSizeMetric Generator::pagesSizeMetric() const { return None; } bool Generator::isAllowed( Permission ) const { return true; } void Generator::rotationChanged( Rotation, Rotation ) { } PageSize::List Generator::pageSizes() const { return PageSize::List(); } void Generator::pageSizeChanged( const PageSize &, const PageSize & ) { } bool Generator::print( QPrinter& ) { return false; } Generator::PrintError Generator::printError() const { return UnknownPrintError; } void Generator::opaqueAction( const BackendOpaqueAction * /*action*/ ) { } QVariant Generator::metaData( const QString &key, const QVariant &option ) const { Q_D( const Generator ); return d->metaData( key, option ); } ExportFormat::List Generator::exportFormats() const { return ExportFormat::List(); } bool Generator::exportTo( const QString&, const ExportFormat& ) { return false; } void Generator::walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const { *walletKey = fileName.section( QLatin1Char('/'), -1, -1); *walletName = KWallet::Wallet::NetworkWallet(); *walletFolder = QStringLiteral("KPdf"); } bool Generator::hasFeature( GeneratorFeature feature ) const { Q_D( const Generator ); return d->m_features.contains( feature ); } void Generator::signalPixmapRequestDone( PixmapRequest * request ) { Q_D( Generator ); if ( d->m_document ) d->m_document->requestDone( request ); else { delete request; } } void Generator::signalTextGenerationDone( Page *page, TextPage *textPage ) { Q_D( Generator ); if ( d->m_document ) d->m_document->textGenerationDone( page ); else delete textPage; } const Document * Generator::document() const { Q_D( const Generator ); if ( d->m_document ) { return d->m_document->m_parent; } return 0; } void Generator::setFeature( GeneratorFeature feature, bool on ) { Q_D( Generator ); if ( on ) d->m_features.insert( feature ); else d->m_features.remove( feature ); } QVariant Generator::documentMetaData( const QString &key, const QVariant &option ) const { Q_D( const Generator ); if ( !d->m_document ) return QVariant(); if (key == QLatin1String("PaperColor")) return documentMetaData(PaperColorMetaData, option); if (key == QLatin1String("GraphicsAntialias")) return documentMetaData(GraphicsAntialiasMetaData, option); if (key == QLatin1String("TextAntialias")) return documentMetaData(TextAntialiasMetaData, option); if (key == QLatin1String("TextHinting")) return documentMetaData(TextHintingMetaData, option); return QVariant(); } QVariant Generator::documentMetaData( const DocumentMetaDataKey key, const QVariant &option ) const { Q_D( const Generator ); if ( !d->m_document ) return QVariant(); return d->m_document->documentMetaData( key, option ); } QMutex* Generator::userMutex() const { Q_D( const Generator ); if ( !d->m_mutex ) { d->m_mutex = new QMutex(); } return d->m_mutex; } void Generator::updatePageBoundingBox( int page, const NormalizedRect & boundingBox ) { Q_D( Generator ); if ( d->m_document ) // still connected to document? d->m_document->setPageBoundingBox( page, boundingBox ); } void Generator::requestFontData(const Okular::FontInfo & /*font*/, QByteArray * /*data*/) { } void Generator::setDPI(const QSizeF & dpi) { Q_D( Generator ); d->m_dpi = dpi; } QSizeF Generator::dpi() const { Q_D( const Generator ); return d->m_dpi; } QAbstractItemModel * Generator::layersModel() const { return 0; } PixmapRequest::PixmapRequest( DocumentObserver *observer, int pageNumber, int width, int height, int priority, PixmapRequestFeatures features ) : d( new PixmapRequestPrivate ) { d->mObserver = observer; d->mPageNumber = pageNumber; d->mWidth = width * qApp->devicePixelRatio(); d->mHeight = height * qApp->devicePixelRatio(); d->mPriority = priority; d->mFeatures = features; d->mForce = false; d->mTile = false; d->mNormalizedRect = NormalizedRect(); + + qDebug() << "PixmapRequest" << d->mWidth << "x" << d->mHeight; } PixmapRequest::~PixmapRequest() { delete d; } DocumentObserver *PixmapRequest::observer() const { return d->mObserver; } int PixmapRequest::pageNumber() const { return d->mPageNumber; } int PixmapRequest::width() const { return d->mWidth; } int PixmapRequest::height() const { return d->mHeight; } int PixmapRequest::priority() const { return d->mPriority; } bool PixmapRequest::asynchronous() const { return d->mFeatures & Asynchronous; } bool PixmapRequest::preload() const { return d->mFeatures & Preload; } Page* PixmapRequest::page() const { return d->mPage; } void PixmapRequest::setTile( bool tile ) { d->mTile = tile; } bool PixmapRequest::isTile() const { return d->mTile; } void PixmapRequest::setNormalizedRect( const NormalizedRect &rect ) { if ( d->mNormalizedRect == rect ) return; d->mNormalizedRect = rect; } const NormalizedRect& PixmapRequest::normalizedRect() const { return d->mNormalizedRect; } Okular::TilesManager* PixmapRequestPrivate::tilesManager() const { return mPage->d->tilesManager(mObserver); } void PixmapRequestPrivate::swap() { qSwap( mWidth, mHeight ); } class Okular::ExportFormatPrivate : public QSharedData { public: ExportFormatPrivate( const QString &description, const QMimeType &mimeType, const QIcon &icon = QIcon() ) : QSharedData(), mDescription( description ), mMimeType( mimeType ), mIcon( icon ) { } ~ExportFormatPrivate() { } QString mDescription; QMimeType mMimeType; QIcon mIcon; }; ExportFormat::ExportFormat() : d( new ExportFormatPrivate( QString(), QMimeType() ) ) { } ExportFormat::ExportFormat( const QString &description, const QMimeType &mimeType ) : d( new ExportFormatPrivate( description, mimeType ) ) { } ExportFormat::ExportFormat( const QIcon &icon, const QString &description, const QMimeType &mimeType ) : d( new ExportFormatPrivate( description, mimeType, icon ) ) { } ExportFormat::~ExportFormat() { } ExportFormat::ExportFormat( const ExportFormat &other ) : d( other.d ) { } ExportFormat& ExportFormat::operator=( const ExportFormat &other ) { if ( this == &other ) return *this; d = other.d; return *this; } QString ExportFormat::description() const { return d->mDescription; } QMimeType ExportFormat::mimeType() const { return d->mMimeType; } QIcon ExportFormat::icon() const { return d->mIcon; } bool ExportFormat::isNull() const { return !d->mMimeType.isValid() || d->mDescription.isNull(); } ExportFormat ExportFormat::standardFormat( StandardExportFormat type ) { QMimeDatabase db; switch ( type ) { case PlainText: return ExportFormat( QIcon::fromTheme( QStringLiteral("text-x-generic") ), i18n( "Plain &Text..." ), db.mimeTypeForName( QStringLiteral("text/plain") ) ); break; case PDF: return ExportFormat( QIcon::fromTheme( QStringLiteral("application-pdf") ), i18n( "PDF" ), db.mimeTypeForName( QStringLiteral("application/pdf") ) ); break; case OpenDocumentText: return ExportFormat( QIcon::fromTheme( QStringLiteral("application-vnd.oasis.opendocument.text") ), i18nc( "This is the document format", "OpenDocument Text" ), db.mimeTypeForName( QStringLiteral("application/vnd.oasis.opendocument.text") ) ); break; case HTML: return ExportFormat( QIcon::fromTheme( QStringLiteral("text-html") ), i18nc( "This is the document format", "HTML" ), db.mimeTypeForName( QStringLiteral("text/html") ) ); break; } return ExportFormat(); } bool ExportFormat::operator==( const ExportFormat &other ) const { return d == other.d; } bool ExportFormat::operator!=( const ExportFormat &other ) const { return d != other.d; } QDebug operator<<( QDebug str, const Okular::PixmapRequest &req ) { QString s = QStringLiteral( "PixmapRequest(#%2, %1, %3x%4, page %6, prio %5)" ) .arg( QString( req.asynchronous() ? QStringLiteral ( "async" ) : QStringLiteral ( "sync" ) ) ) .arg( (qulonglong)req.observer() ) .arg( req.width() ) .arg( req.height() ) .arg( req.priority() ) .arg( req.pageNumber() ); str << qPrintable( s ); return str; } #include "moc_generator.cpp" /* kate: replace-tabs on; indent-width 4; */ diff --git a/core/page.cpp b/core/page.cpp index 3478769ea..4bb9d0ee9 100644 --- a/core/page.cpp +++ b/core/page.cpp @@ -1,1022 +1,1023 @@ /*************************************************************************** * Copyright (C) 2004 by Enrico Ros * * 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 "page.h" #include "page_p.h" // qt/kde includes #include #include #include #include #include #include #include #include #include // local includes #include "action.h" #include "annotations.h" #include "annotations_p.h" #include "area.h" #include "debug_p.h" #include "document.h" #include "document_p.h" #include "form.h" #include "form_p.h" #include "observer.h" #include "pagecontroller_p.h" #include "pagesize.h" #include "pagetransition.h" #include "rotationjob_p.h" #include "textpage.h" #include "textpage_p.h" #include "tile.h" #include "tilesmanager_p.h" #include "utils_p.h" #include #ifdef PAGE_PROFILE #include #endif using namespace Okular; static const double distanceConsideredEqual = 25; // 5px static void deleteObjectRects( QLinkedList< ObjectRect * >& rects, const QSet& which ) { QLinkedList< ObjectRect * >::iterator it = rects.begin(), end = rects.end(); for ( ; it != end; ) if ( which.contains( (*it)->objectType() ) ) { delete *it; it = rects.erase( it ); } else ++it; } PagePrivate::PagePrivate( Page *page, uint n, double w, double h, Rotation o ) : m_page( page ), m_number( n ), m_orientation( o ), m_width( w ), m_height( h ), m_doc( 0 ), m_boundingBox( 0, 0, 1, 1 ), m_rotation( Rotation0 ), m_text( 0 ), m_transition( 0 ), m_textSelections( 0 ), m_openingAction( 0 ), m_closingAction( 0 ), m_duration( -1 ), m_isBoundingBoxKnown( false ) { // avoid Division-By-Zero problems in the program if ( m_width <= 0 ) m_width = 1; if ( m_height <= 0 ) m_height = 1; } PagePrivate::~PagePrivate() { qDeleteAll( formfields ); delete m_openingAction; delete m_closingAction; delete m_text; delete m_transition; } PagePrivate *PagePrivate::get( Page * page ) { return page->d; } void PagePrivate::imageRotationDone( RotationJob * job ) { TilesManager *tm = tilesManager( job->observer() ); if ( tm ) { QPixmap *pixmap = new QPixmap( QPixmap::fromImage( job->image() ) ); tm->setPixmap( pixmap, job->rect() ); delete pixmap; return; } QMap< DocumentObserver*, PixmapObject >::iterator it = m_pixmaps.find( job->observer() ); if ( it != m_pixmaps.end() ) { PixmapObject &object = it.value(); (*object.m_pixmap) = QPixmap::fromImage( job->image() ); object.m_rotation = job->rotation(); } else { PixmapObject object; object.m_pixmap = new QPixmap( QPixmap::fromImage( job->image() ) ); object.m_rotation = job->rotation(); m_pixmaps.insert( job->observer(), object ); } } QTransform PagePrivate::rotationMatrix() const { return Okular::buildRotationMatrix( m_rotation ); } /** class Page **/ Page::Page( uint page, double w, double h, Rotation o ) : d( new PagePrivate( this, page, w, h, o ) ) { } Page::~Page() { deletePixmaps(); deleteRects(); d->deleteHighlights(); deleteAnnotations(); d->deleteTextSelections(); deleteSourceReferences(); delete d; } int Page::number() const { return d->m_number; } Rotation Page::orientation() const { return d->m_orientation; } Rotation Page::rotation() const { return d->m_rotation; } Rotation Page::totalOrientation() const { return (Rotation)( ( (int)d->m_orientation + (int)d->m_rotation ) % 4 ); } double Page::width() const { return d->m_width; } double Page::height() const { return d->m_height; } double Page::ratio() const { return d->m_height / d->m_width; } NormalizedRect Page::boundingBox() const { return d->m_boundingBox; } bool Page::isBoundingBoxKnown() const { return d->m_isBoundingBoxKnown; } void Page::setBoundingBox( const NormalizedRect& bbox ) { if ( d->m_isBoundingBoxKnown && d->m_boundingBox == bbox ) return; // Allow tiny rounding errors (happens during rotation) static const double epsilon = 0.00001; Q_ASSERT( bbox.left >= -epsilon && bbox.top >= -epsilon && bbox.right <= 1 + epsilon && bbox.bottom <= 1 + epsilon ); d->m_boundingBox = bbox & NormalizedRect( 0., 0., 1., 1. ); d->m_isBoundingBoxKnown = true; } bool Page::hasPixmap( DocumentObserver *observer, int width, int height, const NormalizedRect &rect ) const { TilesManager *tm = d->tilesManager( observer ); if ( tm ) { if ( width != tm->width() || height != tm->height() ) { tm->setSize( width, height ); return false; } return tm->hasPixmap( rect ); } QMap< DocumentObserver*, PagePrivate::PixmapObject >::const_iterator it = d->m_pixmaps.constFind( observer ); if ( it == d->m_pixmaps.constEnd() ) return false; if ( width == -1 || height == -1 ) return true; const QPixmap *pixmap = it.value().m_pixmap; return (pixmap->width() == width && pixmap->height() == height); } bool Page::hasTextPage() const { return d->m_text != 0; } RegularAreaRect * Page::wordAt( const NormalizedPoint &p, QString *word ) const { if ( d->m_text ) return d->m_text->wordAt( p, word ); return 0; } RegularAreaRect * Page::textArea ( TextSelection * selection ) const { if ( d->m_text ) return d->m_text->textArea( selection ); return 0; } bool Page::hasObjectRect( double x, double y, double xScale, double yScale ) const { if ( m_rects.isEmpty() ) return false; QLinkedList< ObjectRect * >::const_iterator it = m_rects.begin(), end = m_rects.end(); for ( ; it != end; ++it ) if ( (*it)->distanceSqr( x, y, xScale, yScale ) < distanceConsideredEqual ) return true; return false; } bool Page::hasHighlights( int s_id ) const { // simple case: have no highlights if ( m_highlights.isEmpty() ) return false; // simple case: we have highlights and no id to match if ( s_id == -1 ) return true; // iterate on the highlights list to find an entry by id QLinkedList< HighlightAreaRect * >::const_iterator it = m_highlights.begin(), end = m_highlights.end(); for ( ; it != end; ++it ) if ( (*it)->s_id == s_id ) return true; return false; } bool Page::hasTransition() const { return d->m_transition != 0; } bool Page::hasAnnotations() const { return !m_annotations.isEmpty(); } RegularAreaRect * Page::findText( int id, const QString & text, SearchDirection direction, Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *lastRect ) const { RegularAreaRect* rect = 0; if ( text.isEmpty() || !d->m_text ) return rect; rect = d->m_text->findText( id, text, direction, caseSensitivity, lastRect ); return rect; } QString Page::text( const RegularAreaRect * area ) const { return text( area, TextPage::AnyPixelTextAreaInclusionBehaviour ); } QString Page::text( const RegularAreaRect * area, TextPage::TextAreaInclusionBehaviour b ) const { QString ret; if ( !d->m_text ) return ret; if ( area ) { RegularAreaRect rotatedArea = *area; rotatedArea.transform( d->rotationMatrix().inverted() ); ret = d->m_text->text( &rotatedArea, b ); } else ret = d->m_text->text( 0, b ); return ret; } TextEntity::List Page::words( const RegularAreaRect * area, TextPage::TextAreaInclusionBehaviour b ) const { TextEntity::List ret; if ( !d->m_text ) return ret; if ( area ) { RegularAreaRect rotatedArea = *area; rotatedArea.transform( d->rotationMatrix().inverted() ); ret = d->m_text->words( &rotatedArea, b ); } else ret = d->m_text->words( 0, b ); for (int i = 0; i < ret.length(); ++i) { const TextEntity * orig = ret[i]; ret[i] = new TextEntity( orig->text(), new Okular::NormalizedRect(orig->transformedArea ( d->rotationMatrix() )) ); delete orig; } return ret; } void PagePrivate::rotateAt( Rotation orientation ) { if ( orientation == m_rotation ) return; deleteHighlights(); deleteTextSelections(); if ( ( (int)m_orientation + (int)m_rotation ) % 2 != ( (int)m_orientation + (int)orientation ) % 2 ) qSwap( m_width, m_height ); Rotation oldRotation = m_rotation; m_rotation = orientation; /** * Rotate the images of the page. */ QMapIterator< DocumentObserver*, PagePrivate::PixmapObject > it( m_pixmaps ); while ( it.hasNext() ) { it.next(); const PagePrivate::PixmapObject &object = it.value(); RotationJob *job = new RotationJob( object.m_pixmap->toImage(), object.m_rotation, m_rotation, it.key() ); job->setPage( this ); m_doc->m_pageController->addRotationJob(job); } /** * Rotate tiles manager */ QMapIterator i(m_tilesManagers); while (i.hasNext()) { i.next(); TilesManager *tm = i.value(); if ( tm ) tm->setRotation( m_rotation ); } /** * Rotate the object rects on the page. */ const QTransform matrix = rotationMatrix(); QLinkedList< ObjectRect * >::const_iterator objectIt = m_page->m_rects.begin(), end = m_page->m_rects.end(); for ( ; objectIt != end; ++objectIt ) (*objectIt)->transform( matrix ); QLinkedList< HighlightAreaRect* >::const_iterator hlIt = m_page->m_highlights.begin(), hlItEnd = m_page->m_highlights.end(); for ( ; hlIt != hlItEnd; ++hlIt ) { (*hlIt)->transform( RotationJob::rotationMatrix( oldRotation, m_rotation ) ); } } void PagePrivate::changeSize( const PageSize &size ) { if ( size.isNull() || ( size.width() == m_width && size.height() == m_height ) ) return; m_page->deletePixmaps(); // deleteHighlights(); // deleteTextSelections(); m_width = size.width(); m_height = size.height(); if ( m_rotation % 2 ) qSwap( m_width, m_height ); } const ObjectRect * Page::objectRect( ObjectRect::ObjectType type, double x, double y, double xScale, double yScale ) const { // Walk list in reverse order so that annotations in the foreground are preferred QLinkedListIterator< ObjectRect * > it( m_rects ); it.toBack(); while ( it.hasPrevious() ) { const ObjectRect *objrect = it.previous(); if ( ( objrect->objectType() == type ) && objrect->distanceSqr( x, y, xScale, yScale ) < distanceConsideredEqual ) return objrect; } return 0; } QLinkedList< const ObjectRect * > Page::objectRects( ObjectRect::ObjectType type, double x, double y, double xScale, double yScale ) const { QLinkedList< const ObjectRect * > result; QLinkedListIterator< ObjectRect * > it( m_rects ); it.toBack(); while ( it.hasPrevious() ) { const ObjectRect *objrect = it.previous(); if ( ( objrect->objectType() == type ) && objrect->distanceSqr( x, y, xScale, yScale ) < distanceConsideredEqual ) result.append( objrect ); } return result; } const ObjectRect* Page::nearestObjectRect( ObjectRect::ObjectType type, double x, double y, double xScale, double yScale, double * distance ) const { ObjectRect * res = 0; double minDistance = std::numeric_limits::max(); QLinkedList< ObjectRect * >::const_iterator it = m_rects.constBegin(), end = m_rects.constEnd(); for ( ; it != end; ++it ) { if ( (*it)->objectType() == type ) { double d = (*it)->distanceSqr( x, y, xScale, yScale ); if ( d < minDistance ) { res = (*it); minDistance = d; } } } if ( distance ) *distance = minDistance; return res; } const PageTransition * Page::transition() const { return d->m_transition; } QLinkedList< Annotation* > Page::annotations() const { return m_annotations; } const Action * Page::pageAction( PageAction action ) const { switch ( action ) { case Page::Opening: return d->m_openingAction; break; case Page::Closing: return d->m_closingAction; break; } return 0; } QLinkedList< FormField * > Page::formFields() const { return d->formfields; } void Page::setPixmap( DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect ) { + pixmap->save("/tmp/paint/p_" + QString::number(number()) + "_" + "_setPixmap.png"); if ( d->m_rotation == Rotation0 ) { TilesManager *tm = d->tilesManager( observer ); if ( tm ) { tm->setPixmap( pixmap, rect ); delete pixmap; return; } QMap< DocumentObserver*, PagePrivate::PixmapObject >::iterator it = d->m_pixmaps.find( observer ); if ( it != d->m_pixmaps.end() ) { delete it.value().m_pixmap; } else { it = d->m_pixmaps.insert( observer, PagePrivate::PixmapObject() ); } it.value().m_pixmap = pixmap; it.value().m_rotation = d->m_rotation; } else { RotationJob *job = new RotationJob( pixmap->toImage(), Rotation0, d->m_rotation, observer ); job->setPage( d ); job->setRect( TilesManager::toRotatedRect( rect, d->m_rotation ) ); d->m_doc->m_pageController->addRotationJob(job); delete pixmap; } } void Page::setTextPage( TextPage * textPage ) { delete d->m_text; d->m_text = textPage; if ( d->m_text ) { d->m_text->d->m_page = d; /** * Correct text order for before text selection */ d->m_text->d->correctTextOrder(); } } void Page::setObjectRects( const QLinkedList< ObjectRect * > & rects ) { QSet which; which << ObjectRect::Action << ObjectRect::Image; deleteObjectRects( m_rects, which ); /** * Rotate the object rects of the page. */ const QTransform matrix = d->rotationMatrix(); QLinkedList< ObjectRect * >::const_iterator objectIt = rects.begin(), end = rects.end(); for ( ; objectIt != end; ++objectIt ) (*objectIt)->transform( matrix ); m_rects << rects; } void PagePrivate::setHighlight( int s_id, RegularAreaRect *rect, const QColor & color ) { HighlightAreaRect * hr = new HighlightAreaRect(rect); hr->s_id = s_id; hr->color = color; m_page->m_highlights.append( hr ); } void PagePrivate::setTextSelections( RegularAreaRect *r, const QColor & color ) { deleteTextSelections(); if ( r ) { HighlightAreaRect * hr = new HighlightAreaRect( r ); hr->s_id = -1; hr->color = color; m_textSelections = hr; delete r; } } void Page::setSourceReferences( const QLinkedList< SourceRefObjectRect * > & refRects ) { deleteSourceReferences(); foreach( SourceRefObjectRect * rect, refRects ) m_rects << rect; } void Page::setDuration( double seconds ) { d->m_duration = seconds; } double Page::duration() const { return d->m_duration; } void Page::setLabel( const QString& label ) { d->m_label = label; } QString Page::label() const { return d->m_label; } const RegularAreaRect * Page::textSelection() const { return d->m_textSelections; } QColor Page::textSelectionColor() const { return d->m_textSelections ? d->m_textSelections->color : QColor(); } void Page::addAnnotation( Annotation * annotation ) { // Generate uniqueName: okular-{UUID} if(annotation->uniqueName().isEmpty()) { QString uniqueName = QStringLiteral("okular-") + QUuid::createUuid().toString(); annotation->setUniqueName( uniqueName ); } annotation->d_ptr->m_page = d; m_annotations.append( annotation ); AnnotationObjectRect *rect = new AnnotationObjectRect( annotation ); // Rotate the annotation on the page. const QTransform matrix = d->rotationMatrix(); annotation->d_ptr->annotationTransform( matrix ); m_rects.append( rect ); } bool Page::removeAnnotation( Annotation * annotation ) { if ( !d->m_doc->m_parent->canRemovePageAnnotation(annotation) ) return false; QLinkedList< Annotation * >::iterator aIt = m_annotations.begin(), aEnd = m_annotations.end(); for ( ; aIt != aEnd; ++aIt ) { if((*aIt) && (*aIt)->uniqueName()==annotation->uniqueName()) { int rectfound = false; QLinkedList< ObjectRect * >::iterator it = m_rects.begin(), end = m_rects.end(); for ( ; it != end && !rectfound; ++it ) if ( ( (*it)->objectType() == ObjectRect::OAnnotation ) && ( (*it)->object() == (*aIt) ) ) { delete *it; it = m_rects.erase( it ); rectfound = true; } qCDebug(OkularCoreDebug) << "removed annotation:" << annotation->uniqueName(); annotation->d_ptr->m_page = 0; m_annotations.erase( aIt ); break; } } return true; } void Page::setTransition( PageTransition * transition ) { delete d->m_transition; d->m_transition = transition; } void Page::setPageAction( PageAction action, Action * link ) { switch ( action ) { case Page::Opening: delete d->m_openingAction; d->m_openingAction = link; break; case Page::Closing: delete d->m_closingAction; d->m_closingAction = link; break; } } void Page::setFormFields( const QLinkedList< FormField * >& fields ) { qDeleteAll( d->formfields ); d->formfields = fields; QLinkedList< FormField * >::const_iterator it = d->formfields.begin(), itEnd = d->formfields.end(); for ( ; it != itEnd; ++it ) { (*it)->d_ptr->setDefault(); } } void Page::deletePixmap( DocumentObserver *observer ) { TilesManager *tm = d->tilesManager( observer ); if ( tm ) { delete tm; d->m_tilesManagers.remove(observer); } else { PagePrivate::PixmapObject object = d->m_pixmaps.take( observer ); delete object.m_pixmap; } } void Page::deletePixmaps() { QMapIterator< DocumentObserver*, PagePrivate::PixmapObject > it( d->m_pixmaps ); while ( it.hasNext() ) { it.next(); delete it.value().m_pixmap; } d->m_pixmaps.clear(); qDeleteAll(d->m_tilesManagers); d->m_tilesManagers.clear(); } void Page::deleteRects() { // delete ObjectRects of type Link and Image QSet which; which << ObjectRect::Action << ObjectRect::Image; deleteObjectRects( m_rects, which ); } void PagePrivate::deleteHighlights( int s_id ) { // delete highlights by ID QLinkedList< HighlightAreaRect* >::iterator it = m_page->m_highlights.begin(), end = m_page->m_highlights.end(); while ( it != end ) { HighlightAreaRect* highlight = *it; if ( s_id == -1 || highlight->s_id == s_id ) { it = m_page->m_highlights.erase( it ); delete highlight; } else ++it; } } void PagePrivate::deleteTextSelections() { delete m_textSelections; m_textSelections = 0; } void Page::deleteSourceReferences() { deleteObjectRects( m_rects, QSet() << ObjectRect::SourceRef ); } void Page::deleteAnnotations() { // delete ObjectRects of type Annotation deleteObjectRects( m_rects, QSet() << ObjectRect::OAnnotation ); // delete all stored annotations QLinkedList< Annotation * >::const_iterator aIt = m_annotations.begin(), aEnd = m_annotations.end(); for ( ; aIt != aEnd; ++aIt ) delete *aIt; m_annotations.clear(); } void PagePrivate::restoreLocalContents( const QDomNode & pageNode ) { // iterate over all chilren (annotationList, ...) QDomNode childNode = pageNode.firstChild(); while ( childNode.isElement() ) { QDomElement childElement = childNode.toElement(); childNode = childNode.nextSibling(); // parse annotationList child element if ( childElement.tagName() == QLatin1String("annotationList") ) { #ifdef PAGE_PROFILE QTime time; time.start(); #endif // Clone annotationList as root node in restoredLocalAnnotationList const QDomNode clonedNode = restoredLocalAnnotationList.importNode( childElement, true ); restoredLocalAnnotationList.appendChild( clonedNode ); // iterate over all annotations QDomNode annotationNode = childElement.firstChild(); while( annotationNode.isElement() ) { // get annotation element and advance to next annot QDomElement annotElement = annotationNode.toElement(); annotationNode = annotationNode.nextSibling(); // get annotation from the dom element Annotation * annotation = AnnotationUtils::createAnnotation( annotElement ); // append annotation to the list or show warning if ( annotation ) { m_doc->performAddPageAnnotation(m_number, annotation); qCDebug(OkularCoreDebug) << "restored annot:" << annotation->uniqueName(); } else qCWarning(OkularCoreDebug).nospace() << "page (" << m_number << "): can't restore an annotation from XML."; } #ifdef PAGE_PROFILE qCDebug(OkularCoreDebug).nospace() << "annots: XML Load time: " << time.elapsed() << "ms"; #endif } // parse formList child element else if ( childElement.tagName() == QLatin1String("forms") ) { if ( formfields.isEmpty() ) continue; QHash hashedforms; QLinkedList< FormField * >::const_iterator fIt = formfields.begin(), fItEnd = formfields.end(); for ( ; fIt != fItEnd; ++fIt ) { hashedforms[(*fIt)->id()] = (*fIt); } // iterate over all forms QDomNode formsNode = childElement.firstChild(); while( formsNode.isElement() ) { // get annotation element and advance to next annot QDomElement formElement = formsNode.toElement(); formsNode = formsNode.nextSibling(); if ( formElement.tagName() != QLatin1String("form") ) continue; bool ok = true; int index = formElement.attribute( QStringLiteral("id") ).toInt( &ok ); if ( !ok ) continue; QHash::const_iterator wantedIt = hashedforms.constFind( index ); if ( wantedIt == hashedforms.constEnd() ) continue; QString value = formElement.attribute( QStringLiteral("value") ); (*wantedIt)->d_ptr->setValue( value ); } } } } void PagePrivate::saveLocalContents( QDomNode & parentNode, QDomDocument & document, PageItems what ) const { // create the page node and set the 'number' attribute QDomElement pageElement = document.createElement( QStringLiteral("page") ); pageElement.setAttribute( QStringLiteral("number"), m_number ); #if 0 // add bookmark info if is bookmarked if ( d->m_bookmarked ) { // create the pageElement's 'bookmark' child QDomElement bookmarkElement = document.createElement( "bookmark" ); pageElement.appendChild( bookmarkElement ); // add attributes to the element //bookmarkElement.setAttribute( "name", bookmark name ); } #endif // add annotations info if has got any if ( ( what & AnnotationPageItems ) && ( what & OriginalAnnotationPageItems ) ) { const QDomElement savedDocRoot = restoredLocalAnnotationList.documentElement(); if ( !savedDocRoot.isNull() ) { // Import and append node in target document const QDomNode importedNode = document.importNode( savedDocRoot, true ); pageElement.appendChild( importedNode ); } } else if ( ( what & AnnotationPageItems ) && !m_page->m_annotations.isEmpty() ) { // create the annotationList QDomElement annotListElement = document.createElement( QStringLiteral("annotationList") ); // add every annotation to the annotationList QLinkedList< Annotation * >::const_iterator aIt = m_page->m_annotations.constBegin(), aEnd = m_page->m_annotations.constEnd(); for ( ; aIt != aEnd; ++aIt ) { // get annotation const Annotation * a = *aIt; // only save okular annotations (not the embedded in file ones) if ( !(a->flags() & Annotation::External) ) { // append an filled-up element called 'annotation' to the list QDomElement annElement = document.createElement( QStringLiteral("annotation") ); AnnotationUtils::storeAnnotation( a, annElement, document ); annotListElement.appendChild( annElement ); qCDebug(OkularCoreDebug) << "save annotation:" << a->uniqueName(); } } // append the annotationList element if annotations have been set if ( annotListElement.hasChildNodes() ) pageElement.appendChild( annotListElement ); } // add forms info if has got any if ( ( what & FormFieldPageItems ) && !formfields.isEmpty() ) { // create the formList QDomElement formListElement = document.createElement( QStringLiteral("forms") ); // add every form data to the formList QLinkedList< FormField * >::const_iterator fIt = formfields.constBegin(), fItEnd = formfields.constEnd(); for ( ; fIt != fItEnd; ++fIt ) { // get the form field const FormField * f = *fIt; QString newvalue = f->d_ptr->value(); if ( f->d_ptr->m_default == newvalue ) continue; // append an filled-up element called 'annotation' to the list QDomElement formElement = document.createElement( QStringLiteral("form") ); formElement.setAttribute( QStringLiteral("id"), f->id() ); formElement.setAttribute( QStringLiteral("value"), newvalue ); formListElement.appendChild( formElement ); } // append the annotationList element if annotations have been set if ( formListElement.hasChildNodes() ) pageElement.appendChild( formListElement ); } // append the page element only if has children if ( pageElement.hasChildNodes() ) parentNode.appendChild( pageElement ); } const QPixmap * Page::_o_nearestPixmap( DocumentObserver *observer, int w, int h ) const { Q_UNUSED( h ) const QPixmap * pixmap = 0; // if a pixmap is present for given id, use it QMap< DocumentObserver*, PagePrivate::PixmapObject >::const_iterator itPixmap = d->m_pixmaps.constFind( observer ); if ( itPixmap != d->m_pixmaps.constEnd() ) pixmap = itPixmap.value().m_pixmap; // else find the closest match using pixmaps of other IDs (great optim!) else if ( !d->m_pixmaps.isEmpty() ) { int minDistance = -1; QMap< DocumentObserver*, PagePrivate::PixmapObject >::const_iterator it = d->m_pixmaps.constBegin(), end = d->m_pixmaps.constEnd(); for ( ; it != end; ++it ) { int pixWidth = (*it).m_pixmap->width(), distance = pixWidth > w ? pixWidth - w : w - pixWidth; if ( minDistance == -1 || distance < minDistance ) { pixmap = (*it).m_pixmap; minDistance = distance; } } } return pixmap; } bool Page::hasTilesManager( const DocumentObserver *observer ) const { return d->tilesManager( observer ) != 0; } QList Page::tilesAt( const DocumentObserver *observer, const NormalizedRect &rect ) const { TilesManager *tm = d->m_tilesManagers.value( observer ); if ( tm ) return tm->tilesAt( rect, TilesManager::PixmapTile ); else return QList(); } TilesManager *PagePrivate::tilesManager( const DocumentObserver *observer ) const { return m_tilesManagers.value( observer ); } void PagePrivate::setTilesManager( const DocumentObserver *observer, TilesManager *tm ) { TilesManager *old = m_tilesManagers.value( observer ); delete old; m_tilesManagers.insert(observer, tm); } diff --git a/generators/tiff/generator_tiff.cpp b/generators/tiff/generator_tiff.cpp index 91bb42721..a4c2810ac 100644 --- a/generators/tiff/generator_tiff.cpp +++ b/generators/tiff/generator_tiff.cpp @@ -1,439 +1,450 @@ /*************************************************************************** * Copyright (C) 2006 by Pino Toscano * * * * 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 "generator_tiff.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TiffDebug 4714 tsize_t okular_tiffReadProc( thandle_t handle, tdata_t buf, tsize_t size ) { QIODevice * device = static_cast< QIODevice * >( handle ); return device->isReadable() ? device->read( static_cast< char * >( buf ), size ) : -1; } tsize_t okular_tiffWriteProc( thandle_t handle, tdata_t buf, tsize_t size ) { QIODevice * device = static_cast< QIODevice * >( handle ); return device->write( static_cast< char * >( buf ), size ); } toff_t okular_tiffSeekProc( thandle_t handle, toff_t offset, int whence ) { QIODevice * device = static_cast< QIODevice * >( handle ); switch ( whence ) { case SEEK_SET: device->seek( offset ); break; case SEEK_CUR: device->seek( device->pos() + offset ); break; case SEEK_END: device->seek( device->size() + offset ); break; } return device->pos(); } int okular_tiffCloseProc( thandle_t handle ) { Q_UNUSED( handle ) return 0; } toff_t okular_tiffSizeProc( thandle_t handle ) { QIODevice * device = static_cast< QIODevice * >( handle ); return device->size(); } int okular_tiffMapProc( thandle_t, tdata_t *, toff_t * ) { return 0; } void okular_tiffUnmapProc( thandle_t, tdata_t, toff_t ) { } class TIFFGenerator::Private { public: Private() : tiff( 0 ), dev( 0 ) {} TIFF* tiff; QByteArray data; QIODevice* dev; }; static QDateTime convertTIFFDateTime( const char* tiffdate ) { if ( !tiffdate ) return QDateTime(); return QDateTime::fromString( QString::fromLatin1( tiffdate ), QStringLiteral("yyyy:MM:dd HH:mm:ss") ); } static void adaptSizeToResolution( TIFF *tiff, ttag_t whichres, double dpi, uint32 *size ) { float resvalue = 1.0; uint16 resunit = 0; if ( !TIFFGetField( tiff, whichres, &resvalue ) || !TIFFGetFieldDefaulted( tiff, TIFFTAG_RESOLUTIONUNIT, &resunit ) ) return; float newsize = *size / resvalue; switch ( resunit ) { case RESUNIT_INCH: *size = (uint32)( newsize * dpi ); break; case RESUNIT_CENTIMETER: *size = (uint32)( newsize * 10.0 / 25.4 * dpi ); break; case RESUNIT_NONE: break; } } static Okular::Rotation readTiffRotation( TIFF *tiff ) { uint32 tiffOrientation = 0; if ( !TIFFGetField( tiff, TIFFTAG_ORIENTATION, &tiffOrientation ) ) return Okular::Rotation0; Okular::Rotation ret = Okular::Rotation0; switch ( tiffOrientation ) { case ORIENTATION_TOPLEFT: case ORIENTATION_TOPRIGHT: ret = Okular::Rotation0; break; case ORIENTATION_BOTRIGHT: case ORIENTATION_BOTLEFT: ret = Okular::Rotation180; break; case ORIENTATION_LEFTTOP: case ORIENTATION_LEFTBOT: ret = Okular::Rotation270; break; case ORIENTATION_RIGHTTOP: case ORIENTATION_RIGHTBOT: ret = Okular::Rotation90; break; } return ret; } OKULAR_EXPORT_PLUGIN(TIFFGenerator, "libokularGenerator_tiff.json") TIFFGenerator::TIFFGenerator( QObject *parent, const QVariantList &args ) : Okular::Generator( parent, args ), d( new Private ) { setFeature( Threaded ); setFeature( PrintNative ); setFeature( PrintToFile ); setFeature( ReadRawData ); } TIFFGenerator::~TIFFGenerator() { if ( d->tiff ) { TIFFClose( d->tiff ); d->tiff = 0; } delete d; } bool TIFFGenerator::loadDocument( const QString & fileName, QVector & pagesVector ) { QFile* qfile = new QFile( fileName ); qfile->open( QIODevice::ReadOnly ); d->dev = qfile; d->data = QFile::encodeName( QFileInfo( *qfile ).fileName() ); return loadTiff( pagesVector, d->data.constData() ); } bool TIFFGenerator::loadDocumentFromData( const QByteArray & fileData, QVector< Okular::Page * > & pagesVector ) { d->data = fileData; QBuffer* qbuffer = new QBuffer( &d->data ); qbuffer->open( QIODevice::ReadOnly ); d->dev = qbuffer; return loadTiff( pagesVector, "" ); } bool TIFFGenerator::loadTiff( QVector< Okular::Page * > & pagesVector, const char *name ) { d->tiff = TIFFClientOpen( name, "r", d->dev, okular_tiffReadProc, okular_tiffWriteProc, okular_tiffSeekProc, okular_tiffCloseProc, okular_tiffSizeProc, okular_tiffMapProc, okular_tiffUnmapProc ); if ( !d->tiff ) { delete d->dev; d->dev = 0; d->data.clear(); return false; } loadPages( pagesVector ); return true; } bool TIFFGenerator::doCloseDocument() { // closing the old document if ( d->tiff ) { TIFFClose( d->tiff ); d->tiff = 0; delete d->dev; d->dev = 0; d->data.clear(); m_pageMapping.clear(); } return true; } QImage TIFFGenerator::image( Okular::PixmapRequest * request ) { bool generated = false; QImage img; if ( TIFFSetDirectory( d->tiff, mapPage( request->page()->number() ) ) ) { int rotation = request->page()->rotation(); uint32 width = 1; uint32 height = 1; uint32 orientation = 0; TIFFGetField( d->tiff, TIFFTAG_IMAGEWIDTH, &width ); TIFFGetField( d->tiff, TIFFTAG_IMAGELENGTH, &height ); if ( !TIFFGetField( d->tiff, TIFFTAG_ORIENTATION, &orientation ) ) orientation = ORIENTATION_TOPLEFT; QImage image( width, height, QImage::Format_RGB32 ); uint32 * data = (uint32 *)image.bits(); // read data if ( TIFFReadRGBAImageOriented( d->tiff, width, height, data, orientation ) != 0 ) { // an image read by ReadRGBAImage is ABGR, we need ARGB, so swap red and blue uint32 size = width * height; for ( uint32 i = 0; i < size; ++i ) { uint32 red = ( data[i] & 0x00FF0000 ) >> 16; uint32 blue = ( data[i] & 0x000000FF ) << 16; data[i] = ( data[i] & 0xFF00FF00 ) + red + blue; } int reqwidth = request->width(); int reqheight = request->height(); if ( rotation % 2 == 1 ) qSwap( reqwidth, reqheight ); + + image.save("/tmp/paint/p_" + QString::number(request->page()->number()) + "_" + "_tiff_1.png"); + + qDebug() << "TIFF image" << reqwidth << reqheight; + img = image.scaled( reqwidth, reqheight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); + img.save("/tmp/paint/p_" + QString::number(request->page()->number()) + "_" + "_tiff_2.png"); + generated = true; } } if ( !generated ) { img = QImage( request->width(), request->height(), QImage::Format_RGB32 ); img.fill( qRgb( 255, 255, 255 ) ); } return img; } Okular::DocumentInfo TIFFGenerator::generateDocumentInfo( const QSet &keys ) const { Okular::DocumentInfo docInfo; if ( d->tiff ) { if ( keys.contains( Okular::DocumentInfo::MimeType ) ) docInfo.set( Okular::DocumentInfo::MimeType, QStringLiteral("image/tiff") ); if ( keys.contains( Okular::DocumentInfo::Description ) ) { char* buffer = 0; TIFFGetField( d->tiff, TIFFTAG_IMAGEDESCRIPTION, &buffer ); docInfo.set( Okular::DocumentInfo::Description, buffer ? QString::fromLatin1( buffer ) : QString() ); } if ( keys.contains( Okular::DocumentInfo::Producer ) ) { char* buffer = 0; TIFFGetField( d->tiff, TIFFTAG_SOFTWARE, &buffer ); docInfo.set( Okular::DocumentInfo::Producer, buffer ? QString::fromLatin1( buffer ) : QString() ); } if ( keys.contains( Okular::DocumentInfo::Copyright ) ) { char* buffer = 0; TIFFGetField( d->tiff, TIFFTAG_COPYRIGHT, &buffer ); docInfo.set( Okular::DocumentInfo::Copyright, buffer ? QString::fromLatin1( buffer ) : QString() ); } if ( keys.contains( Okular::DocumentInfo::Author ) ) { char* buffer = 0; TIFFGetField( d->tiff, TIFFTAG_ARTIST, &buffer ); docInfo.set( Okular::DocumentInfo::Author, buffer ? QString::fromLatin1( buffer ) : QString() ); } if ( keys.contains( Okular::DocumentInfo::CreationDate ) ) { char* buffer = 0; TIFFGetField( d->tiff, TIFFTAG_DATETIME, &buffer ); QDateTime date = convertTIFFDateTime( buffer ); docInfo.set( Okular::DocumentInfo::CreationDate, date.isValid() ? QLocale().toString( date, QLocale::LongFormat ) : QString() ); } } return docInfo; } void TIFFGenerator::loadPages( QVector & pagesVector ) { if ( !d->tiff ) return; tdir_t dirs = TIFFNumberOfDirectories( d->tiff ); pagesVector.resize( dirs ); tdir_t realdirs = 0; uint32 width = 0; uint32 height = 0; const QSizeF dpi = Okular::Utils::realDpi(nullptr); for ( tdir_t i = 0; i < dirs; ++i ) { if ( !TIFFSetDirectory( d->tiff, i ) ) continue; if ( TIFFGetField( d->tiff, TIFFTAG_IMAGEWIDTH, &width ) != 1 || TIFFGetField( d->tiff, TIFFTAG_IMAGELENGTH, &height ) != 1 ) continue; adaptSizeToResolution( d->tiff, TIFFTAG_XRESOLUTION, dpi.width(), &width ); adaptSizeToResolution( d->tiff, TIFFTAG_YRESOLUTION, dpi.height(), &height ); + qDebug() << "TIFF: " << width << height; + Okular::Page * page = new Okular::Page( realdirs, width, height, readTiffRotation( d->tiff ) ); pagesVector[ realdirs ] = page; m_pageMapping[ realdirs ] = i; ++realdirs; } pagesVector.resize( realdirs ); } bool TIFFGenerator::print( QPrinter& printer ) { uint32 width = 0; uint32 height = 0; QPainter p( &printer ); QList pageList = Okular::FilePrinter::pageList( printer, document()->pages(), document()->currentPage() + 1, document()->bookmarkedPageList() ); for ( tdir_t i = 0; i < pageList.count(); ++i ) { if ( !TIFFSetDirectory( d->tiff, mapPage( pageList[i] - 1 ) ) ) continue; if ( TIFFGetField( d->tiff, TIFFTAG_IMAGEWIDTH, &width ) != 1 || TIFFGetField( d->tiff, TIFFTAG_IMAGELENGTH, &height ) != 1 ) continue; QImage image( width, height, QImage::Format_RGB32 ); uint32 * data = (uint32 *)image.bits(); // read data if ( TIFFReadRGBAImageOriented( d->tiff, width, height, data, ORIENTATION_TOPLEFT ) != 0 ) { // an image read by ReadRGBAImage is ABGR, we need ARGB, so swap red and blue uint32 size = width * height; for ( uint32 i = 0; i < size; ++i ) { uint32 red = ( data[i] & 0x00FF0000 ) >> 16; uint32 blue = ( data[i] & 0x000000FF ) << 16; data[i] = ( data[i] & 0xFF00FF00 ) + red + blue; } } if ( i != 0 ) printer.newPage(); QSize targetSize = printer.pageRect().size(); + qDebug() << "TIFF: " << image.width() << targetSize.width() << image.height() << targetSize.height(); + if ( (image.width() < targetSize.width()) && (image.height() < targetSize.height()) ) { // draw small images at 100% (don't scale up) p.drawImage( 0, 0, image ); } else { // fit to page p.drawImage( 0, 0, image.scaled( targetSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) ); } } return true; } int TIFFGenerator::mapPage( int page ) const { QHash< int, int >::const_iterator it = m_pageMapping.find( page ); if ( it == m_pageMapping.end() ) { qCWarning(OkularTiffDebug) << "Requesting unmapped page" << page << ":" << m_pageMapping; return -1; } return it.value(); } Q_LOGGING_CATEGORY(OkularTiffDebug, "org.kde.okular.generators.tiff", QtWarningMsg) #include "generator_tiff.moc" diff --git a/ui/pagepainter.cpp b/ui/pagepainter.cpp index 9918540d5..33b81bf32 100644 --- a/ui/pagepainter.cpp +++ b/ui/pagepainter.cpp @@ -1,1061 +1,1066 @@ /*************************************************************************** * Copyright (C) 2005 by Enrico Ros * * * * 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 "pagepainter.h" // qt / kde includes #include #include #include #include #include #include #include #include #include // system includes #include // local includes #include "core/area.h" #include "core/page.h" #include "core/page_p.h" #include "core/annotations.h" #include "core/utils.h" #include "guiutils.h" #include "settings.h" #include "core/observer.h" #include "core/tile.h" #include "settings_core.h" #include "ui/debug_ui.h" Q_GLOBAL_STATIC_WITH_ARGS( QPixmap, busyPixmap, ( KIconLoader::global()->loadIcon(QLatin1String("okular"), KIconLoader::NoGroup, IconSize(KIconLoader::Desktop), KIconLoader::DefaultState, QStringList(), 0, true) ) ) #define TEXTANNOTATION_ICONSIZE 24 inline QPen buildPen( const Okular::Annotation *ann, double width, const QColor &color ) { QPen p( QBrush( color ), width, ann->style().lineStyle() == Okular::Annotation::Dashed ? Qt::DashLine : Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin ); return p; } void PagePainter::paintPageOnPainter( QPainter * destPainter, const Okular::Page * page, Okular::DocumentObserver *observer, int flags, int scaledWidth, int scaledHeight, const QRect &limits ) { paintCroppedPageOnPainter( destPainter, page, observer, flags, scaledWidth, scaledHeight, limits, Okular::NormalizedRect( 0, 0, 1, 1 ), 0 ); } void PagePainter::paintCroppedPageOnPainter( QPainter * destPainter, const Okular::Page * page, Okular::DocumentObserver *observer, int flags, int scaledWidth, int scaledHeight, const QRect &limits, const Okular::NormalizedRect &crop, Okular::NormalizedPoint *viewPortPoint ) { qreal dpr = destPainter->device()->devicePixelRatioF(); /* Calculate the cropped geometry of the page */ QRect scaledCrop = crop.geometry( scaledWidth, scaledHeight ); const QRect dScaledCrop(QRectF(scaledCrop.x() * dpr, scaledCrop.y() * dpr, scaledCrop.width() * dpr, scaledCrop.height() * dpr).toAlignedRect()); int croppedWidth = scaledCrop.width(); int croppedHeight = scaledCrop.height(); int dScaledWidth = ceil(scaledWidth * dpr); int dScaledHeight = ceil(scaledHeight * dpr); const QRect dLimits(QRectF(limits.x() * dpr, limits.y() * dpr, limits.width() * dpr, limits.height() * dpr).toAlignedRect()); QColor paperColor = Qt::white; QColor backgroundColor = paperColor; if ( Okular::SettingsCore::changeColors() ) { switch ( Okular::SettingsCore::renderMode() ) { case Okular::SettingsCore::EnumRenderMode::Inverted: backgroundColor = Qt::black; break; case Okular::SettingsCore::EnumRenderMode::Paper: paperColor = Okular::SettingsCore::paperColor(); backgroundColor = paperColor; break; case Okular::SettingsCore::EnumRenderMode::Recolor: backgroundColor = Okular::Settings::recolorBackground(); break; default: ; } } destPainter->fillRect( limits, backgroundColor ); const bool hasTilesManager = page->hasTilesManager( observer ); QPixmap pixmap; if ( !hasTilesManager ) { /** 1 - RETRIEVE THE 'PAGE+ID' PIXMAP OR A SIMILAR 'PAGE' ONE **/ const QPixmap *p = 0; p = page->_o_nearestPixmap( observer, dScaledWidth, dScaledHeight ); if ( p != NULL ) { pixmap = p->copy(); pixmap.setDevicePixelRatio( qApp->devicePixelRatio() ); } /** 1B - IF NO PIXMAP, DRAW EMPTY PAGE **/ double pixmapRescaleRatio = !pixmap.isNull() ? dScaledWidth / (double)pixmap.width() : -1; long pixmapPixels = !pixmap.isNull() ? (long)pixmap.width() * (long)pixmap.height() : 0; if ( pixmap.isNull() || pixmapRescaleRatio > 20.0 || pixmapRescaleRatio < 0.25 || (dScaledWidth > pixmap.width() && pixmapPixels > 60000000L) ) { // draw something on the blank page: the okular icon or a cross (as a fallback) if ( !busyPixmap()->isNull() ) { busyPixmap->setDevicePixelRatio(dpr); destPainter->drawPixmap( QPoint( 10, 10 ), *busyPixmap() ); } else { destPainter->setPen( Qt::gray ); destPainter->drawLine( 0, 0, croppedWidth-1, croppedHeight-1 ); destPainter->drawLine( 0, croppedHeight-1, croppedWidth-1, 0 ); } return; } } /** 2 - FIND OUT WHAT TO PAINT (Flags + Configuration + Presence) **/ bool canDrawHighlights = (flags & Highlights) && !page->m_highlights.isEmpty(); bool canDrawTextSelection = (flags & TextSelection) && page->textSelection(); bool canDrawAnnotations = (flags & Annotations) && !page->m_annotations.isEmpty(); bool enhanceLinks = (flags & EnhanceLinks) && Okular::Settings::highlightLinks(); bool enhanceImages = (flags & EnhanceImages) && Okular::Settings::highlightImages(); // vectors containing objects to draw // make this a qcolor, rect map, since we don't need // to know s_id here! we are only drawing this right? QList< QPair > * bufferedHighlights = 0; QList< Okular::Annotation * > * bufferedAnnotations = 0; QList< Okular::Annotation * > * unbufferedAnnotations = 0; Okular::Annotation *boundingRectOnlyAnn = 0; // Paint the bounding rect of this annotation // fill up lists with visible annotation/highlight objects/text selections if ( canDrawHighlights || canDrawTextSelection || canDrawAnnotations ) { // precalc normalized 'limits rect' for intersection double nXMin = ( (double)limits.left() / dScaledWidth ) + crop.left, nXMax = ( (double)limits.right() / dScaledWidth ) + crop.left, nYMin = ( (double)limits.top() / dScaledHeight ) + crop.top, nYMax = ( (double)limits.bottom() / dScaledHeight ) + crop.top; // append all highlights inside limits to their list if ( canDrawHighlights ) { if ( !bufferedHighlights ) bufferedHighlights = new QList< QPair >(); /* else {*/ Okular::NormalizedRect* limitRect = new Okular::NormalizedRect(nXMin, nYMin, nXMax, nYMax ); QLinkedList< Okular::HighlightAreaRect * >::const_iterator h2It = page->m_highlights.constBegin(), hEnd = page->m_highlights.constEnd(); Okular::HighlightAreaRect::const_iterator hIt; for ( ; h2It != hEnd; ++h2It ) for (hIt=(*h2It)->constBegin(); hIt!=(*h2It)->constEnd(); ++hIt) { if ((*hIt).intersects(limitRect)) bufferedHighlights->append( qMakePair((*h2It)->color,*hIt) ); } delete limitRect; //} } if ( canDrawTextSelection ) { if ( !bufferedHighlights ) bufferedHighlights = new QList< QPair >(); /* else {*/ Okular::NormalizedRect* limitRect = new Okular::NormalizedRect(nXMin, nYMin, nXMax, nYMax ); const Okular::RegularAreaRect *textSelection = page->textSelection(); Okular::HighlightAreaRect::const_iterator hIt = textSelection->constBegin(), hEnd = textSelection->constEnd(); for ( ; hIt != hEnd; ++hIt ) { if ( (*hIt).intersects( limitRect ) ) bufferedHighlights->append( qMakePair( page->textSelectionColor(), *hIt ) ); } delete limitRect; //} } // append annotations inside limits to the un/buffered list if ( canDrawAnnotations ) { QLinkedList< Okular::Annotation * >::const_iterator aIt = page->m_annotations.constBegin(), aEnd = page->m_annotations.constEnd(); for ( ; aIt != aEnd; ++aIt ) { Okular::Annotation * ann = *aIt; int flags = ann->flags(); if ( flags & Okular::Annotation::Hidden ) continue; if ( flags & Okular::Annotation::ExternallyDrawn ) { // ExternallyDrawn annots are never rendered by PagePainter. // Just paint the boundingRect if the annot is moved or resized. if ( flags & (Okular::Annotation::BeingMoved | Okular::Annotation::BeingResized) ) { boundingRectOnlyAnn = ann; } continue; } bool intersects = ann->transformedBoundingRectangle().intersects( nXMin, nYMin, nXMax, nYMax ); if ( ann->subType() == Okular::Annotation::AText ) { Okular::TextAnnotation * ta = static_cast< Okular::TextAnnotation * >( ann ); if ( ta->textType() == Okular::TextAnnotation::Linked ) { Okular::NormalizedRect iconrect( ann->transformedBoundingRectangle().left, ann->transformedBoundingRectangle().top, ann->transformedBoundingRectangle().left + TEXTANNOTATION_ICONSIZE / page->width(), ann->transformedBoundingRectangle().top + TEXTANNOTATION_ICONSIZE / page->height() ); intersects = iconrect.intersects( nXMin, nYMin, nXMax, nYMax ); } } if ( intersects ) { Okular::Annotation::SubType type = ann->subType(); if ( type == Okular::Annotation::ALine || type == Okular::Annotation::AHighlight || type == Okular::Annotation::AInk /*|| (type == Annotation::AGeom && ann->style().opacity() < 0.99)*/ ) { if ( !bufferedAnnotations ) bufferedAnnotations = new QList< Okular::Annotation * >(); bufferedAnnotations->append( ann ); } else { if ( !unbufferedAnnotations ) unbufferedAnnotations = new QList< Okular::Annotation * >(); unbufferedAnnotations->append( ann ); } } } } // end of intersections checking } /** 3 - ENABLE BACKBUFFERING IF DIRECT IMAGE MANIPULATION IS NEEDED **/ bool bufferAccessibility = (flags & Accessibility) && Okular::SettingsCore::changeColors() && (Okular::SettingsCore::renderMode() != Okular::SettingsCore::EnumRenderMode::Paper); bool useBackBuffer = bufferAccessibility || bufferedHighlights || bufferedAnnotations || viewPortPoint; QPixmap * backPixmap = 0; QPainter * mixedPainter = 0; QRect limitsInPixmap = limits.translated( scaledCrop.topLeft() ); QRect dLimitsInPixmap = dLimits.translated( dScaledCrop.topLeft() ); // limits within full (scaled but uncropped) pixmap /** 4A -- REGULAR FLOW. PAINT PIXMAP NORMAL OR RESCALED USING GIVEN QPAINTER **/ /* when zoomed in */ if ( !useBackBuffer ) { if ( hasTilesManager ) { const Okular::NormalizedRect normalizedLimits( limitsInPixmap, scaledWidth, scaledHeight ); const QList tiles = page->tilesAt( observer, normalizedLimits ); QList::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd(); while ( tIt != tEnd ) { const Okular::Tile &tile = *tIt; QRect tileRect = tile.rect().geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ); QRect dTileRect = QRectF(tileRect.x() * dpr, tileRect.y() * dpr, tileRect.width() * dpr, tileRect.height() * dpr).toAlignedRect(); QRect limitsInTile = limits & tileRect; QRectF dLimitsInTile = dLimits & dTileRect; if ( !limitsInTile.isEmpty() ) { QPixmap tilePixmap = tile.pixmap()->copy(); tilePixmap.setDevicePixelRatio( qApp->devicePixelRatio() ); if ( tilePixmap.width() == dTileRect.width() && tilePixmap.height() == dTileRect.height() ) { destPainter->drawPixmap( limitsInTile.topLeft(), tilePixmap, dLimitsInTile.translated( -dTileRect.topLeft() ) ); qCWarning(OkularUiDebug) << "PagePainter: 4AT1"; } else { destPainter->drawPixmap( tileRect, tilePixmap); qCWarning(OkularUiDebug) << "PagePainter: 4AT2"; } } tIt++; } } else { + QString r = QString::number(qrand()); + qCWarning(OkularUiDebug) << "PagePainter: 4AN" << pixmap.width() << dScaledWidth << ";" << pixmap.height() << dScaledHeight << ";" << dLimitsInPixmap; + pixmap.save("/tmp/paint/p_" + QString::number(page->number()) + "_" + r + "_4an_before.png"); QPixmap scaledCroppedPixmap = pixmap.scaled(dScaledWidth, dScaledHeight).copy(dLimitsInPixmap); + scaledCroppedPixmap.save("/tmp/paint/p_" + QString::number(page->number()) + "_" + r + "_4an.png"); destPainter->drawPixmap( limits.topLeft(), scaledCroppedPixmap, QRectF(0, 0, dLimits.width(),dLimits.height())); - qCWarning(OkularUiDebug) << "PagePainter: 4AN"; + + qCWarning(OkularUiDebug) << "PagePainter: 4AN" << scaledCroppedPixmap.devicePixelRatioF(); } // 4A.2. active painter is the one passed to this method mixedPainter = destPainter; } /** 4B -- BUFFERED FLOW. IMAGE PAINTING + OPERATIONS. QPAINTER OVER PIXMAP **/ else { // the image over which we are going to draw QImage backImage = QImage( dLimits.width(), dLimits.height(), QImage::Format_ARGB32_Premultiplied ); backImage.setDevicePixelRatio(dpr); backImage.fill( paperColor ); QPainter p( &backImage ); bool has_alpha; if ( !pixmap.isNull() ) has_alpha = pixmap.hasAlpha(); else has_alpha = true; if ( hasTilesManager ) { const Okular::NormalizedRect normalizedLimits( limitsInPixmap, scaledWidth, scaledHeight ); const QList tiles = page->tilesAt( observer, normalizedLimits ); QList::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd(); while ( tIt != tEnd ) { const Okular::Tile &tile = *tIt; QRect tileRect = tile.rect().geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ); QRect dTileRect(QRectF(tileRect.x() * dpr, tileRect.y() * dpr, tileRect.width() * dpr, tileRect.height() * dpr).toAlignedRect()); QRect limitsInTile = limits & tileRect; QRect dLimitsInTile = dLimits & dTileRect; if ( !limitsInTile.isEmpty() ) { QPixmap tilePixmap = tile.pixmap()->copy(); tilePixmap.setDevicePixelRatio( qApp->devicePixelRatio() ); if ( !tilePixmap.hasAlpha() ) has_alpha = false; if ( tilePixmap.width() == dTileRect.width() && tilePixmap.height() == dTileRect.height() ) { qCWarning(OkularUiDebug) << "PagePainter: 4BT1"; p.drawPixmap( limitsInTile.translated( -limits.topLeft() ).topLeft(), tilePixmap, dLimitsInTile.translated( -dTileRect.topLeft() ) ); } else { qCWarning(OkularUiDebug) << "PagePainter: 4BT2"; double xScale = tilePixmap.width() / (double)dTileRect.width(); double yScale = tilePixmap.height() / (double)dTileRect.height(); QTransform transform( xScale, 0, 0, yScale, 0, 0 ); p.drawPixmap( limitsInTile.translated( -limits.topLeft() ), tilePixmap, transform.mapRect( dLimitsInTile ).translated( -transform.mapRect( dTileRect ).topLeft() ) ); //tilePixmap.save("/tmp/pix.png"); qCWarning(OkularUiDebug) << "PagePainter: 4BT2 DONE"; } } ++tIt; } } else { // 4B.1. draw the page pixmap: normal or scaled QString r = QString::number(qrand()); qDebug() << "PagePainter: 4BN: " << r; QPixmap scaledCroppedPixmap = pixmap.scaled(dScaledWidth, dScaledHeight).copy(dLimitsInPixmap); scaledCroppedPixmap.setDevicePixelRatio(dpr); - //scaledCroppedPixmap.save("/tmp/paint/p_" + QString::number(page->number()) + "_" + r + "_4bn.png"); + scaledCroppedPixmap.save("/tmp/paint/p_" + QString::number(page->number()) + "_" + r + "_4bn.png"); p.drawPixmap( 0, 0, scaledCroppedPixmap ); } p.end(); // 4B.2. modify pixmap following accessibility settings if ( bufferAccessibility ) { switch ( Okular::SettingsCore::renderMode() ) { case Okular::SettingsCore::EnumRenderMode::Inverted: // Invert image pixels using QImage internal function backImage.invertPixels(QImage::InvertRgb); break; case Okular::SettingsCore::EnumRenderMode::Recolor: recolor(&backImage, Okular::Settings::recolorForeground(), Okular::Settings::recolorBackground()); break; case Okular::SettingsCore::EnumRenderMode::BlackWhite: // Manual Gray and Contrast unsigned int * data = (unsigned int *)backImage.bits(); int val, pixels = backImage.width() * backImage.height(), con = Okular::Settings::bWContrast(), thr = 255 - Okular::Settings::bWThreshold(); for( int i = 0; i < pixels; ++i ) { val = qGray( data[i] ); if ( val > thr ) val = 128 + (127 * (val - thr)) / (255 - thr); else if ( val < thr ) val = (128 * val) / thr; if ( con > 2 ) { val = con * ( val - thr ) / 2 + thr; if ( val > 255 ) val = 255; else if ( val < 0 ) val = 0; } data[i] = qRgba( val, val, val, 255 ); } break; } } // 4B.3. highlight rects in page if ( bufferedHighlights ) { // draw highlights that are inside the 'limits' paint region QList< QPair >::const_iterator hIt = bufferedHighlights->constBegin(), hEnd = bufferedHighlights->constEnd(); for ( ; hIt != hEnd; ++hIt ) { const Okular::NormalizedRect & r = (*hIt).second; // find out the rect to highlight on pixmap QRect highlightRect = r.geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ).intersected( limits ); highlightRect.translate( -limits.left(), -limits.top() ); QPainter painter(&backImage); painter.fillRect(highlightRect, QColor((*hIt).first.red(), (*hIt).first.green(), (*hIt).first.blue(), 150)); // the code below works too, but the mordern method would be to just use a QPainter? // highlight composition (product: highlight color * destcolor) /* QRect highlightRect = r.geometry( dScaledWidth, dScaledHeight ).translated( -dScaledCrop.topLeft() ).intersected( dLimits ); highlightRect.translate( -dLimits.left(), -dLimits.top() ); unsigned int * data = (unsigned int *)backImage.bits(); int val, newR, newG, newB, rh = (*hIt).first.red(), gh = (*hIt).first.green(), bh = (*hIt).first.blue(), offset = highlightRect.top() * backImage.width(); for( int y = highlightRect.top(); y <= highlightRect.bottom(); ++y ) { for( int x = highlightRect.left(); x <= highlightRect.right(); ++x ) { val = data[ x + offset ]; //for odt or epub if(has_alpha) { newR = qRed(val); newG = qGreen(val); newB = qBlue(val); if(newR == newG && newG == newB && newR == 0) newR = newG = newB = 255; newR = (newR * rh) / 255; newG = (newG * gh) / 255; newB = (newB * bh) / 255; } else { newR = (qRed(val) * rh) / 255; newG = (qGreen(val) * gh) / 255; newB = (qBlue(val) * bh) / 255; } data[ x + offset ] = qRgba( newR, newG, newB, 255 ); } offset += backImage.width(); } }*/ } } // 4B.4. paint annotations [COMPOSITED ONES] if ( bufferedAnnotations ) { // Albert: This is quite "heavy" but all the backImage that reach here are QImage::Format_ARGB32_Premultiplied // and have to be so that the QPainter::CompositionMode_Multiply works // we could also put a // backImage = backImage.convertToFormat(QImage::Format_ARGB32_Premultiplied) // that would be almost a noop, but we'll leave the assert for now Q_ASSERT(backImage.format() == QImage::Format_ARGB32_Premultiplied); // precalc constants for normalizing [0,1] page coordinates into normalized [0,1] limit rect coordinates double pageScale = (double)croppedWidth / page->width(); double xOffset = (double)limits.left() / (double)scaledWidth + crop.left, xScale = (double)scaledWidth / (double)limits.width(), yOffset = (double)limits.top() / (double)scaledHeight + crop.top, yScale = (double)scaledHeight / (double)limits.height(); // paint all buffered annotations in the page QList< Okular::Annotation * >::const_iterator aIt = bufferedAnnotations->constBegin(), aEnd = bufferedAnnotations->constEnd(); for ( ; aIt != aEnd; ++aIt ) { Okular::Annotation * a = *aIt; Okular::Annotation::SubType type = a->subType(); QColor acolor = a->style().color(); if ( !acolor.isValid() ) acolor = Qt::yellow; acolor.setAlphaF( a->style().opacity() ); // draw LineAnnotation MISSING: all if ( type == Okular::Annotation::ALine ) { // get the annotation Okular::LineAnnotation * la = (Okular::LineAnnotation *) a; NormalizedPath path; // normalize page point to image const QLinkedList points = la->transformedLinePoints(); QLinkedList::const_iterator it = points.constBegin(); QLinkedList::const_iterator itEnd = points.constEnd(); for ( ; it != itEnd; ++it ) { Okular::NormalizedPoint point; point.x = ( (*it).x - xOffset) * xScale; point.y = ( (*it).y - yOffset) * yScale; path.append( point ); } const QPen linePen = buildPen( a, a->style().width(), a->style().color() ); QBrush fillBrush; if ( la->lineClosed() && la->lineInnerColor().isValid() ) fillBrush = QBrush( la->lineInnerColor() ); // draw the line as normalized path into image drawShapeOnImage( backImage, path, la->lineClosed(), linePen, fillBrush, pageScale ,Multiply); if ( path.count() == 2 && fabs( la->lineLeadingForwardPoint() ) > 0.1 ) { Okular::NormalizedPoint delta( la->transformedLinePoints().last().x - la->transformedLinePoints().first().x, la->transformedLinePoints().first().y - la->transformedLinePoints().last().y ); double angle = atan2( delta.y, delta.x ); if ( delta.y < 0 ) angle += 2 * M_PI; int sign = la->lineLeadingForwardPoint() > 0.0 ? 1 : -1; double LLx = fabs( la->lineLeadingForwardPoint() ) * cos( angle + sign * M_PI_2 + 2 * M_PI ) / page->width(); double LLy = fabs( la->lineLeadingForwardPoint() ) * sin( angle + sign * M_PI_2 + 2 * M_PI ) / page->height(); NormalizedPath path2; NormalizedPath path3; Okular::NormalizedPoint point; point.x = ( la->transformedLinePoints().first().x + LLx - xOffset ) * xScale; point.y = ( la->transformedLinePoints().first().y - LLy - yOffset ) * yScale; path2.append( point ); point.x = ( la->transformedLinePoints().last().x + LLx - xOffset ) * xScale; point.y = ( la->transformedLinePoints().last().y - LLy - yOffset ) * yScale; path3.append( point ); // do we have the extension on the "back"? if ( fabs( la->lineLeadingBackwardPoint() ) > 0.1 ) { double LLEx = la->lineLeadingBackwardPoint() * cos( angle - sign * M_PI_2 + 2 * M_PI ) / page->width(); double LLEy = la->lineLeadingBackwardPoint() * sin( angle - sign * M_PI_2 + 2 * M_PI ) / page->height(); point.x = ( la->transformedLinePoints().first().x + LLEx - xOffset ) * xScale; point.y = ( la->transformedLinePoints().first().y - LLEy - yOffset ) * yScale; path2.append( point ); point.x = ( la->transformedLinePoints().last().x + LLEx - xOffset ) * xScale; point.y = ( la->transformedLinePoints().last().y - LLEy - yOffset ) * yScale; path3.append( point ); } else { path2.append( path[0] ); path3.append( path[1] ); } drawShapeOnImage( backImage, path2, false, linePen, QBrush(), pageScale, Multiply ); drawShapeOnImage( backImage, path3, false, linePen, QBrush(), pageScale, Multiply ); } } // draw HighlightAnnotation MISSING: under/strike width, feather, capping else if ( type == Okular::Annotation::AHighlight ) { // get the annotation Okular::HighlightAnnotation * ha = (Okular::HighlightAnnotation *) a; Okular::HighlightAnnotation::HighlightType type = ha->highlightType(); // draw each quad of the annotation int quads = ha->highlightQuads().size(); for ( int q = 0; q < quads; q++ ) { NormalizedPath path; const Okular::HighlightAnnotation::Quad & quad = ha->highlightQuads()[ q ]; // normalize page point to image for ( int i = 0; i < 4; i++ ) { Okular::NormalizedPoint point; point.x = (quad.transformedPoint( i ).x - xOffset) * xScale; point.y = (quad.transformedPoint( i ).y - yOffset) * yScale; path.append( point ); } // draw the normalized path into image switch ( type ) { // highlight the whole rect case Okular::HighlightAnnotation::Highlight: drawShapeOnImage( backImage, path, true, Qt::NoPen, acolor, pageScale, Multiply ); break; // highlight the bottom part of the rect case Okular::HighlightAnnotation::Squiggly: path[ 3 ].x = ( path[ 0 ].x + path[ 3 ].x ) / 2.0; path[ 3 ].y = ( path[ 0 ].y + path[ 3 ].y ) / 2.0; path[ 2 ].x = ( path[ 1 ].x + path[ 2 ].x ) / 2.0; path[ 2 ].y = ( path[ 1 ].y + path[ 2 ].y ) / 2.0; drawShapeOnImage( backImage, path, true, Qt::NoPen, acolor, pageScale, Multiply ); break; // make a line at 3/4 of the height case Okular::HighlightAnnotation::Underline: path[ 0 ].x = ( 3 * path[ 0 ].x + path[ 3 ].x ) / 4.0; path[ 0 ].y = ( 3 * path[ 0 ].y + path[ 3 ].y ) / 4.0; path[ 1 ].x = ( 3 * path[ 1 ].x + path[ 2 ].x ) / 4.0; path[ 1 ].y = ( 3 * path[ 1 ].y + path[ 2 ].y ) / 4.0; path.pop_back(); path.pop_back(); drawShapeOnImage( backImage, path, false, QPen( acolor, 2 ), QBrush(), pageScale ); break; // make a line at 1/2 of the height case Okular::HighlightAnnotation::StrikeOut: path[ 0 ].x = ( path[ 0 ].x + path[ 3 ].x ) / 2.0; path[ 0 ].y = ( path[ 0 ].y + path[ 3 ].y ) / 2.0; path[ 1 ].x = ( path[ 1 ].x + path[ 2 ].x ) / 2.0; path[ 1 ].y = ( path[ 1 ].y + path[ 2 ].y ) / 2.0; path.pop_back(); path.pop_back(); drawShapeOnImage( backImage, path, false, QPen( acolor, 2 ), QBrush(), pageScale ); break; } } } // draw InkAnnotation MISSING:invar width, PENTRACER else if ( type == Okular::Annotation::AInk ) { // get the annotation Okular::InkAnnotation * ia = (Okular::InkAnnotation *) a; // draw each ink path const QList< QLinkedList > transformedInkPaths = ia->transformedInkPaths(); const QPen inkPen = buildPen( a, a->style().width(), acolor ); int paths = transformedInkPaths.size(); for ( int p = 0; p < paths; p++ ) { NormalizedPath path; const QLinkedList & inkPath = transformedInkPaths[ p ]; // normalize page point to image QLinkedList::const_iterator pIt = inkPath.constBegin(), pEnd = inkPath.constEnd(); for ( ; pIt != pEnd; ++pIt ) { const Okular::NormalizedPoint & inkPoint = *pIt; Okular::NormalizedPoint point; point.x = (inkPoint.x - xOffset) * xScale; point.y = (inkPoint.y - yOffset) * yScale; path.append( point ); } // draw the normalized path into image drawShapeOnImage( backImage, path, false, inkPen, QBrush(), pageScale ); } } } // end current annotation drawing } if(viewPortPoint) { QPainter painter(&backImage); painter.translate( -limits.left(), -limits.top() ); painter.setPen( QApplication::palette().color( QPalette::Active, QPalette::Highlight ) ); painter.drawLine( 0, viewPortPoint->y * scaledHeight + 1, scaledWidth - 1, viewPortPoint->y * scaledHeight + 1 ); // ROTATION CURRENTLY NOT IMPLEMENTED /* if( page->rotation() == Okular::Rotation0) { } else if(page->rotation() == Okular::Rotation270) { painter.drawLine( viewPortPoint->y * scaledHeight + 1, 0, viewPortPoint->y * scaledHeight + 1, scaledWidth - 1); } else if(page->rotation() == Okular::Rotation180) { painter.drawLine( 0, (1.0 - viewPortPoint->y) * scaledHeight - 1, scaledWidth - 1, (1.0 - viewPortPoint->y) * scaledHeight - 1 ); } else if(page->rotation() == Okular::Rotation90) // not right, rotation clock-wise { painter.drawLine( scaledWidth - (viewPortPoint->y * scaledHeight + 1), 0, scaledWidth - (viewPortPoint->y * scaledHeight + 1), scaledWidth - 1); } */ } // 4B.5. create the back pixmap converting from the local image backPixmap = new QPixmap( QPixmap::fromImage( backImage ) ); backPixmap->setDevicePixelRatio(dpr); // 4B.6. create a painter over the pixmap and set it as the active one mixedPainter = new QPainter( backPixmap ); mixedPainter->translate( -limits.left(), -limits.top() ); } /** 5 -- MIXED FLOW. Draw ANNOTATIONS [OPAQUE ONES] on ACTIVE PAINTER **/ if ( unbufferedAnnotations ) { // iterate over annotations and paint AText, AGeom, AStamp QList< Okular::Annotation * >::const_iterator aIt = unbufferedAnnotations->constBegin(), aEnd = unbufferedAnnotations->constEnd(); for ( ; aIt != aEnd; ++aIt ) { Okular::Annotation * a = *aIt; // honor opacity settings on supported types unsigned int opacity = (unsigned int)( 255.0 * a->style().opacity() ); // skip the annotation drawing if all the annotation is fully // transparent, but not with text annotations if ( opacity <= 0 && a->subType() != Okular::Annotation::AText ) continue; QColor acolor = a->style().color(); if ( !acolor.isValid() ) acolor = Qt::yellow; acolor.setAlpha( opacity ); // get annotation boundary and drawn rect QRect annotBoundary = a->transformedBoundingRectangle().geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ); QRect annotRect = annotBoundary.intersected( limits ); QRect innerRect( annotRect.left() - annotBoundary.left(), annotRect.top() - annotBoundary.top(), annotRect.width(), annotRect.height() ); QRectF dInnerRect(innerRect.x() * dpr, innerRect.y() * dpr, innerRect.width() * dpr, innerRect.height() * dpr); Okular::Annotation::SubType type = a->subType(); // draw TextAnnotation if ( type == Okular::Annotation::AText ) { Okular::TextAnnotation * text = (Okular::TextAnnotation *)a; if ( text->textType() == Okular::TextAnnotation::InPlace ) { QImage image( annotBoundary.size(), QImage::Format_ARGB32 ); image.fill( acolor.rgba() ); QPainter painter( &image ); painter.setFont( text->textFont() ); Qt::AlignmentFlag halign = ( text->inplaceAlignment() == 1 ? Qt::AlignHCenter : ( text->inplaceAlignment() == 2 ? Qt::AlignRight : Qt::AlignLeft ) ); const double invXScale = (double)page->width() / scaledWidth; const double invYScale = (double)page->height() / scaledHeight; const double borderWidth = text->style().width(); painter.scale( 1 / invXScale, 1 / invYScale ); painter.drawText( borderWidth * invXScale, borderWidth * invYScale, (image.width() - 2 * borderWidth) * invXScale, (image.height() - 2 * borderWidth) * invYScale, Qt::AlignTop | halign | Qt::TextWrapAnywhere, text->contents() ); painter.resetTransform(); //Required as asking for a zero width pen results //in a default width pen (1.0) being created if ( borderWidth != 0 ) { QPen pen( Qt::black, borderWidth ); painter.setPen( pen ); painter.drawRect( 0, 0, image.width() - 1, image.height() - 1 ); } painter.end(); mixedPainter->drawImage( annotBoundary.topLeft(), image ); } else if ( text->textType() == Okular::TextAnnotation::Linked ) { // get pixmap, colorize and alpha-blend it QString path; QPixmap pixmap = GuiUtils::iconLoader()->loadIcon( text->textIcon().toLower(), KIconLoader::User, 32, KIconLoader::DefaultState, QStringList(), &path, true ); if ( path.isEmpty() ) pixmap = GuiUtils::iconLoader()->loadIcon( text->textIcon().toLower(), KIconLoader::NoGroup, 32 ); QRect annotBoundary2 = QRect( annotBoundary.topLeft(), QSize( TEXTANNOTATION_ICONSIZE * dpr, TEXTANNOTATION_ICONSIZE * dpr ) ); QRect annotRect2 = annotBoundary2.intersected( limits ); QRect innerRect2( annotRect2.left() - annotBoundary2.left(), annotRect2.top() - annotBoundary2.top(), annotRect2.width(), annotRect2.height() ); QPixmap scaledCroppedPixmap = pixmap.scaled(TEXTANNOTATION_ICONSIZE * dpr, TEXTANNOTATION_ICONSIZE * dpr).copy(dInnerRect.toAlignedRect()); scaledCroppedPixmap.setDevicePixelRatio(dpr); QImage scaledCroppedImage = scaledCroppedPixmap.toImage(); // if the annotation color is valid (ie it was set), then // use it to colorize the icon, otherwise the icon will be // "gray" if ( a->style().color().isValid() ) GuiUtils::colorizeImage( scaledCroppedImage, a->style().color(), opacity ); pixmap = QPixmap::fromImage( scaledCroppedImage ); // draw the mangled image to painter mixedPainter->drawPixmap( annotRect.topLeft(), pixmap); } } // draw StampAnnotation else if ( type == Okular::Annotation::AStamp ) { Okular::StampAnnotation * stamp = (Okular::StampAnnotation *)a; // get pixmap and alpha blend it if needed QPixmap pixmap = GuiUtils::loadStamp( stamp->stampIconName(), annotBoundary.size() ); if ( !pixmap.isNull() ) // should never happen but can happen on huge sizes { const QRect dInnerRect(QRectF(innerRect.x() * dpr, innerRect.y() * dpr, innerRect.width() * dpr, innerRect.height() * dpr).toAlignedRect()); QPixmap scaledCroppedPixmap = pixmap.scaled(annotBoundary.width() * dpr, annotBoundary.height() * dpr).copy(dInnerRect); scaledCroppedPixmap.setDevicePixelRatio(dpr); QImage scaledCroppedImage = scaledCroppedPixmap.toImage(); if ( opacity < 255 ) changeImageAlpha( scaledCroppedImage, opacity ); pixmap = QPixmap::fromImage( scaledCroppedImage ); // draw the scaled and al mixedPainter->drawPixmap( annotRect.topLeft(), pixmap ); } } // draw GeomAnnotation else if ( type == Okular::Annotation::AGeom ) { Okular::GeomAnnotation * geom = (Okular::GeomAnnotation *)a; // check whether there's anything to draw if ( geom->style().width() || geom->geometricalInnerColor().isValid() ) { mixedPainter->save(); const double width = geom->style().width() * Okular::Utils::realDpi(nullptr).width() / ( 72.0 * 2.0 ) * scaledWidth / page->width(); QRectF r( .0, .0, annotBoundary.width(), annotBoundary.height() ); r.adjust( width, width, -width, -width ); r.translate( annotBoundary.topLeft() ); if ( geom->geometricalInnerColor().isValid() ) { r.adjust( width, width, -width, -width ); const QColor color = geom->geometricalInnerColor(); mixedPainter->setPen( Qt::NoPen ); mixedPainter->setBrush( QColor( color.red(), color.green(), color.blue(), opacity ) ); if ( geom->geometricalType() == Okular::GeomAnnotation::InscribedSquare ) mixedPainter->drawRect( r ); else mixedPainter->drawEllipse( r ); r.adjust( -width, -width, width, width ); } if ( geom->style().width() ) // need to check the original size here.. { mixedPainter->setPen( buildPen( a, width * 2, acolor ) ); mixedPainter->setBrush( Qt::NoBrush ); if ( geom->geometricalType() == Okular::GeomAnnotation::InscribedSquare ) mixedPainter->drawRect( r ); else mixedPainter->drawEllipse( r ); } mixedPainter->restore(); } } // draw extents rectangle if ( Okular::Settings::debugDrawAnnotationRect() ) { mixedPainter->setPen( a->style().color() ); mixedPainter->drawRect( annotBoundary ); } } } if ( boundingRectOnlyAnn ) { QRect annotBoundary = boundingRectOnlyAnn->transformedBoundingRectangle().geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ); mixedPainter->setPen( Qt::DashLine ); mixedPainter->drawRect( annotBoundary ); } /** 6 -- MIXED FLOW. Draw LINKS+IMAGES BORDER on ACTIVE PAINTER **/ if ( enhanceLinks || enhanceImages ) { mixedPainter->save(); mixedPainter->scale( scaledWidth, scaledHeight ); mixedPainter->translate( -crop.left, -crop.top ); QColor normalColor = QApplication::palette().color( QPalette::Active, QPalette::Highlight ); // enlarging limits for intersection is like growing the 'rectGeometry' below QRect limitsEnlarged = limits; limitsEnlarged.adjust( -2, -2, 2, 2 ); // draw rects that are inside the 'limits' paint region as opaque rects QLinkedList< Okular::ObjectRect * >::const_iterator lIt = page->m_rects.constBegin(), lEnd = page->m_rects.constEnd(); for ( ; lIt != lEnd; ++lIt ) { Okular::ObjectRect * rect = *lIt; if ( (enhanceLinks && rect->objectType() == Okular::ObjectRect::Action) || (enhanceImages && rect->objectType() == Okular::ObjectRect::Image) ) { if ( limitsEnlarged.intersects( rect->boundingRect( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ) ) ) { mixedPainter->strokePath( rect->region(), QPen( normalColor, 0 ) ); } } } mixedPainter->restore(); } /** 7 -- BUFFERED FLOW. Copy BACKPIXMAP on DESTINATION PAINTER **/ if ( useBackBuffer ) { delete mixedPainter; destPainter->drawPixmap( limits.left(), limits.top(), *backPixmap ); delete backPixmap; } // delete object containers delete bufferedHighlights; delete bufferedAnnotations; delete unbufferedAnnotations; qDebug() << "PagePainter END"; } /** Private Helpers :: Pixmap conversion **/ void PagePainter::cropPixmapOnImage( QImage & dest, const QPixmap * src, const QRect & r ) { qreal dpr = src->devicePixelRatioF(); // handle quickly the case in which the whole pixmap has to be converted if ( r == QRect( 0, 0, src->width() / dpr, src->height() / dpr ) ) { dest = src->toImage(); dest = dest.convertToFormat(QImage::Format_ARGB32_Premultiplied); } // else copy a portion of the src to an internal pixmap (smaller) and convert it else { QImage croppedImage( r.width() * dpr, r.height() * dpr, QImage::Format_ARGB32_Premultiplied ); croppedImage.setDevicePixelRatio(dpr); QPainter p( &croppedImage ); p.drawPixmap( 0, 0, *src, r.left(), r.top(), r.width(), r.height() ); p.end(); dest = croppedImage; } } void PagePainter::recolor(QImage *image, const QColor &foreground, const QColor &background) { if (image->format() != QImage::Format_ARGB32_Premultiplied) { qCWarning(OkularUiDebug) << "Wrong image format! Converting..."; *image = image->convertToFormat(QImage::Format_ARGB32_Premultiplied); } Q_ASSERT(image->format() == QImage::Format_ARGB32_Premultiplied); const float scaleRed = background.redF() - foreground.redF(); const float scaleGreen = background.greenF() - foreground.greenF(); const float scaleBlue = background.blueF() - foreground.blueF(); for (int y=0; yheight(); y++) { QRgb *pixels = reinterpret_cast(image->scanLine(y)); for (int x=0; xwidth(); x++) { const int lightness = qGray(pixels[x]); pixels[x] = qRgba(scaleRed * lightness + foreground.red(), scaleGreen * lightness + foreground.green(), scaleBlue * lightness + foreground.blue(), qAlpha(pixels[x])); } } } /** Private Helpers :: Image Drawing **/ // from Arthur - qt4 static inline int qt_div_255(int x) { return (x + (x>>8) + 0x80) >> 8; } void PagePainter::changeImageAlpha( QImage & image, unsigned int destAlpha ) { // iterate over all pixels changing the alpha component value unsigned int * data = (unsigned int *)image.bits(); unsigned int pixels = image.width() * image.height(); int source, sourceAlpha; for( unsigned int i = 0; i < pixels; ++i ) { // optimize this loop keeping byte order into account source = data[i]; if ( (sourceAlpha = qAlpha( source )) == 255 ) { // use destAlpha data[i] = qRgba( qRed(source), qGreen(source), qBlue(source), destAlpha ); } else { // use destAlpha * sourceAlpha product sourceAlpha = qt_div_255( destAlpha * sourceAlpha ); data[i] = qRgba( qRed(source), qGreen(source), qBlue(source), sourceAlpha ); } } } void PagePainter::drawShapeOnImage( QImage & image, const NormalizedPath & normPath, bool closeShape, const QPen & pen, const QBrush & brush, double penWidthMultiplier, RasterOperation op //float antiAliasRadius ) { // safety checks int pointsNumber = normPath.size(); if ( pointsNumber < 2 ) return; int imageWidth = image.width(); int imageHeight = image.height(); double fImageWidth = (double)imageWidth; double fImageHeight = (double)imageHeight; // stroke outline double penWidth = (double)pen.width() * penWidthMultiplier; QPainter painter(&image); painter.setRenderHint(QPainter::Antialiasing); QPen pen2 = pen; pen2.setWidthF(penWidth); painter.setPen(pen2); painter.setBrush(brush); if (op == Multiply) { painter.setCompositionMode(QPainter::CompositionMode_Multiply); } if ( brush.style() == Qt::NoBrush ) { // create a polygon QPolygonF poly( closeShape ? pointsNumber + 1 : pointsNumber ); for ( int i = 0; i < pointsNumber; ++i ) { poly[ i ] = QPointF( normPath[ i ].x * fImageWidth, normPath[ i ].y * fImageHeight ); } if ( closeShape ) poly[ pointsNumber ] = poly[ 0 ]; painter.drawPolyline( poly ); } else { // create a 'path' QPainterPath path; path.setFillRule( Qt::WindingFill ); path.moveTo( normPath[ 0 ].x * fImageWidth, normPath[ 0 ].y * fImageHeight ); for ( int i = 1; i < pointsNumber; i++ ) { path.lineTo( normPath[ i ].x * fImageWidth, normPath[ i ].y * fImageHeight ); } if ( closeShape ) path.closeSubpath(); painter.drawPath( path ); } } /* kate: replace-tabs on; indent-width 4; */