From 09a6eac4a63b32548ecc1ff5b16a5d8fc3ba1c04 Mon Sep 17 00:00:00 2001
From: Shawn Rutledge <shawn.rutledge@qt.io>
Date: Wed, 5 Feb 2020 15:53:57 +0100
Subject: [PATCH] Add QPdfDestination; NavigationStack stores page, location
 and zoom

Push/back/forward behavior seems more correct now, but still no
autotest yet.

QPdfDestination might be useful to represent locations of search results,
for link destinations and maybe named destinations too.

Fixes: QTBUG-77512
Change-Id: I113b2c535a2cd302106e6546104c64e12985d387
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
---
 examples/pdf/pdfviewer/viewer.qml          |   3 +-
 src/pdf/api/qpdfdestination.h              |  81 +++++++++
 src/pdf/api/qpdfdestination_p.h            |  60 +++++++
 src/pdf/pdfcore.pro                        |   3 +
 src/pdf/qpdfdestination.cpp                | 135 +++++++++++++++
 src/pdf/qpdfdocument.cpp                   |   3 +
 src/pdf/quick/qml/PdfMultiPageView.qml     |   4 +-
 src/pdf/quick/qml/PdfPageView.qml          |   3 +-
 src/pdf/quick/qquickpdfnavigationstack.cpp | 188 ++++++++++++++++-----
 src/pdf/quick/qquickpdfnavigationstack_p.h |  21 ++-
 10 files changed, 447 insertions(+), 54 deletions(-)
 create mode 100644 src/pdf/api/qpdfdestination.h
 create mode 100644 src/pdf/api/qpdfdestination_p.h
 create mode 100644 src/pdf/qpdfdestination.cpp

diff --git a/examples/pdf/pdfviewer/viewer.qml b/examples/pdf/pdfviewer/viewer.qml
index a8e581a45..095bc3985 100644
--- a/examples/pdf/pdfviewer/viewer.qml
+++ b/examples/pdf/pdfviewer/viewer.qml
@@ -140,7 +140,7 @@ ApplicationWindow {
                 from: 1
                 to: document.pageCount
                 editable: true
-                onValueChanged: pageView.currentPage = value - 1
+                onValueChanged: pageView.goToPage(value - 1)
                 Shortcut {
                     sequence: StandardKey.MoveToPreviousPage
                     onActivated: currentPageSB.value--
@@ -223,7 +223,6 @@ ApplicationWindow {
 
     PdfPageView {
         id: pageView
-//        currentPage: currentPageSB.value - 1
         // TODO should work but ends up being NaN in QQuickSpinBoxPrivate::setValue() (?!)
 //        onCurrentPageChanged: currentPageSB.value = pageView.currrentPage + 1
         onCurrentPageReallyChanged: currentPageSB.value = page + 1
diff --git a/src/pdf/api/qpdfdestination.h b/src/pdf/api/qpdfdestination.h
new file mode 100644
index 000000000..dc5d6314a
--- /dev/null
+++ b/src/pdf/api/qpdfdestination.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** 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$
+**
+****************************************************************************/
+
+#ifndef QPDFDESTINATION_H
+#define QPDFDESTINATION_H
+
+#include <QtPdf/qtpdfglobal.h>
+#include <QExplicitlySharedDataPointer>
+#include <QObject>
+#include <QPointF>
+
+QT_BEGIN_NAMESPACE
+
+class QPdfDestinationPrivate;
+
+class Q_PDF_EXPORT QPdfDestination
+{
+    Q_GADGET
+    Q_PROPERTY(bool valid READ isValid)
+    Q_PROPERTY(int page READ page)
+    Q_PROPERTY(QPointF location READ location)
+    Q_PROPERTY(qreal zoom READ zoom)
+
+public:
+    QPdfDestination(const QPdfDestination &other);
+    ~QPdfDestination();
+    QPdfDestination &operator=(const QPdfDestination &other);
+    inline QPdfDestination &operator=(QPdfDestination &&other) noexcept { swap(other); return *this; }
+    void swap(QPdfDestination &other) noexcept { d.swap(other.d); }
+    bool isValid() const;
+    int page() const;
+    QPointF location() const;
+    qreal zoom() const;
+
+private:
+    QPdfDestination();
+    QPdfDestination(int page, QPointF location, qreal zoom);
+    QPdfDestination(QPdfDestinationPrivate *d);
+    friend class QPdfDocument;
+    friend class QQuickPdfNavigationStack;
+
+private:
+    QExplicitlySharedDataPointer<QPdfDestinationPrivate> d;
+};
+
+QT_END_NAMESPACE
+
+#endif // QPDFDESTINATION_H
diff --git a/src/pdf/api/qpdfdestination_p.h b/src/pdf/api/qpdfdestination_p.h
new file mode 100644
index 000000000..a5aeb804f
--- /dev/null
+++ b/src/pdf/api/qpdfdestination_p.h
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** 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$
+**
+****************************************************************************/
+
+#ifndef QPDFDESTINATION_P_H
+#define QPDFDESTINATION_P_H
+
+#include <QPointF>
+
+QT_BEGIN_NAMESPACE
+
+class QPdfDestinationPrivate : public QSharedData
+{
+public:
+    QPdfDestinationPrivate() = default;
+    QPdfDestinationPrivate(int page, QPointF location, qreal zoom)
+        : page(page),
+          location(location),
+          zoom(zoom) { }
+
+    int page = -1;
+    QPointF location;
+    qreal zoom = 1;
+};
+
+QT_END_NAMESPACE
+
+#endif // QPDFDESTINATION_P_H
diff --git a/src/pdf/pdfcore.pro b/src/pdf/pdfcore.pro
index ecb1d0cdb..951b5699f 100644
--- a/src/pdf/pdfcore.pro
+++ b/src/pdf/pdfcore.pro
@@ -60,6 +60,7 @@ ios: OBJECTS += $$NINJA_OBJECTS
 
 SOURCES += \
     qpdfbookmarkmodel.cpp \
+    qpdfdestination.cpp \
     qpdfdocument.cpp \
     qpdflinkmodel.cpp \
     qpdfpagenavigation.cpp \
@@ -72,6 +73,8 @@ SOURCES += \
 
 HEADERS += \
     api/qpdfbookmarkmodel.h \
+    api/qpdfdestination.h \
+    api/qpdfdestination_p.h \
     api/qpdfdocument.h \
     api/qpdfdocument_p.h \
     api/qpdfdocumentrenderoptions.h \
diff --git a/src/pdf/qpdfdestination.cpp b/src/pdf/qpdfdestination.cpp
new file mode 100644
index 000000000..86e429dcf
--- /dev/null
+++ b/src/pdf/qpdfdestination.cpp
@@ -0,0 +1,135 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** 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 "qpdfdestination.h"
+#include "qpdfdestination_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+    \class QPdfDestination
+    \since 5.15
+    \inmodule QtPdf
+
+    \brief The QPdfDestination class defines a location on a page in a PDF
+    document, and a suggested zoom level at which it is intended to be viewed.
+*/
+
+/*!
+    Constructs an invalid Destination.
+
+    \sa valid
+*/
+QPdfDestination::QPdfDestination()
+  : d(new QPdfDestinationPrivate())
+{
+}
+
+QPdfDestination::QPdfDestination(int page, QPointF location, qreal zoom)
+  : d(new QPdfDestinationPrivate(page, location, zoom))
+{
+}
+
+QPdfDestination::QPdfDestination(QPdfDestinationPrivate *d)
+  : d(d)
+{
+}
+
+QPdfDestination::QPdfDestination(const QPdfDestination &other)
+  : d(other.d)
+{
+}
+
+QPdfDestination::~QPdfDestination()
+{
+}
+
+/*!
+    \property QPdfDestination::valid
+
+    This property holds whether the destination is valid.
+*/
+bool QPdfDestination::isValid() const
+{
+    return d->page >= 0;
+}
+
+/*!
+    \property QPdfDestination::page
+
+    This property holds the page number.
+*/
+int QPdfDestination::page() const
+{
+    return d->page;
+}
+
+/*!
+    \property QPdfDestination::location
+
+    This property holds the location on the page, in units of points.
+*/
+QPointF QPdfDestination::location() const
+{
+    return d->location;
+}
+
+/*!
+    \property QPdfDestination::zoom
+
+    This property holds the suggested magnification level, where 1.0 means default scale
+    (1 pixel = 1 point).
+*/
+qreal QPdfDestination::zoom() const
+{
+    return d->zoom;
+}
+
+//QDataStream& operator<<(QDataStream& stream, const QPdfDestination& dest)
+//{
+//    stream << *dest.d.data();
+//    return stream;
+//}
+
+QDataStream& operator<<(QDataStream& stream, const QPdfDestinationPrivate& dest)
+{
+    stream << QStringLiteral("QPdfDestination") << dest.page << dest.location ; // << dest.zoom();
+    return stream;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qpdfdestination.cpp"
diff --git a/src/pdf/qpdfdocument.cpp b/src/pdf/qpdfdocument.cpp
index 3719938a2..42cd2492d 100644
--- a/src/pdf/qpdfdocument.cpp
+++ b/src/pdf/qpdfdocument.cpp
@@ -345,6 +345,9 @@ void QPdfDocumentPrivate::checkComplete()
 
 bool QPdfDocumentPrivate::checkPageComplete(int page)
 {
+    if (page < 0 || page >= pageCount)
+        return false;
+
     if (loadComplete)
         return true;
 
diff --git a/src/pdf/quick/qml/PdfMultiPageView.qml b/src/pdf/quick/qml/PdfMultiPageView.qml
index 28436b90d..be41153b6 100644
--- a/src/pdf/quick/qml/PdfMultiPageView.qml
+++ b/src/pdf/quick/qml/PdfMultiPageView.qml
@@ -111,7 +111,7 @@ Item {
         property bool rot90: rotationModulus > 45 && rotationModulus < 135
         property size firstPagePointSize: document.pagePointSize(0)
         onCurrentIndexChanged: {
-            navigationStack.currentPage = currentIndex
+            navigationStack.push(currentIndex, Qt.point(0, 0), root.renderScale)
             root.currentPageReallyChanged(currentIndex)
         }
         delegate: Rectangle {
@@ -244,7 +244,7 @@ Item {
     }
     PdfNavigationStack {
         id: navigationStack
-        onCurrentPageJumped: listView.currentIndex = page
+        onJumped: listView.currentIndex = page
         onCurrentPageChanged: root.currentPageReallyChanged(navigationStack.currentPage)
     }
 }
diff --git a/src/pdf/quick/qml/PdfPageView.qml b/src/pdf/quick/qml/PdfPageView.qml
index cf287ecf7..d03e9dc9d 100644
--- a/src/pdf/quick/qml/PdfPageView.qml
+++ b/src/pdf/quick/qml/PdfPageView.qml
@@ -57,6 +57,7 @@ Rectangle {
     property alias forwardEnabled: navigationStack.forwardAvailable
     function back() { navigationStack.back() }
     function forward() { navigationStack.forward() }
+    function goToPage(page) { navigationStack.push(page, Qt.point(0, 0), renderScale) }
     signal currentPageReallyChanged(page: int)
 
     property real __pageScale: image.paintedWidth / document.pagePointSize(navigationStack.currentPage).width
@@ -203,7 +204,7 @@ Rectangle {
                 cursorShape: Qt.PointingHandCursor
                 onClicked: {
                     if (page >= 0)
-                        navigationStack.currentPage = page
+                        navigationStack.push(page, Qt.point(0, 0), paper.renderScale)
                     else
                         Qt.openUrlExternally(url)
                 }
diff --git a/src/pdf/quick/qquickpdfnavigationstack.cpp b/src/pdf/quick/qquickpdfnavigationstack.cpp
index c19fae735..57acdc4bc 100644
--- a/src/pdf/quick/qquickpdfnavigationstack.cpp
+++ b/src/pdf/quick/qquickpdfnavigationstack.cpp
@@ -46,10 +46,10 @@ Q_LOGGING_CATEGORY(qLcNav, "qt.pdf.navigationstack")
     \instantiates QQuickPdfNavigationStack
     \inqmlmodule QtQuick.Pdf
     \ingroup pdf
-    \brief History of the pages visited within a PDF Document.
+    \brief History of the destinations visited within a PDF Document.
     \since 5.15
 
-    PdfNavigationStack remembers which pages the user has visited in a PDF
+    PdfNavigationStack remembers which destinations the user has visited in a PDF
     document, and provides the ability to traverse backward and forward.
 */
 
@@ -61,102 +61,206 @@ QQuickPdfNavigationStack::QQuickPdfNavigationStack(QObject *parent)
 /*!
     \qmlmethod void PdfNavigationStack::forward()
 
-    Goes back to the page that was being viewed before back() was called, and
-    then emits the \l currentPageJumped() signal.
+    Goes back to the page, location and zoom level that was being viewed before
+    back() was called, and then emits the \l jumped() signal.
 
-    If \l currentPage was set by assignment or binding since the last time
-    \l back() was called, the forward() function does nothing, because there is
-    a branch in the timeline which causes the "future" to be lost.
+    If a new destination was pushed since the last time \l back() was called,
+    the forward() function does nothing, because there is a branch in the
+    timeline which causes the "future" to be lost.
 */
 void QQuickPdfNavigationStack::forward()
 {
-    if (m_nextHistoryIndex >= m_pageHistory.count())
+    if (m_currentHistoryIndex >= m_pageHistory.count() - 1)
         return;
     bool backAvailableWas = backAvailable();
     bool forwardAvailableWas = forwardAvailable();
+    QPointF currentLocationWas = currentLocation();
+    qreal currentZoomWas = currentZoom();
+    ++m_currentHistoryIndex;
     m_changing = true;
-    setCurrentPage(m_pageHistory.at(++m_nextHistoryIndex));
-    m_changing = false;
-    emit currentPageJumped(m_currentPage);
-    if (backAvailableWas != backAvailable())
+    emit jumped(currentPage(), currentLocation(), currentZoom());
+    emit currentPageChanged();
+    if (currentLocationWas != currentLocation())
+        emit currentLocationChanged();
+    if (currentZoomWas != currentZoom())
+        emit currentZoomChanged();
+    if (!backAvailableWas)
         emit backAvailableChanged();
     if (forwardAvailableWas != forwardAvailable())
         emit forwardAvailableChanged();
+    m_changing = false;
 }
 
 /*!
     \qmlmethod void PdfNavigationStack::back()
 
-    Pops the stack and causes the \l currentPage property to change to the
-    most-recently-viewed page, and then emits the \l currentPageJumped()
-    signal.
+    Pops the stack, updates the \l currentPage, \l currentLocation and
+    \l currentZoom properties to the most-recently-viewed destination, and then
+    emits the \l jumped() signal.
 */
 void QQuickPdfNavigationStack::back()
 {
-    if (m_nextHistoryIndex <= 0)
+    if (m_currentHistoryIndex <= 0)
         return;
     bool backAvailableWas = backAvailable();
     bool forwardAvailableWas = forwardAvailable();
+    QPointF currentLocationWas = currentLocation();
+    qreal currentZoomWas = currentZoom();
+    --m_currentHistoryIndex;
     m_changing = true;
-    // TODO don't do that when going back after going forward
-    m_pageHistory.append(m_currentPage);
-    setCurrentPage(m_pageHistory.at(--m_nextHistoryIndex));
-    m_changing = false;
-    emit currentPageJumped(m_currentPage);
+    emit jumped(currentPage(), currentLocation(), currentZoom());
+    emit currentPageChanged();
+    if (currentLocationWas != currentLocation())
+        emit currentLocationChanged();
+    if (currentZoomWas != currentZoom())
+        emit currentZoomChanged();
     if (backAvailableWas != backAvailable())
         emit backAvailableChanged();
-    if (forwardAvailableWas != forwardAvailable())
+    if (!forwardAvailableWas)
         emit forwardAvailableChanged();
+    m_changing = false;
 }
 
 /*!
     \qmlproperty int PdfNavigationStack::currentPage
 
     This property holds the current page that is being viewed.
+    If there is no current page, it holds \c -1.
+*/
+int QQuickPdfNavigationStack::currentPage() const
+{
+    if (m_currentHistoryIndex < 0 || m_currentHistoryIndex >= m_pageHistory.count())
+        return -1;
+    return m_pageHistory.at(m_currentHistoryIndex)->page;
+}
+
+/*!
+    \qmlproperty point PdfNavigationStack::currentLocation
+
+    This property holds the current location on the page that is being viewed.
+*/
+QPointF QQuickPdfNavigationStack::currentLocation() const
+{
+    if (m_currentHistoryIndex < 0 || m_currentHistoryIndex >= m_pageHistory.count())
+        return QPointF();
+    return m_pageHistory.at(m_currentHistoryIndex)->location;
+}
 
-    It should be set when the viewer's current page changes. Every time this
-    property is set, it pushes the current page number onto the stack, such
-    that the history of pages that have been viewed will grow.
+/*!
+    \qmlproperty real PdfNavigationStack::currentZoom
+
+    This property holds the magnification scale on the page that is being viewed.
 */
-void QQuickPdfNavigationStack::setCurrentPage(int currentPage)
+qreal QQuickPdfNavigationStack::currentZoom() const
 {
-    if (m_currentPage == currentPage)
+    if (m_currentHistoryIndex < 0 || m_currentHistoryIndex >= m_pageHistory.count())
+        return 1;
+    return m_pageHistory.at(m_currentHistoryIndex)->zoom;
+}
+
+/*!
+    \qmlmethod void PdfNavigationStack::push(int page, point location, qreal zoom)
+
+    Adds the given destination, consisting of \a page, \a location and \a zoom,
+    to the history of visited locations.
+
+    If forwardAvailable is \c true, calling this function represents a branch
+    in the timeline which causes the "future" to be lost, and therefore
+    forwardAvailable will change to \c false.
+*/
+void QQuickPdfNavigationStack::push(int page, QPointF location, qreal zoom)
+{
+    if (page == currentPage() && location == currentLocation() && zoom == currentZoom())
         return;
+    if (qFuzzyIsNull(zoom))
+        zoom = currentZoom();
     bool backAvailableWas = backAvailable();
     bool forwardAvailableWas = forwardAvailable();
     if (!m_changing) {
-        if (m_nextHistoryIndex >= 0 && m_nextHistoryIndex < m_pageHistory.count())
-            m_pageHistory.remove(m_nextHistoryIndex, m_pageHistory.count() - m_nextHistoryIndex);
-        m_pageHistory.append(m_currentPage);
-        m_nextHistoryIndex = m_pageHistory.count();
+        if (m_currentHistoryIndex >= 0 && forwardAvailableWas)
+            m_pageHistory.remove(m_currentHistoryIndex + 1, m_pageHistory.count() - m_currentHistoryIndex - 1);
+        m_pageHistory.append(QExplicitlySharedDataPointer<QPdfDestinationPrivate>(new QPdfDestinationPrivate(page, location, zoom)));
+        m_currentHistoryIndex = m_pageHistory.count() - 1;
     }
-    m_currentPage = currentPage;
     emit currentPageChanged();
-    if (backAvailableWas != backAvailable())
+    emit currentLocationChanged();
+    emit currentZoomChanged();
+    if (m_changing)
+        return;
+    if (!backAvailableWas)
         emit backAvailableChanged();
-    if (forwardAvailableWas != forwardAvailable())
+    if (forwardAvailableWas)
         emit forwardAvailableChanged();
-    qCDebug(qLcNav) << "current" << m_currentPage << "history" << m_pageHistory;
+    qCDebug(qLcNav) << "push: index" << m_currentHistoryIndex << "page" << page
+                    << "@" << location << "zoom" << zoom << "-> history" <<
+        [this]() {
+            QStringList ret;
+            for (auto d : m_pageHistory)
+                ret << QString::number(d->page);
+            return ret.join(',');
+        }();
+}
+
+/*!
+    \qmlmethod void PdfNavigationStack::update(int page, point location, qreal zoom)
+
+    Modifies the current destination, consisting of \a page, \a location and \a zoom.
+
+    This can be called periodically while the user is manually moving around
+    the document, so that after back() is called, forward() will jump back to
+    the most-recently-viewed destination rather than the destination that was
+    last specified by push().
+
+    The \c currentPageChanged, \c currentLocationChanged and \c currentZoomChanged
+    signals will be emitted if the respective properties are actually changed.
+    The \l jumped signal is not emitted, because this operation
+    represents smooth movement rather than a navigational jump.
+*/
+void QQuickPdfNavigationStack::update(int page, QPointF location, qreal zoom)
+{
+    if (m_currentHistoryIndex < 0 || m_currentHistoryIndex >= m_pageHistory.count())
+        return;
+    int currentPageWas = currentPage();
+    QPointF currentLocationWas = currentLocation();
+    qreal currentZoomWas = currentZoom();
+    if (page == currentPageWas && location == currentLocationWas && zoom == currentZoomWas)
+        return;
+    m_pageHistory[m_currentHistoryIndex]->page = page;
+    m_pageHistory[m_currentHistoryIndex]->location = location;
+    m_pageHistory[m_currentHistoryIndex]->zoom = zoom;
+    if (currentPageWas != page)
+        emit currentPageChanged();
+    if (currentLocationWas != location)
+        emit currentLocationChanged();
+    if (currentZoomWas != zoom)
+        emit currentZoomChanged();
+    qCDebug(qLcNav) << "update: index" << m_currentHistoryIndex << "page" << page
+                    << "@" << location << "zoom" << zoom << "-> history" <<
+        [this]() {
+            QStringList ret;
+            for (auto d : m_pageHistory)
+                ret << QString::number(d->page);
+            return ret.join(',');
+        }();
 }
 
 bool QQuickPdfNavigationStack::backAvailable() const
 {
-    return m_nextHistoryIndex > 0;
+    return m_currentHistoryIndex > 0;
 }
 
 bool QQuickPdfNavigationStack::forwardAvailable() const
 {
-    return m_nextHistoryIndex < m_pageHistory.count();
+    return m_currentHistoryIndex < m_pageHistory.count() - 1;
 }
 
 /*!
-    \qmlsignal PdfNavigationStack::currentPageJumped(int page)
+    \qmlsignal PdfNavigationStack::jumped(int page, point location, qreal zoom)
 
     This signal is emitted when either forward() or back() is called, to
-    distinguish navigational jumps from cases when the \l currentPage property
-    is set by means of a binding or assignment. Contrast with the
-    \c currentPageChanged signal, which is emitted in all cases, and does not
-    include the \c page argument.
+    distinguish navigational jumps from cases when push() is called.
+    Contrast with the \c currentPageChanged signal, which is emitted in all
+    cases, and does not include the \c page, \c location and \c zoom arguments.
 */
 
 QT_END_NAMESPACE
diff --git a/src/pdf/quick/qquickpdfnavigationstack_p.h b/src/pdf/quick/qquickpdfnavigationstack_p.h
index 54713fabb..8d7102fb1 100644
--- a/src/pdf/quick/qquickpdfnavigationstack_p.h
+++ b/src/pdf/quick/qquickpdfnavigationstack_p.h
@@ -49,6 +49,7 @@
 //
 
 #include "qquickpdfdocument_p.h"
+#include "../api/qpdfdestination_p.h"
 
 #include <QtQml/qqml.h>
 
@@ -57,32 +58,38 @@ QT_BEGIN_NAMESPACE
 class QQuickPdfNavigationStack : public QObject
 {
     Q_OBJECT
-    Q_PROPERTY(int currentPage READ currentPage WRITE setCurrentPage NOTIFY currentPageChanged)
+    Q_PROPERTY(int currentPage READ currentPage NOTIFY currentPageChanged)
+    Q_PROPERTY(QPointF currentLocation READ currentLocation NOTIFY currentLocationChanged)
+    Q_PROPERTY(qreal currentZoom READ currentZoom NOTIFY currentZoomChanged)
     Q_PROPERTY(bool backAvailable READ backAvailable NOTIFY backAvailableChanged)
     Q_PROPERTY(bool forwardAvailable READ forwardAvailable NOTIFY forwardAvailableChanged)
 
 public:
     explicit QQuickPdfNavigationStack(QObject *parent = nullptr);
 
+    Q_INVOKABLE void push(int page, QPointF location, qreal zoom);
+    Q_INVOKABLE void update(int page, QPointF location, qreal zoom);
     Q_INVOKABLE void forward();
     Q_INVOKABLE void back();
 
-    int currentPage() const { return m_currentPage; }
-    void setCurrentPage(int currentPage);
+    int currentPage() const;
+    QPointF currentLocation() const;
+    qreal currentZoom() const;
 
     bool backAvailable() const;
     bool forwardAvailable() const;
 
 Q_SIGNALS:
     void currentPageChanged();
-    void currentPageJumped(int page);
+    void currentLocationChanged();
+    void currentZoomChanged();
     void backAvailableChanged();
     void forwardAvailableChanged();
+    void jumped(int page, QPointF location, qreal zoom);
 
 private:
-    QVector<int> m_pageHistory;
-    int m_nextHistoryIndex = 0;
-    int m_currentPage = 0;
+    QVector<QExplicitlySharedDataPointer<QPdfDestinationPrivate>> m_pageHistory;
+    int m_currentHistoryIndex = 0;
     bool m_changing = false;
 
     Q_DISABLE_COPY(QQuickPdfNavigationStack)
-- 
GitLab