mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Issue #10: Remove too old pages from cache
This commit is contained in:
@ -217,7 +217,8 @@ const PDFPrecompiledPage* PDFAsynchronousPageCompiler::getCompiledPage(PDFIntege
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PDFPrecompiledPage* page = m_cache.object(pageIndex);
|
PDFPrecompiledPage* page = m_cache.object(pageIndex);
|
||||||
|
|
||||||
if (!page && compile)
|
if (!page && compile)
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&m_mutex);
|
QMutexLocker locker(&m_mutex);
|
||||||
@ -228,9 +229,43 @@ const PDFPrecompiledPage* PDFAsynchronousPageCompiler::getCompiledPage(PDFIntege
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (page)
|
||||||
|
{
|
||||||
|
page->markAccessed();
|
||||||
|
}
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PDFAsynchronousPageCompiler::smartClearCache(const int milisecondsLimit, const std::vector<PDFInteger>& activePages)
|
||||||
|
{
|
||||||
|
if (m_state != State::Active)
|
||||||
|
{
|
||||||
|
// Jakub Melka: Cache clearing can be done only in active state
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
|
||||||
|
Q_ASSERT(std::is_sorted(activePages.cbegin(), activePages.cend()));
|
||||||
|
|
||||||
|
QList<PDFInteger> pageIndices = m_cache.keys();
|
||||||
|
for (const PDFInteger pageIndex : pageIndices)
|
||||||
|
{
|
||||||
|
if (std::binary_search(activePages.cbegin(), activePages.cend(), pageIndex))
|
||||||
|
{
|
||||||
|
// We do not remove active page
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PDFPrecompiledPage* page = m_cache.object(pageIndex);
|
||||||
|
if (page && page->hasExpired(milisecondsLimit))
|
||||||
|
{
|
||||||
|
m_cache.remove(pageIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PDFAsynchronousPageCompiler::onPageCompiled()
|
void PDFAsynchronousPageCompiler::onPageCompiled()
|
||||||
{
|
{
|
||||||
std::vector<PDFInteger> compiledPages;
|
std::vector<PDFInteger> compiledPages;
|
||||||
@ -249,6 +284,7 @@ void PDFAsynchronousPageCompiler::onPageCompiled()
|
|||||||
{
|
{
|
||||||
// If we are in active state, try to store precompiled page
|
// If we are in active state, try to store precompiled page
|
||||||
PDFPrecompiledPage* page = new PDFPrecompiledPage(std::move(task.precompiledPage));
|
PDFPrecompiledPage* page = new PDFPrecompiledPage(std::move(task.precompiledPage));
|
||||||
|
page->markAccessed();
|
||||||
qint64 memoryConsumptionEstimate = page->getMemoryConsumptionEstimate();
|
qint64 memoryConsumptionEstimate = page->getMemoryConsumptionEstimate();
|
||||||
if (m_cache.insert(it->first, page, memoryConsumptionEstimate))
|
if (m_cache.insert(it->first, page, memoryConsumptionEstimate))
|
||||||
{
|
{
|
||||||
|
@ -106,6 +106,13 @@ 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);
|
||||||
|
|
||||||
|
/// Performs smart cache clear. Too old pages are removed from the cache,
|
||||||
|
/// but only if these pages are not in active pages. Use this function to
|
||||||
|
/// clear cache to avoid huge memory consumption.
|
||||||
|
/// \param milisecondsLimit Pages with access time above this limit will be erased
|
||||||
|
/// \param activePages Sorted vector of active pages, which should remain in cache
|
||||||
|
void smartClearCache(const int milisecondsLimit, const std::vector<PDFInteger>& activePages);
|
||||||
|
|
||||||
/// Is operation being cancelled?
|
/// Is operation being cancelled?
|
||||||
virtual bool isOperationCancelled() const override;
|
virtual bool isOperationCancelled() const override;
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include "pdfannotation.h"
|
#include "pdfannotation.h"
|
||||||
#include "pdfdbgheap.h"
|
#include "pdfdbgheap.h"
|
||||||
|
|
||||||
|
#include <QTimer>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QFontMetrics>
|
#include <QFontMetrics>
|
||||||
|
|
||||||
@ -464,6 +465,7 @@ PDFDrawWidgetProxy::PDFDrawWidgetProxy(QObject* parent) :
|
|||||||
m_textLayoutCompiler(new PDFAsynchronousTextLayoutCompiler(this)),
|
m_textLayoutCompiler(new PDFAsynchronousTextLayoutCompiler(this)),
|
||||||
m_rasterizer(new PDFRasterizer(this)),
|
m_rasterizer(new PDFRasterizer(this)),
|
||||||
m_progress(nullptr),
|
m_progress(nullptr),
|
||||||
|
m_cacheClearTimer(new QTimer(this)),
|
||||||
m_useOpenGL(false)
|
m_useOpenGL(false)
|
||||||
{
|
{
|
||||||
m_controller = new PDFDrawSpaceController(this);
|
m_controller = new PDFDrawSpaceController(this);
|
||||||
@ -473,6 +475,7 @@ PDFDrawWidgetProxy::PDFDrawWidgetProxy(QObject* parent) :
|
|||||||
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);
|
connect(m_textLayoutCompiler, &PDFAsynchronousTextLayoutCompiler::textLayoutChanged, this, &PDFDrawWidgetProxy::onTextLayoutChanged);
|
||||||
|
connect(m_cacheClearTimer, &QTimer::timeout, this, &PDFDrawWidgetProxy::performPageCacheClear);
|
||||||
}
|
}
|
||||||
|
|
||||||
PDFDrawWidgetProxy::~PDFDrawWidgetProxy()
|
PDFDrawWidgetProxy::~PDFDrawWidgetProxy()
|
||||||
@ -484,6 +487,7 @@ void PDFDrawWidgetProxy::setDocument(const PDFModifiedDocument& document)
|
|||||||
{
|
{
|
||||||
if (getDocument() != document)
|
if (getDocument() != document)
|
||||||
{
|
{
|
||||||
|
m_cacheClearTimer->stop();
|
||||||
m_compiler->stop(document.hasReset());
|
m_compiler->stop(document.hasReset());
|
||||||
m_textLayoutCompiler->stop(document.hasReset());
|
m_textLayoutCompiler->stop(document.hasReset());
|
||||||
m_controller->setDocument(document);
|
m_controller->setDocument(document);
|
||||||
@ -495,6 +499,11 @@ void PDFDrawWidgetProxy::setDocument(const PDFModifiedDocument& document)
|
|||||||
|
|
||||||
m_compiler->start();
|
m_compiler->start();
|
||||||
m_textLayoutCompiler->start();
|
m_textLayoutCompiler->start();
|
||||||
|
|
||||||
|
if (document)
|
||||||
|
{
|
||||||
|
m_cacheClearTimer->start(CACHE_CLEAR_TIMEOUT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -951,6 +960,29 @@ std::vector<PDFInteger> PDFDrawWidgetProxy::getPagesIntersectingRect(QRect rect)
|
|||||||
return pages;
|
return pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<PDFInteger> PDFDrawWidgetProxy::getActivePages() const
|
||||||
|
{
|
||||||
|
std::vector<PDFInteger> activePages = getPagesIntersectingRect(m_widget->rect());
|
||||||
|
|
||||||
|
// Consider page prefetching - at least two pages after last current
|
||||||
|
// pages are treated as active.
|
||||||
|
if (!activePages.empty())
|
||||||
|
{
|
||||||
|
if (const PDFDocument* document = getDocument())
|
||||||
|
{
|
||||||
|
const PDFInteger pageIndex = activePages.back();
|
||||||
|
const PDFInteger pageCount = document->getCatalog()->getPageCount();
|
||||||
|
const PDFInteger pageEnd = qMin(pageCount, pageIndex + 3);
|
||||||
|
for (PDFInteger i = pageIndex + 1; i < pageEnd; ++i)
|
||||||
|
{
|
||||||
|
activePages.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return activePages;
|
||||||
|
}
|
||||||
|
|
||||||
PDFInteger PDFDrawWidgetProxy::getPageUnderPoint(QPoint point, QPointF* pagePoint) const
|
PDFInteger PDFDrawWidgetProxy::getPageUnderPoint(QPoint point, QPointF* pagePoint) const
|
||||||
{
|
{
|
||||||
// Iterate trough pages, place them and test, if they intersects with rectangle
|
// Iterate trough pages, place them and test, if they intersects with rectangle
|
||||||
@ -1259,6 +1291,12 @@ QRectF PDFDrawWidgetProxy::fromDeviceSpace(const QRectF& rect) const
|
|||||||
rect.height() * m_deviceSpaceUnitToPixel);
|
rect.height() * m_deviceSpaceUnitToPixel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PDFDrawWidgetProxy::performPageCacheClear()
|
||||||
|
{
|
||||||
|
std::vector<PDFInteger> activePage = getActivePages();
|
||||||
|
m_compiler->smartClearCache(CACHE_PAGE_EXPIRATION_TIMEOUT, activePage);
|
||||||
|
}
|
||||||
|
|
||||||
void PDFDrawWidgetProxy::onTextLayoutChanged()
|
void PDFDrawWidgetProxy::onTextLayoutChanged()
|
||||||
{
|
{
|
||||||
emit repaintNeeded();
|
emit repaintNeeded();
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
class QPainter;
|
class QPainter;
|
||||||
class QScrollBar;
|
class QScrollBar;
|
||||||
|
class QTimer;
|
||||||
|
|
||||||
namespace pdf
|
namespace pdf
|
||||||
{
|
{
|
||||||
@ -307,6 +308,9 @@ public:
|
|||||||
/// \param rect Rectangle to test
|
/// \param rect Rectangle to test
|
||||||
std::vector<PDFInteger> getPagesIntersectingRect(QRect rect) const;
|
std::vector<PDFInteger> getPagesIntersectingRect(QRect rect) const;
|
||||||
|
|
||||||
|
/// Returns sorted vector of page indices, which should remain in the cache
|
||||||
|
std::vector<PDFInteger> getActivePages() const;
|
||||||
|
|
||||||
/// Returns page, under which is point. If no page is under the point,
|
/// Returns page, under which is point. If no page is under the point,
|
||||||
/// then -1 is returned. Point is in widget coordinates. If \p pagePoint
|
/// then -1 is returned. Point is in widget coordinates. If \p pagePoint
|
||||||
/// is not nullptr, then point in page coordinate space is set here.
|
/// is not nullptr, then point in page coordinate space is set here.
|
||||||
@ -431,9 +435,14 @@ private:
|
|||||||
static constexpr PDFReal MIN_ZOOM = 8.0 / 100.0;
|
static constexpr PDFReal MIN_ZOOM = 8.0 / 100.0;
|
||||||
static constexpr PDFReal MAX_ZOOM = 6400.0 / 100.0;
|
static constexpr PDFReal MAX_ZOOM = 6400.0 / 100.0;
|
||||||
|
|
||||||
|
static constexpr qint64 CACHE_CLEAR_TIMEOUT = 5000;
|
||||||
|
static constexpr qint64 CACHE_PAGE_EXPIRATION_TIMEOUT = 30000;
|
||||||
|
|
||||||
/// 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 performPageCacheClear();
|
||||||
|
|
||||||
void onTextLayoutChanged();
|
void onTextLayoutChanged();
|
||||||
void onOptionalContentGroupStateChanged();
|
void onOptionalContentGroupStateChanged();
|
||||||
void onColorManagementSystemChanged();
|
void onColorManagementSystemChanged();
|
||||||
@ -529,6 +538,9 @@ private:
|
|||||||
/// Progress
|
/// Progress
|
||||||
PDFProgress* m_progress;
|
PDFProgress* m_progress;
|
||||||
|
|
||||||
|
/// Cache clear timer
|
||||||
|
QTimer* m_cacheClearTimer;
|
||||||
|
|
||||||
/// Additional drawing interfaces
|
/// Additional drawing interfaces
|
||||||
std::set<IDocumentDrawInterface*> m_drawInterfaces;
|
std::set<IDocumentDrawInterface*> m_drawInterfaces;
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
#include <QPen>
|
#include <QPen>
|
||||||
#include <QBrush>
|
#include <QBrush>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
|
||||||
namespace pdf
|
namespace pdf
|
||||||
{
|
{
|
||||||
@ -239,6 +240,16 @@ public:
|
|||||||
PDFSnapInfo* getSnapInfo() { return &m_snapInfo; }
|
PDFSnapInfo* getSnapInfo() { return &m_snapInfo; }
|
||||||
const PDFSnapInfo* getSnapInfo() const { return &m_snapInfo; }
|
const PDFSnapInfo* getSnapInfo() const { return &m_snapInfo; }
|
||||||
|
|
||||||
|
/// Mark this precompiled page as accessed at a current time
|
||||||
|
void markAccessed() { m_expirationTimer.start(); }
|
||||||
|
|
||||||
|
/// Has page content expired with given timeout? This function
|
||||||
|
/// is used together with function \p markAccessed to control
|
||||||
|
/// cached pages expiration policy. Pages can be marked as accessed,
|
||||||
|
/// and too old accessed pages can be removed.
|
||||||
|
/// \sa markAccessed
|
||||||
|
bool hasExpired(qint64 timeout) const { return m_expirationTimer.hasExpired(timeout); }
|
||||||
|
|
||||||
struct GraphicPieceInfo
|
struct GraphicPieceInfo
|
||||||
{
|
{
|
||||||
enum class Type
|
enum class Type
|
||||||
@ -347,6 +358,7 @@ private:
|
|||||||
std::vector<QPainter::CompositionMode> m_compositionModes;
|
std::vector<QPainter::CompositionMode> m_compositionModes;
|
||||||
QList<PDFRenderError> m_errors;
|
QList<PDFRenderError> m_errors;
|
||||||
PDFSnapInfo m_snapInfo;
|
PDFSnapInfo m_snapInfo;
|
||||||
|
QElapsedTimer m_expirationTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Processor, which processes PDF's page commands and writes them to the precompiled page.
|
/// Processor, which processes PDF's page commands and writes them to the precompiled page.
|
||||||
|
Reference in New Issue
Block a user