1// Scintilla source code edit control
2/** @file EditView.cxx
3 ** Defines the appearance of the main text area of the editor window.
4 **/
5// Copyright 1998-2014 by Neil Hodgson <neilh@scintilla.org>
6// The License.txt file describes the conditions under which this software may be distributed.
7
8#include <cstddef>
9#include <cstdlib>
10#include <cstdint>
11#include <cassert>
12#include <cstring>
13#include <cstdio>
14#include <cmath>
15
16#include <stdexcept>
17#include <string>
18#include <string_view>
19#include <vector>
20#include <map>
21#include <set>
22#include <forward_list>
23#include <optional>
24#include <algorithm>
25#include <iterator>
26#include <memory>
27#include <chrono>
28
29#include "ScintillaTypes.h"
30#include "ScintillaMessages.h"
31#include "ScintillaStructures.h"
32#include "ILoader.h"
33#include "ILexer.h"
34
35#include "Debugging.h"
36#include "Geometry.h"
37#include "Platform.h"
38
39#include "CharacterType.h"
40#include "CharacterCategoryMap.h"
41#include "Position.h"
42#include "UniqueString.h"
43#include "SplitVector.h"
44#include "Partitioning.h"
45#include "RunStyles.h"
46#include "ContractionState.h"
47#include "CellBuffer.h"
48#include "PerLine.h"
49#include "KeyMap.h"
50#include "Indicator.h"
51#include "LineMarker.h"
52#include "Style.h"
53#include "ViewStyle.h"
54#include "CharClassify.h"
55#include "Decoration.h"
56#include "CaseFolder.h"
57#include "Document.h"
58#include "UniConversion.h"
59#include "Selection.h"
60#include "PositionCache.h"
61#include "EditModel.h"
62#include "MarginView.h"
63#include "EditView.h"
64#include "ElapsedPeriod.h"
65
66using namespace Scintilla;
67using namespace Scintilla::Internal;
68
69PrintParameters::PrintParameters() noexcept {
70 magnification = 0;
71 colourMode = PrintOption::Normal;
72 wrapState = Wrap::Word;
73}
74
75namespace Scintilla::Internal {
76
77bool ValidStyledText(const ViewStyle &vs, size_t styleOffset, const StyledText &st) noexcept {
78 if (st.multipleStyles) {
79 for (size_t iStyle = 0; iStyle<st.length; iStyle++) {
80 if (!vs.ValidStyle(styleOffset + st.styles[iStyle]))
81 return false;
82 }
83 } else {
84 if (!vs.ValidStyle(styleOffset + st.style))
85 return false;
86 }
87 return true;
88}
89
90static int WidthStyledText(Surface *surface, const ViewStyle &vs, int styleOffset,
91 const char *text, const unsigned char *styles, size_t len) {
92 int width = 0;
93 size_t start = 0;
94 while (start < len) {
95 const unsigned char style = styles[start];
96 size_t endSegment = start;
97 while ((endSegment + 1 < len) && (styles[endSegment + 1] == style))
98 endSegment++;
99 const Font *fontText = vs.styles[style + styleOffset].font.get();
100 const std::string_view sv(text + start, endSegment - start + 1);
101 width += static_cast<int>(surface->WidthText(fontText, sv));
102 start = endSegment + 1;
103 }
104 return width;
105}
106
107int WidestLineWidth(Surface *surface, const ViewStyle &vs, int styleOffset, const StyledText &st) {
108 int widthMax = 0;
109 size_t start = 0;
110 while (start < st.length) {
111 const size_t lenLine = st.LineLength(start);
112 int widthSubLine;
113 if (st.multipleStyles) {
114 widthSubLine = WidthStyledText(surface, vs, styleOffset, st.text + start, st.styles + start, lenLine);
115 } else {
116 const Font *fontText = vs.styles[styleOffset + st.style].font.get();
117 const std::string_view text(st.text + start, lenLine);
118 widthSubLine = static_cast<int>(surface->WidthText(fontText, text));
119 }
120 if (widthSubLine > widthMax)
121 widthMax = widthSubLine;
122 start += lenLine + 1;
123 }
124 return widthMax;
125}
126
127void DrawTextNoClipPhase(Surface *surface, PRectangle rc, const Style &style, XYPOSITION ybase,
128 std::string_view text, DrawPhase phase) {
129 const Font *fontText = style.font.get();
130 if (FlagSet(phase, DrawPhase::back)) {
131 if (FlagSet(phase, DrawPhase::text)) {
132 // Drawing both
133 surface->DrawTextNoClip(rc, fontText, ybase, text,
134 style.fore, style.back);
135 } else {
136 surface->FillRectangleAligned(rc, Fill(style.back));
137 }
138 } else if (FlagSet(phase, DrawPhase::text)) {
139 surface->DrawTextTransparent(rc, fontText, ybase, text, style.fore);
140 }
141}
142
143void DrawStyledText(Surface *surface, const ViewStyle &vs, int styleOffset, PRectangle rcText,
144 const StyledText &st, size_t start, size_t length, DrawPhase phase) {
145
146 if (st.multipleStyles) {
147 int x = static_cast<int>(rcText.left);
148 size_t i = 0;
149 while (i < length) {
150 size_t end = i;
151 size_t style = st.styles[i + start];
152 while (end < length - 1 && st.styles[start + end + 1] == style)
153 end++;
154 style += styleOffset;
155 const Font *fontText = vs.styles[style].font.get();
156 const std::string_view text(st.text + start + i, end - i + 1);
157 const int width = static_cast<int>(surface->WidthText(fontText, text));
158 PRectangle rcSegment = rcText;
159 rcSegment.left = static_cast<XYPOSITION>(x);
160 rcSegment.right = static_cast<XYPOSITION>(x + width + 1);
161 DrawTextNoClipPhase(surface, rcSegment, vs.styles[style],
162 rcText.top + vs.maxAscent, text, phase);
163 x += width;
164 i = end + 1;
165 }
166 } else {
167 const size_t style = st.style + styleOffset;
168 DrawTextNoClipPhase(surface, rcText, vs.styles[style],
169 rcText.top + vs.maxAscent,
170 std::string_view(st.text + start, length), phase);
171 }
172}
173
174void Hexits(char *hexits, int ch) noexcept {
175 hexits[0] = 'x';
176 hexits[1] = "0123456789ABCDEF"[ch / 0x10];
177 hexits[2] = "0123456789ABCDEF"[ch % 0x10];
178 hexits[3] = 0;
179}
180
181}
182
183EditView::EditView() {
184 tabWidthMinimumPixels = 2; // needed for calculating tab stops for fractional proportional fonts
185 hideSelection = false;
186 drawOverstrikeCaret = true;
187 bufferedDraw = true;
188 phasesDraw = PhasesDraw::Two;
189 lineWidthMaxSeen = 0;
190 additionalCaretsBlink = true;
191 additionalCaretsVisible = true;
192 imeCaretBlockOverride = false;
193 llc.SetLevel(LineCache::Caret);
194 posCache.SetSize(0x400);
195 tabArrowHeight = 4;
196 customDrawTabArrow = nullptr;
197 customDrawWrapMarker = nullptr;
198}
199
200EditView::~EditView() = default;
201
202bool EditView::SetTwoPhaseDraw(bool twoPhaseDraw) noexcept {
203 const PhasesDraw phasesDrawNew = twoPhaseDraw ? PhasesDraw::Two : PhasesDraw::One;
204 const bool redraw = phasesDraw != phasesDrawNew;
205 phasesDraw = phasesDrawNew;
206 return redraw;
207}
208
209bool EditView::SetPhasesDraw(int phases) noexcept {
210 const PhasesDraw phasesDrawNew = static_cast<PhasesDraw>(phases);
211 const bool redraw = phasesDraw != phasesDrawNew;
212 phasesDraw = phasesDrawNew;
213 return redraw;
214}
215
216bool EditView::LinesOverlap() const noexcept {
217 return phasesDraw == PhasesDraw::Multiple;
218}
219
220void EditView::ClearAllTabstops() noexcept {
221 ldTabstops.reset();
222}
223
224XYPOSITION EditView::NextTabstopPos(Sci::Line line, XYPOSITION x, XYPOSITION tabWidth) const noexcept {
225 const int next = GetNextTabstop(line, static_cast<int>(x + tabWidthMinimumPixels));
226 if (next > 0)
227 return static_cast<XYPOSITION>(next);
228 return (static_cast<int>((x + tabWidthMinimumPixels) / tabWidth) + 1) * tabWidth;
229}
230
231bool EditView::ClearTabstops(Sci::Line line) noexcept {
232 return ldTabstops && ldTabstops->ClearTabstops(line);
233}
234
235bool EditView::AddTabstop(Sci::Line line, int x) {
236 if (!ldTabstops) {
237 ldTabstops = std::make_unique<LineTabstops>();
238 }
239 return ldTabstops && ldTabstops->AddTabstop(line, x);
240}
241
242int EditView::GetNextTabstop(Sci::Line line, int x) const noexcept {
243 if (ldTabstops) {
244 return ldTabstops->GetNextTabstop(line, x);
245 } else {
246 return 0;
247 }
248}
249
250void EditView::LinesAddedOrRemoved(Sci::Line lineOfPos, Sci::Line linesAdded) {
251 if (ldTabstops) {
252 if (linesAdded > 0) {
253 for (Sci::Line line = lineOfPos; line < lineOfPos + linesAdded; line++) {
254 ldTabstops->InsertLine(line);
255 }
256 } else {
257 for (Sci::Line line = (lineOfPos + -linesAdded) - 1; line >= lineOfPos; line--) {
258 ldTabstops->RemoveLine(line);
259 }
260 }
261 }
262}
263
264void EditView::DropGraphics() noexcept {
265 pixmapLine.reset();
266 pixmapIndentGuide.reset();
267 pixmapIndentGuideHighlight.reset();
268}
269
270static const char *ControlCharacterString(unsigned char ch) noexcept {
271 const char * const reps[] = {
272 "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
273 "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI",
274 "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
275 "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US"
276 };
277 if (ch < std::size(reps)) {
278 return reps[ch];
279 } else {
280 return "BAD";
281 }
282}
283
284static void DrawTabArrow(Surface *surface, PRectangle rcTab, int ymid,
285 const ViewStyle &vsDraw, Stroke stroke) {
286
287 const XYPOSITION halfWidth = stroke.width / 2.0;
288
289 const XYPOSITION leftStroke = std::round(std::min(rcTab.left + 2, rcTab.right - 1)) + halfWidth;
290 const XYPOSITION rightStroke = std::max(leftStroke, std::round(rcTab.right) - 1.0f - halfWidth);
291 const XYPOSITION yMidAligned = ymid + halfWidth;
292 const Point arrowPoint(rightStroke, yMidAligned);
293 if (rightStroke > leftStroke) {
294 // When not enough room, don't draw the arrow shaft
295 surface->LineDraw(Point(leftStroke, yMidAligned), arrowPoint, stroke);
296 }
297
298 // Draw the arrow head if needed
299 if (vsDraw.tabDrawMode == TabDrawMode::LongArrow) {
300 XYPOSITION ydiff = std::floor(rcTab.Height() / 2.0f);
301 XYPOSITION xhead = rightStroke - ydiff;
302 if (xhead <= rcTab.left) {
303 ydiff -= rcTab.left - xhead;
304 xhead = rcTab.left;
305 }
306 const Point ptsHead[] = {
307 Point(xhead, yMidAligned - ydiff),
308 arrowPoint,
309 Point(xhead, yMidAligned + ydiff)
310 };
311 surface->PolyLine(ptsHead, std::size(ptsHead), stroke);
312 }
313}
314
315void EditView::RefreshPixMaps(Surface *surfaceWindow, const ViewStyle &vsDraw) {
316 if (!pixmapIndentGuide) {
317 // 1 extra pixel in height so can handle odd/even positions and so produce a continuous line
318 pixmapIndentGuide = surfaceWindow->AllocatePixMap(1, vsDraw.lineHeight + 1);
319 pixmapIndentGuideHighlight = surfaceWindow->AllocatePixMap(1, vsDraw.lineHeight + 1);
320 const PRectangle rcIG = PRectangle::FromInts(0, 0, 1, vsDraw.lineHeight);
321 pixmapIndentGuide->FillRectangle(rcIG, vsDraw.styles[StyleIndentGuide].back);
322 pixmapIndentGuideHighlight->FillRectangle(rcIG, vsDraw.styles[StyleBraceLight].back);
323 for (int stripe = 1; stripe < vsDraw.lineHeight + 1; stripe += 2) {
324 const PRectangle rcPixel = PRectangle::FromInts(0, stripe, 1, stripe + 1);
325 pixmapIndentGuide->FillRectangle(rcPixel, vsDraw.styles[StyleIndentGuide].fore);
326 pixmapIndentGuideHighlight->FillRectangle(rcPixel, vsDraw.styles[StyleBraceLight].fore);
327 }
328 pixmapIndentGuide->FlushDrawing();
329 pixmapIndentGuideHighlight->FlushDrawing();
330 }
331}
332
333std::shared_ptr<LineLayout> EditView::RetrieveLineLayout(Sci::Line lineNumber, const EditModel &model) {
334 const Sci::Position posLineStart = model.pdoc->LineStart(lineNumber);
335 const Sci::Position posLineEnd = model.pdoc->LineStart(lineNumber + 1);
336 PLATFORM_ASSERT(posLineEnd >= posLineStart);
337 const Sci::Line lineCaret = model.pdoc->SciLineFromPosition(model.sel.MainCaret());
338 return llc.Retrieve(lineNumber, lineCaret,
339 static_cast<int>(posLineEnd - posLineStart), model.pdoc->GetStyleClock(),
340 model.LinesOnScreen() + 1, model.pdoc->LinesTotal());
341}
342
343namespace {
344
345constexpr XYPOSITION epsilon = 0.0001f; // A small nudge to avoid floating point precision issues
346
347/**
348* Return the chDoc argument with case transformed as indicated by the caseForce argument.
349* chPrevious is needed for camel casing.
350* This only affects ASCII characters and is provided for languages with case-insensitive
351* ASCII keywords where the user wishes to view keywords in a preferred case.
352*/
353inline char CaseForce(Style::CaseForce caseForce, char chDoc, char chPrevious) noexcept {
354 switch (caseForce) {
355 case Style::CaseForce::mixed:
356 return chDoc;
357 case Style::CaseForce::lower:
358 return MakeLowerCase(chDoc);
359 case Style::CaseForce::upper:
360 return MakeUpperCase(chDoc);
361 case Style::CaseForce::camel:
362 default: // default should not occur, included to avoid warnings
363 if (IsUpperOrLowerCase(chDoc) && !IsUpperOrLowerCase(chPrevious)) {
364 return MakeUpperCase(chDoc);
365 } else {
366 return MakeLowerCase(chDoc);
367 }
368 }
369}
370
371bool ViewIsASCII(std::string_view text) {
372 return std::all_of(text.cbegin(), text.cend(), IsASCII);
373}
374
375}
376
377/**
378* Fill in the LineLayout data for the given line.
379* Copy the given @a line and its styles from the document into local arrays.
380* Also determine the x position at which each character starts.
381*/
382void EditView::LayoutLine(const EditModel &model, Surface *surface, const ViewStyle &vstyle, LineLayout *ll, int width) {
383 if (!ll)
384 return;
385
386 const Sci::Line line = ll->LineNumber();
387 PLATFORM_ASSERT(line < model.pdoc->LinesTotal());
388 PLATFORM_ASSERT(ll->chars);
389 const Sci::Position posLineStart = model.pdoc->LineStart(line);
390 Sci::Position posLineEnd = model.pdoc->LineStart(line + 1);
391 // If the line is very long, limit the treatment to a length that should fit in the viewport
392 if (posLineEnd >(posLineStart + ll->maxLineLength)) {
393 posLineEnd = posLineStart + ll->maxLineLength;
394 }
395 // Hard to cope when too narrow, so just assume there is space
396 width = std::max(width, 20);
397
398 if (ll->validity == LineLayout::ValidLevel::checkTextAndStyle) {
399 Sci::Position lineLength = posLineEnd - posLineStart;
400 if (!vstyle.viewEOL) {
401 lineLength = model.pdoc->LineEnd(line) - posLineStart;
402 }
403 if (lineLength == ll->numCharsInLine) {
404 // See if chars, styles, indicators, are all the same
405 bool allSame = true;
406 // Check base line layout
407 char chPrevious = 0;
408 for (Sci::Position numCharsInLine = 0; numCharsInLine < lineLength; numCharsInLine++) {
409 const Sci::Position charInDoc = numCharsInLine + posLineStart;
410 const char chDoc = model.pdoc->CharAt(charInDoc);
411 const int styleByte = model.pdoc->StyleIndexAt(charInDoc);
412 allSame = allSame &&
413 (ll->styles[numCharsInLine] == styleByte);
414 allSame = allSame &&
415 (ll->chars[numCharsInLine] == CaseForce(vstyle.styles[styleByte].caseForce, chDoc, chPrevious));
416 chPrevious = chDoc;
417 }
418 const int styleByteLast = (posLineEnd > posLineStart) ? model.pdoc->StyleIndexAt(posLineEnd - 1) : 0;
419 allSame = allSame && (ll->styles[lineLength] == styleByteLast); // For eolFilled
420 if (allSame) {
421 ll->validity = (ll->widthLine != width) ? LineLayout::ValidLevel::positions : LineLayout::ValidLevel::lines;
422 } else {
423 ll->validity = LineLayout::ValidLevel::invalid;
424 }
425 } else {
426 ll->validity = LineLayout::ValidLevel::invalid;
427 }
428 }
429 if (ll->validity == LineLayout::ValidLevel::invalid) {
430 ll->widthLine = LineLayout::wrapWidthInfinite;
431 ll->lines = 1;
432 if (vstyle.edgeState == EdgeVisualStyle::Background) {
433 Sci::Position edgePosition = model.pdoc->FindColumn(line, vstyle.theEdge.column);
434 if (edgePosition >= posLineStart) {
435 edgePosition -= posLineStart;
436 }
437 ll->edgeColumn = static_cast<int>(edgePosition);
438 } else {
439 ll->edgeColumn = -1;
440 }
441
442 // Fill base line layout
443 const int lineLength = static_cast<int>(posLineEnd - posLineStart);
444 model.pdoc->GetCharRange(ll->chars.get(), posLineStart, lineLength);
445 model.pdoc->GetStyleRange(ll->styles.get(), posLineStart, lineLength);
446 const int numCharsBeforeEOL = static_cast<int>(model.pdoc->LineEnd(line) - posLineStart);
447 const int numCharsInLine = (vstyle.viewEOL) ? lineLength : numCharsBeforeEOL;
448 const unsigned char styleByteLast = (lineLength > 0) ? ll->styles[lineLength - 1] : 0;
449 if (vstyle.someStylesForceCase) {
450 char chPrevious = 0;
451 for (int charInLine = 0; charInLine<lineLength; charInLine++) {
452 const char chDoc = ll->chars[charInLine];
453 ll->chars[charInLine] = CaseForce(vstyle.styles[ll->styles[charInLine]].caseForce, chDoc, chPrevious);
454 chPrevious = chDoc;
455 }
456 }
457 ll->xHighlightGuide = 0;
458 // Extra element at the end of the line to hold end x position and act as
459 ll->chars[numCharsInLine] = 0; // Also triggers processing in the loops as this is a control character
460 ll->styles[numCharsInLine] = styleByteLast; // For eolFilled
461
462 // Layout the line, determining the position of each character,
463 // with an extra element at the end for the end of the line.
464 ll->positions[0] = 0;
465 bool lastSegItalics = false;
466
467 BreakFinder bfLayout(ll, nullptr, Range(0, numCharsInLine), posLineStart, 0, BreakFinder::BreakFor::Text, model.pdoc, &model.reprs, nullptr);
468 while (bfLayout.More()) {
469
470 const TextSegment ts = bfLayout.Next();
471
472 std::fill(&ll->positions[ts.start + 1], &ll->positions[ts.end() + 1], 0.0f);
473 if (vstyle.styles[ll->styles[ts.start]].visible) {
474 if (ts.representation) {
475 XYPOSITION representationWidth = vstyle.controlCharWidth;
476 if (ll->chars[ts.start] == '\t') {
477 // Tab is a special case of representation, taking a variable amount of space
478 const XYPOSITION x = ll->positions[ts.start];
479 representationWidth = NextTabstopPos(line, x, vstyle.tabWidth) - ll->positions[ts.start];
480 } else {
481 if (representationWidth <= 0.0) {
482 assert(ts.representation->stringRep.length() <= Representation::maxLength);
483 XYPOSITION positionsRepr[Representation::maxLength+1];
484 // ts.representation->stringRep is UTF-8 which only matches cache if document is UTF-8
485 // or it only contains ASCII which is a subset of all currently supported encodings.
486 if ((CpUtf8 == model.pdoc->dbcsCodePage) || ViewIsASCII(ts.representation->stringRep)) {
487 posCache.MeasureWidths(surface, vstyle, StyleControlChar, ts.representation->stringRep,
488 positionsRepr);
489 } else {
490 surface->MeasureWidthsUTF8(vstyle.styles[StyleControlChar].font.get(), ts.representation->stringRep, positionsRepr);
491 }
492 representationWidth = positionsRepr[ts.representation->stringRep.length() - 1];
493 if (FlagSet(ts.representation->appearance, RepresentationAppearance::Blob)) {
494 representationWidth += vstyle.ctrlCharPadding;
495 }
496 }
497 }
498 for (int ii = 0; ii < ts.length; ii++)
499 ll->positions[ts.start + 1 + ii] = representationWidth;
500 } else {
501 if ((ts.length == 1) && (' ' == ll->chars[ts.start])) {
502 // Over half the segments are single characters and of these about half are space characters.
503 ll->positions[ts.start + 1] = vstyle.styles[ll->styles[ts.start]].spaceWidth;
504 } else {
505 posCache.MeasureWidths(surface, vstyle, ll->styles[ts.start],
506 std::string_view(&ll->chars[ts.start], ts.length), &ll->positions[ts.start + 1]);
507 }
508 }
509 lastSegItalics = (!ts.representation) && ((ll->chars[ts.end() - 1] != ' ') && vstyle.styles[ll->styles[ts.start]].italic);
510 }
511
512 for (Sci::Position posToIncrease = ts.start + 1; posToIncrease <= ts.end(); posToIncrease++) {
513 ll->positions[posToIncrease] += ll->positions[ts.start];
514 }
515 }
516
517 // Small hack to make lines that end with italics not cut off the edge of the last character
518 if (lastSegItalics) {
519 ll->positions[numCharsInLine] += vstyle.lastSegItalicsOffset;
520 }
521 ll->numCharsInLine = numCharsInLine;
522 ll->numCharsBeforeEOL = numCharsBeforeEOL;
523 ll->validity = LineLayout::ValidLevel::positions;
524 }
525 if ((ll->validity == LineLayout::ValidLevel::positions) || (ll->widthLine != width)) {
526 ll->widthLine = width;
527 if (width == LineLayout::wrapWidthInfinite) {
528 ll->lines = 1;
529 } else if (width > ll->positions[ll->numCharsInLine]) {
530 // Simple common case where line does not need wrapping.
531 ll->lines = 1;
532 } else {
533 if (FlagSet(vstyle.wrap.visualFlags, WrapVisualFlag::End)) {
534 width -= static_cast<int>(vstyle.aveCharWidth); // take into account the space for end wrap mark
535 }
536 XYPOSITION wrapAddIndent = 0; // This will be added to initial indent of line
537 switch (vstyle.wrap.indentMode) {
538 case WrapIndentMode::Fixed:
539 wrapAddIndent = vstyle.wrap.visualStartIndent * vstyle.aveCharWidth;
540 break;
541 case WrapIndentMode::Indent:
542 wrapAddIndent = model.pdoc->IndentSize() * vstyle.spaceWidth;
543 break;
544 case WrapIndentMode::DeepIndent:
545 wrapAddIndent = model.pdoc->IndentSize() * 2 * vstyle.spaceWidth;
546 break;
547 default: // No additional indent for WrapIndentMode::Fixed
548 break;
549 }
550 ll->wrapIndent = wrapAddIndent;
551 if (vstyle.wrap.indentMode != WrapIndentMode::Fixed) {
552 for (int i = 0; i < ll->numCharsInLine; i++) {
553 if (!IsSpaceOrTab(ll->chars[i])) {
554 ll->wrapIndent += ll->positions[i]; // Add line indent
555 break;
556 }
557 }
558 }
559 // Check for text width minimum
560 if (ll->wrapIndent > width - static_cast<int>(vstyle.aveCharWidth) * 15)
561 ll->wrapIndent = wrapAddIndent;
562 // Check for wrapIndent minimum
563 if ((FlagSet(vstyle.wrap.visualFlags, WrapVisualFlag::Start)) && (ll->wrapIndent < vstyle.aveCharWidth))
564 ll->wrapIndent = vstyle.aveCharWidth; // Indent to show start visual
565 ll->lines = 0;
566 // Calculate line start positions based upon width.
567 Sci::Position lastLineStart = 0;
568 XYACCUMULATOR startOffset = width;
569 Sci::Position p = 0;
570 const Wrap wrapState = vstyle.wrap.state;
571 const Sci::Position numCharsInLine = ll->numCharsInLine;
572 while (p < numCharsInLine) {
573 while (p < numCharsInLine && ll->positions[p + 1] < startOffset) {
574 p++;
575 }
576 if (p < numCharsInLine) {
577 // backtrack to find lastGoodBreak
578 Sci::Position lastGoodBreak = p;
579 if (p > 0) {
580 lastGoodBreak = model.pdoc->MovePositionOutsideChar(p + posLineStart, -1) - posLineStart;
581 }
582 if (wrapState != Wrap::Char) {
583 Sci::Position pos = lastGoodBreak;
584 while (pos > lastLineStart) {
585 // style boundary and space
586 if (wrapState != Wrap::WhiteSpace && (ll->styles[pos - 1] != ll->styles[pos])) {
587 break;
588 }
589 if (IsBreakSpace(ll->chars[pos - 1]) && !IsBreakSpace(ll->chars[pos])) {
590 break;
591 }
592 pos = model.pdoc->MovePositionOutsideChar(pos + posLineStart - 1, -1) - posLineStart;
593 }
594 if (pos > lastLineStart) {
595 lastGoodBreak = pos;
596 }
597 }
598 if (lastGoodBreak == lastLineStart) {
599 // Try moving to start of last character
600 if (p > 0) {
601 lastGoodBreak = model.pdoc->MovePositionOutsideChar(p + posLineStart, -1) - posLineStart;
602 }
603 if (lastGoodBreak == lastLineStart) {
604 // Ensure at least one character on line.
605 lastGoodBreak = model.pdoc->MovePositionOutsideChar(lastGoodBreak + posLineStart + 1, 1) - posLineStart;
606 }
607 }
608 lastLineStart = lastGoodBreak;
609 ll->lines++;
610 ll->SetLineStart(ll->lines, static_cast<int>(lastLineStart));
611 startOffset = ll->positions[lastLineStart];
612 // take into account the space for start wrap mark and indent
613 startOffset += width - ll->wrapIndent;
614 p = lastLineStart + 1;
615 }
616 }
617 ll->lines++;
618 }
619 ll->validity = LineLayout::ValidLevel::lines;
620 }
621}
622
623// Fill the LineLayout bidirectional data fields according to each char style
624
625void EditView::UpdateBidiData(const EditModel &model, const ViewStyle &vstyle, LineLayout *ll) {
626 if (model.BidirectionalEnabled()) {
627 ll->EnsureBidiData();
628 for (int stylesInLine = 0; stylesInLine < ll->numCharsInLine; stylesInLine++) {
629 ll->bidiData->stylesFonts[stylesInLine] = vstyle.styles[ll->styles[stylesInLine]].font;
630 }
631 ll->bidiData->stylesFonts[ll->numCharsInLine].reset();
632
633 for (int charsInLine = 0; charsInLine < ll->numCharsInLine; charsInLine++) {
634 const int charWidth = UTF8DrawBytes(reinterpret_cast<unsigned char *>(&ll->chars[charsInLine]), ll->numCharsInLine - charsInLine);
635 const Representation *repr = model.reprs.RepresentationFromCharacter(std::string_view(&ll->chars[charsInLine], charWidth));
636
637 ll->bidiData->widthReprs[charsInLine] = 0.0f;
638 if (repr && ll->chars[charsInLine] != '\t') {
639 ll->bidiData->widthReprs[charsInLine] = ll->positions[charsInLine + charWidth] - ll->positions[charsInLine];
640 }
641 if (charWidth > 1) {
642 for (int c = 1; c < charWidth; c++) {
643 charsInLine++;
644 ll->bidiData->widthReprs[charsInLine] = 0.0f;
645 }
646 }
647 }
648 ll->bidiData->widthReprs[ll->numCharsInLine] = 0.0f;
649 } else {
650 ll->bidiData.reset();
651 }
652}
653
654Point EditView::LocationFromPosition(Surface *surface, const EditModel &model, SelectionPosition pos, Sci::Line topLine,
655 const ViewStyle &vs, PointEnd pe, const PRectangle rcClient) {
656 Point pt;
657 if (pos.Position() == Sci::invalidPosition)
658 return pt;
659 Sci::Line lineDoc = model.pdoc->SciLineFromPosition(pos.Position());
660 Sci::Position posLineStart = model.pdoc->LineStart(lineDoc);
661 if (FlagSet(pe, PointEnd::lineEnd) && (lineDoc > 0) && (pos.Position() == posLineStart)) {
662 // Want point at end of first line
663 lineDoc--;
664 posLineStart = model.pdoc->LineStart(lineDoc);
665 }
666 const Sci::Line lineVisible = model.pcs->DisplayFromDoc(lineDoc);
667 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(lineDoc, model);
668 if (surface && ll) {
669 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
670 const int posInLine = static_cast<int>(pos.Position() - posLineStart);
671 pt = ll->PointFromPosition(posInLine, vs.lineHeight, pe);
672 pt.x += vs.textStart - model.xOffset;
673
674 if (model.BidirectionalEnabled()) {
675 // Fill the line bidi data
676 UpdateBidiData(model, vs, ll.get());
677
678 // Find subLine
679 const int subLine = ll->SubLineFromPosition(posInLine, pe);
680 const int caretPosition = posInLine - ll->LineStart(subLine);
681
682 // Get the point from current position
683 const ScreenLine screenLine(ll.get(), subLine, vs, rcClient.right, tabWidthMinimumPixels);
684 std::unique_ptr<IScreenLineLayout> slLayout = surface->Layout(&screenLine);
685 pt.x = slLayout->XFromPosition(caretPosition);
686
687 pt.x += vs.textStart - model.xOffset;
688
689 pt.y = 0;
690 if (posInLine >= ll->LineStart(subLine)) {
691 pt.y = static_cast<XYPOSITION>(subLine*vs.lineHeight);
692 }
693 }
694 pt.y += (lineVisible - topLine) * vs.lineHeight;
695 }
696 pt.x += pos.VirtualSpace() * vs.styles[ll->EndLineStyle()].spaceWidth;
697 return pt;
698}
699
700Range EditView::RangeDisplayLine(Surface *surface, const EditModel &model, Sci::Line lineVisible, const ViewStyle &vs) {
701 Range rangeSubLine = Range(0, 0);
702 if (lineVisible < 0) {
703 return rangeSubLine;
704 }
705 const Sci::Line lineDoc = model.pcs->DocFromDisplay(lineVisible);
706 const Sci::Position positionLineStart = model.pdoc->LineStart(lineDoc);
707 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(lineDoc, model);
708 if (surface && ll) {
709 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
710 const Sci::Line lineStartSet = model.pcs->DisplayFromDoc(lineDoc);
711 const int subLine = static_cast<int>(lineVisible - lineStartSet);
712 if (subLine < ll->lines) {
713 rangeSubLine = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly);
714 if (subLine == ll->lines-1) {
715 rangeSubLine.end = model.pdoc->LineStart(lineDoc + 1) -
716 positionLineStart;
717 }
718 }
719 }
720 rangeSubLine.start += positionLineStart;
721 rangeSubLine.end += positionLineStart;
722 return rangeSubLine;
723}
724
725SelectionPosition EditView::SPositionFromLocation(Surface *surface, const EditModel &model, PointDocument pt, bool canReturnInvalid,
726 bool charPosition, bool virtualSpace, const ViewStyle &vs, const PRectangle rcClient) {
727 pt.x = pt.x - vs.textStart;
728 Sci::Line visibleLine = static_cast<int>(std::floor(pt.y / vs.lineHeight));
729 if (!canReturnInvalid && (visibleLine < 0))
730 visibleLine = 0;
731 const Sci::Line lineDoc = model.pcs->DocFromDisplay(visibleLine);
732 if (canReturnInvalid && (lineDoc < 0))
733 return SelectionPosition(Sci::invalidPosition);
734 if (lineDoc >= model.pdoc->LinesTotal())
735 return SelectionPosition(canReturnInvalid ? Sci::invalidPosition :
736 model.pdoc->Length());
737 const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc);
738 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(lineDoc, model);
739 if (surface && ll) {
740 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
741 const Sci::Line lineStartSet = model.pcs->DisplayFromDoc(lineDoc);
742 const int subLine = static_cast<int>(visibleLine - lineStartSet);
743 if (subLine < ll->lines) {
744 const Range rangeSubLine = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly);
745 const XYPOSITION subLineStart = ll->positions[rangeSubLine.start];
746 if (subLine > 0) // Wrapped
747 pt.x -= ll->wrapIndent;
748 Sci::Position positionInLine = 0;
749 if (model.BidirectionalEnabled()) {
750 // Fill the line bidi data
751 UpdateBidiData(model, vs, ll.get());
752
753 const ScreenLine screenLine(ll.get(), subLine, vs, rcClient.right, tabWidthMinimumPixels);
754 std::unique_ptr<IScreenLineLayout> slLayout = surface->Layout(&screenLine);
755 positionInLine = slLayout->PositionFromX(static_cast<XYPOSITION>(pt.x), charPosition) +
756 rangeSubLine.start;
757 } else {
758 positionInLine = ll->FindPositionFromX(static_cast<XYPOSITION>(pt.x + subLineStart),
759 rangeSubLine, charPosition);
760 }
761 if (positionInLine < rangeSubLine.end) {
762 return SelectionPosition(model.pdoc->MovePositionOutsideChar(positionInLine + posLineStart, 1));
763 }
764 if (virtualSpace) {
765 const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth;
766 const int spaceOffset = static_cast<int>(
767 (pt.x + subLineStart - ll->positions[rangeSubLine.end] + spaceWidth / 2) / spaceWidth);
768 return SelectionPosition(rangeSubLine.end + posLineStart, spaceOffset);
769 } else if (canReturnInvalid) {
770 if (pt.x < (ll->positions[rangeSubLine.end] - subLineStart)) {
771 return SelectionPosition(model.pdoc->MovePositionOutsideChar(rangeSubLine.end + posLineStart, 1));
772 }
773 } else {
774 return SelectionPosition(rangeSubLine.end + posLineStart);
775 }
776 }
777 if (!canReturnInvalid)
778 return SelectionPosition(ll->numCharsInLine + posLineStart);
779 }
780 return SelectionPosition(canReturnInvalid ? Sci::invalidPosition : posLineStart);
781}
782
783/**
784* Find the document position corresponding to an x coordinate on a particular document line.
785* Ensure is between whole characters when document is in multi-byte or UTF-8 mode.
786* This method is used for rectangular selections and does not work on wrapped lines.
787*/
788SelectionPosition EditView::SPositionFromLineX(Surface *surface, const EditModel &model, Sci::Line lineDoc, int x, const ViewStyle &vs) {
789 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(lineDoc, model);
790 if (surface && ll) {
791 const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc);
792 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
793 const Range rangeSubLine = ll->SubLineRange(0, LineLayout::Scope::visibleOnly);
794 const XYPOSITION subLineStart = ll->positions[rangeSubLine.start];
795 const Sci::Position positionInLine = ll->FindPositionFromX(x + subLineStart, rangeSubLine, false);
796 if (positionInLine < rangeSubLine.end) {
797 return SelectionPosition(model.pdoc->MovePositionOutsideChar(positionInLine + posLineStart, 1));
798 }
799 const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth;
800 const int spaceOffset = static_cast<int>(
801 (x + subLineStart - ll->positions[rangeSubLine.end] + spaceWidth / 2) / spaceWidth);
802 return SelectionPosition(rangeSubLine.end + posLineStart, spaceOffset);
803 }
804 return SelectionPosition(0);
805}
806
807Sci::Line EditView::DisplayFromPosition(Surface *surface, const EditModel &model, Sci::Position pos, const ViewStyle &vs) {
808 const Sci::Line lineDoc = model.pdoc->SciLineFromPosition(pos);
809 Sci::Line lineDisplay = model.pcs->DisplayFromDoc(lineDoc);
810 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(lineDoc, model);
811 if (surface && ll) {
812 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
813 const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc);
814 const Sci::Position posInLine = pos - posLineStart;
815 lineDisplay--; // To make up for first increment ahead.
816 for (int subLine = 0; subLine < ll->lines; subLine++) {
817 if (posInLine >= ll->LineStart(subLine)) {
818 lineDisplay++;
819 }
820 }
821 }
822 return lineDisplay;
823}
824
825Sci::Position EditView::StartEndDisplayLine(Surface *surface, const EditModel &model, Sci::Position pos, bool start, const ViewStyle &vs) {
826 const Sci::Line line = model.pdoc->SciLineFromPosition(pos);
827 std::shared_ptr<LineLayout> ll = RetrieveLineLayout(line, model);
828 Sci::Position posRet = Sci::invalidPosition;
829 if (surface && ll) {
830 const Sci::Position posLineStart = model.pdoc->LineStart(line);
831 LayoutLine(model, surface, vs, ll.get(), model.wrapWidth);
832 const Sci::Position posInLine = pos - posLineStart;
833 if (posInLine <= ll->maxLineLength) {
834 for (int subLine = 0; subLine < ll->lines; subLine++) {
835 if ((posInLine >= ll->LineStart(subLine)) &&
836 (posInLine <= ll->LineStart(subLine + 1)) &&
837 (posInLine <= ll->numCharsBeforeEOL)) {
838 if (start) {
839 posRet = ll->LineStart(subLine) + posLineStart;
840 } else {
841 if (subLine == ll->lines - 1)
842 posRet = ll->numCharsBeforeEOL + posLineStart;
843 else
844 posRet = model.pdoc->MovePositionOutsideChar(ll->LineStart(subLine + 1) + posLineStart - 1, -1, false);
845 }
846 }
847 }
848 }
849 }
850 return posRet;
851}
852
853namespace {
854
855constexpr ColourRGBA bugColour = ColourRGBA(0xff, 0, 0xff, 0xf0);
856
857// Selection background colours are always defined, the value_or is to show if bug
858
859ColourRGBA SelectionBackground(const EditModel &model, const ViewStyle &vsDraw, InSelection inSelection) {
860 if (inSelection == InSelection::inNone)
861 return bugColour; // Not selected is a bug
862
863 Element element = Element::SelectionBack;
864 if (inSelection == InSelection::inAdditional)
865 element = Element::SelectionAdditionalBack;
866 if (!model.primarySelection)
867 element = Element::SelectionSecondaryBack;
868 if (!model.hasFocus && vsDraw.ElementColour(Element::SelectionInactiveBack))
869 element = Element::SelectionInactiveBack;
870 return vsDraw.ElementColour(element).value_or(bugColour);
871}
872
873std::optional<ColourRGBA> SelectionForeground(const EditModel &model, const ViewStyle &vsDraw, InSelection inSelection) {
874 if (inSelection == InSelection::inNone)
875 return {};
876 Element element = Element::SelectionText;
877 if (inSelection == InSelection::inAdditional)
878 element = Element::SelectionAdditionalText;
879 if (!model.primarySelection) // Secondary selection
880 element = Element::SelectionSecondaryText;
881 if (!model.hasFocus) {
882 if (vsDraw.ElementColour(Element::SelectionInactiveText)) {
883 element = Element::SelectionInactiveText;
884 } else {
885 return {};
886 }
887 }
888 return vsDraw.ElementColour(element);
889}
890
891}
892
893static ColourRGBA TextBackground(const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
894 std::optional<ColourRGBA> background, InSelection inSelection, bool inHotspot, int styleMain, Sci::Position i) {
895 if (inSelection && (vsDraw.selection.layer == Layer::Base)) {
896 return SelectionBackground(model, vsDraw, inSelection).Opaque();
897 }
898 if ((vsDraw.edgeState == EdgeVisualStyle::Background) &&
899 (i >= ll->edgeColumn) &&
900 (i < ll->numCharsBeforeEOL))
901 return vsDraw.theEdge.colour;
902 if (inHotspot && vsDraw.ElementColour(Element::HotSpotActiveBack))
903 return vsDraw.ElementColour(Element::HotSpotActiveBack)->Opaque();
904 if (background && (styleMain != StyleBraceLight) && (styleMain != StyleBraceBad)) {
905 return *background;
906 } else {
907 return vsDraw.styles[styleMain].back;
908 }
909}
910
911void EditView::DrawIndentGuide(Surface *surface, Sci::Line lineVisible, int lineHeight, XYPOSITION start, PRectangle rcSegment, bool highlight) {
912 const Point from = Point::FromInts(0, ((lineVisible & 1) && (lineHeight & 1)) ? 1 : 0);
913 const PRectangle rcCopyArea(start + 1, rcSegment.top,
914 start + 2, rcSegment.bottom);
915 surface->Copy(rcCopyArea, from,
916 highlight ? *pixmapIndentGuideHighlight : *pixmapIndentGuide);
917}
918
919static void DrawTextBlob(Surface *surface, const ViewStyle &vsDraw, PRectangle rcSegment,
920 std::string_view text, ColourRGBA textBack, ColourRGBA textFore, bool fillBackground) {
921 if (rcSegment.Empty())
922 return;
923 if (fillBackground) {
924 surface->FillRectangleAligned(rcSegment, Fill(textBack));
925 }
926 const Font *ctrlCharsFont = vsDraw.styles[StyleControlChar].font.get();
927 const int normalCharHeight = static_cast<int>(std::ceil(vsDraw.styles[StyleControlChar].capitalHeight));
928 PRectangle rcCChar = rcSegment;
929 rcCChar.left = rcCChar.left + 1;
930 rcCChar.top = rcSegment.top + vsDraw.maxAscent - normalCharHeight;
931 rcCChar.bottom = rcSegment.top + vsDraw.maxAscent + 1;
932 PRectangle rcCentral = rcCChar;
933 rcCentral.top++;
934 rcCentral.bottom--;
935 surface->FillRectangleAligned(rcCentral, Fill(textFore));
936 PRectangle rcChar = rcCChar;
937 rcChar.left++;
938 rcChar.right--;
939 surface->DrawTextClippedUTF8(rcChar, ctrlCharsFont,
940 rcSegment.top + vsDraw.maxAscent, text,
941 textBack, textFore);
942}
943
944static void DrawCaretLineFramed(Surface *surface, const ViewStyle &vsDraw, const LineLayout *ll, PRectangle rcLine, int subLine) {
945 const std::optional<ColourRGBA> caretlineBack = vsDraw.ElementColour(Element::CaretLineBack);
946 if (!caretlineBack) {
947 return;
948 }
949
950 const ColourRGBA colourFrame = (vsDraw.caretLine.layer == Layer::Base) ?
951 caretlineBack->Opaque() : *caretlineBack;
952
953 const int width = vsDraw.GetFrameWidth();
954
955 // Avoid double drawing the corners by removing the left and right sides when drawing top and bottom borders
956 const PRectangle rcWithoutLeftRight = rcLine.Inset(Point(width, 0.0));
957
958 if (subLine == 0 || ll->wrapIndent == 0 || vsDraw.caretLine.layer != Layer::Base || vsDraw.caretLine.subLine) {
959 // Left
960 surface->FillRectangleAligned(Side(rcLine, Edge::left, width), colourFrame);
961 }
962 if (subLine == 0 || vsDraw.caretLine.subLine) {
963 // Top
964 surface->FillRectangleAligned(Side(rcWithoutLeftRight, Edge::top, width), colourFrame);
965 }
966 if (subLine == ll->lines - 1 || vsDraw.caretLine.layer != Layer::Base || vsDraw.caretLine.subLine) {
967 // Right
968 surface->FillRectangleAligned(Side(rcLine, Edge::right, width), colourFrame);
969 }
970 if (subLine == ll->lines - 1 || vsDraw.caretLine.subLine) {
971 // Bottom
972 surface->FillRectangleAligned(Side(rcWithoutLeftRight, Edge::bottom, width), colourFrame);
973 }
974}
975
976void EditView::DrawEOL(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
977 PRectangle rcLine, Sci::Line line, Sci::Position lineEnd, int xStart, int subLine, XYACCUMULATOR subLineStart,
978 std::optional<ColourRGBA> background) {
979
980 const Sci::Position posLineStart = model.pdoc->LineStart(line);
981 PRectangle rcSegment = rcLine;
982
983 const bool lastSubLine = subLine == (ll->lines - 1);
984 XYPOSITION virtualSpace = 0;
985 if (lastSubLine) {
986 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
987 virtualSpace = model.sel.VirtualSpaceFor(model.pdoc->LineEnd(line)) * spaceWidth;
988 }
989 const XYPOSITION xEol = static_cast<XYPOSITION>(ll->positions[lineEnd] - subLineStart);
990
991 // Fill the virtual space and show selections within it
992 if (virtualSpace > 0.0f) {
993 rcSegment.left = xEol + xStart;
994 rcSegment.right = xEol + xStart + virtualSpace;
995 const ColourRGBA backgroundFill = background.value_or(vsDraw.styles[ll->styles[ll->numCharsInLine]].back);
996 surface->FillRectangleAligned(rcSegment, backgroundFill);
997 if (!hideSelection && (vsDraw.selection.layer == Layer::Base)) {
998 const SelectionSegment virtualSpaceRange(SelectionPosition(model.pdoc->LineEnd(line)),
999 SelectionPosition(model.pdoc->LineEnd(line),
1000 model.sel.VirtualSpaceFor(model.pdoc->LineEnd(line))));
1001 for (size_t r = 0; r<model.sel.Count(); r++) {
1002 const SelectionSegment portion = model.sel.Range(r).Intersect(virtualSpaceRange);
1003 if (!portion.Empty()) {
1004 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
1005 rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] -
1006 static_cast<XYPOSITION>(subLineStart)+portion.start.VirtualSpace() * spaceWidth;
1007 rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] -
1008 static_cast<XYPOSITION>(subLineStart)+portion.end.VirtualSpace() * spaceWidth;
1009 rcSegment.left = (rcSegment.left > rcLine.left) ? rcSegment.left : rcLine.left;
1010 rcSegment.right = (rcSegment.right < rcLine.right) ? rcSegment.right : rcLine.right;
1011 surface->FillRectangleAligned(rcSegment, Fill(
1012 SelectionBackground(model, vsDraw, model.sel.RangeType(r)).Opaque()));
1013 }
1014 }
1015 }
1016 }
1017
1018 InSelection eolInSelection = InSelection::inNone;
1019 if (!hideSelection && lastSubLine) {
1020 eolInSelection = model.LineEndInSelection(line);
1021 }
1022
1023 const ColourRGBA selectionBack = SelectionBackground(model, vsDraw, eolInSelection);
1024
1025 // Draw the [CR], [LF], or [CR][LF] blobs if visible line ends are on
1026 XYPOSITION blobsWidth = 0;
1027 if (lastSubLine) {
1028 for (Sci::Position eolPos = ll->numCharsBeforeEOL; eolPos<ll->numCharsInLine;) {
1029 const int styleMain = ll->styles[eolPos];
1030 const std::optional<ColourRGBA> selectionFore = SelectionForeground(model, vsDraw, eolInSelection);
1031 ColourRGBA textFore = selectionFore.value_or(vsDraw.styles[styleMain].fore);
1032 char hexits[4] = "";
1033 std::string_view ctrlChar;
1034 Sci::Position widthBytes = 1;
1035 RepresentationAppearance appearance = RepresentationAppearance::Blob;
1036 const Representation *repr = model.reprs.RepresentationFromCharacter(std::string_view(&ll->chars[eolPos], ll->numCharsInLine - eolPos));
1037 if (repr) {
1038 // Representation of whole text
1039 widthBytes = ll->numCharsInLine - eolPos;
1040 } else {
1041 repr = model.reprs.RepresentationFromCharacter(std::string_view(&ll->chars[eolPos], 1));
1042 }
1043 if (repr) {
1044 ctrlChar = repr->stringRep;
1045 appearance = repr->appearance;
1046 if (FlagSet(appearance, RepresentationAppearance::Colour)) {
1047 textFore = repr->colour;
1048 }
1049 } else {
1050 const unsigned char chEOL = ll->chars[eolPos];
1051 if (UTF8IsAscii(chEOL)) {
1052 ctrlChar = ControlCharacterString(chEOL);
1053 } else {
1054 Hexits(hexits, chEOL);
1055 ctrlChar = hexits;
1056 }
1057 }
1058
1059 rcSegment.left = xStart + ll->positions[eolPos] - static_cast<XYPOSITION>(subLineStart)+virtualSpace;
1060 rcSegment.right = xStart + ll->positions[eolPos + widthBytes] - static_cast<XYPOSITION>(subLineStart)+virtualSpace;
1061 blobsWidth += rcSegment.Width();
1062 const ColourRGBA textBack = TextBackground(model, vsDraw, ll, background, eolInSelection, false, styleMain, eolPos);
1063 if (eolInSelection && (line < model.pdoc->LinesTotal() - 1)) {
1064 if (vsDraw.selection.layer == Layer::Base) {
1065 surface->FillRectangleAligned(rcSegment, Fill(selectionBack.Opaque()));
1066 } else {
1067 surface->FillRectangleAligned(rcSegment, Fill(textBack));
1068 }
1069 } else {
1070 surface->FillRectangleAligned(rcSegment, Fill(textBack));
1071 }
1072 const bool drawEOLSelection = eolInSelection && (line < model.pdoc->LinesTotal() - 1);
1073 ColourRGBA blobText = textBack;
1074 if (drawEOLSelection && (vsDraw.selection.layer == Layer::UnderText)) {
1075 surface->FillRectangleAligned(rcSegment, selectionBack);
1076 blobText = textBack.MixedWith(selectionBack, selectionBack.GetAlphaComponent());
1077 }
1078 if (FlagSet(appearance, RepresentationAppearance::Blob)) {
1079 DrawTextBlob(surface, vsDraw, rcSegment, ctrlChar, blobText, textFore, phasesDraw == PhasesDraw::One);
1080 } else {
1081 surface->DrawTextTransparentUTF8(rcSegment, vsDraw.styles[StyleControlChar].font.get(),
1082 rcSegment.top + vsDraw.maxAscent, ctrlChar, textFore);
1083 }
1084 if (drawEOLSelection && (vsDraw.selection.layer == Layer::OverText)) {
1085 surface->FillRectangleAligned(rcSegment, selectionBack);
1086 }
1087 eolPos += widthBytes;
1088 }
1089 }
1090
1091 // Draw the eol-is-selected rectangle
1092 rcSegment.left = xEol + xStart + virtualSpace + blobsWidth;
1093 rcSegment.right = rcSegment.left + vsDraw.aveCharWidth;
1094
1095 if (eolInSelection && (line < model.pdoc->LinesTotal() - 1) && (vsDraw.selection.layer == Layer::Base)) {
1096 surface->FillRectangleAligned(rcSegment, Fill(selectionBack.Opaque()));
1097 } else {
1098 if (background) {
1099 surface->FillRectangleAligned(rcSegment, Fill(*background));
1100 } else if (line < model.pdoc->LinesTotal() - 1) {
1101 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.styles[ll->styles[ll->numCharsInLine]].back));
1102 } else if (vsDraw.styles[ll->styles[ll->numCharsInLine]].eolFilled) {
1103 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.styles[ll->styles[ll->numCharsInLine]].back));
1104 } else {
1105 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.styles[StyleDefault].back));
1106 }
1107 if (eolInSelection && (line < model.pdoc->LinesTotal() - 1) && (vsDraw.selection.layer != Layer::Base)) {
1108 surface->FillRectangleAligned(rcSegment, selectionBack);
1109 }
1110 }
1111
1112 rcSegment.left = rcSegment.right;
1113 if (rcSegment.left < rcLine.left)
1114 rcSegment.left = rcLine.left;
1115 rcSegment.right = rcLine.right;
1116
1117 const bool drawEOLAnnotationStyledText = (vsDraw.eolAnnotationVisible != EOLAnnotationVisible::Hidden) && model.pdoc->EOLAnnotationStyledText(line).text;
1118 const bool fillRemainder = (!lastSubLine || (!model.GetFoldDisplayText(line) && !drawEOLAnnotationStyledText));
1119 if (fillRemainder) {
1120 // Fill the remainder of the line
1121 FillLineRemainder(surface, model, vsDraw, ll, line, rcSegment, subLine);
1122 }
1123
1124 bool drawWrapMarkEnd = false;
1125
1126 if (subLine + 1 < ll->lines) {
1127 if (FlagSet(vsDraw.wrap.visualFlags, WrapVisualFlag::End)) {
1128 drawWrapMarkEnd = ll->LineStart(subLine + 1) != 0;
1129 }
1130 if (vsDraw.IsLineFrameOpaque(model.caret.active, ll->containsCaret)) {
1131 // Draw right of frame under marker
1132 surface->FillRectangleAligned(Side(rcLine, Edge::right, vsDraw.GetFrameWidth()),
1133 vsDraw.ElementColour(Element::CaretLineBack)->Opaque());
1134 }
1135 }
1136
1137 if (drawWrapMarkEnd) {
1138 PRectangle rcPlace = rcSegment;
1139
1140 if (FlagSet(vsDraw.wrap.visualFlagsLocation, WrapVisualLocation::EndByText)) {
1141 rcPlace.left = xEol + xStart + virtualSpace;
1142 rcPlace.right = rcPlace.left + vsDraw.aveCharWidth;
1143 } else {
1144 // rcLine is clipped to text area
1145 rcPlace.right = rcLine.right;
1146 rcPlace.left = rcPlace.right - vsDraw.aveCharWidth;
1147 }
1148 if (!customDrawWrapMarker) {
1149 DrawWrapMarker(surface, rcPlace, true, vsDraw.WrapColour());
1150 } else {
1151 customDrawWrapMarker(surface, rcPlace, true, vsDraw.WrapColour());
1152 }
1153 }
1154}
1155
1156static void DrawIndicator(int indicNum, Sci::Position startPos, Sci::Position endPos, Surface *surface, const ViewStyle &vsDraw,
1157 const LineLayout *ll, int xStart, PRectangle rcLine, Sci::Position secondCharacter, int subLine, Indicator::State state,
1158 int value, bool bidiEnabled, int tabWidthMinimumPixels) {
1159
1160 const XYPOSITION subLineStart = ll->positions[ll->LineStart(subLine)];
1161
1162 std::vector<PRectangle> rectangles;
1163
1164 const PRectangle rcIndic(
1165 ll->positions[startPos] + xStart - subLineStart,
1166 rcLine.top + vsDraw.maxAscent,
1167 ll->positions[endPos] + xStart - subLineStart,
1168 rcLine.top + vsDraw.maxAscent + 3);
1169
1170 if (bidiEnabled) {
1171 ScreenLine screenLine(ll, subLine, vsDraw, rcLine.right - xStart, tabWidthMinimumPixels);
1172 const Range lineRange = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly);
1173
1174 std::unique_ptr<IScreenLineLayout> slLayout = surface->Layout(&screenLine);
1175 std::vector<Interval> intervals = slLayout->FindRangeIntervals(
1176 startPos - lineRange.start, endPos - lineRange.start);
1177 for (const Interval &interval : intervals) {
1178 PRectangle rcInterval = rcIndic;
1179 rcInterval.left = interval.left + xStart;
1180 rcInterval.right = interval.right + xStart;
1181 rectangles.push_back(rcInterval);
1182 }
1183 } else {
1184 rectangles.push_back(rcIndic);
1185 }
1186
1187 for (const PRectangle &rc : rectangles) {
1188 PRectangle rcFirstCharacter = rc;
1189 // Allow full descent space for character indicators
1190 rcFirstCharacter.bottom = rcLine.top + vsDraw.maxAscent + vsDraw.maxDescent;
1191 if (secondCharacter >= 0) {
1192 rcFirstCharacter.right = ll->positions[secondCharacter] + xStart - subLineStart;
1193 } else {
1194 // Indicator continued from earlier line so make an empty box and don't draw
1195 rcFirstCharacter.right = rcFirstCharacter.left;
1196 }
1197 vsDraw.indicators[indicNum].Draw(surface, rc, rcLine, rcFirstCharacter, state, value);
1198 }
1199}
1200
1201static void DrawIndicators(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1202 Sci::Line line, int xStart, PRectangle rcLine, int subLine, Sci::Position lineEnd, bool under, int tabWidthMinimumPixels) {
1203 // Draw decorators
1204 const Sci::Position posLineStart = model.pdoc->LineStart(line);
1205 const Sci::Position lineStart = ll->LineStart(subLine);
1206 const Sci::Position posLineEnd = posLineStart + lineEnd;
1207
1208 for (const IDecoration *deco : model.pdoc->decorations->View()) {
1209 if (under == vsDraw.indicators[deco->Indicator()].under) {
1210 Sci::Position startPos = posLineStart + lineStart;
1211 if (!deco->ValueAt(startPos)) {
1212 startPos = deco->EndRun(startPos);
1213 }
1214 while ((startPos < posLineEnd) && (deco->ValueAt(startPos))) {
1215 const Range rangeRun(deco->StartRun(startPos), deco->EndRun(startPos));
1216 const Sci::Position endPos = std::min(rangeRun.end, posLineEnd);
1217 const bool hover = vsDraw.indicators[deco->Indicator()].IsDynamic() &&
1218 rangeRun.ContainsCharacter(model.hoverIndicatorPos);
1219 const int value = deco->ValueAt(startPos);
1220 const Indicator::State state = hover ? Indicator::State::hover : Indicator::State::normal;
1221 const Sci::Position posSecond = model.pdoc->MovePositionOutsideChar(rangeRun.First() + 1, 1);
1222 DrawIndicator(deco->Indicator(), startPos - posLineStart, endPos - posLineStart,
1223 surface, vsDraw, ll, xStart, rcLine, posSecond - posLineStart, subLine, state,
1224 value, model.BidirectionalEnabled(), tabWidthMinimumPixels);
1225 startPos = endPos;
1226 if (!deco->ValueAt(startPos)) {
1227 startPos = deco->EndRun(startPos);
1228 }
1229 }
1230 }
1231 }
1232
1233 // Use indicators to highlight matching braces
1234 if ((vsDraw.braceHighlightIndicatorSet && (model.bracesMatchStyle == StyleBraceLight)) ||
1235 (vsDraw.braceBadLightIndicatorSet && (model.bracesMatchStyle == StyleBraceBad))) {
1236 const int braceIndicator = (model.bracesMatchStyle == StyleBraceLight) ? vsDraw.braceHighlightIndicator : vsDraw.braceBadLightIndicator;
1237 if (under == vsDraw.indicators[braceIndicator].under) {
1238 const Range rangeLine(posLineStart + lineStart, posLineEnd);
1239 if (rangeLine.ContainsCharacter(model.braces[0])) {
1240 const Sci::Position braceOffset = model.braces[0] - posLineStart;
1241 if (braceOffset < ll->numCharsInLine) {
1242 const Sci::Position secondOffset = model.pdoc->MovePositionOutsideChar(model.braces[0] + 1, 1) - posLineStart;
1243 DrawIndicator(braceIndicator, braceOffset, braceOffset + 1, surface, vsDraw, ll, xStart, rcLine, secondOffset,
1244 subLine, Indicator::State::normal, 1, model.BidirectionalEnabled(), tabWidthMinimumPixels);
1245 }
1246 }
1247 if (rangeLine.ContainsCharacter(model.braces[1])) {
1248 const Sci::Position braceOffset = model.braces[1] - posLineStart;
1249 if (braceOffset < ll->numCharsInLine) {
1250 const Sci::Position secondOffset = model.pdoc->MovePositionOutsideChar(model.braces[1] + 1, 1) - posLineStart;
1251 DrawIndicator(braceIndicator, braceOffset, braceOffset + 1, surface, vsDraw, ll, xStart, rcLine, secondOffset,
1252 subLine, Indicator::State::normal, 1, model.BidirectionalEnabled(), tabWidthMinimumPixels);
1253 }
1254 }
1255 }
1256 }
1257}
1258
1259void EditView::DrawFoldDisplayText(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1260 Sci::Line line, int xStart, PRectangle rcLine, int subLine, XYACCUMULATOR subLineStart, DrawPhase phase) {
1261 const bool lastSubLine = subLine == (ll->lines - 1);
1262 if (!lastSubLine)
1263 return;
1264
1265 const char *text = model.GetFoldDisplayText(line);
1266 if (!text)
1267 return;
1268
1269 PRectangle rcSegment = rcLine;
1270 const std::string_view foldDisplayText(text);
1271 const Font *fontText = vsDraw.styles[StyleFoldDisplayText].font.get();
1272 const int widthFoldDisplayText = static_cast<int>(surface->WidthText(fontText, foldDisplayText));
1273
1274 InSelection eolInSelection = InSelection::inNone;
1275 if (!hideSelection) {
1276 eolInSelection = model.LineEndInSelection(line);
1277 }
1278
1279 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
1280 const XYPOSITION virtualSpace = model.sel.VirtualSpaceFor(
1281 model.pdoc->LineEnd(line)) * spaceWidth;
1282 rcSegment.left = xStart + static_cast<XYPOSITION>(ll->positions[ll->numCharsInLine] - subLineStart) + virtualSpace + vsDraw.aveCharWidth;
1283 rcSegment.right = rcSegment.left + static_cast<XYPOSITION>(widthFoldDisplayText);
1284
1285 const std::optional<ColourRGBA> background = vsDraw.Background(model.pdoc->GetMark(line), model.caret.active, ll->containsCaret);
1286 const std::optional<ColourRGBA> selectionFore = SelectionForeground(model, vsDraw, eolInSelection);
1287 const ColourRGBA textFore = selectionFore.value_or(vsDraw.styles[StyleFoldDisplayText].fore);
1288 const ColourRGBA textBack = TextBackground(model, vsDraw, ll, background, eolInSelection,
1289 false, StyleFoldDisplayText, -1);
1290
1291 if (model.trackLineWidth) {
1292 if (rcSegment.right + 1> lineWidthMaxSeen) {
1293 // Fold display text border drawn on rcSegment.right with width 1 is the last visible object of the line
1294 lineWidthMaxSeen = static_cast<int>(rcSegment.right + 1);
1295 }
1296 }
1297
1298 if (FlagSet(phase, DrawPhase::back)) {
1299 surface->FillRectangleAligned(rcSegment, Fill(textBack));
1300
1301 // Fill Remainder of the line
1302 PRectangle rcRemainder = rcSegment;
1303 rcRemainder.left = rcRemainder.right;
1304 if (rcRemainder.left < rcLine.left)
1305 rcRemainder.left = rcLine.left;
1306 rcRemainder.right = rcLine.right;
1307 FillLineRemainder(surface, model, vsDraw, ll, line, rcRemainder, subLine);
1308 }
1309
1310 if (FlagSet(phase, DrawPhase::text)) {
1311 if (phasesDraw != PhasesDraw::One) {
1312 surface->DrawTextTransparent(rcSegment, fontText,
1313 rcSegment.top + vsDraw.maxAscent, foldDisplayText,
1314 textFore);
1315 } else {
1316 surface->DrawTextNoClip(rcSegment, fontText,
1317 rcSegment.top + vsDraw.maxAscent, foldDisplayText,
1318 textFore, textBack);
1319 }
1320 }
1321
1322 if (FlagSet(phase, DrawPhase::indicatorsFore)) {
1323 if (model.foldDisplayTextStyle == FoldDisplayTextStyle::Boxed) {
1324 PRectangle rcBox = rcSegment;
1325 rcBox.left = std::round(rcSegment.left);
1326 rcBox.right = std::round(rcSegment.right);
1327 surface->RectangleFrame(rcBox, Stroke(textFore));
1328 }
1329 }
1330
1331 if (FlagSet(phase, DrawPhase::selectionTranslucent)) {
1332 if (eolInSelection && (line < model.pdoc->LinesTotal() - 1) && (vsDraw.selection.layer != Layer::Base)) {
1333 surface->FillRectangleAligned(rcSegment, SelectionBackground(model, vsDraw, eolInSelection));
1334 }
1335 }
1336}
1337
1338void EditView::DrawEOLAnnotationText(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll, Sci::Line line, int xStart, PRectangle rcLine, int subLine, XYACCUMULATOR subLineStart, DrawPhase phase) {
1339
1340 const bool lastSubLine = subLine == (ll->lines - 1);
1341 if (!lastSubLine)
1342 return;
1343
1344 if (vsDraw.eolAnnotationVisible == EOLAnnotationVisible::Hidden) {
1345 return;
1346 }
1347 const StyledText stEOLAnnotation = model.pdoc->EOLAnnotationStyledText(line);
1348 if (!stEOLAnnotation.text || !ValidStyledText(vsDraw, vsDraw.eolAnnotationStyleOffset, stEOLAnnotation)) {
1349 return;
1350 }
1351 const std::string_view eolAnnotationText(stEOLAnnotation.text, stEOLAnnotation.length);
1352 const size_t style = stEOLAnnotation.style + vsDraw.eolAnnotationStyleOffset;
1353
1354 PRectangle rcSegment = rcLine;
1355 const Font *fontText = vsDraw.styles[style].font.get();
1356
1357 const Surface::Ends ends = static_cast<Surface::Ends>(static_cast<int>(vsDraw.eolAnnotationVisible) & 0xff);
1358 const Surface::Ends leftSide = static_cast<Surface::Ends>(static_cast<int>(ends) & 0xf);
1359 const Surface::Ends rightSide = static_cast<Surface::Ends>(static_cast<int>(ends) & 0xf0);
1360
1361 XYPOSITION leftBoxSpace = 0;
1362 XYPOSITION rightBoxSpace = 0;
1363 if (vsDraw.eolAnnotationVisible >= EOLAnnotationVisible::Boxed) {
1364 leftBoxSpace = 1;
1365 rightBoxSpace = 1;
1366 if (vsDraw.eolAnnotationVisible != EOLAnnotationVisible::Boxed) {
1367 switch (leftSide) {
1368 case Surface::Ends::leftFlat:
1369 leftBoxSpace = 1;
1370 break;
1371 case Surface::Ends::leftAngle:
1372 leftBoxSpace = rcLine.Height() / 2.0;
1373 break;
1374 case Surface::Ends::semiCircles:
1375 default:
1376 leftBoxSpace = rcLine.Height() / 3.0;
1377 break;
1378 }
1379 switch (rightSide) {
1380 case Surface::Ends::rightFlat:
1381 rightBoxSpace = 1;
1382 break;
1383 case Surface::Ends::rightAngle:
1384 rightBoxSpace = rcLine.Height() / 2.0;
1385 break;
1386 case Surface::Ends::semiCircles:
1387 default:
1388 rightBoxSpace = rcLine.Height() / 3.0;
1389 break;
1390 }
1391 }
1392 }
1393 const int widthEOLAnnotationText = static_cast<int>(surface->WidthTextUTF8(fontText, eolAnnotationText) +
1394 leftBoxSpace + rightBoxSpace);
1395
1396 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
1397 const XYPOSITION virtualSpace = model.sel.VirtualSpaceFor(
1398 model.pdoc->LineEnd(line)) * spaceWidth;
1399 rcSegment.left = xStart +
1400 static_cast<XYPOSITION>(ll->positions[ll->numCharsInLine] - subLineStart)
1401 + virtualSpace + vsDraw.aveCharWidth;
1402
1403 const char *textFoldDisplay = model.GetFoldDisplayText(line);
1404 if (textFoldDisplay) {
1405 const std::string_view foldDisplayText(textFoldDisplay);
1406 rcSegment.left += (static_cast<int>(surface->WidthText(fontText, foldDisplayText)) + vsDraw.aveCharWidth);
1407 }
1408 rcSegment.right = rcSegment.left + static_cast<XYPOSITION>(widthEOLAnnotationText);
1409
1410 const std::optional<ColourRGBA> background = vsDraw.Background(model.pdoc->GetMark(line), model.caret.active, ll->containsCaret);
1411 const ColourRGBA textFore = vsDraw.styles[style].fore;
1412 const ColourRGBA textBack = TextBackground(model, vsDraw, ll, background, InSelection::inNone,
1413 false, static_cast<int>(style), -1);
1414
1415 if (model.trackLineWidth) {
1416 if (rcSegment.right + 1> lineWidthMaxSeen) {
1417 // EOL Annotation text border drawn on rcSegment.right with width 1 is the last visible object of the line
1418 lineWidthMaxSeen = static_cast<int>(rcSegment.right + 1);
1419 }
1420 }
1421
1422 if (FlagSet(phase, DrawPhase::back)) {
1423 // This fills in the whole remainder of the line even though
1424 // it may be double drawing. This is to allow stadiums with
1425 // curved or angled ends to have the area outside in the correct
1426 // background colour.
1427 PRectangle rcRemainder = rcSegment;
1428 rcRemainder.right = rcLine.right;
1429 FillLineRemainder(surface, model, vsDraw, ll, line, rcRemainder, subLine);
1430 }
1431
1432 PRectangle rcText = rcSegment;
1433 rcText.left += leftBoxSpace;
1434 rcText.right -= rightBoxSpace;
1435
1436 // For single phase drawing, draw the text then any box over it
1437 if (FlagSet(phase, DrawPhase::text)) {
1438 if (phasesDraw == PhasesDraw::One) {
1439 surface->DrawTextNoClipUTF8(rcText, fontText,
1440 rcText.top + vsDraw.maxAscent, eolAnnotationText,
1441 textFore, textBack);
1442 }
1443 }
1444
1445 // Draw any box or stadium shape
1446 if (FlagSet(phase, DrawPhase::indicatorsBack)) {
1447 if (vsDraw.eolAnnotationVisible >= EOLAnnotationVisible::Boxed) {
1448 PRectangle rcBox = rcSegment;
1449 rcBox.left = std::round(rcSegment.left);
1450 rcBox.right = std::round(rcSegment.right);
1451 if (vsDraw.eolAnnotationVisible == EOLAnnotationVisible::Boxed) {
1452 surface->RectangleFrame(rcBox, Stroke(textFore));
1453 } else {
1454 if (phasesDraw == PhasesDraw::One) {
1455 // Draw an outline around the text
1456 surface->Stadium(rcBox, FillStroke(ColourRGBA(textBack, 0), textFore, 1.0), ends);
1457 } else {
1458 // Draw with a fill to fill the edges of the shape.
1459 surface->Stadium(rcBox, FillStroke(textBack, textFore, 1.0), ends);
1460 }
1461 }
1462 }
1463 }
1464
1465 // For multi-phase drawing draw the text last as transparent over any box
1466 if (FlagSet(phase, DrawPhase::text)) {
1467 if (phasesDraw != PhasesDraw::One) {
1468 surface->DrawTextTransparentUTF8(rcText, fontText,
1469 rcText.top + vsDraw.maxAscent, eolAnnotationText,
1470 textFore);
1471 }
1472 }
1473}
1474
1475static constexpr bool AnnotationBoxedOrIndented(AnnotationVisible annotationVisible) noexcept {
1476 return annotationVisible == AnnotationVisible::Boxed || annotationVisible == AnnotationVisible::Indented;
1477}
1478
1479void EditView::DrawAnnotation(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1480 Sci::Line line, int xStart, PRectangle rcLine, int subLine, DrawPhase phase) {
1481 const int indent = static_cast<int>(model.pdoc->GetLineIndentation(line) * vsDraw.spaceWidth);
1482 PRectangle rcSegment = rcLine;
1483 const int annotationLine = subLine - ll->lines;
1484 const StyledText stAnnotation = model.pdoc->AnnotationStyledText(line);
1485 if (stAnnotation.text && ValidStyledText(vsDraw, vsDraw.annotationStyleOffset, stAnnotation)) {
1486 if (FlagSet(phase, DrawPhase::back)) {
1487 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.styles[0].back));
1488 }
1489 rcSegment.left = static_cast<XYPOSITION>(xStart);
1490 if (model.trackLineWidth || AnnotationBoxedOrIndented(vsDraw.annotationVisible)) {
1491 // Only care about calculating width if tracking or need to draw indented box
1492 int widthAnnotation = WidestLineWidth(surface, vsDraw, vsDraw.annotationStyleOffset, stAnnotation);
1493 if (AnnotationBoxedOrIndented(vsDraw.annotationVisible)) {
1494 widthAnnotation += static_cast<int>(vsDraw.spaceWidth * 2); // Margins
1495 rcSegment.left = static_cast<XYPOSITION>(xStart + indent);
1496 rcSegment.right = rcSegment.left + widthAnnotation;
1497 }
1498 if (widthAnnotation > lineWidthMaxSeen)
1499 lineWidthMaxSeen = widthAnnotation;
1500 }
1501 const int annotationLines = model.pdoc->AnnotationLines(line);
1502 size_t start = 0;
1503 size_t lengthAnnotation = stAnnotation.LineLength(start);
1504 int lineInAnnotation = 0;
1505 while ((lineInAnnotation < annotationLine) && (start < stAnnotation.length)) {
1506 start += lengthAnnotation + 1;
1507 lengthAnnotation = stAnnotation.LineLength(start);
1508 lineInAnnotation++;
1509 }
1510 PRectangle rcText = rcSegment;
1511 if ((FlagSet(phase, DrawPhase::back)) && AnnotationBoxedOrIndented(vsDraw.annotationVisible)) {
1512 surface->FillRectangleAligned(rcText,
1513 Fill(vsDraw.styles[stAnnotation.StyleAt(start) + vsDraw.annotationStyleOffset].back));
1514 rcText.left += vsDraw.spaceWidth;
1515 }
1516 DrawStyledText(surface, vsDraw, vsDraw.annotationStyleOffset, rcText,
1517 stAnnotation, start, lengthAnnotation, phase);
1518 if ((FlagSet(phase, DrawPhase::back)) && (vsDraw.annotationVisible == AnnotationVisible::Boxed)) {
1519 const ColourRGBA colourBorder = vsDraw.styles[vsDraw.annotationStyleOffset].fore;
1520 const PRectangle rcBorder = PixelAlignOutside(rcSegment, surface->PixelDivisions());
1521 surface->FillRectangle(Side(rcBorder, Edge::left, 1), colourBorder);
1522 surface->FillRectangle(Side(rcBorder, Edge::right, 1), colourBorder);
1523 if (subLine == ll->lines) {
1524 surface->FillRectangle(Side(rcBorder, Edge::top, 1), colourBorder);
1525 }
1526 if (subLine == ll->lines + annotationLines - 1) {
1527 surface->FillRectangle(Side(rcBorder, Edge::bottom, 1), colourBorder);
1528 }
1529 }
1530 }
1531}
1532
1533static void DrawBlockCaret(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1534 int subLine, int xStart, Sci::Position offset, Sci::Position posCaret, PRectangle rcCaret, ColourRGBA caretColour) {
1535
1536 const Sci::Position lineStart = ll->LineStart(subLine);
1537 Sci::Position posBefore = posCaret;
1538 Sci::Position posAfter = model.pdoc->MovePositionOutsideChar(posCaret + 1, 1);
1539 Sci::Position numCharsToDraw = posAfter - posCaret;
1540
1541 // Work out where the starting and ending offsets are. We need to
1542 // see if the previous character shares horizontal space, such as a
1543 // glyph / combining character. If so we'll need to draw that too.
1544 Sci::Position offsetFirstChar = offset;
1545 Sci::Position offsetLastChar = offset + (posAfter - posCaret);
1546 while ((posBefore > 0) && ((offsetLastChar - numCharsToDraw) >= lineStart)) {
1547 if ((ll->positions[offsetLastChar] - ll->positions[offsetLastChar - numCharsToDraw]) > 0) {
1548 // The char does not share horizontal space
1549 break;
1550 }
1551 // Char shares horizontal space, update the numChars to draw
1552 // Update posBefore to point to the prev char
1553 posBefore = model.pdoc->MovePositionOutsideChar(posBefore - 1, -1);
1554 numCharsToDraw = posAfter - posBefore;
1555 offsetFirstChar = offset - (posCaret - posBefore);
1556 }
1557
1558 // See if the next character shares horizontal space, if so we'll
1559 // need to draw that too.
1560 if (offsetFirstChar < 0)
1561 offsetFirstChar = 0;
1562 numCharsToDraw = offsetLastChar - offsetFirstChar;
1563 while ((offsetLastChar < ll->LineStart(subLine + 1)) && (offsetLastChar <= ll->numCharsInLine)) {
1564 // Update posAfter to point to the 2nd next char, this is where
1565 // the next character ends, and 2nd next begins. We'll need
1566 // to compare these two
1567 posBefore = posAfter;
1568 posAfter = model.pdoc->MovePositionOutsideChar(posAfter + 1, 1);
1569 offsetLastChar = offset + (posAfter - posCaret);
1570 if ((ll->positions[offsetLastChar] - ll->positions[offsetLastChar - (posAfter - posBefore)]) > 0) {
1571 // The char does not share horizontal space
1572 break;
1573 }
1574 // Char shares horizontal space, update the numChars to draw
1575 numCharsToDraw = offsetLastChar - offsetFirstChar;
1576 }
1577
1578 // We now know what to draw, update the caret drawing rectangle
1579 rcCaret.left = ll->positions[offsetFirstChar] - ll->positions[lineStart] + xStart;
1580 rcCaret.right = ll->positions[offsetFirstChar + numCharsToDraw] - ll->positions[lineStart] + xStart;
1581
1582 // Adjust caret position to take into account any word wrapping symbols.
1583 if ((ll->wrapIndent != 0) && (lineStart != 0)) {
1584 const XYPOSITION wordWrapCharWidth = ll->wrapIndent;
1585 rcCaret.left += wordWrapCharWidth;
1586 rcCaret.right += wordWrapCharWidth;
1587 }
1588
1589 // This character is where the caret block is, we override the colours
1590 // (inversed) for drawing the caret here.
1591 const int styleMain = ll->styles[offsetFirstChar];
1592 const Font *fontText = vsDraw.styles[styleMain].font.get();
1593 const std::string_view text(&ll->chars[offsetFirstChar], numCharsToDraw);
1594 surface->DrawTextClipped(rcCaret, fontText,
1595 rcCaret.top + vsDraw.maxAscent, text, vsDraw.styles[styleMain].back,
1596 caretColour);
1597}
1598
1599void EditView::DrawCarets(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1600 Sci::Line lineDoc, int xStart, PRectangle rcLine, int subLine) const {
1601 // When drag is active it is the only caret drawn
1602 const bool drawDrag = model.posDrag.IsValid();
1603 if (hideSelection && !drawDrag)
1604 return;
1605 const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc);
1606 // For each selection draw
1607 for (size_t r = 0; (r<model.sel.Count()) || drawDrag; r++) {
1608 const bool mainCaret = r == model.sel.Main();
1609 SelectionPosition posCaret = (drawDrag ? model.posDrag : model.sel.Range(r).caret);
1610 if ((vsDraw.DrawCaretInsideSelection(model.inOverstrike, imeCaretBlockOverride)) &&
1611 !drawDrag &&
1612 posCaret > model.sel.Range(r).anchor) {
1613 if (posCaret.VirtualSpace() > 0)
1614 posCaret.SetVirtualSpace(posCaret.VirtualSpace() - 1);
1615 else
1616 posCaret.SetPosition(model.pdoc->MovePositionOutsideChar(posCaret.Position()-1, -1));
1617 }
1618 const int offset = static_cast<int>(posCaret.Position() - posLineStart);
1619 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
1620 const XYPOSITION virtualOffset = posCaret.VirtualSpace() * spaceWidth;
1621 if (ll->InLine(offset, subLine) && offset <= ll->numCharsBeforeEOL) {
1622 XYPOSITION xposCaret = ll->positions[offset] + virtualOffset - ll->positions[ll->LineStart(subLine)];
1623 if (model.BidirectionalEnabled() && (posCaret.VirtualSpace() == 0)) {
1624 // Get caret point
1625 const ScreenLine screenLine(ll, subLine, vsDraw, rcLine.right, tabWidthMinimumPixels);
1626
1627 const int caretPosition = offset - ll->LineStart(subLine);
1628
1629 std::unique_ptr<IScreenLineLayout> slLayout = surface->Layout(&screenLine);
1630 const XYPOSITION caretLeft = slLayout->XFromPosition(caretPosition);
1631
1632 // In case of start of line, the cursor should be at the right
1633 xposCaret = caretLeft + virtualOffset;
1634 }
1635 if (ll->wrapIndent != 0) {
1636 const Sci::Position lineStart = ll->LineStart(subLine);
1637 if (lineStart != 0) // Wrapped
1638 xposCaret += ll->wrapIndent;
1639 }
1640 const bool caretBlinkState = (model.caret.active && model.caret.on) || (!additionalCaretsBlink && !mainCaret);
1641 const bool caretVisibleState = additionalCaretsVisible || mainCaret;
1642 if ((xposCaret >= 0) && vsDraw.IsCaretVisible(mainCaret) &&
1643 (drawDrag || (caretBlinkState && caretVisibleState))) {
1644 bool canDrawBlockCaret = true;
1645 bool drawBlockCaret = false;
1646 XYPOSITION widthOverstrikeCaret;
1647 XYPOSITION caretWidthOffset = 0;
1648 PRectangle rcCaret = rcLine;
1649
1650 if (posCaret.Position() == model.pdoc->Length()) { // At end of document
1651 canDrawBlockCaret = false;
1652 widthOverstrikeCaret = vsDraw.aveCharWidth;
1653 } else if ((posCaret.Position() - posLineStart) >= ll->numCharsInLine) { // At end of line
1654 canDrawBlockCaret = false;
1655 widthOverstrikeCaret = vsDraw.aveCharWidth;
1656 } else {
1657 const int widthChar = model.pdoc->LenChar(posCaret.Position());
1658 widthOverstrikeCaret = ll->positions[offset + widthChar] - ll->positions[offset];
1659 }
1660 if (widthOverstrikeCaret < 3) // Make sure its visible
1661 widthOverstrikeCaret = 3;
1662
1663 if (xposCaret > 0)
1664 caretWidthOffset = 0.51f; // Move back so overlaps both character cells.
1665 xposCaret += xStart;
1666 const ViewStyle::CaretShape caretShape = drawDrag ? ViewStyle::CaretShape::line :
1667 vsDraw.CaretShapeForMode(model.inOverstrike, mainCaret);
1668 if (drawDrag) {
1669 /* Dragging text, use a line caret */
1670 rcCaret.left = std::round(xposCaret - caretWidthOffset);
1671 rcCaret.right = rcCaret.left + vsDraw.caret.width;
1672 } else if ((caretShape == ViewStyle::CaretShape::bar) && drawOverstrikeCaret) {
1673 /* Over-strike (insert mode), use a modified bar caret */
1674 rcCaret.top = rcCaret.bottom - 2;
1675 rcCaret.left = xposCaret + 1;
1676 rcCaret.right = rcCaret.left + widthOverstrikeCaret - 1;
1677 } else if ((caretShape == ViewStyle::CaretShape::block) || imeCaretBlockOverride) {
1678 /* Block caret */
1679 rcCaret.left = xposCaret;
1680 if (canDrawBlockCaret && !(IsControl(ll->chars[offset]))) {
1681 drawBlockCaret = true;
1682 rcCaret.right = xposCaret + widthOverstrikeCaret;
1683 } else {
1684 rcCaret.right = xposCaret + vsDraw.aveCharWidth;
1685 }
1686 } else {
1687 /* Line caret */
1688 rcCaret.left = std::round(xposCaret - caretWidthOffset);
1689 rcCaret.right = rcCaret.left + vsDraw.caret.width;
1690 }
1691 const Element elementCaret = mainCaret ? Element::Caret : Element::CaretAdditional;
1692 const ColourRGBA caretColour = *vsDraw.ElementColour(elementCaret);
1693 //assert(caretColour.IsOpaque());
1694 if (drawBlockCaret) {
1695 DrawBlockCaret(surface, model, vsDraw, ll, subLine, xStart, offset, posCaret.Position(), rcCaret, caretColour);
1696 } else {
1697 surface->FillRectangleAligned(rcCaret, Fill(caretColour));
1698 }
1699 }
1700 }
1701 if (drawDrag)
1702 break;
1703 }
1704}
1705
1706static void DrawWrapIndentAndMarker(Surface *surface, const ViewStyle &vsDraw, const LineLayout *ll,
1707 int xStart, PRectangle rcLine, std::optional<ColourRGBA> background, DrawWrapMarkerFn customDrawWrapMarker,
1708 bool caretActive) {
1709 // default bgnd here..
1710 surface->FillRectangleAligned(rcLine, Fill(background ? *background :
1711 vsDraw.styles[StyleDefault].back));
1712
1713 if (vsDraw.IsLineFrameOpaque(caretActive, ll->containsCaret)) {
1714 // Draw left of frame under marker
1715 surface->FillRectangleAligned(Side(rcLine, Edge::left, vsDraw.GetFrameWidth()),
1716 vsDraw.ElementColour(Element::CaretLineBack)->Opaque());
1717 }
1718
1719 if (FlagSet(vsDraw.wrap.visualFlags, WrapVisualFlag::Start)) {
1720
1721 // draw continuation rect
1722 PRectangle rcPlace = rcLine;
1723
1724 rcPlace.left = static_cast<XYPOSITION>(xStart);
1725 rcPlace.right = rcPlace.left + ll->wrapIndent;
1726
1727 if (FlagSet(vsDraw.wrap.visualFlagsLocation, WrapVisualLocation::StartByText))
1728 rcPlace.left = rcPlace.right - vsDraw.aveCharWidth;
1729 else
1730 rcPlace.right = rcPlace.left + vsDraw.aveCharWidth;
1731
1732 if (!customDrawWrapMarker) {
1733 DrawWrapMarker(surface, rcPlace, false, vsDraw.WrapColour());
1734 } else {
1735 customDrawWrapMarker(surface, rcPlace, false, vsDraw.WrapColour());
1736 }
1737 }
1738}
1739
1740// On the curses platform, the terminal is drawing its own caret, so if the caret is within
1741// the main selection, do not draw the selection at that position.
1742// Use iDoc from DrawBackground and DrawForeground here because TextSegment has been adjusted
1743// such that, if the caret is inside the main selection, the beginning or end of that selection
1744// is at the end of a text segment.
1745// This function should only be called if iDoc is within the main selection.
1746static InSelection CharacterInCursesSelection(Sci::Position iDoc, const EditModel &model, const ViewStyle &vsDraw) {
1747 const SelectionPosition &posCaret = model.sel.RangeMain().caret;
1748 const bool caretAtStart = posCaret < model.sel.RangeMain().anchor && posCaret.Position() == iDoc;
1749 const bool caretAtEnd = posCaret > model.sel.RangeMain().anchor &&
1750 vsDraw.DrawCaretInsideSelection(false, false) &&
1751 model.pdoc->MovePositionOutsideChar(posCaret.Position() - 1, -1) == iDoc;
1752 return (caretAtStart || caretAtEnd) ? InSelection::inNone : InSelection::inMain;
1753}
1754
1755void EditView::DrawBackground(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1756 PRectangle rcLine, Range lineRange, Sci::Position posLineStart, int xStart,
1757 int subLine, std::optional<ColourRGBA> background) const {
1758
1759 const bool selBackDrawn = vsDraw.SelectionBackgroundDrawn();
1760 bool inIndentation = subLine == 0; // Do not handle indentation except on first subline.
1761 const XYACCUMULATOR subLineStart = ll->positions[lineRange.start];
1762 // Does not take margin into account but not significant
1763 const XYPOSITION xStartVisible = static_cast<XYPOSITION>(subLineStart-xStart);
1764
1765 const BreakFinder::BreakFor breakFor = selBackDrawn ? BreakFinder::BreakFor::Selection : BreakFinder::BreakFor::Text;
1766 BreakFinder bfBack(ll, &model.sel, lineRange, posLineStart, xStartVisible, breakFor, model.pdoc, &model.reprs, &vsDraw);
1767
1768 const bool drawWhitespaceBackground = vsDraw.WhitespaceBackgroundDrawn() && !background;
1769
1770 // Background drawing loop
1771 while (bfBack.More()) {
1772
1773 const TextSegment ts = bfBack.Next();
1774 const Sci::Position i = ts.end() - 1;
1775 const Sci::Position iDoc = i + posLineStart;
1776
1777 PRectangle rcSegment = rcLine;
1778 rcSegment.left = ll->positions[ts.start] + xStart - static_cast<XYPOSITION>(subLineStart);
1779 rcSegment.right = ll->positions[ts.end()] + xStart - static_cast<XYPOSITION>(subLineStart);
1780 // Only try to draw if really visible - enhances performance by not calling environment to
1781 // draw strings that are completely past the right side of the window.
1782 if (!rcSegment.Empty() && rcSegment.Intersects(rcLine)) {
1783 // Clip to line rectangle, since may have a huge position which will not work with some platforms
1784 if (rcSegment.left < rcLine.left)
1785 rcSegment.left = rcLine.left;
1786 if (rcSegment.right > rcLine.right)
1787 rcSegment.right = rcLine.right;
1788
1789 InSelection inSelection = hideSelection ? InSelection::inNone : model.sel.CharacterInSelection(iDoc);
1790 if (FlagSet(vsDraw.caret.style, CaretStyle::Curses) && (inSelection == InSelection::inMain))
1791 inSelection = CharacterInCursesSelection(iDoc, model, vsDraw);
1792 const bool inHotspot = model.hotspot.Valid() && model.hotspot.ContainsCharacter(iDoc);
1793 ColourRGBA textBack = TextBackground(model, vsDraw, ll, background, inSelection,
1794 inHotspot, ll->styles[i], i);
1795 if (ts.representation) {
1796 if (ll->chars[i] == '\t') {
1797 // Tab display
1798 if (drawWhitespaceBackground && vsDraw.WhiteSpaceVisible(inIndentation)) {
1799 textBack = vsDraw.ElementColour(Element::WhiteSpaceBack)->Opaque();
1800 }
1801 } else {
1802 // Blob display
1803 inIndentation = false;
1804 }
1805 surface->FillRectangleAligned(rcSegment, Fill(textBack));
1806 } else {
1807 // Normal text display
1808 surface->FillRectangleAligned(rcSegment, Fill(textBack));
1809 if (vsDraw.viewWhitespace != WhiteSpace::Invisible) {
1810 for (int cpos = 0; cpos <= i - ts.start; cpos++) {
1811 if (ll->chars[cpos + ts.start] == ' ') {
1812 if (drawWhitespaceBackground && vsDraw.WhiteSpaceVisible(inIndentation)) {
1813 const PRectangle rcSpace(
1814 ll->positions[cpos + ts.start] + xStart - static_cast<XYPOSITION>(subLineStart),
1815 rcSegment.top,
1816 ll->positions[cpos + ts.start + 1] + xStart - static_cast<XYPOSITION>(subLineStart),
1817 rcSegment.bottom);
1818 surface->FillRectangleAligned(rcSpace,
1819 vsDraw.ElementColour(Element::WhiteSpaceBack)->Opaque());
1820 }
1821 } else {
1822 inIndentation = false;
1823 }
1824 }
1825 }
1826 }
1827 } else if (rcSegment.left > rcLine.right) {
1828 break;
1829 }
1830 }
1831}
1832
1833static void DrawEdgeLine(Surface *surface, const ViewStyle &vsDraw, const LineLayout *ll, PRectangle rcLine,
1834 Range lineRange, int xStart) {
1835 if (vsDraw.edgeState == EdgeVisualStyle::Line) {
1836 PRectangle rcSegment = rcLine;
1837 const int edgeX = static_cast<int>(vsDraw.theEdge.column * vsDraw.spaceWidth);
1838 rcSegment.left = static_cast<XYPOSITION>(edgeX + xStart);
1839 if ((ll->wrapIndent != 0) && (lineRange.start != 0))
1840 rcSegment.left -= ll->wrapIndent;
1841 rcSegment.right = rcSegment.left + 1;
1842 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.theEdge.colour));
1843 } else if (vsDraw.edgeState == EdgeVisualStyle::MultiLine) {
1844 for (size_t edge = 0; edge < vsDraw.theMultiEdge.size(); edge++) {
1845 if (vsDraw.theMultiEdge[edge].column >= 0) {
1846 PRectangle rcSegment = rcLine;
1847 const int edgeX = static_cast<int>(vsDraw.theMultiEdge[edge].column * vsDraw.spaceWidth);
1848 rcSegment.left = static_cast<XYPOSITION>(edgeX + xStart);
1849 if ((ll->wrapIndent != 0) && (lineRange.start != 0))
1850 rcSegment.left -= ll->wrapIndent;
1851 rcSegment.right = rcSegment.left + 1;
1852 surface->FillRectangleAligned(rcSegment, Fill(vsDraw.theMultiEdge[edge].colour));
1853 }
1854 }
1855 }
1856}
1857
1858// Draw underline mark as part of background if on base layer
1859static void DrawMarkUnderline(Surface *surface, const EditModel &model, const ViewStyle &vsDraw,
1860 Sci::Line line, PRectangle rcLine) {
1861 int marks = model.pdoc->GetMark(line);
1862 for (int markBit = 0; (markBit < 32) && marks; markBit++) {
1863 if ((marks & 1) && (vsDraw.markers[markBit].markType == MarkerSymbol::Underline) &&
1864 (vsDraw.markers[markBit].layer == Layer::Base)) {
1865 PRectangle rcUnderline = rcLine;
1866 rcUnderline.top = rcUnderline.bottom - 2;
1867 surface->FillRectangleAligned(rcUnderline, Fill(vsDraw.markers[markBit].back));
1868 }
1869 marks >>= 1;
1870 }
1871}
1872
1873static void DrawTranslucentSelection(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1874 Sci::Line line, PRectangle rcLine, int subLine, Range lineRange, int xStart, int tabWidthMinimumPixels, Layer layer) {
1875 if (vsDraw.selection.layer == layer) {
1876 const Sci::Position posLineStart = model.pdoc->LineStart(line);
1877 const XYACCUMULATOR subLineStart = ll->positions[lineRange.start];
1878 // For each selection draw
1879 Sci::Position virtualSpaces = 0;
1880 if (subLine == (ll->lines - 1)) {
1881 virtualSpaces = model.sel.VirtualSpaceFor(model.pdoc->LineEnd(line));
1882 }
1883 const SelectionPosition posStart(posLineStart + lineRange.start);
1884 const SelectionPosition posEnd(posLineStart + lineRange.end, virtualSpaces);
1885 const SelectionSegment virtualSpaceRange(posStart, posEnd);
1886 for (size_t r = 0; r < model.sel.Count(); r++) {
1887 const SelectionSegment portion = model.sel.Range(r).Intersect(virtualSpaceRange);
1888 if (!portion.Empty()) {
1889 const ColourRGBA selectionBack = SelectionBackground(
1890 model, vsDraw, model.sel.RangeType(r));
1891 const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth;
1892 if (model.BidirectionalEnabled()) {
1893 const int selectionStart = static_cast<int>(portion.start.Position() - posLineStart - lineRange.start);
1894 const int selectionEnd = static_cast<int>(portion.end.Position() - posLineStart - lineRange.start);
1895
1896 const ScreenLine screenLine(ll, subLine, vsDraw, rcLine.right, tabWidthMinimumPixels);
1897 std::unique_ptr<IScreenLineLayout> slLayout = surface->Layout(&screenLine);
1898
1899 const std::vector<Interval> intervals = slLayout->FindRangeIntervals(selectionStart, selectionEnd);
1900 for (const Interval &interval : intervals) {
1901 const XYPOSITION rcRight = interval.right + xStart;
1902 const XYPOSITION rcLeft = interval.left + xStart;
1903 const PRectangle rcSelection(rcLeft, rcLine.top, rcRight, rcLine.bottom);
1904 surface->FillRectangleAligned(rcSelection, selectionBack);
1905 }
1906
1907 if (portion.end.VirtualSpace()) {
1908 const XYPOSITION xStartVirtual = ll->positions[lineRange.end] -
1909 static_cast<XYPOSITION>(subLineStart) + xStart;
1910 PRectangle rcSegment = rcLine;
1911 rcSegment.left = xStartVirtual + portion.start.VirtualSpace() * spaceWidth;
1912 rcSegment.right = xStartVirtual + portion.end.VirtualSpace() * spaceWidth;
1913 surface->FillRectangleAligned(rcSegment, selectionBack);
1914 }
1915 } else {
1916 PRectangle rcSegment = rcLine;
1917 rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] -
1918 static_cast<XYPOSITION>(subLineStart) + portion.start.VirtualSpace() * spaceWidth;
1919 rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] -
1920 static_cast<XYPOSITION>(subLineStart) + portion.end.VirtualSpace() * spaceWidth;
1921 if ((ll->wrapIndent != 0) && (lineRange.start != 0)) {
1922 if ((portion.start.Position() - posLineStart) == lineRange.start && model.sel.Range(r).ContainsCharacter(portion.start.Position() - 1))
1923 rcSegment.left -= static_cast<int>(ll->wrapIndent); // indentation added to xStart was truncated to int, so we do the same here
1924 }
1925 rcSegment.left = (rcSegment.left > rcLine.left) ? rcSegment.left : rcLine.left;
1926 rcSegment.right = (rcSegment.right < rcLine.right) ? rcSegment.right : rcLine.right;
1927 if (rcSegment.right > rcLine.left)
1928 surface->FillRectangleAligned(rcSegment, selectionBack);
1929 }
1930 }
1931 }
1932 }
1933}
1934
1935// Draw any translucent whole line states
1936static void DrawTranslucentLineState(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1937 Sci::Line line, PRectangle rcLine, int subLine, Layer layer) {
1938 if ((model.caret.active || vsDraw.caretLine.alwaysShow) && vsDraw.ElementColour(Element::CaretLineBack) && ll->containsCaret &&
1939 vsDraw.caretLine.layer == layer) {
1940 if (vsDraw.caretLine.frame) {
1941 DrawCaretLineFramed(surface, vsDraw, ll, rcLine, subLine);
1942 } else {
1943 surface->FillRectangleAligned(rcLine, *vsDraw.ElementColour(Element::CaretLineBack));
1944 }
1945 }
1946 const int marksOfLine = model.pdoc->GetMark(line);
1947 int marksDrawnInText = marksOfLine & vsDraw.maskDrawInText;
1948 for (int markBit = 0; (markBit < 32) && marksDrawnInText; markBit++) {
1949 if ((marksDrawnInText & 1) && (vsDraw.markers[markBit].layer == layer)) {
1950 if (vsDraw.markers[markBit].markType == MarkerSymbol::Background) {
1951 surface->FillRectangleAligned(rcLine, vsDraw.markers[markBit].BackWithAlpha());
1952 } else if (vsDraw.markers[markBit].markType == MarkerSymbol::Underline) {
1953 PRectangle rcUnderline = rcLine;
1954 rcUnderline.top = rcUnderline.bottom - 2;
1955 surface->FillRectangleAligned(rcUnderline, vsDraw.markers[markBit].BackWithAlpha());
1956 }
1957 }
1958 marksDrawnInText >>= 1;
1959 }
1960 int marksDrawnInLine = marksOfLine & vsDraw.maskInLine;
1961 for (int markBit = 0; (markBit < 32) && marksDrawnInLine; markBit++) {
1962 if ((marksDrawnInLine & 1) && (vsDraw.markers[markBit].layer == layer)) {
1963 surface->FillRectangleAligned(rcLine, vsDraw.markers[markBit].BackWithAlpha());
1964 }
1965 marksDrawnInLine >>= 1;
1966 }
1967}
1968
1969void EditView::DrawForeground(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
1970 Sci::Line lineVisible, PRectangle rcLine, Range lineRange, Sci::Position posLineStart, int xStart,
1971 int subLine, std::optional<ColourRGBA> background) {
1972
1973 const bool selBackDrawn = vsDraw.SelectionBackgroundDrawn();
1974 const bool drawWhitespaceBackground = vsDraw.WhitespaceBackgroundDrawn() && !background;
1975 bool inIndentation = subLine == 0; // Do not handle indentation except on first subline.
1976
1977 const XYACCUMULATOR subLineStart = ll->positions[lineRange.start];
1978 const XYPOSITION indentWidth = model.pdoc->IndentSize() * vsDraw.spaceWidth;
1979
1980 // Does not take margin into account but not significant
1981 const XYPOSITION xStartVisible = static_cast<XYPOSITION>(subLineStart-xStart);
1982
1983 // Foreground drawing loop
1984 const BreakFinder::BreakFor breakFor = (((phasesDraw == PhasesDraw::One) && selBackDrawn) || vsDraw.SelectionTextDrawn())
1985 ? BreakFinder::BreakFor::ForegroundAndSelection : BreakFinder::BreakFor::Foreground;
1986 BreakFinder bfFore(ll, &model.sel, lineRange, posLineStart, xStartVisible, breakFor, model.pdoc, &model.reprs, &vsDraw);
1987
1988 while (bfFore.More()) {
1989
1990 const TextSegment ts = bfFore.Next();
1991 const Sci::Position i = ts.end() - 1;
1992 const Sci::Position iDoc = i + posLineStart;
1993
1994 PRectangle rcSegment = rcLine;
1995 rcSegment.left = ll->positions[ts.start] + xStart - static_cast<XYPOSITION>(subLineStart);
1996 rcSegment.right = ll->positions[ts.end()] + xStart - static_cast<XYPOSITION>(subLineStart);
1997 // Only try to draw if really visible - enhances performance by not calling environment to
1998 // draw strings that are completely past the right side of the window.
1999 if (rcSegment.Intersects(rcLine)) {
2000 const int styleMain = ll->styles[i];
2001 ColourRGBA textFore = vsDraw.styles[styleMain].fore;
2002 const Font *textFont = vsDraw.styles[styleMain].font.get();
2003 // Hot-spot foreground
2004 const bool inHotspot = model.hotspot.Valid() && model.hotspot.ContainsCharacter(iDoc);
2005 if (inHotspot) {
2006 if (vsDraw.ElementColour(Element::HotSpotActive))
2007 textFore = *vsDraw.ElementColour(Element::HotSpotActive);
2008 }
2009 if (vsDraw.indicatorsSetFore) {
2010 // At least one indicator sets the text colour so see if it applies to this segment
2011 for (const IDecoration *deco : model.pdoc->decorations->View()) {
2012 const int indicatorValue = deco->ValueAt(ts.start + posLineStart);
2013 if (indicatorValue) {
2014 const Indicator &indicator = vsDraw.indicators[deco->Indicator()];
2015 bool hover = false;
2016 if (indicator.IsDynamic()) {
2017 const Sci::Position startPos = ts.start + posLineStart;
2018 const Range rangeRun(deco->StartRun(startPos), deco->EndRun(startPos));
2019 hover = rangeRun.ContainsCharacter(model.hoverIndicatorPos);
2020 }
2021 if (hover) {
2022 if (indicator.sacHover.style == IndicatorStyle::TextFore) {
2023 textFore = indicator.sacHover.fore;
2024 }
2025 } else {
2026 if (indicator.sacNormal.style == IndicatorStyle::TextFore) {
2027 if (FlagSet(indicator.Flags(), IndicFlag::ValueFore))
2028 textFore = ColourRGBA::FromRGB(indicatorValue & static_cast<int>(IndicValue::Mask));
2029 else
2030 textFore = indicator.sacNormal.fore;
2031 }
2032 }
2033 }
2034 }
2035 }
2036 InSelection inSelection = hideSelection ? InSelection::inNone : model.sel.CharacterInSelection(iDoc);
2037 if (FlagSet(vsDraw.caret.style, CaretStyle::Curses) && (inSelection == InSelection::inMain))
2038 inSelection = CharacterInCursesSelection(iDoc, model, vsDraw);
2039 const std::optional<ColourRGBA> selectionFore = SelectionForeground(model, vsDraw, inSelection);
2040 if (selectionFore) {
2041 textFore = *selectionFore;
2042 }
2043 ColourRGBA textBack = TextBackground(model, vsDraw, ll, background, inSelection, inHotspot, styleMain, i);
2044 if (ts.representation) {
2045 if (ll->chars[i] == '\t') {
2046 // Tab display
2047 if (phasesDraw == PhasesDraw::One) {
2048 if (drawWhitespaceBackground && vsDraw.WhiteSpaceVisible(inIndentation))
2049 textBack = vsDraw.ElementColour(Element::WhiteSpaceBack)->Opaque();
2050 surface->FillRectangleAligned(rcSegment, Fill(textBack));
2051 }
2052 if (inIndentation && vsDraw.viewIndentationGuides == IndentView::Real) {
2053 for (int indentCount = static_cast<int>((ll->positions[i] + epsilon) / indentWidth);
2054 indentCount <= (ll->positions[i + 1] - epsilon) / indentWidth;
2055 indentCount++) {
2056 if (indentCount > 0) {
2057 const XYPOSITION xIndent = std::floor(indentCount * indentWidth);
2058 DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcSegment,
2059 (ll->xHighlightGuide == xIndent));
2060 }
2061 }
2062 }
2063 if (vsDraw.viewWhitespace != WhiteSpace::Invisible) {
2064 if (vsDraw.WhiteSpaceVisible(inIndentation)) {
2065 const PRectangle rcTab(rcSegment.left + 1, rcSegment.top + tabArrowHeight,
2066 rcSegment.right - 1, rcSegment.bottom - vsDraw.maxDescent);
2067 const int segmentTop = static_cast<int>(rcSegment.top) + vsDraw.lineHeight / 2;
2068 const ColourRGBA whiteSpaceFore = vsDraw.ElementColour(Element::WhiteSpace).value_or(textFore);
2069 if (!customDrawTabArrow)
2070 DrawTabArrow(surface, rcTab, segmentTop, vsDraw, Stroke(whiteSpaceFore, 1.0f));
2071 else
2072 customDrawTabArrow(surface, rcTab, segmentTop, vsDraw, Stroke(whiteSpaceFore, 1.0f));
2073 }
2074 }
2075 } else {
2076 inIndentation = false;
2077 if (vsDraw.controlCharSymbol >= 32) {
2078 // Using one font for all control characters so it can be controlled independently to ensure
2079 // the box goes around the characters tightly. Seems to be no way to work out what height
2080 // is taken by an individual character - internal leading gives varying results.
2081 const Font *ctrlCharsFont = vsDraw.styles[StyleControlChar].font.get();
2082 const char cc[2] = { static_cast<char>(vsDraw.controlCharSymbol), '\0' };
2083 surface->DrawTextNoClip(rcSegment, ctrlCharsFont,
2084 rcSegment.top + vsDraw.maxAscent,
2085 cc, textBack, textFore);
2086 } else {
2087 if (FlagSet(ts.representation->appearance, RepresentationAppearance::Colour)) {
2088 textFore = ts.representation->colour;
2089 }
2090 if (FlagSet(ts.representation->appearance, RepresentationAppearance::Blob)) {
2091 DrawTextBlob(surface, vsDraw, rcSegment, ts.representation->stringRep,
2092 textBack, textFore, phasesDraw == PhasesDraw::One);
2093 } else {
2094 surface->DrawTextTransparentUTF8(rcSegment, vsDraw.styles[StyleControlChar].font.get(),
2095 rcSegment.top + vsDraw.maxAscent, ts.representation->stringRep, textFore);
2096 }
2097 }
2098 }
2099 } else {
2100 // Normal text display
2101 if (vsDraw.styles[styleMain].visible) {
2102 const std::string_view text(&ll->chars[ts.start], i - ts.start + 1);
2103 if (phasesDraw != PhasesDraw::One) {
2104 surface->DrawTextTransparent(rcSegment, textFont,
2105 rcSegment.top + vsDraw.maxAscent, text, textFore);
2106 } else {
2107 surface->DrawTextNoClip(rcSegment, textFont,
2108 rcSegment.top + vsDraw.maxAscent, text, textFore, textBack);
2109 }
2110 }
2111 if (vsDraw.viewWhitespace != WhiteSpace::Invisible ||
2112 (inIndentation && vsDraw.viewIndentationGuides != IndentView::None)) {
2113 for (int cpos = 0; cpos <= i - ts.start; cpos++) {
2114 if (ll->chars[cpos + ts.start] == ' ') {
2115 if (vsDraw.viewWhitespace != WhiteSpace::Invisible) {
2116 if (vsDraw.WhiteSpaceVisible(inIndentation)) {
2117 const XYPOSITION xmid = (ll->positions[cpos + ts.start] + ll->positions[cpos + ts.start + 1]) / 2;
2118 if ((phasesDraw == PhasesDraw::One) && drawWhitespaceBackground) {
2119 textBack = vsDraw.ElementColour(Element::WhiteSpaceBack)->Opaque();
2120 const PRectangle rcSpace(
2121 ll->positions[cpos + ts.start] + xStart - static_cast<XYPOSITION>(subLineStart),
2122 rcSegment.top,
2123 ll->positions[cpos + ts.start + 1] + xStart - static_cast<XYPOSITION>(subLineStart),
2124 rcSegment.bottom);
2125 surface->FillRectangleAligned(rcSpace, Fill(textBack));
2126 }
2127 const int halfDotWidth = vsDraw.whitespaceSize / 2;
2128 PRectangle rcDot(xmid + xStart - halfDotWidth - static_cast<XYPOSITION>(subLineStart),
2129 rcSegment.top + vsDraw.lineHeight / 2, 0.0f, 0.0f);
2130 rcDot.right = rcDot.left + vsDraw.whitespaceSize;
2131 rcDot.bottom = rcDot.top + vsDraw.whitespaceSize;
2132 const ColourRGBA whiteSpaceFore = vsDraw.ElementColour(Element::WhiteSpace).value_or(textFore);
2133 surface->FillRectangleAligned(rcDot, Fill(whiteSpaceFore));
2134 }
2135 }
2136 if (inIndentation && vsDraw.viewIndentationGuides == IndentView::Real) {
2137 for (int indentCount = static_cast<int>((ll->positions[cpos + ts.start] + epsilon) / indentWidth);
2138 indentCount <= (ll->positions[cpos + ts.start + 1] - epsilon) / indentWidth;
2139 indentCount++) {
2140 if (indentCount > 0) {
2141 const XYPOSITION xIndent = std::floor(indentCount * indentWidth);
2142 DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcSegment,
2143 (ll->xHighlightGuide == xIndent));
2144 }
2145 }
2146 }
2147 } else {
2148 inIndentation = false;
2149 }
2150 }
2151 }
2152 }
2153 if (inHotspot && vsDraw.hotspotUnderline) {
2154 PRectangle rcUL = rcSegment;
2155 rcUL.top = rcUL.top + vsDraw.maxAscent + 1;
2156 rcUL.bottom = rcUL.top + 1;
2157 if (vsDraw.ElementColour(Element::HotSpotActive))
2158 surface->FillRectangleAligned(rcUL, Fill(*vsDraw.ElementColour(Element::HotSpotActive)));
2159 else
2160 surface->FillRectangleAligned(rcUL, Fill(textFore));
2161 } else if (vsDraw.styles[styleMain].underline) {
2162 PRectangle rcUL = rcSegment;
2163 rcUL.top = rcUL.top + vsDraw.maxAscent + 1;
2164 rcUL.bottom = rcUL.top + 1;
2165 surface->FillRectangleAligned(rcUL, Fill(textFore));
2166 }
2167 } else if (rcSegment.left > rcLine.right) {
2168 break;
2169 }
2170 }
2171}
2172
2173void EditView::DrawIndentGuidesOverEmpty(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
2174 Sci::Line line, Sci::Line lineVisible, PRectangle rcLine, int xStart, int subLine) {
2175 if ((vsDraw.viewIndentationGuides == IndentView::LookForward || vsDraw.viewIndentationGuides == IndentView::LookBoth)
2176 && (subLine == 0)) {
2177 const Sci::Position posLineStart = model.pdoc->LineStart(line);
2178 int indentSpace = model.pdoc->GetLineIndentation(line);
2179 int xStartText = static_cast<int>(ll->positions[model.pdoc->GetLineIndentPosition(line) - posLineStart]);
2180
2181 // Find the most recent line with some text
2182
2183 Sci::Line lineLastWithText = line;
2184 while (lineLastWithText > std::max(line - 20, static_cast<Sci::Line>(0)) && model.pdoc->IsWhiteLine(lineLastWithText)) {
2185 lineLastWithText--;
2186 }
2187 if (lineLastWithText < line) {
2188 xStartText = 100000; // Don't limit to visible indentation on empty line
2189 // This line is empty, so use indentation of last line with text
2190 int indentLastWithText = model.pdoc->GetLineIndentation(lineLastWithText);
2191 const int isFoldHeader = LevelIsHeader(model.pdoc->GetFoldLevel(lineLastWithText));
2192 if (isFoldHeader) {
2193 // Level is one more level than parent
2194 indentLastWithText += model.pdoc->IndentSize();
2195 }
2196 if (vsDraw.viewIndentationGuides == IndentView::LookForward) {
2197 // In viLookForward mode, previous line only used if it is a fold header
2198 if (isFoldHeader) {
2199 indentSpace = std::max(indentSpace, indentLastWithText);
2200 }
2201 } else { // viLookBoth
2202 indentSpace = std::max(indentSpace, indentLastWithText);
2203 }
2204 }
2205
2206 Sci::Line lineNextWithText = line;
2207 while (lineNextWithText < std::min(line + 20, model.pdoc->LinesTotal()) && model.pdoc->IsWhiteLine(lineNextWithText)) {
2208 lineNextWithText++;
2209 }
2210 if (lineNextWithText > line) {
2211 xStartText = 100000; // Don't limit to visible indentation on empty line
2212 // This line is empty, so use indentation of first next line with text
2213 indentSpace = std::max(indentSpace,
2214 model.pdoc->GetLineIndentation(lineNextWithText));
2215 }
2216
2217 for (int indentPos = model.pdoc->IndentSize(); indentPos < indentSpace; indentPos += model.pdoc->IndentSize()) {
2218 const XYPOSITION xIndent = std::floor(indentPos * vsDraw.spaceWidth);
2219 if (xIndent < xStartText) {
2220 DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcLine,
2221 (ll->xHighlightGuide == xIndent));
2222 }
2223 }
2224 }
2225}
2226
2227void EditView::DrawLine(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
2228 Sci::Line line, Sci::Line lineVisible, int xStart, PRectangle rcLine, int subLine, DrawPhase phase) {
2229
2230 if (subLine >= ll->lines) {
2231 DrawAnnotation(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, phase);
2232 return; // No further drawing
2233 }
2234
2235 // See if something overrides the line background colour.
2236 const std::optional<ColourRGBA> background = vsDraw.Background(model.pdoc->GetMark(line), model.caret.active, ll->containsCaret);
2237
2238 const Sci::Position posLineStart = model.pdoc->LineStart(line);
2239
2240 const Range lineRange = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly);
2241 const Range lineRangeIncludingEnd = ll->SubLineRange(subLine, LineLayout::Scope::includeEnd);
2242 const XYACCUMULATOR subLineStart = ll->positions[lineRange.start];
2243
2244 if ((ll->wrapIndent != 0) && (subLine > 0)) {
2245 if (FlagSet(phase, DrawPhase::back)) {
2246 DrawWrapIndentAndMarker(surface, vsDraw, ll, xStart, rcLine, background, customDrawWrapMarker, model.caret.active);
2247 }
2248 xStart += static_cast<int>(ll->wrapIndent);
2249 }
2250
2251 if (phasesDraw != PhasesDraw::One) {
2252 if (FlagSet(phase, DrawPhase::back)) {
2253 DrawBackground(surface, model, vsDraw, ll, rcLine, lineRange, posLineStart, xStart,
2254 subLine, background);
2255 DrawFoldDisplayText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, DrawPhase::back);
2256 DrawEOLAnnotationText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, DrawPhase::back);
2257 // Remove drawBack to not draw again in DrawFoldDisplayText
2258 phase = static_cast<DrawPhase>(static_cast<int>(phase) & ~static_cast<int>(DrawPhase::back));
2259 DrawEOL(surface, model, vsDraw, ll, rcLine, line, lineRange.end,
2260 xStart, subLine, subLineStart, background);
2261 if (vsDraw.IsLineFrameOpaque(model.caret.active, ll->containsCaret))
2262 DrawCaretLineFramed(surface, vsDraw, ll, rcLine, subLine);
2263 }
2264
2265 if (FlagSet(phase, DrawPhase::indicatorsBack)) {
2266 DrawIndicators(surface, model, vsDraw, ll, line, xStart, rcLine, subLine,
2267 lineRangeIncludingEnd.end, true, tabWidthMinimumPixels);
2268 DrawEdgeLine(surface, vsDraw, ll, rcLine, lineRange, xStart);
2269 DrawMarkUnderline(surface, model, vsDraw, line, rcLine);
2270 }
2271 }
2272
2273 if (FlagSet(phase, DrawPhase::text)) {
2274 DrawTranslucentSelection(surface, model, vsDraw, ll, line, rcLine, subLine, lineRange, xStart, tabWidthMinimumPixels, Layer::UnderText);
2275 DrawTranslucentLineState(surface, model, vsDraw, ll, line, rcLine, subLine, Layer::UnderText);
2276 DrawForeground(surface, model, vsDraw, ll, lineVisible, rcLine, lineRange, posLineStart, xStart,
2277 subLine, background);
2278 }
2279
2280 if (FlagSet(phase, DrawPhase::indentationGuides)) {
2281 DrawIndentGuidesOverEmpty(surface, model, vsDraw, ll, line, lineVisible, rcLine, xStart, subLine);
2282 }
2283
2284 if (FlagSet(phase, DrawPhase::indicatorsFore)) {
2285 DrawIndicators(surface, model, vsDraw, ll, line, xStart, rcLine, subLine,
2286 lineRangeIncludingEnd.end, false, tabWidthMinimumPixels);
2287 }
2288
2289 DrawFoldDisplayText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, phase);
2290 DrawEOLAnnotationText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, phase);
2291
2292 if (phasesDraw == PhasesDraw::One) {
2293 DrawEOL(surface, model, vsDraw, ll, rcLine, line, lineRange.end,
2294 xStart, subLine, subLineStart, background);
2295 if (vsDraw.IsLineFrameOpaque(model.caret.active, ll->containsCaret))
2296 DrawCaretLineFramed(surface, vsDraw, ll, rcLine, subLine);
2297 DrawEdgeLine(surface, vsDraw, ll, rcLine, lineRange, xStart);
2298 DrawMarkUnderline(surface, model, vsDraw, line, rcLine);
2299 }
2300
2301 if (!hideSelection && FlagSet(phase, DrawPhase::selectionTranslucent)) {
2302 DrawTranslucentSelection(surface, model, vsDraw, ll, line, rcLine, subLine, lineRange, xStart, tabWidthMinimumPixels, Layer::OverText);
2303 }
2304
2305 if (FlagSet(phase, DrawPhase::lineTranslucent)) {
2306 DrawTranslucentLineState(surface, model, vsDraw, ll, line, rcLine, subLine, Layer::OverText);
2307 }
2308}
2309
2310static void DrawFoldLines(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
2311 Sci::Line line, PRectangle rcLine, int subLine) {
2312 const bool lastSubLine = subLine == (ll->lines - 1);
2313 const bool expanded = model.pcs->GetExpanded(line);
2314 const FoldLevel level = model.pdoc->GetFoldLevel(line);
2315 const FoldLevel levelNext = model.pdoc->GetFoldLevel(line + 1);
2316 if (LevelIsHeader(level) &&
2317 (LevelNumber(level) < LevelNumber(levelNext))) {
2318 const ColourRGBA foldLineColour = vsDraw.ElementColour(Element::FoldLine).value_or(
2319 vsDraw.styles[StyleDefault].fore);
2320 // Paint the line above the fold
2321 if ((subLine == 0) &&
2322 ((expanded && (FlagSet(model.foldFlags, FoldFlag::LineBeforeExpanded)))
2323 ||
2324 (!expanded && (FlagSet(model.foldFlags, FoldFlag::LineBeforeContracted))))) {
2325 surface->FillRectangleAligned(Side(rcLine, Edge::top, 1.0), foldLineColour);
2326 }
2327 // Paint the line below the fold
2328 if (lastSubLine &&
2329 ((expanded && (FlagSet(model.foldFlags, FoldFlag::LineAfterExpanded)))
2330 ||
2331 (!expanded && (FlagSet(model.foldFlags, FoldFlag::LineAfterContracted))))) {
2332 surface->FillRectangleAligned(Side(rcLine, Edge::bottom, 1.0), foldLineColour);
2333 // If contracted fold line drawn then don't overwrite with hidden line
2334 // as fold lines are more specific then hidden lines.
2335 if (!expanded) {
2336 return;
2337 }
2338 }
2339 }
2340 if (lastSubLine && model.pcs->GetVisible(line) && !model.pcs->GetVisible(line + 1)) {
2341 std::optional<ColourRGBA> hiddenLineColour = vsDraw.ElementColour(Element::HiddenLine);
2342 if (hiddenLineColour) {
2343 surface->FillRectangleAligned(Side(rcLine, Edge::bottom, 1.0), *hiddenLineColour);
2344 }
2345 }
2346}
2347
2348void EditView::PaintText(Surface *surfaceWindow, const EditModel &model, PRectangle rcArea,
2349 PRectangle rcClient, const ViewStyle &vsDraw) {
2350 // Allow text at start of line to overlap 1 pixel into the margin as this displays
2351 // serifs and italic stems for aliased text.
2352 const int leftTextOverlap = ((model.xOffset == 0) && (vsDraw.leftMarginWidth > 0)) ? 1 : 0;
2353
2354 // Do the painting
2355 if (rcArea.right > vsDraw.textStart - leftTextOverlap) {
2356
2357 Surface *surface = surfaceWindow;
2358 if (bufferedDraw) {
2359 surface = pixmapLine.get();
2360 PLATFORM_ASSERT(pixmapLine->Initialised());
2361 }
2362 surface->SetMode(SurfaceMode(model.pdoc->dbcsCodePage, model.BidirectionalR2L()));
2363
2364 const Point ptOrigin = model.GetVisibleOriginInMain();
2365
2366 const int screenLinePaintFirst = static_cast<int>(rcArea.top) / vsDraw.lineHeight;
2367 const int xStart = vsDraw.textStart - model.xOffset + static_cast<int>(ptOrigin.x);
2368
2369 const SelectionPosition posCaret = model.posDrag.IsValid() ? model.posDrag : model.sel.RangeMain().caret;
2370 const Sci::Line lineCaret = model.pdoc->SciLineFromPosition(posCaret.Position());
2371 const int caretOffset = static_cast<int>(posCaret.Position() - model.pdoc->LineStart(lineCaret));
2372
2373 PRectangle rcTextArea = rcClient;
2374 if (vsDraw.marginInside) {
2375 rcTextArea.left += vsDraw.textStart;
2376 rcTextArea.right -= vsDraw.rightMarginWidth;
2377 } else {
2378 rcTextArea = rcArea;
2379 }
2380
2381 // Remove selection margin from drawing area so text will not be drawn
2382 // on it in unbuffered mode.
2383 const bool clipping = !bufferedDraw && vsDraw.marginInside;
2384 if (clipping) {
2385 PRectangle rcClipText = rcTextArea;
2386 rcClipText.left -= leftTextOverlap;
2387 surfaceWindow->SetClip(rcClipText);
2388 }
2389
2390 // Loop on visible lines
2391#if defined(TIME_PAINTING)
2392 double durLayout = 0.0;
2393 double durPaint = 0.0;
2394 double durCopy = 0.0;
2395 ElapsedPeriod epWhole;
2396#endif
2397 const bool bracesIgnoreStyle = ((vsDraw.braceHighlightIndicatorSet && (model.bracesMatchStyle == StyleBraceLight)) ||
2398 (vsDraw.braceBadLightIndicatorSet && (model.bracesMatchStyle == StyleBraceBad)));
2399
2400 Sci::Line lineDocPrevious = -1; // Used to avoid laying out one document line multiple times
2401 std::shared_ptr<LineLayout> ll;
2402 std::vector<DrawPhase> phases;
2403 if ((phasesDraw == PhasesDraw::Multiple) && !bufferedDraw) {
2404 for (DrawPhase phase = DrawPhase::back; phase <= DrawPhase::carets; phase = static_cast<DrawPhase>(static_cast<int>(phase) * 2)) {
2405 phases.push_back(phase);
2406 }
2407 } else {
2408 phases.push_back(DrawPhase::all);
2409 }
2410 for (const DrawPhase &phase : phases) {
2411 int ypos = 0;
2412 if (!bufferedDraw)
2413 ypos += screenLinePaintFirst * vsDraw.lineHeight;
2414 int yposScreen = screenLinePaintFirst * vsDraw.lineHeight;
2415 Sci::Line visibleLine = model.TopLineOfMain() + screenLinePaintFirst;
2416 while (visibleLine < model.pcs->LinesDisplayed() && yposScreen < rcArea.bottom) {
2417
2418 const Sci::Line lineDoc = model.pcs->DocFromDisplay(visibleLine);
2419 // Only visible lines should be handled by the code within the loop
2420 PLATFORM_ASSERT(model.pcs->GetVisible(lineDoc));
2421 const Sci::Line lineStartSet = model.pcs->DisplayFromDoc(lineDoc);
2422 const int subLine = static_cast<int>(visibleLine - lineStartSet);
2423
2424 // Copy this line and its styles from the document into local arrays
2425 // and determine the x position at which each character starts.
2426#if defined(TIME_PAINTING)
2427 ElapsedPeriod ep;
2428#endif
2429 if (lineDoc != lineDocPrevious) {
2430 ll = RetrieveLineLayout(lineDoc, model);
2431 LayoutLine(model, surface, vsDraw, ll.get(), model.wrapWidth);
2432 lineDocPrevious = lineDoc;
2433 }
2434#if defined(TIME_PAINTING)
2435 durLayout += ep.Duration(true);
2436#endif
2437 if (ll) {
2438 ll->containsCaret = !hideSelection && (lineDoc == lineCaret)
2439 && (ll->lines == 1 || !vsDraw.caretLine.subLine || ll->InLine(caretOffset, subLine));
2440
2441 PRectangle rcLine = rcTextArea;
2442 rcLine.top = static_cast<XYPOSITION>(ypos);
2443 rcLine.bottom = static_cast<XYPOSITION>(ypos + vsDraw.lineHeight);
2444
2445 const Range rangeLine(model.pdoc->LineStart(lineDoc),
2446 model.pdoc->LineStart(lineDoc + 1));
2447
2448 // Highlight the current braces if any
2449 ll->SetBracesHighlight(rangeLine, model.braces, static_cast<char>(model.bracesMatchStyle),
2450 static_cast<int>(model.highlightGuideColumn * vsDraw.spaceWidth), bracesIgnoreStyle);
2451
2452 if (leftTextOverlap && (bufferedDraw || ((phasesDraw < PhasesDraw::Multiple) && (FlagSet(phase, DrawPhase::back))))) {
2453 // Clear the left margin
2454 PRectangle rcSpacer = rcLine;
2455 rcSpacer.right = rcSpacer.left;
2456 rcSpacer.left -= 1;
2457 surface->FillRectangleAligned(rcSpacer, Fill(vsDraw.styles[StyleDefault].back));
2458 }
2459
2460 if (model.BidirectionalEnabled()) {
2461 // Fill the line bidi data
2462 UpdateBidiData(model, vsDraw, ll.get());
2463 }
2464
2465 DrawLine(surface, model, vsDraw, ll.get(), lineDoc, visibleLine, xStart, rcLine, subLine, phase);
2466#if defined(TIME_PAINTING)
2467 durPaint += ep.Duration(true);
2468#endif
2469 // Restore the previous styles for the brace highlights in case layout is in cache.
2470 ll->RestoreBracesHighlight(rangeLine, model.braces, bracesIgnoreStyle);
2471
2472 if (FlagSet(phase, DrawPhase::foldLines)) {
2473 DrawFoldLines(surface, model, vsDraw, ll.get(), lineDoc, rcLine, subLine);
2474 }
2475
2476 if (FlagSet(phase, DrawPhase::carets)) {
2477 DrawCarets(surface, model, vsDraw, ll.get(), lineDoc, xStart, rcLine, subLine);
2478 }
2479
2480 if (bufferedDraw) {
2481 const Point from = Point::FromInts(vsDraw.textStart - leftTextOverlap, 0);
2482 const PRectangle rcCopyArea = PRectangle::FromInts(vsDraw.textStart - leftTextOverlap, yposScreen,
2483 static_cast<int>(rcClient.right - vsDraw.rightMarginWidth),
2484 yposScreen + vsDraw.lineHeight);
2485 pixmapLine->FlushDrawing();
2486 surfaceWindow->Copy(rcCopyArea, from, *pixmapLine);
2487 }
2488
2489 lineWidthMaxSeen = std::max(
2490 lineWidthMaxSeen, static_cast<int>(ll->positions[ll->numCharsInLine]));
2491#if defined(TIME_PAINTING)
2492 durCopy += ep.Duration(true);
2493#endif
2494 }
2495
2496 if (!bufferedDraw) {
2497 ypos += vsDraw.lineHeight;
2498 }
2499
2500 yposScreen += vsDraw.lineHeight;
2501 visibleLine++;
2502 }
2503 }
2504 ll.reset();
2505#if defined(TIME_PAINTING)
2506 if (durPaint < 0.00000001)
2507 durPaint = 0.00000001;
2508#endif
2509 // Right column limit indicator
2510 PRectangle rcBeyondEOF = (vsDraw.marginInside) ? rcClient : rcArea;
2511 rcBeyondEOF.left = static_cast<XYPOSITION>(vsDraw.textStart);
2512 rcBeyondEOF.right = rcBeyondEOF.right - ((vsDraw.marginInside) ? vsDraw.rightMarginWidth : 0);
2513 rcBeyondEOF.top = static_cast<XYPOSITION>((model.pcs->LinesDisplayed() - model.TopLineOfMain()) * vsDraw.lineHeight);
2514 if (rcBeyondEOF.top < rcBeyondEOF.bottom) {
2515 surfaceWindow->FillRectangleAligned(rcBeyondEOF, Fill(vsDraw.styles[StyleDefault].back));
2516 if (vsDraw.edgeState == EdgeVisualStyle::Line) {
2517 const int edgeX = static_cast<int>(vsDraw.theEdge.column * vsDraw.spaceWidth);
2518 rcBeyondEOF.left = static_cast<XYPOSITION>(edgeX + xStart);
2519 rcBeyondEOF.right = rcBeyondEOF.left + 1;
2520 surfaceWindow->FillRectangleAligned(rcBeyondEOF, Fill(vsDraw.theEdge.colour));
2521 } else if (vsDraw.edgeState == EdgeVisualStyle::MultiLine) {
2522 for (size_t edge = 0; edge < vsDraw.theMultiEdge.size(); edge++) {
2523 if (vsDraw.theMultiEdge[edge].column >= 0) {
2524 const int edgeX = static_cast<int>(vsDraw.theMultiEdge[edge].column * vsDraw.spaceWidth);
2525 rcBeyondEOF.left = static_cast<XYPOSITION>(edgeX + xStart);
2526 rcBeyondEOF.right = rcBeyondEOF.left + 1;
2527 surfaceWindow->FillRectangleAligned(rcBeyondEOF, Fill(vsDraw.theMultiEdge[edge].colour));
2528 }
2529 }
2530 }
2531 }
2532
2533 if (clipping)
2534 surfaceWindow->PopClip();
2535
2536 //Platform::DebugPrintf("start display %d, offset = %d\n", model.pdoc->Length(), model.xOffset);
2537#if defined(TIME_PAINTING)
2538 Platform::DebugPrintf(
2539 "Layout:%9.6g Paint:%9.6g Ratio:%9.6g Copy:%9.6g Total:%9.6g\n",
2540 durLayout, durPaint, durLayout / durPaint, durCopy, epWhole.Duration());
2541#endif
2542 }
2543}
2544
2545void EditView::FillLineRemainder(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
2546 Sci::Line line, PRectangle rcArea, int subLine) const {
2547 InSelection eolInSelection = InSelection::inNone;
2548 if ((!hideSelection) && (subLine == (ll->lines - 1))) {
2549 eolInSelection = model.LineEndInSelection(line);
2550 }
2551
2552 const std::optional<ColourRGBA> background = vsDraw.Background(model.pdoc->GetMark(line), model.caret.active, ll->containsCaret);
2553
2554 if (eolInSelection && vsDraw.selection.eolFilled && (line < model.pdoc->LinesTotal() - 1) && (vsDraw.selection.layer == Layer::Base)) {
2555 surface->FillRectangleAligned(rcArea, Fill(SelectionBackground(model, vsDraw, eolInSelection).Opaque()));
2556 } else {
2557 if (background) {
2558 surface->FillRectangleAligned(rcArea, Fill(*background));
2559 } else if (vsDraw.styles[ll->styles[ll->numCharsInLine]].eolFilled) {
2560 surface->FillRectangleAligned(rcArea, Fill(vsDraw.styles[ll->styles[ll->numCharsInLine]].back));
2561 } else {
2562 surface->FillRectangleAligned(rcArea, Fill(vsDraw.styles[StyleDefault].back));
2563 }
2564 if (eolInSelection && vsDraw.selection.eolFilled && (line < model.pdoc->LinesTotal() - 1) && (vsDraw.selection.layer != Layer::Base)) {
2565 surface->FillRectangleAligned(rcArea, SelectionBackground(model, vsDraw, eolInSelection));
2566 }
2567 }
2568}
2569
2570// Space (3 space characters) between line numbers and text when printing.
2571#define lineNumberPrintSpace " "
2572
2573static ColourRGBA InvertedLight(ColourRGBA orig) noexcept {
2574 unsigned int r = orig.GetRed();
2575 unsigned int g = orig.GetGreen();
2576 unsigned int b = orig.GetBlue();
2577 const unsigned int l = (r + g + b) / 3; // There is a better calculation for this that matches human eye
2578 const unsigned int il = 0xff - l;
2579 if (l == 0)
2580 return ColourRGBA(0xff, 0xff, 0xff);
2581 r = r * il / l;
2582 g = g * il / l;
2583 b = b * il / l;
2584 return ColourRGBA(std::min(r, 0xffu), std::min(g, 0xffu), std::min(b, 0xffu));
2585}
2586
2587Sci::Position EditView::FormatRange(bool draw, const RangeToFormat *pfr, Surface *surface, Surface *surfaceMeasure,
2588 const EditModel &model, const ViewStyle &vs) {
2589 // Can't use measurements cached for screen
2590 posCache.Clear();
2591
2592 ViewStyle vsPrint(vs);
2593 vsPrint.technology = Technology::Default;
2594
2595 // Modify the view style for printing as do not normally want any of the transient features to be printed
2596 // Printing supports only the line number margin.
2597 int lineNumberIndex = -1;
2598 for (size_t margin = 0; margin < vs.ms.size(); margin++) {
2599 if ((vsPrint.ms[margin].style == MarginType::Number) && (vsPrint.ms[margin].width > 0)) {
2600 lineNumberIndex = static_cast<int>(margin);
2601 } else {
2602 vsPrint.ms[margin].width = 0;
2603 }
2604 }
2605 vsPrint.fixedColumnWidth = 0;
2606 vsPrint.zoomLevel = printParameters.magnification;
2607 // Don't show indentation guides
2608 // If this ever gets changed, cached pixmap would need to be recreated if technology != Technology::Default
2609 vsPrint.viewIndentationGuides = IndentView::None;
2610 // Don't show the selection when printing
2611 vsPrint.elementColours.clear();
2612 vsPrint.elementBaseColours.clear();
2613 vsPrint.caretLine.alwaysShow = false;
2614 // Don't highlight matching braces using indicators
2615 vsPrint.braceHighlightIndicatorSet = false;
2616 vsPrint.braceBadLightIndicatorSet = false;
2617
2618 // Set colours for printing according to users settings
2619 const PrintOption colourMode = printParameters.colourMode;
2620 const std::vector<Style>::iterator endStyles = (colourMode == PrintOption::ColourOnWhiteDefaultBG) ?
2621 vsPrint.styles.begin() + StyleLineNumber : vsPrint.styles.end();
2622 for (std::vector<Style>::iterator it = vsPrint.styles.begin(); it != endStyles; ++it) {
2623 if (colourMode == PrintOption::InvertLight) {
2624 it->fore = InvertedLight(it->fore);
2625 it->back = InvertedLight(it->back);
2626 } else if (colourMode == PrintOption::BlackOnWhite) {
2627 it->fore = ColourRGBA(0, 0, 0);
2628 it->back = ColourRGBA(0xff, 0xff, 0xff);
2629 } else if (colourMode == PrintOption::ColourOnWhite || colourMode == PrintOption::ColourOnWhiteDefaultBG) {
2630 it->back = ColourRGBA(0xff, 0xff, 0xff);
2631 }
2632 }
2633 // White background for the line numbers if PrintOption::ScreenColours isn't used
2634 if (colourMode != PrintOption::ScreenColours) {
2635 vsPrint.styles[StyleLineNumber].back = ColourRGBA(0xff, 0xff, 0xff);
2636 }
2637
2638 // Printing uses different margins, so reset screen margins
2639 vsPrint.leftMarginWidth = 0;
2640 vsPrint.rightMarginWidth = 0;
2641
2642 vsPrint.Refresh(*surfaceMeasure, model.pdoc->tabInChars);
2643 // Determining width must happen after fonts have been realised in Refresh
2644 int lineNumberWidth = 0;
2645 if (lineNumberIndex >= 0) {
2646 lineNumberWidth = static_cast<int>(surfaceMeasure->WidthText(vsPrint.styles[StyleLineNumber].font.get(),
2647 "99999" lineNumberPrintSpace));
2648 vsPrint.ms[lineNumberIndex].width = lineNumberWidth;
2649 vsPrint.Refresh(*surfaceMeasure, model.pdoc->tabInChars); // Recalculate fixedColumnWidth
2650 }
2651
2652 const Sci::Line linePrintStart = model.pdoc->SciLineFromPosition(pfr->chrg.cpMin);
2653 Sci::Line linePrintLast = linePrintStart + (pfr->rc.bottom - pfr->rc.top) / vsPrint.lineHeight - 1;
2654 if (linePrintLast < linePrintStart)
2655 linePrintLast = linePrintStart;
2656 const Sci::Line linePrintMax = model.pdoc->SciLineFromPosition(pfr->chrg.cpMax);
2657 if (linePrintLast > linePrintMax)
2658 linePrintLast = linePrintMax;
2659 //Platform::DebugPrintf("Formatting lines=[%0d,%0d,%0d] top=%0d bottom=%0d line=%0d %0d\n",
2660 // linePrintStart, linePrintLast, linePrintMax, pfr->rc.top, pfr->rc.bottom, vsPrint.lineHeight,
2661 // surfaceMeasure->Height(vsPrint.styles[StyleLineNumber].font));
2662 Sci::Position endPosPrint = model.pdoc->Length();
2663 if (linePrintLast < model.pdoc->LinesTotal())
2664 endPosPrint = model.pdoc->LineStart(linePrintLast + 1);
2665
2666 // Ensure we are styled to where we are formatting.
2667 model.pdoc->EnsureStyledTo(endPosPrint);
2668
2669 const int xStart = vsPrint.fixedColumnWidth + pfr->rc.left;
2670 int ypos = pfr->rc.top;
2671
2672 Sci::Line lineDoc = linePrintStart;
2673
2674 Sci::Position nPrintPos = pfr->chrg.cpMin;
2675 int visibleLine = 0;
2676 int widthPrint = pfr->rc.right - pfr->rc.left - vsPrint.fixedColumnWidth;
2677 if (printParameters.wrapState == Wrap::None)
2678 widthPrint = LineLayout::wrapWidthInfinite;
2679
2680 while (lineDoc <= linePrintLast && ypos < pfr->rc.bottom) {
2681
2682 // When printing, the hdc and hdcTarget may be the same, so
2683 // changing the state of surfaceMeasure may change the underlying
2684 // state of surface. Therefore, any cached state is discarded before
2685 // using each surface.
2686 surfaceMeasure->FlushCachedState();
2687
2688 // Copy this line and its styles from the document into local arrays
2689 // and determine the x position at which each character starts.
2690 LineLayout ll(lineDoc, static_cast<int>(model.pdoc->LineStart(lineDoc + 1) - model.pdoc->LineStart(lineDoc) + 1));
2691 LayoutLine(model, surfaceMeasure, vsPrint, &ll, widthPrint);
2692
2693 ll.containsCaret = false;
2694
2695 PRectangle rcLine = PRectangle::FromInts(
2696 pfr->rc.left,
2697 ypos,
2698 pfr->rc.right - 1,
2699 ypos + vsPrint.lineHeight);
2700
2701 // When document line is wrapped over multiple display lines, find where
2702 // to start printing from to ensure a particular position is on the first
2703 // line of the page.
2704 if (visibleLine == 0) {
2705 const Sci::Position startWithinLine = nPrintPos -
2706 model.pdoc->LineStart(lineDoc);
2707 for (int iwl = 0; iwl < ll.lines - 1; iwl++) {
2708 if (ll.LineStart(iwl) <= startWithinLine && ll.LineStart(iwl + 1) >= startWithinLine) {
2709 visibleLine = -iwl;
2710 }
2711 }
2712
2713 if (ll.lines > 1 && startWithinLine >= ll.LineStart(ll.lines - 1)) {
2714 visibleLine = -(ll.lines - 1);
2715 }
2716 }
2717
2718 if (draw && lineNumberWidth &&
2719 (ypos + vsPrint.lineHeight <= pfr->rc.bottom) &&
2720 (visibleLine >= 0)) {
2721 const std::string number = std::to_string(lineDoc + 1) + lineNumberPrintSpace;
2722 PRectangle rcNumber = rcLine;
2723 rcNumber.right = rcNumber.left + lineNumberWidth;
2724 // Right justify
2725 rcNumber.left = rcNumber.right - surfaceMeasure->WidthText(
2726 vsPrint.styles[StyleLineNumber].font.get(), number);
2727 surface->FlushCachedState();
2728 surface->DrawTextNoClip(rcNumber, vsPrint.styles[StyleLineNumber].font.get(),
2729 ypos + vsPrint.maxAscent, number,
2730 vsPrint.styles[StyleLineNumber].fore,
2731 vsPrint.styles[StyleLineNumber].back);
2732 }
2733
2734 // Draw the line
2735 surface->FlushCachedState();
2736
2737 for (int iwl = 0; iwl < ll.lines; iwl++) {
2738 if (ypos + vsPrint.lineHeight <= pfr->rc.bottom) {
2739 if (visibleLine >= 0) {
2740 if (draw) {
2741 rcLine.top = static_cast<XYPOSITION>(ypos);
2742 rcLine.bottom = static_cast<XYPOSITION>(ypos + vsPrint.lineHeight);
2743 DrawLine(surface, model, vsPrint, &ll, lineDoc, visibleLine, xStart, rcLine, iwl, DrawPhase::all);
2744 }
2745 ypos += vsPrint.lineHeight;
2746 }
2747 visibleLine++;
2748 if (iwl == ll.lines - 1)
2749 nPrintPos = model.pdoc->LineStart(lineDoc + 1);
2750 else
2751 nPrintPos += ll.LineStart(iwl + 1) - ll.LineStart(iwl);
2752 }
2753 }
2754
2755 ++lineDoc;
2756 }
2757
2758 // Clear cache so measurements are not used for screen
2759 posCache.Clear();
2760
2761 return nPrintPos;
2762}
2763