gfxCoreTextShaper.cpp (25949B)
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "gfxCoreTextShaper.h" 7 #include "gfxMacFont.h" 8 #include "gfxFontUtils.h" 9 #include "gfxTextRun.h" 10 #include "mozilla/gfx/2D.h" 11 #include "mozilla/gfx/ScaledFontMac.h" 12 #include "mozilla/UniquePtrExtensions.h" 13 14 #include <algorithm> 15 16 #include <dlfcn.h> 17 18 using namespace mozilla; 19 using namespace mozilla::gfx; 20 21 // standard font descriptors that we construct the first time they're needed 22 CTFontDescriptorRef gfxCoreTextShaper::sFeaturesDescriptor[kMaxFontInstances]; 23 24 // Helper to create a CFDictionary with the right attributes for shaping our 25 // text, including imposing the given directionality. 26 CFDictionaryRef gfxCoreTextShaper::CreateAttrDict(bool aRightToLeft) { 27 // Because we always shape unidirectional runs, and may have applied 28 // directional overrides, we want to force a direction rather than 29 // allowing CoreText to do its own unicode-based bidi processing. 30 SInt16 dirOverride = kCTWritingDirectionOverride | 31 (aRightToLeft ? kCTWritingDirectionRightToLeft 32 : kCTWritingDirectionLeftToRight); 33 CFNumberRef dirNumber = 34 ::CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &dirOverride); 35 CFArrayRef dirArray = ::CFArrayCreate( 36 kCFAllocatorDefault, (const void**)&dirNumber, 1, &kCFTypeArrayCallBacks); 37 ::CFRelease(dirNumber); 38 CFTypeRef attrs[] = {kCTFontAttributeName, kCTWritingDirectionAttributeName}; 39 CFTypeRef values[] = {mCTFont[0], dirArray}; 40 CFDictionaryRef attrDict = ::CFDictionaryCreate( 41 kCFAllocatorDefault, attrs, values, std::size(attrs), 42 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 43 ::CFRelease(dirArray); 44 return attrDict; 45 } 46 47 gfxCoreTextShaper::gfxCoreTextShaper(gfxMacFont* aFont) 48 : gfxFontShaper(aFont), 49 mAttributesDictLTR(nullptr), 50 mAttributesDictRTL(nullptr) { 51 for (size_t i = 0; i < kMaxFontInstances; i++) { 52 mCTFont[i] = nullptr; 53 } 54 // Create our default CTFontRef 55 mCTFont[0] = CreateCTFontWithFeatures( 56 aFont->GetAdjustedSize(), GetFeaturesDescriptor(kDefaultFeatures)); 57 } 58 59 gfxCoreTextShaper::~gfxCoreTextShaper() { 60 if (mAttributesDictLTR) { 61 ::CFRelease(mAttributesDictLTR); 62 } 63 if (mAttributesDictRTL) { 64 ::CFRelease(mAttributesDictRTL); 65 } 66 for (size_t i = 0; i < kMaxFontInstances; i++) { 67 if (mCTFont[i]) { 68 ::CFRelease(mCTFont[i]); 69 } 70 } 71 } 72 73 static bool IsBuggyIndicScript(intl::Script aScript) { 74 return aScript == intl::Script::BENGALI || aScript == intl::Script::KANNADA || 75 aScript == intl::Script::ORIYA || aScript == intl::Script::KHMER; 76 } 77 78 bool gfxCoreTextShaper::ShapeText(DrawTarget* aDrawTarget, 79 const char16_t* aText, uint32_t aOffset, 80 uint32_t aLength, Script aScript, 81 nsAtom* aLanguage, bool aVertical, 82 RoundingFlags aRounding, 83 gfxShapedText* aShapedText) { 84 // Create a CFAttributedString with text and style info, so we can use 85 // CoreText to lay it out. 86 bool isRightToLeft = aShapedText->IsRightToLeft(); 87 const UniChar* text = reinterpret_cast<const UniChar*>(aText); 88 89 CFStringRef stringObj = ::CFStringCreateWithCharactersNoCopy( 90 kCFAllocatorDefault, text, aLength, kCFAllocatorNull); 91 92 // Figure out whether we should try to set the AAT small-caps feature: 93 // examine OpenType tags for the requested style, and see if 'smcp' is 94 // among them. 95 const gfxFontStyle* style = mFont->GetStyle(); 96 gfxFontEntry* entry = mFont->GetFontEntry(); 97 auto handleFeatureTag = [](uint32_t aTag, uint32_t aValue, 98 void* aUserArg) -> void { 99 if (aTag == HB_TAG('s', 'm', 'c', 'p') && aValue) { 100 *static_cast<bool*>(aUserArg) = true; 101 } 102 }; 103 bool addSmallCaps = false; 104 MergeFontFeatures(style, entry->mFeatureSettings, false, entry->FamilyName(), 105 false, handleFeatureTag, &addSmallCaps); 106 107 // Get an attributes dictionary suitable for shaping text in the 108 // current direction, creating it if necessary. 109 CFDictionaryRef attrObj = 110 isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR; 111 if (!attrObj) { 112 attrObj = CreateAttrDict(isRightToLeft); 113 (isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR) = attrObj; 114 } 115 116 FeatureFlags featureFlags = kDefaultFeatures; 117 if (IsBuggyIndicScript(aScript)) { 118 // To work around buggy Indic AAT fonts shipped with OS X, 119 // we re-enable the Line Initial Smart Swashes feature that is needed 120 // for "split vowels" to work in at least Bengali and Kannada fonts. 121 // Affected fonts include Bangla MN, Bangla Sangam MN, Kannada MN, 122 // Kannada Sangam MN. See bugs 686225, 728557, 953231, 1145515. 123 // Also applies to Oriya and Khmer, see bug 1370927 and bug 1403166. 124 featureFlags |= kIndicFeatures; 125 } 126 if (aShapedText->DisableLigatures()) { 127 // For letterspacing (or maybe other situations) we need to make 128 // a copy of the CTFont with the ligature feature disabled. 129 featureFlags |= kDisableLigatures; 130 } 131 if (addSmallCaps) { 132 featureFlags |= kAddSmallCaps; 133 } 134 135 // For the disabled-ligature, buggy-indic-font or small-caps case, replace 136 // the default CTFont in the attribute dictionary with a tweaked version. 137 CFMutableDictionaryRef mutableAttr = nullptr; 138 if (featureFlags != 0) { 139 if (!mCTFont[featureFlags]) { 140 mCTFont[featureFlags] = CreateCTFontWithFeatures( 141 mFont->GetAdjustedSize(), GetFeaturesDescriptor(featureFlags)); 142 } 143 mutableAttr = 144 ::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 2, attrObj); 145 ::CFDictionaryReplaceValue(mutableAttr, kCTFontAttributeName, 146 mCTFont[featureFlags]); 147 attrObj = mutableAttr; 148 } 149 150 // Now we can create an attributed string 151 CFAttributedStringRef attrStringObj = 152 ::CFAttributedStringCreate(kCFAllocatorDefault, stringObj, attrObj); 153 ::CFRelease(stringObj); 154 155 // Create the CoreText line from our string, then we're done with it 156 CTLineRef line = ::CTLineCreateWithAttributedString(attrStringObj); 157 ::CFRelease(attrStringObj); 158 159 // and finally retrieve the glyph data and store into the gfxTextRun 160 CFArrayRef glyphRuns = ::CTLineGetGlyphRuns(line); 161 uint32_t numRuns = ::CFArrayGetCount(glyphRuns); 162 163 // Iterate through the glyph runs. 164 bool success = true; 165 for (uint32_t runIndex = 0; runIndex < numRuns; runIndex++) { 166 CTRunRef aCTRun = (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex); 167 CFRange range = ::CTRunGetStringRange(aCTRun); 168 CFDictionaryRef runAttr = ::CTRunGetAttributes(aCTRun); 169 if (runAttr != attrObj) { 170 // If Core Text manufactured a new dictionary, this may indicate 171 // unexpected font substitution. In that case, we fail (and fall 172 // back to harfbuzz shaping)... 173 const void* font1 = ::CFDictionaryGetValue(attrObj, kCTFontAttributeName); 174 const void* font2 = ::CFDictionaryGetValue(runAttr, kCTFontAttributeName); 175 if (font1 != font2) { 176 // ...except that if the fallback was only for a variation 177 // selector or join control that is otherwise unsupported, 178 // we just ignore it. 179 if (range.length == 1) { 180 char16_t ch = aText[range.location]; 181 if (gfxFontUtils::IsJoinControl(ch) || 182 gfxFontUtils::IsVarSelector(ch)) { 183 continue; 184 } 185 } 186 NS_WARNING("unexpected font fallback in Core Text"); 187 success = false; 188 break; 189 } 190 } 191 if (SetGlyphsFromRun(aShapedText, aOffset, aLength, aCTRun) != NS_OK) { 192 success = false; 193 break; 194 } 195 } 196 197 if (mutableAttr) { 198 ::CFRelease(mutableAttr); 199 } 200 ::CFRelease(line); 201 202 return success; 203 } 204 205 #define SMALL_GLYPH_RUN \ 206 128 // preallocated size of our auto arrays for per-glyph data; 207 // some testing indicates that 90%+ of glyph runs will fit 208 // without requiring a separate allocation 209 210 nsresult gfxCoreTextShaper::SetGlyphsFromRun(gfxShapedText* aShapedText, 211 uint32_t aOffset, uint32_t aLength, 212 CTRunRef aCTRun) { 213 typedef gfxShapedText::CompressedGlyph CompressedGlyph; 214 215 int32_t direction = aShapedText->IsRightToLeft() ? -1 : 1; 216 217 int32_t numGlyphs = ::CTRunGetGlyphCount(aCTRun); 218 if (numGlyphs == 0) { 219 return NS_OK; 220 } 221 222 int32_t wordLength = aLength; 223 224 // character offsets get really confusing here, as we have to keep track of 225 // (a) the text in the actual textRun we're constructing 226 // (c) the string that was handed to CoreText, which contains the text of 227 // the font run 228 // (d) the CTRun currently being processed, which may be a sub-run of the 229 // CoreText line 230 231 // get the source string range within the CTLine's text 232 CFRange stringRange = ::CTRunGetStringRange(aCTRun); 233 // skip the run if it is entirely outside the actual range of the font run 234 if (stringRange.location + stringRange.length <= 0 || 235 stringRange.location >= wordLength) { 236 return NS_OK; 237 } 238 239 // retrieve the laid-out glyph data from the CTRun 240 UniquePtr<CGGlyph[]> glyphsArray; 241 UniquePtr<CGPoint[]> positionsArray; 242 UniquePtr<CFIndex[]> glyphToCharArray; 243 const CGGlyph* glyphs = nullptr; 244 const CGPoint* positions = nullptr; 245 const CFIndex* glyphToChar = nullptr; 246 247 // Testing indicates that CTRunGetGlyphsPtr (almost?) always succeeds, 248 // and so allocating a new array and copying data with CTRunGetGlyphs 249 // will be extremely rare. 250 // If this were not the case, we could use an AutoTArray<> to 251 // try and avoid the heap allocation for small runs. 252 // It's possible that some future change to CoreText will mean that 253 // CTRunGetGlyphsPtr fails more often; if this happens, AutoTArray<> 254 // may become an attractive option. 255 glyphs = ::CTRunGetGlyphsPtr(aCTRun); 256 if (!glyphs) { 257 glyphsArray = MakeUniqueFallible<CGGlyph[]>(numGlyphs); 258 if (!glyphsArray) { 259 return NS_ERROR_OUT_OF_MEMORY; 260 } 261 ::CTRunGetGlyphs(aCTRun, ::CFRangeMake(0, 0), glyphsArray.get()); 262 glyphs = glyphsArray.get(); 263 } 264 265 positions = ::CTRunGetPositionsPtr(aCTRun); 266 if (!positions) { 267 positionsArray = MakeUniqueFallible<CGPoint[]>(numGlyphs); 268 if (!positionsArray) { 269 return NS_ERROR_OUT_OF_MEMORY; 270 } 271 ::CTRunGetPositions(aCTRun, ::CFRangeMake(0, 0), positionsArray.get()); 272 positions = positionsArray.get(); 273 } 274 275 // Remember that the glyphToChar indices relate to the CoreText line, 276 // not to the beginning of the textRun, the font run, 277 // or the stringRange of the glyph run 278 glyphToChar = ::CTRunGetStringIndicesPtr(aCTRun); 279 if (!glyphToChar) { 280 glyphToCharArray = MakeUniqueFallible<CFIndex[]>(numGlyphs); 281 if (!glyphToCharArray) { 282 return NS_ERROR_OUT_OF_MEMORY; 283 } 284 ::CTRunGetStringIndices(aCTRun, ::CFRangeMake(0, 0), 285 glyphToCharArray.get()); 286 glyphToChar = glyphToCharArray.get(); 287 } 288 289 double runWidth = ::CTRunGetTypographicBounds(aCTRun, ::CFRangeMake(0, 0), 290 nullptr, nullptr, nullptr); 291 292 AutoTArray<gfxShapedText::DetailedGlyph, 1> detailedGlyphs; 293 CompressedGlyph* charGlyphs = aShapedText->GetCharacterGlyphs() + aOffset; 294 295 // CoreText gives us the glyphindex-to-charindex mapping, which relates each 296 // glyph to a source text character; we also need the charindex-to-glyphindex 297 // mapping to find the glyph for a given char. Note that some chars may not 298 // map to any glyph (ligature continuations), and some may map to several 299 // glyphs (eg Indic split vowels). We set the glyph index to NO_GLYPH for 300 // chars that have no associated glyph, and we record the last glyph index for 301 // cases where the char maps to several glyphs, so that our clumping will 302 // include all the glyph fragments for the character. 303 304 // The charToGlyph array is indexed by char position within the stringRange of 305 // the glyph run. 306 307 static const int32_t NO_GLYPH = -1; 308 AutoTArray<int32_t, SMALL_GLYPH_RUN> charToGlyphArray; 309 if (!charToGlyphArray.SetLength(stringRange.length, fallible)) { 310 return NS_ERROR_OUT_OF_MEMORY; 311 } 312 int32_t* charToGlyph = charToGlyphArray.Elements(); 313 for (int32_t offset = 0; offset < stringRange.length; ++offset) { 314 charToGlyph[offset] = NO_GLYPH; 315 } 316 for (int32_t i = 0; i < numGlyphs; ++i) { 317 int32_t loc = glyphToChar[i] - stringRange.location; 318 if (loc >= 0 && loc < stringRange.length) { 319 charToGlyph[loc] = i; 320 } 321 } 322 323 // Find character and glyph clumps that correspond, allowing for ligatures, 324 // indic reordering, split glyphs, etc. 325 // 326 // The idea is that we'll find a character sequence starting at the first char 327 // of stringRange, and extend it until it includes the character associated 328 // with the first glyph; we also extend it as long as there are "holes" in the 329 // range of glyphs. So we will eventually have a contiguous sequence of 330 // characters, starting at the beginning of the range, that map to a 331 // contiguous sequence of glyphs, starting at the beginning of the glyph 332 // array. That's a clump; then we update the starting positions and repeat. 333 // 334 // NB: In the case of RTL layouts, we iterate over the stringRange in reverse. 335 // 336 337 // This may find characters that fall outside the range 0:wordLength, 338 // so we won't necessarily use everything we find here. 339 340 bool isRightToLeft = aShapedText->IsRightToLeft(); 341 int32_t glyphStart = 342 0; // looking for a clump that starts at this glyph index 343 int32_t charStart = 344 isRightToLeft 345 ? stringRange.length - 1 346 : 0; // and this char index (in the stringRange of the glyph run) 347 348 while (glyphStart < 349 numGlyphs) { // keep finding groups until all glyphs are accounted for 350 bool inOrder = true; 351 int32_t charEnd = glyphToChar[glyphStart] - stringRange.location; 352 NS_WARNING_ASSERTION(charEnd >= 0 && charEnd < stringRange.length, 353 "glyph-to-char mapping points outside string range"); 354 // clamp charEnd to the valid range of the string 355 charEnd = std::max(charEnd, 0); 356 charEnd = std::min(charEnd, int32_t(stringRange.length)); 357 358 int32_t glyphEnd = glyphStart; 359 int32_t charLimit = isRightToLeft ? -1 : stringRange.length; 360 do { 361 // This is normally executed once for each iteration of the outer loop, 362 // but in unusual cases where the character/glyph association is complex, 363 // the initial character range might correspond to a non-contiguous 364 // glyph range with "holes" in it. If so, we will repeat this loop to 365 // extend the character range until we have a contiguous glyph sequence. 366 NS_ASSERTION((direction > 0 && charEnd < charLimit) || 367 (direction < 0 && charEnd > charLimit), 368 "no characters left in range?"); 369 charEnd += direction; 370 while (charEnd != charLimit && charToGlyph[charEnd] == NO_GLYPH) { 371 charEnd += direction; 372 } 373 374 // find the maximum glyph index covered by the clump so far 375 if (isRightToLeft) { 376 for (int32_t i = charStart; i > charEnd; --i) { 377 if (charToGlyph[i] != NO_GLYPH) { 378 // update extent of glyph range 379 glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1); 380 } 381 } 382 } else { 383 for (int32_t i = charStart; i < charEnd; ++i) { 384 if (charToGlyph[i] != NO_GLYPH) { 385 // update extent of glyph range 386 glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1); 387 } 388 } 389 } 390 391 if (glyphEnd == glyphStart + 1) { 392 // for the common case of a single-glyph clump, we can skip the 393 // following checks 394 break; 395 } 396 397 if (glyphEnd == glyphStart) { 398 // no glyphs, try to extend the clump 399 continue; 400 } 401 402 // check whether all glyphs in the range are associated with the 403 // characters in our clump; if not, we have a discontinuous range, and 404 // should extend it unless we've reached the end of the text 405 bool allGlyphsAreWithinCluster = true; 406 int32_t prevGlyphCharIndex = charStart; 407 for (int32_t i = glyphStart; i < glyphEnd; ++i) { 408 int32_t glyphCharIndex = glyphToChar[i] - stringRange.location; 409 if (isRightToLeft) { 410 if (glyphCharIndex > charStart || glyphCharIndex <= charEnd) { 411 allGlyphsAreWithinCluster = false; 412 break; 413 } 414 if (glyphCharIndex > prevGlyphCharIndex) { 415 inOrder = false; 416 } 417 prevGlyphCharIndex = glyphCharIndex; 418 } else { 419 if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) { 420 allGlyphsAreWithinCluster = false; 421 break; 422 } 423 if (glyphCharIndex < prevGlyphCharIndex) { 424 inOrder = false; 425 } 426 prevGlyphCharIndex = glyphCharIndex; 427 } 428 } 429 if (allGlyphsAreWithinCluster) { 430 break; 431 } 432 } while (charEnd != charLimit); 433 434 NS_WARNING_ASSERTION(glyphStart < glyphEnd, 435 "character/glyph clump contains no glyphs!"); 436 if (glyphStart == glyphEnd) { 437 ++glyphStart; // make progress - avoid potential infinite loop 438 charStart = charEnd; 439 continue; 440 } 441 442 NS_WARNING_ASSERTION(charStart != charEnd, 443 "character/glyph clump contains no characters!"); 444 if (charStart == charEnd) { 445 glyphStart = glyphEnd; // this is bad - we'll discard the glyph(s), 446 // as there's nowhere to attach them 447 continue; 448 } 449 450 // Now charStart..charEnd is a ligature clump, corresponding to 451 // glyphStart..glyphEnd; Set baseCharIndex to the char we'll actually attach 452 // the glyphs to (1st of ligature), and endCharIndex to the limit (position 453 // beyond the last char), adjusting for the offset of the stringRange 454 // relative to the textRun. 455 int32_t baseCharIndex, endCharIndex; 456 if (isRightToLeft) { 457 while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) { 458 charEnd--; 459 } 460 baseCharIndex = charEnd + stringRange.location + 1; 461 endCharIndex = charStart + stringRange.location + 1; 462 } else { 463 while (charEnd < stringRange.length && charToGlyph[charEnd] == NO_GLYPH) { 464 charEnd++; 465 } 466 baseCharIndex = charStart + stringRange.location; 467 endCharIndex = charEnd + stringRange.location; 468 } 469 470 // Then we check if the clump falls outside our actual string range; if so, 471 // just go to the next. 472 if (endCharIndex <= 0 || baseCharIndex >= wordLength) { 473 glyphStart = glyphEnd; 474 charStart = charEnd; 475 continue; 476 } 477 // Ensure we won't try to go beyond the valid length of the word's text 478 baseCharIndex = std::max(baseCharIndex, 0); 479 endCharIndex = std::min(endCharIndex, wordLength); 480 481 // Now we're ready to set the glyph info in the textRun; measure the glyph 482 // width of the first (perhaps only) glyph, to see if it is "Simple" 483 int32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit(); 484 double toNextGlyph; 485 if (glyphStart < numGlyphs - 1) { 486 toNextGlyph = positions[glyphStart + 1].x - positions[glyphStart].x; 487 } else { 488 toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x; 489 } 490 int32_t advance = int32_t(toNextGlyph * appUnitsPerDevUnit); 491 492 // Check if it's a simple one-to-one mapping 493 int32_t glyphsInClump = glyphEnd - glyphStart; 494 if (glyphsInClump == 1 && 495 gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyphs[glyphStart]) && 496 gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) && 497 charGlyphs[baseCharIndex].IsClusterStart() && 498 positions[glyphStart].y == 0.0) { 499 charGlyphs[baseCharIndex].SetSimpleGlyph(advance, glyphs[glyphStart]); 500 } else { 501 // collect all glyphs in a list to be assigned to the first char; 502 // there must be at least one in the clump, and we already measured its 503 // advance, hence the placement of the loop-exit test and the measurement 504 // of the next glyph 505 while (true) { 506 gfxTextRun::DetailedGlyph* details = detailedGlyphs.AppendElement(); 507 details->mGlyphID = glyphs[glyphStart]; 508 details->mOffset.y = -positions[glyphStart].y * appUnitsPerDevUnit; 509 details->mAdvance = advance; 510 if (++glyphStart >= glyphEnd) { 511 break; 512 } 513 if (glyphStart < numGlyphs - 1) { 514 toNextGlyph = positions[glyphStart + 1].x - positions[glyphStart].x; 515 } else { 516 toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x; 517 } 518 advance = int32_t(toNextGlyph * appUnitsPerDevUnit); 519 } 520 521 aShapedText->SetDetailedGlyphs(aOffset + baseCharIndex, 522 detailedGlyphs.Length(), 523 detailedGlyphs.Elements()); 524 525 detailedGlyphs.Clear(); 526 } 527 528 // the rest of the chars in the group are ligature continuations, no 529 // associated glyphs 530 while (++baseCharIndex != endCharIndex && baseCharIndex < wordLength) { 531 CompressedGlyph& shapedTextGlyph = charGlyphs[baseCharIndex]; 532 NS_ASSERTION(!shapedTextGlyph.IsSimpleGlyph(), 533 "overwriting a simple glyph"); 534 shapedTextGlyph.SetComplex(inOrder && shapedTextGlyph.IsClusterStart(), 535 false); 536 } 537 538 glyphStart = glyphEnd; 539 charStart = charEnd; 540 } 541 542 return NS_OK; 543 } 544 545 #undef SMALL_GLYPH_RUN 546 547 // Construct the font attribute descriptor that we'll apply by default when 548 // creating a CTFontRef. This will turn off line-edge swashes by default, 549 // because we don't know the actual line breaks when doing glyph shaping. 550 551 // We also cache feature descriptors for shaping with disabled ligatures, and 552 // for buggy Indic AAT font workarounds, created on an as-needed basis. 553 554 #define MAX_FEATURES 5 // max used by any of our Get*Descriptor functions 555 556 CTFontDescriptorRef gfxCoreTextShaper::CreateFontFeaturesDescriptor( 557 const std::pair<SInt16, SInt16>* aFeatures, size_t aCount) { 558 MOZ_ASSERT(aCount <= MAX_FEATURES); 559 560 CFDictionaryRef featureSettings[MAX_FEATURES]; 561 562 for (size_t i = 0; i < aCount; i++) { 563 CFNumberRef type = ::CFNumberCreate( 564 kCFAllocatorDefault, kCFNumberSInt16Type, &aFeatures[i].first); 565 CFNumberRef selector = ::CFNumberCreate( 566 kCFAllocatorDefault, kCFNumberSInt16Type, &aFeatures[i].second); 567 568 CFTypeRef keys[] = {kCTFontFeatureTypeIdentifierKey, 569 kCTFontFeatureSelectorIdentifierKey}; 570 CFTypeRef values[] = {type, selector}; 571 featureSettings[i] = ::CFDictionaryCreate( 572 kCFAllocatorDefault, (const void**)keys, (const void**)values, 573 std::size(keys), &kCFTypeDictionaryKeyCallBacks, 574 &kCFTypeDictionaryValueCallBacks); 575 576 ::CFRelease(selector); 577 ::CFRelease(type); 578 } 579 580 CFArrayRef featuresArray = 581 ::CFArrayCreate(kCFAllocatorDefault, (const void**)featureSettings, 582 aCount, // not std::size(featureSettings), as we 583 // may not have used all the allocated slots 584 &kCFTypeArrayCallBacks); 585 586 for (size_t i = 0; i < aCount; i++) { 587 ::CFRelease(featureSettings[i]); 588 } 589 590 const CFTypeRef attrKeys[] = {kCTFontFeatureSettingsAttribute}; 591 const CFTypeRef attrValues[] = {featuresArray}; 592 CFDictionaryRef attributesDict = ::CFDictionaryCreate( 593 kCFAllocatorDefault, (const void**)attrKeys, (const void**)attrValues, 594 std::size(attrKeys), &kCFTypeDictionaryKeyCallBacks, 595 &kCFTypeDictionaryValueCallBacks); 596 ::CFRelease(featuresArray); 597 598 CTFontDescriptorRef descriptor = 599 ::CTFontDescriptorCreateWithAttributes(attributesDict); 600 ::CFRelease(attributesDict); 601 602 return descriptor; 603 } 604 605 CTFontDescriptorRef gfxCoreTextShaper::GetFeaturesDescriptor( 606 FeatureFlags aFeatureFlags) { 607 MOZ_ASSERT(aFeatureFlags < kMaxFontInstances); 608 if (!sFeaturesDescriptor[aFeatureFlags]) { 609 typedef std::pair<SInt16, SInt16> FeatT; 610 AutoTArray<FeatT, MAX_FEATURES> features; 611 features.AppendElement( 612 FeatT(kSmartSwashType, kLineFinalSwashesOffSelector)); 613 if ((aFeatureFlags & kIndicFeatures) == 0) { 614 features.AppendElement( 615 FeatT(kSmartSwashType, kLineInitialSwashesOffSelector)); 616 } 617 if (aFeatureFlags & kAddSmallCaps) { 618 features.AppendElement(FeatT(kLetterCaseType, kSmallCapsSelector)); 619 features.AppendElement( 620 FeatT(kLowerCaseType, kLowerCaseSmallCapsSelector)); 621 } 622 if (aFeatureFlags & kDisableLigatures) { 623 features.AppendElement( 624 FeatT(kLigaturesType, kCommonLigaturesOffSelector)); 625 } 626 MOZ_ASSERT(features.Length() <= MAX_FEATURES); 627 sFeaturesDescriptor[aFeatureFlags] = 628 CreateFontFeaturesDescriptor(features.Elements(), features.Length()); 629 } 630 return sFeaturesDescriptor[aFeatureFlags]; 631 } 632 633 CTFontRef gfxCoreTextShaper::CreateCTFontWithFeatures( 634 CGFloat aSize, CTFontDescriptorRef aDescriptor) { 635 const gfxFontEntry* fe = mFont->GetFontEntry(); 636 bool isInstalledFont = !fe->IsUserFont() || fe->IsLocalUserFont(); 637 CGFontRef cgFont = static_cast<gfxMacFont*>(mFont)->GetCGFontRef(); 638 return CreateCTFontFromCGFontWithVariations(cgFont, aSize, isInstalledFont, 639 aDescriptor); 640 } 641 642 void gfxCoreTextShaper::Shutdown() // [static] 643 { 644 for (size_t i = 0; i < kMaxFontInstances; i++) { 645 if (sFeaturesDescriptor[i] != nullptr) { 646 ::CFRelease(sFeaturesDescriptor[i]); 647 sFeaturesDescriptor[i] = nullptr; 648 } 649 } 650 }