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