TerminalPainter.cpp 28.1 KB
Newer Older
1
/*
2
3
4
5
6
    SPDX-FileCopyrightText: 2020-2020 Gustavo Carneiro <gcarneiroa@hotmail.com>
    SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
    SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>

    SPDX-License-Identifier: GPL-2.0-or-later
7
8
9
*/

// Own
10
#include "TerminalPainter.h"
11
12

// Konsole
13
#include "TerminalScrollBar.h"
14
#include "../characters/ExtendedCharTable.h"
15
#include "../characters/LineBlockCharacters.h"
16
#include "TerminalColor.h"
17
#include "TerminalFonts.h"
18
19
20
21
22
23
24
25
26
27
28
29
30

// Qt
#include <QRect>
#include <QColor>
#include <QRegion>
#include <QPainter>
#include <QString>
#include <QVector>
#include <QChar>
#include <QMatrix>
#include <QTransform>
#include <QPen>
#include <QDebug>
31
#include <QtMath>
32

Gustavo Carneiro's avatar
Gustavo Carneiro committed
33
34
// we use this to force QPainter to display text in LTR mode
// more information can be found in: https://unicode.org/reports/tr9/
35
36
37
38
39
const QChar LTR_OVERRIDE_CHAR(0x202D);

namespace Konsole
{

40
41
    TerminalPainter::TerminalPainter(QObject *parent)
        : QObject(parent)
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    {}

    static inline bool isLineCharString(const QString &string)
    {
        if (string.length() == 0) {
            return false;
        }
        return LineBlockCharacters::canDraw(string.at(0).unicode());
    }

    static int baseCodePoint(const Character &ch)
    {
        if (ch.rendition & RE_EXTENDED_CHAR) {
            ushort extendedCharLength = 0;
            const uint *chars = ExtendedCharTable::instance.lookupExtendedChar(ch.character, extendedCharLength);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
57
            // FIXME: Coverity-Dereferencing chars, which is known to be nullptr
58
59
60
61
62
63
            return chars[0];
        } else {
            return ch.character;
        }
    }

64
65
66
    void TerminalPainter::drawContents(Character *image, QPainter &paint, const QRect &rect,
                                       bool printerFriendly, int imageSize, bool bidiEnabled,
                                       QVector<LineProperty> lineProperties)
67
    {
68
69
        const auto display = qobject_cast<TerminalDisplay*>(sender());

70
        QVector<uint> univec;
71
72
        univec.reserve(display->usedColumns());

73
74
        for (int y = rect.y(); y <= rect.bottom(); y++) {
            int x = rect.x();
75
            bool doubleHeightLinePair = false;
76
77

            // Search for start of multi-column character
78
            if ((image[display->loc(rect.x(), y)].character == 0u) && (x != 0)) {
79
                x--;
80
            }
81

82
83
84
85
            for (; x <= rect.right(); x++) {
                int len = 1;

                // is this a single character or a sequence of characters ?
86
                if ((image[display->loc(x, y)].rendition & RE_EXTENDED_CHAR) != 0) {
87
88
                    // sequence of characters
                    ushort extendedCharLength = 0;
89
                    const uint *chars = ExtendedCharTable::instance.lookupExtendedChar(image[display->loc(x, y)].character, extendedCharLength);
90
91
92
                    if (chars != nullptr) {
                        Q_ASSERT(extendedCharLength > 1);
                        for (int index = 0; index < extendedCharLength; index++) {
93
                            univec << chars[index];
94
95
96
                        }
                    }
                } else {
97
                    const uint c = image[display->loc(x, y)].character;
98
                    if (c != 0u) {
99
                        univec << c;
100
101
102
                    }
                }

103
104
105
106
107
108
                const bool lineDraw = LineBlockCharacters::canDraw(image[display->loc(x, y)].character);
                const bool doubleWidth = (image[qMin(display->loc(x, y) + 1, imageSize - 1)].character == 0);
                const CharacterColor currentForeground = image[display->loc(x, y)].foregroundColor;
                const CharacterColor currentBackground = image[display->loc(x, y)].backgroundColor;
                const RenditionFlags currentRendition = image[display->loc(x, y)].rendition;
                const QChar::Script currentScript = QChar::script(baseCodePoint(image[display->loc(x, y)]));
109
110
111

                const auto isInsideDrawArea = [&](int column) { return column <= rect.right(); };
                const auto hasSameColors = [&](int column) {
112
113
                    return image[display->loc(column, y)].foregroundColor == currentForeground
                        && image[display->loc(column, y)].backgroundColor == currentBackground;
114
115
                };
                const auto hasSameRendition = [&](int column) {
116
                    return (image[display->loc(column, y)].rendition & ~RE_EXTENDED_CHAR)
117
118
119
                        == (currentRendition & ~RE_EXTENDED_CHAR);
                };
                const auto hasSameWidth = [&](int column) {
120
121
                    const int characterLoc = qMin(display->loc(column, y) + 1, imageSize - 1);
                    return (image[characterLoc].character == 0) == doubleWidth;
122
123
                };
                const auto hasSameLineDrawStatus = [&](int column) {
124
                    return LineBlockCharacters::canDraw(image[display->loc(column, y)].character)
125
126
127
                        == lineDraw;
                };
                const auto isSameScript = [&](int column) {
128
                    const QChar::Script script = QChar::script(baseCodePoint(image[display->loc(column, y)]));
129
130
131
132
133
134
135
                    if (currentScript == QChar::Script_Common || script == QChar::Script_Common
                        || currentScript == QChar::Script_Inherited || script == QChar::Script_Inherited) {
                        return true;
                    }
                    return currentScript == script;
                };
                const auto canBeGrouped = [&](int column) {
136
137
138
                    return image[display->loc(column, y)].character <= 0x7e
                            || (image[display->loc(column, y)].rendition & RE_EXTENDED_CHAR)
                            || (bidiEnabled && !doubleWidth);
139
140
141
142
                };

                if (canBeGrouped(x)) {
                    while (isInsideDrawArea(x + len) && hasSameColors(x + len)
143
144
145
                           && hasSameRendition(x + len) && hasSameWidth(x + len)
                           && hasSameLineDrawStatus(x + len) && isSameScript(x + len)
                           && canBeGrouped(x + len)) {
146
147
                        const uint c = image[display->loc(x + len, y)].character;
                        if ((image[display->loc(x + len, y)].rendition & RE_EXTENDED_CHAR) != 0) {
148
149
150
151
152
153
                            // sequence of characters
                            ushort extendedCharLength = 0;
                            const uint *chars = ExtendedCharTable::instance.lookupExtendedChar(c, extendedCharLength);
                            if (chars != nullptr) {
                                Q_ASSERT(extendedCharLength > 1);
                                for (int index = 0; index < extendedCharLength; index++) {
154
                                    univec << chars[index];
155
156
157
158
159
                                }
                            }
                        } else {
                            // single character
                            if (c != 0u) {
160
                                univec << c;
161
162
163
164
165
166
167
168
169
170
171
172
                            }
                        }

                        if (doubleWidth) {
                            len++;
                        }
                        len++;
                    }
                } else {
                    // Group spaces following any non-wide character with the character. This allows for
                    // rendering ambiguous characters with wide glyphs without clipping them.
                    while (!doubleWidth && isInsideDrawArea(x + len)
173
                            && image[display->loc(x + len, y)].character == ' ' && hasSameColors(x + len)
174
                            && hasSameRendition(x + len)) {
175
                        // univec intentionally not modified - trailing spaces are meaningless
176
177
178
                        len++;
                    }
                }
179
180

                // Adjust for trailing part of multi-column character
181
                if ((x + len < display->usedColumns()) && (image[display->loc(x + len, y)].character == 0u)) {
182
                    len++;
183
184
185
                }

                QMatrix textScale;
186
                bool doubleHeight = false;
187
                bool doubleWidthLine = false;
188

189
190
                if (y < lineProperties.size()) {
                    if ((lineProperties[y] & LINE_DOUBLEWIDTH) != 0) {
191
                        textScale.scale(2, 1);
192
                        doubleWidthLine = true;
193
194
                    }

195
196
                    doubleHeight = lineProperties[y] & (LINE_DOUBLEHEIGHT_TOP | LINE_DOUBLEHEIGHT_BOTTOM);
                    if (doubleHeight) {
197
198
199
200
                        textScale.scale(1, 2);
                    }
                }

201
202
203
204
205
206
                if (y < lineProperties.size() - 1) {
                    if (((lineProperties[y] & LINE_DOUBLEHEIGHT_TOP) != 0) && ((lineProperties[y+1] & LINE_DOUBLEHEIGHT_BOTTOM) != 0)) {
                        doubleHeightLinePair = true;
                    }
                }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
207
                // Apply text scaling matrix
208
209
210
                paint.setWorldTransform(QTransform(textScale), true);

                // Calculate the area in which the text will be drawn
211
                QRect textArea = QRect(display->contentRect().left() + display->contentsRect().left() + display->terminalFont()->fontWidth() * x * (doubleWidthLine ? 2 : 1),
212
213
                                       display->contentRect().top() + display->contentsRect().top() + display->terminalFont()->fontHeight() * y,
                                       display->terminalFont()->fontWidth() * len,
214
                                       doubleHeight && !doubleHeightLinePair ? display->terminalFont()->fontHeight() / 2 : display->terminalFont()->fontHeight());
215
216
217
218
219
220
221
222
223
224
225

                //move the calculated area to take account of scaling applied to the painter.
                //the position of the area from the origin (0,0) is scaled
                //by the opposite of whatever
                //transformation has been applied to the painter.  this ensures that
                //painting does actually start from textArea.topLeft()
                //(instead of textArea.topLeft() * painter-scale)
                textArea.moveTopLeft(textScale.inverted().map(textArea.topLeft()));

                QString unistr = QString::fromUcs4(univec.data(), univec.length());

226
227
                univec.clear();

228
                // paint text fragment
229
                if (printerFriendly) {
230
231
232
                    drawPrinterFriendlyTextFragment(paint,
                                                    textArea,
                                                    unistr,
233
234
                                                    &image[display->loc(x, y)],
                                                    y < lineProperties.size() ? lineProperties[y] : 0);
235
236
                } else {
                    drawTextFragment(paint,
237
238
239
                                     textArea,
                                     unistr,
                                     &image[display->loc(x, y)],
240
241
                                     display->terminalColor()->colorTable(),
                                     y < lineProperties.size() ? lineProperties[y] : 0);
242
243
244
245
246
247
                }

                paint.setWorldTransform(QTransform(textScale.inverted()), true);

                x += len - 1;
            }
248

249
250
            if (doubleHeightLinePair) {
                y++;
251
            }
252
253
        }
    }
254

255
    void TerminalPainter::drawCurrentResultRect(QPainter &painter, QRect searchResultRect)
256
    {
257
258
259
        const auto display = qobject_cast<TerminalDisplay*>(sender());

        if (display->screenWindow()->currentResultLine() == -1) {
260
261
            return;
        }
262

263
264
        searchResultRect.setRect(0, display->contentRect().top() + (display->screenWindow()->currentResultLine() - display->screenWindow()->currentLine()) * display->terminalFont()->fontHeight(),
            display->columns() * display->terminalFont()->fontWidth(), display->terminalFont()->fontHeight());
265
        painter.fillRect(searchResultRect, QColor(0, 0, 255, 80));
266
    }
267
268

    void TerminalPainter::highlightScrolledLines(QPainter &painter, bool isTimerActive, QRect rect)
269
    {
270
        const auto display = qobject_cast<TerminalDisplay *>(sender());
271

272
        QColor color = QColor(display->terminalColor()->colorTable()[Color4Index]);
273
        color.setAlpha(isTimerActive ? 255 : 150);
274
        painter.fillRect(rect, color);
275
    }
276
277

    QRegion TerminalPainter::highlightScrolledLinesRegion(bool nothingChanged, TerminalScrollBar* scrollBar)
278
    {
279
280
        const auto display = qobject_cast<TerminalDisplay*>(sender());

281
        QRegion dirtyRegion;
282
        const int highlightLeftPosition = display->scrollBar()->scrollBarPosition() == Enum::ScrollBarLeft ? display->scrollBar()->width() : 0;
283
284

        int start = 0;
285
286
        int nb_lines = abs(display->screenWindow()->scrollCount());
        if (nb_lines > 0 && display->scrollBar()->maximum() > 0) {
287
            QRect new_highlight;
288
289
            bool addToCurrentHighlight = scrollBar->highlightScrolledLines().isTimerActive() &&
                                         (display->screenWindow()->scrollCount() * scrollBar->highlightScrolledLines().getPreviousScrollCount() > 0);
290
            if (addToCurrentHighlight) {
291
                const int oldScrollCount = scrollBar->highlightScrolledLines().getPreviousScrollCount();
292
                if (display->screenWindow()->scrollCount() > 0) {
293
                    start = -1 * (oldScrollCount + display->screenWindow()->scrollCount()) + display->screenWindow()->windowLines();
294
                } else {
295
                    start = -1 * oldScrollCount;
296
                }
297
                scrollBar->highlightScrolledLines().setPreviousScrollCount(oldScrollCount + display->screenWindow()->scrollCount());
298
            } else {
299
                start = display->screenWindow()->scrollCount() > 0 ? display->screenWindow()->windowLines() - nb_lines : 0;
300
                scrollBar->highlightScrolledLines().setPreviousScrollCount(display->screenWindow()->scrollCount());
301
302
            }

303
304
            new_highlight.setRect(highlightLeftPosition, display->contentRect().top() + start * display->terminalFont()->fontHeight(),
                                  scrollBar->highlightScrolledLines().HIGHLIGHT_SCROLLED_LINES_WIDTH, nb_lines * display->terminalFont()->fontHeight());
305
306
            new_highlight.setTop(std::max(new_highlight.top(), display->contentRect().top()));
            new_highlight.setBottom(std::min(new_highlight.bottom(), display->contentRect().bottom()));
307
308
309
310
311
            if (!new_highlight.isValid()) {
                new_highlight = QRect(0, 0, 0, 0);
            }

            if (addToCurrentHighlight) {
312
                scrollBar->highlightScrolledLines().rect() |= new_highlight;
313
            } else {
314
315
                dirtyRegion |= scrollBar->highlightScrolledLines().rect();
                scrollBar->highlightScrolledLines().rect() = new_highlight;
316
317
            }

318
319
320
321
322
            scrollBar->highlightScrolledLines().startTimer();
        } else if (!nothingChanged || scrollBar->highlightScrolledLines().isNeedToClear()) {
            dirtyRegion = scrollBar->highlightScrolledLines().rect();
            scrollBar->highlightScrolledLines().rect().setRect(0, 0, 0, 0);
            scrollBar->highlightScrolledLines().setNeedToClear(false);
323
324
325
326
        }

        return dirtyRegion;
    }
327

328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
    QColor alphaBlend(const QColor &foreground, const QColor &background) {
        const auto foregroundAlpha = foreground.alphaF();
        const auto inverseForegroundAlpha = 1.0 - foregroundAlpha;
        const auto backgroundAlpha = background.alphaF();

        if (foregroundAlpha == 0.0) {
            return background;
        }

        if (backgroundAlpha == 1.0) {
            return QColor::fromRgb(
                (foregroundAlpha*foreground.red()) + (inverseForegroundAlpha*background.red()),
                (foregroundAlpha*foreground.green()) + (inverseForegroundAlpha*background.green()),
                (foregroundAlpha*foreground.blue()) + (inverseForegroundAlpha*background.blue()),
                0xff
            );
        } else {
            const auto inverseBackgroundAlpha = (backgroundAlpha * inverseForegroundAlpha);
            const auto finalAlpha = foregroundAlpha + inverseBackgroundAlpha;
            Q_ASSERT(finalAlpha != 0.0);

            return QColor::fromRgb(
                (foregroundAlpha*foreground.red()) + (inverseBackgroundAlpha*background.red()),
                (foregroundAlpha*foreground.green()) + (inverseBackgroundAlpha*background.green()),
                (foregroundAlpha*foreground.blue()) + (inverseBackgroundAlpha*background.blue()),
                finalAlpha
            );
        }
    }

    qreal wcag20AdjustColorPart(qreal v)
    {
        return v <= 0.03928 ? v/12.92 : qPow((v+0.055)/1.055, 2.4);
    }

    qreal wcag20RelativeLuminosity(const QColor &of)
    {
        auto r = of.redF(), g = of.greenF(), b = of.blueF();

        const auto a = wcag20AdjustColorPart;

        auto r2 = a(r), g2 = a(g), b2 = a(b);

        return r2 * 0.2126 + g2 * 0.7152 + b2 * 0.0722;
    }

    qreal wcag20Contrast(const QColor &c1, const QColor &c2)
    {
        const auto l1 = wcag20RelativeLuminosity(c1)+0.05, l2 = wcag20RelativeLuminosity(c2)+0.05;

        return (l1 > l2) ? l1/l2 : l2/l1;
    }

381
    std::optional<QColor> calculateBackgroundColor(const Character* style, const QColor *colorTable)
382
383
384
385
386
387
    {
        auto c1 = style->backgroundColor.color(colorTable);
        if (!(style->rendition & RE_SELECTED)) {
            return c1;
        }

388
389
        const auto initialBG = c1;

390
391
392
393
394
395
        c1.setAlphaF(0.8);

        const auto blend1 = alphaBlend(c1, colorTable[DEFAULT_FORE_COLOR]), blend2 = alphaBlend(c1, colorTable[DEFAULT_BACK_COLOR]);
        const auto fg = style->foregroundColor.color(colorTable);

        const auto contrast1 = wcag20Contrast(fg, blend1), contrast2 = wcag20Contrast(fg, blend2);
396
397
398
399
400
401
402
403
        const auto contrastBG1 = wcag20Contrast(blend1, initialBG), contrastBG2 = wcag20Contrast(blend2, initialBG);

        const auto fgFactor = 5.5; // if text contrast is too low against our calculated bg, then we flip to reverse
        const auto bgFactor = 1.6; // if bg contrast is too low against default bg, then we flip to reverse

        if ((contrast1 < fgFactor && contrast2 < fgFactor) || (contrastBG1 < bgFactor && contrastBG2 < bgFactor)) {
            return {};
        }
404
405
406
407

        return (contrast1 < contrast2) ? blend1 : blend2;
    }

408
    void TerminalPainter::drawTextFragment(QPainter &painter, const QRect &rect, const QString &text,
409
                                const Character *style, const QColor *colorTable, const LineProperty lineProperty)
410
411
    {
        // setup painter
412
413
414
415
416
        QColor foregroundColor = style->foregroundColor.color(colorTable);
        const QColor backgroundColor = calculateBackgroundColor(style, colorTable).value_or(foregroundColor);
        if (backgroundColor == foregroundColor) {
            foregroundColor = style->backgroundColor.color(colorTable);
        }
417

418
        if (backgroundColor != colorTable[DEFAULT_BACK_COLOR]) {
419
420
421
            drawBackground(painter, rect, backgroundColor, false);
        }

422
        QColor characterColor = foregroundColor;
423
424
425
426
427
        if ((style->rendition & RE_CURSOR) != 0) {
            drawCursor(painter, rect, foregroundColor, backgroundColor, characterColor);
        }

        // draw text
428
        drawCharacters(painter, rect, text, style, characterColor, lineProperty);
429
    }
430

431
    void TerminalPainter::drawPrinterFriendlyTextFragment(QPainter &painter, const QRect &rect, const QString &text,
432
                                                const Character *style, const LineProperty lineProperty)
433
434
435
436
437
    {
        Character print_style = *style;
        print_style.foregroundColor = CharacterColor(COLOR_SPACE_RGB, 0x00000000);
        print_style.backgroundColor = CharacterColor(COLOR_SPACE_RGB, 0xFFFFFFFF);

438
        drawCharacters(painter, rect, text, &print_style, QColor(), lineProperty);
439
    }
440

441
    void TerminalPainter::drawBackground(QPainter &painter, const QRect &rect, const QColor &backgroundColor,
442
                                bool useOpacitySetting)
443
    {
444
445
446
        const auto display = qobject_cast<TerminalDisplay*>(sender());

        if (useOpacitySetting && !display->wallpaper()->isNull() &&
447
448
               display->wallpaper()->draw(painter, rect, display->terminalColor()->opacity())) {
        } else if (qAlpha(display->terminalColor()->blendColor()) < 0xff && useOpacitySetting) {
449
450
451
452
453
454
#if defined(Q_OS_MACOS)
            // TODO: On MacOS, using CompositionMode doesn't work. Altering the
            //       transparency in the color scheme alters the brightness.
            painter.fillRect(rect, backgroundColor);
#else
            QColor color(backgroundColor);
455
            color.setAlpha(qAlpha(display->terminalColor()->blendColor()));
456
457
458
459
460
461
462
463
464
465

            const QPainter::CompositionMode originalMode = painter.compositionMode();
            painter.setCompositionMode(QPainter::CompositionMode_Source);
            painter.fillRect(rect, color);
            painter.setCompositionMode(originalMode);
#endif
        } else {
            painter.fillRect(rect, backgroundColor);
        }
    }
466

467
    void TerminalPainter::drawCursor(QPainter &painter, const QRect &rect, const QColor &foregroundColor,
468
                            const QColor &backgroundColor, QColor &characterColor)
469
    {
470
471
472
        const auto display = qobject_cast<TerminalDisplay*>(sender());

        if (display->cursorBlinking()) {
473
474
475
476
477
            return;
        }

        QRectF cursorRect = rect.adjusted(0, 1, 0, 0);

478
479
480
        QColor color = display->terminalColor()->cursorColor();
        QColor cursorColor = color.isValid() ? color : foregroundColor;

481
482
483
484
        QPen pen(cursorColor);
        // TODO: the relative pen width to draw the cursor is a bit hacky
        // and set to 1/12 of the font width. Visually it seems to work at
        // all scales but there must be better ways to do it
485
        const qreal width = qMax(display->terminalFont()->fontWidth() / 12.0, 1.0);
486
487
488
489
        const qreal halfWidth = width / 2.0;
        pen.setWidthF(width);
        painter.setPen(pen);

490
        if (display->cursorShape() == Enum::BlockCursor) {
491
492
            painter.drawRect(cursorRect.adjusted(halfWidth, halfWidth, -halfWidth, -halfWidth));

493
            if (display->hasFocus()) {
494
495
                painter.fillRect(cursorRect, cursorColor);

496
497
                QColor cursorTextColor = display->terminalColor()->cursorTextColor();
                characterColor = cursorTextColor.isValid() ? cursorTextColor : backgroundColor;
498
            }
499
        } else if (display->cursorShape() == Enum::UnderlineCursor) {
500
501
502
503
504
505
            QLineF line(cursorRect.left() + halfWidth,
                        cursorRect.bottom() - halfWidth,
                        cursorRect.right() - halfWidth,
                        cursorRect.bottom() - halfWidth);
            painter.drawLine(line);

506
        } else if (display->cursorShape() == Enum::IBeamCursor) {
507
508
509
510
511
512
513
            QLineF line(cursorRect.left() + halfWidth,
                        cursorRect.top() + halfWidth,
                        cursorRect.left() + halfWidth,
                        cursorRect.bottom() - halfWidth);
            painter.drawLine(line);
        }
    }
514

515
    void TerminalPainter::drawCharacters(QPainter &painter, const QRect &rect, const QString &text,
516
                                const Character *style, const QColor &characterColor, const LineProperty lineProperty)
517
    {
518
519
520
        const auto display = qobject_cast<TerminalDisplay*>(sender());

        if (display->textBlinking() && ((style->rendition & RE_BLINK) != 0)) {
521
522
523
524
525
526
527
528
            return;
        }

        if ((style->rendition & RE_CONCEAL) != 0) {
            return;
        }

        static constexpr int MaxFontWeight = 99;
529

530
        const int normalWeight = display->font().weight();
531
532
533
534
535

        const int boldWeight = qMin(normalWeight + 26, MaxFontWeight);

        const auto isBold = [boldWeight](const QFont &font) { return font.weight() >= boldWeight; };

536
        const bool useBold = (((style->rendition & RE_BOLD) != 0) && display->terminalFont()->boldIntense());
537
538
539
540
        const bool useUnderline = ((style->rendition & RE_UNDERLINE) != 0) || display->font().underline();
        const bool useItalic = ((style->rendition & RE_ITALIC) != 0) || display->font().italic();
        const bool useStrikeOut = ((style->rendition & RE_STRIKEOUT) != 0) || display->font().strikeOut();
        const bool useOverline = ((style->rendition & RE_OVERLINE) != 0) || display->font().overline();
541
542
543
544
545
546
547
548

        QFont currentFont = painter.font();

        if (isBold(currentFont) != useBold
                || currentFont.underline() != useUnderline
                || currentFont.italic() != useItalic
                || currentFont.strikeOut() != useStrikeOut
                || currentFont.overline() != useOverline) {
549

550
551
552
553
554
555
556
557
558
            currentFont.setWeight(useBold ? boldWeight : normalWeight);
            currentFont.setUnderline(useUnderline);
            currentFont.setItalic(useItalic);
            currentFont.setStrikeOut(useStrikeOut);
            currentFont.setOverline(useOverline);
            painter.setFont(currentFont);
        }

        // setup pen
559
        const QColor foregroundColor = style->foregroundColor.color(display->terminalColor()->colorTable());
560
561
562
563
564
565
566
567
568
569
570
        const QColor color = characterColor.isValid() ? characterColor : foregroundColor;
        QPen pen = painter.pen();
        if (pen.color() != color) {
            pen.setColor(color);
            painter.setPen(color);
        }

        const bool origClipping = painter.hasClipping();
        const auto origClipRegion = painter.clipRegion();
        painter.setClipRect(rect);
        // draw text
571
        if (isLineCharString(text) && !display->terminalFont()->useFontLineCharacters()) {
572
573
574
575
576
577
578
            int y = rect.y();

            if (lineProperty & LINE_DOUBLEHEIGHT_BOTTOM) {
                y -= display->terminalFont()->fontHeight() / 2;
            }

            drawLineCharString(display, painter, rect.x(), y, text, style);
579
580
        } else {
            painter.setLayoutDirection(Qt::LeftToRight);
581
582
583
584
585
586
587
            int y = rect.y() + display->terminalFont()->fontAscent();

            if (lineProperty & LINE_DOUBLEHEIGHT_BOTTOM) {
                y -= display->terminalFont()->fontHeight() / 2;
            } else {
                y += display->terminalFont()->lineSpacing();
            }
588

589
            if (display->bidiEnabled()) {
590
                painter.drawText(rect.x(), y, text);
591
            } else {
592
                painter.drawText(rect.x(), y, LTR_OVERRIDE_CHAR + text);
593
594
595
596
597
            }
        }
        painter.setClipRegion(origClipRegion);
        painter.setClipping(origClipping);
    }
598

599
    void TerminalPainter::drawLineCharString(TerminalDisplay *display, QPainter &painter, int x, int y, const QString &str, const Character *attributes)
600
    {
601
        painter.setRenderHint(QPainter::Antialiasing, display->terminalFont()->antialiasText());
602

603
        const bool useBoldPen = (attributes->rendition & RE_BOLD) != 0 && display->terminalFont()->boldIntense();
604

605
        QRect cellRect = {x, y, display->terminalFont()->fontWidth(), display->terminalFont()->fontHeight()};
606
        for (int i = 0; i < str.length(); i++) {
607
            LineBlockCharacters::draw(painter, cellRect.translated(i * display->terminalFont()->fontWidth(), 0), str[i], useBoldPen);
608
609
610
        }
        painter.setRenderHint(QPainter::Antialiasing, false);
    }
611

612
    void TerminalPainter::drawInputMethodPreeditString(QPainter &painter, const QRect &rect, TerminalDisplay::InputMethodData &inputMethodData, Character *image)
613
    {
614
615
616
        const auto display = qobject_cast<TerminalDisplay*>(sender());

        if (inputMethodData.preeditString.isEmpty() || !display->isCursorOnDisplay()) {
617
618
            return;
        }
619

620
        const QPoint cursorPos = display->cursorPosition();
621
622

        QColor characterColor;
623
624
        const QColor background = display->terminalColor()->colorTable()[DEFAULT_BACK_COLOR];
        const QColor foreground = display->terminalColor()->colorTable()[DEFAULT_FORE_COLOR];
625
        const Character *style = &image[display->loc(cursorPos.x(), cursorPos.y())];
626
627
628

        drawBackground(painter, rect, background, true);
        drawCursor(painter, rect, foreground, background, characterColor);
629
        drawCharacters(painter, rect, inputMethodData.preeditString, style, characterColor, 0);
630

631
        inputMethodData.previousPreeditRect = rect;
632
633
634
    }

}