blob: b90ad2d7f6c7f204d22d30594a42db3542fa4a12 [file] [log] [blame]
/****************************************************************************
**
** 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$
**
****************************************************************************/
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Pdf 5.15
import QtQuick.Shapes 1.14
import Qt.labs.animation 1.0
Rectangle {
// public API
// TODO 5.15: required property
property var document: undefined
property alias status: image.status
property alias selectedText: selection.text
function selectAll() {
selection.selectAll()
}
function copySelectionToClipboard() {
selection.copyToClipboard()
}
// page navigation
property alias currentPage: navigationStack.currentPage
property alias backEnabled: navigationStack.backAvailable
property alias forwardEnabled: navigationStack.forwardAvailable
function back() { navigationStack.back() }
function forward() { navigationStack.forward() }
function goToPage(page) { goToLocation(page, Qt.point(0, 0), 0) }
function goToLocation(page, location, zoom) {
if (zoom > 0)
root.renderScale = zoom
navigationStack.push(page, location, zoom)
}
// page scaling
property real renderScale: 1
property alias sourceSize: image.sourceSize
function resetScale() {
image.sourceSize.width = 0
image.sourceSize.height = 0
root.x = 0
root.y = 0
root.scale = 1
}
function scaleToWidth(width, height) {
var halfRotation = Math.abs(root.rotation % 180)
image.sourceSize = Qt.size((halfRotation > 45 && halfRotation < 135) ? height : width, 0)
root.x = 0
root.y = 0
image.centerInSize = Qt.size(width, height)
image.centerOnLoad = true
image.vCenterOnLoad = (halfRotation > 45 && halfRotation < 135)
root.scale = 1
}
function scaleToPage(width, height) {
var windowAspect = width / height
var halfRotation = Math.abs(root.rotation % 180)
var pagePointSize = document.pagePointSize(navigationStack.currentPage)
if (halfRotation > 45 && halfRotation < 135) {
// rotated 90 or 270ยบ
var pageAspect = pagePointSize.height / pagePointSize.width
if (windowAspect > pageAspect) {
image.sourceSize = Qt.size(height, 0)
} else {
image.sourceSize = Qt.size(0, width)
}
} else {
var pageAspect = pagePointSize.width / pagePointSize.height
if (windowAspect > pageAspect) {
image.sourceSize = Qt.size(0, height)
} else {
image.sourceSize = Qt.size(width, 0)
}
}
image.centerInSize = Qt.size(width, height)
image.centerOnLoad = true
image.vCenterOnLoad = true
root.scale = 1
}
// text search
property alias searchModel: searchModel
property alias searchString: searchModel.searchString
function searchBack() { --searchModel.currentResult }
function searchForward() { ++searchModel.currentResult }
// implementation
id: root
width: image.width
height: image.height
PdfSelection {
id: selection
document: root.document
page: navigationStack.currentPage
fromPoint: Qt.point(textSelectionDrag.centroid.pressPosition.x / image.pageScale, textSelectionDrag.centroid.pressPosition.y / image.pageScale)
toPoint: Qt.point(textSelectionDrag.centroid.position.x / image.pageScale, textSelectionDrag.centroid.position.y / image.pageScale)
hold: !textSelectionDrag.active && !tapHandler.pressed
}
PdfSearchModel {
id: searchModel
document: root.document === undefined ? null : root.document
onCurrentPageChanged: root.goToPage(currentPage)
}
PdfNavigationStack {
id: navigationStack
onCurrentPageChanged: searchModel.currentPage = currentPage
// TODO onCurrentLocationChanged: position currentLocation.x and .y in middle // currentPageChanged() MUST occur first!
onCurrentZoomChanged: root.renderScale = currentZoom
// TODO deal with horizontal location (need WheelHandler or Flickable probably)
}
Image {
id: image
currentFrame: navigationStack.currentPage
source: document.status === PdfDocument.Ready ? document.source : ""
asynchronous: true
fillMode: Image.PreserveAspectFit
property bool centerOnLoad: false
property bool vCenterOnLoad: false
property size centerInSize
property real pageScale: image.paintedWidth / document.pagePointSize(navigationStack.currentPage).width
function reRenderIfNecessary() {
var newSourceWidth = image.sourceSize.width * root.scale
var ratio = newSourceWidth / image.sourceSize.width
if (ratio > 1.1 || ratio < 0.9) {
image.sourceSize.width = newSourceWidth
image.sourceSize.height = 0
root.scale = 1
}
}
onStatusChanged:
if (status == Image.Ready && centerOnLoad) {
root.x = (centerInSize.width - image.implicitWidth) / 2
root.y = vCenterOnLoad ? (centerInSize.height - image.implicitHeight) / 2 : 0
centerOnLoad = false
vCenterOnLoad = false
}
}
onRenderScaleChanged: {
image.sourceSize.width = document.pagePointSize(navigationStack.currentPage).width * renderScale
image.sourceSize.height = 0
root.scale = 1
}
Shape {
anchors.fill: parent
opacity: 0.25
visible: image.status === Image.Ready
ShapePath {
strokeWidth: 1
strokeColor: "cyan"
fillColor: "steelblue"
scale: Qt.size(image.pageScale, image.pageScale)
PathMultiline {
paths: searchModel.currentPageBoundingPolygons
}
}
ShapePath {
strokeWidth: 1
strokeColor: "orange"
fillColor: "cyan"
scale: Qt.size(image.pageScale, image.pageScale)
PathMultiline {
paths: searchModel.currentResultBoundingPolygons
}
}
ShapePath {
fillColor: "orange"
scale: Qt.size(image.pageScale, image.pageScale)
PathMultiline {
paths: selection.geometry
}
}
}
Repeater {
model: PdfLinkModel {
id: linkModel
document: root.document
page: navigationStack.currentPage
}
delegate: Rectangle {
color: "transparent"
border.color: "lightgrey"
x: rect.x * image.pageScale
y: rect.y * image.pageScale
width: rect.width * image.pageScale
height: rect.height * image.pageScale
MouseArea { // TODO switch to TapHandler / HoverHandler in 5.15
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (page >= 0)
navigationStack.push(page, Qt.point(0, 0), root.renderScale)
else
Qt.openUrlExternally(url)
}
}
}
}
PinchHandler {
id: pinch
minimumScale: 0.1
maximumScale: 10
minimumRotation: 0
maximumRotation: 0
onActiveChanged: if (!active) image.reRenderIfNecessary()
grabPermissions: PinchHandler.TakeOverForbidden // don't allow takeover if pinch has started
}
DragHandler {
id: pageMovingTouchDrag
acceptedDevices: PointerDevice.TouchScreen
}
DragHandler {
id: pageMovingMiddleMouseDrag
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus
acceptedButtons: Qt.MiddleButton
snapMode: DragHandler.NoSnap
}
DragHandler {
id: textSelectionDrag
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus
target: null
}
TapHandler {
id: tapHandler
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus
}
// prevent it from being scrolled out of view
BoundaryRule on x {
minimum: 100 - root.width
maximum: root.parent.width - 100
}
BoundaryRule on y {
minimum: 100 - root.height
maximum: root.parent.height - 100
}
}