QLabel文本轮廓的正确定位

2024-09-28 05:39:38 发布

您现在位置:Python中文网/ 问答频道 /正文

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

我做了很多搜索,找到了使用qpainter path的方法。但问题是文本总是被截断enter image description here

根据函数,文本应该从左下角开始,但它开始得太低,太左。我知道我可以通过试错找到一个点,这样它就不会被切断-你可以-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一样的正确定位,否则如果有其他更好的方法,请告诉我。


Tags: 方法text文本importselfinitdeflabel
1条回答
网友
1楼 · 发布于 2024-09-28 05:39:38

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

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

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(即25磅字体的轮廓厚度为1倍)。使用setOutlineThickness更改它。如果您想要不基于点大小的固定轮廓(例如3px),请调用setScaledOutlineMode(False)setOutlineThickness(3)

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

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

enter image description here

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选项从中获得比纯色文本和轮廓更多的内容:

enter image description here

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方法将OutlineLabel视为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))

相关问题 更多 >

    热门问题