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