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"
69 using namespace Scintilla
;
70 using namespace Scintilla::Internal
;
72 PrintParameters::PrintParameters() noexcept
{
74 colourMode
= PrintOption::Normal
;
75 wrapState
= Wrap::Word
;
80 int WidthStyledText(Surface
*surface
, const ViewStyle
&vs
, int styleOffset
,
81 const char *text
, const unsigned char *styles
, size_t len
) {
85 const unsigned char style
= styles
[start
];
86 size_t endSegment
= start
;
87 while ((endSegment
+ 1 < len
) && (styles
[endSegment
+ 1] == style
))
89 const Font
*fontText
= vs
.styles
[style
+ styleOffset
].font
.get();
90 const std::string_view
sv(text
+ start
, endSegment
- start
+ 1);
91 width
+= static_cast<int>(surface
->WidthText(fontText
, sv
));
92 start
= endSegment
+ 1;
99 namespace Scintilla::Internal
{
101 bool ValidStyledText(const ViewStyle
&vs
, size_t styleOffset
, const StyledText
&st
) noexcept
{
102 if (st
.multipleStyles
) {
103 for (size_t iStyle
= 0; iStyle
<st
.length
; iStyle
++) {
104 if (!vs
.ValidStyle(styleOffset
+ st
.styles
[iStyle
]))
108 if (!vs
.ValidStyle(styleOffset
+ st
.style
))
114 int WidestLineWidth(Surface
*surface
, const ViewStyle
&vs
, int styleOffset
, const StyledText
&st
) {
117 while (start
< st
.length
) {
118 const size_t lenLine
= st
.LineLength(start
);
120 if (st
.multipleStyles
) {
121 widthSubLine
= WidthStyledText(surface
, vs
, styleOffset
, st
.text
+ start
, st
.styles
+ start
, lenLine
);
123 const Font
*fontText
= vs
.styles
[styleOffset
+ st
.style
].font
.get();
124 const std::string_view
text(st
.text
+ start
, lenLine
);
125 widthSubLine
= static_cast<int>(surface
->WidthText(fontText
, text
));
127 if (widthSubLine
> widthMax
)
128 widthMax
= widthSubLine
;
129 start
+= lenLine
+ 1;
134 void DrawTextNoClipPhase(Surface
*surface
, PRectangle rc
, const Style
&style
, XYPOSITION ybase
,
135 std::string_view text
, DrawPhase phase
) {
136 const Font
*fontText
= style
.font
.get();
137 if (FlagSet(phase
, DrawPhase::back
)) {
138 if (FlagSet(phase
, DrawPhase::text
)) {
140 surface
->DrawTextNoClip(rc
, fontText
, ybase
, text
,
141 style
.fore
, style
.back
);
143 surface
->FillRectangleAligned(rc
, Fill(style
.back
));
145 } else if (FlagSet(phase
, DrawPhase::text
)) {
146 surface
->DrawTextTransparent(rc
, fontText
, ybase
, text
, style
.fore
);
150 void DrawStyledText(Surface
*surface
, const ViewStyle
&vs
, int styleOffset
, PRectangle rcText
,
151 const StyledText
&st
, size_t start
, size_t length
, DrawPhase phase
) {
153 if (st
.multipleStyles
) {
154 int x
= static_cast<int>(rcText
.left
);
158 size_t style
= st
.styles
[i
+ start
];
159 while (end
< length
- 1 && st
.styles
[start
+ end
+ 1] == style
)
161 style
+= styleOffset
;
162 const Font
*fontText
= vs
.styles
[style
].font
.get();
163 const std::string_view
text(st
.text
+ start
+ i
, end
- i
+ 1);
164 const int width
= static_cast<int>(surface
->WidthText(fontText
, text
));
165 PRectangle rcSegment
= rcText
;
166 rcSegment
.left
= static_cast<XYPOSITION
>(x
);
167 rcSegment
.right
= static_cast<XYPOSITION
>(x
+ width
+ 1);
168 DrawTextNoClipPhase(surface
, rcSegment
, vs
.styles
[style
],
169 rcText
.top
+ vs
.maxAscent
, text
, phase
);
174 const size_t style
= st
.style
+ styleOffset
;
175 DrawTextNoClipPhase(surface
, rcText
, vs
.styles
[style
],
176 rcText
.top
+ vs
.maxAscent
,
177 std::string_view(st
.text
+ start
, length
), phase
);
183 EditView::EditView() {
184 tabWidthMinimumPixels
= 2; // needed for calculating tab stops for fractional proportional fonts
185 drawOverstrikeCaret
= true;
187 phasesDraw
= PhasesDraw::Two
;
188 lineWidthMaxSeen
= 0;
189 additionalCaretsBlink
= true;
190 additionalCaretsVisible
= true;
191 imeCaretBlockOverride
= false;
192 llc
.SetLevel(LineCache::Caret
);
193 posCache
= CreatePositionCache();
194 posCache
->SetSize(0x400);
195 maxLayoutThreads
= 1;
197 customDrawTabArrow
= nullptr;
198 customDrawWrapMarker
= nullptr;
201 EditView::~EditView() = default;
203 bool EditView::SetTwoPhaseDraw(bool twoPhaseDraw
) noexcept
{
204 const PhasesDraw phasesDrawNew
= twoPhaseDraw
? PhasesDraw::Two
: PhasesDraw::One
;
205 const bool redraw
= phasesDraw
!= phasesDrawNew
;
206 phasesDraw
= phasesDrawNew
;
210 bool EditView::SetPhasesDraw(int phases
) noexcept
{
211 const PhasesDraw phasesDrawNew
= static_cast<PhasesDraw
>(phases
);
212 const bool redraw
= phasesDraw
!= phasesDrawNew
;
213 phasesDraw
= phasesDrawNew
;
217 bool EditView::LinesOverlap() const noexcept
{
218 return phasesDraw
== PhasesDraw::Multiple
;
221 void EditView::SetLayoutThreads(unsigned int threads
) noexcept
{
222 maxLayoutThreads
= std::clamp(threads
, 1U, std::thread::hardware_concurrency());
225 unsigned int EditView::GetLayoutThreads() const noexcept
{
226 return maxLayoutThreads
;
229 void EditView::ClearAllTabstops() noexcept
{
233 XYPOSITION
EditView::NextTabstopPos(Sci::Line line
, XYPOSITION x
, XYPOSITION tabWidth
) const noexcept
{
234 const int next
= GetNextTabstop(line
, static_cast<int>(x
+ tabWidthMinimumPixels
));
236 return static_cast<XYPOSITION
>(next
);
237 return (static_cast<int>((x
+ tabWidthMinimumPixels
) / tabWidth
) + 1) * tabWidth
;
240 bool EditView::ClearTabstops(Sci::Line line
) noexcept
{
241 return ldTabstops
&& ldTabstops
->ClearTabstops(line
);
244 bool EditView::AddTabstop(Sci::Line line
, int x
) {
246 ldTabstops
= std::make_unique
<LineTabstops
>();
248 return ldTabstops
&& ldTabstops
->AddTabstop(line
, x
);
251 int EditView::GetNextTabstop(Sci::Line line
, int x
) const noexcept
{
253 return ldTabstops
->GetNextTabstop(line
, x
);
259 void EditView::LinesAddedOrRemoved(Sci::Line lineOfPos
, Sci::Line linesAdded
) {
261 if (linesAdded
> 0) {
262 for (Sci::Line line
= lineOfPos
; line
< lineOfPos
+ linesAdded
; line
++) {
263 ldTabstops
->InsertLine(line
);
266 for (Sci::Line line
= (lineOfPos
+ -linesAdded
) - 1; line
>= lineOfPos
; line
--) {
267 ldTabstops
->RemoveLine(line
);
273 void EditView::DropGraphics() noexcept
{
275 pixmapIndentGuide
.reset();
276 pixmapIndentGuideHighlight
.reset();
279 void EditView::RefreshPixMaps(Surface
*surfaceWindow
, const ViewStyle
&vsDraw
) {
280 if (!pixmapIndentGuide
) {
281 // 1 extra pixel in height so can handle odd/even positions and so produce a continuous line
282 pixmapIndentGuide
= surfaceWindow
->AllocatePixMap(1, vsDraw
.lineHeight
+ 1);
283 pixmapIndentGuideHighlight
= surfaceWindow
->AllocatePixMap(1, vsDraw
.lineHeight
+ 1);
284 const PRectangle rcIG
= PRectangle::FromInts(0, 0, 1, vsDraw
.lineHeight
);
285 pixmapIndentGuide
->FillRectangle(rcIG
, vsDraw
.styles
[StyleIndentGuide
].back
);
286 pixmapIndentGuideHighlight
->FillRectangle(rcIG
, vsDraw
.styles
[StyleBraceLight
].back
);
287 for (int stripe
= 1; stripe
< vsDraw
.lineHeight
+ 1; stripe
+= 2) {
288 const PRectangle rcPixel
= PRectangle::FromInts(0, stripe
, 1, stripe
+ 1);
289 pixmapIndentGuide
->FillRectangle(rcPixel
, vsDraw
.styles
[StyleIndentGuide
].fore
);
290 pixmapIndentGuideHighlight
->FillRectangle(rcPixel
, vsDraw
.styles
[StyleBraceLight
].fore
);
292 pixmapIndentGuide
->FlushDrawing();
293 pixmapIndentGuideHighlight
->FlushDrawing();
297 std::shared_ptr
<LineLayout
> EditView::RetrieveLineLayout(Sci::Line lineNumber
, const EditModel
&model
) {
298 const Sci::Position posLineStart
= model
.pdoc
->LineStart(lineNumber
);
299 const Sci::Position posLineEnd
= model
.pdoc
->LineStart(lineNumber
+ 1);
300 PLATFORM_ASSERT(posLineEnd
>= posLineStart
);
301 const Sci::Line lineCaret
= model
.pdoc
->SciLineFromPosition(model
.sel
.MainCaret());
302 return llc
.Retrieve(lineNumber
, lineCaret
,
303 static_cast<int>(posLineEnd
- posLineStart
), model
.pdoc
->GetStyleClock(),
304 model
.LinesOnScreen() + 1, model
.pdoc
->LinesTotal());
309 constexpr XYPOSITION epsilon
= 0.0001f
; // A small nudge to avoid floating point precision issues
312 * Return the chDoc argument with case transformed as indicated by the caseForce argument.
313 * chPrevious is needed for camel casing.
314 * This only affects ASCII characters and is provided for languages with case-insensitive
315 * ASCII keywords where the user wishes to view keywords in a preferred case.
317 inline char CaseForce(Style::CaseForce caseForce
, char chDoc
, char chPrevious
) noexcept
{
319 case Style::CaseForce::mixed
:
321 case Style::CaseForce::lower
:
322 return MakeLowerCase(chDoc
);
323 case Style::CaseForce::upper
:
324 return MakeUpperCase(chDoc
);
325 case Style::CaseForce::camel
:
326 default: // default should not occur, included to avoid warnings
327 if (IsUpperOrLowerCase(chDoc
) && !IsUpperOrLowerCase(chPrevious
)) {
328 return MakeUpperCase(chDoc
);
330 return MakeLowerCase(chDoc
);
335 void LayoutSegments(IPositionCache
*pCache
,
337 const ViewStyle
&vstyle
,
339 const std::vector
<TextSegment
> &segments
,
340 std::atomic
<uint32_t> &nextIndex
,
341 const bool textUnicode
,
342 const bool multiThreaded
) {
344 const uint32_t i
= nextIndex
.fetch_add(1, std::memory_order_acq_rel
);
345 if (i
>= segments
.size()) {
348 const TextSegment
&ts
= segments
[i
];
349 const unsigned int styleSegment
= ll
->styles
[ts
.start
];
350 XYPOSITION
*positions
= &ll
->positions
[ts
.start
+ 1];
351 if (vstyle
.styles
[styleSegment
].visible
) {
352 if (ts
.representation
) {
353 XYPOSITION representationWidth
= 0.0;
354 // Tab is a special case of representation, taking a variable amount of space
355 // which will be filled in later.
356 if (ll
->chars
[ts
.start
] != '\t') {
357 representationWidth
= vstyle
.controlCharWidth
;
358 if (representationWidth
<= 0.0) {
359 assert(ts
.representation
->stringRep
.length() <= Representation::maxLength
);
360 XYPOSITION positionsRepr
[Representation::maxLength
+ 1];
361 // ts.representation->stringRep is UTF-8.
362 pCache
->MeasureWidths(surface
, vstyle
, StyleControlChar
, true, ts
.representation
->stringRep
,
363 positionsRepr
, multiThreaded
);
364 representationWidth
= positionsRepr
[ts
.representation
->stringRep
.length() - 1];
365 if (FlagSet(ts
.representation
->appearance
, RepresentationAppearance::Blob
)) {
366 representationWidth
+= vstyle
.ctrlCharPadding
;
370 std::fill(positions
, positions
+ ts
.length
, representationWidth
);
372 if ((ts
.length
== 1) && (' ' == ll
->chars
[ts
.start
])) {
373 // Over half the segments are single characters and of these about half are space characters.
374 positions
[0] = vstyle
.styles
[styleSegment
].spaceWidth
;
376 pCache
->MeasureWidths(surface
, vstyle
, styleSegment
, textUnicode
,
377 std::string_view(&ll
->chars
[ts
.start
], ts
.length
), positions
, multiThreaded
);
380 } else if (vstyle
.styles
[styleSegment
].invisibleRepresentation
[0]) {
381 const std::string_view text
= vstyle
.styles
[styleSegment
].invisibleRepresentation
;
382 XYPOSITION positionsRepr
[Representation::maxLength
+ 1];
383 // invisibleRepresentation is UTF-8.
384 pCache
->MeasureWidths(surface
, vstyle
, styleSegment
, true, text
, positionsRepr
, multiThreaded
);
385 const XYPOSITION representationWidth
= positionsRepr
[text
.length() - 1];
386 std::fill(positions
, positions
+ ts
.length
, representationWidth
);
394 * Fill in the LineLayout data for the given line.
395 * Copy the given @a line and its styles from the document into local arrays.
396 * Also determine the x position at which each character starts.
398 void EditView::LayoutLine(const EditModel
&model
, Surface
*surface
, const ViewStyle
&vstyle
, LineLayout
*ll
, int width
, bool callerMultiThreaded
) {
401 const Sci::Line line
= ll
->LineNumber();
402 PLATFORM_ASSERT(line
< model
.pdoc
->LinesTotal());
403 PLATFORM_ASSERT(ll
->chars
);
404 const Sci::Position posLineStart
= model
.pdoc
->LineStart(line
);
405 Sci::Position posLineEnd
= model
.pdoc
->LineStart(line
+ 1);
406 // If the line is very long, limit the treatment to a length that should fit in the viewport
407 if (posLineEnd
>(posLineStart
+ ll
->maxLineLength
)) {
408 posLineEnd
= posLineStart
+ ll
->maxLineLength
;
410 // Hard to cope when too narrow, so just assume there is space
411 width
= std::max(width
, 20);
413 if (ll
->validity
== LineLayout::ValidLevel::checkTextAndStyle
) {
414 Sci::Position lineLength
= posLineEnd
- posLineStart
;
415 if (!vstyle
.viewEOL
) {
416 lineLength
= model
.pdoc
->LineEnd(line
) - posLineStart
;
418 if (lineLength
== ll
->numCharsInLine
) {
419 // See if chars, styles, indicators, are all the same
421 // Check base line layout
423 for (Sci::Position numCharsInLine
= 0; numCharsInLine
< lineLength
; numCharsInLine
++) {
424 const Sci::Position charInDoc
= numCharsInLine
+ posLineStart
;
425 const char chDoc
= model
.pdoc
->CharAt(charInDoc
);
426 const int styleByte
= model
.pdoc
->StyleIndexAt(charInDoc
);
428 (ll
->styles
[numCharsInLine
] == styleByte
);
430 (ll
->chars
[numCharsInLine
] == CaseForce(vstyle
.styles
[styleByte
].caseForce
, chDoc
, chPrevious
));
433 const int styleByteLast
= (posLineEnd
> posLineStart
) ? model
.pdoc
->StyleIndexAt(posLineEnd
- 1) : 0;
434 allSame
= allSame
&& (ll
->styles
[lineLength
] == styleByteLast
); // For eolFilled
436 ll
->validity
= (ll
->widthLine
!= width
) ? LineLayout::ValidLevel::positions
: LineLayout::ValidLevel::lines
;
438 ll
->validity
= LineLayout::ValidLevel::invalid
;
441 ll
->validity
= LineLayout::ValidLevel::invalid
;
444 if (ll
->validity
== LineLayout::ValidLevel::invalid
) {
445 ll
->widthLine
= LineLayout::wrapWidthInfinite
;
447 if (vstyle
.edgeState
== EdgeVisualStyle::Background
) {
448 Sci::Position edgePosition
= model
.pdoc
->FindColumn(line
, vstyle
.theEdge
.column
);
449 if (edgePosition
>= posLineStart
) {
450 edgePosition
-= posLineStart
;
452 ll
->edgeColumn
= static_cast<int>(edgePosition
);
457 // Fill base line layout
458 const int lineLength
= static_cast<int>(posLineEnd
- posLineStart
);
459 model
.pdoc
->GetCharRange(ll
->chars
.get(), posLineStart
, lineLength
);
460 model
.pdoc
->GetStyleRange(ll
->styles
.get(), posLineStart
, lineLength
);
461 const int numCharsBeforeEOL
= static_cast<int>(model
.pdoc
->LineEnd(line
) - posLineStart
);
462 const int numCharsInLine
= (vstyle
.viewEOL
) ? lineLength
: numCharsBeforeEOL
;
463 const unsigned char styleByteLast
= (lineLength
> 0) ? ll
->styles
[lineLength
- 1] : 0;
464 if (vstyle
.someStylesForceCase
) {
466 for (int charInLine
= 0; charInLine
<lineLength
; charInLine
++) {
467 const char chDoc
= ll
->chars
[charInLine
];
468 ll
->chars
[charInLine
] = CaseForce(vstyle
.styles
[ll
->styles
[charInLine
]].caseForce
, chDoc
, chPrevious
);
472 ll
->xHighlightGuide
= 0;
473 // Extra element at the end of the line to hold end x position and act as
474 ll
->chars
[numCharsInLine
] = 0; // Also triggers processing in the loops as this is a control character
475 ll
->styles
[numCharsInLine
] = styleByteLast
; // For eolFilled
477 // Layout the line, determining the position of each character,
478 // with an extra element at the end for the end of the line.
479 ll
->positions
[0] = 0;
480 bool lastSegItalics
= false;
482 std::vector
<TextSegment
> segments
;
483 BreakFinder
bfLayout(ll
, nullptr, Range(0, numCharsInLine
), posLineStart
, 0, BreakFinder::BreakFor::Text
, model
.pdoc
, model
.reprs
.get(), nullptr);
484 while (bfLayout
.More()) {
485 segments
.push_back(bfLayout
.Next());
488 ll
->ClearPositions();
490 if (!segments
.empty()) {
492 const size_t threadsForLength
= std::max(1, numCharsInLine
/ bytesPerLayoutThread
);
493 size_t threads
= std::min
<size_t>({ segments
.size(), threadsForLength
, maxLayoutThreads
});
494 if (!surface
->SupportsFeature(Supports::ThreadSafeMeasureWidths
) || callerMultiThreaded
) {
498 std::atomic
<uint32_t> nextIndex
= 0;
500 const bool textUnicode
= CpUtf8
== model
.pdoc
->dbcsCodePage
;
501 const bool multiThreaded
= threads
> 1;
502 const bool multiThreadedContext
= multiThreaded
|| callerMultiThreaded
;
503 IPositionCache
*pCache
= posCache
.get();
505 // If only 1 thread needed then use the main thread, else spin up multiple
506 const std::launch policy
= (multiThreaded
) ? std::launch::async
: std::launch::deferred
;
508 std::vector
<std::future
<void>> futures
;
509 for (size_t th
= 0; th
< threads
; th
++) {
510 // Find relative positions of everything except for tabs
511 std::future
<void> fut
= std::async(policy
,
512 [pCache
, surface
, &vstyle
, &ll
, &segments
, &nextIndex
, textUnicode
, multiThreadedContext
]() {
513 LayoutSegments(pCache
, surface
, vstyle
, ll
, segments
, nextIndex
, textUnicode
, multiThreadedContext
);
515 futures
.push_back(std::move(fut
));
517 for (const std::future
<void> &f
: futures
) {
522 // Accumulate absolute positions from relative positions within segments and expand tabs
523 XYPOSITION xPosition
= 0.0;
525 ll
->positions
[iByte
++] = xPosition
;
526 for (const TextSegment
&ts
: segments
) {
527 if (vstyle
.styles
[ll
->styles
[ts
.start
]].visible
&&
529 (ll
->chars
[ts
.start
] == '\t')) {
530 // Simple visible tab, go to next tab stop
531 const XYPOSITION startTab
= ll
->positions
[ts
.start
];
532 const XYPOSITION nextTab
= NextTabstopPos(line
, startTab
, vstyle
.tabWidth
);
533 xPosition
+= nextTab
- startTab
;
535 const XYPOSITION xBeginSegment
= xPosition
;
536 for (int i
= 0; i
< ts
.length
; i
++) {
537 xPosition
= ll
->positions
[iByte
] + xBeginSegment
;
538 ll
->positions
[iByte
++] = xPosition
;
542 if (!segments
.empty()) {
543 // Not quite the same as before which would effectively ignore trailing invisible segments
544 const TextSegment
&ts
= segments
.back();
545 lastSegItalics
= (!ts
.representation
) && ((ll
->chars
[ts
.end() - 1] != ' ') && vstyle
.styles
[ll
->styles
[ts
.start
]].italic
);
548 // Small hack to make lines that end with italics not cut off the edge of the last character
549 if (lastSegItalics
) {
550 ll
->positions
[numCharsInLine
] += vstyle
.lastSegItalicsOffset
;
552 ll
->numCharsInLine
= numCharsInLine
;
553 ll
->numCharsBeforeEOL
= numCharsBeforeEOL
;
554 ll
->validity
= LineLayout::ValidLevel::positions
;
556 if ((ll
->validity
== LineLayout::ValidLevel::positions
) || (ll
->widthLine
!= width
)) {
557 ll
->widthLine
= width
;
558 if (width
== LineLayout::wrapWidthInfinite
) {
560 } else if (width
> ll
->positions
[ll
->numCharsInLine
]) {
561 // Simple common case where line does not need wrapping.
564 if (FlagSet(vstyle
.wrap
.visualFlags
, WrapVisualFlag::End
)) {
565 width
-= static_cast<int>(vstyle
.aveCharWidth
); // take into account the space for end wrap mark
567 XYPOSITION wrapAddIndent
= 0; // This will be added to initial indent of line
568 switch (vstyle
.wrap
.indentMode
) {
569 case WrapIndentMode::Fixed
:
570 wrapAddIndent
= vstyle
.wrap
.visualStartIndent
* vstyle
.aveCharWidth
;
572 case WrapIndentMode::Indent
:
573 wrapAddIndent
= model
.pdoc
->IndentSize() * vstyle
.spaceWidth
;
575 case WrapIndentMode::DeepIndent
:
576 wrapAddIndent
= model
.pdoc
->IndentSize() * 2 * vstyle
.spaceWidth
;
578 default: // No additional indent for WrapIndentMode::Fixed
581 ll
->wrapIndent
= wrapAddIndent
;
582 if (vstyle
.wrap
.indentMode
!= WrapIndentMode::Fixed
) {
583 for (int i
= 0; i
< ll
->numCharsInLine
; i
++) {
584 if (!IsSpaceOrTab(ll
->chars
[i
])) {
585 ll
->wrapIndent
+= ll
->positions
[i
]; // Add line indent
590 // Check for text width minimum
591 if (ll
->wrapIndent
> width
- static_cast<int>(vstyle
.aveCharWidth
) * 15)
592 ll
->wrapIndent
= wrapAddIndent
;
593 // Check for wrapIndent minimum
594 if ((FlagSet(vstyle
.wrap
.visualFlags
, WrapVisualFlag::Start
)) && (ll
->wrapIndent
< vstyle
.aveCharWidth
))
595 ll
->wrapIndent
= vstyle
.aveCharWidth
; // Indent to show start visual
596 ll
->WrapLine(model
.pdoc
, posLineStart
, vstyle
.wrap
.state
, width
);
598 ll
->validity
= LineLayout::ValidLevel::lines
;
602 // Fill the LineLayout bidirectional data fields according to each char style
604 void EditView::UpdateBidiData(const EditModel
&model
, const ViewStyle
&vstyle
, LineLayout
*ll
) {
605 if (model
.BidirectionalEnabled()) {
606 ll
->EnsureBidiData();
607 for (int stylesInLine
= 0; stylesInLine
< ll
->numCharsInLine
; stylesInLine
++) {
608 ll
->bidiData
->stylesFonts
[stylesInLine
] = vstyle
.styles
[ll
->styles
[stylesInLine
]].font
;
610 ll
->bidiData
->stylesFonts
[ll
->numCharsInLine
].reset();
612 for (int charsInLine
= 0; charsInLine
< ll
->numCharsInLine
; charsInLine
++) {
613 const int charWidth
= UTF8DrawBytes(&ll
->chars
[charsInLine
], ll
->numCharsInLine
- charsInLine
);
614 const Representation
*repr
= model
.reprs
->RepresentationFromCharacter(std::string_view(&ll
->chars
[charsInLine
], charWidth
));
616 ll
->bidiData
->widthReprs
[charsInLine
] = 0.0f
;
617 if (repr
&& ll
->chars
[charsInLine
] != '\t') {
618 ll
->bidiData
->widthReprs
[charsInLine
] = ll
->positions
[charsInLine
+ charWidth
] - ll
->positions
[charsInLine
];
621 for (int c
= 1; c
< charWidth
; c
++) {
623 ll
->bidiData
->widthReprs
[charsInLine
] = 0.0f
;
627 ll
->bidiData
->widthReprs
[ll
->numCharsInLine
] = 0.0f
;
629 ll
->bidiData
.reset();
633 Point
EditView::LocationFromPosition(Surface
*surface
, const EditModel
&model
, SelectionPosition pos
, Sci::Line topLine
,
634 const ViewStyle
&vs
, PointEnd pe
, const PRectangle rcClient
) {
636 if (pos
.Position() == Sci::invalidPosition
)
638 Sci::Line lineDoc
= model
.pdoc
->SciLineFromPosition(pos
.Position());
639 Sci::Position posLineStart
= model
.pdoc
->LineStart(lineDoc
);
640 if (FlagSet(pe
, PointEnd::lineEnd
) && (lineDoc
> 0) && (pos
.Position() == posLineStart
)) {
641 // Want point at end of first line
643 posLineStart
= model
.pdoc
->LineStart(lineDoc
);
645 const Sci::Line lineVisible
= model
.pcs
->DisplayFromDoc(lineDoc
);
646 std::shared_ptr
<LineLayout
> ll
= RetrieveLineLayout(lineDoc
, model
);
648 LayoutLine(model
, surface
, vs
, ll
.get(), model
.wrapWidth
);
649 const int posInLine
= static_cast<int>(pos
.Position() - posLineStart
);
650 pt
= ll
->PointFromPosition(posInLine
, vs
.lineHeight
, pe
);
651 pt
.x
+= vs
.textStart
- model
.xOffset
;
653 if (model
.BidirectionalEnabled()) {
654 // Fill the line bidi data
655 UpdateBidiData(model
, vs
, ll
.get());
658 const int subLine
= ll
->SubLineFromPosition(posInLine
, pe
);
659 const int caretPosition
= posInLine
- ll
->LineStart(subLine
);
661 // Get the point from current position
662 const ScreenLine
screenLine(ll
.get(), subLine
, vs
, rcClient
.right
, tabWidthMinimumPixels
);
663 std::unique_ptr
<IScreenLineLayout
> slLayout
= surface
->Layout(&screenLine
);
664 pt
.x
= slLayout
->XFromPosition(caretPosition
);
666 pt
.x
+= vs
.textStart
- model
.xOffset
;
669 if (posInLine
>= ll
->LineStart(subLine
)) {
670 pt
.y
= static_cast<XYPOSITION
>(subLine
*vs
.lineHeight
);
673 pt
.y
+= (lineVisible
- topLine
) * vs
.lineHeight
;
674 pt
.x
+= pos
.VirtualSpace() * vs
.styles
[ll
->EndLineStyle()].spaceWidth
;
679 Range
EditView::RangeDisplayLine(Surface
*surface
, const EditModel
&model
, Sci::Line lineVisible
, const ViewStyle
&vs
) {
680 Range rangeSubLine
= Range(0, 0);
681 if (lineVisible
< 0) {
684 const Sci::Line lineDoc
= model
.pcs
->DocFromDisplay(lineVisible
);
685 const Sci::Position positionLineStart
= model
.pdoc
->LineStart(lineDoc
);
686 std::shared_ptr
<LineLayout
> ll
= RetrieveLineLayout(lineDoc
, model
);
688 LayoutLine(model
, surface
, vs
, ll
.get(), model
.wrapWidth
);
689 const Sci::Line lineStartSet
= model
.pcs
->DisplayFromDoc(lineDoc
);
690 const int subLine
= static_cast<int>(lineVisible
- lineStartSet
);
691 if (subLine
< ll
->lines
) {
692 rangeSubLine
= ll
->SubLineRange(subLine
, LineLayout::Scope::visibleOnly
);
693 if (subLine
== ll
->lines
-1) {
694 rangeSubLine
.end
= model
.pdoc
->LineStart(lineDoc
+ 1) -
699 rangeSubLine
.start
+= positionLineStart
;
700 rangeSubLine
.end
+= positionLineStart
;
704 SelectionPosition
EditView::SPositionFromLocation(Surface
*surface
, const EditModel
&model
, PointDocument pt
, bool canReturnInvalid
,
705 bool charPosition
, bool virtualSpace
, const ViewStyle
&vs
, const PRectangle rcClient
) {
706 pt
.x
= pt
.x
- vs
.textStart
;
707 Sci::Line visibleLine
= static_cast<int>(std::floor(pt
.y
/ vs
.lineHeight
));
708 if (!canReturnInvalid
&& (visibleLine
< 0))
710 const Sci::Line lineDoc
= model
.pcs
->DocFromDisplay(visibleLine
);
711 if (canReturnInvalid
&& (lineDoc
< 0))
712 return SelectionPosition(Sci::invalidPosition
);
713 if (lineDoc
>= model
.pdoc
->LinesTotal())
714 return SelectionPosition(canReturnInvalid
? Sci::invalidPosition
:
715 model
.pdoc
->Length());
716 const Sci::Position posLineStart
= model
.pdoc
->LineStart(lineDoc
);
717 std::shared_ptr
<LineLayout
> ll
= RetrieveLineLayout(lineDoc
, model
);
719 LayoutLine(model
, surface
, vs
, ll
.get(), model
.wrapWidth
);
720 const Sci::Line lineStartSet
= model
.pcs
->DisplayFromDoc(lineDoc
);
721 const int subLine
= static_cast<int>(visibleLine
- lineStartSet
);
722 if (subLine
< ll
->lines
) {
723 const Range rangeSubLine
= ll
->SubLineRange(subLine
, LineLayout::Scope::visibleOnly
);
724 const XYPOSITION subLineStart
= ll
->positions
[rangeSubLine
.start
];
725 if (subLine
> 0) // Wrapped
726 pt
.x
-= ll
->wrapIndent
;
727 Sci::Position positionInLine
= 0;
728 if (model
.BidirectionalEnabled()) {
729 // Fill the line bidi data
730 UpdateBidiData(model
, vs
, ll
.get());
732 const ScreenLine
screenLine(ll
.get(), subLine
, vs
, rcClient
.right
, tabWidthMinimumPixels
);
733 std::unique_ptr
<IScreenLineLayout
> slLayout
= surface
->Layout(&screenLine
);
734 positionInLine
= slLayout
->PositionFromX(pt
.x
, charPosition
) +
737 positionInLine
= ll
->FindPositionFromX(pt
.x
+ subLineStart
,
738 rangeSubLine
, charPosition
);
740 if (positionInLine
< rangeSubLine
.end
) {
741 return SelectionPosition(model
.pdoc
->MovePositionOutsideChar(positionInLine
+ posLineStart
, 1));
744 const XYPOSITION spaceWidth
= vs
.styles
[ll
->EndLineStyle()].spaceWidth
;
745 const int spaceOffset
= static_cast<int>(
746 (pt
.x
+ subLineStart
- ll
->positions
[rangeSubLine
.end
] + spaceWidth
/ 2) / spaceWidth
);
747 return SelectionPosition(rangeSubLine
.end
+ posLineStart
, spaceOffset
);
748 } else if (canReturnInvalid
) {
749 if (pt
.x
< (ll
->positions
[rangeSubLine
.end
] - subLineStart
)) {
750 return SelectionPosition(model
.pdoc
->MovePositionOutsideChar(rangeSubLine
.end
+ posLineStart
, 1));
753 return SelectionPosition(rangeSubLine
.end
+ posLineStart
);
756 if (!canReturnInvalid
)
757 return SelectionPosition(ll
->numCharsInLine
+ posLineStart
);
759 return SelectionPosition(canReturnInvalid
? Sci::invalidPosition
: posLineStart
);
763 * Find the document position corresponding to an x coordinate on a particular document line.
764 * Ensure is between whole characters when document is in multi-byte or UTF-8 mode.
765 * This method is used for rectangular selections and does not work on wrapped lines.
767 SelectionPosition
EditView::SPositionFromLineX(Surface
*surface
, const EditModel
&model
, Sci::Line lineDoc
, int x
, const ViewStyle
&vs
) {
768 std::shared_ptr
<LineLayout
> ll
= RetrieveLineLayout(lineDoc
, model
);
770 const Sci::Position posLineStart
= model
.pdoc
->LineStart(lineDoc
);
771 LayoutLine(model
, surface
, vs
, ll
.get(), model
.wrapWidth
);
772 const Range rangeSubLine
= ll
->SubLineRange(0, LineLayout::Scope::visibleOnly
);
773 const XYPOSITION subLineStart
= ll
->positions
[rangeSubLine
.start
];
774 const Sci::Position positionInLine
= ll
->FindPositionFromX(x
+ subLineStart
, rangeSubLine
, false);
775 if (positionInLine
< rangeSubLine
.end
) {
776 return SelectionPosition(model
.pdoc
->MovePositionOutsideChar(positionInLine
+ posLineStart
, 1));
778 const XYPOSITION spaceWidth
= vs
.styles
[ll
->EndLineStyle()].spaceWidth
;
779 const int spaceOffset
= static_cast<int>(
780 (x
+ subLineStart
- ll
->positions
[rangeSubLine
.end
] + spaceWidth
/ 2) / spaceWidth
);
781 return SelectionPosition(rangeSubLine
.end
+ posLineStart
, spaceOffset
);
783 return SelectionPosition(0);
786 Sci::Line
EditView::DisplayFromPosition(Surface
*surface
, const EditModel
&model
, Sci::Position pos
, const ViewStyle
&vs
) {
787 const Sci::Line lineDoc
= model
.pdoc
->SciLineFromPosition(pos
);
788 Sci::Line lineDisplay
= model
.pcs
->DisplayFromDoc(lineDoc
);
789 std::shared_ptr
<LineLayout
> ll
= RetrieveLineLayout(lineDoc
, model
);
791 LayoutLine(model
, surface
, vs
, ll
.get(), model
.wrapWidth
);
792 const Sci::Position posLineStart
= model
.pdoc
->LineStart(lineDoc
);
793 const Sci::Position posInLine
= pos
- posLineStart
;
794 lineDisplay
--; // To make up for first increment ahead.
795 for (int subLine
= 0; subLine
< ll
->lines
; subLine
++) {
796 if (posInLine
>= ll
->LineStart(subLine
)) {
804 Sci::Position
EditView::StartEndDisplayLine(Surface
*surface
, const EditModel
&model
, Sci::Position pos
, bool start
, const ViewStyle
&vs
) {
805 const Sci::Line line
= model
.pdoc
->SciLineFromPosition(pos
);
806 std::shared_ptr
<LineLayout
> ll
= RetrieveLineLayout(line
, model
);
807 Sci::Position posRet
= Sci::invalidPosition
;
809 const Sci::Position posLineStart
= model
.pdoc
->LineStart(line
);
810 LayoutLine(model
, surface
, vs
, ll
.get(), model
.wrapWidth
);
811 const Sci::Position posInLine
= pos
- posLineStart
;
812 if (posInLine
<= ll
->maxLineLength
) {
813 for (int subLine
= 0; subLine
< ll
->lines
; subLine
++) {
814 if ((posInLine
>= ll
->LineStart(subLine
)) &&
815 (posInLine
<= ll
->LineStart(subLine
+ 1)) &&
816 (posInLine
<= ll
->numCharsBeforeEOL
)) {
818 posRet
= ll
->LineStart(subLine
) + posLineStart
;
820 if (subLine
== ll
->lines
- 1)
821 posRet
= ll
->numCharsBeforeEOL
+ posLineStart
;
823 posRet
= model
.pdoc
->MovePositionOutsideChar(ll
->LineStart(subLine
+ 1) + posLineStart
- 1, -1, false);
834 constexpr ColourRGBA
colourBug(0xff, 0, 0xfe, 0xf0);
836 // Selection background colours are always defined, the value_or is to show if bug
838 ColourRGBA
SelectionBackground(const EditModel
&model
, const ViewStyle
&vsDraw
, InSelection inSelection
) {
839 if (inSelection
== InSelection::inNone
)
840 return colourBug
; // Not selected is a bug
842 Element element
= Element::SelectionBack
;
843 if (inSelection
== InSelection::inAdditional
)
844 element
= Element::SelectionAdditionalBack
;
845 if (!model
.primarySelection
)
846 element
= Element::SelectionSecondaryBack
;
847 if (!model
.hasFocus
) {
848 if (inSelection
== InSelection::inAdditional
) {
849 if (ColourOptional colour
= vsDraw
.ElementColour(Element::SelectionInactiveAdditionalBack
)) {
853 if (ColourOptional colour
= vsDraw
.ElementColour(Element::SelectionInactiveBack
)) {
857 return vsDraw
.ElementColour(element
).value_or(colourBug
);
860 ColourOptional
SelectionForeground(const EditModel
&model
, const ViewStyle
&vsDraw
, InSelection inSelection
) {
861 if (inSelection
== InSelection::inNone
)
863 Element element
= Element::SelectionText
;
864 if (inSelection
== InSelection::inAdditional
)
865 element
= Element::SelectionAdditionalText
;
866 if (!model
.primarySelection
) // Secondary selection
867 element
= Element::SelectionSecondaryText
;
868 if (!model
.hasFocus
) {
869 if (inSelection
== InSelection::inAdditional
) {
870 if (ColourOptional colour
= vsDraw
.ElementColour(Element::SelectionInactiveAdditionalText
)) {
874 element
= Element::SelectionInactiveText
;
876 return vsDraw
.ElementColour(element
);
879 ColourRGBA
TextBackground(const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
880 ColourOptional background
, InSelection inSelection
, bool inHotspot
, int styleMain
, Sci::Position i
) {
881 if (inSelection
&& (vsDraw
.selection
.layer
== Layer::Base
)) {
882 return SelectionBackground(model
, vsDraw
, inSelection
).Opaque();
884 if ((vsDraw
.edgeState
== EdgeVisualStyle::Background
) &&
885 (i
>= ll
->edgeColumn
) &&
886 (i
< ll
->numCharsBeforeEOL
))
887 return vsDraw
.theEdge
.colour
;
889 if (const ColourOptional colourHotSpotBack
= vsDraw
.ElementColour(Element::HotSpotActiveBack
)) {
890 return colourHotSpotBack
->Opaque();
893 if (background
&& (styleMain
!= StyleBraceLight
) && (styleMain
!= StyleBraceBad
)) {
896 return vsDraw
.styles
[styleMain
].back
;
900 void DrawTextBlob(Surface
*surface
, const ViewStyle
&vsDraw
, PRectangle rcSegment
,
901 std::string_view text
, ColourRGBA textBack
, ColourRGBA textFore
, bool fillBackground
) {
902 if (rcSegment
.Empty())
904 if (fillBackground
) {
905 surface
->FillRectangleAligned(rcSegment
, Fill(textBack
));
907 const Font
*ctrlCharsFont
= vsDraw
.styles
[StyleControlChar
].font
.get();
908 const int normalCharHeight
= static_cast<int>(std::ceil(vsDraw
.styles
[StyleControlChar
].capitalHeight
));
909 PRectangle rcCChar
= rcSegment
;
910 rcCChar
.left
= rcCChar
.left
+ 1;
911 rcCChar
.top
= rcSegment
.top
+ vsDraw
.maxAscent
- normalCharHeight
;
912 rcCChar
.bottom
= rcSegment
.top
+ vsDraw
.maxAscent
+ 1;
913 PRectangle rcCentral
= rcCChar
;
916 surface
->FillRectangleAligned(rcCentral
, Fill(textFore
));
917 PRectangle rcChar
= rcCChar
;
920 surface
->DrawTextClippedUTF8(rcChar
, ctrlCharsFont
,
921 rcSegment
.top
+ vsDraw
.maxAscent
, text
,
925 void FillLineRemainder(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
926 Sci::Line line
, PRectangle rcArea
, int subLine
) {
927 InSelection eolInSelection
= InSelection::inNone
;
928 if (vsDraw
.selection
.visible
&& (subLine
== (ll
->lines
- 1))) {
929 eolInSelection
= model
.LineEndInSelection(line
);
932 if (eolInSelection
&& vsDraw
.selection
.eolFilled
&& (line
< model
.pdoc
->LinesTotal() - 1) && (vsDraw
.selection
.layer
== Layer::Base
)) {
933 surface
->FillRectangleAligned(rcArea
, Fill(SelectionBackground(model
, vsDraw
, eolInSelection
).Opaque()));
935 const ColourOptional background
= vsDraw
.Background(model
.GetMark(line
), model
.caret
.active
, ll
->containsCaret
);
937 surface
->FillRectangleAligned(rcArea
, Fill(*background
));
938 } else if (vsDraw
.styles
[ll
->styles
[ll
->numCharsInLine
]].eolFilled
) {
939 surface
->FillRectangleAligned(rcArea
, Fill(vsDraw
.styles
[ll
->styles
[ll
->numCharsInLine
]].back
));
941 surface
->FillRectangleAligned(rcArea
, Fill(vsDraw
.styles
[StyleDefault
].back
));
943 if (eolInSelection
&& vsDraw
.selection
.eolFilled
&& (line
< model
.pdoc
->LinesTotal() - 1) && (vsDraw
.selection
.layer
!= Layer::Base
)) {
944 surface
->FillRectangleAligned(rcArea
, SelectionBackground(model
, vsDraw
, eolInSelection
));
951 void EditView::DrawEOL(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
952 Sci::Line line
, int xStart
, PRectangle rcLine
, int subLine
, Sci::Position lineEnd
, XYPOSITION subLineStart
, ColourOptional background
) {
954 const Sci::Position posLineStart
= model
.pdoc
->LineStart(line
);
955 PRectangle rcSegment
= rcLine
;
957 const bool lastSubLine
= subLine
== (ll
->lines
- 1);
958 XYPOSITION virtualSpace
= 0;
960 const XYPOSITION spaceWidth
= vsDraw
.styles
[ll
->EndLineStyle()].spaceWidth
;
961 virtualSpace
= model
.sel
.VirtualSpaceFor(model
.pdoc
->LineEnd(line
)) * spaceWidth
;
963 const XYPOSITION xEol
= ll
->positions
[lineEnd
] - subLineStart
;
965 // Fill the virtual space and show selections within it
966 if (virtualSpace
> 0.0f
) {
967 rcSegment
.left
= xEol
+ xStart
;
968 rcSegment
.right
= xEol
+ xStart
+ virtualSpace
;
969 const ColourRGBA backgroundFill
= background
.value_or(vsDraw
.styles
[ll
->styles
[ll
->numCharsInLine
]].back
);
970 surface
->FillRectangleAligned(rcSegment
, backgroundFill
);
971 if (vsDraw
.selection
.visible
&& (vsDraw
.selection
.layer
== Layer::Base
)) {
972 const SelectionSegment
virtualSpaceRange(SelectionPosition(model
.pdoc
->LineEnd(line
)),
973 SelectionPosition(model
.pdoc
->LineEnd(line
),
974 model
.sel
.VirtualSpaceFor(model
.pdoc
->LineEnd(line
))));
975 for (size_t r
= 0; r
<model
.sel
.Count(); r
++) {
976 const SelectionSegment portion
= model
.sel
.Range(r
).Intersect(virtualSpaceRange
);
977 if (!portion
.Empty()) {
978 const XYPOSITION spaceWidth
= vsDraw
.styles
[ll
->EndLineStyle()].spaceWidth
;
979 rcSegment
.left
= xStart
+ ll
->positions
[portion
.start
.Position() - posLineStart
] -
980 subLineStart
+portion
.start
.VirtualSpace() * spaceWidth
;
981 rcSegment
.right
= xStart
+ ll
->positions
[portion
.end
.Position() - posLineStart
] -
982 subLineStart
+portion
.end
.VirtualSpace() * spaceWidth
;
983 rcSegment
.left
= (rcSegment
.left
> rcLine
.left
) ? rcSegment
.left
: rcLine
.left
;
984 rcSegment
.right
= (rcSegment
.right
< rcLine
.right
) ? rcSegment
.right
: rcLine
.right
;
985 surface
->FillRectangleAligned(rcSegment
, Fill(
986 SelectionBackground(model
, vsDraw
, model
.sel
.RangeType(r
)).Opaque()));
992 InSelection eolInSelection
= InSelection::inNone
;
993 if (vsDraw
.selection
.visible
&& lastSubLine
) {
994 eolInSelection
= model
.LineEndInSelection(line
);
997 const ColourRGBA selectionBack
= SelectionBackground(model
, vsDraw
, eolInSelection
);
999 // Draw the [CR], [LF], or [CR][LF] blobs if visible line ends are on
1000 XYPOSITION blobsWidth
= 0;
1002 for (Sci::Position eolPos
= ll
->numCharsBeforeEOL
; eolPos
<ll
->numCharsInLine
;) {
1003 const int styleMain
= ll
->styles
[eolPos
];
1004 const ColourOptional selectionFore
= SelectionForeground(model
, vsDraw
, eolInSelection
);
1005 ColourRGBA textFore
= selectionFore
.value_or(vsDraw
.styles
[styleMain
].fore
);
1006 char hexits
[4] = "";
1007 std::string_view ctrlChar
;
1008 Sci::Position widthBytes
= 1;
1009 RepresentationAppearance appearance
= RepresentationAppearance::Blob
;
1010 const Representation
*repr
= model
.reprs
->RepresentationFromCharacter(std::string_view(&ll
->chars
[eolPos
], ll
->numCharsInLine
- eolPos
));
1012 // Representation of whole text
1013 widthBytes
= ll
->numCharsInLine
- eolPos
;
1015 repr
= model
.reprs
->RepresentationFromCharacter(std::string_view(&ll
->chars
[eolPos
], 1));
1018 ctrlChar
= repr
->stringRep
;
1019 appearance
= repr
->appearance
;
1020 if (FlagSet(appearance
, RepresentationAppearance::Colour
)) {
1021 textFore
= repr
->colour
;
1024 const unsigned char chEOL
= ll
->chars
[eolPos
];
1025 if (UTF8IsAscii(chEOL
)) {
1026 ctrlChar
= ControlCharacterString(chEOL
);
1028 Hexits(hexits
, chEOL
);
1033 rcSegment
.left
= xStart
+ ll
->positions
[eolPos
] - subLineStart
+ virtualSpace
;
1034 rcSegment
.right
= xStart
+ ll
->positions
[eolPos
+ widthBytes
] - subLineStart
+ virtualSpace
;
1035 blobsWidth
+= rcSegment
.Width();
1036 const ColourRGBA textBack
= TextBackground(model
, vsDraw
, ll
, background
, eolInSelection
, false, styleMain
, eolPos
);
1037 if (eolInSelection
&& (line
< model
.pdoc
->LinesTotal() - 1)) {
1038 if (vsDraw
.selection
.layer
== Layer::Base
) {
1039 surface
->FillRectangleAligned(rcSegment
, Fill(selectionBack
.Opaque()));
1041 surface
->FillRectangleAligned(rcSegment
, Fill(textBack
));
1044 surface
->FillRectangleAligned(rcSegment
, Fill(textBack
));
1046 const bool drawEOLSelection
= eolInSelection
&& (line
< model
.pdoc
->LinesTotal() - 1);
1047 ColourRGBA blobText
= textBack
;
1048 if (drawEOLSelection
&& (vsDraw
.selection
.layer
== Layer::UnderText
)) {
1049 surface
->FillRectangleAligned(rcSegment
, selectionBack
);
1050 blobText
= textBack
.MixedWith(selectionBack
, selectionBack
.GetAlphaComponent());
1052 if (FlagSet(appearance
, RepresentationAppearance::Blob
)) {
1053 DrawTextBlob(surface
, vsDraw
, rcSegment
, ctrlChar
, blobText
, textFore
, phasesDraw
== PhasesDraw::One
);
1055 surface
->DrawTextTransparentUTF8(rcSegment
, vsDraw
.styles
[StyleControlChar
].font
.get(),
1056 rcSegment
.top
+ vsDraw
.maxAscent
, ctrlChar
, textFore
);
1058 if (drawEOLSelection
&& (vsDraw
.selection
.layer
== Layer::OverText
)) {
1059 surface
->FillRectangleAligned(rcSegment
, selectionBack
);
1061 eolPos
+= widthBytes
;
1065 // Draw the eol-is-selected rectangle
1066 rcSegment
.left
= xEol
+ xStart
+ virtualSpace
+ blobsWidth
;
1067 rcSegment
.right
= rcSegment
.left
+ vsDraw
.aveCharWidth
;
1069 if (eolInSelection
&& (line
< model
.pdoc
->LinesTotal() - 1) && (vsDraw
.selection
.layer
== Layer::Base
)) {
1070 surface
->FillRectangleAligned(rcSegment
, Fill(selectionBack
.Opaque()));
1073 surface
->FillRectangleAligned(rcSegment
, Fill(*background
));
1074 } else if (line
< model
.pdoc
->LinesTotal() - 1) {
1075 surface
->FillRectangleAligned(rcSegment
, Fill(vsDraw
.styles
[ll
->styles
[ll
->numCharsInLine
]].back
));
1076 } else if (vsDraw
.styles
[ll
->styles
[ll
->numCharsInLine
]].eolFilled
) {
1077 surface
->FillRectangleAligned(rcSegment
, Fill(vsDraw
.styles
[ll
->styles
[ll
->numCharsInLine
]].back
));
1079 surface
->FillRectangleAligned(rcSegment
, Fill(vsDraw
.styles
[StyleDefault
].back
));
1081 if (eolInSelection
&& (line
< model
.pdoc
->LinesTotal() - 1) && (vsDraw
.selection
.layer
!= Layer::Base
)) {
1082 surface
->FillRectangleAligned(rcSegment
, selectionBack
);
1086 rcSegment
.left
= rcSegment
.right
;
1087 if (rcSegment
.left
< rcLine
.left
)
1088 rcSegment
.left
= rcLine
.left
;
1089 rcSegment
.right
= rcLine
.right
;
1091 const bool drawEOLAnnotationStyledText
= (vsDraw
.eolAnnotationVisible
!= EOLAnnotationVisible::Hidden
) && model
.pdoc
->EOLAnnotationStyledText(line
).text
;
1092 const bool fillRemainder
= (!lastSubLine
|| (!model
.GetFoldDisplayText(line
) && !drawEOLAnnotationStyledText
));
1093 if (fillRemainder
) {
1094 // Fill the remainder of the line
1095 FillLineRemainder(surface
, model
, vsDraw
, ll
, line
, rcSegment
, subLine
);
1098 bool drawWrapMarkEnd
= false;
1100 if (subLine
+ 1 < ll
->lines
) {
1101 if (FlagSet(vsDraw
.wrap
.visualFlags
, WrapVisualFlag::End
)) {
1102 drawWrapMarkEnd
= ll
->LineStart(subLine
+ 1) != 0;
1104 if (vsDraw
.IsLineFrameOpaque(model
.caret
.active
, ll
->containsCaret
)) {
1105 // Draw right of frame under marker
1106 surface
->FillRectangleAligned(Side(rcLine
, Edge::right
, vsDraw
.GetFrameWidth()),
1107 vsDraw
.ElementColourForced(Element::CaretLineBack
).Opaque());
1111 if (drawWrapMarkEnd
) {
1112 PRectangle rcPlace
= rcSegment
;
1113 const XYPOSITION maxLeft
= rcPlace
.right
- vsDraw
.aveCharWidth
;
1115 if (FlagSet(vsDraw
.wrap
.visualFlagsLocation
, WrapVisualLocation::EndByText
)) {
1116 rcPlace
.left
= std::min(xEol
+ xStart
+ virtualSpace
, maxLeft
);
1117 rcPlace
.right
= rcPlace
.left
+ vsDraw
.aveCharWidth
;
1119 // rcLine is clipped to text area
1120 rcPlace
.right
= rcLine
.right
;
1121 rcPlace
.left
= maxLeft
;
1123 if (!customDrawWrapMarker
) {
1124 DrawWrapMarker(surface
, rcPlace
, true, vsDraw
.WrapColour());
1126 customDrawWrapMarker(surface
, rcPlace
, true, vsDraw
.WrapColour());
1131 void EditView::DrawFoldDisplayText(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1132 Sci::Line line
, int xStart
, PRectangle rcLine
, int subLine
, XYPOSITION subLineStart
, DrawPhase phase
) {
1133 const bool lastSubLine
= subLine
== (ll
->lines
- 1);
1137 const char *text
= model
.GetFoldDisplayText(line
);
1141 PRectangle rcSegment
= rcLine
;
1142 const std::string_view
foldDisplayText(text
);
1143 const Font
*fontText
= vsDraw
.styles
[StyleFoldDisplayText
].font
.get();
1144 const int widthFoldDisplayText
= static_cast<int>(surface
->WidthText(fontText
, foldDisplayText
));
1146 InSelection eolInSelection
= InSelection::inNone
;
1147 if (vsDraw
.selection
.visible
) {
1148 eolInSelection
= model
.LineEndInSelection(line
);
1151 const XYPOSITION spaceWidth
= vsDraw
.styles
[ll
->EndLineStyle()].spaceWidth
;
1152 const XYPOSITION virtualSpace
= model
.sel
.VirtualSpaceFor(
1153 model
.pdoc
->LineEnd(line
)) * spaceWidth
;
1154 rcSegment
.left
= xStart
+ ll
->positions
[ll
->numCharsInLine
] - subLineStart
+ virtualSpace
+ vsDraw
.aveCharWidth
;
1155 rcSegment
.right
= rcSegment
.left
+ static_cast<XYPOSITION
>(widthFoldDisplayText
);
1157 const ColourOptional background
= vsDraw
.Background(model
.GetMark(line
), model
.caret
.active
, ll
->containsCaret
);
1158 const ColourOptional selectionFore
= SelectionForeground(model
, vsDraw
, eolInSelection
);
1159 const ColourRGBA textFore
= selectionFore
.value_or(vsDraw
.styles
[StyleFoldDisplayText
].fore
);
1160 const ColourRGBA textBack
= TextBackground(model
, vsDraw
, ll
, background
, eolInSelection
,
1161 false, StyleFoldDisplayText
, -1);
1163 if (model
.trackLineWidth
) {
1164 if (rcSegment
.right
+ 1> lineWidthMaxSeen
) {
1165 // Fold display text border drawn on rcSegment.right with width 1 is the last visible object of the line
1166 lineWidthMaxSeen
= static_cast<int>(rcSegment
.right
+ 1);
1170 if (FlagSet(phase
, DrawPhase::back
)) {
1171 surface
->FillRectangleAligned(rcSegment
, Fill(textBack
));
1173 // Fill Remainder of the line
1174 PRectangle rcRemainder
= rcSegment
;
1175 rcRemainder
.left
= rcRemainder
.right
;
1176 if (rcRemainder
.left
< rcLine
.left
)
1177 rcRemainder
.left
= rcLine
.left
;
1178 rcRemainder
.right
= rcLine
.right
;
1179 FillLineRemainder(surface
, model
, vsDraw
, ll
, line
, rcRemainder
, subLine
);
1182 if (FlagSet(phase
, DrawPhase::text
)) {
1183 if (phasesDraw
!= PhasesDraw::One
) {
1184 surface
->DrawTextTransparent(rcSegment
, fontText
,
1185 rcSegment
.top
+ vsDraw
.maxAscent
, foldDisplayText
,
1188 surface
->DrawTextNoClip(rcSegment
, fontText
,
1189 rcSegment
.top
+ vsDraw
.maxAscent
, foldDisplayText
,
1190 textFore
, textBack
);
1194 if (FlagSet(phase
, DrawPhase::indicatorsFore
)) {
1195 if (model
.foldDisplayTextStyle
== FoldDisplayTextStyle::Boxed
) {
1196 PRectangle rcBox
= rcSegment
;
1197 rcBox
.left
= std::round(rcSegment
.left
);
1198 rcBox
.right
= std::round(rcSegment
.right
);
1199 surface
->RectangleFrame(rcBox
, Stroke(textFore
));
1203 if (FlagSet(phase
, DrawPhase::selectionTranslucent
)) {
1204 if (eolInSelection
&& (line
< model
.pdoc
->LinesTotal() - 1) && (vsDraw
.selection
.layer
!= Layer::Base
)) {
1205 surface
->FillRectangleAligned(rcSegment
, SelectionBackground(model
, vsDraw
, eolInSelection
));
1210 void EditView::DrawEOLAnnotationText(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1211 Sci::Line line
, int xStart
, PRectangle rcLine
, int subLine
, XYPOSITION subLineStart
, DrawPhase phase
) {
1213 const bool lastSubLine
= subLine
== (ll
->lines
- 1);
1217 if (vsDraw
.eolAnnotationVisible
== EOLAnnotationVisible::Hidden
) {
1220 const StyledText stEOLAnnotation
= model
.pdoc
->EOLAnnotationStyledText(line
);
1221 if (!stEOLAnnotation
.text
|| !ValidStyledText(vsDraw
, vsDraw
.eolAnnotationStyleOffset
, stEOLAnnotation
)) {
1224 const std::string_view
eolAnnotationText(stEOLAnnotation
.text
, stEOLAnnotation
.length
);
1225 const size_t style
= stEOLAnnotation
.style
+ vsDraw
.eolAnnotationStyleOffset
;
1227 PRectangle rcSegment
= rcLine
;
1228 const Font
*fontText
= vsDraw
.styles
[style
].font
.get();
1230 const Surface::Ends ends
= static_cast<Surface::Ends
>(static_cast<int>(vsDraw
.eolAnnotationVisible
) & 0xff);
1231 const Surface::Ends leftSide
= static_cast<Surface::Ends
>(static_cast<int>(ends
) & 0xf);
1232 const Surface::Ends rightSide
= static_cast<Surface::Ends
>(static_cast<int>(ends
) & 0xf0);
1234 XYPOSITION leftBoxSpace
= 0;
1235 XYPOSITION rightBoxSpace
= 0;
1236 if (vsDraw
.eolAnnotationVisible
>= EOLAnnotationVisible::Boxed
) {
1239 if (vsDraw
.eolAnnotationVisible
!= EOLAnnotationVisible::Boxed
) {
1241 case Surface::Ends::leftFlat
:
1244 case Surface::Ends::leftAngle
:
1245 leftBoxSpace
= rcLine
.Height() / 2.0;
1247 case Surface::Ends::semiCircles
:
1249 leftBoxSpace
= rcLine
.Height() / 3.0;
1252 switch (rightSide
) {
1253 case Surface::Ends::rightFlat
:
1256 case Surface::Ends::rightAngle
:
1257 rightBoxSpace
= rcLine
.Height() / 2.0;
1259 case Surface::Ends::semiCircles
:
1261 rightBoxSpace
= rcLine
.Height() / 3.0;
1266 const int widthEOLAnnotationText
= static_cast<int>(surface
->WidthTextUTF8(fontText
, eolAnnotationText
) +
1267 leftBoxSpace
+ rightBoxSpace
);
1269 const XYPOSITION spaceWidth
= vsDraw
.styles
[ll
->EndLineStyle()].spaceWidth
;
1270 const XYPOSITION virtualSpace
= model
.sel
.VirtualSpaceFor(
1271 model
.pdoc
->LineEnd(line
)) * spaceWidth
;
1272 rcSegment
.left
= xStart
+
1273 ll
->positions
[ll
->numCharsInLine
] - subLineStart
1274 + virtualSpace
+ vsDraw
.aveCharWidth
;
1276 const char *textFoldDisplay
= model
.GetFoldDisplayText(line
);
1277 if (textFoldDisplay
) {
1278 const std::string_view
foldDisplayText(textFoldDisplay
);
1279 rcSegment
.left
+= static_cast<int>(
1280 surface
->WidthText(vsDraw
.styles
[StyleFoldDisplayText
].font
.get(), foldDisplayText
)) +
1281 vsDraw
.aveCharWidth
;
1283 rcSegment
.right
= rcSegment
.left
+ static_cast<XYPOSITION
>(widthEOLAnnotationText
);
1285 const ColourOptional background
= vsDraw
.Background(model
.GetMark(line
), model
.caret
.active
, ll
->containsCaret
);
1286 const ColourRGBA textFore
= vsDraw
.styles
[style
].fore
;
1287 const ColourRGBA textBack
= TextBackground(model
, vsDraw
, ll
, background
, InSelection::inNone
,
1288 false, static_cast<int>(style
), -1);
1290 if (model
.trackLineWidth
) {
1291 if (rcSegment
.right
+ 1> lineWidthMaxSeen
) {
1292 // EOL Annotation text border drawn on rcSegment.right with width 1 is the last visible object of the line
1293 lineWidthMaxSeen
= static_cast<int>(rcSegment
.right
+ 1);
1297 if (FlagSet(phase
, DrawPhase::back
)) {
1298 // This fills in the whole remainder of the line even though
1299 // it may be double drawing. This is to allow stadiums with
1300 // curved or angled ends to have the area outside in the correct
1301 // background colour.
1302 PRectangle rcRemainder
= rcSegment
;
1303 rcRemainder
.right
= rcLine
.right
;
1304 FillLineRemainder(surface
, model
, vsDraw
, ll
, line
, rcRemainder
, subLine
);
1307 PRectangle rcText
= rcSegment
;
1308 rcText
.left
+= leftBoxSpace
;
1309 rcText
.right
-= rightBoxSpace
;
1311 // For single phase drawing, draw the text then any box over it
1312 if (FlagSet(phase
, DrawPhase::text
)) {
1313 if (phasesDraw
== PhasesDraw::One
) {
1314 surface
->DrawTextNoClipUTF8(rcText
, fontText
,
1315 rcText
.top
+ vsDraw
.maxAscent
, eolAnnotationText
,
1316 textFore
, textBack
);
1320 // Draw any box or stadium shape
1321 if (FlagSet(phase
, DrawPhase::indicatorsBack
)) {
1322 const PRectangle rcBox
= PixelAlign(rcSegment
, 1);
1324 switch (vsDraw
.eolAnnotationVisible
) {
1325 case EOLAnnotationVisible::Standard
:
1326 if (phasesDraw
!= PhasesDraw::One
) {
1327 surface
->FillRectangle(rcBox
, textBack
);
1331 case EOLAnnotationVisible::Boxed
:
1332 if (phasesDraw
== PhasesDraw::One
) {
1333 // Draw a rectangular outline around the text
1334 surface
->RectangleFrame(rcBox
, textFore
);
1336 // Draw with a fill to fill the edges of the rectangle.
1337 surface
->RectangleDraw(rcBox
, FillStroke(textBack
, textFore
));
1342 if (phasesDraw
== PhasesDraw::One
) {
1343 // Draw an outline around the text
1344 surface
->Stadium(rcBox
, FillStroke(ColourRGBA(textBack
, 0), textFore
), ends
);
1346 // Draw with a fill to fill the edges of the shape.
1347 surface
->Stadium(rcBox
, FillStroke(textBack
, textFore
), ends
);
1353 // For multi-phase drawing draw the text last as transparent over any box
1354 if (FlagSet(phase
, DrawPhase::text
)) {
1355 if (phasesDraw
!= PhasesDraw::One
) {
1356 surface
->DrawTextTransparentUTF8(rcText
, fontText
,
1357 rcText
.top
+ vsDraw
.maxAscent
, eolAnnotationText
,
1365 constexpr bool AnnotationBoxedOrIndented(AnnotationVisible annotationVisible
) noexcept
{
1366 return annotationVisible
== AnnotationVisible::Boxed
|| annotationVisible
== AnnotationVisible::Indented
;
1371 void EditView::DrawAnnotation(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1372 Sci::Line line
, int xStart
, PRectangle rcLine
, int subLine
, DrawPhase phase
) {
1373 const int indent
= static_cast<int>(model
.pdoc
->GetLineIndentation(line
) * vsDraw
.spaceWidth
);
1374 PRectangle rcSegment
= rcLine
;
1375 const int annotationLine
= subLine
- ll
->lines
;
1376 const StyledText stAnnotation
= model
.pdoc
->AnnotationStyledText(line
);
1377 if (stAnnotation
.text
&& ValidStyledText(vsDraw
, vsDraw
.annotationStyleOffset
, stAnnotation
)) {
1378 if (FlagSet(phase
, DrawPhase::back
)) {
1379 surface
->FillRectangleAligned(rcSegment
, Fill(vsDraw
.styles
[0].back
));
1381 rcSegment
.left
= static_cast<XYPOSITION
>(xStart
);
1382 if (model
.trackLineWidth
|| AnnotationBoxedOrIndented(vsDraw
.annotationVisible
)) {
1383 // Only care about calculating width if tracking or need to draw indented box
1384 int widthAnnotation
= WidestLineWidth(surface
, vsDraw
, vsDraw
.annotationStyleOffset
, stAnnotation
);
1385 if (AnnotationBoxedOrIndented(vsDraw
.annotationVisible
)) {
1386 widthAnnotation
+= static_cast<int>(vsDraw
.spaceWidth
* 2); // Margins
1387 rcSegment
.left
= static_cast<XYPOSITION
>(xStart
+ indent
);
1388 rcSegment
.right
= rcSegment
.left
+ widthAnnotation
;
1390 if (widthAnnotation
> lineWidthMaxSeen
)
1391 lineWidthMaxSeen
= widthAnnotation
;
1393 const int annotationLines
= model
.pdoc
->AnnotationLines(line
);
1395 size_t lengthAnnotation
= stAnnotation
.LineLength(start
);
1396 int lineInAnnotation
= 0;
1397 while ((lineInAnnotation
< annotationLine
) && (start
< stAnnotation
.length
)) {
1398 start
+= lengthAnnotation
+ 1;
1399 lengthAnnotation
= stAnnotation
.LineLength(start
);
1402 PRectangle rcText
= rcSegment
;
1403 if ((FlagSet(phase
, DrawPhase::back
)) && AnnotationBoxedOrIndented(vsDraw
.annotationVisible
)) {
1404 surface
->FillRectangleAligned(rcText
,
1405 Fill(vsDraw
.styles
[stAnnotation
.StyleAt(start
) + vsDraw
.annotationStyleOffset
].back
));
1406 rcText
.left
+= vsDraw
.spaceWidth
;
1408 DrawStyledText(surface
, vsDraw
, vsDraw
.annotationStyleOffset
, rcText
,
1409 stAnnotation
, start
, lengthAnnotation
, phase
);
1410 if ((FlagSet(phase
, DrawPhase::back
)) && (vsDraw
.annotationVisible
== AnnotationVisible::Boxed
)) {
1411 const ColourRGBA colourBorder
= vsDraw
.styles
[vsDraw
.annotationStyleOffset
].fore
;
1412 const PRectangle rcBorder
= PixelAlignOutside(rcSegment
, surface
->PixelDivisions());
1413 surface
->FillRectangle(Side(rcBorder
, Edge::left
, 1), colourBorder
);
1414 surface
->FillRectangle(Side(rcBorder
, Edge::right
, 1), colourBorder
);
1415 if (subLine
== ll
->lines
) {
1416 surface
->FillRectangle(Side(rcBorder
, Edge::top
, 1), colourBorder
);
1418 if (subLine
== ll
->lines
+ annotationLines
- 1) {
1419 surface
->FillRectangle(Side(rcBorder
, Edge::bottom
, 1), colourBorder
);
1423 // No annotation to draw so show bug with colourBug
1424 if (FlagSet(phase
, DrawPhase::back
)) {
1425 surface
->FillRectangle(rcSegment
, colourBug
.Opaque());
1432 void DrawBlockCaret(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1433 int subLine
, int xStart
, Sci::Position offset
, Sci::Position posCaret
, PRectangle rcCaret
, ColourRGBA caretColour
) {
1435 const Sci::Position lineStart
= ll
->LineStart(subLine
);
1436 Sci::Position posBefore
= posCaret
;
1437 Sci::Position posAfter
= model
.pdoc
->MovePositionOutsideChar(posCaret
+ 1, 1);
1438 Sci::Position numCharsToDraw
= posAfter
- posCaret
;
1440 // Work out where the starting and ending offsets are. We need to
1441 // see if the previous character shares horizontal space, such as a
1442 // glyph / combining character. If so we'll need to draw that too.
1443 Sci::Position offsetFirstChar
= offset
;
1444 Sci::Position offsetLastChar
= offset
+ (posAfter
- posCaret
);
1445 while ((posBefore
> 0) && ((offsetLastChar
- numCharsToDraw
) >= lineStart
)) {
1446 if ((ll
->positions
[offsetLastChar
] - ll
->positions
[offsetLastChar
- numCharsToDraw
]) > 0) {
1447 // The char does not share horizontal space
1450 // Char shares horizontal space, update the numChars to draw
1451 // Update posBefore to point to the prev char
1452 posBefore
= model
.pdoc
->MovePositionOutsideChar(posBefore
- 1, -1);
1453 numCharsToDraw
= posAfter
- posBefore
;
1454 offsetFirstChar
= offset
- (posCaret
- posBefore
);
1457 // See if the next character shares horizontal space, if so we'll
1458 // need to draw that too.
1459 if (offsetFirstChar
< 0)
1460 offsetFirstChar
= 0;
1461 numCharsToDraw
= offsetLastChar
- offsetFirstChar
;
1462 while ((offsetLastChar
< ll
->LineStart(subLine
+ 1)) && (offsetLastChar
<= ll
->numCharsInLine
)) {
1463 // Update posAfter to point to the 2nd next char, this is where
1464 // the next character ends, and 2nd next begins. We'll need
1465 // to compare these two
1466 posBefore
= posAfter
;
1467 posAfter
= model
.pdoc
->MovePositionOutsideChar(posAfter
+ 1, 1);
1468 offsetLastChar
= offset
+ (posAfter
- posCaret
);
1469 if ((ll
->positions
[offsetLastChar
] - ll
->positions
[offsetLastChar
- (posAfter
- posBefore
)]) > 0) {
1470 // The char does not share horizontal space
1473 // Char shares horizontal space, update the numChars to draw
1474 numCharsToDraw
= offsetLastChar
- offsetFirstChar
;
1477 // We now know what to draw, update the caret drawing rectangle
1478 rcCaret
.left
= ll
->positions
[offsetFirstChar
] - ll
->positions
[lineStart
] + xStart
;
1479 rcCaret
.right
= ll
->positions
[offsetFirstChar
+ numCharsToDraw
] - ll
->positions
[lineStart
] + xStart
;
1481 // Adjust caret position to take into account any word wrapping symbols.
1482 if ((ll
->wrapIndent
!= 0) && (lineStart
!= 0)) {
1483 const XYPOSITION wordWrapCharWidth
= ll
->wrapIndent
;
1484 rcCaret
.left
+= wordWrapCharWidth
;
1485 rcCaret
.right
+= wordWrapCharWidth
;
1488 // This character is where the caret block is, we override the colours
1489 // (inversed) for drawing the caret here.
1490 const int styleMain
= ll
->styles
[offsetFirstChar
];
1491 const Font
*fontText
= vsDraw
.styles
[styleMain
].font
.get();
1492 const std::string_view
text(&ll
->chars
[offsetFirstChar
], numCharsToDraw
);
1493 surface
->DrawTextClipped(rcCaret
, fontText
,
1494 rcCaret
.top
+ vsDraw
.maxAscent
, text
, vsDraw
.styles
[styleMain
].back
,
1500 void EditView::DrawCarets(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1501 Sci::Line lineDoc
, int xStart
, PRectangle rcLine
, int subLine
) const {
1502 // When drag is active it is the only caret drawn
1503 const bool drawDrag
= model
.posDrag
.IsValid();
1504 if (!vsDraw
.selection
.visible
&& !drawDrag
)
1506 const Sci::Position posLineStart
= model
.pdoc
->LineStart(lineDoc
);
1507 // For each selection draw
1508 for (size_t r
= 0; (r
<model
.sel
.Count()) || drawDrag
; r
++) {
1509 const bool mainCaret
= r
== model
.sel
.Main();
1510 SelectionPosition posCaret
= (drawDrag
? model
.posDrag
: model
.sel
.Range(r
).caret
);
1511 if ((vsDraw
.DrawCaretInsideSelection(model
.inOverstrike
, imeCaretBlockOverride
)) &&
1513 posCaret
> model
.sel
.Range(r
).anchor
) {
1514 if (posCaret
.VirtualSpace() > 0)
1515 posCaret
.SetVirtualSpace(posCaret
.VirtualSpace() - 1);
1517 posCaret
.SetPosition(model
.pdoc
->MovePositionOutsideChar(posCaret
.Position()-1, -1));
1519 const int offset
= static_cast<int>(posCaret
.Position() - posLineStart
);
1520 const XYPOSITION spaceWidth
= vsDraw
.styles
[ll
->EndLineStyle()].spaceWidth
;
1521 const XYPOSITION virtualOffset
= posCaret
.VirtualSpace() * spaceWidth
;
1522 if (ll
->InLine(offset
, subLine
) && offset
<= ll
->numCharsBeforeEOL
) {
1523 XYPOSITION xposCaret
= ll
->positions
[offset
] + virtualOffset
- ll
->positions
[ll
->LineStart(subLine
)];
1524 if (model
.BidirectionalEnabled() && (posCaret
.VirtualSpace() == 0)) {
1526 const ScreenLine
screenLine(ll
, subLine
, vsDraw
, rcLine
.right
, tabWidthMinimumPixels
);
1528 const int caretPosition
= offset
- ll
->LineStart(subLine
);
1530 std::unique_ptr
<IScreenLineLayout
> slLayout
= surface
->Layout(&screenLine
);
1531 const XYPOSITION caretLeft
= slLayout
->XFromPosition(caretPosition
);
1533 // In case of start of line, the cursor should be at the right
1534 xposCaret
= caretLeft
+ virtualOffset
;
1536 if (ll
->wrapIndent
!= 0) {
1537 const Sci::Position lineStart
= ll
->LineStart(subLine
);
1538 if (lineStart
!= 0) // Wrapped
1539 xposCaret
+= ll
->wrapIndent
;
1541 const bool caretBlinkState
= (model
.caret
.active
&& model
.caret
.on
) || (!additionalCaretsBlink
&& !mainCaret
);
1542 const bool caretVisibleState
= additionalCaretsVisible
|| mainCaret
;
1543 if ((xposCaret
>= 0) && vsDraw
.IsCaretVisible(mainCaret
) &&
1544 (drawDrag
|| (caretBlinkState
&& caretVisibleState
))) {
1545 bool canDrawBlockCaret
= true;
1546 bool drawBlockCaret
= false;
1547 XYPOSITION widthOverstrikeCaret
;
1548 XYPOSITION caretWidthOffset
= 0;
1549 PRectangle rcCaret
= rcLine
;
1551 if (posCaret
.Position() == model
.pdoc
->Length()) { // At end of document
1552 canDrawBlockCaret
= false;
1553 widthOverstrikeCaret
= vsDraw
.aveCharWidth
;
1554 } else if ((posCaret
.Position() - posLineStart
) >= ll
->numCharsInLine
) { // At end of line
1555 canDrawBlockCaret
= false;
1556 widthOverstrikeCaret
= vsDraw
.aveCharWidth
;
1558 const int widthChar
= model
.pdoc
->LenChar(posCaret
.Position());
1559 widthOverstrikeCaret
= ll
->positions
[offset
+ widthChar
] - ll
->positions
[offset
];
1561 if (widthOverstrikeCaret
< 3) // Make sure its visible
1562 widthOverstrikeCaret
= 3;
1565 caretWidthOffset
= 0.51f
; // Move back so overlaps both character cells.
1566 xposCaret
+= xStart
;
1567 const ViewStyle::CaretShape caretShape
= drawDrag
? ViewStyle::CaretShape::line
:
1568 vsDraw
.CaretShapeForMode(model
.inOverstrike
, mainCaret
);
1570 /* Dragging text, use a line caret */
1571 rcCaret
.left
= std::round(xposCaret
- caretWidthOffset
);
1572 rcCaret
.right
= rcCaret
.left
+ vsDraw
.caret
.width
;
1573 } else if ((caretShape
== ViewStyle::CaretShape::bar
) && drawOverstrikeCaret
) {
1574 /* Over-strike (insert mode), use a modified bar caret */
1575 rcCaret
.top
= rcCaret
.bottom
- 2;
1576 rcCaret
.left
= xposCaret
+ 1;
1577 rcCaret
.right
= rcCaret
.left
+ widthOverstrikeCaret
- 1;
1578 } else if ((caretShape
== ViewStyle::CaretShape::block
) || imeCaretBlockOverride
) {
1580 rcCaret
.left
= xposCaret
;
1581 if (canDrawBlockCaret
&& !(IsControl(ll
->chars
[offset
]))) {
1582 drawBlockCaret
= true;
1583 rcCaret
.right
= xposCaret
+ widthOverstrikeCaret
;
1585 rcCaret
.right
= xposCaret
+ vsDraw
.aveCharWidth
;
1589 rcCaret
.left
= std::round(xposCaret
- caretWidthOffset
);
1590 rcCaret
.right
= rcCaret
.left
+ vsDraw
.caret
.width
;
1592 const Element elementCaret
= mainCaret
? Element::Caret
: Element::CaretAdditional
;
1593 const ColourRGBA caretColour
= vsDraw
.ElementColourForced(elementCaret
);
1594 //assert(caretColour.IsOpaque());
1595 if (drawBlockCaret
) {
1596 DrawBlockCaret(surface
, model
, vsDraw
, ll
, subLine
, xStart
, offset
, posCaret
.Position(), rcCaret
, caretColour
);
1598 surface
->FillRectangleAligned(rcCaret
, Fill(caretColour
));
1609 void DrawWrapIndentAndMarker(Surface
*surface
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1610 int xStart
, PRectangle rcLine
, ColourOptional background
, DrawWrapMarkerFn customDrawWrapMarker
,
1612 // default background here..
1613 surface
->FillRectangleAligned(rcLine
, Fill(background
.value_or(vsDraw
.styles
[StyleDefault
].back
)));
1615 if (vsDraw
.IsLineFrameOpaque(caretActive
, ll
->containsCaret
)) {
1616 // Draw left of frame under marker
1617 surface
->FillRectangleAligned(Side(rcLine
, Edge::left
, vsDraw
.GetFrameWidth()),
1618 vsDraw
.ElementColourForced(Element::CaretLineBack
).Opaque());
1621 if (FlagSet(vsDraw
.wrap
.visualFlags
, WrapVisualFlag::Start
)) {
1623 // draw continuation rect
1624 PRectangle rcPlace
= rcLine
;
1626 rcPlace
.left
= static_cast<XYPOSITION
>(xStart
);
1627 rcPlace
.right
= rcPlace
.left
+ ll
->wrapIndent
;
1629 if (FlagSet(vsDraw
.wrap
.visualFlagsLocation
, WrapVisualLocation::StartByText
))
1630 rcPlace
.left
= rcPlace
.right
- vsDraw
.aveCharWidth
;
1632 rcPlace
.right
= rcPlace
.left
+ vsDraw
.aveCharWidth
;
1634 if (!customDrawWrapMarker
) {
1635 DrawWrapMarker(surface
, rcPlace
, false, vsDraw
.WrapColour());
1637 customDrawWrapMarker(surface
, rcPlace
, false, vsDraw
.WrapColour());
1642 // On the curses platform, the terminal is drawing its own caret, so if the caret is within
1643 // the main selection, do not draw the selection at that position.
1644 // Use iDoc from DrawBackground and DrawForeground here because TextSegment has been adjusted
1645 // such that, if the caret is inside the main selection, the beginning or end of that selection
1646 // is at the end of a text segment.
1647 // This function should only be called if iDoc is within the main selection.
1648 InSelection
CharacterInCursesSelection(Sci::Position iDoc
, const EditModel
&model
, const ViewStyle
&vsDraw
) noexcept
{
1649 const SelectionPosition
&posCaret
= model
.sel
.RangeMain().caret
;
1650 const bool caretAtStart
= posCaret
< model
.sel
.RangeMain().anchor
&& posCaret
.Position() == iDoc
;
1651 const bool caretAtEnd
= posCaret
> model
.sel
.RangeMain().anchor
&&
1652 vsDraw
.DrawCaretInsideSelection(false, false) &&
1653 model
.pdoc
->MovePositionOutsideChar(posCaret
.Position() - 1, -1) == iDoc
;
1654 return (caretAtStart
|| caretAtEnd
) ? InSelection::inNone
: InSelection::inMain
;
1657 void DrawBackground(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1658 int xStart
, PRectangle rcLine
, int subLine
, Range lineRange
, Sci::Position posLineStart
,
1659 ColourOptional background
) {
1661 const bool selBackDrawn
= vsDraw
.SelectionBackgroundDrawn();
1662 bool inIndentation
= subLine
== 0; // Do not handle indentation except on first subline.
1663 const XYPOSITION subLineStart
= ll
->positions
[lineRange
.start
];
1664 const XYPOSITION horizontalOffset
= xStart
- subLineStart
;
1665 // Does not take margin into account but not significant
1666 const XYPOSITION xStartVisible
= subLineStart
- xStart
;
1668 const BreakFinder::BreakFor breakFor
= selBackDrawn
? BreakFinder::BreakFor::Selection
: BreakFinder::BreakFor::Text
;
1669 BreakFinder
bfBack(ll
, &model
.sel
, lineRange
, posLineStart
, xStartVisible
, breakFor
, model
.pdoc
, model
.reprs
.get(), &vsDraw
);
1671 const bool drawWhitespaceBackground
= vsDraw
.WhitespaceBackgroundDrawn() && !background
;
1673 // Background drawing loop
1674 while (bfBack
.More()) {
1676 const TextSegment ts
= bfBack
.Next();
1677 const Sci::Position i
= ts
.end() - 1;
1678 const Sci::Position iDoc
= i
+ posLineStart
;
1680 const Interval horizontal
= ll
->Span(ts
.start
, ts
.end()).Offset(horizontalOffset
);
1681 // Only try to draw if really visible - enhances performance by not calling environment to
1682 // draw strings that are completely past the right side of the window.
1683 if (!horizontal
.Empty() && rcLine
.Intersects(horizontal
)) {
1684 const PRectangle rcSegment
= Intersection(rcLine
, horizontal
);
1686 InSelection inSelection
= vsDraw
.selection
.visible
? model
.sel
.CharacterInSelection(iDoc
) : InSelection::inNone
;
1687 if (FlagSet(vsDraw
.caret
.style
, CaretStyle::Curses
) && (inSelection
== InSelection::inMain
))
1688 inSelection
= CharacterInCursesSelection(iDoc
, model
, vsDraw
);
1689 const bool inHotspot
= model
.hotspot
.Valid() && model
.hotspot
.ContainsCharacter(iDoc
);
1690 ColourRGBA textBack
= TextBackground(model
, vsDraw
, ll
, background
, inSelection
,
1691 inHotspot
, ll
->styles
[i
], i
);
1692 if (ts
.representation
) {
1693 if (ll
->chars
[i
] == '\t') {
1695 if (drawWhitespaceBackground
&& vsDraw
.WhiteSpaceVisible(inIndentation
)) {
1696 textBack
= vsDraw
.ElementColourForced(Element::WhiteSpaceBack
).Opaque();
1700 inIndentation
= false;
1702 surface
->FillRectangleAligned(rcSegment
, Fill(textBack
));
1704 // Normal text display
1705 surface
->FillRectangleAligned(rcSegment
, Fill(textBack
));
1706 if (vsDraw
.viewWhitespace
!= WhiteSpace::Invisible
) {
1707 for (int cpos
= 0; cpos
<= i
- ts
.start
; cpos
++) {
1708 if (ll
->chars
[cpos
+ ts
.start
] == ' ') {
1709 if (drawWhitespaceBackground
&& vsDraw
.WhiteSpaceVisible(inIndentation
)) {
1710 const PRectangle rcSpace
= Intersection(rcLine
,
1711 ll
->SpanByte(cpos
+ ts
.start
).Offset(horizontalOffset
));
1712 surface
->FillRectangleAligned(rcSpace
,
1713 vsDraw
.ElementColourForced(Element::WhiteSpaceBack
).Opaque());
1716 inIndentation
= false;
1721 } else if (horizontal
.left
> rcLine
.right
) {
1727 void DrawEdgeLine(Surface
*surface
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1728 int xStart
, PRectangle rcLine
, Range lineRange
) {
1729 if (vsDraw
.edgeState
== EdgeVisualStyle::Line
) {
1730 PRectangle rcSegment
= rcLine
;
1731 const int edgeX
= static_cast<int>(vsDraw
.theEdge
.column
* vsDraw
.spaceWidth
);
1732 rcSegment
.left
= static_cast<XYPOSITION
>(edgeX
+ xStart
);
1733 if ((ll
->wrapIndent
!= 0) && (lineRange
.start
!= 0))
1734 rcSegment
.left
-= ll
->wrapIndent
;
1735 rcSegment
.right
= rcSegment
.left
+ 1;
1736 surface
->FillRectangleAligned(rcSegment
, Fill(vsDraw
.theEdge
.colour
));
1737 } else if (vsDraw
.edgeState
== EdgeVisualStyle::MultiLine
) {
1738 for (size_t edge
= 0; edge
< vsDraw
.theMultiEdge
.size(); edge
++) {
1739 if (vsDraw
.theMultiEdge
[edge
].column
>= 0) {
1740 PRectangle rcSegment
= rcLine
;
1741 const int edgeX
= static_cast<int>(vsDraw
.theMultiEdge
[edge
].column
* vsDraw
.spaceWidth
);
1742 rcSegment
.left
= static_cast<XYPOSITION
>(edgeX
+ xStart
);
1743 if ((ll
->wrapIndent
!= 0) && (lineRange
.start
!= 0))
1744 rcSegment
.left
-= ll
->wrapIndent
;
1745 rcSegment
.right
= rcSegment
.left
+ 1;
1746 surface
->FillRectangleAligned(rcSegment
, Fill(vsDraw
.theMultiEdge
[edge
].colour
));
1752 // Draw underline mark as part of background if on base layer
1753 void DrawMarkUnderline(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
,
1754 Sci::Line line
, PRectangle rcLine
) {
1755 int marks
= model
.GetMark(line
);
1756 for (int markBit
= 0; (markBit
<= MarkerMax
) && marks
; markBit
++) {
1757 if ((marks
& 1) && (vsDraw
.markers
[markBit
].markType
== MarkerSymbol::Underline
) &&
1758 (vsDraw
.markers
[markBit
].layer
== Layer::Base
)) {
1759 PRectangle rcUnderline
= rcLine
;
1760 rcUnderline
.top
= rcUnderline
.bottom
- 2;
1761 surface
->FillRectangleAligned(rcUnderline
, Fill(vsDraw
.markers
[markBit
].back
));
1767 void DrawTranslucentSelection(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1768 Sci::Line line
, int xStart
, PRectangle rcLine
, int subLine
, Range lineRange
, int tabWidthMinimumPixels
, Layer layer
) {
1769 if (vsDraw
.selection
.layer
== layer
) {
1770 const Sci::Position posLineStart
= model
.pdoc
->LineStart(line
);
1771 const XYPOSITION subLineStart
= ll
->positions
[lineRange
.start
];
1772 const XYPOSITION horizontalOffset
= xStart
- subLineStart
;
1773 // For each selection draw
1774 Sci::Position virtualSpaces
= 0;
1775 if (subLine
== (ll
->lines
- 1)) {
1776 virtualSpaces
= model
.sel
.VirtualSpaceFor(model
.pdoc
->LineEnd(line
));
1778 const SelectionPosition
posStart(posLineStart
+ lineRange
.start
);
1779 const SelectionPosition
posEnd(posLineStart
+ lineRange
.end
, virtualSpaces
);
1780 const SelectionSegment
virtualSpaceRange(posStart
, posEnd
);
1781 for (size_t r
= 0; r
< model
.sel
.Count(); r
++) {
1782 const SelectionSegment portion
= model
.sel
.Range(r
).Intersect(virtualSpaceRange
);
1783 if (!portion
.Empty()) {
1784 const SelectionSegment portionInLine
= portion
.Subtract(posLineStart
);
1785 const ColourRGBA selectionBack
= SelectionBackground(
1786 model
, vsDraw
, model
.sel
.RangeType(r
));
1787 const XYPOSITION spaceWidth
= vsDraw
.styles
[ll
->EndLineStyle()].spaceWidth
;
1788 const Interval intervalVirtual
{ portion
.start
.VirtualSpace() * spaceWidth
, portion
.end
.VirtualSpace() * spaceWidth
};
1789 if (model
.BidirectionalEnabled()) {
1790 const SelectionSegment portionInSubLine
= portionInLine
.Subtract(lineRange
.start
);
1792 const ScreenLine
screenLine(ll
, subLine
, vsDraw
, rcLine
.right
, tabWidthMinimumPixels
);
1793 std::unique_ptr
<IScreenLineLayout
> slLayout
= surface
->Layout(&screenLine
);
1796 const std::vector
<Interval
> intervals
= slLayout
->FindRangeIntervals(
1797 portionInSubLine
.start
.Position(), portionInSubLine
.end
.Position());
1798 for (const Interval
&interval
: intervals
) {
1799 const PRectangle rcSelection
= rcLine
.WithHorizontalBounds(interval
.Offset(xStart
));
1800 surface
->FillRectangleAligned(rcSelection
, selectionBack
);
1804 if (portion
.end
.VirtualSpace()) {
1805 const XYPOSITION xStartVirtual
= ll
->positions
[lineRange
.end
] + horizontalOffset
;
1806 const PRectangle rcSegment
= rcLine
.WithHorizontalBounds(intervalVirtual
.Offset(xStartVirtual
));
1807 surface
->FillRectangleAligned(rcSegment
, selectionBack
);
1810 Interval intervalSegment
= ll
->Span(
1811 static_cast<int>(portionInLine
.start
.Position()),
1812 static_cast<int>(portionInLine
.end
.Position()))
1813 .Offset(horizontalOffset
);
1814 intervalSegment
.left
+= intervalVirtual
.left
;
1815 intervalSegment
.right
+= intervalVirtual
.right
;
1816 if ((ll
->wrapIndent
!= 0) && (lineRange
.start
!= 0)) {
1817 if ((portionInLine
.start
.Position() == lineRange
.start
) &&
1818 model
.sel
.Range(r
).ContainsCharacter(portion
.start
.Position() - 1))
1819 intervalSegment
.left
-= static_cast<int>(ll
->wrapIndent
); // indentation added to xStart was truncated to int, so we do the same here
1821 const PRectangle rcSegment
= Intersection(rcLine
, intervalSegment
);
1822 if (rcSegment
.right
> rcLine
.left
)
1823 surface
->FillRectangleAligned(rcSegment
, selectionBack
);
1830 void DrawCaretLineFramed(Surface
*surface
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1831 PRectangle rcLine
, int subLine
) {
1832 const ColourOptional caretlineBack
= vsDraw
.ElementColour(Element::CaretLineBack
);
1833 if (!caretlineBack
) {
1837 const ColourRGBA colourFrame
= (vsDraw
.caretLine
.layer
== Layer::Base
) ?
1838 caretlineBack
->Opaque() : *caretlineBack
;
1840 const int width
= vsDraw
.GetFrameWidth();
1842 // Avoid double drawing the corners by removing the left and right sides when drawing top and bottom borders
1843 const PRectangle rcWithoutLeftRight
= rcLine
.Inset(Point(width
, 0.0));
1845 if (subLine
== 0 || ll
->wrapIndent
== 0 || vsDraw
.caretLine
.layer
!= Layer::Base
|| vsDraw
.caretLine
.subLine
) {
1847 surface
->FillRectangleAligned(Side(rcLine
, Edge::left
, width
), colourFrame
);
1849 if (subLine
== 0 || vsDraw
.caretLine
.subLine
) {
1851 surface
->FillRectangleAligned(Side(rcWithoutLeftRight
, Edge::top
, width
), colourFrame
);
1853 if (subLine
== ll
->lines
- 1 || vsDraw
.caretLine
.layer
!= Layer::Base
|| vsDraw
.caretLine
.subLine
) {
1855 surface
->FillRectangleAligned(Side(rcLine
, Edge::right
, width
), colourFrame
);
1857 if (subLine
== ll
->lines
- 1 || vsDraw
.caretLine
.subLine
) {
1859 surface
->FillRectangleAligned(Side(rcWithoutLeftRight
, Edge::bottom
, width
), colourFrame
);
1863 // Draw any translucent whole line states
1864 void DrawTranslucentLineState(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1865 Sci::Line line
, PRectangle rcLine
, int subLine
, Layer layer
) {
1866 if ((model
.caret
.active
|| vsDraw
.caretLine
.alwaysShow
) && vsDraw
.ElementColour(Element::CaretLineBack
) && ll
->containsCaret
&&
1867 vsDraw
.caretLine
.layer
== layer
) {
1868 if (vsDraw
.caretLine
.frame
) {
1869 DrawCaretLineFramed(surface
, vsDraw
, ll
, rcLine
, subLine
);
1871 surface
->FillRectangleAligned(rcLine
, vsDraw
.ElementColourForced(Element::CaretLineBack
));
1874 const int marksOfLine
= model
.GetMark(line
);
1875 int marksDrawnInText
= marksOfLine
& vsDraw
.maskDrawInText
;
1876 for (int markBit
= 0; (markBit
<= MarkerMax
) && marksDrawnInText
; markBit
++) {
1877 if ((marksDrawnInText
& 1) && (vsDraw
.markers
[markBit
].layer
== layer
)) {
1878 if (vsDraw
.markers
[markBit
].markType
== MarkerSymbol::Background
) {
1879 surface
->FillRectangleAligned(rcLine
, vsDraw
.markers
[markBit
].BackWithAlpha());
1880 } else if (vsDraw
.markers
[markBit
].markType
== MarkerSymbol::Underline
) {
1881 PRectangle rcUnderline
= rcLine
;
1882 rcUnderline
.top
= rcUnderline
.bottom
- 2;
1883 surface
->FillRectangleAligned(rcUnderline
, vsDraw
.markers
[markBit
].BackWithAlpha());
1886 marksDrawnInText
>>= 1;
1888 int marksDrawnInLine
= marksOfLine
& vsDraw
.maskInLine
;
1889 for (int markBit
= 0; (markBit
<= MarkerMax
) && marksDrawnInLine
; markBit
++) {
1890 if ((marksDrawnInLine
& 1) && (vsDraw
.markers
[markBit
].layer
== layer
)) {
1891 surface
->FillRectangleAligned(rcLine
, vsDraw
.markers
[markBit
].BackWithAlpha());
1893 marksDrawnInLine
>>= 1;
1897 void DrawTabArrow(Surface
*surface
, PRectangle rcTab
, int ymid
,
1898 const ViewStyle
&vsDraw
, Stroke stroke
) {
1900 const XYPOSITION halfWidth
= stroke
.width
/ 2.0;
1902 const XYPOSITION leftStroke
= std::round(std::min(rcTab
.left
+ 2, rcTab
.right
- 1)) + halfWidth
;
1903 const XYPOSITION rightStroke
= std::max(leftStroke
, std::round(rcTab
.right
) - 1.0f
- halfWidth
);
1904 const XYPOSITION yMidAligned
= ymid
+ halfWidth
;
1905 const Point
arrowPoint(rightStroke
, yMidAligned
);
1906 if (rightStroke
> leftStroke
) {
1907 // When not enough room, don't draw the arrow shaft
1908 surface
->LineDraw(Point(leftStroke
, yMidAligned
), arrowPoint
, stroke
);
1911 // Draw the arrow head if needed
1912 if (vsDraw
.tabDrawMode
== TabDrawMode::LongArrow
) {
1913 XYPOSITION ydiff
= std::floor(rcTab
.Height() / 2.0f
);
1914 XYPOSITION xhead
= rightStroke
- ydiff
;
1915 if (xhead
<= rcTab
.left
) {
1916 ydiff
-= rcTab
.left
- xhead
;
1919 const Point ptsHead
[] = {
1920 Point(xhead
, yMidAligned
- ydiff
),
1922 Point(xhead
, yMidAligned
+ ydiff
)
1924 surface
->PolyLine(ptsHead
, std::size(ptsHead
), stroke
);
1928 void DrawIndicator(int indicNum
, Sci::Position startPos
, Sci::Position endPos
, Surface
*surface
, const ViewStyle
&vsDraw
,
1929 const LineLayout
*ll
, int xStart
, PRectangle rcLine
, Sci::Position secondCharacter
, int subLine
, Indicator::State state
,
1930 int value
, bool bidiEnabled
, int tabWidthMinimumPixels
) {
1932 const XYPOSITION subLineStart
= ll
->positions
[ll
->LineStart(subLine
)];
1933 const XYPOSITION horizontalOffset
= xStart
- subLineStart
;
1935 std::vector
<PRectangle
> rectangles
;
1937 const XYPOSITION left
= ll
->XInLine(startPos
) + horizontalOffset
;
1938 const XYPOSITION right
= ll
->XInLine(endPos
) + horizontalOffset
;
1939 const PRectangle
rcIndic(left
, rcLine
.top
+ vsDraw
.maxAscent
, right
,
1940 std::max(rcLine
.top
+ vsDraw
.maxAscent
+ 3, rcLine
.bottom
));
1943 ScreenLine
screenLine(ll
, subLine
, vsDraw
, rcLine
.right
- xStart
, tabWidthMinimumPixels
);
1944 const Range lineRange
= ll
->SubLineRange(subLine
, LineLayout::Scope::visibleOnly
);
1946 std::unique_ptr
<IScreenLineLayout
> slLayout
= surface
->Layout(&screenLine
);
1947 std::vector
<Interval
> intervals
= slLayout
->FindRangeIntervals(
1948 startPos
- lineRange
.start
, endPos
- lineRange
.start
);
1949 for (const Interval
&interval
: intervals
) {
1950 PRectangle rcInterval
= rcIndic
;
1951 rcInterval
.left
= interval
.left
+ xStart
;
1952 rcInterval
.right
= interval
.right
+ xStart
;
1953 rectangles
.push_back(rcInterval
);
1956 rectangles
.push_back(rcIndic
);
1959 for (const PRectangle
&rc
: rectangles
) {
1960 PRectangle rcFirstCharacter
= rc
;
1961 // Allow full descent space for character indicators
1962 rcFirstCharacter
.bottom
= rcLine
.top
+ vsDraw
.maxAscent
+ vsDraw
.maxDescent
;
1963 if (secondCharacter
>= 0) {
1964 rcFirstCharacter
.right
= ll
->XInLine(secondCharacter
) + horizontalOffset
;
1966 // Indicator continued from earlier line so make an empty box and don't draw
1967 rcFirstCharacter
.right
= rcFirstCharacter
.left
;
1969 vsDraw
.indicators
[indicNum
].Draw(surface
, rc
, rcLine
, rcFirstCharacter
, state
, value
);
1973 void DrawIndicators(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
1974 Sci::Line line
, int xStart
, PRectangle rcLine
, int subLine
, Sci::Position lineEnd
, bool under
, int tabWidthMinimumPixels
) {
1976 const Sci::Position posLineStart
= model
.pdoc
->LineStart(line
);
1977 const Sci::Position lineStart
= ll
->LineStart(subLine
);
1978 const Sci::Position posLineEnd
= posLineStart
+ lineEnd
;
1980 for (const IDecoration
*deco
: model
.pdoc
->decorations
->View()) {
1981 if (under
== vsDraw
.indicators
[deco
->Indicator()].under
) {
1982 Sci::Position startPos
= posLineStart
+ lineStart
;
1983 while (startPos
< posLineEnd
) {
1984 const Range
rangeRun(deco
->StartRun(startPos
), deco
->EndRun(startPos
));
1985 const Sci::Position endPos
= std::min(rangeRun
.end
, posLineEnd
);
1986 const int value
= deco
->ValueAt(startPos
);
1988 const bool hover
= vsDraw
.indicators
[deco
->Indicator()].IsDynamic() &&
1989 rangeRun
.ContainsCharacter(model
.hoverIndicatorPos
);
1990 const Indicator::State state
= hover
? Indicator::State::hover
: Indicator::State::normal
;
1991 const Sci::Position posSecond
= model
.pdoc
->MovePositionOutsideChar(rangeRun
.First() + 1, 1);
1992 DrawIndicator(deco
->Indicator(), startPos
- posLineStart
, endPos
- posLineStart
,
1993 surface
, vsDraw
, ll
, xStart
, rcLine
, posSecond
- posLineStart
, subLine
, state
,
1994 value
, model
.BidirectionalEnabled(), tabWidthMinimumPixels
);
2001 // Use indicators to highlight matching braces
2002 if ((vsDraw
.braceHighlightIndicatorSet
&& (model
.bracesMatchStyle
== StyleBraceLight
)) ||
2003 (vsDraw
.braceBadLightIndicatorSet
&& (model
.bracesMatchStyle
== StyleBraceBad
))) {
2004 const int braceIndicator
= (model
.bracesMatchStyle
== StyleBraceLight
) ? vsDraw
.braceHighlightIndicator
: vsDraw
.braceBadLightIndicator
;
2005 if (under
== vsDraw
.indicators
[braceIndicator
].under
) {
2006 const Range
rangeLine(posLineStart
+ lineStart
, posLineEnd
);
2007 for (size_t brace
= 0; brace
<= 1; brace
++) {
2008 if (rangeLine
.ContainsCharacter(model
.braces
[brace
])) {
2009 const Sci::Position braceOffset
= model
.braces
[brace
] - posLineStart
;
2010 if (braceOffset
< ll
->numCharsInLine
) {
2011 const Sci::Position braceEnd
= model
.pdoc
->MovePositionOutsideChar(model
.braces
[brace
] + 1, 1) - posLineStart
;
2012 DrawIndicator(braceIndicator
, braceOffset
, braceEnd
,
2013 surface
, vsDraw
, ll
, xStart
, rcLine
, braceEnd
, subLine
, Indicator::State::normal
,
2014 1, model
.BidirectionalEnabled(), tabWidthMinimumPixels
);
2021 if (FlagSet(model
.changeHistoryOption
, ChangeHistoryOption::Indicators
)) {
2023 constexpr int indexHistory
= static_cast<int>(IndicatorNumbers::HistoryRevertedToOriginInsertion
);
2026 Sci::Position startPos
= posLineStart
+ lineStart
;
2027 while (startPos
< posLineEnd
) {
2028 const Range
rangeRun(startPos
, model
.pdoc
->EditionEndRun(startPos
));
2029 const Sci::Position endPos
= std::min(rangeRun
.end
, posLineEnd
);
2030 const int edition
= model
.pdoc
->EditionAt(startPos
);
2032 const int indicator
= (edition
- 1) * 2 + indexHistory
;
2033 const Sci::Position posSecond
= model
.pdoc
->MovePositionOutsideChar(rangeRun
.First() + 1, 1);
2034 DrawIndicator(indicator
, startPos
- posLineStart
, endPos
- posLineStart
,
2035 surface
, vsDraw
, ll
, xStart
, rcLine
, posSecond
- posLineStart
, subLine
, Indicator::State::normal
,
2036 1, model
.BidirectionalEnabled(), tabWidthMinimumPixels
);
2043 Sci::Position startPos
= posLineStart
+ lineStart
;
2044 while (startPos
<= posLineEnd
) {
2045 const unsigned int editions
= model
.pdoc
->EditionDeletesAt(startPos
);
2046 const Sci::Position posSecond
= model
.pdoc
->MovePositionOutsideChar(startPos
+ 1, 1);
2047 for (unsigned int edition
= 0; edition
< 4; edition
++) {
2048 if (editions
& (1 << edition
)) {
2049 const int indicator
= edition
* 2 + indexHistory
+ 1;
2050 DrawIndicator(indicator
, startPos
- posLineStart
, posSecond
- posLineStart
,
2051 surface
, vsDraw
, ll
, xStart
, rcLine
, posSecond
- posLineStart
, subLine
, Indicator::State::normal
,
2052 1, model
.BidirectionalEnabled(), tabWidthMinimumPixels
);
2055 startPos
= model
.pdoc
->EditionNextDelete(startPos
);
2061 void DrawFoldLines(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
2062 Sci::Line line
, PRectangle rcLine
, int subLine
) {
2063 const bool lastSubLine
= subLine
== (ll
->lines
- 1);
2064 const bool expanded
= model
.pcs
->GetExpanded(line
);
2065 const FoldLevel level
= model
.pdoc
->GetFoldLevel(line
);
2066 const FoldLevel levelNext
= model
.pdoc
->GetFoldLevel(line
+ 1);
2067 if (LevelIsHeader(level
) &&
2068 (LevelNumber(level
) < LevelNumber(levelNext
))) {
2069 const ColourRGBA foldLineColour
= vsDraw
.ElementColour(Element::FoldLine
).value_or(
2070 vsDraw
.styles
[StyleDefault
].fore
);
2071 // Paint the line above the fold
2072 if ((subLine
== 0) && FlagSet(model
.foldFlags
, (expanded
? FoldFlag::LineBeforeExpanded
: FoldFlag::LineBeforeContracted
))) {
2073 surface
->FillRectangleAligned(Side(rcLine
, Edge::top
, 1.0), foldLineColour
);
2075 // Paint the line below the fold
2076 if (lastSubLine
&& FlagSet(model
.foldFlags
, (expanded
? FoldFlag::LineAfterExpanded
: FoldFlag::LineAfterContracted
))) {
2077 surface
->FillRectangleAligned(Side(rcLine
, Edge::bottom
, 1.0), foldLineColour
);
2078 // If contracted fold line drawn then don't overwrite with hidden line
2079 // as fold lines are more specific then hidden lines.
2085 if (lastSubLine
&& model
.pcs
->GetVisible(line
) && !model
.pcs
->GetVisible(line
+ 1)) {
2086 if (const ColourOptional hiddenLineColour
= vsDraw
.ElementColour(Element::HiddenLine
)) {
2087 surface
->FillRectangleAligned(Side(rcLine
, Edge::bottom
, 1.0), *hiddenLineColour
);
2092 ColourRGBA
InvertedLight(ColourRGBA orig
) noexcept
{
2093 unsigned int r
= orig
.GetRed();
2094 unsigned int g
= orig
.GetGreen();
2095 unsigned int b
= orig
.GetBlue();
2096 const unsigned int l
= (r
+ g
+ b
) / 3; // There is a better calculation for this that matches human eye
2097 const unsigned int il
= 0xff - l
;
2103 return ColourRGBA(std::min(r
, 0xffu
), std::min(g
, 0xffu
), std::min(b
, 0xffu
));
2108 void EditView::DrawIndentGuide(Surface
*surface
, XYPOSITION start
, PRectangle rcSegment
, bool highlight
, bool offset
) {
2109 const Point from
= Point::FromInts(0, offset
? 1 : 0);
2110 const PRectangle
rcCopyArea(start
+ 1, rcSegment
.top
,
2111 start
+ 2, rcSegment
.bottom
);
2112 surface
->Copy(rcCopyArea
, from
,
2113 highlight
? *pixmapIndentGuideHighlight
: *pixmapIndentGuide
);
2116 void EditView::DrawForeground(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
2117 int xStart
, PRectangle rcLine
, int subLine
, Sci::Line lineVisible
, Range lineRange
, Sci::Position posLineStart
,
2118 ColourOptional background
) {
2120 const bool selBackDrawn
= vsDraw
.SelectionBackgroundDrawn();
2121 const bool drawWhitespaceBackground
= vsDraw
.WhitespaceBackgroundDrawn() && !background
;
2122 bool inIndentation
= subLine
== 0; // Do not handle indentation except on first subline.
2124 const XYPOSITION subLineStart
= ll
->positions
[lineRange
.start
];
2125 const XYPOSITION horizontalOffset
= xStart
- subLineStart
;
2126 const XYPOSITION indentWidth
= model
.pdoc
->IndentSize() * vsDraw
.spaceWidth
;
2128 // Does not take margin into account but not significant
2129 const XYPOSITION xStartVisible
= subLineStart
- xStart
;
2131 // When lineHeight is odd, dotted indent guides are drawn offset by 1 on odd lines to join together.
2132 const bool offsetGuide
= (lineVisible
& 1) && (vsDraw
.lineHeight
& 1);
2134 // Same baseline used for all text
2135 const XYPOSITION ybase
= rcLine
.top
+ vsDraw
.maxAscent
;
2137 // Foreground drawing loop
2138 const BreakFinder::BreakFor breakFor
= (((phasesDraw
== PhasesDraw::One
) && selBackDrawn
) || vsDraw
.SelectionTextDrawn())
2139 ? BreakFinder::BreakFor::ForegroundAndSelection
: BreakFinder::BreakFor::Foreground
;
2140 BreakFinder
bfFore(ll
, &model
.sel
, lineRange
, posLineStart
, xStartVisible
, breakFor
, model
.pdoc
, model
.reprs
.get(), &vsDraw
);
2142 while (bfFore
.More()) {
2144 const TextSegment ts
= bfFore
.Next();
2145 const Sci::Position i
= ts
.end() - 1;
2146 const Sci::Position iDoc
= i
+ posLineStart
;
2148 const Interval horizontal
= ll
->Span(ts
.start
, ts
.end()).Offset(horizontalOffset
);
2149 // Only try to draw if really visible - enhances performance by not calling environment to
2150 // draw strings that are completely past the right side of the window.
2151 if (rcLine
.Intersects(horizontal
)) {
2152 const PRectangle rcSegment
= rcLine
.WithHorizontalBounds(horizontal
);
2153 const int styleMain
= ll
->styles
[i
];
2154 ColourRGBA textFore
= vsDraw
.styles
[styleMain
].fore
;
2155 const Font
*textFont
= vsDraw
.styles
[styleMain
].font
.get();
2156 // Hot-spot foreground
2157 const bool inHotspot
= model
.hotspot
.Valid() && model
.hotspot
.ContainsCharacter(iDoc
);
2159 if (const ColourOptional colourHotSpot
= vsDraw
.ElementColour(Element::HotSpotActive
)) {
2160 textFore
= *colourHotSpot
;
2163 if (vsDraw
.indicatorsSetFore
) {
2164 // At least one indicator sets the text colour so see if it applies to this segment
2165 for (const IDecoration
*deco
: model
.pdoc
->decorations
->View()) {
2166 const int indicatorValue
= deco
->ValueAt(ts
.start
+ posLineStart
);
2167 if (indicatorValue
) {
2168 const Indicator
&indicator
= vsDraw
.indicators
[deco
->Indicator()];
2170 if (indicator
.IsDynamic()) {
2171 const Sci::Position startPos
= ts
.start
+ posLineStart
;
2172 const Range
rangeRun(deco
->StartRun(startPos
), deco
->EndRun(startPos
));
2173 hover
= rangeRun
.ContainsCharacter(model
.hoverIndicatorPos
);
2176 if (indicator
.sacHover
.style
== IndicatorStyle::TextFore
) {
2177 textFore
= indicator
.sacHover
.fore
;
2180 if (indicator
.sacNormal
.style
== IndicatorStyle::TextFore
) {
2181 if (FlagSet(indicator
.Flags(), IndicFlag::ValueFore
))
2182 textFore
= ColourRGBA::FromRGB(indicatorValue
& static_cast<int>(IndicValue::Mask
));
2184 textFore
= indicator
.sacNormal
.fore
;
2190 InSelection inSelection
= vsDraw
.selection
.visible
? model
.sel
.CharacterInSelection(iDoc
) : InSelection::inNone
;
2191 if (FlagSet(vsDraw
.caret
.style
, CaretStyle::Curses
) && (inSelection
== InSelection::inMain
))
2192 inSelection
= CharacterInCursesSelection(iDoc
, model
, vsDraw
);
2193 if (const ColourOptional selectionFore
= SelectionForeground(model
, vsDraw
, inSelection
)) {
2194 textFore
= *selectionFore
;
2196 ColourRGBA textBack
= TextBackground(model
, vsDraw
, ll
, background
, inSelection
, inHotspot
, styleMain
, i
);
2197 if (ts
.representation
) {
2198 if (ll
->chars
[i
] == '\t') {
2200 if (phasesDraw
== PhasesDraw::One
) {
2201 if (drawWhitespaceBackground
&& vsDraw
.WhiteSpaceVisible(inIndentation
))
2202 textBack
= vsDraw
.ElementColourForced(Element::WhiteSpaceBack
).Opaque();
2203 surface
->FillRectangleAligned(rcSegment
, Fill(textBack
));
2205 if (inIndentation
&& vsDraw
.viewIndentationGuides
== IndentView::Real
) {
2206 const Interval intervalCharacter
= ll
->SpanByte(static_cast<int>(i
));
2207 for (int indentCount
= static_cast<int>((intervalCharacter
.left
+ epsilon
) / indentWidth
);
2208 indentCount
<= (intervalCharacter
.right
- epsilon
) / indentWidth
;
2210 if (indentCount
> 0) {
2211 const XYPOSITION xIndent
= std::floor(indentCount
* indentWidth
);
2212 DrawIndentGuide(surface
, xIndent
+ xStart
, rcSegment
, ll
->xHighlightGuide
== xIndent
, offsetGuide
);
2216 if (vsDraw
.viewWhitespace
!= WhiteSpace::Invisible
) {
2217 if (vsDraw
.WhiteSpaceVisible(inIndentation
)) {
2218 const PRectangle
rcTab(rcSegment
.left
+ 1, rcSegment
.top
+ tabArrowHeight
,
2219 rcSegment
.right
- 1, rcSegment
.bottom
- vsDraw
.maxDescent
);
2220 const int segmentTop
= static_cast<int>(rcSegment
.top
) + vsDraw
.lineHeight
/ 2;
2221 const ColourRGBA whiteSpaceFore
= vsDraw
.ElementColour(Element::WhiteSpace
).value_or(textFore
);
2222 if (!customDrawTabArrow
)
2223 DrawTabArrow(surface
, rcTab
, segmentTop
, vsDraw
, Stroke(whiteSpaceFore
, 1.0f
));
2225 customDrawTabArrow(surface
, rcTab
, segmentTop
, vsDraw
, Stroke(whiteSpaceFore
, 1.0f
));
2229 inIndentation
= false;
2230 if (vsDraw
.controlCharSymbol
>= 32) {
2231 // Using one font for all control characters so it can be controlled independently to ensure
2232 // the box goes around the characters tightly. Seems to be no way to work out what height
2233 // is taken by an individual character - internal leading gives varying results.
2234 const Font
*ctrlCharsFont
= vsDraw
.styles
[StyleControlChar
].font
.get();
2235 const char cc
[2] = { static_cast<char>(vsDraw
.controlCharSymbol
), '\0' };
2236 surface
->DrawTextNoClip(rcSegment
, ctrlCharsFont
,
2237 ybase
, cc
, textBack
, textFore
);
2239 if (FlagSet(ts
.representation
->appearance
, RepresentationAppearance::Colour
)) {
2240 textFore
= ts
.representation
->colour
;
2242 if (FlagSet(ts
.representation
->appearance
, RepresentationAppearance::Blob
)) {
2243 DrawTextBlob(surface
, vsDraw
, rcSegment
, ts
.representation
->stringRep
,
2244 textBack
, textFore
, phasesDraw
== PhasesDraw::One
);
2246 surface
->DrawTextTransparentUTF8(rcSegment
, vsDraw
.styles
[StyleControlChar
].font
.get(),
2247 ybase
, ts
.representation
->stringRep
, textFore
);
2252 // Normal text display
2253 if (vsDraw
.styles
[styleMain
].visible
) {
2254 const std::string_view
text(&ll
->chars
[ts
.start
], i
- ts
.start
+ 1);
2255 if (phasesDraw
!= PhasesDraw::One
) {
2256 surface
->DrawTextTransparent(rcSegment
, textFont
,
2257 ybase
, text
, textFore
);
2259 surface
->DrawTextNoClip(rcSegment
, textFont
,
2260 ybase
, text
, textFore
, textBack
);
2262 } else if (vsDraw
.styles
[styleMain
].invisibleRepresentation
[0]) {
2263 const std::string_view text
= vsDraw
.styles
[styleMain
].invisibleRepresentation
;
2264 if (phasesDraw
!= PhasesDraw::One
) {
2265 surface
->DrawTextTransparentUTF8(rcSegment
, textFont
,
2266 ybase
, text
, textFore
);
2268 surface
->DrawTextNoClipUTF8(rcSegment
, textFont
,
2269 ybase
, text
, textFore
, textBack
);
2272 if (vsDraw
.viewWhitespace
!= WhiteSpace::Invisible
||
2273 (inIndentation
&& vsDraw
.viewIndentationGuides
!= IndentView::None
)) {
2274 for (int cpos
= 0; cpos
<= i
- ts
.start
; cpos
++) {
2275 if (ll
->chars
[cpos
+ ts
.start
] == ' ') {
2276 if (vsDraw
.viewWhitespace
!= WhiteSpace::Invisible
) {
2277 if (vsDraw
.WhiteSpaceVisible(inIndentation
)) {
2278 const Interval intervalSpace
= ll
->SpanByte(cpos
+ ts
.start
).Offset(horizontalOffset
);
2279 const XYPOSITION xmid
= (intervalSpace
.left
+ intervalSpace
.right
) / 2;
2280 if ((phasesDraw
== PhasesDraw::One
) && drawWhitespaceBackground
) {
2281 textBack
= vsDraw
.ElementColourForced(Element::WhiteSpaceBack
).Opaque();
2282 const PRectangle rcSpace
= rcLine
.WithHorizontalBounds(intervalSpace
);
2283 surface
->FillRectangleAligned(rcSpace
, Fill(textBack
));
2285 const int halfDotWidth
= vsDraw
.whitespaceSize
/ 2;
2286 PRectangle
rcDot(xmid
- halfDotWidth
,
2287 rcSegment
.top
+ vsDraw
.lineHeight
/ 2, 0.0f
, 0.0f
);
2288 rcDot
.right
= rcDot
.left
+ vsDraw
.whitespaceSize
;
2289 rcDot
.bottom
= rcDot
.top
+ vsDraw
.whitespaceSize
;
2290 const ColourRGBA whiteSpaceFore
= vsDraw
.ElementColour(Element::WhiteSpace
).value_or(textFore
);
2291 surface
->FillRectangleAligned(rcDot
, Fill(whiteSpaceFore
));
2294 if (inIndentation
&& vsDraw
.viewIndentationGuides
== IndentView::Real
) {
2295 const Interval intervalCharacter
= ll
->SpanByte(cpos
+ ts
.start
);
2296 for (int indentCount
= static_cast<int>((intervalCharacter
.left
+ epsilon
) / indentWidth
);
2297 indentCount
<= (intervalCharacter
.right
- epsilon
) / indentWidth
;
2299 if (indentCount
> 0) {
2300 const XYPOSITION xIndent
= std::floor(indentCount
* indentWidth
);
2301 DrawIndentGuide(surface
, xIndent
+ xStart
, rcSegment
, ll
->xHighlightGuide
== xIndent
, offsetGuide
);
2306 inIndentation
= false;
2311 if ((inHotspot
&& vsDraw
.hotspotUnderline
) || vsDraw
.styles
[styleMain
].underline
) {
2312 PRectangle rcUL
= rcSegment
;
2313 rcUL
.top
= ybase
+ 1;
2314 rcUL
.bottom
= ybase
+ 2;
2315 ColourRGBA colourUnderline
= textFore
;
2316 if (inHotspot
&& vsDraw
.hotspotUnderline
) {
2317 colourUnderline
= vsDraw
.ElementColour(Element::HotSpotActive
).value_or(textFore
);
2319 surface
->FillRectangleAligned(rcUL
, colourUnderline
);
2321 } else if (horizontal
.left
> rcLine
.right
) {
2327 void EditView::DrawIndentGuidesOverEmpty(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
2328 Sci::Line line
, int xStart
, PRectangle rcLine
, int subLine
, Sci::Line lineVisible
) {
2329 if ((vsDraw
.viewIndentationGuides
== IndentView::LookForward
|| vsDraw
.viewIndentationGuides
== IndentView::LookBoth
)
2330 && (subLine
== 0)) {
2331 const Sci::Position posLineStart
= model
.pdoc
->LineStart(line
);
2332 int indentSpace
= model
.pdoc
->GetLineIndentation(line
);
2333 int xStartText
= static_cast<int>(ll
->positions
[model
.pdoc
->GetLineIndentPosition(line
) - posLineStart
]);
2335 // Find the most recent line with some text
2337 Sci::Line lineLastWithText
= line
;
2338 while (lineLastWithText
> std::max(line
- 20, static_cast<Sci::Line
>(0)) && model
.pdoc
->IsWhiteLine(lineLastWithText
)) {
2341 if (lineLastWithText
< line
) {
2342 xStartText
= 100000; // Don't limit to visible indentation on empty line
2343 // This line is empty, so use indentation of last line with text
2344 int indentLastWithText
= model
.pdoc
->GetLineIndentation(lineLastWithText
);
2345 const int isFoldHeader
= LevelIsHeader(model
.pdoc
->GetFoldLevel(lineLastWithText
));
2347 // Level is one more level than parent
2348 indentLastWithText
+= model
.pdoc
->IndentSize();
2350 if (vsDraw
.viewIndentationGuides
== IndentView::LookForward
) {
2351 // In viLookForward mode, previous line only used if it is a fold header
2353 indentSpace
= std::max(indentSpace
, indentLastWithText
);
2355 } else { // viLookBoth
2356 indentSpace
= std::max(indentSpace
, indentLastWithText
);
2360 Sci::Line lineNextWithText
= line
;
2361 while (lineNextWithText
< std::min(line
+ 20, model
.pdoc
->LinesTotal()) && model
.pdoc
->IsWhiteLine(lineNextWithText
)) {
2364 if (lineNextWithText
> line
) {
2365 xStartText
= 100000; // Don't limit to visible indentation on empty line
2366 // This line is empty, so use indentation of first next line with text
2367 indentSpace
= std::max(indentSpace
,
2368 model
.pdoc
->GetLineIndentation(lineNextWithText
));
2371 const bool offsetGuide
= (lineVisible
& 1) && (vsDraw
.lineHeight
& 1);
2372 for (int indentPos
= model
.pdoc
->IndentSize(); indentPos
< indentSpace
; indentPos
+= model
.pdoc
->IndentSize()) {
2373 const XYPOSITION xIndent
= std::floor(indentPos
* vsDraw
.spaceWidth
);
2374 if (xIndent
< xStartText
) {
2375 DrawIndentGuide(surface
, xIndent
+ xStart
, rcLine
, ll
->xHighlightGuide
== xIndent
, offsetGuide
);
2381 void EditView::DrawLine(Surface
*surface
, const EditModel
&model
, const ViewStyle
&vsDraw
, const LineLayout
*ll
,
2382 Sci::Line line
, Sci::Line lineVisible
, int xStart
, PRectangle rcLine
, int subLine
, DrawPhase phase
) {
2384 if (subLine
>= ll
->lines
) {
2385 DrawAnnotation(surface
, model
, vsDraw
, ll
, line
, xStart
, rcLine
, subLine
, phase
);
2386 return; // No further drawing
2389 const bool clipLine
= !bufferedDraw
&& !LinesOverlap();
2391 surface
->SetClip(rcLine
);
2394 // See if something overrides the line background colour.
2395 const ColourOptional background
= vsDraw
.Background(model
.GetMark(line
), model
.caret
.active
, ll
->containsCaret
);
2397 const Sci::Position posLineStart
= model
.pdoc
->LineStart(line
);
2399 const Range lineRange
= ll
->SubLineRange(subLine
, LineLayout::Scope::visibleOnly
);
2400 const Range lineRangeIncludingEnd
= ll
->SubLineRange(subLine
, LineLayout::Scope::includeEnd
);
2401 const XYPOSITION subLineStart
= ll
->positions
[lineRange
.start
];
2403 if ((ll
->wrapIndent
!= 0) && (subLine
> 0)) {
2404 if (FlagSet(phase
, DrawPhase::back
)) {
2405 DrawWrapIndentAndMarker(surface
, vsDraw
, ll
, xStart
, rcLine
, background
, customDrawWrapMarker
, model
.caret
.active
);
2407 xStart
+= static_cast<int>(ll
->wrapIndent
);
2410 if (phasesDraw
!= PhasesDraw::One
) {
2411 if (FlagSet(phase
, DrawPhase::back
)) {
2412 DrawBackground(surface
, model
, vsDraw
, ll
,
2413 xStart
, rcLine
, subLine
, lineRange
, posLineStart
,
2415 DrawFoldDisplayText(surface
, model
, vsDraw
, ll
, line
, xStart
, rcLine
, subLine
, subLineStart
, DrawPhase::back
);
2416 DrawEOLAnnotationText(surface
, model
, vsDraw
, ll
, line
, xStart
, rcLine
, subLine
, subLineStart
, DrawPhase::back
);
2417 // Remove drawBack to not draw again in DrawFoldDisplayText
2418 phase
= static_cast<DrawPhase
>(static_cast<int>(phase
) & ~static_cast<int>(DrawPhase::back
));
2419 DrawEOL(surface
, model
, vsDraw
, ll
,
2420 line
, xStart
, rcLine
, subLine
, lineRange
.end
, subLineStart
, background
);
2421 if (vsDraw
.IsLineFrameOpaque(model
.caret
.active
, ll
->containsCaret
))
2422 DrawCaretLineFramed(surface
, vsDraw
, ll
, rcLine
, subLine
);
2425 if (FlagSet(phase
, DrawPhase::indicatorsBack
)) {
2426 DrawIndicators(surface
, model
, vsDraw
, ll
, line
, xStart
, rcLine
, subLine
,
2427 lineRangeIncludingEnd
.end
, true, tabWidthMinimumPixels
);
2428 DrawEdgeLine(surface
, vsDraw
, ll
, xStart
, rcLine
, lineRange
);
2429 DrawMarkUnderline(surface
, model
, vsDraw
, line
, rcLine
);
2433 if (FlagSet(phase
, DrawPhase::text
)) {
2434 if (vsDraw
.selection
.visible
) {
2435 DrawTranslucentSelection(surface
, model
, vsDraw
, ll
,
2436 line
, xStart
, rcLine
, subLine
, lineRange
, tabWidthMinimumPixels
, Layer::UnderText
);
2438 DrawTranslucentLineState(surface
, model
, vsDraw
, ll
, line
, rcLine
, subLine
, Layer::UnderText
);
2439 DrawForeground(surface
, model
, vsDraw
, ll
,
2440 xStart
, rcLine
, subLine
, lineVisible
, lineRange
, posLineStart
,
2444 if (FlagSet(phase
, DrawPhase::indentationGuides
)) {
2445 DrawIndentGuidesOverEmpty(surface
, model
, vsDraw
, ll
, line
, xStart
, rcLine
, subLine
, lineVisible
);
2448 if (FlagSet(phase
, DrawPhase::indicatorsFore
)) {
2449 DrawIndicators(surface
, model
, vsDraw
, ll
, line
, xStart
, rcLine
, subLine
,
2450 lineRangeIncludingEnd
.end
, false, tabWidthMinimumPixels
);
2453 DrawFoldDisplayText(surface
, model
, vsDraw
, ll
, line
, xStart
, rcLine
, subLine
, subLineStart
, phase
);
2454 DrawEOLAnnotationText(surface
, model
, vsDraw
, ll
, line
, xStart
, rcLine
, subLine
, subLineStart
, phase
);
2456 if (phasesDraw
== PhasesDraw::One
) {
2457 DrawEOL(surface
, model
, vsDraw
, ll
,
2458 line
, xStart
, rcLine
, subLine
, lineRange
.end
, subLineStart
, background
);
2459 if (vsDraw
.IsLineFrameOpaque(model
.caret
.active
, ll
->containsCaret
))
2460 DrawCaretLineFramed(surface
, vsDraw
, ll
, rcLine
, subLine
);
2461 DrawEdgeLine(surface
, vsDraw
, ll
, xStart
, rcLine
, lineRange
);
2462 DrawMarkUnderline(surface
, model
, vsDraw
, line
, rcLine
);
2465 if (vsDraw
.selection
.visible
&& FlagSet(phase
, DrawPhase::selectionTranslucent
)) {
2466 DrawTranslucentSelection(surface
, model
, vsDraw
, ll
,
2467 line
, xStart
, rcLine
, subLine
, lineRange
, tabWidthMinimumPixels
, Layer::OverText
);
2470 if (FlagSet(phase
, DrawPhase::lineTranslucent
)) {
2471 DrawTranslucentLineState(surface
, model
, vsDraw
, ll
, line
, rcLine
, subLine
, Layer::OverText
);
2479 void EditView::PaintText(Surface
*surfaceWindow
, const EditModel
&model
, const ViewStyle
&vsDraw
,
2480 PRectangle rcArea
, PRectangle rcClient
) {
2481 // Allow text at start of line to overlap 1 pixel into the margin as this displays
2482 // serifs and italic stems for aliased text.
2483 const int leftTextOverlap
= ((model
.xOffset
== 0) && (vsDraw
.leftMarginWidth
> 0)) ? 1 : 0;
2486 if (rcArea
.right
> vsDraw
.textStart
- leftTextOverlap
) {
2488 Surface
*surface
= surfaceWindow
;
2490 surface
= pixmapLine
.get();
2491 PLATFORM_ASSERT(pixmapLine
->Initialised());
2493 surface
->SetMode(model
.CurrentSurfaceMode());
2495 const Point ptOrigin
= model
.GetVisibleOriginInMain();
2497 const int screenLinePaintFirst
= static_cast<int>(rcArea
.top
) / vsDraw
.lineHeight
;
2498 const int xStart
= vsDraw
.textStart
- model
.xOffset
+ static_cast<int>(ptOrigin
.x
);
2500 const SelectionPosition posCaret
= model
.posDrag
.IsValid() ? model
.posDrag
: model
.sel
.RangeMain().caret
;
2501 const Sci::Line lineCaret
= model
.pdoc
->SciLineFromPosition(posCaret
.Position());
2502 const int caretOffset
= static_cast<int>(posCaret
.Position() - model
.pdoc
->LineStart(lineCaret
));
2504 PRectangle rcTextArea
= rcClient
;
2505 if (vsDraw
.marginInside
) {
2506 rcTextArea
.left
+= vsDraw
.textStart
;
2507 rcTextArea
.right
-= vsDraw
.rightMarginWidth
;
2509 rcTextArea
= rcArea
;
2512 // Remove selection margin from drawing area so text will not be drawn
2513 // on it in unbuffered mode.
2514 const bool clipping
= !bufferedDraw
&& vsDraw
.marginInside
;
2516 PRectangle rcClipText
= rcTextArea
;
2517 rcClipText
.left
-= leftTextOverlap
;
2518 surfaceWindow
->SetClip(rcClipText
);
2521 // Loop on visible lines
2522 #if defined(TIME_PAINTING)
2523 double durLayout
= 0.0;
2524 double durPaint
= 0.0;
2525 double durCopy
= 0.0;
2526 ElapsedPeriod epWhole
;
2528 const bool bracesIgnoreStyle
= ((vsDraw
.braceHighlightIndicatorSet
&& (model
.bracesMatchStyle
== StyleBraceLight
)) ||
2529 (vsDraw
.braceBadLightIndicatorSet
&& (model
.bracesMatchStyle
== StyleBraceBad
)));
2531 Sci::Line lineDocPrevious
= -1; // Used to avoid laying out one document line multiple times
2532 std::shared_ptr
<LineLayout
> ll
;
2533 std::vector
<DrawPhase
> phases
;
2534 if ((phasesDraw
== PhasesDraw::Multiple
) && !bufferedDraw
) {
2535 for (DrawPhase phase
= DrawPhase::back
; phase
<= DrawPhase::carets
; phase
= static_cast<DrawPhase
>(static_cast<int>(phase
) * 2)) {
2536 phases
.push_back(phase
);
2539 phases
.push_back(DrawPhase::all
);
2541 for (const DrawPhase
&phase
: phases
) {
2544 ypos
+= screenLinePaintFirst
* vsDraw
.lineHeight
;
2545 int yposScreen
= screenLinePaintFirst
* vsDraw
.lineHeight
;
2546 Sci::Line visibleLine
= model
.TopLineOfMain() + screenLinePaintFirst
;
2547 while (visibleLine
< model
.pcs
->LinesDisplayed() && yposScreen
< rcArea
.bottom
) {
2549 const Sci::Line lineDoc
= model
.pcs
->DocFromDisplay(visibleLine
);
2550 // Only visible lines should be handled by the code within the loop
2551 PLATFORM_ASSERT(model
.pcs
->GetVisible(lineDoc
));
2552 const Sci::Line lineStartSet
= model
.pcs
->DisplayFromDoc(lineDoc
);
2553 const int subLine
= static_cast<int>(visibleLine
- lineStartSet
);
2555 // Copy this line and its styles from the document into local arrays
2556 // and determine the x position at which each character starts.
2557 #if defined(TIME_PAINTING)
2560 if (lineDoc
!= lineDocPrevious
) {
2561 ll
= RetrieveLineLayout(lineDoc
, model
);
2562 LayoutLine(model
, surface
, vsDraw
, ll
.get(), model
.wrapWidth
);
2563 lineDocPrevious
= lineDoc
;
2565 #if defined(TIME_PAINTING)
2566 durLayout
+= ep
.Duration(true);
2569 ll
->containsCaret
= vsDraw
.selection
.visible
&& (lineDoc
== lineCaret
)
2570 && (ll
->lines
== 1 || !vsDraw
.caretLine
.subLine
|| ll
->InLine(caretOffset
, subLine
));
2572 PRectangle rcLine
= rcTextArea
;
2573 rcLine
.top
= static_cast<XYPOSITION
>(ypos
);
2574 rcLine
.bottom
= static_cast<XYPOSITION
>(ypos
+ vsDraw
.lineHeight
);
2576 const Range
rangeLine(model
.pdoc
->LineStart(lineDoc
),
2577 model
.pdoc
->LineStart(lineDoc
+ 1));
2579 // Highlight the current braces if any
2580 ll
->SetBracesHighlight(rangeLine
, model
.braces
, static_cast<char>(model
.bracesMatchStyle
),
2581 static_cast<int>(model
.highlightGuideColumn
* vsDraw
.spaceWidth
), bracesIgnoreStyle
);
2583 if (leftTextOverlap
&& (bufferedDraw
|| ((phasesDraw
< PhasesDraw::Multiple
) && (FlagSet(phase
, DrawPhase::back
))))) {
2584 // Clear the left margin
2585 PRectangle rcSpacer
= rcLine
;
2586 rcSpacer
.right
= rcSpacer
.left
;
2588 surface
->FillRectangleAligned(rcSpacer
, Fill(vsDraw
.styles
[StyleDefault
].back
));
2591 if (model
.BidirectionalEnabled()) {
2592 // Fill the line bidi data
2593 UpdateBidiData(model
, vsDraw
, ll
.get());
2596 DrawLine(surface
, model
, vsDraw
, ll
.get(), lineDoc
, visibleLine
, xStart
, rcLine
, subLine
, phase
);
2597 #if defined(TIME_PAINTING)
2598 durPaint
+= ep
.Duration(true);
2600 // Restore the previous styles for the brace highlights in case layout is in cache.
2601 ll
->RestoreBracesHighlight(rangeLine
, model
.braces
, bracesIgnoreStyle
);
2603 if (FlagSet(phase
, DrawPhase::foldLines
)) {
2604 DrawFoldLines(surface
, model
, vsDraw
, ll
.get(), lineDoc
, rcLine
, subLine
);
2607 if (FlagSet(phase
, DrawPhase::carets
)) {
2608 DrawCarets(surface
, model
, vsDraw
, ll
.get(), lineDoc
, xStart
, rcLine
, subLine
);
2612 const Point from
= Point::FromInts(vsDraw
.textStart
- leftTextOverlap
, 0);
2613 const PRectangle rcCopyArea
= PRectangle::FromInts(vsDraw
.textStart
- leftTextOverlap
, yposScreen
,
2614 static_cast<int>(rcClient
.right
- vsDraw
.rightMarginWidth
),
2615 yposScreen
+ vsDraw
.lineHeight
);
2616 pixmapLine
->FlushDrawing();
2617 surfaceWindow
->Copy(rcCopyArea
, from
, *pixmapLine
);
2620 lineWidthMaxSeen
= std::max(
2621 lineWidthMaxSeen
, static_cast<int>(ll
->positions
[ll
->numCharsInLine
]));
2622 #if defined(TIME_PAINTING)
2623 durCopy
+= ep
.Duration(true);
2627 if (!bufferedDraw
) {
2628 ypos
+= vsDraw
.lineHeight
;
2631 yposScreen
+= vsDraw
.lineHeight
;
2636 #if defined(TIME_PAINTING)
2637 if (durPaint
< 0.00000001)
2638 durPaint
= 0.00000001;
2640 // Right column limit indicator
2641 PRectangle rcBeyondEOF
= (vsDraw
.marginInside
) ? rcClient
: rcArea
;
2642 rcBeyondEOF
.left
= static_cast<XYPOSITION
>(vsDraw
.textStart
);
2643 rcBeyondEOF
.right
= rcBeyondEOF
.right
- ((vsDraw
.marginInside
) ? vsDraw
.rightMarginWidth
: 0);
2644 rcBeyondEOF
.top
= static_cast<XYPOSITION
>((model
.pcs
->LinesDisplayed() - model
.TopLineOfMain()) * vsDraw
.lineHeight
);
2645 if (rcBeyondEOF
.top
< rcBeyondEOF
.bottom
) {
2646 surfaceWindow
->FillRectangleAligned(rcBeyondEOF
, Fill(vsDraw
.styles
[StyleDefault
].back
));
2647 if (vsDraw
.edgeState
== EdgeVisualStyle::Line
) {
2648 const int edgeX
= static_cast<int>(vsDraw
.theEdge
.column
* vsDraw
.spaceWidth
);
2649 rcBeyondEOF
.left
= static_cast<XYPOSITION
>(edgeX
+ xStart
);
2650 rcBeyondEOF
.right
= rcBeyondEOF
.left
+ 1;
2651 surfaceWindow
->FillRectangleAligned(rcBeyondEOF
, Fill(vsDraw
.theEdge
.colour
));
2652 } else if (vsDraw
.edgeState
== EdgeVisualStyle::MultiLine
) {
2653 for (size_t edge
= 0; edge
< vsDraw
.theMultiEdge
.size(); edge
++) {
2654 if (vsDraw
.theMultiEdge
[edge
].column
>= 0) {
2655 const int edgeX
= static_cast<int>(vsDraw
.theMultiEdge
[edge
].column
* vsDraw
.spaceWidth
);
2656 rcBeyondEOF
.left
= static_cast<XYPOSITION
>(edgeX
+ xStart
);
2657 rcBeyondEOF
.right
= rcBeyondEOF
.left
+ 1;
2658 surfaceWindow
->FillRectangleAligned(rcBeyondEOF
, Fill(vsDraw
.theMultiEdge
[edge
].colour
));
2665 surfaceWindow
->PopClip();
2667 //Platform::DebugPrintf("start display %d, offset = %d\n", model.pdoc->Length(), model.xOffset);
2668 #if defined(TIME_PAINTING)
2669 Platform::DebugPrintf(
2670 "Layout:%9.6g Paint:%9.6g Ratio:%9.6g Copy:%9.6g Total:%9.6g\n",
2671 durLayout
, durPaint
, durLayout
/ durPaint
, durCopy
, epWhole
.Duration());
2676 // Space (3 space characters) between line numbers and text when printing.
2677 #define lineNumberPrintSpace " "
2679 Sci::Position
EditView::FormatRange(bool draw
, CharacterRangeFull chrg
, Rectangle rc
, Surface
*surface
, Surface
*surfaceMeasure
,
2680 const EditModel
&model
, const ViewStyle
&vs
) {
2681 // Can't use measurements cached for screen
2684 ViewStyle
vsPrint(vs
);
2685 vsPrint
.technology
= Technology::Default
;
2687 // Modify the view style for printing as do not normally want any of the transient features to be printed
2688 // Printing supports only the line number margin.
2689 int lineNumberIndex
= -1;
2690 for (size_t margin
= 0; margin
< vs
.ms
.size(); margin
++) {
2691 if ((vsPrint
.ms
[margin
].style
== MarginType::Number
) && (vsPrint
.ms
[margin
].width
> 0)) {
2692 lineNumberIndex
= static_cast<int>(margin
);
2694 vsPrint
.ms
[margin
].width
= 0;
2697 vsPrint
.fixedColumnWidth
= 0;
2698 vsPrint
.zoomLevel
= printParameters
.magnification
;
2699 // Don't show indentation guides
2700 // If this ever gets changed, cached pixmap would need to be recreated if technology != Technology::Default
2701 vsPrint
.viewIndentationGuides
= IndentView::None
;
2702 // Don't show the selection when printing
2703 vsPrint
.selection
.visible
= false;
2704 vsPrint
.elementColours
.clear();
2705 vsPrint
.elementBaseColours
.clear();
2706 vsPrint
.caretLine
.alwaysShow
= false;
2707 // Don't highlight matching braces using indicators
2708 vsPrint
.braceHighlightIndicatorSet
= false;
2709 vsPrint
.braceBadLightIndicatorSet
= false;
2711 // Set colours for printing according to users settings
2712 const PrintOption colourMode
= printParameters
.colourMode
;
2713 const std::vector
<Style
>::iterator endStyles
= (colourMode
== PrintOption::ColourOnWhiteDefaultBG
) ?
2714 vsPrint
.styles
.begin() + StyleLineNumber
: vsPrint
.styles
.end();
2715 for (std::vector
<Style
>::iterator it
= vsPrint
.styles
.begin(); it
!= endStyles
; ++it
) {
2716 if (colourMode
== PrintOption::InvertLight
) {
2717 it
->fore
= InvertedLight(it
->fore
);
2718 it
->back
= InvertedLight(it
->back
);
2719 } else if (colourMode
== PrintOption::BlackOnWhite
) {
2722 } else if (colourMode
== PrintOption::ColourOnWhite
|| colourMode
== PrintOption::ColourOnWhiteDefaultBG
) {
2726 // White background for the line numbers if PrintOption::ScreenColours isn't used
2727 if (colourMode
!= PrintOption::ScreenColours
) {
2728 vsPrint
.styles
[StyleLineNumber
].back
= white
;
2731 // Printing uses different margins, so reset screen margins
2732 vsPrint
.leftMarginWidth
= 0;
2733 vsPrint
.rightMarginWidth
= 0;
2735 vsPrint
.Refresh(*surfaceMeasure
, model
.pdoc
->tabInChars
);
2736 // Determining width must happen after fonts have been realised in Refresh
2737 int lineNumberWidth
= 0;
2738 if (lineNumberIndex
>= 0) {
2739 lineNumberWidth
= static_cast<int>(surfaceMeasure
->WidthText(vsPrint
.styles
[StyleLineNumber
].font
.get(),
2740 "99999" lineNumberPrintSpace
));
2741 vsPrint
.ms
[lineNumberIndex
].width
= lineNumberWidth
;
2742 vsPrint
.Refresh(*surfaceMeasure
, model
.pdoc
->tabInChars
); // Recalculate fixedColumnWidth
2745 // Turn off change history marker backgrounds
2746 constexpr unsigned int changeMarkers
=
2747 1u << static_cast<unsigned int>(MarkerOutline::HistoryRevertedToOrigin
) |
2748 1u << static_cast<unsigned int>(MarkerOutline::HistorySaved
) |
2749 1u << static_cast<unsigned int>(MarkerOutline::HistoryModified
) |
2750 1u << static_cast<unsigned int>(MarkerOutline::HistoryRevertedToModified
);
2751 vsPrint
.maskInLine
&= ~changeMarkers
;
2753 const Sci::Line linePrintStart
= model
.pdoc
->SciLineFromPosition(chrg
.cpMin
);
2754 Sci::Line linePrintLast
= linePrintStart
+ (rc
.bottom
- rc
.top
) / vsPrint
.lineHeight
- 1;
2755 if (linePrintLast
< linePrintStart
)
2756 linePrintLast
= linePrintStart
;
2757 const Sci::Line linePrintMax
= model
.pdoc
->SciLineFromPosition(chrg
.cpMax
);
2758 if (linePrintLast
> linePrintMax
)
2759 linePrintLast
= linePrintMax
;
2760 //Platform::DebugPrintf("Formatting lines=[%0d,%0d,%0d] top=%0d bottom=%0d line=%0d %0d\n",
2761 // linePrintStart, linePrintLast, linePrintMax, rc.top, rc.bottom, vsPrint.lineHeight,
2762 // surfaceMeasure->Height(vsPrint.styles[StyleLineNumber].font));
2763 Sci::Position endPosPrint
= model
.pdoc
->Length();
2764 if (linePrintLast
< model
.pdoc
->LinesTotal())
2765 endPosPrint
= model
.pdoc
->LineStart(linePrintLast
+ 1);
2767 // Ensure we are styled to where we are formatting.
2768 model
.pdoc
->EnsureStyledTo(endPosPrint
);
2770 const int xStart
= vsPrint
.fixedColumnWidth
+ rc
.left
;
2773 Sci::Line lineDoc
= linePrintStart
;
2775 Sci::Position nPrintPos
= chrg
.cpMin
;
2776 int visibleLine
= 0;
2777 int widthPrint
= rc
.right
- rc
.left
- vsPrint
.fixedColumnWidth
;
2778 if (printParameters
.wrapState
== Wrap::None
)
2779 widthPrint
= LineLayout::wrapWidthInfinite
;
2781 while (lineDoc
<= linePrintLast
&& ypos
< rc
.bottom
) {
2783 // When printing, the hdc and hdcTarget may be the same, so
2784 // changing the state of surfaceMeasure may change the underlying
2785 // state of surface. Therefore, any cached state is discarded before
2786 // using each surface.
2787 surfaceMeasure
->FlushCachedState();
2789 // Copy this line and its styles from the document into local arrays
2790 // and determine the x position at which each character starts.
2791 LineLayout
ll(lineDoc
, static_cast<int>(model
.pdoc
->LineStart(lineDoc
+ 1) - model
.pdoc
->LineStart(lineDoc
) + 1));
2792 LayoutLine(model
, surfaceMeasure
, vsPrint
, &ll
, widthPrint
);
2794 ll
.containsCaret
= false;
2796 PRectangle rcLine
= PRectangle::FromInts(
2800 ypos
+ vsPrint
.lineHeight
);
2802 // When document line is wrapped over multiple display lines, find where
2803 // to start printing from to ensure a particular position is on the first
2804 // line of the page.
2805 if (visibleLine
== 0) {
2806 const Sci::Position startWithinLine
= nPrintPos
-
2807 model
.pdoc
->LineStart(lineDoc
);
2808 for (int iwl
= 0; iwl
< ll
.lines
- 1; iwl
++) {
2809 if (ll
.LineStart(iwl
) <= startWithinLine
&& ll
.LineStart(iwl
+ 1) >= startWithinLine
) {
2814 if (ll
.lines
> 1 && startWithinLine
>= ll
.LineStart(ll
.lines
- 1)) {
2815 visibleLine
= -(ll
.lines
- 1);
2819 if (draw
&& lineNumberWidth
&&
2820 (ypos
+ vsPrint
.lineHeight
<= rc
.bottom
) &&
2821 (visibleLine
>= 0)) {
2822 const std::string number
= std::to_string(lineDoc
+ 1) + lineNumberPrintSpace
;
2823 PRectangle rcNumber
= rcLine
;
2824 rcNumber
.right
= rcNumber
.left
+ lineNumberWidth
;
2826 rcNumber
.left
= rcNumber
.right
- surfaceMeasure
->WidthText(
2827 vsPrint
.styles
[StyleLineNumber
].font
.get(), number
);
2828 surface
->FlushCachedState();
2829 surface
->DrawTextNoClip(rcNumber
, vsPrint
.styles
[StyleLineNumber
].font
.get(),
2830 ypos
+ vsPrint
.maxAscent
, number
,
2831 vsPrint
.styles
[StyleLineNumber
].fore
,
2832 vsPrint
.styles
[StyleLineNumber
].back
);
2836 surface
->FlushCachedState();
2838 for (int iwl
= 0; iwl
< ll
.lines
; iwl
++) {
2839 if (ypos
+ vsPrint
.lineHeight
<= rc
.bottom
) {
2840 if (visibleLine
>= 0) {
2842 rcLine
.top
= static_cast<XYPOSITION
>(ypos
);
2843 rcLine
.bottom
= static_cast<XYPOSITION
>(ypos
+ vsPrint
.lineHeight
);
2844 DrawLine(surface
, model
, vsPrint
, &ll
, lineDoc
, visibleLine
, xStart
, rcLine
, iwl
, DrawPhase::all
);
2846 ypos
+= vsPrint
.lineHeight
;
2849 if (iwl
== ll
.lines
- 1)
2850 nPrintPos
= model
.pdoc
->LineStart(lineDoc
+ 1);
2852 nPrintPos
+= ll
.LineStart(iwl
+ 1) - ll
.LineStart(iwl
);
2859 // Clear cache so measurements are not used for screen