Asynchronous calculation of document text layout

This commit is contained in:
Jakub Melka
2020-01-01 18:23:18 +01:00
parent c832c4ecef
commit e9481fc446
12 changed files with 313 additions and 81 deletions

View File

@ -18,6 +18,7 @@
#include "pdfcompiler.h" #include "pdfcompiler.h"
#include "pdfcms.h" #include "pdfcms.h"
#include "pdfdrawspacecontroller.h" #include "pdfdrawspacecontroller.h"
#include "pdfprogress.h"
#include <QtConcurrent/QtConcurrent> #include <QtConcurrent/QtConcurrent>
@ -74,7 +75,6 @@ void PDFAsynchronousPageCompiler::stop()
} }
m_tasks.clear(); m_tasks.clear();
m_cache.clear(); m_cache.clear();
m_textLayouts.dirty();
m_state = State::Inactive; m_state = State::Inactive;
break; break;
@ -121,7 +121,7 @@ const PDFPrecompiledPage* PDFAsynchronousPageCompiler::getCompiledPage(PDFIntege
return compiledPage; return compiledPage;
}; };
m_proxy->getFontCache()->setCacheShrinkEnabled(false); m_proxy->getFontCache()->setCacheShrinkEnabled(this, false);
CompileTask& task = m_tasks[pageIndex]; CompileTask& task = m_tasks[pageIndex];
task.taskFuture = QtConcurrent::run(compilePage); task.taskFuture = QtConcurrent::run(compilePage);
task.taskWatcher = new QFutureWatcher<PDFPrecompiledPage>(this); task.taskWatcher = new QFutureWatcher<PDFPrecompiledPage>(this);
@ -132,17 +132,6 @@ const PDFPrecompiledPage* PDFAsynchronousPageCompiler::getCompiledPage(PDFIntege
return page; return page;
} }
PDFTextLayout PDFAsynchronousPageCompiler::getTextLayout(PDFInteger pageIndex)
{
if (m_state != State::Active || !m_proxy->getDocument())
{
// Engine is not active, always return empty layout
return PDFTextLayout();
}
return m_textLayouts.get(this, &PDFAsynchronousPageCompiler::getTextLayoutsImpl).getTextLayout(pageIndex);
}
void PDFAsynchronousPageCompiler::onPageCompiled() void PDFAsynchronousPageCompiler::onPageCompiled()
{ {
std::vector<PDFInteger> compiledPages; std::vector<PDFInteger> compiledPages;
@ -182,7 +171,7 @@ void PDFAsynchronousPageCompiler::onPageCompiled()
} }
// We allow font cache shrinking, when we aren't doing something in parallel. // We allow font cache shrinking, when we aren't doing something in parallel.
m_proxy->getFontCache()->setCacheShrinkEnabled(m_tasks.empty()); m_proxy->getFontCache()->setCacheShrinkEnabled(this, m_tasks.empty());
if (!compiledPages.empty()) if (!compiledPages.empty())
{ {
@ -268,15 +257,118 @@ void PDFTextLayoutGenerator::performOutputCharacter(const PDFTextCharacterInfo&
m_textLayout.addCharacter(info); m_textLayout.addCharacter(info);
} }
PDFTextLayoutStorage PDFAsynchronousPageCompiler::getTextLayoutsImpl() PDFAsynchronousTextLayoutCompiler::PDFAsynchronousTextLayoutCompiler(PDFDrawWidgetProxy* proxy) :
BaseClass(proxy),
m_proxy(proxy)
{ {
m_proxy->getFontCache()->setCacheShrinkEnabled(false); connect(&m_textLayoutCompileFutureWatcher, &QFutureWatcher<PDFTextLayoutStorage>::finished, this, &PDFAsynchronousTextLayoutCompiler::onTextLayoutCreated);
}
void PDFAsynchronousTextLayoutCompiler::start()
{
switch (m_state)
{
case State::Inactive:
{
m_state = State::Active;
break;
}
case State::Active:
break; // We have nothing to do...
case State::Stopping:
{
// We shouldn't call this function while stopping!
Q_ASSERT(false);
break;
}
}
}
void PDFAsynchronousTextLayoutCompiler::stop()
{
switch (m_state)
{
case State::Inactive:
break; // We have nothing to do...
case State::Active:
{
// Stop the engine
m_state = State::Stopping;
m_textLayoutCompileFutureWatcher.waitForFinished();
m_textLayouts = std::nullopt;
m_state = State::Inactive;
break;
}
case State::Stopping:
{
// We shouldn't call this function while stopping!
Q_ASSERT(false);
break;
}
}
}
void PDFAsynchronousTextLayoutCompiler::reset()
{
stop();
start();
}
PDFTextLayout PDFAsynchronousTextLayoutCompiler::getTextLayout(PDFInteger pageIndex)
{
if (m_state != State::Active || !m_proxy->getDocument())
{
// Engine is not active, always return empty layout
return PDFTextLayout();
}
if (m_textLayouts)
{
return m_textLayouts->getTextLayout(pageIndex);
}
return PDFTextLayout();
}
void PDFAsynchronousTextLayoutCompiler::makeTextLayout()
{
if (m_state != State::Active || !m_proxy->getDocument())
{
// Engine is not active, do not calculate layout
return;
}
if (m_textLayouts.has_value())
{
// Value is computed already
return;
}
if (m_textLayoutCompileFuture.isRunning())
{
// Text layout is already being processed
return;
}
ProgressStartupInfo info;
info.showDialog = true;
info.text = tr("Generating text layouts for pages...");
m_proxy->getFontCache()->setCacheShrinkEnabled(this, false);
const PDFCatalog* catalog = m_proxy->getDocument()->getCatalog(); const PDFCatalog* catalog = m_proxy->getDocument()->getCatalog();
m_proxy->getProgress()->start(catalog->getPageCount(), qMove(info));
PDFCMSPointer cms = m_proxy->getCMSManager()->getCurrentCMS(); PDFCMSPointer cms = m_proxy->getCMSManager()->getCurrentCMS();
auto createTextLayout = [this, cms, catalog]() -> PDFTextLayoutStorage
{
PDFTextLayoutStorage result(catalog->getPageCount()); PDFTextLayoutStorage result(catalog->getPageCount());
QMutex mutex; QMutex mutex;
auto generateTextLayout = [this, &result, &mutex, &cms, catalog](PDFInteger pageIndex) auto generateTextLayout = [this, &result, &mutex, cms, catalog](PDFInteger pageIndex)
{ {
if (!catalog->getPage(pageIndex)) if (!catalog->getPage(pageIndex))
{ {
@ -291,15 +383,26 @@ PDFTextLayoutStorage PDFAsynchronousPageCompiler::getTextLayoutsImpl()
PDFTextLayoutGenerator generator(m_proxy->getFeatures(), page, m_proxy->getDocument(), m_proxy->getFontCache(), cms.data(), m_proxy->getOptionalContentActivity(), QMatrix(), m_proxy->getMeshQualitySettings()); PDFTextLayoutGenerator generator(m_proxy->getFeatures(), page, m_proxy->getDocument(), m_proxy->getFontCache(), cms.data(), m_proxy->getOptionalContentActivity(), QMatrix(), m_proxy->getMeshQualitySettings());
generator.processContents(); generator.processContents();
result.setTextLayout(pageIndex, generator.createTextLayout(), &mutex); result.setTextLayout(pageIndex, generator.createTextLayout(), &mutex);
m_proxy->getProgress()->step();
}; };
auto pageRange = PDFIntegerRange<PDFInteger>(0, catalog->getPageCount()); auto pageRange = PDFIntegerRange<PDFInteger>(0, catalog->getPageCount());
std::for_each(std::execution::parallel_policy(), pageRange.begin(), pageRange.end(), generateTextLayout); std::for_each(std::execution::parallel_policy(), pageRange.begin(), pageRange.end(), generateTextLayout);
// We allow font cache shrinking, when we aren't doing something in parallel.
m_proxy->getFontCache()->setCacheShrinkEnabled(m_tasks.empty());
return result; return result;
};
Q_ASSERT(!m_textLayoutCompileFuture.isRunning());
m_textLayoutCompileFuture = QtConcurrent::run(createTextLayout);
m_textLayoutCompileFutureWatcher.setFuture(m_textLayoutCompileFuture);
}
void PDFAsynchronousTextLayoutCompiler::onTextLayoutCreated()
{
m_proxy->getFontCache()->setCacheShrinkEnabled(this, true);
m_proxy->getProgress()->finish();
m_textLayouts = m_textLayoutCompileFuture.result();
emit textLayoutChanged();
} }
} // namespace pdf } // namespace pdf

View File

@ -73,11 +73,6 @@ public:
/// \param compile Compile the page, if it is not found in the cache /// \param compile Compile the page, if it is not found in the cache
const PDFPrecompiledPage* getCompiledPage(PDFInteger pageIndex, bool compile); const PDFPrecompiledPage* getCompiledPage(PDFInteger pageIndex, bool compile);
/// Returns text layout of the page. If page index is invalid,
/// then empty text layout is returned.
/// \param pageIndex Page index
PDFTextLayout getTextLayout(PDFInteger pageIndex);
signals: signals:
void pageImageChanged(bool all, const std::vector<PDFInteger>& pages); void pageImageChanged(bool all, const std::vector<PDFInteger>& pages);
void renderingError(PDFInteger pageIndex, const QList<PDFRenderError>& errors); void renderingError(PDFInteger pageIndex, const QList<PDFRenderError>& errors);
@ -85,9 +80,6 @@ signals:
private: private:
void onPageCompiled(); void onPageCompiled();
/// Returns text layouts for all pages
PDFTextLayoutStorage getTextLayoutsImpl();
struct CompileTask struct CompileTask
{ {
QFuture<PDFPrecompiledPage> taskFuture; QFuture<PDFPrecompiledPage> taskFuture;
@ -98,7 +90,57 @@ private:
State m_state = State::Inactive; State m_state = State::Inactive;
QCache<PDFInteger, PDFPrecompiledPage> m_cache; QCache<PDFInteger, PDFPrecompiledPage> m_cache;
std::map<PDFInteger, CompileTask> m_tasks; std::map<PDFInteger, CompileTask> m_tasks;
PDFCachedItem<PDFTextLayoutStorage> m_textLayouts; };
class PDFAsynchronousTextLayoutCompiler : public QObject
{
Q_OBJECT
private:
using BaseClass = QObject;
public:
explicit PDFAsynchronousTextLayoutCompiler(PDFDrawWidgetProxy* proxy);
/// Starts the engine. Call this function only if the engine
/// is stopped.
void start();
/// Stops the engine and all underlying asynchronous tasks. Also
/// clears the cache. Call this function only if engine is active.
void stop();
/// Resets the engine - calls stop and then calls start.
void reset();
enum class State
{
Inactive,
Active,
Stopping
};
/// Returns text layout of the page. If page index is invalid,
/// then empty text layout is returned.
/// \param pageIndex Page index
PDFTextLayout getTextLayout(PDFInteger pageIndex);
/// Create text layout for the document. Function is asynchronous,
/// it returns immediately. After text layout is created, signal
/// \p textLayoutChanged is emitted.
void makeTextLayout();
signals:
void textLayoutChanged();
private:
void onTextLayoutCreated();
PDFDrawWidgetProxy* m_proxy;
State m_state = State::Inactive;
std::optional<PDFTextLayoutStorage> m_textLayouts;
QFuture<PDFTextLayoutStorage> m_textLayoutCompileFuture;
QFutureWatcher<PDFTextLayoutStorage> m_textLayoutCompileFutureWatcher;
}; };
} // namespace pdf } // namespace pdf

View File

@ -269,7 +269,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
}; };
// Now, we are ready to scan all objects // Now, we are ready to scan all objects
progressStart(occupiedEntries.size()); progressStart(occupiedEntries.size(), PDFTranslationContext::tr("Reading contents of document..."));
std::for_each(std::execution::parallel_policy(), occupiedEntries.cbegin(), occupiedEntries.cend(), processEntry); std::for_each(std::execution::parallel_policy(), occupiedEntries.cbegin(), occupiedEntries.cend(), processEntry);
progressFinish(); progressFinish();
@ -369,7 +369,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
objects[entry.reference.objectNumber].object = securityHandler->decryptObject(objects[entry.reference.objectNumber].object, entry.reference); objects[entry.reference.objectNumber].object = securityHandler->decryptObject(objects[entry.reference.objectNumber].object, entry.reference);
}; };
progressStart(occupiedEntries.size()); progressStart(occupiedEntries.size(), PDFTranslationContext::tr("Decrypting encrypted contents of document..."));
std::for_each(std::execution::parallel_policy(), occupiedEntries.cbegin(), occupiedEntries.cend(), decryptEntry); std::for_each(std::execution::parallel_policy(), occupiedEntries.cbegin(), occupiedEntries.cend(), decryptEntry);
progressFinish(); progressFinish();
} }
@ -529,11 +529,15 @@ int PDFDocumentReader::findFromEnd(const char* what, const QByteArray& byteArray
return FIND_NOT_FOUND_RESULT; return FIND_NOT_FOUND_RESULT;
} }
void PDFDocumentReader::progressStart(size_t stepCount) void PDFDocumentReader::progressStart(size_t stepCount, QString text)
{ {
if (m_progress) if (m_progress)
{ {
m_progress->start(stepCount); ProgressStartupInfo info;
info.showDialog = !text.isEmpty();
info.text = qMove(text);
m_progress->start(stepCount, qMove(info));
} }
} }

View File

@ -87,7 +87,7 @@ private:
/// \returns Position of string, or FIND_NOT_FOUND_RESULT /// \returns Position of string, or FIND_NOT_FOUND_RESULT
int findFromEnd(const char* what, const QByteArray& byteArray, int limit); int findFromEnd(const char* what, const QByteArray& byteArray, int limit);
void progressStart(size_t stepCount); void progressStart(size_t stepCount, QString text);
void progressStep(); void progressStep();
void progressFinish(); void progressFinish();

View File

@ -59,6 +59,11 @@ void PDFDrawSpaceController::setDocument(const PDFDocument* document, const PDFO
} }
} }
void PDFDrawSpaceController::onOptionalContentGroupStateChanged()
{
emit pageImageChanged(true, { });
}
void PDFDrawSpaceController::setPageLayout(PageLayout pageLayout) void PDFDrawSpaceController::setPageLayout(PageLayout pageLayout)
{ {
if (m_pageLayoutMode != pageLayout) if (m_pageLayoutMode != pageLayout)
@ -138,11 +143,6 @@ QSizeF PDFDrawSpaceController::getReferenceBoundingBox() const
return rect.size(); return rect.size();
} }
void PDFDrawSpaceController::onOptionalContentGroupStateChanged()
{
emit pageImageChanged(true, { });
}
void PDFDrawSpaceController::recalculate() void PDFDrawSpaceController::recalculate()
{ {
if (!m_document) if (!m_document)
@ -400,7 +400,9 @@ PDFDrawWidgetProxy::PDFDrawWidgetProxy(QObject* parent) :
m_verticalScrollbar(nullptr), m_verticalScrollbar(nullptr),
m_features(PDFRenderer::getDefaultFeatures()), m_features(PDFRenderer::getDefaultFeatures()),
m_compiler(new PDFAsynchronousPageCompiler(this)), m_compiler(new PDFAsynchronousPageCompiler(this)),
m_rasterizer(new PDFRasterizer(this)) m_textLayoutCompiler(new PDFAsynchronousTextLayoutCompiler(this)),
m_rasterizer(new PDFRasterizer(this)),
m_progress(nullptr)
{ {
m_controller = new PDFDrawSpaceController(this); m_controller = new PDFDrawSpaceController(this);
connect(m_controller, &PDFDrawSpaceController::drawSpaceChanged, this, &PDFDrawWidgetProxy::update); connect(m_controller, &PDFDrawSpaceController::drawSpaceChanged, this, &PDFDrawWidgetProxy::update);
@ -408,6 +410,7 @@ PDFDrawWidgetProxy::PDFDrawWidgetProxy(QObject* parent) :
connect(m_controller, &PDFDrawSpaceController::pageImageChanged, this, &PDFDrawWidgetProxy::pageImageChanged); connect(m_controller, &PDFDrawSpaceController::pageImageChanged, this, &PDFDrawWidgetProxy::pageImageChanged);
connect(m_compiler, &PDFAsynchronousPageCompiler::renderingError, this, &PDFDrawWidgetProxy::renderingError); connect(m_compiler, &PDFAsynchronousPageCompiler::renderingError, this, &PDFDrawWidgetProxy::renderingError);
connect(m_compiler, &PDFAsynchronousPageCompiler::pageImageChanged, this, &PDFDrawWidgetProxy::pageImageChanged); connect(m_compiler, &PDFAsynchronousPageCompiler::pageImageChanged, this, &PDFDrawWidgetProxy::pageImageChanged);
connect(m_textLayoutCompiler, &PDFAsynchronousTextLayoutCompiler::textLayoutChanged, this, &PDFDrawWidgetProxy::onTextLayoutChanged);
} }
PDFDrawWidgetProxy::~PDFDrawWidgetProxy() PDFDrawWidgetProxy::~PDFDrawWidgetProxy()
@ -418,8 +421,10 @@ PDFDrawWidgetProxy::~PDFDrawWidgetProxy()
void PDFDrawWidgetProxy::setDocument(const PDFDocument* document, const PDFOptionalContentActivity* optionalContentActivity) void PDFDrawWidgetProxy::setDocument(const PDFDocument* document, const PDFOptionalContentActivity* optionalContentActivity)
{ {
m_compiler->stop(); m_compiler->stop();
m_textLayoutCompiler->stop();
m_controller->setDocument(document, optionalContentActivity); m_controller->setDocument(document, optionalContentActivity);
m_compiler->start(); m_compiler->start();
m_textLayoutCompiler->start();
} }
void PDFDrawWidgetProxy::init(PDFWidget* widget) void PDFDrawWidgetProxy::init(PDFWidget* widget)
@ -626,7 +631,8 @@ void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect)
// Draw text blocks/text lines, if it is enabled // Draw text blocks/text lines, if it is enabled
if (m_features.testFlag(PDFRenderer::DebugTextBlocks)) if (m_features.testFlag(PDFRenderer::DebugTextBlocks))
{ {
PDFTextLayout layout = m_compiler->getTextLayout(item.pageIndex); m_textLayoutCompiler->makeTextLayout();
PDFTextLayout layout = m_textLayoutCompiler->getTextLayout(item.pageIndex);
const PDFTextBlocks& textBlocks = layout.getTextBlocks(); const PDFTextBlocks& textBlocks = layout.getTextBlocks();
painter->save(); painter->save();
@ -648,7 +654,8 @@ void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect)
} }
if (m_features.testFlag(PDFRenderer::DebugTextLines)) if (m_features.testFlag(PDFRenderer::DebugTextLines))
{ {
PDFTextLayout layout = m_compiler->getTextLayout(item.pageIndex); m_textLayoutCompiler->makeTextLayout();
PDFTextLayout layout = m_textLayoutCompiler->getTextLayout(item.pageIndex);
const PDFTextBlocks& textBlocks = layout.getTextBlocks(); const PDFTextBlocks& textBlocks = layout.getTextBlocks();
painter->save(); painter->save();
@ -989,6 +996,11 @@ QRectF PDFDrawWidgetProxy::fromDeviceSpace(const QRectF& rect) const
rect.height() * m_deviceSpaceUnitToPixel); rect.height() * m_deviceSpaceUnitToPixel);
} }
void PDFDrawWidgetProxy::onTextLayoutChanged()
{
emit repaintNeeded();
}
bool PDFDrawWidgetProxy::isBlockMode() const bool PDFDrawWidgetProxy::isBlockMode() const
{ {
switch (m_controller->getPageLayout()) switch (m_controller->getPageLayout())
@ -1137,8 +1149,10 @@ void PDFDrawWidgetProxy::setFeatures(PDFRenderer::Features features)
if (m_features != features) if (m_features != features)
{ {
m_compiler->stop(); m_compiler->stop();
m_textLayoutCompiler->stop();
m_features = features; m_features = features;
m_compiler->start(); m_compiler->start();
m_textLayoutCompiler->start();
emit pageImageChanged(true, { }); emit pageImageChanged(true, { });
} }
} }

View File

@ -32,10 +32,12 @@ class QScrollBar;
namespace pdf namespace pdf
{ {
class PDFProgress;
class PDFWidget; class PDFWidget;
class IDrawWidget; class IDrawWidget;
class PDFCMSManager; class PDFCMSManager;
class PDFAsynchronousPageCompiler; class PDFAsynchronousPageCompiler;
class PDFAsynchronousTextLayoutCompiler;
/// This class controls draw space - page layout. Pages are divided into blocks /// This class controls draw space - page layout. Pages are divided into blocks
/// each block can contain one or multiple pages. Units are in milimeters. /// each block can contain one or multiple pages. Units are in milimeters.
@ -274,6 +276,8 @@ public:
const PDFMeshQualitySettings& getMeshQualitySettings() const { return m_meshQualitySettings; } const PDFMeshQualitySettings& getMeshQualitySettings() const { return m_meshQualitySettings; }
PDFAsynchronousPageCompiler* getCompiler() const { return m_compiler; } PDFAsynchronousPageCompiler* getCompiler() const { return m_compiler; }
const PDFCMSManager* getCMSManager() const; const PDFCMSManager* getCMSManager() const;
PDFProgress* getProgress() const { return m_progress; }
void setProgress(PDFProgress* progress) { m_progress = progress; }
void setFeatures(PDFRenderer::Features features); void setFeatures(PDFRenderer::Features features);
void setPreferredMeshResolutionRatio(PDFReal ratio); void setPreferredMeshResolutionRatio(PDFReal ratio);
@ -326,6 +330,7 @@ private:
/// Converts rectangle from device space to the pixel space /// Converts rectangle from device space to the pixel space
QRectF fromDeviceSpace(const QRectF& rect) const; QRectF fromDeviceSpace(const QRectF& rect) const;
void onTextLayoutChanged();
void onColorManagementSystemChanged(); void onColorManagementSystemChanged();
void onHorizontalScrollbarValueChanged(int value); void onHorizontalScrollbarValueChanged(int value);
void onVerticalScrollbarValueChanged(int value); void onVerticalScrollbarValueChanged(int value);
@ -408,8 +413,14 @@ private:
/// Page compiler /// Page compiler
PDFAsynchronousPageCompiler* m_compiler; PDFAsynchronousPageCompiler* m_compiler;
/// Text layout compiler
PDFAsynchronousTextLayoutCompiler* m_textLayoutCompiler;
/// Page image rasterizer for thumbnails /// Page image rasterizer for thumbnails
PDFRasterizer* m_rasterizer; PDFRasterizer* m_rasterizer;
/// Progress
PDFProgress* m_progress;
}; };
} // namespace pdf } // namespace pdf

View File

@ -1610,7 +1610,7 @@ PDFFontPointer PDFFontCache::getFont(const PDFObject& fontObject) const
// We must create the font // We must create the font
PDFFontPointer font = PDFFont::createFont(fontObject, m_document); PDFFontPointer font = PDFFont::createFont(fontObject, m_document);
if (m_fontCacheShrinkEnabled && m_fontCache.size() >= m_fontCacheLimit) if (m_fontCacheShrinkDisabledObjects.empty() && m_fontCache.size() >= m_fontCacheLimit)
{ {
// We have exceeded the cache limit. Clear the cache. // We have exceeded the cache limit. Clear the cache.
m_fontCache.clear(); m_fontCache.clear();
@ -1638,7 +1638,7 @@ PDFRealizedFontPointer PDFFontCache::getRealizedFont(const PDFFontPointer& font,
// We must create the realized font // We must create the realized font
PDFRealizedFontPointer realizedFont = PDFRealizedFont::createRealizedFont(font, size, reporter); PDFRealizedFontPointer realizedFont = PDFRealizedFont::createRealizedFont(font, size, reporter);
if (m_fontCacheShrinkEnabled && m_realizedFontCache.size() >= m_realizedFontCacheLimit) if (m_fontCacheShrinkDisabledObjects.empty() && m_realizedFontCache.size() >= m_realizedFontCacheLimit)
{ {
m_realizedFontCache.clear(); m_realizedFontCache.clear();
} }
@ -1649,13 +1649,19 @@ PDFRealizedFontPointer PDFFontCache::getRealizedFont(const PDFFontPointer& font,
return it->second; return it->second;
} }
void PDFFontCache::setCacheShrinkEnabled(bool enabled) void PDFFontCache::setCacheShrinkEnabled(void* source, bool enabled)
{ {
if (m_fontCacheShrinkEnabled != enabled) QMutexLocker lock(&m_mutex);
if (enabled)
{ {
m_fontCacheShrinkEnabled = enabled; m_fontCacheShrinkDisabledObjects.erase(source);
lock.unlock();
shrink(); shrink();
} }
else
{
m_fontCacheShrinkDisabledObjects.insert(source);
}
} }
void PDFFontCache::setCacheLimits(int fontCacheLimit, int instancedFontCacheLimit) void PDFFontCache::setCacheLimits(int fontCacheLimit, int instancedFontCacheLimit)
@ -1669,10 +1675,10 @@ void PDFFontCache::setCacheLimits(int fontCacheLimit, int instancedFontCacheLimi
} }
void PDFFontCache::shrink() void PDFFontCache::shrink()
{
if (m_fontCacheShrinkEnabled)
{ {
QMutexLocker lock(&m_mutex); QMutexLocker lock(&m_mutex);
if (m_fontCacheShrinkDisabledObjects.empty())
{
if (m_fontCache.size() >= m_fontCacheLimit) if (m_fontCache.size() >= m_fontCacheLimit)
{ {
m_fontCache.clear(); m_fontCache.clear();

View File

@ -27,6 +27,7 @@
#include <QSharedPointer> #include <QSharedPointer>
#include <QTreeWidgetItem> #include <QTreeWidgetItem>
#include <set>
#include <unordered_map> #include <unordered_map>
class QPainterPath; class QPainterPath;
@ -388,7 +389,6 @@ class PDFFontCache
{ {
public: public:
inline explicit PDFFontCache(size_t fontCacheLimit, size_t realizedFontCacheLimit) : inline explicit PDFFontCache(size_t fontCacheLimit, size_t realizedFontCacheLimit) :
m_fontCacheShrinkEnabled(true),
m_fontCacheLimit(fontCacheLimit), m_fontCacheLimit(fontCacheLimit),
m_realizedFontCacheLimit(realizedFontCacheLimit), m_realizedFontCacheLimit(realizedFontCacheLimit),
m_document(nullptr) m_document(nullptr)
@ -414,7 +414,12 @@ public:
/// Sets or unsets font shrinking (i.e. font can be deleted from the cache). In multithreading environment, /// Sets or unsets font shrinking (i.e. font can be deleted from the cache). In multithreading environment,
/// font deletion is not thread safe. For this reason, disable font deletion by calling this function. /// font deletion is not thread safe. For this reason, disable font deletion by calling this function.
void setCacheShrinkEnabled(bool enabled); /// First parameter, \p source determines which object enables cache shrinking (so some objects can
/// enable shrinking, while some objects will disable it). Only if all objects enables cache shrinking,
/// then cache can shrink.
/// \param source Source object
/// \param enabled Enable or disable cache shrinking
void setCacheShrinkEnabled(void* source, bool enabled);
/// Set font cache limits /// Set font cache limits
void setCacheLimits(int fontCacheLimit, int instancedFontCacheLimit); void setCacheLimits(int fontCacheLimit, int instancedFontCacheLimit);
@ -423,13 +428,13 @@ public:
void shrink(); void shrink();
private: private:
bool m_fontCacheShrinkEnabled;
size_t m_fontCacheLimit; size_t m_fontCacheLimit;
size_t m_realizedFontCacheLimit; size_t m_realizedFontCacheLimit;
mutable QMutex m_mutex; mutable QMutex m_mutex;
const PDFDocument* m_document; const PDFDocument* m_document;
mutable std::map<PDFObjectReference, PDFFontPointer> m_fontCache; mutable std::map<PDFObjectReference, PDFFontPointer> m_fontCache;
mutable std::map<std::pair<PDFFontPointer, PDFReal>, PDFRealizedFontPointer> m_realizedFontCache; mutable std::map<std::pair<PDFFontPointer, PDFReal>, PDFRealizedFontPointer> m_realizedFontCache;
mutable std::set<void*> m_fontCacheShrinkDisabledObjects;
}; };
/// Performs mapping from CID to GID (even identity mapping, if byte array is empty) /// Performs mapping from CID to GID (even identity mapping, if byte array is empty)

View File

@ -20,7 +20,13 @@
namespace pdf namespace pdf
{ {
void PDFProgress::start(size_t stepCount) PDFProgress::PDFProgress(QObject* parent) :
QObject(parent)
{
qRegisterMetaType<pdf::ProgressStartupInfo>();
}
void PDFProgress::start(size_t stepCount, ProgressStartupInfo startupInfo)
{ {
Q_ASSERT(stepCount > 0); Q_ASSERT(stepCount > 0);
@ -28,7 +34,7 @@ void PDFProgress::start(size_t stepCount)
m_stepCount = stepCount; m_stepCount = stepCount;
m_percentage = 0; m_percentage = 0;
emit progressStarted(); emit progressStarted(qMove(startupInfo));
} }
void PDFProgress::step() void PDFProgress::step()

View File

@ -27,19 +27,25 @@
namespace pdf namespace pdf
{ {
struct ProgressStartupInfo
{
bool showDialog = false;
QString text;
};
class PDFFORQTLIBSHARED_EXPORT PDFProgress : public QObject class PDFFORQTLIBSHARED_EXPORT PDFProgress : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit PDFProgress(QObject* parent) : QObject(parent) { } explicit PDFProgress(QObject* parent);
void start(size_t stepCount); void start(size_t stepCount, ProgressStartupInfo startupInfo);
void step(); void step();
void finish(); void finish();
signals: signals:
void progressStarted(); void progressStarted(ProgressStartupInfo info);
void progressStep(int percentage); void progressStep(int percentage);
void progressFinished(); void progressFinished();
@ -49,6 +55,8 @@ private:
std::atomic<int> m_percentage = 0; std::atomic<int> m_percentage = 0;
}; };
Q_DECLARE_METATYPE(ProgressStartupInfo)
} // namespace pdf } // namespace pdf
#endif // PDFPROGRESS_H #endif // PDFPROGRESS_H

View File

@ -73,7 +73,9 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
m_isLoadingUI(false), m_isLoadingUI(false),
m_progress(new pdf::PDFProgress(this)), m_progress(new pdf::PDFProgress(this)),
m_taskbarButton(new QWinTaskbarButton(this)), m_taskbarButton(new QWinTaskbarButton(this)),
m_progressTaskbarIndicator(nullptr) m_progressTaskbarIndicator(nullptr),
m_progressDialog(nullptr),
m_isBusy(false)
{ {
ui->setupUi(this); ui->setupUi(this);
@ -159,6 +161,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
setCentralWidget(m_pdfWidget); setCentralWidget(m_pdfWidget);
setFocusProxy(m_pdfWidget); setFocusProxy(m_pdfWidget);
m_pdfWidget->updateCacheLimits(m_settings->getCompiledPageCacheLimit() * 1024, m_settings->getThumbnailsCacheLimit(), m_settings->getFontCacheLimit(), m_settings->getInstancedFontCacheLimit()); m_pdfWidget->updateCacheLimits(m_settings->getCompiledPageCacheLimit() * 1024, m_settings->getThumbnailsCacheLimit(), m_settings->getFontCacheLimit(), m_settings->getInstancedFontCacheLimit());
m_pdfWidget->getDrawWidgetProxy()->setProgress(m_progress);
m_sidebarWidget = new PDFSidebarWidget(m_pdfWidget->getDrawWidgetProxy(), this); m_sidebarWidget = new PDFSidebarWidget(m_pdfWidget->getDrawWidgetProxy(), this);
m_sidebarDockWidget = new QDockWidget(tr("Sidebar"), this); m_sidebarDockWidget = new QDockWidget(tr("Sidebar"), this);
@ -507,21 +510,46 @@ void PDFViewerMainWindow::onActionTriggered(const pdf::PDFAction* action)
} }
} }
void PDFViewerMainWindow::onProgressStarted() void PDFViewerMainWindow::onProgressStarted(pdf::ProgressStartupInfo info)
{ {
Q_ASSERT(!m_progressDialog);
if (info.showDialog)
{
m_progressDialog = new QProgressDialog(info.text, QString(), 0, 100, this);
m_progressDialog->setWindowModality(Qt::WindowModal);
m_progressDialog->setCancelButton(nullptr);
}
m_progressTaskbarIndicator->setRange(0, 100); m_progressTaskbarIndicator->setRange(0, 100);
m_progressTaskbarIndicator->reset(); m_progressTaskbarIndicator->reset();
m_progressTaskbarIndicator->show(); m_progressTaskbarIndicator->show();
m_isBusy = true;
updateActionsAvailability();
} }
void PDFViewerMainWindow::onProgressStep(int percentage) void PDFViewerMainWindow::onProgressStep(int percentage)
{ {
if (m_progressDialog)
{
m_progressDialog->setValue(percentage);
}
m_progressTaskbarIndicator->setValue(percentage); m_progressTaskbarIndicator->setValue(percentage);
} }
void PDFViewerMainWindow::onProgressFinished() void PDFViewerMainWindow::onProgressFinished()
{ {
if (m_progressDialog)
{
m_progressDialog->hide();
m_progressDialog->deleteLater();
m_progressDialog = nullptr;
}
m_progressTaskbarIndicator->hide(); m_progressTaskbarIndicator->hide();
m_isBusy = false;
updateActionsAvailability();
} }
void PDFViewerMainWindow::readSettings() void PDFViewerMainWindow::readSettings()
@ -697,19 +725,20 @@ void PDFViewerMainWindow::updateUI(bool fullUpdate)
void PDFViewerMainWindow::updateActionsAvailability() void PDFViewerMainWindow::updateActionsAvailability()
{ {
const bool isReading = m_futureWatcher.isRunning(); const bool isBusy = m_futureWatcher.isRunning() || m_isBusy;
const bool hasDocument = m_pdfDocument; const bool hasDocument = m_pdfDocument;
const bool hasValidDocument = !isReading && hasDocument; const bool hasValidDocument = !isBusy && hasDocument;
ui->actionOpen->setEnabled(!isReading); ui->actionOpen->setEnabled(!isBusy);
ui->actionClose->setEnabled(hasValidDocument); ui->actionClose->setEnabled(hasValidDocument);
ui->actionQuit->setEnabled(!isReading); ui->actionQuit->setEnabled(!isBusy);
ui->actionOptions->setEnabled(!isReading); ui->actionOptions->setEnabled(!isBusy);
ui->actionAbout->setEnabled(!isReading); ui->actionAbout->setEnabled(!isBusy);
ui->actionFitPage->setEnabled(hasValidDocument); ui->actionFitPage->setEnabled(hasValidDocument);
ui->actionFitWidth->setEnabled(hasValidDocument); ui->actionFitWidth->setEnabled(hasValidDocument);
ui->actionFitHeight->setEnabled(hasValidDocument); ui->actionFitHeight->setEnabled(hasValidDocument);
ui->actionRendering_Errors->setEnabled(hasValidDocument); ui->actionRendering_Errors->setEnabled(hasValidDocument);
setEnabled(!isBusy);
} }
void PDFViewerMainWindow::onViewerSettingsChanged() void PDFViewerMainWindow::onViewerSettingsChanged()

View File

@ -33,6 +33,7 @@
#include <QWinTaskbarButton> #include <QWinTaskbarButton>
#include <QWinTaskbarProgress> #include <QWinTaskbarProgress>
#include <QFutureWatcher> #include <QFutureWatcher>
#include <QProgressDialog>
class QLabel; class QLabel;
class QSpinBox; class QSpinBox;
@ -99,7 +100,7 @@ private:
void onPageZoomSpinboxEditingFinished(); void onPageZoomSpinboxEditingFinished();
void onActionTriggered(const pdf::PDFAction* action); void onActionTriggered(const pdf::PDFAction* action);
void onProgressStarted(); void onProgressStarted(pdf::ProgressStartupInfo info);
void onProgressStep(int percentage); void onProgressStep(int percentage);
void onProgressFinished(); void onProgressFinished();
@ -156,6 +157,9 @@ private:
QFuture<AsyncReadingResult> m_future; QFuture<AsyncReadingResult> m_future;
QFutureWatcher<AsyncReadingResult> m_futureWatcher; QFutureWatcher<AsyncReadingResult> m_futureWatcher;
QProgressDialog* m_progressDialog;
bool m_isBusy;
}; };
} // namespace pdfviewer } // namespace pdfviewer