From 25a371caa376c513f22d5c01e425a18629657fdc Mon Sep 17 00:00:00 2001
From: Shawn Rutledge <shawn.rutledge@qt.io>
Date: Mon, 3 Feb 2020 18:11:16 +0100
Subject: [PATCH] Add zoom and rotation to PdfMultiPageView

Currently, scaleToWidth() and scaleToPage() choose the scale of the
first page to fit the given viewport size, and as long as all pages are
the same size, it works.  On the other hand, the PinchHandler only
affects the scale of the page on which the pinch gesture occurs.
Calling resetScale(), scaleToWidth() or scaleToPage() undoes the
effect of any previous pinch gesture or any other kind of scaling change.

Task-number: QTBUG-77513
Change-Id: Ia3227ca9c4af263eb8505dbd6336657984c66ab0
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
---
 examples/pdf/multipage/viewer.qml      | 13 ++--
 src/pdf/quick/qml/PdfMultiPageView.qml | 95 ++++++++++++++++++++++----
 2 files changed, 88 insertions(+), 20 deletions(-)

diff --git a/examples/pdf/multipage/viewer.qml b/examples/pdf/multipage/viewer.qml
index d20ad4a5b..77c06f80f 100644
--- a/examples/pdf/multipage/viewer.qml
+++ b/examples/pdf/multipage/viewer.qml
@@ -75,11 +75,10 @@ ApplicationWindow {
                     onTriggered: fileDialog.open()
                 }
             }
-            /* TODO zoom & rotation
             ToolButton {
                 action: Action {
                     shortcut: StandardKey.ZoomIn
-                    enabled: view.sourceSize.width < 10000
+                    enabled: view.renderScale < 10
                     icon.source: "resources/zoom-in.svg"
                     onTriggered: view.renderScale *= Math.sqrt(2)
                 }
@@ -87,7 +86,7 @@ ApplicationWindow {
             ToolButton {
                 action: Action {
                     shortcut: StandardKey.ZoomOut
-                    enabled: view.sourceSize.width > 50
+                    enabled: view.renderScale > 0.1
                     icon.source: "resources/zoom-out.svg"
                     onTriggered: view.renderScale /= Math.sqrt(2)
                 }
@@ -115,17 +114,16 @@ ApplicationWindow {
                 action: Action {
                     shortcut: "Ctrl+L"
                     icon.source: "resources/rotate-left.svg"
-                    onTriggered: view.rotation -= 90
+                    onTriggered: view.pageRotation -= 90
                 }
             }
             ToolButton {
                 action: Action {
                     shortcut: "Ctrl+R"
                     icon.source: "resources/rotate-right.svg"
-                    onTriggered: view.rotation += 90
+                    onTriggered: view.pageRotation += 90
                 }
             }
-            */
             ToolButton {
                 action: Action {
                     icon.source: "resources/go-previous-view-page.svg"
@@ -269,7 +267,8 @@ ApplicationWindow {
             x: 6
             property size implicitPointSize: document.pagePointSize(view.currentPage)
             text: "page " + (currentPageSB.value) + " of " + document.pageCount +
-                  " original " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1)
+                  " scale " + view.renderScale.toFixed(2) +
+                  " original size " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1) + " pt"
             visible: document.pageCount > 0
         }
     }
diff --git a/src/pdf/quick/qml/PdfMultiPageView.qml b/src/pdf/quick/qml/PdfMultiPageView.qml
index f64238eec..28436b90d 100644
--- a/src/pdf/quick/qml/PdfMultiPageView.qml
+++ b/src/pdf/quick/qml/PdfMultiPageView.qml
@@ -59,6 +59,7 @@ Item {
     // TODO 5.15: required property
     property var document: undefined
     property real renderScale: 1
+    property real pageRotation: 0
     property string searchString
     property string selectedText
     property alias currentPage: listView.currentIndex
@@ -72,6 +73,32 @@ Item {
     function forward() { navigationStack.forward() }
     signal currentPageReallyChanged(page: int)
 
+    function resetScale() {
+        root.renderScale = 1
+    }
+
+    function scaleToWidth(width, height) {
+        root.renderScale = width / (listView.rot90 ? listView.firstPagePointSize.height : listView.firstPagePointSize.width)
+    }
+
+    function scaleToPage(width, height) {
+        var windowAspect = width / height
+        var pageAspect = listView.firstPagePointSize.width / listView.firstPagePointSize.height
+        if (listView.rot90) {
+            if (windowAspect > pageAspect) {
+                root.renderScale = height / listView.firstPagePointSize.width
+            } else {
+                root.renderScale = width / listView.firstPagePointSize.height
+            }
+        } else {
+            if (windowAspect > pageAspect) {
+                root.renderScale = height / listView.firstPagePointSize.height
+            } else {
+                root.renderScale = width / listView.firstPagePointSize.width
+            }
+        }
+    }
+
     id: root
     ListView {
         id: listView
@@ -80,24 +107,38 @@ Item {
         spacing: 6
         highlightRangeMode: ListView.ApplyRange
         highlightMoveVelocity: 2000 // TODO increase velocity when setting currentIndex somehow, too
+        property real rotationModulus: Math.abs(root.pageRotation % 180)
+        property bool rot90: rotationModulus > 45 && rotationModulus < 135
+        property size firstPagePointSize: document.pagePointSize(0)
         onCurrentIndexChanged: {
             navigationStack.currentPage = currentIndex
             root.currentPageReallyChanged(currentIndex)
         }
         delegate: Rectangle {
             id: paper
-            width: image.width
-            height: image.height
+            implicitWidth: image.width
+            implicitHeight: image.height
+            rotation: root.pageRotation
             property alias selection: selection
-            property real __pageScale: image.paintedWidth / document.pagePointSize(index).width
+            property size pagePointSize: document.pagePointSize(index)
+            property real pageScale: image.paintedWidth / pagePointSize.width
             Image {
                 id: image
                 source: document.source
                 currentFrame: index
                 asynchronous: true
                 fillMode: Image.PreserveAspectFit
-                width: document.pagePointSize(currentFrame).width
-                height: document.pagePointSize(currentFrame).height
+                width: pagePointSize.width * root.renderScale
+                height: pagePointSize.height * root.renderScale
+                property real renderScale: root.renderScale
+                property real oldRenderScale: 1
+                onRenderScaleChanged: {
+                    image.sourceSize.width = pagePointSize.width * renderScale
+                    image.sourceSize.height = 0
+                    paper.scale = 1
+                    paper.x = 0
+                    paper.y = 0
+                }
             }
             Shape {
                 anchors.fill: parent
@@ -107,7 +148,7 @@ Item {
                     strokeWidth: 1
                     strokeColor: "blue"
                     fillColor: "cyan"
-                    scale: Qt.size(paper.__pageScale, paper.__pageScale)
+                    scale: Qt.size(paper.pageScale, paper.pageScale)
                     PathMultiline {
                         id: searchResultBoundaries
                         paths: searchModel.matchGeometry
@@ -115,7 +156,7 @@ Item {
                 }
                 ShapePath {
                     fillColor: "orange"
-                    scale: Qt.size(paper.__pageScale, paper.__pageScale)
+                    scale: Qt.size(paper.pageScale, paper.pageScale)
                     PathMultiline {
                         id: selectionBoundaries
                         paths: selection.geometry
@@ -132,11 +173,39 @@ Item {
                 id: selection
                 document: root.document
                 page: image.currentFrame
-                fromPoint: Qt.point(textSelectionDrag.centroid.pressPosition.x / paper.__pageScale, textSelectionDrag.centroid.pressPosition.y / paper.__pageScale)
-                toPoint: Qt.point(textSelectionDrag.centroid.position.x / paper.__pageScale, textSelectionDrag.centroid.position.y / paper.__pageScale)
+                fromPoint: Qt.point(textSelectionDrag.centroid.pressPosition.x / paper.pageScale, textSelectionDrag.centroid.pressPosition.y / paper.pageScale)
+                toPoint: Qt.point(textSelectionDrag.centroid.position.x / paper.pageScale, textSelectionDrag.centroid.position.y / paper.pageScale)
                 hold: !textSelectionDrag.active && !tapHandler.pressed
                 onTextChanged: root.selectedText = text
             }
+            function reRenderIfNecessary() {
+                var newSourceWidth = image.sourceSize.width * paper.scale
+                var ratio = newSourceWidth / image.sourceSize.width
+                if (ratio > 1.1 || ratio < 0.9) {
+                    image.sourceSize.height = 0
+                    image.sourceSize.width = newSourceWidth
+                    paper.scale = 1
+                }
+            }
+            PinchHandler {
+                id: pinch
+                minimumScale: 0.1
+                maximumScale: 10
+                minimumRotation: 0
+                maximumRotation: 0
+                onActiveChanged:
+                    if (active) {
+                        paper.z = 10
+                    } else {
+                        paper.x = 0
+                        paper.y = 0
+                        paper.z = 0
+                        image.width = undefined
+                        image.height = undefined
+                        paper.reRenderIfNecessary()
+                    }
+                grabPermissions: PointerHandler.CanTakeOverFromAnything
+            }
             DragHandler {
                 id: textSelectionDrag
                 acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus
@@ -155,10 +224,10 @@ Item {
                 delegate: Rectangle {
                     color: "transparent"
                     border.color: "lightgrey"
-                    x: rect.x * paper.__pageScale
-                    y: rect.y * paper.__pageScale
-                    width: rect.width * paper.__pageScale
-                    height: rect.height * paper.__pageScale
+                    x: rect.x * paper.pageScale
+                    y: rect.y * paper.pageScale
+                    width: rect.width * paper.pageScale
+                    height: rect.height * paper.pageScale
                     MouseArea { // TODO switch to TapHandler / HoverHandler in 5.15
                         anchors.fill: parent
                         cursorShape: Qt.PointingHandCursor
-- 
GitLab