summaryrefslogtreecommitdiffstats
path: root/src/pdfwidgets/qpdfview.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/pdfwidgets/qpdfview.cpp')
-rw-r--r--src/pdfwidgets/qpdfview.cpp493
1 files changed, 493 insertions, 0 deletions
diff --git a/src/pdfwidgets/qpdfview.cpp b/src/pdfwidgets/qpdfview.cpp
new file mode 100644
index 000000000..19e5b719a
--- /dev/null
+++ b/src/pdfwidgets/qpdfview.cpp
@@ -0,0 +1,493 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com>
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtPDF module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qpdfview.h"
+#include "qpdfview_p.h"
+
+#include "qpdfpagerenderer.h"
+
+#include <QGuiApplication>
+#include <QPdfDocument>
+#include <QPdfPageNavigation>
+#include <QScreen>
+#include <QScrollBar>
+#include <QScroller>
+
+QT_BEGIN_NAMESPACE
+
+QPdfViewPrivate::QPdfViewPrivate()
+ : QAbstractScrollAreaPrivate()
+ , m_document(nullptr)
+ , m_pageNavigation(nullptr)
+ , m_pageRenderer(nullptr)
+ , m_pageMode(QPdfView::SinglePage)
+ , m_zoomMode(QPdfView::CustomZoom)
+ , m_zoomFactor(1.0)
+ , m_pageSpacing(3)
+ , m_documentMargins(6, 6, 6, 6)
+ , m_blockPageScrolling(false)
+ , m_pageCacheLimit(20)
+ , m_screenResolution(QGuiApplication::primaryScreen()->logicalDotsPerInch() / 72.0)
+{
+}
+
+void QPdfViewPrivate::init()
+{
+ Q_Q(QPdfView);
+
+ m_pageNavigation = new QPdfPageNavigation(q);
+ m_pageRenderer = new QPdfPageRenderer(q);
+ m_pageRenderer->setRenderMode(QPdfPageRenderer::MultiThreadedRenderMode);
+}
+
+void QPdfViewPrivate::documentStatusChanged()
+{
+ updateDocumentLayout();
+ invalidatePageCache();
+}
+
+void QPdfViewPrivate::currentPageChanged(int currentPage)
+{
+ Q_Q(QPdfView);
+
+ if (m_blockPageScrolling)
+ return;
+
+ q->verticalScrollBar()->setValue(yPositionForPage(currentPage));
+
+ if (m_pageMode == QPdfView::SinglePage)
+ invalidateDocumentLayout();
+}
+
+void QPdfViewPrivate::calculateViewport()
+{
+ Q_Q(QPdfView);
+
+ const int x = q->horizontalScrollBar()->value();
+ const int y = q->verticalScrollBar()->value();
+ const int width = q->viewport()->width();
+ const int height = q->viewport()->height();
+
+ setViewport(QRect(x, y, width, height));
+}
+
+void QPdfViewPrivate::setViewport(QRect viewport)
+{
+ if (m_viewport == viewport)
+ return;
+
+ const QSize oldSize = m_viewport.size();
+
+ m_viewport = viewport;
+
+ if (oldSize != m_viewport.size()) {
+ updateDocumentLayout();
+
+ if (m_zoomMode != QPdfView::CustomZoom) {
+ invalidatePageCache();
+ }
+ }
+
+ if (m_pageMode == QPdfView::MultiPage) {
+ // An imaginary, 2px height line at the upper half of the viewport, which is used to
+ // determine which page is currently located there -> we propagate that as 'current' page
+ // to the QPdfPageNavigation object
+ const QRect currentPageLine(m_viewport.x(), m_viewport.y() + m_viewport.height() * 0.4, m_viewport.width(), 2);
+
+ int currentPage = 0;
+ for (auto it = m_documentLayout.pageGeometries.cbegin(); it != m_documentLayout.pageGeometries.cend(); ++it) {
+ const QRect pageGeometry = it.value();
+ if (pageGeometry.intersects(currentPageLine)) {
+ currentPage = it.key();
+ break;
+ }
+ }
+
+ if (currentPage != m_pageNavigation->currentPage()) {
+ m_blockPageScrolling = true;
+ m_pageNavigation->setCurrentPage(currentPage);
+ m_blockPageScrolling = false;
+ }
+ }
+}
+
+void QPdfViewPrivate::updateScrollBars()
+{
+ Q_Q(QPdfView);
+
+ const QSize p = q->viewport()->size();
+ const QSize v = m_documentLayout.documentSize;
+
+ q->horizontalScrollBar()->setRange(0, v.width() - p.width());
+ q->horizontalScrollBar()->setPageStep(p.width());
+ q->verticalScrollBar()->setRange(0, v.height() - p.height());
+ q->verticalScrollBar()->setPageStep(p.height());
+}
+
+void QPdfViewPrivate::pageRendered(int pageNumber, QSize imageSize, const QImage &image, quint64 requestId)
+{
+ Q_Q(QPdfView);
+
+ Q_UNUSED(imageSize)
+ Q_UNUSED(requestId)
+
+ if (!m_cachedPagesLRU.contains(pageNumber)) {
+ if (m_cachedPagesLRU.length() > m_pageCacheLimit)
+ m_pageCache.remove(m_cachedPagesLRU.takeFirst());
+
+ m_cachedPagesLRU.append(pageNumber);
+ }
+
+ m_pageCache.insert(pageNumber, image);
+
+ q->viewport()->update();
+}
+
+void QPdfViewPrivate::invalidateDocumentLayout()
+{
+ updateDocumentLayout();
+ invalidatePageCache();
+}
+
+void QPdfViewPrivate::invalidatePageCache()
+{
+ Q_Q(QPdfView);
+
+ m_pageCache.clear();
+ q->viewport()->update();
+}
+
+QPdfViewPrivate::DocumentLayout QPdfViewPrivate::calculateDocumentLayout() const
+{
+ // The DocumentLayout describes a virtual layout where all pages are positioned inside
+ // - For SinglePage mode, this is just an area as large as the current page surrounded
+ // by the m_documentMargins.
+ // - For MultiPage mode, this is the area that is covered by all pages which are placed
+ // below each other, with m_pageSpacing inbetween and surrounded by m_documentMargins
+
+ DocumentLayout documentLayout;
+
+ if (!m_document || m_document->status() != QPdfDocument::Ready)
+ return documentLayout;
+
+ QHash<int, QRect> pageGeometries;
+
+ const int pageCount = m_document->pageCount();
+
+ int totalWidth = 0;
+
+ const int startPage = (m_pageMode == QPdfView::SinglePage ? m_pageNavigation->currentPage() : 0);
+ const int endPage = (m_pageMode == QPdfView::SinglePage ? m_pageNavigation->currentPage() + 1 : pageCount);
+
+ // calculate page sizes
+ for (int page = startPage; page < endPage; ++page) {
+ QSize pageSize;
+ if (m_zoomMode == QPdfView::CustomZoom) {
+ pageSize = QSizeF(m_document->pageSize(page) * m_screenResolution * m_zoomFactor).toSize();
+ } else if (m_zoomMode == QPdfView::FitToWidth) {
+ pageSize = QSizeF(m_document->pageSize(page) * m_screenResolution).toSize();
+ const qreal factor = (qreal(m_viewport.width() - m_documentMargins.left() - m_documentMargins.right()) / qreal(pageSize.width()));
+ pageSize *= factor;
+ } else if (m_zoomMode == QPdfView::FitInView) {
+ const QSize viewportSize(m_viewport.size() + QSize(-m_documentMargins.left() - m_documentMargins.right(), -m_pageSpacing));
+
+ pageSize = QSizeF(m_document->pageSize(page) * m_screenResolution).toSize();
+ pageSize = pageSize.scaled(viewportSize, Qt::KeepAspectRatio);
+ }
+
+ totalWidth = qMax(totalWidth, pageSize.width());
+
+ pageGeometries[page] = QRect(QPoint(0, 0), pageSize);
+ }
+
+ totalWidth += m_documentMargins.left() + m_documentMargins.right();
+
+ int pageY = m_documentMargins.top();
+
+ // calculate page positions
+ for (int page = startPage; page < endPage; ++page) {
+ const QSize pageSize = pageGeometries[page].size();
+
+ // center horizontal inside the viewport
+ const int pageX = (qMax(totalWidth, m_viewport.width()) - pageSize.width()) / 2;
+
+ pageGeometries[page].moveTopLeft(QPoint(pageX, pageY));
+
+ pageY += pageSize.height() + m_pageSpacing;
+ }
+
+ pageY += m_documentMargins.bottom();
+
+ documentLayout.pageGeometries = pageGeometries;
+
+ // calculate overall document size
+ documentLayout.documentSize = QSize(totalWidth, pageY);
+
+ return documentLayout;
+}
+
+qreal QPdfViewPrivate::yPositionForPage(int pageNumber) const
+{
+ const auto it = m_documentLayout.pageGeometries.constFind(pageNumber);
+ if (it == m_documentLayout.pageGeometries.cend())
+ return 0.0;
+
+ return (*it).y();
+}
+
+void QPdfViewPrivate::updateDocumentLayout()
+{
+ m_documentLayout = calculateDocumentLayout();
+
+ updateScrollBars();
+}
+
+
+QPdfView::QPdfView(QWidget *parent)
+ : QAbstractScrollArea(*new QPdfViewPrivate(), parent)
+{
+ Q_D(QPdfView);
+
+ d->init();
+
+ connect(d->m_pageNavigation, &QPdfPageNavigation::currentPageChanged, this, [d](int page){ d->currentPageChanged(page); });
+
+ connect(d->m_pageRenderer, &QPdfPageRenderer::pageRendered,
+ this, [d](int pageNumber, QSize imageSize, const QImage &image, QPdfDocumentRenderOptions, quint64 requestId){ d->pageRendered(pageNumber, imageSize, image, requestId); });
+
+ verticalScrollBar()->setSingleStep(20);
+ horizontalScrollBar()->setSingleStep(20);
+
+ QScroller::grabGesture(this);
+
+ d->calculateViewport();
+}
+
+/*!
+ \internal
+*/
+QPdfView::QPdfView(QPdfViewPrivate &dd, QWidget *parent)
+ : QAbstractScrollArea(dd, parent)
+{
+}
+
+QPdfView::~QPdfView()
+{
+}
+
+void QPdfView::setDocument(QPdfDocument *document)
+{
+ Q_D(QPdfView);
+
+ if (d->m_document == document)
+ return;
+
+ if (d->m_document)
+ disconnect(d->m_documentStatusChangedConnection);
+
+ d->m_document = document;
+ emit documentChanged(d->m_document);
+
+ if (d->m_document)
+ d->m_documentStatusChangedConnection = connect(d->m_document.data(), &QPdfDocument::statusChanged, this, [d](){ d->documentStatusChanged(); });
+
+ d->m_pageNavigation->setDocument(d->m_document);
+ d->m_pageRenderer->setDocument(d->m_document);
+
+ d->documentStatusChanged();
+}
+
+QPdfDocument *QPdfView::document() const
+{
+ Q_D(const QPdfView);
+
+ return d->m_document;
+}
+
+QPdfPageNavigation *QPdfView::pageNavigation() const
+{
+ Q_D(const QPdfView);
+
+ return d->m_pageNavigation;
+}
+
+QPdfView::PageMode QPdfView::pageMode() const
+{
+ Q_D(const QPdfView);
+
+ return d->m_pageMode;
+}
+
+void QPdfView::setPageMode(PageMode mode)
+{
+ Q_D(QPdfView);
+
+ if (d->m_pageMode == mode)
+ return;
+
+ d->m_pageMode = mode;
+ d->invalidateDocumentLayout();
+
+ emit pageModeChanged(d->m_pageMode);
+}
+
+QPdfView::ZoomMode QPdfView::zoomMode() const
+{
+ Q_D(const QPdfView);
+
+ return d->m_zoomMode;
+}
+
+void QPdfView::setZoomMode(ZoomMode mode)
+{
+ Q_D(QPdfView);
+
+ if (d->m_zoomMode == mode)
+ return;
+
+ d->m_zoomMode = mode;
+ d->invalidateDocumentLayout();
+
+ emit zoomModeChanged(d->m_zoomMode);
+}
+
+qreal QPdfView::zoomFactor() const
+{
+ Q_D(const QPdfView);
+
+ return d->m_zoomFactor;
+}
+
+void QPdfView::setZoomFactor(qreal factor)
+{
+ Q_D(QPdfView);
+
+ if (d->m_zoomFactor == factor)
+ return;
+
+ d->m_zoomFactor = factor;
+ d->invalidateDocumentLayout();
+
+ emit zoomFactorChanged(d->m_zoomFactor);
+}
+
+int QPdfView::pageSpacing() const
+{
+ Q_D(const QPdfView);
+
+ return d->m_pageSpacing;
+}
+
+void QPdfView::setPageSpacing(int spacing)
+{
+ Q_D(QPdfView);
+
+ if (d->m_pageSpacing == spacing)
+ return;
+
+ d->m_pageSpacing = spacing;
+ d->invalidateDocumentLayout();
+
+ emit pageSpacingChanged(d->m_pageSpacing);
+}
+
+QMargins QPdfView::documentMargins() const
+{
+ Q_D(const QPdfView);
+
+ return d->m_documentMargins;
+}
+
+void QPdfView::setDocumentMargins(QMargins margins)
+{
+ Q_D(QPdfView);
+
+ if (d->m_documentMargins == margins)
+ return;
+
+ d->m_documentMargins = margins;
+ d->invalidateDocumentLayout();
+
+ emit documentMarginsChanged(d->m_documentMargins);
+}
+
+void QPdfView::paintEvent(QPaintEvent *event)
+{
+ Q_D(QPdfView);
+
+ QPainter painter(viewport());
+ painter.fillRect(event->rect(), palette().brush(QPalette::Dark));
+ painter.translate(-d->m_viewport.x(), -d->m_viewport.y());
+
+ for (auto it = d->m_documentLayout.pageGeometries.cbegin(); it != d->m_documentLayout.pageGeometries.cend(); ++it) {
+ const QRect pageGeometry = it.value();
+ if (pageGeometry.intersects(d->m_viewport)) { // page needs to be painted
+ painter.fillRect(pageGeometry, Qt::white);
+
+ const int page = it.key();
+ const auto pageIt = d->m_pageCache.constFind(page);
+ if (pageIt != d->m_pageCache.cend()) {
+ const QImage &img = pageIt.value();
+ painter.drawImage(pageGeometry.topLeft(), img);
+ } else {
+ d->m_pageRenderer->requestPage(page, pageGeometry.size());
+ }
+ }
+ }
+}
+
+void QPdfView::resizeEvent(QResizeEvent *event)
+{
+ Q_D(QPdfView);
+
+ QAbstractScrollArea::resizeEvent(event);
+
+ d->updateScrollBars();
+ d->calculateViewport();
+}
+
+void QPdfView::scrollContentsBy(int dx, int dy)
+{
+ Q_D(QPdfView);
+
+ QAbstractScrollArea::scrollContentsBy(dx, dy);
+
+ d->calculateViewport();
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qpdfview.cpp"