I had a slightly different problem a few weeks ago, i had to create a QLineEdit with a QCompleter and a View which displays the current match. Example QLineEdit with QListView and QStyledItemDelegate.
The simplest solution is to use a QStyledItemDelegate. There you have a custom QFontMetric which holds information about the Textstyle. You simply draw the text at the given coordinates with the given style information.
I think there is not the "right" way to draw it like Sublime Text 3, but with QFont, QFontMetrics and the right QRectF its a good start.
Some helpful links:
Get bounding boxes for each character
Example of QStyledItemDelegate
Example paint function of subclassed QStyleItemDelegate class:
def paint(self, painter: QPainter, option: 'QStyleOptionViewItem', index: QModelIndex) -> None:
"""
paint the text
:param painter: painter to draw
:param option: style options
:param index: index with data
:return: None
"""
if not index.isValid():
super(CompleterItemDelegate, self).paint(painter, option, index)
return
# self.initStyleOption(option, index) # we do not init the options because this draws the text
painter.setFont(QApplication.font())
# draw correct background
style: QStyle = option.widget.style() if option.widget else QApplication.style()
style.drawControl(QStyle.CE_ItemViewItem, option, painter, option.widget)
# get current string
txt = str(index.data(Qt.DisplayRole))
if len(txt) == 0 or len(self._search) == 0:
painter.drawText(option.rect, Qt.AlignVCenter, txt)
painter.restore()
return
equal: int = 0
for j in range(len(self._search)):
c_s = self._search[j]
for i in range(len(txt)):
c = txt.lower()[i]
if c == c_s and i == j:
equal += 1
if self._color is None:
self._color = option.palette.color(QPalette.ColorGroup.Active, QPalette.ColorRole.HighlightedText)
# calculate and draw the selected text
left_text = txt[:equal]
right_text = txt[equal:]
w1 = option.fontMetrics.horizontalAdvance(left_text)
w2 = option.fontMetrics.horizontalAdvance(right_text)
rect = option.rect
h = option.fontMetrics.height()
margins = option.widget.contentsMargins()
p1 = rect.topLeft() + QPointF(margins.left(), (rect.height() - h) / 2)
p2 = p1 + QPointF(w1, 0)
rect1 = QRectF(p1, QSizeF(w1, h))
rect2 = QRectF(p2, QSizeF(w2, h))
painter.setPen(self._color)
painter.drawText(rect1, left_text)
painter.setPen(option.palette.color(QPalette.Text))
painter.drawText(rect2, Qt.AlignVCenter, txt[equal:])