/* * 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 #endif #ifdef SK_BUILD_FOR_IOS #include #include #include #include #endif #include "include/ports/SkTypeface_mac.h" #include "include/private/SkTemplates.h" #include "src/utils/SkUTF.h" #include "src/utils/mac/SkCGBase.h" #include "src/utils/mac/SkUniqueCFRef.h" #include #include 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::MakeCoreText() { return std::make_unique(); } 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 nextLine() { CFRange stringRange {fStart, CTTypesetterSuggestLineBreak(fTypesetter, fStart, fWidth)}; if (stringRange.length == 0) { return nullptr; } fStart += stringRange.length; return SkUniqueCFRef(CTTypesetterCreateLine(fTypesetter, stringRange)); } }; static void dict_add_double(CFMutableDictionaryRef d, const void* name, double value) { SkUniqueCFRef number( CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value)); CFDictionaryAddValue(d, name, number.get()); } static SkUniqueCFRef create_ctfont_from_font(const SkFont& font) { auto typeface = font.getTypefaceOrDefault(); auto ctfont = SkTypeface_GetCTFontRef(typeface); return SkUniqueCFRef( 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(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(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 mapRange(size_t start, size_t size) const { auto utf8Start = mapIndex(start); return {utf8Start, mapIndex(start + size) - utf8Start}; } private: std::vector 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 textString( CFStringCreateWithBytes(kCFAllocatorDefault, (const uint8_t*)utf8, utf8Bytes, kCFStringEncodingUTF8, false)); UTF16ToUTF8IndicesMap utf8IndicesMap; if (!utf8IndicesMap.setUTF8(utf8, utf8Bytes)) { return; } SkUniqueCFRef ctfont = create_ctfont_from_font(font); SkUniqueCFRef 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 attrString( CFAttributedStringCreate(kCFAllocatorDefault, textString.get(), attr.get())); SkUniqueCFRef 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 fontStorage; std::vector infos; LineBreakIter iter(typesetter.get(), width); while (SkUniqueCFRef 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)); SkAutoSTArray<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); SkAutoSTArray<4096, CGPoint> positions(runGlyphs); CTRunGetPositions(run, {0, runGlyphs}, positions.data()); SkAutoSTArray<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(); } }