I am now currently working on a MS Word clone with Python 3 and want some effect with pagination support. I saw some implementations in https://forum.qt.io/topic/847/paginating-a-qtextedit/2, and I found the repository of dimkanovikov (https://github.com/dimkanovikov/PagesTextEdit). After that, I found the implementation of the translation for this code (https://github.com/BrightSoftwareFoundation/PagesTextEdit), but it is buggy, so I made a few changes to it. Here is a Minimal Reproducible Example:
QPageMetrics.py
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class PageMetrics:
def __init__(self):
self.m_mmPageSize = QSizeF()
self.m_mmPageMargins = QMarginsF()
self.m_pxPageSize = QSizeF()
self.m_pxPageMargins = QMarginsF()
self.m_zoomRange = 0.0
def mmToInches(self, mm):
return mm * 0.039370147
def mmToPx(self, _mm, _x=True):
return self.mmToInches(_mm) * \
(qApp.desktop().logicalDpiX() if _x else
qApp.desktop().logicalDpiY())
def pageMetrics(self, _pageFormat=QPageSize.PageSizeId, _mmPageMargins=QMarginsF()):
self.update(_pageFormat, _mmPageMargins)
def setPxPageSize(self, _width, _height):
self.m_mmPageSize = QSizeF(_width, _height)
self.m_pxPageSize = QSizeF(self.mmToPx(self.m_mmPageSize.width(), True),
self.mmToPx(self.m_mmPageSize.height(), False))
def update(self, _pageFormat, _mmPageMargins=QMarginsF()):
self.m_pageFormat = _pageFormat
self.m_mmPageSize = QPageSize(self.m_pageFormat).rect(QPageSize.Unit.Millimeter).size()
self.m_mmPageMargins = _mmPageMargins
x = True
y = False
self.m_pxPageSize = QSizeF(self.mmToPx(self.m_mmPageSize.width(), x),
self.mmToPx(self.m_mmPageSize.height(), y))
self.m_pxPageMargins = QMarginsF(self.mmToPx(self.m_mmPageMargins.left(), x),
self.mmToPx(self.m_mmPageMargins.top(), y),
self.mmToPx(self.m_mmPageMargins.right(), x),
self.mmToPx(self.m_mmPageMargins.bottom(), y))
def pageFormat(self):
return self.m_pageFormat
def mmPageSize(self):
return self.m_mmPageSize
def mmPageMargins(self):
return self.m_mmPageMargins
def pxPageSize(self):
return QSizeF(self.m_pxPageSize.width(),
self.m_pxPageSize.height())
def pxPageMargins(self):
return QMarginsF(self.m_pxPageMargins.left(),
self.m_pxPageMargins.top(),
self.m_pxPageMargins.right(),
self.m_pxPageMargins.bottom())
def zoomIn(self, _zoomRange=float()):
self.m_zoomRange = _zoomRange
PagesTextEdit.py
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import QPageMetrics as pe
import sys
class PagesTextEdit(QTextEdit):
def __init__(self):
super().__init__()
self.m_document = QTextDocument()
self.m_usePageMode = True
self.m_addBottomSpace = True
self.m_showPageNumbers = True
self.m_pageNumbersAlignment = Qt.AlignTop | Qt.AlignRight
self.m_pageMetrics = pe.PageMetrics()
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.aboutDocumentChanged()
self.textChanged.connect(self.aboutDocumentChanged)
self.verticalScrollBar().rangeChanged.connect(self.aboutVerticalScrollRangeChanged)
def setPageFormat(self, _pageFormat):
self.m_pageMetrics.update(_pageFormat)
self.repaint()
def setPageMargins(self, _margins):
self.m_pageMetrics.update(self.m_pageMetrics.pageFormat(), _margins)
self.repaint()
def paintEvent(self, _event):
self.updateVerticalScrollRange()
self.paintPageView()
self.paintPageNumbers()
super().paintEvent(_event)
def resizeEvent(self, _event):
self.updateViewportMargins()
super().resizeEvent(_event)
def updateViewportMargins(self):
self.viewportMargins = QMargins()
if self.m_usePageMode:
self.pageWidth = int(self.m_pageMetrics.pxPageSize().width())
self.pageHeight = int(self.m_pageMetrics.pxPageSize().height())
self.DEFAULT_TOP_MARGIN = int(20)
self.DEFAULT_BOTTOM_MARGIN = int(20)
self.leftMargin = int(0)
self.rightMargin = int(0)
if self.width() > self.pageWidth:
self.BORDERS_WIDTH = int(4)
self.VERTICAL_SCROLLBAR_WIDTH = int(self.verticalScrollBar().width()
if self.verticalScrollBar().isVisible()
else 0)
self.leftMargin = self.rightMargin = int((self.width() - self.pageWidth -
self.VERTICAL_SCROLLBAR_WIDTH - self.BORDERS_WIDTH) / 2)
self.topMargin = int(self.DEFAULT_TOP_MARGIN)
self.bottomMargin = int(self.DEFAULT_BOTTOM_MARGIN)
self.documentHeight = int(self.pageHeight * self.document().pageCount())
if (self.height() - self.documentHeight) > (self.DEFAULT_TOP_MARGIN + self.DEFAULT_BOTTOM_MARGIN):
self.BORDERS_HEIGHT = int(2)
self.HORIZONTAL_SCROLLBAR_HEIGHT = int(self.horizontalScrollBar().height()
if self.horizontalScrollBar().isVisible()
else 0)
self.bottomMargin = int(self.height() - self.documentHeight -
self.HORIZONTAL_SCROLLBAR_HEIGHT - self.DEFAULT_TOP_MARGIN -
self.BORDERS_HEIGHT)
self.viewportMargins = QMargins(self.leftMargin, self.topMargin, self.rightMargin, self.bottomMargin)
self.setViewportMargins(self.viewportMargins)
self.aboutUpdateDocumentGeometry()
def updateVerticalScrollRange(self):
if self.m_usePageMode:
self.pageHeight = int(self.m_pageMetrics.pxPageSize().height())
self.documentHeight = int(self.pageHeight * self.document().pageCount())
self.maximumValue = int(self.documentHeight - self.viewport().height())
if self.verticalScrollBar().maximum() != self.maximumValue:
self.verticalScrollBar().setMaximum(self.maximumValue)
else:
self.SCROLL_DELTA = int(800)
self.maximumValue = int(self.document().size().height() - self.viewport().size().height() +
(self.SCROLL_DELTA if self.m_addBottomSpace else 0))
if self.verticalScrollBar().maximum() != self.maximumValue:
self.verticalScrollBar().setMaximum(self.maximumValue)
def paintPageView(self):
if self.m_usePageMode:
self.pageWidth = float(self.m_pageMetrics.pxPageSize().width())
self.pageHeight = float(self.m_pageMetrics.pxPageSize().height())
self.p = QPainter(self.viewport())
self.spacePen = QPen(self.palette().window(), 9)
self.borderPen = QPen(self.palette().dark(), 1)
self.curHeight = float(self.pageHeight - (self.verticalScrollBar().value() %
int(self.pageHeight)))
self.canDrawNextPageLine = self.verticalScrollBar().value() != \
self.verticalScrollBar().maximum()
self.xValueF = self.pageWidth + (1 if (self.width() % 2 == 0) else 0)
self.xValue = int(self.xValueF)
self.horizontalDelta = int(self.horizontalScrollBar().value())
if (self.curHeight - self.pageHeight) >= 0:
self.p.setPen(self.borderPen)
self.p.drawLine(QLineF(float(0), self.curHeight - self.pageHeight,
self.xValueF, self.curHeight - self.pageHeight))
while self.curHeight <= self.height():
self.p.setPen(self.spacePen)
self.p.drawLine(QLineF(float(0), float(self.curHeight - 4),
float(self.width()), float(self.curHeight - 4)))
self.p.setPen(self.borderPen)
self.p.drawLine(QLineF(float(0), float(self.curHeight - 0),
self.xValueF, self.curHeight))
if self.canDrawNextPageLine:
self.p.drawLine(QLineF(float(0),
float(self.curHeight - 8),
float(self.pageWidth),
float(self.curHeight - 8)))
self.p.drawLine(QLineF(float(0 - self.horizontalDelta), float(self.curHeight - self.pageHeight),
float(0 - self.horizontalDelta), float(self.curHeight - 8)))
self.p.drawLine(QLineF(float(self.xValueF - self.horizontalDelta),
float(self.curHeight - self.pageHeight),
float(self.xValueF - self.horizontalDelta),
float(self.curHeight - 8)))
self.curHeight += self.pageHeight
if self.curHeight >= self.height():
self.p.setPen(self.borderPen)
self.p.drawLine(QLineF(float(0 - self.horizontalDelta), float(self.curHeight - self.pageHeight),
float(0 - self.horizontalDelta), float(self.height())))
self.p.drawLine(QLineF(float(self.xValueF - self.horizontalDelta),
float(self.curHeight - self.pageHeight),
float(self.xValueF - self.horizontalDelta),
float(self.height())))
def paintPageNumbers(self):
if (self.m_usePageMode and not self.m_pageMetrics.pxPageMargins().isNull() and
self.m_showPageNumbers):
self.pageSize = QSizeF(self.m_pageMetrics.pxPageSize())
self.pageMargins = QMarginsF(self.m_pageMetrics.pxPageMargins())
self.p = QPainter(self.viewport())
self.p.setFont(self.document().defaultFont())
self.p.setPen(QPen(self.palette().text(), 1))
self.curHeight = float(self.pageSize.height() - (self.verticalScrollBar().value() %
int(self.pageSize.height())))
self.leftMarginPosition = float(self.pageMargins.left() - self.pageMargins.right())
self.marginWidth = float(self.pageSize.width() - self.pageMargins.left() - self.pageMargins.right())
self.pageNumber = int(self.verticalScrollBar().value() / self.pageSize.height() + 1)
if self.curHeight - self.pageMargins.top() >= 0:
self.topMarginRect = QRectF(float(self.leftMarginPosition),
float(self.curHeight - self.pageSize.height()),
float(self.marginWidth),
float(self.pageMargins.top()))
self.paintPageNumber(self.p, self.topMarginRect, True, self.pageNumber)
while self.curHeight < self.height():
self.bottomMarginRect = QRect(int(self.leftMarginPosition),
int(self.curHeight - self.pageMargins.bottom()),
int(self.marginWidth),
int(self.pageMargins.bottom()))
self.paintPageNumber(self.p, self.bottomMarginRect, False, self.pageNumber)
self.pageNumber += 1
self.topMarginRect = QRect(int(self.leftMarginPosition), int(self.curHeight), int(self.marginWidth),
int(self.pageMargins.top()))
self.paintPageNumber(self.p, self.topMarginRect, True, self.pageNumber)
self.curHeight += self.pageSize.height()
self.p.end()
def paintPageNumber(self, _painter, _rect, _isHeader, _number):
if _isHeader:
if self.m_pageNumbersAlignment == Qt.AlignTop:
_painter.drawText(_rect, Qt.AlignVCenter | (self.m_pageNumbersAlignment ^ Qt.AlignTop),
str(_number))
else:
if self.m_pageNumbersAlignment == Qt.AlignBottom:
_painter.drawText(_rect, Qt.AlignVCenter | (self.m_pageNumbersAlignment ^ Qt.AlignBottom),
str(_number))
def aboutVerticalScrollRangeChanged(self, _minimum, _maximum):
self.updateViewportMargins()
self.scrollValue = int(self.verticalScrollBar().value())
if self.scrollValue > _maximum:
self.updateVerticalScrollRange()
def aboutDocumentChanged(self):
if self.m_document != self.document():
self.m_document = self.document()
self.document().documentLayout().update.connect(self.aboutUpdateDocumentGeometry)
def aboutUpdateDocumentGeometry(self):
self.documentSize = QSizeF(float(self.width() - self.verticalScrollBar().width()), float(-1))
if self.m_usePageMode:
self.pageWidth = (self.m_pageMetrics.pxPageSize().width())
self.pageHeight = (self.m_pageMetrics.pxPageSize().height())
self.documentSize = QSizeF(self.pageWidth, self.pageHeight)
if self.width() > self.pageWidth:
self.viewportMargins = QMargins(
int(int(self.width() - self.pageWidth - self.verticalScrollBar().width() - 2) / 2),
20,
int(int(self.width() - self.pageWidth - self.verticalScrollBar().width() - 2) / 2),
20
)
else:
self.viewportMargins = QMargins(0, 20, 0, 20)
if self.document().pageSize() != self.documentSize:
self.document().setPageSize(self.documentSize)
if self.document().documentMargin() != 0:
self.document().setDocumentMargin(0)
rootFrameMargin = self.m_pageMetrics.pxPageMargins()
rootFrameFormat = self.document().rootFrame().frameFormat()
self.setViewportMargins(self.viewportMargins)
if ((rootFrameFormat.leftMargin() != rootFrameMargin.left())
| (rootFrameFormat.topMargin() != rootFrameMargin.top())
| (rootFrameFormat.rightMargin() != rootFrameMargin.right())
| (rootFrameFormat.bottomMargin() != rootFrameMargin.bottom())):
rootFrameFormat.setLeftMargin(rootFrameMargin.left())
rootFrameFormat.setTopMargin(rootFrameMargin.top())
rootFrameFormat.setRightMargin(rootFrameMargin.right())
rootFrameFormat.setBottomMargin(rootFrameMargin.bottom())
self.document().rootFrame().setFrameFormat(rootFrameFormat)
Here are some few questions I want to ask:
How to implement a zooming function in the paged text edit like MS Word that zooms the whole page but not only text? I have tried different methods: using
QGraphicsProxyWidget, delete theresizeEvent, try to implement aQScrollAreawithsetFrameRect, ... It crashes often,Process finished with exit code -1073740791 (0xC0000409) / RESTART: SHELLorRecursionErroroften appears.How to change the page size? It does not crash, only for
self.setPageSize(QPageSize.PageSizeId.A4)or.A5). For another size or custom one, it crashes withProcess finished with exit code -1073740791 (0xC0000409) / RESTART: SHELL.My page numbering / header and footer cannot be shown for an unknown reason. Can somebody help me to solve this?
Any comments / answers are appreciated!