| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- // 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
- }
- }
|