Qt обертывание текста в boundingRect
Я пишу приложение на PyQt, и мне нужно обернуть текст, который я рисую. Я использую метод
QFontMetrics
класс, чтобы определить размер, а затем
QPainter.drawText
рисовать. Мне нужно, чтобы текст поместился в заданный прямоугольник. Я использую эту строку:
rect = metrics.boundingRect(rect, Qt.TextWordWrap, text)
Однако из-за флага
Qt.TextWordWrap
, если в тексте нет пробелов, он не переносится и не помещается в прямоугольник. Я старался
Qt.TextWrapAnywhere
но это разбивает слова, даже если есть пробелы. В
Qt.TextFlag
похоже, не имеет флага, который ставит в приоритет перенос слов, и только если это невозможно, разбивает слова на части, например
QTextOption.WrapAtWordBoundaryOrAnywhere
. Есть ли способ обернуть такой текст, используя
boundingRect
и
drawText
?
2 ответа
Для подобных ситуаций обычно лучше использовать QTextDocument, который позволяет использовать более продвинутые параметры.
class WrapAnywhereLabel(QtWidgets.QWidget):
def __init__(self, text=''):
super().__init__()
self.doc = QtGui.QTextDocument(text)
self.doc.setDocumentMargin(0)
opt = QtGui.QTextOption()
opt.setWrapMode(opt.WrapAtWordBoundaryOrAnywhere)
self.doc.setDefaultTextOption(opt)
def hasHeightForWidth(self):
return True
def heightForWidth(self, width):
self.doc.setTextWidth(width)
return self.doc.size().height()
def sizeHint(self):
return self.doc.size().toSize()
def resizeEvent(self, event):
self.doc.setTextWidth(self.width())
def paintEvent(self, event):
qp = QtGui.QPainter(self)
self.doc.drawContents(qp, QtCore.QRectF(self.rect()))
import sys
app = QtWidgets.QApplication(sys.argv)
w = WrapAnywhereLabel('hellooooo hello hellooooooooooooooo')
w.show()
sys.exit(app.exec_())
Такую функцию легко реализовать с помощью
QFontMetricsF
from PyQt5 import QtCore, QtGui, QtWidgets
import math
def drawText(painter, rect, text):
metrics = QtGui.QFontMetricsF(painter.font())
space = metrics.horizontalAdvance(" ")
width = rect.width()
def lineWidth(line):
return sum([word[1] for word in line]) + space * (len(line) - 1)
def canFit(line, word):
return lineWidth(line + [word]) < width
def forceSplit(word):
charSize = [metrics.horizontalAdvance(c) for c in word[0]]
for i in reversed(range(1,len(charSize))):
if sum(charSize[:i]) < width:
return [(word, metrics.horizontalAdvance(word)) for word in [word[0][:i], word[0][i:]]]
queue = [(word, metrics.horizontalAdvance(word)) for word in text.split(" ")]
lines = []
line = []
while len(queue) > 0:
word = queue.pop(0)
if canFit(line, word):
line.append(word)
else:
if len(line) == 0:
word1, word2 = forceSplit(word)
line.append(word1)
lines.append(line)
line = []
queue.insert(0, word2)
else:
lines.append(line)
line = []
queue.insert(0, word)
if len(line) > 0:
lines.append(line)
line = []
painter.save()
painter.setClipRect(rect)
x = rect.x()
y = rect.y() + metrics.height()
for line in lines:
text = " ".join([word[0] for word in line])
painter.drawText(int(x), int(y), text)
y += metrics.leading() + metrics.height()
painter.restore()
def replaceSomeSpaces(text, n):
res = []
for i,word in enumerate(text.split(" ")):
res.append(word)
if (i % n) == 0:
res.append(" ")
else:
res.append("_")
return "".join(res)
class Widget(QtWidgets.QWidget):
def __init__(self, parent = None):
super().__init__(parent)
self._text = None
def setText(self, text):
self._text = text
def paintEvent(self, event):
if self._text is None:
return
painter = QtGui.QPainter(self)
rect = self.rect()
# test clipping
# rect.setHeight(rect.height() / 2)
drawText(painter, rect, self._text)
def sizeHint(self):
return QtCore.QSize(200,200)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
lorem = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
widget = Widget()
widget.setFont(QtGui.QFont("Arial", 12))
widget.setText(replaceSomeSpaces(lorem, 3))
widget.show()
app.exec()