File indexing completed on 2024-04-14 03:55:15

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch>
0004     SPDX-FileCopyrightText: 2003-2005 Hamish Rodda <rodda@kde.org>
0005     SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org>
0006     SPDX-FileCopyrightText: 2001 Joseph Wenninger <jowenn@kde.org>
0007     SPDX-FileCopyrightText: 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de>
0008     SPDX-FileCopyrightText: 2013 Andrey Matveyakin <a.matveyakin@gmail.com>
0009 
0010     SPDX-License-Identifier: LGPL-2.0-only
0011 */
0012 
0013 #include "katerenderer.h"
0014 
0015 #include "inlinenotedata.h"
0016 #include "katebuffer.h"
0017 #include "katedocument.h"
0018 #include "kateextendedattribute.h"
0019 #include "katehighlight.h"
0020 #include "katerenderrange.h"
0021 #include "katetextlayout.h"
0022 #include "kateview.h"
0023 
0024 #include "ktexteditor/attribute.h"
0025 #include "ktexteditor/inlinenote.h"
0026 #include "ktexteditor/inlinenoteprovider.h"
0027 
0028 #include "katepartdebug.h"
0029 
0030 #include <QBrush>
0031 #include <QPaintEngine>
0032 #include <QPainter>
0033 #include <QPainterPath>
0034 #include <QRegularExpression>
0035 #include <QStack>
0036 #include <QtMath> // qCeil
0037 
0038 static const QChar tabChar(QLatin1Char('\t'));
0039 static const QChar spaceChar(QLatin1Char(' '));
0040 static const QChar nbSpaceChar(0xa0); // non-breaking space
0041 
0042 KateRenderer::KateRenderer(KTextEditor::DocumentPrivate *doc, Kate::TextFolding &folding, KTextEditor::ViewPrivate *view)
0043     : m_doc(doc)
0044     , m_folding(folding)
0045     , m_view(view)
0046     , m_tabWidth(m_doc->config()->tabWidth())
0047     , m_indentWidth(m_doc->config()->indentationWidth())
0048     , m_caretStyle(KTextEditor::caretStyles::Line)
0049     , m_drawCaret(true)
0050     , m_showSelections(true)
0051     , m_showTabs(true)
0052     , m_showSpaces(KateDocumentConfig::Trailing)
0053     , m_showNonPrintableSpaces(false)
0054     , m_printerFriendly(false)
0055     , m_config(new KateRendererConfig(this))
0056     , m_font(m_config->baseFont())
0057     , m_fontMetrics(m_font)
0058 {
0059     updateAttributes();
0060 
0061     // initialize with a sane font height
0062     updateFontHeight();
0063 
0064     // make the proper calculation for markerSize
0065     updateMarkerSize();
0066 }
0067 
0068 void KateRenderer::updateAttributes()
0069 {
0070     m_attributes = m_doc->highlight()->attributes(config()->schema());
0071 }
0072 
0073 const KTextEditor::Attribute::Ptr &KateRenderer::attribute(uint pos) const
0074 {
0075     if (pos < (uint)m_attributes.count()) {
0076         return m_attributes[pos];
0077     }
0078 
0079     return m_attributes[0];
0080 }
0081 
0082 KTextEditor::Attribute::Ptr KateRenderer::specificAttribute(int context) const
0083 {
0084     if (context >= 0 && context < m_attributes.count()) {
0085         return m_attributes[context];
0086     }
0087 
0088     return m_attributes[0];
0089 }
0090 
0091 void KateRenderer::setDrawCaret(bool drawCaret)
0092 {
0093     m_drawCaret = drawCaret;
0094 }
0095 
0096 void KateRenderer::setCaretStyle(KTextEditor::caretStyles style)
0097 {
0098     m_caretStyle = style;
0099 }
0100 
0101 void KateRenderer::setShowTabs(bool showTabs)
0102 {
0103     m_showTabs = showTabs;
0104 }
0105 
0106 void KateRenderer::setShowSpaces(KateDocumentConfig::WhitespaceRendering showSpaces)
0107 {
0108     m_showSpaces = showSpaces;
0109 }
0110 
0111 void KateRenderer::setShowNonPrintableSpaces(const bool on)
0112 {
0113     m_showNonPrintableSpaces = on;
0114 }
0115 
0116 void KateRenderer::setTabWidth(int tabWidth)
0117 {
0118     m_tabWidth = tabWidth;
0119 }
0120 
0121 bool KateRenderer::showIndentLines() const
0122 {
0123     return m_config->showIndentationLines();
0124 }
0125 
0126 void KateRenderer::setShowIndentLines(bool showIndentLines)
0127 {
0128     // invalidate our "active indent line" cached stuff
0129     m_currentBracketRange = KTextEditor::Range::invalid();
0130     m_currentBracketX = -1;
0131 
0132     m_config->setShowIndentationLines(showIndentLines);
0133 }
0134 
0135 void KateRenderer::setIndentWidth(int indentWidth)
0136 {
0137     m_indentWidth = indentWidth;
0138 }
0139 
0140 void KateRenderer::setShowSelections(bool showSelections)
0141 {
0142     m_showSelections = showSelections;
0143 }
0144 
0145 void KateRenderer::increaseFontSizes(qreal step) const
0146 {
0147     QFont f(config()->baseFont());
0148     f.setPointSizeF(f.pointSizeF() + step);
0149     config()->setFont(f);
0150 }
0151 
0152 void KateRenderer::resetFontSizes() const
0153 {
0154     QFont f(KateRendererConfig::global()->baseFont());
0155     config()->setFont(f);
0156 }
0157 
0158 void KateRenderer::decreaseFontSizes(qreal step) const
0159 {
0160     QFont f(config()->baseFont());
0161     if ((f.pointSizeF() - step) > 0) {
0162         f.setPointSizeF(f.pointSizeF() - step);
0163     }
0164     config()->setFont(f);
0165 }
0166 
0167 bool KateRenderer::isPrinterFriendly() const
0168 {
0169     return m_printerFriendly;
0170 }
0171 
0172 void KateRenderer::setPrinterFriendly(bool printerFriendly)
0173 {
0174     m_printerFriendly = printerFriendly;
0175     setShowTabs(false);
0176     setShowSpaces(KateDocumentConfig::None);
0177     setShowSelections(false);
0178     setDrawCaret(false);
0179 }
0180 
0181 void KateRenderer::paintTextLineBackground(QPainter &paint, KateLineLayout *layout, int currentViewLine, int xStart, int xEnd)
0182 {
0183     if (isPrinterFriendly()) {
0184         return;
0185     }
0186 
0187     // Normal background color
0188     QColor backgroundColor(config()->backgroundColor());
0189 
0190     // paint the current line background if we're on the current line
0191     QColor currentLineColor = config()->highlightedLineColor();
0192 
0193     // Check for mark background
0194     int markRed = 0;
0195     int markGreen = 0;
0196     int markBlue = 0;
0197     int markCount = 0;
0198 
0199     // Retrieve marks for this line
0200     uint mrk = m_doc->mark(layout->line());
0201     if (mrk) {
0202         for (uint bit = 0; bit < 32; bit++) {
0203             KTextEditor::Document::MarkTypes markType = (KTextEditor::Document::MarkTypes)(1U << bit);
0204             if (mrk & markType) {
0205                 QColor markColor = config()->lineMarkerColor(markType);
0206 
0207                 if (markColor.isValid()) {
0208                     markCount++;
0209                     markRed += markColor.red();
0210                     markGreen += markColor.green();
0211                     markBlue += markColor.blue();
0212                 }
0213             }
0214         } // for
0215     } // Marks
0216 
0217     if (markCount) {
0218         markRed /= markCount;
0219         markGreen /= markCount;
0220         markBlue /= markCount;
0221         backgroundColor.setRgb(int((backgroundColor.red() * 0.9) + (markRed * 0.1)),
0222                                int((backgroundColor.green() * 0.9) + (markGreen * 0.1)),
0223                                int((backgroundColor.blue() * 0.9) + (markBlue * 0.1)),
0224                                backgroundColor.alpha());
0225     }
0226 
0227     // Draw line background
0228     paint.fillRect(0, 0, xEnd - xStart, lineHeight() * layout->viewLineCount(), backgroundColor);
0229 
0230     // paint the current line background if we're on the current line
0231     const bool currentLineHasSelection = m_view && m_view->selection() && m_view->selectionRange().overlapsLine(layout->line());
0232     if (currentViewLine != -1 && !currentLineHasSelection) {
0233         if (markCount) {
0234             markRed /= markCount;
0235             markGreen /= markCount;
0236             markBlue /= markCount;
0237             currentLineColor.setRgb(int((currentLineColor.red() * 0.9) + (markRed * 0.1)),
0238                                     int((currentLineColor.green() * 0.9) + (markGreen * 0.1)),
0239                                     int((currentLineColor.blue() * 0.9) + (markBlue * 0.1)),
0240                                     currentLineColor.alpha());
0241         }
0242 
0243         paint.fillRect(0, lineHeight() * currentViewLine, xEnd - xStart, lineHeight(), currentLineColor);
0244     }
0245 }
0246 
0247 void KateRenderer::paintTabstop(QPainter &paint, qreal x, qreal y) const
0248 {
0249     QPen penBackup(paint.pen());
0250     QPen pen(config()->tabMarkerColor());
0251     pen.setWidthF(qMax(1.0, spaceWidth() / 10.0));
0252     paint.setPen(pen);
0253 
0254     int dist = spaceWidth() * 0.3;
0255     QPoint points[8];
0256     points[0] = QPoint(x - dist, y - dist);
0257     points[1] = QPoint(x, y);
0258     points[2] = QPoint(x, y);
0259     points[3] = QPoint(x - dist, y + dist);
0260     x += spaceWidth() / 3.0;
0261     points[4] = QPoint(x - dist, y - dist);
0262     points[5] = QPoint(x, y);
0263     points[6] = QPoint(x, y);
0264     points[7] = QPoint(x - dist, y + dist);
0265     paint.drawLines(points, 4);
0266     paint.setPen(penBackup);
0267 }
0268 
0269 void KateRenderer::paintSpaces(QPainter &paint, const QPointF *points, const int count) const
0270 {
0271     if (count == 0) {
0272         return;
0273     }
0274     QPen penBackup(paint.pen());
0275     QPen pen(config()->tabMarkerColor());
0276 
0277     pen.setWidthF(m_markerSize);
0278     pen.setCapStyle(Qt::RoundCap);
0279     paint.setPen(pen);
0280     paint.setRenderHint(QPainter::Antialiasing, true);
0281     paint.drawPoints(points, count);
0282     paint.setPen(penBackup);
0283     paint.setRenderHint(QPainter::Antialiasing, false);
0284 }
0285 
0286 void KateRenderer::paintNonBreakSpace(QPainter &paint, qreal x, qreal y) const
0287 {
0288     QPen penBackup(paint.pen());
0289     QPen pen(config()->tabMarkerColor());
0290     pen.setWidthF(qMax(1.0, spaceWidth() / 10.0));
0291     paint.setPen(pen);
0292 
0293     const int height = fontHeight();
0294     const int width = spaceWidth();
0295 
0296     QPoint points[6];
0297     points[0] = QPoint(x + width / 10, y + height / 4);
0298     points[1] = QPoint(x + width / 10, y + height / 3);
0299     points[2] = QPoint(x + width / 10, y + height / 3);
0300     points[3] = QPoint(x + width - width / 10, y + height / 3);
0301     points[4] = QPoint(x + width - width / 10, y + height / 3);
0302     points[5] = QPoint(x + width - width / 10, y + height / 4);
0303     paint.drawLines(points, 3);
0304     paint.setPen(penBackup);
0305 }
0306 
0307 void KateRenderer::paintNonPrintableSpaces(QPainter &paint, qreal x, qreal y, const QChar &chr)
0308 {
0309     paint.save();
0310     QPen pen(config()->spellingMistakeLineColor());
0311     pen.setWidthF(qMax(1.0, spaceWidth() * 0.1));
0312     paint.setPen(pen);
0313 
0314     const int height = fontHeight();
0315     const int width = m_fontMetrics.boundingRect(chr).width();
0316     const int offset = spaceWidth() * 0.1;
0317 
0318     QPoint points[8];
0319     points[0] = QPoint(x - offset, y + offset);
0320     points[1] = QPoint(x + width + offset, y + offset);
0321     points[2] = QPoint(x + width + offset, y + offset);
0322     points[3] = QPoint(x + width + offset, y - height - offset);
0323     points[4] = QPoint(x + width + offset, y - height - offset);
0324     points[5] = QPoint(x - offset, y - height - offset);
0325     points[6] = QPoint(x - offset, y - height - offset);
0326     points[7] = QPoint(x - offset, y + offset);
0327     paint.drawLines(points, 4);
0328     paint.restore();
0329 }
0330 
0331 /**
0332  * Helper function that checks if our cursor is at a bracket
0333  * and calculates X position for opening/closing brackets. We
0334  * then use this data to color the indentation line differently.
0335  * @p view is current view
0336  * @p range is the current range from @ref paintTextLine
0337  * @p spaceWidth width of space char
0338  * @p c is the position of cursor
0339  * @p bracketXPos will be X position of close bracket or -1 if not found
0340  */
0341 static KTextEditor::Range cursorAtBracket(KTextEditor::ViewPrivate *view, const KateLineLayout *range, int spaceWidth, KTextEditor::Cursor c, int &bracketXPos)
0342 {
0343     bracketXPos = -1;
0344     if (range->line() != c.line()) {
0345         return KTextEditor::Range::invalid();
0346     }
0347 
0348     auto *doc = view->doc();
0349     // Avoid work if we are below tabwidth
0350     if (c.column() < doc->config()->tabWidth()) {
0351         return KTextEditor::Range::invalid();
0352     }
0353 
0354     // We match these brackets only
0355     static constexpr QChar brackets[] = {QLatin1Char('{'), QLatin1Char('}'), QLatin1Char('('), QLatin1Char(')')};
0356     // look for character in front
0357     QChar right = doc->characterAt(c);
0358     auto it = std::find(std::begin(brackets), std::end(brackets), right);
0359 
0360     KTextEditor::Range ret = KTextEditor::Range::invalid();
0361     bool found = false;
0362     if (it != std::end(brackets)) {
0363         found = true;
0364     } else {
0365         // look at previous character
0366         QChar left = doc->characterAt({c.line(), c.column() - 1});
0367         it = std::find(std::begin(brackets), std::end(brackets), left);
0368         if (it != std::end(brackets)) {
0369             found = true;
0370         }
0371     }
0372 
0373     // We have a bracket
0374     if (found) {
0375         ret = doc->findMatchingBracket(c, 500);
0376         if (!ret.isValid()) {
0377             return ret;
0378         }
0379         bracketXPos = (ret.end().column() * spaceWidth) + 1;
0380     }
0381 
0382     return ret;
0383 }
0384 
0385 void KateRenderer::paintIndentMarker(QPainter &paint, uint x, int line)
0386 {
0387     const QPen penBackup(paint.pen());
0388     static const QList<qreal> dashPattern = QList<qreal>() << 1 << 1;
0389     QPen myPen;
0390 
0391     const bool onBracket = m_currentBracketX == (int)x;
0392     if (onBracket && m_currentBracketRange.containsLine(line)) {
0393         QColor c = view()->theme().textColor(KSyntaxHighlighting::Theme::Normal);
0394         c.setAlphaF(0.7);
0395         myPen.setColor(c);
0396     } else {
0397         myPen.setColor(config()->indentationLineColor());
0398         myPen.setDashPattern(dashPattern);
0399     }
0400 
0401     paint.setPen(myPen);
0402 
0403     QPainter::RenderHints renderHints = paint.renderHints();
0404     paint.setRenderHints(renderHints, false);
0405 
0406     paint.drawLine(x + 2, 0, x + 2, lineHeight());
0407 
0408     paint.setRenderHints(renderHints, true);
0409 
0410     paint.setPen(penBackup);
0411 }
0412 
0413 static bool rangeLessThanForRenderer(const Kate::TextRange *a, const Kate::TextRange *b)
0414 {
0415     // compare Z-Depth first
0416     // smaller Z-Depths should win!
0417     if (a->zDepth() > b->zDepth()) {
0418         return true;
0419     } else if (a->zDepth() < b->zDepth()) {
0420         return false;
0421     }
0422 
0423     // end of a > end of b?
0424     if (a->end().toCursor() > b->end().toCursor()) {
0425         return true;
0426     }
0427 
0428     // if ends are equal, start of a < start of b?
0429     if (a->end().toCursor() == b->end().toCursor()) {
0430         return a->start().toCursor() < b->start().toCursor();
0431     }
0432 
0433     return false;
0434 }
0435 
0436 QList<QTextLayout::FormatRange> KateRenderer::decorationsForLine(const Kate::TextLine &textLine, int line, bool selectionsOnly) const
0437 {
0438     // limit number of attributes we can highlight in reasonable time
0439     const int limitOfRanges = 1024;
0440     auto rangesWithAttributes = m_doc->buffer().rangesForLine(line, m_printerFriendly ? nullptr : m_view, true);
0441     if (rangesWithAttributes.size() > limitOfRanges) {
0442         rangesWithAttributes.clear();
0443     }
0444 
0445     // Don't compute the highlighting if there isn't going to be any highlighting
0446     const auto &al = textLine.attributesList();
0447     if (!(selectionsOnly || !al.empty() || !rangesWithAttributes.empty())) {
0448         return QList<QTextLayout::FormatRange>();
0449     }
0450 
0451     // Add the inbuilt highlighting to the list, limit with limitOfRanges
0452     RenderRangeVector renderRanges;
0453     if (!al.empty()) {
0454         auto &currentRange = renderRanges.pushNewRange();
0455         for (int i = 0; i < std::min<int>(al.size(), limitOfRanges); ++i) {
0456             if (al[i].length > 0 && al[i].attributeValue > 0) {
0457                 currentRange.addRange(KTextEditor::Range(KTextEditor::Cursor(line, al[i].offset), al[i].length), specificAttribute(al[i].attributeValue));
0458             }
0459         }
0460     }
0461 
0462     // check for dynamic hl stuff
0463     const QSet<Kate::TextRange *> *rangesMouseIn = m_view ? m_view->rangesMouseIn() : nullptr;
0464     const QSet<Kate::TextRange *> *rangesCaretIn = m_view ? m_view->rangesCaretIn() : nullptr;
0465     bool anyDynamicHlsActive = m_view && (!rangesMouseIn->empty() || !rangesCaretIn->empty());
0466 
0467     // sort all ranges, we want that the most specific ranges win during rendering, multiple equal ranges are kind of random, still better than old smart
0468     // rangs behavior ;)
0469     std::sort(rangesWithAttributes.begin(), rangesWithAttributes.end(), rangeLessThanForRenderer);
0470 
0471     renderRanges.reserve(rangesWithAttributes.size());
0472     // loop over all ranges
0473     for (int i = 0; i < rangesWithAttributes.size(); ++i) {
0474         // real range
0475         Kate::TextRange *kateRange = rangesWithAttributes[i];
0476 
0477         // calculate attribute, default: normal attribute
0478         KTextEditor::Attribute::Ptr attribute = kateRange->attribute();
0479         if (anyDynamicHlsActive) {
0480             // check mouse in
0481             if (KTextEditor::Attribute::Ptr attributeMouseIn = attribute->dynamicAttribute(KTextEditor::Attribute::ActivateMouseIn)) {
0482                 if (rangesMouseIn->contains(kateRange)) {
0483                     attribute = attributeMouseIn;
0484                 }
0485             }
0486 
0487             // check caret in
0488             if (KTextEditor::Attribute::Ptr attributeCaretIn = attribute->dynamicAttribute(KTextEditor::Attribute::ActivateCaretIn)) {
0489                 if (rangesCaretIn->contains(kateRange)) {
0490                     attribute = attributeCaretIn;
0491                 }
0492             }
0493         }
0494 
0495         // span range
0496         renderRanges.pushNewRange().addRange(*kateRange, std::move(attribute));
0497     }
0498 
0499     // Add selection highlighting if we're creating the selection decorations
0500     if ((m_view && selectionsOnly && showSelections() && m_view->selection()) || (m_view && m_view->blockSelection())) {
0501         auto &currentRange = renderRanges.pushNewRange();
0502 
0503         // Set up the selection background attribute TODO: move this elsewhere, eg. into the config?
0504         static KTextEditor::Attribute::Ptr backgroundAttribute;
0505         if (!backgroundAttribute) {
0506             backgroundAttribute = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute());
0507         }
0508 
0509         if (!hasCustomLineHeight()) {
0510             backgroundAttribute->setBackground(config()->selectionColor());
0511         }
0512         backgroundAttribute->setForeground(attribute(KSyntaxHighlighting::Theme::TextStyle::Normal)->selectedForeground().color());
0513 
0514         // Create a range for the current selection
0515         if (m_view->blockSelection() && m_view->selectionRange().overlapsLine(line)) {
0516             currentRange.addRange(m_doc->rangeOnLine(m_view->selectionRange(), line), backgroundAttribute);
0517         } else {
0518             currentRange.addRange(m_view->selectionRange(), backgroundAttribute);
0519         }
0520     }
0521 
0522     // no render ranges, nothing to do, else we loop below endless!
0523     if (renderRanges.isEmpty()) {
0524         return QList<QTextLayout::FormatRange>();
0525     }
0526 
0527     // Calculate the range which we need to iterate in order to get the highlighting for just this line
0528     KTextEditor::Cursor currentPosition;
0529     KTextEditor::Cursor endPosition;
0530     if (m_view && selectionsOnly) {
0531         if (m_view->blockSelection()) {
0532             KTextEditor::Range subRange = m_doc->rangeOnLine(m_view->selectionRange(), line);
0533             currentPosition = subRange.start();
0534             endPosition = subRange.end();
0535         } else {
0536             KTextEditor::Range rangeNeeded = m_view->selectionRange() & KTextEditor::Range(line, 0, line + 1, 0);
0537 
0538             currentPosition = qMax(KTextEditor::Cursor(line, 0), rangeNeeded.start());
0539             endPosition = qMin(KTextEditor::Cursor(line + 1, 0), rangeNeeded.end());
0540         }
0541     } else {
0542         currentPosition = KTextEditor::Cursor(line, 0);
0543         endPosition = KTextEditor::Cursor(line + 1, 0);
0544     }
0545 
0546     // Background formats have lower priority so they get overriden by selection
0547     const bool shoudlClearBackground = m_view && hasCustomLineHeight() && m_view->selection();
0548     const KTextEditor::Range selectionRange = shoudlClearBackground ? m_view->selectionRange() : KTextEditor::Range::invalid();
0549 
0550     // Main iterative loop.  This walks through each set of highlighting ranges, and stops each
0551     // time the highlighting changes.  It then creates the corresponding QTextLayout::FormatRanges.
0552     QList<QTextLayout::FormatRange> newHighlight;
0553     while (currentPosition < endPosition) {
0554         renderRanges.advanceTo(currentPosition);
0555 
0556         if (!renderRanges.hasAttribute()) {
0557             // No attribute, don't need to create a FormatRange for this text range
0558             currentPosition = renderRanges.nextBoundary();
0559             continue;
0560         }
0561 
0562         KTextEditor::Cursor nextPosition = renderRanges.nextBoundary();
0563 
0564         // Create the format range and populate with the correct start, length and format info
0565         QTextLayout::FormatRange fr;
0566         fr.start = currentPosition.column();
0567 
0568         if (nextPosition < endPosition || endPosition.line() <= line) {
0569             fr.length = nextPosition.column() - currentPosition.column();
0570 
0571         } else {
0572             // before we did here +1 to force background drawing at the end of the line when it's warranted
0573             // we now skip this, we don't draw e.g. full line backgrounds
0574             fr.length = textLine.length() - currentPosition.column();
0575         }
0576 
0577         KTextEditor::Attribute::Ptr a = renderRanges.generateAttribute();
0578         if (a) {
0579             fr.format = *a;
0580 
0581             if (selectionsOnly) {
0582                 assignSelectionBrushesFromAttribute(fr, *a);
0583             }
0584         }
0585 
0586         // Clear background if this position overlaps selection
0587         if (shoudlClearBackground && selectionRange.contains(currentPosition) && fr.format.hasProperty(QTextFormat::BackgroundBrush)) {
0588             fr.format.clearBackground();
0589         }
0590 
0591         newHighlight.append(fr);
0592 
0593         currentPosition = nextPosition;
0594     }
0595 
0596     // ensure bold & italic fonts work, even if the main font is e.g. light or something like that
0597     for (auto &formatRange : newHighlight) {
0598         if (formatRange.format.fontWeight() == QFont::Bold || formatRange.format.fontItalic()) {
0599             formatRange.format.setFontStyleName(QString());
0600         }
0601     }
0602 
0603     return newHighlight;
0604 }
0605 
0606 void KateRenderer::assignSelectionBrushesFromAttribute(QTextLayout::FormatRange &target, const KTextEditor::Attribute &attribute)
0607 {
0608     if (attribute.hasProperty(SelectedForeground)) {
0609         target.format.setForeground(attribute.selectedForeground());
0610     }
0611     if (attribute.hasProperty(SelectedBackground)) {
0612         target.format.setBackground(attribute.selectedBackground());
0613     }
0614 }
0615 
0616 void KateRenderer::paintTextBackground(QPainter &paint, KateLineLayout *layout, const QList<QTextLayout::FormatRange> &selRanges, const QBrush &brush) const
0617 {
0618     const bool rtl = layout->isRightToLeft();
0619 
0620     for (const auto &sel : selRanges) {
0621         const int s = sel.start;
0622         const int e = sel.start + sel.length;
0623         QBrush br;
0624 
0625         // Prefer using the brush supplied by user
0626         if (brush != Qt::NoBrush) {
0627             br = brush;
0628         } else if (sel.format.background() != Qt::NoBrush) {
0629             // Otherwise use the brush in format
0630             br = sel.format.background();
0631         } else {
0632             // skip if no brush to fill with
0633             continue;
0634         }
0635 
0636         const int startViewLine = layout->viewLineForColumn(s);
0637         const int endViewLine = layout->viewLineForColumn(e);
0638         if (startViewLine == endViewLine) {
0639             KateTextLayout l = layout->viewLine(startViewLine);
0640             const int startX = cursorToX(l, s);
0641             const int endX = cursorToX(l, e);
0642             const int y = startViewLine * lineHeight();
0643             QRect r(startX, y, (endX - startX), lineHeight());
0644             paint.fillRect(r, br);
0645         } else {
0646             QPainterPath p;
0647             for (int l = startViewLine; l <= endViewLine; ++l) {
0648                 auto kateLayout = layout->viewLine(l);
0649                 int sx = 0;
0650                 int width = rtl ? kateLayout.lineLayout().width() : kateLayout.lineLayout().naturalTextWidth();
0651 
0652                 if (l == startViewLine) {
0653                     if (rtl) {
0654                         // For rtl, Rect starts at 0 and ends at selection start
0655                         sx = 0;
0656                         width = kateLayout.lineLayout().cursorToX(s);
0657                     } else {
0658                         sx = kateLayout.lineLayout().cursorToX(s);
0659                     }
0660                 } else if (l == endViewLine) {
0661                     if (rtl) {
0662                         // Drawing will start at selection end, and end at the view border
0663                         sx = kateLayout.lineLayout().cursorToX(e);
0664                     } else {
0665                         width = kateLayout.lineLayout().cursorToX(e);
0666                     }
0667                 }
0668 
0669                 const int y = l * lineHeight();
0670                 QRect r(sx, y, width - sx, lineHeight());
0671                 p.addRect(r);
0672             }
0673             paint.fillPath(p, br);
0674         }
0675     }
0676 }
0677 
0678 void KateRenderer::paintTextLine(QPainter &paint,
0679                                  KateLineLayout *range,
0680                                  int xStart,
0681                                  int xEnd,
0682                                  const QRectF &textClipRect,
0683                                  const KTextEditor::Cursor *cursor,
0684                                  PaintTextLineFlags flags)
0685 {
0686     Q_ASSERT(range->isValid());
0687 
0688     //   qCDebug(LOG_KTE)<<"KateRenderer::paintTextLine";
0689 
0690     // font data
0691     const QFontMetricsF &fm = m_fontMetrics;
0692 
0693     int currentViewLine = -1;
0694     if (cursor && cursor->line() == range->line()) {
0695         currentViewLine = range->viewLineForColumn(cursor->column());
0696     }
0697 
0698     paintTextLineBackground(paint, range, currentViewLine, xStart, xEnd);
0699 
0700     // Draws the dashed underline at the start of a folded block of text.
0701     if (!(flags & SkipDrawFirstInvisibleLineUnderlined) && range->startsInvisibleBlock()) {
0702         QPen pen(config()->foldingColor());
0703         pen.setCosmetic(true);
0704         pen.setStyle(Qt::DashLine);
0705         pen.setDashOffset(xStart);
0706         pen.setWidth(2);
0707         paint.setPen(pen);
0708         paint.drawLine(0, (lineHeight() * range->viewLineCount()) - 2, xEnd - xStart, (lineHeight() * range->viewLineCount()) - 2);
0709     }
0710 
0711     if (range->layout()) {
0712         bool drawSelection =
0713             m_view && m_view->selection() && showSelections() && m_view->selectionRange().overlapsLine(range->line()) && !flags.testFlag(SkipDrawLineSelection);
0714         // Draw selection in block selection mode. We need 2 kinds of selections that QTextLayout::draw can't render:
0715         //   - past-end-of-line selection and
0716         //   - 0-column-wide selection (used to indicate where text will be typed)
0717         if (drawSelection && m_view->blockSelection()) {
0718             int selectionStartColumn = m_doc->fromVirtualColumn(range->line(), m_doc->toVirtualColumn(m_view->selectionRange().start()));
0719             int selectionEndColumn = m_doc->fromVirtualColumn(range->line(), m_doc->toVirtualColumn(m_view->selectionRange().end()));
0720             QBrush selectionBrush = config()->selectionColor();
0721             if (selectionStartColumn != selectionEndColumn) {
0722                 KateTextLayout lastLine = range->viewLine(range->viewLineCount() - 1);
0723                 if (selectionEndColumn > lastLine.startCol()) {
0724                     int selectionStartX = (selectionStartColumn > lastLine.startCol()) ? cursorToX(lastLine, selectionStartColumn, true) : 0;
0725                     int selectionEndX = cursorToX(lastLine, selectionEndColumn, true);
0726                     paint.fillRect(QRect(selectionStartX - xStart, (int)lastLine.lineLayout().y(), selectionEndX - selectionStartX, lineHeight()),
0727                                    selectionBrush);
0728                 }
0729             } else {
0730                 const int selectStickWidth = 2;
0731                 KateTextLayout selectionLine = range->viewLine(range->viewLineForColumn(selectionStartColumn));
0732                 int selectionX = cursorToX(selectionLine, selectionStartColumn, true);
0733                 paint.fillRect(QRect(selectionX - xStart, (int)selectionLine.lineLayout().y(), selectStickWidth, lineHeight()), selectionBrush);
0734             }
0735         }
0736 
0737         QList<QTextLayout::FormatRange> additionalFormats;
0738         if (range->length() > 0) {
0739             // We may have changed the pen, be absolutely sure it gets set back to
0740             // normal foreground color before drawing text for text that does not
0741             // set the pen color
0742             paint.setPen(attribute(KSyntaxHighlighting::Theme::TextStyle::Normal)->foreground().color());
0743             // Draw the text :)
0744 
0745             if (range->layout()->textOption().textDirection() == Qt::RightToLeft) {
0746                 // If the text is RTL, we draw text background ourselves
0747                 auto decos = decorationsForLine(range->textLine(), range->line(), false);
0748                 auto sr = view()->selectionRange();
0749                 auto c = config()->selectionColor();
0750                 int line = range->line();
0751                 // Remove "selection" format from decorations
0752                 // "selection" will get painted below
0753                 decos.erase(std::remove_if(decos.begin(),
0754                                            decos.end(),
0755                                            [sr, c, line](const QTextLayout::FormatRange &fr) {
0756                                                return sr.overlapsLine(line) && sr.overlapsColumn(fr.start) && fr.format.background().color() == c;
0757                                            }),
0758                             decos.end());
0759                 paintTextBackground(paint, range, decos, Qt::NoBrush);
0760             }
0761 
0762             if (drawSelection) {
0763                 additionalFormats = decorationsForLine(range->textLine(), range->line(), true);
0764                 if (hasCustomLineHeight()) {
0765                     paintTextBackground(paint, range, additionalFormats, config()->selectionColor());
0766                 }
0767                 range->layout()->draw(&paint, QPoint(-xStart, 0), additionalFormats, textClipRect);
0768 
0769             } else {
0770                 range->layout()->draw(&paint, QPoint(-xStart, 0), QList<QTextLayout::FormatRange>{}, textClipRect);
0771             }
0772         }
0773 
0774         // Check if we are at a bracket and color the indentation
0775         // line differently
0776         const bool indentLinesEnabled = showIndentLines();
0777         if (cursor && indentLinesEnabled) {
0778             auto cur = *cursor;
0779             cur.setColumn(cur.column() - 1);
0780             if (!m_currentBracketRange.boundaryAtCursor(*cursor) && m_currentBracketRange.end() != cur && m_currentBracketRange.start() != cur) {
0781                 m_currentBracketRange = cursorAtBracket(view(), range, spaceWidth(), *cursor, m_currentBracketX);
0782             }
0783         }
0784 
0785         // Loop each individual line for additional text decoration etc.
0786         for (int i = 0; i < range->viewLineCount(); ++i) {
0787             KateTextLayout line = range->viewLine(i);
0788 
0789             // Draw indent lines
0790             if (!m_printerFriendly && (indentLinesEnabled && i == 0)) {
0791                 const qreal w = spaceWidth();
0792                 const int lastIndentColumn = range->textLine().indentDepth(m_tabWidth);
0793                 for (int x = m_indentWidth; x < lastIndentColumn; x += m_indentWidth) {
0794                     auto xPos = x * w + 1 - xStart;
0795                     if (xPos >= 0) {
0796                         paintIndentMarker(paint, xPos, range->line());
0797                     }
0798                 }
0799             }
0800 
0801             // draw an open box to mark non-breaking spaces
0802             const QString &text = range->textLine().text();
0803             int y = lineHeight() * i + m_fontAscent - fm.strikeOutPos();
0804             int nbSpaceIndex = text.indexOf(nbSpaceChar, line.lineLayout().xToCursor(xStart));
0805 
0806             while (nbSpaceIndex != -1 && nbSpaceIndex < line.endCol()) {
0807                 int x = line.lineLayout().cursorToX(nbSpaceIndex);
0808                 if (x > xEnd) {
0809                     break;
0810                 }
0811                 paintNonBreakSpace(paint, x - xStart, y);
0812                 nbSpaceIndex = text.indexOf(nbSpaceChar, nbSpaceIndex + 1);
0813             }
0814 
0815             // draw tab stop indicators
0816             if (showTabs()) {
0817                 int tabIndex = text.indexOf(tabChar, line.lineLayout().xToCursor(xStart));
0818                 while (tabIndex != -1 && tabIndex < line.endCol()) {
0819                     int x = line.lineLayout().cursorToX(tabIndex);
0820                     if (x > xEnd) {
0821                         break;
0822                     }
0823                     paintTabstop(paint, x - xStart + spaceWidth() / 2.0, y);
0824                     tabIndex = text.indexOf(tabChar, tabIndex + 1);
0825                 }
0826             }
0827 
0828             // draw trailing spaces
0829             if (showSpaces() != KateDocumentConfig::None) {
0830                 int spaceIndex = line.endCol() - 1;
0831                 const int trailingPos = showSpaces() == KateDocumentConfig::All ? 0 : qMax(range->textLine().lastChar(), 0);
0832 
0833                 if (spaceIndex >= trailingPos) {
0834                     QVarLengthArray<int, 32> spacePositions;
0835                     // Adjust to visible contents
0836                     const auto dir = range->layout()->textOption().textDirection();
0837                     const bool isRTL = dir == Qt::RightToLeft && m_view->dynWordWrap();
0838                     int start = isRTL ? xEnd : xStart;
0839                     int end = isRTL ? xStart : xEnd;
0840 
0841                     spaceIndex = std::min(line.lineLayout().xToCursor(end), spaceIndex);
0842                     int visibleStart = line.lineLayout().xToCursor(start);
0843 
0844                     for (; spaceIndex >= line.startCol(); --spaceIndex) {
0845                         if (!text.at(spaceIndex).isSpace()) {
0846                             if (showSpaces() == KateDocumentConfig::Trailing) {
0847                                 break;
0848                             } else {
0849                                 continue;
0850                             }
0851                         }
0852                         if (text.at(spaceIndex) != QLatin1Char('\t') || !showTabs()) {
0853                             spacePositions << spaceIndex;
0854                         }
0855 
0856                         if (spaceIndex < visibleStart) {
0857                             break;
0858                         }
0859                     }
0860 
0861                     QPointF prev;
0862                     QVarLengthArray<QPointF, 32> spacePoints;
0863                     const auto spaceWidth = this->spaceWidth();
0864                     // reverse because we want to look at the spaces at the beginning of line first
0865                     for (auto rit = spacePositions.rbegin(); rit != spacePositions.rend(); ++rit) {
0866                         const int spaceIdx = *rit;
0867                         qreal x = line.lineLayout().cursorToX(spaceIdx) - xStart;
0868                         int dir = 1; // 1 == ltr, -1 == rtl
0869                         if (range->layout()->textOption().alignment() == Qt::AlignRight) {
0870                             dir = -1;
0871                             if (spaceIdx > 0) {
0872                                 QChar c = text.at(spaceIdx - 1);
0873                                 // line is LTR aligned, but is the char ltr or rtl?
0874                                 if (!isLineRightToLeft(QStringView(&c, 1))) {
0875                                     dir = 1;
0876                                 }
0877                             }
0878                         } else {
0879                             if (spaceIdx > 0) {
0880                                 // line is LTR aligned, but is the char ltr or rtl?
0881                                 QChar c = text.at(spaceIdx - 1);
0882                                 if (isLineRightToLeft(QStringView(&c, 1))) {
0883                                     dir = -1;
0884                                 }
0885                             }
0886                         }
0887 
0888                         x += dir * (spaceWidth / 2.0);
0889 
0890                         const QPointF currentPoint(x, y);
0891                         if (!prev.isNull() && currentPoint == prev) {
0892                             break;
0893                         }
0894                         spacePoints << currentPoint;
0895                         prev = QPointF(x, y);
0896                     }
0897                     if (!spacePoints.isEmpty()) {
0898                         paintSpaces(paint, spacePoints.constData(), spacePoints.size());
0899                     }
0900                 }
0901             }
0902 
0903             if (showNonPrintableSpaces()) {
0904                 const int y = lineHeight() * i + m_fontAscent;
0905 
0906                 static const QRegularExpression nonPrintableSpacesRegExp(
0907                     QStringLiteral("[\\x{2000}-\\x{200F}\\x{2028}-\\x{202F}\\x{205F}-\\x{2064}\\x{206A}-\\x{206F}]"));
0908                 QRegularExpressionMatchIterator i = nonPrintableSpacesRegExp.globalMatch(text, line.lineLayout().xToCursor(xStart));
0909 
0910                 while (i.hasNext()) {
0911                     const int charIndex = i.next().capturedStart();
0912 
0913                     const int x = line.lineLayout().cursorToX(charIndex);
0914                     if (x > xEnd) {
0915                         break;
0916                     }
0917 
0918                     paintNonPrintableSpaces(paint, x - xStart, y, text[charIndex]);
0919                 }
0920             }
0921 
0922             // draw word-wrap-honor-indent filling
0923             if ((i > 0) && range->shiftX && (range->shiftX > xStart)) {
0924                 // fill background first with selection if we had selection from the previous line
0925                 if (drawSelection && !m_view->blockSelection() && m_view->selectionRange().start() < line.start()
0926                     && m_view->selectionRange().end() >= line.start()) {
0927                     paint.fillRect(0, lineHeight() * i, range->shiftX - xStart, lineHeight(), QBrush(config()->selectionColor()));
0928                 }
0929 
0930                 // paint the normal filling for the word wrap markers
0931                 paint.fillRect(0, lineHeight() * i, range->shiftX - xStart, lineHeight(), QBrush(config()->wordWrapMarkerColor(), Qt::Dense4Pattern));
0932             }
0933         }
0934 
0935         // Draw carets
0936         if (m_view && cursor && drawCaret()) {
0937             const auto &secCursors = view()->secondaryCursors();
0938             // Find carets on this line
0939             auto mIt = std::lower_bound(secCursors.begin(), secCursors.end(), range->line(), [](const KTextEditor::ViewPrivate::SecondaryCursor &l, int line) {
0940                 return l.pos->line() < line;
0941             });
0942             if (mIt != secCursors.end() && mIt->cursor().line() == range->line()) {
0943                 for (; mIt != secCursors.end(); ++mIt) {
0944                     auto cursor = mIt->cursor();
0945                     if (cursor.line() == range->line()) {
0946                         paintCaret(cursor, range, paint, xStart, xEnd);
0947                     } else {
0948                         break;
0949                     }
0950                 }
0951             }
0952             paintCaret(*cursor, range, paint, xStart, xEnd);
0953         }
0954     }
0955 
0956     // show word wrap marker if desirable
0957     if ((!isPrinterFriendly()) && config()->wordWrapMarker()) {
0958         const QPainter::RenderHints backupRenderHints = paint.renderHints();
0959         paint.setPen(config()->wordWrapMarkerColor());
0960         int _x = qreal(m_doc->config()->wordWrapAt()) * fm.horizontalAdvance(QLatin1Char('x')) - xStart;
0961         paint.drawLine(_x, 0, _x, lineHeight());
0962         paint.setRenderHints(backupRenderHints);
0963     }
0964 
0965     // Draw inline notes
0966     if (!isPrinterFriendly()) {
0967         const auto inlineNotes = m_view->inlineNotes(range->line());
0968         for (const auto &inlineNoteData : inlineNotes) {
0969             KTextEditor::InlineNote inlineNote(inlineNoteData);
0970             const int column = inlineNote.position().column();
0971             const int viewLine = range->viewLineForColumn(column);
0972             // We only consider a line "rtl" if dynamic wrap is enabled. If it is disabled, our
0973             // text is always on the left side of the view
0974             const auto dir = range->layout()->textOption().textDirection();
0975             const bool isRTL = dir == Qt::RightToLeft && m_view->dynWordWrap();
0976 
0977             // Determine the position where to paint the note.
0978             // We start by getting the x coordinate of cursor placed to the column.
0979             // If the text is ltr or rtl + dyn wrap, get the X from column
0980             qreal x;
0981             if (dir == Qt::LeftToRight || (dir == Qt::RightToLeft && m_view->dynWordWrap())) {
0982                 x = range->viewLine(viewLine).lineLayout().cursorToX(column) - xStart;
0983             } else /* rtl + dynWordWrap == false */ {
0984                 // if text is rtl and dynamic wrap is false, the x offsets are in the opposite
0985                 // direction i.e., [0] == biggest offset, [1] = next
0986                 x = range->viewLine(viewLine).lineLayout().cursorToX(range->length() - column) - xStart;
0987             }
0988             int textLength = range->length();
0989             if (column == 0 || column < textLength) {
0990                 // If the note is inside text or at the beginning, then there is a hole in the text where the
0991                 // note should be painted and the cursor gets placed at the right side of it. So we have to
0992                 // subtract the width of the note to get to left side of the hole.
0993                 x -= inlineNote.width();
0994             } else {
0995                 // If the note is outside the text, then the X coordinate is located at the end of the line.
0996                 // Add appropriate amount of spaces to reach the required column.
0997                 const auto spaceToAdd = spaceWidth() * (column - textLength);
0998                 x += isRTL ? -spaceToAdd : spaceToAdd;
0999             }
1000 
1001             qreal y = lineHeight() * viewLine;
1002 
1003             // Paint the note
1004             paint.save();
1005             paint.translate(x, y);
1006             inlineNote.provider()->paintInlineNote(inlineNote, paint, isRTL ? Qt::RightToLeft : Qt::LeftToRight);
1007             paint.restore();
1008         }
1009     }
1010 }
1011 
1012 static void drawCursor(const QTextLayout &layout, QPainter *p, const QPointF &pos, int cursorPosition, int width, const int height)
1013 {
1014     cursorPosition = qBound(0, cursorPosition, layout.text().length());
1015     const QTextLine l = layout.lineForTextPosition(cursorPosition);
1016     Q_ASSERT(l.isValid());
1017     if (!l.isValid()) {
1018         return;
1019     }
1020     const QPainter::CompositionMode origCompositionMode = p->compositionMode();
1021     if (p->paintEngine()->hasFeature(QPaintEngine::RasterOpModes)) {
1022         p->setCompositionMode(QPainter::RasterOp_NotDestination);
1023     }
1024 
1025     const QPointF position = pos + layout.position();
1026     const qreal x = position.x() + l.cursorToX(cursorPosition);
1027     const qreal y = l.lineNumber() * height;
1028     p->fillRect(QRectF(x, y, (qreal)width, (qreal)height), p->pen().brush());
1029     p->setCompositionMode(origCompositionMode);
1030 }
1031 
1032 void KateRenderer::paintCaret(KTextEditor::Cursor cursor, KateLineLayout *range, QPainter &paint, int xStart, int xEnd)
1033 {
1034     if (range->includesCursor(cursor)) {
1035         int caretWidth;
1036         int lineWidth = 2;
1037         QColor color;
1038         QTextLine line = range->layout()->lineForTextPosition(qMin(cursor.column(), range->length()));
1039 
1040         // Determine the caret's style
1041         KTextEditor::caretStyles style = caretStyle();
1042 
1043         // Make the caret the desired width
1044         if (style == KTextEditor::caretStyles::Line) {
1045             caretWidth = lineWidth;
1046         } else if (line.isValid() && cursor.column() < range->length()) {
1047             caretWidth = int(line.cursorToX(cursor.column() + 1) - line.cursorToX(cursor.column()));
1048             if (caretWidth < 0) {
1049                 caretWidth = -caretWidth;
1050             }
1051         } else {
1052             caretWidth = spaceWidth();
1053         }
1054 
1055         // Determine the color
1056         if (m_caretOverrideColor.isValid()) {
1057             // Could actually use the real highlighting system for this...
1058             // would be slower, but more accurate for corner cases
1059             color = m_caretOverrideColor;
1060         } else {
1061             // search for the FormatRange that includes the cursor
1062             const auto formatRanges = range->layout()->formats();
1063             for (const QTextLayout::FormatRange &r : formatRanges) {
1064                 if ((r.start <= cursor.column()) && ((r.start + r.length) > cursor.column())) {
1065                     // check for Qt::NoBrush, as the returned color is black() and no invalid QColor
1066                     QBrush foregroundBrush = r.format.foreground();
1067                     if (foregroundBrush != Qt::NoBrush) {
1068                         color = r.format.foreground().color();
1069                     }
1070                     break;
1071                 }
1072             }
1073             // still no color found, fall back to default style
1074             if (!color.isValid()) {
1075                 color = attribute(KSyntaxHighlighting::Theme::TextStyle::Normal)->foreground().color();
1076             }
1077         }
1078 
1079         paint.save();
1080         switch (style) {
1081         case KTextEditor::caretStyles::Line:
1082             paint.setPen(QPen(color, caretWidth));
1083             break;
1084         case KTextEditor::caretStyles::Block:
1085             // use a gray caret so it's possible to see the character
1086             color.setAlpha(128);
1087             paint.setPen(QPen(color, caretWidth));
1088             break;
1089         case KTextEditor::caretStyles::Underline:
1090             break;
1091         case KTextEditor::caretStyles::Half:
1092             color.setAlpha(128);
1093             paint.setPen(QPen(color, caretWidth));
1094             break;
1095         }
1096 
1097         if (cursor.column() <= range->length()) {
1098             // Ensure correct cursor placement for RTL text
1099             if (range->layout()->textOption().textDirection() == Qt::RightToLeft) {
1100                 xStart += caretWidth;
1101             }
1102             qreal width = 0;
1103             const auto inlineNotes = m_view->inlineNotes(range->line());
1104             for (const auto &inlineNoteData : inlineNotes) {
1105                 KTextEditor::InlineNote inlineNote(inlineNoteData);
1106                 if (inlineNote.position().column() == cursor.column()) {
1107                     width = inlineNote.width() + (caretStyle() == KTextEditor::caretStyles::Line ? 2.0 : 0.0);
1108                 }
1109             }
1110             drawCursor(*range->layout(), &paint, QPoint(-xStart - width, 0), cursor.column(), caretWidth, lineHeight());
1111         } else {
1112             // Off the end of the line... must be block mode. Draw the caret ourselves.
1113             const KateTextLayout &lastLine = range->viewLine(range->viewLineCount() - 1);
1114             int x = cursorToX(lastLine, KTextEditor::Cursor(range->line(), cursor.column()), true);
1115             if ((x >= xStart) && (x <= xEnd)) {
1116                 paint.fillRect(x - xStart, (int)lastLine.lineLayout().y(), caretWidth, lineHeight(), color);
1117             }
1118         }
1119 
1120         paint.restore();
1121     }
1122 }
1123 
1124 uint KateRenderer::fontHeight() const
1125 {
1126     return m_fontHeight;
1127 }
1128 
1129 uint KateRenderer::documentHeight() const
1130 {
1131     return m_doc->lines() * lineHeight();
1132 }
1133 
1134 int KateRenderer::lineHeight() const
1135 {
1136     return fontHeight();
1137 }
1138 
1139 bool KateRenderer::getSelectionBounds(int line, int lineLength, int &start, int &end) const
1140 {
1141     bool hasSel = false;
1142 
1143     if (m_view->selection() && !m_view->blockSelection()) {
1144         if (m_view->lineIsSelection(line)) {
1145             start = m_view->selectionRange().start().column();
1146             end = m_view->selectionRange().end().column();
1147             hasSel = true;
1148         } else if (line == m_view->selectionRange().start().line()) {
1149             start = m_view->selectionRange().start().column();
1150             end = lineLength;
1151             hasSel = true;
1152         } else if (m_view->selectionRange().containsLine(line)) {
1153             start = 0;
1154             end = lineLength;
1155             hasSel = true;
1156         } else if (line == m_view->selectionRange().end().line()) {
1157             start = 0;
1158             end = m_view->selectionRange().end().column();
1159             hasSel = true;
1160         }
1161     } else if (m_view->lineHasSelected(line)) {
1162         start = m_view->selectionRange().start().column();
1163         end = m_view->selectionRange().end().column();
1164         hasSel = true;
1165     }
1166 
1167     if (start > end) {
1168         int temp = end;
1169         end = start;
1170         start = temp;
1171     }
1172 
1173     return hasSel;
1174 }
1175 
1176 void KateRenderer::updateConfig()
1177 {
1178     // update the attribute list pointer
1179     updateAttributes();
1180 
1181     // update font height, do this before we update the view!
1182     updateFontHeight();
1183 
1184     // trigger view update, if any!
1185     if (m_view) {
1186         m_view->updateRendererConfig();
1187     }
1188 }
1189 
1190 bool KateRenderer::hasCustomLineHeight() const
1191 {
1192     return !qFuzzyCompare(config()->lineHeightMultiplier(), 1.0);
1193 }
1194 
1195 void KateRenderer::updateFontHeight()
1196 {
1197     // cache font + metrics
1198     m_font = config()->baseFont();
1199     m_fontMetrics = QFontMetricsF(m_font);
1200 
1201     // ensure minimal height of one pixel to not fall in the div by 0 trap somewhere
1202     //
1203     // use a line spacing that matches the code in qt to layout/paint text
1204     //
1205     // see bug 403868
1206     // https://github.com/qt/qtbase/blob/5.12/src/gui/text/qtextlayout.cpp (line 2270 at the moment) where the text height is set as:
1207     //
1208     // qreal height = maxY + fontHeight - minY;
1209     //
1210     // with fontHeight:
1211     //
1212     // qreal fontHeight = font.ascent() + font.descent();
1213     m_fontHeight = qMax(1, qCeil(m_fontMetrics.ascent() + m_fontMetrics.descent()));
1214     m_fontAscent = m_fontMetrics.ascent();
1215 
1216     if (hasCustomLineHeight()) {
1217         const auto oldFontHeight = m_fontHeight;
1218         const qreal newFontHeight = qreal(m_fontHeight) * config()->lineHeightMultiplier();
1219         m_fontHeight = newFontHeight;
1220 
1221         qreal diff = std::abs(oldFontHeight - newFontHeight);
1222         m_fontAscent += (diff / 2);
1223     }
1224 }
1225 
1226 void KateRenderer::updateMarkerSize()
1227 {
1228     // marker size will be calculated over the value defined
1229     // on dialog
1230 
1231     m_markerSize = spaceWidth() / (3.5 - (m_doc->config()->markerSize() * 0.5));
1232 }
1233 
1234 qreal KateRenderer::spaceWidth() const
1235 {
1236     return m_fontMetrics.horizontalAdvance(spaceChar);
1237 }
1238 
1239 void KateRenderer::layoutLine(KateLineLayout *lineLayout, int maxwidth, bool cacheLayout) const
1240 {
1241     // if maxwidth == -1 we have no wrap
1242 
1243     Kate::TextLine textLine = lineLayout->textLine();
1244 
1245     QTextLayout *l = lineLayout->layout();
1246     if (!l) {
1247         l = new QTextLayout(textLine.text(), m_font);
1248     } else {
1249         l->setText(textLine.text());
1250         l->setFont(m_font);
1251     }
1252 
1253     l->setCacheEnabled(cacheLayout);
1254 
1255     // Initial setup of the QTextLayout.
1256 
1257     // Tab width
1258     QTextOption opt;
1259     opt.setFlags(QTextOption::IncludeTrailingSpaces);
1260     opt.setTabStopDistance(m_tabWidth * m_fontMetrics.horizontalAdvance(spaceChar));
1261     if (m_view && m_view->config()->dynWrapAnywhere()) {
1262         opt.setWrapMode(QTextOption::WrapAnywhere);
1263     } else {
1264         opt.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
1265     }
1266 
1267     // Find the first strong character in the string.
1268     // If it is an RTL character, set the base layout direction of the string to RTL.
1269     //
1270     // See https://www.unicode.org/reports/tr9/#The_Paragraph_Level (Sections P2 & P3).
1271     // Qt's text renderer ("scribe") version 4.2 assumes a "higher-level protocol"
1272     // (such as KatePart) will specify the paragraph level, so it does not apply P2 & P3
1273     // by itself. If this ever change in Qt, the next code block could be removed.
1274     // -----
1275     // Only force RTL direction if dynWordWrap is on. Otherwise the view has infinite width
1276     // and the lines will never be forced RTL no matter what direction we set. The layout
1277     // can't force a line to the right if it doesn't know where the "right" is
1278     if (isLineRightToLeft(textLine.text()) || (view()->dynWordWrap() && view()->forceRTLDirection())) {
1279         opt.setAlignment(Qt::AlignRight);
1280         opt.setTextDirection(Qt::RightToLeft);
1281         // Must turn off this flag otherwise cursor placement
1282         // is totally broken.
1283         if (view()->config()->dynWordWrap()) {
1284             auto flags = opt.flags();
1285             flags.setFlag(QTextOption::IncludeTrailingSpaces, false);
1286             opt.setFlags(flags);
1287         }
1288     } else {
1289         opt.setAlignment(Qt::AlignLeft);
1290         opt.setTextDirection(Qt::LeftToRight);
1291     }
1292 
1293     l->setTextOption(opt);
1294 
1295     // Syntax highlighting, inbuilt and arbitrary
1296     QList<QTextLayout::FormatRange> decorations = decorationsForLine(textLine, lineLayout->line());
1297 
1298     // Qt works badly if you have RTL text and formats set on that text.
1299     // It will shape the text according to the given format ranges which
1300     // produces incorrect results as a letter in RTL can have a different
1301     // shape depending upon where in the word it resides. The resulting output
1302     // looks like: وقار vs وق ار, i.e, the ligature قا is broken into ق ا which
1303     // is really bad for readability
1304     if (opt.textDirection() == Qt::RightToLeft) {
1305         // We can fix this to a large extent here by using QGlyphRun etc, but for now
1306         // we only fix this for formats which have a background color and a foreground
1307         // color that is same as "dsNormal". Reasoning is that, it is unlikely that RTL
1308         // text will have a lot of cases where you have partially colored ligatures. BG
1309         // formats are different, you can easily have a format that covers a ligature partially
1310         // as a result of "Search" or "multiple cursor selection"
1311         QColor c = view()->theme().textColor(KSyntaxHighlighting::Theme::Normal);
1312         decorations.erase(std::remove_if(decorations.begin(),
1313                                          decorations.end(),
1314                                          [c](const QTextLayout::FormatRange &fr) {
1315                                              return fr.format.hasProperty(QTextFormat::BackgroundBrush)
1316                                                  && (fr.format.property(QTextFormat::ForegroundBrush).value<QBrush>().color() == c
1317                                                      || fr.format.foreground() == Qt::NoBrush);
1318                                          }),
1319                           decorations.end());
1320     }
1321 
1322     int firstLineOffset = 0;
1323 
1324     if (!isPrinterFriendly() && m_view) {
1325         const auto inlineNotes = m_view->inlineNotes(lineLayout->line());
1326         for (const KateInlineNoteData &noteData : inlineNotes) {
1327             const KTextEditor::InlineNote inlineNote(noteData);
1328             const int column = inlineNote.position().column();
1329             int width = inlineNote.width();
1330 
1331             // Make space for every inline note.
1332             // If it is on column 0 (at the beginning of the line), we must offset the first line.
1333             // If it is inside the text, we use absolute letter spacing to create space for it between the two letters.
1334             // If it is outside of the text, we don't have to make space for it.
1335             if (column == 0) {
1336                 firstLineOffset = width;
1337             } else if (column < l->text().length()) {
1338                 QTextCharFormat text_char_format;
1339                 const qreal caretWidth = caretStyle() == KTextEditor::caretStyles::Line ? 2.0 : 0.0;
1340                 text_char_format.setFontLetterSpacing(width + caretWidth);
1341                 text_char_format.setFontLetterSpacingType(QFont::AbsoluteSpacing);
1342                 decorations.append(QTextLayout::FormatRange{column - 1, 1, text_char_format});
1343             }
1344         }
1345     }
1346     l->setFormats(decorations);
1347 
1348     // Begin layouting
1349     l->beginLayout();
1350 
1351     int height = 0;
1352     int shiftX = 0;
1353 
1354     bool needShiftX = (maxwidth != -1) && m_view && (m_view->config()->dynWordWrapAlignIndent() > 0);
1355 
1356     while (true) {
1357         QTextLine line = l->createLine();
1358         if (!line.isValid()) {
1359             break;
1360         }
1361 
1362         if (maxwidth > 0) {
1363             line.setLineWidth(maxwidth);
1364         } else {
1365             line.setLineWidth(INT_MAX);
1366         }
1367 
1368         // we include the leading, this must match the ::updateFontHeight code!
1369         line.setLeadingIncluded(true);
1370 
1371         line.setPosition(QPoint(line.lineNumber() ? shiftX : firstLineOffset, height - line.ascent() + m_fontAscent));
1372 
1373         if (needShiftX && line.width() > 0) {
1374             needShiftX = false;
1375             // Determine x offset for subsequent-lines-of-paragraph indenting
1376             int pos = textLine.nextNonSpaceChar(0);
1377 
1378             if (pos > 0) {
1379                 shiftX = (int)line.cursorToX(pos);
1380             }
1381 
1382             // check for too deep shift value and limit if necessary
1383             if (m_view && shiftX > ((double)maxwidth / 100 * m_view->config()->dynWordWrapAlignIndent())) {
1384                 shiftX = 0;
1385             }
1386 
1387             // if shiftX > 0, the maxwidth has to adapted
1388             maxwidth -= shiftX;
1389 
1390             lineLayout->shiftX = shiftX;
1391         }
1392 
1393         height += lineHeight();
1394     }
1395 
1396     l->endLayout();
1397 
1398     lineLayout->setLayout(l);
1399 }
1400 
1401 // 1) QString::isRightToLeft() sux
1402 // 2) QString::isRightToLeft() is marked as internal (WTF?)
1403 // 3) QString::isRightToLeft() does not seem to work on my setup
1404 // 4) isStringRightToLeft() should behave much better than QString::isRightToLeft() therefore:
1405 // 5) isStringRightToLeft() kicks ass
1406 bool KateRenderer::isLineRightToLeft(QStringView str)
1407 {
1408     // borrowed from QString::updateProperties()
1409     for (auto c : str) {
1410         switch (c.direction()) {
1411         case QChar::DirL:
1412         case QChar::DirLRO:
1413         case QChar::DirLRE:
1414             return false;
1415 
1416         case QChar::DirR:
1417         case QChar::DirAL:
1418         case QChar::DirRLO:
1419         case QChar::DirRLE:
1420             return true;
1421 
1422         default:
1423             break;
1424         }
1425     }
1426 
1427     return false;
1428 #if 0
1429     // or should we use the direction of the widget?
1430     QWidget *display = qobject_cast<QWidget *>(view()->parent());
1431     if (!display) {
1432         return false;
1433     }
1434     return display->layoutDirection() == Qt::RightToLeft;
1435 #endif
1436 }
1437 
1438 int KateRenderer::cursorToX(const KateTextLayout &range, int col, bool returnPastLine) const
1439 {
1440     return cursorToX(range, KTextEditor::Cursor(range.line(), col), returnPastLine);
1441 }
1442 
1443 int KateRenderer::cursorToX(const KateTextLayout &range, const KTextEditor::Cursor pos, bool returnPastLine) const
1444 {
1445     Q_ASSERT(range.isValid());
1446 
1447     int x;
1448     if (range.lineLayout().width() > 0) {
1449         x = (int)range.lineLayout().cursorToX(pos.column());
1450     } else {
1451         x = 0;
1452     }
1453 
1454     int over = pos.column() - range.endCol();
1455     if (returnPastLine && over > 0) {
1456         x += over * spaceWidth();
1457     }
1458 
1459     return x;
1460 }
1461 
1462 KTextEditor::Cursor KateRenderer::xToCursor(const KateTextLayout &range, int x, bool returnPastLine) const
1463 {
1464     Q_ASSERT(range.isValid());
1465     KTextEditor::Cursor ret(range.line(), range.lineLayout().xToCursor(x));
1466 
1467     // Do not wrap to the next line. (bug #423253)
1468     if (range.wrap() && ret.column() >= range.endCol() && range.length() > 0) {
1469         ret.setColumn(range.endCol() - 1);
1470     }
1471     // TODO wrong for RTL lines?
1472     if (returnPastLine && range.endCol(true) == -1 && x > range.width() + range.xOffset()) {
1473         ret.setColumn(ret.column() + round((x - (range.width() + range.xOffset())) / spaceWidth()));
1474     }
1475 
1476     return ret;
1477 }
1478 
1479 void KateRenderer::setCaretOverrideColor(const QColor &color)
1480 {
1481     m_caretOverrideColor = color;
1482 }
1483 
1484 void KateRenderer::paintSelection(QPaintDevice *d, int startLine, int xStart, int endLine, int xEnd, qreal scale)
1485 {
1486     if (!d || scale < 0.0) {
1487         return;
1488     }
1489 
1490     const int lineHeight = std::max(1, this->lineHeight());
1491     QPainter paint(d);
1492     paint.scale(scale, scale);
1493 
1494     // clip out non selected parts of start / end line
1495     {
1496         QRect mainRect(0, 0, d->width(), d->height());
1497         QRegion main(mainRect);
1498         // start line
1499         QRect startRect(0, 0, xStart, lineHeight);
1500         QRegion startRegion(startRect);
1501         // end line
1502         QRect endRect(mainRect.bottomLeft().x() + xEnd, mainRect.bottomRight().y() - lineHeight, mainRect.width() - xEnd, lineHeight);
1503         QRegion drawRegion = main.subtracted(startRegion).subtracted(QRegion(endRect));
1504         paint.setClipRegion(drawRegion);
1505     }
1506 
1507     for (int line = startLine; line <= endLine; ++line) {
1508         // get real line, skip if invalid!
1509         if (line < 0 || line >= doc()->lines()) {
1510             continue;
1511         }
1512 
1513         // compute layout WITHOUT cache to not poison it + render it
1514         KateLineLayout lineLayout(*this);
1515         lineLayout.setLine(line, -1);
1516         layoutLine(&lineLayout, -1 /* no wrap */, false /* no layout cache */);
1517         KateRenderer::PaintTextLineFlags flags;
1518         flags.setFlag(KateRenderer::SkipDrawFirstInvisibleLineUnderlined);
1519         flags.setFlag(KateRenderer::SkipDrawLineSelection);
1520         paintTextLine(paint, &lineLayout, 0, 0, QRectF{}, nullptr, flags);
1521 
1522         // translate for next line
1523         paint.translate(0, lineHeight);
1524     }
1525 }