File indexing completed on 2024-04-28 05:50:52

0001 /*
0002     SPDX-FileCopyrightText: 2020-2020 Gustavo Carneiro <gcarneiroa@hotmail.com>
0003     SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
0004     SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 // Own
0010 #include "TerminalPainter.h"
0011 
0012 // Konsole
0013 #include "../Screen.h"
0014 #include "../characters/LineBlockCharacters.h"
0015 #include "../filterHotSpots/FilterChain.h"
0016 #include "../session/SessionManager.h"
0017 #include "TerminalColor.h"
0018 #include "TerminalFonts.h"
0019 #include "TerminalScrollBar.h"
0020 
0021 // Qt
0022 #include <QChar>
0023 #include <QColor>
0024 #include <QDebug>
0025 #include <QElapsedTimer>
0026 #include <QPainter>
0027 #include <QPen>
0028 #include <QRect>
0029 #include <QRegion>
0030 #include <QString>
0031 #include <QTransform>
0032 #include <QtMath>
0033 
0034 #include <optional>
0035 
0036 // we use this to force QPainter to display text in LTR mode
0037 // more information can be found in: https://unicode.org/reports/tr9/
0038 const QChar LTR_OVERRIDE_CHAR(0x202D);
0039 
0040 namespace Konsole
0041 {
0042 TerminalPainter::TerminalPainter(TerminalDisplay *parent)
0043     : QObject(parent)
0044     , m_parentDisplay(parent)
0045 {
0046 }
0047 
0048 static inline bool isLineCharString(const QString &string)
0049 {
0050     if (string.length() == 0) {
0051         return false;
0052     }
0053     if (LineBlockCharacters::canDraw(string.at(0).unicode())) {
0054         return true;
0055     }
0056     if (string.length() <= 1 || !string[0].isSurrogate()) {
0057         return false;
0058     }
0059     uint ucs4;
0060     if (string[0].isHighSurrogate()) {
0061         ucs4 = QChar::surrogateToUcs4(string[0], string[1]);
0062     } else {
0063         ucs4 = QChar::surrogateToUcs4(string[1], string[0]);
0064     }
0065     return LineBlockCharacters::isLegacyComputingSymbol(ucs4);
0066 }
0067 
0068 QColor alphaBlend(const QColor &foreground, const QColor &background)
0069 {
0070     const auto foregroundAlpha = foreground.alphaF();
0071     const auto inverseForegroundAlpha = 1.0 - foregroundAlpha;
0072     const auto backgroundAlpha = background.alphaF();
0073 
0074     if (foregroundAlpha == 0.0) {
0075         return background;
0076     }
0077 
0078     if (backgroundAlpha == 1.0) {
0079         return QColor::fromRgb((foregroundAlpha * foreground.red()) + (inverseForegroundAlpha * background.red()),
0080                                (foregroundAlpha * foreground.green()) + (inverseForegroundAlpha * background.green()),
0081                                (foregroundAlpha * foreground.blue()) + (inverseForegroundAlpha * background.blue()),
0082                                0xff);
0083     } else {
0084         const auto inverseBackgroundAlpha = (backgroundAlpha * inverseForegroundAlpha);
0085         const auto finalAlpha = foregroundAlpha + inverseBackgroundAlpha;
0086         Q_ASSERT(finalAlpha != 0.0);
0087 
0088         return QColor::fromRgb((foregroundAlpha * foreground.red()) + (inverseBackgroundAlpha * background.red()),
0089                                (foregroundAlpha * foreground.green()) + (inverseBackgroundAlpha * background.green()),
0090                                (foregroundAlpha * foreground.blue()) + (inverseBackgroundAlpha * background.blue()),
0091                                finalAlpha);
0092     }
0093 }
0094 
0095 qreal wcag20AdjustColorPart(qreal v)
0096 {
0097     return v <= 0.03928 ? v / 12.92 : qPow((v + 0.055) / 1.055, 2.4);
0098 }
0099 
0100 qreal wcag20RelativeLuminosity(const QColor &of)
0101 {
0102     auto r = of.redF(), g = of.greenF(), b = of.blueF();
0103 
0104     const auto a = wcag20AdjustColorPart;
0105 
0106     auto r2 = a(r), g2 = a(g), b2 = a(b);
0107 
0108     return r2 * 0.2126 + g2 * 0.7152 + b2 * 0.0722;
0109 }
0110 
0111 qreal wcag20Contrast(const QColor &c1, const QColor &c2)
0112 {
0113     const auto l1 = wcag20RelativeLuminosity(c1) + 0.05, l2 = wcag20RelativeLuminosity(c2) + 0.05;
0114 
0115     return (l1 > l2) ? l1 / l2 : l2 / l1;
0116 }
0117 
0118 std::optional<QColor> calculateBackgroundColor(const Character style, const QColor *colorTable)
0119 {
0120     auto c1 = style.backgroundColor.color(colorTable);
0121     const auto initialBG = c1;
0122 
0123     c1.setAlphaF(0.8);
0124 
0125     const auto blend1 = alphaBlend(c1, colorTable[DEFAULT_FORE_COLOR]), blend2 = alphaBlend(c1, colorTable[DEFAULT_BACK_COLOR]);
0126     const auto fg = style.foregroundColor.color(colorTable);
0127 
0128     const auto contrast1 = wcag20Contrast(fg, blend1), contrast2 = wcag20Contrast(fg, blend2);
0129     const auto contrastBG1 = wcag20Contrast(blend1, initialBG), contrastBG2 = wcag20Contrast(blend2, initialBG);
0130 
0131     const auto fgFactor = 5.5; // if text contrast is too low against our calculated bg, then we flip to reverse
0132     const auto bgFactor = 1.6; // if bg contrast is too low against default bg, then we flip to reverse
0133 
0134     if ((contrast1 < fgFactor && contrast2 < fgFactor) || (contrastBG1 < bgFactor && contrastBG2 < bgFactor)) {
0135         return {};
0136     }
0137 
0138     return (contrast1 < contrast2) ? blend1 : blend2;
0139 }
0140 
0141 static void reverseRendition(Character &p)
0142 {
0143     CharacterColor f = p.foregroundColor;
0144     CharacterColor b = p.backgroundColor;
0145 
0146     p.foregroundColor = b;
0147     p.backgroundColor = f;
0148 }
0149 
0150 void TerminalPainter::drawContents(Character *image,
0151                                    QPainter &paint,
0152                                    const QRect &rect,
0153                                    bool printerFriendly,
0154                                    int imageSize,
0155                                    bool bidiEnabled,
0156                                    QVector<LineProperty> lineProperties,
0157                                    CharacterColor const *ulColorTable)
0158 {
0159     auto currentProfile = SessionManager::instance()->sessionProfile(m_parentDisplay->session());
0160     const bool wordMode = currentProfile ? currentProfile->property<bool>(Profile::WordMode) : false;
0161     const bool wordModeAttr = currentProfile ? currentProfile->property<bool>(Profile::WordModeAttr) : true;
0162     const bool wordModeAscii = currentProfile ? currentProfile->property<bool>(Profile::WordModeAscii) : true;
0163     const bool wordModeBrahmic = currentProfile ? currentProfile->property<bool>(Profile::WordModeBrahmic) : true;
0164     const bool invertedRendition = currentProfile ? currentProfile->property<bool>(Profile::InvertSelectionColors) : false;
0165     const Enum::Hints semanticHints = currentProfile ? static_cast<Enum::Hints>(currentProfile->semanticHints()) : Enum::HintsNever;
0166     const Enum::Hints lineNumbers = currentProfile ? static_cast<Enum::Hints>(currentProfile->lineNumbers()) : Enum::HintsNever;
0167     const Enum::Hints errorBars = currentProfile ? static_cast<Enum::Hints>(currentProfile->property<int>(Profile::ErrorBars)) : Enum::HintsNever;
0168     const Enum::Hints errorBackground = currentProfile ? static_cast<Enum::Hints>(currentProfile->property<int>(Profile::ErrorBackground)) : Enum::HintsNever;
0169     const Enum::Hints alternatingBars = currentProfile ? static_cast<Enum::Hints>(currentProfile->property<int>(Profile::AlternatingBars)) : Enum::HintsNever;
0170     const Enum::Hints alternatingBackground =
0171         currentProfile ? static_cast<Enum::Hints>(currentProfile->property<int>(Profile::AlternatingBackground)) : Enum::HintsNever;
0172     const bool showHints = m_parentDisplay->filterChain()->showUrlHint();
0173 #define hintActive(h) const bool h##Active = ((h == Enum::HintsURL && showHints) || h == Enum::HintsAlways)
0174     hintActive(semanticHints);
0175     hintActive(lineNumbers);
0176     hintActive(errorBars);
0177     hintActive(errorBackground);
0178     hintActive(alternatingBars);
0179     hintActive(alternatingBackground);
0180     QColor red;
0181     QColor gray;
0182     if (m_parentDisplay->terminalColor()->backgroundColor().red() > 128) {
0183         // Bright background
0184         red = QColor(255, 64, 64);
0185         gray = QColor(192, 192, 192);
0186     } else {
0187         red = QColor(48, 0, 0);
0188         gray = QColor(40, 40, 40);
0189     }
0190 
0191     QVector<uint> univec;
0192     univec.reserve(m_parentDisplay->usedColumns());
0193     int placementIdx = 0;
0194 
0195     const int leftPadding = m_parentDisplay->contentRect().left() + m_parentDisplay->contentsRect().left();
0196     const int topPadding = m_parentDisplay->contentRect().top() + m_parentDisplay->contentsRect().top();
0197     const int fontWidth = m_parentDisplay->terminalFont()->fontWidth();
0198     const int fontHeight = m_parentDisplay->terminalFont()->fontHeight();
0199     const QRect textArea(QPoint(leftPadding + fontWidth * rect.x(), topPadding + rect.y() * fontHeight),
0200                          QSize(rect.width() * fontWidth, rect.height() * fontHeight));
0201     if (!printerFriendly) {
0202         drawImagesBelowText(paint, textArea, fontWidth, fontHeight, placementIdx);
0203     }
0204 
0205     static const QFont::Weight FontWeights[] = {
0206         QFont::Thin,
0207         QFont::Light,
0208         QFont::Normal,
0209         QFont::Bold,
0210         QFont::Black,
0211     };
0212     const QFont::Weight normalWeight = static_cast<QFont::Weight>(m_parentDisplay->font().weight()); // Qt6: cast can go away
0213     auto it = std::upper_bound(std::begin(FontWeights), std::end(FontWeights), normalWeight);
0214     const QFont::Weight boldWeight = it != std::end(FontWeights) ? *it : QFont::Black;
0215     paint.setLayoutDirection(Qt::LeftToRight);
0216     const QColor *colorTable = m_parentDisplay->terminalColor()->colorTable();
0217 
0218     for (int y = rect.y(); y <= rect.bottom(); y++) {
0219         int pos = m_parentDisplay->loc(0, y);
0220         if (pos > imageSize) {
0221             break;
0222         }
0223         int right = rect.right();
0224         if (pos + right > imageSize) {
0225             right = imageSize - pos;
0226         }
0227 
0228         const int textY = topPadding + fontHeight * y;
0229         bool doubleHeightLinePair = false;
0230         int x = rect.x();
0231         LineProperty lineProperty = y < lineProperties.size() ? lineProperties[y] : LineProperty();
0232 
0233         // Search for start of multi-column character
0234         if (image[m_parentDisplay->loc(rect.x(), y)].isRightHalfOfDoubleWide() && (x != 0)) {
0235             x--;
0236         }
0237         QTransform textScale;
0238         bool doubleHeight = false;
0239         bool doubleWidthLine = false;
0240 
0241         if ((lineProperty.flags.f.doublewidth) != 0) {
0242             textScale.scale(2, 1);
0243             doubleWidthLine = true;
0244         }
0245 
0246         doubleHeight = lineProperty.flags.f.doubleheight_top | lineProperty.flags.f.doubleheight_bottom;
0247         if (doubleHeight) {
0248             textScale.scale(1, 2);
0249         }
0250 
0251         if (y < lineProperties.size() - 1) {
0252             if (((lineProperties[y].flags.f.doubleheight_top) != 0) && ((lineProperties[y + 1].flags.f.doubleheight_bottom) != 0)) {
0253                 doubleHeightLinePair = true;
0254             }
0255         }
0256 
0257         // Apply text scaling matrix
0258         paint.setWorldTransform(textScale, true);
0259         // Calculate the area in which the text will be drawn
0260         const int textX = leftPadding + fontWidth * rect.x() * (doubleWidthLine ? 2 : 1);
0261         const int textWidth = fontWidth * rect.width();
0262         const int textHeight = doubleHeight && !doubleHeightLinePair ? fontHeight / 2 : fontHeight;
0263 
0264         // move the calculated area to take account of scaling applied to the painter.
0265         // the position of the area from the origin (0,0) is scaled
0266         // by the opposite of whatever
0267         // transformation has been applied to the painter.  this ensures that
0268         // painting does actually start from textArea.topLeft()
0269         //(instead of textArea.topLeft() * painter-scale)
0270         QString line;
0271 #define MAX_LINE_WIDTH 1024
0272 #define vis2log(x) ((bidiEnabled && (x) <= lastNonSpace) ? line2log[vis2line[x]] : (x))
0273         int log2line[MAX_LINE_WIDTH];
0274         int line2log[MAX_LINE_WIDTH];
0275         uint16_t shapemap[MAX_LINE_WIDTH];
0276         int32_t vis2line[MAX_LINE_WIDTH];
0277         bool shaped;
0278         int lastNonSpace = m_parentDisplay->bidiMap(image + pos, line, log2line, line2log, shapemap, vis2line, shaped, bidiEnabled, bidiEnabled);
0279         const QRect textArea(textScale.inverted().map(QPoint(textX, textY)), QSize(textWidth, textHeight));
0280         if (!printerFriendly) {
0281             QColor background = m_parentDisplay->terminalColor()->backgroundColor();
0282             if (lineProperty.flags.f.error && errorBackgroundActive) {
0283                 background = red;
0284             } else if ((lineProperty.counter & 1) && alternatingBackgroundActive) {
0285                 background = gray;
0286             }
0287             drawBelowText(paint,
0288                           textArea,
0289                           image + pos,
0290                           rect.x(),
0291                           rect.width(),
0292                           fontWidth,
0293                           colorTable,
0294                           invertedRendition,
0295                           vis2line,
0296                           line2log,
0297                           bidiEnabled,
0298                           lastNonSpace,
0299                           background);
0300         }
0301 
0302         RenditionFlags oldRendition = -1;
0303         QColor oldColor = QColor();
0304         int lastCharType = 0;
0305         QString word_str;
0306         int word_x = 0;
0307         int word_log_x = 0;
0308         for (; x <= right; x++) {
0309             if (x > lastNonSpace) {
0310                 // What about the cursor?
0311                 // break;
0312             }
0313             const int log_x = vis2log(x);
0314             const int log_next = vis2log(x + 1); // to know if this character is resolved as RTL, e.g. emojis in RTL context
0315 
0316             const Character char_value = image[pos + log_x];
0317             const bool doubleWidth = image[qMin(pos + log_x + 1, imageSize - 1)].isRightHalfOfDoubleWide(); // East_Asian_Width wide character
0318 
0319             if (!printerFriendly && lastCharType == 0 && char_value.isSpace() && char_value.rendition.f.cursor == 0) {
0320                 continue;
0321             }
0322 
0323             QString unistr = line.mid(log2line[log_x], log2line[log_x + 1] - log2line[log_x]);
0324             if (shaped) {
0325                 unistr[0] = shapemap[log2line[log_x]];
0326             }
0327 
0328             // paint text fragment
0329             if (unistr.length() && unistr[0] != QChar(0)) {
0330                 int textWidth = fontWidth * (doubleWidth ? 2 : 1);
0331                 int textX = leftPadding + fontWidth * x * (doubleWidthLine ? 2 : 1);
0332                 // East_Asian_Width wide character behaving as RTL, e.g. wide emoji inside RTL context
0333                 if (doubleWidth && log_next < log_x) {
0334                     textX -= fontWidth * (doubleWidthLine ? 2 : 1);
0335                 }
0336                 if (!printerFriendly && char_value.rendition.f.cursor) {
0337                     Character style = char_value;
0338                     m_parentDisplay->setVisualCursorPosition(x);
0339 
0340                     if (style.rendition.f.selected) {
0341                         if (invertedRendition) {
0342                             reverseRendition(style);
0343                         }
0344                     }
0345 
0346                     QColor foregroundColor = style.foregroundColor.color(colorTable);
0347                     QColor backgroundColor = style.backgroundColor.color(colorTable);
0348 
0349                     if (style.rendition.f.selected) {
0350                         if (!invertedRendition) {
0351                             backgroundColor = calculateBackgroundColor(style, colorTable).value_or(foregroundColor);
0352                             if (backgroundColor == foregroundColor) {
0353                                 foregroundColor = style.backgroundColor.color(colorTable);
0354                             }
0355                         }
0356                     }
0357                     drawCursor(paint,
0358                                QRect(textScale.inverted().map(QPoint(textX, textY)), QSize(textWidth, textHeight)),
0359                                foregroundColor,
0360                                backgroundColor,
0361                                foregroundColor);
0362                 }
0363                 if (wordMode) {
0364                     int charType = 0;
0365                     if (wordModeAscii && char_value.flags & EF_ASCII_WORD) {
0366                         charType = 1;
0367                     }
0368                     if (wordModeBrahmic && char_value.flags & EF_BRAHMIC_WORD) {
0369                         charType = 2;
0370                     }
0371                     if (lastCharType != charType || (!wordModeAttr && lastCharType != 0 && char_value.notSameAttributesText(image[pos + vis2log(x - 1)]))) {
0372                         if (lastCharType != 0) {
0373                             drawTextCharacters(paint,
0374                                                QRect(textScale.inverted().map(QPoint(word_x, textY)), QSize(textWidth, textHeight)),
0375                                                word_str,
0376                                                image[pos + word_log_x],
0377                                                colorTable,
0378                                                invertedRendition,
0379                                                lineProperty,
0380                                                printerFriendly,
0381                                                oldRendition,
0382                                                oldColor,
0383                                                normalWeight,
0384                                                boldWeight);
0385                             lastCharType = charType;
0386                         }
0387                         if (charType != 0) {
0388                             // start new
0389                             lastCharType = charType;
0390                             word_str = QString(unistr);
0391                             word_x = textX;
0392                             word_log_x = log_x;
0393                             continue;
0394                         }
0395                     } else if (lastCharType != 0) {
0396                         word_str += unistr;
0397                         continue;
0398                     }
0399                 }
0400                 const QRect textAreaOneChar(textScale.inverted().map(QPoint(textX, textY)), QSize(textWidth, textHeight));
0401                 drawTextCharacters(paint,
0402                                    textAreaOneChar,
0403                                    unistr,
0404                                    image[pos + log_x],
0405                                    colorTable,
0406                                    invertedRendition,
0407                                    lineProperty,
0408                                    printerFriendly,
0409                                    oldRendition,
0410                                    oldColor,
0411                                    normalWeight,
0412                                    boldWeight);
0413             }
0414         }
0415         if (wordMode && lastCharType != 0) {
0416             drawTextCharacters(paint,
0417                                QRect(textScale.inverted().map(QPoint(word_x, textY)), QSize(textWidth, textHeight)),
0418                                word_str,
0419                                image[pos + word_log_x],
0420                                colorTable,
0421                                invertedRendition,
0422                                lineProperty,
0423                                printerFriendly,
0424                                oldRendition,
0425                                oldColor,
0426                                normalWeight,
0427                                boldWeight);
0428         }
0429         if (!printerFriendly) {
0430             drawAboveText(paint,
0431                           textArea,
0432                           image + pos,
0433                           rect.x(),
0434                           rect.width(),
0435                           fontWidth,
0436                           colorTable,
0437                           invertedRendition,
0438                           vis2line,
0439                           line2log,
0440                           bidiEnabled,
0441                           lastNonSpace,
0442                           ulColorTable);
0443         }
0444 
0445         paint.setWorldTransform(textScale.inverted(), true);
0446         if (lineProperty.flags.f.prompt_start && semanticHintsActive) {
0447             QPen pen(m_parentDisplay->terminalColor()->foregroundColor());
0448             paint.setPen(pen);
0449             paint.drawLine(leftPadding, textY, m_parentDisplay->contentRect().right(), textY);
0450         }
0451         auto opacity = paint.opacity();
0452         if ((lineProperty.counter & 1) && alternatingBarsActive) {
0453             QPen pen(QColor("dark gray"));
0454             pen.setWidth(2);
0455             paint.setPen(pen);
0456             paint.setOpacity(0.5);
0457             paint.drawLine(leftPadding + 1, textY + 1, leftPadding + 1, textY + fontHeight - 1);
0458         }
0459         if (lineProperty.flags.f.error && errorBarsActive) {
0460             QPen pen(QColor("red"));
0461             pen.setWidth(4);
0462             paint.setPen(pen);
0463             paint.setOpacity(0.5);
0464             paint.drawLine(leftPadding + 2, textY + 2, leftPadding + 2, textY + fontHeight - 2);
0465         }
0466         paint.setOpacity(opacity);
0467         if (lineNumbersActive) {
0468             QRect rect(m_parentDisplay->contentRect().right() - 4 * fontWidth, textY, m_parentDisplay->contentRect().right(), textY + fontHeight);
0469             QPen pen(QColor(0xC00000));
0470             paint.setPen(pen);
0471             QFont currentFont = paint.font();
0472             currentFont.setWeight(normalWeight);
0473             currentFont.setItalic(false);
0474             paint.setFont(currentFont);
0475             paint.drawText(rect, Qt::AlignLeft, QString::number(y + m_parentDisplay->screenWindow()->currentLine()));
0476         }
0477 
0478         if (doubleHeightLinePair) {
0479             y++;
0480         }
0481     }
0482     if (!printerFriendly) {
0483         drawImagesAboveText(paint, textArea, fontWidth, fontHeight, placementIdx);
0484     }
0485 }
0486 
0487 void TerminalPainter::drawCurrentResultRect(QPainter &painter, const QRect &searchResultRect)
0488 {
0489     painter.fillRect(searchResultRect, QColor(0, 0, 255, 80));
0490 }
0491 
0492 void TerminalPainter::highlightScrolledLines(QPainter &painter, bool isTimerActive, QRect rect)
0493 {
0494     QColor color = QColor(m_parentDisplay->terminalColor()->colorTable()[Color4Index]);
0495     color.setAlpha(isTimerActive ? 255 : 150);
0496     painter.fillRect(rect, color);
0497 }
0498 
0499 QRegion TerminalPainter::highlightScrolledLinesRegion(TerminalScrollBar *scrollBar)
0500 {
0501     QRegion dirtyRegion;
0502     const int highlightLeftPosition = m_parentDisplay->scrollBar()->scrollBarPosition() == Enum::ScrollBarLeft ? m_parentDisplay->scrollBar()->width() : 0;
0503 
0504     int start = 0;
0505     int nb_lines = abs(m_parentDisplay->screenWindow()->scrollCount());
0506     if (nb_lines > 0 && m_parentDisplay->scrollBar()->maximum() > 0) {
0507         QRect new_highlight;
0508         bool addToCurrentHighlight = scrollBar->highlightScrolledLines().isTimerActive()
0509             && (m_parentDisplay->screenWindow()->scrollCount() * scrollBar->highlightScrolledLines().getPreviousScrollCount() > 0);
0510         if (addToCurrentHighlight) {
0511             const int oldScrollCount = scrollBar->highlightScrolledLines().getPreviousScrollCount();
0512             if (m_parentDisplay->screenWindow()->scrollCount() > 0) {
0513                 start = -1 * (oldScrollCount + m_parentDisplay->screenWindow()->scrollCount()) + m_parentDisplay->screenWindow()->windowLines();
0514             } else {
0515                 start = -1 * oldScrollCount;
0516             }
0517             scrollBar->highlightScrolledLines().setPreviousScrollCount(oldScrollCount + m_parentDisplay->screenWindow()->scrollCount());
0518         } else {
0519             start = m_parentDisplay->screenWindow()->scrollCount() > 0 ? m_parentDisplay->screenWindow()->windowLines() - nb_lines : 0;
0520             scrollBar->highlightScrolledLines().setPreviousScrollCount(m_parentDisplay->screenWindow()->scrollCount());
0521         }
0522 
0523         new_highlight.setRect(highlightLeftPosition,
0524                               m_parentDisplay->contentRect().top() + start * m_parentDisplay->terminalFont()->fontHeight(),
0525                               scrollBar->highlightScrolledLines().HIGHLIGHT_SCROLLED_LINES_WIDTH,
0526                               nb_lines * m_parentDisplay->terminalFont()->fontHeight());
0527         new_highlight.setTop(std::max(new_highlight.top(), m_parentDisplay->contentRect().top()));
0528         new_highlight.setBottom(std::min(new_highlight.bottom(), m_parentDisplay->contentRect().bottom()));
0529         new_highlight = highdpi_adjust_rect(new_highlight);
0530         if (!new_highlight.isValid()) {
0531             new_highlight = QRect(0, 0, 0, 0);
0532         }
0533 
0534         if (addToCurrentHighlight) {
0535             scrollBar->highlightScrolledLines().rect() |= new_highlight;
0536         } else {
0537             dirtyRegion |= scrollBar->highlightScrolledLines().rect();
0538             scrollBar->highlightScrolledLines().rect() = new_highlight;
0539         }
0540         dirtyRegion |= new_highlight;
0541 
0542         scrollBar->highlightScrolledLines().startTimer();
0543     }
0544 
0545     return dirtyRegion;
0546 }
0547 
0548 void TerminalPainter::drawBackground(QPainter &painter, const QRect &rect, const QColor &backgroundColor, bool useOpacitySetting)
0549 {
0550     if (!m_parentDisplay->wallpaper()->isNull()
0551         && m_parentDisplay->wallpaper()->draw(painter, rect, useOpacitySetting ? m_parentDisplay->terminalColor()->opacity() : 1.0, backgroundColor)) {
0552     } else if (qAlpha(m_parentDisplay->terminalColor()->blendColor()) < 0xff && useOpacitySetting) {
0553 #if defined(Q_OS_MACOS)
0554         // TODO: On MacOS, using CompositionMode doesn't work. Altering the
0555         //       transparency in the color scheme alters the brightness.
0556         painter.fillRect(rect, backgroundColor);
0557 #else
0558         QColor color(backgroundColor);
0559         color.setAlpha(qAlpha(m_parentDisplay->terminalColor()->blendColor()));
0560 
0561         const QPainter::CompositionMode originalMode = painter.compositionMode();
0562         painter.setCompositionMode(QPainter::CompositionMode_Source);
0563         painter.fillRect(rect, color);
0564         painter.setCompositionMode(originalMode);
0565 #endif
0566     } else {
0567         painter.fillRect(rect, backgroundColor);
0568     }
0569 }
0570 
0571 void TerminalPainter::updateCursorTextColor(const QColor &backgroundColor, QColor &characterColor)
0572 {
0573     if (m_parentDisplay->cursorShape() == Enum::BlockCursor) {
0574         if (m_parentDisplay->hasFocus()) {
0575             // invert the color used to draw the text to ensure that the character at
0576             // the cursor position is readable
0577             QColor cursorTextColor = m_parentDisplay->terminalColor()->cursorTextColor();
0578 
0579             characterColor = cursorTextColor.isValid() ? cursorTextColor : backgroundColor;
0580         }
0581     }
0582 }
0583 
0584 void TerminalPainter::drawCursor(QPainter &painter, const QRect &rect, const QColor &foregroundColor, const QColor &backgroundColor, QColor &characterColor)
0585 {
0586     if (m_parentDisplay->cursorBlinking()) {
0587         return;
0588     }
0589 
0590     // shift rectangle top down one pixel to leave some space
0591     // between top and bottom
0592     // noticeable when linespace>1
0593     QRectF cursorRect = rect.adjusted(0, 1, 0, 0);
0594 
0595     QColor color = m_parentDisplay->terminalColor()->cursorColor();
0596     QColor cursorColor = color.isValid() ? color : foregroundColor;
0597 
0598     QPen pen(cursorColor);
0599     // TODO: the relative pen width to draw the cursor is a bit hacky
0600     // and set to 1/12 of the font width. Visually it seems to work at
0601     // all scales but there must be better ways to do it
0602     const qreal width = qMax(m_parentDisplay->terminalFont()->fontWidth() / 12.0, 1.0);
0603     const qreal halfWidth = width / 2.0;
0604     pen.setWidthF(width);
0605     painter.setPen(pen);
0606 
0607     if (m_parentDisplay->cursorShape() == Enum::BlockCursor) {
0608         if (m_parentDisplay->hasFocus()) {
0609             painter.fillRect(cursorRect, cursorColor);
0610 
0611             updateCursorTextColor(backgroundColor, characterColor);
0612         } else {
0613             // draw the cursor outline, adjusting the area so that
0614             // it is drawn entirely inside cursorRect
0615             painter.drawRect(cursorRect.adjusted(halfWidth, halfWidth, -halfWidth, -halfWidth));
0616         }
0617     } else if (m_parentDisplay->cursorShape() == Enum::UnderlineCursor) {
0618         QLineF line(cursorRect.left() + halfWidth, cursorRect.bottom() - halfWidth, cursorRect.right() - halfWidth, cursorRect.bottom() - halfWidth);
0619         painter.drawLine(line);
0620 
0621     } else if (m_parentDisplay->cursorShape() == Enum::IBeamCursor) {
0622         QLineF line(cursorRect.left() + halfWidth, cursorRect.top() + halfWidth, cursorRect.left() + halfWidth, cursorRect.bottom() - halfWidth);
0623         painter.drawLine(line);
0624     }
0625 }
0626 
0627 void TerminalPainter::drawCharacters(QPainter &painter,
0628                                      const QRect &rect,
0629                                      const QString &text,
0630                                      const Character style,
0631                                      const QColor &characterColor,
0632                                      const LineProperty lineProperty)
0633 {
0634     if (m_parentDisplay->textBlinking() && (style.rendition.f.blink != 0)) {
0635         return;
0636     }
0637 
0638     if (style.rendition.f.conceal != 0) {
0639         return;
0640     }
0641 
0642     static const QFont::Weight FontWeights[] = {
0643         QFont::Thin,
0644         QFont::Light,
0645         QFont::Normal,
0646         QFont::Bold,
0647         QFont::Black,
0648     };
0649 
0650     // The weight used as bold depends on selected font's weight.
0651     // "Regular" will use "Bold", but e.g. "Thin" will use "Light".
0652     // Note that QFont::weight/setWeight() returns/takes an int in Qt5,
0653     // and a QFont::Weight in Qt6
0654     const auto normalWeight = m_parentDisplay->font().weight();
0655     auto it = std::upper_bound(std::begin(FontWeights), std::end(FontWeights), normalWeight);
0656     const QFont::Weight boldWeight = it != std::end(FontWeights) ? *it : QFont::Black;
0657 
0658     const bool useBold = ((style.rendition.f.bold != 0) && m_parentDisplay->terminalFont()->boldIntense());
0659     const bool useUnderline = (style.rendition.f.underline != 0) || m_parentDisplay->font().underline();
0660     const bool useItalic = (style.rendition.f.italic != 0) || m_parentDisplay->font().italic();
0661     const bool useStrikeOut = (style.rendition.f.strikeout != 0) || m_parentDisplay->font().strikeOut();
0662     const bool useOverline = (style.rendition.f.overline != 0) || m_parentDisplay->font().overline();
0663 
0664     QFont currentFont = painter.font();
0665 
0666     const bool isCurrentBold = currentFont.weight() >= boldWeight;
0667     // clang-format off
0668     if (isCurrentBold != useBold
0669         || currentFont.underline() != useUnderline
0670         || currentFont.italic() != useItalic
0671         || currentFont.strikeOut() != useStrikeOut
0672         || currentFont.overline() != useOverline
0673     )
0674     { // clang-format on
0675         currentFont.setWeight(useBold ? boldWeight : normalWeight);
0676         currentFont.setUnderline(useUnderline);
0677         currentFont.setItalic(useItalic);
0678         currentFont.setStrikeOut(useStrikeOut);
0679         currentFont.setOverline(useOverline);
0680         painter.setFont(currentFont);
0681     }
0682 
0683     // setup pen
0684     const QColor foregroundColor = style.foregroundColor.color(m_parentDisplay->terminalColor()->colorTable());
0685     const QColor color = characterColor.isValid() ? characterColor : foregroundColor;
0686     QPen pen = painter.pen();
0687     if (pen.color() != color) {
0688         pen.setColor(color);
0689         painter.setPen(color);
0690     }
0691     // draw text
0692     if (isLineCharString(text) && !m_parentDisplay->terminalFont()->useFontLineCharacters()) {
0693         int y = rect.y();
0694 
0695         if (lineProperty.flags.f.doubleheight_bottom) {
0696             y -= m_parentDisplay->terminalFont()->fontHeight() / 2;
0697         }
0698 
0699         drawLineCharString(m_parentDisplay, painter, rect.x(), y, text, style);
0700     } else {
0701         painter.setLayoutDirection(Qt::LeftToRight);
0702         int y = rect.y() + m_parentDisplay->terminalFont()->fontAscent();
0703 
0704         int shifted = 0;
0705         if (lineProperty.flags.f.doubleheight_bottom) {
0706             y -= m_parentDisplay->terminalFont()->fontHeight() / 2;
0707         } else {
0708             // We shift half way down here to center
0709             shifted = m_parentDisplay->terminalFont()->lineSpacing() / 2;
0710             y += shifted;
0711         }
0712 
0713         if (m_parentDisplay->bidiEnabled()) {
0714             painter.drawText(rect.x(), y, text);
0715         } else {
0716             painter.drawText(rect.x(), y, LTR_OVERRIDE_CHAR + text);
0717         }
0718 
0719         if (shifted > 0) {
0720             // To avoid rounding errors we shift the rest this way
0721             y += m_parentDisplay->terminalFont()->lineSpacing() - shifted;
0722         }
0723     }
0724 }
0725 
0726 void TerminalPainter::drawLineCharString(TerminalDisplay *display, QPainter &painter, int x, int y, const QString &str, const Character attributes)
0727 {
0728     // only turn on anti-aliasing during this short time for the "text"
0729     // for the normal text we have TextAntialiasing on demand on
0730     // otherwise we have rendering artifacts
0731     // set https://bugreports.qt.io/browse/QTBUG-66036
0732     painter.setRenderHint(QPainter::Antialiasing, display->terminalFont()->antialiasText());
0733 
0734     const bool useBoldPen = (attributes.rendition.f.bold) != 0 && display->terminalFont()->boldIntense();
0735     QRect cellRect = {x, y, display->terminalFont()->fontWidth(), display->terminalFont()->fontHeight()};
0736     QVector<uint> ucs4str = str.toUcs4();
0737     for (int i = 0; i < ucs4str.length(); i++) {
0738         LineBlockCharacters::draw(painter, cellRect.translated(i * display->terminalFont()->fontWidth(), 0), ucs4str[i], useBoldPen);
0739     }
0740     painter.setRenderHint(QPainter::Antialiasing, false);
0741 }
0742 
0743 void TerminalPainter::drawInputMethodPreeditString(QPainter &painter, const QRect &rect, TerminalDisplay::InputMethodData &inputMethodData, Character *image)
0744 {
0745     if (inputMethodData.preeditString.isEmpty() || !m_parentDisplay->isCursorOnDisplay()) {
0746         return;
0747     }
0748 
0749     const QPoint cursorPos = m_parentDisplay->cursorPosition();
0750 
0751     QColor characterColor;
0752     const QColor background = m_parentDisplay->terminalColor()->colorTable()[DEFAULT_BACK_COLOR];
0753     const QColor foreground = m_parentDisplay->terminalColor()->colorTable()[DEFAULT_FORE_COLOR];
0754     const Character style = image[m_parentDisplay->loc(cursorPos.x(), cursorPos.y())];
0755 
0756     drawBackground(painter, rect, background, true);
0757     drawCursor(painter, rect, foreground, background, characterColor);
0758     drawCharacters(painter, rect, inputMethodData.preeditString, style, characterColor, LineProperty());
0759 
0760     inputMethodData.previousPreeditRect = rect;
0761 }
0762 
0763 void TerminalPainter::drawBelowText(QPainter &painter,
0764                                     const QRect &rect,
0765                                     Character *style,
0766                                     int startX,
0767                                     int width,
0768                                     int fontWidth,
0769                                     const QColor *colorTable,
0770                                     const bool invertedRendition,
0771                                     int *vis2line,
0772                                     int *line2log,
0773                                     bool bidiEnabled,
0774                                     int lastNonSpace,
0775                                     QColor background)
0776 {
0777     // setup painter
0778 
0779     bool first = true;
0780     QRect constRect(0, 0, 0, 0);
0781     QColor backgroundColor;
0782     QColor foregroundColor;
0783     bool drawBG = false;
0784     int lastX = 0;
0785 
0786     for (int i = 0;; i++) {
0787         int x = vis2log(i + startX);
0788 
0789         if (first || i == width || style[x].rendition.all != style[lastX].rendition.all || style[x].foregroundColor != style[lastX].foregroundColor
0790             || style[x].backgroundColor != style[lastX].backgroundColor) {
0791             if (first) {
0792                 first = false;
0793             } else {
0794                 if (drawBG) {
0795                     painter.fillRect(constRect, backgroundColor);
0796                 }
0797             }
0798             if (i == width) {
0799                 return;
0800             }
0801             // Sets the text selection colors, either:
0802             // - using reverseRendition(), which inverts the foreground/background
0803             //   colors OR
0804             // - blending the foreground/background colors
0805             if (style[x].rendition.f.selected && invertedRendition) {
0806                 backgroundColor = style[x].foregroundColor.color(colorTable);
0807                 foregroundColor = style[x].backgroundColor.color(colorTable);
0808             } else {
0809                 foregroundColor = style[x].foregroundColor.color(colorTable);
0810                 backgroundColor = style[x].backgroundColor.color(colorTable);
0811             }
0812 
0813             if (style[x].rendition.f.selected) {
0814                 if (!invertedRendition) {
0815                     backgroundColor = calculateBackgroundColor(style[x], colorTable).value_or(foregroundColor);
0816                     if (backgroundColor == foregroundColor) {
0817                         foregroundColor = style[x].backgroundColor.color(colorTable);
0818                     }
0819                 }
0820             }
0821             if (backgroundColor == colorTable[DEFAULT_BACK_COLOR]) {
0822                 backgroundColor = background;
0823             }
0824             drawBG = backgroundColor != colorTable[DEFAULT_BACK_COLOR];
0825             if (style[x].rendition.f.transparent) {
0826                 drawBG = false;
0827             }
0828             constRect = QRect(rect.x() + fontWidth * i, rect.y(), fontWidth, rect.height());
0829         } else {
0830             constRect.setWidth(constRect.width() + fontWidth);
0831         }
0832         lastX = x;
0833     }
0834 }
0835 
0836 void TerminalPainter::drawAboveText(QPainter &painter,
0837                                     const QRect &rect,
0838                                     Character *style,
0839                                     int startX,
0840                                     int width,
0841                                     int fontWidth,
0842                                     const QColor *colorTable,
0843                                     const bool invertedRendition,
0844                                     int *vis2line,
0845                                     int *line2log,
0846                                     bool bidiEnabled,
0847                                     int lastNonSpace,
0848                                     CharacterColor const *ulColorTable)
0849 {
0850     bool first = true;
0851     QColor backgroundColor;
0852     QColor foregroundColor;
0853     int lastX = 0;
0854     int startUnderline = -1;
0855     int startOverline = -1;
0856     int startStrikeOut = -1;
0857 
0858     for (int i = 0;; i++) {
0859         int x = vis2log(i + startX);
0860 
0861         if (first || i == width || ((style[x].rendition.all ^ style[lastX].rendition.all) & RE_MASK_ABOVE)
0862             || ((style[x].flags ^ style[lastX].flags) & EF_UNDERLINE_COLOR) || style[x].foregroundColor != style[lastX].foregroundColor
0863             || style[x].backgroundColor != style[lastX].backgroundColor) {
0864             if (first) {
0865                 first = false;
0866             } else {
0867                 if ((i == width || style[x].rendition.f.strikeout == 0) && startStrikeOut >= 0) {
0868                     QPen pen(foregroundColor);
0869                     int y = rect.y() + m_parentDisplay->terminalFont()->fontAscent();
0870                     pen.setWidth(m_parentDisplay->terminalFont()->lineWidth());
0871                     painter.setPen(pen);
0872                     painter.drawLine(rect.x() + fontWidth * startStrikeOut,
0873                                      y - m_parentDisplay->terminalFont()->strikeOutPos(),
0874                                      rect.x() + fontWidth * i - 1,
0875                                      y - m_parentDisplay->terminalFont()->strikeOutPos());
0876                     startStrikeOut = -1;
0877                 }
0878                 if ((i == width || style[x].rendition.f.overline == 0) && startOverline >= 0) {
0879                     QPen pen(foregroundColor);
0880                     qreal y = rect.y() + m_parentDisplay->terminalFont()->fontAscent() - m_parentDisplay->terminalFont()->overlinePos()
0881                         + m_parentDisplay->terminalFont()->lineSpacing() / static_cast<qreal>(2);
0882                     pen.setWidth(m_parentDisplay->terminalFont()->lineWidth());
0883                     painter.setPen(pen);
0884                     painter.drawLine(QLineF(rect.x() + fontWidth * startOverline, y, rect.x() + fontWidth * i - 1, y));
0885                     startOverline = -1;
0886                 }
0887                 int underline = style[lastX].rendition.f.underline;
0888                 if ((i == width || style[x].rendition.f.underline != underline || ((style[x].flags ^ style[lastX].flags) & EF_UNDERLINE_COLOR))
0889                     && startUnderline >= 0) {
0890                     QPen pen(foregroundColor);
0891                     if (ulColorTable != nullptr && (style[lastX].flags & EF_UNDERLINE_COLOR) != 0) {
0892                         pen.setColor(ulColorTable[((style[lastX].flags & EF_UNDERLINE_COLOR)) / EF_UNDERLINE_COLOR_1 - 1].color(colorTable));
0893                     }
0894                     int lw = m_parentDisplay->terminalFont()->lineWidth();
0895                     qreal y = rect.y() + m_parentDisplay->terminalFont()->fontAscent() + m_parentDisplay->terminalFont()->underlinePos()
0896                         + m_parentDisplay->terminalFont()->lineSpacing() / static_cast<qreal>(2);
0897                     if (underline == RE_UNDERLINE_DOUBLE || underline == RE_UNDERLINE_CURL) {
0898                         y = rect.bottom() - 1;
0899                         lw = 1;
0900                     }
0901                     pen.setWidth(lw);
0902                     if (underline == RE_UNDERLINE_DOT) {
0903                         pen.setStyle(Qt::DotLine);
0904                     } else if (underline == RE_UNDERLINE_DASH) {
0905                         pen.setStyle(Qt::DashLine);
0906                     }
0907                     if (underline == RE_UNDERLINE_CURL) {
0908                         QVector<qreal> dashes(2, 2);
0909                         pen.setDashPattern(dashes);
0910                     }
0911                     painter.setPen(pen);
0912                     painter.drawLine(QLineF(rect.x() + fontWidth * startUnderline, y, rect.x() + fontWidth * i - 1, y));
0913                     if (underline == RE_UNDERLINE_DOUBLE) {
0914                         painter.drawLine(rect.x() + fontWidth * startUnderline, y - 2, rect.x() + fontWidth * i - 1, y - 2);
0915                     }
0916                     if (underline == RE_UNDERLINE_CURL) {
0917                         painter.drawLine(QLineF(rect.x() + fontWidth * startUnderline + 2, y - 1, rect.x() + fontWidth * i - 1, y - 1));
0918                     }
0919 
0920                     startUnderline = -1;
0921                 }
0922             }
0923             if (i == width) {
0924                 return;
0925             }
0926             // Sets the text selection colors, either:
0927             // - using reverseRendition(), which inverts the foreground/background
0928             //   colors OR
0929             // - blending the foreground/background colors
0930             if (style[x].rendition.f.selected && invertedRendition) {
0931                 backgroundColor = style[x].foregroundColor.color(colorTable);
0932                 foregroundColor = style[x].backgroundColor.color(colorTable);
0933             } else {
0934                 foregroundColor = style[x].foregroundColor.color(colorTable);
0935                 backgroundColor = style[x].backgroundColor.color(colorTable);
0936             }
0937 
0938             if (style[x].rendition.f.selected) {
0939                 if (!invertedRendition) {
0940                     backgroundColor = calculateBackgroundColor(style[x], colorTable).value_or(foregroundColor);
0941                     if (backgroundColor == foregroundColor) {
0942                         foregroundColor = style[x].backgroundColor.color(colorTable);
0943                     }
0944                 }
0945             }
0946             if (style[x].rendition.f.strikeout && startStrikeOut == -1) {
0947                 startStrikeOut = i;
0948             }
0949             if (style[x].rendition.f.overline && startOverline == -1) {
0950                 startOverline = i;
0951             }
0952             if (style[x].rendition.f.underline && startUnderline == -1) {
0953                 startUnderline = i;
0954             }
0955             lastX = x;
0956         }
0957     }
0958 }
0959 
0960 void TerminalPainter::drawImagesBelowText(QPainter &painter, const QRect &rect, int fontWidth, int fontHeight, int &placementIdx)
0961 {
0962     Screen *screen = m_parentDisplay->screenWindow()->screen();
0963 
0964     placementIdx = 0;
0965     qreal opacity = painter.opacity();
0966     int scrollDelta = m_parentDisplay->terminalFont()->fontHeight() * (m_parentDisplay->screenWindow()->currentLine() - screen->getHistLines());
0967     const bool origClipping = painter.hasClipping();
0968     const auto origClipRegion = painter.clipRegion();
0969     if (screen->hasGraphics()) {
0970         painter.setClipRect(rect);
0971         while (1) {
0972             TerminalGraphicsPlacement_t *p = screen->getGraphicsPlacement(placementIdx);
0973             if (!p || p->z >= 0) {
0974                 break;
0975             }
0976             int x = p->col * fontWidth + p->X + m_parentDisplay->contentRect().left();
0977             int y = p->row * fontHeight + p->Y + m_parentDisplay->contentRect().top();
0978             QRectF srcRect(0, 0, p->pixmap.width(), p->pixmap.height());
0979             QRectF dstRect(x, y - scrollDelta, p->pixmap.width(), p->pixmap.height());
0980             painter.setOpacity(p->opacity);
0981             painter.drawPixmap(dstRect, p->pixmap, srcRect);
0982             placementIdx++;
0983         }
0984         painter.setOpacity(opacity);
0985         painter.setClipRegion(origClipRegion);
0986         painter.setClipping(origClipping);
0987     }
0988 }
0989 
0990 void TerminalPainter::drawImagesAboveText(QPainter &painter, const QRect &rect, int fontWidth, int fontHeight, int &placementIdx)
0991 {
0992     // setup painter
0993     Screen *screen = m_parentDisplay->screenWindow()->screen();
0994 
0995     qreal opacity = painter.opacity();
0996     int scrollDelta = fontHeight * (m_parentDisplay->screenWindow()->currentLine() - screen->getHistLines());
0997     const bool origClipping = painter.hasClipping();
0998     const auto origClipRegion = painter.clipRegion();
0999 
1000     if (screen->hasGraphics()) {
1001         painter.setClipRect(rect);
1002         while (1) {
1003             TerminalGraphicsPlacement_t *p = screen->getGraphicsPlacement(placementIdx);
1004             if (!p) {
1005                 break;
1006             }
1007             QPixmap image = p->pixmap;
1008             int x = p->col * fontWidth + p->X + m_parentDisplay->contentRect().left();
1009             int y = p->row * fontHeight + p->Y + m_parentDisplay->contentRect().top();
1010             QRectF srcRect(0, 0, image.width(), image.height());
1011             QRectF dstRect(x, y - scrollDelta, image.width(), image.height());
1012             painter.setOpacity(p->opacity);
1013             painter.drawPixmap(dstRect, image, srcRect);
1014             placementIdx++;
1015         }
1016         painter.setOpacity(opacity);
1017         painter.setClipRegion(origClipRegion);
1018         painter.setClipping(origClipping);
1019     }
1020 }
1021 
1022 void TerminalPainter::drawTextCharacters(QPainter &painter,
1023                                          const QRect &rect,
1024                                          const QString &text,
1025                                          Character style,
1026                                          const QColor *colorTable,
1027                                          const bool invertedRendition,
1028                                          const LineProperty lineProperty,
1029                                          bool printerFriendly,
1030                                          RenditionFlags &oldRendition,
1031                                          QColor oldColor,
1032                                          QFont::Weight normalWeight,
1033                                          QFont::Weight boldWeight)
1034 {
1035     // setup painter
1036     if (style.rendition.f.conceal != 0) {
1037         return;
1038     }
1039     QColor characterColor;
1040     if (!printerFriendly) {
1041         // Sets the text selection colors, either:
1042         // - invertedRendition, which inverts the foreground/background colors OR
1043         // - blending the foreground/background colors
1044         if (m_parentDisplay->textBlinking() && (style.rendition.f.blink != 0)) {
1045             return;
1046         }
1047 
1048         if (style.rendition.f.selected) {
1049             if (invertedRendition) {
1050                 reverseRendition(style);
1051             }
1052         }
1053 
1054         QColor foregroundColor = style.foregroundColor.color(colorTable);
1055         QColor backgroundColor = style.backgroundColor.color(colorTable);
1056 
1057         if (style.rendition.f.selected) {
1058             if (!invertedRendition) {
1059                 backgroundColor = calculateBackgroundColor(style, colorTable).value_or(foregroundColor);
1060                 if (backgroundColor == foregroundColor) {
1061                     foregroundColor = style.backgroundColor.color(colorTable);
1062                 }
1063             }
1064         }
1065         characterColor = foregroundColor;
1066         if (style.rendition.f.cursor != 0 && !m_parentDisplay->cursorBlinking()) {
1067             updateCursorTextColor(backgroundColor, characterColor);
1068         }
1069         if (m_parentDisplay->filterChain()->showUrlHint()) {
1070             if ((style.flags & EF_REPL) == EF_REPL_PROMPT) {
1071                 int h, s, v;
1072                 characterColor.getHsv(&h, &s, &v);
1073                 s = s / 2;
1074                 v = v / 2;
1075                 characterColor.setHsv(h, s, v);
1076             }
1077             if ((style.flags & EF_REPL) == EF_REPL_INPUT) {
1078                 int h, s, v;
1079                 characterColor.getHsv(&h, &s, &v);
1080                 s = (511 + s) / 3;
1081                 v = (511 + v) / 3;
1082                 characterColor.setHsv(h, s, v);
1083             }
1084         }
1085     } else {
1086         characterColor = QColor(0, 0, 0);
1087     }
1088 
1089     // The weight used as bold depends on selected font's weight.
1090     // "Regular" will use "Bold", but e.g. "Thin" will use "Light".
1091     // Note that QFont::weight/setWeight() returns/takes an int in Qt5,
1092     // and a QFont::Weight in Qt6
1093     QFont savedFont;
1094     bool restoreFont = false;
1095     if ((style.flags & EF_EMOJI_REPRESENTATION) && m_parentDisplay->terminalFont()->hasExtraFont(0)) {
1096         savedFont = painter.font();
1097         restoreFont = true;
1098         painter.setFont(m_parentDisplay->terminalFont()->getExtraFont(0));
1099     } else {
1100         if (oldRendition != style.rendition.all) {
1101             const bool useBold = ((style.rendition.f.bold != 0) && m_parentDisplay->terminalFont()->boldIntense());
1102             const bool useItalic = (style.rendition.f.italic != 0) || m_parentDisplay->font().italic();
1103 
1104             QFont currentFont = painter.font();
1105 
1106             const bool isCurrentBold = currentFont.weight() >= boldWeight;
1107             if (isCurrentBold != useBold || currentFont.italic() != useItalic) {
1108                 currentFont.setWeight(useBold ? boldWeight : normalWeight);
1109                 currentFont.setItalic(useItalic);
1110                 painter.setFont(currentFont);
1111             }
1112         }
1113     }
1114 
1115     if (characterColor != oldColor) {
1116         QPen pen = painter.pen();
1117         if (pen.color() != characterColor) {
1118             painter.setPen(characterColor);
1119         }
1120     }
1121     // const bool origClipping = painter.hasClipping();
1122     // const auto origClipRegion = painter.clipRegion();
1123     // painter.setClipRect(rect);
1124     // draw text
1125     if (isLineCharString(text) && !m_parentDisplay->terminalFont()->useFontLineCharacters()) {
1126         int y = rect.y();
1127 
1128         if (lineProperty.flags.f.doubleheight_bottom) {
1129             y -= m_parentDisplay->terminalFont()->fontHeight() / 2;
1130         }
1131 
1132         drawLineCharString(m_parentDisplay, painter, rect.x(), y, text, style);
1133     } else {
1134         int y = rect.y() + m_parentDisplay->terminalFont()->fontAscent();
1135 
1136         if (lineProperty.flags.f.doubleheight_bottom) {
1137             y -= m_parentDisplay->terminalFont()->fontHeight() / 2;
1138         } else {
1139             // We shift half way down here to center
1140             y += m_parentDisplay->terminalFont()->lineSpacing() / 2;
1141         }
1142         painter.drawText(rect.x(), y, text);
1143         if (0 && text.toUcs4().length() >= 1) {
1144             fprintf(stderr, " %lli  ", (qint64)text.toUcs4().length());
1145             for (int i = 0; i < text.toUcs4().length(); i++) {
1146                 fprintf(stderr, " %04x  ", text.toUcs4()[i]);
1147             }
1148             fprintf(stderr, "\n");
1149         }
1150     }
1151     if (restoreFont) {
1152         painter.setFont(savedFont);
1153     }
1154 }
1155 }