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