PDF.js Viewer for Qt (PySide6 / PyQt6)
Production-ready, embeddable PDF viewer widgets for PySide6 and PyQt6 applications, powered by Mozilla's PDF.js with basic annotation support.
Executive Summary
The PDF.js Viewer packages provides extensive PDF viewing and basic annotation capabilities for Qt-based Python applications. Available in two variants—pdfjs-viewer-pyside6 (LGPL) and pdfjs-viewer-pyqt6 (GPL)—these packages offer identical APIs with different licensing to accommodate both open-source and proprietary use cases.
The packages are lightweight wrappers around the PDF.js reference implementation. With extensive configuration options, individual features of the viewer can be enabled or disabled according to your needs. In addition, the packages provide a connection between the viewer and Qt, allowing it to be controlled through function calls. They also expose signals that enable the main application to react to events occurring within the viewer.
The quick‑start information on this page is complemented comprehensive example implementations, which are available in the ‘examples’ directories of our GitHub repositories for pdfjs-viewer-pyside6 and pdfjs-viewer-pyqt6.
- Full-featured PDF viewing with zoom, rotation, and navigation
- Basic annotation tools (highlight, text, ink, stamps)
- Save PDFs with annotations
- Flexible print request handling with multiple modes
- Automatic light/dark mode support
- PyInstaller-ready for distribution
- 100% API compatibility between PySide6 and PyQt6 versions
Which Package Should You Use?
| Package | Qt Binding | License | Proprietary Use |
|---|---|---|---|
| pdfjs-viewer-pyside6 | PySide6 | LGPL v3 | ✅ Yes |
| pdfjs-viewer-pyqt6 | PyQt6 | GPL v3 | ❌ No |
Features
🖼️ PDF.js Integration
View, zoom, rotate, and navigate PDFs with reference implementation's capabilities.
✏️ Rich Annotations
Highlight, draw, add text and stamps to PDFs.
💾 Save with Annotations
Export PDFs with annotations.
🖨️ Flexible Printing
Three print modes: system viewer, Qt dialog for printing, or custom handling.
🎨 Theme Support
Automatic light/dark mode following system preferences.
⚙️ Viewer Options
Control page, zoom, and sidebar when loading PDFs.
🔒 Security
Basic checks added, unused features disabled, suppress external links.
📦 PyInstaller Ready
Automatic bundling for frozen applications.
🎛️ Feature Control
Enable/disable specific UI features via configuration.
🔧 7 Configuration Presets
Pre-configured settings for common use cases.
🌐 Cross-Platform
Works on Windows, macOS, and Linux.
📡 Signal-Based API
Qt signals for all major events and state changes.
Installation
PySide6 Version (LGPL)
pip install pdfjs-viewer-pyside6
Optional dependencies for Qt print dialog:
pip install pdfjs-viewer-pyside6[qt-print]
PyQt6 Version (GPL)
pip install pdfjs-viewer-pyqt6
Optional dependencies for Qt print dialog:
pip install pdfjs-viewer-pyqt6[qt-print]
- Python ≥ 3.8
- PySide6 ≥ 6.10.0 or PyQt6 ≥ 6.10.0
- PySide6-WebEngine or PyQt6-WebEngine ≥ 6.10.0
This module uses QWebEngineView. Please note that bugs in this component can lead to critical errors that may cause the main application to crash. Before reporting an issue to us, please check whether downgrading or upgrading the component by one version resolves the problem, and consult our GitHub repository for notes on known versions with issues related to the integration of PDF.js
Quick Start
Basic Viewer (PySide6)
from PySide6.QtWidgets import QApplication, QMainWindow
from pdfjs_viewer import PDFViewerWidget
app = QApplication([])
window = QMainWindow()
window.resize(1024, 768)
# Create viewer
viewer = PDFViewerWidget()
viewer.load_pdf("document.pdf")
# or show blank viewer
# viewer.show_blank_page()
# Note: PDF.js loads a demo file if you don't load a PDF or show a blank page!
# Connect signals
viewer.pdf_loaded.connect(lambda meta: print(f"Loaded: {meta['filename']}"))
viewer.pdf_saved.connect(lambda data, path: print(f"Saved to {path}"))
window.setCentralWidget(viewer)
window.show()
app.exec()
Basic Viewer (PyQt6)
from PyQt6.QtWidgets import QApplication, QMainWindow
from pdfjs_viewer import PDFViewerWidget
app = QApplication([])
window = QMainWindow()
window.resize(1024, 768)
# Create viewer
viewer = PDFViewerWidget()
viewer.load_pdf("document.pdf")
# or show blank viewer
# viewer.show_blank_page()
# Note: PDF.js loads a demo file if you don't load a PDF or show a blank page!
# Connect signals
viewer.pdf_loaded.connect(lambda meta: print(f"Loaded: {meta['filename']}"))
viewer.pdf_saved.connect(lambda data, path: print(f"Saved to {path}"))
window.setCentralWidget(viewer)
window.show()
app.exec()
Viewer Options
Control how PDFs are displayed when loaded:
# Open at specific page with custom zoom
viewer.load_pdf("document.pdf", page=5, zoom="page-width")
# Open with bookmarks sidebar visible
viewer.load_pdf("document.pdf", pagemode="bookmarks")
# Combine multiple options
viewer.load_pdf(
"document.pdf",
page=10,
zoom=150, # 150% zoom
pagemode="thumbs" # Show thumbnails
)
# Open at specific page with custom zoom
viewer.load_pdf("document.pdf", page=5, zoom="page-width")
# Open with bookmarks sidebar visible
viewer.load_pdf("document.pdf", pagemode="bookmarks")
# Combine multiple options
viewer.load_pdf(
"document.pdf",
page=10,
zoom=150, # 150% zoom
pagemode="thumbs" # Show thumbnails
)
Supported options:
page: Page number to open (1-indexed)zoom: Named ("page-width","page-height","page-fit","auto") or numeric (10-1000)pagemode: Sidebar state -"none","thumbs","bookmarks","attachments"nameddest: Named destination to navigate to
API Reference
PDFViewerWidget
Main widget class for viewing PDFs.
Methods
| Method | Description |
|---|---|
load_pdf(source, page, zoom, pagemode, nameddest) |
Load PDF from file path or bytes with optional viewer options |
show_blank_page() |
Show empty viewer (respects current theme) |
save_pdf(output_path=None) |
Save PDF with annotations (returns bytes) |
print_pdf() |
Trigger print dialog (behavior depends on print_handler config) |
get_pdf_data() |
Get current PDF data with annotations as bytes |
has_annotations() |
Check if PDF has been annotated (returns bool) |
goto_page(page) |
Navigate to specific page |
get_page_count() |
Get total page count (returns int) |
get_current_page() |
Get current page number (returns int) |
set_features_enabled(features) |
Update feature flags dynamically |
Signals
All signals developers can listen to:
| Signal | Parameters | Description |
|---|---|---|
pdf_loaded |
metadata: dict |
Emitted when PDF successfully loads. Metadata includes filename, page count, and PDF information. |
pdf_saved |
data: bytes, path: str |
Emitted when PDF is saved. Provides PDF data with annotations and save path. |
print_requested |
data: bytes |
Emitted when print is triggered using SYSTEM or QT_DIALOG handlers. |
print_data_ready |
data: bytes, filename: str |
Emitted when using EMIT_SIGNAL print handler for custom print handling. |
annotation_modified |
none | Emitted when annotations are added, changed, or removed. |
page_changed |
current: int, total: int |
Emitted when current page changes. Provides page number and total count. |
error_occurred |
message: str |
Emitted when errors occur during PDF operations. |
external_link_blocked |
url: str |
Emitted when an external link is blocked by security settings. |
Configuration
Print Handling
The viewer supports three print handling modes:
| Mode | Signal | Requires pypdfium2 | Use Case |
|---|---|---|---|
SYSTEM |
print_requested(bytes) |
No | OS-based viewing / printing (default) |
QT_DIALOG |
print_requested(bytes) |
Yes | Embedded Qt print dialog |
EMIT_SIGNAL |
print_data_ready(bytes, str) |
No | Custom print handling |
from pdfjs_viewer import PDFViewerWidget, PDFViewerConfig, PrintHandler
# SYSTEM mode (default) - Open PDF in OS default application
config = PDFViewerConfig(print_handler=PrintHandler.SYSTEM)
viewer = PDFViewerWidget(config=config)
# QT_DIALOG mode - a basic print dialog
config = PDFViewerConfig(
print_handler=PrintHandler.QT_DIALOG,
print_dpi=300,
print_fit_to_page=True,
print_parallel_pages=4
)
viewer = PDFViewerWidget(config=config)
# EMIT_SIGNAL mode (custom handling)
config = PDFViewerConfig(print_handler=PrintHandler.EMIT_SIGNAL)
viewer = PDFViewerWidget(config=config)
viewer.print_data_ready.connect(my_custom_print_handler)
from pdfjs_viewer import PDFViewerWidget, PDFViewerConfig, PrintHandler
# SYSTEM mode (default) - Open PDF in OS default application
config = PDFViewerConfig(print_handler=PrintHandler.SYSTEM)
viewer = PDFViewerWidget(config=config)
# QT_DIALOG mode a basic print dialog
config = PDFViewerConfig(
print_handler=PrintHandler.QT_DIALOG,
print_dpi=300,
print_fit_to_page=True,
print_parallel_pages=4
)
viewer = PDFViewerWidget(config=config)
# EMIT_SIGNAL mode (custom handling)
config = PDFViewerConfig(print_handler=PrintHandler.EMIT_SIGNAL)
viewer = PDFViewerWidget(config=config)
viewer.print_data_ready.connect(my_custom_print_handler)
Configuration Presets
7 pre-configured presets for common use cases:
| Preset (click for details) | Features | Best For |
|---|---|---|
readonly |
No printing, saving, or annotations | Kiosk displays, untrusted PDFs |
simple |
Print, save, basic annotations | General PDF viewing |
annotation |
All annotation tools enabled | PDF review, collaboration |
form |
Text input, signatures | Form filling, contracts |
kiosk |
Print only, maximum stability | 24/7 public terminals |
safer |
Minimal features, all stability options | Embedded systems, older Qt |
unrestricted |
Everything enabled (default) | Development, testing |
from pdfjs_viewer import PDFViewerWidget, ConfigPresets, PrintHandler
# Simple preset usage
viewer = PDFViewerWidget(preset="readonly")
# Customize a preset
viewer = PDFViewerWidget(
preset="readonly",
customize={
"features": {
"save_enabled": True,
"freetext_enabled": True,
"ink_enabled": True,
},
"security": {
"allow_external_links": True,
"block_remote_content": False,
},
"stability": {
"safer_mode": True,
"disable_webgl": False,
}
}
)
# Hybrid approach (recommended)
config = ConfigPresets.annotation()
config.print_handler = PrintHandler.EMIT_SIGNAL
config.features.stamp_enabled = False
viewer = PDFViewerWidget(config=config)
from pdfjs_viewer import PDFViewerWidget, ConfigPresets, PrintHandler
# Simple preset usage
viewer = PDFViewerWidget(preset="readonly")
# Customize a preset
viewer = PDFViewerWidget(
preset="readonly",
customize={"features": {"save_enabled": True}}
)
# Hybrid approach (recommended)
config = ConfigPresets.annotation()
config.print_handler = PrintHandler.EMIT_SIGNAL
config.features.stamp_enabled = False
config.features.save_enabled = False
viewer = PDFViewerWidget(config=config)
All Configurable Options
Complete reference of all configuration options available:
Click to expand all configurable options
PDFFeatures (features)
| Option | Type | Default | Description |
|---|---|---|---|
print_enabled |
bool | True | Enable print button in toolbar |
save_enabled |
bool | True | Enable save button in toolbar |
load_enabled |
bool | False | Enable load PDF button in toolbar |
presentation_mode |
bool | False | Enable fullscreen presentation mode button (not working in Qt) |
highlight_enabled |
bool | True | Enable highlight annotation tool |
freetext_enabled |
bool | True | Enable text annotation tool |
ink_enabled |
bool | True | Enable drawing/ink annotation tool |
stamp_enabled |
bool | True | Enable stamp annotation tool |
stamp_alttext_enabled |
bool | True | Enable alt-text dialog for stamps |
bookmark_enabled |
bool | False | Show URL of loaded document button (not working in Qt) |
scroll_mode_buttons |
bool | False | Show scroll mode buttons (vertical/horizontal/wrapped) |
spread_mode_buttons |
bool | False | Show spread mode buttons (single/two-page) |
PDFSecurityConfig (security)
| Option | Type | Default | Description |
|---|---|---|---|
allow_external_links |
bool | False | Allow clicking external links in PDFs |
block_remote_content |
bool | True | Block loading remote images and resources |
allowed_protocols |
list[str] | ["http", "https"] | Allowed URL protocols for links |
custom_csp |
str | None | Custom Content Security Policy |
PDFStabilityConfig (stability)
| Option | Type | Default | Description |
|---|---|---|---|
safer_mode |
bool | True | Enable recommended stability settings |
use_isolated_profile |
bool | True | Each viewer gets its own profile |
profile_name |
str | None | Custom profile name (auto-generated if None) |
disable_webgl |
bool | True | Disable WebGL rendering |
disable_gpu |
bool | True | Disable GPU acceleration |
disable_gpu_compositing |
bool | True | Disable GPU compositing |
disable_cache |
bool | True | Disable disk cache |
disable_local_storage |
bool | True | Disable localStorage API |
disable_session_storage |
bool | True | Disable sessionStorage API |
disable_databases |
bool | True | Disable Web SQL and IndexedDB |
disable_service_workers |
bool | True | Disable service workers |
disable_background_networking |
bool | True | Disable background network requests |
disable_software_rasterizer |
bool | False | Disable software rendering (keep enabled) |
force_prefers_reduced_motion |
bool | False | Reduce animations |
max_cache_size_mb |
int | 0 | Maximum cache size (0 = minimal) |
PDFViewerConfig (main)
| Option | Type | Default | Description |
|---|---|---|---|
auto_open_folder_on_save |
bool | True | Open folder after saving PDF |
confirm_before_external_link |
bool | True | Show confirmation dialog for external links |
disable_context_menu |
bool | True | Disable Qt's native context menu |
print_handler |
PrintHandler | SYSTEM | Print mode: SYSTEM, QT_DIALOG, or EMIT_SIGNAL |
print_dpi |
int | 300 | DPI for Qt print dialog rendering |
print_fit_to_page |
bool | True | Scale to fit page vs actual size |
print_parallel_pages |
int | 4 | Number of pages to render in parallel (Qt dialog) |
default_zoom |
str | "auto" | Default zoom: "auto", "page-fit", "page-width", or percentage |
sidebar_visible |
bool | False | Show sidebar by default |
spread_mode |
str | "none" | Page spread mode: "none", "odd", "even" |
License & Dependencies
Package Licensing
| Package | License | Qt Binding | Qt Binding License | Proprietary Use |
|---|---|---|---|---|
| pdfjs-viewer-pyside6 | LGPL v3 | PySide6 | LGPL v3 | ✅ Yes |
| pdfjs-viewer-pyqt6 | GPL v3 | PyQt6 | GPL v3 | ❌ No |
This module uses PySide6 (Qt for Python), licensed under the LGPL v3.
You may replace the PySide6 and Qt shared libraries in the application directory.
This module uses PyQt6, licensed under the GPL v3.
Applications using this module must be licensed under GPL v3 or compatible license and provide source code to users.
Bundled Dependencies
| Component | Version | License | Homepage | Repository |
|---|---|---|---|---|
| PDF.js | 5.4.530 | Apache 2.0 | mozilla.github.io/pdf.js | github.com/mozilla/pdf.js |
| PySide6 | ≥ 6.10.0 | LGPL v3 | qt.io/qt-for-python | code.qt.io/pyside |
| PyQt6 | ≥ 6.10.0 | GPL v3 | riverbankcomputing.com/pyqt | github.com/PyQt |
| pypdfium2 (optional) | ≥ 4.0.0 | Apache 2.0 | github.com/pypdfium2 | github.com/pypdfium2 |
License Comparison: PySide6 vs PyQt6
PySide6 (LGPL):
- ✅ Can be used in proprietary applications
- ✅ No need to open-source your application
- ⚠️ Must keep Qt libraries as external shared libraries (not statically linked)
- ⚠️ Users must be able to replace PySide6/Qt libraries
PyQt6 (GPL):
- ⚠️ Must license your application under GPL v3
- ⚠️ Must provide source code to users
- ⚠️ Must include GPL v3 license and copyright notices
- ✅ No library replacement requirements
This license information is provided to the best of our knowledge but is NOT legal advice. The information may be incomplete or incorrect. If you are unsure which license to use for your project, please consult with a professional lawyer or licensing expert who can provide guidance specific to your situation.