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()
Другие вопросы по тегам