summaryrefslogtreecommitdiffstats
path: root/src/pdfquick/PdfPageView.qml
diff options
context:
space:
mode:
Diffstat (limited to 'src/pdfquick/PdfPageView.qml')
-rw-r--r--src/pdfquick/PdfPageView.qml439
1 files changed, 439 insertions, 0 deletions
diff --git a/src/pdfquick/PdfPageView.qml b/src/pdfquick/PdfPageView.qml
new file mode 100644
index 000000000..e1d97f57b
--- /dev/null
+++ b/src/pdfquick/PdfPageView.qml
@@ -0,0 +1,439 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Pdf
+import QtQuick.Shapes
+
+/*!
+ \qmltype PdfPageView
+ \inqmlmodule QtQuick.Pdf
+ \brief A PDF viewer component to show one page a time.
+
+ PdfPageView provides a PDF viewer component that shows one whole page at a
+ time, without scrolling. It supports selecting text and copying it to the
+ clipboard, zooming in and out, clicking an internal link to jump to another
+ section in the document, rotating the view, and searching for text.
+
+ The implementation is a QML assembly of smaller building blocks that are
+ available separately. In case you want to make changes in your own version
+ of this component, you can copy the QML, which is installed into the
+ \c QtQuick/Pdf/qml module directory, and modify it as needed.
+
+ \sa PdfScrollablePageView, PdfMultiPageView, PdfStyle
+*/
+Rectangle {
+ /*!
+ \qmlproperty PdfDocument PdfPageView::document
+
+ A PdfDocument object with a valid \c source URL is required:
+
+ \snippet pdfpageview.qml 0
+ */
+ required property PdfDocument document
+
+ /*!
+ \qmlproperty int PdfPageView::status
+
+ This property holds the \l {QtQuick::Image::status}{rendering status} of
+ the \l {currentPage}{current page}.
+ */
+ property alias status: image.status
+
+ /*!
+ \qmlproperty PdfDocument PdfPageView::selectedText
+
+ The selected text.
+ */
+ property alias selectedText: selection.text
+
+ /*!
+ \qmlmethod void PdfPageView::selectAll()
+
+ Selects all the text on the \l {currentPage}{current page}, and makes it
+ available as the system \l {QClipboard::Selection}{selection} on systems
+ that support that feature.
+
+ \sa copySelectionToClipboard()
+ */
+ function selectAll() {
+ selection.selectAll()
+ }
+
+ /*!
+ \qmlmethod void PdfPageView::copySelectionToClipboard()
+
+ Copies the selected text (if any) to the
+ \l {QClipboard::Clipboard}{system clipboard}.
+
+ \sa selectAll()
+ */
+ function copySelectionToClipboard() {
+ selection.copyToClipboard()
+ }
+
+ // --------------------------------
+ // page navigation
+
+ /*!
+ \qmlproperty int PdfPageView::currentPage
+ \readonly
+
+ This property holds the zero-based page number of the page visible in the
+ scrollable view. If there is no current page, it holds -1.
+
+ This property is read-only, and is typically used in a binding (or
+ \c onCurrentPageChanged script) to update the part of the user interface
+ that shows the current page number, such as a \l SpinBox.
+
+ \sa PdfPageNavigator::currentPage
+ */
+ property alias currentPage: pageNavigator.currentPage
+
+ /*!
+ \qmlproperty bool PdfPageView::backEnabled
+ \readonly
+
+ This property indicates if it is possible to go back in the navigation
+ history to a previous-viewed page.
+
+ \sa PdfPageNavigator::backAvailable, back()
+ */
+ property alias backEnabled: pageNavigator.backAvailable
+
+ /*!
+ \qmlproperty bool PdfPageView::forwardEnabled
+ \readonly
+
+ This property indicates if it is possible to go to next location in the
+ navigation history.
+
+ \sa PdfPageNavigator::forwardAvailable, forward()
+ */
+ property alias forwardEnabled: pageNavigator.forwardAvailable
+
+ /*!
+ \qmlmethod void PdfPageView::back()
+
+ Scrolls the view back to the previous page that the user visited most
+ recently; or does nothing if there is no previous location on the
+ navigation stack.
+
+ \sa PdfPageNavigator::back(), currentPage, backEnabled
+ */
+ function back() { pageNavigator.back() }
+
+ /*!
+ \qmlmethod void PdfPageView::forward()
+
+ Scrolls the view to the page that the user was viewing when the back()
+ method was called; or does nothing if there is no "next" location on the
+ navigation stack.
+
+ \sa PdfPageNavigator::forward(), currentPage
+ */
+ function forward() { pageNavigator.forward() }
+
+ /*!
+ \qmlmethod void PdfPageView::goToPage(int page)
+
+ Changes the view to the \a page, if possible.
+
+ \sa PdfPageNavigator::jump(), currentPage
+ */
+ function goToPage(page) { goToLocation(page, Qt.point(0, 0), 0) }
+
+ /*!
+ \qmlmethod void PdfPageView::goToLocation(int page, point location, real zoom)
+
+ Scrolls the view to the \a location on the \a page, if possible,
+ and sets the \a zoom level.
+
+ \sa PdfPageNavigator::jump(), currentPage
+ */
+ function goToLocation(page, location, zoom) {
+ if (zoom > 0)
+ root.renderScale = zoom
+ pageNavigator.jump(page, location, zoom)
+ }
+
+ // --------------------------------
+ // page scaling
+
+ /*!
+ \qmlproperty bool PdfPageView::zoomEnabled
+
+ This property holds whether the user can use the pinch gesture or
+ Control + mouse wheel to zoom. The default is \c true.
+
+ When the user zooms the page, the size of PdfPageView changes.
+ */
+ property bool zoomEnabled: true
+
+ /*!
+ \qmlproperty real PdfPageView::renderScale
+
+ This property holds the ratio of pixels to points. The default is \c 1,
+ meaning one point (1/72 of an inch) equals 1 logical pixel.
+ */
+ property real renderScale: 1
+
+ /*!
+ \qmlproperty size PdfPageView::sourceSize
+
+ This property holds the scaled width and height of the full-frame image.
+
+ \sa {QtQuick::Image::sourceSize}{Image.sourceSize}
+ */
+ property alias sourceSize: image.sourceSize
+
+ /*!
+ \qmlmethod void PdfPageView::resetScale()
+
+ Sets \l renderScale back to its default value of \c 1.
+ */
+ function resetScale() {
+ image.sourceSize.width = 0
+ image.sourceSize.height = 0
+ root.scale = 1
+ }
+
+ /*!
+ \qmlmethod void PdfPageView::scaleToWidth(real width, real height)
+
+ Sets \l renderScale such that the width of the first page will fit into a
+ viewport with the given \a width and \a height. If the page is not rotated,
+ it will be scaled so that its width fits \a width. If it is rotated +/- 90
+ degrees, it will be scaled so that its width fits \a height.
+ */
+ function scaleToWidth(width, height) {
+ const halfRotation = Math.abs(root.rotation % 180)
+ image.sourceSize = Qt.size((halfRotation > 45 && halfRotation < 135) ? height : width, 0)
+ image.centerInSize = Qt.size(width, height)
+ image.centerOnLoad = true
+ image.vCenterOnLoad = (halfRotation > 45 && halfRotation < 135)
+ root.scale = 1
+ }
+
+ /*!
+ \qmlmethod void PdfPageView::scaleToPage(real width, real height)
+
+ Sets \l renderScale such that the whole first page will fit into a viewport
+ with the given \a width and \a height. The resulting \l renderScale depends
+ on page rotation: the page will fit into the viewport at a larger size if it
+ is first rotated to have a matching aspect ratio.
+ */
+ function scaleToPage(width, height) {
+ const windowAspect = width / height
+ const halfRotation = Math.abs(root.rotation % 180)
+ const pagePointSize = document.pagePointSize(pageNavigator.currentPage)
+ const pageAspect = pagePointSize.height / pagePointSize.width
+ if (halfRotation > 45 && halfRotation < 135) {
+ // rotated 90 or 270ยบ
+ if (windowAspect > pageAspect) {
+ image.sourceSize = Qt.size(height, 0)
+ } else {
+ image.sourceSize = Qt.size(0, width)
+ }
+ } else {
+ 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
+
+ /*!
+ \qmlproperty PdfSearchModel PdfPageView::searchModel
+
+ This property holds a PdfSearchModel containing the list of search results
+ for a given \l searchString.
+
+ \sa PdfSearchModel
+ */
+ property alias searchModel: searchModel
+
+ /*!
+ \qmlproperty string PdfPageView::searchString
+
+ This property holds the search string that the user may choose to search
+ for. It is typically used in a binding to the \c text property of a
+ TextField.
+
+ \sa searchModel
+ */
+ property alias searchString: searchModel.searchString
+
+ /*!
+ \qmlmethod void PdfPageView::searchBack()
+
+ Decrements the
+ \l{PdfSearchModel::currentResult}{searchModel's current result}
+ so that the view will jump to the previous search result.
+ */
+ function searchBack() { --searchModel.currentResult }
+
+ /*!
+ \qmlmethod void PdfPageView::searchForward()
+
+ Increments the
+ \l{PdfSearchModel::currentResult}{searchModel's current result}
+ so that the view will jump to the next search result.
+ */
+ function searchForward() { ++searchModel.currentResult }
+
+ // --------------------------------
+ // implementation
+ id: root
+ width: image.width
+ height: image.height
+
+ PdfSelection {
+ id: selection
+ document: root.document
+ page: pageNavigator.currentPage
+ from: Qt.point(textSelectionDrag.centroid.pressPosition.x / image.pageScale, textSelectionDrag.centroid.pressPosition.y / image.pageScale)
+ to: 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)
+ }
+
+ PdfPageNavigator {
+ id: pageNavigator
+ onCurrentPageChanged: searchModel.currentPage = currentPage
+ onCurrentZoomChanged: root.renderScale = currentZoom
+
+ property url documentSource: root.document.source
+ onDocumentSourceChanged: {
+ pageNavigator.clear()
+ root.goToPage(0)
+ }
+ }
+
+ PdfPageImage {
+ id: image
+ document: root.document
+ currentFrame: pageNavigator.currentPage
+ asynchronous: true
+ fillMode: Image.PreserveAspectFit
+ property bool centerOnLoad: false
+ property bool vCenterOnLoad: false
+ property size centerInSize
+ property real pageScale: image.paintedWidth / document.pagePointSize(pageNavigator.currentPage).width
+ function reRenderIfNecessary() {
+ const newSourceWidth = image.sourceSize.width * root.scale * Screen.devicePixelRatio
+ const 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(pageNavigator.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: pageNavigator.currentPage
+ }
+ delegate: PdfLinkDelegate {
+ x: rectangle.x * image.pageScale
+ y: rectangle.y * image.pageScale
+ width: rectangle.width * image.pageScale
+ height: rectangle.height * image.pageScale
+ visible: image.status === Image.Ready
+ onTapped:
+ (link) => {
+ if (link.page >= 0)
+ pageNavigator.jump(link)
+ else
+ Qt.openUrlExternally(url)
+ }
+ }
+ }
+
+ PinchHandler {
+ id: pinch
+ enabled: root.zoomEnabled && root.scale * root.renderScale <= 10 && root.scale * root.renderScale >= 0.1
+ 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
+ }
+ WheelHandler {
+ enabled: pinch.enabled
+ acceptedModifiers: Qt.ControlModifier
+ property: "scale"
+ onActiveChanged: if (!active) image.reRenderIfNecessary()
+ }
+ DragHandler {
+ id: textSelectionDrag
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus
+ target: null
+ }
+ TapHandler {
+ id: tapHandler
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus
+ }
+}