PDF Viewer Example#
A Qt Quick PDF viewer that allows scrolling through the pages.
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
import sys
from argparse import ArgumentParser, RawTextHelpFormatter
from pathlib import Path
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtGui import QGuiApplication
from PySide6.QtCore import QCoreApplication, QUrl
import rc_viewer
"""PySide6 port of the pdf/pdfviewer example from Qt v6.x"""
if __name__ == "__main__":
name = "Qt Quick PDF Viewer Example"
QCoreApplication.setApplicationName(name)
QCoreApplication.setOrganizationName("QtProject")
app = QGuiApplication(sys.argv)
dir = Path(__file__).resolve().parent
argument_parser = ArgumentParser(description=name,
formatter_class=RawTextHelpFormatter)
argument_parser.add_argument("file", help="The file to open",
nargs='?', type=str)
options = argument_parser.parse_args()
url = None
if options.file:
url = QUrl.fromLocalFile(options.file)
else:
url = QUrl.fromLocalFile(os.fspath(dir / "resources" / "test.pdf"))
engine = QQmlApplicationEngine()
engine.setInitialProperties({"source": url})
engine.load(QUrl.fromLocalFile(os.fspath(dir / "viewer.qml")))
if not engine.rootObjects():
sys.exit(-1)
exit_code = QCoreApplication.exec()
del engine
sys.exit(exit_code)
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
import QtQuick.Layouts
import QtQuick.Pdf
ApplicationWindow {
id: root
width: 800
height: 1024
color: "lightgrey"
title: document.title
visible: true
required property url source // for main.py
property real scaleStep: Math.sqrt(2)
header: ToolBar {
RowLayout {
anchors.fill: parent
anchors.rightMargin: 6
ToolButton {
action: Action {
shortcut: StandardKey.Open
icon.source: "qrc:/pdfviewer/resources/document-open.svg"
onTriggered: fileDialog.open()
}
}
ToolButton {
action: Action {
shortcut: StandardKey.ZoomIn
enabled: view.sourceSize.width < 10000
icon.source: "qrc:/pdfviewer/resources/zoom-in.svg"
onTriggered: view.renderScale *= root.scaleStep
}
}
ToolButton {
action: Action {
shortcut: StandardKey.ZoomOut
enabled: view.sourceSize.width > 50
icon.source: "qrc:/pdfviewer/resources/zoom-out.svg"
onTriggered: view.renderScale /= root.scaleStep
}
}
ToolButton {
action: Action {
icon.source: "qrc:/pdfviewer/resources/zoom-fit-width.svg"
onTriggered: view.scaleToWidth(root.contentItem.width, root.contentItem.height)
}
}
ToolButton {
action: Action {
icon.source: "qrc:/pdfviewer/resources/zoom-fit-best.svg"
onTriggered: view.scaleToPage(root.contentItem.width, root.contentItem.height)
}
}
ToolButton {
action: Action {
shortcut: "Ctrl+0"
icon.source: "qrc:/pdfviewer/resources/zoom-original.svg"
onTriggered: view.resetScale()
}
}
ToolButton {
action: Action {
shortcut: "Ctrl+L"
icon.source: "qrc:/pdfviewer/resources/rotate-left.svg"
onTriggered: view.pageRotation -= 90
}
}
ToolButton {
action: Action {
shortcut: "Ctrl+R"
icon.source: "qrc:/pdfviewer/resources/rotate-right.svg"
onTriggered: view.pageRotation += 90
}
}
ToolButton {
action: Action {
icon.source: "qrc:/pdfviewer/resources/go-previous-view-page.svg"
enabled: view.backEnabled
onTriggered: view.back()
}
ToolTip.visible: enabled && hovered
ToolTip.delay: 2000
ToolTip.text: "go back"
}
SpinBox {
id: currentPageSB
from: 1
to: document.pageCount
editable: true
value: view.currentPage + 1
onValueModified: view.goToPage(value - 1)
Shortcut {
sequence: StandardKey.MoveToPreviousPage
onActivated: view.goToPage(currentPageSB.value - 2)
}
Shortcut {
sequence: StandardKey.MoveToNextPage
onActivated: view.goToPage(currentPageSB.value)
}
}
ToolButton {
action: Action {
icon.source: "qrc:/pdfviewer/resources/go-next-view-page.svg"
enabled: view.forwardEnabled
onTriggered: view.forward()
}
ToolTip.visible: enabled && hovered
ToolTip.delay: 2000
ToolTip.text: "go forward"
}
ToolButton {
action: Action {
shortcut: StandardKey.SelectAll
icon.source: "qrc:/pdfviewer/resources/edit-select-all.svg"
onTriggered: view.selectAll()
}
}
ToolButton {
action: Action {
shortcut: StandardKey.Copy
icon.source: "qrc:/pdfviewer/resources/edit-copy.svg"
enabled: view.selectedText !== ""
onTriggered: view.copySelectionToClipboard()
}
}
Shortcut {
sequence: StandardKey.Find
onActivated: searchField.forceActiveFocus()
}
Shortcut {
sequence: StandardKey.Quit
onActivated: Qt.quit()
}
}
}
FileDialog {
id: fileDialog
title: "Open a PDF file"
nameFilters: [ "PDF files (*.pdf)" ]
onAccepted: document.source = selectedFile
}
Dialog {
id: passwordDialog
title: "Password"
standardButtons: Dialog.Ok | Dialog.Cancel
modal: true
closePolicy: Popup.CloseOnEscape
anchors.centerIn: parent
width: 300
contentItem: TextField {
id: passwordField
placeholderText: qsTr("Please provide the password")
echoMode: TextInput.Password
width: parent.width
onAccepted: passwordDialog.accept()
}
onOpened: function() { passwordField.forceActiveFocus() }
onAccepted: document.password = passwordField.text
}
Dialog {
id: errorDialog
title: "Error loading " + document.source
standardButtons: Dialog.Close
modal: true
closePolicy: Popup.CloseOnEscape
anchors.centerIn: parent
width: 300
visible: document.status === PdfDocument.Error
contentItem: Label {
id: errorField
text: document.error
}
}
PdfScrollablePageView {
id: view
anchors.fill: parent
anchors.leftMargin: searchDrawer.position * searchDrawer.width
document: PdfDocument {
id: document
source: Qt.resolvedUrl(root.source)
onPasswordRequired: passwordDialog.open()
}
searchString: searchField.text
}
Drawer {
id: searchDrawer
edge: Qt.LeftEdge
// modal: false
// dim: false // commented out as workaround for QTBUG-83859
width: 300
y: root.header.height
height: view.height
clip: true
ListView {
id: searchResultsList
anchors.fill: parent
anchors.margins: 2
model: view.searchModel
currentIndex: view.searchModel.currentResult
ScrollBar.vertical: ScrollBar { }
delegate: ItemDelegate {
id: resultDelegate
required property int index
required property int page
required property string contextBefore
required property string contextAfter
width: parent ? parent.width : 0
RowLayout {
anchors.fill: parent
spacing: 0
Label {
text: "Page " + (resultDelegate.page + 1) + ": "
}
Label {
text: resultDelegate.contextBefore
elide: Text.ElideLeft
horizontalAlignment: Text.AlignRight
Layout.fillWidth: true
Layout.preferredWidth: parent.width / 2
}
Label {
font.bold: true
text: view.searchString
width: implicitWidth
}
Label {
text: resultDelegate.contextAfter
elide: Text.ElideRight
Layout.fillWidth: true
Layout.preferredWidth: parent.width / 2
}
}
highlighted: ListView.isCurrentItem
onClicked: view.searchModel.currentResult = resultDelegate.index
}
}
}
footer: ToolBar {
height: footerRow.implicitHeight
RowLayout {
id: footerRow
anchors.fill: parent
ToolButton {
action: Action {
icon.source: "qrc:/pdfviewer/resources/go-up-search.svg"
shortcut: StandardKey.FindPrevious
onTriggered: view.searchBack()
}
ToolTip.visible: enabled && hovered
ToolTip.delay: 2000
ToolTip.text: "find previous"
}
TextField {
id: searchField
placeholderText: "search"
Layout.minimumWidth: 150
Layout.maximumWidth: 300
Layout.fillWidth: true
onAccepted: searchDrawer.open()
Image {
visible: searchField.text !== ""
source: "qrc:/pdfviewer/resources/edit-clear.svg"
anchors {
right: parent.right
top: parent.top
bottom: parent.bottom
margins: 3
rightMargin: 5
}
TapHandler {
onTapped: searchField.clear()
}
}
}
ToolButton {
action: Action {
icon.source: "qrc:/pdfviewer/resources/go-down-search.svg"
shortcut: StandardKey.FindNext
onTriggered: view.searchForward()
}
ToolTip.visible: enabled && hovered
ToolTip.delay: 2000
ToolTip.text: "find next"
}
Label {
Layout.fillWidth: true
property size implicitPointSize: document.pagePointSize(view.currentPage)
text: "page " + (view.currentPage + 1) + " of " + document.pageCount +
" scale " + view.renderScale.toFixed(2) +
" original " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1) + "pts"
visible: document.status === PdfDocument.Ready
}
}
}
}
<RCC>
<qresource prefix="/pdfviewer">
<file>resources/document-open.svg</file>
<file>resources/edit-clear.svg</file>
<file>resources/edit-copy.svg</file>
<file>resources/edit-select-all.svg</file>
<file>resources/go-down-search.svg</file>
<file>resources/go-next-view-page.svg</file>
<file>resources/go-previous-view-page.svg</file>
<file>resources/go-up-search.svg</file>
<file>resources/rotate-left.svg</file>
<file>resources/rotate-right.svg</file>
<file>resources/test.pdf</file>
<file>resources/zoom-in.svg</file>
<file>resources/zoom-fit-best.svg</file>
<file>resources/zoom-fit-width.svg</file>
<file>resources/zoom-original.svg</file>
<file>resources/zoom-out.svg</file>
</qresource>
</RCC>