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