PDF Viewer Example

The PDF Viewer example demonstrates how to use the QPdfView class to render PDF documents and the QPdfPageNavigator class to navigate them.

Download this example

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

import sys
from argparse import ArgumentParser, RawTextHelpFormatter

from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QCoreApplication, QUrl

from mainwindow import MainWindow

"""PySide6 port of the pdfwidgets/pdfviewer example from Qt v6.x"""


if __name__ == "__main__":
    argument_parser = ArgumentParser(description="PDF Viewer",
                                     formatter_class=RawTextHelpFormatter)
    argument_parser.add_argument("file", help="The file to open",
                                 nargs='?', type=str)
    options = argument_parser.parse_args()

    a = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    if options.file:
        w.open(QUrl.fromLocalFile(options.file))
    sys.exit(QCoreApplication.exec())
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

import math
import sys

from PySide6.QtPdf import QPdfBookmarkModel, QPdfDocument
from PySide6.QtPdfWidgets import QPdfView
from PySide6.QtWidgets import (QDialog, QFileDialog, QMainWindow, QMessageBox,
                               QSpinBox)
from PySide6.QtCore import QModelIndex, QPoint, QStandardPaths, QUrl, Slot

from zoomselector import ZoomSelector
from ui_mainwindow import Ui_MainWindow


ZOOM_MULTIPLIER = math.sqrt(2.0)


class MainWindow(QMainWindow):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_MainWindow()
        self.m_zoomSelector = ZoomSelector(self)
        self.m_pageSelector = QSpinBox(self)
        self.m_document = QPdfDocument(self)
        self.m_fileDialog = None

        self.ui.setupUi(self)

        self.m_zoomSelector.setMaximumWidth(150)
        self.ui.mainToolBar.insertWidget(self.ui.actionZoom_In, self.m_zoomSelector)

        self.ui.mainToolBar.insertWidget(self.ui.actionForward, self.m_pageSelector)
        self.m_pageSelector.valueChanged.connect(self.page_selected)
        nav = self.ui.pdfView.pageNavigator()
        nav.currentPageChanged.connect(self.m_pageSelector.setValue)
        nav.backAvailableChanged.connect(self.ui.actionBack.setEnabled)
        nav.forwardAvailableChanged.connect(self.ui.actionForward.setEnabled)

        self.m_zoomSelector.zoom_mode_changed.connect(self.ui.pdfView.setZoomMode)
        self.m_zoomSelector.zoom_factor_changed.connect(self.ui.pdfView.setZoomFactor)
        self.m_zoomSelector.reset()

        bookmark_model = QPdfBookmarkModel(self)
        bookmark_model.setDocument(self.m_document)

        self.ui.bookmarkView.setModel(bookmark_model)
        self.ui.bookmarkView.activated.connect(self.bookmark_selected)

        self.ui.tabWidget.setTabEnabled(1, False)  # disable 'Pages' tab for now

        self.ui.pdfView.setDocument(self.m_document)

        self.ui.pdfView.zoomFactorChanged.connect(self.m_zoomSelector.set_zoom_factor)

    @Slot(QUrl)
    def open(self, doc_location):
        if doc_location.isLocalFile():
            self.m_document.load(doc_location.toLocalFile())
            document_title = self.m_document.metaData(QPdfDocument.MetaDataField.Title)
            self.setWindowTitle(document_title if document_title else "PDF Viewer")
            self.page_selected(0)
            self.m_pageSelector.setMaximum(self.m_document.pageCount() - 1)
        else:
            message = f"{doc_location} is not a valid local file"
            print(message, file=sys.stderr)
            QMessageBox.critical(self, "Failed to open", message)

    @Slot(QModelIndex)
    def bookmark_selected(self, index):
        if not index.isValid():
            return
        page = index.data(int(QPdfBookmarkModel.Role.Page))
        zoom_level = index.data(int(QPdfBookmarkModel.Role.Level))
        self.ui.pdfView.pageNavigator().jump(page, QPoint(), zoom_level)

    @Slot(int)
    def page_selected(self, page):
        nav = self.ui.pdfView.pageNavigator()
        nav.jump(page, QPoint(), nav.currentZoom())

    @Slot()
    def on_actionOpen_triggered(self):
        if not self.m_fileDialog:
            directory = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation)
            self.m_fileDialog = QFileDialog(self, "Choose a PDF", directory)
            self.m_fileDialog.setAcceptMode(QFileDialog.AcceptOpen)
            self.m_fileDialog.setMimeTypeFilters(["application/pdf"])
        if self.m_fileDialog.exec() == QDialog.Accepted:
            to_open = self.m_fileDialog.selectedUrls()[0]
            if to_open.isValid():
                self.open(to_open)

    @Slot()
    def on_actionQuit_triggered(self):
        self.close()

    @Slot()
    def on_actionAbout_triggered(self):
        QMessageBox.about(self, "About PdfViewer",
                          "An example using QPdfDocument")

    @Slot()
    def on_actionAbout_Qt_triggered(self):
        QMessageBox.aboutQt(self)

    @Slot()
    def on_actionZoom_In_triggered(self):
        factor = self.ui.pdfView.zoomFactor() * ZOOM_MULTIPLIER
        self.ui.pdfView.setZoomFactor(factor)

    @Slot()
    def on_actionZoom_Out_triggered(self):
        factor = self.ui.pdfView.zoomFactor() / ZOOM_MULTIPLIER
        self.ui.pdfView.setZoomFactor(factor)

    @Slot()
    def on_actionPrevious_Page_triggered(self):
        nav = self.ui.pdfView.pageNavigator()
        nav.jump(nav.currentPage() - 1, QPoint(), nav.currentZoom())

    @Slot()
    def on_actionNext_Page_triggered(self):
        nav = self.ui.pdfView.pageNavigator()
        nav.jump(nav.currentPage() + 1, QPoint(), nav.currentZoom())

    @Slot()
    def on_actionContinuous_triggered(self):
        cont_checked = self.ui.actionContinuous.isChecked()
        mode = QPdfView.PageMode.MultiPage if cont_checked else QPdfView.PageMode.SinglePage
        self.ui.pdfView.setPageMode(mode)

    @Slot()
    def on_actionBack_triggered(self):
        self.ui.pdfView.pageNavigator().back()

    @Slot()
    def on_actionForward_triggered(self):
        self.ui.pdfView.pageNavigator().forward()
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

from PySide6.QtPdfWidgets import QPdfView
from PySide6.QtWidgets import QComboBox
from PySide6.QtCore import Signal, Slot


class ZoomSelector(QComboBox):

    zoom_mode_changed = Signal(QPdfView.ZoomMode)
    zoom_factor_changed = Signal(float)

    def __init__(self, parent):
        super().__init__(parent)
        self.setEditable(True)

        self.addItem("Fit Width")
        self.addItem("Fit Page")
        self.addItem("12%")
        self.addItem("25%")
        self.addItem("33%")
        self.addItem("50%")
        self.addItem("66%")
        self.addItem("75%")
        self.addItem("100%")
        self.addItem("125%")
        self.addItem("150%")
        self.addItem("200%")
        self.addItem("400%")

        self.currentTextChanged.connect(self.on_current_text_changed)
        self.lineEdit().editingFinished.connect(self._editing_finished)

    @Slot(float)
    def set_zoom_factor(self, zoomFactor):
        percent = int(zoomFactor * 100)
        self.setCurrentText(f"{percent}%")

    @Slot()
    def reset(self):
        self.setCurrentIndex(8)  # 100%

    @Slot(str)
    def on_current_text_changed(self, text):
        if text == "Fit Width":
            self.zoom_mode_changed.emit(QPdfView.ZoomMode.FitToWidth)
        elif text == "Fit Page":
            self.zoom_mode_changed.emit(QPdfView.ZoomMode.FitInView)
        elif text.endswith("%"):
            factor = 1.0
            zoom_level = int(text[:-1])
            factor = zoom_level / 100.0
            self.zoom_mode_changed.emit(QPdfView.ZoomMode.Custom)
            self.zoom_factor_changed.emit(factor)

    @Slot()
    def _editing_finished(self):
        self.on_current_text_changed(self.lineEdit().text())
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>700</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>PDF Viewer</string>
  </property>
  <property name="unifiedTitleAndToolBarOnMac">
   <bool>true</bool>
  </property>
  <widget class="QWidget" name="centralWidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <property name="spacing">
     <number>0</number>
    </property>
    <property name="leftMargin">
     <number>0</number>
    </property>
    <property name="topMargin">
     <number>0</number>
    </property>
    <property name="rightMargin">
     <number>0</number>
    </property>
    <property name="bottomMargin">
     <number>0</number>
    </property>
    <item>
     <widget class="QWidget" name="widget" native="true">
      <layout class="QVBoxLayout" name="verticalLayout_2">
       <property name="spacing">
        <number>0</number>
       </property>
       <property name="leftMargin">
        <number>0</number>
       </property>
       <property name="topMargin">
        <number>0</number>
       </property>
       <property name="rightMargin">
        <number>0</number>
       </property>
       <property name="bottomMargin">
        <number>0</number>
       </property>
       <item>
        <widget class="QSplitter" name="splitter">
         <property name="orientation">
          <enum>Qt::Orientation::Horizontal</enum>
         </property>
         <widget class="QTabWidget" name="tabWidget">
          <property name="sizePolicy">
           <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
            <horstretch>0</horstretch>
            <verstretch>0</verstretch>
           </sizepolicy>
          </property>
          <property name="tabPosition">
           <enum>QTabWidget::TabPosition::West</enum>
          </property>
          <property name="currentIndex">
           <number>0</number>
          </property>
          <property name="documentMode">
           <bool>false</bool>
          </property>
          <widget class="QWidget" name="bookmarkTab">
           <attribute name="title">
            <string>Bookmarks</string>
           </attribute>
           <layout class="QVBoxLayout" name="verticalLayout_3">
            <property name="spacing">
             <number>0</number>
            </property>
            <property name="leftMargin">
             <number>2</number>
            </property>
            <property name="topMargin">
             <number>2</number>
            </property>
            <property name="rightMargin">
             <number>2</number>
            </property>
            <property name="bottomMargin">
             <number>2</number>
            </property>
            <item>
             <widget class="QTreeView" name="bookmarkView">
              <property name="sizePolicy">
               <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
                <horstretch>0</horstretch>
                <verstretch>0</verstretch>
               </sizepolicy>
              </property>
              <property name="headerHidden">
               <bool>true</bool>
              </property>
             </widget>
            </item>
           </layout>
          </widget>
          <widget class="QWidget" name="pagesTab">
           <attribute name="title">
            <string>Pages</string>
           </attribute>
          </widget>
         </widget>
         <widget class="QPdfView" name="pdfView" native="true">
          <property name="sizePolicy">
           <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
            <horstretch>10</horstretch>
            <verstretch>0</verstretch>
           </sizepolicy>
          </property>
         </widget>
        </widget>
       </item>
      </layout>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menuBar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>700</width>
     <height>26</height>
    </rect>
   </property>
   <widget class="QMenu" name="menuFile">
    <property name="title">
     <string>File</string>
    </property>
    <addaction name="actionOpen"/>
    <addaction name="actionQuit"/>
   </widget>
   <widget class="QMenu" name="menuHelp">
    <property name="title">
     <string>Help</string>
    </property>
    <addaction name="actionAbout"/>
    <addaction name="actionAbout_Qt"/>
   </widget>
   <widget class="QMenu" name="menuView">
    <property name="title">
     <string>View</string>
    </property>
    <addaction name="actionZoom_In"/>
    <addaction name="actionZoom_Out"/>
    <addaction name="actionPrevious_Page"/>
    <addaction name="actionNext_Page"/>
    <addaction name="separator"/>
    <addaction name="actionContinuous"/>
   </widget>
   <addaction name="menuFile"/>
   <addaction name="menuView"/>
   <addaction name="menuHelp"/>
  </widget>
  <widget class="QToolBar" name="mainToolBar">
   <property name="movable">
    <bool>false</bool>
   </property>
   <property name="floatable">
    <bool>false</bool>
   </property>
   <attribute name="toolBarArea">
    <enum>TopToolBarArea</enum>
   </attribute>
   <attribute name="toolBarBreak">
    <bool>false</bool>
   </attribute>
   <addaction name="actionOpen"/>
   <addaction name="separator"/>
   <addaction name="actionZoom_Out"/>
   <addaction name="actionZoom_In"/>
   <addaction name="separator"/>
   <addaction name="actionBack"/>
   <addaction name="actionForward"/>
  </widget>
  <widget class="QStatusBar" name="statusBar"/>
  <action name="actionOpen">
   <property name="icon">
    <iconset theme="document-open" resource="resources.qrc">
     <normaloff>:/icons/images/document-open.svgz</normaloff>:/icons/images/document-open.svgz</iconset>
   </property>
   <property name="text">
    <string>Open...</string>
   </property>
   <property name="shortcut">
    <string>Ctrl+O</string>
   </property>
  </action>
  <action name="actionQuit">
   <property name="icon">
    <iconset theme="application-exit"/>
   </property>
   <property name="text">
    <string>Quit</string>
   </property>
   <property name="shortcut">
    <string>Ctrl+Q</string>
   </property>
  </action>
  <action name="actionAbout">
   <property name="icon">
    <iconset theme="help-about"/>
   </property>
   <property name="text">
    <string>About</string>
   </property>
  </action>
  <action name="actionAbout_Qt">
   <property name="text">
    <string>About Qt</string>
   </property>
  </action>
  <action name="actionZoom_In">
   <property name="icon">
    <iconset theme="zoom-in" resource="resources.qrc">
     <normaloff>:/icons/images/zoom-in.svgz</normaloff>:/icons/images/zoom-in.svgz</iconset>
   </property>
   <property name="text">
    <string>Zoom In</string>
   </property>
   <property name="shortcut">
    <string>Ctrl++</string>
   </property>
  </action>
  <action name="actionZoom_Out">
   <property name="icon">
    <iconset theme="zoom-out" resource="resources.qrc">
     <normaloff>:/icons/images/zoom-out.svgz</normaloff>:/icons/images/zoom-out.svgz</iconset>
   </property>
   <property name="text">
    <string>Zoom Out</string>
   </property>
   <property name="shortcut">
    <string>Ctrl+-</string>
   </property>
  </action>
  <action name="actionPrevious_Page">
   <property name="icon">
    <iconset theme="go-previous-view-page" resource="resources.qrc">
     <normaloff>:/icons/images/go-previous-view-page.svgz</normaloff>:/icons/images/go-previous-view-page.svgz</iconset>
   </property>
   <property name="text">
    <string>Previous Page</string>
   </property>
   <property name="shortcut">
    <string>PgUp</string>
   </property>
  </action>
  <action name="actionNext_Page">
   <property name="icon">
    <iconset theme="go-next-view-page" resource="resources.qrc">
     <normaloff>:/icons/images/go-next-view-page.svgz</normaloff>:/icons/images/go-next-view-page.svgz</iconset>
   </property>
   <property name="text">
    <string>Next Page</string>
   </property>
   <property name="shortcut">
    <string>PgDown</string>
   </property>
  </action>
  <action name="actionContinuous">
   <property name="checkable">
    <bool>true</bool>
   </property>
   <property name="text">
    <string>Continuous</string>
   </property>
  </action>
  <action name="actionBack">
   <property name="enabled">
    <bool>false</bool>
   </property>
   <property name="icon">
    <iconset resource="resources.qrc">
     <normaloff>:/icons/images/go-previous-view.svgz</normaloff>:/icons/images/go-previous-view.svgz</iconset>
   </property>
   <property name="text">
    <string>Back</string>
   </property>
   <property name="toolTip">
    <string>back to previous view</string>
   </property>
  </action>
  <action name="actionForward">
   <property name="enabled">
    <bool>false</bool>
   </property>
   <property name="icon">
    <iconset resource="resources.qrc">
     <normaloff>:/icons/images/go-next-view.svgz</normaloff>:/icons/images/go-next-view.svgz</iconset>
   </property>
   <property name="text">
    <string>Forward</string>
   </property>
   <property name="toolTip">
    <string>forward to next view</string>
   </property>
  </action>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <customwidgets>
  <customwidget>
   <class>QPdfView</class>
   <extends>QWidget</extends>
   <header location="global">qpdfview.h</header>
   <container>1</container>
  </customwidget>
 </customwidgets>
 <resources>
  <include location="resources.qrc"/>
 </resources>
 <connections/>
</ui>
<RCC>
    <qresource prefix="/icons">
        <file>images/document-open.svgz</file>
        <file>images/go-next-view.svgz</file>
        <file>images/go-previous-view.svgz</file>
        <file>images/go-next-view-page.svgz</file>
        <file>images/go-previous-view-page.svgz</file>
        <file>images/zoom-in.svgz</file>
        <file>images/zoom-out.svgz</file>
    </qresource>
</RCC>