在 PyQt5 界面中显示 PDF(使用 PyMuPDF)
以下是将 PyMuPDF 渲染的 PDF 页面显示在 PyQt5 界面中的完整实现方案:
1. 基础实现:使用 QLabel 显示 PDF 页面
import fitz
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QScrollArea, QVBoxLayout, QWidget
from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtCore import Qt
class PdfViewer(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("PDF 查看器 (PyMuPDF)")
self.setGeometry(100, 100, 800, 600)
# 主部件和布局
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# 创建滚动区域
self.scroll_area = QScrollArea()
self.scroll_area.setWidgetResizable(True)
# 用于显示PDF的标签
self.pdf_label = QLabel()
self.pdf_label.setAlignment(Qt.AlignCenter)
self.scroll_area.setWidget(self.pdf_label)
layout.addWidget(self.scroll_area)
# 加载PDF
self.load_pdf("example.pdf")
def load_pdf(self, file_path):
# 打开PDF文档
self.doc = fitz.open(file_path)
self.current_page = 0
# 显示第一页
self.show_page(self.current_page)
def show_page(self, page_num):
# 加载指定页面
page = self.doc.load_page(page_num)
# 渲染为图像 (300 DPI高质量)
zoom = 3.0 # 缩放因子 (3.0 = 300 DPI)
mat = fitz.Matrix(zoom, zoom)
pix = page.get_pixmap(matrix=mat, alpha=False)
# 转换为Qt可用的图像格式
img = QImage(
pix.samples,
pix.width,
pix.height,
pix.stride,
QImage.Format_RGB888
)
# 显示在QLabel上
self.pdf_label.setPixmap(QPixmap.fromImage(img))
self.setWindowTitle(f"PDF 查看器 - 第 {page_num+1}/{self.doc.page_count} 页")
if __name__ == "__main__":
app = QApplication([])
viewer = PdfViewer()
viewer.show()
app.exec_()
2. 增强版:添加导航工具栏
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QScrollArea,
QVBoxLayout, QWidget, QToolBar, QAction,
QSpinBox, Q***boBox)
class EnhancedPdfViewer(QMainWindow):
def __init__(self):
super().__init__()
self.setup_ui()
self.load_pdf("example.pdf")
def setup_ui(self):
self.setWindowTitle("PDF 查看器 (增强版)")
self.setGeometry(100, 100, 1000, 800)
# 主部件和布局
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# 创建工具栏
self.setup_toolbar()
# 创建滚动区域
self.scroll_area = QScrollArea()
self.scroll_area.setWidgetResizable(True)
# PDF显示标签
self.pdf_label = QLabel()
self.pdf_label.setAlignment(Qt.AlignCenter)
self.scroll_area.setWidget(self.pdf_label)
layout.addWidget(self.scroll_area)
def setup_toolbar(self):
toolbar = QToolBar("PDF 控制")
self.addToolBar(toolbar)
# 上一页/下一页
prev_action = QAction("上一页", self)
prev_action.triggered.connect(self.prev_page)
toolbar.addAction(prev_action)
next_action = QAction("下一页", self)
next_action.triggered.connect(self.next_page)
toolbar.addAction(next_action)
# 页面跳转
toolbar.addSeparator()
self.page_spin = QSpinBox()
self.page_spin.setMinimum(1)
self.page_spin.valueChanged.connect(self.go_to_page)
toolbar.addWidget(self.page_spin)
# 缩放控制
toolbar.addSeparator()
self.zoom_***bo = Q***boBox()
self.zoom_***bo.addItems(["50%", "75%", "100%", "150%", "200%", "300%"])
self.zoom_***bo.setCurrentText("100%")
self.zoom_***bo.currentTextChanged.connect(self.apply_zoom)
toolbar.addWidget(self.zoom_***bo)
# 旋转按钮
toolbar.addSeparator()
rotate_left = QAction("左转90°", self)
rotate_left.triggered.connect(lambda: self.rotate_page(-90))
toolbar.addAction(rotate_left)
rotate_right = QAction("右转90°", self)
rotate_right.triggered.connect(lambda: self.rotate_page(90))
toolbar.addAction(rotate_right)
def load_pdf(self, file_path):
self.doc = fitz.open(file_path)
self.current_page = 0
self.zoom_factor = 1.0
self.rotation = 0
# 初始化页面选择器
self.page_spin.setMaximum(self.doc.page_count)
self.page_spin.setValue(1)
self.show_page(self.current_page)
def show_page(self, page_num):
if not 0 <= page_num < self.doc.page_count:
return
self.current_page = page_num
page = self.doc.load_page(page_num)
# 应用缩放和旋转
mat = fitz.Matrix(self.zoom_factor, self.zoom_factor)
mat.prerotate(self.rotation)
pix = page.get_pixmap(matrix=mat, alpha=False)
# 转换为Qt图像
img = QImage(
pix.samples,
pix.width,
pix.height,
pix.stride,
QImage.Format_RGB888
)
self.pdf_label.setPixmap(QPixmap.fromImage(img))
self.page_spin.setValue(page_num + 1)
self.setWindowTitle(f"PDF 查看器 - 第 {page_num+1}/{self.doc.page_count} 页")
def prev_page(self):
if self.current_page > 0:
self.show_page(self.current_page - 1)
def next_page(self):
if self.current_page < self.doc.page_count - 1:
self.show_page(self.current_page + 1)
def go_to_page(self, page_num):
self.show_page(page_num - 1)
def apply_zoom(self, zoom_text):
zoom_factor = float(zoom_text.strip("%")) / 100.0
self.zoom_factor = zoom_factor
self.show_page(self.current_page)
def rotate_page(self, angle):
self.rotation = (self.rotation + angle) % 360
self.show_page(self.current_page)
if __name__ == "__main__":
app = QApplication([])
viewer = EnhancedPdfViewer()
viewer.show()
app.exec_()
3. 高级功能扩展
添加文本搜索功能
# 在EnhancedPdfViewer类中添加
def setup_toolbar(self):
# ... 其他工具栏代码 ...
# 添加搜索功能
toolbar.addSeparator()
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("搜索文本...")
self.search_input.returnPressed.connect(self.search_text)
toolbar.addWidget(self.search_input)
search_action = QAction("搜索", self)
search_action.triggered.connect(self.search_text)
toolbar.addAction(search_action)
def search_text(self):
search_term = self.search_input.text()
if not search_term:
return
# 从当前页开始搜索
for page_num in range(self.current_page, self.doc.page_count):
page = self.doc.load_page(page_num)
text_instances = page.search_for(search_term)
if text_instances:
self.show_page(page_num)
# 高亮显示搜索结果(需要实现高亮逻辑)
self.highlight_search_results(text_instances)
break
def highlight_search_results(self, rect_list):
# 创建透明的高亮层
pixmap = self.pdf_label.pixmap().copy()
painter = QPainter(pixmap)
painter.setPen(QPen(Qt.red, 2))
for rect in rect_list:
# 将PDF坐标转换为屏幕坐标
x, y = rect.x0 * self.zoom_factor, rect.y0 * self.zoom_factor
width = (rect.x1 - rect.x0) * self.zoom_factor
height = (rect.y1 - rect.y0) * self.zoom_factor
# 绘制高亮矩形
painter.drawRect(int(x), int(y), int(width), int(height))
painter.end()
self.pdf_label.setPixmap(pixmap)
4. 性能优化建议
- 页面缓存:
from functools import lru_cache
class PdfViewer:
def __init__(self):
self.page_cache = {}
@lru_cache(maxsize=10)
def get_page_image(self, page_num, zoom_factor, rotation):
# 实现带缓存的页面渲染
pass
- 后台渲染:
from PyQt5.QtCore import QThread, pyqtSignal
class RenderThread(QThread):
finished = pyqtSignal(QImage)
def __init__(self, doc, page_num, zoom, rotation):
super().__init__()
self.doc = doc
self.page_num = page_num
self.zoom = zoom
self.rotation = rotation
def run(self):
page = self.doc.load_page(self.page_num)
mat = fitz.Matrix(self.zoom, self.zoom)
mat.prerotate(self.rotation)
pix = page.get_pixmap(matrix=mat, alpha=False)
img = QImage(
pix.samples,
pix.width,
pix.height,
pix.stride,
QImage.Format_RGB888
)
self.finished.emit(img)
5. 替代方案比较
| 方案 | 优点 | 缺点 |
|---|---|---|
| PyMuPDF + QLabel | 高性能,功能丰富,支持高级PDF操作 | 需要自己实现界面交互 |
| QWebEngineView | 内置支持,显示效果好 | 功能有限,占用内存大 |
| python-poppler-qt5 | 原生Qt集成 | 安装复杂,维护不佳 |
对于大多数应用,PyMuPDF方案是最佳选择,它提供了最佳的性能和灵活性平衡。