From 7d489f57b1b54fe5e895798bc29e7050e992b71c Mon Sep 17 00:00:00 2001 From: Neil Date: Sat, 2 Jun 2018 08:51:29 +1000 Subject: Implement IScreenLineLayout for Cocoa Core Text as ScreenLineLayout. --- cocoa/PlatCocoa.mm | 215 +++++++++++++++++++++++++++++++++++++++++++++++- cocoa/ScintillaCocoa.mm | 7 ++ scripts/HeaderOrder.txt | 1 + 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 #include #include +#include #include +#include #include @@ -84,7 +86,7 @@ Font::~Font() { //-------------------------------------------------------------------------------------------------- -static QuartzTextStyle *TextStyleFromFont(Font &f) { +static QuartzTextStyle *TextStyleFromFont(const Font &f) { return static_cast(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(refCon); + delete blob; +} + +static CGFloat BlobGetWidth(void *refCon) { + Blob *blob = static_cast(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 FindRangeIntervals(size_t start, size_t end) override; +}; + +ScreenLineLayout::ScreenLineLayout(const IScreenLine *screenLine) : text(screenLine->Text()) { + const UInt8 *puiBuffer = reinterpret_cast(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; bpRepresentationWidth(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(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 &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 ScreenLineLayout::FindRangeIntervals(size_t start, size_t end) { + if (!line) { + return {}; + } + + std::vector 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(CFArrayGetValueAtIndex(runs, run)); + const CFIndex glyphCount = CTRunGetGlyphCount(aRun); + const CFRange rangeAll = CFRangeMake(0, glyphCount); + std::vector indices(glyphCount); + CTRunGetStringIndices(aRun, rangeAll, indices.data()); + std::vector positions(glyphCount); + CTRunGetPositions(aRun, rangeAll, positions.data()); + std::vector advances(glyphCount); + CTRunGetAdvances(aRun, rangeAll, advances.data()); + for (CFIndex glyph=0; glyph= 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 &positions) { + + // Find the advances of the text + std::vector lineAdvances(positions.size()); + CFArrayRef runs = CTLineGetGlyphRuns(line); + const CFIndex runCount = CFArrayGetCount(runs); + for (CFIndex run=0; run(CFArrayGetValueAtIndex(runs, run)); + const CFIndex glyphCount = CTRunGetGlyphCount(aRun); + const CFRange rangeAll = CFRangeMake(0, glyphCount); + std::vector indices(glyphCount); + CTRunGetStringIndices(aRun, rangeAll, indices.data()); + std::vector advances(glyphCount); + CTRunGetAdvances(aRun, rangeAll, advances.data()); + for (CFIndex glyph=0; glyph= positions.size()) { + return; + } + lineAdvances[glyphIndex] = advances[glyph].width; + } + } + + // Accumulate advances into positions + std::partial_sum(lineAdvances.begin(), lineAdvances.end(), + positions.begin(), std::plus()); +} + +} + //----------------- 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 SurfaceImpl::Layout(const IScreenLine *screenLine) { - return {}; + return std::make_unique(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 linePositions(fit); + GetPositions(mLine, linePositions); while (ui(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(this); + case SCI_SETBIDIRECTIONAL: + bidirectional = static_cast(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 #include #include +#include #include #include #include -- cgit v1.2.3