1 /*
2     SPDX-FileCopyrightText: 2020-2020 Gustavo Carneiro <gcarneiroa@hotmail.com>
3     SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
4     SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
5 
6     SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 // Own
10 #include "TerminalPainter.h"
11 
12 // Konsole
13 #include "../characters/ExtendedCharTable.h"
14 #include "../characters/LineBlockCharacters.h"
15 #include "TerminalColor.h"
16 #include "TerminalFonts.h"
17 #include "TerminalScrollBar.h"
18 
19 // Qt
20 #include <QChar>
21 #include <QColor>
22 #include <QDebug>
23 #include <QMatrix>
24 #include <QPainter>
25 #include <QPen>
26 #include <QRect>
27 #include <QRegion>
28 #include <QString>
29 #include <QTransform>
30 #include <QtMath>
31 
32 #include <optional>
33 
34 // we use this to force QPainter to display text in LTR mode
35 // more information can be found in: https://unicode.org/reports/tr9/
36 const QChar LTR_OVERRIDE_CHAR(0x202D);
37 
38 namespace Konsole
39 {
TerminalPainter(QObject * parent)40 TerminalPainter::TerminalPainter(QObject *parent)
41     : QObject(parent)
42 {
43 }
44 
isLineCharString(const QString & string)45 static inline bool isLineCharString(const QString &string)
46 {
47     if (string.length() == 0) {
48         return false;
49     }
50     return LineBlockCharacters::canDraw(string.at(0).unicode());
51 }
52 
baseCodePoint(const Character & ch)53 static int baseCodePoint(const Character &ch)
54 {
55     if (ch.rendition & RE_EXTENDED_CHAR) {
56         ushort extendedCharLength = 0;
57         const uint *chars = ExtendedCharTable::instance.lookupExtendedChar(ch.character, extendedCharLength);
58         // FIXME: Coverity-Dereferencing chars, which is known to be nullptr
59         return chars[0];
60     } else {
61         return ch.character;
62     }
63 }
64 
drawContents(Character * image,QPainter & paint,const QRect & rect,bool printerFriendly,int imageSize,bool bidiEnabled,QVector<LineProperty> lineProperties)65 void TerminalPainter::drawContents(Character *image,
66                                    QPainter &paint,
67                                    const QRect &rect,
68                                    bool printerFriendly,
69                                    int imageSize,
70                                    bool bidiEnabled,
71                                    QVector<LineProperty> lineProperties)
72 {
73     const auto display = qobject_cast<TerminalDisplay *>(sender());
74 
75     QVector<uint> univec;
76     univec.reserve(display->usedColumns());
77 
78     for (int y = rect.y(); y <= rect.bottom(); y++) {
79         int x = rect.x();
80         bool doubleHeightLinePair = false;
81 
82         // Search for start of multi-column character
83         if ((image[display->loc(rect.x(), y)].character == 0u) && (x != 0)) {
84             x--;
85         }
86 
87         for (; x <= rect.right(); x++) {
88             int len = 1;
89 
90             // is this a single character or a sequence of characters ?
91             if ((image[display->loc(x, y)].rendition & RE_EXTENDED_CHAR) != 0) {
92                 // sequence of characters
93                 ushort extendedCharLength = 0;
94                 const uint *chars = ExtendedCharTable::instance.lookupExtendedChar(image[display->loc(x, y)].character, extendedCharLength);
95                 if (chars != nullptr) {
96                     Q_ASSERT(extendedCharLength > 1);
97                     for (int index = 0; index < extendedCharLength; index++) {
98                         univec << chars[index];
99                     }
100                 }
101             } else {
102                 const uint c = image[display->loc(x, y)].character;
103                 if (c != 0u) {
104                     univec << c;
105                 }
106             }
107 
108             const bool lineDraw = LineBlockCharacters::canDraw(image[display->loc(x, y)].character);
109             const bool doubleWidth = (image[qMin(display->loc(x, y) + 1, imageSize - 1)].character == 0);
110             const CharacterColor currentForeground = image[display->loc(x, y)].foregroundColor;
111             const CharacterColor currentBackground = image[display->loc(x, y)].backgroundColor;
112             const RenditionFlags currentRendition = image[display->loc(x, y)].rendition;
113             const QChar::Script currentScript = QChar::script(baseCodePoint(image[display->loc(x, y)]));
114 
115             const auto isInsideDrawArea = [&](int column) {
116                 return column <= rect.right();
117             };
118             const auto hasSameColors = [&](int column) {
119                 return image[display->loc(column, y)].foregroundColor == currentForeground
120                     && image[display->loc(column, y)].backgroundColor == currentBackground;
121             };
122             const auto hasSameRendition = [&](int column) {
123                 return (image[display->loc(column, y)].rendition & ~RE_EXTENDED_CHAR) == (currentRendition & ~RE_EXTENDED_CHAR);
124             };
125             const auto hasSameWidth = [&](int column) {
126                 const int characterLoc = qMin(display->loc(column, y) + 1, imageSize - 1);
127                 return (image[characterLoc].character == 0) == doubleWidth;
128             };
129             const auto hasSameLineDrawStatus = [&](int column) {
130                 return LineBlockCharacters::canDraw(image[display->loc(column, y)].character) == lineDraw;
131             };
132             const auto isSameScript = [&](int column) {
133                 const QChar::Script script = QChar::script(baseCodePoint(image[display->loc(column, y)]));
134                 if (currentScript == QChar::Script_Common || script == QChar::Script_Common || currentScript == QChar::Script_Inherited
135                     || script == QChar::Script_Inherited) {
136                     return true;
137                 }
138                 return currentScript == script;
139             };
140             const auto canBeGrouped = [&](int column) {
141                 return image[display->loc(column, y)].character <= 0x7e || (image[display->loc(column, y)].rendition & RE_EXTENDED_CHAR)
142                     || (bidiEnabled && !doubleWidth);
143             };
144 
145             if (canBeGrouped(x)) {
146                 while (isInsideDrawArea(x + len) && hasSameColors(x + len) && hasSameRendition(x + len) && hasSameWidth(x + len)
147                        && hasSameLineDrawStatus(x + len) && isSameScript(x + len) && canBeGrouped(x + len)) {
148                     const uint c = image[display->loc(x + len, y)].character;
149                     if ((image[display->loc(x + len, y)].rendition & RE_EXTENDED_CHAR) != 0) {
150                         // sequence of characters
151                         ushort extendedCharLength = 0;
152                         const uint *chars = ExtendedCharTable::instance.lookupExtendedChar(c, extendedCharLength);
153                         if (chars != nullptr) {
154                             Q_ASSERT(extendedCharLength > 1);
155                             for (int index = 0; index < extendedCharLength; index++) {
156                                 univec << chars[index];
157                             }
158                         }
159                     } else {
160                         // single character
161                         if (c != 0u) {
162                             univec << c;
163                         }
164                     }
165 
166                     if (doubleWidth) {
167                         len++;
168                     }
169                     len++;
170                 }
171             } else {
172                 // Group spaces following any non-wide character with the character. This allows for
173                 // rendering ambiguous characters with wide glyphs without clipping them.
174                 while (!doubleWidth && isInsideDrawArea(x + len) && image[display->loc(x + len, y)].character == ' ' && hasSameColors(x + len)
175                        && hasSameRendition(x + len)) {
176                     // univec intentionally not modified - trailing spaces are meaningless
177                     len++;
178                 }
179             }
180 
181             // Adjust for trailing part of multi-column character
182             if ((x + len < display->usedColumns()) && (image[display->loc(x + len, y)].character == 0u)) {
183                 len++;
184             }
185 
186             QMatrix textScale;
187             bool doubleHeight = false;
188             bool doubleWidthLine = false;
189 
190             if (y < lineProperties.size()) {
191                 if ((lineProperties[y] & LINE_DOUBLEWIDTH) != 0) {
192                     textScale.scale(2, 1);
193                     doubleWidthLine = true;
194                 }
195 
196                 doubleHeight = lineProperties[y] & (LINE_DOUBLEHEIGHT_TOP | LINE_DOUBLEHEIGHT_BOTTOM);
197                 if (doubleHeight) {
198                     textScale.scale(1, 2);
199                 }
200             }
201 
202             if (y < lineProperties.size() - 1) {
203                 if (((lineProperties[y] & LINE_DOUBLEHEIGHT_TOP) != 0) && ((lineProperties[y + 1] & LINE_DOUBLEHEIGHT_BOTTOM) != 0)) {
204                     doubleHeightLinePair = true;
205                 }
206             }
207 
208             // Apply text scaling matrix
209             paint.setWorldTransform(QTransform(textScale), true);
210 
211             // Calculate the area in which the text will be drawn
212             QRect textArea =
213                 QRect(display->contentRect().left() + display->contentsRect().left() + display->terminalFont()->fontWidth() * x * (doubleWidthLine ? 2 : 1),
214                       display->contentRect().top() + display->contentsRect().top() + display->terminalFont()->fontHeight() * y,
215                       display->terminalFont()->fontWidth() * len,
216                       doubleHeight && !doubleHeightLinePair ? display->terminalFont()->fontHeight() / 2 : display->terminalFont()->fontHeight());
217 
218             // move the calculated area to take account of scaling applied to the painter.
219             // the position of the area from the origin (0,0) is scaled
220             // by the opposite of whatever
221             // transformation has been applied to the painter.  this ensures that
222             // painting does actually start from textArea.topLeft()
223             //(instead of textArea.topLeft() * painter-scale)
224             textArea.moveTopLeft(textScale.inverted().map(textArea.topLeft()));
225 
226             QString unistr = QString::fromUcs4(univec.data(), univec.length());
227 
228             univec.clear();
229 
230             // paint text fragment
231             if (printerFriendly) {
232                 drawPrinterFriendlyTextFragment(paint, textArea, unistr, &image[display->loc(x, y)], y < lineProperties.size() ? lineProperties[y] : 0);
233             } else {
234                 drawTextFragment(paint,
235                                  textArea,
236                                  unistr,
237                                  &image[display->loc(x, y)],
238                                  display->terminalColor()->colorTable(),
239                                  y < lineProperties.size() ? lineProperties[y] : 0);
240             }
241 
242             paint.setWorldTransform(QTransform(textScale.inverted()), true);
243 
244             x += len - 1;
245         }
246 
247         if (doubleHeightLinePair) {
248             y++;
249         }
250     }
251 }
252 
drawCurrentResultRect(QPainter & painter,QRect searchResultRect)253 void TerminalPainter::drawCurrentResultRect(QPainter &painter, QRect searchResultRect)
254 {
255     const auto display = qobject_cast<TerminalDisplay *>(sender());
256 
257     if (display->screenWindow()->currentResultLine() == -1) {
258         return;
259     }
260 
261     searchResultRect.setRect(0,
262                              display->contentRect().top()
263                                  + (display->screenWindow()->currentResultLine() - display->screenWindow()->currentLine())
264                                      * display->terminalFont()->fontHeight(),
265                              display->columns() * display->terminalFont()->fontWidth(),
266                              display->terminalFont()->fontHeight());
267     painter.fillRect(searchResultRect, QColor(0, 0, 255, 80));
268 }
269 
highlightScrolledLines(QPainter & painter,bool isTimerActive,QRect rect)270 void TerminalPainter::highlightScrolledLines(QPainter &painter, bool isTimerActive, QRect rect)
271 {
272     const auto display = qobject_cast<TerminalDisplay *>(sender());
273 
274     QColor color = QColor(display->terminalColor()->colorTable()[Color4Index]);
275     color.setAlpha(isTimerActive ? 255 : 150);
276     painter.fillRect(rect, color);
277 }
278 
highlightScrolledLinesRegion(bool nothingChanged,TerminalScrollBar * scrollBar)279 QRegion TerminalPainter::highlightScrolledLinesRegion(bool nothingChanged, TerminalScrollBar *scrollBar)
280 {
281     const auto display = qobject_cast<TerminalDisplay *>(sender());
282 
283     QRegion dirtyRegion;
284     const int highlightLeftPosition = display->scrollBar()->scrollBarPosition() == Enum::ScrollBarLeft ? display->scrollBar()->width() : 0;
285 
286     int start = 0;
287     int nb_lines = abs(display->screenWindow()->scrollCount());
288     if (nb_lines > 0 && display->scrollBar()->maximum() > 0) {
289         QRect new_highlight;
290         bool addToCurrentHighlight = scrollBar->highlightScrolledLines().isTimerActive()
291             && (display->screenWindow()->scrollCount() * scrollBar->highlightScrolledLines().getPreviousScrollCount() > 0);
292         if (addToCurrentHighlight) {
293             const int oldScrollCount = scrollBar->highlightScrolledLines().getPreviousScrollCount();
294             if (display->screenWindow()->scrollCount() > 0) {
295                 start = -1 * (oldScrollCount + display->screenWindow()->scrollCount()) + display->screenWindow()->windowLines();
296             } else {
297                 start = -1 * oldScrollCount;
298             }
299             scrollBar->highlightScrolledLines().setPreviousScrollCount(oldScrollCount + display->screenWindow()->scrollCount());
300         } else {
301             start = display->screenWindow()->scrollCount() > 0 ? display->screenWindow()->windowLines() - nb_lines : 0;
302             scrollBar->highlightScrolledLines().setPreviousScrollCount(display->screenWindow()->scrollCount());
303         }
304 
305         new_highlight.setRect(highlightLeftPosition,
306                               display->contentRect().top() + start * display->terminalFont()->fontHeight(),
307                               scrollBar->highlightScrolledLines().HIGHLIGHT_SCROLLED_LINES_WIDTH,
308                               nb_lines * display->terminalFont()->fontHeight());
309         new_highlight.setTop(std::max(new_highlight.top(), display->contentRect().top()));
310         new_highlight.setBottom(std::min(new_highlight.bottom(), display->contentRect().bottom()));
311         if (!new_highlight.isValid()) {
312             new_highlight = QRect(0, 0, 0, 0);
313         }
314 
315         if (addToCurrentHighlight) {
316             scrollBar->highlightScrolledLines().rect() |= new_highlight;
317         } else {
318             dirtyRegion |= scrollBar->highlightScrolledLines().rect();
319             scrollBar->highlightScrolledLines().rect() = new_highlight;
320         }
321         dirtyRegion |= new_highlight;
322 
323         scrollBar->highlightScrolledLines().startTimer();
324     } else if (!nothingChanged || scrollBar->highlightScrolledLines().isNeedToClear()) {
325         dirtyRegion = scrollBar->highlightScrolledLines().rect();
326         scrollBar->highlightScrolledLines().rect().setRect(0, 0, 0, 0);
327         scrollBar->highlightScrolledLines().setNeedToClear(false);
328     }
329 
330     return dirtyRegion;
331 }
332 
alphaBlend(const QColor & foreground,const QColor & background)333 QColor alphaBlend(const QColor &foreground, const QColor &background)
334 {
335     const auto foregroundAlpha = foreground.alphaF();
336     const auto inverseForegroundAlpha = 1.0 - foregroundAlpha;
337     const auto backgroundAlpha = background.alphaF();
338 
339     if (foregroundAlpha == 0.0) {
340         return background;
341     }
342 
343     if (backgroundAlpha == 1.0) {
344         return QColor::fromRgb((foregroundAlpha * foreground.red()) + (inverseForegroundAlpha * background.red()),
345                                (foregroundAlpha * foreground.green()) + (inverseForegroundAlpha * background.green()),
346                                (foregroundAlpha * foreground.blue()) + (inverseForegroundAlpha * background.blue()),
347                                0xff);
348     } else {
349         const auto inverseBackgroundAlpha = (backgroundAlpha * inverseForegroundAlpha);
350         const auto finalAlpha = foregroundAlpha + inverseBackgroundAlpha;
351         Q_ASSERT(finalAlpha != 0.0);
352 
353         return QColor::fromRgb((foregroundAlpha * foreground.red()) + (inverseBackgroundAlpha * background.red()),
354                                (foregroundAlpha * foreground.green()) + (inverseBackgroundAlpha * background.green()),
355                                (foregroundAlpha * foreground.blue()) + (inverseBackgroundAlpha * background.blue()),
356                                finalAlpha);
357     }
358 }
359 
wcag20AdjustColorPart(qreal v)360 qreal wcag20AdjustColorPart(qreal v)
361 {
362     return v <= 0.03928 ? v / 12.92 : qPow((v + 0.055) / 1.055, 2.4);
363 }
364 
wcag20RelativeLuminosity(const QColor & of)365 qreal wcag20RelativeLuminosity(const QColor &of)
366 {
367     auto r = of.redF(), g = of.greenF(), b = of.blueF();
368 
369     const auto a = wcag20AdjustColorPart;
370 
371     auto r2 = a(r), g2 = a(g), b2 = a(b);
372 
373     return r2 * 0.2126 + g2 * 0.7152 + b2 * 0.0722;
374 }
375 
wcag20Contrast(const QColor & c1,const QColor & c2)376 qreal wcag20Contrast(const QColor &c1, const QColor &c2)
377 {
378     const auto l1 = wcag20RelativeLuminosity(c1) + 0.05, l2 = wcag20RelativeLuminosity(c2) + 0.05;
379 
380     return (l1 > l2) ? l1 / l2 : l2 / l1;
381 }
382 
calculateBackgroundColor(const Character * style,const QColor * colorTable)383 std::optional<QColor> calculateBackgroundColor(const Character *style, const QColor *colorTable)
384 {
385     auto c1 = style->backgroundColor.color(colorTable);
386     if (!(style->rendition & RE_BLEND_SELECTION_COLORS)) {
387         return c1;
388     }
389 
390     const auto initialBG = c1;
391 
392     c1.setAlphaF(0.8);
393 
394     const auto blend1 = alphaBlend(c1, colorTable[DEFAULT_FORE_COLOR]), blend2 = alphaBlend(c1, colorTable[DEFAULT_BACK_COLOR]);
395     const auto fg = style->foregroundColor.color(colorTable);
396 
397     const auto contrast1 = wcag20Contrast(fg, blend1), contrast2 = wcag20Contrast(fg, blend2);
398     const auto contrastBG1 = wcag20Contrast(blend1, initialBG), contrastBG2 = wcag20Contrast(blend2, initialBG);
399 
400     const auto fgFactor = 5.5; // if text contrast is too low against our calculated bg, then we flip to reverse
401     const auto bgFactor = 1.6; // if bg contrast is too low against default bg, then we flip to reverse
402 
403     if ((contrast1 < fgFactor && contrast2 < fgFactor) || (contrastBG1 < bgFactor && contrastBG2 < bgFactor)) {
404         return {};
405     }
406 
407     return (contrast1 < contrast2) ? blend1 : blend2;
408 }
409 
drawTextFragment(QPainter & painter,const QRect & rect,const QString & text,const Character * style,const QColor * colorTable,const LineProperty lineProperty)410 void TerminalPainter::drawTextFragment(QPainter &painter,
411                                        const QRect &rect,
412                                        const QString &text,
413                                        const Character *style,
414                                        const QColor *colorTable,
415                                        const LineProperty lineProperty)
416 {
417     // setup painter
418     QColor foregroundColor = style->foregroundColor.color(colorTable);
419     const QColor backgroundColor = calculateBackgroundColor(style, colorTable).value_or(foregroundColor);
420     if (backgroundColor == foregroundColor) {
421         foregroundColor = style->backgroundColor.color(colorTable);
422     }
423 
424     if (backgroundColor != colorTable[DEFAULT_BACK_COLOR]) {
425         drawBackground(painter, rect, backgroundColor, false);
426     }
427 
428     QColor characterColor = foregroundColor;
429     if ((style->rendition & RE_CURSOR) != 0) {
430         drawCursor(painter, rect, foregroundColor, backgroundColor, characterColor);
431     }
432 
433     // draw text
434     drawCharacters(painter, rect, text, style, characterColor, lineProperty);
435 }
436 
drawPrinterFriendlyTextFragment(QPainter & painter,const QRect & rect,const QString & text,const Character * style,const LineProperty lineProperty)437 void TerminalPainter::drawPrinterFriendlyTextFragment(QPainter &painter,
438                                                       const QRect &rect,
439                                                       const QString &text,
440                                                       const Character *style,
441                                                       const LineProperty lineProperty)
442 {
443     Character print_style = *style;
444     print_style.foregroundColor = CharacterColor(COLOR_SPACE_RGB, 0x00000000);
445     print_style.backgroundColor = CharacterColor(COLOR_SPACE_RGB, 0xFFFFFFFF);
446 
447     drawCharacters(painter, rect, text, &print_style, QColor(), lineProperty);
448 }
449 
drawBackground(QPainter & painter,const QRect & rect,const QColor & backgroundColor,bool useOpacitySetting)450 void TerminalPainter::drawBackground(QPainter &painter, const QRect &rect, const QColor &backgroundColor, bool useOpacitySetting)
451 {
452     const auto display = qobject_cast<TerminalDisplay *>(sender());
453 
454     if (useOpacitySetting && !display->wallpaper()->isNull() && display->wallpaper()->draw(painter, rect, display->terminalColor()->opacity())) {
455     } else if (qAlpha(display->terminalColor()->blendColor()) < 0xff && useOpacitySetting) {
456 #if defined(Q_OS_MACOS)
457         // TODO: On MacOS, using CompositionMode doesn't work. Altering the
458         //       transparency in the color scheme alters the brightness.
459         painter.fillRect(rect, backgroundColor);
460 #else
461         QColor color(backgroundColor);
462         color.setAlpha(qAlpha(display->terminalColor()->blendColor()));
463 
464         const QPainter::CompositionMode originalMode = painter.compositionMode();
465         painter.setCompositionMode(QPainter::CompositionMode_Source);
466         painter.fillRect(rect, color);
467         painter.setCompositionMode(originalMode);
468 #endif
469     } else {
470         painter.fillRect(rect, backgroundColor);
471     }
472 }
473 
drawCursor(QPainter & painter,const QRect & rect,const QColor & foregroundColor,const QColor & backgroundColor,QColor & characterColor)474 void TerminalPainter::drawCursor(QPainter &painter, const QRect &rect, const QColor &foregroundColor, const QColor &backgroundColor, QColor &characterColor)
475 {
476     const auto display = qobject_cast<TerminalDisplay *>(sender());
477 
478     if (display->cursorBlinking()) {
479         return;
480     }
481 
482     QRectF cursorRect = rect.adjusted(0, 1, 0, 0);
483 
484     QColor color = display->terminalColor()->cursorColor();
485     QColor cursorColor = color.isValid() ? color : foregroundColor;
486 
487     QPen pen(cursorColor);
488     // TODO: the relative pen width to draw the cursor is a bit hacky
489     // and set to 1/12 of the font width. Visually it seems to work at
490     // all scales but there must be better ways to do it
491     const qreal width = qMax(display->terminalFont()->fontWidth() / 12.0, 1.0);
492     const qreal halfWidth = width / 2.0;
493     pen.setWidthF(width);
494     painter.setPen(pen);
495 
496     if (display->cursorShape() == Enum::BlockCursor) {
497         painter.drawRect(cursorRect.adjusted(halfWidth, halfWidth, -halfWidth, -halfWidth));
498 
499         if (display->hasFocus()) {
500             painter.fillRect(cursorRect, cursorColor);
501 
502             QColor cursorTextColor = display->terminalColor()->cursorTextColor();
503             characterColor = cursorTextColor.isValid() ? cursorTextColor : backgroundColor;
504         }
505     } else if (display->cursorShape() == Enum::UnderlineCursor) {
506         QLineF line(cursorRect.left() + halfWidth, cursorRect.bottom() - halfWidth, cursorRect.right() - halfWidth, cursorRect.bottom() - halfWidth);
507         painter.drawLine(line);
508 
509     } else if (display->cursorShape() == Enum::IBeamCursor) {
510         QLineF line(cursorRect.left() + halfWidth, cursorRect.top() + halfWidth, cursorRect.left() + halfWidth, cursorRect.bottom() - halfWidth);
511         painter.drawLine(line);
512     }
513 }
514 
drawCharacters(QPainter & painter,const QRect & rect,const QString & text,const Character * style,const QColor & characterColor,const LineProperty lineProperty)515 void TerminalPainter::drawCharacters(QPainter &painter,
516                                      const QRect &rect,
517                                      const QString &text,
518                                      const Character *style,
519                                      const QColor &characterColor,
520                                      const LineProperty lineProperty)
521 {
522     const auto display = qobject_cast<TerminalDisplay *>(sender());
523 
524     if (display->textBlinking() && ((style->rendition & RE_BLINK) != 0)) {
525         return;
526     }
527 
528     if ((style->rendition & RE_CONCEAL) != 0) {
529         return;
530     }
531 
532     static constexpr int MaxFontWeight = 99;
533 
534     const int normalWeight = display->font().weight();
535 
536     const int boldWeight = qMin(normalWeight + 26, MaxFontWeight);
537 
538     const auto isBold = [boldWeight](const QFont &font) {
539         return font.weight() >= boldWeight;
540     };
541 
542     const bool useBold = (((style->rendition & RE_BOLD) != 0) && display->terminalFont()->boldIntense());
543     const bool useUnderline = ((style->rendition & RE_UNDERLINE) != 0) || display->font().underline();
544     const bool useItalic = ((style->rendition & RE_ITALIC) != 0) || display->font().italic();
545     const bool useStrikeOut = ((style->rendition & RE_STRIKEOUT) != 0) || display->font().strikeOut();
546     const bool useOverline = ((style->rendition & RE_OVERLINE) != 0) || display->font().overline();
547 
548     QFont currentFont = painter.font();
549 
550     if (isBold(currentFont) != useBold || currentFont.underline() != useUnderline || currentFont.italic() != useItalic
551         || currentFont.strikeOut() != useStrikeOut || currentFont.overline() != useOverline) {
552         currentFont.setWeight(useBold ? boldWeight : normalWeight);
553         currentFont.setUnderline(useUnderline);
554         currentFont.setItalic(useItalic);
555         currentFont.setStrikeOut(useStrikeOut);
556         currentFont.setOverline(useOverline);
557         painter.setFont(currentFont);
558     }
559 
560     // setup pen
561     const QColor foregroundColor = style->foregroundColor.color(display->terminalColor()->colorTable());
562     const QColor color = characterColor.isValid() ? characterColor : foregroundColor;
563     QPen pen = painter.pen();
564     if (pen.color() != color) {
565         pen.setColor(color);
566         painter.setPen(color);
567     }
568 
569     const bool origClipping = painter.hasClipping();
570     const auto origClipRegion = painter.clipRegion();
571     painter.setClipRect(rect);
572     // draw text
573     if (isLineCharString(text) && !display->terminalFont()->useFontLineCharacters()) {
574         int y = rect.y();
575 
576         if (lineProperty & LINE_DOUBLEHEIGHT_BOTTOM) {
577             y -= display->terminalFont()->fontHeight() / 2;
578         }
579 
580         drawLineCharString(display, painter, rect.x(), y, text, style);
581     } else {
582         painter.setLayoutDirection(Qt::LeftToRight);
583         int y = rect.y() + display->terminalFont()->fontAscent();
584 
585         if (lineProperty & LINE_DOUBLEHEIGHT_BOTTOM) {
586             y -= display->terminalFont()->fontHeight() / 2;
587         } else {
588             y += display->terminalFont()->lineSpacing();
589         }
590 
591         if (display->bidiEnabled()) {
592             painter.drawText(rect.x(), y, text);
593         } else {
594             painter.drawText(rect.x(), y, LTR_OVERRIDE_CHAR + text);
595         }
596     }
597     painter.setClipRegion(origClipRegion);
598     painter.setClipping(origClipping);
599 }
600 
drawLineCharString(TerminalDisplay * display,QPainter & painter,int x,int y,const QString & str,const Character * attributes)601 void TerminalPainter::drawLineCharString(TerminalDisplay *display, QPainter &painter, int x, int y, const QString &str, const Character *attributes)
602 {
603     painter.setRenderHint(QPainter::Antialiasing, display->terminalFont()->antialiasText());
604 
605     const bool useBoldPen = (attributes->rendition & RE_BOLD) != 0 && display->terminalFont()->boldIntense();
606 
607     QRect cellRect = {x, y, display->terminalFont()->fontWidth(), display->terminalFont()->fontHeight()};
608     for (int i = 0; i < str.length(); i++) {
609         LineBlockCharacters::draw(painter, cellRect.translated(i * display->terminalFont()->fontWidth(), 0), str[i], useBoldPen);
610     }
611     painter.setRenderHint(QPainter::Antialiasing, false);
612 }
613 
drawInputMethodPreeditString(QPainter & painter,const QRect & rect,TerminalDisplay::InputMethodData & inputMethodData,Character * image)614 void TerminalPainter::drawInputMethodPreeditString(QPainter &painter, const QRect &rect, TerminalDisplay::InputMethodData &inputMethodData, Character *image)
615 {
616     const auto display = qobject_cast<TerminalDisplay *>(sender());
617 
618     if (inputMethodData.preeditString.isEmpty() || !display->isCursorOnDisplay()) {
619         return;
620     }
621 
622     const QPoint cursorPos = display->cursorPosition();
623 
624     QColor characterColor;
625     const QColor background = display->terminalColor()->colorTable()[DEFAULT_BACK_COLOR];
626     const QColor foreground = display->terminalColor()->colorTable()[DEFAULT_FORE_COLOR];
627     const Character *style = &image[display->loc(cursorPos.x(), cursorPos.y())];
628 
629     drawBackground(painter, rect, background, true);
630     drawCursor(painter, rect, foreground, background, characterColor);
631     drawCharacters(painter, rect, inputMethodData.preeditString, style, characterColor, 0);
632 
633     inputMethodData.previousPreeditRect = rect;
634 }
635 
636 }
637