File indexing completed on 2024-04-28 11:45:13

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