TerminalPainter.cpp 27 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

33
34
#include <optional>

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

namespace Konsole
{

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

    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
59
            // FIXME: Coverity-Dereferencing chars, which is known to be nullptr
60
61
62
63
64
65
            return chars[0];
        } else {
            return ch.character;
        }
    }

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

72
        QVector<uint> univec;
73
74
        univec.reserve(display->usedColumns());

75
76
        for (int y = rect.y(); y <= rect.bottom(); y++) {
            int x = rect.x();
77
78

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

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

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

104
105
106
107
108
109
                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)]));
110
111
112

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

                if (canBeGrouped(x)) {
                    while (isInsideDrawArea(x + len) && hasSameColors(x + len)
144
145
146
                           && hasSameRendition(x + len) && hasSameWidth(x + len)
                           && hasSameLineDrawStatus(x + len) && isSameScript(x + len)
                           && canBeGrouped(x + len)) {
147
148
                        const uint c = image[display->loc(x + len, y)].character;
                        if ((image[display->loc(x + len, y)].rendition & RE_EXTENDED_CHAR) != 0) {
149
150
151
152
153
154
                            // 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++) {
155
                                    univec << chars[index];
156
157
158
159
160
                                }
                            }
                        } else {
                            // single character
                            if (c != 0u) {
161
                                univec << c;
162
163
164
165
166
167
168
169
170
171
172
173
                            }
                        }

                        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)
174
                            && image[display->loc(x + len, y)].character == ' ' && hasSameColors(x + len)
175
                            && hasSameRendition(x + len)) {
176
                        // univec intentionally not modified - trailing spaces are meaningless
177
178
179
                        len++;
                    }
                }
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
187
                }

                QMatrix textScale;

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

193
                    if ((lineProperties[y] & LINE_DOUBLEHEIGHT) != 0) {
194
195
196
197
                        textScale.scale(1, 2);
                    }
                }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
198
                // Apply text scaling matrix
199
200
201
                paint.setWorldTransform(QTransform(textScale), true);

                // Calculate the area in which the text will be drawn
202
                QRect textArea = QRect(display->contentRect().left() + display->contentsRect().left() + display->terminalFont()->fontWidth() * x,
203
204
205
                                       display->contentRect().top() + display->contentsRect().top() + display->terminalFont()->fontHeight() * y,
                                       display->terminalFont()->fontWidth() * len,
                                       display->terminalFont()->fontHeight());
206
207
208
209
210
211
212
213
214
215
216

                //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());

217
218
                univec.clear();

219
                // paint text fragment
220
                if (printerFriendly) {
221
222
223
                    drawPrinterFriendlyTextFragment(paint,
                                                    textArea,
                                                    unistr,
224
                                                    &image[display->loc(x, y)]);
225
226
                } else {
                    drawTextFragment(paint,
227
228
229
230
                                     textArea,
                                     unistr,
                                     &image[display->loc(x, y)],
                                     display->terminalColor()->colorTable());
231
232
233
234
                }

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

235
236
                if (y < lineProperties.size() - 1) {
                    if ((lineProperties[y] & LINE_DOUBLEHEIGHT) != 0) {
237
238
239
240
241
242
243
244
                        y++;
                    }
                }

                x += len - 1;
            }
        }
    }
245

246
    void TerminalPainter::drawCurrentResultRect(QPainter &painter, QRect searchResultRect)
247
    {
248
249
250
        const auto display = qobject_cast<TerminalDisplay*>(sender());

        if (display->screenWindow()->currentResultLine() == -1) {
251
252
            return;
        }
253

254
255
        searchResultRect.setRect(0, display->contentRect().top() + (display->screenWindow()->currentResultLine() - display->screenWindow()->currentLine()) * display->terminalFont()->fontHeight(),
            display->columns() * display->terminalFont()->fontWidth(), display->terminalFont()->fontHeight());
256
        painter.fillRect(searchResultRect, QColor(0, 0, 255, 80));
257
    }
258
259

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

263
        QColor color = QColor(display->terminalColor()->colorTable()[Color4Index]);
264
        color.setAlpha(isTimerActive ? 255 : 150);
265
        painter.fillRect(rect, color);
266
    }
267
268

    QRegion TerminalPainter::highlightScrolledLinesRegion(bool nothingChanged, TerminalScrollBar* scrollBar)
269
    {
270
271
        const auto display = qobject_cast<TerminalDisplay*>(sender());

272
        QRegion dirtyRegion;
273
        const int highlightLeftPosition = display->scrollBar()->scrollBarPosition() == Enum::ScrollBarLeft ? display->scrollBar()->width() : 0;
274
275

        int start = 0;
276
277
        int nb_lines = abs(display->screenWindow()->scrollCount());
        if (nb_lines > 0 && display->scrollBar()->maximum() > 0) {
278
            QRect new_highlight;
279
280
            bool addToCurrentHighlight = scrollBar->highlightScrolledLines().isTimerActive() &&
                                         (display->screenWindow()->scrollCount() * scrollBar->highlightScrolledLines().getPreviousScrollCount() > 0);
281
            if (addToCurrentHighlight) {
282
                const int oldScrollCount = scrollBar->highlightScrolledLines().getPreviousScrollCount();
283
                if (display->screenWindow()->scrollCount() > 0) {
284
                    start = -1 * (oldScrollCount + display->screenWindow()->scrollCount()) + display->screenWindow()->windowLines();
285
                } else {
286
                    start = -1 * oldScrollCount;
287
                }
288
                scrollBar->highlightScrolledLines().setPreviousScrollCount(oldScrollCount + display->screenWindow()->scrollCount());
289
            } else {
290
                start = display->screenWindow()->scrollCount() > 0 ? display->screenWindow()->windowLines() - nb_lines : 0;
291
                scrollBar->highlightScrolledLines().setPreviousScrollCount(display->screenWindow()->scrollCount());
292
293
            }

294
295
            new_highlight.setRect(highlightLeftPosition, display->contentRect().top() + start * display->terminalFont()->fontHeight(),
                                  scrollBar->highlightScrolledLines().HIGHLIGHT_SCROLLED_LINES_WIDTH, nb_lines * display->terminalFont()->fontHeight());
296
297
            new_highlight.setTop(std::max(new_highlight.top(), display->contentRect().top()));
            new_highlight.setBottom(std::min(new_highlight.bottom(), display->contentRect().bottom()));
298
299
300
301
302
            if (!new_highlight.isValid()) {
                new_highlight = QRect(0, 0, 0, 0);
            }

            if (addToCurrentHighlight) {
303
                scrollBar->highlightScrolledLines().rect() |= new_highlight;
304
            } else {
305
306
                dirtyRegion |= scrollBar->highlightScrolledLines().rect();
                scrollBar->highlightScrolledLines().rect() = new_highlight;
307
308
            }

309
310
311
312
313
            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);
314
315
316
317
        }

        return dirtyRegion;
    }
318

319
320
321
322
323
324
325
326
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
    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;
    }

372
    std::optional<QColor> calculateBackgroundColor(const Character* style, const QColor *colorTable)
373
374
375
376
377
378
    {
        auto c1 = style->backgroundColor.color(colorTable);
        if (!(style->rendition & RE_SELECTED)) {
            return c1;
        }

379
380
        const auto initialBG = c1;

381
382
383
384
385
386
        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);
387
388
389
390
391
392
393
394
        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 {};
        }
395
396
397
398

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

399
    void TerminalPainter::drawTextFragment(QPainter &painter, const QRect &rect, const QString &text,
400
                                const Character *style, const QColor *colorTable)
401
402
    {
        // setup painter
403
404
405
406
407
        QColor foregroundColor = style->foregroundColor.color(colorTable);
        const QColor backgroundColor = calculateBackgroundColor(style, colorTable).value_or(foregroundColor);
        if (backgroundColor == foregroundColor) {
            foregroundColor = style->backgroundColor.color(colorTable);
        }
408

409
        if (backgroundColor != colorTable[DEFAULT_BACK_COLOR]) {
410
411
412
            drawBackground(painter, rect, backgroundColor, false);
        }

413
        QColor characterColor = foregroundColor;
414
415
416
417
418
419
420
        if ((style->rendition & RE_CURSOR) != 0) {
            drawCursor(painter, rect, foregroundColor, backgroundColor, characterColor);
        }

        // draw text
        drawCharacters(painter, rect, text, style, characterColor);
    }
421

422
    void TerminalPainter::drawPrinterFriendlyTextFragment(QPainter &painter, const QRect &rect, const QString &text,
423
                                                const Character *style)
424
425
426
427
428
429
430
    {
        Character print_style = *style;
        print_style.foregroundColor = CharacterColor(COLOR_SPACE_RGB, 0x00000000);
        print_style.backgroundColor = CharacterColor(COLOR_SPACE_RGB, 0xFFFFFFFF);

        drawCharacters(painter, rect, text, &print_style, QColor());
    }
431

432
    void TerminalPainter::drawBackground(QPainter &painter, const QRect &rect, const QColor &backgroundColor,
433
                                bool useOpacitySetting)
434
    {
435
436
437
        const auto display = qobject_cast<TerminalDisplay*>(sender());

        if (useOpacitySetting && !display->wallpaper()->isNull() &&
438
439
               display->wallpaper()->draw(painter, rect, display->terminalColor()->opacity())) {
        } else if (qAlpha(display->terminalColor()->blendColor()) < 0xff && useOpacitySetting) {
440
441
442
443
444
445
#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);
446
            color.setAlpha(qAlpha(display->terminalColor()->blendColor()));
447
448
449
450
451
452
453
454
455
456

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

458
    void TerminalPainter::drawCursor(QPainter &painter, const QRect &rect, const QColor &foregroundColor,
459
                            const QColor &backgroundColor, QColor &characterColor)
460
    {
461
462
463
        const auto display = qobject_cast<TerminalDisplay*>(sender());

        if (display->cursorBlinking()) {
464
465
466
467
468
            return;
        }

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

469
470
471
        QColor color = display->terminalColor()->cursorColor();
        QColor cursorColor = color.isValid() ? color : foregroundColor;

472
473
474
475
        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
476
        const qreal width = qMax(display->terminalFont()->fontWidth() / 12.0, 1.0);
477
478
479
480
        const qreal halfWidth = width / 2.0;
        pen.setWidthF(width);
        painter.setPen(pen);

481
        if (display->cursorShape() == Enum::BlockCursor) {
482
483
            painter.drawRect(cursorRect.adjusted(halfWidth, halfWidth, -halfWidth, -halfWidth));

484
            if (display->hasFocus()) {
485
486
                painter.fillRect(cursorRect, cursorColor);

487
488
                QColor cursorTextColor = display->terminalColor()->cursorTextColor();
                characterColor = cursorTextColor.isValid() ? cursorTextColor : backgroundColor;
489
            }
490
        } else if (display->cursorShape() == Enum::UnderlineCursor) {
491
492
493
494
495
496
            QLineF line(cursorRect.left() + halfWidth,
                        cursorRect.bottom() - halfWidth,
                        cursorRect.right() - halfWidth,
                        cursorRect.bottom() - halfWidth);
            painter.drawLine(line);

497
        } else if (display->cursorShape() == Enum::IBeamCursor) {
498
499
500
501
502
503
504
            QLineF line(cursorRect.left() + halfWidth,
                        cursorRect.top() + halfWidth,
                        cursorRect.left() + halfWidth,
                        cursorRect.bottom() - halfWidth);
            painter.drawLine(line);
        }
    }
505

506
    void TerminalPainter::drawCharacters(QPainter &painter, const QRect &rect, const QString &text,
507
                                const Character *style, const QColor &characterColor)
508
    {
509
510
511
        const auto display = qobject_cast<TerminalDisplay*>(sender());

        if (display->textBlinking() && ((style->rendition & RE_BLINK) != 0)) {
512
513
514
515
516
517
518
519
            return;
        }

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

        static constexpr int MaxFontWeight = 99;
520

521
        const int normalWeight = display->font().weight();
522
523
524
525
526

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

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

527
        const bool useBold = (((style->rendition & RE_BOLD) != 0) && display->terminalFont()->boldIntense());
528
529
530
531
        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();
532
533
534
535
536
537
538
539

        QFont currentFont = painter.font();

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

541
542
543
544
545
546
547
548
549
            currentFont.setWeight(useBold ? boldWeight : normalWeight);
            currentFont.setUnderline(useUnderline);
            currentFont.setItalic(useItalic);
            currentFont.setStrikeOut(useStrikeOut);
            currentFont.setOverline(useOverline);
            painter.setFont(currentFont);
        }

        // setup pen
550
        const QColor foregroundColor = style->foregroundColor.color(display->terminalColor()->colorTable());
551
552
553
554
555
556
557
558
559
560
561
        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
562
        if (isLineCharString(text) && !display->terminalFont()->useFontLineCharacters()) {
563
            drawLineCharString(display, painter, rect.x(), rect.y(), text, style);
564
565
566
        } else {
            painter.setLayoutDirection(Qt::LeftToRight);

567
            if (display->bidiEnabled()) {
568
                painter.drawText(rect.x(), rect.y() + display->terminalFont()->fontAscent() + display->terminalFont()->lineSpacing(), text);
569
            } else {
570
                painter.drawText(rect.x(), rect.y() + display->terminalFont()->fontAscent() + display->terminalFont()->lineSpacing(), LTR_OVERRIDE_CHAR + text);
571
572
573
574
575
            }
        }
        painter.setClipRegion(origClipRegion);
        painter.setClipping(origClipping);
    }
576

577
    void TerminalPainter::drawLineCharString(TerminalDisplay *display, QPainter &painter, int x, int y, const QString &str, const Character *attributes)
578
    {
579
        painter.setRenderHint(QPainter::Antialiasing, display->terminalFont()->antialiasText());
580

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

583
        QRect cellRect = {x, y, display->terminalFont()->fontWidth(), display->terminalFont()->fontHeight()};
584
        for (int i = 0; i < str.length(); i++) {
585
            LineBlockCharacters::draw(painter, cellRect.translated(i * display->terminalFont()->fontWidth(), 0), str[i], useBoldPen);
586
587
588
        }
        painter.setRenderHint(QPainter::Antialiasing, false);
    }
589

590
    void TerminalPainter::drawInputMethodPreeditString(QPainter &painter, const QRect &rect, TerminalDisplay::InputMethodData &inputMethodData, Character *image)
591
    {
592
593
594
        const auto display = qobject_cast<TerminalDisplay*>(sender());

        if (inputMethodData.preeditString.isEmpty() || !display->isCursorOnDisplay()) {
595
596
            return;
        }
597

598
        const QPoint cursorPos = display->cursorPosition();
599
600

        QColor characterColor;
601
602
        const QColor background = display->terminalColor()->colorTable()[DEFAULT_BACK_COLOR];
        const QColor foreground = display->terminalColor()->colorTable()[DEFAULT_FORE_COLOR];
603
        const Character *style = &image[display->loc(cursorPos.x(), cursorPos.y())];
604
605
606

        drawBackground(painter, rect, background, true);
        drawCursor(painter, rect, foreground, background, characterColor);
607
        drawCharacters(painter, rect, inputMethodData.preeditString, style, characterColor);
608

609
        inputMethodData.previousPreeditRect = rect;
610
611
612
    }

}