1 /*
2 * Copyright 2020 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "modules/skshaper/include/SkShaper.h"
9
10 #ifdef SK_BUILD_FOR_MAC
11 #import <ApplicationServices/ApplicationServices.h>
12 #endif
13
14 #ifdef SK_BUILD_FOR_IOS
15 #include <CoreText/CoreText.h>
16 #include <CoreText/CTFontManager.h>
17 #include <CoreGraphics/CoreGraphics.h>
18 #include <CoreFoundation/CoreFoundation.h>
19 #endif
20
21 #include "include/ports/SkTypeface_mac.h"
22 #include "include/private/base/SkTemplates.h"
23 #include "src/base/SkUTF.h"
24 #include "src/core/SkFontPriv.h"
25 #include "src/utils/mac/SkCGBase.h"
26 #include "src/utils/mac/SkUniqueCFRef.h"
27
28 #include <vector>
29 #include <utility>
30
31 using namespace skia_private;
32
33 class SkShaper_CoreText : public SkShaper {
34 public:
SkShaper_CoreText()35 SkShaper_CoreText() {}
36 private:
37 #if !defined(SK_DISABLE_LEGACY_SKSHAPER_FUNCTIONS)
38 void shape(const char* utf8, size_t utf8Bytes,
39 const SkFont& srcFont,
40 bool leftToRight,
41 SkScalar width,
42 RunHandler*) const override;
43
44 void shape(const char* utf8, size_t utf8Bytes,
45 FontRunIterator&,
46 BiDiRunIterator&,
47 ScriptRunIterator&,
48 LanguageRunIterator&,
49 SkScalar width,
50 RunHandler*) const override;
51 #endif
52
53 void shape(const char* utf8, size_t utf8Bytes,
54 FontRunIterator&,
55 BiDiRunIterator&,
56 ScriptRunIterator&,
57 LanguageRunIterator&,
58 const Feature*, size_t featureSize,
59 SkScalar width,
60 RunHandler*) const override;
61 };
62
63 // CTFramesetter/CTFrame can do this, but require version 10.14
64 class LineBreakIter {
65 CTTypesetterRef fTypesetter;
66 double fWidth;
67 CFIndex fStart;
68
69 public:
LineBreakIter(CTTypesetterRef ts,SkScalar width)70 LineBreakIter(CTTypesetterRef ts, SkScalar width) : fTypesetter(ts), fWidth(width) {
71 fStart = 0;
72 }
73
nextLine()74 SkUniqueCFRef<CTLineRef> nextLine() {
75 CFRange stringRange {fStart, CTTypesetterSuggestLineBreak(fTypesetter, fStart, fWidth)};
76 if (stringRange.length == 0) {
77 return nullptr;
78 }
79 fStart += stringRange.length;
80 return SkUniqueCFRef<CTLineRef>(CTTypesetterCreateLine(fTypesetter, stringRange));
81 }
82 };
83
dict_add_double(CFMutableDictionaryRef d,const void * name,double value)84 [[maybe_unused]] static void dict_add_double(CFMutableDictionaryRef d, const void* name, double value) {
85 SkUniqueCFRef<CFNumberRef> number(
86 CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value));
87 CFDictionaryAddValue(d, name, number.get());
88 }
89
create_ctfont_from_font(const SkFont & font)90 static SkUniqueCFRef<CTFontRef> create_ctfont_from_font(const SkFont& font) {
91 auto typeface = font.getTypeface();
92 auto ctfont = SkTypeface_GetCTFontRef(typeface);
93 if (!ctfont) {
94 return nullptr;
95 }
96 return SkUniqueCFRef<CTFontRef>(
97 CTFontCreateCopyWithAttributes(ctfont, font.getSize(), nullptr, nullptr));
98 }
99
run_to_font(CTRunRef run,const SkFont & orig)100 static SkFont run_to_font(CTRunRef run, const SkFont& orig) {
101 CFDictionaryRef attr = CTRunGetAttributes(run);
102 CTFontRef ct = (CTFontRef)CFDictionaryGetValue(attr, kCTFontAttributeName);
103 if (!ct) {
104 SkDebugf("no ctfont in Run Attributes\n");
105 CFShow(attr);
106 return orig;
107 }
108 // Do I need to add a local cache, or allow the caller to manage this lookup?
109 SkFont font(orig);
110 font.setTypeface(SkMakeTypefaceFromCTFont(ct));
111 return font;
112 }
113
114 namespace {
115 class UTF16ToUTF8IndicesMap {
116 public:
117 /** Builds a UTF-16 to UTF-8 indices map; the text is not retained
118 * @return true if successful
119 */
setUTF8(const char * utf8,size_t size)120 bool setUTF8(const char* utf8, size_t size) {
121 SkASSERT(utf8 != nullptr);
122
123 if (!SkTFitsIn<int32_t>(size)) {
124 SkDEBUGF("UTF16ToUTF8IndicesMap: text too long");
125 return false;
126 }
127
128 auto utf16Size = SkUTF::UTF8ToUTF16(nullptr, 0, utf8, size);
129 if (utf16Size < 0) {
130 SkDEBUGF("UTF16ToUTF8IndicesMap: Invalid utf8 input");
131 return false;
132 }
133
134 // utf16Size+1 to also store the size
135 fUtf16ToUtf8Indices = std::vector<size_t>(utf16Size + 1);
136 auto utf16 = fUtf16ToUtf8Indices.begin();
137 auto utf8Begin = utf8, utf8End = utf8 + size;
138 while (utf8Begin < utf8End) {
139 *utf16 = utf8Begin - utf8;
140 utf16 += SkUTF::ToUTF16(SkUTF::NextUTF8(&utf8Begin, utf8End), nullptr);
141 }
142 *utf16 = size;
143
144 return true;
145 }
146
mapIndex(size_t index) const147 size_t mapIndex(size_t index) const {
148 SkASSERT(index < fUtf16ToUtf8Indices.size());
149 return fUtf16ToUtf8Indices[index];
150 }
151
mapRange(size_t start,size_t size) const152 std::pair<size_t, size_t> mapRange(size_t start, size_t size) const {
153 auto utf8Start = mapIndex(start);
154 return {utf8Start, mapIndex(start + size) - utf8Start};
155 }
156 private:
157 std::vector<size_t> fUtf16ToUtf8Indices;
158 };
159 } // namespace
160
161 // kCTTrackingAttributeName not available until 10.12
162 const CFStringRef kCTTracking_AttributeName = CFSTR("CTTracking");
163
164 #if !defined(SK_DISABLE_LEGACY_SKSHAPER_FUNCTIONS)
shape(const char * utf8,size_t utf8Bytes,FontRunIterator & font,BiDiRunIterator & bidi,ScriptRunIterator & script,LanguageRunIterator & lang,SkScalar width,RunHandler * handler) const165 void SkShaper_CoreText::shape(const char* utf8,
166 size_t utf8Bytes,
167 FontRunIterator& font,
168 BiDiRunIterator& bidi,
169 ScriptRunIterator& script,
170 LanguageRunIterator& lang,
171 SkScalar width,
172 RunHandler* handler) const {
173 return this->shape(utf8, utf8Bytes, font, bidi, script, lang, nullptr, 0, width, handler);
174 }
175
shape(const char * utf8,size_t utf8Bytes,const SkFont & font,bool,SkScalar width,RunHandler * handler) const176 void SkShaper_CoreText::shape(const char* utf8,
177 size_t utf8Bytes,
178 const SkFont& font,
179 bool,
180 SkScalar width,
181 RunHandler* handler) const {
182 std::unique_ptr<FontRunIterator> fontRuns(
183 MakeFontMgrRunIterator(utf8, utf8Bytes, font, nullptr));
184 if (!fontRuns) {
185 return;
186 }
187 // bidi, script, and lang are all unused so we can construct them with empty data.
188 TrivialBiDiRunIterator bidi{0, 0};
189 TrivialScriptRunIterator script{0, 0};
190 TrivialLanguageRunIterator lang{nullptr, 0};
191 return this->shape(utf8, utf8Bytes, *fontRuns, bidi, script, lang, nullptr, 0, width, handler);
192 }
193 #endif
194
shape(const char * utf8,size_t utf8Bytes,FontRunIterator & fontRuns,BiDiRunIterator &,ScriptRunIterator &,LanguageRunIterator &,const Feature *,size_t,SkScalar width,RunHandler * handler) const195 void SkShaper_CoreText::shape(const char* utf8,
196 size_t utf8Bytes,
197 FontRunIterator& fontRuns,
198 BiDiRunIterator&,
199 ScriptRunIterator&,
200 LanguageRunIterator&,
201 const Feature*,
202 size_t,
203 SkScalar width,
204 RunHandler* handler) const {
205 SkFont font;
206 if (!fontRuns.atEnd()) {
207 fontRuns.consume();
208 font = fontRuns.currentFont();
209 }
210
211 SkUniqueCFRef<CFStringRef> textString(
212 CFStringCreateWithBytes(kCFAllocatorDefault, (const uint8_t*)utf8, utf8Bytes,
213 kCFStringEncodingUTF8, false));
214
215 UTF16ToUTF8IndicesMap utf8IndicesMap;
216 if (!utf8IndicesMap.setUTF8(utf8, utf8Bytes)) {
217 return;
218 }
219
220 SkUniqueCFRef<CTFontRef> ctfont = create_ctfont_from_font(font);
221 if (!ctfont) {
222 return;
223 }
224
225 SkUniqueCFRef<CFMutableDictionaryRef> attr(
226 CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
227 &kCFTypeDictionaryKeyCallBacks,
228 &kCFTypeDictionaryValueCallBacks));
229 CFDictionaryAddValue(attr.get(), kCTFontAttributeName, ctfont.get());
230 if ((false)) {
231 // trying to see what these affect
232 dict_add_double(attr.get(), kCTTracking_AttributeName, 1);
233 dict_add_double(attr.get(), kCTKernAttributeName, 0.0);
234 }
235
236 SkUniqueCFRef<CFAttributedStringRef> attrString(
237 CFAttributedStringCreate(kCFAllocatorDefault, textString.get(), attr.get()));
238
239 SkUniqueCFRef<CTTypesetterRef> typesetter(
240 CTTypesetterCreateWithAttributedString(attrString.get()));
241
242 // We have to compute RunInfos in a loop, and then reuse them in a 2nd loop,
243 // so we store them in an array (we reuse the array's storage for each line).
244 std::vector<SkFont> fontStorage;
245 std::vector<SkShaper::RunHandler::RunInfo> infos;
246
247 LineBreakIter iter(typesetter.get(), width);
248 while (SkUniqueCFRef<CTLineRef> line = iter.nextLine()) {
249 CFArrayRef run_array = CTLineGetGlyphRuns(line.get());
250 CFIndex runCount = CFArrayGetCount(run_array);
251 if (runCount == 0) {
252 continue;
253 }
254 handler->beginLine();
255 fontStorage.clear();
256 fontStorage.reserve(runCount); // ensure the refs won't get invalidated
257 infos.clear();
258 for (CFIndex j = 0; j < runCount; ++j) {
259 CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(run_array, j);
260 CFIndex runGlyphs = CTRunGetGlyphCount(run);
261
262 SkASSERT(sizeof(CGGlyph) == sizeof(uint16_t));
263
264 AutoSTArray<4096, CGSize> advances(runGlyphs);
265 CTRunGetAdvances(run, {0, runGlyphs}, advances.data());
266 SkScalar adv = 0;
267 for (CFIndex k = 0; k < runGlyphs; ++k) {
268 adv += advances[k].width;
269 }
270
271 CFRange cfRange = CTRunGetStringRange(run);
272 auto range = utf8IndicesMap.mapRange(cfRange.location, cfRange.length);
273
274 fontStorage.push_back(run_to_font(run, font));
275 infos.push_back({
276 fontStorage.back(), // info just stores a ref to the font
277 0, // need fBidiLevel
278 {adv, 0},
279 (size_t)runGlyphs,
280 {range.first, range.second},
281 });
282 handler->runInfo(infos.back());
283 }
284 handler->commitRunInfo();
285
286 // Now loop through again and fill in the buffers
287 SkScalar lineAdvance = 0;
288 for (CFIndex j = 0; j < runCount; ++j) {
289 const auto& info = infos[j];
290 auto buffer = handler->runBuffer(info);
291
292 CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(run_array, j);
293 CFIndex runGlyphs = info.glyphCount;
294 SkASSERT(CTRunGetGlyphCount(run) == (CFIndex)info.glyphCount);
295
296 CTRunGetGlyphs(run, {0, runGlyphs}, buffer.glyphs);
297
298 AutoSTArray<4096, CGPoint> positions(runGlyphs);
299 CTRunGetPositions(run, {0, runGlyphs}, positions.data());
300 AutoSTArray<4096, CFIndex> indices;
301 if (buffer.clusters) {
302 indices.reset(runGlyphs);
303 CTRunGetStringIndices(run, {0, runGlyphs}, indices.data());
304 }
305
306 for (CFIndex k = 0; k < runGlyphs; ++k) {
307 buffer.positions[k] = {
308 buffer.point.fX + SkScalarFromCGFloat(positions[k].x) - lineAdvance,
309 buffer.point.fY,
310 };
311 if (buffer.offsets) {
312 buffer.offsets[k] = {0, 0}; // offset relative to the origin for this glyph
313 }
314 if (buffer.clusters) {
315 buffer.clusters[k] = utf8IndicesMap.mapIndex(indices[k]);
316 }
317 }
318 handler->commitRunBuffer(info);
319 lineAdvance += info.fAdvance.fX;
320 }
321 handler->commitLine();
322 }
323 }
324
325 namespace SkShapers::CT {
CoreText()326 std::unique_ptr<SkShaper> CoreText() { return std::make_unique<SkShaper_CoreText>(); }
327 } // namespace SkShapers::CT
328