1 // Scintilla source code edit control
3 ** Defines the appearance of the main text area of the editor window.
5 // Copyright 1998-2014 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
18 #include <string_view>
22 #include <forward_list>
32 #include "ScintillaTypes.h"
33 #include "ScintillaMessages.h"
34 #include "ScintillaStructures.h"
38 #include "Debugging.h"
42 #include "CharacterType.h"
43 #include "CharacterCategoryMap.h"
45 #include "UniqueString.h"
46 #include "SplitVector.h"
47 #include "Partitioning.h"
48 #include "RunStyles.h"
49 #include "ContractionState.h"
50 #include "CellBuffer.h"
53 #include "Indicator.h"
54 #include "LineMarker.h"
56 #include "ViewStyle.h"
57 #include "CharClassify.h"
58 #include "Decoration.h"
59 #include "CaseFolder.h"
61 #include "UniConversion.h"
62 #include "Selection.h"
63 #include "PositionCache.h"
64 #include "EditModel.h"
65 #include "MarginView.h"
67 #include "ElapsedPeriod.h"
70 using namespace Scintilla
;
71 using namespace Scintilla::Internal
;
73 PrintParameters::PrintParameters() noexcept
{
75 colourMode
= PrintOption::Normal
;
76 wrapState
= Wrap::Word
;
81 int WidthStyledText(Surface
*surface
, const ViewStyle
&vs
, int styleOffset
,
82 const char *text
, const unsigned char *styles
, size_t len
) {
86 const unsigned char style
= styles
[start
];
87 size_t endSegment
= start
;
88 while ((endSegment
+ 1 < len
) && (styles
[endSegment
+ 1] == style
))
90 const Font
*fontText
= vs
.styles
[style
+ styleOffset
].font
.get();
91 const std::string_view
sv(text
+ start
, endSegment
- start
+ 1);
92 width
+= static_cast<int>(surface
->WidthText(fontText
, sv
));
93 start
= endSegment
+ 1;
100 namespace Scintilla::Internal
{
102 bool ValidStyledText(const ViewStyle
&vs
, size_t styleOffset
, const StyledText
&st
) noexcept
{
103 if (st
.multipleStyles
) {
104 for (size_t iStyle
= 0; iStyle
<st
.length
; iStyle
++) {
105 if (!vs
.ValidStyle(styleOffset
+ st
.styles
[iStyle
]))
109 if (!vs
.ValidStyle(styleOffset
+ st
.style
))
115 int WidestLineWidth(Surface
*surface
, const ViewStyle
&vs
, int styleOffset
, const StyledText
&st
) {
118 while (start
< st
.length
) {
119 const size_t lenLine
= st
.LineLength(start
);
121 if (st
.multipleStyles
) {
122 widthSubLine
= WidthStyledText(surface
, vs
, styleOffset
, st
.text
+ start
, st
.styles
+ start
, lenLine
);
124 const Font
*fontText
= vs
.styles
[styleOffset
+ st
.style
].font
.get();
125 const std::string_view
text(st
.text
+ start
, lenLine
);
126 widthSubLine
= static_cast<int>(surface
->WidthText(fontText
, text
));
128 if (widthSubLine
> widthMax
)
129 widthMax
= widthSubLine
;
130 start
+= lenLine
+ 1;
135 void DrawTextNoClipPhase(Surface
*surface
, PRectangle rc
, const Style
&style
, XYPOSITION ybase
,
136 std::string_view text
, DrawPhase phase
) {
137 const Font
*fontText
= style
.font
.get();
138 if (FlagSet(phase
, DrawPhase::back
)) {
139 if (FlagSet(phase
, DrawPhase::text
)) {
141 surface
->DrawTextNoClip(rc
, fontText
, ybase
, text
,
142 style
.fore
, style
.back
);
144 surface
->FillRectangleAligned(rc
, Fill(style
.back
));
146 } else if (FlagSet(phase
, DrawPhase::text
)) {
147 surface
->DrawTextTransparent(rc
, fontText
, ybase
, text
, style
.fore
);
151 void DrawStyledText(Surface
*surface
, const ViewStyle
&vs
, int styleOffset
, PRectangle rcText
,
152 const StyledText
&st
, size_t start
, size_t length
, DrawPhase phase
) {
154 if (st
.multipleStyles
) {
155 int x
= static_cast<int>(rcText
.left
);
159 size_t style
= st
.styles
[i
+ start
];
160 while (end
< length
- 1 && st
.styles
[start
+ end
+ 1] == style
)
162 style
+= styleOffset
;
163 const Font
*fontText
= vs
.styles
[style
].font
.get();
164 const std::string_view
text(st
.text
+ start
+ i
, end
- i
+ 1);
165 const int width
= static_cast<int>(surface
->WidthText(fontText
, text
));
166 PRectangle rcSegment
= rcText
;
167 rcSegment
.left
= static_cast<XYPOSITION
>(x
);
168 rcSegment
.right
= static_cast<XYPOSITION
>(x
+ width
+ 1);
169 DrawTextNoClipPhase(surface
, rcSegment
, vs
.styles
[style
],
170 rcText
.top
+ vs
.maxAscent
, text
, phase
);
175 const size_t style
= st
.style
+ styleOffset
;
176 DrawTextNoClipPhase(surface
, rcText
, vs
.styles
[style
],
177 rcText
.top
+ vs
.maxAscent
,
178 std::string_view(st
.text
+ start
, length
), phase
);
184 EditView::EditView() {
185 tabWidthMinimumPixels
= 2; // needed for calculating tab stops for fractional proportional fonts
186 drawOverstrikeCaret
= true;
188 phasesDraw
= PhasesDraw::Two
;
189 lineWidthMaxSeen
= 0;
190 additionalCaretsBlink
= true;
191 additionalCaretsVisible
= true;
192 imeCaretBlockOverride
= false;
193 llc
.SetLevel(LineCache::Caret
);
194 posCache
= CreatePositionCache();
195 posCache
->SetSize(0x400);
196 maxLayoutThreads
= 1;
198 customDrawTabArrow
= nullptr;
199 customDrawWrapMarker
= nullptr;
203 EditView::~EditView() = default;
205 bool EditView::SetTwoPhaseDraw(bool twoPhaseDraw
) noexcept
{
206 const PhasesDraw phasesDrawNew
= twoPhaseDraw
? PhasesDraw::Two
: PhasesDraw::One
;
207 const bool redraw
= phasesDraw
!= phasesDrawNew
;
208 phasesDraw
= phasesDrawNew
;
212 bool EditView::SetPhasesDraw(int phases
) noexcept
{
213 const PhasesDraw phasesDrawNew
= static_cast<PhasesDraw
>(phases
);
214 const bool redraw
= phasesDraw
!= phasesDrawNew
;
215 phasesDraw
= phasesDrawNew
;
219 bool EditView::LinesOverlap() const noexcept
{
220 return phasesDraw
== PhasesDraw::Multiple
;
223 void EditView::SetLayoutThreads(unsigned int threads
) noexcept
{
224 maxLayoutThreads
= std::clamp(threads
, 1U, std::thread::hardware_concurrency());
227 unsigned int EditView::GetLayoutThreads() const noexcept
{
228 return maxLayoutThreads
;
231 void EditView::ClearAllTabstops() noexcept
{
235 XYPOSITION
EditView::NextTabstopPos(Sci::Line line
, XYPOSITION x
, XYPOSITION tabWidth
) const noexcept
{
236 const int next
= GetNextTabstop(line
, static_cast<int>(x
+ tabWidthMinimumPixels
));
238 return static_cast<XYPOSITION
>(next
);
239 return (static_cast<int>((x
+ tabWidthMinimumPixels
) / tabWidth
) + 1) * tabWidth
;
242 bool EditView::ClearTabstops(Sci::Line line
) noexcept
{
243 return ldTabstops
&& ldTabstops
->ClearTabstops(line
);
246 bool EditView::AddTabstop(Sci::Line line
, int x
) {
248 ldTabstops
= std::make_unique
<LineTabstops
>();
250 return ldTabstops
&& ldTabstops
->AddTabstop(line
, x
);
253 int EditView::GetNextTabstop(Sci::Line line
, int x
) const noexcept
{
255 return ldTabstops
->GetNextTabstop(line
, x
);
261 void EditView::LinesAddedOrRemoved(Sci::Line lineOfPos
, Sci::Line linesAdded
) {
263 if (linesAdded
> 0) {
264 for (Sci::Line line
= lineOfPos
; line
< lineOfPos
+ linesAdded
; line
++) {
265 ldTabstops
->InsertLine(line
);
268 for (Sci::Line line
= (lineOfPos
+ -linesAdded
) - 1; line
>= lineOfPos
; line
--) {
269 ldTabstops
->RemoveLine(line
);
275 void EditView::DropGraphics() noexcept
{
277 pixmapIndentGuide
.reset();
278 pixmapIndentGuideHighlight
.reset();
281 void EditView::RefreshPixMaps(Surface
*surfaceWindow
, const ViewStyle
&vsDraw
) {
282 if (!pixmapIndentGuide
) {
283 // 1 extra pixel in height so can handle odd/even positions and so produce a continuous line
284 pixmapIndentGuide
= surfaceWindow
->AllocatePixMap(1, vsDraw
.lineHeight
+ 1);
285 pixmapIndentGuideHighlight
= surfaceWindow
->AllocatePixMap(1, vsDraw
.lineHeight
+ 1);
286 const PRectangle rcIG
= PRectangle::FromInts(0, 0, 1, vsDraw
.lineHeight
);
287 pixmapIndentGuide
->FillRectangle(rcIG
, vsDraw
.styles
[StyleIndentGuide
].back
);
288 pixmapIndentGuideHighlight
->FillRectangle(rcIG
, vsDraw
.styles
[StyleBraceLight
].back
);
289 for (int stripe
= 1; stripe
< vsDraw
.lineHeight
+ 1; stripe
+= 2) {
290 const PRectangle rcPixel
= PRectangle::FromInts(0, stripe
, 1, stripe
+ 1);
291 pixmapIndentGuide
->FillRectangle(rcPixel
, vsDraw
.styles
[StyleIndentGuide
].fore
);
292 pixmapIndentGuideHighlight
->FillRectangle(rcPixel
, vsDraw
.styles
[StyleBraceLight
].fore
);
294 pixmapIndentGuide
->FlushDrawing();
295 pixmapIndentGuideHighlight
->FlushDrawing();
299 std::shared_ptr
<LineLayout
> EditView::RetrieveLineLayout(Sci::Line lineNumber
, const EditModel
&model
) {
300 const Sci::Position posLineStart
= model
.pdoc
->LineStart(lineNumber
);
301 const Sci::Position posLineEnd
= model
.pdoc
->LineStart(lineNumber
+ 1);
302 PLATFORM_ASSERT(posLineEnd
>= posLineStart
);
303 const Sci::Line lineCaret
= model
.pdoc
->SciLineFromPosition(model
.sel
.MainCaret());
304 return llc
.Retrieve(lineNumber
, lineCaret
,
305 static_cast<int>(posLineEnd
- posLineStart
), model
.pdoc
->GetStyleClock(),
306 model
.LinesOnScreen() + 1, model
.pdoc
->LinesTotal());
311 constexpr XYPOSITION epsilon
= 0.0001f
; // A small nudge to avoid floating point precision issues
314 * Return the chDoc argument with case transformed as indicated by the caseForce argument.
315 * chPrevious is needed for camel casing.
316 * This only affects ASCII characters and is provided for languages with case-insensitive
317 * ASCII keywords where the user wishes to view keywords in a preferred case.
319 inline char CaseForce(Style::CaseForce caseForce
, char chDoc
, char chPrevious
) noexcept
{
321 case Style::CaseForce::mixed
:
323 case Style::CaseForce::lower
:
324 return MakeLowerCase(chDoc
);
325 case Style::CaseForce::upper
:
326 return MakeUpperCase(chDoc
);
327 case Style::CaseForce::camel
:
328 default: // default should not occur, included to avoid warnings
329 if (IsUpperOrLowerCase(chDoc
) && !IsUpperOrLowerCase(chPrevious
)) {
330 return MakeUpperCase(chDoc
);
332 return MakeLowerCase(chDoc
);
337 void LayoutSegments(IPositionCache
*pCache
,
339 const ViewStyle
&vstyle
,
341 const std::vector
<TextSegment
> &segments
,
342 std::atomic
<uint32_t> &nextIndex
,
343 const bool textUnicode
,
344 const bool multiThreaded
) {
346 const uint32_t i
= nextIndex
.fetch_add(1, std::memory_order_acq_rel
);
347 if (i
>= segments
.size()) {
350 const TextSegment
&ts
= segments
[i
];
351 const unsigned int styleSegment
= ll
->styles
[ts
.start
];
352 XYPOSITION
*positions
= &ll
->positions
[ts
.start
+ 1];
353 if (vstyle
.styles
[styleSegment
].visible
) {
354 if (ts
.representation
) {
355 XYPOSITION representationWidth
= 0.0;
356 // Tab is a special case of representation, taking a variable amount of space
357 // which will be filled in later.
358 if (ll
->chars
[ts
.start
] != '\t') {
359 representationWidth
= vstyle
.controlCharWidth
;
360 if (representationWidth
<= 0.0) {
361 assert(ts
.representation
->stringRep
.length() <= Representation::maxLength
);
362 XYPOSITION positionsRepr
[Representation::maxLength
+ 1];
363 // ts.representation->stringRep is UTF-8.
364 pCache
->MeasureWidths(surface
, vstyle
, StyleControlChar
, true, ts
.representation
->stringRep
,
365 positionsRepr
, multiThreaded
);
366 representationWidth
= positionsRepr
[ts
.representation
->stringRep
.length() - 1];
367 if (FlagSet(ts
.representation
->appearance
, RepresentationAppearance::Blob
)) {
368 representationWidth
+= vstyle
.ctrlCharPadding
;
372 std::fill(positions
, positions
+ ts
.length
, representationWidth
);
374 if ((ts
.length
== 1) && (' ' == ll
->chars
[ts
.start
])) {
375 // Over half the segments are single characters and of these about half are space characters.
376 positions
[0] = vstyle
.styles
[styleSegment
].spaceWidth
;
378 pCache
->MeasureWidths(surface
, vstyle
, styleSegment
, textUnicode
,
379 std::string_view(&ll
->chars
[ts
.start
], ts
.length
), positions
, multiThreaded
);
382 } else if (vstyle
.styles
[styleSegment
].invisibleRepresentation
[0]) {
383 const std::string_view text
= vstyle
.styles
[styleSegment
].invisibleRepresentation
;
384 XYPOSITION positionsRepr
[Representation::maxLength
+ 1];
385 // invisibleRepresentation is UTF-8.
386 pCache
->MeasureWidths(surface
, vstyle
, styleSegment
, true, text
, positionsRepr
, multiThreaded
);
387 const XYPOSITION representationWidth
= positionsRepr
[text
.length() - 1];
388 std::fill(positions
, positions
+ ts
.length
, representationWidth
);
396 * Fill in the LineLayout data for the given line.
397 * Copy the given @a line and its styles from the document into local arrays.
398 * Also determine the x position at which each character starts.
400 void EditView::LayoutLine(const EditModel
&model
, Surface
*surface
, const ViewStyle
&vstyle
, LineLayout
*ll
, int width
, bool callerMultiThreaded
) {
403 const Sci::Line line
= ll
->LineNumber();
404 PLATFORM_ASSERT(line
< model
.pdoc
->LinesTotal());
405 PLATFORM_ASSERT(ll
->chars
);
406 const Sci::Position posLineStart
= model
.pdoc
->LineStart(line
);
407 Sci::Position posLineEnd
= model
.pdoc
->LineStart(line
+ 1);
408 // If the line is very long, limit the treatment to a length that should fit in the viewport
409 if (posLineEnd
>(posLineStart
+ ll
->maxLineLength
)) {
410 posLineEnd
= posLineStart
+ ll
->maxLineLength
;
412 // Hard to cope when too narrow, so just assume there is space
413 width
= std::max(width
, 20);
415 if (ll
->validity
== LineLayout::ValidLevel::checkTextAndStyle
) {
416 Sci::Position lineLength
= posLineEnd
- posLineStart
;
417 if (!vstyle
.viewEOL
) {
418 lineLength
= model
.pdoc
->LineEnd(line
) - posLineStart
;
420 if (lineLength
== ll
->numCharsInLine
) {
421 // See if chars, styles, indicators, are all the same
423 // Check base line layout
425 for (Sci::Position numCharsInLine
= 0; numCharsInLine
< lineLength
; numCharsInLine
++) {
426 const Sci::Position charInDoc
= numCharsInLine
+ posLineStart
;
427 const char chDoc
= model
.pdoc
->CharAt(charInDoc
);
428 const int styleByte
= model
.pdoc
->StyleIndexAt(charInDoc
);
430 (ll
->styles
[numCharsInLine
] == styleByte
);
432 (ll
->chars
[numCharsInLine
] == CaseForce(vstyle
.styles
[styleByte
].caseForce
, chDoc
, chPrevious
));
435 const int styleByteLast
= (posLineEnd
> posLineStart
) ? model
.pdoc
->StyleIndexAt(posLineEnd
- 1) : 0;
436 allSame
= allSame
&& (ll
->styles
[lineLength
] == styleByteLast
); // For eolFilled
438 ll
->validity
= (ll
->widthLine
!= width
) ? LineLayout::ValidLevel::positions
: LineLayout::ValidLevel::lines
;
440 ll
->validity
= LineLayout::ValidLevel::invalid
;
443 ll
->validity
= LineLayout::ValidLevel::invalid
;
446 if (ll
->validity
== LineLayout::ValidLevel::invalid
) {
447 ll
->widthLine
= LineLayout::wrapWidthInfinite
;
449 if (vstyle
.edgeState
== EdgeVisualStyle::Background
) {
450 Sci::Position edgePosition
= model
.pdoc
->FindColumn(line
, vstyle
.theEdge
.column
);
451 if (edgePosition
>= posLineStart
) {
452 edgePosition
-= posLineStart
;
454 ll
->edgeColumn
= static_cast<int>(edgePosition
);
459 // Fill base line layout
460 const int lineLength
= static_cast<int>(posLineEnd
- posLineStart
);
461 model
.pdoc
->GetCharRange(ll
->chars
.get(), posLineStart
, lineLength
);
462 model
.pdoc
->GetStyleRange(ll
->styles
.get(), posLineStart
, lineLength
);
463 const int numCharsBeforeEOL
= static_cast<int>(model
.pdoc
->LineEnd(line
) - posLineStart
);
464 const int numCharsInLine
= (vstyle
.viewEOL
) ? lineLength
: numCharsBeforeEOL
;
465 const unsigned char styleByteLast
= (lineLength
> 0) ? ll
->styles
[lineLength
- 1] : 0;
466 if (vstyle
.someStylesForceCase
) {
468 for (int charInLine
= 0; charInLine
<lineLength
; charInLine
++) {
469 const char chDoc
= ll
->chars
[charInLine
];
470 ll
->chars
[charInLine
] = CaseForce(vstyle
.styles
[ll
->styles
[charInLine
]].caseForce
, chDoc
, chPrevious
);
474 ll
->xHighlightGuide
= 0;
475 // Extra element at the end of the line to hold end x position and act as
476 ll
->chars
[numCharsInLine
] = 0; // Also triggers processing in the loops as this is a control character
477 ll
->styles
[numCharsInLine
] = styleByteLast
; // For eolFilled
479 // Layout the line, determining the position of each character,
480 // with an extra element at the end for the end of the line.
481 ll
->positions
[0] = 0;
482 bool lastSegItalics
= false;
484 std::vector
<TextSegment
> segments
;
485 BreakFinder
bfLayout(ll
, nullptr, Range(0, numCharsInLine
), posLineStart
, 0, BreakFinder::BreakFor::Text
, model
.pdoc
, &model
.reprs
, nullptr);
486 while (bfLayout
.More()) {
487 segments
.push_back(bfLayout
.Next());
490 ll
->ClearPositions();
492 if (!segments
.empty()) {
494 const size_t threadsForLength
= std::max(1, numCharsInLine
/ bytesPerLayoutThread
);
495 size_t threads
= std::min
<size_t>({ segments
.size(), threadsForLength
, maxLayoutThreads
});
496 if (!surface
->SupportsFeature(Supports::ThreadSafeMeasureWidths
) || callerMultiThreaded
) {
500 std::atomic
<uint32_t> nextIndex
= 0;
502 const bool textUnicode
= CpUtf8
== model
.pdoc
->dbcsCodePage
;
503 const bool multiThreaded
= threads
> 1;
504 const bool multiThreadedContext
= multiThreaded
|| callerMultiThreaded
;
505 IPositionCache
*pCache
= posCache
.get();
507 // If only 1 thread needed then use the main thread, else spin up multiple
508 const std::launch policy
= (multiThreaded
) ? std::launch::async
: std::launch::deferred
;
510 std::vector
<std::future
<void>> futures
;
511 for (size_t th
= 0; th
< threads
; th
++) {
512 // Find relative positions of everything except for tabs
513 std::future
<void> fut
= std::async(policy
,
514 [pCache
, surface
, &vstyle
, &ll
, &segments
, &nextIndex
, textUnicode
, multiThreadedContext
]() {
515 LayoutSegments(pCache
, surface
, vstyle
, ll
, segments
, nextIndex
, textUnicode
, multiThreadedContext
);
517 futures
.push_back(std::move(fut
));
519 for (const std::future
<void> &f
: futures
) {
524 // Accumulate absolute positions from relative positions within segments and expand tabs
525 XYPOSITION xPosition
= 0.0;
527 ll
->positions
[iByte
++] = xPosition
;
528 for (const TextSegment
&ts
: segments
) {
529 if (vstyle
.styles
[ll
->styles
[ts
.start
]].visible
&&
531 (ll
->chars
[ts
.start
] == '\t')) {
532 // Simple visible tab, go to next tab stop
533 const XYPOSITION startTab
= ll
->positions
[ts
.start
];
534 const XYPOSITION nextTab
= NextTabstopPos(line
, startTab
, vstyle
.tabWidth
);
535 xPosition
+= nextTab
- startTab
;
537 const XYPOSITION xBeginSegment
= xPosition
;
538 for (int i
= 0; i
< ts
.length
; i
++) {
539 xPosition
= ll
->positions
[iByte
] + xBeginSegment
;
540 ll
->positions
[iByte
++] = xPosition
;
544 if (!segments
.empty()) {
545 // Not quite the same as before which would effectively ignore trailing invisible segments
546 const TextSegment
&ts
= segments
.back();
547 lastSegItalics
= (!ts
.representation
) && ((ll
->chars
[ts
.end() - 1] != ' ') && vstyle
.styles
[ll
->styles
[ts
.start
]].italic
);
550 // Small hack to make lines that end with italics not cut off the edge of the last character
551 if (lastSegItalics
) {
552 ll
->positions
[numCharsInLine
] += vstyle
.lastSegItalicsOffset
;
554 ll
->numCharsInLine
= numCharsInLine
;
555 ll
->numCharsBeforeEOL
= numCharsBeforeEOL
;
556 ll
->validity
= LineLayout::ValidLevel::positions
;
558 if ((ll
->validity
== LineLayout::ValidLevel::positions
) || (ll
->widthLine
!= width
)) {
559 ll
->widthLine
= width
;
560 if (width
== LineLayout::wrapWidthInfinite
) {
562 } else if (width
> ll
->positions
[ll
->numCharsInLine
]) {
563 // Simple common case where line does not need wrapping.
566 if (FlagSet(vstyle
.wrap
.visualFlags
, WrapVisualFlag::End
)) {
567 width
-= static_cast<int>(vstyle
.aveCharWidth
); // take into account the space for end wrap mark
569 XYPOSITION wrapAddIndent
= 0; // This will be added to initial indent of line
570 switch (vstyle
.wrap
.indentMode
) {
571 case WrapIndentMode::Fixed
:
572 wrapAddIndent
= vstyle
.wrap
.visualStartIndent
* vstyle
.aveCharWidth
;
574 case WrapIndentMode::Indent
:
575 wrapAddIndent
= model
.pdoc
->IndentSize() * vstyle
.spaceWidth
;
577 case WrapIndentMode::DeepIndent
:
578 wrapAddIndent
= model
.pdoc
->IndentSize() * 2 * vstyle
.spaceWidth
;
580 default: // No additional indent for WrapIndentMode::Fixed
583 ll
->wrapIndent
= wrapAddIndent
;
584 if (vstyle
.wrap
.indentMode
!= WrapIndentMode::Fixed
) {
585 for (int i
= 0; i
< ll
->numCharsInLine
; i
++) {
586 if (!IsSpaceOrTab(ll
->chars
[i
])) {
587 ll
->wrapIndent
+= ll
->positions
[i
]; // Add line indent
592 // Check for text width minimum
593 if (ll
->wrapIndent
> width
- static_cast<int>(vstyle
.aveCharWidth
) * 15)
594 ll
->wrapIndent
= wrapAddIndent
;
595 // Check for wrapIndent minimum
596 if ((FlagSet(vstyle
.wrap
.visualFlags
, WrapVisualFlag::Start
)) && (ll
->wrapIndent
< vstyle
.aveCharWidth
))
597 ll
->wrapIndent
= vstyle
.aveCharWidth
; // Indent to show start visual
598 ll
->WrapLine(model
.pdoc
, posLineStart
, vstyle
.wrap
.state
, width
);
600 ll
->validity
= LineLayout::ValidLevel::lines
;
604 // Fill the LineLayout bidirectional data fields according to each char style
606 void EditView::UpdateBidiData(const EditModel
&model
, const ViewStyle
&vstyle
, LineLayout
*ll
) {
607 if (model
.BidirectionalEnabled()) {
608 ll
->EnsureBidiData();
609 for (int stylesInLine
= 0; stylesInLine
< ll
->numCharsInLine
; stylesInLine
++) {
610 ll
->bidiData
->stylesFonts
[stylesInLine
] = vstyle
.styles
[ll
->styles
[stylesInLine
]].font
;
612 ll
->bidiData
->stylesFonts
[ll
->numCharsInLine
].reset();
614 for (int charsInLine
= 0; charsInLine
< ll
->numCharsInLine
; charsInLine
++) {
615 const int charWidth
= UTF8DrawBytes(&ll
->chars
[charsInLine
], ll
->numCharsInLine
- charsInLine
);
616 const Representation
*repr
= model
.reprs
.RepresentationFromCharacter(std::string_view(&ll
->chars
[charsInLine
], charWidth
));
618 ll
->bidiData
->widthReprs
[charsInLine
] = 0.0f
;
619 if (repr
&& ll
->chars
[charsInLine
] != '\t') {
620 ll
->bidiData
->widthReprs
[charsInLine
] = ll
->positions
[charsInLine
+ charWidth
] - ll
->positions
[charsInLine
];
623 for (int c
= 1; c
< charWidth
; c
++) {
625 ll
->bidiData
->widthReprs
[charsInLine
] = 0.0f
;
629 ll
->bidiData
->widthReprs
[ll
->numCharsInLine
] = 0.0f
;
631 ll
->bidiData
.reset();
635 Point
EditView::LocationFromPosition(Surface
*surface
, const EditModel
&model
, SelectionPosition pos
, Sci::Line topLine
,
636 const ViewStyle
&vs
, PointEnd pe
, const PRectangle rcClient
) {
638 if (pos
.Position() == Sci::invalidPosition
)
640 Sci::Line lineDoc
= model
.pdoc
->SciLineFromPosition(pos
.Position());
641 Sci::Position posLineStart
= model
.pdoc
->LineStart(lineDoc
);
642 if (FlagSet(pe
, PointEnd::lineEnd
) && (lineDoc
> 0) && (pos
.Position() == posLineStart
)) {
643 // Want point at end of first line
645 posLineStart
= model
.pdoc
->LineStart(lineDoc
);
647 const Sci::Line lineVisible
= model
.pcs
->DisplayFromDoc(lineDoc
);
648 std::shared_ptr
<LineLayout
> ll
= RetrieveLineLayout(lineDoc
, model
);
650 LayoutLine(model
, surface
, vs
, ll
.get(), model
.wrapWidth
);
651 const int posInLine
= static_cast<int>(pos
.Position() - posLineStart
);
652 pt
= ll
->PointFromPosition(posInLine
, vs
.lineHeight
, pe
);
653 pt
.x
+= vs
.textStart
- model
.xOffset
;
655 if (model
.BidirectionalEnabled()) {
656 // Fill the line bidi data
657 UpdateBidiData(model
, vs
, ll
.get());
660 const int subLine
= ll
->SubLineFromPosition(posInLine
, pe
);
661 const int caretPosition
= posInLine
- ll
->LineStart(subLine
);
663 // Get the point from current position
664 const ScreenLine
screenLine(ll
.get(), subLine
, vs
, rcClient
.right
, tabWidthMinimumPixels
);
665 std::unique_ptr
<IScreenLineLayout
> slLayout
= surface
->Layout(&screenLine
);
666 pt
.x
= slLayout
->XFromPosition(caretPosition
);
668 pt
.x
+= vs
.textStart
- model
.xOffset
;
671 if (posInLine
>= ll
->LineStart(subLine
)) {
672 pt
.y
= static_cast<XYPOSITION
>(subLine
*vs
.lineHeight
);
675 pt
.y
+= (lineVisible
- topLine
) * vs
.lineHeight
;
676 pt
.x
+= pos
.VirtualSpace() * vs
.styles
[ll
->EndLineStyle()].spaceWidth
;
681 Range
EditView::RangeDisplayLine(Surface
*surface
, const EditModel
&model
, Sci::Line lineVisible
, const ViewStyle
&vs
) {
682 Range rangeSubLine
= Range(0, 0);
683 if (lineVisible
< 0) {
686 const Sci::Line lineDoc
= model
.pcs
->DocFromDisplay(lineVisible
);
687 const Sci::Position positionLineStart
= model
.pdoc
->LineStart(lineDoc
);
688 std::shared_ptr
<LineLayout
> ll
= RetrieveLineLayout(lineDoc
, model
);
690 LayoutLine(model
, surface
, vs
, ll
.get(), model
.wrapWidth
);
691 const Sci::Line lineStartSet
= model
.pcs
->DisplayFromDoc(lineDoc
);
692 const int subLine
= static_cast<int>(lineVisible
- lineStartSet
);
693 if (subLine
< ll
->lines
) {
694 rangeSubLine
= ll
->SubLineRange(subLine
, LineLayout::Scope::visibleOnly
);
695 if (subLine
== ll
->lines
-1) {
696 rangeSubLine
.end
= model
.pdoc
->LineStart(lineDoc
+ 1) -
701 rangeSubLine
.start
+= positionLineStart
;
702 rangeSubLine
.end
+= positionLineStart
;
706 SelectionPosition
EditView::SPositionFromLocation(Surface
*surface
, const EditModel
&model
, PointDocument pt
, bool canReturnInvalid
,
707 bool charPosition
, bool virtualSpace
, const ViewStyle
&vs
, const PRectangle rcClient
) {
708 pt
.x
= pt
.x
- vs
.textStart
;
709 Sci::Line visibleLine
= static_cast<int>(std::floor(pt
.y
/ vs
.lineHeight
));
710 if (!canReturnInvalid
&& (visibleLine
< 0))
712 const Sci::Line lineDoc
= model
.pcs
->DocFromDisplay(visibleLine
);
713 if (canReturnInvalid
&& (lineDoc
< 0))
714 return SelectionPosition(Sci::invalidPosition
);
715 if (lineDoc
>= model
.pdoc
->LinesTotal())
716 return SelectionPosition(canReturnInvalid
? Sci::invalidPosition
:
717 model
.pdoc
->Length());
718 const Sci::Position posLineStart
= model
.pdoc
->LineStart(lineDoc
);
719 std::shared_ptr
<LineLayout
> ll
= RetrieveLineLayout(lineDoc
, model
);
721 LayoutLine(model
, surface
, vs
, ll
.get(), model
.wrapWidth
);
722 const Sci::Line lineStartSet
= model
.pcs
->DisplayFromDoc(lineDoc
);
723 const int subLine
= static_cast<int>(visibleLine
- lineStartSet
);
724 if (subLine
< ll
->lines
) {
725 const Range rangeSubLine
= ll
->SubLineRange(subLine
, LineLayout::Scope::visibleOnly
);
726 const XYPOSITION subLineStart
= ll
->positions
[rangeSubLine
.start
];
727 if (subLine
> 0) // Wrapped
728 pt
.x
-= ll
->wrapIndent
;
729 Sci::Position positionInLine
= 0;
730 if (model
.BidirectionalEnabled()) {
731 // Fill the line bidi data
732 UpdateBidiData(model
, vs
, ll
.get());
734 const ScreenLine
screenLine(ll
.get(), subLine
, vs
, rcClient
.right
, tabWidthMinimumPixels
);
735 std::unique_ptr
<IScreenLineLayout
> slLayout
= surface
->Layout(&screenLine
);
736 positionInLine
= slLayout
->PositionFromX(pt
.x
, charPosition
) +
739 positionInLine
= ll
->FindPositionFromX(pt
.x
+ subLineStart
,
740 rangeSubLine
, charPosition
);
742 if (positionInLine
< rangeSubLine
.end
) {
743 return SelectionPosition(model
.pdoc
->MovePositionOutsideChar(positionInLine
+ posLineStart
, 1));
746 const XYPOSITION spaceWidth
= vs
.styles
[ll
->EndLineStyle()].spaceWidth
;
747 const int spaceOffset
= static_cast<int>(
748 (pt
.x
+ subLineStart
- ll
->positions
[rangeSubLine
.end
] + spaceWidth
/ 2) / spaceWidth
);
749 return SelectionPosition(rangeSubLine
.end
+ posLineStart
, spaceOffset
);
750 } else if (canReturnInvalid
) {
751 if (pt
.x
< (ll
->positions
[rangeSubLine
.end
] - subLineStart
)) {
752 return SelectionPosition(model
.pdoc
->MovePositionOutsideChar(rangeSubLine
.end
+ posLineStart
, 1));
755 return SelectionPosition(rangeSubLine
.end
+ posLineStart
);
758 if (!canReturnInvalid
)
759 return SelectionPosition(ll
->numCharsInLine
+ posLineStart
);
761 return SelectionPosition(canReturnInvalid
? Sci::invalidPosition
: posLineStart
);
765 * Find the document position corresponding to an x coordinate on a particular document line.
766 * Ensure is between whole characters when document is in multi-byte or UTF-8 mode.
767 * This method is used for rectangular selections and does not work on wrapped lines.
769 SelectionPosition
EditView::SPositionFromLineX(Surface
*surface
, const EditModel
&model
, Sci::Line lineDoc
, int x
, const ViewStyle
&vs
) {
770 std::shared_ptr
<LineLayout
> ll
= RetrieveLineLayout(lineDoc
, model
);
772 const Sci::Position posLineStart
= model
.pdoc
->LineStart(lineDoc
);
773 LayoutLine(model
, surface
, vs
, ll
.get(), model
.wrapWidth
);
774 const Range rangeSubLine
= ll
->SubLineRange(0, LineLayout::Scope::visibleOnly
);
775 const XYPOSITION subLineStart
= ll
->positions
[rangeSubLine
.start
];
776 const Sci::Position positionInLine
= ll
->FindPositionFromX(x
+ subLineStart
, rangeSubLine
, false);
777 if (positionInLine
< rangeSubLine
.end
) {
778 return SelectionPosition(model
.pdoc
->MovePositionOutsideChar(positionInLine
+ posLineStart
, 1));
780 const XYPOSITION spaceWidth
= vs
.styles
[ll
->EndLineStyle()].spaceWidth
;
781 const int spaceOffset
= static_cast<int>(
782 (x
+ subLineStart
- ll
->positions
[rangeSubLine
.end
] + spaceWidth
/ 2) / spaceWidth
);
783 return SelectionPosition(rangeSubLine
.end
+ posLineStart
, spaceOffset
);
785 return SelectionPosition(0);
788 Sci::Line
EditView::DisplayFromPosition(Surface
*surface
, const EditModel
&model
, Sci::Position pos
, const ViewStyle
&vs
) {
789 const Sci::Line lineDoc
= model
.pdoc
->SciLineFromPosition(pos
);
790 Sci::Line lineDisplay
= model
.pcs
->DisplayFromDoc(lineDoc
);
791 std::shared_ptr
<LineLayout
> ll
= RetrieveLineLayout(lineDoc
, model
);
793 LayoutLine(model
, surface
, vs
, ll
.get(), model
.wrapWidth
);
794 const Sci::Position posLineStart
= model
.pdoc
->LineStart(lineDoc
);
795 const Sci::Position posInLine
= pos
- posLineStart
;
796 lineDisplay
--; // To make up for first increment ahead.
797 for (int subLine
= 0; subLine
< ll
->lines
; subLine
++) {
798 if (posInLine
>= ll
->LineStart(subLine
)) {
806 Sci::Position
EditView::StartEndDisplayLine(Surface
*surface
, const EditModel
&model
, Sci::Position pos
, bool start
, const ViewStyle
&vs
) {
807 const Sci::Line line
= model
.pdoc
->SciLineFromPosition(pos
);
808 std::shared_ptr
<LineLayout
> ll
= RetrieveLineLayout(line
, model
);
809 Sci::Position posRet
= Sci::invalidPosition
;
811 const Sci::Position posLineStart
= model
.pdoc
->LineStart(line
);
812 LayoutLine(model
, surface
, vs
, ll
.get(), model
.wrapWidth
);
813 const Sci::Position posInLine
= pos
- posLineStart
;
814 if (posInLine
<= ll
->maxLineLength
) {
815 for (int subLine
= 0; subLine
< ll
->lines
; subLine
++) {
816 if ((posInLine
>= ll
->LineStart(subLine
)) &&
817 (posInLine
<= ll
->LineStart(subLine
+ 1)) &&
818 (posInLine
<= ll
->numCharsBeforeEOL
)) {
820 posRet
= ll
->LineStart(subLine
) + posLineStart
;
822 if (subLine
== ll
->lines
- 1)
823 posRet
= ll
->numCharsBeforeEOL
+ posLineStart
;
825 posRet
= model
.pdoc
->MovePositionOutsideChar(ll
->LineStart(subLine
+ 1) + posLineStart
- 1, -1, false);
836 constexpr ColourRGBA
colourBug(0xff, 0, 0xfe, 0xf0);
838 // Selection background colours are always defined, the value_or is to show if bug
840 ColourRGBA
SelectionBackground(const EditModel
&model
, const ViewStyle
&vsDraw
, InSelection inSelection
) {
841 if (inSelection
== InSelection::inNone
)
842 return colourBug
; // Not selected is a bug
844 Element element
= Element::SelectionBack
;
845 if (inSelection
== InSelection::inAdditional
)
846 element
= Element::SelectionAdditionalBack
;
847 if (!model
.primarySelection
)
848 element
= Element::SelectionSecondaryBack
;
849 if (!model
.hasFocus
) {
850 if (inSelection
== InSelection::inAdditional
) {
851 if (ColourOptional colour
= vsDraw
.ElementColour(Element::SelectionInactiveAdditionalBack
)) {
855 if (ColourOptional colour
= vsDraw
.ElementColour(Element::SelectionInactiveBack
)) {
859 return vsDraw
.ElementColour(element
).value_or(colourBug
);
862 ColourOptional
SelectionForeground(const EditModel
&model
, const ViewStyle
&vsDraw
, InSelection inSelection
) {
863 if (inSelection
== InSelection::inNone
)
865 Element element
= Element::SelectionText
;
866 if (inSelection
== InSelection::inAdditional
)
867 element
= Element::SelectionAdditionalText
;
868 if (!model
.primarySelection
) // Secondary selection
869 element
= Element::SelectionSecondaryText
;
870 if (!model
.hasFocus
) {
871 if (inSelection
== InSelection::inAdditional
) {
872 if (ColourOptional colour
= vsDraw
.ElementColour(Element::SelectionInactiveAdditionalText
)) {
876 element
= Element::SelectionInactiveText
;
878 return vsDraw
.ElementColour(element
);
881 ColourRGBA
TextBackground(const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
882 ColourOptional background
, InSelection inSelection
, bool inHotspot
, int styleMain
, Sci::Position i
) {
883 if (inSelection
&& (vsDraw
.selection
.layer
== Layer::Base
)) {
884 return SelectionBackground(model
, vsDraw
, inSelection
).Opaque();
886 if ((vsDraw
.edgeState
== EdgeVisualStyle::Background
) &&
887 (i
>= ll
->edgeColumn
) &&
888 (i
< ll
->numCharsBeforeEOL
))
889 return vsDraw
.theEdge
.colour
;
891 if (const ColourOptional colourHotSpotBack
= vsDraw
.ElementColour(Element::HotSpotActiveBack
)) {
892 return colourHotSpotBack
->Opaque();
895 if (background
&& (styleMain
!= StyleBraceLight
) && (styleMain
!= StyleBraceBad
)) {
898 return vsDraw
.styles
[styleMain
].back
;
902 void DrawTextBlob(Surface
*surface
, const ViewStyle
&vsDraw
, PRectangle rcSegment
,
903 std::string_view text
, ColourRGBA textBack
, ColourRGBA textFore
, bool fillBackground
) {
904 if (rcSegment
.Empty())
906 if (fillBackground
) {
907 surface
->FillRectangleAligned(rcSegment
, Fill(textBack
));
909 const Font
*ctrlCharsFont
= vsDraw
.styles
[StyleControlChar
].font
.get();
910 const int normalCharHeight
= static_cast<int>(std::ceil(vsDraw
.styles
[StyleControlChar
].capitalHeight
));
911 PRectangle rcCChar
= rcSegment
;
912 rcCChar
.left
= rcCChar
.left
+ 1;
913 rcCChar
.top
= rcSegment
.top
+ vsDraw
.maxAscent
- normalCharHeight
;
914 rcCChar
.bottom
= rcSegment
.top
+ vsDraw
.maxAscent
+ 1;
915 PRectangle rcCentral
= rcCChar
;
918 surface
->FillRectangleAligned(rcCentral
, Fill(textFore
));
919 PRectangle rcChar
= rcCChar
;
922 surface
->DrawTextClippedUTF8(rcChar
, ctrlCharsFont
,
923 rcSegment
.top
+ vsDraw
.maxAscent
, text
,
927 void FillLineRemainder(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
928 Sci::Line line
, PRectangle rcArea
, int subLine
) {
929 InSelection eolInSelection
= InSelection::inNone
;
930 if (vsDraw
.selection
.visible
&& (subLine
== (ll
->lines
- 1))) {
931 eolInSelection
= model
.LineEndInSelection(line
);
934 if (eolInSelection
&& vsDraw
.selection
.eolFilled
&& (line
< model
.pdoc
->LinesTotal() - 1) && (vsDraw
.selection
.layer
== Layer::Base
)) {
935 surface
->FillRectangleAligned(rcArea
, Fill(SelectionBackground(model
, vsDraw
, eolInSelection
).Opaque()));
937 const ColourOptional background
= vsDraw
.Background(model
.GetMark(line
), model
.caret
.active
, ll
->containsCaret
);
939 surface
->FillRectangleAligned(rcArea
, Fill(*background
));
940 } else if (vsDraw
.styles
[ll
->styles
[ll
->numCharsInLine
]].eolFilled
) {
941 surface
->FillRectangleAligned(rcArea
, Fill(vsDraw
.styles
[ll
->styles
[ll
->numCharsInLine
]].back
));
943 surface
->FillRectangleAligned(rcArea
, Fill(vsDraw
.styles
[StyleDefault
].back
));
945 if (eolInSelection
&& vsDraw
.selection
.eolFilled
&& (line
< model
.pdoc
->LinesTotal() - 1) && (vsDraw
.selection
.layer
!= Layer::Base
)) {
946 surface
->FillRectangleAligned(rcArea
, SelectionBackground(model
, vsDraw
, eolInSelection
));
953 void EditView::DrawEOL(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
954 Sci::Line line
, int xStart
, PRectangle rcLine
, int subLine
, Sci::Position lineEnd
, XYPOSITION subLineStart
, ColourOptional background
) {
956 const Sci::Position posLineStart
= model
.pdoc
->LineStart(line
);
957 PRectangle rcSegment
= rcLine
;
959 const bool lastSubLine
= subLine
== (ll
->lines
- 1);
960 XYPOSITION virtualSpace
= 0;
962 const XYPOSITION spaceWidth
= vsDraw
.styles
[ll
->EndLineStyle()].spaceWidth
;
963 virtualSpace
= model
.sel
.VirtualSpaceFor(model
.pdoc
->LineEnd(line
)) * spaceWidth
;
965 const XYPOSITION xEol
= ll
->positions
[lineEnd
] - subLineStart
;
967 // Fill the virtual space and show selections within it
968 if (virtualSpace
> 0.0f
) {
969 rcSegment
.left
= xEol
+ xStart
;
970 rcSegment
.right
= xEol
+ xStart
+ virtualSpace
;
971 const ColourRGBA backgroundFill
= background
.value_or(vsDraw
.styles
[ll
->styles
[ll
->numCharsInLine
]].back
);
972 surface
->FillRectangleAligned(rcSegment
, backgroundFill
);
973 if (vsDraw
.selection
.visible
&& (vsDraw
.selection
.layer
== Layer::Base
)) {
974 const SelectionSegment
virtualSpaceRange(SelectionPosition(model
.pdoc
->LineEnd(line
)),
975 SelectionPosition(model
.pdoc
->LineEnd(line
),
976 model
.sel
.VirtualSpaceFor(model
.pdoc
->LineEnd(line
))));
977 for (size_t r
= 0; r
<model
.sel
.Count(); r
++) {
978 const SelectionSegment portion
= model
.sel
.Range(r
).Intersect(virtualSpaceRange
);
979 if (!portion
.Empty()) {
980 const XYPOSITION spaceWidth
= vsDraw
.styles
[ll
->EndLineStyle()].spaceWidth
;
981 rcSegment
.left
= xStart
+ ll
->positions
[portion
.start
.Position() - posLineStart
] -
982 subLineStart
+portion
.start
.VirtualSpace() * spaceWidth
;
983 rcSegment
.right
= xStart
+ ll
->positions
[portion
.end
.Position() - posLineStart
] -
984 subLineStart
+portion
.end
.VirtualSpace() * spaceWidth
;
985 rcSegment
.left
= (rcSegment
.left
> rcLine
.left
) ? rcSegment
.left
: rcLine
.left
;
986 rcSegment
.right
= (rcSegment
.right
< rcLine
.right
) ? rcSegment
.right
: rcLine
.right
;
987 surface
->FillRectangleAligned(rcSegment
, Fill(
988 SelectionBackground(model
, vsDraw
, model
.sel
.RangeType(r
)).Opaque()));
994 InSelection eolInSelection
= InSelection::inNone
;
995 if (vsDraw
.selection
.visible
&& lastSubLine
) {
996 eolInSelection
= model
.LineEndInSelection(line
);
999 const ColourRGBA selectionBack
= SelectionBackground(model
, vsDraw
, eolInSelection
);
1001 // Draw the [CR], [LF], or [CR][LF] blobs if visible line ends are on
1002 XYPOSITION blobsWidth
= 0;
1004 for (Sci::Position eolPos
= ll
->numCharsBeforeEOL
; eolPos
<ll
->numCharsInLine
;) {
1005 const int styleMain
= ll
->styles
[eolPos
];
1006 const ColourOptional selectionFore
= SelectionForeground(model
, vsDraw
, eolInSelection
);
1007 ColourRGBA textFore
= selectionFore
.value_or(vsDraw
.styles
[styleMain
].fore
);
1008 char hexits
[4] = "";
1009 std::string_view ctrlChar
;
1010 Sci::Position widthBytes
= 1;
1011 RepresentationAppearance appearance
= RepresentationAppearance::Blob
;
1012 const Representation
*repr
= model
.reprs
.RepresentationFromCharacter(std::string_view(&ll
->chars
[eolPos
], ll
->numCharsInLine
- eolPos
));
1014 // Representation of whole text
1015 widthBytes
= ll
->numCharsInLine
- eolPos
;
1017 repr
= model
.reprs
.RepresentationFromCharacter(std::string_view(&ll
->chars
[eolPos
], 1));
1020 ctrlChar
= repr
->stringRep
;
1021 appearance
= repr
->appearance
;
1022 if (FlagSet(appearance
, RepresentationAppearance::Colour
)) {
1023 textFore
= repr
->colour
;
1026 const unsigned char chEOL
= ll
->chars
[eolPos
];
1027 if (UTF8IsAscii(chEOL
)) {
1028 ctrlChar
= ControlCharacterString(chEOL
);
1030 Hexits(hexits
, chEOL
);
1035 rcSegment
.left
= xStart
+ ll
->positions
[eolPos
] - subLineStart
+ virtualSpace
;
1036 rcSegment
.right
= xStart
+ ll
->positions
[eolPos
+ widthBytes
] - subLineStart
+ virtualSpace
;
1037 blobsWidth
+= rcSegment
.Width();
1038 const ColourRGBA textBack
= TextBackground(model
, vsDraw
, ll
, background
, eolInSelection
, false, styleMain
, eolPos
);
1039 if (eolInSelection
&& (line
< model
.pdoc
->LinesTotal() - 1)) {
1040 if (vsDraw
.selection
.layer
== Layer::Base
) {
1041 surface
->FillRectangleAligned(rcSegment
, Fill(selectionBack
.Opaque()));
1043 surface
->FillRectangleAligned(rcSegment
, Fill(textBack
));
1046 surface
->FillRectangleAligned(rcSegment
, Fill(textBack
));
1048 const bool drawEOLSelection
= eolInSelection
&& (line
< model
.pdoc
->LinesTotal() - 1);
1049 ColourRGBA blobText
= textBack
;
1050 if (drawEOLSelection
&& (vsDraw
.selection
.layer
== Layer::UnderText
)) {
1051 surface
->FillRectangleAligned(rcSegment
, selectionBack
);
1052 blobText
= textBack
.MixedWith(selectionBack
, selectionBack
.GetAlphaComponent());
1054 if (FlagSet(appearance
, RepresentationAppearance::Blob
)) {
1055 DrawTextBlob(surface
, vsDraw
, rcSegment
, ctrlChar
, blobText
, textFore
, phasesDraw
== PhasesDraw::One
);
1057 surface
->DrawTextTransparentUTF8(rcSegment
, vsDraw
.styles
[StyleControlChar
].font
.get(),
1058 rcSegment
.top
+ vsDraw
.maxAscent
, ctrlChar
, textFore
);
1060 if (drawEOLSelection
&& (vsDraw
.selection
.layer
== Layer::OverText
)) {
1061 surface
->FillRectangleAligned(rcSegment
, selectionBack
);
1063 eolPos
+= widthBytes
;
1067 // Draw the eol-is-selected rectangle
1068 rcSegment
.left
= xEol
+ xStart
+ virtualSpace
+ blobsWidth
;
1069 rcSegment
.right
= rcSegment
.left
+ vsDraw
.aveCharWidth
;
1071 if (eolInSelection
&& (line
< model
.pdoc
->LinesTotal() - 1) && (vsDraw
.selection
.layer
== Layer::Base
)) {
1072 surface
->FillRectangleAligned(rcSegment
, Fill(selectionBack
.Opaque()));
1075 surface
->FillRectangleAligned(rcSegment
, Fill(*background
));
1076 } else if (line
< model
.pdoc
->LinesTotal() - 1) {
1077 surface
->FillRectangleAligned(rcSegment
, Fill(vsDraw
.styles
[ll
->styles
[ll
->numCharsInLine
]].back
));
1078 } else if (vsDraw
.styles
[ll
->styles
[ll
->numCharsInLine
]].eolFilled
) {
1079 surface
->FillRectangleAligned(rcSegment
, Fill(vsDraw
.styles
[ll
->styles
[ll
->numCharsInLine
]].back
));
1081 surface
->FillRectangleAligned(rcSegment
, Fill(vsDraw
.styles
[StyleDefault
].back
));
1083 if (eolInSelection
&& (line
< model
.pdoc
->LinesTotal() - 1) && (vsDraw
.selection
.layer
!= Layer::Base
)) {
1084 surface
->FillRectangleAligned(rcSegment
, selectionBack
);
1088 rcSegment
.left
= rcSegment
.right
;
1089 if (rcSegment
.left
< rcLine
.left
)
1090 rcSegment
.left
= rcLine
.left
;
1091 rcSegment
.right
= rcLine
.right
;
1093 const bool drawEOLAnnotationStyledText
= (vsDraw
.eolAnnotationVisible
!= EOLAnnotationVisible::Hidden
) && model
.pdoc
->EOLAnnotationStyledText(line
).text
;
1094 const bool fillRemainder
= (!lastSubLine
|| (!model
.GetFoldDisplayText(line
) && !drawEOLAnnotationStyledText
));
1095 if (fillRemainder
) {
1096 // Fill the remainder of the line
1097 FillLineRemainder(surface
, model
, vsDraw
, ll
, line
, rcSegment
, subLine
);
1100 bool drawWrapMarkEnd
= false;
1102 if (subLine
+ 1 < ll
->lines
) {
1103 if (FlagSet(vsDraw
.wrap
.visualFlags
, WrapVisualFlag::End
)) {
1104 drawWrapMarkEnd
= ll
->LineStart(subLine
+ 1) != 0;
1106 if (vsDraw
.IsLineFrameOpaque(model
.caret
.active
, ll
->containsCaret
)) {
1107 // Draw right of frame under marker
1108 surface
->FillRectangleAligned(Side(rcLine
, Edge::right
, vsDraw
.GetFrameWidth()),
1109 vsDraw
.ElementColourForced(Element::CaretLineBack
).Opaque());
1113 if (drawWrapMarkEnd
) {
1114 PRectangle rcPlace
= rcSegment
;
1115 const XYPOSITION maxLeft
= rcPlace
.right
- vsDraw
.aveCharWidth
;
1117 if (FlagSet(vsDraw
.wrap
.visualFlagsLocation
, WrapVisualLocation::EndByText
)) {
1118 rcPlace
.left
= std::min(xEol
+ xStart
+ virtualSpace
, maxLeft
);
1119 rcPlace
.right
= rcPlace
.left
+ vsDraw
.aveCharWidth
;
1121 // rcLine is clipped to text area
1122 rcPlace
.right
= rcLine
.right
;
1123 rcPlace
.left
= maxLeft
;
1125 if (!customDrawWrapMarker
) {
1126 DrawWrapMarker(surface
, rcPlace
, true, vsDraw
.WrapColour());
1128 customDrawWrapMarker(surface
, rcPlace
, true, vsDraw
.WrapColour());
1133 void EditView::DrawFoldDisplayText(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1134 Sci::Line line
, int xStart
, PRectangle rcLine
, int subLine
, XYPOSITION subLineStart
, DrawPhase phase
) {
1135 const bool lastSubLine
= subLine
== (ll
->lines
- 1);
1139 const char *text
= model
.GetFoldDisplayText(line
);
1143 PRectangle rcSegment
= rcLine
;
1144 const std::string_view
foldDisplayText(text
);
1145 const Font
*fontText
= vsDraw
.styles
[StyleFoldDisplayText
].font
.get();
1146 const int widthFoldDisplayText
= static_cast<int>(surface
->WidthText(fontText
, foldDisplayText
));
1148 InSelection eolInSelection
= InSelection::inNone
;
1149 if (vsDraw
.selection
.visible
) {
1150 eolInSelection
= model
.LineEndInSelection(line
);
1153 const XYPOSITION spaceWidth
= vsDraw
.styles
[ll
->EndLineStyle()].spaceWidth
;
1154 const XYPOSITION virtualSpace
= model
.sel
.VirtualSpaceFor(
1155 model
.pdoc
->LineEnd(line
)) * spaceWidth
;
1156 rcSegment
.left
= xStart
+ ll
->positions
[ll
->numCharsInLine
] - subLineStart
+ virtualSpace
+ vsDraw
.aveCharWidth
;
1157 rcSegment
.right
= rcSegment
.left
+ static_cast<XYPOSITION
>(widthFoldDisplayText
);
1159 const ColourOptional background
= vsDraw
.Background(model
.GetMark(line
), model
.caret
.active
, ll
->containsCaret
);
1160 const ColourOptional selectionFore
= SelectionForeground(model
, vsDraw
, eolInSelection
);
1161 const ColourRGBA textFore
= selectionFore
.value_or(vsDraw
.styles
[StyleFoldDisplayText
].fore
);
1162 const ColourRGBA textBack
= TextBackground(model
, vsDraw
, ll
, background
, eolInSelection
,
1163 false, StyleFoldDisplayText
, -1);
1165 if (model
.trackLineWidth
) {
1166 if (rcSegment
.right
+ 1> lineWidthMaxSeen
) {
1167 // Fold display text border drawn on rcSegment.right with width 1 is the last visible object of the line
1168 lineWidthMaxSeen
= static_cast<int>(rcSegment
.right
+ 1);
1172 if (FlagSet(phase
, DrawPhase::back
)) {
1173 surface
->FillRectangleAligned(rcSegment
, Fill(textBack
));
1175 // Fill Remainder of the line
1176 PRectangle rcRemainder
= rcSegment
;
1177 rcRemainder
.left
= rcRemainder
.right
;
1178 if (rcRemainder
.left
< rcLine
.left
)
1179 rcRemainder
.left
= rcLine
.left
;
1180 rcRemainder
.right
= rcLine
.right
;
1181 FillLineRemainder(surface
, model
, vsDraw
, ll
, line
, rcRemainder
, subLine
);
1184 if (FlagSet(phase
, DrawPhase::text
)) {
1185 if (phasesDraw
!= PhasesDraw::One
) {
1186 surface
->DrawTextTransparent(rcSegment
, fontText
,
1187 rcSegment
.top
+ vsDraw
.maxAscent
, foldDisplayText
,
1190 surface
->DrawTextNoClip(rcSegment
, fontText
,
1191 rcSegment
.top
+ vsDraw
.maxAscent
, foldDisplayText
,
1192 textFore
, textBack
);
1196 if (FlagSet(phase
, DrawPhase::indicatorsFore
)) {
1197 if (model
.foldDisplayTextStyle
== FoldDisplayTextStyle::Boxed
) {
1198 PRectangle rcBox
= rcSegment
;
1199 rcBox
.left
= std::round(rcSegment
.left
);
1200 rcBox
.right
= std::round(rcSegment
.right
);
1201 surface
->RectangleFrame(rcBox
, Stroke(textFore
));
1205 if (FlagSet(phase
, DrawPhase::selectionTranslucent
)) {
1206 if (eolInSelection
&& (line
< model
.pdoc
->LinesTotal() - 1) && (vsDraw
.selection
.layer
!= Layer::Base
)) {
1207 surface
->FillRectangleAligned(rcSegment
, SelectionBackground(model
, vsDraw
, eolInSelection
));
1212 void EditView::DrawEOLAnnotationText(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1213 Sci::Line line
, int xStart
, PRectangle rcLine
, int subLine
, XYPOSITION subLineStart
, DrawPhase phase
) {
1215 const bool lastSubLine
= subLine
== (ll
->lines
- 1);
1219 if (vsDraw
.eolAnnotationVisible
== EOLAnnotationVisible::Hidden
) {
1222 const StyledText stEOLAnnotation
= model
.pdoc
->EOLAnnotationStyledText(line
);
1223 if (!stEOLAnnotation
.text
|| !ValidStyledText(vsDraw
, vsDraw
.eolAnnotationStyleOffset
, stEOLAnnotation
)) {
1226 const std::string_view
eolAnnotationText(stEOLAnnotation
.text
, stEOLAnnotation
.length
);
1227 const size_t style
= stEOLAnnotation
.style
+ vsDraw
.eolAnnotationStyleOffset
;
1229 PRectangle rcSegment
= rcLine
;
1230 const Font
*fontText
= vsDraw
.styles
[style
].font
.get();
1232 const Surface::Ends ends
= static_cast<Surface::Ends
>(static_cast<int>(vsDraw
.eolAnnotationVisible
) & 0xff);
1233 const Surface::Ends leftSide
= static_cast<Surface::Ends
>(static_cast<int>(ends
) & 0xf);
1234 const Surface::Ends rightSide
= static_cast<Surface::Ends
>(static_cast<int>(ends
) & 0xf0);
1236 XYPOSITION leftBoxSpace
= 0;
1237 XYPOSITION rightBoxSpace
= 0;
1238 if (vsDraw
.eolAnnotationVisible
>= EOLAnnotationVisible::Boxed
) {
1241 if (vsDraw
.eolAnnotationVisible
!= EOLAnnotationVisible::Boxed
) {
1243 case Surface::Ends::leftFlat
:
1246 case Surface::Ends::leftAngle
:
1247 leftBoxSpace
= rcLine
.Height() / 2.0;
1249 case Surface::Ends::semiCircles
:
1251 leftBoxSpace
= rcLine
.Height() / 3.0;
1254 switch (rightSide
) {
1255 case Surface::Ends::rightFlat
:
1258 case Surface::Ends::rightAngle
:
1259 rightBoxSpace
= rcLine
.Height() / 2.0;
1261 case Surface::Ends::semiCircles
:
1263 rightBoxSpace
= rcLine
.Height() / 3.0;
1268 const int widthEOLAnnotationText
= static_cast<int>(surface
->WidthTextUTF8(fontText
, eolAnnotationText
) +
1269 leftBoxSpace
+ rightBoxSpace
);
1271 const XYPOSITION spaceWidth
= vsDraw
.styles
[ll
->EndLineStyle()].spaceWidth
;
1272 const XYPOSITION virtualSpace
= model
.sel
.VirtualSpaceFor(
1273 model
.pdoc
->LineEnd(line
)) * spaceWidth
;
1274 rcSegment
.left
= xStart
+
1275 ll
->positions
[ll
->numCharsInLine
] - subLineStart
1276 + virtualSpace
+ vsDraw
.aveCharWidth
;
1278 const char *textFoldDisplay
= model
.GetFoldDisplayText(line
);
1279 if (textFoldDisplay
) {
1280 const std::string_view
foldDisplayText(textFoldDisplay
);
1281 rcSegment
.left
+= static_cast<int>(
1282 surface
->WidthText(vsDraw
.styles
[StyleFoldDisplayText
].font
.get(), foldDisplayText
)) +
1283 vsDraw
.aveCharWidth
;
1285 rcSegment
.right
= rcSegment
.left
+ static_cast<XYPOSITION
>(widthEOLAnnotationText
);
1287 const ColourOptional background
= vsDraw
.Background(model
.GetMark(line
), model
.caret
.active
, ll
->containsCaret
);
1288 const ColourRGBA textFore
= vsDraw
.styles
[style
].fore
;
1289 const ColourRGBA textBack
= TextBackground(model
, vsDraw
, ll
, background
, InSelection::inNone
,
1290 false, static_cast<int>(style
), -1);
1292 if (model
.trackLineWidth
) {
1293 if (rcSegment
.right
+ 1> lineWidthMaxSeen
) {
1294 // EOL Annotation text border drawn on rcSegment.right with width 1 is the last visible object of the line
1295 lineWidthMaxSeen
= static_cast<int>(rcSegment
.right
+ 1);
1299 if (FlagSet(phase
, DrawPhase::back
)) {
1300 // This fills in the whole remainder of the line even though
1301 // it may be double drawing. This is to allow stadiums with
1302 // curved or angled ends to have the area outside in the correct
1303 // background colour.
1304 PRectangle rcRemainder
= rcSegment
;
1305 rcRemainder
.right
= rcLine
.right
;
1306 FillLineRemainder(surface
, model
, vsDraw
, ll
, line
, rcRemainder
, subLine
);
1309 PRectangle rcText
= rcSegment
;
1310 rcText
.left
+= leftBoxSpace
;
1311 rcText
.right
-= rightBoxSpace
;
1313 // For single phase drawing, draw the text then any box over it
1314 if (FlagSet(phase
, DrawPhase::text
)) {
1315 if (phasesDraw
== PhasesDraw::One
) {
1316 surface
->DrawTextNoClipUTF8(rcText
, fontText
,
1317 rcText
.top
+ vsDraw
.maxAscent
, eolAnnotationText
,
1318 textFore
, textBack
);
1322 // Draw any box or stadium shape
1323 if (FlagSet(phase
, DrawPhase::indicatorsBack
)) {
1324 const PRectangle rcBox
= PixelAlign(rcSegment
, 1);
1326 switch (vsDraw
.eolAnnotationVisible
) {
1327 case EOLAnnotationVisible::Standard
:
1328 if (phasesDraw
!= PhasesDraw::One
) {
1329 surface
->FillRectangle(rcBox
, textBack
);
1333 case EOLAnnotationVisible::Boxed
:
1334 if (phasesDraw
== PhasesDraw::One
) {
1335 // Draw a rectangular outline around the text
1336 surface
->RectangleFrame(rcBox
, textFore
);
1338 // Draw with a fill to fill the edges of the rectangle.
1339 surface
->RectangleDraw(rcBox
, FillStroke(textBack
, textFore
));
1344 if (phasesDraw
== PhasesDraw::One
) {
1345 // Draw an outline around the text
1346 surface
->Stadium(rcBox
, FillStroke(ColourRGBA(textBack
, 0), textFore
), ends
);
1348 // Draw with a fill to fill the edges of the shape.
1349 surface
->Stadium(rcBox
, FillStroke(textBack
, textFore
), ends
);
1355 // For multi-phase drawing draw the text last as transparent over any box
1356 if (FlagSet(phase
, DrawPhase::text
)) {
1357 if (phasesDraw
!= PhasesDraw::One
) {
1358 surface
->DrawTextTransparentUTF8(rcText
, fontText
,
1359 rcText
.top
+ vsDraw
.maxAscent
, eolAnnotationText
,
1367 constexpr bool AnnotationBoxedOrIndented(AnnotationVisible annotationVisible
) noexcept
{
1368 return annotationVisible
== AnnotationVisible::Boxed
|| annotationVisible
== AnnotationVisible::Indented
;
1373 void EditView::DrawAnnotation(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1374 Sci::Line line
, int xStart
, PRectangle rcLine
, int subLine
, DrawPhase phase
) {
1375 const int indent
= static_cast<int>(model
.pdoc
->GetLineIndentation(line
) * vsDraw
.spaceWidth
);
1376 PRectangle rcSegment
= rcLine
;
1377 const int annotationLine
= subLine
- ll
->lines
;
1378 const StyledText stAnnotation
= model
.pdoc
->AnnotationStyledText(line
);
1379 if (stAnnotation
.text
&& ValidStyledText(vsDraw
, vsDraw
.annotationStyleOffset
, stAnnotation
)) {
1380 if (FlagSet(phase
, DrawPhase::back
)) {
1381 surface
->FillRectangleAligned(rcSegment
, Fill(vsDraw
.styles
[0].back
));
1383 rcSegment
.left
= static_cast<XYPOSITION
>(xStart
);
1384 if (model
.trackLineWidth
|| AnnotationBoxedOrIndented(vsDraw
.annotationVisible
)) {
1385 // Only care about calculating width if tracking or need to draw indented box
1386 int widthAnnotation
= WidestLineWidth(surface
, vsDraw
, vsDraw
.annotationStyleOffset
, stAnnotation
);
1387 if (AnnotationBoxedOrIndented(vsDraw
.annotationVisible
)) {
1388 widthAnnotation
+= static_cast<int>(vsDraw
.spaceWidth
* 2); // Margins
1389 rcSegment
.left
= static_cast<XYPOSITION
>(xStart
+ indent
);
1390 rcSegment
.right
= rcSegment
.left
+ widthAnnotation
;
1392 if (widthAnnotation
> lineWidthMaxSeen
)
1393 lineWidthMaxSeen
= widthAnnotation
;
1395 const int annotationLines
= model
.pdoc
->AnnotationLines(line
);
1397 size_t lengthAnnotation
= stAnnotation
.LineLength(start
);
1398 int lineInAnnotation
= 0;
1399 while ((lineInAnnotation
< annotationLine
) && (start
< stAnnotation
.length
)) {
1400 start
+= lengthAnnotation
+ 1;
1401 lengthAnnotation
= stAnnotation
.LineLength(start
);
1404 PRectangle rcText
= rcSegment
;
1405 if ((FlagSet(phase
, DrawPhase::back
)) && AnnotationBoxedOrIndented(vsDraw
.annotationVisible
)) {
1406 surface
->FillRectangleAligned(rcText
,
1407 Fill(vsDraw
.styles
[stAnnotation
.StyleAt(start
) + vsDraw
.annotationStyleOffset
].back
));
1408 rcText
.left
+= vsDraw
.spaceWidth
;
1410 DrawStyledText(surface
, vsDraw
, vsDraw
.annotationStyleOffset
, rcText
,
1411 stAnnotation
, start
, lengthAnnotation
, phase
);
1412 if ((FlagSet(phase
, DrawPhase::back
)) && (vsDraw
.annotationVisible
== AnnotationVisible::Boxed
)) {
1413 const ColourRGBA colourBorder
= vsDraw
.styles
[vsDraw
.annotationStyleOffset
].fore
;
1414 const PRectangle rcBorder
= PixelAlignOutside(rcSegment
, surface
->PixelDivisions());
1415 surface
->FillRectangle(Side(rcBorder
, Edge::left
, 1), colourBorder
);
1416 surface
->FillRectangle(Side(rcBorder
, Edge::right
, 1), colourBorder
);
1417 if (subLine
== ll
->lines
) {
1418 surface
->FillRectangle(Side(rcBorder
, Edge::top
, 1), colourBorder
);
1420 if (subLine
== ll
->lines
+ annotationLines
- 1) {
1421 surface
->FillRectangle(Side(rcBorder
, Edge::bottom
, 1), colourBorder
);
1425 // No annotation to draw so show bug with colourBug
1426 if (FlagSet(phase
, DrawPhase::back
)) {
1427 surface
->FillRectangle(rcSegment
, colourBug
.Opaque());
1434 void DrawBlockCaret(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1435 int subLine
, int xStart
, Sci::Position offset
, Sci::Position posCaret
, PRectangle rcCaret
, ColourRGBA caretColour
) {
1437 const Sci::Position lineStart
= ll
->LineStart(subLine
);
1438 Sci::Position posBefore
= posCaret
;
1439 Sci::Position posAfter
= model
.pdoc
->MovePositionOutsideChar(posCaret
+ 1, 1);
1440 Sci::Position numCharsToDraw
= posAfter
- posCaret
;
1442 // Work out where the starting and ending offsets are. We need to
1443 // see if the previous character shares horizontal space, such as a
1444 // glyph / combining character. If so we'll need to draw that too.
1445 Sci::Position offsetFirstChar
= offset
;
1446 Sci::Position offsetLastChar
= offset
+ (posAfter
- posCaret
);
1447 while ((posBefore
> 0) && ((offsetLastChar
- numCharsToDraw
) >= lineStart
)) {
1448 if ((ll
->positions
[offsetLastChar
] - ll
->positions
[offsetLastChar
- numCharsToDraw
]) > 0) {
1449 // The char does not share horizontal space
1452 // Char shares horizontal space, update the numChars to draw
1453 // Update posBefore to point to the prev char
1454 posBefore
= model
.pdoc
->MovePositionOutsideChar(posBefore
- 1, -1);
1455 numCharsToDraw
= posAfter
- posBefore
;
1456 offsetFirstChar
= offset
- (posCaret
- posBefore
);
1459 // See if the next character shares horizontal space, if so we'll
1460 // need to draw that too.
1461 if (offsetFirstChar
< 0)
1462 offsetFirstChar
= 0;
1463 numCharsToDraw
= offsetLastChar
- offsetFirstChar
;
1464 while ((offsetLastChar
< ll
->LineStart(subLine
+ 1)) && (offsetLastChar
<= ll
->numCharsInLine
)) {
1465 // Update posAfter to point to the 2nd next char, this is where
1466 // the next character ends, and 2nd next begins. We'll need
1467 // to compare these two
1468 posBefore
= posAfter
;
1469 posAfter
= model
.pdoc
->MovePositionOutsideChar(posAfter
+ 1, 1);
1470 offsetLastChar
= offset
+ (posAfter
- posCaret
);
1471 if ((ll
->positions
[offsetLastChar
] - ll
->positions
[offsetLastChar
- (posAfter
- posBefore
)]) > 0) {
1472 // The char does not share horizontal space
1475 // Char shares horizontal space, update the numChars to draw
1476 numCharsToDraw
= offsetLastChar
- offsetFirstChar
;
1479 // We now know what to draw, update the caret drawing rectangle
1480 rcCaret
.left
= ll
->positions
[offsetFirstChar
] - ll
->positions
[lineStart
] + xStart
;
1481 rcCaret
.right
= ll
->positions
[offsetFirstChar
+ numCharsToDraw
] - ll
->positions
[lineStart
] + xStart
;
1483 // Adjust caret position to take into account any word wrapping symbols.
1484 if ((ll
->wrapIndent
!= 0) && (lineStart
!= 0)) {
1485 const XYPOSITION wordWrapCharWidth
= ll
->wrapIndent
;
1486 rcCaret
.left
+= wordWrapCharWidth
;
1487 rcCaret
.right
+= wordWrapCharWidth
;
1490 // This character is where the caret block is, we override the colours
1491 // (inversed) for drawing the caret here.
1492 const int styleMain
= ll
->styles
[offsetFirstChar
];
1493 const Font
*fontText
= vsDraw
.styles
[styleMain
].font
.get();
1494 const std::string_view
text(&ll
->chars
[offsetFirstChar
], numCharsToDraw
);
1495 surface
->DrawTextClipped(rcCaret
, fontText
,
1496 rcCaret
.top
+ vsDraw
.maxAscent
, text
, vsDraw
.styles
[styleMain
].back
,
1502 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 (!vsDraw
.selection
.visible
&& !drawDrag
)
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
)) &&
1515 posCaret
> model
.sel
.Range(r
).anchor
) {
1516 if (posCaret
.VirtualSpace() > 0)
1517 posCaret
.SetVirtualSpace(posCaret
.VirtualSpace() - 1);
1519 posCaret
.SetPosition(model
.pdoc
->MovePositionOutsideChar(posCaret
.Position()-1, -1));
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)) {
1528 const ScreenLine
screenLine(ll
, subLine
, vsDraw
, rcLine
.right
, tabWidthMinimumPixels
);
1530 const int caretPosition
= offset
- ll
->LineStart(subLine
);
1532 std::unique_ptr
<IScreenLineLayout
> slLayout
= surface
->Layout(&screenLine
);
1533 const XYPOSITION caretLeft
= slLayout
->XFromPosition(caretPosition
);
1535 // In case of start of line, the cursor should be at the right
1536 xposCaret
= caretLeft
+ virtualOffset
;
1538 if (ll
->wrapIndent
!= 0) {
1539 const Sci::Position lineStart
= ll
->LineStart(subLine
);
1540 if (lineStart
!= 0) // Wrapped
1541 xposCaret
+= ll
->wrapIndent
;
1543 const bool caretBlinkState
= (model
.caret
.active
&& model
.caret
.on
) || (!additionalCaretsBlink
&& !mainCaret
);
1544 const bool caretVisibleState
= additionalCaretsVisible
|| mainCaret
;
1545 if ((xposCaret
>= 0) && vsDraw
.IsCaretVisible(mainCaret
) &&
1546 (drawDrag
|| (caretBlinkState
&& caretVisibleState
))) {
1547 bool canDrawBlockCaret
= true;
1548 bool drawBlockCaret
= false;
1549 XYPOSITION widthOverstrikeCaret
;
1550 XYPOSITION caretWidthOffset
= 0;
1551 PRectangle rcCaret
= rcLine
;
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
;
1560 const int widthChar
= model
.pdoc
->LenChar(posCaret
.Position());
1561 widthOverstrikeCaret
= ll
->positions
[offset
+ widthChar
] - ll
->positions
[offset
];
1563 if (widthOverstrikeCaret
< 3) // Make sure its visible
1564 widthOverstrikeCaret
= 3;
1567 caretWidthOffset
= 0.51f
; // Move back so overlaps both character cells.
1568 xposCaret
+= xStart
;
1569 const ViewStyle::CaretShape caretShape
= drawDrag
? ViewStyle::CaretShape::line
:
1570 vsDraw
.CaretShapeForMode(model
.inOverstrike
, mainCaret
);
1572 /* Dragging text, use a line caret */
1573 rcCaret
.left
= std::round(xposCaret
- caretWidthOffset
);
1574 rcCaret
.right
= rcCaret
.left
+ vsDraw
.caret
.width
;
1575 } else if ((caretShape
== ViewStyle::CaretShape::bar
) && drawOverstrikeCaret
) {
1576 /* Over-strike (insert mode), use a modified bar caret */
1577 rcCaret
.top
= rcCaret
.bottom
- 2;
1578 rcCaret
.left
= xposCaret
+ 1;
1579 rcCaret
.right
= rcCaret
.left
+ widthOverstrikeCaret
- 1;
1580 } else if ((caretShape
== ViewStyle::CaretShape::block
) || imeCaretBlockOverride
) {
1582 rcCaret
.left
= xposCaret
;
1583 if (canDrawBlockCaret
&& !(IsControl(ll
->chars
[offset
]))) {
1584 drawBlockCaret
= true;
1585 rcCaret
.right
= xposCaret
+ widthOverstrikeCaret
;
1587 rcCaret
.right
= xposCaret
+ vsDraw
.aveCharWidth
;
1591 rcCaret
.left
= std::round(xposCaret
- caretWidthOffset
);
1592 rcCaret
.right
= rcCaret
.left
+ vsDraw
.caret
.width
;
1594 const Element elementCaret
= mainCaret
? Element::Caret
: Element::CaretAdditional
;
1595 const ColourRGBA caretColour
= vsDraw
.ElementColourForced(elementCaret
);
1596 //assert(caretColour.IsOpaque());
1597 if (drawBlockCaret
) {
1598 DrawBlockCaret(surface
, model
, vsDraw
, ll
, subLine
, xStart
, offset
, posCaret
.Position(), rcCaret
, caretColour
);
1600 surface
->FillRectangleAligned(rcCaret
, Fill(caretColour
));
1611 void DrawWrapIndentAndMarker(Surface
*surface
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1612 int xStart
, PRectangle rcLine
, ColourOptional background
, DrawWrapMarkerFn customDrawWrapMarker
,
1614 // default background here..
1615 surface
->FillRectangleAligned(rcLine
, Fill(background
.value_or(vsDraw
.styles
[StyleDefault
].back
)));
1617 if (vsDraw
.IsLineFrameOpaque(caretActive
, ll
->containsCaret
)) {
1618 // Draw left of frame under marker
1619 surface
->FillRectangleAligned(Side(rcLine
, Edge::left
, vsDraw
.GetFrameWidth()),
1620 vsDraw
.ElementColourForced(Element::CaretLineBack
).Opaque());
1623 if (FlagSet(vsDraw
.wrap
.visualFlags
, WrapVisualFlag::Start
)) {
1625 // draw continuation rect
1626 PRectangle rcPlace
= rcLine
;
1628 rcPlace
.left
= static_cast<XYPOSITION
>(xStart
);
1629 rcPlace
.right
= rcPlace
.left
+ ll
->wrapIndent
;
1631 if (FlagSet(vsDraw
.wrap
.visualFlagsLocation
, WrapVisualLocation::StartByText
))
1632 rcPlace
.left
= rcPlace
.right
- vsDraw
.aveCharWidth
;
1634 rcPlace
.right
= rcPlace
.left
+ vsDraw
.aveCharWidth
;
1636 if (!customDrawWrapMarker
) {
1637 DrawWrapMarker(surface
, rcPlace
, false, vsDraw
.WrapColour());
1639 customDrawWrapMarker(surface
, rcPlace
, false, vsDraw
.WrapColour());
1644 // On the curses platform, the terminal is drawing its own caret, so if the caret is within
1645 // the main selection, do not draw the selection at that position.
1646 // Use iDoc from DrawBackground and DrawForeground here because TextSegment has been adjusted
1647 // such that, if the caret is inside the main selection, the beginning or end of that selection
1648 // is at the end of a text segment.
1649 // This function should only be called if iDoc is within the main selection.
1650 InSelection
CharacterInCursesSelection(Sci::Position iDoc
, const EditModel
&model
, const ViewStyle
&vsDraw
) noexcept
{
1651 const SelectionPosition
&posCaret
= model
.sel
.RangeMain().caret
;
1652 const bool caretAtStart
= posCaret
< model
.sel
.RangeMain().anchor
&& posCaret
.Position() == iDoc
;
1653 const bool caretAtEnd
= posCaret
> model
.sel
.RangeMain().anchor
&&
1654 vsDraw
.DrawCaretInsideSelection(false, false) &&
1655 model
.pdoc
->MovePositionOutsideChar(posCaret
.Position() - 1, -1) == iDoc
;
1656 return (caretAtStart
|| caretAtEnd
) ? InSelection::inNone
: InSelection::inMain
;
1659 void DrawBackground(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1660 int xStart
, PRectangle rcLine
, int subLine
, Range lineRange
, Sci::Position posLineStart
,
1661 ColourOptional background
) {
1663 const bool selBackDrawn
= vsDraw
.SelectionBackgroundDrawn();
1664 bool inIndentation
= subLine
== 0; // Do not handle indentation except on first subline.
1665 const XYPOSITION subLineStart
= ll
->positions
[lineRange
.start
];
1666 const XYPOSITION horizontalOffset
= xStart
- subLineStart
;
1667 // Does not take margin into account but not significant
1668 const XYPOSITION xStartVisible
= subLineStart
- xStart
;
1670 const BreakFinder::BreakFor breakFor
= selBackDrawn
? BreakFinder::BreakFor::Selection
: BreakFinder::BreakFor::Text
;
1671 BreakFinder
bfBack(ll
, &model
.sel
, lineRange
, posLineStart
, xStartVisible
, breakFor
, model
.pdoc
, &model
.reprs
, &vsDraw
);
1673 const bool drawWhitespaceBackground
= vsDraw
.WhitespaceBackgroundDrawn() && !background
;
1675 // Background drawing loop
1676 while (bfBack
.More()) {
1678 const TextSegment ts
= bfBack
.Next();
1679 const Sci::Position i
= ts
.end() - 1;
1680 const Sci::Position iDoc
= i
+ posLineStart
;
1682 const Interval horizontal
= ll
->Span(ts
.start
, ts
.end()).Offset(horizontalOffset
);
1683 // Only try to draw if really visible - enhances performance by not calling environment to
1684 // draw strings that are completely past the right side of the window.
1685 if (!horizontal
.Empty() && rcLine
.Intersects(horizontal
)) {
1686 const PRectangle rcSegment
= Intersection(rcLine
, horizontal
);
1688 InSelection inSelection
= vsDraw
.selection
.visible
? model
.sel
.CharacterInSelection(iDoc
) : InSelection::inNone
;
1689 if (FlagSet(vsDraw
.caret
.style
, CaretStyle::Curses
) && (inSelection
== InSelection::inMain
))
1690 inSelection
= CharacterInCursesSelection(iDoc
, model
, vsDraw
);
1691 const bool inHotspot
= model
.hotspot
.Valid() && model
.hotspot
.ContainsCharacter(iDoc
);
1692 ColourRGBA textBack
= TextBackground(model
, vsDraw
, ll
, background
, inSelection
,
1693 inHotspot
, ll
->styles
[i
], i
);
1694 if (ts
.representation
) {
1695 if (ll
->chars
[i
] == '\t') {
1697 if (drawWhitespaceBackground
&& vsDraw
.WhiteSpaceVisible(inIndentation
)) {
1698 textBack
= vsDraw
.ElementColourForced(Element::WhiteSpaceBack
).Opaque();
1702 inIndentation
= false;
1704 surface
->FillRectangleAligned(rcSegment
, Fill(textBack
));
1706 // Normal text display
1707 surface
->FillRectangleAligned(rcSegment
, Fill(textBack
));
1708 if (vsDraw
.viewWhitespace
!= WhiteSpace::Invisible
) {
1709 for (int cpos
= 0; cpos
<= i
- ts
.start
; cpos
++) {
1710 if (ll
->chars
[cpos
+ ts
.start
] == ' ') {
1711 if (drawWhitespaceBackground
&& vsDraw
.WhiteSpaceVisible(inIndentation
)) {
1712 const PRectangle rcSpace
= Intersection(rcLine
,
1713 ll
->SpanByte(cpos
+ ts
.start
).Offset(horizontalOffset
));
1714 surface
->FillRectangleAligned(rcSpace
,
1715 vsDraw
.ElementColourForced(Element::WhiteSpaceBack
).Opaque());
1718 inIndentation
= false;
1723 } else if (horizontal
.left
> rcLine
.right
) {
1729 void DrawEdgeLine(Surface
*surface
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1730 int xStart
, PRectangle rcLine
, Range lineRange
) {
1731 if (vsDraw
.edgeState
== EdgeVisualStyle::Line
) {
1732 PRectangle rcSegment
= rcLine
;
1733 const int edgeX
= static_cast<int>(vsDraw
.theEdge
.column
* vsDraw
.spaceWidth
);
1734 rcSegment
.left
= static_cast<XYPOSITION
>(edgeX
+ xStart
);
1735 if ((ll
->wrapIndent
!= 0) && (lineRange
.start
!= 0))
1736 rcSegment
.left
-= ll
->wrapIndent
;
1737 rcSegment
.right
= rcSegment
.left
+ 1;
1738 surface
->FillRectangleAligned(rcSegment
, Fill(vsDraw
.theEdge
.colour
));
1739 } else if (vsDraw
.edgeState
== EdgeVisualStyle::MultiLine
) {
1740 for (size_t edge
= 0; edge
< vsDraw
.theMultiEdge
.size(); edge
++) {
1741 if (vsDraw
.theMultiEdge
[edge
].column
>= 0) {
1742 PRectangle rcSegment
= rcLine
;
1743 const int edgeX
= static_cast<int>(vsDraw
.theMultiEdge
[edge
].column
* vsDraw
.spaceWidth
);
1744 rcSegment
.left
= static_cast<XYPOSITION
>(edgeX
+ xStart
);
1745 if ((ll
->wrapIndent
!= 0) && (lineRange
.start
!= 0))
1746 rcSegment
.left
-= ll
->wrapIndent
;
1747 rcSegment
.right
= rcSegment
.left
+ 1;
1748 surface
->FillRectangleAligned(rcSegment
, Fill(vsDraw
.theMultiEdge
[edge
].colour
));
1754 // Draw underline mark as part of background if on base layer
1755 void DrawMarkUnderline(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
,
1756 Sci::Line line
, PRectangle rcLine
) {
1757 int marks
= model
.GetMark(line
);
1758 for (int markBit
= 0; (markBit
<= MarkerMax
) && marks
; markBit
++) {
1759 if ((marks
& 1) && (vsDraw
.markers
[markBit
].markType
== MarkerSymbol::Underline
) &&
1760 (vsDraw
.markers
[markBit
].layer
== Layer::Base
)) {
1761 PRectangle rcUnderline
= rcLine
;
1762 rcUnderline
.top
= rcUnderline
.bottom
- 2;
1763 surface
->FillRectangleAligned(rcUnderline
, Fill(vsDraw
.markers
[markBit
].back
));
1769 void DrawTranslucentSelection(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1770 Sci::Line line
, int xStart
, PRectangle rcLine
, int subLine
, Range lineRange
, int tabWidthMinimumPixels
, Layer layer
) {
1771 if (vsDraw
.selection
.layer
== layer
) {
1772 const Sci::Position posLineStart
= model
.pdoc
->LineStart(line
);
1773 const XYPOSITION subLineStart
= ll
->positions
[lineRange
.start
];
1774 const XYPOSITION horizontalOffset
= xStart
- subLineStart
;
1775 // For each selection draw
1776 Sci::Position virtualSpaces
= 0;
1777 if (subLine
== (ll
->lines
- 1)) {
1778 virtualSpaces
= model
.sel
.VirtualSpaceFor(model
.pdoc
->LineEnd(line
));
1780 const SelectionPosition
posStart(posLineStart
+ lineRange
.start
);
1781 const SelectionPosition
posEnd(posLineStart
+ lineRange
.end
, virtualSpaces
);
1782 const SelectionSegment
virtualSpaceRange(posStart
, posEnd
);
1783 for (size_t r
= 0; r
< model
.sel
.Count(); r
++) {
1784 const SelectionSegment portion
= model
.sel
.Range(r
).Intersect(virtualSpaceRange
);
1785 if (!portion
.Empty()) {
1786 const SelectionSegment portionInLine
= portion
.Subtract(posLineStart
);
1787 const ColourRGBA selectionBack
= SelectionBackground(
1788 model
, vsDraw
, model
.sel
.RangeType(r
));
1789 const XYPOSITION spaceWidth
= vsDraw
.styles
[ll
->EndLineStyle()].spaceWidth
;
1790 const Interval intervalVirtual
{ portion
.start
.VirtualSpace() * spaceWidth
, portion
.end
.VirtualSpace() * spaceWidth
};
1791 if (model
.BidirectionalEnabled()) {
1792 const SelectionSegment portionInSubLine
= portionInLine
.Subtract(lineRange
.start
);
1794 const ScreenLine
screenLine(ll
, subLine
, vsDraw
, rcLine
.right
, tabWidthMinimumPixels
);
1795 std::unique_ptr
<IScreenLineLayout
> slLayout
= surface
->Layout(&screenLine
);
1798 const std::vector
<Interval
> intervals
= slLayout
->FindRangeIntervals(
1799 portionInSubLine
.start
.Position(), portionInSubLine
.end
.Position());
1800 for (const Interval
&interval
: intervals
) {
1801 const PRectangle rcSelection
= rcLine
.WithHorizontalBounds(interval
.Offset(xStart
));
1802 surface
->FillRectangleAligned(rcSelection
, selectionBack
);
1806 if (portion
.end
.VirtualSpace()) {
1807 const XYPOSITION xStartVirtual
= ll
->positions
[lineRange
.end
] + horizontalOffset
;
1808 const PRectangle rcSegment
= rcLine
.WithHorizontalBounds(intervalVirtual
.Offset(xStartVirtual
));
1809 surface
->FillRectangleAligned(rcSegment
, selectionBack
);
1812 Interval intervalSegment
= ll
->Span(
1813 static_cast<int>(portionInLine
.start
.Position()),
1814 static_cast<int>(portionInLine
.end
.Position()))
1815 .Offset(horizontalOffset
);
1816 intervalSegment
.left
+= intervalVirtual
.left
;
1817 intervalSegment
.right
+= intervalVirtual
.right
;
1818 if ((ll
->wrapIndent
!= 0) && (lineRange
.start
!= 0)) {
1819 if ((portionInLine
.start
.Position() == lineRange
.start
) &&
1820 model
.sel
.Range(r
).ContainsCharacter(portion
.start
.Position() - 1))
1821 intervalSegment
.left
-= static_cast<int>(ll
->wrapIndent
); // indentation added to xStart was truncated to int, so we do the same here
1823 const PRectangle rcSegment
= Intersection(rcLine
, intervalSegment
);
1824 if (rcSegment
.right
> rcLine
.left
)
1825 surface
->FillRectangleAligned(rcSegment
, selectionBack
);
1832 void DrawCaretLineFramed(Surface
*surface
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1833 PRectangle rcLine
, int subLine
) {
1834 const ColourOptional caretlineBack
= vsDraw
.ElementColour(Element::CaretLineBack
);
1835 if (!caretlineBack
) {
1839 const ColourRGBA colourFrame
= (vsDraw
.caretLine
.layer
== Layer::Base
) ?
1840 caretlineBack
->Opaque() : *caretlineBack
;
1842 const int width
= vsDraw
.GetFrameWidth();
1844 // Avoid double drawing the corners by removing the left and right sides when drawing top and bottom borders
1845 const PRectangle rcWithoutLeftRight
= rcLine
.Inset(Point(width
, 0.0));
1847 if (subLine
== 0 || ll
->wrapIndent
== 0 || vsDraw
.caretLine
.layer
!= Layer::Base
|| vsDraw
.caretLine
.subLine
) {
1849 surface
->FillRectangleAligned(Side(rcLine
, Edge::left
, width
), colourFrame
);
1851 if (subLine
== 0 || vsDraw
.caretLine
.subLine
) {
1853 surface
->FillRectangleAligned(Side(rcWithoutLeftRight
, Edge::top
, width
), colourFrame
);
1855 if (subLine
== ll
->lines
- 1 || vsDraw
.caretLine
.layer
!= Layer::Base
|| vsDraw
.caretLine
.subLine
) {
1857 surface
->FillRectangleAligned(Side(rcLine
, Edge::right
, width
), colourFrame
);
1859 if (subLine
== ll
->lines
- 1 || vsDraw
.caretLine
.subLine
) {
1861 surface
->FillRectangleAligned(Side(rcWithoutLeftRight
, Edge::bottom
, width
), colourFrame
);
1865 // Draw any translucent whole line states
1866 void DrawTranslucentLineState(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1867 Sci::Line line
, PRectangle rcLine
, int subLine
, Layer layer
) {
1868 if ((model
.caret
.active
|| vsDraw
.caretLine
.alwaysShow
) && vsDraw
.ElementColour(Element::CaretLineBack
) && ll
->containsCaret
&&
1869 vsDraw
.caretLine
.layer
== layer
) {
1870 if (vsDraw
.caretLine
.frame
) {
1871 DrawCaretLineFramed(surface
, vsDraw
, ll
, rcLine
, subLine
);
1873 surface
->FillRectangleAligned(rcLine
, vsDraw
.ElementColourForced(Element::CaretLineBack
));
1876 const int marksOfLine
= model
.GetMark(line
);
1877 int marksDrawnInText
= marksOfLine
& vsDraw
.maskDrawInText
;
1878 for (int markBit
= 0; (markBit
<= MarkerMax
) && marksDrawnInText
; markBit
++) {
1879 if ((marksDrawnInText
& 1) && (vsDraw
.markers
[markBit
].layer
== layer
)) {
1880 if (vsDraw
.markers
[markBit
].markType
== MarkerSymbol::Background
) {
1881 surface
->FillRectangleAligned(rcLine
, vsDraw
.markers
[markBit
].BackWithAlpha());
1882 } else if (vsDraw
.markers
[markBit
].markType
== MarkerSymbol::Underline
) {
1883 PRectangle rcUnderline
= rcLine
;
1884 rcUnderline
.top
= rcUnderline
.bottom
- 2;
1885 surface
->FillRectangleAligned(rcUnderline
, vsDraw
.markers
[markBit
].BackWithAlpha());
1888 marksDrawnInText
>>= 1;
1890 int marksDrawnInLine
= marksOfLine
& vsDraw
.maskInLine
;
1891 for (int markBit
= 0; (markBit
<= MarkerMax
) && marksDrawnInLine
; markBit
++) {
1892 if ((marksDrawnInLine
& 1) && (vsDraw
.markers
[markBit
].layer
== layer
)) {
1893 surface
->FillRectangleAligned(rcLine
, vsDraw
.markers
[markBit
].BackWithAlpha());
1895 marksDrawnInLine
>>= 1;
1899 void DrawTabArrow(Surface
*surface
, PRectangle rcTab
, int ymid
,
1900 const ViewStyle
&vsDraw
, Stroke stroke
) {
1902 const XYPOSITION halfWidth
= stroke
.width
/ 2.0;
1904 const XYPOSITION leftStroke
= std::round(std::min(rcTab
.left
+ 2, rcTab
.right
- 1)) + halfWidth
;
1905 const XYPOSITION rightStroke
= std::max(leftStroke
, std::round(rcTab
.right
) - 1.0f
- halfWidth
);
1906 const XYPOSITION yMidAligned
= ymid
+ halfWidth
;
1907 const Point
arrowPoint(rightStroke
, yMidAligned
);
1908 if (rightStroke
> leftStroke
) {
1909 // When not enough room, don't draw the arrow shaft
1910 surface
->LineDraw(Point(leftStroke
, yMidAligned
), arrowPoint
, stroke
);
1913 // Draw the arrow head if needed
1914 if (vsDraw
.tabDrawMode
== TabDrawMode::LongArrow
) {
1915 XYPOSITION ydiff
= std::floor(rcTab
.Height() / 2.0f
);
1916 XYPOSITION xhead
= rightStroke
- ydiff
;
1917 if (xhead
<= rcTab
.left
) {
1918 ydiff
-= rcTab
.left
- xhead
;
1921 const Point ptsHead
[] = {
1922 Point(xhead
, yMidAligned
- ydiff
),
1924 Point(xhead
, yMidAligned
+ ydiff
)
1926 surface
->PolyLine(ptsHead
, std::size(ptsHead
), stroke
);
1930 void DrawIndicator(int indicNum
, Sci::Position startPos
, Sci::Position endPos
, Surface
*surface
, const ViewStyle
&vsDraw
,
1931 const LineLayout
*ll
, int xStart
, PRectangle rcLine
, Sci::Position secondCharacter
, int subLine
, Indicator::State state
,
1932 int value
, bool bidiEnabled
, int tabWidthMinimumPixels
) {
1934 const XYPOSITION subLineStart
= ll
->positions
[ll
->LineStart(subLine
)];
1935 const XYPOSITION horizontalOffset
= xStart
- subLineStart
;
1937 std::vector
<PRectangle
> rectangles
;
1939 const XYPOSITION left
= ll
->XInLine(startPos
) + horizontalOffset
;
1940 const XYPOSITION right
= ll
->XInLine(endPos
) + horizontalOffset
;
1941 const PRectangle
rcIndic(left
, rcLine
.top
+ vsDraw
.maxAscent
, right
,
1942 std::max(rcLine
.top
+ vsDraw
.maxAscent
+ 3, rcLine
.bottom
));
1945 ScreenLine
screenLine(ll
, subLine
, vsDraw
, rcLine
.right
- xStart
, tabWidthMinimumPixels
);
1946 const Range lineRange
= ll
->SubLineRange(subLine
, LineLayout::Scope::visibleOnly
);
1948 std::unique_ptr
<IScreenLineLayout
> slLayout
= surface
->Layout(&screenLine
);
1949 std::vector
<Interval
> intervals
= slLayout
->FindRangeIntervals(
1950 startPos
- lineRange
.start
, endPos
- lineRange
.start
);
1951 for (const Interval
&interval
: intervals
) {
1952 PRectangle rcInterval
= rcIndic
;
1953 rcInterval
.left
= interval
.left
+ xStart
;
1954 rcInterval
.right
= interval
.right
+ xStart
;
1955 rectangles
.push_back(rcInterval
);
1958 rectangles
.push_back(rcIndic
);
1961 for (const PRectangle
&rc
: rectangles
) {
1962 PRectangle rcFirstCharacter
= rc
;
1963 // Allow full descent space for character indicators
1964 rcFirstCharacter
.bottom
= rcLine
.top
+ vsDraw
.maxAscent
+ vsDraw
.maxDescent
;
1965 if (secondCharacter
>= 0) {
1966 rcFirstCharacter
.right
= ll
->XInLine(secondCharacter
) + horizontalOffset
;
1968 // Indicator continued from earlier line so make an empty box and don't draw
1969 rcFirstCharacter
.right
= rcFirstCharacter
.left
;
1971 vsDraw
.indicators
[indicNum
].Draw(surface
, rc
, rcLine
, rcFirstCharacter
, state
, value
);
1975 void DrawIndicators(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1976 Sci::Line line
, int xStart
, PRectangle rcLine
, int subLine
, Sci::Position lineEnd
, bool under
, int tabWidthMinimumPixels
) {
1978 const Sci::Position posLineStart
= model
.pdoc
->LineStart(line
);
1979 const Sci::Position lineStart
= ll
->LineStart(subLine
);
1980 const Sci::Position posLineEnd
= posLineStart
+ lineEnd
;
1982 for (const IDecoration
*deco
: model
.pdoc
->decorations
->View()) {
1983 if (under
== vsDraw
.indicators
[deco
->Indicator()].under
) {
1984 Sci::Position startPos
= posLineStart
+ lineStart
;
1985 while (startPos
< posLineEnd
) {
1986 const Range
rangeRun(deco
->StartRun(startPos
), deco
->EndRun(startPos
));
1987 const Sci::Position endPos
= std::min(rangeRun
.end
, posLineEnd
);
1988 const int value
= deco
->ValueAt(startPos
);
1990 const bool hover
= vsDraw
.indicators
[deco
->Indicator()].IsDynamic() &&
1991 rangeRun
.ContainsCharacter(model
.hoverIndicatorPos
);
1992 const Indicator::State state
= hover
? Indicator::State::hover
: Indicator::State::normal
;
1993 const Sci::Position posSecond
= model
.pdoc
->MovePositionOutsideChar(rangeRun
.First() + 1, 1);
1994 DrawIndicator(deco
->Indicator(), startPos
- posLineStart
, endPos
- posLineStart
,
1995 surface
, vsDraw
, ll
, xStart
, rcLine
, posSecond
- posLineStart
, subLine
, state
,
1996 value
, model
.BidirectionalEnabled(), tabWidthMinimumPixels
);
2003 // Use indicators to highlight matching braces
2004 if ((vsDraw
.braceHighlightIndicatorSet
&& (model
.bracesMatchStyle
== StyleBraceLight
)) ||
2005 (vsDraw
.braceBadLightIndicatorSet
&& (model
.bracesMatchStyle
== StyleBraceBad
))) {
2006 const int braceIndicator
= (model
.bracesMatchStyle
== StyleBraceLight
) ? vsDraw
.braceHighlightIndicator
: vsDraw
.braceBadLightIndicator
;
2007 if (under
== vsDraw
.indicators
[braceIndicator
].under
) {
2008 const Range
rangeLine(posLineStart
+ lineStart
, posLineEnd
);
2009 for (size_t brace
= 0; brace
<= 1; brace
++) {
2010 if (rangeLine
.ContainsCharacter(model
.braces
[brace
])) {
2011 const Sci::Position braceOffset
= model
.braces
[brace
] - posLineStart
;
2012 if (braceOffset
< ll
->numCharsInLine
) {
2013 const Sci::Position braceEnd
= model
.pdoc
->MovePositionOutsideChar(model
.braces
[brace
] + 1, 1) - posLineStart
;
2014 DrawIndicator(braceIndicator
, braceOffset
, braceEnd
,
2015 surface
, vsDraw
, ll
, xStart
, rcLine
, braceEnd
, subLine
, Indicator::State::normal
,
2016 1, model
.BidirectionalEnabled(), tabWidthMinimumPixels
);
2023 if (FlagSet(model
.changeHistoryOption
, ChangeHistoryOption::Indicators
)) {
2025 constexpr int indexHistory
= static_cast<int>(IndicatorNumbers::HistoryRevertedToOriginInsertion
);
2028 Sci::Position startPos
= posLineStart
+ lineStart
;
2029 while (startPos
< posLineEnd
) {
2030 const Range
rangeRun(startPos
, model
.pdoc
->EditionEndRun(startPos
));
2031 const Sci::Position endPos
= std::min(rangeRun
.end
, posLineEnd
);
2032 const int edition
= model
.pdoc
->EditionAt(startPos
);
2034 const int indicator
= (edition
- 1) * 2 + indexHistory
;
2035 const Sci::Position posSecond
= model
.pdoc
->MovePositionOutsideChar(rangeRun
.First() + 1, 1);
2036 DrawIndicator(indicator
, startPos
- posLineStart
, endPos
- posLineStart
,
2037 surface
, vsDraw
, ll
, xStart
, rcLine
, posSecond
- posLineStart
, subLine
, Indicator::State::normal
,
2038 1, model
.BidirectionalEnabled(), tabWidthMinimumPixels
);
2045 Sci::Position startPos
= posLineStart
+ lineStart
;
2046 while (startPos
<= posLineEnd
) {
2047 const unsigned int editions
= model
.pdoc
->EditionDeletesAt(startPos
);
2048 const Sci::Position posSecond
= model
.pdoc
->MovePositionOutsideChar(startPos
+ 1, 1);
2049 for (unsigned int edition
= 0; edition
< 4; edition
++) {
2050 if (editions
& (1 << edition
)) {
2051 const int indicator
= edition
* 2 + indexHistory
+ 1;
2052 DrawIndicator(indicator
, startPos
- posLineStart
, posSecond
- posLineStart
,
2053 surface
, vsDraw
, ll
, xStart
, rcLine
, posSecond
- posLineStart
, subLine
, Indicator::State::normal
,
2054 1, model
.BidirectionalEnabled(), tabWidthMinimumPixels
);
2057 startPos
= model
.pdoc
->EditionNextDelete(startPos
);
2063 void DrawFoldLines(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
2064 Sci::Line line
, PRectangle rcLine
, int subLine
) {
2065 const bool lastSubLine
= subLine
== (ll
->lines
- 1);
2066 const bool expanded
= model
.pcs
->GetExpanded(line
);
2067 const FoldLevel level
= model
.pdoc
->GetFoldLevel(line
);
2068 const FoldLevel levelNext
= model
.pdoc
->GetFoldLevel(line
+ 1);
2069 if (LevelIsHeader(level
) &&
2070 (LevelNumber(level
) < LevelNumber(levelNext
))) {
2071 const ColourRGBA foldLineColour
= vsDraw
.ElementColour(Element::FoldLine
).value_or(
2072 vsDraw
.styles
[StyleDefault
].fore
);
2073 // Paint the line above the fold
2074 if ((subLine
== 0) && FlagSet(model
.foldFlags
, (expanded
? FoldFlag::LineBeforeExpanded
: FoldFlag::LineBeforeContracted
))) {
2075 surface
->FillRectangleAligned(Side(rcLine
, Edge::top
, 1.0), foldLineColour
);
2077 // Paint the line below the fold
2078 if (lastSubLine
&& FlagSet(model
.foldFlags
, (expanded
? FoldFlag::LineAfterExpanded
: FoldFlag::LineAfterContracted
))) {
2079 surface
->FillRectangleAligned(Side(rcLine
, Edge::bottom
, 1.0), foldLineColour
);
2080 // If contracted fold line drawn then don't overwrite with hidden line
2081 // as fold lines are more specific then hidden lines.
2087 if (lastSubLine
&& model
.pcs
->GetVisible(line
) && !model
.pcs
->GetVisible(line
+ 1)) {
2088 if (const ColourOptional hiddenLineColour
= vsDraw
.ElementColour(Element::HiddenLine
)) {
2089 surface
->FillRectangleAligned(Side(rcLine
, Edge::bottom
, 1.0), *hiddenLineColour
);
2094 ColourRGBA
InvertedLight(ColourRGBA orig
) noexcept
{
2095 unsigned int r
= orig
.GetRed();
2096 unsigned int g
= orig
.GetGreen();
2097 unsigned int b
= orig
.GetBlue();
2098 const unsigned int l
= (r
+ g
+ b
) / 3; // There is a better calculation for this that matches human eye
2099 const unsigned int il
= 0xff - l
;
2105 return ColourRGBA(std::min(r
, 0xffu
), std::min(g
, 0xffu
), std::min(b
, 0xffu
));
2110 void EditView::DrawIndentGuide(Surface
*surface
, XYPOSITION start
, PRectangle rcSegment
, bool highlight
, bool offset
) {
2111 const Point from
= Point::FromInts(0, offset
? 1 : 0);
2112 const PRectangle
rcCopyArea(start
+ 1, rcSegment
.top
,
2113 start
+ 2, rcSegment
.bottom
);
2114 surface
->Copy(rcCopyArea
, from
,
2115 highlight
? *pixmapIndentGuideHighlight
: *pixmapIndentGuide
);
2118 void EditView::DrawForeground(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
2119 int xStart
, PRectangle rcLine
, int subLine
, Sci::Line lineVisible
, Range lineRange
, Sci::Position posLineStart
,
2120 ColourOptional background
) {
2122 const bool selBackDrawn
= vsDraw
.SelectionBackgroundDrawn();
2123 const bool drawWhitespaceBackground
= vsDraw
.WhitespaceBackgroundDrawn() && !background
;
2124 bool inIndentation
= subLine
== 0; // Do not handle indentation except on first subline.
2126 const XYPOSITION subLineStart
= ll
->positions
[lineRange
.start
];
2127 const XYPOSITION horizontalOffset
= xStart
- subLineStart
;
2128 const XYPOSITION indentWidth
= model
.pdoc
->IndentSize() * vsDraw
.spaceWidth
;
2130 // Does not take margin into account but not significant
2131 const XYPOSITION xStartVisible
= subLineStart
- xStart
;
2133 // When lineHeight is odd, dotted indent guides are drawn offset by 1 on odd lines to join together.
2134 const bool offsetGuide
= (lineVisible
& 1) && (vsDraw
.lineHeight
& 1);
2136 // Same baseline used for all text
2137 const XYPOSITION ybase
= rcLine
.top
+ vsDraw
.maxAscent
;
2139 // Foreground drawing loop
2140 const BreakFinder::BreakFor breakFor
= (((phasesDraw
== PhasesDraw::One
) && selBackDrawn
) || vsDraw
.SelectionTextDrawn())
2141 ? BreakFinder::BreakFor::ForegroundAndSelection
: BreakFinder::BreakFor::Foreground
;
2142 BreakFinder
bfFore(ll
, &model
.sel
, lineRange
, posLineStart
, xStartVisible
, breakFor
, model
.pdoc
, &model
.reprs
, &vsDraw
);
2144 while (bfFore
.More()) {
2146 const TextSegment ts
= bfFore
.Next();
2147 const Sci::Position i
= ts
.end() - 1;
2148 const Sci::Position iDoc
= i
+ posLineStart
;
2150 const Interval horizontal
= ll
->Span(ts
.start
, ts
.end()).Offset(horizontalOffset
);
2151 // Only try to draw if really visible - enhances performance by not calling environment to
2152 // draw strings that are completely past the right side of the window.
2153 if (rcLine
.Intersects(horizontal
)) {
2154 const PRectangle rcSegment
= rcLine
.WithHorizontalBounds(horizontal
);
2155 const int styleMain
= ll
->styles
[i
];
2156 ColourRGBA textFore
= vsDraw
.styles
[styleMain
].fore
;
2157 const Font
*textFont
= vsDraw
.styles
[styleMain
].font
.get();
2158 // Hot-spot foreground
2159 const bool inHotspot
= model
.hotspot
.Valid() && model
.hotspot
.ContainsCharacter(iDoc
);
2161 if (const ColourOptional colourHotSpot
= vsDraw
.ElementColour(Element::HotSpotActive
)) {
2162 textFore
= *colourHotSpot
;
2165 if (vsDraw
.indicatorsSetFore
) {
2166 // At least one indicator sets the text colour so see if it applies to this segment
2167 for (const IDecoration
*deco
: model
.pdoc
->decorations
->View()) {
2168 const int indicatorValue
= deco
->ValueAt(ts
.start
+ posLineStart
);
2169 if (indicatorValue
) {
2170 const Indicator
&indicator
= vsDraw
.indicators
[deco
->Indicator()];
2172 if (indicator
.IsDynamic()) {
2173 const Sci::Position startPos
= ts
.start
+ posLineStart
;
2174 const Range
rangeRun(deco
->StartRun(startPos
), deco
->EndRun(startPos
));
2175 hover
= rangeRun
.ContainsCharacter(model
.hoverIndicatorPos
);
2178 if (indicator
.sacHover
.style
== IndicatorStyle::TextFore
) {
2179 textFore
= indicator
.sacHover
.fore
;
2182 if (indicator
.sacNormal
.style
== IndicatorStyle::TextFore
) {
2183 if (FlagSet(indicator
.Flags(), IndicFlag::ValueFore
))
2184 textFore
= ColourRGBA::FromRGB(indicatorValue
& static_cast<int>(IndicValue::Mask
));
2186 textFore
= indicator
.sacNormal
.fore
;
2192 InSelection inSelection
= vsDraw
.selection
.visible
? model
.sel
.CharacterInSelection(iDoc
) : InSelection::inNone
;
2193 if (FlagSet(vsDraw
.caret
.style
, CaretStyle::Curses
) && (inSelection
== InSelection::inMain
))
2194 inSelection
= CharacterInCursesSelection(iDoc
, model
, vsDraw
);
2195 if (const ColourOptional selectionFore
= SelectionForeground(model
, vsDraw
, inSelection
)) {
2196 textFore
= *selectionFore
;
2198 ColourRGBA textBack
= TextBackground(model
, vsDraw
, ll
, background
, inSelection
, inHotspot
, styleMain
, i
);
2199 if (ts
.representation
) {
2200 if (ll
->chars
[i
] == '\t') {
2202 if (phasesDraw
== PhasesDraw::One
) {
2203 if (drawWhitespaceBackground
&& vsDraw
.WhiteSpaceVisible(inIndentation
))
2204 textBack
= vsDraw
.ElementColourForced(Element::WhiteSpaceBack
).Opaque();
2205 surface
->FillRectangleAligned(rcSegment
, Fill(textBack
));
2207 if (inIndentation
&& vsDraw
.viewIndentationGuides
== IndentView::Real
) {
2208 const Interval intervalCharacter
= ll
->SpanByte(static_cast<int>(i
));
2209 for (int indentCount
= static_cast<int>((intervalCharacter
.left
+ epsilon
) / indentWidth
);
2210 indentCount
<= (intervalCharacter
.right
- epsilon
) / indentWidth
;
2212 if (indentCount
> 0) {
2213 const XYPOSITION xIndent
= std::floor(indentCount
* indentWidth
);
2214 DrawIndentGuide(surface
, xIndent
+ xStart
, rcSegment
, ll
->xHighlightGuide
== xIndent
, offsetGuide
);
2218 if (vsDraw
.viewWhitespace
!= WhiteSpace::Invisible
) {
2219 if (vsDraw
.WhiteSpaceVisible(inIndentation
)) {
2220 const PRectangle
rcTab(rcSegment
.left
+ 1, rcSegment
.top
+ tabArrowHeight
,
2221 rcSegment
.right
- 1, rcSegment
.bottom
- vsDraw
.maxDescent
);
2222 const int segmentTop
= static_cast<int>(rcSegment
.top
) + vsDraw
.lineHeight
/ 2;
2223 const ColourRGBA whiteSpaceFore
= vsDraw
.ElementColour(Element::WhiteSpace
).value_or(textFore
);
2224 if (!customDrawTabArrow
)
2225 DrawTabArrow(surface
, rcTab
, segmentTop
, vsDraw
, Stroke(whiteSpaceFore
, 1.0f
));
2227 customDrawTabArrow(surface
, rcTab
, segmentTop
, vsDraw
, Stroke(whiteSpaceFore
, 1.0f
));
2231 inIndentation
= false;
2232 if (vsDraw
.controlCharSymbol
>= 32) {
2233 // Using one font for all control characters so it can be controlled independently to ensure
2234 // the box goes around the characters tightly. Seems to be no way to work out what height
2235 // is taken by an individual character - internal leading gives varying results.
2236 const Font
*ctrlCharsFont
= vsDraw
.styles
[StyleControlChar
].font
.get();
2237 const char cc
[2] = { static_cast<char>(vsDraw
.controlCharSymbol
), '\0' };
2238 surface
->DrawTextNoClip(rcSegment
, ctrlCharsFont
,
2239 ybase
, cc
, textBack
, textFore
);
2241 if (FlagSet(ts
.representation
->appearance
, RepresentationAppearance::Colour
)) {
2242 textFore
= ts
.representation
->colour
;
2244 if (FlagSet(ts
.representation
->appearance
, RepresentationAppearance::Blob
)) {
2245 DrawTextBlob(surface
, vsDraw
, rcSegment
, ts
.representation
->stringRep
,
2246 textBack
, textFore
, phasesDraw
== PhasesDraw::One
);
2248 surface
->DrawTextTransparentUTF8(rcSegment
, vsDraw
.styles
[StyleControlChar
].font
.get(),
2249 ybase
, ts
.representation
->stringRep
, textFore
);
2254 // Normal text display
2255 if (vsDraw
.styles
[styleMain
].visible
) {
2256 const std::string_view
text(&ll
->chars
[ts
.start
], i
- ts
.start
+ 1);
2257 if (phasesDraw
!= PhasesDraw::One
) {
2258 surface
->DrawTextTransparent(rcSegment
, textFont
,
2259 ybase
, text
, textFore
);
2261 surface
->DrawTextNoClip(rcSegment
, textFont
,
2262 ybase
, text
, textFore
, textBack
);
2264 } else if (vsDraw
.styles
[styleMain
].invisibleRepresentation
[0]) {
2265 const std::string_view text
= vsDraw
.styles
[styleMain
].invisibleRepresentation
;
2266 if (phasesDraw
!= PhasesDraw::One
) {
2267 surface
->DrawTextTransparentUTF8(rcSegment
, textFont
,
2268 ybase
, text
, textFore
);
2270 surface
->DrawTextNoClipUTF8(rcSegment
, textFont
,
2271 ybase
, text
, textFore
, textBack
);
2274 if (vsDraw
.viewWhitespace
!= WhiteSpace::Invisible
||
2275 (inIndentation
&& vsDraw
.viewIndentationGuides
!= IndentView::None
)) {
2276 for (int cpos
= 0; cpos
<= i
- ts
.start
; cpos
++) {
2277 if (ll
->chars
[cpos
+ ts
.start
] == ' ') {
2278 if (vsDraw
.viewWhitespace
!= WhiteSpace::Invisible
) {
2279 if (vsDraw
.WhiteSpaceVisible(inIndentation
)) {
2280 const Interval intervalSpace
= ll
->SpanByte(cpos
+ ts
.start
).Offset(horizontalOffset
);
2281 const XYPOSITION xmid
= (intervalSpace
.left
+ intervalSpace
.right
) / 2;
2282 if ((phasesDraw
== PhasesDraw::One
) && drawWhitespaceBackground
) {
2283 textBack
= vsDraw
.ElementColourForced(Element::WhiteSpaceBack
).Opaque();
2284 const PRectangle rcSpace
= rcLine
.WithHorizontalBounds(intervalSpace
);
2285 surface
->FillRectangleAligned(rcSpace
, Fill(textBack
));
2287 const int halfDotWidth
= vsDraw
.whitespaceSize
/ 2;
2288 PRectangle
rcDot(xmid
- halfDotWidth
,
2289 rcSegment
.top
+ vsDraw
.lineHeight
/ 2, 0.0f
, 0.0f
);
2290 rcDot
.right
= rcDot
.left
+ vsDraw
.whitespaceSize
;
2291 rcDot
.bottom
= rcDot
.top
+ vsDraw
.whitespaceSize
;
2292 const ColourRGBA whiteSpaceFore
= vsDraw
.ElementColour(Element::WhiteSpace
).value_or(textFore
);
2293 surface
->FillRectangleAligned(rcDot
, Fill(whiteSpaceFore
));
2296 if (inIndentation
&& vsDraw
.viewIndentationGuides
== IndentView::Real
) {
2297 const Interval intervalCharacter
= ll
->SpanByte(cpos
+ ts
.start
);
2298 for (int indentCount
= static_cast<int>((intervalCharacter
.left
+ epsilon
) / indentWidth
);
2299 indentCount
<= (intervalCharacter
.right
- epsilon
) / indentWidth
;
2301 if (indentCount
> 0) {
2302 const XYPOSITION xIndent
= std::floor(indentCount
* indentWidth
);
2303 DrawIndentGuide(surface
, xIndent
+ xStart
, rcSegment
, ll
->xHighlightGuide
== xIndent
, offsetGuide
);
2308 inIndentation
= false;
2313 if ((inHotspot
&& vsDraw
.hotspotUnderline
) || vsDraw
.styles
[styleMain
].underline
) {
2314 PRectangle rcUL
= rcSegment
;
2315 rcUL
.top
= ybase
+ 1;
2316 rcUL
.bottom
= ybase
+ 2;
2317 ColourRGBA colourUnderline
= textFore
;
2318 if (inHotspot
&& vsDraw
.hotspotUnderline
) {
2319 colourUnderline
= vsDraw
.ElementColour(Element::HotSpotActive
).value_or(textFore
);
2321 surface
->FillRectangleAligned(rcUL
, colourUnderline
);
2323 } else if (horizontal
.left
> rcLine
.right
) {
2329 void EditView::DrawIndentGuidesOverEmpty(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
2330 Sci::Line line
, int xStart
, PRectangle rcLine
, int subLine
, Sci::Line lineVisible
) {
2331 if ((vsDraw
.viewIndentationGuides
== IndentView::LookForward
|| vsDraw
.viewIndentationGuides
== IndentView::LookBoth
)
2332 && (subLine
== 0)) {
2333 const Sci::Position posLineStart
= model
.pdoc
->LineStart(line
);
2334 int indentSpace
= model
.pdoc
->GetLineIndentation(line
);
2335 int xStartText
= static_cast<int>(ll
->positions
[model
.pdoc
->GetLineIndentPosition(line
) - posLineStart
]);
2337 // Find the most recent line with some text
2339 Sci::Line lineLastWithText
= line
;
2340 while (lineLastWithText
> std::max(line
- 20, static_cast<Sci::Line
>(0)) && model
.pdoc
->IsWhiteLine(lineLastWithText
)) {
2343 if (lineLastWithText
< line
) {
2344 xStartText
= 100000; // Don't limit to visible indentation on empty line
2345 // This line is empty, so use indentation of last line with text
2346 int indentLastWithText
= model
.pdoc
->GetLineIndentation(lineLastWithText
);
2347 const int isFoldHeader
= LevelIsHeader(model
.pdoc
->GetFoldLevel(lineLastWithText
));
2349 // Level is one more level than parent
2350 indentLastWithText
+= model
.pdoc
->IndentSize();
2352 if (vsDraw
.viewIndentationGuides
== IndentView::LookForward
) {
2353 // In viLookForward mode, previous line only used if it is a fold header
2355 indentSpace
= std::max(indentSpace
, indentLastWithText
);
2357 } else { // viLookBoth
2358 indentSpace
= std::max(indentSpace
, indentLastWithText
);
2362 Sci::Line lineNextWithText
= line
;
2363 while (lineNextWithText
< std::min(line
+ 20, model
.pdoc
->LinesTotal()) && model
.pdoc
->IsWhiteLine(lineNextWithText
)) {
2366 if (lineNextWithText
> line
) {
2367 xStartText
= 100000; // Don't limit to visible indentation on empty line
2368 // This line is empty, so use indentation of first next line with text
2369 indentSpace
= std::max(indentSpace
,
2370 model
.pdoc
->GetLineIndentation(lineNextWithText
));
2373 const bool offsetGuide
= (lineVisible
& 1) && (vsDraw
.lineHeight
& 1);
2374 for (int indentPos
= model
.pdoc
->IndentSize(); indentPos
< indentSpace
; indentPos
+= model
.pdoc
->IndentSize()) {
2375 const XYPOSITION xIndent
= std::floor(indentPos
* vsDraw
.spaceWidth
);
2376 if (xIndent
< xStartText
) {
2377 DrawIndentGuide(surface
, xIndent
+ xStart
, rcLine
, ll
->xHighlightGuide
== xIndent
, offsetGuide
);
2383 void EditView::DrawLine(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
2384 Sci::Line line
, Sci::Line lineVisible
, int xStart
, PRectangle rcLine
, int subLine
, DrawPhase phase
) {
2386 if (subLine
>= ll
->lines
) {
2387 DrawAnnotation(surface
, model
, vsDraw
, ll
, line
, xStart
, rcLine
, subLine
, phase
);
2388 return; // No further drawing
2391 const bool clipLine
= !bufferedDraw
&& !LinesOverlap();
2393 surface
->SetClip(rcLine
);
2396 // See if something overrides the line background colour.
2397 ColourOptional background
= vsDraw
.Background(model
.GetMark(line
), model
.caret
.active
, ll
->containsCaret
);
2398 SCNotification scn
= { 0 };
2399 scn
.nmhdr
.code
= SCN_GETBKCOLOR
;
2403 ((Editor
*)editor
)->NotifyParent(&scn
);
2404 if (scn
.lParam
!= -1)
2405 background
= ColourRGBA::FromRGB(static_cast<int>(scn
.lParam
));
2407 const Sci::Position posLineStart
= model
.pdoc
->LineStart(line
);
2409 const Range lineRange
= ll
->SubLineRange(subLine
, LineLayout::Scope::visibleOnly
);
2410 const Range lineRangeIncludingEnd
= ll
->SubLineRange(subLine
, LineLayout::Scope::includeEnd
);
2411 const XYPOSITION subLineStart
= ll
->positions
[lineRange
.start
];
2413 if ((ll
->wrapIndent
!= 0) && (subLine
> 0)) {
2414 if (FlagSet(phase
, DrawPhase::back
)) {
2415 DrawWrapIndentAndMarker(surface
, vsDraw
, ll
, xStart
, rcLine
, background
, customDrawWrapMarker
, model
.caret
.active
);
2417 xStart
+= static_cast<int>(ll
->wrapIndent
);
2420 if (phasesDraw
!= PhasesDraw::One
) {
2421 if (FlagSet(phase
, DrawPhase::back
)) {
2422 DrawBackground(surface
, model
, vsDraw
, ll
,
2423 xStart
, rcLine
, subLine
, lineRange
, posLineStart
,
2425 DrawFoldDisplayText(surface
, model
, vsDraw
, ll
, line
, xStart
, rcLine
, subLine
, subLineStart
, DrawPhase::back
);
2426 DrawEOLAnnotationText(surface
, model
, vsDraw
, ll
, line
, xStart
, rcLine
, subLine
, subLineStart
, DrawPhase::back
);
2427 // Remove drawBack to not draw again in DrawFoldDisplayText
2428 phase
= static_cast<DrawPhase
>(static_cast<int>(phase
) & ~static_cast<int>(DrawPhase::back
));
2429 DrawEOL(surface
, model
, vsDraw
, ll
,
2430 line
, xStart
, rcLine
, subLine
, lineRange
.end
, subLineStart
, background
);
2431 if (vsDraw
.IsLineFrameOpaque(model
.caret
.active
, ll
->containsCaret
))
2432 DrawCaretLineFramed(surface
, vsDraw
, ll
, rcLine
, subLine
);
2435 if (FlagSet(phase
, DrawPhase::indicatorsBack
)) {
2436 DrawIndicators(surface
, model
, vsDraw
, ll
, line
, xStart
, rcLine
, subLine
,
2437 lineRangeIncludingEnd
.end
, true, tabWidthMinimumPixels
);
2438 DrawEdgeLine(surface
, vsDraw
, ll
, xStart
, rcLine
, lineRange
);
2439 DrawMarkUnderline(surface
, model
, vsDraw
, line
, rcLine
);
2443 if (FlagSet(phase
, DrawPhase::text
)) {
2444 if (vsDraw
.selection
.visible
) {
2445 DrawTranslucentSelection(surface
, model
, vsDraw
, ll
,
2446 line
, xStart
, rcLine
, subLine
, lineRange
, tabWidthMinimumPixels
, Layer::UnderText
);
2448 DrawTranslucentLineState(surface
, model
, vsDraw
, ll
, line
, rcLine
, subLine
, Layer::UnderText
);
2449 DrawForeground(surface
, model
, vsDraw
, ll
,
2450 xStart
, rcLine
, subLine
, lineVisible
, lineRange
, posLineStart
,
2454 if (FlagSet(phase
, DrawPhase::indentationGuides
)) {
2455 DrawIndentGuidesOverEmpty(surface
, model
, vsDraw
, ll
, line
, xStart
, rcLine
, subLine
, lineVisible
);
2458 if (FlagSet(phase
, DrawPhase::indicatorsFore
)) {
2459 DrawIndicators(surface
, model
, vsDraw
, ll
, line
, xStart
, rcLine
, subLine
,
2460 lineRangeIncludingEnd
.end
, false, tabWidthMinimumPixels
);
2463 DrawFoldDisplayText(surface
, model
, vsDraw
, ll
, line
, xStart
, rcLine
, subLine
, subLineStart
, phase
);
2464 DrawEOLAnnotationText(surface
, model
, vsDraw
, ll
, line
, xStart
, rcLine
, subLine
, subLineStart
, phase
);
2466 if (phasesDraw
== PhasesDraw::One
) {
2467 DrawEOL(surface
, model
, vsDraw
, ll
,
2468 line
, xStart
, rcLine
, subLine
, lineRange
.end
, subLineStart
, background
);
2469 if (vsDraw
.IsLineFrameOpaque(model
.caret
.active
, ll
->containsCaret
))
2470 DrawCaretLineFramed(surface
, vsDraw
, ll
, rcLine
, subLine
);
2471 DrawEdgeLine(surface
, vsDraw
, ll
, xStart
, rcLine
, lineRange
);
2472 DrawMarkUnderline(surface
, model
, vsDraw
, line
, rcLine
);
2475 if (vsDraw
.selection
.visible
&& FlagSet(phase
, DrawPhase::selectionTranslucent
)) {
2476 DrawTranslucentSelection(surface
, model
, vsDraw
, ll
,
2477 line
, xStart
, rcLine
, subLine
, lineRange
, tabWidthMinimumPixels
, Layer::OverText
);
2480 if (FlagSet(phase
, DrawPhase::lineTranslucent
)) {
2481 DrawTranslucentLineState(surface
, model
, vsDraw
, ll
, line
, rcLine
, subLine
, Layer::OverText
);
2489 void EditView::PaintText(Surface
*surfaceWindow
, const EditModel
&model
, const ViewStyle
&vsDraw
,
2490 PRectangle rcArea
, PRectangle rcClient
) {
2491 // Allow text at start of line to overlap 1 pixel into the margin as this displays
2492 // serifs and italic stems for aliased text.
2493 const int leftTextOverlap
= ((model
.xOffset
== 0) && (vsDraw
.leftMarginWidth
> 0)) ? 1 : 0;
2496 if (rcArea
.right
> vsDraw
.textStart
- leftTextOverlap
) {
2498 Surface
*surface
= surfaceWindow
;
2500 surface
= pixmapLine
.get();
2501 PLATFORM_ASSERT(pixmapLine
->Initialised());
2503 surface
->SetMode(model
.CurrentSurfaceMode());
2505 const Point ptOrigin
= model
.GetVisibleOriginInMain();
2507 const int screenLinePaintFirst
= static_cast<int>(rcArea
.top
) / vsDraw
.lineHeight
;
2508 const int xStart
= vsDraw
.textStart
- model
.xOffset
+ static_cast<int>(ptOrigin
.x
);
2510 const SelectionPosition posCaret
= model
.posDrag
.IsValid() ? model
.posDrag
: model
.sel
.RangeMain().caret
;
2511 const Sci::Line lineCaret
= model
.pdoc
->SciLineFromPosition(posCaret
.Position());
2512 const int caretOffset
= static_cast<int>(posCaret
.Position() - model
.pdoc
->LineStart(lineCaret
));
2514 PRectangle rcTextArea
= rcClient
;
2515 if (vsDraw
.marginInside
) {
2516 rcTextArea
.left
+= vsDraw
.textStart
;
2517 rcTextArea
.right
-= vsDraw
.rightMarginWidth
;
2519 rcTextArea
= rcArea
;
2522 // Remove selection margin from drawing area so text will not be drawn
2523 // on it in unbuffered mode.
2524 const bool clipping
= !bufferedDraw
&& vsDraw
.marginInside
;
2526 PRectangle rcClipText
= rcTextArea
;
2527 rcClipText
.left
-= leftTextOverlap
;
2528 surfaceWindow
->SetClip(rcClipText
);
2531 // Loop on visible lines
2532 #if defined(TIME_PAINTING)
2533 double durLayout
= 0.0;
2534 double durPaint
= 0.0;
2535 double durCopy
= 0.0;
2536 ElapsedPeriod epWhole
;
2538 const bool bracesIgnoreStyle
= ((vsDraw
.braceHighlightIndicatorSet
&& (model
.bracesMatchStyle
== StyleBraceLight
)) ||
2539 (vsDraw
.braceBadLightIndicatorSet
&& (model
.bracesMatchStyle
== StyleBraceBad
)));
2541 Sci::Line lineDocPrevious
= -1; // Used to avoid laying out one document line multiple times
2542 std::shared_ptr
<LineLayout
> ll
;
2543 std::vector
<DrawPhase
> phases
;
2544 if ((phasesDraw
== PhasesDraw::Multiple
) && !bufferedDraw
) {
2545 for (DrawPhase phase
= DrawPhase::back
; phase
<= DrawPhase::carets
; phase
= static_cast<DrawPhase
>(static_cast<int>(phase
) * 2)) {
2546 phases
.push_back(phase
);
2549 phases
.push_back(DrawPhase::all
);
2551 for (const DrawPhase
&phase
: phases
) {
2554 ypos
+= screenLinePaintFirst
* vsDraw
.lineHeight
;
2555 int yposScreen
= screenLinePaintFirst
* vsDraw
.lineHeight
;
2556 Sci::Line visibleLine
= model
.TopLineOfMain() + screenLinePaintFirst
;
2557 while (visibleLine
< model
.pcs
->LinesDisplayed() && yposScreen
< rcArea
.bottom
) {
2559 const Sci::Line lineDoc
= model
.pcs
->DocFromDisplay(visibleLine
);
2560 // Only visible lines should be handled by the code within the loop
2561 PLATFORM_ASSERT(model
.pcs
->GetVisible(lineDoc
));
2562 const Sci::Line lineStartSet
= model
.pcs
->DisplayFromDoc(lineDoc
);
2563 const int subLine
= static_cast<int>(visibleLine
- lineStartSet
);
2565 // Copy this line and its styles from the document into local arrays
2566 // and determine the x position at which each character starts.
2567 #if defined(TIME_PAINTING)
2570 if (lineDoc
!= lineDocPrevious
) {
2571 ll
= RetrieveLineLayout(lineDoc
, model
);
2572 LayoutLine(model
, surface
, vsDraw
, ll
.get(), model
.wrapWidth
);
2573 lineDocPrevious
= lineDoc
;
2575 #if defined(TIME_PAINTING)
2576 durLayout
+= ep
.Duration(true);
2579 ll
->containsCaret
= vsDraw
.selection
.visible
&& (lineDoc
== lineCaret
)
2580 && (ll
->lines
== 1 || !vsDraw
.caretLine
.subLine
|| ll
->InLine(caretOffset
, subLine
));
2582 PRectangle rcLine
= rcTextArea
;
2583 rcLine
.top
= static_cast<XYPOSITION
>(ypos
);
2584 rcLine
.bottom
= static_cast<XYPOSITION
>(ypos
+ vsDraw
.lineHeight
);
2586 const Range
rangeLine(model
.pdoc
->LineStart(lineDoc
),
2587 model
.pdoc
->LineStart(lineDoc
+ 1));
2589 // Highlight the current braces if any
2590 ll
->SetBracesHighlight(rangeLine
, model
.braces
, static_cast<char>(model
.bracesMatchStyle
),
2591 static_cast<int>(model
.highlightGuideColumn
* vsDraw
.spaceWidth
), bracesIgnoreStyle
);
2593 if (leftTextOverlap
&& (bufferedDraw
|| ((phasesDraw
< PhasesDraw::Multiple
) && (FlagSet(phase
, DrawPhase::back
))))) {
2594 // Clear the left margin
2595 PRectangle rcSpacer
= rcLine
;
2596 rcSpacer
.right
= rcSpacer
.left
;
2598 surface
->FillRectangleAligned(rcSpacer
, Fill(vsDraw
.styles
[StyleDefault
].back
));
2601 if (model
.BidirectionalEnabled()) {
2602 // Fill the line bidi data
2603 UpdateBidiData(model
, vsDraw
, ll
.get());
2606 DrawLine(surface
, model
, vsDraw
, ll
.get(), lineDoc
, visibleLine
, xStart
, rcLine
, subLine
, phase
);
2607 #if defined(TIME_PAINTING)
2608 durPaint
+= ep
.Duration(true);
2610 // Restore the previous styles for the brace highlights in case layout is in cache.
2611 ll
->RestoreBracesHighlight(rangeLine
, model
.braces
, bracesIgnoreStyle
);
2613 if (FlagSet(phase
, DrawPhase::foldLines
)) {
2614 DrawFoldLines(surface
, model
, vsDraw
, ll
.get(), lineDoc
, rcLine
, subLine
);
2617 if (FlagSet(phase
, DrawPhase::carets
)) {
2618 DrawCarets(surface
, model
, vsDraw
, ll
.get(), lineDoc
, xStart
, rcLine
, subLine
);
2622 const Point from
= Point::FromInts(vsDraw
.textStart
- leftTextOverlap
, 0);
2623 const PRectangle rcCopyArea
= PRectangle::FromInts(vsDraw
.textStart
- leftTextOverlap
, yposScreen
,
2624 static_cast<int>(rcClient
.right
- vsDraw
.rightMarginWidth
),
2625 yposScreen
+ vsDraw
.lineHeight
);
2626 pixmapLine
->FlushDrawing();
2627 surfaceWindow
->Copy(rcCopyArea
, from
, *pixmapLine
);
2630 lineWidthMaxSeen
= std::max(
2631 lineWidthMaxSeen
, static_cast<int>(ll
->positions
[ll
->numCharsInLine
]));
2632 #if defined(TIME_PAINTING)
2633 durCopy
+= ep
.Duration(true);
2637 if (!bufferedDraw
) {
2638 ypos
+= vsDraw
.lineHeight
;
2641 yposScreen
+= vsDraw
.lineHeight
;
2646 #if defined(TIME_PAINTING)
2647 if (durPaint
< 0.00000001)
2648 durPaint
= 0.00000001;
2650 // Right column limit indicator
2651 PRectangle rcBeyondEOF
= (vsDraw
.marginInside
) ? rcClient
: rcArea
;
2652 rcBeyondEOF
.left
= static_cast<XYPOSITION
>(vsDraw
.textStart
);
2653 rcBeyondEOF
.right
= rcBeyondEOF
.right
- ((vsDraw
.marginInside
) ? vsDraw
.rightMarginWidth
: 0);
2654 rcBeyondEOF
.top
= static_cast<XYPOSITION
>((model
.pcs
->LinesDisplayed() - model
.TopLineOfMain()) * vsDraw
.lineHeight
);
2655 if (rcBeyondEOF
.top
< rcBeyondEOF
.bottom
) {
2656 surfaceWindow
->FillRectangleAligned(rcBeyondEOF
, Fill(vsDraw
.styles
[StyleDefault
].back
));
2657 if (vsDraw
.edgeState
== EdgeVisualStyle::Line
) {
2658 const int edgeX
= static_cast<int>(vsDraw
.theEdge
.column
* vsDraw
.spaceWidth
);
2659 rcBeyondEOF
.left
= static_cast<XYPOSITION
>(edgeX
+ xStart
);
2660 rcBeyondEOF
.right
= rcBeyondEOF
.left
+ 1;
2661 surfaceWindow
->FillRectangleAligned(rcBeyondEOF
, Fill(vsDraw
.theEdge
.colour
));
2662 } else if (vsDraw
.edgeState
== EdgeVisualStyle::MultiLine
) {
2663 for (size_t edge
= 0; edge
< vsDraw
.theMultiEdge
.size(); edge
++) {
2664 if (vsDraw
.theMultiEdge
[edge
].column
>= 0) {
2665 const int edgeX
= static_cast<int>(vsDraw
.theMultiEdge
[edge
].column
* vsDraw
.spaceWidth
);
2666 rcBeyondEOF
.left
= static_cast<XYPOSITION
>(edgeX
+ xStart
);
2667 rcBeyondEOF
.right
= rcBeyondEOF
.left
+ 1;
2668 surfaceWindow
->FillRectangleAligned(rcBeyondEOF
, Fill(vsDraw
.theMultiEdge
[edge
].colour
));
2675 surfaceWindow
->PopClip();
2677 //Platform::DebugPrintf("start display %d, offset = %d\n", model.pdoc->Length(), model.xOffset);
2678 #if defined(TIME_PAINTING)
2679 Platform::DebugPrintf(
2680 "Layout:%9.6g Paint:%9.6g Ratio:%9.6g Copy:%9.6g Total:%9.6g\n",
2681 durLayout
, durPaint
, durLayout
/ durPaint
, durCopy
, epWhole
.Duration());
2686 // Space (3 space characters) between line numbers and text when printing.
2687 #define lineNumberPrintSpace " "
2689 Sci::Position
EditView::FormatRange(bool draw
, CharacterRangeFull chrg
, Rectangle rc
, Surface
*surface
, Surface
*surfaceMeasure
,
2690 const EditModel
&model
, const ViewStyle
&vs
) {
2691 // Can't use measurements cached for screen
2694 ViewStyle
vsPrint(vs
);
2695 vsPrint
.technology
= Technology::Default
;
2697 // Modify the view style for printing as do not normally want any of the transient features to be printed
2698 // Printing supports only the line number margin.
2699 int lineNumberIndex
= -1;
2700 for (size_t margin
= 0; margin
< vs
.ms
.size(); margin
++) {
2701 if ((vsPrint
.ms
[margin
].style
== MarginType::Number
) && (vsPrint
.ms
[margin
].width
> 0)) {
2702 lineNumberIndex
= static_cast<int>(margin
);
2704 vsPrint
.ms
[margin
].width
= 0;
2707 vsPrint
.fixedColumnWidth
= 0;
2708 vsPrint
.zoomLevel
= printParameters
.magnification
;
2709 // Don't show indentation guides
2710 // If this ever gets changed, cached pixmap would need to be recreated if technology != Technology::Default
2711 vsPrint
.viewIndentationGuides
= IndentView::None
;
2712 // Don't show the selection when printing
2713 vsPrint
.selection
.visible
= false;
2714 vsPrint
.elementColours
.clear();
2715 vsPrint
.elementBaseColours
.clear();
2716 vsPrint
.caretLine
.alwaysShow
= false;
2717 // Don't highlight matching braces using indicators
2718 vsPrint
.braceHighlightIndicatorSet
= false;
2719 vsPrint
.braceBadLightIndicatorSet
= false;
2721 // Set colours for printing according to users settings
2722 const PrintOption colourMode
= printParameters
.colourMode
;
2723 const std::vector
<Style
>::iterator endStyles
= (colourMode
== PrintOption::ColourOnWhiteDefaultBG
) ?
2724 vsPrint
.styles
.begin() + StyleLineNumber
: vsPrint
.styles
.end();
2725 for (std::vector
<Style
>::iterator it
= vsPrint
.styles
.begin(); it
!= endStyles
; ++it
) {
2726 if (colourMode
== PrintOption::InvertLight
) {
2727 it
->fore
= InvertedLight(it
->fore
);
2728 it
->back
= InvertedLight(it
->back
);
2729 } else if (colourMode
== PrintOption::BlackOnWhite
) {
2732 } else if (colourMode
== PrintOption::ColourOnWhite
|| colourMode
== PrintOption::ColourOnWhiteDefaultBG
) {
2736 // White background for the line numbers if PrintOption::ScreenColours isn't used
2737 if (colourMode
!= PrintOption::ScreenColours
) {
2738 vsPrint
.styles
[StyleLineNumber
].back
= white
;
2741 // Printing uses different margins, so reset screen margins
2742 vsPrint
.leftMarginWidth
= 0;
2743 vsPrint
.rightMarginWidth
= 0;
2745 vsPrint
.Refresh(*surfaceMeasure
, model
.pdoc
->tabInChars
);
2746 // Determining width must happen after fonts have been realised in Refresh
2747 int lineNumberWidth
= 0;
2748 if (lineNumberIndex
>= 0) {
2749 lineNumberWidth
= static_cast<int>(surfaceMeasure
->WidthText(vsPrint
.styles
[StyleLineNumber
].font
.get(),
2750 "99999" lineNumberPrintSpace
));
2751 vsPrint
.ms
[lineNumberIndex
].width
= lineNumberWidth
;
2752 vsPrint
.Refresh(*surfaceMeasure
, model
.pdoc
->tabInChars
); // Recalculate fixedColumnWidth
2755 // Turn off change history marker backgrounds
2756 constexpr unsigned int changeMarkers
=
2757 1u << static_cast<unsigned int>(MarkerOutline::HistoryRevertedToOrigin
) |
2758 1u << static_cast<unsigned int>(MarkerOutline::HistorySaved
) |
2759 1u << static_cast<unsigned int>(MarkerOutline::HistoryModified
) |
2760 1u << static_cast<unsigned int>(MarkerOutline::HistoryRevertedToModified
);
2761 vsPrint
.maskInLine
&= ~changeMarkers
;
2763 const Sci::Line linePrintStart
= model
.pdoc
->SciLineFromPosition(chrg
.cpMin
);
2764 Sci::Line linePrintLast
= linePrintStart
+ (rc
.bottom
- rc
.top
) / vsPrint
.lineHeight
- 1;
2765 if (linePrintLast
< linePrintStart
)
2766 linePrintLast
= linePrintStart
;
2767 const Sci::Line linePrintMax
= model
.pdoc
->SciLineFromPosition(chrg
.cpMax
);
2768 if (linePrintLast
> linePrintMax
)
2769 linePrintLast
= linePrintMax
;
2770 //Platform::DebugPrintf("Formatting lines=[%0d,%0d,%0d] top=%0d bottom=%0d line=%0d %0d\n",
2771 // linePrintStart, linePrintLast, linePrintMax, rc.top, rc.bottom, vsPrint.lineHeight,
2772 // surfaceMeasure->Height(vsPrint.styles[StyleLineNumber].font));
2773 Sci::Position endPosPrint
= model
.pdoc
->Length();
2774 if (linePrintLast
< model
.pdoc
->LinesTotal())
2775 endPosPrint
= model
.pdoc
->LineStart(linePrintLast
+ 1);
2777 // Ensure we are styled to where we are formatting.
2778 model
.pdoc
->EnsureStyledTo(endPosPrint
);
2780 const int xStart
= vsPrint
.fixedColumnWidth
+ rc
.left
;
2783 Sci::Line lineDoc
= linePrintStart
;
2785 Sci::Position nPrintPos
= chrg
.cpMin
;
2786 int visibleLine
= 0;
2787 int widthPrint
= rc
.right
- rc
.left
- vsPrint
.fixedColumnWidth
;
2788 if (printParameters
.wrapState
== Wrap::None
)
2789 widthPrint
= LineLayout::wrapWidthInfinite
;
2791 while (lineDoc
<= linePrintLast
&& ypos
< rc
.bottom
) {
2793 // When printing, the hdc and hdcTarget may be the same, so
2794 // changing the state of surfaceMeasure may change the underlying
2795 // state of surface. Therefore, any cached state is discarded before
2796 // using each surface.
2797 surfaceMeasure
->FlushCachedState();
2799 // Copy this line and its styles from the document into local arrays
2800 // and determine the x position at which each character starts.
2801 LineLayout
ll(lineDoc
, static_cast<int>(model
.pdoc
->LineStart(lineDoc
+ 1) - model
.pdoc
->LineStart(lineDoc
) + 1));
2802 LayoutLine(model
, surfaceMeasure
, vsPrint
, &ll
, widthPrint
);
2804 ll
.containsCaret
= false;
2806 PRectangle rcLine
= PRectangle::FromInts(
2810 ypos
+ vsPrint
.lineHeight
);
2812 // When document line is wrapped over multiple display lines, find where
2813 // to start printing from to ensure a particular position is on the first
2814 // line of the page.
2815 if (visibleLine
== 0) {
2816 const Sci::Position startWithinLine
= nPrintPos
-
2817 model
.pdoc
->LineStart(lineDoc
);
2818 for (int iwl
= 0; iwl
< ll
.lines
- 1; iwl
++) {
2819 if (ll
.LineStart(iwl
) <= startWithinLine
&& ll
.LineStart(iwl
+ 1) >= startWithinLine
) {
2824 if (ll
.lines
> 1 && startWithinLine
>= ll
.LineStart(ll
.lines
- 1)) {
2825 visibleLine
= -(ll
.lines
- 1);
2829 if (draw
&& lineNumberWidth
&&
2830 (ypos
+ vsPrint
.lineHeight
<= rc
.bottom
) &&
2831 (visibleLine
>= 0)) {
2832 const std::string number
= std::to_string(lineDoc
+ 1) + lineNumberPrintSpace
;
2833 PRectangle rcNumber
= rcLine
;
2834 rcNumber
.right
= rcNumber
.left
+ lineNumberWidth
;
2836 rcNumber
.left
= rcNumber
.right
- surfaceMeasure
->WidthText(
2837 vsPrint
.styles
[StyleLineNumber
].font
.get(), number
);
2838 surface
->FlushCachedState();
2839 surface
->DrawTextNoClip(rcNumber
, vsPrint
.styles
[StyleLineNumber
].font
.get(),
2840 ypos
+ vsPrint
.maxAscent
, number
,
2841 vsPrint
.styles
[StyleLineNumber
].fore
,
2842 vsPrint
.styles
[StyleLineNumber
].back
);
2846 surface
->FlushCachedState();
2848 for (int iwl
= 0; iwl
< ll
.lines
; iwl
++) {
2849 if (ypos
+ vsPrint
.lineHeight
<= rc
.bottom
) {
2850 if (visibleLine
>= 0) {
2852 rcLine
.top
= static_cast<XYPOSITION
>(ypos
);
2853 rcLine
.bottom
= static_cast<XYPOSITION
>(ypos
+ vsPrint
.lineHeight
);
2854 DrawLine(surface
, model
, vsPrint
, &ll
, lineDoc
, visibleLine
, xStart
, rcLine
, iwl
, DrawPhase::all
);
2856 ypos
+= vsPrint
.lineHeight
;
2859 if (iwl
== ll
.lines
- 1)
2860 nPrintPos
= model
.pdoc
->LineStart(lineDoc
+ 1);
2862 nPrintPos
+= ll
.LineStart(iwl
+ 1) - ll
.LineStart(iwl
);
2869 // Clear cache so measurements are not used for screen