| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487 |
- // 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.Controls
- import QtQuick.Pdf
- import QtQuick.Shapes
- /*!
- \qmltype PdfScrollablePageView
- \inqmlmodule QtQuick.Pdf
- \brief A complete PDF viewer component to show one page a time, with scrolling.
- PdfScrollablePageView provides a PDF viewer component that shows one page
- at a time, with scrollbars to move around the page. It also 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 pdfviewer example
- demonstrates how to use these features in an application.
- 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 PdfPageView, PdfMultiPageView, PdfStyle
- */
- Flickable {
- /*!
- \qmlproperty PdfDocument PdfScrollablePageView::document
- A PdfDocument object with a valid \c source URL is required:
- \snippet multipageview.qml 0
- */
- required property PdfDocument document
- /*!
- \qmlproperty int PdfScrollablePageView::status
- This property holds the \l {QtQuick::Image::status}{rendering status} of
- the \l {currentPage}{current page}.
- */
- property alias status: image.status
- /*!
- \qmlproperty PdfDocument PdfScrollablePageView::selectedText
- The selected text.
- */
- property alias selectedText: selection.text
- /*!
- \qmlmethod void PdfScrollablePageView::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 PdfScrollablePageView::copySelectionToClipboard()
- Copies the selected text (if any) to the
- \l {QClipboard::Clipboard}{system clipboard}.
- \sa selectAll()
- */
- function copySelectionToClipboard() {
- selection.copyToClipboard()
- }
- // --------------------------------
- // page navigation
- /*!
- \qmlproperty int PdfScrollablePageView::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 PdfScrollablePageView::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 PdfScrollablePageView::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 PdfScrollablePageView::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 PdfScrollablePageView::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 PdfScrollablePageView::goToPage(int page)
- Changes the view to the \a page, if possible.
- \sa PdfPageNavigator::jump(), currentPage
- */
- function goToPage(page) {
- if (page === pageNavigator.currentPage)
- return
- goToLocation(page, Qt.point(0, 0), 0)
- }
- /*!
- \qmlmethod void PdfScrollablePageView::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 real PdfScrollablePageView::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 real PdfScrollablePageView::pageRotation
- This property holds the clockwise rotation of the pages.
- The default value is \c 0 degrees (that is, no rotation relative to the
- orientation of the pages as stored in the PDF file).
- */
- property real pageRotation: 0
- /*!
- \qmlproperty size PdfScrollablePageView::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 PdfScrollablePageView::resetScale()
- Sets \l renderScale back to its default value of \c 1.
- */
- function resetScale() {
- paper.scale = 1
- root.renderScale = 1
- }
- /*!
- \qmlmethod void PdfScrollablePageView::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 pagePointSize = document.pagePointSize(pageNavigator.currentPage)
- root.renderScale = root.width / (paper.rot90 ? pagePointSize.height : pagePointSize.width)
- console.log(lcSPV, "scaling", pagePointSize, "to fit", root.width, "rotated?", paper.rot90, "scale", root.renderScale)
- root.contentX = 0
- root.contentY = 0
- }
- /*!
- \qmlmethod void PdfScrollablePageView::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 \l pageRotation: 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 pagePointSize = document.pagePointSize(pageNavigator.currentPage)
- root.renderScale = Math.min(
- root.width / (paper.rot90 ? pagePointSize.height : pagePointSize.width),
- root.height / (paper.rot90 ? pagePointSize.width : pagePointSize.height) )
- root.contentX = 0
- root.contentY = 0
- }
- // --------------------------------
- // text search
- /*!
- \qmlproperty PdfSearchModel PdfScrollablePageView::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 PdfScrollablePageView::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 PdfScrollablePageView::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 PdfScrollablePageView::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
- PdfStyle { id: style }
- contentWidth: paper.width
- contentHeight: paper.height
- ScrollBar.vertical: ScrollBar {
- onActiveChanged:
- if (!active ) {
- const currentLocation = Qt.point((root.contentX + root.width / 2) / root.renderScale,
- (root.contentY + root.height / 2) / root.renderScale)
- pageNavigator.update(pageNavigator.currentPage, currentLocation, root.renderScale)
- }
- }
- ScrollBar.horizontal: ScrollBar {
- onActiveChanged:
- if (!active ) {
- const currentLocation = Qt.point((root.contentX + root.width / 2) / root.renderScale,
- (root.contentY + root.height / 2) / root.renderScale)
- pageNavigator.update(pageNavigator.currentPage, currentLocation, root.renderScale)
- }
- }
- onRenderScaleChanged: {
- paper.scale = 1
- const currentLocation = Qt.point((root.contentX + root.width / 2) / root.renderScale,
- (root.contentY + root.height / 2) / root.renderScale)
- pageNavigator.update(pageNavigator.currentPage, currentLocation, root.renderScale)
- }
- PdfSearchModel {
- id: searchModel
- document: root.document === undefined ? null : root.document
- onCurrentResultChanged: pageNavigator.jump(currentResultLink)
- }
- PdfPageNavigator {
- id: pageNavigator
- onJumped: function(current) {
- root.renderScale = current.zoom
- const dx = Math.max(0, current.location.x * root.renderScale - root.width / 2) - root.contentX
- const dy = Math.max(0, current.location.y * root.renderScale - root.height / 2) - root.contentY
- // don't jump if location is in the viewport already, i.e. if the "error" between desired and actual contentX/Y is small
- if (Math.abs(dx) > root.width / 3)
- root.contentX += dx
- if (Math.abs(dy) > root.height / 3)
- root.contentY += dy
- console.log(lcSPV, "going to zoom", current.zoom, "loc", current.location,
- "on page", current.page, "ended up @", root.contentX + ", " + root.contentY)
- }
- onCurrentPageChanged: searchModel.currentPage = currentPage
- property url documentSource: root.document.source
- onDocumentSourceChanged: {
- pageNavigator.clear()
- root.resetScale()
- root.contentX = 0
- root.contentY = 0
- }
- }
- LoggingCategory {
- id: lcSPV
- name: "qt.pdf.singlepageview"
- }
- Rectangle {
- id: paper
- width: rot90 ? image.height : image.width
- height: rot90 ? image.width : image.height
- property real rotationModulus: Math.abs(root.pageRotation % 180)
- property bool rot90: rotationModulus > 45 && rotationModulus < 135
- property real minScale: 0.1
- property real maxScale: 10
- PdfPageImage {
- id: image
- document: root.document
- currentFrame: pageNavigator.currentPage
- asynchronous: true
- fillMode: Image.PreserveAspectFit
- rotation: root.pageRotation
- anchors.centerIn: parent
- property real pageScale: image.paintedWidth / document.pagePointSize(pageNavigator.currentPage).width
- width: document.pagePointSize(pageNavigator.currentPage).width * root.renderScale
- height: document.pagePointSize(pageNavigator.currentPage).height * root.renderScale
- sourceSize.width: width * Screen.devicePixelRatio
- sourceSize.height: 0
- Shape {
- anchors.fill: parent
- visible: image.status === Image.Ready
- ShapePath {
- strokeWidth: -1
- fillColor: style.pageSearchResultsColor
- scale: Qt.size(image.pageScale, image.pageScale)
- PathMultiline {
- paths: searchModel.currentPageBoundingPolygons
- }
- }
- ShapePath {
- strokeWidth: style.currentSearchResultStrokeWidth
- strokeColor: style.currentSearchResultStrokeColor
- fillColor: "transparent"
- scale: Qt.size(image.pageScale, image.pageScale)
- PathMultiline {
- paths: searchModel.currentResultBoundingPolygons
- }
- }
- ShapePath {
- fillColor: style.selectionColor
- 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.page, link.location, link.zoom)
- else
- Qt.openUrlExternally(url)
- }
- }
- }
- DragHandler {
- id: textSelectionDrag
- acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus
- target: null
- }
- TapHandler {
- id: mouseClickHandler
- acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus
- }
- TapHandler {
- id: touchTapHandler
- acceptedDevices: PointerDevice.TouchScreen
- onTapped: {
- selection.clear()
- selection.focus = true
- }
- }
- }
- PdfSelection {
- id: selection
- anchors.fill: parent
- document: root.document
- page: pageNavigator.currentPage
- renderScale: image.pageScale == 0 ? 1.0 : image.pageScale
- from: textSelectionDrag.centroid.pressPosition
- to: textSelectionDrag.centroid.position
- hold: !textSelectionDrag.active && !mouseClickHandler.pressed
- focus: true
- }
- PinchHandler {
- id: pinch
- minimumScale: paper.minScale / root.renderScale
- maximumScale: Math.max(1, paper.maxScale / root.renderScale)
- minimumRotation: 0
- maximumRotation: 0
- onActiveChanged:
- if (!active) {
- const centroidInPoints = Qt.point(pinch.centroid.position.x / root.renderScale,
- pinch.centroid.position.y / root.renderScale)
- const centroidInFlickable = root.mapFromItem(paper, pinch.centroid.position.x, pinch.centroid.position.y)
- const newSourceWidth = image.sourceSize.width * paper.scale
- const ratio = newSourceWidth / image.sourceSize.width
- console.log(lcSPV, "pinch ended with centroid", pinch.centroid.position, centroidInPoints, "wrt flickable", centroidInFlickable,
- "page at", paper.x.toFixed(2), paper.y.toFixed(2),
- "contentX/Y were", root.contentX.toFixed(2), root.contentY.toFixed(2))
- if (ratio > 1.1 || ratio < 0.9) {
- const centroidOnPage = Qt.point(centroidInPoints.x * root.renderScale * ratio, centroidInPoints.y * root.renderScale * ratio)
- paper.scale = 1
- paper.x = 0
- paper.y = 0
- root.contentX = centroidOnPage.x - centroidInFlickable.x
- root.contentY = centroidOnPage.y - centroidInFlickable.y
- root.renderScale *= ratio // onRenderScaleChanged calls pageNavigator.update() so we don't need to here
- console.log(lcSPV, "contentX/Y adjusted to", root.contentX.toFixed(2), root.contentY.toFixed(2))
- } else {
- paper.x = 0
- paper.y = 0
- }
- }
- grabPermissions: PointerHandler.CanTakeOverFromAnything
- }
- }
- }
|