aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--cocoa/PlatCocoa.mm215
-rw-r--r--cocoa/ScintillaCocoa.mm7
-rw-r--r--scripts/HeaderOrder.txt1
3 files changed, 219 insertions, 4 deletions
diff --git a/cocoa/PlatCocoa.mm b/cocoa/PlatCocoa.mm
index aca57b81a..66d9aad4e 100644
--- a/cocoa/PlatCocoa.mm
+++ b/cocoa/PlatCocoa.mm
@@ -22,7 +22,9 @@
#include <string_view>
#include <vector>
#include <map>
+#include <functional>
#include <memory>
+#include <numeric>
#include <sys/time.h>
@@ -84,7 +86,7 @@ Font::~Font() {
//--------------------------------------------------------------------------------------------------
-static QuartzTextStyle *TextStyleFromFont(Font &f) {
+static QuartzTextStyle *TextStyleFromFont(const Font &f) {
return static_cast<QuartzTextStyle *>(f.GetID());
}
@@ -119,6 +121,209 @@ void Font::Release() {
fid = 0;
}
+//--------------------------------------------------------------------------------------------------
+
+// Bidirectional text support for Arabic and Hebrew.
+
+namespace {
+
+CFIndex IndexFromPosition(std::string_view text, size_t position) {
+ const std::string_view textUptoPosition = text.substr(0, position);
+ return UTF16Length(textUptoPosition);
+}
+
+// Handling representations and tabs
+
+struct Blob {
+ XYPOSITION width;
+ Blob(XYPOSITION width_) : width(width_) {
+ }
+};
+
+static void BlobDealloc(void *refCon) {
+ Blob *blob = static_cast<Blob *>(refCon);
+ delete blob;
+}
+
+static CGFloat BlobGetWidth(void *refCon) {
+ Blob *blob = static_cast<Blob *>(refCon);
+ return blob->width;
+}
+
+class ScreenLineLayout : public IScreenLineLayout {
+ CTLineRef line = NULL;
+ const std::string text;
+public:
+ ScreenLineLayout(const IScreenLine *screenLine);
+ ~ScreenLineLayout();
+ // IScreenLineLayout implementation
+ size_t PositionFromX(XYPOSITION xDistance, bool charPosition) override;
+ XYPOSITION XFromPosition(size_t caretPosition) override;
+ std::vector<Interval> FindRangeIntervals(size_t start, size_t end) override;
+};
+
+ScreenLineLayout::ScreenLineLayout(const IScreenLine *screenLine) : text(screenLine->Text()) {
+ const UInt8 *puiBuffer = reinterpret_cast<const UInt8 *>(text.data());
+
+ // Start with an empty mutable attributed string and add each character to it.
+ CFMutableAttributedStringRef mas = CFAttributedStringCreateMutable(NULL, 0);
+
+ for (size_t bp=0; bp<text.length();) {
+ const unsigned char uch = text[bp];
+ const unsigned int byteCount = UTF8BytesOfLead[uch];
+ XYPOSITION repWidth = screenLine->RepresentationWidth(bp);
+ if (uch == '\t') {
+ // Find the size up to the tab
+ NSMutableAttributedString *nas = (__bridge NSMutableAttributedString *)mas;
+ const NSSize sizeUpTo = [nas size];
+ const XYPOSITION nextTab = screenLine->TabPositionAfter(sizeUpTo.width);
+ repWidth = nextTab - sizeUpTo.width;
+ }
+ CFAttributedStringRef as = NULL;
+ if (repWidth > 0.0f) {
+ CTRunDelegateCallbacks callbacks = {
+ .version = kCTRunDelegateVersion1,
+ .dealloc = BlobDealloc,
+ .getWidth = BlobGetWidth
+ };
+ CTRunDelegateRef runDelegate = CTRunDelegateCreate(&callbacks, new Blob(repWidth));
+ NSMutableAttributedString *masBlob = [[NSMutableAttributedString alloc] initWithString:@"X"];
+ NSRange rangeX = NSMakeRange(0, 1);
+ [masBlob addAttribute: (NSString *)kCTRunDelegateAttributeName value: (__bridge id)runDelegate range:rangeX];
+ CFRelease(runDelegate);
+ as = (CFAttributedStringRef)CFBridgingRetain(masBlob);
+ } else {
+ CFStringRef piece = CFStringCreateWithBytes(NULL,
+ &puiBuffer[bp],
+ byteCount,
+ kCFStringEncodingUTF8,
+ false);
+ QuartzTextStyle *qts = static_cast<QuartzTextStyle *>(screenLine->FontOfPosition(bp)->GetID());
+ CFMutableDictionaryRef pieceAttributes = qts->getCTStyle();
+ as = CFAttributedStringCreate(NULL, piece, pieceAttributes);
+ CFRelease(piece);
+ }
+ CFAttributedStringReplaceAttributedString(mas,
+ CFRangeMake(CFAttributedStringGetLength(mas), 0),
+ as);
+ bp += byteCount;
+ CFRelease(as);
+ }
+
+ line = CTLineCreateWithAttributedString(mas);
+ CFRelease(mas);
+}
+
+ScreenLineLayout::~ScreenLineLayout() {
+ CFRelease(line);
+}
+
+size_t ScreenLineLayout::PositionFromX(XYPOSITION xDistance, bool charPosition) {
+ if (!line) {
+ return 0;
+ }
+ const CGPoint ptDistance = CGPointMake(xDistance, 0);
+ const CFIndex offset = CTLineGetStringIndexForPosition(line, ptDistance);
+ if (offset == kCFNotFound) {
+ return 0;
+ }
+ // Convert back to UTF-8 positions
+ return UTF8PositionFromUTF16Position(text, offset);
+}
+
+XYPOSITION ScreenLineLayout::XFromPosition(size_t caretPosition) {
+ if (!line) {
+ return 0.0;
+ }
+ // Convert from UTF-8 position
+ const CFIndex caretIndex = IndexFromPosition(text, caretPosition);
+
+ const CGFloat distance = CTLineGetOffsetForStringIndex(line, caretIndex, nullptr);
+ return distance;
+}
+
+void AddToIntervalVector(std::vector<Interval> &vi, XYPOSITION left, XYPOSITION right) {
+ const Interval interval = {left, right};
+ if (vi.empty()) {
+ vi.push_back(interval);
+ } else {
+ Interval &last = vi.back();
+ if (fabs(last.right-interval.left) < 0.01) {
+ // If new left is very close to previous right then extend last item
+ last.right = interval.right;
+ } else {
+ vi.push_back(interval);
+ }
+ }
+}
+
+std::vector<Interval> ScreenLineLayout::FindRangeIntervals(size_t start, size_t end) {
+ if (!line) {
+ return {};
+ }
+
+ std::vector<Interval> ret;
+
+ // Convert from UTF-8 position
+ const CFIndex startIndex = IndexFromPosition(text, start);
+ const CFIndex endIndex = IndexFromPosition(text, end);
+
+ CFArrayRef runs = CTLineGetGlyphRuns(line);
+ const CFIndex runCount = CFArrayGetCount(runs);
+ for (CFIndex run=0; run<runCount; run++) {
+ CTRunRef aRun = static_cast<CTRunRef>(CFArrayGetValueAtIndex(runs, run));
+ const CFIndex glyphCount = CTRunGetGlyphCount(aRun);
+ const CFRange rangeAll = CFRangeMake(0, glyphCount);
+ std::vector<CFIndex> indices(glyphCount);
+ CTRunGetStringIndices(aRun, rangeAll, indices.data());
+ std::vector<CGPoint> positions(glyphCount);
+ CTRunGetPositions(aRun, rangeAll, positions.data());
+ std::vector<CGSize> advances(glyphCount);
+ CTRunGetAdvances(aRun, rangeAll, advances.data());
+ for (CFIndex glyph=0; glyph<glyphCount; glyph++) {
+ const CFIndex glyphIndex = indices[glyph];
+ const XYPOSITION xPosition = positions[glyph].x;
+ const XYPOSITION width = advances[glyph].width;
+ if ((glyphIndex >= startIndex) && (glyphIndex < endIndex)) {
+ AddToIntervalVector(ret, xPosition, xPosition + width);
+ }
+ }
+ }
+ return ret;
+}
+
+// Helper for SurfaceImpl::MeasureWidths that examines the glyph runs in a layout
+
+void GetPositions(CTLineRef line, std::vector<CGFloat> &positions) {
+
+ // Find the advances of the text
+ std::vector<CGFloat> lineAdvances(positions.size());
+ CFArrayRef runs = CTLineGetGlyphRuns(line);
+ const CFIndex runCount = CFArrayGetCount(runs);
+ for (CFIndex run=0; run<runCount; run++) {
+ CTRunRef aRun = static_cast<CTRunRef>(CFArrayGetValueAtIndex(runs, run));
+ const CFIndex glyphCount = CTRunGetGlyphCount(aRun);
+ const CFRange rangeAll = CFRangeMake(0, glyphCount);
+ std::vector<CFIndex> indices(glyphCount);
+ CTRunGetStringIndices(aRun, rangeAll, indices.data());
+ std::vector<CGSize> advances(glyphCount);
+ CTRunGetAdvances(aRun, rangeAll, advances.data());
+ for (CFIndex glyph=0; glyph<glyphCount; glyph++) {
+ const CFIndex glyphIndex = indices[glyph];
+ if (glyphIndex >= positions.size()) {
+ return;
+ }
+ lineAdvances[glyphIndex] = advances[glyph].width;
+ }
+ }
+
+ // Accumulate advances into positions
+ std::partial_sum(lineAdvances.begin(), lineAdvances.end(),
+ positions.begin(), std::plus<CGFloat>());
+}
+
+}
+
//----------------- SurfaceImpl --------------------------------------------------------------------
SurfaceImpl::SurfaceImpl() {
@@ -819,10 +1024,10 @@ void SurfaceImpl::Copy(PRectangle rc, Scintilla::Point from, Surface &surfaceSou
//--------------------------------------------------------------------------------------------------
-// Bidirectional text support for Arabic and Hebrew not currently implemented on Cocoa.
+// Bidirectional text support for Arabic and Hebrew.
std::unique_ptr<IScreenLineLayout> SurfaceImpl::Layout(const IScreenLine *screenLine) {
- return {};
+ return std::make_unique<ScreenLineLayout>(screenLine);
}
//--------------------------------------------------------------------------------------------------
@@ -939,11 +1144,13 @@ void SurfaceImpl::MeasureWidths(Font &font_, std::string_view text, XYPOSITION *
CFIndex fit = textLayout->getStringLength();
int ui=0;
int i=0;
+ std::vector<CGFloat> linePositions(fit);
+ GetPositions(mLine, linePositions);
while (ui<fit) {
const unsigned char uch = text[i];
const unsigned int byteCount = UTF8BytesOfLead[uch];
const int codeUnits = UTF16LengthFromUTF8ByteCount(byteCount);
- CGFloat xPosition = CTLineGetOffsetForStringIndex(mLine, ui+codeUnits, NULL);
+ const CGFloat xPosition = linePositions[ui];
for (unsigned int bytePos=0; (bytePos<byteCount) && (i<text.length()); bytePos++) {
positions[i++] = static_cast<XYPOSITION>(xPosition);
}
diff --git a/cocoa/ScintillaCocoa.mm b/cocoa/ScintillaCocoa.mm
index 86c090f9d..0354c2df8 100644
--- a/cocoa/ScintillaCocoa.mm
+++ b/cocoa/ScintillaCocoa.mm
@@ -842,6 +842,13 @@ sptr_t ScintillaCocoa::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lPar
case SCI_GETDIRECTPOINTER:
return reinterpret_cast<sptr_t>(this);
+ case SCI_SETBIDIRECTIONAL:
+ bidirectional = static_cast<EditModel::Bidirectional>(wParam);
+ // Invalidate all cached information including layout.
+ DropGraphics(true);
+ InvalidateStyleRedraw();
+ return 0;
+
case SCI_TARGETASUTF8:
return TargetAsUTF8(CharPtrFromSPtr(lParam));
diff --git a/scripts/HeaderOrder.txt b/scripts/HeaderOrder.txt
index 37ef50241..7b317e824 100644
--- a/scripts/HeaderOrder.txt
+++ b/scripts/HeaderOrder.txt
@@ -44,6 +44,7 @@
#include <iterator>
#include <functional>
#include <memory>
+#include <numeric>
#include <chrono>
#include <regex>
#include <iostream>