aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorNeil <nyamatongwe@gmail.com>2018-05-23 16:59:41 +1000
committerNeil <nyamatongwe@gmail.com>2018-05-23 16:59:41 +1000
commit071602224b06d3bc65b8eda49ec8f589ccf66159 (patch)
treec7e84825e23691624609d1b8d7200eeab41fd41e
parent7ee2a70c18f899a4845621622241adc578b0400c (diff)
downloadscintilla-mirror-071602224b06d3bc65b8eda49ec8f589ccf66159.tar.gz
Implement bidirectional mode bidiL2R for DirectDraw on Win32.
-rw-r--r--src/EditModel.cxx9
-rw-r--r--src/EditModel.h2
-rw-r--r--src/EditView.cxx200
-rw-r--r--src/EditView.h6
-rw-r--r--src/Editor.cxx15
-rw-r--r--src/Editor.h2
-rw-r--r--src/PositionCache.cxx103
-rw-r--r--src/PositionCache.h36
-rw-r--r--win32/PlatWin.cxx425
-rw-r--r--win32/ScintillaWin.cxx1
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)