Sylve
4th April 2021, 20:03
Hello everyone,
I have been using Qt for a personal project and I am so far pleasantly surprised how powerful it is.
One issue I am still struggling with despite several hours of digging is the following. I want to write text inside a QTextEdit with an outline around the text. I manage to do it with the following code, however there is an apparent problem (see the picture below): the outline is drawn not only outside the text, but also inside.
from PyQt5.QtWidgets import *
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPen, QFont
from PyQt5.QtGui import QTextCursor
class MyTextEdit(QTextEdit):
def __init__(self):
super().__init__()
self.resize(800, 400)
font = self.currentFont()
font.setPointSize(80)
font.setStyleStrategy(QFont.PreferAntialias)
self.setFont(font)
my_cursor = self.textCursor()
my_cursor.movePosition(QTextCursor.Start)
my_char_format = my_cursor.charFormat()
my_cursor.setCharFormat(my_char_format)
my_cursor.insertText("Text example", my_char_format)
outline_format = my_char_format
outline_format.setTextOutline(QPen(Qt.red, 5))
my_cursor.setCharFormat(outline_format)
my_cursor.insertBlock()
my_cursor.insertText("Text example")
if __name__ == '__main__':
app = QApplication([sys.argv])
win = MyTextEdit()
win.show()
app.exec_()
Result:
https://i.imgur.com/cFnU3AM.png
This problem has already been encountered, for example here: https://stackoverflow.com/questions/13966868/qt-outlined-text-without-thinning-font and here https://stackoverflow.com/questions/15166754/stroking-a-path-only-inside-outside .
However, I do not understand the answer given using the print() method, which is not available for QTextEdit, although it is available for QGraphicsTextItem.
I have a hard constraint that the text should still be selectable, and nicely selectable. I do not need the text to be editable.
I wonder if, for example, overriding the paintEvent function would allow me to do more advanced text painting (for example, writing first the text with outline, and then overriding with the text without outline but keeping the first painting under it), but still having the text selectable.
Thanks a lot for your help, I will still be thinking meanwhile and post if I find a solution :)
Edit 2:
I think I got it. I should implement a class inheriting from QTextDocument, and overwrite its print() method as detailed in the Stackoverflow answer.
Then, for my custom QTextEdit in my init, I should use setDocument() with my custom QTextDocument class where print() is overwritten.
See https://doc.qt.io/qt-5/qtextedit.html#document-prop
I will try that later and let you know if this works.
Edit:
Here is an example where the outline gets indeed only outside the text, however the text is not selectable anymore:
from PyQt5.QtWidgets import *
import sys
from PyQt5 import QtGui
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor, QPainter, QPainterPath, QBrush, QPen, QPalette, QFont
class MyTextEdit(QTextEdit):
def __init__(self, parent = None):
super(MyTextEdit, self).__init__(parent)
self.resize(800, 400)
def paintEvent(self, event):
painter = QPainter(self.viewport())
painter.setRenderHint(QPainter.Antialiasing);
painter.drawLine(10, 10, 200, 10) # just for testing purposes
#font = self.font()
font = QFont("Arial", 72, 50, False);
text = "Text example"
text_path = QPainterPath()
text_path.addText(0, 100, font, text)
painter.setFont(font)
# draw outline
painter.setPen(outline_pen)
painter.drawPath(text_path)
# draw text
color = self.palette().color(QPalette.Text)
painter.setPen(color)
painter.drawText(0, 100, text)
super(MyTextEdit, self).paintEvent(event)
if __name__ == '__main__':
app = QApplication([sys.argv])
outline_color = QColor(200, 0, 0, 180)
outline_brush = QBrush(outline_color, Qt.SolidPattern)
outline_pen = QPen(outline_brush, 15, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
win = MyTextEdit()
win.show()
app.exec_()
I have been using Qt for a personal project and I am so far pleasantly surprised how powerful it is.
One issue I am still struggling with despite several hours of digging is the following. I want to write text inside a QTextEdit with an outline around the text. I manage to do it with the following code, however there is an apparent problem (see the picture below): the outline is drawn not only outside the text, but also inside.
from PyQt5.QtWidgets import *
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPen, QFont
from PyQt5.QtGui import QTextCursor
class MyTextEdit(QTextEdit):
def __init__(self):
super().__init__()
self.resize(800, 400)
font = self.currentFont()
font.setPointSize(80)
font.setStyleStrategy(QFont.PreferAntialias)
self.setFont(font)
my_cursor = self.textCursor()
my_cursor.movePosition(QTextCursor.Start)
my_char_format = my_cursor.charFormat()
my_cursor.setCharFormat(my_char_format)
my_cursor.insertText("Text example", my_char_format)
outline_format = my_char_format
outline_format.setTextOutline(QPen(Qt.red, 5))
my_cursor.setCharFormat(outline_format)
my_cursor.insertBlock()
my_cursor.insertText("Text example")
if __name__ == '__main__':
app = QApplication([sys.argv])
win = MyTextEdit()
win.show()
app.exec_()
Result:
https://i.imgur.com/cFnU3AM.png
This problem has already been encountered, for example here: https://stackoverflow.com/questions/13966868/qt-outlined-text-without-thinning-font and here https://stackoverflow.com/questions/15166754/stroking-a-path-only-inside-outside .
However, I do not understand the answer given using the print() method, which is not available for QTextEdit, although it is available for QGraphicsTextItem.
I have a hard constraint that the text should still be selectable, and nicely selectable. I do not need the text to be editable.
I wonder if, for example, overriding the paintEvent function would allow me to do more advanced text painting (for example, writing first the text with outline, and then overriding with the text without outline but keeping the first painting under it), but still having the text selectable.
Thanks a lot for your help, I will still be thinking meanwhile and post if I find a solution :)
Edit 2:
I think I got it. I should implement a class inheriting from QTextDocument, and overwrite its print() method as detailed in the Stackoverflow answer.
Then, for my custom QTextEdit in my init, I should use setDocument() with my custom QTextDocument class where print() is overwritten.
See https://doc.qt.io/qt-5/qtextedit.html#document-prop
I will try that later and let you know if this works.
Edit:
Here is an example where the outline gets indeed only outside the text, however the text is not selectable anymore:
from PyQt5.QtWidgets import *
import sys
from PyQt5 import QtGui
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor, QPainter, QPainterPath, QBrush, QPen, QPalette, QFont
class MyTextEdit(QTextEdit):
def __init__(self, parent = None):
super(MyTextEdit, self).__init__(parent)
self.resize(800, 400)
def paintEvent(self, event):
painter = QPainter(self.viewport())
painter.setRenderHint(QPainter.Antialiasing);
painter.drawLine(10, 10, 200, 10) # just for testing purposes
#font = self.font()
font = QFont("Arial", 72, 50, False);
text = "Text example"
text_path = QPainterPath()
text_path.addText(0, 100, font, text)
painter.setFont(font)
# draw outline
painter.setPen(outline_pen)
painter.drawPath(text_path)
# draw text
color = self.palette().color(QPalette.Text)
painter.setPen(color)
painter.drawText(0, 100, text)
super(MyTextEdit, self).paintEvent(event)
if __name__ == '__main__':
app = QApplication([sys.argv])
outline_color = QColor(200, 0, 0, 180)
outline_brush = QBrush(outline_color, Qt.SolidPattern)
outline_pen = QPen(outline_brush, 15, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
win = MyTextEdit()
win.show()
app.exec_()