【问题标题】:QLabel correct positioning for text outlineQLabel 正确定位文本轮廓
【发布时间】:2020-10-10 06:14:16
【问题描述】:

我正在尝试创建带有文本轮廓的标签。我只想要一个带有黑色轮廓的简单白色文本。我第一次尝试像这样在 css 中做到这一点label.setStyleSheet("color:white; outline:2px black;")
但是大纲没有做任何事情。

我进行了大量搜索,并找到了使用 qpainter 路径的方法。但问题是文本总是被截断。

根据函数,文本应该从左下角开始,但它开始太低和太左了。我知道我可以通过试错找到一个点,这样它就不会被切断——你可以 -20 到高度,这对这个来说就足够了。但它只会修复这个特定的文本!对于任何具有不同大小或文本或字体的标签,它都不相同。

我会把最小的代码示例放在这里

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QWidget, QLabel, QMainWindow


class MainLabel(QLabel):
    def __init__(self, text):
        super(MainLabel, self).__init__(text)

    def paintEvent(self, event):
        qp = QtGui.QPainter()
        qp.begin(self)
        qp.setRenderHint(QtGui.QPainter.Antialiasing)
        font=QtGui.QFont()
        font.setPointSize(70)
        painterPath = QtGui.QPainterPath()
        #how to get the right positioning for addText
        painterPath.addText(0, self.height(), font,self.text())#HERE
        qp.strokePath(painterPath, QtGui.QPen(QtGui.QColor(0,0,0), 6))
        qp.fillPath(painterPath, QtGui.QColor(255,255,255))
        qp.end()


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.centralWidget=QWidget(self)
        self.setCentralWidget(self.centralWidget)
        self.lay = QtWidgets.QVBoxLayout()
        self.centralWidget.setLayout(self.lay)
        self.label = MainLabel("text gets cut off")
        self.label.setStyleSheet("font-size:70pt;color:white; outline:2px black;")
        self.lay.addWidget(self.label)
        self.show()


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    sys.exit(app.exec_())

这是一个很常见的事情,我在任何地方都能看到,但是在 PYQT 中没有一个简单的函数来解决这个问题而不会引起更多问题?所以普通的 Qlabel 会自动处理定位,但是如果你想要文本轮廓你必须放弃它!

所以我想问如果这是获得文本轮廓的唯一方法,如何像普通 Qlabel 一样找到正确的定位,否则如果有其他更好的方法,请告诉我。

【问题讨论】:

    标签: python pyqt5 outline


    【解决方案1】:

    没有方便的函数或样式表属性并不意味着没有一致的解决方案!

    设置文本的基线位置需要考虑许多属性:QLabel 的几何形状、文本的 boundingRect、对齐方式、缩进、字体度量。概述的文本总体上将大于相同点大小的常规文本,因此重新实现了sizeHintminimumSizeHint 以解决它。文档解释了如何计算 indent 并将其与对齐方式一起使用。文本和字符几何、上升、下降和方位从 QFontMetrics 获得。有了这些信息,QPainterPath.addText 的位置就可以确定,这将模拟 QLabel。

    import sys, math
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    from PyQt5.QtGui import *
            
    class OutlinedLabel(QLabel):
        
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.w = 1 / 25
            self.mode = True
            self.setBrush(Qt.white)
            self.setPen(Qt.black)
    
        def scaledOutlineMode(self):
            return self.mode
    
        def setScaledOutlineMode(self, state):
            self.mode = state
    
        def outlineThickness(self):
            return self.w * self.font().pointSize() if self.mode else self.w
    
        def setOutlineThickness(self, value):
            self.w = value
    
        def setBrush(self, brush):
            if not isinstance(brush, QBrush):
                brush = QBrush(brush)
            self.brush = brush
    
        def setPen(self, pen):
            if not isinstance(pen, QPen):
                pen = QPen(pen)
            pen.setJoinStyle(Qt.RoundJoin)
            self.pen = pen
    
        def sizeHint(self):
            w = math.ceil(self.outlineThickness() * 2)
            return super().sizeHint() + QSize(w, w)
        
        def minimumSizeHint(self):
            w = math.ceil(self.outlineThickness() * 2)
            return super().minimumSizeHint() + QSize(w, w)
        
        def paintEvent(self, event):
            w = self.outlineThickness()
            rect = self.rect()
            metrics = QFontMetrics(self.font())
            tr = metrics.boundingRect(self.text()).adjusted(0, 0, w, w)
            if self.indent() == -1:
                if self.frameWidth():
                    indent = (metrics.boundingRect('x').width() + w * 2) / 2
                else:
                    indent = w
            else:
                indent = self.indent()
    
            if self.alignment() & Qt.AlignLeft:
                x = rect.left() + indent - min(metrics.leftBearing(self.text()[0]), 0)
            elif self.alignment() & Qt.AlignRight:
                x = rect.x() + rect.width() - indent - tr.width()
            else:
                x = (rect.width() - tr.width()) / 2
                
            if self.alignment() & Qt.AlignTop:
                y = rect.top() + indent + metrics.ascent()
            elif self.alignment() & Qt.AlignBottom:
                y = rect.y() + rect.height() - indent - metrics.descent()
            else:
                y = (rect.height() + metrics.ascent() - metrics.descent()) / 2
    
            path = QPainterPath()
            path.addText(x, y, self.font(), self.text())
            qp = QPainter(self)
            qp.setRenderHint(QPainter.Antialiasing)
    
            self.pen.setWidthF(w * 2)
            qp.strokePath(path, self.pen)
            if 1 < self.brush.style() < 15:
                qp.fillPath(path, self.palette().window())
            qp.fillPath(path, self.brush)
    

    您可以使用setBrushsetPen 设置OutlinedLabel 填充和轮廓颜色。默认为带有黑色轮廓的白色文本。轮廓粗细取决于字体的磅值,默认比例为 1/25(即 25pt 的字体将有 1px 粗的轮廓)。使用setOutlineThickness 更改它。如果您想要一个不基于点大小的固定轮廓(例如 3px),请致电 setScaledOutlineMode(False)setOutlineThickness(3)

    此类仅支持左/右/上/下/居中对齐的单行纯文本字符串。如果您想要其他 QLabel 功能,如超链接、自动换行、省略文本等,也需要实现这些功能。但在这些情况下,您可能不会使用文本大纲。

    下面是一个示例,说明它适用于各种标签:

    class Template(QWidget):
    
        def __init__(self):
            super().__init__()
            vbox = QVBoxLayout(self)
            label = OutlinedLabel('Lorem ipsum dolor sit amet consectetur adipiscing elit,')
            label.setStyleSheet('font-family: Monaco; font-size: 20pt')
            vbox.addWidget(label)
    
            label = OutlinedLabel('sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.')
            label.setStyleSheet('font-family: Helvetica; font-size: 30pt; font-weight: bold')
            vbox.addWidget(label)
    
            label = OutlinedLabel('Ut enim ad minim veniam,', alignment=Qt.AlignCenter)
            label.setStyleSheet('font-family: Comic Sans MS; font-size: 40pt')
            vbox.addWidget(label)
    
            label = OutlinedLabel('quis nostrud exercitation ullamco laboris nisi ut')
            label.setStyleSheet('font-family: Arial; font-size: 50pt; font-style: italic')
            vbox.addWidget(label)
    
            label = OutlinedLabel('aliquip ex ea commodo consequat.')
            label.setStyleSheet('font-family: American Typewriter; font-size: 60pt')
            label.setPen(Qt.red)
            vbox.addWidget(label)
    
            label = OutlinedLabel('Duis aute irure dolor', alignment=Qt.AlignRight)
            label.setStyleSheet('font-family: Luminari; font-size: 70pt')
            label.setPen(Qt.red); label.setBrush(Qt.black)
            vbox.addWidget(label)
    
            label = OutlinedLabel('in reprehenderit')
            label.setStyleSheet('font-family: Zapfino; font-size: 80pt')
            label.setBrush(Qt.red)
            vbox.addWidget(label)
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        window = Template()
        window.show()
        sys.exit(app.exec_())
    

    现在 Qt 真的很重要,因为您可以从中获得更多,而不仅仅是纯色文本和带有所有 QBrush/QPen 选项的轮廓:

    class Template(QWidget):
    
        def __init__(self):
            super().__init__()                                          
            vbox = QVBoxLayout(self)
            text = 'Felicitations'
            
            label = OutlinedLabel(text)
            linearGrad = QLinearGradient(0, 1, 0, 0)
            linearGrad.setCoordinateMode(QGradient.ObjectBoundingMode)
            linearGrad.setColorAt(0, QColor('#0fd850'))
            linearGrad.setColorAt(1, QColor('#f9f047'))
            label.setBrush(linearGrad)
            label.setPen(Qt.darkGreen)
            vbox.addWidget(label)
    
            label = OutlinedLabel(text)
            radialGrad = QRadialGradient(0.3, 0.7, 0.05)
            radialGrad.setCoordinateMode(QGradient.ObjectBoundingMode)
            radialGrad.setSpread(QGradient.ReflectSpread)
            radialGrad.setColorAt(0, QColor('#0250c5'))
            radialGrad.setColorAt(1, QColor('#2575fc'))
            label.setBrush(radialGrad)
            label.setPen(QColor('Navy'))
            vbox.addWidget(label)
            
            label = OutlinedLabel(text)
            linearGrad.setStart(0, 0); linearGrad.setFinalStop(1, 0)
            linearGrad.setColorAt(0, Qt.cyan); linearGrad.setColorAt(1, Qt.magenta)
            label.setPen(QPen(linearGrad, 1)) # pen width is ignored
            vbox.addWidget(label)
    
            label = OutlinedLabel(text)
            linearGrad.setFinalStop(1, 1)
            for x in [(0, '#231557'), (0.29, '#44107A'), (0.67, '#FF1361'), (1, '#FFF800')]:
                linearGrad.setColorAt(x[0], QColor(x[1]))
            label.setBrush(linearGrad)
            label.setPen(QPen(QBrush(QColor('RoyalBlue'), Qt.Dense4Pattern), 1))
            label.setOutlineThickness(1 / 15)
            vbox.addWidget(label)
    
            label = OutlinedLabel(text)
            label.setBrush(QBrush(Qt.darkBlue, Qt.BDiagPattern))
            label.setPen(Qt.darkGray)
            vbox.addWidget(label)
    
            label = OutlinedLabel(text, styleSheet='background-color: black')
            label.setBrush(QPixmap('paint.jpg'))
            label.setPen(QColor('Lavender'))
            vbox.addWidget(label)
            
            self.setStyleSheet('''
            OutlinedLabel {
                font-family: Ubuntu;
                font-size: 60pt;
                font-weight: bold;
            }''')
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        window = Template()
        window.show()
        sys.exit(app.exec_())
    

    请注意,我选择使用 setBrush/setPen 方法将 OutlinedLabel 视为 QGraphicsItem。如果你想为文本颜色使用样式表,用qp.fillPath(path, self.palette().text())填充路径

    另一个替代调用QPainter.strokePath 然后QPainter.fillPath 的选项是使用QPainterPathStroker 生成文本路径的可填充轮廓,但我注意到它比较慢。我只会使用它来调整非常小的文本的清晰度,方法是为笔划设置比笔更大的宽度。尝试将paintEvent 中的最后 5 行替换为:

    qp.setBrush(self.brush)
    self.pen.setWidthF(w)
    qp.setPen(self.pen)
    stroker = QPainterPathStroker()
    stroker.setWidth(w)
    qp.drawPath(stroker.createStroke(path).united(path))
    

    【讨论】:

    • 很好的例子,谢谢!!当文本包含换行符时,该示例表现不佳。多行你会怎么做?
    • @laurapons 您需要为每一行文本调用QPainterPath.addText,使用path.addText(x, y, font, line),随着您的进行更新XY值,例如将 Y 值增加metrics.height() 或索引/行数。
    猜你喜欢
    • 1970-01-01
    • 2021-08-21
    • 2015-06-25
    • 1970-01-01
    • 1970-01-01
    • 2012-01-04
    • 2021-10-29
    • 2012-07-08
    • 1970-01-01
    相关资源
    最近更新 更多