| /* |
| * Copyright 2020 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "modules/skshaper/include/SkShaper.h" |
| |
| #ifdef SK_BUILD_FOR_MAC |
| #import <ApplicationServices/ApplicationServices.h> |
| #endif |
| |
| #ifdef SK_BUILD_FOR_IOS |
| #include <CoreText/CoreText.h> |
| #include <CoreText/CTFontManager.h> |
| #include <CoreGraphics/CoreGraphics.h> |
| #include <CoreFoundation/CoreFoundation.h> |
| #endif |
| |
| #include "include/ports/SkTypeface_mac.h" |
| #include "include/private/base/SkTemplates.h" |
| #include "src/base/SkUTF.h" |
| #include "src/utils/mac/SkCGBase.h" |
| #include "src/utils/mac/SkUniqueCFRef.h" |
| |
| #include <vector> |
| #include <utility> |
| |
| using namespace skia_private; |
| |
| class SkShaper_CoreText : public SkShaper { |
| public: |
| SkShaper_CoreText() {} |
| private: |
| void shape(const char* utf8, size_t utf8Bytes, |
| const SkFont& srcFont, |
| bool leftToRight, |
| SkScalar width, |
| RunHandler*) const override; |
| |
| void shape(const char* utf8, size_t utf8Bytes, |
| FontRunIterator&, |
| BiDiRunIterator&, |
| ScriptRunIterator&, |
| LanguageRunIterator&, |
| SkScalar width, |
| RunHandler*) const override; |
| |
| void shape(const char* utf8, size_t utf8Bytes, |
| FontRunIterator&, |
| BiDiRunIterator&, |
| ScriptRunIterator&, |
| LanguageRunIterator&, |
| const Feature*, size_t featureSize, |
| SkScalar width, |
| RunHandler*) const override; |
| }; |
| |
| std::unique_ptr<SkShaper> SkShaper::MakeCoreText() { |
| return std::make_unique<SkShaper_CoreText>(); |
| } |
| |
| void SkShaper_CoreText::shape(const char* utf8, size_t utf8Bytes, |
| FontRunIterator& font, |
| BiDiRunIterator& bidi, |
| ScriptRunIterator&, |
| LanguageRunIterator&, |
| SkScalar width, |
| RunHandler* handler) const |
| { |
| SkFont skfont; |
| if (!font.atEnd()) { |
| font.consume(); |
| skfont = font.currentFont(); |
| } else { |
| skfont.setTypeface(sk_ref_sp(skfont.getTypefaceOrDefault())); |
| } |
| SkASSERT(skfont.getTypeface()); |
| bool skbidi = 0; |
| if (!bidi.atEnd()) { |
| bidi.consume(); |
| skbidi = (bidi.currentLevel() % 2) == 0; |
| } |
| return this->shape(utf8, utf8Bytes, skfont, skbidi, width, handler); |
| } |
| |
| void SkShaper_CoreText::shape(const char* utf8, size_t utf8Bytes, |
| FontRunIterator& font, |
| BiDiRunIterator& bidi, |
| ScriptRunIterator&, |
| LanguageRunIterator&, |
| const Feature*, size_t, |
| SkScalar width, |
| RunHandler* handler) const { |
| font.consume(); |
| SkASSERT(font.currentFont().getTypeface()); |
| bidi.consume(); |
| return this->shape(utf8, utf8Bytes, font.currentFont(), (bidi.currentLevel() % 2) == 0, |
| width, handler); |
| } |
| |
| // CTFramesetter/CTFrame can do this, but require version 10.14 |
| class LineBreakIter { |
| CTTypesetterRef fTypesetter; |
| double fWidth; |
| CFIndex fStart; |
| |
| public: |
| LineBreakIter(CTTypesetterRef ts, SkScalar width) : fTypesetter(ts), fWidth(width) { |
| fStart = 0; |
| } |
| |
| SkUniqueCFRef<CTLineRef> nextLine() { |
| CFRange stringRange {fStart, CTTypesetterSuggestLineBreak(fTypesetter, fStart, fWidth)}; |
| if (stringRange.length == 0) { |
| return nullptr; |
| } |
| fStart += stringRange.length; |
| return SkUniqueCFRef<CTLineRef>(CTTypesetterCreateLine(fTypesetter, stringRange)); |
| } |
| }; |
| |
| static void dict_add_double(CFMutableDictionaryRef d, const void* name, double value) { |
| SkUniqueCFRef<CFNumberRef> number( |
| CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value)); |
| CFDictionaryAddValue(d, name, number.get()); |
| } |
| |
| static SkUniqueCFRef<CTFontRef> create_ctfont_from_font(const SkFont& font) { |
| auto typeface = font.getTypefaceOrDefault(); |
| auto ctfont = SkTypeface_GetCTFontRef(typeface); |
| return SkUniqueCFRef<CTFontRef>( |
| CTFontCreateCopyWithAttributes(ctfont, font.getSize(), nullptr, nullptr)); |
| } |
| |
| static SkFont run_to_font(CTRunRef run, const SkFont& orig) { |
| CFDictionaryRef attr = CTRunGetAttributes(run); |
| CTFontRef ct = (CTFontRef)CFDictionaryGetValue(attr, kCTFontAttributeName); |
| if (!ct) { |
| SkDebugf("no ctfont in Run Attributes\n"); |
| CFShow(attr); |
| return orig; |
| } |
| // Do I need to add a local cache, or allow the caller to manage this lookup? |
| SkFont font(orig); |
| font.setTypeface(SkMakeTypefaceFromCTFont(ct)); |
| return font; |
| } |
| |
| namespace { |
| class UTF16ToUTF8IndicesMap { |
| public: |
| /** Builds a UTF-16 to UTF-8 indices map; the text is not retained |
| * @return true if successful |
| */ |
| bool setUTF8(const char* utf8, size_t size) { |
| SkASSERT(utf8 != nullptr); |
| |
| if (!SkTFitsIn<int32_t>(size)) { |
| SkDEBUGF("UTF16ToUTF8IndicesMap: text too long"); |
| return false; |
| } |
| |
| auto utf16Size = SkUTF::UTF8ToUTF16(nullptr, 0, utf8, size); |
| if (utf16Size < 0) { |
| SkDEBUGF("UTF16ToUTF8IndicesMap: Invalid utf8 input"); |
| return false; |
| } |
| |
| // utf16Size+1 to also store the size |
| fUtf16ToUtf8Indices = std::vector<size_t>(utf16Size + 1); |
| auto utf16 = fUtf16ToUtf8Indices.begin(); |
| auto utf8Begin = utf8, utf8End = utf8 + size; |
| while (utf8Begin < utf8End) { |
| *utf16 = utf8Begin - utf8; |
| utf16 += SkUTF::ToUTF16(SkUTF::NextUTF8(&utf8Begin, utf8End), nullptr); |
| } |
| *utf16 = size; |
| |
| return true; |
| } |
| |
| size_t mapIndex(size_t index) const { |
| SkASSERT(index < fUtf16ToUtf8Indices.size()); |
| return fUtf16ToUtf8Indices[index]; |
| } |
| |
| std::pair<size_t, size_t> mapRange(size_t start, size_t size) const { |
| auto utf8Start = mapIndex(start); |
| return {utf8Start, mapIndex(start + size) - utf8Start}; |
| } |
| private: |
| std::vector<size_t> fUtf16ToUtf8Indices; |
| }; |
| } // namespace |
| |
| // kCTTrackingAttributeName not available until 10.12 |
| const CFStringRef kCTTracking_AttributeName = CFSTR("CTTracking"); |
| |
| void SkShaper_CoreText::shape(const char* utf8, size_t utf8Bytes, |
| const SkFont& font, |
| bool /* leftToRight */, |
| SkScalar width, |
| RunHandler* handler) const { |
| SkUniqueCFRef<CFStringRef> textString( |
| CFStringCreateWithBytes(kCFAllocatorDefault, (const uint8_t*)utf8, utf8Bytes, |
| kCFStringEncodingUTF8, false)); |
| |
| UTF16ToUTF8IndicesMap utf8IndicesMap; |
| if (!utf8IndicesMap.setUTF8(utf8, utf8Bytes)) { |
| return; |
| } |
| |
| SkUniqueCFRef<CTFontRef> ctfont = create_ctfont_from_font(font); |
| |
| SkUniqueCFRef<CFMutableDictionaryRef> attr( |
| CFDictionaryCreateMutable(kCFAllocatorDefault, 0, |
| &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks)); |
| CFDictionaryAddValue(attr.get(), kCTFontAttributeName, ctfont.get()); |
| if (false) { |
| // trying to see what these affect |
| dict_add_double(attr.get(), kCTTracking_AttributeName, 1); |
| dict_add_double(attr.get(), kCTKernAttributeName, 0.0); |
| } |
| |
| SkUniqueCFRef<CFAttributedStringRef> attrString( |
| CFAttributedStringCreate(kCFAllocatorDefault, textString.get(), attr.get())); |
| |
| SkUniqueCFRef<CTTypesetterRef> typesetter( |
| CTTypesetterCreateWithAttributedString(attrString.get())); |
| |
| // We have to compute RunInfos in a loop, and then reuse them in a 2nd loop, |
| // so we store them in an array (we reuse the array's storage for each line). |
| std::vector<SkFont> fontStorage; |
| std::vector<SkShaper::RunHandler::RunInfo> infos; |
| |
| LineBreakIter iter(typesetter.get(), width); |
| while (SkUniqueCFRef<CTLineRef> line = iter.nextLine()) { |
| CFArrayRef run_array = CTLineGetGlyphRuns(line.get()); |
| CFIndex runCount = CFArrayGetCount(run_array); |
| if (runCount == 0) { |
| continue; |
| } |
| handler->beginLine(); |
| fontStorage.clear(); |
| fontStorage.reserve(runCount); // ensure the refs won't get invalidated |
| infos.clear(); |
| for (CFIndex j = 0; j < runCount; ++j) { |
| CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(run_array, j); |
| CFIndex runGlyphs = CTRunGetGlyphCount(run); |
| |
| SkASSERT(sizeof(CGGlyph) == sizeof(uint16_t)); |
| |
| AutoSTArray<4096, CGSize> advances(runGlyphs); |
| CTRunGetAdvances(run, {0, runGlyphs}, advances.data()); |
| SkScalar adv = 0; |
| for (CFIndex k = 0; k < runGlyphs; ++k) { |
| adv += advances[k].width; |
| } |
| |
| CFRange cfRange = CTRunGetStringRange(run); |
| auto range = utf8IndicesMap.mapRange(cfRange.location, cfRange.length); |
| |
| fontStorage.push_back(run_to_font(run, font)); |
| infos.push_back({ |
| fontStorage.back(), // info just stores a ref to the font |
| 0, // need fBidiLevel |
| {adv, 0}, |
| (size_t)runGlyphs, |
| {range.first, range.second}, |
| }); |
| handler->runInfo(infos.back()); |
| } |
| handler->commitRunInfo(); |
| |
| // Now loop through again and fill in the buffers |
| SkScalar lineAdvance = 0; |
| for (CFIndex j = 0; j < runCount; ++j) { |
| const auto& info = infos[j]; |
| auto buffer = handler->runBuffer(info); |
| |
| CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(run_array, j); |
| CFIndex runGlyphs = info.glyphCount; |
| SkASSERT(CTRunGetGlyphCount(run) == (CFIndex)info.glyphCount); |
| |
| CTRunGetGlyphs(run, {0, runGlyphs}, buffer.glyphs); |
| |
| AutoSTArray<4096, CGPoint> positions(runGlyphs); |
| CTRunGetPositions(run, {0, runGlyphs}, positions.data()); |
| AutoSTArray<4096, CFIndex> indices; |
| if (buffer.clusters) { |
| indices.reset(runGlyphs); |
| CTRunGetStringIndices(run, {0, runGlyphs}, indices.data()); |
| } |
| |
| for (CFIndex k = 0; k < runGlyphs; ++k) { |
| buffer.positions[k] = { |
| buffer.point.fX + SkScalarFromCGFloat(positions[k].x) - lineAdvance, |
| buffer.point.fY, |
| }; |
| if (buffer.offsets) { |
| buffer.offsets[k] = {0, 0}; // offset relative to the origin for this glyph |
| } |
| if (buffer.clusters) { |
| buffer.clusters[k] = utf8IndicesMap.mapIndex(indices[k]); |
| } |
| } |
| handler->commitRunBuffer(info); |
| lineAdvance += info.fAdvance.fX; |
| } |
| handler->commitLine(); |
| } |
| } |