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 }