diff options
author | Neil <nyamatongwe@gmail.com> | 2018-05-23 16:59:41 +1000 |
---|---|---|
committer | Neil <nyamatongwe@gmail.com> | 2018-05-23 16:59:41 +1000 |
commit | 071602224b06d3bc65b8eda49ec8f589ccf66159 (patch) | |
tree | c7e84825e23691624609d1b8d7200eeab41fd41e | |
parent | 7ee2a70c18f899a4845621622241adc578b0400c (diff) | |
download | scintilla-mirror-071602224b06d3bc65b8eda49ec8f589ccf66159.tar.gz |
Implement bidirectional mode bidiL2R for DirectDraw on Win32.
-rw-r--r-- | src/EditModel.cxx | 9 | ||||
-rw-r--r-- | src/EditModel.h | 2 | ||||
-rw-r--r-- | src/EditView.cxx | 200 | ||||
-rw-r--r-- | src/EditView.h | 6 | ||||
-rw-r--r-- | src/Editor.cxx | 15 | ||||
-rw-r--r-- | src/Editor.h | 2 | ||||
-rw-r--r-- | src/PositionCache.cxx | 103 | ||||
-rw-r--r-- | src/PositionCache.h | 36 | ||||
-rw-r--r-- | win32/PlatWin.cxx | 425 | ||||
-rw-r--r-- | win32/ScintillaWin.cxx | 1 |
10 files changed, 748 insertions, 51 deletions
diff --git a/src/EditModel.cxx b/src/EditModel.cxx index fcca51405..e640e05bb 100644 --- a/src/EditModel.cxx +++ b/src/EditModel.cxx @@ -77,3 +77,12 @@ EditModel::~EditModel() { pdoc->Release(); pdoc = 0; } + +bool EditModel::BidirectionalEnabled() const { + return (bidirectional != Bidirectional::bidiDisabled) && + (SC_CP_UTF8 == pdoc->dbcsCodePage); +} + +bool EditModel::BidirectionalR2L() const { + return bidirectional == Bidirectional::bidiR2L; +} diff --git a/src/EditModel.h b/src/EditModel.h index 7ad719c12..3da6afb58 100644 --- a/src/EditModel.h +++ b/src/EditModel.h @@ -63,6 +63,8 @@ public: virtual Point GetVisibleOriginInMain() const = 0; virtual Sci::Line LinesOnScreen() const = 0; virtual Range GetHotSpotRange() const = 0; + bool BidirectionalEnabled() const; + bool BidirectionalR2L() const; }; } diff --git a/src/EditView.cxx b/src/EditView.cxx index 6ca22e4f2..76edf1c68 100644 --- a/src/EditView.cxx +++ b/src/EditView.cxx @@ -589,8 +589,39 @@ void EditView::LayoutLine(const EditModel &model, Sci::Line line, Surface *surfa } } +// 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<unsigned char *>(&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 ViewStyle &vs, PointEnd pe, const PRectangle rcClient) { Point pt; if (pos.Position() == INVALID_POSITION) return pt; @@ -607,8 +638,28 @@ Point EditView::LocationFromPosition(Surface *surface, const EditModel &model, S LayoutLine(model, lineDoc, surface, vs, ll, model.wrapWidth); const int posInLine = static_cast<int>(pos.Position() - posLineStart); pt = ll->PointFromPosition(posInLine, vs.lineHeight, pe); - pt.y += (lineVisible - topLine) * vs.lineHeight; 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); + pt.x = surface->XFromPosition(&screenLine, caretPosition); + + pt.x += vs.textStart - model.xOffset; + + pt.y = 0; + if (posInLine >= ll->LineStart(subLine)) { + pt.y = static_cast<XYPOSITION>(subLine*vs.lineHeight); + } + } + pt.y += (lineVisible - topLine) * vs.lineHeight; } pt.x += pos.VirtualSpace() * vs.styles[ll->EndLineStyle()].spaceWidth; return pt; @@ -639,7 +690,8 @@ Range EditView::RangeDisplayLine(Surface *surface, const EditModel &model, Sci:: return rangeSubLine; } -SelectionPosition EditView::SPositionFromLocation(Surface *surface, const EditModel &model, PointDocument pt, bool canReturnInvalid, bool charPosition, bool virtualSpace, const ViewStyle &vs) { +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<int>(floor(pt.y / vs.lineHeight)); if (!canReturnInvalid && (visibleLine < 0)) @@ -661,8 +713,18 @@ SelectionPosition EditView::SPositionFromLocation(Surface *surface, const EditMo const XYPOSITION subLineStart = ll->positions[rangeSubLine.start]; if (subLine > 0) // Wrapped pt.x -= ll->wrapIndent; - const Sci::Position positionInLine = ll->FindPositionFromX(static_cast<XYPOSITION>(pt.x + subLineStart), - rangeSubLine, charPosition); + 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); + positionInLine = surface->PositionFromX(&screenLine, static_cast<XYPOSITION>(pt.x), charPosition) + + rangeSubLine.start; + } else { + positionInLine = ll->FindPositionFromX(static_cast<XYPOSITION>(pt.x + subLineStart), + rangeSubLine, charPosition); + } if (positionInLine < rangeSubLine.end) { return SelectionPosition(model.pdoc->MovePositionOutsideChar(positionInLine + posLineStart, 1)); } @@ -869,7 +931,7 @@ void EditView::DrawEOL(Surface *surface, const EditModel &model, const ViewStyle const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth; virtualSpace = model.sel.VirtualSpaceFor(model.pdoc->LineEnd(line)) * spaceWidth; } - const XYPOSITION xEol = static_cast<XYPOSITION>(ll->positions[lineEnd] - subLineStart); + XYPOSITION xEol = static_cast<XYPOSITION>(ll->positions[lineEnd] - subLineStart); // Fill the virtual space and show selections within it if (virtualSpace > 0.0f) { @@ -1017,27 +1079,51 @@ void EditView::DrawEOL(Surface *surface, const EditModel &model, const ViewStyle } 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::DrawState drawState, int value) { + const LineLayout *ll, int xStart, PRectangle rcLine, Sci::Position secondCharacter, int subLine, Indicator::DrawState drawState, + int value, bool bidiEnabled, int tabWidthMinimumPixels) { + const XYPOSITION subLineStart = ll->positions[ll->LineStart(subLine)]; + + std::vector<PRectangle> rectangles; + const PRectangle rcIndic( ll->positions[startPos] + xStart - subLineStart, rcLine.top + vsDraw.maxAscent, ll->positions[endPos] + xStart - subLineStart, rcLine.top + vsDraw.maxAscent + 3); - PRectangle rcFirstCharacter = rcIndic; - // 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; + + if (bidiEnabled) { + ScreenLine screenLine(ll, subLine, vsDraw, rcLine.right - xStart, tabWidthMinimumPixels); + const Range lineRange = ll->SubLineRange(subLine, LineLayout::Scope::visibleOnly); + + std::vector<Interval> intervals = surface->FindRangeIntervals(&screenLine, + 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 { - // Indicator continued from earlier line so make an empty box and don't draw - rcFirstCharacter.right = rcFirstCharacter.left; + 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, drawState, value); } - vsDraw.indicators[indicNum].Draw(surface, rcIndic, rcLine, rcFirstCharacter, drawState, 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, Sci::Position hoverIndicatorPos) { + 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); @@ -1053,12 +1139,13 @@ static void DrawIndicators(Surface *surface, const EditModel &model, const ViewS 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(hoverIndicatorPos); + rangeRun.ContainsCharacter(model.hoverIndicatorPos); const int value = deco->ValueAt(startPos); const Indicator::DrawState drawState = hover ? Indicator::drawHover : Indicator::drawNormal; 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, drawState, value); + surface, vsDraw, ll, xStart, rcLine, posSecond - posLineStart, subLine, drawState, + value, model.BidirectionalEnabled(), tabWidthMinimumPixels); startPos = endPos; if (!deco->ValueAt(startPos)) { startPos = deco->EndRun(startPos); @@ -1077,14 +1164,16 @@ static void DrawIndicators(Surface *surface, const EditModel &model, const ViewS 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::drawNormal, 1); + DrawIndicator(braceIndicator, braceOffset, braceOffset + 1, surface, vsDraw, ll, xStart, rcLine, secondOffset, + subLine, Indicator::drawNormal, 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::drawNormal, 1); + DrawIndicator(braceIndicator, braceOffset, braceOffset + 1, surface, vsDraw, ll, xStart, rcLine, secondOffset, + subLine, Indicator::drawNormal, 1, model.BidirectionalEnabled(), tabWidthMinimumPixels); } } } @@ -1334,6 +1423,17 @@ void EditView::DrawCarets(Surface *surface, const EditModel &model, const ViewSt 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 = static_cast<int>(posCaret.Position() - posLineStart - ll->LineStart(subLine)); + + const XYPOSITION caretLeft = surface->XFromPosition(&screenLine, 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 @@ -1549,8 +1649,9 @@ static void DrawMarkUnderline(Surface *surface, const EditModel &model, const Vi 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) { + 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]; @@ -1567,20 +1668,37 @@ static void DrawTranslucentSelection(Surface *surface, const EditModel &model, c 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; - PRectangle rcSegment = rcLine; - rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] - - static_cast<XYPOSITION>(subLineStart)+portion.start.VirtualSpace() * spaceWidth; - rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] - - static_cast<XYPOSITION>(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<int>(ll->wrapIndent); // indentation added to xStart was truncated to int, so we do the same here + if (model.BidirectionalEnabled()) { + const int selectionStart = static_cast<int>(portion.start.Position() - posLineStart - lineRange.start); + const int selectionEnd = static_cast<int>(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); + + const std::vector<Interval> intervals = surface->FindRangeIntervals(&screenLine, 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); + } + } else { + const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth; + PRectangle rcSegment = rcLine; + rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] - + static_cast<XYPOSITION>(subLineStart) + portion.start.VirtualSpace() * spaceWidth; + rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] - + static_cast<XYPOSITION>(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<int>(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); } - 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); } } } @@ -1904,7 +2022,8 @@ void EditView::DrawLine(Surface *surface, const EditModel &model, const ViewStyl } if (phase & drawIndicatorsBack) { - DrawIndicators(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, lineRangeIncludingEnd.end, true, model.hoverIndicatorPos); + 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); } @@ -1920,7 +2039,8 @@ void EditView::DrawLine(Surface *surface, const EditModel &model, const ViewStyl } if (phase & drawIndicatorsFore) { - DrawIndicators(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, lineRangeIncludingEnd.end, false, model.hoverIndicatorPos); + DrawIndicators(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, + lineRangeIncludingEnd.end, false, tabWidthMinimumPixels); } DrawFoldDisplayText(surface, model, vsDraw, ll, line, xStart, rcLine, subLine, subLineStart, phase); @@ -1935,7 +2055,7 @@ void EditView::DrawLine(Surface *surface, const EditModel &model, const ViewStyl } if (!hideSelection && (phase & drawSelectionTranslucent)) { - DrawTranslucentSelection(surface, model, vsDraw, ll, line, rcLine, subLine, lineRange, xStart); + DrawTranslucentSelection(surface, model, vsDraw, ll, line, rcLine, subLine, lineRange, xStart, tabWidthMinimumPixels); } if (phase & drawLineTranslucent) { @@ -1984,6 +2104,7 @@ void EditView::PaintText(Surface *surfaceWindow, const EditModel &model, PRectan } surface->SetUnicodeMode(SC_CP_UTF8 == model.pdoc->dbcsCodePage); surface->SetDBCSMode(model.pdoc->dbcsCodePage); + surface->SetBidiR2L(model.BidirectionalR2L()); const Point ptOrigin = model.GetVisibleOriginInMain(); @@ -2082,6 +2203,11 @@ void EditView::PaintText(Surface *surfaceWindow, const EditModel &model, PRectan 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); diff --git a/src/EditView.h b/src/EditView.h index 51e2a1e53..16da4e904 100644 --- a/src/EditView.h +++ b/src/EditView.h @@ -116,11 +116,13 @@ public: void LayoutLine(const EditModel &model, Sci::Line line, Surface *surface, const ViewStyle &vstyle, LineLayout *ll, int width = LineLayout::wrapWidthInfinite); + void UpdateBidiData(const EditModel &model, const ViewStyle &vstyle, LineLayout *ll); + Point LocationFromPosition(Surface *surface, const EditModel &model, SelectionPosition pos, Sci::Line topLine, - const ViewStyle &vs, PointEnd pe); + const ViewStyle &vs, PointEnd pe, const PRectangle rcClient); Range RangeDisplayLine(Surface *surface, const EditModel &model, Sci::Line lineVisible, const ViewStyle &vs); SelectionPosition SPositionFromLocation(Surface *surface, const EditModel &model, PointDocument pt, bool canReturnInvalid, - bool charPosition, bool virtualSpace, const ViewStyle &vs); + bool charPosition, bool virtualSpace, const ViewStyle &vs, const PRectangle rcClient); SelectionPosition SPositionFromLineX(Surface *surface, const EditModel &model, Sci::Line lineDoc, int x, const ViewStyle &vs); Sci::Line DisplayFromPosition(Surface *surface, const EditModel &model, Sci::Position pos, const ViewStyle &vs); Sci::Position StartEndDisplayLine(Surface *surface, const EditModel &model, Sci::Position pos, bool start, const ViewStyle &vs); diff --git a/src/Editor.cxx b/src/Editor.cxx index 04d61bea6..9e81a2ef3 100644 --- a/src/Editor.cxx +++ b/src/Editor.cxx @@ -377,9 +377,10 @@ SelectionPosition Editor::ClampPositionIntoDocument(SelectionPosition sp) const } Point Editor::LocationFromPosition(SelectionPosition pos, PointEnd pe) { + const PRectangle rcClient = GetTextRectangle(); RefreshStyleData(); AutoSurface surface(this); - return view.LocationFromPosition(surface, *this, pos, topLine, vs, pe); + return view.LocationFromPosition(surface, *this, pos, topLine, vs, pe, rcClient); } Point Editor::LocationFromPosition(Sci::Position pos, PointEnd pe) { @@ -395,11 +396,12 @@ SelectionPosition Editor::SPositionFromLocation(Point pt, bool canReturnInvalid, RefreshStyleData(); AutoSurface surface(this); + PRectangle rcClient = GetTextRectangle(); + // May be in scroll view coordinates so translate back to main view + const Point ptOrigin = GetVisibleOriginInMain(); + rcClient.Move(-ptOrigin.x, -ptOrigin.y); + if (canReturnInvalid) { - PRectangle rcClient = GetTextRectangle(); - // May be in scroll view coordinates so translate back to main view - const Point ptOrigin = GetVisibleOriginInMain(); - rcClient.Move(-ptOrigin.x, -ptOrigin.y); if (!rcClient.Contains(pt)) return SelectionPosition(INVALID_POSITION); if (pt.x < vs.textStart) @@ -408,7 +410,8 @@ SelectionPosition Editor::SPositionFromLocation(Point pt, bool canReturnInvalid, return SelectionPosition(INVALID_POSITION); } const PointDocument ptdoc = DocumentPointFromView(pt); - return view.SPositionFromLocation(surface, *this, ptdoc, canReturnInvalid, charPosition, virtualSpace, vs); + return view.SPositionFromLocation(surface, *this, ptdoc, canReturnInvalid, + charPosition, virtualSpace, vs, rcClient); } Sci::Position Editor::PositionFromLocation(Point pt, bool canReturnInvalid, bool charPosition) { diff --git a/src/Editor.h b/src/Editor.h index bc295260e..3c21ee092 100644 --- a/src/Editor.h +++ b/src/Editor.h @@ -618,6 +618,7 @@ public: surf->Init(ed->wMain.GetID()); surf->SetUnicodeMode(SC_CP_UTF8 == ed->CodePage()); surf->SetDBCSMode(ed->CodePage()); + surf->SetBidiR2L(ed->BidirectionalR2L()); } } AutoSurface(SurfaceID sid, Editor *ed, int technology = -1) { @@ -626,6 +627,7 @@ public: surf->Init(sid, ed->wMain.GetID()); surf->SetUnicodeMode(SC_CP_UTF8 == ed->CodePage()); surf->SetDBCSMode(ed->CodePage()); + surf->SetBidiR2L(ed->BidirectionalR2L()); } } // Deleted so AutoSurface objects can not be copied. diff --git a/src/PositionCache.cxx b/src/PositionCache.cxx index 31a8601f5..b218c39e0 100644 --- a/src/PositionCache.cxx +++ b/src/PositionCache.cxx @@ -46,6 +46,11 @@ using namespace Scintilla; +void BidiData::Resize(size_t maxLineLength_) { + stylesFonts.resize(maxLineLength_ + 1); + widthReprs.resize(maxLineLength_ + 1); +} + LineLayout::LineLayout(int maxLineLength_) : lenLineStarts(0), lineNumber(-1), @@ -79,15 +84,27 @@ void LineLayout::Resize(int maxLineLength_) { // Extra position allocated as sometimes the Windows // GetTextExtentExPoint API writes an extra element. positions = std::make_unique<XYPOSITION []>(maxLineLength_ + 1 + 1); + if (bidiData) { + bidiData->Resize(maxLineLength_); + } + maxLineLength = maxLineLength_; } } +void LineLayout::EnsureBidiData() { + if (!bidiData) { + bidiData = std::make_unique<BidiData>(); + bidiData->Resize(maxLineLength); + } +} + void LineLayout::Free() { chars.reset(); styles.reset(); positions.reset(); lineStarts.reset(); + bidiData.reset(); } void LineLayout::Invalidate(validLevel validity_) { @@ -105,6 +122,16 @@ int LineLayout::LineStart(int line) const { } } +int Scintilla::LineLayout::LineLength(int line) const { + if (!lineStarts) { + return numCharsInLine; + } if (line >= lines - 1) { + return numCharsInLine - lineStarts[line]; + } else { + return lineStarts[line + 1] - lineStarts[line]; + } +} + int LineLayout::LineLastVisible(int line, Scope scope) const { if (line < 0) { return 0; @@ -124,6 +151,25 @@ bool LineLayout::InLine(int offset, int line) const { ((offset == numCharsInLine) && (line == (lines-1))); } +int LineLayout::SubLineFromPosition(int posInLine, PointEnd pe) const { + if (!lineStarts || (posInLine > maxLineLength)) { + return lines - 1; + } + + for (int line = 0; line < lines; line++) { + if (pe & peSubLineEnd) { + // Return subline not start of next + if (lineStarts[line + 1] <= posInLine + 1) + return line; + } else { + if (lineStarts[line + 1] <= posInLine) + return line; + } + } + + return lines - 1; +} + void LineLayout::SetLineStart(int line, int start) { if ((line >= lenLineStarts) && (line != 0)) { const int newMaxLines = line + 20; @@ -244,6 +290,63 @@ int LineLayout::EndLineStyle() const { return styles[numCharsBeforeEOL > 0 ? numCharsBeforeEOL-1 : 0]; } +ScreenLine::ScreenLine( + const LineLayout *ll_, + int subLine, + const ViewStyle &vs, + XYPOSITION width_, + int tabWidthMinimumPixels_) : + ll(ll_), + start(ll->LineStart(subLine)), + len(ll->LineLength(subLine)), + width(width_), + height(static_cast<float>(vs.lineHeight)), + ctrlCharPadding(vs.ctrlCharPadding), + tabWidth(vs.tabWidth), + tabWidthMinimumPixels(tabWidthMinimumPixels_) { +} + +ScreenLine::~ScreenLine() { +} + +std::string_view ScreenLine::Text() const { + return std::string_view(&ll->chars[start], len); +} + +size_t ScreenLine::Length() const { + return len; +} + +size_t ScreenLine::RepresentationCount() const { + return std::count_if(&ll->bidiData->widthReprs[start], + &ll->bidiData->widthReprs[start + len], + [](XYPOSITION w) {return w > 0.0f; }); +} + +XYPOSITION ScreenLine::Width() const { + return width; +} + +XYPOSITION ScreenLine::Height() const { + return height; +} + +XYPOSITION ScreenLine::TabWidth() const { + return tabWidth; +} + +XYPOSITION ScreenLine::TabWidthMinimumPixels() const { + return static_cast<XYPOSITION>(tabWidthMinimumPixels); +} + +const Font *ScreenLine::FontOfPosition(size_t position) const { + return &ll->bidiData->stylesFonts[start + position]; +} + +XYPOSITION ScreenLine::RepresentationWidth(size_t position) const { + return ll->bidiData->widthReprs[start + position]; +} + LineLayoutCache::LineLayoutCache() : level(0), allInvalidated(false), styleClock(-1), useCount(0) { diff --git a/src/PositionCache.h b/src/PositionCache.h index 7f50c4dec..512ec13f5 100644 --- a/src/PositionCache.h +++ b/src/PositionCache.h @@ -40,6 +40,13 @@ enum PointEnd { peSubLineEnd = 0x2 }; +class BidiData { +public: + std::vector<FontAlias> stylesFonts; + std::vector<XYPOSITION> widthReprs; + void Resize(size_t maxLineLength_); +}; + /** */ class LineLayout { @@ -66,6 +73,8 @@ public: std::unique_ptr<XYPOSITION[]> positions; char bracePreviousStyles[2]; + std::unique_ptr<BidiData> bidiData; + // Hotspot support Range hotspot; @@ -82,13 +91,16 @@ public: void operator=(LineLayout &&) = delete; virtual ~LineLayout(); void Resize(int maxLineLength_); + void EnsureBidiData(); void Free(); void Invalidate(validLevel validity_); int LineStart(int line) const; + int LineLength(int line) const; enum class Scope { visibleOnly, includeEnd }; int LineLastVisible(int line, Scope scope) const; Range SubLineRange(int subLine, Scope scope) const; bool InLine(int offset, int line) const; + int SubLineFromPosition(int posInLine, PointEnd pe) const; void SetLineStart(int line, int start); void SetBracesHighlight(Range rangeLine, const Sci::Position braces[], char bracesMatchStyle, int xHighlight, bool ignoreStyle); @@ -99,6 +111,30 @@ public: int EndLineStyle() const; }; +struct ScreenLine : public IScreenLine { + const LineLayout *ll; + size_t start; + size_t len; + XYPOSITION width; + XYPOSITION height; + int ctrlCharPadding; + XYPOSITION tabWidth; + int tabWidthMinimumPixels; + + ScreenLine(const LineLayout *ll_, int subLine, const ViewStyle &vs, XYPOSITION width_, int tabWidthMinimumPixels_); + virtual ~ScreenLine(); + + std::string_view Text() const override; + size_t Length() const override; + size_t RepresentationCount() const override; + XYPOSITION Width() const override; + XYPOSITION Height() const override; + XYPOSITION TabWidth() const override; + XYPOSITION TabWidthMinimumPixels() const override; + const Font *FontOfPosition(size_t position) const override; + XYPOSITION RepresentationWidth(size_t position) const override; +}; + /** */ class LineLayoutCache { diff --git a/win32/PlatWin.cxx b/win32/PlatWin.cxx index cdaea41e4..82b269c83 100644 --- a/win32/PlatWin.cxx +++ b/win32/PlatWin.cxx @@ -1094,6 +1094,8 @@ void SurfaceGDI::SetBidiR2L(bool) { #if defined(USE_D2D) +class BlobInline; + class SurfaceD2D : public Surface { bool unicodeMode; int x, y; @@ -1160,6 +1162,11 @@ public: XYPOSITION XFromPosition(const IScreenLine *screenLine, size_t caretPosition) override; std::vector<Interval> FindRangeIntervals(const IScreenLine *screenLine, size_t start, size_t end) override; + static void FillTextLayoutFormats(const IScreenLine *screenLine, IDWriteTextLayout *textLayout, std::vector<BlobInline> &blobs); + static std::wstring ReplaceRepresentation(std::string_view text); + static size_t GetPositionInLayout(std::string_view text, size_t position); + static size_t GetPositionInString(std::string_view text, size_t position); + void DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, UINT fuOptions); void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; @@ -1615,16 +1622,422 @@ void SurfaceD2D::Copy(PRectangle rc, Point from, Surface &surfaceSource) { } } -size_t SurfaceD2D::PositionFromX(const IScreenLine *, XYPOSITION, bool) { - return 0; +class BlobInline : public IDWriteInlineObject { + XYPOSITION width; + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) override; + STDMETHODIMP_(ULONG)AddRef() override; + STDMETHODIMP_(ULONG)Release() override; + + // IDWriteInlineObject + STDMETHODIMP Draw( + void *clientDrawingContext, + IDWriteTextRenderer *renderer, + FLOAT originX, + FLOAT originY, + BOOL isSideways, + BOOL isRightToLeft, + IUnknown *clientDrawingEffect + ) override; + STDMETHODIMP GetMetrics(DWRITE_INLINE_OBJECT_METRICS *metrics) override; + STDMETHODIMP GetOverhangMetrics(DWRITE_OVERHANG_METRICS *overhangs) override; + STDMETHODIMP GetBreakConditions( + DWRITE_BREAK_CONDITION *breakConditionBefore, + DWRITE_BREAK_CONDITION *breakConditionAfter) override; +public: + BlobInline(XYPOSITION width_=0.0f) noexcept : width(width_) { + } + virtual ~BlobInline() { + } +}; + +/// Implement IUnknown +STDMETHODIMP BlobInline::QueryInterface(REFIID riid, PVOID *ppv) { + // Never called so not checked. + *ppv = NULL; + if (riid == IID_IUnknown) + *ppv = static_cast<IUnknown *>(this); + if (riid == __uuidof(IDWriteInlineObject)) + *ppv = static_cast<IDWriteInlineObject *>(this); + if (!*ppv) + return E_NOINTERFACE; + return S_OK; +} + +STDMETHODIMP_(ULONG) BlobInline::AddRef() { + // Lifetime tied to Platform methods so ignore any reference operations. + return 1; +} + +STDMETHODIMP_(ULONG) BlobInline::Release() { + // Lifetime tied to Platform methods so ignore any reference operations. + return 1; +} + +/// Implement IDWriteInlineObject +HRESULT STDMETHODCALLTYPE BlobInline::Draw( + void*, + IDWriteTextRenderer*, + FLOAT, + FLOAT, + BOOL, + BOOL, + IUnknown*) { + // Since not performing drawing, not necessary to implement + // Could be implemented by calling back into platform-independent code. + // This would allow more of the drawing to be mediated through DirectWrite. + return S_OK; +} + +HRESULT STDMETHODCALLTYPE BlobInline::GetMetrics( + DWRITE_INLINE_OBJECT_METRICS *metrics +) { + metrics->width = width; + metrics->height = 2; + metrics->baseline = 1; + metrics->supportsSideways = FALSE; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE BlobInline::GetOverhangMetrics( + DWRITE_OVERHANG_METRICS *overhangs +) { + overhangs->left = 0; + overhangs->top = 0; + overhangs->right = 0; + overhangs->bottom = 0; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE BlobInline::GetBreakConditions( + DWRITE_BREAK_CONDITION *breakConditionBefore, + DWRITE_BREAK_CONDITION *breakConditionAfter +) { + // Since not performing 2D layout, not necessary to implement + *breakConditionBefore = DWRITE_BREAK_CONDITION_NEUTRAL; + *breakConditionAfter = DWRITE_BREAK_CONDITION_NEUTRAL; + return S_OK; +} + +// Get the position from the provided x + +size_t SurfaceD2D::PositionFromX(const IScreenLine *screenLine, XYPOSITION xDistance, bool charPosition) { + // If the text is empty, then no need to go through this function + if (!screenLine->Length()) + return 0; + + // Get textFormat + FormatAndMetrics *pfm = static_cast<FormatAndMetrics *>(screenLine->FontOfPosition(0)->GetID()); + + if (!pIDWriteFactory || !pfm->pTextFormat) { + return 0; + } + + // Convert the string to wstring and replace the original control characters with their representative chars. + std::wstring buffer = ReplaceRepresentation(screenLine->Text()); + + // Create a text layout + IDWriteTextLayout *textLayout = 0; + + const HRESULT hrCreate = pIDWriteFactory->CreateTextLayout(buffer.c_str(), static_cast<UINT32>(buffer.length()), + pfm->pTextFormat, screenLine->Width(), screenLine->Height(), &textLayout); + if (!SUCCEEDED(hrCreate)) { + return 0; + } + + std::vector<BlobInline> blobs; + + // Fill the textLayout chars with their own formats + FillTextLayoutFormats(screenLine, textLayout, blobs); + + // Returns the text position corresponding to the mouse x,y. + // If hitting the trailing side of a cluster, return the + // leading edge of the following text position. + + BOOL isTrailingHit; + BOOL isInside; + DWRITE_HIT_TEST_METRICS caretMetrics; + + textLayout->HitTestPoint( + xDistance, + 0.0f, + &isTrailingHit, + &isInside, + &caretMetrics + ); + + DWRITE_HIT_TEST_METRICS hitTestMetrics = {}; + if (isTrailingHit) { + FLOAT caretX = 0.0f; + FLOAT caretY = 0.0f; + + // Uses hit-testing to align the current caret position to a whole cluster, + // rather than residing in the middle of a base character + diacritic, + // surrogate pair, or character + UVS. + + // Align the caret to the nearest whole cluster. + textLayout->HitTestTextPosition( + caretMetrics.textPosition, + false, + &caretX, + &caretY, + &hitTestMetrics + ); + } + + textLayout->Release(); + size_t pos; + if (charPosition) { + pos = isTrailingHit ? hitTestMetrics.textPosition : caretMetrics.textPosition; + } else { + pos = isTrailingHit ? hitTestMetrics.textPosition + hitTestMetrics.length : caretMetrics.textPosition; + } + + // Get the character position in original string + return GetPositionInString(screenLine->Text(), pos); } -XYPOSITION SurfaceD2D::XFromPosition(const IScreenLine *, size_t) { - return 0; +// Finds the point of the caret position + +XYPOSITION SurfaceD2D::XFromPosition(const IScreenLine *screenLine, size_t caretPosition) { + // If the text is empty, then no need to go through this function + if (!screenLine->Length()) + return 0.0f; + + // Convert byte positions to wchar_t positions + const size_t position = GetPositionInLayout(screenLine->Text(), caretPosition); + + // Get textFormat + FormatAndMetrics *pfm = static_cast<FormatAndMetrics *>(screenLine->FontOfPosition(0)->GetID()); + + if (!pIDWriteFactory || !pfm->pTextFormat) { + return 0.0f; + } + + // Convert the string to wstring and replace the original control characters with their representative chars. + std::wstring buffer = ReplaceRepresentation(screenLine->Text()); + + // Create a text layout + IDWriteTextLayout *textLayout = 0; + + const HRESULT hrCreate = pIDWriteFactory->CreateTextLayout(buffer.c_str(), static_cast<UINT32>(buffer.length()+1), + pfm->pTextFormat, screenLine->Width(), screenLine->Height(), &textLayout); + + if (!SUCCEEDED(hrCreate)) { + return 0.0f; + } + + std::vector<BlobInline> blobs; + + // Fill the textLayout chars with their own formats + FillTextLayoutFormats(screenLine, textLayout, blobs); + + // Translate text character offset to point x,y. + DWRITE_HIT_TEST_METRICS caretMetrics; + Point pt; + + textLayout->HitTestTextPosition( + static_cast<UINT32>(position), + false, // trailing if false, else leading edge + &pt.x, + &pt.y, + &caretMetrics + ); + + textLayout->Release(); + + return pt.x; } -std::vector<Interval> SurfaceD2D::FindRangeIntervals(const IScreenLine *, size_t, size_t) { - return std::vector<Interval>(); +// Find the selection range rectangles + +std::vector<Interval> SurfaceD2D::FindRangeIntervals(const IScreenLine *screenLine, size_t start, size_t end) { + std::vector<Interval> ret; + + // If the text is empty, then no need to go through this function + if (!screenLine->Length()) { + return ret; + } + + // Convert byte positions to wchar_t positions + const size_t startPos = GetPositionInLayout(screenLine->Text(), start); + const size_t endPos = GetPositionInLayout(screenLine->Text(), end); + + // Get textFormat + FormatAndMetrics *pfm = static_cast<FormatAndMetrics *>(screenLine->FontOfPosition(0)->GetID()); + + if (!pIDWriteFactory || !pfm->pTextFormat) { + return ret; + } + + // Convert the string to wstring and replace the original control characters with their representative chars. + std::wstring buffer = ReplaceRepresentation(screenLine->Text()); + + // Create a text layout + IDWriteTextLayout *textLayout = 0; + + const HRESULT hrCreate = pIDWriteFactory->CreateTextLayout(buffer.c_str(), static_cast<UINT32>(buffer.length() + 1), + pfm->pTextFormat, screenLine->Width(), screenLine->Height(), &textLayout); + + if (!SUCCEEDED(hrCreate)) { + return ret; + } + + std::vector<BlobInline> blobs; + + // Fill the textLayout chars with their own formats + FillTextLayoutFormats(screenLine, textLayout, blobs); + + // Find selection range length + size_t rangeLength = (endPos > startPos) ? (endPos - startPos) : (startPos - endPos); + + // Determine actual number of hit-test ranges + UINT32 actualHitTestCount = 0; + + if (rangeLength > 0) { + textLayout->HitTestTextRange( + static_cast<UINT32>(startPos), + static_cast<UINT32>(rangeLength), + 0, // x + 0, // y + NULL, + 0, // metrics count + &actualHitTestCount + ); + } + + // Allocate enough room to return all hit-test metrics. + std::vector<DWRITE_HIT_TEST_METRICS> hitTestMetrics(actualHitTestCount); + + if (rangeLength > 0) { + textLayout->HitTestTextRange( + static_cast<UINT32>(startPos), + static_cast<UINT32>(rangeLength), + 0, // x + 0, // y + &hitTestMetrics[0], + static_cast<UINT32>(hitTestMetrics.size()), + &actualHitTestCount + ); + } + + // Get the selection ranges behind the text. + + if (actualHitTestCount > 0) { + for (size_t i = 0; i < actualHitTestCount; ++i) { + // Draw selection rectangle + const DWRITE_HIT_TEST_METRICS &htm = hitTestMetrics[i]; + Interval selectionInterval; + + selectionInterval.left = htm.left; + selectionInterval.right = htm.left + htm.width; + + ret.push_back(selectionInterval); + } + } + return ret; +} + +// Each char can have its own style, so we fill the textLayout with the textFormat of each char + +void SurfaceD2D::FillTextLayoutFormats(const IScreenLine *screenLine, IDWriteTextLayout *textLayout, std::vector<BlobInline> &blobs) { + // Reserve enough entries up front so they are not moved and the pointers handed + // to textLayout remain valid. + const ptrdiff_t numRepresentations = screenLine->RepresentationCount(); + std::string_view text = screenLine->Text(); + const ptrdiff_t numTabs = std::count(std::begin(text), std::end(text), '\t'); + blobs.reserve(numRepresentations + numTabs); + + UINT32 layoutPosition = 0; + + for (size_t bytePosition = 0; bytePosition < screenLine->Length();) { + const unsigned char uch = screenLine->Text()[bytePosition]; + const unsigned int byteCount = UTF8BytesOfLead[uch]; + const UINT32 codeUnits = static_cast<UINT32>(UTF16LengthFromUTF8ByteCount(byteCount)); + const DWRITE_TEXT_RANGE textRange = { layoutPosition, codeUnits }; + + XYPOSITION representationWidth = screenLine->RepresentationWidth(bytePosition); + if ((representationWidth == 0.0f) && (screenLine->Text()[bytePosition] == '\t')) { + Point realPt; + DWRITE_HIT_TEST_METRICS realCaretMetrics; + textLayout->HitTestTextPosition( + layoutPosition, + false, // trailing if false, else leading edge + &realPt.x, + &realPt.y, + &realCaretMetrics + ); + + const XYPOSITION nextTab = (static_cast<int>((realPt.x + screenLine->TabWidthMinimumPixels()) / screenLine->TabWidth()) + 1) * screenLine->TabWidth(); + representationWidth = nextTab - realPt.x; + } + if (representationWidth > 0.0f) { + blobs.push_back(BlobInline(representationWidth)); + textLayout->SetInlineObject(&blobs.back(), textRange); + }; + + FormatAndMetrics *pfm = + static_cast<FormatAndMetrics *>(screenLine->FontOfPosition(bytePosition)->GetID()); + + const unsigned int fontFamilyNameSize = pfm->pTextFormat->GetFontFamilyNameLength(); + std::vector<WCHAR> fontFamilyName(fontFamilyNameSize + 1); + + pfm->pTextFormat->GetFontFamilyName(fontFamilyName.data(), fontFamilyNameSize + 1); + textLayout->SetFontFamilyName(fontFamilyName.data(), textRange); + + textLayout->SetFontSize(pfm->pTextFormat->GetFontSize(), textRange); + textLayout->SetFontWeight(pfm->pTextFormat->GetFontWeight(), textRange); + textLayout->SetFontStyle(pfm->pTextFormat->GetFontStyle(), textRange); + + const unsigned int localNameSize = pfm->pTextFormat->GetLocaleNameLength(); + std::vector<WCHAR> localName(localNameSize + 1); + + pfm->pTextFormat->GetLocaleName(localName.data(), localNameSize); + textLayout->SetLocaleName(localName.data(), textRange); + + textLayout->SetFontStretch(pfm->pTextFormat->GetFontStretch(), textRange); + + IDWriteFontCollection *fontCollection; + if (SUCCEEDED(pfm->pTextFormat->GetFontCollection(&fontCollection))) { + textLayout->SetFontCollection(fontCollection, textRange); + } + + bytePosition += byteCount; + layoutPosition += codeUnits; + } + +} + +/* Convert to a wide character string and replace tabs with X to stop DirectWrite tab expansion */ + +std::wstring SurfaceD2D::ReplaceRepresentation(std::string_view text) { + const TextWide wideText(text, true); + std::wstring ws(wideText.buffer, wideText.tlen); + std::replace(ws.begin(), ws.end(), L'\t', L'X'); + return ws; +} + +// Finds the position in the wide character version of the text. + +size_t SurfaceD2D::GetPositionInLayout(std::string_view text, size_t position) { + const std::string_view textUptoPosition = text.substr(0, position); + return UTF16Length(textUptoPosition); +} + +// Converts position in wide character string to position in string. + +size_t SurfaceD2D::GetPositionInString(std::string_view text, size_t position) { + size_t posInString = 0; + for (size_t wLength = 0; (!text.empty()) && (wLength < position);) { + const unsigned char uch = text[0]; + const unsigned int byteCount = UTF8BytesOfLead[uch]; + wLength += UTF16LengthFromUTF8ByteCount(byteCount); + posInString += byteCount; + text.remove_prefix(byteCount); + } + + return posInString; } void SurfaceD2D::DrawTextCommon(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, UINT fuOptions) { diff --git a/win32/ScintillaWin.cxx b/win32/ScintillaWin.cxx index d60e2b31c..faab3844e 100644 --- a/win32/ScintillaWin.cxx +++ b/win32/ScintillaWin.cxx @@ -3364,6 +3364,7 @@ LRESULT PASCAL ScintillaWin::CTWndProc( } surfaceWindow->SetUnicodeMode(SC_CP_UTF8 == sciThis->ct.codePage); surfaceWindow->SetDBCSMode(sciThis->ct.codePage); + surfaceWindow->SetBidiR2L(sciThis->BidirectionalR2L()); sciThis->ct.PaintCT(surfaceWindow.get()); #if defined(USE_D2D) if (pCTRenderTarget) |