Qt 在 boundingRect 中包装文本
Qt wrapping text in boundingRect
我正在用 PyQt 编写一个应用程序,我需要包装我正在绘制的文本。我使用boundingRect
QFontMetrics
class 的方法来定义尺寸然后QPainter.drawText
来绘制。我需要文本适合给定的矩形。这是我使用的行:
rect = metrics.boundingRect(rect, Qt.TextWordWrap, text)
但是,由于标志 Qt.TextWordWrap
,如果文本没有任何空格,它不会换行并且不适合矩形。我试过 Qt.TextWrapAnywhere
但这会把单词分开,即使有空格也是如此。 Qt.TextFlag
似乎没有任何标志,优先包装单词,只有在不可能时,才将单词分开,如 QTextOption.WrapAtWordBoundaryOrAnywhere
。
有没有办法使用 boundingRect
和 drawText
来换行这样的文本?
对于这种情况,通常最好使用 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()
我正在用 PyQt 编写一个应用程序,我需要包装我正在绘制的文本。我使用boundingRect
QFontMetrics
class 的方法来定义尺寸然后QPainter.drawText
来绘制。我需要文本适合给定的矩形。这是我使用的行:
rect = metrics.boundingRect(rect, Qt.TextWordWrap, text)
但是,由于标志 Qt.TextWordWrap
,如果文本没有任何空格,它不会换行并且不适合矩形。我试过 Qt.TextWrapAnywhere
但这会把单词分开,即使有空格也是如此。 Qt.TextFlag
似乎没有任何标志,优先包装单词,只有在不可能时,才将单词分开,如 QTextOption.WrapAtWordBoundaryOrAnywhere
。
有没有办法使用 boundingRect
和 drawText
来换行这样的文本?
对于这种情况,通常最好使用 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()