// Scintilla source code edit control /** @file EditView.cxx ** Defines the appearance of the main text area of the editor window. **/ // Copyright 1998-2014 by Neil Hodgson // The License.txt file describes the conditions under which this software may be distributed. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Platform.h" #include "ILoader.h" #include "ILexer.h" #include "Scintilla.h" #include "CharacterSet.h" #include "CharacterCategory.h" #include "Position.h" #include "IntegerRectangle.h" #include "UniqueString.h" #include "SplitVector.h" #include "Partitioning.h" #include "RunStyles.h" #include "ContractionState.h" #include "CellBuffer.h" #include "PerLine.h" #include "KeyMap.h" #include "Indicator.h" #include "LineMarker.h" #include "Style.h" #include "ViewStyle.h" #include "CharClassify.h" #include "Decoration.h" #include "CaseFolder.h" #include "Document.h" #include "UniConversion.h" #include "Selection.h" #include "PositionCache.h" #include "EditModel.h" #include "MarginView.h" #include "EditView.h" #include "ElapsedPeriod.h" using namespace Scintilla; PrintParameters::PrintParameters() noexcept { magnification = 0; colourMode = SC_PRINT_NORMAL; wrapState = WrapMode::word; } namespace Scintilla { bool ValidStyledText(const ViewStyle &vs, size_t styleOffset, const StyledText &st) noexcept { if (st.multipleStyles) { for (size_t iStyle = 0; iStyle(surface->WidthText(fontText, sv)); start = endSegment + 1; } return width; } int WidestLineWidth(Surface *surface, const ViewStyle &vs, int styleOffset, const StyledText &st) { int widthMax = 0; size_t start = 0; while (start < st.length) { const size_t lenLine = st.LineLength(start); int widthSubLine; if (st.multipleStyles) { widthSubLine = WidthStyledText(surface, vs, styleOffset, st.text + start, st.styles + start, lenLine); } else { FontAlias fontText = vs.styles[styleOffset + st.style].font; const std::string_view text(st.text + start, lenLine); widthSubLine = static_cast(surface->WidthText(fontText, text)); } if (widthSubLine > widthMax) widthMax = widthSubLine; start += lenLine + 1; } return widthMax; } void DrawTextNoClipPhase(Surface *surface, PRectangle rc, const Style &style, XYPOSITION ybase, std::string_view text, DrawPhase phase) { FontAlias fontText = style.font; if (phase & drawBack) { if (phase & drawText) { // Drawing both surface->DrawTextNoClip(rc, fontText, ybase, text, style.fore, style.back); } else { surface->FillRectangle(rc, style.back); } } else if (phase & drawText) { surface->DrawTextTransparent(rc, fontText, ybase, text, style.fore); } } void DrawStyledText(Surface *surface, const ViewStyle &vs, int styleOffset, PRectangle rcText, const StyledText &st, size_t start, size_t length, DrawPhase phase) { if (st.multipleStyles) { int x = static_cast(rcText.left); size_t i = 0; while (i < length) { size_t end = i; size_t style = st.styles[i + start]; while (end < length - 1 && st.styles[start + end + 1] == style) end++; style += styleOffset; FontAlias fontText = vs.styles[style].font; const std::string_view text(st.text + start + i, end - i + 1); const int width = static_cast(surface->WidthText(fontText, text)); PRectangle rcSegment = rcText; rcSegment.left = static_cast(x); rcSegment.right = static_cast(x + width + 1); DrawTextNoClipPhase(surface, rcSegment, vs.styles[style], rcText.top + vs.maxAscent, text, phase); x += width; i = end + 1; } } else { const size_t style = st.style + styleOffset; DrawTextNoClipPhase(surface, rcText, vs.styles[style], rcText.top + vs.maxAscent, std::string_view(st.text + start, length), phase); } } } EditView::EditView() { tabWidthMinimumPixels = 2; // needed for calculating tab stops for fractional proportional fonts hideSelection = false; drawOverstrikeCaret = true; bufferedDraw = true; phasesDraw = phasesTwo; lineWidthMaxSeen = 0; additionalCaretsBlink = true; additionalCaretsVisible = true; imeCaretBlockOverride = false; llc.SetLevel(LineLayoutCache::llcCaret); posCache.SetSize(0x400); tabArrowHeight = 4; customDrawTabArrow = nullptr; customDrawWrapMarker = nullptr; } EditView::~EditView() { } bool EditView::SetTwoPhaseDraw(bool twoPhaseDraw) noexcept { const PhasesDraw phasesDrawNew = twoPhaseDraw ? phasesTwo : phasesOne; const bool redraw = phasesDraw != phasesDrawNew; phasesDraw = phasesDrawNew; return redraw; } bool EditView::SetPhasesDraw(int phases) noexcept { const PhasesDraw phasesDrawNew = static_cast(phases); const bool redraw = phasesDraw != phasesDrawNew; phasesDraw = phasesDrawNew; return redraw; } bool EditView::LinesOverlap() const noexcept { return phasesDraw == phasesMultiple; } void EditView::ClearAllTabstops() noexcept { ldTabstops.reset(); } XYPOSITION EditView::NextTabstopPos(Sci::Line line, XYPOSITION x, XYPOSITION tabWidth) const noexcept { const int next = GetNextTabstop(line, static_cast(x + tabWidthMinimumPixels)); if (next > 0) return static_cast(next); return (static_cast((x + tabWidthMinimumPixels) / tabWidth) + 1) * tabWidth; } bool EditView::ClearTabstops(Sci::Line line) noexcept { return ldTabstops && ldTabstops->ClearTabstops(line); } bool EditView::AddTabstop(Sci::Line line, int x) { if (!ldTabstops) { ldTabstops = std::make_unique(); } return ldTabstops && ldTabstops->AddTabstop(line, x); } int EditView::GetNextTabstop(Sci::Line line, int x) const noexcept { if (ldTabstops) { return ldTabstops->GetNextTabstop(line, x); } else { return 0; } } void EditView::LinesAddedOrRemoved(Sci::Line lineOfPos, Sci::Line linesAdded) { if (ldTabstops) { if (linesAdded > 0) { for (Sci::Line line = lineOfPos; line < lineOfPos + linesAdded; line++) { ldTabstops->InsertLine(line); } } else { for (Sci::Line line = (lineOfPos + -linesAdded) - 1; line >= lineOfPos; line--) { ldTabstops->RemoveLine(line); } } } } void EditView::DropGraphics(bool freeObjects) { if (freeObjects) { pixmapLine.reset(); pixmapIndentGuide.reset(); pixmapIndentGuideHighlight.reset(); } else { if (pixmapLine) pixmapLine->Release(); if (pixmapIndentGuide) pixmapIndentGuide->Release(); if (pixmapIndentGuideHighlight) pixmapIndentGuideHighlight->Release(); } } void EditView::AllocateGraphics(const ViewStyle &vsDraw) { if (!pixmapLine) pixmapLine.reset(Surface::Allocate(vsDraw.technology)); if (!pixmapIndentGuide) pixmapIndentGuide.reset(Surface::Allocate(vsDraw.technology)); if (!pixmapIndentGuideHighlight) pixmapIndentGuideHighlight.reset(Surface::Allocate(vsDraw.technology)); } static const char *ControlCharacterString(unsigned char ch) noexcept { const char * const reps[] = { "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US" }; if (ch < std::size(reps)) { return reps[ch]; } else { return "BAD"; } } static void DrawTabArrow(Surface *surface, PRectangle rcTab, int ymid, const ViewStyle &vsDraw) { const IntegerRectangle ircTab(rcTab); if ((rcTab.left + 2) < (rcTab.right - 1)) surface->MoveTo(ircTab.left + 2, ymid); else surface->MoveTo(ircTab.right - 1, ymid); surface->LineTo(ircTab.right - 1, ymid); // Draw the arrow head if needed if (vsDraw.tabDrawMode == tdLongArrow) { int ydiff = (ircTab.bottom - ircTab.top) / 2; int xhead = ircTab.right - 1 - ydiff; if (xhead <= rcTab.left) { ydiff -= ircTab.left - xhead - 1; xhead = ircTab.left - 1; } surface->LineTo(xhead, ymid - ydiff); surface->MoveTo(ircTab.right - 1, ymid); surface->LineTo(xhead, ymid + ydiff); } } void EditView::RefreshPixMaps(Surface *surfaceWindow, WindowID wid, const ViewStyle &vsDraw) { if (!pixmapIndentGuide->Initialised()) { // 1 extra pixel in height so can handle odd/even positions and so produce a continuous line pixmapIndentGuide->InitPixMap(1, vsDraw.lineHeight + 1, surfaceWindow, wid); pixmapIndentGuideHighlight->InitPixMap(1, vsDraw.lineHeight + 1, surfaceWindow, wid); const PRectangle rcIG = PRectangle::FromInts(0, 0, 1, vsDraw.lineHeight); pixmapIndentGuide->FillRectangle(rcIG, vsDraw.styles[STYLE_INDENTGUIDE].back); pixmapIndentGuideHighlight->FillRectangle(rcIG, vsDraw.styles[STYLE_BRACELIGHT].back); for (int stripe = 1; stripe < vsDraw.lineHeight + 1; stripe += 2) { const PRectangle rcPixel = PRectangle::FromInts(0, stripe, 1, stripe + 1); pixmapIndentGuide->FillRectangle(rcPixel, vsDraw.styles[STYLE_INDENTGUIDE].fore); pixmapIndentGuideHighlight->FillRectangle(rcPixel, vsDraw.styles[STYLE_BRACELIGHT].fore); } } } LineLayout *EditView::RetrieveLineLayout(Sci::Line lineNumber, const EditModel &model) { const Sci::Position posLineStart = model.pdoc->LineStart(lineNumber); const Sci::Position posLineEnd = model.pdoc->LineStart(lineNumber + 1); PLATFORM_ASSERT(posLineEnd >= posLineStart); const Sci::Line lineCaret = model.pdoc->SciLineFromPosition(model.sel.MainCaret()); return llc.Retrieve(lineNumber, lineCaret, static_cast(posLineEnd - posLineStart), model.pdoc->GetStyleClock(), model.LinesOnScreen() + 1, model.pdoc->LinesTotal()); } namespace { constexpr XYPOSITION epsilon = 0.0001f; // A small nudge to avoid floating point precision issues /** * Return the chDoc argument with case transformed as indicated by the caseForce argument. * chPrevious is needed for camel casing. * This only affects ASCII characters and is provided for languages with case-insensitive * ASCII keywords where the user wishes to view keywords in a preferred case. */ inline char CaseForce(Style::ecaseForced caseForce, char chDoc, char chPrevious) { switch (caseForce) { case Style::caseMixed: return chDoc; case Style::caseLower: return MakeLowerCase(chDoc); case Style::caseUpper: return MakeUpperCase(chDoc); case Style::caseCamel: default: // default should not occur, included to avoid warnings if (IsUpperOrLowerCase(chDoc) && !IsUpperOrLowerCase(chPrevious)) { return MakeUpperCase(chDoc); } else { return MakeLowerCase(chDoc); } } } constexpr bool IsControlCharacter(int ch) noexcept { // iscntrl returns true for lots of chars > 127 which are displayable, // currently only check C0 control characters. return (ch >= 0 && ch < ' ') || (ch == 127); } } /** * Fill in the LineLayout data for the given line. * Copy the given @a line and its styles from the document into local arrays. * Also determine the x position at which each character starts. */ void EditView::LayoutLine(const EditModel &model, Sci::Line line, Surface *surface, const ViewStyle &vstyle, LineLayout *ll, int width) { if (!ll) return; PLATFORM_ASSERT(line < model.pdoc->LinesTotal()); PLATFORM_ASSERT(ll->chars != NULL); const Sci::Position posLineStart = model.pdoc->LineStart(line); Sci::Position posLineEnd = model.pdoc->LineStart(line + 1); // If the line is very long, limit the treatment to a length that should fit in the viewport if (posLineEnd >(posLineStart + ll->maxLineLength)) { posLineEnd = posLineStart + ll->maxLineLength; } // Hard to cope when too narrow, so just assume there is space width = std::max(width, 20); if (ll->validity == LineLayout::ValidLevel::checkTextAndStyle) { Sci::Position lineLength = posLineEnd - posLineStart; if (!vstyle.viewEOL) { lineLength = model.pdoc->LineEnd(line) - posLineStart; } if (lineLength == ll->numCharsInLine) { // See if chars, styles, indicators, are all the same bool allSame = true; // Check base line layout char chPrevious = 0; for (Sci::Position numCharsInLine = 0; numCharsInLine < lineLength; numCharsInLine++) { const Sci::Position charInDoc = numCharsInLine + posLineStart; const char chDoc = model.pdoc->CharAt(charInDoc); const int styleByte = model.pdoc->StyleIndexAt(charInDoc); allSame = allSame && (ll->styles[numCharsInLine] == styleByte); allSame = allSame && (ll->chars[numCharsInLine] == CaseForce(vstyle.styles[styleByte].caseForce, chDoc, chPrevious)); chPrevious = chDoc; } const int styleByteLast = (posLineEnd > posLineStart) ? model.pdoc->StyleIndexAt(posLineEnd - 1) : 0; allSame = allSame && (ll->styles[lineLength] == styleByteLast); // For eolFilled if (allSame) { ll->validity = (ll->widthLine != width) ? LineLayout::ValidLevel::positions : LineLayout::ValidLevel::lines; } else { ll->validity = LineLayout::ValidLevel::invalid; } } else { ll->validity = LineLayout::ValidLevel::invalid; } } if (ll->validity == LineLayout::ValidLevel::invalid) { ll->widthLine = LineLayout::wrapWidthInfinite; ll->lines = 1; if (vstyle.edgeState == EDGE_BACKGROUND) { Sci::Position edgePosition = model.pdoc->FindColumn(line, vstyle.theEdge.column); if (edgePosition >= posLineStart) { edgePosition -= posLineStart; } ll->edgeColumn = static_cast(edgePosition); } else { ll->edgeColumn = -1; } // Fill base line layout const int lineLength = static_cast(posLineEnd - posLineStart); model.pdoc->GetCharRange(ll->chars.get(), posLineStart, lineLength); model.pdoc->GetStyleRange(ll->styles.get(), posLineStart, lineLength); const int numCharsBeforeEOL = static_cast(model.pdoc->LineEnd(line) - posLineStart); const int numCharsInLine = (vstyle.viewEOL) ? lineLength : numCharsBeforeEOL; const unsigned char styleByteLast = (lineLength > 0) ? ll->styles[lineLength - 1] : 0; if (vstyle.someStylesForceCase) { char chPrevious = 0; for (int charInLine = 0; charInLinechars[charInLine]; ll->chars[charInLine] = CaseForce(vstyle.styles[ll->styles[charInLine]].caseForce, chDoc, chPrevious); chPrevious = chDoc; } } ll->xHighlightGuide = 0; // Extra element at the end of the line to hold end x position and act as ll->chars[numCharsInLine] = 0; // Also triggers processing in the loops as this is a control character ll->styles[numCharsInLine] = styleByteLast; // For eolFilled // Layout the line, determining the position of each character, // with an extra element at the end for the end of the line. ll->positions[0] = 0; bool lastSegItalics = false; BreakFinder bfLayout(ll, nullptr, Range(0, numCharsInLine), posLineStart, 0, false, model.pdoc, &model.reprs, nullptr); while (bfLayout.More()) { const TextSegment ts = bfLayout.Next(); std::fill(&ll->positions[ts.start + 1], &ll->positions[ts.end() + 1], 0.0f); if (vstyle.styles[ll->styles[ts.start]].visible) { if (ts.representation) { XYPOSITION representationWidth = vstyle.controlCharWidth; if (ll->chars[ts.start] == '\t') { // Tab is a special case of representation, taking a variable amount of space const XYPOSITION x = ll->positions[ts.start]; representationWidth = NextTabstopPos(line, x, vstyle.tabWidth) - ll->positions[ts.start]; } else { if (representationWidth <= 0.0) { XYPOSITION positionsRepr[256]; // Should expand when needed posCache.MeasureWidths(surface, vstyle, STYLE_CONTROLCHAR, ts.representation->stringRep.c_str(), static_cast(ts.representation->stringRep.length()), positionsRepr, model.pdoc); representationWidth = positionsRepr[ts.representation->stringRep.length() - 1] + vstyle.ctrlCharPadding; } } for (int ii = 0; ii < ts.length; ii++) ll->positions[ts.start + 1 + ii] = representationWidth; } else { if ((ts.length == 1) && (' ' == ll->chars[ts.start])) { // Over half the segments are single characters and of these about half are space characters. ll->positions[ts.start + 1] = vstyle.styles[ll->styles[ts.start]].spaceWidth; } else { posCache.MeasureWidths(surface, vstyle, ll->styles[ts.start], &ll->chars[ts.start], ts.length, &ll->positions[ts.start + 1], model.pdoc); } } lastSegItalics = (!ts.representation) && ((ll->chars[ts.end() - 1] != ' ') && vstyle.styles[ll->styles[ts.start]].italic); } for (Sci::Position posToIncrease = ts.start + 1; posToIncrease <= ts.end(); posToIncrease++) { ll->positions[posToIncrease] += ll->positions[ts.start]; } } // Small hack to make lines that end with italics not cut off the edge of the last character if (lastSegItalics) { ll->positions[numCharsInLine] += vstyle.lastSegItalicsOffset; } ll->numCharsInLine = numCharsInLine; ll->numCharsBeforeEOL = numCharsBeforeEOL; ll->validity = LineLayout::ValidLevel::positions; } if ((ll->validity == LineLayout::ValidLevel::positions) || (ll->widthLine != width)) { ll->widthLine = width; if (width == LineLayout::wrapWidthInfinite) { ll->lines = 1; } else if (width > ll->positions[ll->numCharsInLine]) { // Simple common case where line does not need wrapping. ll->lines = 1; } else { if (vstyle.wrapVisualFlags & SC_WRAPVISUALFLAG_END) { width -= static_cast(vstyle.aveCharWidth); // take into account the space for end wrap mark } XYPOSITION wrapAddIndent = 0; // This will be added to initial indent of line switch (vstyle.wrapIndentMode) { case SC_WRAPINDENT_FIXED: wrapAddIndent = vstyle.wrapVisualStartIndent * vstyle.aveCharWidth; break; case SC_WRAPINDENT_INDENT: wrapAddIndent = model.pdoc->IndentSize() * vstyle.spaceWidth; break; case SC_WRAPINDENT_DEEPINDENT: wrapAddIndent = model.pdoc->IndentSize() * 2 * vstyle.spaceWidth; break; } ll->wrapIndent = wrapAddIndent; if (vstyle.wrapIndentMode != SC_WRAPINDENT_FIXED) { for (int i = 0; i < ll->numCharsInLine; i++) { if (!IsSpaceOrTab(ll->chars[i])) { ll->wrapIndent += ll->positions[i]; // Add line indent break; } } } // Check for text width minimum if (ll->wrapIndent > width - static_cast(vstyle.aveCharWidth) * 15) ll->wrapIndent = wrapAddIndent; // Check for wrapIndent minimum if ((vstyle.wrapVisualFlags & SC_WRAPVISUALFLAG_START) && (ll->wrapIndent < vstyle.aveCharWidth)) ll->wrapIndent = vstyle.aveCharWidth; // Indent to show start visual ll->lines = 0; // Calculate line start positions based upon width. Sci::Position lastGoodBreak = 0; Sci::Position lastLineStart = 0; XYACCUMULATOR startOffset = 0; Sci::Position p = 0; while (p < ll->numCharsInLine) { if ((ll->positions[p + 1] - startOffset) >= width) { if (lastGoodBreak == lastLineStart) { // Try moving to start of last character if (p > 0) { lastGoodBreak = model.pdoc->MovePositionOutsideChar(p + posLineStart, -1) - posLineStart; } if (lastGoodBreak == lastLineStart) { // Ensure at least one character on line. lastGoodBreak = model.pdoc->MovePositionOutsideChar(lastGoodBreak + posLineStart + 1, 1) - posLineStart; } } lastLineStart = lastGoodBreak; ll->lines++; ll->SetLineStart(ll->lines, static_cast(lastGoodBreak)); startOffset = ll->positions[lastGoodBreak]; // take into account the space for start wrap mark and indent startOffset -= ll->wrapIndent; p = lastGoodBreak + 1; continue; } if (p > 0) { if (vstyle.wrapState == WrapMode::character) { lastGoodBreak = model.pdoc->MovePositionOutsideChar(p + posLineStart, -1) - posLineStart; p = model.pdoc->MovePositionOutsideChar(p + 1 + posLineStart, 1) - posLineStart; continue; } else if ((vstyle.wrapState == WrapMode::word) && (ll->styles[p] != ll->styles[p - 1])) { lastGoodBreak = p; } else if (IsSpaceOrTab(ll->chars[p - 1]) && !IsSpaceOrTab(ll->chars[p])) { lastGoodBreak = p; } } p++; } ll->lines++; } ll->validity = LineLayout::ValidLevel::lines; } } // Fill the LineLayout bidirectional data fields according to each char style void EditView::UpdateBidiData(const EditModel &model, const ViewStyle &vstyle, LineLayout *ll) { if (model.BidirectionalEnabled()) { ll->EnsureBidiData(); for (int stylesInLine = 0; stylesInLine < ll->numCharsInLine; stylesInLine++) { ll->bidiData->stylesFonts[stylesInLine].MakeAlias(vstyle.styles[ll->styles[stylesInLine]].font); } ll->bidiData->stylesFonts[ll->numCharsInLine].ClearFont(); for (int charsInLine = 0; charsInLine < ll->numCharsInLine; charsInLine++) { const int charWidth = UTF8DrawBytes(reinterpret_cast(&ll->chars[charsInLine]), ll->numCharsInLine - charsInLine); const Representation *repr = model.reprs.RepresentationFromCharacter(&ll->chars[charsInLine], charWidth); ll->bidiData->widthReprs[charsInLine] = 0.0f; if (repr && ll->chars[charsInLine] != '\t') { ll->bidiData->widthReprs[charsInLine] = ll->positions[charsInLine + charWidth] - ll->positions[charsInLine]; } if (charWidth > 1) { for (int c = 1; c < charWidth; c++) { charsInLine++; ll->bidiData->widthReprs[charsInLine] = 0.0f; } } } ll->bidiData->widthReprs[ll->numCharsInLine] = 0.0f; } else { ll->bidiData.reset(); } } Point EditView::LocationFromPosition(Surface *surface, const EditModel &model, SelectionPosition pos, Sci::Line topLine, const ViewStyle &vs, PointEnd pe, const PRectangle rcClient) { Point pt; if (pos.Position() == INVALID_POSITION) return pt; Sci::Line lineDoc = model.pdoc->SciLineFromPosition(pos.Position()); Sci::Position posLineStart = model.pdoc->LineStart(lineDoc); if ((pe & peLineEnd) && (lineDoc > 0) && (pos.Position() == posLineStart)) { // Want point at end of first line lineDoc--; posLineStart = model.pdoc->LineStart(lineDoc); } const Sci::Line lineVisible = model.pcs->DisplayFromDoc(lineDoc); AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc, model)); if (surface && ll) { LayoutLine(model, lineDoc, surface, vs, ll, model.wrapWidth); const int posInLine = static_cast(pos.Position() - posLineStart); pt = ll->PointFromPosition(posInLine, vs.lineHeight, pe); pt.x += vs.textStart - model.xOffset; if (model.BidirectionalEnabled()) { // Fill the line bidi data UpdateBidiData(model, vs, ll); // Find subLine const int subLine = ll->SubLineFromPosition(posInLine, pe); const int caretPosition = posInLine - ll->LineStart(subLine); // Get the point from current position const ScreenLine screenLine(ll, subLine, vs, rcClient.right, tabWidthMinimumPixels); std::unique_ptr slLayout = surface->Layout(&screenLine); pt.x = slLayout->XFromPosition(caretPosition); pt.x += vs.textStart - model.xOffset; pt.y = 0; if (posInLine >= ll->LineStart(subLine)) { pt.y = static_cast(subLine*vs.lineHeight); } } pt.y += (lineVisible - topLine) * vs.lineHeight; } pt.x += pos.VirtualSpace() * vs.styles[ll->EndLineStyle()].spaceWidth; return pt; } Range EditView::RangeDisplayLine(Surface *surface, const EditModel &model, Sci::Line lineVisible, const ViewStyle &vs) { Range rangeSubLine = Range(0, 0); if (lineVisible < 0) { return rangeSubLine; } const Sci::Line lineDoc = model.pcs->DocFromDisplay(lineVisible); const Sci::Position positionLineStart = model.pdoc->LineStart(lineDoc); AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc, model)); if (surface && ll) { LayoutLine(model, lineDoc, surface, vs, ll, model.wrapWidth); const Sci::Line lineStartSet = model.pcs->DisplayFromDoc(lineDoc); const int subLine = static_cast(lineVisible - lineStartSet); if (subLine < ll->lines) { rangeSubLine = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly); if (subLine == ll->lines-1) { rangeSubLine.end = model.pdoc->LineStart(lineDoc + 1) - positionLineStart; } } } rangeSubLine.start += positionLineStart; rangeSubLine.end += positionLineStart; return rangeSubLine; } SelectionPosition EditView::SPositionFromLocation(Surface *surface, const EditModel &model, PointDocument pt, bool canReturnInvalid, bool charPosition, bool virtualSpace, const ViewStyle &vs, const PRectangle rcClient) { pt.x = pt.x - vs.textStart; Sci::Line visibleLine = static_cast(std::floor(pt.y / vs.lineHeight)); if (!canReturnInvalid && (visibleLine < 0)) visibleLine = 0; const Sci::Line lineDoc = model.pcs->DocFromDisplay(visibleLine); if (canReturnInvalid && (lineDoc < 0)) return SelectionPosition(INVALID_POSITION); if (lineDoc >= model.pdoc->LinesTotal()) return SelectionPosition(canReturnInvalid ? INVALID_POSITION : model.pdoc->Length()); const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc); AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc, model)); if (surface && ll) { LayoutLine(model, lineDoc, surface, vs, ll, model.wrapWidth); const Sci::Line lineStartSet = model.pcs->DisplayFromDoc(lineDoc); const int subLine = static_cast(visibleLine - lineStartSet); if (subLine < ll->lines) { const Range rangeSubLine = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly); const XYPOSITION subLineStart = ll->positions[rangeSubLine.start]; if (subLine > 0) // Wrapped pt.x -= ll->wrapIndent; Sci::Position positionInLine = 0; if (model.BidirectionalEnabled()) { // Fill the line bidi data UpdateBidiData(model, vs, ll); const ScreenLine screenLine(ll, subLine, vs, rcClient.right, tabWidthMinimumPixels); std::unique_ptr slLayout = surface->Layout(&screenLine); positionInLine = slLayout->PositionFromX(static_cast(pt.x), charPosition) + rangeSubLine.start; } else { positionInLine = ll->FindPositionFromX(static_cast(pt.x + subLineStart), rangeSubLine, charPosition); } if (positionInLine < rangeSubLine.end) { return SelectionPosition(model.pdoc->MovePositionOutsideChar(positionInLine + posLineStart, 1)); } if (virtualSpace) { const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth; const int spaceOffset = static_cast( (pt.x + subLineStart - ll->positions[rangeSubLine.end] + spaceWidth / 2) / spaceWidth); return SelectionPosition(rangeSubLine.end + posLineStart, spaceOffset); } else if (canReturnInvalid) { if (pt.x < (ll->positions[rangeSubLine.end] - subLineStart)) { return SelectionPosition(model.pdoc->MovePositionOutsideChar(rangeSubLine.end + posLineStart, 1)); } } else { return SelectionPosition(rangeSubLine.end + posLineStart); } } if (!canReturnInvalid) return SelectionPosition(ll->numCharsInLine + posLineStart); } return SelectionPosition(canReturnInvalid ? INVALID_POSITION : posLineStart); } /** * Find the document position corresponding to an x coordinate on a particular document line. * Ensure is between whole characters when document is in multi-byte or UTF-8 mode. * This method is used for rectangular selections and does not work on wrapped lines. */ SelectionPosition EditView::SPositionFromLineX(Surface *surface, const EditModel &model, Sci::Line lineDoc, int x, const ViewStyle &vs) { AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc, model)); if (surface && ll) { const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc); LayoutLine(model, lineDoc, surface, vs, ll, model.wrapWidth); const Range rangeSubLine = ll->SubLineRange(0, LineLayout::Scope::visibleOnly); const XYPOSITION subLineStart = ll->positions[rangeSubLine.start]; const Sci::Position positionInLine = ll->FindPositionFromX(x + subLineStart, rangeSubLine, false); if (positionInLine < rangeSubLine.end) { return SelectionPosition(model.pdoc->MovePositionOutsideChar(positionInLine + posLineStart, 1)); } const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth; const int spaceOffset = static_cast( (x + subLineStart - ll->positions[rangeSubLine.end] + spaceWidth / 2) / spaceWidth); return SelectionPosition(rangeSubLine.end + posLineStart, spaceOffset); } return SelectionPosition(0); } Sci::Line EditView::DisplayFromPosition(Surface *surface, const EditModel &model, Sci::Position pos, const ViewStyle &vs) { const Sci::Line lineDoc = model.pdoc->SciLineFromPosition(pos); Sci::Line lineDisplay = model.pcs->DisplayFromDoc(lineDoc); AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc, model)); if (surface && ll) { LayoutLine(model, lineDoc, surface, vs, ll, model.wrapWidth); const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc); const Sci::Position posInLine = pos - posLineStart; lineDisplay--; // To make up for first increment ahead. for (int subLine = 0; subLine < ll->lines; subLine++) { if (posInLine >= ll->LineStart(subLine)) { lineDisplay++; } } } return lineDisplay; } Sci::Position EditView::StartEndDisplayLine(Surface *surface, const EditModel &model, Sci::Position pos, bool start, const ViewStyle &vs) { const Sci::Line line = model.pdoc->SciLineFromPosition(pos); AutoLineLayout ll(llc, RetrieveLineLayout(line, model)); Sci::Position posRet = INVALID_POSITION; if (surface && ll) { const Sci::Position posLineStart = model.pdoc->LineStart(line); LayoutLine(model, line, surface, vs, ll, model.wrapWidth); const Sci::Position posInLine = pos - posLineStart; if (posInLine <= ll->maxLineLength) { for (int subLine = 0; subLine < ll->lines; subLine++) { if ((posInLine >= ll->LineStart(subLine)) && (posInLine <= ll->LineStart(subLine + 1)) && (posInLine <= ll->numCharsBeforeEOL)) { if (start) { posRet = ll->LineStart(subLine) + posLineStart; } else { if (subLine == ll->lines - 1) posRet = ll->numCharsBeforeEOL + posLineStart; else posRet = ll->LineStart(subLine + 1) + posLineStart - 1; } } } } } return posRet; } static ColourDesired SelectionBackground(const ViewStyle &vsDraw, bool main, bool primarySelection) noexcept { return main ? (primarySelection ? vsDraw.selColours.back : vsDraw.selBackground2) : vsDraw.selAdditionalBackground; } static ColourDesired TextBackground(const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll, ColourOptional background, int inSelection, bool inHotspot, int styleMain, Sci::Position i) noexcept { if (inSelection == 1) { if (vsDraw.selColours.back.isSet && (vsDraw.selAlpha == SC_ALPHA_NOALPHA)) { return SelectionBackground(vsDraw, true, model.primarySelection); } } else if (inSelection == 2) { if (vsDraw.selColours.back.isSet && (vsDraw.selAdditionalAlpha == SC_ALPHA_NOALPHA)) { return SelectionBackground(vsDraw, false, model.primarySelection); } } else { if ((vsDraw.edgeState == EDGE_BACKGROUND) && (i >= ll->edgeColumn) && (i < ll->numCharsBeforeEOL)) return vsDraw.theEdge.colour; if (inHotspot && vsDraw.hotspotColours.back.isSet) return vsDraw.hotspotColours.back; } if (background.isSet && (styleMain != STYLE_BRACELIGHT) && (styleMain != STYLE_BRACEBAD)) { return background; } else { return vsDraw.styles[styleMain].back; } } void EditView::DrawIndentGuide(Surface *surface, Sci::Line lineVisible, int lineHeight, XYPOSITION start, PRectangle rcSegment, bool highlight) { const Point from = Point::FromInts(0, ((lineVisible & 1) && (lineHeight & 1)) ? 1 : 0); const PRectangle rcCopyArea(start + 1, rcSegment.top, start + 2, rcSegment.bottom); surface->Copy(rcCopyArea, from, highlight ? *pixmapIndentGuideHighlight : *pixmapIndentGuide); } static void SimpleAlphaRectangle(Surface *surface, PRectangle rc, ColourDesired fill, int alpha) { if (alpha != SC_ALPHA_NOALPHA) { surface->AlphaRectangle(rc, 0, fill, alpha, fill, alpha, 0); } } static void DrawTextBlob(Surface *surface, const ViewStyle &vsDraw, PRectangle rcSegment, std::string_view text, ColourDesired textBack, ColourDesired textFore, bool fillBackground) { if (rcSegment.Empty()) return; if (fillBackground) { surface->FillRectangle(rcSegment, textBack); } FontAlias ctrlCharsFont = vsDraw.styles[STYLE_CONTROLCHAR].font; const int normalCharHeight = static_cast(std::ceil(vsDraw.styles[STYLE_CONTROLCHAR].capitalHeight)); PRectangle rcCChar = rcSegment; rcCChar.left = rcCChar.left + 1; rcCChar.top = rcSegment.top + vsDraw.maxAscent - normalCharHeight; rcCChar.bottom = rcSegment.top + vsDraw.maxAscent + 1; PRectangle rcCentral = rcCChar; rcCentral.top++; rcCentral.bottom--; surface->FillRectangle(rcCentral, textFore); PRectangle rcChar = rcCChar; rcChar.left++; rcChar.right--; surface->DrawTextClipped(rcChar, ctrlCharsFont, rcSegment.top + vsDraw.maxAscent, text, textBack, textFore); } static void DrawFrame(Surface *surface, ColourDesired colour, int alpha, PRectangle rcFrame) { if (alpha != SC_ALPHA_NOALPHA) surface->AlphaRectangle(rcFrame, 0, colour, alpha, colour, alpha, 0); else surface->FillRectangle(rcFrame, colour); } static void DrawCaretLineFramed(Surface *surface, const ViewStyle &vsDraw, const LineLayout *ll, PRectangle rcLine, int subLine) { const int width = vsDraw.GetFrameWidth(); if (subLine == 0 || ll->wrapIndent == 0 || vsDraw.caretLineAlpha != SC_ALPHA_NOALPHA) { // Left DrawFrame(surface, vsDraw.caretLineBackground, vsDraw.caretLineAlpha, PRectangle(rcLine.left, rcLine.top, rcLine.left + width, rcLine.bottom)); } if (subLine == 0) { // Top DrawFrame(surface, vsDraw.caretLineBackground, vsDraw.caretLineAlpha, PRectangle(rcLine.left + width, rcLine.top, rcLine.right - width, rcLine.top + width)); } if (subLine == ll->lines - 1 || vsDraw.caretLineAlpha != SC_ALPHA_NOALPHA) { // Right DrawFrame(surface, vsDraw.caretLineBackground, vsDraw.caretLineAlpha, PRectangle(rcLine.right - width, rcLine.top, rcLine.right, rcLine.bottom)); } if (subLine == ll->lines - 1) { // Bottom DrawFrame(surface, vsDraw.caretLineBackground, vsDraw.caretLineAlpha, PRectangle(rcLine.left + width, rcLine.bottom - width, rcLine.right - width, rcLine.bottom)); } } void EditView::DrawEOL(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll, PRectangle rcLine, Sci::Line line, Sci::Position lineEnd, int xStart, int subLine, XYACCUMULATOR subLineStart, ColourOptional background) { const Sci::Position posLineStart = model.pdoc->LineStart(line); PRectangle rcSegment = rcLine; const bool lastSubLine = subLine == (ll->lines - 1); XYPOSITION virtualSpace = 0; if (lastSubLine) { const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth; virtualSpace = model.sel.VirtualSpaceFor(model.pdoc->LineEnd(line)) * spaceWidth; } const XYPOSITION xEol = static_cast(ll->positions[lineEnd] - subLineStart); // Fill the virtual space and show selections within it if (virtualSpace > 0.0f) { rcSegment.left = xEol + xStart; rcSegment.right = xEol + xStart + virtualSpace; surface->FillRectangle(rcSegment, background.isSet ? background : vsDraw.styles[ll->styles[ll->numCharsInLine]].back); if (!hideSelection && ((vsDraw.selAlpha == SC_ALPHA_NOALPHA) || (vsDraw.selAdditionalAlpha == SC_ALPHA_NOALPHA))) { const SelectionSegment virtualSpaceRange(SelectionPosition(model.pdoc->LineEnd(line)), SelectionPosition(model.pdoc->LineEnd(line), model.sel.VirtualSpaceFor(model.pdoc->LineEnd(line)))); for (size_t r = 0; rEndLineStyle()].spaceWidth; rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] - static_cast(subLineStart)+portion.start.VirtualSpace() * spaceWidth; rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] - static_cast(subLineStart)+portion.end.VirtualSpace() * spaceWidth; rcSegment.left = (rcSegment.left > rcLine.left) ? rcSegment.left : rcLine.left; rcSegment.right = (rcSegment.right < rcLine.right) ? rcSegment.right : rcLine.right; surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, r == model.sel.Main(), model.primarySelection)); } } } } } int eolInSelection = 0; int alpha = SC_ALPHA_NOALPHA; if (!hideSelection) { const Sci::Position posAfterLineEnd = model.pdoc->LineStart(line + 1); eolInSelection = lastSubLine ? model.sel.InSelectionForEOL(posAfterLineEnd) : 0; alpha = (eolInSelection == 1) ? vsDraw.selAlpha : vsDraw.selAdditionalAlpha; } // Draw the [CR], [LF], or [CR][LF] blobs if visible line ends are on XYPOSITION blobsWidth = 0; if (lastSubLine) { for (Sci::Position eolPos = ll->numCharsBeforeEOL; eolPosnumCharsInLine; eolPos++) { rcSegment.left = xStart + ll->positions[eolPos] - static_cast(subLineStart)+virtualSpace; rcSegment.right = xStart + ll->positions[eolPos + 1] - static_cast(subLineStart)+virtualSpace; blobsWidth += rcSegment.Width(); char hexits[4] = ""; const char *ctrlChar; const unsigned char chEOL = ll->chars[eolPos]; const int styleMain = ll->styles[eolPos]; const ColourDesired textBack = TextBackground(model, vsDraw, ll, background, eolInSelection, false, styleMain, eolPos); if (UTF8IsAscii(chEOL)) { ctrlChar = ControlCharacterString(chEOL); } else { const Representation *repr = model.reprs.RepresentationFromCharacter(&ll->chars[eolPos], ll->numCharsInLine - eolPos); if (repr) { ctrlChar = repr->stringRep.c_str(); eolPos = ll->numCharsInLine; } else { sprintf(hexits, "x%2X", chEOL); ctrlChar = hexits; } } ColourDesired textFore = vsDraw.styles[styleMain].fore; if (eolInSelection && vsDraw.selColours.fore.isSet) { textFore = (eolInSelection == 1) ? vsDraw.selColours.fore : vsDraw.selAdditionalForeground; } if (eolInSelection && vsDraw.selColours.back.isSet && (line < model.pdoc->LinesTotal() - 1)) { if (alpha == SC_ALPHA_NOALPHA) { surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, eolInSelection == 1, model.primarySelection)); } else { surface->FillRectangle(rcSegment, textBack); } } else { surface->FillRectangle(rcSegment, textBack); } DrawTextBlob(surface, vsDraw, rcSegment, ctrlChar, textBack, textFore, phasesDraw == phasesOne); if (eolInSelection && vsDraw.selColours.back.isSet && (line < model.pdoc->LinesTotal() - 1) && (alpha != SC_ALPHA_NOALPHA)) { SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, eolInSelection == 1, model.primarySelection), alpha); } } } // Draw the eol-is-selected rectangle rcSegment.left = xEol + xStart + virtualSpace + blobsWidth; rcSegment.right = rcSegment.left + vsDraw.aveCharWidth; if (eolInSelection && vsDraw.selColours.back.isSet && (line < model.pdoc->LinesTotal() - 1) && (alpha == SC_ALPHA_NOALPHA)) { surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, eolInSelection == 1, model.primarySelection)); } else { if (background.isSet) { surface->FillRectangle(rcSegment, background); } else if (line < model.pdoc->LinesTotal() - 1) { surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine]].back); } else if (vsDraw.styles[ll->styles[ll->numCharsInLine]].eolFilled) { surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine]].back); } else { surface->FillRectangle(rcSegment, vsDraw.styles[STYLE_DEFAULT].back); } if (eolInSelection && vsDraw.selColours.back.isSet && (line < model.pdoc->LinesTotal() - 1) && (alpha != SC_ALPHA_NOALPHA)) { SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, eolInSelection == 1, model.primarySelection), alpha); } } rcSegment.left = rcSegment.right; if (rcSegment.left < rcLine.left) rcSegment.left = rcLine.left; rcSegment.right = rcLine.right; const bool drawEOLAnnotationStyledText = (vsDraw.eolAnnotationVisible != EOLANNOTATION_HIDDEN) && model.pdoc->EOLAnnotationStyledText(line).text; const bool fillRemainder = (!lastSubLine || (!model.GetFoldDisplayText(line) && !drawEOLAnnotationStyledText)); if (fillRemainder) { // Fill the remainder of the line FillLineRemainder(surface, model, vsDraw, ll, line, rcSegment, subLine); } bool drawWrapMarkEnd = false; if (subLine + 1 < ll->lines) { if (vsDraw.wrapVisualFlags & SC_WRAPVISUALFLAG_END) { drawWrapMarkEnd = ll->LineStart(subLine + 1) != 0; } if (vsDraw.IsLineFrameOpaque(model.caret.active, ll->containsCaret)) { const int width = vsDraw.GetFrameWidth(); // Draw right of frame under marker DrawFrame(surface, vsDraw.caretLineBackground, vsDraw.caretLineAlpha, PRectangle(rcLine.right - width, rcLine.top, rcLine.right, rcLine.bottom)); } } if (drawWrapMarkEnd) { PRectangle rcPlace = rcSegment; if (vsDraw.wrapVisualFlagsLocation & SC_WRAPVISUALFLAGLOC_END_BY_TEXT) { rcPlace.left = xEol + xStart + virtualSpace; rcPlace.right = rcPlace.left + vsDraw.aveCharWidth; } else { // rcLine is clipped to text area rcPlace.right = rcLine.right; rcPlace.left = rcPlace.right - vsDraw.aveCharWidth; } if (!customDrawWrapMarker) { DrawWrapMarker(surface, rcPlace, true, vsDraw.WrapColour()); } else { customDrawWrapMarker(surface, rcPlace, true, vsDraw.WrapColour()); } } } static void DrawIndicator(int indicNum, Sci::Position startPos, Sci::Position endPos, Surface *surface, const ViewStyle &vsDraw, const LineLayout *ll, int xStart, PRectangle rcLine, Sci::Position secondCharacter, int subLine, Indicator::State state, int value, bool bidiEnabled, int tabWidthMinimumPixels) { const XYPOSITION subLineStart = ll->positions[ll->LineStart(subLine)]; std::vector rectangles; const PRectangle rcIndic( ll->positions[startPos] + xStart - subLineStart, rcLine.top + vsDraw.maxAscent, ll->positions[endPos] + xStart - subLineStart, rcLine.top + vsDraw.maxAscent + 3); if (bidiEnabled) { ScreenLine screenLine(ll, subLine, vsDraw, rcLine.right - xStart, tabWidthMinimumPixels); const Range lineRange = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly); std::unique_ptr slLayout = surface->Layout(&screenLine); std::vector intervals = slLayout->FindRangeIntervals( startPos - lineRange.start, endPos - lineRange.start); for (const Interval &interval : intervals) { PRectangle rcInterval = rcIndic; rcInterval.left = interval.left + xStart; rcInterval.right = interval.right + xStart; rectangles.push_back(rcInterval); } } else { rectangles.push_back(rcIndic); } for (const PRectangle &rc : rectangles) { PRectangle rcFirstCharacter = rc; // Allow full descent space for character indicators rcFirstCharacter.bottom = rcLine.top + vsDraw.maxAscent + vsDraw.maxDescent; if (secondCharacter >= 0) { rcFirstCharacter.right = ll->positions[secondCharacter] + xStart - subLineStart; } else { // Indicator continued from earlier line so make an empty box and don't draw rcFirstCharacter.right = rcFirstCharacter.left; } vsDraw.indicators[indicNum].Draw(surface, rc, rcLine, rcFirstCharacter, state, value); } } static void DrawIndicators(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll, Sci::Line line, int xStart, PRectangle rcLine, int subLine, Sci::Position lineEnd, bool under, int tabWidthMinimumPixels) { // Draw decorators const Sci::Position posLineStart = model.pdoc->LineStart(line); const Sci::Position lineStart = ll->LineStart(subLine); const Sci::Position posLineEnd = posLineStart + lineEnd; for (const IDecoration *deco : model.pdoc->decorations->View()) { if (under == vsDraw.indicators[deco->Indicator()].under) { Sci::Position startPos = posLineStart + lineStart; if (!deco->ValueAt(startPos)) { startPos = deco->EndRun(startPos); } while ((startPos < posLineEnd) && (deco->ValueAt(startPos))) { const Range rangeRun(deco->StartRun(startPos), deco->EndRun(startPos)); const Sci::Position endPos = std::min(rangeRun.end, posLineEnd); const bool hover = vsDraw.indicators[deco->Indicator()].IsDynamic() && rangeRun.ContainsCharacter(model.hoverIndicatorPos); const int value = deco->ValueAt(startPos); const Indicator::State state = hover ? Indicator::State::hover : Indicator::State::normal; const Sci::Position posSecond = model.pdoc->MovePositionOutsideChar(rangeRun.First() + 1, 1); DrawIndicator(deco->Indicator(), startPos - posLineStart, endPos - posLineStart, surface, vsDraw, ll, xStart, rcLine, posSecond - posLineStart, subLine, state, value, model.BidirectionalEnabled(), tabWidthMinimumPixels); startPos = endPos; if (!deco->ValueAt(startPos)) { startPos = deco->EndRun(startPos); } } } } // Use indicators to highlight matching braces if ((vsDraw.braceHighlightIndicatorSet && (model.bracesMatchStyle == STYLE_BRACELIGHT)) || (vsDraw.braceBadLightIndicatorSet && (model.bracesMatchStyle == STYLE_BRACEBAD))) { const int braceIndicator = (model.bracesMatchStyle == STYLE_BRACELIGHT) ? vsDraw.braceHighlightIndicator : vsDraw.braceBadLightIndicator; if (under == vsDraw.indicators[braceIndicator].under) { const Range rangeLine(posLineStart + lineStart, posLineEnd); if (rangeLine.ContainsCharacter(model.braces[0])) { const Sci::Position braceOffset = model.braces[0] - posLineStart; if (braceOffset < ll->numCharsInLine) { const Sci::Position secondOffset = model.pdoc->MovePositionOutsideChar(model.braces[0] + 1, 1) - posLineStart; DrawIndicator(braceIndicator, braceOffset, braceOffset + 1, surface, vsDraw, ll, xStart, rcLine, secondOffset, subLine, Indicator::State::normal, 1, model.BidirectionalEnabled(), tabWidthMinimumPixels); } } if (rangeLine.ContainsCharacter(model.braces[1])) { const Sci::Position braceOffset = model.braces[1] - posLineStart; if (braceOffset < ll->numCharsInLine) { const Sci::Position secondOffset = model.pdoc->MovePositionOutsideChar(model.braces[1] + 1, 1) - posLineStart; DrawIndicator(braceIndicator, braceOffset, braceOffset + 1, surface, vsDraw, ll, xStart, rcLine, secondOffset, subLine, Indicator::State::normal, 1, model.BidirectionalEnabled(), tabWidthMinimumPixels); } } } } } void EditView::DrawFoldDisplayText(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll, Sci::Line line, int xStart, PRectangle rcLine, int subLine, XYACCUMULATOR subLineStart, DrawPhase phase) { const bool lastSubLine = subLine == (ll->lines - 1); if (!lastSubLine) return; const char *text = model.GetFoldDisplayText(line); if (!text) return; PRectangle rcSegment = rcLine; const std::string_view foldDisplayText(text); FontAlias fontText = vsDraw.styles[STYLE_FOLDDISPLAYTEXT].font; const int widthFoldDisplayText = static_cast(surface->WidthText(fontText, foldDisplayText)); int eolInSelection = 0; int alpha = SC_ALPHA_NOALPHA; if (!hideSelection) { const Sci::Position posAfterLineEnd = model.pdoc->LineStart(line + 1); eolInSelection = (subLine == (ll->lines - 1)) ? model.sel.InSelectionForEOL(posAfterLineEnd) : 0; alpha = (eolInSelection == 1) ? vsDraw.selAlpha : vsDraw.selAdditionalAlpha; } const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth; const XYPOSITION virtualSpace = model.sel.VirtualSpaceFor( model.pdoc->LineEnd(line)) * spaceWidth; rcSegment.left = xStart + static_cast(ll->positions[ll->numCharsInLine] - subLineStart) + virtualSpace + vsDraw.aveCharWidth; rcSegment.right = rcSegment.left + static_cast(widthFoldDisplayText); const ColourOptional background = vsDraw.Background(model.pdoc->GetMark(line), model.caret.active, ll->containsCaret); ColourDesired textFore = vsDraw.styles[STYLE_FOLDDISPLAYTEXT].fore; if (eolInSelection && (vsDraw.selColours.fore.isSet)) { textFore = (eolInSelection == 1) ? vsDraw.selColours.fore : vsDraw.selAdditionalForeground; } const ColourDesired textBack = TextBackground(model, vsDraw, ll, background, eolInSelection, false, STYLE_FOLDDISPLAYTEXT, -1); if (model.trackLineWidth) { if (rcSegment.right + 1> lineWidthMaxSeen) { // Fold display text border drawn on rcSegment.right with width 1 is the last visible object of the line lineWidthMaxSeen = static_cast(rcSegment.right + 1); } } if (phase & drawBack) { surface->FillRectangle(rcSegment, textBack); // Fill Remainder of the line PRectangle rcRemainder = rcSegment; rcRemainder.left = rcRemainder.right; if (rcRemainder.left < rcLine.left) rcRemainder.left = rcLine.left; rcRemainder.right = rcLine.right; FillLineRemainder(surface, model, vsDraw, ll, line, rcRemainder, subLine); } if (phase & drawText) { if (phasesDraw != phasesOne) { surface->DrawTextTransparent(rcSegment, fontText, rcSegment.top + vsDraw.maxAscent, foldDisplayText, textFore); } else { surface->DrawTextNoClip(rcSegment, fontText, rcSegment.top + vsDraw.maxAscent, foldDisplayText, textFore, textBack); } } if (phase & drawIndicatorsFore) { if (model.foldDisplayTextStyle == SC_FOLDDISPLAYTEXT_BOXED) { surface->PenColour(textFore); PRectangle rcBox = rcSegment; rcBox.left = std::round(rcSegment.left); rcBox.right = std::round(rcSegment.right); const IntegerRectangle ircBox(rcBox); surface->MoveTo(ircBox.left, ircBox.top); surface->LineTo(ircBox.left, ircBox.bottom); surface->MoveTo(ircBox.right, ircBox.top); surface->LineTo(ircBox.right, ircBox.bottom); surface->MoveTo(ircBox.left, ircBox.top); surface->LineTo(ircBox.right, ircBox.top); surface->MoveTo(ircBox.left, ircBox.bottom - 1); surface->LineTo(ircBox.right, ircBox.bottom - 1); } } if (phase & drawSelectionTranslucent) { if (eolInSelection && vsDraw.selColours.back.isSet && (line < model.pdoc->LinesTotal() - 1) && alpha != SC_ALPHA_NOALPHA) { SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, eolInSelection == 1, model.primarySelection), alpha); } } } 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) { const bool lastSubLine = subLine == (ll->lines - 1); if (!lastSubLine) return; if (vsDraw.eolAnnotationVisible == EOLANNOTATION_HIDDEN) { return; } const StyledText stEOLAnnotation = model.pdoc->EOLAnnotationStyledText(line); if (!stEOLAnnotation.text || !ValidStyledText(vsDraw, vsDraw.eolAnnotationStyleOffset, stEOLAnnotation)) { return; } const std::string_view eolAnnotationText(stEOLAnnotation.text, stEOLAnnotation.length); const size_t style = stEOLAnnotation.style + vsDraw.eolAnnotationStyleOffset; PRectangle rcSegment = rcLine; FontAlias fontText = vsDraw.styles[style].font; const int widthEOLAnnotationText = static_cast(surface->WidthText(fontText, eolAnnotationText)); const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth; const XYPOSITION virtualSpace = model.sel.VirtualSpaceFor( model.pdoc->LineEnd(line)) * spaceWidth; rcSegment.left = xStart + static_cast(ll->positions[ll->numCharsInLine] - subLineStart) + virtualSpace + vsDraw.aveCharWidth; const char *textFoldDisplay = model.GetFoldDisplayText(line); if (textFoldDisplay) { const std::string_view foldDisplayText(textFoldDisplay); rcSegment.left += (static_cast(surface->WidthText(fontText, foldDisplayText)) + vsDraw.aveCharWidth); } rcSegment.right = rcSegment.left + static_cast(widthEOLAnnotationText); const ColourOptional background = vsDraw.Background(model.pdoc->GetMark(line), model.caret.active, ll->containsCaret); ColourDesired textFore = vsDraw.styles[style].fore; const ColourDesired textBack = TextBackground(model, vsDraw, ll, background, false, false, static_cast(style), -1); if (model.trackLineWidth) { if (rcSegment.right + 1> lineWidthMaxSeen) { // EOL Annotation text border drawn on rcSegment.right with width 1 is the last visible object of the line lineWidthMaxSeen = static_cast(rcSegment.right + 1); } } if (phase & drawBack) { surface->FillRectangle(rcSegment, textBack); // Fill Remainder of the line PRectangle rcRemainder = rcSegment; rcRemainder.left = rcRemainder.right; if (rcRemainder.left < rcLine.left) rcRemainder.left = rcLine.left; rcRemainder.right = rcLine.right; FillLineRemainder(surface, model, vsDraw, ll, line, rcRemainder, subLine); } if (phase & drawText) { if (phasesDraw != phasesOne) { surface->DrawTextTransparent(rcSegment, fontText, rcSegment.top + vsDraw.maxAscent, eolAnnotationText, textFore); } else { surface->DrawTextNoClip(rcSegment, fontText, rcSegment.top + vsDraw.maxAscent, eolAnnotationText, textFore, textBack); } } if (phase & drawIndicatorsFore) { if (vsDraw.eolAnnotationVisible == EOLANNOTATION_BOXED ) { surface->PenColour(textFore); PRectangle rcBox = rcSegment; rcBox.left = std::round(rcSegment.left); rcBox.right = std::round(rcSegment.right); const IntegerRectangle ircBox(rcBox); surface->MoveTo(ircBox.left, ircBox.top); surface->LineTo(ircBox.left, ircBox.bottom); surface->MoveTo(ircBox.right, ircBox.top); surface->LineTo(ircBox.right, ircBox.bottom); surface->MoveTo(ircBox.left, ircBox.top); surface->LineTo(ircBox.right, ircBox.top); surface->MoveTo(ircBox.left, ircBox.bottom - 1); surface->LineTo(ircBox.right, ircBox.bottom - 1); } } } static constexpr bool AnnotationBoxedOrIndented(int annotationVisible) noexcept { return annotationVisible == ANNOTATION_BOXED || annotationVisible == ANNOTATION_INDENTED; } void EditView::DrawAnnotation(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll, Sci::Line line, int xStart, PRectangle rcLine, int subLine, DrawPhase phase) { const int indent = static_cast(model.pdoc->GetLineIndentation(line) * vsDraw.spaceWidth); PRectangle rcSegment = rcLine; const int annotationLine = subLine - ll->lines; const StyledText stAnnotation = model.pdoc->AnnotationStyledText(line); if (stAnnotation.text && ValidStyledText(vsDraw, vsDraw.annotationStyleOffset, stAnnotation)) { if (phase & drawBack) { surface->FillRectangle(rcSegment, vsDraw.styles[0].back); } rcSegment.left = static_cast(xStart); if (model.trackLineWidth || AnnotationBoxedOrIndented(vsDraw.annotationVisible)) { // Only care about calculating width if tracking or need to draw indented box int widthAnnotation = WidestLineWidth(surface, vsDraw, vsDraw.annotationStyleOffset, stAnnotation); if (AnnotationBoxedOrIndented(vsDraw.annotationVisible)) { widthAnnotation += static_cast(vsDraw.spaceWidth * 2); // Margins rcSegment.left = static_cast(xStart + indent); rcSegment.right = rcSegment.left + widthAnnotation; } if (widthAnnotation > lineWidthMaxSeen) lineWidthMaxSeen = widthAnnotation; } const int annotationLines = model.pdoc->AnnotationLines(line); size_t start = 0; size_t lengthAnnotation = stAnnotation.LineLength(start); int lineInAnnotation = 0; while ((lineInAnnotation < annotationLine) && (start < stAnnotation.length)) { start += lengthAnnotation + 1; lengthAnnotation = stAnnotation.LineLength(start); lineInAnnotation++; } PRectangle rcText = rcSegment; if ((phase & drawBack) && AnnotationBoxedOrIndented(vsDraw.annotationVisible)) { surface->FillRectangle(rcText, vsDraw.styles[stAnnotation.StyleAt(start) + vsDraw.annotationStyleOffset].back); rcText.left += vsDraw.spaceWidth; } DrawStyledText(surface, vsDraw, vsDraw.annotationStyleOffset, rcText, stAnnotation, start, lengthAnnotation, phase); if ((phase & drawBack) && (vsDraw.annotationVisible == ANNOTATION_BOXED)) { surface->PenColour(vsDraw.styles[vsDraw.annotationStyleOffset].fore); const IntegerRectangle ircSegment(rcSegment); surface->MoveTo(ircSegment.left, ircSegment.top); surface->LineTo(ircSegment.left, ircSegment.bottom); surface->MoveTo(ircSegment.right, ircSegment.top); surface->LineTo(ircSegment.right, ircSegment.bottom); if (subLine == ll->lines) { surface->MoveTo(ircSegment.left, ircSegment.top); surface->LineTo(ircSegment.right, ircSegment.top); } if (subLine == ll->lines + annotationLines - 1) { surface->MoveTo(ircSegment.left, ircSegment.bottom - 1); surface->LineTo(ircSegment.right, ircSegment.bottom - 1); } } } } static void DrawBlockCaret(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll, int subLine, int xStart, Sci::Position offset, Sci::Position posCaret, PRectangle rcCaret, ColourDesired caretColour) { const Sci::Position lineStart = ll->LineStart(subLine); Sci::Position posBefore = posCaret; Sci::Position posAfter = model.pdoc->MovePositionOutsideChar(posCaret + 1, 1); Sci::Position numCharsToDraw = posAfter - posCaret; // Work out where the starting and ending offsets are. We need to // see if the previous character shares horizontal space, such as a // glyph / combining character. If so we'll need to draw that too. Sci::Position offsetFirstChar = offset; Sci::Position offsetLastChar = offset + (posAfter - posCaret); while ((posBefore > 0) && ((offsetLastChar - numCharsToDraw) >= lineStart)) { if ((ll->positions[offsetLastChar] - ll->positions[offsetLastChar - numCharsToDraw]) > 0) { // The char does not share horizontal space break; } // Char shares horizontal space, update the numChars to draw // Update posBefore to point to the prev char posBefore = model.pdoc->MovePositionOutsideChar(posBefore - 1, -1); numCharsToDraw = posAfter - posBefore; offsetFirstChar = offset - (posCaret - posBefore); } // See if the next character shares horizontal space, if so we'll // need to draw that too. if (offsetFirstChar < 0) offsetFirstChar = 0; numCharsToDraw = offsetLastChar - offsetFirstChar; while ((offsetLastChar < ll->LineStart(subLine + 1)) && (offsetLastChar <= ll->numCharsInLine)) { // Update posAfter to point to the 2nd next char, this is where // the next character ends, and 2nd next begins. We'll need // to compare these two posBefore = posAfter; posAfter = model.pdoc->MovePositionOutsideChar(posAfter + 1, 1); offsetLastChar = offset + (posAfter - posCaret); if ((ll->positions[offsetLastChar] - ll->positions[offsetLastChar - (posAfter - posBefore)]) > 0) { // The char does not share horizontal space break; } // Char shares horizontal space, update the numChars to draw numCharsToDraw = offsetLastChar - offsetFirstChar; } // We now know what to draw, update the caret drawing rectangle rcCaret.left = ll->positions[offsetFirstChar] - ll->positions[lineStart] + xStart; rcCaret.right = ll->positions[offsetFirstChar + numCharsToDraw] - ll->positions[lineStart] + xStart; // Adjust caret position to take into account any word wrapping symbols. if ((ll->wrapIndent != 0) && (lineStart != 0)) { const XYPOSITION wordWrapCharWidth = ll->wrapIndent; rcCaret.left += wordWrapCharWidth; rcCaret.right += wordWrapCharWidth; } // This character is where the caret block is, we override the colours // (inversed) for drawing the caret here. const int styleMain = ll->styles[offsetFirstChar]; FontAlias fontText = vsDraw.styles[styleMain].font; const std::string_view text(&ll->chars[offsetFirstChar], numCharsToDraw); surface->DrawTextClipped(rcCaret, fontText, rcCaret.top + vsDraw.maxAscent, text, vsDraw.styles[styleMain].back, caretColour); } void EditView::DrawCarets(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll, Sci::Line lineDoc, int xStart, PRectangle rcLine, int subLine) const { // When drag is active it is the only caret drawn const bool drawDrag = model.posDrag.IsValid(); if (hideSelection && !drawDrag) return; const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc); // For each selection draw for (size_t r = 0; (r model.sel.Range(r).anchor) { if (posCaret.VirtualSpace() > 0) posCaret.SetVirtualSpace(posCaret.VirtualSpace() - 1); else posCaret.SetPosition(model.pdoc->MovePositionOutsideChar(posCaret.Position()-1, -1)); } const int offset = static_cast(posCaret.Position() - posLineStart); const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth; const XYPOSITION virtualOffset = posCaret.VirtualSpace() * spaceWidth; if (ll->InLine(offset, subLine) && offset <= ll->numCharsBeforeEOL) { XYPOSITION xposCaret = ll->positions[offset] + virtualOffset - ll->positions[ll->LineStart(subLine)]; if (model.BidirectionalEnabled() && (posCaret.VirtualSpace() == 0)) { // Get caret point const ScreenLine screenLine(ll, subLine, vsDraw, rcLine.right, tabWidthMinimumPixels); const int caretPosition = offset - ll->LineStart(subLine); std::unique_ptr slLayout = surface->Layout(&screenLine); const XYPOSITION caretLeft = slLayout->XFromPosition(caretPosition); // In case of start of line, the cursor should be at the right xposCaret = caretLeft + virtualOffset; } if (ll->wrapIndent != 0) { const Sci::Position lineStart = ll->LineStart(subLine); if (lineStart != 0) // Wrapped xposCaret += ll->wrapIndent; } const bool caretBlinkState = (model.caret.active && model.caret.on) || (!additionalCaretsBlink && !mainCaret); const bool caretVisibleState = additionalCaretsVisible || mainCaret; if ((xposCaret >= 0) && vsDraw.IsCaretVisible() && (drawDrag || (caretBlinkState && caretVisibleState))) { bool canDrawBlockCaret = true; bool drawBlockCaret = false; XYPOSITION widthOverstrikeCaret; XYPOSITION caretWidthOffset = 0; PRectangle rcCaret = rcLine; if (posCaret.Position() == model.pdoc->Length()) { // At end of document canDrawBlockCaret = false; widthOverstrikeCaret = vsDraw.aveCharWidth; } else if ((posCaret.Position() - posLineStart) >= ll->numCharsInLine) { // At end of line canDrawBlockCaret = false; widthOverstrikeCaret = vsDraw.aveCharWidth; } else { const int widthChar = model.pdoc->LenChar(posCaret.Position()); widthOverstrikeCaret = ll->positions[offset + widthChar] - ll->positions[offset]; } if (widthOverstrikeCaret < 3) // Make sure its visible widthOverstrikeCaret = 3; if (xposCaret > 0) caretWidthOffset = 0.51f; // Move back so overlaps both character cells. xposCaret += xStart; const ViewStyle::CaretShape caretShape = drawDrag ? ViewStyle::CaretShape::line : vsDraw.CaretShapeForMode(model.inOverstrike); if (drawDrag) { /* Dragging text, use a line caret */ rcCaret.left = std::round(xposCaret - caretWidthOffset); rcCaret.right = rcCaret.left + vsDraw.caretWidth; } else if ((caretShape == ViewStyle::CaretShape::bar) && drawOverstrikeCaret) { /* Overstrike (insert mode), use a modified bar caret */ rcCaret.top = rcCaret.bottom - 2; rcCaret.left = xposCaret + 1; rcCaret.right = rcCaret.left + widthOverstrikeCaret - 1; } else if ((caretShape == ViewStyle::CaretShape::block) || imeCaretBlockOverride) { /* Block caret */ rcCaret.left = xposCaret; if (canDrawBlockCaret && !(IsControlCharacter(ll->chars[offset]))) { drawBlockCaret = true; rcCaret.right = xposCaret + widthOverstrikeCaret; } else { rcCaret.right = xposCaret + vsDraw.aveCharWidth; } } else { /* Line caret */ rcCaret.left = std::round(xposCaret - caretWidthOffset); rcCaret.right = rcCaret.left + vsDraw.caretWidth; } const ColourDesired caretColour = mainCaret ? vsDraw.caretcolour : vsDraw.additionalCaretColour; if (drawBlockCaret) { DrawBlockCaret(surface, model, vsDraw, ll, subLine, xStart, offset, posCaret.Position(), rcCaret, caretColour); } else { surface->FillRectangle(rcCaret, caretColour); } } } if (drawDrag) break; } } static void DrawWrapIndentAndMarker(Surface *surface, const ViewStyle &vsDraw, const LineLayout *ll, int xStart, PRectangle rcLine, ColourOptional background, DrawWrapMarkerFn customDrawWrapMarker, bool caretActive) { // default bgnd here.. surface->FillRectangle(rcLine, background.isSet ? background : vsDraw.styles[STYLE_DEFAULT].back); if (vsDraw.IsLineFrameOpaque(caretActive, ll->containsCaret)) { const int width = vsDraw.GetFrameWidth(); // Draw left of frame under marker DrawFrame(surface, vsDraw.caretLineBackground, vsDraw.caretLineAlpha, PRectangle(rcLine.left, rcLine.top, rcLine.left + width, rcLine.bottom)); } if (vsDraw.wrapVisualFlags & SC_WRAPVISUALFLAG_START) { // draw continuation rect PRectangle rcPlace = rcLine; rcPlace.left = static_cast(xStart); rcPlace.right = rcPlace.left + ll->wrapIndent; if (vsDraw.wrapVisualFlagsLocation & SC_WRAPVISUALFLAGLOC_START_BY_TEXT) rcPlace.left = rcPlace.right - vsDraw.aveCharWidth; else rcPlace.right = rcPlace.left + vsDraw.aveCharWidth; if (!customDrawWrapMarker) { DrawWrapMarker(surface, rcPlace, false, vsDraw.WrapColour()); } else { customDrawWrapMarker(surface, rcPlace, false, vsDraw.WrapColour()); } } } void EditView::DrawBackground(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll, PRectangle rcLine, Range lineRange, Sci::Position posLineStart, int xStart, int subLine, ColourOptional background) const { const bool selBackDrawn = vsDraw.SelectionBackgroundDrawn(); bool inIndentation = subLine == 0; // Do not handle indentation except on first subline. const XYACCUMULATOR subLineStart = ll->positions[lineRange.start]; // Does not take margin into account but not significant const int xStartVisible = static_cast(subLineStart)-xStart; BreakFinder bfBack(ll, &model.sel, lineRange, posLineStart, xStartVisible, selBackDrawn, model.pdoc, &model.reprs, nullptr); const bool drawWhitespaceBackground = vsDraw.WhitespaceBackgroundDrawn() && !background.isSet; // Background drawing loop while (bfBack.More()) { const TextSegment ts = bfBack.Next(); const Sci::Position i = ts.end() - 1; const Sci::Position iDoc = i + posLineStart; PRectangle rcSegment = rcLine; rcSegment.left = ll->positions[ts.start] + xStart - static_cast(subLineStart); rcSegment.right = ll->positions[ts.end()] + xStart - static_cast(subLineStart); // Only try to draw if really visible - enhances performance by not calling environment to // draw strings that are completely past the right side of the window. if (!rcSegment.Empty() && rcSegment.Intersects(rcLine)) { // Clip to line rectangle, since may have a huge position which will not work with some platforms if (rcSegment.left < rcLine.left) rcSegment.left = rcLine.left; if (rcSegment.right > rcLine.right) rcSegment.right = rcLine.right; const int inSelection = hideSelection ? 0 : model.sel.CharacterInSelection(iDoc); const bool inHotspot = (ll->hotspot.Valid()) && ll->hotspot.ContainsCharacter(iDoc); ColourDesired textBack = TextBackground(model, vsDraw, ll, background, inSelection, inHotspot, ll->styles[i], i); if (ts.representation) { if (ll->chars[i] == '\t') { // Tab display if (drawWhitespaceBackground && vsDraw.WhiteSpaceVisible(inIndentation)) textBack = vsDraw.whitespaceColours.back; } else { // Blob display inIndentation = false; } surface->FillRectangle(rcSegment, textBack); } else { // Normal text display surface->FillRectangle(rcSegment, textBack); if (vsDraw.viewWhitespace != wsInvisible) { for (int cpos = 0; cpos <= i - ts.start; cpos++) { if (ll->chars[cpos + ts.start] == ' ') { if (drawWhitespaceBackground && vsDraw.WhiteSpaceVisible(inIndentation)) { const PRectangle rcSpace( ll->positions[cpos + ts.start] + xStart - static_cast(subLineStart), rcSegment.top, ll->positions[cpos + ts.start + 1] + xStart - static_cast(subLineStart), rcSegment.bottom); surface->FillRectangle(rcSpace, vsDraw.whitespaceColours.back); } } else { inIndentation = false; } } } } } else if (rcSegment.left > rcLine.right) { break; } } } static void DrawEdgeLine(Surface *surface, const ViewStyle &vsDraw, const LineLayout *ll, PRectangle rcLine, Range lineRange, int xStart) { if (vsDraw.edgeState == EDGE_LINE) { PRectangle rcSegment = rcLine; const int edgeX = static_cast(vsDraw.theEdge.column * vsDraw.spaceWidth); rcSegment.left = static_cast(edgeX + xStart); if ((ll->wrapIndent != 0) && (lineRange.start != 0)) rcSegment.left -= ll->wrapIndent; rcSegment.right = rcSegment.left + 1; surface->FillRectangle(rcSegment, vsDraw.theEdge.colour); } else if (vsDraw.edgeState == EDGE_MULTILINE) { for (size_t edge = 0; edge < vsDraw.theMultiEdge.size(); edge++) { if (vsDraw.theMultiEdge[edge].column >= 0) { PRectangle rcSegment = rcLine; const int edgeX = static_cast(vsDraw.theMultiEdge[edge].column * vsDraw.spaceWidth); rcSegment.left = static_cast(edgeX + xStart); if ((ll->wrapIndent != 0) && (lineRange.start != 0)) rcSegment.left -= ll->wrapIndent; rcSegment.right = rcSegment.left + 1; surface->FillRectangle(rcSegment, vsDraw.theMultiEdge[edge].colour); } } } } // Draw underline mark as part of background if not transparent static void DrawMarkUnderline(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, Sci::Line line, PRectangle rcLine) { int marks = model.pdoc->GetMark(line); for (int markBit = 0; (markBit < 32) && marks; markBit++) { if ((marks & 1) && (vsDraw.markers[markBit].markType == SC_MARK_UNDERLINE) && (vsDraw.markers[markBit].alpha == SC_ALPHA_NOALPHA)) { PRectangle rcUnderline = rcLine; rcUnderline.top = rcUnderline.bottom - 2; surface->FillRectangle(rcUnderline, vsDraw.markers[markBit].back); } marks >>= 1; } } static void DrawTranslucentSelection(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll, Sci::Line line, PRectangle rcLine, int subLine, Range lineRange, int xStart, int tabWidthMinimumPixels) { if ((vsDraw.selAlpha != SC_ALPHA_NOALPHA) || (vsDraw.selAdditionalAlpha != SC_ALPHA_NOALPHA)) { const Sci::Position posLineStart = model.pdoc->LineStart(line); const XYACCUMULATOR subLineStart = ll->positions[lineRange.start]; // For each selection draw Sci::Position virtualSpaces = 0; if (subLine == (ll->lines - 1)) { virtualSpaces = model.sel.VirtualSpaceFor(model.pdoc->LineEnd(line)); } const SelectionPosition posStart(posLineStart + lineRange.start); const SelectionPosition posEnd(posLineStart + lineRange.end, virtualSpaces); const SelectionSegment virtualSpaceRange(posStart, posEnd); for (size_t r = 0; r < model.sel.Count(); r++) { const int alpha = (r == model.sel.Main()) ? vsDraw.selAlpha : vsDraw.selAdditionalAlpha; if (alpha != SC_ALPHA_NOALPHA) { const SelectionSegment portion = model.sel.Range(r).Intersect(virtualSpaceRange); if (!portion.Empty()) { const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth; if (model.BidirectionalEnabled()) { const int selectionStart = static_cast(portion.start.Position() - posLineStart - lineRange.start); const int selectionEnd = static_cast(portion.end.Position() - posLineStart - lineRange.start); const ColourDesired background = SelectionBackground(vsDraw, r == model.sel.Main(), model.primarySelection); const ScreenLine screenLine(ll, subLine, vsDraw, rcLine.right, tabWidthMinimumPixels); std::unique_ptr slLayout = surface->Layout(&screenLine); const std::vector intervals = slLayout->FindRangeIntervals(selectionStart, selectionEnd); for (const Interval &interval : intervals) { const XYPOSITION rcRight = interval.right + xStart; const XYPOSITION rcLeft = interval.left + xStart; const PRectangle rcSelection(rcLeft, rcLine.top, rcRight, rcLine.bottom); SimpleAlphaRectangle(surface, rcSelection, background, alpha); } if (portion.end.VirtualSpace()) { const XYPOSITION xStartVirtual = ll->positions[lineRange.end] - static_cast(subLineStart) + xStart; PRectangle rcSegment = rcLine; rcSegment.left = xStartVirtual + portion.start.VirtualSpace() * spaceWidth; rcSegment.right = xStartVirtual + portion.end.VirtualSpace() * spaceWidth; SimpleAlphaRectangle(surface, rcSegment, background, alpha); } } else { PRectangle rcSegment = rcLine; rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] - static_cast(subLineStart) + portion.start.VirtualSpace() * spaceWidth; rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] - static_cast(subLineStart) + portion.end.VirtualSpace() * spaceWidth; if ((ll->wrapIndent != 0) && (lineRange.start != 0)) { if ((portion.start.Position() - posLineStart) == lineRange.start && model.sel.Range(r).ContainsCharacter(portion.start.Position() - 1)) rcSegment.left -= static_cast(ll->wrapIndent); // indentation added to xStart was truncated to int, so we do the same here } rcSegment.left = (rcSegment.left > rcLine.left) ? rcSegment.left : rcLine.left; rcSegment.right = (rcSegment.right < rcLine.right) ? rcSegment.right : rcLine.right; if (rcSegment.right > rcLine.left) SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, r == model.sel.Main(), model.primarySelection), alpha); } } } } } } // Draw any translucent whole line states static void DrawTranslucentLineState(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll, Sci::Line line, PRectangle rcLine, int subLine) { if ((model.caret.active || vsDraw.alwaysShowCaretLineBackground) && vsDraw.showCaretLineBackground && ll->containsCaret && vsDraw.caretLineAlpha != SC_ALPHA_NOALPHA) { if (vsDraw.caretLineFrame) { DrawCaretLineFramed(surface, vsDraw, ll, rcLine, subLine); } else { SimpleAlphaRectangle(surface, rcLine, vsDraw.caretLineBackground, vsDraw.caretLineAlpha); } } const int marksOfLine = model.pdoc->GetMark(line); int marksDrawnInText = marksOfLine & vsDraw.maskDrawInText; for (int markBit = 0; (markBit < 32) && marksDrawnInText; markBit++) { if (marksDrawnInText & 1) { if (vsDraw.markers[markBit].markType == SC_MARK_BACKGROUND) { SimpleAlphaRectangle(surface, rcLine, vsDraw.markers[markBit].back, vsDraw.markers[markBit].alpha); } else if (vsDraw.markers[markBit].markType == SC_MARK_UNDERLINE) { PRectangle rcUnderline = rcLine; rcUnderline.top = rcUnderline.bottom - 2; SimpleAlphaRectangle(surface, rcUnderline, vsDraw.markers[markBit].back, vsDraw.markers[markBit].alpha); } } marksDrawnInText >>= 1; } int marksDrawnInLine = marksOfLine & vsDraw.maskInLine; for (int markBit = 0; (markBit < 32) && marksDrawnInLine; markBit++) { if (marksDrawnInLine & 1) { SimpleAlphaRectangle(surface, rcLine, vsDraw.markers[markBit].back, vsDraw.markers[markBit].alpha); } marksDrawnInLine >>= 1; } } void EditView::DrawForeground(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll, Sci::Line lineVisible, PRectangle rcLine, Range lineRange, Sci::Position posLineStart, int xStart, int subLine, ColourOptional background) { const bool selBackDrawn = vsDraw.SelectionBackgroundDrawn(); const bool drawWhitespaceBackground = vsDraw.WhitespaceBackgroundDrawn() && !background.isSet; bool inIndentation = subLine == 0; // Do not handle indentation except on first subline. const XYACCUMULATOR subLineStart = ll->positions[lineRange.start]; const XYPOSITION indentWidth = model.pdoc->IndentSize() * vsDraw.spaceWidth; // Does not take margin into account but not significant const int xStartVisible = static_cast(subLineStart)-xStart; // Foreground drawing loop BreakFinder bfFore(ll, &model.sel, lineRange, posLineStart, xStartVisible, (((phasesDraw == phasesOne) && selBackDrawn) || vsDraw.selColours.fore.isSet), model.pdoc, &model.reprs, &vsDraw); while (bfFore.More()) { const TextSegment ts = bfFore.Next(); const Sci::Position i = ts.end() - 1; const Sci::Position iDoc = i + posLineStart; PRectangle rcSegment = rcLine; rcSegment.left = ll->positions[ts.start] + xStart - static_cast(subLineStart); rcSegment.right = ll->positions[ts.end()] + xStart - static_cast(subLineStart); // Only try to draw if really visible - enhances performance by not calling environment to // draw strings that are completely past the right side of the window. if (rcSegment.Intersects(rcLine)) { const int styleMain = ll->styles[i]; ColourDesired textFore = vsDraw.styles[styleMain].fore; FontAlias textFont = vsDraw.styles[styleMain].font; //hotspot foreground const bool inHotspot = (ll->hotspot.Valid()) && ll->hotspot.ContainsCharacter(iDoc); if (inHotspot) { if (vsDraw.hotspotColours.fore.isSet) textFore = vsDraw.hotspotColours.fore; } if (vsDraw.indicatorsSetFore) { // At least one indicator sets the text colour so see if it applies to this segment for (const IDecoration *deco : model.pdoc->decorations->View()) { const int indicatorValue = deco->ValueAt(ts.start + posLineStart); if (indicatorValue) { const Indicator &indicator = vsDraw.indicators[deco->Indicator()]; bool hover = false; if (indicator.IsDynamic()) { const Sci::Position startPos = ts.start + posLineStart; const Range rangeRun(deco->StartRun(startPos), deco->EndRun(startPos)); hover = rangeRun.ContainsCharacter(model.hoverIndicatorPos); } if (hover) { if (indicator.sacHover.style == INDIC_TEXTFORE) { textFore = indicator.sacHover.fore; } } else { if (indicator.sacNormal.style == INDIC_TEXTFORE) { if (indicator.Flags() & SC_INDICFLAG_VALUEFORE) textFore = ColourDesired(indicatorValue & SC_INDICVALUEMASK); else textFore = indicator.sacNormal.fore; } } } } } const int inSelection = hideSelection ? 0 : model.sel.CharacterInSelection(iDoc); if (inSelection && (vsDraw.selColours.fore.isSet)) { textFore = (inSelection == 1) ? vsDraw.selColours.fore : vsDraw.selAdditionalForeground; } ColourDesired textBack = TextBackground(model, vsDraw, ll, background, inSelection, inHotspot, styleMain, i); if (ts.representation) { if (ll->chars[i] == '\t') { // Tab display if (phasesDraw == phasesOne) { if (drawWhitespaceBackground && vsDraw.WhiteSpaceVisible(inIndentation)) textBack = vsDraw.whitespaceColours.back; surface->FillRectangle(rcSegment, textBack); } if (inIndentation && vsDraw.viewIndentationGuides == ivReal) { for (int indentCount = static_cast((ll->positions[i] + epsilon) / indentWidth); indentCount <= (ll->positions[i + 1] - epsilon) / indentWidth; indentCount++) { if (indentCount > 0) { const XYPOSITION xIndent = std::floor(indentCount * indentWidth); DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcSegment, (ll->xHighlightGuide == xIndent)); } } } if (vsDraw.viewWhitespace != wsInvisible) { if (vsDraw.WhiteSpaceVisible(inIndentation)) { if (vsDraw.whitespaceColours.fore.isSet) textFore = vsDraw.whitespaceColours.fore; surface->PenColour(textFore); const PRectangle rcTab(rcSegment.left + 1, rcSegment.top + tabArrowHeight, rcSegment.right - 1, rcSegment.bottom - vsDraw.maxDescent); const int segmentTop = static_cast(rcSegment.top + vsDraw.lineHeight / 2); if (!customDrawTabArrow) DrawTabArrow(surface, rcTab, segmentTop, vsDraw); else customDrawTabArrow(surface, rcTab, segmentTop); } } } else { inIndentation = false; if (vsDraw.controlCharSymbol >= 32) { // Using one font for all control characters so it can be controlled independently to ensure // the box goes around the characters tightly. Seems to be no way to work out what height // is taken by an individual character - internal leading gives varying results. FontAlias ctrlCharsFont = vsDraw.styles[STYLE_CONTROLCHAR].font; const char cc[2] = { static_cast(vsDraw.controlCharSymbol), '\0' }; surface->DrawTextNoClip(rcSegment, ctrlCharsFont, rcSegment.top + vsDraw.maxAscent, cc, textBack, textFore); } else { DrawTextBlob(surface, vsDraw, rcSegment, ts.representation->stringRep, textBack, textFore, phasesDraw == phasesOne); } } } else { // Normal text display if (vsDraw.styles[styleMain].visible) { const std::string_view text(&ll->chars[ts.start], i - ts.start + 1); if (phasesDraw != phasesOne) { surface->DrawTextTransparent(rcSegment, textFont, rcSegment.top + vsDraw.maxAscent, text, textFore); } else { surface->DrawTextNoClip(rcSegment, textFont, rcSegment.top + vsDraw.maxAscent, text, textFore, textBack); } } if (vsDraw.viewWhitespace != wsInvisible || (inIndentation && vsDraw.viewIndentationGuides != ivNone)) { for (int cpos = 0; cpos <= i - ts.start; cpos++) { if (ll->chars[cpos + ts.start] == ' ') { if (vsDraw.viewWhitespace != wsInvisible) { if (vsDraw.whitespaceColours.fore.isSet) textFore = vsDraw.whitespaceColours.fore; if (vsDraw.WhiteSpaceVisible(inIndentation)) { const XYPOSITION xmid = (ll->positions[cpos + ts.start] + ll->positions[cpos + ts.start + 1]) / 2; if ((phasesDraw == phasesOne) && drawWhitespaceBackground) { textBack = vsDraw.whitespaceColours.back; const PRectangle rcSpace( ll->positions[cpos + ts.start] + xStart - static_cast(subLineStart), rcSegment.top, ll->positions[cpos + ts.start + 1] + xStart - static_cast(subLineStart), rcSegment.bottom); surface->FillRectangle(rcSpace, textBack); } const int halfDotWidth = vsDraw.whitespaceSize / 2; PRectangle rcDot(xmid + xStart - halfDotWidth - static_cast(subLineStart), rcSegment.top + vsDraw.lineHeight / 2, 0.0f, 0.0f); rcDot.right = rcDot.left + vsDraw.whitespaceSize; rcDot.bottom = rcDot.top + vsDraw.whitespaceSize; surface->FillRectangle(rcDot, textFore); } } if (inIndentation && vsDraw.viewIndentationGuides == ivReal) { for (int indentCount = static_cast((ll->positions[cpos + ts.start] + epsilon) / indentWidth); indentCount <= (ll->positions[cpos + ts.start + 1] - epsilon) / indentWidth; indentCount++) { if (indentCount > 0) { const XYPOSITION xIndent = std::floor(indentCount * indentWidth); DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcSegment, (ll->xHighlightGuide == xIndent)); } } } } else { inIndentation = false; } } } } if (ll->hotspot.Valid() && vsDraw.hotspotUnderline && ll->hotspot.ContainsCharacter(iDoc)) { PRectangle rcUL = rcSegment; rcUL.top = rcUL.top + vsDraw.maxAscent + 1; rcUL.bottom = rcUL.top + 1; if (vsDraw.hotspotColours.fore.isSet) surface->FillRectangle(rcUL, vsDraw.hotspotColours.fore); else surface->FillRectangle(rcUL, textFore); } else if (vsDraw.styles[styleMain].underline) { PRectangle rcUL = rcSegment; rcUL.top = rcUL.top + vsDraw.maxAscent + 1; rcUL.bottom = rcUL.top + 1; surface->FillRectangle(rcUL, textFore); } } else if (rcSegment.left > rcLine.right) { break; } } } void EditView::DrawIndentGuidesOverEmpty(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll, Sci::Line line, Sci::Line lineVisible, PRectangle rcLine, int xStart, int subLine) { if ((vsDraw.viewIndentationGuides == ivLookForward || vsDraw.viewIndentationGuides == ivLookBoth) && (subLine == 0)) { const Sci::Position posLineStart = model.pdoc->LineStart(line); int indentSpace = model.pdoc->GetLineIndentation(line); int xStartText = static_cast(ll->positions[model.pdoc->GetLineIndentPosition(line) - posLineStart]); // Find the most recent line with some text Sci::Line lineLastWithText = line; while (lineLastWithText > std::max(line - 20, static_cast(0)) && model.pdoc->IsWhiteLine(lineLastWithText)) { lineLastWithText--; } if (lineLastWithText < line) { xStartText = 100000; // Don't limit to visible indentation on empty line // This line is empty, so use indentation of last line with text int indentLastWithText = model.pdoc->GetLineIndentation(lineLastWithText); const int isFoldHeader = model.pdoc->GetLevel(lineLastWithText) & SC_FOLDLEVELHEADERFLAG; if (isFoldHeader) { // Level is one more level than parent indentLastWithText += model.pdoc->IndentSize(); } if (vsDraw.viewIndentationGuides == ivLookForward) { // In viLookForward mode, previous line only used if it is a fold header if (isFoldHeader) { indentSpace = std::max(indentSpace, indentLastWithText); } } else { // viLookBoth indentSpace = std::max(indentSpace, indentLastWithText); } } Sci::Line lineNextWithText = line; while (lineNextWithText < std::min(line + 20, model.pdoc->LinesTotal()) && model.pdoc->IsWhiteLine(lineNextWithText)) { lineNextWithText++; } if (lineNextWithText > line) { xStartText = 100000; // Don't limit to visible indentation on empty line // This line is empty, so use indentation of first next line with text indentSpace = std::max(indentSpace, model.pdoc->GetLineIndentation(lineNextWithText)); } for (int indentPos = model.pdoc->IndentSize(); indentPos < indentSpace; indentPos += model.pdoc->IndentSize()) { const XYPOSITION xIndent = std::floor(indentPos * vsDraw.spaceWidth); if (xIndent < xStartText) { DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcLine, (ll->xHighlightGuide == xIndent)); } } } } void EditView::DrawLine(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll, Sci::Line line, Sci::Line lineVisible, int xStart, PRectangle rcLine, int subLine, DrawPhase phase) { if (subLine >= ll->lines) { DrawAnnotation(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, phase); return; // No further drawing } // See if something overrides the line background colour. const ColourOptional background = vsDraw.Background(model.pdoc->GetMark(line), model.caret.active, ll->containsCaret); const Sci::Position posLineStart = model.pdoc->LineStart(line); const Range lineRange = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly); const Range lineRangeIncludingEnd = ll->SubLineRange(subLine, LineLayout::Scope::includeEnd); const XYACCUMULATOR subLineStart = ll->positions[lineRange.start]; if ((ll->wrapIndent != 0) && (subLine > 0)) { if (phase & drawBack) { DrawWrapIndentAndMarker(surface, vsDraw, ll, xStart, rcLine, background, customDrawWrapMarker, model.caret.active); } xStart += static_cast(ll->wrapIndent); } if (phasesDraw != phasesOne) { if (phase & drawBack) { DrawBackground(surface, model, vsDraw, ll, rcLine, lineRange, posLineStart, xStart, subLine, background); DrawFoldDisplayText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, drawBack); DrawEOLAnnotationText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, drawBack); phase = static_cast(phase & ~drawBack); // Remove drawBack to not draw again in DrawFoldDisplayText DrawEOL(surface, model, vsDraw, ll, rcLine, line, lineRange.end, xStart, subLine, subLineStart, background); if (vsDraw.IsLineFrameOpaque(model.caret.active, ll->containsCaret)) DrawCaretLineFramed(surface, vsDraw, ll, rcLine, subLine); } if (phase & drawIndicatorsBack) { DrawIndicators(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, lineRangeIncludingEnd.end, true, tabWidthMinimumPixels); DrawEdgeLine(surface, vsDraw, ll, rcLine, lineRange, xStart); DrawMarkUnderline(surface, model, vsDraw, line, rcLine); } } if (phase & drawText) { DrawForeground(surface, model, vsDraw, ll, lineVisible, rcLine, lineRange, posLineStart, xStart, subLine, background); } if (phase & drawIndentationGuides) { DrawIndentGuidesOverEmpty(surface, model, vsDraw, ll, line, lineVisible, rcLine, xStart, subLine); } if (phase & drawIndicatorsFore) { DrawIndicators(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, lineRangeIncludingEnd.end, false, tabWidthMinimumPixels); } DrawFoldDisplayText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, phase); DrawEOLAnnotationText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, phase); if (phasesDraw == phasesOne) { DrawEOL(surface, model, vsDraw, ll, rcLine, line, lineRange.end, xStart, subLine, subLineStart, background); if (vsDraw.IsLineFrameOpaque(model.caret.active, ll->containsCaret)) DrawCaretLineFramed(surface, vsDraw, ll, rcLine, subLine); DrawEdgeLine(surface, vsDraw, ll, rcLine, lineRange, xStart); DrawMarkUnderline(surface, model, vsDraw, line, rcLine); } if (!hideSelection && (phase & drawSelectionTranslucent)) { DrawTranslucentSelection(surface, model, vsDraw, ll, line, rcLine, subLine, lineRange, xStart, tabWidthMinimumPixels); } if (phase & drawLineTranslucent) { DrawTranslucentLineState(surface, model, vsDraw, ll, line, rcLine, subLine); } } static void DrawFoldLines(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, Sci::Line line, PRectangle rcLine) { const bool expanded = model.pcs->GetExpanded(line); const int level = model.pdoc->GetLevel(line); const int levelNext = model.pdoc->GetLevel(line + 1); if ((level & SC_FOLDLEVELHEADERFLAG) && (LevelNumber(level) < LevelNumber(levelNext))) { // Paint the line above the fold if ((expanded && (model.foldFlags & SC_FOLDFLAG_LINEBEFORE_EXPANDED)) || (!expanded && (model.foldFlags & SC_FOLDFLAG_LINEBEFORE_CONTRACTED))) { PRectangle rcFoldLine = rcLine; rcFoldLine.bottom = rcFoldLine.top + 1; surface->FillRectangle(rcFoldLine, vsDraw.styles[STYLE_DEFAULT].fore); } // Paint the line below the fold if ((expanded && (model.foldFlags & SC_FOLDFLAG_LINEAFTER_EXPANDED)) || (!expanded && (model.foldFlags & SC_FOLDFLAG_LINEAFTER_CONTRACTED))) { PRectangle rcFoldLine = rcLine; rcFoldLine.top = rcFoldLine.bottom - 1; surface->FillRectangle(rcFoldLine, vsDraw.styles[STYLE_DEFAULT].fore); } } } void EditView::PaintText(Surface *surfaceWindow, const EditModel &model, PRectangle rcArea, PRectangle rcClient, const ViewStyle &vsDraw) { // Allow text at start of line to overlap 1 pixel into the margin as this displays // serifs and italic stems for aliased text. const int leftTextOverlap = ((model.xOffset == 0) && (vsDraw.leftMarginWidth > 0)) ? 1 : 0; // Do the painting if (rcArea.right > vsDraw.textStart - leftTextOverlap) { Surface *surface = surfaceWindow; if (bufferedDraw) { surface = pixmapLine.get(); PLATFORM_ASSERT(pixmapLine->Initialised()); } surface->SetUnicodeMode(SC_CP_UTF8 == model.pdoc->dbcsCodePage); surface->SetDBCSMode(model.pdoc->dbcsCodePage); surface->SetBidiR2L(model.BidirectionalR2L()); const Point ptOrigin = model.GetVisibleOriginInMain(); const int screenLinePaintFirst = static_cast(rcArea.top) / vsDraw.lineHeight; const int xStart = vsDraw.textStart - model.xOffset + static_cast(ptOrigin.x); SelectionPosition posCaret = model.sel.RangeMain().caret; if (model.posDrag.IsValid()) posCaret = model.posDrag; const Sci::Line lineCaret = model.pdoc->SciLineFromPosition(posCaret.Position()); PRectangle rcTextArea = rcClient; if (vsDraw.marginInside) { rcTextArea.left += vsDraw.textStart; rcTextArea.right -= vsDraw.rightMarginWidth; } else { rcTextArea = rcArea; } // Remove selection margin from drawing area so text will not be drawn // on it in unbuffered mode. if (!bufferedDraw && vsDraw.marginInside) { PRectangle rcClipText = rcTextArea; rcClipText.left -= leftTextOverlap; surfaceWindow->SetClip(rcClipText); } // Loop on visible lines #if defined(TIME_PAINTING) double durLayout = 0.0; double durPaint = 0.0; double durCopy = 0.0; ElapsedPeriod epWhole; #endif const bool bracesIgnoreStyle = ((vsDraw.braceHighlightIndicatorSet && (model.bracesMatchStyle == STYLE_BRACELIGHT)) || (vsDraw.braceBadLightIndicatorSet && (model.bracesMatchStyle == STYLE_BRACEBAD))); Sci::Line lineDocPrevious = -1; // Used to avoid laying out one document line multiple times AutoLineLayout ll(llc, nullptr); std::vector phases; if ((phasesDraw == phasesMultiple) && !bufferedDraw) { for (DrawPhase phase = drawBack; phase <= drawCarets; phase = static_cast(phase * 2)) { phases.push_back(phase); } } else { phases.push_back(drawAll); } for (const DrawPhase &phase : phases) { int ypos = 0; if (!bufferedDraw) ypos += screenLinePaintFirst * vsDraw.lineHeight; int yposScreen = screenLinePaintFirst * vsDraw.lineHeight; Sci::Line visibleLine = model.TopLineOfMain() + screenLinePaintFirst; while (visibleLine < model.pcs->LinesDisplayed() && yposScreen < rcArea.bottom) { const Sci::Line lineDoc = model.pcs->DocFromDisplay(visibleLine); // Only visible lines should be handled by the code within the loop PLATFORM_ASSERT(model.pcs->GetVisible(lineDoc)); const Sci::Line lineStartSet = model.pcs->DisplayFromDoc(lineDoc); const int subLine = static_cast(visibleLine - lineStartSet); // Copy this line and its styles from the document into local arrays // and determine the x position at which each character starts. #if defined(TIME_PAINTING) ElapsedPeriod ep; #endif if (lineDoc != lineDocPrevious) { ll.Set(nullptr); ll.Set(RetrieveLineLayout(lineDoc, model)); LayoutLine(model, lineDoc, surface, vsDraw, ll, model.wrapWidth); lineDocPrevious = lineDoc; } #if defined(TIME_PAINTING) durLayout += ep.Duration(true); #endif if (ll) { ll->containsCaret = !hideSelection && (lineDoc == lineCaret); ll->hotspot = model.GetHotSpotRange(); PRectangle rcLine = rcTextArea; rcLine.top = static_cast(ypos); rcLine.bottom = static_cast(ypos + vsDraw.lineHeight); const Range rangeLine(model.pdoc->LineStart(lineDoc), model.pdoc->LineStart(lineDoc + 1)); // Highlight the current braces if any ll->SetBracesHighlight(rangeLine, model.braces, static_cast(model.bracesMatchStyle), static_cast(model.highlightGuideColumn * vsDraw.spaceWidth), bracesIgnoreStyle); if (leftTextOverlap && (bufferedDraw || ((phasesDraw < phasesMultiple) && (phase & drawBack)))) { // Clear the left margin PRectangle rcSpacer = rcLine; rcSpacer.right = rcSpacer.left; rcSpacer.left -= 1; surface->FillRectangle(rcSpacer, vsDraw.styles[STYLE_DEFAULT].back); } if (model.BidirectionalEnabled()) { // Fill the line bidi data UpdateBidiData(model, vsDraw, ll); } DrawLine(surface, model, vsDraw, ll, lineDoc, visibleLine, xStart, rcLine, subLine, phase); #if defined(TIME_PAINTING) durPaint += ep.Duration(true); #endif // Restore the previous styles for the brace highlights in case layout is in cache. ll->RestoreBracesHighlight(rangeLine, model.braces, bracesIgnoreStyle); if (phase & drawFoldLines) { DrawFoldLines(surface, model, vsDraw, lineDoc, rcLine); } if (phase & drawCarets) { DrawCarets(surface, model, vsDraw, ll, lineDoc, xStart, rcLine, subLine); } if (bufferedDraw) { const Point from = Point::FromInts(vsDraw.textStart - leftTextOverlap, 0); const PRectangle rcCopyArea = PRectangle::FromInts(vsDraw.textStart - leftTextOverlap, yposScreen, static_cast(rcClient.right - vsDraw.rightMarginWidth), yposScreen + vsDraw.lineHeight); surfaceWindow->Copy(rcCopyArea, from, *pixmapLine); } lineWidthMaxSeen = std::max( lineWidthMaxSeen, static_cast(ll->positions[ll->numCharsInLine])); #if defined(TIME_PAINTING) durCopy += ep.Duration(true); #endif } if (!bufferedDraw) { ypos += vsDraw.lineHeight; } yposScreen += vsDraw.lineHeight; visibleLine++; } } ll.Set(nullptr); #if defined(TIME_PAINTING) if (durPaint < 0.00000001) durPaint = 0.00000001; #endif // Right column limit indicator PRectangle rcBeyondEOF = (vsDraw.marginInside) ? rcClient : rcArea; rcBeyondEOF.left = static_cast(vsDraw.textStart); rcBeyondEOF.right = rcBeyondEOF.right - ((vsDraw.marginInside) ? vsDraw.rightMarginWidth : 0); rcBeyondEOF.top = static_cast((model.pcs->LinesDisplayed() - model.TopLineOfMain()) * vsDraw.lineHeight); if (rcBeyondEOF.top < rcBeyondEOF.bottom) { surfaceWindow->FillRectangle(rcBeyondEOF, vsDraw.styles[STYLE_DEFAULT].back); if (vsDraw.edgeState == EDGE_LINE) { const int edgeX = static_cast(vsDraw.theEdge.column * vsDraw.spaceWidth); rcBeyondEOF.left = static_cast(edgeX + xStart); rcBeyondEOF.right = rcBeyondEOF.left + 1; surfaceWindow->FillRectangle(rcBeyondEOF, vsDraw.theEdge.colour); } else if (vsDraw.edgeState == EDGE_MULTILINE) { for (size_t edge = 0; edge < vsDraw.theMultiEdge.size(); edge++) { if (vsDraw.theMultiEdge[edge].column >= 0) { const int edgeX = static_cast(vsDraw.theMultiEdge[edge].column * vsDraw.spaceWidth); rcBeyondEOF.left = static_cast(edgeX + xStart); rcBeyondEOF.right = rcBeyondEOF.left + 1; surfaceWindow->FillRectangle(rcBeyondEOF, vsDraw.theMultiEdge[edge].colour); } } } } //Platform::DebugPrintf("start display %d, offset = %d\n", model.pdoc->Length(), model.xOffset); #if defined(TIME_PAINTING) Platform::DebugPrintf( "Layout:%9.6g Paint:%9.6g Ratio:%9.6g Copy:%9.6g Total:%9.6g\n", durLayout, durPaint, durLayout / durPaint, durCopy, epWhole.Duration()); #endif } } void EditView::FillLineRemainder(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll, Sci::Line line, PRectangle rcArea, int subLine) const { int eolInSelection = 0; int alpha = SC_ALPHA_NOALPHA; if (!hideSelection) { const Sci::Position posAfterLineEnd = model.pdoc->LineStart(line + 1); eolInSelection = (subLine == (ll->lines - 1)) ? model.sel.InSelectionForEOL(posAfterLineEnd) : 0; alpha = (eolInSelection == 1) ? vsDraw.selAlpha : vsDraw.selAdditionalAlpha; } const ColourOptional background = vsDraw.Background(model.pdoc->GetMark(line), model.caret.active, ll->containsCaret); if (eolInSelection && vsDraw.selEOLFilled && vsDraw.selColours.back.isSet && (line < model.pdoc->LinesTotal() - 1) && (alpha == SC_ALPHA_NOALPHA)) { surface->FillRectangle(rcArea, SelectionBackground(vsDraw, eolInSelection == 1, model.primarySelection)); } else { if (background.isSet) { surface->FillRectangle(rcArea, background); } else if (vsDraw.styles[ll->styles[ll->numCharsInLine]].eolFilled) { surface->FillRectangle(rcArea, vsDraw.styles[ll->styles[ll->numCharsInLine]].back); } else { surface->FillRectangle(rcArea, vsDraw.styles[STYLE_DEFAULT].back); } if (eolInSelection && vsDraw.selEOLFilled && vsDraw.selColours.back.isSet && (line < model.pdoc->LinesTotal() - 1) && (alpha != SC_ALPHA_NOALPHA)) { SimpleAlphaRectangle(surface, rcArea, SelectionBackground(vsDraw, eolInSelection == 1, model.primarySelection), alpha); } } } // Space (3 space characters) between line numbers and text when printing. #define lineNumberPrintSpace " " static ColourDesired InvertedLight(ColourDesired orig) noexcept { unsigned int r = orig.GetRed(); unsigned int g = orig.GetGreen(); unsigned int b = orig.GetBlue(); const unsigned int l = (r + g + b) / 3; // There is a better calculation for this that matches human eye const unsigned int il = 0xff - l; if (l == 0) return ColourDesired(0xff, 0xff, 0xff); r = r * il / l; g = g * il / l; b = b * il / l; return ColourDesired(std::min(r, 0xffu), std::min(g, 0xffu), std::min(b, 0xffu)); } Sci::Position EditView::FormatRange(bool draw, const Sci_RangeToFormat *pfr, Surface *surface, Surface *surfaceMeasure, const EditModel &model, const ViewStyle &vs) { // Can't use measurements cached for screen posCache.Clear(); ViewStyle vsPrint(vs); vsPrint.technology = SC_TECHNOLOGY_DEFAULT; // Modify the view style for printing as do not normally want any of the transient features to be printed // Printing supports only the line number margin. int lineNumberIndex = -1; for (size_t margin = 0; margin < vs.ms.size(); margin++) { if ((vsPrint.ms[margin].style == SC_MARGIN_NUMBER) && (vsPrint.ms[margin].width > 0)) { lineNumberIndex = static_cast(margin); } else { vsPrint.ms[margin].width = 0; } } vsPrint.fixedColumnWidth = 0; vsPrint.zoomLevel = printParameters.magnification; // Don't show indentation guides // If this ever gets changed, cached pixmap would need to be recreated if technology != SC_TECHNOLOGY_DEFAULT vsPrint.viewIndentationGuides = ivNone; // Don't show the selection when printing vsPrint.selColours.back.isSet = false; vsPrint.selColours.fore.isSet = false; vsPrint.selAlpha = SC_ALPHA_NOALPHA; vsPrint.selAdditionalAlpha = SC_ALPHA_NOALPHA; vsPrint.whitespaceColours.back.isSet = false; vsPrint.whitespaceColours.fore.isSet = false; vsPrint.showCaretLineBackground = false; vsPrint.alwaysShowCaretLineBackground = false; // Don't highlight matching braces using indicators vsPrint.braceHighlightIndicatorSet = false; vsPrint.braceBadLightIndicatorSet = false; // Set colours for printing according to users settings for (size_t sty = 0; sty < vsPrint.styles.size(); sty++) { if (printParameters.colourMode == SC_PRINT_INVERTLIGHT) { vsPrint.styles[sty].fore = InvertedLight(vsPrint.styles[sty].fore); vsPrint.styles[sty].back = InvertedLight(vsPrint.styles[sty].back); } else if (printParameters.colourMode == SC_PRINT_BLACKONWHITE) { vsPrint.styles[sty].fore = ColourDesired(0, 0, 0); vsPrint.styles[sty].back = ColourDesired(0xff, 0xff, 0xff); } else if (printParameters.colourMode == SC_PRINT_COLOURONWHITE) { vsPrint.styles[sty].back = ColourDesired(0xff, 0xff, 0xff); } else if (printParameters.colourMode == SC_PRINT_COLOURONWHITEDEFAULTBG) { if (sty <= STYLE_DEFAULT) { vsPrint.styles[sty].back = ColourDesired(0xff, 0xff, 0xff); } } } // White background for the line numbers if SC_PRINT_SCREENCOLOURS isn't used if (printParameters.colourMode != SC_PRINT_SCREENCOLOURS) vsPrint.styles[STYLE_LINENUMBER].back = ColourDesired(0xff, 0xff, 0xff); // Printing uses different margins, so reset screen margins vsPrint.leftMarginWidth = 0; vsPrint.rightMarginWidth = 0; vsPrint.Refresh(*surfaceMeasure, model.pdoc->tabInChars); // Determining width must happen after fonts have been realised in Refresh int lineNumberWidth = 0; if (lineNumberIndex >= 0) { lineNumberWidth = static_cast(surfaceMeasure->WidthText(vsPrint.styles[STYLE_LINENUMBER].font, "99999" lineNumberPrintSpace)); vsPrint.ms[lineNumberIndex].width = lineNumberWidth; vsPrint.Refresh(*surfaceMeasure, model.pdoc->tabInChars); // Recalculate fixedColumnWidth } const Sci::Line linePrintStart = model.pdoc->SciLineFromPosition(pfr->chrg.cpMin); Sci::Line linePrintLast = linePrintStart + (pfr->rc.bottom - pfr->rc.top) / vsPrint.lineHeight - 1; if (linePrintLast < linePrintStart) linePrintLast = linePrintStart; const Sci::Line linePrintMax = model.pdoc->SciLineFromPosition(pfr->chrg.cpMax); if (linePrintLast > linePrintMax) linePrintLast = linePrintMax; //Platform::DebugPrintf("Formatting lines=[%0d,%0d,%0d] top=%0d bottom=%0d line=%0d %0d\n", // linePrintStart, linePrintLast, linePrintMax, pfr->rc.top, pfr->rc.bottom, vsPrint.lineHeight, // surfaceMeasure->Height(vsPrint.styles[STYLE_LINENUMBER].font)); Sci::Position endPosPrint = model.pdoc->Length(); if (linePrintLast < model.pdoc->LinesTotal()) endPosPrint = model.pdoc->LineStart(linePrintLast + 1); // Ensure we are styled to where we are formatting. model.pdoc->EnsureStyledTo(endPosPrint); const int xStart = vsPrint.fixedColumnWidth + pfr->rc.left; int ypos = pfr->rc.top; Sci::Line lineDoc = linePrintStart; Sci::Position nPrintPos = pfr->chrg.cpMin; int visibleLine = 0; int widthPrint = pfr->rc.right - pfr->rc.left - vsPrint.fixedColumnWidth; if (printParameters.wrapState == WrapMode::none) widthPrint = LineLayout::wrapWidthInfinite; while (lineDoc <= linePrintLast && ypos < pfr->rc.bottom) { // When printing, the hdc and hdcTarget may be the same, so // changing the state of surfaceMeasure may change the underlying // state of surface. Therefore, any cached state is discarded before // using each surface. surfaceMeasure->FlushCachedState(); // Copy this line and its styles from the document into local arrays // and determine the x position at which each character starts. LineLayout ll(static_cast(model.pdoc->LineStart(lineDoc + 1) - model.pdoc->LineStart(lineDoc) + 1)); LayoutLine(model, lineDoc, surfaceMeasure, vsPrint, &ll, widthPrint); ll.containsCaret = false; PRectangle rcLine = PRectangle::FromInts( pfr->rc.left, ypos, pfr->rc.right - 1, ypos + vsPrint.lineHeight); // When document line is wrapped over multiple display lines, find where // to start printing from to ensure a particular position is on the first // line of the page. if (visibleLine == 0) { const Sci::Position startWithinLine = nPrintPos - model.pdoc->LineStart(lineDoc); for (int iwl = 0; iwl < ll.lines - 1; iwl++) { if (ll.LineStart(iwl) <= startWithinLine && ll.LineStart(iwl + 1) >= startWithinLine) { visibleLine = -iwl; } } if (ll.lines > 1 && startWithinLine >= ll.LineStart(ll.lines - 1)) { visibleLine = -(ll.lines - 1); } } if (draw && lineNumberWidth && (ypos + vsPrint.lineHeight <= pfr->rc.bottom) && (visibleLine >= 0)) { const std::string number = std::to_string(lineDoc + 1) + lineNumberPrintSpace; PRectangle rcNumber = rcLine; rcNumber.right = rcNumber.left + lineNumberWidth; // Right justify rcNumber.left = rcNumber.right - surfaceMeasure->WidthText( vsPrint.styles[STYLE_LINENUMBER].font, number); surface->FlushCachedState(); surface->DrawTextNoClip(rcNumber, vsPrint.styles[STYLE_LINENUMBER].font, static_cast(ypos + vsPrint.maxAscent), number, vsPrint.styles[STYLE_LINENUMBER].fore, vsPrint.styles[STYLE_LINENUMBER].back); } // Draw the line surface->FlushCachedState(); for (int iwl = 0; iwl < ll.lines; iwl++) { if (ypos + vsPrint.lineHeight <= pfr->rc.bottom) { if (visibleLine >= 0) { if (draw) { rcLine.top = static_cast(ypos); rcLine.bottom = static_cast(ypos + vsPrint.lineHeight); DrawLine(surface, model, vsPrint, &ll, lineDoc, visibleLine, xStart, rcLine, iwl, drawAll); } ypos += vsPrint.lineHeight; } visibleLine++; if (iwl == ll.lines - 1) nPrintPos = model.pdoc->LineStart(lineDoc + 1); else nPrintPos += ll.LineStart(iwl + 1) - ll.LineStart(iwl); } } ++lineDoc; } // Clear cache so measurements are not used for screen posCache.Clear(); return nPrintPos; }