我正在 QPdfView 中显示 pdf 文档,我需要在几页中的某些区域上绘制覆盖图。
我已经成功绘制了覆盖层,但我需要一些帮助来调整滚动时其位置的调整方式。
这是代码:
from PySide6.QtCore import QRectF
from PySide6.QtGui import QPainter, QColor
from PySide6.QtPdfWidgets import QPdfView
from PySide6.QtCore import Qt
from PySide6.QtPdf import QPdfDocument
class CustomPdfView(QPdfView):
def __init__(self, parent=None):
super().__init__(parent)
# Placeholder areas
self.highlights: dict[int, list[QRectF]] = {
i : [QRectF(150.0, 150.0, 300.0, 300.0)] for i in range(200)
}
def paintEvent(self, event):
super().paintEvent(event)
if self.document().status() == QPdfDocument.Status.Ready:
current_page_number = self.pageNavigator().currentPage()
if current_page_number in self.highlights.keys():
painter = QPainter(self.viewport())
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
painter.setBrush(QColor(255, 255, 0, 100))
painter.setPen(Qt.PenStyle.NoPen)
total_pages = self.document().pageCount()
current_page_size = self.document().pagePointSize(current_page_number)
viewport_size = self.viewport().size()
scroll_bar = self.verticalScrollBar()
max_scroll = scroll_bar.maximum()
scroll_per_page = max_scroll / total_pages
scroll_calculated = scroll_per_page * current_page_number
for rect in self.highlights[current_page_number]:
h_diff = scroll_bar.value() - scroll_calculated
scaled_rect = QRectF(
(rect.x() / current_page_size.width()) * viewport_size.width(),
rect.y() - h_diff,
(rect.width() / current_page_size.width()) * viewport_size.width(),
rect.height()
)
painter.drawRect(scaled_rect)
painter.end()
在最好的情况下,我想知道当前页面有多少像素已在视口中渲染,因此 h_diff 变为 page_height - 可见像素
您的方法有多个问题,最重要的是:
currentPage
,但在MultiPage
模式下可以一次看到多个页面,“当前”仅是与视图上半部分的假想线相交的页面;FitToWidth
/FitInView
模式可能的缩放;QtPdf 相对较新且简单,不幸的是缺乏许多有用的方面(例如了解每个页面的视觉边界矩形),因此需要自定义实现。
通过检查官方来源我们可以看到它在内部根据当前设置创建了一个私有的“文档布局”,考虑了视口大小、实际比例、边距和页面间距。它从根本上来说是一个基本映射,每个页面都有一个基于视觉位置的特定 QRect 和比例。
通过访问该布局,我们可以根据每个页面的实际坐标正确映射每个“突出显示”。
首先,我们需要创建一个计算所有文档布局的函数,并确保每当以下任何方面发生更改时它始终会更新:
setDocumentMargins()
);SinglePage
模式;resizeEvent()
);布局使用
{page: (pageRect, scale)}
形式的映射,并通过循环所有可见页面来计算:对于每个页面,它获取原始大小,对其进行缩放(考虑缩放模式、屏幕 DPI 和可能的自定义缩放系数,或者可能考虑到水平边距),然后为上面的 dict 映射创建值,默认 QRect 位于 0, 0
并具有新大小,并且比例基于新大小与新大小之间的比率 原来的。在此过程中,还考虑了最小宽度,考虑边距和实际页面宽度。然后我们显然需要在
paintEvent()
中绘制“高光”,它循环遍历所有布局的页面,检查页面当前是否在视口中,如果它包含高光,则根据比例映射高光矩形该页面的:x 和 y 被转换为页面原点,然后与大小一起乘以页面比例。
以下使用基于上面链接的来源的
updateDocumentLayout
函数:
class CustomPdfView(QPdfView):
def __init__(self, parent=None):
super().__init__(parent)
self.documentLayout = OrderedDict()
self.documentChanged.connect(self.updateDocumentLayout)
self.documentMarginsChanged.connect(self.updateDocumentLayout)
self.zoomFactorChanged.connect(self.updateDocumentLayout)
self.zoomModeChanged.connect(self.updateDocumentLayout)
self.pageSpacingChanged.connect(self.updateDocumentLayout)
self.pageNavigator().currentPageChanged.connect(
self.currentPageChanged)
# Placeholder areas
self.highlights = {
i : [QRectF(150.0, 150.0, 300.0, 300.0)] for i in range(200)
}
def currentPageChanged(self, currentPage):
if self.pageMode() == self.PageMode.SinglePage:
self.updateDocumentLayout()
self.viewport().update()
def documentStatusChanged(self, status):
if status == QPdfDocument.Status.Ready:
self.updateDocumentLayout()
def updateDocumentLayout(self):
self.documentLayout.clear()
if self.document() is None:
return
elif self.document().status() != QPdfDocument.Status.Ready:
# the document may have been just changed, so we need to
# connect its statusChanged signal, but only once
try:
self.document().statusChanged.connect(
self.documentStatusChanged,
Qt.ConnectionType.UniqueConnection
)
except TypeError:
pass
return
screenRes = QApplication.primaryScreen().logicalDotsPerInch() / 72
viewSize = self.viewport().size()
viewWidth = viewSize.width()
spacing = self.pageSpacing()
totalWidth = 0
margins = self.documentMargins()
left = margins.left()
right = margins.right()
top = margins.top()
if self.pageMode() == self.PageMode.SinglePage:
startPage = self.pageNavigator.currentPage()
endPage = startPage + 1
else:
startPage = 0
endPage = self.document().pageCount()
zoomMode = self.zoomMode()
zoomFactor = self.zoomFactor()
sizeFunc = self.document().pagePointSize
for page in range(startPage, endPage):
origSize = sizeFunc(page)
pageScale = zoomFactor
if zoomMode == self.ZoomMode.Custom:
pageSize = QSizeF(origSize * screenRes * zoomFactor).toSize()
elif zoomMode == self.ZoomMode.FitToWidth:
pageSize = QSizeF(origSize * screenRes).toSize()
pageScale = (viewWidth - left - right) / pageSize.width()
pageSize *= factor
else:
vsize = QSize(viewSize + QSize(-left - right, -spacing))
pageSize = QSizeF(origSize * screenRes).toSize()
scaledSize = pageSize.scaled(vsize, Qt.AspectRatioMode.KeepAspectRatio)
pageScale = scaledSize.width() / pageSize.width()
pageSize = scaledSize
if pageSize.width() > totalWidth:
totalWidth = pageSize.width()
self.documentLayout[page] = QRect(QPoint(), pageSize), pageScale
totalWidth += left + right
pageY = top
for page in range(startPage, endPage):
pageRect = self.documentLayout[page][0]
pageX = (max(totalWidth, viewWidth) - pageRect.width()) // 2
pageRect.moveTopLeft(QPoint(pageX, pageY))
pageY += pageRect.height() + spacing
def resizeEvent(self, event):
super().resizeEvent(event)
self.updateDocumentLayout()
def paintEvent(self, event):
super().paintEvent(event)
if not self.documentLayout:
return
viewRect = QRect(
self.horizontalScrollBar().value(),
self.verticalScrollBar().value(),
self.viewport().width(),
self.viewport().height()
)
bottom = viewRect.bottom()
qp = QPainter(self.viewport())
qp.translate(-viewRect.x(), -viewRect.y())
for page, (pageRect, scale) in self.documentLayout.items():
if not pageRect.intersects(viewRect):
if pageRect.y() > bottom:
# no need to go further
break
continue
qp.drawRect(pageRect)
if not page in self.highlights:
continue
for rect in self.highlights[page]:
scaled = QRectF(
QPointF(pageRect.topLeft()) + rect.topLeft() * scale,
rect.size() * scale
)
qp.drawRect(scaled)