Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit 2d8b2c7e authored by Albert Astals Cid's avatar Albert Astals Cid Committed by Albert Astals Cid

Add support for cancellable image rendering and text extraction

Summary:
Only supported by the pdf backend if using poppler >= 0.63

Sadly had to change the generator API

Text cancellation is a bit wobbly still since poppler has large parts
of the code where doesn't check for the cancellation flag, but that
is something that will get automagically fixed for us if the poppler
side is improved

Test Plan: Needs https://bugs.freedesktop.org/show_bug.cgi?id=104263

Reviewers: ervin, rkflx

Reviewed By: ervin, rkflx

Subscribers: #okular

Tags: #okular

Differential Revision: https://phabricator.kde.org/D9328
parent c762b86b
......@@ -300,6 +300,18 @@ class OKULARCORE_EXPORT NormalizedRect
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.
*/
......
This diff is collapsed.
......@@ -150,6 +150,7 @@ class DocumentPrivate
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 );
......
......@@ -22,6 +22,7 @@
#include <QtCore/QDebug>
#include <QIcon>
#include <QMimeDatabase>
#include <QTimer>
#include <KLocalizedString>
#include <kwallet.h>
......@@ -90,13 +91,14 @@ void GeneratorPrivate::pixmapGenerationFinished()
{
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 @@ void GeneratorPrivate::pixmapGenerationFinished()
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 @@ QImage GeneratorPrivate::image( PixmapRequest * )
}
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,6 +267,15 @@ void Generator::generatePixmap( PixmapRequest *request )
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 );
/**
......@@ -261,8 +284,15 @@ void Generator::generatePixmap( PixmapRequest *request )
*/
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 @@ bool Generator::canGenerateTextPage() const
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 @@ QImage Generator::image( PixmapRequest *request )
return d->image( request );
}
TextPage* Generator::textPage( Page* )
TextPage* Generator::textPage( TextRequest * )
{
return nullptr;
}
......@@ -413,7 +444,11 @@ void Generator::signalTextGenerationDone( Page *page, TextPage *textPage )
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 @@ QAbstractItemModel * Generator::layersModel() const
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 @@ PixmapRequest::PixmapRequest( DocumentObserver *observer, int pageNumber, int wi
d->mTile = false;
d->mNormalizedRect = NormalizedRect();
d->mPartialUpdatesWanted = false;
d->mShouldAbortRender = 0;
}
PixmapRequest::~PixmapRequest()
......@@ -597,11 +667,21 @@ bool PixmapRequest::partialUpdatesWanted() const
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 @@ bool ExportFormat::operator!=( const ExportFormat &other ) const
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;
}
......
......@@ -57,6 +57,8 @@ class Page;
class PixmapRequest;
class PixmapRequestPrivate;
class TextPage;
class TextRequest;
class TextRequestPrivate;
class NormalizedRect;
class SourceReference;
......@@ -212,7 +214,8 @@ class OKULARCORE_EXPORT Generator : public QObject
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 @@ class OKULARCORE_EXPORT Generator : public QObject
* 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 @@ class OKULARCORE_EXPORT Generator : public QObject
* 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,6 +756,13 @@ class OKULARCORE_EXPORT PixmapRequest
*/
bool partialUpdatesWanted() const;
/**
* Should the request be aborted if possible?
*
* @since 1.4
*/
bool shouldAbortRender() const;
private:
Q_DISABLE_COPY( PixmapRequest )
......@@ -756,6 +770,43 @@ class OKULARCORE_EXPORT PixmapRequest
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)
......
......@@ -42,7 +42,7 @@ PixmapRequest *PixmapGenerationThread::request() const
QImage PixmapGenerationThread::image() const
{
return mImage;
return mRequest ? PixmapRequestPrivate::get(mRequest)->mResultImage : QImage();
}
bool PixmapGenerationThread::calcBoundingBox() const
......@@ -57,37 +57,49 @@ NormalizedRect PixmapGenerationThread::boundingBox() const
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
......@@ -95,12 +107,34 @@ 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;
}
}
......
......@@ -22,6 +22,7 @@
class QEventLoop;
class QMutex;
#include "generator.h"
#include "page.h"
namespace Okular {
......@@ -80,6 +81,8 @@ class PixmapRequestPrivate
void swap();
TilesManager *tilesManager() const;
static PixmapRequestPrivate *get(const PixmapRequest *req);
DocumentObserver *mObserver;
int mPageNumber;
int mWidth;
......@@ -91,6 +94,18 @@ class PixmapRequestPrivate
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 @@ class PixmapGenerationThread : public QThread
private:
Generator *mGenerator;
PixmapRequest *mRequest;
QImage mImage;
NormalizedRect mBoundingBox;
bool mCalcBoundingBox : 1;
};
......@@ -132,20 +146,24 @@ class TextPageGenerationThread : public QThread
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
......
......@@ -105,7 +105,7 @@ void PagePrivate::imageRotationDone( RotationJob * job )
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 @@ QLinkedList< FormField * > Page::formFields() const
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;
......
......@@ -134,10 +134,12 @@ class PagePrivate
*/
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;
......
......@@ -17,6 +17,7 @@ RotationJob::RotationJob( const QImage &image, Rotation oldRotation, Rotation ne
: ThreadWeaver::QObjectDecorator( new RotationJobInternal( image, oldRotation, newRotation ) )
, mObserver( observer ), m_pd( nullptr )
, mRect( NormalizedRect() )
, mIsPartialUpdate( false )
{
}
......@@ -30,6 +31,11 @@ void RotationJob::setRect( const NormalizedRect &rect )
mRect = rect;
}
void RotationJob::setIsPartialUpdate( bool partialUpdate )
{
mIsPartialUpdate = partialUpdate;
}
DocumentObserver * RotationJob::observer() const
{
return mObserver;
......@@ -45,6 +51,11 @@ NormalizedRect RotationJob::rect() const
return mRect;
}
bool RotationJob::isPartialUpdate() const
{
return mIsPartialUpdate;
}
QTransform RotationJob::rotationMatrix( Rotation from, Rotation to )
{
QTransform matrix;
......
......@@ -53,12 +53,14 @@ class RotationJob : public ThreadWeaver::QObjectDecorator
void setPage( PagePrivate * pd );
void setRect( const NormalizedRect &rect );
void setIsPartialUpdate( bool partialUpdate );
QImage image() const { return static_cast<const RotationJobInternal*>(job())->image(); }
Rotation rotation() const { return static_cast<const RotationJobInternal*>(job())->rotation(); }
DocumentObserver *observer() const;
PagePrivate * page() const;
NormalizedRect rect() const;
bool isPartialUpdate() const;
static QTransform rotationMatrix( Rotation from, Rotation to );
......@@ -66,6 +68,7 @@ class RotationJob : public ThreadWeaver::QObjectDecorator
DocumentObserver *mObserver;
PagePrivate * m_pd;
NormalizedRect mRect;
bool mIsPartialUpdate;
};
}
......
......@@ -426,10 +426,10 @@ QImage TextDocumentGeneratorPrivate::image( PixmapRequest * request )
return image;
}