diff --git a/core/area.h b/core/area.h --- a/core/area.h +++ b/core/area.h @@ -300,6 +300,18 @@ return pow( distX * xScale, 2 ) + pow( distY * yScale, 2 ); } + /// @since 1.4 + double width() const + { + return right - left; + } + + /// @since 1.4 + double height() const + { + return bottom - top; + } + /** * The normalized left coordinate. */ diff --git a/core/document.cpp b/core/document.cpp --- a/core/document.cpp +++ b/core/document.cpp @@ -1307,7 +1307,7 @@ if ( pixmap ) { tilesManager = new TilesManager( r->pageNumber(), pixmap->width(), pixmap->height(), r->page()->rotation() ); - tilesManager->setPixmap( pixmap, NormalizedRect( 0, 0, 1, 1 ) ); + tilesManager->setPixmap( pixmap, NormalizedRect( 0, 0, 1, 1 ), true /*isPartialPixmap*/ ); tilesManager->setSize( r->width(), r->height() ); } else @@ -1416,7 +1416,11 @@ request->setNormalizedRect( TilesManager::fromRotatedRect( request->normalizedRect(), m_rotation ) ); - request->setPartialUpdatesWanted( request->asynchronous() && !request->page()->hasPixmap( request->observer() ) ); + // If set elsewhere we already know we want it to be partial + if ( !request->partialUpdatesWanted() ) + { + request->setPartialUpdatesWanted( request->asynchronous() && !request->page()->hasPixmap( request->observer() ) ); + } // we always have to unlock _before_ the generatePixmap() because // a sync generation would end with requestDone() -> deadlock, and @@ -1512,18 +1516,29 @@ if ( !page ) return; - QLinkedList< Okular::PixmapRequest * > requestedPixmaps; QMap< DocumentObserver*, PagePrivate::PixmapObject >::ConstIterator it = page->d->m_pixmaps.constBegin(), itEnd = page->d->m_pixmaps.constEnd(); + QVector< Okular::PixmapRequest * > pixmapsToRequest; for ( ; it != itEnd; ++it ) { - QSize size = (*it).m_pixmap->size(); + const QSize size = (*it).m_pixmap->size(); PixmapRequest * p = new PixmapRequest( it.key(), pageNumber, size.width() / qApp->devicePixelRatio(), size.height() / qApp->devicePixelRatio(), 1, PixmapRequest::Asynchronous ); p->d->mForce = true; - requestedPixmaps.push_back( p ); + pixmapsToRequest << p; + } + + // Need to do this ↑↓ in two steps since requestPixmaps can end up calling cancelRenderingBecauseOf + // which changes m_pixmaps and thus breaks the loop above + for ( PixmapRequest *pr : qAsConst( pixmapsToRequest ) ) + { + QLinkedList< Okular::PixmapRequest * > requestedPixmaps; + pixmapsToRequest.push_back( pr ); + m_parent->requestPixmaps( requestedPixmaps, Okular::Document::NoOption ); } foreach (DocumentObserver *observer, m_observers) { + QLinkedList< Okular::PixmapRequest * > requestedPixmaps; + TilesManager *tilesManager = page->d->tilesManager( observer ); if ( tilesManager ) { @@ -1557,10 +1572,10 @@ delete p; } } - } - if ( !requestedPixmaps.isEmpty() ) m_parent->requestPixmaps( requestedPixmaps, Okular::Document::NoOption ); + } + } void DocumentPrivate::_o_configChanged() @@ -2547,6 +2562,16 @@ { d->m_pixmapRequestsMutex.lock(); startEventLoop = !d->m_executingPixmapRequests.isEmpty(); + + if ( d->m_generator->hasFeature( Generator::SupportsCancelling ) ) + { + for ( PixmapRequest *executingRequest : qAsConst( d->m_executingPixmapRequests ) ) + executingRequest->d->mShouldAbortRender = 1; + + if ( d->m_generator->d_ptr->mTextPageGenerationThread ) + d->m_generator->d_ptr->mTextPageGenerationThread->abortExtraction(); + } + d->m_pixmapRequestsMutex.unlock(); if ( startEventLoop ) { @@ -3095,6 +3120,82 @@ return QString(); } +static bool shouldCancelRenderingBecauseOf( const PixmapRequest & executingRequest, const PixmapRequest & otherRequest ) +{ + // New request has higher priority -> cancel + if ( executingRequest.priority() > otherRequest.priority() ) + return true; + + // New request has lower priority -> don't cancel + if ( executingRequest.priority() < otherRequest.priority() ) + return false; + + // New request has same priority and is from a different observer -> don't cancel + // AFAIK this never happens since all observers have different priorities + if ( executingRequest.observer() != otherRequest.observer() ) + return false; + + // Same priority and observer, different page number -> don't cancel + // may still end up cancelled later in the parent caller if none of the requests + // is of the executingRequest page and RemoveAllPrevious is specified + if ( executingRequest.pageNumber() != otherRequest.pageNumber() ) + return false; + + // Same priority, observer, page, different size -> cancel + if ( executingRequest.width() != otherRequest.width() ) + return true; + + // Same priority, observer, page, different size -> cancel + if ( executingRequest.height() != otherRequest.height() ) + return true; + + // Same priority, observer, page, different tiling -> cancel + if ( executingRequest.isTile() != otherRequest.isTile() ) + return true; + + // Same priority, observer, page, different tiling -> cancel + if ( executingRequest.isTile() ) + { + const NormalizedRect bothRequestsRect = executingRequest.normalizedRect() | otherRequest.normalizedRect(); + if ( !( bothRequestsRect == executingRequest.normalizedRect() ) ) + return true; + } + + return false; +} + +bool DocumentPrivate::cancelRenderingBecauseOf( PixmapRequest *executingRequest, PixmapRequest *newRequest ) +{ + // No point in aborting the rendering already finished, let it go through + if ( !executingRequest->d->mResultImage.isNull() ) + return false; + + if ( newRequest && executingRequest->partialUpdatesWanted() ) { + newRequest->setPartialUpdatesWanted( true ); + } + + TilesManager *tm = executingRequest->d->tilesManager(); + if ( tm ) + { + tm->setPixmap( nullptr, executingRequest->normalizedRect(), true /*isPartialPixmap*/ ); + tm->setRequest( NormalizedRect(), 0, 0 ); + } + PagePrivate::PixmapObject object = executingRequest->page()->d->m_pixmaps.take( executingRequest->observer() ); + delete object.m_pixmap; + + if ( executingRequest->d->mShouldAbortRender != 0) + return false; + + executingRequest->d->mShouldAbortRender = 1; + + if ( m_generator->d_ptr->mTextPageGenerationThread && m_generator->d_ptr->mTextPageGenerationThread->page() == executingRequest->page() ) + { + m_generator->d_ptr->mTextPageGenerationThread->abortExtraction(); + } + + return true; +} + void Document::requestPixmaps( const QLinkedList< PixmapRequest * > & requests ) { requestPixmaps( requests, RemoveAllPrevious ); @@ -3115,14 +3216,18 @@ return; } + QSet< DocumentObserver * > observersPixmapCleared; + // 1. [CLEAN STACK] remove previous requests of requesterID - // FIXME This assumes all requests come from the same observer, that is true atm but not enforced anywhere DocumentObserver *requesterObserver = requests.first()->observer(); QSet< int > requestedPages; { QLinkedList< PixmapRequest * >::const_iterator rIt = requests.constBegin(), rEnd = requests.constEnd(); for ( ; rIt != rEnd; ++rIt ) + { + Q_ASSERT( (*rIt)->observer() == requesterObserver ); requestedPages.insert( (*rIt)->pageNumber() ); + } } const bool removeAllPrevious = reqOptions & RemoveAllPrevious; d->m_pixmapRequestsMutex.lock(); @@ -3140,12 +3245,10 @@ ++sIt; } - // 2. [ADD TO STACK] add requests to stack - QLinkedList< PixmapRequest * >::const_iterator rIt = requests.constBegin(), rEnd = requests.constEnd(); - for ( ; rIt != rEnd; ++rIt ) + // 1.B [PREPROCESS REQUESTS] tweak some values of the requests + for ( PixmapRequest *request : requests ) { // set the 'page field' (see PixmapRequest) and check if it is valid - PixmapRequest * request = *rIt; qCDebug(OkularCoreDebug).nospace() << "request observer=" << request->observer() << " " <width() << "x" << request->height() << "@" << request->pageNumber(); if ( d->m_pagesVector.value( request->pageNumber() ) == 0 ) { @@ -3182,7 +3285,44 @@ if ( !request->asynchronous() ) request->d->mPriority = 0; + } + + // 1.C [CANCEL REQUESTS] cancel those requests that are running and should be cancelled because of the new requests coming in + if ( d->m_generator->hasFeature( Generator::SupportsCancelling ) ) + { + for ( PixmapRequest *executingRequest : qAsConst( d->m_executingPixmapRequests ) ) + { + bool newRequestsContainExecutingRequestPage = false; + bool requestCancelled = false; + for ( PixmapRequest *newRequest : requests ) + { + if ( newRequest->pageNumber() == executingRequest->pageNumber() && requesterObserver == executingRequest->observer()) + { + newRequestsContainExecutingRequestPage = true; + } + + if ( shouldCancelRenderingBecauseOf( *executingRequest, *newRequest ) ) + { + requestCancelled = d->cancelRenderingBecauseOf( executingRequest, newRequest ); + } + } + + // If we were told to remove all the previous requests and the executing request page is not part of the new requests, cancel it + if ( !requestCancelled && removeAllPrevious && requesterObserver == executingRequest->observer() && !newRequestsContainExecutingRequestPage ) + { + requestCancelled = d->cancelRenderingBecauseOf( executingRequest, nullptr ); + } + + if ( requestCancelled ) + { + observersPixmapCleared << executingRequest->observer(); + } + } + } + // 2. [ADD TO STACK] add requests to stack + for ( PixmapRequest *request : requests ) + { // add request to the 'stack' at the right place if ( !request->priority() ) // add priority zero requests to the top of the stack @@ -3205,6 +3345,9 @@ // all handling of requests put into sendGeneratorPixmapRequest // if ( generator->canRequestPixmap() ) d->sendGeneratorPixmapRequest(); + + for ( DocumentObserver *o : qAsConst( observersPixmapCleared ) ) + o->notifyContentsCleared( Okular::DocumentObserver::Pixmap ); } void Document::requestTextPage( uint page ) @@ -4859,41 +5002,44 @@ qCDebug(OkularCoreDebug) << "requestDone with generator not in READY state."; #endif - // [MEM] 1.1 find and remove a previous entry for the same page and id - QLinkedList< AllocatedPixmap * >::iterator aIt = m_allocatedPixmaps.begin(); - QLinkedList< AllocatedPixmap * >::iterator aEnd = m_allocatedPixmaps.end(); - for ( ; aIt != aEnd; ++aIt ) - if ( (*aIt)->page == req->pageNumber() && (*aIt)->observer == req->observer() ) - { - AllocatedPixmap * p = *aIt; - m_allocatedPixmaps.erase( aIt ); - m_allocatedPixmapsTotalMemory -= p->memory; - delete p; - break; - } - - DocumentObserver *observer = req->observer(); - if ( m_observers.contains(observer) ) + if ( !req->shouldAbortRender() ) { - // [MEM] 1.2 append memory allocation descriptor to the FIFO - qulonglong memoryBytes = 0; - const TilesManager *tm = req->d->tilesManager(); - if ( tm ) - memoryBytes = tm->totalMemory(); - else - memoryBytes = 4 * req->width() * req->height(); + // [MEM] 1.1 find and remove a previous entry for the same page and id + QLinkedList< AllocatedPixmap * >::iterator aIt = m_allocatedPixmaps.begin(); + QLinkedList< AllocatedPixmap * >::iterator aEnd = m_allocatedPixmaps.end(); + for ( ; aIt != aEnd; ++aIt ) + if ( (*aIt)->page == req->pageNumber() && (*aIt)->observer == req->observer() ) + { + AllocatedPixmap * p = *aIt; + m_allocatedPixmaps.erase( aIt ); + m_allocatedPixmapsTotalMemory -= p->memory; + delete p; + break; + } + + DocumentObserver *observer = req->observer(); + if ( m_observers.contains(observer) ) + { + // [MEM] 1.2 append memory allocation descriptor to the FIFO + qulonglong memoryBytes = 0; + const TilesManager *tm = req->d->tilesManager(); + if ( tm ) + memoryBytes = tm->totalMemory(); + else + memoryBytes = 4 * req->width() * req->height(); - AllocatedPixmap * memoryPage = new AllocatedPixmap( req->observer(), req->pageNumber(), memoryBytes ); - m_allocatedPixmaps.append( memoryPage ); - m_allocatedPixmapsTotalMemory += memoryBytes; + AllocatedPixmap * memoryPage = new AllocatedPixmap( req->observer(), req->pageNumber(), memoryBytes ); + m_allocatedPixmaps.append( memoryPage ); + m_allocatedPixmapsTotalMemory += memoryBytes; - // 2. notify an observer that its pixmap changed - observer->notifyPageChanged( req->pageNumber(), DocumentObserver::Pixmap ); - } + // 2. notify an observer that its pixmap changed + observer->notifyPageChanged( req->pageNumber(), DocumentObserver::Pixmap ); + } #ifndef NDEBUG - else - qCWarning(OkularCoreDebug) << "Receiving a done request for the defunct observer" << observer; + else + qCWarning(OkularCoreDebug) << "Receiving a done request for the defunct observer" << observer; #endif + } // 3. delete request m_pixmapRequestsMutex.lock(); diff --git a/core/document_p.h b/core/document_p.h --- a/core/document_p.h +++ b/core/document_p.h @@ -150,6 +150,7 @@ bool canModifyExternalAnnotations() const; bool canRemoveExternalAnnotations() const; OKULARCORE_EXPORT static QString docDataFileName(const QUrl &url, qint64 document_size); + bool cancelRenderingBecauseOf( PixmapRequest *executingRequest, PixmapRequest *newRequest ); // Methods that implement functionality needed by undo commands void performAddPageAnnotation( int page, Annotation *annotation ); diff --git a/core/generator.h b/core/generator.h --- a/core/generator.h +++ b/core/generator.h @@ -57,6 +57,8 @@ class PixmapRequest; class PixmapRequestPrivate; class TextPage; +class TextRequest; +class TextRequestPrivate; class NormalizedRect; class SourceReference; @@ -212,7 +214,8 @@ PrintPostscript, ///< Whether the Generator supports postscript-based file printing. PrintToFile, ///< Whether the Generator supports export to PDF & PS through the Print Dialog TiledRendering, ///< Whether the Generator can render tiles @since 0.16 (KDE 4.10) - SwapBackingFile ///< Whether the Generator can hot-swap the file it's reading from @since 1.3 + SwapBackingFile, ///< Whether the Generator can hot-swap the file it's reading from @since 1.3 + SupportsCancelling ///< Whether the Generator can cancel requests @since 1.4 }; /** @@ -325,13 +328,11 @@ * This method can be called to trigger the generation of * a text page for the given @p page. * - * The generation is done synchronous or asynchronous, depending - * on the @p type parameter and the capabilities of the - * generator (e.g. multithreading). + * The generation is done in the calling thread. * * @see TextPage */ - virtual void generateTextPage( Page * page ); + void generateTextPage( Page * page ); /** * Returns the general information object of the document. @@ -520,18 +521,24 @@ * Returns the image of the page as specified in * the passed pixmap @p request. * + * Must return a null image if the request was cancelled and the generator supports cancelling + * * @warning this method may be executed in its own separated thread if the * @ref Threaded is enabled! */ virtual QImage image( PixmapRequest *page ); /** - * Returns the text page for the given @p page. + * Returns the text page for the given @p request. + * + * Must return a null pointer if the request was cancelled and the generator supports cancelling * * @warning this method may be executed in its own separated thread if the * @ref Threaded is enabled! + * + * @since 1.4 */ - virtual TextPage* textPage( Page *page ); + virtual TextPage* textPage( TextRequest *request ); /** * Returns a pointer to the document. @@ -749,13 +756,57 @@ */ bool partialUpdatesWanted() const; + /** + * Should the request be aborted if possible? + * + * @since 1.4 + */ + bool shouldAbortRender() const; + private: Q_DISABLE_COPY( PixmapRequest ) friend class PixmapRequestPrivate; PixmapRequestPrivate* const d; }; +/** + * @short Describes a text request. + * + * @since 1.4 + */ +class OKULARCORE_EXPORT TextRequest +{ + public: + /** + * Creates a new text request. + */ + TextRequest( Page *page ); + + TextRequest(); + + /** + * Destroys the pixmap request. + */ + ~TextRequest(); + + /** + * Returns a pointer to the page where the pixmap shall be generated for. + */ + Page *page() const; + + /** + * Should the request be aborted if possible? + */ + bool shouldAbortExtraction() const; + + private: + Q_DISABLE_COPY( TextRequest ) + + friend TextRequestPrivate; + TextRequestPrivate* const d; +}; + } Q_DECLARE_METATYPE(Okular::Generator::PrintError) diff --git a/core/generator.cpp b/core/generator.cpp --- a/core/generator.cpp +++ b/core/generator.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -90,13 +91,14 @@ { Q_Q( Generator ); PixmapRequest *request = mPixmapGenerationThread->request(); + const QImage& img = mPixmapGenerationThread->image(); mPixmapGenerationThread->endGeneration(); QMutexLocker locker( threadsLock() ); - mPixmapReady = true; if ( m_closing ) { + mPixmapReady = true; delete request; if ( mTextPageReady ) { @@ -106,12 +108,25 @@ 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() ); + if ( !request->shouldAbortRender() ) + { + 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() ); + } + else + { + // Cancel the text page generation too if it's still running + if ( mTextPageGenerationThread && mTextPageGenerationThread->isRunning() ) { + mTextPageGenerationThread->abortExtraction(); + mTextPageGenerationThread->wait(); + } + } + + mPixmapReady = true; q->signalPixmapRequestDone( request ); } @@ -161,11 +176,10 @@ } -Generator::Generator(QObject* parent, const QVariantList&) - : QObject(parent) - , d_ptr( new GeneratorPrivate() ) +Generator::Generator(QObject* parent, const QVariantList &args) + : Generator( *new GeneratorPrivate(), parent, args ) { - d_ptr->q_ptr = this; + // the delegated constructor does it all } Generator::Generator(GeneratorPrivate &dd, QObject *parent, const QVariantList &args) @@ -253,16 +267,32 @@ if ( request->asynchronous() && hasFeature( Threaded ) ) { + if ( d->textPageGenerationThread()->isFinished() && !canGenerateTextPage() ) + { + // It can happen that the text generation has already finished but + // mTextPageReady is still false because textpageGenerationFinished + // didn't have time to run, if so queue ourselves + QTimer::singleShot(0, this, [this, request] { generatePixmap(request); }); + return; + } + 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; - // Queue the text generation request so that pixmap generation gets a chance to start before the text generation - QMetaObject::invokeMethod(d->textPageGenerationThread(), "startGeneration", Qt::QueuedConnection, Q_ARG(Okular::Page*, request->page())); + d->textPageGenerationThread()->setPage( request->page() ); + + // dummy is used as a way to make sure the lambda gets disconnected each time it is executed + // since not all the times the pixmap generation thread starts we want the text generation thread to also start + QObject *dummy = new QObject(); + connect(d_ptr->pixmapGenerationThread(), &QThread::started, dummy, [this, dummy] { + delete dummy; + d_ptr->textPageGenerationThread()->startGeneration(); + }); } return; @@ -287,7 +317,8 @@ void Generator::generateTextPage( Page *page ) { - TextPage *tp = textPage( page ); + TextRequest treq( page ); + TextPage *tp = textPage( &treq ); page->setTextPage( tp ); signalTextGenerationDone( page, tp ); } @@ -298,7 +329,7 @@ return d->image( request ); } -TextPage* Generator::textPage( Page* ) +TextPage* Generator::textPage( TextRequest * ) { return nullptr; } @@ -413,7 +444,11 @@ void Generator::signalPartialPixmapRequest( PixmapRequest *request, const QImage &image ) { - request->page()->setPixmap( request->observer(), new QPixmap( QPixmap::fromImage( image ) ), request->normalizedRect() ); + if ( request->shouldAbortRender() ) + return; + + PagePrivate *pagePrivate = PagePrivate::get( request->page() ); + pagePrivate->setPixmap( request->observer(), new QPixmap( QPixmap::fromImage( image ) ), request->normalizedRect(), true /* isPartialPixmap */ ); const int pageNumber = request->page()->number(); request->observer()->notifyPageChanged( pageNumber, Okular::DocumentObserver::Pixmap ); @@ -504,6 +539,40 @@ return nullptr; } +TextRequest::TextRequest() +: d( new TextRequestPrivate ) +{ + d->mPage = nullptr; + d->mShouldAbortExtraction = 0; +} + +TextRequest::TextRequest( Page *page ) +: d( new TextRequestPrivate ) +{ + d->mPage = page; + d->mShouldAbortExtraction = 0; +} + +TextRequest::~TextRequest() +{ + delete d; +} + +Page *TextRequest::page() const +{ + return d->mPage; +} + +bool TextRequest::shouldAbortExtraction() const +{ + return d->mShouldAbortExtraction != 0; +} + +TextRequestPrivate *TextRequestPrivate::get(const TextRequest *req) +{ + return req->d; +} + PixmapRequest::PixmapRequest( DocumentObserver *observer, int pageNumber, int width, int height, int priority, PixmapRequestFeatures features ) : d( new PixmapRequestPrivate ) { @@ -517,6 +586,7 @@ d->mTile = false; d->mNormalizedRect = NormalizedRect(); d->mPartialUpdatesWanted = false; + d->mShouldAbortRender = 0; } PixmapRequest::~PixmapRequest() @@ -597,11 +667,21 @@ return d->mPartialUpdatesWanted; } +bool PixmapRequest::shouldAbortRender() const +{ + return d->mShouldAbortRender != 0; +} + Okular::TilesManager* PixmapRequestPrivate::tilesManager() const { return mPage->d->tilesManager(mObserver); } +PixmapRequestPrivate *PixmapRequestPrivate::get(const PixmapRequest *req) +{ + return req->d; +} + void PixmapRequestPrivate::swap() { qSwap( mWidth, mHeight ); @@ -713,14 +793,21 @@ 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 ); + PixmapRequestPrivate *reqPriv = PixmapRequestPrivate::get(&req); + + str << "PixmapRequest:" << &req; + str << "- observer:" << (qulonglong)req.observer(); + str << "- page:" << req.pageNumber(); + str << "- width:" << req.width(); + str << "- height:" << req.height(); + str << "- priority:" << req.priority(); + str << "- async:" << ( req.asynchronous() ? "true" : "false" ); + str << "- tile:" << ( req.isTile() ? "true" : "false" ); + str << "- rect:" << req.normalizedRect(); + str << "- preload:" << ( req.preload() ? "true" : "false" ); + str << "- partialUpdates:" << ( req.partialUpdatesWanted() ? "true" : "false" ); + str << "- shouldAbort:" << ( req.shouldAbortRender() ? "true" : "false" ); + str << "- force:" << ( reqPriv->mForce ? "true" : "false" ); return str; } diff --git a/core/generator_p.h b/core/generator_p.h --- a/core/generator_p.h +++ b/core/generator_p.h @@ -22,6 +22,7 @@ class QEventLoop; class QMutex; +#include "generator.h" #include "page.h" namespace Okular { @@ -80,6 +81,8 @@ void swap(); TilesManager *tilesManager() const; + static PixmapRequestPrivate *get(const PixmapRequest *req); + DocumentObserver *mObserver; int mPageNumber; int mWidth; @@ -91,6 +94,18 @@ bool mPartialUpdatesWanted : 1; Page *mPage; NormalizedRect mNormalizedRect; + QAtomicInt mShouldAbortRender; + QImage mResultImage; +}; + + +class TextRequestPrivate +{ + public: + static TextRequestPrivate *get(const TextRequest *req); + + Page *mPage; + QAtomicInt mShouldAbortExtraction; }; @@ -117,7 +132,6 @@ private: Generator *mGenerator; PixmapRequest *mRequest; - QImage mImage; NormalizedRect mBoundingBox; bool mCalcBoundingBox : 1; }; @@ -132,20 +146,24 @@ void endGeneration(); + void setPage( Page *page ); Page *page() const; TextPage* textPage() const; + void abortExtraction(); + bool shouldAbortExtraction() const; + public slots: - void startGeneration( Okular::Page *page ); + void startGeneration(); protected: void run() override; private: Generator *mGenerator; - Page *mPage; TextPage *mTextPage; + TextRequest mTextRequest; }; class FontExtractionThread : public QThread diff --git a/core/generator_p.cpp b/core/generator_p.cpp --- a/core/generator_p.cpp +++ b/core/generator_p.cpp @@ -42,7 +42,7 @@ QImage PixmapGenerationThread::image() const { - return mImage; + return mRequest ? PixmapRequestPrivate::get(mRequest)->mResultImage : QImage(); } bool PixmapGenerationThread::calcBoundingBox() const @@ -57,50 +57,84 @@ void PixmapGenerationThread::run() { - mImage = QImage(); - if ( mRequest ) { - mImage = mGenerator->image( mRequest ); + PixmapRequestPrivate::get(mRequest)->mResultImage = mGenerator->image( mRequest ); + if ( mCalcBoundingBox ) - mBoundingBox = Utils::imageBoundingBox( &mImage ); + mBoundingBox = Utils::imageBoundingBox( &PixmapRequestPrivate::get(mRequest)->mResultImage ); } } TextPageGenerationThread::TextPageGenerationThread( Generator *generator ) - : mGenerator( generator ), mPage( nullptr ) + : mGenerator( generator ), mTextPage( nullptr ) { + TextRequestPrivate *treqPriv = TextRequestPrivate::get( &mTextRequest ); + treqPriv->mPage = nullptr; + treqPriv->mShouldAbortExtraction = 0; } -void TextPageGenerationThread::startGeneration( Page *page ) +void TextPageGenerationThread::startGeneration() { - mPage = page; - - start( QThread::InheritPriority ); + if ( page() ) + { + start( QThread::InheritPriority ); + } } void TextPageGenerationThread::endGeneration() { - mPage = nullptr; + TextRequestPrivate *treqPriv = TextRequestPrivate::get( &mTextRequest ); + treqPriv->mPage = nullptr; + treqPriv->mShouldAbortExtraction = 0; +} + +void TextPageGenerationThread::setPage( Page *page ) +{ + TextRequestPrivate *treqPriv = TextRequestPrivate::get( &mTextRequest ); + treqPriv->mPage = page; + treqPriv->mShouldAbortExtraction = 0; } Page *TextPageGenerationThread::page() const { - return mPage; + return mTextRequest.page(); } TextPage* TextPageGenerationThread::textPage() const { return mTextPage; } +void TextPageGenerationThread::abortExtraction() +{ + // If extraction already finished no point in aborting + if ( !mTextPage ) + { + TextRequestPrivate *treqPriv = TextRequestPrivate::get( &mTextRequest ); + treqPriv->mShouldAbortExtraction = 1; + } +} + +bool TextPageGenerationThread::shouldAbortExtraction() const +{ + return mTextRequest.shouldAbortExtraction(); +} + void TextPageGenerationThread::run() { mTextPage = nullptr; - if ( mPage ) - mTextPage = mGenerator->textPage( mPage ); + Q_ASSERT ( page() ); + + mTextPage = mGenerator->textPage( &mTextRequest ); + + if ( mTextRequest.shouldAbortExtraction() ) + { + delete mTextPage; + mTextPage = nullptr; + } } diff --git a/core/page.cpp b/core/page.cpp --- a/core/page.cpp +++ b/core/page.cpp @@ -105,7 +105,7 @@ if ( tm ) { QPixmap *pixmap = new QPixmap( QPixmap::fromImage( job->image() ) ); - tm->setPixmap( pixmap, job->rect() ); + tm->setPixmap( pixmap, job->rect(), job->isPartialUpdate() ); delete pixmap; return; } @@ -531,34 +531,40 @@ void Page::setPixmap( DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect ) { - if ( d->m_rotation == Rotation0 ) { - TilesManager *tm = d->tilesManager( observer ); + d->setPixmap( observer, pixmap, rect, false /*isPartialPixmap*/ ); +} + +void PagePrivate::setPixmap( DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect, bool isPartialPixmap ) +{ + if ( m_rotation == Rotation0 ) { + TilesManager *tm = tilesManager( observer ); if ( tm ) { - tm->setPixmap( pixmap, rect ); + tm->setPixmap( pixmap, rect, isPartialPixmap ); delete pixmap; return; } - QMap< DocumentObserver*, PagePrivate::PixmapObject >::iterator it = d->m_pixmaps.find( observer ); - if ( it != d->m_pixmaps.end() ) + QMap< DocumentObserver*, PagePrivate::PixmapObject >::iterator it = m_pixmaps.find( observer ); + if ( it != m_pixmaps.end() ) { delete it.value().m_pixmap; } else { - it = d->m_pixmaps.insert( observer, PagePrivate::PixmapObject() ); + it = m_pixmaps.insert( observer, PagePrivate::PixmapObject() ); } it.value().m_pixmap = pixmap; - it.value().m_rotation = d->m_rotation; + it.value().m_rotation = m_rotation; } else { // it can happen that we get a setPixmap while closing and thus the page controller is gone - if ( d->m_doc->m_pageController ) + if ( m_doc->m_pageController ) { - 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); + RotationJob *job = new RotationJob( pixmap->toImage(), Rotation0, m_rotation, observer ); + job->setPage( this ); + job->setRect( TilesManager::toRotatedRect( rect, m_rotation ) ); + job->setIsPartialUpdate( isPartialPixmap ); + m_doc->m_pageController->addRotationJob(job); } delete pixmap; diff --git a/core/page_p.h b/core/page_p.h --- a/core/page_p.h +++ b/core/page_p.h @@ -134,10 +134,12 @@ */ OKULARCORE_EXPORT static FormField *findEquivalentForm( const Page *p, FormField *oldField ); + void setPixmap( DocumentObserver *observer, QPixmap *pixmap, const NormalizedRect &rect, bool isPartialPixmap ); + class PixmapObject { public: - QPixmap *m_pixmap; + QPixmap *m_pixmap = nullptr; Rotation m_rotation; }; QMap< DocumentObserver*, PixmapObject > m_pixmaps; diff --git a/core/rotationjob.cpp b/core/rotationjob.cpp --- a/core/rotationjob.cpp +++ b/core/rotationjob.cpp @@ -17,6 +17,7 @@ : ThreadWeaver::QObjectDecorator( new RotationJobInternal( image, oldRotation, newRotation ) ) , mObserver( observer ), m_pd( nullptr ) , mRect( NormalizedRect() ) + , mIsPartialUpdate( false ) { } @@ -30,6 +31,11 @@ mRect = rect; } +void RotationJob::setIsPartialUpdate( bool partialUpdate ) +{ + mIsPartialUpdate = partialUpdate; +} + DocumentObserver * RotationJob::observer() const { return mObserver; @@ -45,6 +51,11 @@ return mRect; } +bool RotationJob::isPartialUpdate() const +{ + return mIsPartialUpdate; +} + QTransform RotationJob::rotationMatrix( Rotation from, Rotation to ) { QTransform matrix; diff --git a/core/rotationjob_p.h b/core/rotationjob_p.h --- a/core/rotationjob_p.h +++ b/core/rotationjob_p.h @@ -53,19 +53,22 @@ void setPage( PagePrivate * pd ); void setRect( const NormalizedRect &rect ); + void setIsPartialUpdate( bool partialUpdate ); QImage image() const { return static_cast(job())->image(); } Rotation rotation() const { return static_cast(job())->rotation(); } DocumentObserver *observer() const; PagePrivate * page() const; NormalizedRect rect() const; + bool isPartialUpdate() const; static QTransform rotationMatrix( Rotation from, Rotation to ); private: DocumentObserver *mObserver; PagePrivate * m_pd; NormalizedRect mRect; + bool mIsPartialUpdate; }; } diff --git a/core/textdocumentgenerator.h b/core/textdocumentgenerator.h --- a/core/textdocumentgenerator.h +++ b/core/textdocumentgenerator.h @@ -208,7 +208,7 @@ protected: bool doCloseDocument() override; - Okular::TextPage* textPage( Okular::Page *page ) override; + Okular::TextPage* textPage( Okular::TextRequest *request ) override; private: Q_DECLARE_PRIVATE( TextDocumentGenerator ) diff --git a/core/textdocumentgenerator.cpp b/core/textdocumentgenerator.cpp --- a/core/textdocumentgenerator.cpp +++ b/core/textdocumentgenerator.cpp @@ -426,10 +426,10 @@ return image; } -Okular::TextPage* TextDocumentGenerator::textPage( Okular::Page * page ) +Okular::TextPage* TextDocumentGenerator::textPage( Okular::TextRequest * request ) { Q_D( TextDocumentGenerator ); - return d->createTextPage( page->number() ); + return d->createTextPage( request->page()->number() ); } bool TextDocumentGenerator::print( QPrinter& printer ) diff --git a/core/tilesmanager.cpp b/core/tilesmanager.cpp --- a/core/tilesmanager.cpp +++ b/core/tilesmanager.cpp @@ -35,7 +35,7 @@ bool hasPixmap( const NormalizedRect &rect, const TileNode &tile ) const; void tilesAt( const NormalizedRect &rect, TileNode &tile, QList &result, TileLeaf tileLeaf ); - void setPixmap( const QPixmap *pixmap, const NormalizedRect &rect, TileNode &tile ); + void setPixmap( const QPixmap *pixmap, const NormalizedRect &rect, TileNode &tile, bool isPartialPixmap ); /** * Mark @p tile and all its children as dirty @@ -185,42 +185,45 @@ } } -void TilesManager::setPixmap( const QPixmap *pixmap, const NormalizedRect &rect ) +void TilesManager::setPixmap( const QPixmap *pixmap, const NormalizedRect &rect, bool isPartialPixmap ) { - NormalizedRect rotatedRect = TilesManager::fromRotatedRect( rect, d->rotation ); + const NormalizedRect rotatedRect = TilesManager::fromRotatedRect( rect, d->rotation ); if ( !d->requestRect.isNull() ) { if ( !(d->requestRect == rect) ) return; - // Check whether the pixmap has the same absolute size of the expected - // request. - // If the document is rotated, rotate requestRect back to the original - // rotation before comparing to pixmap's size. This is to avoid - // conversion issues. The pixmap request was made using an unrotated - // rect. - QSize pixmapSize = pixmap->size(); - int w = width(); - int h = height(); - if ( d->rotation % 2 ) + if ( pixmap ) { - qSwap(w, h); - pixmapSize.transpose(); - } + // Check whether the pixmap has the same absolute size of the expected + // request. + // If the document is rotated, rotate requestRect back to the original + // rotation before comparing to pixmap's size. This is to avoid + // conversion issues. The pixmap request was made using an unrotated + // rect. + QSize pixmapSize = pixmap->size(); + int w = width(); + int h = height(); + if ( d->rotation % 2 ) + { + qSwap(w, h); + pixmapSize.transpose(); + } - if ( rotatedRect.geometry( w, h ).size() != pixmapSize ) - return; + if ( rotatedRect.geometry( w, h ).size() != pixmapSize ) + return; + } d->requestRect = NormalizedRect(); } for ( int i = 0; i < 16; ++i ) { - d->setPixmap( pixmap, rotatedRect, d->tiles[ i ] ); + d->setPixmap( pixmap, rotatedRect, d->tiles[ i ], isPartialPixmap ); } } -void TilesManager::Private::setPixmap( const QPixmap *pixmap, const NormalizedRect &rect, TileNode &tile ) +void TilesManager::Private::setPixmap( const QPixmap *pixmap, const NormalizedRect &rect, TileNode &tile, bool isPartialPixmap ) { QRect pixmapRect = TilesManager::toRotatedRect( rect, rotation ).geometry( width, height ); @@ -236,7 +239,7 @@ if ( tile.nTiles > 0 ) { for ( int i = 0; i < tile.nTiles; ++i ) - setPixmap( pixmap, rect, tile.tiles[ i ] ); + setPixmap( pixmap, rect, tile.tiles[ i ], isPartialPixmap ); delete tile.pixmap; tile.pixmap = nullptr; @@ -248,7 +251,7 @@ // the tile lies entirely within the viewport if ( tile.nTiles == 0 ) { - tile.dirty = false; + tile.dirty = isPartialPixmap; // check whether the tile size is big and split it if necessary if ( !splitBigTiles( tile, rect ) ) @@ -258,10 +261,17 @@ totalPixels -= tile.pixmap->width()*tile.pixmap->height(); delete tile.pixmap; } - NormalizedRect rotatedRect = TilesManager::toRotatedRect( tile.rect, rotation ); - tile.pixmap = new QPixmap( pixmap->copy( rotatedRect.geometry( width, height ).translated( -pixmapRect.topLeft() ) ) ); tile.rotation = rotation; - totalPixels += tile.pixmap->width()*tile.pixmap->height(); + if ( pixmap ) + { + const NormalizedRect rotatedRect = TilesManager::toRotatedRect( tile.rect, rotation ); + tile.pixmap = new QPixmap( pixmap->copy( rotatedRect.geometry( width, height ).translated( -pixmapRect.topLeft() ) ) ); + totalPixels += tile.pixmap->width()*tile.pixmap->height(); + } + else + { + tile.pixmap = nullptr; + } } else { @@ -273,7 +283,7 @@ } for ( int i = 0; i < tile.nTiles; ++i ) - setPixmap( pixmap, rect, tile.tiles[ i ] ); + setPixmap( pixmap, rect, tile.tiles[ i ], isPartialPixmap ); } } else @@ -283,16 +293,16 @@ // small, discards the children tiles and use the current one if ( tileRect.width()*tileRect.height() >= TILES_MAXSIZE ) { - tile.dirty = false; + tile.dirty = isPartialPixmap; if ( tile.pixmap ) { totalPixels -= tile.pixmap->width()*tile.pixmap->height(); delete tile.pixmap; tile.pixmap = nullptr; } for ( int i = 0; i < tile.nTiles; ++i ) - setPixmap( pixmap, rect, tile.tiles[ i ] ); + setPixmap( pixmap, rect, tile.tiles[ i ], isPartialPixmap ); } else { @@ -313,11 +323,18 @@ totalPixels -= tile.pixmap->width()*tile.pixmap->height(); delete tile.pixmap; } - NormalizedRect rotatedRect = TilesManager::toRotatedRect( tile.rect, rotation ); - tile.pixmap = new QPixmap( pixmap->copy( rotatedRect.geometry( width, height ).translated( -pixmapRect.topLeft() ) ) ); tile.rotation = rotation; - totalPixels += tile.pixmap->width()*tile.pixmap->height(); - tile.dirty = false; + if ( pixmap ) + { + const NormalizedRect rotatedRect = TilesManager::toRotatedRect( tile.rect, rotation ); + tile.pixmap = new QPixmap( pixmap->copy( rotatedRect.geometry( width, height ).translated( -pixmapRect.topLeft() ) ) ); + totalPixels += tile.pixmap->width()*tile.pixmap->height(); + } + else + { + tile.pixmap = nullptr; + } + tile.dirty = isPartialPixmap; } } } @@ -336,7 +353,8 @@ bool TilesManager::Private::hasPixmap( const NormalizedRect &rect, const TileNode &tile ) const { - if ( !tile.rect.intersects( rect ) ) + const NormalizedRect rectIntersection = tile.rect & rect; + if ( rectIntersection.width() <= 0 || rectIntersection.height() <= 0 ) return true; if ( tile.nTiles == 0 ) diff --git a/core/tilesmanager_p.h b/core/tilesmanager_p.h --- a/core/tilesmanager_p.h +++ b/core/tilesmanager_p.h @@ -116,7 +116,7 @@ * Also it checks the dimensions of the given parameters against the * current request as to avoid setting pixmaps of late requests. */ - void setPixmap( const QPixmap *pixmap, const NormalizedRect &rect ); + void setPixmap( const QPixmap *pixmap, const NormalizedRect &rect, bool isPartialPixmap ); /** * Checks whether all tiles intersecting with @p rect are available. diff --git a/generators/chm/generator_chm.h b/generators/chm/generator_chm.h --- a/generators/chm/generator_chm.h +++ b/generators/chm/generator_chm.h @@ -50,7 +50,7 @@ protected: bool doCloseDocument() override; - Okular::TextPage* textPage( Okular::Page *page ) override; + Okular::TextPage* textPage( Okular::TextRequest *request ) override; private: void additionalRequestData(); diff --git a/generators/chm/generator_chm.cpp b/generators/chm/generator_chm.cpp --- a/generators/chm/generator_chm.cpp +++ b/generators/chm/generator_chm.cpp @@ -408,10 +408,11 @@ } } -Okular::TextPage* CHMGenerator::textPage( Okular::Page * page ) +Okular::TextPage* CHMGenerator::textPage( Okular::TextRequest * request ) { userMutex()->lock(); + const Okular::Page *page = request->page(); m_syncGen->view()->resize(page->width(), page->height()); preparePageForSyncOperation(m_pageUrl[page->number()]); diff --git a/generators/djvu/generator_djvu.h b/generators/djvu/generator_djvu.h --- a/generators/djvu/generator_djvu.h +++ b/generators/djvu/generator_djvu.h @@ -43,7 +43,7 @@ bool doCloseDocument() override; // pixmap generation QImage image( Okular::PixmapRequest *request ) override; - Okular::TextPage* textPage( Okular::Page *page ) override; + Okular::TextPage* textPage( Okular::TextRequest *request ) override; private: void loadPages( QVector & pagesVector, int rotation ); diff --git a/generators/djvu/generator_djvu.cpp b/generators/djvu/generator_djvu.cpp --- a/generators/djvu/generator_djvu.cpp +++ b/generators/djvu/generator_djvu.cpp @@ -206,9 +206,10 @@ return QVariant(); } -Okular::TextPage* DjVuGenerator::textPage( Okular::Page *page ) +Okular::TextPage* DjVuGenerator::textPage( Okular::TextRequest *request ) { userMutex()->lock(); + const Okular::Page *page = request->page(); QList te; #if 0 m_djvu->textEntities( page->number(), "char" ); diff --git a/generators/dvi/generator_dvi.h b/generators/dvi/generator_dvi.h --- a/generators/dvi/generator_dvi.h +++ b/generators/dvi/generator_dvi.h @@ -47,7 +47,7 @@ protected: bool doCloseDocument() override; QImage image( Okular::PixmapRequest * request ) override; - Okular::TextPage* textPage( Okular::Page *page ) override; + Okular::TextPage* textPage( Okular::TextRequest *request ) override; private: double m_resolution; diff --git a/generators/dvi/generator_dvi.cpp b/generators/dvi/generator_dvi.cpp --- a/generators/dvi/generator_dvi.cpp +++ b/generators/dvi/generator_dvi.cpp @@ -247,8 +247,10 @@ return ret; } -Okular::TextPage* DviGenerator::textPage( Okular::Page *page ) +Okular::TextPage* DviGenerator::textPage( Okular::TextRequest *request ) { + const Okular::Page *page = request->page(); + qCDebug(OkularDviDebug); dviPageInfo *pageInfo = new dviPageInfo(); pageSize ps; diff --git a/generators/poppler/CMakeLists.txt b/generators/poppler/CMakeLists.txt --- a/generators/poppler/CMakeLists.txt +++ b/generators/poppler/CMakeLists.txt @@ -61,6 +61,17 @@ } " HAVE_POPPLER_0_62) +check_cxx_source_compiles(" +#include +#include +int main() +{ + Poppler::Page *p; + p->renderToImage(0, 0, 0, 0, 0, 0, Poppler::Page::Rotate0, nullptr, nullptr, nullptr, QVariant()); + return 0; +} +" HAVE_POPPLER_0_63) + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/config-okular-poppler.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-okular-poppler.h diff --git a/generators/poppler/config-okular-poppler.h.cmake b/generators/poppler/config-okular-poppler.h.cmake --- a/generators/poppler/config-okular-poppler.h.cmake +++ b/generators/poppler/config-okular-poppler.h.cmake @@ -21,3 +21,6 @@ /* Defined if we have the 0.62 version of the Poppler library */ #cmakedefine HAVE_POPPLER_0_62 1 + +/* Defined if we have the 0.63 version of the Poppler library */ +#cmakedefine HAVE_POPPLER_0_63 1 diff --git a/generators/poppler/generator_pdf.h b/generators/poppler/generator_pdf.h --- a/generators/poppler/generator_pdf.h +++ b/generators/poppler/generator_pdf.h @@ -104,7 +104,7 @@ protected: SwapBackingFileResult swapBackingFile( QString const &newFileName, QVector & newPagesVector ) override; bool doCloseDocument() override; - Okular::TextPage* textPage( Okular::Page *page ) override; + Okular::TextPage* textPage( Okular::TextRequest *request ) override; protected Q_SLOTS: void requestFontData(const Okular::FontInfo &font, QByteArray *data); diff --git a/generators/poppler/generator_pdf.cpp b/generators/poppler/generator_pdf.cpp --- a/generators/poppler/generator_pdf.cpp +++ b/generators/poppler/generator_pdf.cpp @@ -517,6 +517,9 @@ setFeature( ReadRawData ); setFeature( TiledRendering ); setFeature( SwapBackingFile ); +#ifdef HAVE_POPPLER_0_63 + setFeature( SupportsCancelling ); +#endif // You only need to do it once not for each of the documents but it is cheap enough // so doing it all the time won't hurt either @@ -910,9 +913,9 @@ } #ifdef HAVE_POPPLER_0_62 -struct PartialUpdatePayload +struct RenderImagePayload { - PartialUpdatePayload(PDFGenerator *g, Okular::PixmapRequest *r) : + RenderImagePayload(PDFGenerator *g, Okular::PixmapRequest *r) : generator(g), request(r) { // Don't report partial updates for the first 500 ms @@ -925,11 +928,11 @@ Okular::PixmapRequest *request; QTimer timer; }; -Q_DECLARE_METATYPE(PartialUpdatePayload*) +Q_DECLARE_METATYPE(RenderImagePayload*) static bool shouldDoPartialUpdateCallback(const QVariant &vPayload) { - auto payload = vPayload.value(); + auto payload = vPayload.value(); // Since the timer lives in a thread without an event loop we need to stop it ourselves // when the remaining time has reached 0 @@ -942,11 +945,19 @@ static void partialUpdateCallback(const QImage &image, const QVariant &vPayload) { - auto payload = vPayload.value(); + auto payload = vPayload.value(); QMetaObject::invokeMethod(payload->generator, "signalPartialPixmapRequest", Qt::QueuedConnection, Q_ARG(Okular::PixmapRequest*, payload->request), Q_ARG(QImage, image)); } #endif +#ifdef HAVE_POPPLER_0_63 +static bool shouldAbortRenderCallback(const QVariant &vPayload) +{ + auto payload = vPayload.value(); + return payload->request->shouldAbortRender(); +} +#endif + QImage PDFGenerator::image( Okular::PixmapRequest * request ) { // debug requests to this (xpdf) generator @@ -970,45 +981,93 @@ // 0. LOCK [waits for the thread end] userMutex()->lock(); + if ( request->shouldAbortRender() ) + { + userMutex()->unlock(); + return QImage(); + } + // 1. Set OutputDev parameters and Generate contents // note: thread safety is set on 'false' for the GUI (this) thread Poppler::Page *p = pdfdoc->page(page->number()); // 2. Take data from outputdev and attach it to the Page QImage img; if (p) { +#ifdef HAVE_POPPLER_0_63 if ( request->isTile() ) { - QRect rect = request->normalizedRect().geometry( request->width(), request->height() ); -#ifdef HAVE_POPPLER_0_62 + const QRect rect = request->normalizedRect().geometry( request->width(), request->height() ); if ( request->partialUpdatesWanted() ) { - PartialUpdatePayload payload( this, request ); + RenderImagePayload payload( this, request ); + img = p->renderToImage( fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0, + partialUpdateCallback, shouldDoPartialUpdateCallback, shouldAbortRenderCallback, QVariant::fromValue( &payload ) ); + } + else + { + RenderImagePayload payload( this, request ); + img = p->renderToImage( fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0, + nullptr, nullptr, shouldAbortRenderCallback, QVariant::fromValue( &payload ) ); + } + } + else + { + if ( request->partialUpdatesWanted() ) + { + RenderImagePayload payload( this, request ); + img = p->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0, + partialUpdateCallback, shouldDoPartialUpdateCallback, shouldAbortRenderCallback, QVariant::fromValue( &payload ) ); + } + else + { + RenderImagePayload payload( this, request ); + img = p->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0, + nullptr, nullptr, shouldAbortRenderCallback, QVariant::fromValue( &payload ) ); + } + + } +#elif defined(HAVE_POPPLER_0_62) + if ( request->isTile() ) + { + const QRect rect = request->normalizedRect().geometry( request->width(), request->height() ); + if ( request->partialUpdatesWanted() ) + { + RenderImagePayload payload( this, request ); img = p->renderToImage( fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0, partialUpdateCallback, shouldDoPartialUpdateCallback, QVariant::fromValue( &payload ) ); } else -#endif { img = p->renderToImage( fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0 ); } } else { -#ifdef HAVE_POPPLER_0_62 if ( request->partialUpdatesWanted() ) { - PartialUpdatePayload payload(this, request); + RenderImagePayload payload( this, request ); img = p->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0, partialUpdateCallback, shouldDoPartialUpdateCallback, QVariant::fromValue( &payload ) ); } else -#endif { - img = p->renderToImage(fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0 ); + img = p->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0 ); } + } +#else + if ( request->isTile() ) + { + const QRect rect = request->normalizedRect().geometry( request->width(), request->height() ); + img = p->renderToImage( fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0 ); + } + else + { + img = p->renderToImage( fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0 ); + } +#endif } else { @@ -1100,8 +1159,28 @@ resolveMediaLinkReference( field->activationAction() ); } -Okular::TextPage* PDFGenerator::textPage( Okular::Page *page ) +#ifdef HAVE_POPPLER_0_63 +struct TextExtractionPayload { + TextExtractionPayload(Okular::TextRequest *r) : + request(r) + { + } + + Okular::TextRequest *request; +}; +Q_DECLARE_METATYPE(TextExtractionPayload*) + +static bool shouldAbortTextExtractionCallback(const QVariant &vPayload) +{ + auto payload = vPayload.value(); + return payload->request->shouldAbortExtraction(); +} +#endif + +Okular::TextPage* PDFGenerator::textPage( Okular::TextRequest *request ) +{ + const Okular::Page *page = request->page(); #ifdef PDFGENERATOR_DEBUG qCDebug(OkularPdfDebug) << "page" << page->number(); #endif @@ -1112,7 +1191,12 @@ if (pp) { userMutex()->lock(); +#ifdef HAVE_POPPLER_0_63 + TextExtractionPayload payload(request); + textList = pp->textList( Poppler::Page::Rotate0, shouldAbortTextExtractionCallback, QVariant::fromValue( &payload ) ); +#else textList = pp->textList(); +#endif userMutex()->unlock(); QSizeF s = pp->pageSizeF(); @@ -1127,6 +1211,9 @@ pageHeight = defaultPageHeight; } + if ( textList.isEmpty() && request->shouldAbortExtraction() ) + return nullptr; + Okular::TextPage *tp = abstractTextPage(textList, pageHeight, pageWidth, (Poppler::Page::Rotation)page->orientation()); qDeleteAll(textList); return tp; diff --git a/generators/xps/generator_xps.h b/generators/xps/generator_xps.h --- a/generators/xps/generator_xps.h +++ b/generators/xps/generator_xps.h @@ -319,7 +319,7 @@ protected: bool doCloseDocument() override; QImage image( Okular::PixmapRequest *page ) override; - Okular::TextPage* textPage( Okular::Page * page ) override; + Okular::TextPage* textPage( Okular::TextRequest * request ) override; private: XpsFile *m_xpsFile; diff --git a/generators/xps/generator_xps.cpp b/generators/xps/generator_xps.cpp --- a/generators/xps/generator_xps.cpp +++ b/generators/xps/generator_xps.cpp @@ -2117,10 +2117,10 @@ return image; } -Okular::TextPage* XpsGenerator::textPage( Okular::Page * page ) +Okular::TextPage* XpsGenerator::textPage( Okular::TextRequest * request ) { QMutexLocker lock( userMutex() ); - XpsPage * xpsPage = m_xpsFile->page( page->number() ); + XpsPage * xpsPage = m_xpsFile->page( request->page()->number() ); return xpsPage->textPage(); }