QLabel 正确定位文本轮廓 [英] QLabel correct positioning for text outline

查看:58
本文介绍了QLabel 正确定位文本轮廓的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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

from PyQt5 import QtCore, QtGui, QtWidgets从 PyQt5.QtWidgets 导入 QWidget、QLabel、QMainWindow类 MainLabel(QLabel):def __init__(self, text):super(MainLabel, self).__init__(text)defpaintEvent(self, event):qp = QtGui.QPainter()qp.begin(self)qp.setRenderHint(QtGui.QPainter.Antialiasing)字体=QtGui.QFont()font.setPointSize(70)PainterPath = QtGui.QPainterPath()#如何为 addText 获得正确的定位PainterPath.addText(0, self.height(), font,self.text())#HEREqp.strokePath(painterPath, QtGui.QPen(QtGui.QColor(0,0,0), 6))qp.fillPath(painterPath, QtGui.QColor(255,255,255))qp.end()类 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(文本被截断")self.label.setStyleSheet("font-size:70pt;color:white;outline:2px black;")self.lay.addWidget(self.label)自我展示()如果 __name__ == '__main__':导入系统app = QtWidgets.QApplication(sys.argv)w = 主窗口()sys.exit(app.exec_())

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

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

解决方案

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

有许多属性需要考虑来设置文本的基线位置:QLabel 的几何形状、文本的 boundingRect、对齐方式、缩进、字体度量.轮廓文本总体上将比相同点大小的常规文本大,因此重新实现 sizeHintminimumSizeHint 以解决这个问题.文档解释了如何计算

类模板(QWidget):def __init__(self):super().__init__()vbox = QVBoxLayout(self)label = OutlinedLabel('Lorem ipsum dolor sat amet consectetur adipiscing elit,')label.setStyleSheet('font-family: Monaco; font-size: 20pt')vbox.addWidget(标签)label = OutlinedLabel('sed do eiusmod tempor incididunt ut laboure et dolore magna aliqua.')label.setStyleSheet('font-family: Helvetica; font-size: 30pt; font-weight: bold')vbox.addWidget(标签)label = OutlinedLabel('Ut enim ad minim veniam,',alignment=Qt.AlignCenter)label.setStyleSheet('font-family: Comic Sans MS; font-size: 40pt')vbox.addWidget(标签)label = OutlinedLabel('quis nostrud exeritation ullamcolaboris nisi ut')label.setStyleSheet('font-family: Arial; font-size: 50pt; font-style: italic')vbox.addWidget(标签)label = OutlinedLabel('aliquip ex ea commodo consequat.')label.setStyleSheet('font-family: American Typewriter; font-size: 60pt')label.setPen(Qt.red)vbox.addWidget(标签)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 = OutlinedLabel('in reprehenderit')label.setStyleSheet('font-family: Zapfino; font-size: 80pt')label.setBrush(Qt.red)vbox.addWidget(标签)如果 __name__ == '__main__':app = QApplication(sys.argv)窗口 = 模板()window.show()sys.exit(app.exec_())

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

类模板(QWidget):def __init__(self):super().__init__()vbox = QVBoxLayout(self)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(标签)标签 = 大纲标签(文本)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('海军'))vbox.addWidget(标签)标签 = 大纲标签(文本)linearGrad.setStart(0, 0);linearGrad.setFinalStop(1, 0)linearGrad.setColorAt(0, Qt.cyan);linearGrad.setColorAt(1, Qt.magenta)label.setPen(QPen(linearGrad, 1)) # 笔宽被忽略vbox.addWidget(标签)标签 = 大纲标签(文本)linearGrad.setFinalStop(1, 1)对于 [(0, '#231557'), (0.29, '#44107A'), (0.67, '#FF1361'), (1, '#FFF800')] 中的 x: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.setBrush(QBrush(Qt.darkBlue,Qt.BDiagPattern))label.setPen(Qt.darkGray)vbox.addWidget(标签)label = OutlinedLabel(text, styleSheet='background-color: black')label.setBrush(QPixmap('paint.jpg'))label.setPen(QColor('薰衣草'))vbox.addWidget(标签)self.setStyleSheet('''大纲标签{字体系列:Ubuntu;字体大小:60pt;字体粗细:粗体;}''')如果 __name__ == '__main__':app = QApplication(sys.argv)窗口 = 模板()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)笔画 = QPainterPathStroker()描边.setWidth(w)qp.drawPath(stroker.createStroke(path).united(path))

I am trying to create a label with text outline. I just want a simple white text with black outline. I first tried to do it in css like this label.setStyleSheet("color:white; outline:2px black;")
but outline didn’t do anything.

I did lots of searching and found the way to do it with qpainter path. But the problem is that the text is always cut off.

According to the function the text is supposed to be started from the bottom left but it starts too low and left. I know I can find a point by trial error so it doesn’t gets cut off- you can -20 to the height and it will be fine enough for this one. But it will only fix this specific text! It wont be the same for any label with a different size or text or font.

I will put the minimal code example here

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_())

It is such a common thing that I see literally everywhere but yet there is no simple function for this in PYQT without causing more issue? So the normal Qlabel will deal with the positioning automatically, but if you want text outline you have to give that up!

So I am asking how to find the correct positioning like a normal Qlabel if this is the only way to have text outline, otherwise if there is some other way that is better please tell me.

解决方案

Just because there is no convenient function or stylesheet property does not mean there is no consistent solution!

There are a number of properties to consider to set the baseline position of the text: the geometry of the QLabel, boundingRect of the text, alignment, indent, font metrics. The outlined text is going to be larger overall than regular text of the same point size, so the sizeHint and minimumSizeHint are reimplemented to account for it. The docs explain how indent is calculated and used with alignment. The text and character geometry, ascent, descent, and bearings are obtained from QFontMetrics. With this information a position for QPainterPath.addText can be determined that will emulate 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)

You can set the OutlinedLabel fill and outline color with setBrush and setPen. The default is white text with a black outline. The outline thickness is based on the point size of the font, the default ratio is 1/25 (i.e. a 25pt font will have a 1px thick outline). Use setOutlineThickness to change it. If you want a fixed outline not based on the point size (e.g. 3px), call setScaledOutlineMode(False) and setOutlineThickness(3).

This class only supports single line, plain text strings with left/right/top/bottom/center alignment. If you want other QLabel features like hyperlinks, word wrap, elided text, etc. those will need to be implemented too. But chances are you wouldn’t use text outline in those cases anyway.

Here is an example to show that it will work for a variety of labels:

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_())

Now Qt actually comes in clutch because you can get so much more out of this than just solid color text and outlines with all the QBrush/QPen options:

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_())

Note that I’ve chosen to treat OutlinedLabel like a QGraphicsItem with the setBrush/setPen methods. If you want to use style sheets for the text color fill the path with qp.fillPath(path, self.palette().text())

Another option instead of calling QPainter.strokePath and then QPainter.fillPath is to generate a fillable outline of the text path with QPainterPathStroker, but I’ve noticed it’s slower. I would only use it to adjust the clarity of very small text by setting a larger width to the stroker than the pen. To try it replace the last 5 lines in paintEvent with:

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

这篇关于QLabel 正确定位文本轮廓的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆