gfxFont.cpp (186474B)
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 "gfxFont.h" 7 8 #include "mozilla/DebugOnly.h" 9 #include "mozilla/FontPropertyTypes.h" 10 #include "mozilla/gfx/2D.h" 11 #include "mozilla/intl/Segmenter.h" 12 #include "mozilla/StaticPrefs_gfx.h" 13 #include "mozilla/ScopeExit.h" 14 #include "mozilla/SVGContextPaint.h" 15 16 #include "mozilla/Logging.h" 17 18 #include "nsITimer.h" 19 20 #include "gfxGlyphExtents.h" 21 #include "gfxPlatform.h" 22 #include "gfxTextRun.h" 23 #include "nsGkAtoms.h" 24 25 #include "gfxTypes.h" 26 #include "gfxContext.h" 27 #include "gfxFontMissingGlyphs.h" 28 #include "gfxGraphiteShaper.h" 29 #include "gfxHarfBuzzShaper.h" 30 #include "gfxUserFontSet.h" 31 #include "nsCRT.h" 32 #include "nsContentUtils.h" 33 #include "nsSpecialCasingData.h" 34 #include "nsTextRunTransformations.h" 35 #include "nsUGenCategory.h" 36 #include "nsUnicodeProperties.h" 37 #include "nsStyleConsts.h" 38 #include "mozilla/AppUnits.h" 39 #include "mozilla/HashTable.h" 40 #include "mozilla/Likely.h" 41 #include "mozilla/MemoryReporting.h" 42 #include "mozilla/Preferences.h" 43 #include "mozilla/Services.h" 44 #include "mozilla/glean/GfxMetrics.h" 45 #include "gfxMathTable.h" 46 #include "gfxSVGGlyphs.h" 47 #include "gfx2DGlue.h" 48 #include "TextDrawTarget.h" 49 50 #include "ThebesRLBox.h" 51 52 #include "GreekCasing.h" 53 54 #include "cairo.h" 55 #ifdef XP_WIN 56 # include "cairo-win32.h" 57 # include "gfxWindowsPlatform.h" 58 #endif 59 60 #include "harfbuzz/hb.h" 61 #include "harfbuzz/hb-ot.h" 62 63 #include <algorithm> 64 #include <limits> 65 #include <cmath> 66 67 using namespace mozilla; 68 using namespace mozilla::gfx; 69 using namespace mozilla::unicode; 70 using mozilla::services::GetObserverService; 71 72 gfxFontCache* gfxFontCache::gGlobalCache = nullptr; 73 74 #ifdef DEBUG_roc 75 # define DEBUG_TEXT_RUN_STORAGE_METRICS 76 #endif 77 78 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS 79 uint32_t gTextRunStorageHighWaterMark = 0; 80 uint32_t gTextRunStorage = 0; 81 uint32_t gFontCount = 0; 82 uint32_t gGlyphExtentsCount = 0; 83 uint32_t gGlyphExtentsWidthsTotalSize = 0; 84 uint32_t gGlyphExtentsSetupEagerSimple = 0; 85 uint32_t gGlyphExtentsSetupEagerTight = 0; 86 uint32_t gGlyphExtentsSetupLazyTight = 0; 87 uint32_t gGlyphExtentsSetupFallBackToTight = 0; 88 #endif 89 90 #define LOG_FONTINIT(args) \ 91 MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args) 92 #define LOG_FONTINIT_ENABLED() \ 93 MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug) 94 95 /* 96 * gfxFontCache - global cache of gfxFont instances. 97 * Expires unused fonts after a short interval; 98 * notifies fonts to age their cached shaped-word records; 99 * observes memory-pressure notification and tells fonts to clear their 100 * shaped-word caches to free up memory. 101 */ 102 103 MOZ_DEFINE_MALLOC_SIZE_OF(FontCacheMallocSizeOf) 104 105 NS_IMPL_ISUPPORTS(gfxFontCache::MemoryReporter, nsIMemoryReporter) 106 107 /*virtual*/ 108 gfxTextRunFactory::~gfxTextRunFactory() { 109 // Should not be dropped by stylo 110 MOZ_ASSERT(!Servo_IsWorkerThread()); 111 } 112 113 NS_IMETHODIMP 114 gfxFontCache::MemoryReporter::CollectReports( 115 nsIHandleReportCallback* aHandleReport, nsISupports* aData, 116 bool aAnonymize) { 117 FontCacheSizes sizes; 118 119 gfxFontCache::GetCache()->AddSizeOfIncludingThis(&FontCacheMallocSizeOf, 120 &sizes); 121 122 MOZ_COLLECT_REPORT("explicit/gfx/font-cache", KIND_HEAP, UNITS_BYTES, 123 sizes.mFontInstances, 124 "Memory used for active font instances."); 125 126 MOZ_COLLECT_REPORT("explicit/gfx/font-shaped-words", KIND_HEAP, UNITS_BYTES, 127 sizes.mShapedWords, 128 "Memory used to cache shaped glyph data."); 129 130 return NS_OK; 131 } 132 133 NS_IMPL_ISUPPORTS(gfxFontCache::Observer, nsIObserver) 134 135 NS_IMETHODIMP 136 gfxFontCache::Observer::Observe(nsISupports* aSubject, const char* aTopic, 137 const char16_t* someData) { 138 if (!nsCRT::strcmp(aTopic, "memory-pressure")) { 139 gfxFontCache* fontCache = gfxFontCache::GetCache(); 140 if (fontCache) { 141 fontCache->FlushShapedWordCaches(); 142 } 143 } else { 144 MOZ_ASSERT_UNREACHABLE("unexpected notification topic"); 145 } 146 return NS_OK; 147 } 148 149 nsresult gfxFontCache::Init() { 150 NS_ASSERTION(!gGlobalCache, "Where did this come from?"); 151 gGlobalCache = new gfxFontCache(GetMainThreadSerialEventTarget()); 152 if (!gGlobalCache) { 153 return NS_ERROR_OUT_OF_MEMORY; 154 } 155 RegisterStrongMemoryReporter(new MemoryReporter()); 156 return NS_OK; 157 } 158 159 void gfxFontCache::Shutdown() { 160 delete gGlobalCache; 161 gGlobalCache = nullptr; 162 163 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS 164 printf("Textrun storage high water mark=%d\n", gTextRunStorageHighWaterMark); 165 printf("Total number of fonts=%d\n", gFontCount); 166 printf("Total glyph extents allocated=%d (size %d)\n", gGlyphExtentsCount, 167 int(gGlyphExtentsCount * sizeof(gfxGlyphExtents))); 168 printf("Total glyph extents width-storage size allocated=%d\n", 169 gGlyphExtentsWidthsTotalSize); 170 printf("Number of simple glyph extents eagerly requested=%d\n", 171 gGlyphExtentsSetupEagerSimple); 172 printf("Number of tight glyph extents eagerly requested=%d\n", 173 gGlyphExtentsSetupEagerTight); 174 printf("Number of tight glyph extents lazily requested=%d\n", 175 gGlyphExtentsSetupLazyTight); 176 printf("Number of simple glyph extent setups that fell back to tight=%d\n", 177 gGlyphExtentsSetupFallBackToTight); 178 #endif 179 } 180 181 gfxFontCache::gfxFontCache(nsIEventTarget* aEventTarget) 182 : ExpirationTrackerImpl<gfxFont, 3, Lock, AutoLock>( 183 FONT_TIMEOUT_SECONDS * 1000, "gfxFontCache"_ns, aEventTarget) { 184 nsCOMPtr<nsIObserverService> obs = GetObserverService(); 185 if (obs) { 186 obs->AddObserver(new Observer, "memory-pressure", false); 187 } 188 189 nsIEventTarget* target = nullptr; 190 if (XRE_IsContentProcess() && NS_IsMainThread()) { 191 target = aEventTarget; 192 } 193 194 // Create the timer used to expire shaped-word records from each font's 195 // cache after a short period of non-use. We have a single timer in 196 // gfxFontCache that loops over all fonts known to the cache, to avoid 197 // the overhead of individual timers in each font instance. 198 // The timer will be started any time shaped word records are cached 199 // (and pauses itself when all caches become empty). 200 mWordCacheExpirationTimer = NS_NewTimer(target); 201 } 202 203 gfxFontCache::~gfxFontCache() { 204 // Ensure the user font cache releases its references to font entries, 205 // so they aren't kept alive after the font instances and font-list 206 // have been shut down. 207 gfxUserFontSet::UserFontCache::Shutdown(); 208 209 if (mWordCacheExpirationTimer) { 210 mWordCacheExpirationTimer->Cancel(); 211 mWordCacheExpirationTimer = nullptr; 212 } 213 214 // Expire everything manually so we don't leak them. 215 Flush(); 216 } 217 218 bool gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey) const { 219 const gfxCharacterMap* fontUnicodeRangeMap = mFont->GetUnicodeRangeMap(); 220 return aKey->mFontEntry == mFont->GetFontEntry() && 221 aKey->mStyle->Equals(*mFont->GetStyle()) && 222 ((!aKey->mUnicodeRangeMap && !fontUnicodeRangeMap) || 223 (aKey->mUnicodeRangeMap && fontUnicodeRangeMap && 224 aKey->mUnicodeRangeMap->Equals(fontUnicodeRangeMap))); 225 } 226 227 already_AddRefed<gfxFont> gfxFontCache::Lookup( 228 const gfxFontEntry* aFontEntry, const gfxFontStyle* aStyle, 229 const gfxCharacterMap* aUnicodeRangeMap) { 230 MutexAutoLock lock(mMutex); 231 232 Key key(aFontEntry, aStyle, aUnicodeRangeMap); 233 HashEntry* entry = mFonts.GetEntry(key); 234 235 glean::fontlist::font_cache_hit 236 .EnumGet( 237 static_cast<glean::fontlist::FontCacheHitLabel>(entry != nullptr)) 238 .Add(); 239 240 if (!entry) { 241 return nullptr; 242 } 243 244 RefPtr<gfxFont> font = entry->mFont; 245 if (font->GetExpirationState()->IsTracked()) { 246 RemoveObjectLocked(font, lock); 247 } 248 return font.forget(); 249 } 250 251 already_AddRefed<gfxFont> gfxFontCache::MaybeInsert(gfxFont* aFont) { 252 MOZ_ASSERT(aFont); 253 MutexAutoLock lock(mMutex); 254 255 Key key(aFont->GetFontEntry(), aFont->GetStyle(), 256 aFont->GetUnicodeRangeMap()); 257 HashEntry* entry = mFonts.PutEntry(key); 258 if (!entry) { 259 return do_AddRef(aFont); 260 } 261 262 // If it is null, then we are inserting a new entry. Otherwise we are 263 // attempting to replace an existing font, probably due to a thread race, in 264 // which case stick with the original font. 265 if (!entry->mFont) { 266 entry->mFont = aFont; 267 // Assert that we can find the entry we just put in (this fails if the key 268 // has a NaN float value in it, e.g. 'sizeAdjust'). 269 MOZ_ASSERT(entry == mFonts.GetEntry(key)); 270 } else { 271 MOZ_ASSERT(entry->mFont != aFont); 272 aFont->Destroy(); 273 if (entry->mFont->GetExpirationState()->IsTracked()) { 274 RemoveObjectLocked(entry->mFont, lock); 275 } 276 } 277 278 return do_AddRef(entry->mFont); 279 } 280 281 bool gfxFontCache::MaybeDestroy(gfxFont* aFont) { 282 MOZ_ASSERT(aFont); 283 MutexAutoLock lock(mMutex); 284 285 // If the font has a non-zero refcount, then we must have lost the race with 286 // gfxFontCache::Lookup and the same font was reacquired. 287 if (aFont->GetRefCount() > 0) { 288 return false; 289 } 290 291 Key key(aFont->GetFontEntry(), aFont->GetStyle(), 292 aFont->GetUnicodeRangeMap()); 293 HashEntry* entry = mFonts.GetEntry(key); 294 if (!entry || entry->mFont != aFont) { 295 MOZ_ASSERT(!aFont->GetExpirationState()->IsTracked()); 296 return true; 297 } 298 299 // If the font is being tracked, we must have then also lost another race with 300 // gfxFontCache::MaybeDestroy which re-added it to the tracker. 301 if (aFont->GetExpirationState()->IsTracked()) { 302 return false; 303 } 304 305 // Typically this won't fail, but it may during startup/shutdown if the timer 306 // service is not available. 307 nsresult rv = AddObjectLocked(aFont, lock); 308 if (NS_SUCCEEDED(rv)) { 309 return false; 310 } 311 312 mFonts.RemoveEntry(entry); 313 return true; 314 } 315 316 void gfxFontCache::NotifyExpiredLocked(gfxFont* aFont, const AutoLock& aLock) { 317 MOZ_ASSERT(aFont->GetRefCount() == 0); 318 319 RemoveObjectLocked(aFont, aLock); 320 mTrackerDiscard.AppendElement(aFont); 321 322 Key key(aFont->GetFontEntry(), aFont->GetStyle(), 323 aFont->GetUnicodeRangeMap()); 324 HashEntry* entry = mFonts.GetEntry(key); 325 if (!entry || entry->mFont != aFont) { 326 MOZ_ASSERT_UNREACHABLE("Invalid font?"); 327 return; 328 } 329 330 mFonts.RemoveEntry(entry); 331 } 332 333 void gfxFontCache::NotifyHandlerEnd() { 334 nsTArray<gfxFont*> discard; 335 { 336 MutexAutoLock lock(mMutex); 337 discard = std::move(mTrackerDiscard); 338 } 339 DestroyDiscard(discard); 340 } 341 342 void gfxFontCache::DestroyDiscard(nsTArray<gfxFont*>& aDiscard) { 343 for (auto& font : aDiscard) { 344 NS_ASSERTION(font->GetRefCount() == 0, 345 "Destroying with refs outside cache!"); 346 font->ClearCachedWords(); 347 font->Destroy(); 348 } 349 aDiscard.Clear(); 350 } 351 352 void gfxFontCache::Flush() { 353 nsTArray<gfxFont*> discard; 354 { 355 MutexAutoLock lock(mMutex); 356 discard.SetCapacity(mFonts.Count()); 357 for (auto iter = mFonts.Iter(); !iter.Done(); iter.Next()) { 358 HashEntry* entry = static_cast<HashEntry*>(iter.Get()); 359 if (!entry || !entry->mFont) { 360 MOZ_ASSERT_UNREACHABLE("Invalid font?"); 361 continue; 362 } 363 364 if (entry->mFont->GetRefCount() == 0) { 365 // If we are not tracked, then we must have won the race with 366 // gfxFont::MaybeDestroy and it is waiting on the mutex. To avoid a 367 // double free, we let gfxFont::MaybeDestroy handle the freeing when it 368 // acquires the mutex and discovers there is no matching entry in the 369 // hashtable. 370 if (entry->mFont->GetExpirationState()->IsTracked()) { 371 RemoveObjectLocked(entry->mFont, lock); 372 discard.AppendElement(entry->mFont); 373 } 374 } else { 375 MOZ_ASSERT(!entry->mFont->GetExpirationState()->IsTracked()); 376 } 377 } 378 MOZ_ASSERT(IsEmptyLocked(lock), 379 "Cache tracker still has fonts after flush!"); 380 mFonts.Clear(); 381 } 382 DestroyDiscard(discard); 383 } 384 385 /*static*/ 386 void gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer, 387 void* aCache) { 388 gfxFontCache* cache = static_cast<gfxFontCache*>(aCache); 389 cache->AgeCachedWords(); 390 } 391 392 void gfxFontCache::AgeCachedWords() { 393 bool allEmpty = true; 394 { 395 MutexAutoLock lock(mMutex); 396 for (const auto& entry : mFonts) { 397 allEmpty = entry.mFont->AgeCachedWords() && allEmpty; 398 } 399 } 400 if (allEmpty) { 401 PauseWordCacheExpirationTimer(); 402 } 403 } 404 405 void gfxFontCache::FlushShapedWordCaches() { 406 { 407 MutexAutoLock lock(mMutex); 408 for (const auto& entry : mFonts) { 409 entry.mFont->ClearCachedWords(); 410 } 411 } 412 PauseWordCacheExpirationTimer(); 413 } 414 415 void gfxFontCache::NotifyGlyphsChanged() { 416 MutexAutoLock lock(mMutex); 417 for (const auto& entry : mFonts) { 418 entry.mFont->NotifyGlyphsChanged(); 419 } 420 } 421 422 void gfxFontCache::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, 423 FontCacheSizes* aSizes) const { 424 // TODO: add the overhead of the expiration tracker (generation arrays) 425 426 MutexAutoLock lock(*const_cast<Mutex*>(&mMutex)); 427 aSizes->mFontInstances += mFonts.ShallowSizeOfExcludingThis(aMallocSizeOf); 428 for (const auto& entry : mFonts) { 429 entry.mFont->AddSizeOfExcludingThis(aMallocSizeOf, aSizes); 430 } 431 } 432 433 void gfxFontCache::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, 434 FontCacheSizes* aSizes) const { 435 aSizes->mFontInstances += aMallocSizeOf(this); 436 AddSizeOfExcludingThis(aMallocSizeOf, aSizes); 437 } 438 439 #define MAX_SSXX_VALUE 99 440 #define MAX_CVXX_VALUE 99 441 442 static void LookupAlternateValues(const gfxFontFeatureValueSet& aFeatureLookup, 443 const nsACString& aFamily, 444 const StyleVariantAlternates& aAlternates, 445 nsTArray<gfxFontFeature>& aFontFeatures) { 446 using Tag = StyleVariantAlternates::Tag; 447 448 // historical-forms gets handled in nsFont::AddFontFeaturesToStyle. 449 if (aAlternates.IsHistoricalForms()) { 450 return; 451 } 452 453 gfxFontFeature feature; 454 if (aAlternates.IsCharacterVariant()) { 455 for (auto& ident : aAlternates.AsCharacterVariant().AsSpan()) { 456 Span<const uint32_t> values = aFeatureLookup.GetFontFeatureValuesFor( 457 aFamily, NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT, 458 ident.AsAtom()); 459 // nothing defined, skip 460 if (values.IsEmpty()) { 461 continue; 462 } 463 NS_ASSERTION(values.Length() <= 2, 464 "too many values allowed for character-variant"); 465 // character-variant(12 3) ==> 'cv12' = 3 466 uint32_t nn = values[0]; 467 // ignore values greater than 99 468 if (nn == 0 || nn > MAX_CVXX_VALUE) { 469 continue; 470 } 471 feature.mValue = values.Length() > 1 ? values[1] : 1; 472 feature.mTag = HB_TAG('c', 'v', ('0' + nn / 10), ('0' + nn % 10)); 473 aFontFeatures.AppendElement(feature); 474 } 475 return; 476 } 477 478 if (aAlternates.IsStyleset()) { 479 for (auto& ident : aAlternates.AsStyleset().AsSpan()) { 480 Span<const uint32_t> values = aFeatureLookup.GetFontFeatureValuesFor( 481 aFamily, NS_FONT_VARIANT_ALTERNATES_STYLESET, ident.AsAtom()); 482 483 // styleset(1 2 7) ==> 'ss01' = 1, 'ss02' = 1, 'ss07' = 1 484 feature.mValue = 1; 485 for (uint32_t nn : values) { 486 if (nn == 0 || nn > MAX_SSXX_VALUE) { 487 continue; 488 } 489 feature.mTag = HB_TAG('s', 's', ('0' + nn / 10), ('0' + nn % 10)); 490 aFontFeatures.AppendElement(feature); 491 } 492 } 493 return; 494 } 495 496 uint32_t constant = 0; 497 nsAtom* name = nullptr; 498 switch (aAlternates.tag) { 499 case Tag::Swash: 500 constant = NS_FONT_VARIANT_ALTERNATES_SWASH; 501 name = aAlternates.AsSwash().AsAtom(); 502 break; 503 case Tag::Stylistic: 504 constant = NS_FONT_VARIANT_ALTERNATES_STYLISTIC; 505 name = aAlternates.AsStylistic().AsAtom(); 506 break; 507 case Tag::Ornaments: 508 constant = NS_FONT_VARIANT_ALTERNATES_ORNAMENTS; 509 name = aAlternates.AsOrnaments().AsAtom(); 510 break; 511 case Tag::Annotation: 512 constant = NS_FONT_VARIANT_ALTERNATES_ANNOTATION; 513 name = aAlternates.AsAnnotation().AsAtom(); 514 break; 515 default: 516 MOZ_ASSERT_UNREACHABLE("Unknown font-variant-alternates value!"); 517 return; 518 } 519 520 Span<const uint32_t> values = 521 aFeatureLookup.GetFontFeatureValuesFor(aFamily, constant, name); 522 if (values.IsEmpty()) { 523 return; 524 } 525 MOZ_ASSERT(values.Length() == 1, 526 "too many values for font-specific font-variant-alternates"); 527 528 feature.mValue = values[0]; 529 switch (aAlternates.tag) { 530 case Tag::Swash: // swsh, cswh 531 feature.mTag = HB_TAG('s', 'w', 's', 'h'); 532 aFontFeatures.AppendElement(feature); 533 feature.mTag = HB_TAG('c', 's', 'w', 'h'); 534 break; 535 case Tag::Stylistic: // salt 536 feature.mTag = HB_TAG('s', 'a', 'l', 't'); 537 break; 538 case Tag::Ornaments: // ornm 539 feature.mTag = HB_TAG('o', 'r', 'n', 'm'); 540 break; 541 case Tag::Annotation: // nalt 542 feature.mTag = HB_TAG('n', 'a', 'l', 't'); 543 break; 544 default: 545 MOZ_ASSERT_UNREACHABLE("how?"); 546 return; 547 } 548 aFontFeatures.AppendElement(feature); 549 } 550 551 /* static */ 552 void gfxFontShaper::MergeFontFeatures( 553 const gfxFontStyle* aStyle, const nsTArray<gfxFontFeature>& aFontFeatures, 554 bool aDisableLigatures, const nsACString& aFamilyName, bool aAddSmallCaps, 555 void (*aHandleFeature)(uint32_t, uint32_t, void*), 556 void* aHandleFeatureData) { 557 const nsTArray<gfxFontFeature>& styleRuleFeatures = aStyle->featureSettings; 558 559 // Bail immediately if nothing to do, which is the common case. 560 if (styleRuleFeatures.IsEmpty() && aFontFeatures.IsEmpty() && 561 !aDisableLigatures && 562 aStyle->variantCaps == NS_FONT_VARIANT_CAPS_NORMAL && 563 aStyle->variantSubSuper == NS_FONT_VARIANT_POSITION_NORMAL && 564 aStyle->variantAlternates.IsEmpty()) { 565 return; 566 } 567 568 AutoTArray<gfxFontFeature, 32> mergedFeatures; 569 570 struct FeatureTagCmp { 571 bool Equals(const gfxFontFeature& a, const gfxFontFeature& b) const { 572 return a.mTag == b.mTag; 573 } 574 bool LessThan(const gfxFontFeature& a, const gfxFontFeature& b) const { 575 return a.mTag < b.mTag; 576 } 577 } cmp; 578 579 auto addOrReplace = [&](const gfxFontFeature& aFeature) { 580 auto index = mergedFeatures.BinaryIndexOf(aFeature, cmp); 581 if (index == nsTArray<gfxFontFeature>::NoIndex) { 582 mergedFeatures.InsertElementSorted(aFeature, cmp); 583 } else { 584 mergedFeatures[index].mValue = aFeature.mValue; 585 } 586 }; 587 588 // add feature values from font 589 for (const gfxFontFeature& feature : aFontFeatures) { 590 addOrReplace(feature); 591 } 592 593 // font-variant-caps - handled here due to the need for fallback handling 594 // petite caps cases can fallback to appropriate smallcaps 595 uint32_t variantCaps = aStyle->variantCaps; 596 switch (variantCaps) { 597 case NS_FONT_VARIANT_CAPS_NORMAL: 598 break; 599 600 case NS_FONT_VARIANT_CAPS_ALLSMALL: 601 addOrReplace(gfxFontFeature{HB_TAG('c', '2', 's', 'c'), 1}); 602 // fall through to the small-caps case 603 [[fallthrough]]; 604 605 case NS_FONT_VARIANT_CAPS_SMALLCAPS: 606 addOrReplace(gfxFontFeature{HB_TAG('s', 'm', 'c', 'p'), 1}); 607 break; 608 609 case NS_FONT_VARIANT_CAPS_ALLPETITE: 610 addOrReplace(gfxFontFeature{aAddSmallCaps ? HB_TAG('c', '2', 's', 'c') 611 : HB_TAG('c', '2', 'p', 'c'), 612 1}); 613 // fall through to the petite-caps case 614 [[fallthrough]]; 615 616 case NS_FONT_VARIANT_CAPS_PETITECAPS: 617 addOrReplace(gfxFontFeature{aAddSmallCaps ? HB_TAG('s', 'm', 'c', 'p') 618 : HB_TAG('p', 'c', 'a', 'p'), 619 1}); 620 break; 621 622 case NS_FONT_VARIANT_CAPS_TITLING: 623 addOrReplace(gfxFontFeature{HB_TAG('t', 'i', 't', 'l'), 1}); 624 break; 625 626 case NS_FONT_VARIANT_CAPS_UNICASE: 627 addOrReplace(gfxFontFeature{HB_TAG('u', 'n', 'i', 'c'), 1}); 628 break; 629 630 default: 631 MOZ_ASSERT_UNREACHABLE("Unexpected variantCaps"); 632 break; 633 } 634 635 // font-variant-position - handled here due to the need for fallback 636 switch (aStyle->variantSubSuper) { 637 case NS_FONT_VARIANT_POSITION_NORMAL: 638 break; 639 case NS_FONT_VARIANT_POSITION_SUPER: 640 addOrReplace(gfxFontFeature{HB_TAG('s', 'u', 'p', 's'), 1}); 641 break; 642 case NS_FONT_VARIANT_POSITION_SUB: 643 addOrReplace(gfxFontFeature{HB_TAG('s', 'u', 'b', 's'), 1}); 644 break; 645 default: 646 MOZ_ASSERT_UNREACHABLE("Unexpected variantSubSuper"); 647 break; 648 } 649 650 // add font-specific feature values from style rules 651 if (aStyle->featureValueLookup && !aStyle->variantAlternates.IsEmpty()) { 652 AutoTArray<gfxFontFeature, 4> featureList; 653 654 // insert list of alternate feature settings 655 for (auto& alternate : aStyle->variantAlternates.AsSpan()) { 656 LookupAlternateValues(*aStyle->featureValueLookup, aFamilyName, alternate, 657 featureList); 658 } 659 660 for (const gfxFontFeature& feature : featureList) { 661 addOrReplace(gfxFontFeature{feature.mTag, feature.mValue}); 662 } 663 } 664 665 auto disableOptionalLigatures = [&]() -> void { 666 addOrReplace(gfxFontFeature{HB_TAG('l', 'i', 'g', 'a'), 0}); 667 addOrReplace(gfxFontFeature{HB_TAG('c', 'l', 'i', 'g'), 0}); 668 addOrReplace(gfxFontFeature{HB_TAG('d', 'l', 'i', 'g'), 0}); 669 addOrReplace(gfxFontFeature{HB_TAG('h', 'l', 'i', 'g'), 0}); 670 }; 671 672 // Add features that are already resolved to tags & values in the style. 673 if (styleRuleFeatures.IsEmpty()) { 674 // Disable optional ligatures if non-zero letter-spacing is in effect. 675 if (aDisableLigatures) { 676 disableOptionalLigatures(); 677 } 678 } else { 679 for (const gfxFontFeature& feature : styleRuleFeatures) { 680 // A dummy feature (0,0) is used as a sentinel to separate features 681 // originating from font-variant-* or other high-level properties from 682 // those directly specified as font-feature-settings. The high-level 683 // features may be overridden by aDisableLigatures, while low-level 684 // features specified directly as tags will come last and therefore 685 // take precedence over everything else. 686 if (feature.mTag) { 687 addOrReplace(gfxFontFeature{feature.mTag, feature.mValue}); 688 } else if (aDisableLigatures) { 689 // Handle ligature-disabling setting at the boundary between high- 690 // and low-level features. 691 disableOptionalLigatures(); 692 } 693 } 694 } 695 696 for (const auto& f : mergedFeatures) { 697 aHandleFeature(f.mTag, f.mValue, aHandleFeatureData); 698 } 699 } 700 701 void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset, 702 const char16_t* aString, 703 uint32_t aLength) { 704 if (aLength == 0) { 705 return; 706 } 707 708 CompressedGlyph* const glyphs = GetCharacterGlyphs() + aOffset; 709 CompressedGlyph extendCluster = CompressedGlyph::MakeComplex(false, true); 710 711 // GraphemeClusterBreakIteratorUtf16 won't be able to tell us if the string 712 // _begins_ with a cluster-extender, so we handle that here 713 uint32_t ch = aString[0]; 714 if (aLength > 1 && NS_IS_SURROGATE_PAIR(ch, aString[1])) { 715 ch = SURROGATE_TO_UCS4(ch, aString[1]); 716 } 717 if (IsClusterExtender(ch)) { 718 glyphs[0] = extendCluster; 719 } 720 721 intl::GraphemeClusterBreakIteratorUtf16 iter( 722 Span<const char16_t>(aString, aLength)); 723 uint32_t pos = 0; 724 725 const char16_t kIdeographicSpace = 0x3000; 726 // Special case for Bengali: although Virama normally clusters with the 727 // preceding letter, we *also* want to cluster it with a following Ya 728 // so that when the Virama+Ya form ya-phala, this is not separated from the 729 // preceding letter by any letter-spacing or justification. 730 const char16_t kBengaliVirama = 0x09CD; 731 const char16_t kBengaliYa = 0x09AF; 732 bool prevWasHyphen = false; 733 while (pos < aLength) { 734 const char16_t ch = aString[pos]; 735 if (prevWasHyphen) { 736 if (nsContentUtils::IsAlphanumeric(ch)) { 737 glyphs[pos].SetCanBreakBefore( 738 CompressedGlyph::FLAG_BREAK_TYPE_EMERGENCY_WRAP); 739 } 740 prevWasHyphen = false; 741 } 742 if (ch == char16_t(' ') || ch == kIdeographicSpace) { 743 glyphs[pos].SetIsSpace(); 744 } else if (nsContentUtils::IsHyphen(ch) && pos && 745 nsContentUtils::IsAlphanumeric(aString[pos - 1])) { 746 prevWasHyphen = true; 747 } else if (ch == kBengaliYa) { 748 // Unless we're at the start, check for a preceding virama. 749 if (pos > 0 && aString[pos - 1] == kBengaliVirama) { 750 glyphs[pos] = extendCluster; 751 } 752 } 753 // advance iter to the next cluster-start (or end of text) 754 const uint32_t nextPos = *iter.Next(); 755 // step past the first char of the cluster 756 ++pos; 757 // mark all the rest as cluster-continuations 758 for (; pos < nextPos; ++pos) { 759 glyphs[pos] = extendCluster; 760 } 761 } 762 } 763 764 void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset, 765 const uint8_t* aString, 766 uint32_t aLength) { 767 CompressedGlyph* glyphs = GetCharacterGlyphs() + aOffset; 768 uint32_t pos = 0; 769 bool prevWasHyphen = false; 770 while (pos < aLength) { 771 uint8_t ch = aString[pos]; 772 if (prevWasHyphen) { 773 if (nsContentUtils::IsAlphanumeric(ch)) { 774 glyphs->SetCanBreakBefore( 775 CompressedGlyph::FLAG_BREAK_TYPE_EMERGENCY_WRAP); 776 } 777 prevWasHyphen = false; 778 } 779 if (ch == uint8_t(' ')) { 780 glyphs->SetIsSpace(); 781 } else if (ch == uint8_t('-') && pos && 782 nsContentUtils::IsAlphanumeric(aString[pos - 1])) { 783 prevWasHyphen = true; 784 } 785 ++pos; 786 ++glyphs; 787 } 788 } 789 790 gfxShapedText::DetailedGlyph* gfxShapedText::AllocateDetailedGlyphs( 791 uint32_t aIndex, uint32_t aCount) { 792 NS_ASSERTION(aIndex < GetLength(), "Index out of range"); 793 794 if (!mDetailedGlyphs) { 795 mDetailedGlyphs = MakeUnique<DetailedGlyphStore>(); 796 } 797 798 return mDetailedGlyphs->Allocate(aIndex, aCount); 799 } 800 801 void gfxShapedText::SetDetailedGlyphs(uint32_t aIndex, uint32_t aGlyphCount, 802 const DetailedGlyph* aGlyphs) { 803 CompressedGlyph& g = GetCharacterGlyphs()[aIndex]; 804 805 MOZ_ASSERT(aIndex > 0 || g.IsLigatureGroupStart(), 806 "First character can't be a ligature continuation!"); 807 808 if (aGlyphCount > 0) { 809 DetailedGlyph* details = AllocateDetailedGlyphs(aIndex, aGlyphCount); 810 memcpy(details, aGlyphs, sizeof(DetailedGlyph) * aGlyphCount); 811 } 812 813 g.SetGlyphCount(aGlyphCount); 814 } 815 816 #define ZWNJ 0x200C 817 #define ZWJ 0x200D 818 static inline bool IsIgnorable(uint32_t aChar) { 819 return (IsDefaultIgnorable(aChar)) || aChar == ZWNJ || aChar == ZWJ; 820 } 821 822 void gfxShapedText::SetMissingGlyph(uint32_t aIndex, uint32_t aChar, 823 gfxFont* aFont) { 824 CompressedGlyph& g = GetCharacterGlyphs()[aIndex]; 825 uint8_t category = GetGeneralCategory(aChar); 826 if (category >= HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK && 827 category <= HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) { 828 g.SetComplex(false, true); 829 } 830 831 // Leaving advance as zero will prevent drawing the hexbox for ignorables. 832 int32_t advance = 0; 833 if (!IsIgnorable(aChar)) { 834 gfxFloat width = 835 std::max(aFont->GetMetrics(nsFontMetrics::eHorizontal).aveCharWidth, 836 gfxFloat(gfxFontMissingGlyphs::GetDesiredMinWidth( 837 aChar, mAppUnitsPerDevUnit))); 838 advance = int32_t(width * mAppUnitsPerDevUnit); 839 } 840 DetailedGlyph detail = {aChar, advance, gfx::Point()}; 841 SetDetailedGlyphs(aIndex, 1, &detail); 842 g.SetMissing(); 843 } 844 845 bool gfxShapedText::FilterIfIgnorable(uint32_t aIndex, uint32_t aCh) { 846 if (IsIgnorable(aCh)) { 847 // There are a few default-ignorables of Letter category (currently, 848 // just the Hangul filler characters) that we'd better not discard 849 // if they're followed by additional characters in the same cluster. 850 // Some fonts use them to carry the width of a whole cluster of 851 // combining jamos; see bug 1238243. 852 auto* charGlyphs = GetCharacterGlyphs(); 853 if (GetGenCategory(aCh) == nsUGenCategory::kLetter && 854 aIndex + 1 < GetLength() && !charGlyphs[aIndex + 1].IsClusterStart()) { 855 return false; 856 } 857 // A compressedGlyph that is set to MISSING but has no DetailedGlyphs list 858 // will be zero-width/invisible, which is what we want here. 859 CompressedGlyph& g = charGlyphs[aIndex]; 860 g.SetComplex(g.IsClusterStart(), g.IsLigatureGroupStart()).SetMissing(); 861 return true; 862 } 863 return false; 864 } 865 866 void gfxShapedText::ApplyTrackingToClusters(gfxFloat aTrackingAdjustment, 867 uint32_t aOffset, 868 uint32_t aLength) { 869 int32_t appUnitAdjustment = 870 NS_round(aTrackingAdjustment * gfxFloat(mAppUnitsPerDevUnit)); 871 CompressedGlyph* charGlyphs = GetCharacterGlyphs(); 872 for (uint32_t i = aOffset; i < aOffset + aLength; ++i) { 873 CompressedGlyph* glyphData = charGlyphs + i; 874 if (glyphData->IsSimpleGlyph()) { 875 // simple glyphs ==> just add the advance 876 int32_t advance = glyphData->GetSimpleAdvance(); 877 if (advance > 0) { 878 advance = std::max(0, advance + appUnitAdjustment); 879 if (CompressedGlyph::IsSimpleAdvance(advance)) { 880 glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph()); 881 } else { 882 // rare case, tested by making this the default 883 uint32_t glyphIndex = glyphData->GetSimpleGlyph(); 884 // convert the simple CompressedGlyph to an empty complex record 885 glyphData->SetComplex(true, true); 886 // then set its details (glyph ID with its new advance) 887 DetailedGlyph detail = {glyphIndex, advance, gfx::Point()}; 888 SetDetailedGlyphs(i, 1, &detail); 889 } 890 } 891 } else { 892 // complex glyphs ==> add offset at cluster/ligature boundaries 893 uint32_t detailedLength = glyphData->GetGlyphCount(); 894 if (detailedLength) { 895 DetailedGlyph* details = GetDetailedGlyphs(i); 896 if (!details) { 897 continue; 898 } 899 auto& advance = IsRightToLeft() ? details[0].mAdvance 900 : details[detailedLength - 1].mAdvance; 901 if (advance > 0) { 902 advance = std::max(0, advance + appUnitAdjustment); 903 } 904 } 905 } 906 } 907 } 908 909 size_t gfxShapedWord::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { 910 size_t total = aMallocSizeOf(this); 911 if (mDetailedGlyphs) { 912 total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf); 913 } 914 return total; 915 } 916 917 float gfxFont::AngleForSyntheticOblique() const { 918 // First check conditions that mean no synthetic slant should be used: 919 if (mStyle.style == FontSlantStyle::NORMAL) { 920 return 0.0f; // Requested style is 'normal'. 921 } 922 if (mStyle.synthesisStyle == StyleFontSynthesisStyle::None) { 923 return 0.0f; // Synthetic obliquing is disabled. 924 } 925 if (!mFontEntry->MayUseSyntheticSlant()) { 926 return 0.0f; // The resource supports "real" slant, so don't synthesize. 927 } 928 929 // If style calls for italic, and face doesn't support it, use default 930 // oblique angle as a simulation, but only if synthesis setting allows it. 931 if (mStyle.style.IsItalic()) { 932 return mFontEntry->SupportsItalic() ? 0.0f 933 : mStyle.synthesisStyle == StyleFontSynthesisStyle::Auto 934 ? FontSlantStyle::DEFAULT_OBLIQUE_DEGREES 935 : 0.0f; 936 } 937 938 // OK, we're going to use synthetic oblique: return the requested angle. 939 return mStyle.style.ObliqueAngle(); 940 } 941 942 float gfxFont::SkewForSyntheticOblique() const { 943 // Precomputed value of tan(kDefaultAngle), the default italic/oblique slant; 944 // avoids calling tan() at runtime except for custom oblique values. 945 static const float kTanDefaultAngle = 946 tan(FontSlantStyle::DEFAULT_OBLIQUE_DEGREES * (M_PI / 180.0)); 947 948 float angle = AngleForSyntheticOblique(); 949 if (angle == 0.0f) { 950 return 0.0f; 951 } else if (angle == FontSlantStyle::DEFAULT_OBLIQUE_DEGREES) { 952 return kTanDefaultAngle; 953 } else { 954 return tan(angle * (M_PI / 180.0)); 955 } 956 } 957 958 void gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, 959 bool aOtherIsOnLeft) { 960 mAscent = std::max(mAscent, aOther.mAscent); 961 mDescent = std::max(mDescent, aOther.mDescent); 962 if (aOtherIsOnLeft) { 963 mBoundingBox = (mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0)) 964 .Union(aOther.mBoundingBox); 965 } else { 966 mBoundingBox = 967 mBoundingBox.Union(aOther.mBoundingBox + gfxPoint(mAdvanceWidth, 0)); 968 } 969 mAdvanceWidth += aOther.mAdvanceWidth; 970 } 971 972 gfxFont::gfxFont(const RefPtr<UnscaledFont>& aUnscaledFont, 973 gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle, 974 AntialiasOption anAAOption) 975 : mFontEntry(aFontEntry), 976 mLock("gfxFont lock"), 977 mUnscaledFont(aUnscaledFont), 978 mStyle(*aFontStyle), 979 mAdjustedSize(-1.0), // negative to indicate "not yet initialized" 980 mFUnitsConvFactor(-1.0f), // negative to indicate "not yet initialized" 981 mAntialiasOption(anAAOption), 982 mIsValid(true), 983 mApplySyntheticBold(false), 984 mKerningEnabled(false), 985 mMathInitialized(false) { 986 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS 987 ++gFontCount; 988 #endif 989 990 if (MOZ_UNLIKELY(StaticPrefs::gfx_text_disable_aa_AtStartup())) { 991 mAntialiasOption = kAntialiasNone; 992 } 993 994 // Turn off AA for Ahem for testing purposes when requested. 995 if (MOZ_UNLIKELY(StaticPrefs::gfx_font_rendering_ahem_antialias_none() && 996 mFontEntry->FamilyName().EqualsLiteral("Ahem"))) { 997 mAntialiasOption = kAntialiasNone; 998 } 999 1000 mKerningSet = HasFeatureSet(HB_TAG('k', 'e', 'r', 'n'), mKerningEnabled); 1001 1002 // Ensure the gfxFontEntry's unitsPerEm and extents fields are initialized, 1003 // so that GetFontExtents can use them without risk of races. 1004 (void)mFontEntry->UnitsPerEm(); 1005 } 1006 1007 gfxFont::~gfxFont() { 1008 mFontEntry->NotifyFontDestroyed(this); 1009 1010 // Delete objects owned through atomic pointers. (Some of these may be null, 1011 // but that's OK.) 1012 delete mVerticalMetrics.exchange(nullptr); 1013 delete mHarfBuzzShaper.exchange(nullptr); 1014 delete mGraphiteShaper.exchange(nullptr); 1015 delete mMathTable.exchange(nullptr); 1016 delete mNonAAFont.exchange(nullptr); 1017 1018 if (auto* scaledFont = mAzureScaledFont.exchange(nullptr)) { 1019 scaledFont->Release(); 1020 } 1021 1022 if (mGlyphChangeObservers) { 1023 for (const auto& key : *mGlyphChangeObservers) { 1024 key->ForgetFont(); 1025 } 1026 } 1027 } 1028 1029 // Work out whether cairo will snap inter-glyph spacing to pixels. 1030 // 1031 // Layout does not align text to pixel boundaries, so, with font drawing 1032 // backends that snap glyph positions to pixels, it is important that 1033 // inter-glyph spacing within words is always an integer number of pixels. 1034 // This ensures that the drawing backend snaps all of the word's glyphs in the 1035 // same direction and so inter-glyph spacing remains the same. 1036 // 1037 gfxFont::RoundingFlags gfxFont::GetRoundOffsetsToPixels( 1038 DrawTarget* aDrawTarget) { 1039 // Could do something fancy here for ScaleFactors of 1040 // AxisAlignedTransforms, but we leave things simple. 1041 // Not much point rounding if a matrix will mess things up anyway. 1042 // Also check if the font already knows hint metrics is off... 1043 if (aDrawTarget->GetTransform().HasNonTranslation() || !ShouldHintMetrics()) { 1044 return RoundingFlags(0); 1045 } 1046 1047 cairo_t* cr = static_cast<cairo_t*>( 1048 aDrawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT)); 1049 if (cr) { 1050 cairo_surface_t* target = cairo_get_target(cr); 1051 1052 // Check whether the cairo surface's font options hint metrics. 1053 cairo_font_options_t* fontOptions = cairo_font_options_create(); 1054 cairo_surface_get_font_options(target, fontOptions); 1055 cairo_hint_metrics_t hintMetrics = 1056 cairo_font_options_get_hint_metrics(fontOptions); 1057 cairo_font_options_destroy(fontOptions); 1058 1059 switch (hintMetrics) { 1060 case CAIRO_HINT_METRICS_OFF: 1061 return RoundingFlags(0); 1062 case CAIRO_HINT_METRICS_ON: 1063 return RoundingFlags::kRoundX | RoundingFlags::kRoundY; 1064 default: 1065 break; 1066 } 1067 } 1068 1069 if (ShouldRoundXOffset(cr)) { 1070 return RoundingFlags::kRoundX | RoundingFlags::kRoundY; 1071 } else { 1072 return RoundingFlags::kRoundY; 1073 } 1074 } 1075 1076 gfxHarfBuzzShaper* gfxFont::GetHarfBuzzShaper() { 1077 if (!mHarfBuzzShaper) { 1078 auto* shaper = new gfxHarfBuzzShaper(this); 1079 shaper->Initialize(); 1080 if (!mHarfBuzzShaper.compareExchange(nullptr, shaper)) { 1081 delete shaper; 1082 } 1083 } 1084 gfxHarfBuzzShaper* shaper = mHarfBuzzShaper; 1085 return shaper->IsInitialized() ? shaper : nullptr; 1086 } 1087 1088 gfxFloat gfxFont::GetGlyphAdvance(uint16_t aGID, bool aVertical) { 1089 if (!aVertical && ProvidesGlyphWidths()) { 1090 return GetGlyphWidth(aGID) / 65536.0; 1091 } 1092 if (mFUnitsConvFactor < 0.0f) { 1093 // Metrics haven't been initialized; lock while we do that. 1094 AutoWriteLock lock(mLock); 1095 if (mFUnitsConvFactor < 0.0f) { 1096 GetMetrics(nsFontMetrics::eHorizontal); 1097 } 1098 } 1099 NS_ASSERTION(mFUnitsConvFactor >= 0.0f, 1100 "missing font unit conversion factor"); 1101 if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) { 1102 if (aVertical) { 1103 // Note that GetGlyphVAdvance may return -1 to indicate it was unable 1104 // to retrieve vertical metrics; in that case we fall back to the 1105 // aveCharWidth value as a default advance. 1106 int32_t advance = shaper->GetGlyphVAdvance(aGID); 1107 if (advance < 0) { 1108 return GetMetrics(nsFontMetrics::eVertical).aveCharWidth; 1109 } 1110 return advance / 65536.0; 1111 } 1112 return shaper->GetGlyphHAdvance(aGID) / 65536.0; 1113 } 1114 return 0.0; 1115 } 1116 1117 gfxFloat gfxFont::GetCharAdvance(uint32_t aUnicode, bool aVertical) { 1118 uint32_t gid = 0; 1119 if (ProvidesGetGlyph()) { 1120 gid = GetGlyph(aUnicode, 0); 1121 } else { 1122 if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) { 1123 gid = shaper->GetNominalGlyph(aUnicode); 1124 } 1125 } 1126 if (!gid) { 1127 return -1.0; 1128 } 1129 return GetGlyphAdvance(gid, aVertical); 1130 } 1131 1132 static void CollectLookupsByFeature(hb_face_t* aFace, hb_tag_t aTableTag, 1133 uint32_t aFeatureIndex, 1134 hb_set_t* aLookups) { 1135 uint32_t lookups[32]; 1136 uint32_t i, len, offset; 1137 1138 offset = 0; 1139 do { 1140 len = std::size(lookups); 1141 hb_ot_layout_feature_get_lookups(aFace, aTableTag, aFeatureIndex, offset, 1142 &len, lookups); 1143 for (i = 0; i < len; i++) { 1144 hb_set_add(aLookups, lookups[i]); 1145 } 1146 offset += len; 1147 } while (len == std::size(lookups)); 1148 } 1149 1150 static void CollectLookupsByLanguage( 1151 hb_face_t* aFace, hb_tag_t aTableTag, 1152 const nsTHashSet<uint32_t>& aSpecificFeatures, hb_set_t* aOtherLookups, 1153 hb_set_t* aSpecificFeatureLookups, uint32_t aScriptIndex, 1154 uint32_t aLangIndex) { 1155 uint32_t reqFeatureIndex; 1156 if (hb_ot_layout_language_get_required_feature_index( 1157 aFace, aTableTag, aScriptIndex, aLangIndex, &reqFeatureIndex)) { 1158 CollectLookupsByFeature(aFace, aTableTag, reqFeatureIndex, aOtherLookups); 1159 } 1160 1161 uint32_t featureIndexes[32]; 1162 uint32_t i, len, offset; 1163 1164 offset = 0; 1165 do { 1166 len = std::size(featureIndexes); 1167 hb_ot_layout_language_get_feature_indexes(aFace, aTableTag, aScriptIndex, 1168 aLangIndex, offset, &len, 1169 featureIndexes); 1170 1171 for (i = 0; i < len; i++) { 1172 uint32_t featureIndex = featureIndexes[i]; 1173 1174 // get the feature tag 1175 hb_tag_t featureTag; 1176 uint32_t tagLen = 1; 1177 hb_ot_layout_language_get_feature_tags(aFace, aTableTag, aScriptIndex, 1178 aLangIndex, offset + i, &tagLen, 1179 &featureTag); 1180 1181 // collect lookups 1182 hb_set_t* lookups = aSpecificFeatures.Contains(featureTag) 1183 ? aSpecificFeatureLookups 1184 : aOtherLookups; 1185 CollectLookupsByFeature(aFace, aTableTag, featureIndex, lookups); 1186 } 1187 offset += len; 1188 } while (len == std::size(featureIndexes)); 1189 } 1190 1191 static bool HasLookupRuleWithGlyphByScript( 1192 hb_face_t* aFace, hb_tag_t aTableTag, hb_tag_t aScriptTag, 1193 uint32_t aScriptIndex, uint16_t aGlyph, 1194 const nsTHashSet<uint32_t>& aDefaultFeatures, 1195 bool& aHasDefaultFeatureWithGlyph) { 1196 uint32_t numLangs, lang; 1197 hb_set_t* defaultFeatureLookups = hb_set_create(); 1198 hb_set_t* nonDefaultFeatureLookups = hb_set_create(); 1199 1200 // default lang 1201 CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures, 1202 nonDefaultFeatureLookups, defaultFeatureLookups, 1203 aScriptIndex, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX); 1204 1205 // iterate over langs 1206 numLangs = hb_ot_layout_script_get_language_tags( 1207 aFace, aTableTag, aScriptIndex, 0, nullptr, nullptr); 1208 for (lang = 0; lang < numLangs; lang++) { 1209 CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures, 1210 nonDefaultFeatureLookups, defaultFeatureLookups, 1211 aScriptIndex, lang); 1212 } 1213 1214 // look for the glyph among default feature lookups 1215 aHasDefaultFeatureWithGlyph = false; 1216 hb_set_t* glyphs = hb_set_create(); 1217 hb_codepoint_t index = -1; 1218 while (hb_set_next(defaultFeatureLookups, &index)) { 1219 hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs, 1220 glyphs, nullptr); 1221 if (hb_set_has(glyphs, aGlyph)) { 1222 aHasDefaultFeatureWithGlyph = true; 1223 break; 1224 } 1225 } 1226 1227 // look for the glyph among non-default feature lookups 1228 // if no default feature lookups contained spaces 1229 bool hasNonDefaultFeatureWithGlyph = false; 1230 if (!aHasDefaultFeatureWithGlyph) { 1231 hb_set_clear(glyphs); 1232 index = -1; 1233 while (hb_set_next(nonDefaultFeatureLookups, &index)) { 1234 hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, 1235 glyphs, glyphs, nullptr); 1236 if (hb_set_has(glyphs, aGlyph)) { 1237 hasNonDefaultFeatureWithGlyph = true; 1238 break; 1239 } 1240 } 1241 } 1242 1243 hb_set_destroy(glyphs); 1244 hb_set_destroy(defaultFeatureLookups); 1245 hb_set_destroy(nonDefaultFeatureLookups); 1246 1247 return aHasDefaultFeatureWithGlyph || hasNonDefaultFeatureWithGlyph; 1248 } 1249 1250 static void HasLookupRuleWithGlyph(hb_face_t* aFace, hb_tag_t aTableTag, 1251 bool& aHasGlyph, hb_tag_t aSpecificFeature, 1252 bool& aHasGlyphSpecific, uint16_t aGlyph) { 1253 // iterate over the scripts in the font 1254 uint32_t numScripts, numLangs, script, lang; 1255 hb_set_t* otherLookups = hb_set_create(); 1256 hb_set_t* specificFeatureLookups = hb_set_create(); 1257 nsTHashSet<uint32_t> specificFeature(1); 1258 1259 specificFeature.Insert(aSpecificFeature); 1260 1261 numScripts = 1262 hb_ot_layout_table_get_script_tags(aFace, aTableTag, 0, nullptr, nullptr); 1263 1264 for (script = 0; script < numScripts; script++) { 1265 // default lang 1266 CollectLookupsByLanguage(aFace, aTableTag, specificFeature, otherLookups, 1267 specificFeatureLookups, script, 1268 HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX); 1269 1270 // iterate over langs 1271 numLangs = hb_ot_layout_script_get_language_tags( 1272 aFace, HB_OT_TAG_GPOS, script, 0, nullptr, nullptr); 1273 for (lang = 0; lang < numLangs; lang++) { 1274 CollectLookupsByLanguage(aFace, aTableTag, specificFeature, otherLookups, 1275 specificFeatureLookups, script, lang); 1276 } 1277 } 1278 1279 // look for the glyph among non-specific feature lookups 1280 hb_set_t* glyphs = hb_set_create(); 1281 hb_codepoint_t index = -1; 1282 while (hb_set_next(otherLookups, &index)) { 1283 hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs, 1284 glyphs, nullptr); 1285 if (hb_set_has(glyphs, aGlyph)) { 1286 aHasGlyph = true; 1287 break; 1288 } 1289 } 1290 1291 // look for the glyph among specific feature lookups 1292 hb_set_clear(glyphs); 1293 index = -1; 1294 while (hb_set_next(specificFeatureLookups, &index)) { 1295 hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs, 1296 glyphs, nullptr); 1297 if (hb_set_has(glyphs, aGlyph)) { 1298 aHasGlyphSpecific = true; 1299 break; 1300 } 1301 } 1302 1303 hb_set_destroy(glyphs); 1304 hb_set_destroy(specificFeatureLookups); 1305 hb_set_destroy(otherLookups); 1306 } 1307 1308 Atomic<nsTHashMap<nsUint32HashKey, intl::Script>*> gfxFont::sScriptTagToCode; 1309 Atomic<nsTHashSet<uint32_t>*> gfxFont::sDefaultFeatures; 1310 1311 static inline bool HasSubstitution(uint32_t* aBitVector, intl::Script aScript) { 1312 return (aBitVector[static_cast<uint32_t>(aScript) >> 5] & 1313 (1 << (static_cast<uint32_t>(aScript) & 0x1f))) != 0; 1314 } 1315 1316 // union of all default substitution features across scripts 1317 static const hb_tag_t defaultFeatures[] = { 1318 HB_TAG('a', 'b', 'v', 'f'), HB_TAG('a', 'b', 'v', 's'), 1319 HB_TAG('a', 'k', 'h', 'n'), HB_TAG('b', 'l', 'w', 'f'), 1320 HB_TAG('b', 'l', 'w', 's'), HB_TAG('c', 'a', 'l', 't'), 1321 HB_TAG('c', 'c', 'm', 'p'), HB_TAG('c', 'f', 'a', 'r'), 1322 HB_TAG('c', 'j', 'c', 't'), HB_TAG('c', 'l', 'i', 'g'), 1323 HB_TAG('f', 'i', 'n', '2'), HB_TAG('f', 'i', 'n', '3'), 1324 HB_TAG('f', 'i', 'n', 'a'), HB_TAG('h', 'a', 'l', 'f'), 1325 HB_TAG('h', 'a', 'l', 'n'), HB_TAG('i', 'n', 'i', 't'), 1326 HB_TAG('i', 's', 'o', 'l'), HB_TAG('l', 'i', 'g', 'a'), 1327 HB_TAG('l', 'j', 'm', 'o'), HB_TAG('l', 'o', 'c', 'l'), 1328 HB_TAG('l', 't', 'r', 'a'), HB_TAG('l', 't', 'r', 'm'), 1329 HB_TAG('m', 'e', 'd', '2'), HB_TAG('m', 'e', 'd', 'i'), 1330 HB_TAG('m', 's', 'e', 't'), HB_TAG('n', 'u', 'k', 't'), 1331 HB_TAG('p', 'r', 'e', 'f'), HB_TAG('p', 'r', 'e', 's'), 1332 HB_TAG('p', 's', 't', 'f'), HB_TAG('p', 's', 't', 's'), 1333 HB_TAG('r', 'c', 'l', 't'), HB_TAG('r', 'l', 'i', 'g'), 1334 HB_TAG('r', 'k', 'r', 'f'), HB_TAG('r', 'p', 'h', 'f'), 1335 HB_TAG('r', 't', 'l', 'a'), HB_TAG('r', 't', 'l', 'm'), 1336 HB_TAG('t', 'j', 'm', 'o'), HB_TAG('v', 'a', 't', 'u'), 1337 HB_TAG('v', 'e', 'r', 't'), HB_TAG('v', 'j', 'm', 'o')}; 1338 1339 void gfxFont::CheckForFeaturesInvolvingSpace() const { 1340 gfxFontEntry::SpaceFeatures flags = gfxFontEntry::SpaceFeatures::None; 1341 1342 // mFontEntry->mHasSpaceFeatures is a std::atomic<>, so we set it with 1343 // `exchange` to avoid a potential data race. It's ok if two threads both 1344 // try to set it; they'll end up with the same value, so it doesn't matter 1345 // that one will overwrite the other. 1346 auto setFlags = 1347 MakeScopeExit([&]() { mFontEntry->mHasSpaceFeatures.exchange(flags); }); 1348 1349 bool log = LOG_FONTINIT_ENABLED(); 1350 TimeStamp start; 1351 if (MOZ_UNLIKELY(log)) { 1352 start = TimeStamp::Now(); 1353 } 1354 1355 uint32_t spaceGlyph = GetSpaceGlyph(); 1356 if (!spaceGlyph) { 1357 return; 1358 } 1359 1360 auto face(GetFontEntry()->GetHBFace()); 1361 1362 // GSUB lookups - examine per script 1363 if (hb_ot_layout_has_substitution(face)) { 1364 // Get the script ==> code hashtable, creating it on first use. 1365 nsTHashMap<nsUint32HashKey, Script>* tagToCode = sScriptTagToCode; 1366 if (!tagToCode) { 1367 tagToCode = new nsTHashMap<nsUint32HashKey, Script>( 1368 size_t(Script::NUM_SCRIPT_CODES)); 1369 tagToCode->InsertOrUpdate(HB_TAG('D', 'F', 'L', 'T'), Script::COMMON); 1370 // Ensure that we don't try to look at script codes beyond what the 1371 // current version of ICU (at runtime -- in case of system ICU) 1372 // knows about. 1373 Script scriptCount = Script( 1374 std::min<int>(intl::UnicodeProperties::GetMaxNumberOfScripts() + 1, 1375 int(Script::NUM_SCRIPT_CODES))); 1376 for (Script s = Script::ARABIC; s < scriptCount; 1377 s = Script(static_cast<int>(s) + 1)) { 1378 hb_script_t script = hb_script_t(GetScriptTagForCode(s)); 1379 unsigned int scriptCount = 4; 1380 hb_tag_t scriptTags[4]; 1381 hb_ot_tags_from_script_and_language(script, HB_LANGUAGE_INVALID, 1382 &scriptCount, scriptTags, nullptr, 1383 nullptr); 1384 for (unsigned int i = 0; i < scriptCount; i++) { 1385 tagToCode->InsertOrUpdate(scriptTags[i], s); 1386 } 1387 } 1388 if (!sScriptTagToCode.compareExchange(nullptr, tagToCode)) { 1389 // We lost a race! Discard our new table and use the winner. 1390 delete tagToCode; 1391 tagToCode = sScriptTagToCode; 1392 } 1393 } 1394 1395 // Set up the default-features hashset on first use. 1396 if (!sDefaultFeatures) { 1397 uint32_t numDefaultFeatures = std::size(defaultFeatures); 1398 auto* set = new nsTHashSet<uint32_t>(numDefaultFeatures); 1399 for (uint32_t i = 0; i < numDefaultFeatures; i++) { 1400 set->Insert(defaultFeatures[i]); 1401 } 1402 if (!sDefaultFeatures.compareExchange(nullptr, set)) { 1403 delete set; 1404 } 1405 } 1406 1407 // iterate over the scripts in the font 1408 hb_tag_t scriptTags[8]; 1409 1410 uint32_t len, offset = 0; 1411 do { 1412 len = std::size(scriptTags); 1413 hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, offset, &len, 1414 scriptTags); 1415 for (uint32_t i = 0; i < len; i++) { 1416 bool isDefaultFeature = false; 1417 Script s; 1418 if (!HasLookupRuleWithGlyphByScript( 1419 face, HB_OT_TAG_GSUB, scriptTags[i], offset + i, spaceGlyph, 1420 *sDefaultFeatures, isDefaultFeature) || 1421 !tagToCode->Get(scriptTags[i], &s)) { 1422 continue; 1423 } 1424 flags = flags | gfxFontEntry::SpaceFeatures::HasFeatures; 1425 uint32_t index = static_cast<uint32_t>(s) >> 5; 1426 uint32_t bit = static_cast<uint32_t>(s) & 0x1f; 1427 MutexAutoLock lock(mFontEntry->mFeatureInfoLock); 1428 if (isDefaultFeature) { 1429 mFontEntry->mDefaultSubSpaceFeatures[index] |= (1 << bit); 1430 } else { 1431 mFontEntry->mNonDefaultSubSpaceFeatures[index] |= (1 << bit); 1432 } 1433 } 1434 offset += len; 1435 } while (len == std::size(scriptTags)); 1436 } 1437 1438 // spaces in default features of default script? 1439 // ==> can't use word cache, skip GPOS analysis 1440 bool canUseWordCache = true; 1441 { 1442 MutexAutoLock lock(mFontEntry->mFeatureInfoLock); 1443 if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, Script::COMMON)) { 1444 canUseWordCache = false; 1445 } 1446 } 1447 1448 // GPOS lookups - distinguish kerning from non-kerning features 1449 if (canUseWordCache && hb_ot_layout_has_positioning(face)) { 1450 bool hasKerning = false, hasNonKerning = false; 1451 HasLookupRuleWithGlyph(face, HB_OT_TAG_GPOS, hasNonKerning, 1452 HB_TAG('k', 'e', 'r', 'n'), hasKerning, spaceGlyph); 1453 if (hasKerning) { 1454 flags |= gfxFontEntry::SpaceFeatures::HasFeatures | 1455 gfxFontEntry::SpaceFeatures::Kerning; 1456 } 1457 if (hasNonKerning) { 1458 flags |= gfxFontEntry::SpaceFeatures::HasFeatures | 1459 gfxFontEntry::SpaceFeatures::NonKerning; 1460 } 1461 } 1462 1463 if (MOZ_UNLIKELY(log)) { 1464 MutexAutoLock lock(mFontEntry->mFeatureInfoLock); 1465 TimeDuration elapsed = TimeStamp::Now() - start; 1466 LOG_FONTINIT(( 1467 "(fontinit-spacelookups) font: %s - " 1468 "subst default: %8.8x %8.8x %8.8x %8.8x " 1469 "subst non-default: %8.8x %8.8x %8.8x %8.8x " 1470 "kerning: %s non-kerning: %s time: %6.3f\n", 1471 mFontEntry->Name().get(), mFontEntry->mDefaultSubSpaceFeatures[3], 1472 mFontEntry->mDefaultSubSpaceFeatures[2], 1473 mFontEntry->mDefaultSubSpaceFeatures[1], 1474 mFontEntry->mDefaultSubSpaceFeatures[0], 1475 mFontEntry->mNonDefaultSubSpaceFeatures[3], 1476 mFontEntry->mNonDefaultSubSpaceFeatures[2], 1477 mFontEntry->mNonDefaultSubSpaceFeatures[1], 1478 mFontEntry->mNonDefaultSubSpaceFeatures[0], 1479 (mFontEntry->mHasSpaceFeatures & gfxFontEntry::SpaceFeatures::Kerning 1480 ? "true" 1481 : "false"), 1482 (mFontEntry->mHasSpaceFeatures & gfxFontEntry::SpaceFeatures::NonKerning 1483 ? "true" 1484 : "false"), 1485 elapsed.ToMilliseconds())); 1486 } 1487 } 1488 1489 bool gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript) const { 1490 NS_ASSERTION(GetFontEntry()->mHasSpaceFeatures != 1491 gfxFontEntry::SpaceFeatures::Uninitialized, 1492 "need to initialize space lookup flags"); 1493 NS_ASSERTION(aRunScript < Script::NUM_SCRIPT_CODES, "weird script code"); 1494 if (aRunScript == Script::INVALID || aRunScript >= Script::NUM_SCRIPT_CODES) { 1495 return false; 1496 } 1497 1498 // default features have space lookups ==> true 1499 MutexAutoLock lock(mFontEntry->mFeatureInfoLock); 1500 if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, Script::COMMON) || 1501 HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, aRunScript)) { 1502 return true; 1503 } 1504 1505 // non-default features have space lookups and some type of 1506 // font feature, in font or style is specified ==> true 1507 if ((HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures, 1508 Script::COMMON) || 1509 HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures, aRunScript)) && 1510 (!mStyle.featureSettings.IsEmpty() || 1511 !mFontEntry->mFeatureSettings.IsEmpty())) { 1512 return true; 1513 } 1514 1515 return false; 1516 } 1517 1518 tainted_boolean_hint gfxFont::SpaceMayParticipateInShaping( 1519 Script aRunScript) const { 1520 // avoid checking fonts known not to include default space-dependent features 1521 if (MOZ_UNLIKELY(mFontEntry->mSkipDefaultFeatureSpaceCheck)) { 1522 if (!mKerningSet && mStyle.featureSettings.IsEmpty() && 1523 mFontEntry->mFeatureSettings.IsEmpty()) { 1524 return false; 1525 } 1526 } 1527 1528 if (FontCanSupportGraphite()) { 1529 if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) { 1530 return mFontEntry->HasGraphiteSpaceContextuals(); 1531 } 1532 } 1533 1534 // We record the presence of space-dependent features in the font entry 1535 // so that subsequent instantiations for the same font face won't 1536 // require us to re-check the tables; however, the actual check is done 1537 // by gfxFont because not all font entry subclasses know how to create 1538 // a harfbuzz face for introspection. 1539 gfxFontEntry::SpaceFeatures flags = mFontEntry->mHasSpaceFeatures; 1540 if (flags == gfxFontEntry::SpaceFeatures::Uninitialized) { 1541 CheckForFeaturesInvolvingSpace(); 1542 flags = mFontEntry->mHasSpaceFeatures; 1543 } 1544 1545 if (!(flags & gfxFontEntry::SpaceFeatures::HasFeatures)) { 1546 return false; 1547 } 1548 1549 // if font has substitution rules or non-kerning positioning rules 1550 // that involve spaces, bypass 1551 if (HasSubstitutionRulesWithSpaceLookups(aRunScript) || 1552 (flags & gfxFontEntry::SpaceFeatures::NonKerning)) { 1553 return true; 1554 } 1555 1556 // if kerning explicitly enabled/disabled via font-feature-settings or 1557 // font-kerning and kerning rules use spaces, only bypass when enabled 1558 if (mKerningSet && (flags & gfxFontEntry::SpaceFeatures::Kerning)) { 1559 return mKerningEnabled; 1560 } 1561 1562 return false; 1563 } 1564 1565 bool gfxFont::SupportsFeature(Script aScript, uint32_t aFeatureTag) { 1566 if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { 1567 return GetFontEntry()->SupportsGraphiteFeature(aFeatureTag); 1568 } 1569 return GetFontEntry()->SupportsOpenTypeFeature(aScript, aFeatureTag); 1570 } 1571 1572 bool gfxFont::SupportsVariantCaps(Script aScript, uint32_t aVariantCaps, 1573 bool& aFallbackToSmallCaps, 1574 bool& aSyntheticLowerToSmallCaps, 1575 bool& aSyntheticUpperToSmallCaps) { 1576 bool ok = true; // cases without fallback are fine 1577 aFallbackToSmallCaps = false; 1578 aSyntheticLowerToSmallCaps = false; 1579 aSyntheticUpperToSmallCaps = false; 1580 switch (aVariantCaps) { 1581 case NS_FONT_VARIANT_CAPS_SMALLCAPS: 1582 ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')); 1583 if (!ok) { 1584 aSyntheticLowerToSmallCaps = true; 1585 } 1586 break; 1587 case NS_FONT_VARIANT_CAPS_ALLSMALL: 1588 ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')) && 1589 SupportsFeature(aScript, HB_TAG('c', '2', 's', 'c')); 1590 if (!ok) { 1591 aSyntheticLowerToSmallCaps = true; 1592 aSyntheticUpperToSmallCaps = true; 1593 } 1594 break; 1595 case NS_FONT_VARIANT_CAPS_PETITECAPS: 1596 ok = SupportsFeature(aScript, HB_TAG('p', 'c', 'a', 'p')); 1597 if (!ok) { 1598 ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')); 1599 aFallbackToSmallCaps = ok; 1600 } 1601 if (!ok) { 1602 aSyntheticLowerToSmallCaps = true; 1603 } 1604 break; 1605 case NS_FONT_VARIANT_CAPS_ALLPETITE: 1606 ok = SupportsFeature(aScript, HB_TAG('p', 'c', 'a', 'p')) && 1607 SupportsFeature(aScript, HB_TAG('c', '2', 'p', 'c')); 1608 if (!ok) { 1609 ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')) && 1610 SupportsFeature(aScript, HB_TAG('c', '2', 's', 'c')); 1611 aFallbackToSmallCaps = ok; 1612 } 1613 if (!ok) { 1614 aSyntheticLowerToSmallCaps = true; 1615 aSyntheticUpperToSmallCaps = true; 1616 } 1617 break; 1618 default: 1619 break; 1620 } 1621 1622 NS_ASSERTION( 1623 !(ok && (aSyntheticLowerToSmallCaps || aSyntheticUpperToSmallCaps)), 1624 "shouldn't use synthetic features if we found real ones"); 1625 1626 NS_ASSERTION(!(!ok && aFallbackToSmallCaps), 1627 "if we found a usable fallback, that counts as ok"); 1628 1629 return ok; 1630 } 1631 1632 bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript, 1633 const uint8_t* aString, uint32_t aLength, 1634 Script aRunScript) { 1635 NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aString), 1636 aLength); 1637 return SupportsSubSuperscript(aSubSuperscript, unicodeString.get(), aLength, 1638 aRunScript); 1639 } 1640 1641 bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript, 1642 const char16_t* aString, uint32_t aLength, 1643 Script aRunScript) { 1644 NS_ASSERTION(aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER || 1645 aSubSuperscript == NS_FONT_VARIANT_POSITION_SUB, 1646 "unknown value of font-variant-position"); 1647 1648 uint32_t feature = aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER 1649 ? HB_TAG('s', 'u', 'p', 's') 1650 : HB_TAG('s', 'u', 'b', 's'); 1651 1652 if (!SupportsFeature(aRunScript, feature)) { 1653 return false; 1654 } 1655 1656 // xxx - for graphite, don't really know how to sniff lookups so bail 1657 if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { 1658 return true; 1659 } 1660 1661 gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper(); 1662 if (!shaper) { 1663 return false; 1664 } 1665 1666 // get the hbset containing input glyphs for the feature 1667 const hb_set_t* inputGlyphs = 1668 mFontEntry->InputsForOpenTypeFeature(aRunScript, feature); 1669 1670 // create an hbset containing default glyphs for the script run 1671 hb_set_t* defaultGlyphsInRun = hb_set_create(); 1672 1673 // for each character, get the glyph id 1674 for (uint32_t i = 0; i < aLength; i++) { 1675 uint32_t ch = aString[i]; 1676 1677 if (i + 1 < aLength && NS_IS_SURROGATE_PAIR(ch, aString[i + 1])) { 1678 i++; 1679 ch = SURROGATE_TO_UCS4(ch, aString[i]); 1680 } 1681 1682 hb_codepoint_t gid = shaper->GetNominalGlyph(ch); 1683 hb_set_add(defaultGlyphsInRun, gid); 1684 } 1685 1686 // intersect with input glyphs, if size is not the same ==> fallback 1687 uint32_t origSize = hb_set_get_population(defaultGlyphsInRun); 1688 hb_set_intersect(defaultGlyphsInRun, inputGlyphs); 1689 uint32_t intersectionSize = hb_set_get_population(defaultGlyphsInRun); 1690 hb_set_destroy(defaultGlyphsInRun); 1691 1692 return origSize == intersectionSize; 1693 } 1694 1695 bool gfxFont::FeatureWillHandleChar(Script aRunScript, uint32_t aFeature, 1696 uint32_t aUnicode) { 1697 if (!SupportsFeature(aRunScript, aFeature)) { 1698 return false; 1699 } 1700 1701 // xxx - for graphite, don't really know how to sniff lookups so bail 1702 if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { 1703 return true; 1704 } 1705 1706 if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) { 1707 // get the hbset containing input glyphs for the feature 1708 const hb_set_t* inputGlyphs = 1709 mFontEntry->InputsForOpenTypeFeature(aRunScript, aFeature); 1710 1711 hb_codepoint_t gid = shaper->GetNominalGlyph(aUnicode); 1712 return hb_set_has(inputGlyphs, gid); 1713 } 1714 1715 return false; 1716 } 1717 1718 bool gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn) { 1719 aFeatureOn = false; 1720 1721 if (mStyle.featureSettings.IsEmpty() && 1722 GetFontEntry()->mFeatureSettings.IsEmpty()) { 1723 return false; 1724 } 1725 1726 // add feature values from font 1727 bool featureSet = false; 1728 uint32_t i, count; 1729 1730 nsTArray<gfxFontFeature>& fontFeatures = GetFontEntry()->mFeatureSettings; 1731 count = fontFeatures.Length(); 1732 for (i = 0; i < count; i++) { 1733 const gfxFontFeature& feature = fontFeatures.ElementAt(i); 1734 if (feature.mTag == aFeature) { 1735 featureSet = true; 1736 aFeatureOn = (feature.mValue != 0); 1737 } 1738 } 1739 1740 // add feature values from style rules 1741 nsTArray<gfxFontFeature>& styleFeatures = mStyle.featureSettings; 1742 count = styleFeatures.Length(); 1743 for (i = 0; i < count; i++) { 1744 const gfxFontFeature& feature = styleFeatures.ElementAt(i); 1745 if (feature.mTag == aFeature) { 1746 featureSet = true; 1747 aFeatureOn = (feature.mValue != 0); 1748 } 1749 } 1750 1751 return featureSet; 1752 } 1753 1754 already_AddRefed<mozilla::gfx::ScaledFont> gfxFont::GetScaledFont( 1755 mozilla::gfx::DrawTarget* aDrawTarget) { 1756 mozilla::gfx::PaletteCache dummy; 1757 TextRunDrawParams params(dummy); 1758 return GetScaledFont(params); 1759 } 1760 1761 void gfxFont::InitializeScaledFont( 1762 const RefPtr<mozilla::gfx::ScaledFont>& aScaledFont) { 1763 if (!aScaledFont) { 1764 return; 1765 } 1766 1767 float angle = AngleForSyntheticOblique(); 1768 if (angle != 0.0f) { 1769 aScaledFont->SetSyntheticObliqueAngle(angle); 1770 } 1771 } 1772 1773 /** 1774 * A helper function in case we need to do any rounding or other 1775 * processing here. 1776 */ 1777 #define ToDeviceUnits(aAppUnits, aDevUnitsPerAppUnit) \ 1778 (double(aAppUnits) * double(aDevUnitsPerAppUnit)) 1779 1780 static AntialiasMode Get2DAAMode(gfxFont::AntialiasOption aAAOption) { 1781 switch (aAAOption) { 1782 case gfxFont::kAntialiasSubpixel: 1783 return AntialiasMode::SUBPIXEL; 1784 case gfxFont::kAntialiasGrayscale: 1785 return AntialiasMode::GRAY; 1786 case gfxFont::kAntialiasNone: 1787 return AntialiasMode::NONE; 1788 default: 1789 return AntialiasMode::DEFAULT; 1790 } 1791 } 1792 1793 class GlyphBufferAzure { 1794 #define AUTO_BUFFER_SIZE (2048 / sizeof(Glyph)) 1795 1796 typedef mozilla::image::imgDrawingParams imgDrawingParams; 1797 1798 public: 1799 GlyphBufferAzure(const TextRunDrawParams& aRunParams, 1800 const FontDrawParams& aFontParams) 1801 : mRunParams(aRunParams), 1802 mFontParams(aFontParams), 1803 mBuffer(*mAutoBuffer.addr()), 1804 mBufSize(AUTO_BUFFER_SIZE), 1805 mCapacity(0), 1806 mNumGlyphs(0) {} 1807 1808 ~GlyphBufferAzure() { 1809 if (mNumGlyphs > 0) { 1810 FlushGlyphs(); 1811 } 1812 1813 if (mBuffer != *mAutoBuffer.addr()) { 1814 free(mBuffer); 1815 } 1816 } 1817 1818 // Ensure the buffer has enough space for aGlyphCount glyphs to be added, 1819 // considering the supplied strike multipler aStrikeCount. 1820 // This MUST be called before OutputGlyph is used to actually store glyph 1821 // records in the buffer. It may be called repeated to add further capacity 1822 // in case we don't know up-front exactly what will be needed. 1823 void AddCapacity(uint32_t aGlyphCount, uint32_t aStrikeCount) { 1824 // Calculate the new capacity and ensure it will fit within the maximum 1825 // allowed capacity. 1826 static const uint64_t kMaxCapacity = 64 * 1024; 1827 mCapacity = uint32_t(std::min( 1828 kMaxCapacity, 1829 uint64_t(mCapacity) + uint64_t(aGlyphCount) * uint64_t(aStrikeCount))); 1830 // See if the required capacity fits within the already-allocated space 1831 if (mCapacity <= mBufSize) { 1832 return; 1833 } 1834 // We need to grow the buffer: determine a new size, allocate, and 1835 // copy the existing data over if we didn't use realloc (which would 1836 // do it automatically). 1837 mBufSize = std::max(mCapacity, mBufSize * 2); 1838 if (mBuffer == *mAutoBuffer.addr()) { 1839 // switching from autobuffer to malloc, so we need to copy 1840 mBuffer = reinterpret_cast<Glyph*>(moz_xmalloc(mBufSize * sizeof(Glyph))); 1841 std::memcpy(mBuffer, *mAutoBuffer.addr(), mNumGlyphs * sizeof(Glyph)); 1842 } else { 1843 mBuffer = reinterpret_cast<Glyph*>( 1844 moz_xrealloc(mBuffer, mBufSize * sizeof(Glyph))); 1845 } 1846 } 1847 1848 void OutputGlyph(uint32_t aGlyphID, const gfx::Point& aPt) { 1849 // If the buffer is full, flush to make room for the new glyph. 1850 if (mNumGlyphs >= mCapacity) { 1851 // Check that AddCapacity has been used appropriately! 1852 MOZ_ASSERT(mCapacity > 0 && mNumGlyphs == mCapacity); 1853 Flush(); 1854 } 1855 Glyph* glyph = mBuffer + mNumGlyphs++; 1856 glyph->mIndex = aGlyphID; 1857 glyph->mPosition = aPt; 1858 } 1859 1860 void Flush() { 1861 if (mNumGlyphs > 0) { 1862 FlushGlyphs(); 1863 mNumGlyphs = 0; 1864 } 1865 } 1866 1867 const TextRunDrawParams& mRunParams; 1868 const FontDrawParams& mFontParams; 1869 1870 private: 1871 static DrawMode GetStrokeMode(DrawMode aMode) { 1872 return aMode & (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH); 1873 } 1874 1875 // Render the buffered glyphs to the draw target. 1876 void FlushGlyphs() { 1877 gfx::GlyphBuffer buf; 1878 buf.mGlyphs = mBuffer; 1879 buf.mNumGlyphs = mNumGlyphs; 1880 1881 const gfxContext::AzureState& state = mRunParams.context->CurrentState(); 1882 1883 // Draw stroke first if the UNDERNEATH flag is set in drawMode. 1884 if (mRunParams.strokeOpts && 1885 GetStrokeMode(mRunParams.drawMode) == 1886 (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH)) { 1887 DrawStroke(state, buf); 1888 } 1889 1890 if (mRunParams.drawMode & DrawMode::GLYPH_FILL) { 1891 if (state.pattern || mFontParams.contextPaint) { 1892 Pattern* pat; 1893 1894 RefPtr<gfxPattern> fillPattern; 1895 if (mFontParams.contextPaint) { 1896 imgDrawingParams imgParams; 1897 fillPattern = mFontParams.contextPaint->GetFillPattern( 1898 mRunParams.context->GetDrawTarget(), 1899 mRunParams.context->CurrentMatrixDouble(), imgParams); 1900 } 1901 if (!fillPattern) { 1902 if (state.pattern) { 1903 RefPtr<gfxPattern> statePattern = 1904 mRunParams.context->CurrentState().pattern; 1905 pat = statePattern->GetPattern(mRunParams.dt, 1906 state.patternTransformChanged 1907 ? &state.patternTransform 1908 : nullptr); 1909 } else { 1910 pat = nullptr; 1911 } 1912 } else { 1913 pat = fillPattern->GetPattern(mRunParams.dt); 1914 } 1915 1916 if (pat) { 1917 mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf, *pat, 1918 mFontParams.drawOptions); 1919 } 1920 } else { 1921 mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf, 1922 ColorPattern(state.color), 1923 mFontParams.drawOptions); 1924 } 1925 } 1926 1927 // Draw stroke if the UNDERNEATH flag is not set. 1928 if (mRunParams.strokeOpts && 1929 GetStrokeMode(mRunParams.drawMode) == DrawMode::GLYPH_STROKE) { 1930 DrawStroke(state, buf); 1931 } 1932 1933 if (mRunParams.drawMode & DrawMode::GLYPH_PATH) { 1934 mRunParams.context->EnsurePathBuilder(); 1935 Matrix mat = mRunParams.dt->GetTransform(); 1936 mFontParams.scaledFont->CopyGlyphsToBuilder( 1937 buf, mRunParams.context->mPathBuilder, &mat); 1938 } 1939 } 1940 1941 void DrawStroke(const gfxContext::AzureState& aState, 1942 gfx::GlyphBuffer& aBuffer) { 1943 if (mRunParams.textStrokePattern) { 1944 Pattern* pat = mRunParams.textStrokePattern->GetPattern( 1945 mRunParams.dt, 1946 aState.patternTransformChanged ? &aState.patternTransform : nullptr); 1947 1948 if (pat) { 1949 FlushStroke(aBuffer, *pat); 1950 } 1951 } else { 1952 FlushStroke(aBuffer, 1953 ColorPattern(ToDeviceColor(mRunParams.textStrokeColor))); 1954 } 1955 } 1956 1957 void FlushStroke(gfx::GlyphBuffer& aBuf, const Pattern& aPattern) { 1958 mRunParams.dt->StrokeGlyphs(mFontParams.scaledFont, aBuf, aPattern, 1959 *mRunParams.strokeOpts, 1960 mFontParams.drawOptions); 1961 } 1962 1963 // We use an "inline" buffer automatically allocated (on the stack) as part 1964 // of the GlyphBufferAzure object to hold the glyphs in most cases, falling 1965 // back to a separately-allocated heap buffer if the count of buffered 1966 // glyphs gets too big. 1967 // 1968 // This is basically a rudimentary AutoTArray; so why not use AutoTArray 1969 // itself? 1970 // 1971 // If we used an AutoTArray, we'd want to avoid using SetLength or 1972 // AppendElements to allocate the space we actually need, because those 1973 // methods would default-construct the new elements. 1974 // 1975 // Could we use SetCapacity to reserve the necessary buffer space without 1976 // default-constructing all the Glyph records? No, because of a failure 1977 // that could occur when we need to grow the buffer, which happens when we 1978 // encounter a DetailedGlyph in the textrun that refers to a sequence of 1979 // several real glyphs. At that point, we need to add some extra capacity 1980 // to the buffer we initially allocated based on the length of the textrun 1981 // range we're rendering. 1982 // 1983 // This buffer growth would work fine as long as it still fits within the 1984 // array's inline buffer (we just use a bit more of it), or if the buffer 1985 // was already heap-allocated (in which case AutoTArray will use realloc(), 1986 // preserving its contents). But a problem will arise when the initial 1987 // capacity we allocated (based on the length of the run) fits within the 1988 // array's inline buffer, but subsequently we need to extend the buffer 1989 // beyond the inline buffer size, so we reallocate to the heap. Because we 1990 // haven't "officially" filled the array with SetLength or AppendElements, 1991 // its mLength is still zero; as far as it's concerned the buffer is just 1992 // uninitialized space, and when it switches to use a malloc'd buffer it 1993 // won't copy the existing contents. 1994 1995 // Allocate space for a buffer of Glyph records, without initializing them. 1996 AlignedStorage2<Glyph[AUTO_BUFFER_SIZE]> mAutoBuffer; 1997 1998 // Pointer to the buffer we're currently using -- initially mAutoBuffer, 1999 // but may be changed to a malloc'd buffer, in which case that buffer must 2000 // be free'd on destruction. 2001 Glyph* mBuffer; 2002 2003 uint32_t mBufSize; // size of allocated buffer; capacity can grow to 2004 // this before reallocation is needed 2005 uint32_t mCapacity; // amount of buffer size reserved 2006 uint32_t mNumGlyphs; // number of glyphs actually present in the buffer 2007 2008 #undef AUTO_BUFFER_SIZE 2009 }; 2010 2011 // Bug 674909. When synthetic bolding text by drawing twice, need to 2012 // render using a pixel offset in device pixels, otherwise text 2013 // doesn't appear bolded, it appears as if a bad text shadow exists 2014 // when a non-identity transform exists. Use an offset factor so that 2015 // the second draw occurs at a constant offset in device pixels. 2016 2017 gfx::Float gfxFont::CalcXScale(DrawTarget* aDrawTarget) { 2018 // determine magnitude of a 1px x offset in device space 2019 Size t = aDrawTarget->GetTransform().TransformSize(Size(1.0, 0.0)); 2020 if (t.width == 1.0 && t.height == 0.0) { 2021 // short-circuit the most common case to avoid sqrt() and division 2022 return 1.0; 2023 } 2024 2025 gfx::Float m = sqrtf(t.width * t.width + t.height * t.height); 2026 2027 NS_ASSERTION(m != 0.0, "degenerate transform while synthetic bolding"); 2028 if (m == 0.0) { 2029 return 0.0; // effectively disables offset 2030 } 2031 2032 // scale factor so that offsets are 1px in device pixels 2033 return 1.0 / m; 2034 } 2035 2036 // Draw a run of CharacterGlyph records from the given offset in aShapedText. 2037 // Returns true if glyph paths were actually emitted. 2038 template <gfxFont::FontComplexityT FC, gfxFont::SpacingT S> 2039 bool gfxFont::DrawGlyphs(const gfxShapedText* aShapedText, 2040 uint32_t aOffset, // offset in the textrun 2041 uint32_t aCount, // length of run to draw 2042 gfx::Point* aPt, 2043 const gfx::Matrix* aOffsetMatrix, // may be null 2044 GlyphBufferAzure& aBuffer) { 2045 float& inlineCoord = 2046 aBuffer.mFontParams.isVerticalFont ? aPt->y.value : aPt->x.value; 2047 2048 const gfxShapedText::CompressedGlyph* glyphData = 2049 &aShapedText->GetCharacterGlyphs()[aOffset]; 2050 2051 if (S == SpacingT::HasSpacing) { 2052 float space = aBuffer.mRunParams.spacing[0].mBefore * 2053 aBuffer.mFontParams.advanceDirection; 2054 inlineCoord += space; 2055 } 2056 2057 // Allocate buffer space for the run, assuming all simple glyphs. 2058 uint32_t capacityMult = 1 + aBuffer.mFontParams.extraStrikes; 2059 aBuffer.AddCapacity(aCount, capacityMult); 2060 2061 bool emittedGlyphs = false; 2062 2063 for (uint32_t i = 0; i < aCount; ++i, ++glyphData) { 2064 if (glyphData->IsSimpleGlyph()) { 2065 float advance = 2066 glyphData->GetSimpleAdvance() * aBuffer.mFontParams.advanceDirection; 2067 if (aBuffer.mRunParams.isRTL) { 2068 inlineCoord += advance; 2069 } 2070 DrawOneGlyph<FC>(glyphData->GetSimpleGlyph(), *aPt, aBuffer, 2071 &emittedGlyphs); 2072 if (!aBuffer.mRunParams.isRTL) { 2073 inlineCoord += advance; 2074 } 2075 } else { 2076 uint32_t glyphCount = glyphData->GetGlyphCount(); 2077 if (glyphCount > 0) { 2078 // Add extra buffer capacity to allow for multiple-glyph entry. 2079 aBuffer.AddCapacity(glyphCount - 1, capacityMult); 2080 const gfxShapedText::DetailedGlyph* details = 2081 aShapedText->GetDetailedGlyphs(aOffset + i); 2082 MOZ_ASSERT(details, "missing DetailedGlyph!"); 2083 for (uint32_t j = 0; j < glyphCount; ++j, ++details) { 2084 float advance = 2085 details->mAdvance * aBuffer.mFontParams.advanceDirection; 2086 if (aBuffer.mRunParams.isRTL) { 2087 inlineCoord += advance; 2088 } 2089 if (glyphData->IsMissing()) { 2090 if (!DrawMissingGlyph(aBuffer.mRunParams, aBuffer.mFontParams, 2091 details, *aPt)) { 2092 return false; 2093 } 2094 } else { 2095 gfx::Point glyphPt( 2096 *aPt + (aOffsetMatrix 2097 ? aOffsetMatrix->TransformPoint(details->mOffset) 2098 : details->mOffset)); 2099 DrawOneGlyph<FC>(details->mGlyphID, glyphPt, aBuffer, 2100 &emittedGlyphs); 2101 } 2102 if (!aBuffer.mRunParams.isRTL) { 2103 inlineCoord += advance; 2104 } 2105 } 2106 } 2107 } 2108 2109 if (S == SpacingT::HasSpacing) { 2110 float space = aBuffer.mRunParams.spacing[i].mAfter; 2111 if (i + 1 < aCount) { 2112 space += aBuffer.mRunParams.spacing[i + 1].mBefore; 2113 } 2114 space *= aBuffer.mFontParams.advanceDirection; 2115 inlineCoord += space; 2116 } 2117 } 2118 2119 return emittedGlyphs; 2120 } 2121 2122 // Draw an individual glyph at a specific location. 2123 // *aPt is the glyph position in appUnits; it is converted to device 2124 // coordinates (devPt) here. 2125 template <gfxFont::FontComplexityT FC> 2126 void gfxFont::DrawOneGlyph(uint32_t aGlyphID, const gfx::Point& aPt, 2127 GlyphBufferAzure& aBuffer, bool* aEmittedGlyphs) { 2128 const TextRunDrawParams& runParams(aBuffer.mRunParams); 2129 2130 gfx::Point devPt(ToDeviceUnits(aPt.x, runParams.devPerApp), 2131 ToDeviceUnits(aPt.y, runParams.devPerApp)); 2132 2133 auto* textDrawer = runParams.textDrawer; 2134 if (textDrawer) { 2135 // If the glyph is entirely outside the clip rect, we don't need to draw it 2136 // at all. (We check the font extents here rather than the individual glyph 2137 // bounds because that's cheaper to look up, and provides a conservative 2138 // "worst case" for where this glyph might want to draw.) 2139 LayoutDeviceRect extents = 2140 LayoutDeviceRect::FromUnknownRect(aBuffer.mFontParams.fontExtents); 2141 extents.MoveBy(LayoutDevicePoint::FromUnknownPoint(devPt)); 2142 if (!extents.Intersects(runParams.clipRect)) { 2143 return; 2144 } 2145 } 2146 2147 if (FC == FontComplexityT::ComplexFont) { 2148 const FontDrawParams& fontParams(aBuffer.mFontParams); 2149 2150 gfxContextMatrixAutoSaveRestore matrixRestore; 2151 2152 if (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont && 2153 !textDrawer) { 2154 // We have to flush each glyph individually when doing 2155 // synthetic-oblique for vertical-upright text, because 2156 // the skew transform needs to be applied to a separate 2157 // origin for each glyph, not once for the whole run. 2158 aBuffer.Flush(); 2159 matrixRestore.SetContext(runParams.context); 2160 gfx::Point skewPt( 2161 devPt.x + GetMetrics(nsFontMetrics::eVertical).emHeight / 2, devPt.y); 2162 gfx::Matrix mat = 2163 runParams.context->CurrentMatrix() 2164 .PreTranslate(skewPt) 2165 .PreMultiply(gfx::Matrix(1, fontParams.obliqueSkew, 0, 1, 0, 0)) 2166 .PreTranslate(-skewPt); 2167 runParams.context->SetMatrix(mat); 2168 } 2169 2170 if (fontParams.haveSVGGlyphs) { 2171 if (!runParams.paintSVGGlyphs) { 2172 return; 2173 } 2174 NS_WARNING_ASSERTION( 2175 runParams.drawMode != DrawMode::GLYPH_PATH, 2176 "Rendering SVG glyph despite request for glyph path"); 2177 if (RenderSVGGlyph(runParams.context, textDrawer, devPt, aGlyphID, 2178 fontParams.contextPaint, runParams.callbacks, 2179 *aEmittedGlyphs)) { 2180 return; 2181 } 2182 } 2183 2184 if (fontParams.haveColorGlyphs && !UseNativeColrFontSupport() && 2185 RenderColorGlyph(runParams.dt, runParams.context, textDrawer, 2186 fontParams, devPt, aGlyphID)) { 2187 return; 2188 } 2189 2190 aBuffer.OutputGlyph(aGlyphID, devPt); 2191 2192 // Synthetic bolding (if required) by multi-striking. 2193 for (int32_t i = 0; i < fontParams.extraStrikes; ++i) { 2194 if (fontParams.isVerticalFont) { 2195 devPt.y += fontParams.synBoldOnePixelOffset; 2196 } else { 2197 devPt.x += fontParams.synBoldOnePixelOffset; 2198 } 2199 aBuffer.OutputGlyph(aGlyphID, devPt); 2200 } 2201 2202 if (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont && 2203 !textDrawer) { 2204 aBuffer.Flush(); 2205 } 2206 } else { 2207 aBuffer.OutputGlyph(aGlyphID, devPt); 2208 } 2209 2210 *aEmittedGlyphs = true; 2211 } 2212 2213 bool gfxFont::DrawMissingGlyph(const TextRunDrawParams& aRunParams, 2214 const FontDrawParams& aFontParams, 2215 const gfxShapedText::DetailedGlyph* aDetails, 2216 const gfx::Point& aPt) { 2217 // Default-ignorable chars will have zero advance width; 2218 // we don't have to draw the hexbox for them. 2219 float advance = aDetails->mAdvance; 2220 if (aRunParams.drawMode != DrawMode::GLYPH_PATH && advance > 0) { 2221 auto* textDrawer = aRunParams.textDrawer; 2222 const Matrix* matPtr = nullptr; 2223 Matrix mat; 2224 if (textDrawer) { 2225 // Generate an orientation matrix for the current writing mode 2226 wr::FontInstanceFlags flags = textDrawer->GetWRGlyphFlags(); 2227 if (flags & wr::FontInstanceFlags::TRANSPOSE) { 2228 std::swap(mat._11, mat._12); 2229 std::swap(mat._21, mat._22); 2230 } 2231 mat.PostScale(flags & wr::FontInstanceFlags::FLIP_X ? -1.0f : 1.0f, 2232 flags & wr::FontInstanceFlags::FLIP_Y ? -1.0f : 1.0f); 2233 matPtr = &mat; 2234 } 2235 2236 Point pt(Float(ToDeviceUnits(aPt.x, aRunParams.devPerApp)), 2237 Float(ToDeviceUnits(aPt.y, aRunParams.devPerApp))); 2238 Float advanceDevUnits = Float(ToDeviceUnits(advance, aRunParams.devPerApp)); 2239 Float height = GetMetrics(nsFontMetrics::eHorizontal).maxAscent; 2240 // Horizontally center if drawing vertically upright with no sideways 2241 // transform. 2242 Rect glyphRect = 2243 aFontParams.isVerticalFont && !mat.HasNonAxisAlignedTransform() 2244 ? Rect(pt.x - height / 2, pt.y, height, advanceDevUnits) 2245 : Rect(pt.x, pt.y - height, advanceDevUnits, height); 2246 2247 // If there's a fake-italic skew in effect as part 2248 // of the drawTarget's transform, we need to undo 2249 // this before drawing the hexbox. (Bug 983985) 2250 gfxContextMatrixAutoSaveRestore matrixRestore; 2251 if (aFontParams.obliqueSkew != 0.0f && !aFontParams.isVerticalFont && 2252 !textDrawer) { 2253 matrixRestore.SetContext(aRunParams.context); 2254 gfx::Matrix mat = 2255 aRunParams.context->CurrentMatrix() 2256 .PreTranslate(pt) 2257 .PreMultiply(gfx::Matrix(1, 0, aFontParams.obliqueSkew, 1, 0, 0)) 2258 .PreTranslate(-pt); 2259 aRunParams.context->SetMatrix(mat); 2260 } 2261 2262 gfxFontMissingGlyphs::DrawMissingGlyph( 2263 aDetails->mGlyphID, glyphRect, *aRunParams.dt, 2264 PatternFromState(aRunParams.context), matPtr); 2265 } 2266 return true; 2267 } 2268 2269 // This method is mostly parallel to DrawGlyphs. 2270 void gfxFont::DrawEmphasisMarks(const gfxTextRun* aShapedText, gfx::Point* aPt, 2271 uint32_t aOffset, uint32_t aCount, 2272 const EmphasisMarkDrawParams& aParams) { 2273 float& inlineCoord = aParams.isVertical ? aPt->y.value : aPt->x.value; 2274 gfxTextRun::Range markRange(aParams.mark); 2275 gfxTextRun::DrawParams params(aParams.context, aParams.paletteCache); 2276 2277 float clusterStart = -std::numeric_limits<float>::infinity(); 2278 bool shouldDrawEmphasisMark = false; 2279 for (uint32_t i = 0, idx = aOffset; i < aCount; ++i, ++idx) { 2280 if (aParams.spacing) { 2281 inlineCoord += aParams.direction * aParams.spacing[i].mBefore; 2282 } 2283 if (aShapedText->IsClusterStart(idx) || 2284 clusterStart == -std::numeric_limits<float>::infinity()) { 2285 clusterStart = inlineCoord; 2286 } 2287 if (aShapedText->CharMayHaveEmphasisMark(idx)) { 2288 shouldDrawEmphasisMark = true; 2289 } 2290 inlineCoord += aParams.direction * aShapedText->GetAdvanceForGlyph(idx); 2291 if (shouldDrawEmphasisMark && 2292 (i + 1 == aCount || aShapedText->IsClusterStart(idx + 1))) { 2293 float clusterAdvance = inlineCoord - clusterStart; 2294 // Move the coord backward to get the needed start point. 2295 float delta = (clusterAdvance + aParams.advance) / 2; 2296 inlineCoord -= delta; 2297 aParams.mark->Draw(markRange, *aPt, params); 2298 inlineCoord += delta; 2299 shouldDrawEmphasisMark = false; 2300 } 2301 if (aParams.spacing) { 2302 inlineCoord += aParams.direction * aParams.spacing[i].mAfter; 2303 } 2304 } 2305 } 2306 2307 void gfxFont::Draw(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd, 2308 gfx::Point* aPt, TextRunDrawParams& aRunParams, 2309 gfx::ShapedTextFlags aOrientation) { 2310 NS_ASSERTION(aRunParams.drawMode == DrawMode::GLYPH_PATH || 2311 !(int(aRunParams.drawMode) & int(DrawMode::GLYPH_PATH)), 2312 "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or " 2313 "GLYPH_STROKE_UNDERNEATH"); 2314 2315 if (aStart >= aEnd) { 2316 return; 2317 } 2318 2319 FontDrawParams fontParams; 2320 2321 if (aRunParams.drawOpts) { 2322 fontParams.drawOptions = *aRunParams.drawOpts; 2323 } 2324 2325 fontParams.scaledFont = GetScaledFont(aRunParams); 2326 if (!fontParams.scaledFont) { 2327 return; 2328 } 2329 auto* textDrawer = aRunParams.textDrawer; 2330 2331 fontParams.obliqueSkew = SkewForSyntheticOblique(); 2332 fontParams.haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this); 2333 fontParams.haveColorGlyphs = GetFontEntry()->TryGetColorGlyphs(); 2334 fontParams.hasTextShadow = aRunParams.hasTextShadow; 2335 fontParams.contextPaint = aRunParams.runContextPaint; 2336 2337 if (fontParams.haveColorGlyphs && !UseNativeColrFontSupport()) { 2338 DeviceColor ctxColor; 2339 fontParams.currentColor = aRunParams.context->GetDeviceColor(ctxColor) 2340 ? sRGBColor::FromABGR(ctxColor.ToABGR()) 2341 : sRGBColor::OpaqueBlack(); 2342 fontParams.palette = aRunParams.paletteCache.GetPaletteFor( 2343 GetFontEntry(), aRunParams.fontPalette); 2344 } 2345 2346 if (textDrawer) { 2347 fontParams.isVerticalFont = aRunParams.isVerticalRun; 2348 } else { 2349 fontParams.isVerticalFont = 2350 aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; 2351 } 2352 2353 gfxContextMatrixAutoSaveRestore matrixRestore; 2354 layout::TextDrawTarget::AutoRestoreWRGlyphFlags glyphFlagsRestore; 2355 2356 // Save the current baseline offset for restoring later, in case it is 2357 // modified. 2358 float& baseline = fontParams.isVerticalFont ? aPt->x.value : aPt->y.value; 2359 float origBaseline = baseline; 2360 2361 // The point may be advanced in local-space, while the resulting point on 2362 // return must be advanced in transformed space. So save the original point so 2363 // we can properly transform the advance later. 2364 gfx::Point origPt = *aPt; 2365 const gfx::Matrix* offsetMatrix = nullptr; 2366 2367 // Default to advancing along the +X direction (-X if RTL). 2368 fontParams.advanceDirection = aRunParams.isRTL ? -1.0f : 1.0f; 2369 // Default to offsetting baseline downward along the +Y direction. 2370 float baselineDir = 1.0f; 2371 // The direction of sideways rotation, if applicable. 2372 // -1 for rotating left/counter-clockwise 2373 // 1 for rotating right/clockwise 2374 // 0 for no rotation 2375 float sidewaysDir = 2376 (aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT 2377 ? -1.0f 2378 : (aOrientation == 2379 gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT 2380 ? 1.0f 2381 : 0.0f)); 2382 // If we're rendering a sideways run, we need to push a rotation transform to 2383 // the context. 2384 if (sidewaysDir != 0.0f) { 2385 if (textDrawer) { 2386 // For WebRender, we can't use a DrawTarget transform and must instead use 2387 // flags that locally transform the glyph, without affecting the glyph 2388 // origin. The glyph origins must thus be offset in the transformed 2389 // directions (instead of local-space directions). Modify the advance and 2390 // baseline directions to account for the indicated transform. 2391 2392 // The default text orientation is down being +Y and right being +X. 2393 // Rotating 90 degrees left/CCW makes down be +X and right be -Y. 2394 // Rotating 90 degrees right/CW makes down be -X and right be +Y. 2395 // Thus the advance direction (moving right) is just sidewaysDir, 2396 // i.e. negative along Y axis if rotated left and positive if 2397 // rotated right. 2398 fontParams.advanceDirection *= sidewaysDir; 2399 // The baseline direction (moving down) is negated relative to the 2400 // advance direction for sideways transforms. 2401 baselineDir *= -sidewaysDir; 2402 2403 glyphFlagsRestore.Save(textDrawer); 2404 // Set the transform flags accordingly. Both sideways rotations transpose 2405 // X and Y, while left rotation flips the resulting Y axis, and right 2406 // rotation flips the resulting X axis. 2407 textDrawer->SetWRGlyphFlags( 2408 textDrawer->GetWRGlyphFlags() | wr::FontInstanceFlags::TRANSPOSE | 2409 (aOrientation == 2410 gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT 2411 ? wr::FontInstanceFlags::FLIP_Y 2412 : wr::FontInstanceFlags::FLIP_X)); 2413 // We also need to set up a transform for the glyph offset vector that 2414 // may be present in DetailedGlyph records. 2415 static const gfx::Matrix kSidewaysLeft = {0, -1, 1, 0, 0, 0}; 2416 static const gfx::Matrix kSidewaysRight = {0, 1, -1, 0, 0, 0}; 2417 offsetMatrix = 2418 (aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT) 2419 ? &kSidewaysLeft 2420 : &kSidewaysRight; 2421 } else { 2422 // For non-WebRender targets, just push a rotation transform. 2423 matrixRestore.SetContext(aRunParams.context); 2424 gfxPoint p(aPt->x * aRunParams.devPerApp, aPt->y * aRunParams.devPerApp); 2425 // Get a matrix we can use to draw the (horizontally-shaped) textrun 2426 // with 90-degree CW rotation. 2427 const gfxFloat rotation = sidewaysDir * M_PI / 2.0f; 2428 gfxMatrix mat = aRunParams.context->CurrentMatrixDouble() 2429 .PreTranslate(p) 2430 . // translate origin for rotation 2431 PreRotate(rotation) 2432 . // turn 90deg CCW (sideways-left) or CW (*-right) 2433 PreTranslate(-p); // undo the translation 2434 2435 aRunParams.context->SetMatrixDouble(mat); 2436 } 2437 2438 // If we're drawing rotated horizontal text for an element styled 2439 // text-orientation:mixed, the dominant baseline will be vertical- 2440 // centered. So in this case, we need to adjust the position so that 2441 // the rotated horizontal text (which uses an alphabetic baseline) will 2442 // look OK when juxtaposed with upright glyphs (rendered on a centered 2443 // vertical baseline). The adjustment here is somewhat ad hoc; we 2444 // should eventually look for baseline tables[1] in the fonts and use 2445 // those if available. 2446 // [1] See http://www.microsoft.com/typography/otspec/base.htm 2447 if (aTextRun->UseCenterBaseline()) { 2448 const Metrics& metrics = GetMetrics(nsFontMetrics::eHorizontal); 2449 float baseAdj = (metrics.emAscent - metrics.emDescent) / 2; 2450 baseline += baseAdj * aTextRun->GetAppUnitsPerDevUnit() * baselineDir; 2451 } 2452 } else if (textDrawer && 2453 aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) { 2454 glyphFlagsRestore.Save(textDrawer); 2455 textDrawer->SetWRGlyphFlags(textDrawer->GetWRGlyphFlags() | 2456 wr::FontInstanceFlags::VERTICAL); 2457 } 2458 2459 if (fontParams.obliqueSkew != 0.0f && !fontParams.isVerticalFont && 2460 !textDrawer) { 2461 // Adjust matrix for synthetic-oblique, except if we're doing vertical- 2462 // upright text, in which case this will be handled for each glyph 2463 // individually in DrawOneGlyph. 2464 if (!matrixRestore.HasMatrix()) { 2465 matrixRestore.SetContext(aRunParams.context); 2466 } 2467 gfx::Point p(aPt->x * aRunParams.devPerApp, aPt->y * aRunParams.devPerApp); 2468 gfx::Matrix mat = 2469 aRunParams.context->CurrentMatrix() 2470 .PreTranslate(p) 2471 .PreMultiply(gfx::Matrix(1, 0, -fontParams.obliqueSkew, 1, 0, 0)) 2472 .PreTranslate(-p); 2473 aRunParams.context->SetMatrix(mat); 2474 } 2475 2476 RefPtr<SVGContextPaint> contextPaint; 2477 if (fontParams.haveSVGGlyphs && !fontParams.contextPaint) { 2478 // If no pattern is specified for fill, use the current pattern 2479 NS_ASSERTION((int(aRunParams.drawMode) & int(DrawMode::GLYPH_STROKE)) == 0, 2480 "no pattern supplied for stroking text"); 2481 RefPtr<gfxPattern> fillPattern = aRunParams.context->GetPattern(); 2482 contextPaint = new SimpleTextContextPaint( 2483 fillPattern, nullptr, aRunParams.context->CurrentMatrixDouble()); 2484 fontParams.contextPaint = contextPaint.get(); 2485 } 2486 2487 // Synthetic-bold strikes are each offset one device pixel in run direction 2488 // (these values are only needed if ApplySyntheticBold() is true). 2489 // If drawing via webrender, it will do multistrike internally so we don't 2490 // need to handle it here. 2491 bool doMultistrikeBold = ApplySyntheticBold() && !textDrawer; 2492 if (doMultistrikeBold) { 2493 // For screen display, we want to try and repeat strikes with an offset of 2494 // one device pixel, accounting for zoom or other transforms that may be 2495 // in effect, so compute x-axis scale factor from the drawtarget. 2496 // However, when generating PDF output the drawtarget's transform does not 2497 // really bear any relation to "device pixels", and may result in an 2498 // excessively large offset relative to the font size (bug 1823888), so 2499 // we limit it based on the used font size to avoid this. 2500 // The constant 48.0 reflects the threshold where the calculation in 2501 // gfxFont::GetSyntheticBoldOffset() switches to a simple origin-based 2502 // slope, though the exact value is somewhat arbitrary; it's selected to 2503 // allow a visible amount of boldness while preventing the offset from 2504 // becoming "large" in relation to the glyphs. 2505 Float xscale = 2506 std::min<Float>(GetAdjustedSize() / 48.0, 2507 CalcXScale(aRunParams.context->GetDrawTarget())); 2508 fontParams.synBoldOnePixelOffset = aRunParams.direction * xscale; 2509 if (xscale != 0.0) { 2510 static const int32_t kMaxExtraStrikes = 128; 2511 gfxFloat extraStrikes = GetSyntheticBoldOffset() / xscale; 2512 if (extraStrikes > kMaxExtraStrikes) { 2513 // if too many strikes are required, limit them and increase the step 2514 // size to compensate 2515 fontParams.extraStrikes = kMaxExtraStrikes; 2516 fontParams.synBoldOnePixelOffset = aRunParams.direction * 2517 GetSyntheticBoldOffset() / 2518 fontParams.extraStrikes; 2519 } else { 2520 // use as many strikes as needed for the increased advance 2521 fontParams.extraStrikes = NS_lroundf(std::max(1.0, extraStrikes)); 2522 } 2523 } else { 2524 // Degenerate transform?! 2525 fontParams.extraStrikes = 0; 2526 } 2527 } else { 2528 fontParams.synBoldOnePixelOffset = 0; 2529 fontParams.extraStrikes = 0; 2530 } 2531 2532 // Figure out the maximum extents for the font, accounting for synthetic 2533 // oblique and bold. 2534 if (mFUnitsConvFactor > 0.0) { 2535 fontParams.fontExtents = GetFontEntry()->GetFontExtents(mFUnitsConvFactor); 2536 } else { 2537 // Was it not an sfnt? Maybe on Linux... use arbitrary huge extents, so we 2538 // don't inadvertently clip stuff. A bit less efficient than true extents, 2539 // but this should be extremely rare. 2540 auto size = GetAdjustedSize(); 2541 fontParams.fontExtents = Rect(-2 * size, -2 * size, 5 * size, 5 * size); 2542 } 2543 if (fontParams.obliqueSkew != 0.0f) { 2544 gfx::Point p(fontParams.fontExtents.x, fontParams.fontExtents.y); 2545 gfx::Matrix skew(1, 0, fontParams.obliqueSkew, 1, 0, 0); 2546 fontParams.fontExtents = skew.TransformBounds(fontParams.fontExtents); 2547 } 2548 if (fontParams.extraStrikes) { 2549 if (fontParams.isVerticalFont) { 2550 fontParams.fontExtents.height += 2551 float(fontParams.extraStrikes) * fontParams.synBoldOnePixelOffset; 2552 } else { 2553 fontParams.fontExtents.width += 2554 float(fontParams.extraStrikes) * fontParams.synBoldOnePixelOffset; 2555 } 2556 } 2557 2558 bool oldSubpixelAA = aRunParams.dt->GetPermitSubpixelAA(); 2559 if (!AllowSubpixelAA()) { 2560 aRunParams.dt->SetPermitSubpixelAA(false); 2561 } 2562 2563 Matrix mat; 2564 Matrix oldMat = aRunParams.dt->GetTransform(); 2565 2566 fontParams.drawOptions.mAntialiasMode = Get2DAAMode(mAntialiasOption); 2567 2568 if (mStyle.baselineOffset != 0.0) { 2569 baseline += 2570 mStyle.baselineOffset * aTextRun->GetAppUnitsPerDevUnit() * baselineDir; 2571 } 2572 2573 bool emittedGlyphs; 2574 { 2575 // Select appropriate version of the templated DrawGlyphs method 2576 // to output glyphs to the buffer, depending on complexity needed 2577 // for the type of font, and whether added inter-glyph spacing 2578 // is specified. 2579 GlyphBufferAzure buffer(aRunParams, fontParams); 2580 if (fontParams.haveSVGGlyphs || fontParams.haveColorGlyphs || 2581 fontParams.extraStrikes || 2582 (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont && 2583 !textDrawer)) { 2584 if (aRunParams.spacing) { 2585 emittedGlyphs = 2586 DrawGlyphs<FontComplexityT::ComplexFont, SpacingT::HasSpacing>( 2587 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer); 2588 } else { 2589 emittedGlyphs = 2590 DrawGlyphs<FontComplexityT::ComplexFont, SpacingT::NoSpacing>( 2591 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer); 2592 } 2593 } else { 2594 if (aRunParams.spacing) { 2595 emittedGlyphs = 2596 DrawGlyphs<FontComplexityT::SimpleFont, SpacingT::HasSpacing>( 2597 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer); 2598 } else { 2599 emittedGlyphs = 2600 DrawGlyphs<FontComplexityT::SimpleFont, SpacingT::NoSpacing>( 2601 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer); 2602 } 2603 } 2604 } 2605 2606 baseline = origBaseline; 2607 2608 if (aRunParams.callbacks && emittedGlyphs) { 2609 aRunParams.callbacks->NotifyGlyphPathEmitted(); 2610 } 2611 2612 aRunParams.dt->SetTransform(oldMat); 2613 aRunParams.dt->SetPermitSubpixelAA(oldSubpixelAA); 2614 2615 if (sidewaysDir != 0.0f && !textDrawer) { 2616 // Adjust updated aPt to account for the transform we were using. 2617 // The advance happened horizontally in local-space, but the transformed 2618 // sideways advance is actually vertical, with sign depending on the 2619 // direction of rotation. 2620 float advance = aPt->x - origPt.x; 2621 *aPt = gfx::Point(origPt.x, origPt.y + advance * sidewaysDir); 2622 } 2623 } 2624 2625 bool gfxFont::RenderSVGGlyph(gfxContext* aContext, 2626 layout::TextDrawTarget* aTextDrawer, 2627 gfx::Point aPoint, uint32_t aGlyphId, 2628 SVGContextPaint* aContextPaint) const { 2629 if (!GetFontEntry()->HasSVGGlyph(aGlyphId)) { 2630 return false; 2631 } 2632 2633 if (aTextDrawer) { 2634 // WebRender doesn't support SVG Glyphs. 2635 // (pretend to succeed, output doesn't matter, we will emit a blob) 2636 aTextDrawer->FoundUnsupportedFeature(); 2637 return true; 2638 } 2639 2640 const gfxFloat devUnitsPerSVGUnit = 2641 GetAdjustedSize() / GetFontEntry()->UnitsPerEm(); 2642 gfxContextMatrixAutoSaveRestore matrixRestore(aContext); 2643 2644 aContext->SetMatrix(aContext->CurrentMatrix() 2645 .PreTranslate(aPoint.x, aPoint.y) 2646 .PreScale(devUnitsPerSVGUnit, devUnitsPerSVGUnit)); 2647 2648 aContextPaint->InitStrokeGeometry(aContext, devUnitsPerSVGUnit); 2649 2650 GetFontEntry()->RenderSVGGlyph(aContext, aGlyphId, aContextPaint); 2651 aContext->NewPath(); 2652 return true; 2653 } 2654 2655 bool gfxFont::RenderSVGGlyph(gfxContext* aContext, 2656 layout::TextDrawTarget* aTextDrawer, 2657 gfx::Point aPoint, uint32_t aGlyphId, 2658 SVGContextPaint* aContextPaint, 2659 gfxTextRunDrawCallbacks* aCallbacks, 2660 bool& aEmittedGlyphs) const { 2661 if (aCallbacks && aEmittedGlyphs) { 2662 aCallbacks->NotifyGlyphPathEmitted(); 2663 aEmittedGlyphs = false; 2664 } 2665 return RenderSVGGlyph(aContext, aTextDrawer, aPoint, aGlyphId, aContextPaint); 2666 } 2667 2668 bool gfxFont::RenderColorGlyph(DrawTarget* aDrawTarget, gfxContext* aContext, 2669 layout::TextDrawTarget* aTextDrawer, 2670 const FontDrawParams& aFontParams, 2671 const Point& aPoint, uint32_t aGlyphId) { 2672 if (aTextDrawer && aFontParams.hasTextShadow) { 2673 aTextDrawer->FoundUnsupportedFeature(); 2674 return true; 2675 } 2676 2677 auto* colr = GetFontEntry()->GetCOLR(); 2678 const auto* paintGraph = COLRFonts::GetGlyphPaintGraph(colr, aGlyphId); 2679 const gfxHarfBuzzShaper* hbShaper = nullptr; 2680 if (paintGraph) { 2681 // We need the hbShaper to get color glyph bounds, so check that it's 2682 // usable. 2683 hbShaper = GetHarfBuzzShaper(); 2684 if (!hbShaper && !hbShaper->IsInitialized()) { 2685 return false; 2686 } 2687 if (aTextDrawer) { 2688 aTextDrawer->FoundUnsupportedFeature(); 2689 return true; 2690 } 2691 } 2692 const auto* layers = 2693 paintGraph ? nullptr : COLRFonts::GetGlyphLayers(colr, aGlyphId); 2694 2695 if (!paintGraph && !layers) { 2696 return false; 2697 } 2698 2699 // For reasonable font sizes, use a cache of rasterized glyphs. 2700 bool useCache = GetAdjustedSize() <= 256.0; 2701 2702 // If the composition op is not OVER, rasterize to a temporary surface 2703 // and then composite to the destination, even if we're not caching. 2704 // But we can't do this if the target is a TextDrawTarget, as it doesn't 2705 // support DrawSurface. 2706 RefPtr<SourceSurface> snapshot; 2707 if ((useCache || 2708 aFontParams.drawOptions.mCompositionOp != CompositionOp::OP_OVER) && 2709 aDrawTarget->GetBackendType() != BackendType::WEBRENDER_TEXT) { 2710 AutoWriteLock lock(mLock); 2711 if (!mColorGlyphCache && useCache) { 2712 mColorGlyphCache = MakeUnique<ColorGlyphCache>(); 2713 } 2714 2715 Rect bounds; 2716 if (paintGraph) { 2717 bounds = COLRFonts::GetColorGlyphBounds( 2718 colr, hbShaper->GetHBFont(), aGlyphId, aDrawTarget, 2719 aFontParams.scaledFont, mFUnitsConvFactor); 2720 } else { 2721 bounds = GetFontEntry()->GetFontExtents(mFUnitsConvFactor); 2722 } 2723 bounds.RoundOut(); 2724 2725 // Tell the cache what colors we're using; if they have changed, it 2726 // will discard any currently-cached entries. 2727 HashMap<uint32_t, RefPtr<SourceSurface>>::AddPtr cached; 2728 if (useCache) { 2729 mColorGlyphCache->SetColors(aFontParams.currentColor, 2730 aFontParams.palette); 2731 cached = mColorGlyphCache->mCache.lookupForAdd(aGlyphId); 2732 if (cached) { 2733 snapshot = cached->value(); 2734 } 2735 } 2736 2737 const int kScale = 2; 2738 if (!snapshot) { 2739 // Create a temporary DrawTarget and render the glyph to it. 2740 IntSize size(int(bounds.width), int(bounds.height)); 2741 SurfaceFormat format = SurfaceFormat::B8G8R8A8; 2742 RefPtr target = 2743 Factory::CreateDrawTarget(BackendType::SKIA, size * kScale, format); 2744 if (target) { 2745 // Use OP_OVER and opaque alpha to create the glyph snapshot. 2746 Matrix m; 2747 m.PreScale(kScale, kScale); 2748 target->SetTransform(m); 2749 DrawOptions drawOptions(aFontParams.drawOptions); 2750 drawOptions.mCompositionOp = CompositionOp::OP_OVER; 2751 drawOptions.mAlpha = 1.0f; 2752 bool ok = false; 2753 if (paintGraph) { 2754 ok = COLRFonts::PaintGlyphGraph( 2755 colr, hbShaper->GetHBFont(), paintGraph, target, nullptr, 2756 aFontParams.scaledFont, drawOptions, -bounds.TopLeft(), 2757 aFontParams.currentColor, aFontParams.palette->Colors(), aGlyphId, 2758 mFUnitsConvFactor); 2759 } else { 2760 auto face(GetFontEntry()->GetHBFace()); 2761 ok = COLRFonts::PaintGlyphLayers( 2762 colr, face, layers, target, nullptr, aFontParams.scaledFont, 2763 drawOptions, -bounds.TopLeft(), aFontParams.currentColor, 2764 aFontParams.palette->Colors()); 2765 } 2766 if (ok) { 2767 snapshot = target->Snapshot(); 2768 if (useCache) { 2769 // Save a snapshot of the rendering in the cache. 2770 // (We ignore potential failure here, and just paint the snapshot 2771 // without caching it.) 2772 (void)mColorGlyphCache->mCache.add(cached, aGlyphId, snapshot); 2773 } 2774 } 2775 } 2776 } 2777 if (snapshot) { 2778 // Paint the snapshot using the appropriate composition op. 2779 Point snappedPoint = Point(roundf(aPoint.x), roundf(aPoint.y)); 2780 aDrawTarget->DrawSurface( 2781 snapshot, Rect(snappedPoint + bounds.TopLeft(), bounds.Size()), 2782 Rect(Point(), bounds.Size() * kScale), DrawSurfaceOptions(), 2783 aFontParams.drawOptions); 2784 return true; 2785 } 2786 } 2787 2788 // If we didn't paint from a cached or temporary snapshot, just render 2789 // directly to the destination drawTarget. 2790 if (paintGraph) { 2791 return COLRFonts::PaintGlyphGraph( 2792 colr, hbShaper->GetHBFont(), paintGraph, aDrawTarget, aTextDrawer, 2793 aFontParams.scaledFont, aFontParams.drawOptions, aPoint, 2794 aFontParams.currentColor, aFontParams.palette->Colors(), aGlyphId, 2795 mFUnitsConvFactor); 2796 } 2797 2798 if (layers) { 2799 auto face(GetFontEntry()->GetHBFace()); 2800 return COLRFonts::PaintGlyphLayers( 2801 colr, face, layers, aDrawTarget, aTextDrawer, aFontParams.scaledFont, 2802 aFontParams.drawOptions, aPoint, aFontParams.currentColor, 2803 aFontParams.palette->Colors()); 2804 } 2805 2806 return false; 2807 } 2808 2809 void gfxFont::ColorGlyphCache::SetColors(sRGBColor aCurrentColor, 2810 FontPalette* aPalette) { 2811 if (aCurrentColor != mCurrentColor || aPalette != mPalette) { 2812 mCache.clear(); 2813 mCurrentColor = aCurrentColor; 2814 mPalette = aPalette; 2815 } 2816 } 2817 2818 bool gfxFont::HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh) { 2819 // Bitmap fonts are assumed to provide "color" glyphs for all supported chars. 2820 gfxFontEntry* fe = GetFontEntry(); 2821 if (fe->HasColorBitmapTable()) { 2822 return true; 2823 } 2824 // Use harfbuzz shaper to look up the default glyph ID for the character. 2825 auto* shaper = GetHarfBuzzShaper(); 2826 if (!shaper) { 2827 return false; 2828 } 2829 uint32_t gid = 0; 2830 if (gfxFontUtils::IsVarSelector(aNextCh)) { 2831 gid = shaper->GetVariationGlyph(aCh, aNextCh); 2832 if (gid) { 2833 if (aNextCh == kVariationSelector16) { 2834 // If the font explicitly supports the character + VS16, we accept it 2835 // as implying it will provide an emoji-style glyph. 2836 return true; 2837 } 2838 if (aNextCh == kVariationSelector15) { 2839 // Explicit support for VS15 implies a text-style glyph. 2840 return false; 2841 } 2842 } 2843 } 2844 gid = shaper->GetNominalGlyph(aCh); 2845 if (!gid) { 2846 return false; 2847 } 2848 2849 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1801521: 2850 // Emoji special-case: flag sequences NOT based on Regional Indicator pairs 2851 // use the BLACK FLAG character plus a series of plane-14 TAG LETTERs, e.g. 2852 // England = <black-flag, tag-G, tag-B, tag-E, tag-N, tag-G, tag-cancel> 2853 // Here, we don't check for support of the entire sequence (too much 2854 // expensive lookahead), but we check that the font at least supports the 2855 // first of the tag letter codes, because if it doesn't, we're at risk of 2856 // just getting an undifferentiated black flag glyph. 2857 if (gfxFontUtils::IsEmojiFlagAndTag(aCh, aNextCh)) { 2858 if (!shaper->GetNominalGlyph(aNextCh)) { 2859 return false; 2860 } 2861 } 2862 2863 // Check if there is a COLR/CPAL or SVG glyph for this ID. 2864 if (fe->TryGetColorGlyphs() && 2865 (COLRFonts::GetGlyphPaintGraph(fe->GetCOLR(), gid) || 2866 COLRFonts::GetGlyphLayers(fe->GetCOLR(), gid))) { 2867 return true; 2868 } 2869 if (fe->TryGetSVGData(this) && fe->HasSVGGlyph(gid)) { 2870 return true; 2871 } 2872 return false; 2873 } 2874 2875 static void UnionRange(gfxFloat aX, gfxFloat* aDestMin, gfxFloat* aDestMax) { 2876 *aDestMin = std::min(*aDestMin, aX); 2877 *aDestMax = std::max(*aDestMax, aX); 2878 } 2879 2880 // We get precise glyph extents if the textrun creator requested them, or 2881 // if the font is a user font --- in which case the author may be relying 2882 // on overflowing glyphs. 2883 static bool NeedsGlyphExtents(gfxFont* aFont, const gfxTextRun* aTextRun) { 2884 return (aTextRun->GetFlags() & 2885 gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX) || 2886 aFont->GetFontEntry()->IsUserFont(); 2887 } 2888 2889 bool gfxFont::IsSpaceGlyphInvisible(DrawTarget* aRefDrawTarget, 2890 const gfxTextRun* aTextRun) { 2891 gfxFontEntry::LazyFlag flag = mFontEntry->mSpaceGlyphIsInvisible; 2892 if (flag == gfxFontEntry::LazyFlag::Uninitialized && 2893 GetAdjustedSize() >= 1.0) { 2894 gfxGlyphExtents* extents = 2895 GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit()); 2896 gfxRect glyphExtents; 2897 flag = extents->GetTightGlyphExtentsAppUnits( 2898 this, aRefDrawTarget, GetSpaceGlyph(), &glyphExtents) && 2899 glyphExtents.IsEmpty() 2900 ? gfxFontEntry::LazyFlag::Yes 2901 : gfxFontEntry::LazyFlag::No; 2902 mFontEntry->mSpaceGlyphIsInvisible = flag; 2903 } 2904 return flag == gfxFontEntry::LazyFlag::Yes; 2905 } 2906 2907 bool gfxFont::MeasureGlyphs(const gfxTextRun* aTextRun, uint32_t aStart, 2908 uint32_t aEnd, BoundingBoxType aBoundingBoxType, 2909 DrawTarget* aRefDrawTarget, Spacing* aSpacing, 2910 gfxGlyphExtents* aExtents, bool aIsRTL, 2911 bool aNeedsGlyphExtents, RunMetrics& aMetrics, 2912 gfxFloat* aAdvanceMin, gfxFloat* aAdvanceMax) { 2913 const gfxTextRun::CompressedGlyph* charGlyphs = 2914 aTextRun->GetCharacterGlyphs(); 2915 uint32_t spaceGlyph = GetSpaceGlyph(); 2916 bool allGlyphsInvisible = true; 2917 2918 AutoReadLock lock(aExtents->mLock); 2919 2920 double x = 0; 2921 for (uint32_t i = aStart; i < aEnd; ++i) { 2922 if (aSpacing) { 2923 x += aSpacing->mBefore; 2924 } 2925 const gfxTextRun::CompressedGlyph* glyphData = &charGlyphs[i]; 2926 if (glyphData->IsSimpleGlyph()) { 2927 double advance = glyphData->GetSimpleAdvance(); 2928 uint32_t glyphIndex = glyphData->GetSimpleGlyph(); 2929 if (allGlyphsInvisible) { 2930 if (glyphIndex != spaceGlyph) { 2931 allGlyphsInvisible = false; 2932 } else { 2933 gfxFontEntry::LazyFlag flag = mFontEntry->mSpaceGlyphIsInvisible; 2934 if (flag == gfxFontEntry::LazyFlag::Uninitialized && 2935 GetAdjustedSize() >= 1.0) { 2936 gfxRect glyphExtents; 2937 flag = aExtents->GetTightGlyphExtentsAppUnitsLocked( 2938 this, aRefDrawTarget, spaceGlyph, &glyphExtents) && 2939 glyphExtents.IsEmpty() 2940 ? gfxFontEntry::LazyFlag::Yes 2941 : gfxFontEntry::LazyFlag::No; 2942 mFontEntry->mSpaceGlyphIsInvisible = flag; 2943 } 2944 if (flag == gfxFontEntry::LazyFlag::No) { 2945 allGlyphsInvisible = false; 2946 } 2947 } 2948 } 2949 // Only get the real glyph horizontal extent if we were asked 2950 // for the tight bounding box or we're in quality mode 2951 if (aBoundingBoxType != LOOSE_INK_EXTENTS || aNeedsGlyphExtents) { 2952 uint16_t extentsWidth = 2953 aExtents->GetContainedGlyphWidthAppUnitsLocked(glyphIndex); 2954 if (extentsWidth != gfxGlyphExtents::INVALID_WIDTH && 2955 aBoundingBoxType == LOOSE_INK_EXTENTS) { 2956 UnionRange(x, aAdvanceMin, aAdvanceMax); 2957 UnionRange(x + extentsWidth, aAdvanceMin, aAdvanceMax); 2958 } else { 2959 gfxRect glyphRect; 2960 if (!aExtents->GetTightGlyphExtentsAppUnitsLocked( 2961 this, aRefDrawTarget, glyphIndex, &glyphRect)) { 2962 glyphRect = gfxRect(0, aMetrics.mBoundingBox.Y(), advance, 2963 aMetrics.mBoundingBox.Height()); 2964 } 2965 if (aIsRTL) { 2966 // In effect, swap left and right sidebearings of the glyph, for 2967 // proper accumulation of potentially-overlapping glyph rects. 2968 glyphRect.MoveToX(advance - glyphRect.XMost()); 2969 } 2970 glyphRect.MoveByX(x); 2971 aMetrics.mBoundingBox = aMetrics.mBoundingBox.Union(glyphRect); 2972 } 2973 } 2974 x += advance; 2975 } else { 2976 allGlyphsInvisible = false; 2977 uint32_t glyphCount = glyphData->GetGlyphCount(); 2978 if (glyphCount > 0) { 2979 const gfxTextRun::DetailedGlyph* details = 2980 aTextRun->GetDetailedGlyphs(i); 2981 NS_ASSERTION(details != nullptr, 2982 "detailedGlyph record should not be missing!"); 2983 uint32_t j; 2984 for (j = 0; j < glyphCount; ++j, ++details) { 2985 uint32_t glyphIndex = details->mGlyphID; 2986 double advance = details->mAdvance; 2987 gfxRect glyphRect; 2988 if (glyphData->IsMissing() || 2989 !aExtents->GetTightGlyphExtentsAppUnitsLocked( 2990 this, aRefDrawTarget, glyphIndex, &glyphRect)) { 2991 // We might have failed to get glyph extents due to 2992 // OOM or something 2993 glyphRect = gfxRect(0, -aMetrics.mAscent, advance, 2994 aMetrics.mAscent + aMetrics.mDescent); 2995 } 2996 if (aIsRTL) { 2997 // Swap left/right sidebearings of the glyph, because we're doing 2998 // mirrored measurement. 2999 glyphRect.MoveToX(advance - glyphRect.XMost()); 3000 // Move to current x position, mirroring any x-offset amount. 3001 glyphRect.MoveByX(x - details->mOffset.x); 3002 } else { 3003 glyphRect.MoveByX(x + details->mOffset.x); 3004 } 3005 glyphRect.MoveByY(details->mOffset.y); 3006 aMetrics.mBoundingBox = aMetrics.mBoundingBox.Union(glyphRect); 3007 x += advance; 3008 } 3009 } 3010 } 3011 if (aSpacing) { 3012 x += aSpacing->mAfter; 3013 ++aSpacing; 3014 } 3015 } 3016 3017 aMetrics.mAdvanceWidth = x; 3018 return allGlyphsInvisible; 3019 } 3020 3021 bool gfxFont::MeasureGlyphs(const gfxTextRun* aTextRun, uint32_t aStart, 3022 uint32_t aEnd, BoundingBoxType aBoundingBoxType, 3023 DrawTarget* aRefDrawTarget, Spacing* aSpacing, 3024 bool aIsRTL, RunMetrics& aMetrics) { 3025 const gfxTextRun::CompressedGlyph* charGlyphs = 3026 aTextRun->GetCharacterGlyphs(); 3027 double x = 0; 3028 if (aSpacing) { 3029 x += aSpacing[0].mBefore; 3030 } 3031 uint32_t spaceGlyph = GetSpaceGlyph(); 3032 bool allGlyphsInvisible = true; 3033 3034 for (uint32_t i = aStart; i < aEnd; ++i) { 3035 const gfxTextRun::CompressedGlyph* glyphData = &charGlyphs[i]; 3036 if (glyphData->IsSimpleGlyph()) { 3037 double advance = glyphData->GetSimpleAdvance(); 3038 uint32_t glyphIndex = glyphData->GetSimpleGlyph(); 3039 if (allGlyphsInvisible && 3040 (glyphIndex != spaceGlyph || 3041 !IsSpaceGlyphInvisible(aRefDrawTarget, aTextRun))) { 3042 allGlyphsInvisible = false; 3043 } 3044 x += advance; 3045 } else { 3046 allGlyphsInvisible = false; 3047 uint32_t glyphCount = glyphData->GetGlyphCount(); 3048 if (glyphCount > 0) { 3049 const gfxTextRun::DetailedGlyph* details = 3050 aTextRun->GetDetailedGlyphs(i); 3051 NS_ASSERTION(details != nullptr, 3052 "detailedGlyph record should not be missing!"); 3053 uint32_t j; 3054 for (j = 0; j < glyphCount; ++j, ++details) { 3055 double advance = details->mAdvance; 3056 gfxRect glyphRect(0, -aMetrics.mAscent, advance, 3057 aMetrics.mAscent + aMetrics.mDescent); 3058 if (aIsRTL) { 3059 // Swap left/right sidebearings of the glyph, because we're doing 3060 // mirrored measurement. 3061 glyphRect.MoveToX(advance - glyphRect.XMost()); 3062 // Move to current x position, mirroring any x-offset amount. 3063 glyphRect.MoveByX(x - details->mOffset.x); 3064 } else { 3065 glyphRect.MoveByX(x + details->mOffset.x); 3066 } 3067 glyphRect.MoveByY(details->mOffset.y); 3068 aMetrics.mBoundingBox = aMetrics.mBoundingBox.Union(glyphRect); 3069 x += advance; 3070 } 3071 } 3072 } 3073 if (aSpacing) { 3074 double space = aSpacing[i - aStart].mAfter; 3075 if (i + 1 < aEnd) { 3076 space += aSpacing[i + 1 - aStart].mBefore; 3077 } 3078 x += space; 3079 } 3080 } 3081 3082 aMetrics.mAdvanceWidth = x; 3083 return allGlyphsInvisible; 3084 } 3085 3086 gfxFont::RunMetrics gfxFont::Measure(const gfxTextRun* aTextRun, 3087 uint32_t aStart, uint32_t aEnd, 3088 BoundingBoxType aBoundingBoxType, 3089 DrawTarget* aRefDrawTarget, 3090 Spacing* aSpacing, 3091 gfx::ShapedTextFlags aOrientation) { 3092 // If aBoundingBoxType is TIGHT_HINTED_OUTLINE_EXTENTS 3093 // and the underlying cairo font may be antialiased, 3094 // we need to create a copy in order to avoid getting cached extents. 3095 // This is only used by MathML layout at present. 3096 if (aBoundingBoxType == TIGHT_HINTED_OUTLINE_EXTENTS && 3097 mAntialiasOption != kAntialiasNone) { 3098 gfxFont* nonAA = mNonAAFont; 3099 if (!nonAA) { 3100 nonAA = CopyWithAntialiasOption(kAntialiasNone); 3101 if (nonAA) { 3102 if (!mNonAAFont.compareExchange(nullptr, nonAA)) { 3103 delete nonAA; 3104 nonAA = mNonAAFont; 3105 } 3106 } 3107 } 3108 // if font subclass doesn't implement CopyWithAntialiasOption(), 3109 // it will return null and we'll proceed to use the existing font 3110 if (nonAA) { 3111 return nonAA->Measure(aTextRun, aStart, aEnd, 3112 TIGHT_HINTED_OUTLINE_EXTENTS, aRefDrawTarget, 3113 aSpacing, aOrientation); 3114 } 3115 } 3116 3117 const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); 3118 // Current position in appunits 3119 Orientation orientation = 3120 aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT 3121 ? nsFontMetrics::eVertical 3122 : nsFontMetrics::eHorizontal; 3123 const gfxFont::Metrics& fontMetrics = GetMetrics(orientation); 3124 3125 gfxFloat baselineOffset = 0; 3126 if (aTextRun->UseCenterBaseline() && 3127 orientation == nsFontMetrics::eHorizontal) { 3128 // For a horizontal font being used in vertical writing mode with 3129 // text-orientation:mixed, the overall metrics we're accumulating 3130 // will be aimed at a center baseline. But this font's metrics were 3131 // based on the alphabetic baseline. So we compute a baseline offset 3132 // that will be applied to ascent/descent values and glyph rects 3133 // to effectively shift them relative to the baseline. 3134 // XXX Eventually we should probably use the BASE table, if present. 3135 // But it usually isn't, so we need an ad hoc adjustment for now. 3136 baselineOffset = 3137 appUnitsPerDevUnit * (fontMetrics.emAscent - fontMetrics.emDescent) / 2; 3138 } 3139 3140 RunMetrics metrics; 3141 metrics.mAscent = fontMetrics.maxAscent * appUnitsPerDevUnit; 3142 metrics.mDescent = fontMetrics.maxDescent * appUnitsPerDevUnit; 3143 3144 if (aStart == aEnd) { 3145 // exit now before we look at aSpacing[0], which is undefined 3146 metrics.mAscent -= baselineOffset; 3147 metrics.mDescent += baselineOffset; 3148 metrics.mBoundingBox = 3149 gfxRect(0, -metrics.mAscent, 0, metrics.mAscent + metrics.mDescent); 3150 return metrics; 3151 } 3152 3153 gfxFloat advanceMin = 0, advanceMax = 0; 3154 bool isRTL = aTextRun->IsRightToLeft(); 3155 bool needsGlyphExtents = NeedsGlyphExtents(this, aTextRun); 3156 gfxGlyphExtents* extents = 3157 ((aBoundingBoxType == LOOSE_INK_EXTENTS && !needsGlyphExtents && 3158 !aTextRun->HasDetailedGlyphs()) || 3159 MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero())) 3160 ? nullptr 3161 : GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit()); 3162 3163 bool allGlyphsInvisible; 3164 if (extents) { 3165 allGlyphsInvisible = MeasureGlyphs( 3166 aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget, aSpacing, 3167 extents, isRTL, needsGlyphExtents, metrics, &advanceMin, &advanceMax); 3168 } else { 3169 allGlyphsInvisible = 3170 MeasureGlyphs(aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget, 3171 aSpacing, isRTL, metrics); 3172 } 3173 3174 if (allGlyphsInvisible) { 3175 metrics.mBoundingBox.SetEmpty(); 3176 } else if (aBoundingBoxType == LOOSE_INK_EXTENTS) { 3177 UnionRange(metrics.mAdvanceWidth, &advanceMin, &advanceMax); 3178 gfxRect fontBox(advanceMin, -metrics.mAscent, advanceMax - advanceMin, 3179 metrics.mAscent + metrics.mDescent); 3180 metrics.mBoundingBox = metrics.mBoundingBox.Union(fontBox); 3181 } 3182 3183 if (isRTL) { 3184 // Reverse the effect of having swapped each glyph's sidebearings, to get 3185 // the correct sidebearings of the merged bounding box. 3186 metrics.mBoundingBox.MoveToX(metrics.mAdvanceWidth - 3187 metrics.mBoundingBox.XMost()); 3188 } 3189 3190 // If the font may be rendered with a fake-italic effect, we need to allow 3191 // for the top-right of the glyphs being skewed to the right, and the 3192 // bottom-left being skewed further left. 3193 gfxFloat skew = SkewForSyntheticOblique(); 3194 if (skew != 0.0) { 3195 gfxFloat extendLeftEdge, extendRightEdge; 3196 if (orientation == nsFontMetrics::eVertical) { 3197 // The glyph will actually be skewed vertically, but "left" and "right" 3198 // here refer to line-left (physical top) and -right (bottom), so these 3199 // are still the directions in which we need to extend the box. 3200 extendLeftEdge = skew < 0.0 ? ceil(-skew * metrics.mBoundingBox.XMost()) 3201 : ceil(skew * -metrics.mBoundingBox.X()); 3202 extendRightEdge = skew < 0.0 ? ceil(-skew * -metrics.mBoundingBox.X()) 3203 : ceil(skew * metrics.mBoundingBox.XMost()); 3204 } else { 3205 extendLeftEdge = skew < 0.0 ? ceil(-skew * -metrics.mBoundingBox.Y()) 3206 : ceil(skew * metrics.mBoundingBox.YMost()); 3207 extendRightEdge = skew < 0.0 ? ceil(-skew * metrics.mBoundingBox.YMost()) 3208 : ceil(skew * -metrics.mBoundingBox.Y()); 3209 } 3210 metrics.mBoundingBox.SetWidth(metrics.mBoundingBox.Width() + 3211 extendLeftEdge + extendRightEdge); 3212 metrics.mBoundingBox.MoveByX(-extendLeftEdge); 3213 } 3214 3215 if (baselineOffset != 0) { 3216 metrics.mAscent -= baselineOffset; 3217 metrics.mDescent += baselineOffset; 3218 metrics.mBoundingBox.MoveByY(baselineOffset); 3219 } 3220 3221 return metrics; 3222 } 3223 3224 bool gfxFont::AgeCachedWords() { 3225 mozilla::AutoWriteLock lock(mLock); 3226 if (mWordCache) { 3227 for (auto it = mWordCache->modIter(); !it.done(); it.next()) { 3228 auto& entry = it.get().value(); 3229 if (!entry) { 3230 NS_ASSERTION(entry, "cache entry has no gfxShapedWord!"); 3231 it.remove(); 3232 } else if (entry->IncrementAge() == kShapedWordCacheMaxAge) { 3233 it.remove(); 3234 } 3235 } 3236 mWordCache->compact(); 3237 return mWordCache->empty(); 3238 } 3239 return true; 3240 } 3241 3242 void gfxFont::NotifyGlyphsChanged() const { 3243 AutoReadLock lock(mLock); 3244 uint32_t i, count = mGlyphExtentsArray.Length(); 3245 for (i = 0; i < count; ++i) { 3246 // Flush cached extents array 3247 mGlyphExtentsArray[i]->NotifyGlyphsChanged(); 3248 } 3249 3250 if (mGlyphChangeObservers) { 3251 for (const auto& key : *mGlyphChangeObservers) { 3252 key->NotifyGlyphsChanged(); 3253 } 3254 } 3255 } 3256 3257 // If aChar is a "word boundary" for shaped-word caching purposes, return it; 3258 // else return 0. 3259 static char16_t IsBoundarySpace(char16_t aChar, char16_t aNextChar) { 3260 if ((aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar)) { 3261 return aChar; 3262 } 3263 return 0; 3264 } 3265 3266 // In 8-bit text, there cannot be any cluster-extenders. 3267 static uint8_t IsBoundarySpace(uint8_t aChar, uint8_t aNextChar) { 3268 if (aChar == ' ' || aChar == 0x00A0) { 3269 return aChar; 3270 } 3271 return 0; 3272 } 3273 3274 #ifdef __GNUC__ 3275 # define GFX_MAYBE_UNUSED __attribute__((unused)) 3276 #else 3277 # define GFX_MAYBE_UNUSED 3278 #endif 3279 3280 template <typename T, typename Func> 3281 bool gfxFont::ProcessShapedWordInternal( 3282 DrawTarget* aDrawTarget, const T* aText, uint32_t aLength, uint32_t aHash, 3283 Script aRunScript, nsAtom* aLanguage, bool aVertical, 3284 int32_t aAppUnitsPerDevUnit, gfx::ShapedTextFlags aFlags, 3285 RoundingFlags aRounding, gfxTextPerfMetrics* aTextPerf GFX_MAYBE_UNUSED, 3286 Func aCallback) { 3287 WordCacheKey key(aText, aLength, aHash, aRunScript, aLanguage, 3288 aAppUnitsPerDevUnit, aFlags, aRounding); 3289 { 3290 // If we have a word cache, attempt to look up the word in it. 3291 AutoReadLock lock(mLock); 3292 if (mWordCache) { 3293 // if there's a cached entry for this word, just return it 3294 if (auto entry = mWordCache->lookup(key)) { 3295 entry->value()->ResetAge(); 3296 #ifndef RELEASE_OR_BETA 3297 if (aTextPerf) { 3298 // XXX we should make sure this is atomic 3299 aTextPerf->current.wordCacheHit++; 3300 } 3301 #endif 3302 aCallback(entry->value().get()); 3303 return true; 3304 } 3305 } 3306 } 3307 3308 // We didn't find a cached word (or don't even have a cache yet), so create 3309 // a new gfxShapedWord and cache it. We don't have to lock during shaping, 3310 // only when it comes time to cache the new entry. 3311 3312 UniquePtr<gfxShapedWord> newShapedWord( 3313 gfxShapedWord::Create(aText, aLength, aRunScript, aLanguage, 3314 aAppUnitsPerDevUnit, aFlags, aRounding)); 3315 if (!newShapedWord) { 3316 NS_WARNING("failed to create gfxShapedWord - expect missing text"); 3317 return false; 3318 } 3319 DebugOnly<bool> ok = 3320 ShapeText(aDrawTarget, aText, 0, aLength, aRunScript, aLanguage, 3321 aVertical, aRounding, newShapedWord.get()); 3322 NS_WARNING_ASSERTION(ok, "failed to shape word - expect garbled text"); 3323 3324 { 3325 // We're going to cache the new shaped word, so lock for writing now. 3326 AutoWriteLock lock(mLock); 3327 if (!mWordCache) { 3328 mWordCache = MakeUnique<HashMap<WordCacheKey, UniquePtr<gfxShapedWord>, 3329 WordCacheKey::HashPolicy>>(); 3330 } else { 3331 // If the cache is getting too big, flush it and start over. 3332 uint32_t wordCacheMaxEntries = 3333 gfxPlatform::GetPlatform()->WordCacheMaxEntries(); 3334 if (mWordCache->count() > wordCacheMaxEntries) { 3335 // Flush the cache if it is getting overly big. 3336 NS_WARNING("flushing shaped-word cache"); 3337 ClearCachedWordsLocked(); 3338 } 3339 } 3340 3341 // Update key so that it references the text stored in the newShapedWord, 3342 // which is guaranteed to live as long as the hashtable entry. 3343 if ((key.mTextIs8Bit = newShapedWord->TextIs8Bit())) { 3344 key.mText.mSingle = newShapedWord->Text8Bit(); 3345 } else { 3346 key.mText.mDouble = newShapedWord->TextUnicode(); 3347 } 3348 auto entry = mWordCache->lookupForAdd(key); 3349 3350 // It's unlikely, but maybe another thread got there before us... 3351 if (entry) { 3352 // Use the existing entry; the newShapedWord will be discarded. 3353 entry->value()->ResetAge(); 3354 #ifndef RELEASE_OR_BETA 3355 if (aTextPerf) { 3356 aTextPerf->current.wordCacheHit++; 3357 } 3358 #endif 3359 aCallback(entry->value().get()); 3360 return true; 3361 } 3362 3363 if (!mWordCache->add(entry, key, std::move(newShapedWord))) { 3364 NS_WARNING("failed to cache gfxShapedWord - expect missing text"); 3365 return false; 3366 } 3367 3368 #ifndef RELEASE_OR_BETA 3369 if (aTextPerf) { 3370 aTextPerf->current.wordCacheMiss++; 3371 } 3372 #endif 3373 aCallback(entry->value().get()); 3374 } 3375 3376 gfxFontCache::GetCache()->RunWordCacheExpirationTimer(); 3377 return true; 3378 } 3379 3380 bool gfxFont::WordCacheKey::HashPolicy::match(const Key& aKey, 3381 const Lookup& aLookup) { 3382 if (aKey.mLength != aLookup.mLength || aKey.mFlags != aLookup.mFlags || 3383 aKey.mRounding != aLookup.mRounding || 3384 aKey.mAppUnitsPerDevUnit != aLookup.mAppUnitsPerDevUnit || 3385 aKey.mScript != aLookup.mScript || aKey.mLanguage != aLookup.mLanguage) { 3386 return false; 3387 } 3388 3389 if (aKey.mTextIs8Bit) { 3390 if (aLookup.mTextIs8Bit) { 3391 return (0 == memcmp(aKey.mText.mSingle, aLookup.mText.mSingle, 3392 aKey.mLength * sizeof(uint8_t))); 3393 } 3394 // The lookup key has 16-bit text, even though all the characters are < 256, 3395 // so the TEXT_IS_8BIT flag was set and the cached ShapedWord we're 3396 // comparing with will have 8-bit text. 3397 const uint8_t* s1 = aKey.mText.mSingle; 3398 const char16_t* s2 = aLookup.mText.mDouble; 3399 const char16_t* s2end = s2 + aKey.mLength; 3400 while (s2 < s2end) { 3401 if (*s1++ != *s2++) { 3402 return false; 3403 } 3404 } 3405 return true; 3406 } 3407 NS_ASSERTION(!(aLookup.mFlags & gfx::ShapedTextFlags::TEXT_IS_8BIT) && 3408 !aLookup.mTextIs8Bit, 3409 "didn't expect 8-bit text here"); 3410 return (0 == memcmp(aKey.mText.mDouble, aLookup.mText.mDouble, 3411 aKey.mLength * sizeof(char16_t))); 3412 } 3413 3414 bool gfxFont::ProcessSingleSpaceShapedWord( 3415 DrawTarget* aDrawTarget, bool aVertical, int32_t aAppUnitsPerDevUnit, 3416 gfx::ShapedTextFlags aFlags, RoundingFlags aRounding, 3417 const std::function<void(gfxShapedWord*)>& aCallback) { 3418 static const uint8_t space = ' '; 3419 return ProcessShapedWordInternal( 3420 aDrawTarget, &space, 1, gfxShapedWord::HashMix(0, ' '), Script::LATIN, 3421 /* aLanguage = */ nullptr, aVertical, aAppUnitsPerDevUnit, aFlags, 3422 aRounding, nullptr, aCallback); 3423 } 3424 3425 bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const uint8_t* aText, 3426 uint32_t aOffset, uint32_t aLength, Script aScript, 3427 nsAtom* aLanguage, bool aVertical, 3428 RoundingFlags aRounding, gfxShapedText* aShapedText) { 3429 nsDependentCSubstring ascii((const char*)aText, aLength); 3430 nsAutoString utf16; 3431 AppendASCIItoUTF16(ascii, utf16); 3432 if (utf16.Length() != aLength) { 3433 return false; 3434 } 3435 return ShapeText(aDrawTarget, utf16.BeginReading(), aOffset, aLength, aScript, 3436 aLanguage, aVertical, aRounding, aShapedText); 3437 } 3438 3439 bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText, 3440 uint32_t aOffset, uint32_t aLength, Script aScript, 3441 nsAtom* aLanguage, bool aVertical, 3442 RoundingFlags aRounding, gfxShapedText* aShapedText) { 3443 // XXX Currently, we do all vertical shaping through harfbuzz. 3444 // Vertical graphite support may be wanted as a future enhancement. 3445 // XXX Graphite shaping currently only supported on the main thread! 3446 // Worker-thread shaping (offscreen canvas) will always go via harfbuzz. 3447 if (FontCanSupportGraphite() && !aVertical && NS_IsMainThread()) { 3448 if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) { 3449 gfxGraphiteShaper* shaper = mGraphiteShaper; 3450 if (!shaper) { 3451 shaper = new gfxGraphiteShaper(this); 3452 if (!mGraphiteShaper.compareExchange(nullptr, shaper)) { 3453 delete shaper; 3454 shaper = mGraphiteShaper; 3455 } 3456 } 3457 if (shaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, 3458 aLanguage, aVertical, aRounding, aShapedText)) { 3459 PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical, 3460 aShapedText); 3461 return true; 3462 } 3463 } 3464 } 3465 3466 gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper(); 3467 if (shaper && 3468 shaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, 3469 aLanguage, aVertical, aRounding, aShapedText)) { 3470 PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical, 3471 aShapedText); 3472 if (GetFontEntry()->HasTrackingTable()) { 3473 // Convert font size from device pixels back to CSS px 3474 // to use in selecting tracking value 3475 gfxFloat trackSize = GetAdjustedSize() * 3476 aShapedText->GetAppUnitsPerDevUnit() / 3477 AppUnitsPerCSSPixel(); 3478 // Usually, a given font will be used with the same appunit scale, so we 3479 // can cache the tracking value rather than recompute it every time. 3480 { 3481 AutoReadLock lock(mLock); 3482 if (trackSize == mCachedTrackingSize) { 3483 // Applying tracking is a lot like the adjustment we do for 3484 // synthetic bold: we want to apply between clusters, not to 3485 // non-spacing glyphs within a cluster. So we can reuse that 3486 // helper here. 3487 aShapedText->ApplyTrackingToClusters(mTracking, aOffset, aLength); 3488 return true; 3489 } 3490 } 3491 // We didn't have the appropriate tracking value cached yet. 3492 AutoWriteLock lock(mLock); 3493 if (trackSize != mCachedTrackingSize) { 3494 mCachedTrackingSize = trackSize; 3495 mTracking = 3496 GetFontEntry()->TrackingForCSSPx(trackSize) * mFUnitsConvFactor; 3497 } 3498 aShapedText->ApplyTrackingToClusters(mTracking, aOffset, aLength); 3499 } 3500 return true; 3501 } 3502 3503 NS_WARNING_ASSERTION(false, "shaper failed, expect scrambled/missing text"); 3504 return false; 3505 } 3506 3507 void gfxFont::PostShapingFixup(DrawTarget* aDrawTarget, const char16_t* aText, 3508 uint32_t aOffset, uint32_t aLength, 3509 bool aVertical, gfxShapedText* aShapedText) { 3510 if (ApplySyntheticBold()) { 3511 const Metrics& metrics = GetMetrics(aVertical ? nsFontMetrics::eVertical 3512 : nsFontMetrics::eHorizontal); 3513 if (metrics.maxAdvance > metrics.aveCharWidth) { 3514 aShapedText->ApplyTrackingToClusters(GetSyntheticBoldOffset(), aOffset, 3515 aLength); 3516 } 3517 } 3518 } 3519 3520 #define MAX_SHAPING_LENGTH \ 3521 32760 // slightly less than 32K, trying to avoid 3522 // over-stressing platform shapers 3523 #define BACKTRACK_LIMIT \ 3524 16 // backtrack this far looking for a good place 3525 // to split into fragments for separate shaping 3526 3527 template <typename T> 3528 bool gfxFont::ShapeFragmentWithoutWordCache(DrawTarget* aDrawTarget, 3529 const T* aText, uint32_t aOffset, 3530 uint32_t aLength, Script aScript, 3531 nsAtom* aLanguage, bool aVertical, 3532 RoundingFlags aRounding, 3533 gfxTextRun* aTextRun) { 3534 aTextRun->SetupClusterBoundaries(aOffset, aText, aLength); 3535 3536 bool ok = true; 3537 3538 while (ok && aLength > 0) { 3539 uint32_t fragLen = aLength; 3540 3541 // limit the length of text we pass to shapers in a single call 3542 if (fragLen > MAX_SHAPING_LENGTH) { 3543 fragLen = MAX_SHAPING_LENGTH; 3544 3545 // in the 8-bit case, there are no multi-char clusters, 3546 // so we don't need to do this check 3547 if constexpr (sizeof(T) == sizeof(char16_t)) { 3548 uint32_t i; 3549 for (i = 0; i < BACKTRACK_LIMIT; ++i) { 3550 if (aTextRun->IsClusterStart(aOffset + fragLen - i)) { 3551 fragLen -= i; 3552 break; 3553 } 3554 } 3555 if (i == BACKTRACK_LIMIT) { 3556 // if we didn't find any cluster start while backtracking, 3557 // just check that we're not in the middle of a surrogate 3558 // pair; back up by one code unit if we are. 3559 if (NS_IS_SURROGATE_PAIR(aText[fragLen - 1], aText[fragLen])) { 3560 --fragLen; 3561 } 3562 } 3563 } 3564 } 3565 3566 ok = ShapeText(aDrawTarget, aText, aOffset, fragLen, aScript, aLanguage, 3567 aVertical, aRounding, aTextRun); 3568 3569 aText += fragLen; 3570 aOffset += fragLen; 3571 aLength -= fragLen; 3572 } 3573 3574 return ok; 3575 } 3576 3577 // Check if aCh is an unhandled control character that should be displayed 3578 // as a hexbox rather than rendered by some random font on the system. 3579 // We exclude \r as stray s are rather common (bug 941940). 3580 // Note that \n and \t don't come through here, as they have specific 3581 // meanings that have already been handled. 3582 static bool IsInvalidControlChar(uint32_t aCh) { 3583 return aCh != '\r' && ((aCh & 0x7f) < 0x20 || aCh == 0x7f); 3584 } 3585 3586 template <typename T> 3587 bool gfxFont::ShapeTextWithoutWordCache(DrawTarget* aDrawTarget, const T* aText, 3588 uint32_t aOffset, uint32_t aLength, 3589 Script aScript, nsAtom* aLanguage, 3590 bool aVertical, RoundingFlags aRounding, 3591 gfxTextRun* aTextRun) { 3592 uint32_t fragStart = 0; 3593 bool ok = true; 3594 3595 for (uint32_t i = 0; i <= aLength && ok; ++i) { 3596 T ch = (i < aLength) ? aText[i] : '\n'; 3597 bool invalid = gfxFontGroup::IsInvalidChar(ch); 3598 uint32_t length = i - fragStart; 3599 3600 // break into separate fragments when we hit an invalid char 3601 if (!invalid) { 3602 continue; 3603 } 3604 3605 if (length > 0) { 3606 ok = ShapeFragmentWithoutWordCache( 3607 aDrawTarget, aText + fragStart, aOffset + fragStart, length, aScript, 3608 aLanguage, aVertical, aRounding, aTextRun); 3609 } 3610 3611 if (i == aLength) { 3612 break; 3613 } 3614 3615 // fragment was terminated by an invalid char: skip it, 3616 // unless it's a control char that we want to show as a hexbox, 3617 // but record where TAB or NEWLINE occur 3618 if (ch == '\t') { 3619 aTextRun->SetIsTab(aOffset + i); 3620 } else if (ch == '\n') { 3621 aTextRun->SetIsNewline(aOffset + i); 3622 } else if (GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_FORMAT) { 3623 aTextRun->SetIsFormattingControl(aOffset + i); 3624 } else if (IsInvalidControlChar(ch) && 3625 !(aTextRun->GetFlags() & 3626 gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS)) { 3627 if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) { 3628 ShapeFragmentWithoutWordCache(aDrawTarget, aText + i, aOffset + i, 1, 3629 aScript, aLanguage, aVertical, aRounding, 3630 aTextRun); 3631 } else { 3632 aTextRun->SetMissingGlyph(aOffset + i, ch, this); 3633 } 3634 } 3635 fragStart = i + 1; 3636 } 3637 3638 NS_WARNING_ASSERTION(ok, "failed to shape text - expect garbled text"); 3639 return ok; 3640 } 3641 3642 #ifndef RELEASE_OR_BETA 3643 # define TEXT_PERF_INCR(tp, m) (tp ? (tp)->current.m++ : 0) 3644 #else 3645 # define TEXT_PERF_INCR(tp, m) 3646 #endif 3647 3648 inline static bool IsChar8Bit(uint8_t /*aCh*/) { return true; } 3649 inline static bool IsChar8Bit(char16_t aCh) { return aCh < 0x100; } 3650 3651 inline static bool HasSpaces(const uint8_t* aString, uint32_t aLen) { 3652 return memchr(aString, 0x20, aLen) != nullptr; 3653 } 3654 3655 inline static bool HasSpaces(const char16_t* aString, uint32_t aLen) { 3656 for (const char16_t* ch = aString; ch < aString + aLen; ch++) { 3657 if (*ch == 0x20) { 3658 return true; 3659 } 3660 } 3661 return false; 3662 } 3663 3664 template <typename T> 3665 bool gfxFont::SplitAndInitTextRun( 3666 DrawTarget* aDrawTarget, gfxTextRun* aTextRun, 3667 const T* aString, // text for this font run 3668 uint32_t aRunStart, // position in the textrun 3669 uint32_t aRunLength, Script aRunScript, nsAtom* aLanguage, 3670 ShapedTextFlags aOrientation) { 3671 if (aRunLength == 0) { 3672 return true; 3673 } 3674 3675 gfxTextPerfMetrics* tp = nullptr; 3676 RoundingFlags rounding = GetRoundOffsetsToPixels(aDrawTarget); 3677 3678 #ifndef RELEASE_OR_BETA 3679 tp = aTextRun->GetFontGroup()->GetTextPerfMetrics(); 3680 if (tp) { 3681 if (mStyle.systemFont) { 3682 tp->current.numChromeTextRuns++; 3683 } else { 3684 tp->current.numContentTextRuns++; 3685 } 3686 tp->current.numChars += aRunLength; 3687 if (aRunLength > tp->current.maxTextRunLen) { 3688 tp->current.maxTextRunLen = aRunLength; 3689 } 3690 } 3691 #endif 3692 3693 uint32_t wordCacheCharLimit = 3694 gfxPlatform::GetPlatform()->WordCacheCharLimit(); 3695 3696 bool vertical = aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; 3697 3698 // If spaces can participate in shaping (e.g. within lookups for automatic 3699 // fractions), need to shape without using the word cache which segments 3700 // textruns on space boundaries. Word cache can be used if the textrun 3701 // is short enough to fit in the word cache and it lacks spaces. 3702 tainted_boolean_hint t_canParticipate = 3703 SpaceMayParticipateInShaping(aRunScript); 3704 bool canParticipate = t_canParticipate.unverified_safe_because( 3705 "We need to ensure that this function operates safely independent of " 3706 "t_canParticipate. The worst that can happen here is that the decision " 3707 "to use the cache is incorrectly made, resulting in a bad " 3708 "rendering/slowness. However, this would not compromise the memory " 3709 "safety of Firefox in any way, and can thus be permitted"); 3710 3711 if (canParticipate) { 3712 if (aRunLength > wordCacheCharLimit || HasSpaces(aString, aRunLength)) { 3713 TEXT_PERF_INCR(tp, wordCacheSpaceRules); 3714 return ShapeTextWithoutWordCache(aDrawTarget, aString, aRunStart, 3715 aRunLength, aRunScript, aLanguage, 3716 vertical, rounding, aTextRun); 3717 } 3718 } 3719 3720 // the only flags we care about for ShapedWord construction/caching 3721 gfx::ShapedTextFlags flags = aTextRun->GetFlags(); 3722 flags &= (gfx::ShapedTextFlags::TEXT_IS_RTL | 3723 gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES | 3724 gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT | 3725 gfx::ShapedTextFlags::TEXT_ORIENT_MASK); 3726 if constexpr (sizeof(T) == sizeof(uint8_t)) { 3727 flags |= gfx::ShapedTextFlags::TEXT_IS_8BIT; 3728 } 3729 3730 uint32_t wordStart = 0; 3731 uint32_t hash = 0; 3732 bool wordIs8Bit = true; 3733 int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); 3734 3735 T nextCh = aString[0]; 3736 for (uint32_t i = 0; i <= aRunLength; ++i) { 3737 T ch = nextCh; 3738 nextCh = (i < aRunLength - 1) ? aString[i + 1] : '\n'; 3739 T boundary = IsBoundarySpace(ch, nextCh); 3740 bool invalid = !boundary && gfxFontGroup::IsInvalidChar(ch); 3741 uint32_t length = i - wordStart; 3742 3743 // break into separate ShapedWords when we hit an invalid char, 3744 // or a boundary space (always handled individually), 3745 // or the first non-space after a space 3746 if (!boundary && !invalid) { 3747 if (!IsChar8Bit(ch)) { 3748 wordIs8Bit = false; 3749 } 3750 // include this character in the hash, and move on to next 3751 hash = gfxShapedWord::HashMix(hash, ch); 3752 continue; 3753 } 3754 3755 // We've decided to break here (i.e. we're at the end of a "word"); 3756 // shape the word and add it to the textrun. 3757 // For words longer than the limit, we don't use the 3758 // font's word cache but just shape directly into the textrun. 3759 if (length > wordCacheCharLimit) { 3760 TEXT_PERF_INCR(tp, wordCacheLong); 3761 bool ok = ShapeFragmentWithoutWordCache( 3762 aDrawTarget, aString + wordStart, aRunStart + wordStart, length, 3763 aRunScript, aLanguage, vertical, rounding, aTextRun); 3764 if (!ok) { 3765 return false; 3766 } 3767 } else if (length > 0) { 3768 gfx::ShapedTextFlags wordFlags = flags; 3769 // in the 8-bit version of this method, TEXT_IS_8BIT was 3770 // already set as part of |flags|, so no need for a per-word 3771 // adjustment here 3772 if (sizeof(T) == sizeof(char16_t)) { 3773 if (wordIs8Bit) { 3774 wordFlags |= gfx::ShapedTextFlags::TEXT_IS_8BIT; 3775 } 3776 } 3777 bool processed = ProcessShapedWordInternal( 3778 aDrawTarget, aString + wordStart, length, hash, aRunScript, aLanguage, 3779 vertical, appUnitsPerDevUnit, wordFlags, rounding, tp, 3780 [&](gfxShapedWord* aShapedWord) { 3781 aTextRun->CopyGlyphDataFrom(aShapedWord, aRunStart + wordStart); 3782 }); 3783 if (!processed) { 3784 return false; // failed, presumably out of memory? 3785 } 3786 } 3787 3788 if (boundary) { 3789 // word was terminated by a space: add that to the textrun 3790 MOZ_ASSERT(aOrientation != ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED, 3791 "text-orientation:mixed should be resolved earlier"); 3792 if (boundary != ' ' || !aTextRun->SetSpaceGlyphIfSimple( 3793 this, aRunStart + i, ch, aOrientation)) { 3794 // Currently, the only "boundary" characters we recognize are 3795 // space and no-break space, which are both 8-bit, so we force 3796 // that flag (below). If we ever change IsBoundarySpace, we 3797 // may need to revise this. 3798 // Avoid tautological-constant-out-of-range-compare in 8-bit: 3799 DebugOnly<char16_t> boundary16 = boundary; 3800 NS_ASSERTION(boundary16 < 256, "unexpected boundary!"); 3801 bool processed = ProcessShapedWordInternal( 3802 aDrawTarget, &boundary, 1, gfxShapedWord::HashMix(0, boundary), 3803 aRunScript, aLanguage, vertical, appUnitsPerDevUnit, 3804 flags | gfx::ShapedTextFlags::TEXT_IS_8BIT, rounding, tp, 3805 [&](gfxShapedWord* aShapedWord) { 3806 aTextRun->CopyGlyphDataFrom(aShapedWord, aRunStart + i); 3807 if (boundary == ' ') { 3808 aTextRun->GetCharacterGlyphs()[aRunStart + i].SetIsSpace(); 3809 } 3810 }); 3811 if (!processed) { 3812 return false; 3813 } 3814 } 3815 hash = 0; 3816 wordStart = i + 1; 3817 wordIs8Bit = true; 3818 continue; 3819 } 3820 3821 if (i == aRunLength) { 3822 break; 3823 } 3824 3825 NS_ASSERTION(invalid, "how did we get here except via an invalid char?"); 3826 3827 // word was terminated by an invalid char: skip it, 3828 // unless it's a control char that we want to show as a hexbox, 3829 // but record where TAB or NEWLINE occur 3830 if (ch == '\t') { 3831 aTextRun->SetIsTab(aRunStart + i); 3832 } else if (ch == '\n') { 3833 aTextRun->SetIsNewline(aRunStart + i); 3834 } else if (GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_FORMAT) { 3835 aTextRun->SetIsFormattingControl(aRunStart + i); 3836 } else if (IsInvalidControlChar(ch) && 3837 !(aTextRun->GetFlags() & 3838 gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS)) { 3839 if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) { 3840 ShapeFragmentWithoutWordCache(aDrawTarget, aString + i, aRunStart + i, 3841 1, aRunScript, aLanguage, vertical, 3842 rounding, aTextRun); 3843 } else { 3844 aTextRun->SetMissingGlyph(aRunStart + i, ch, this); 3845 } 3846 } 3847 3848 hash = 0; 3849 wordStart = i + 1; 3850 wordIs8Bit = true; 3851 } 3852 3853 return true; 3854 } 3855 3856 // Explicit instantiations of SplitAndInitTextRun, to avoid libxul link failure 3857 template bool gfxFont::SplitAndInitTextRun( 3858 DrawTarget* aDrawTarget, gfxTextRun* aTextRun, const uint8_t* aString, 3859 uint32_t aRunStart, uint32_t aRunLength, Script aRunScript, 3860 nsAtom* aLanguage, ShapedTextFlags aOrientation); 3861 template bool gfxFont::SplitAndInitTextRun( 3862 DrawTarget* aDrawTarget, gfxTextRun* aTextRun, const char16_t* aString, 3863 uint32_t aRunStart, uint32_t aRunLength, Script aRunScript, 3864 nsAtom* aLanguage, ShapedTextFlags aOrientation); 3865 3866 template <> 3867 bool gfxFont::InitFakeSmallCapsRun( 3868 FontVisibilityProvider* aFontVisibilityProvider, DrawTarget* aDrawTarget, 3869 gfxTextRun* aTextRun, const char16_t* aText, uint32_t aOffset, 3870 uint32_t aLength, FontMatchType aMatchType, 3871 gfx::ShapedTextFlags aOrientation, Script aScript, nsAtom* aLanguage, 3872 bool aSyntheticLower, bool aSyntheticUpper) { 3873 bool ok = true; 3874 3875 RefPtr<gfxFont> smallCapsFont = GetSmallCapsFont(); 3876 if (!smallCapsFont) { 3877 NS_WARNING("failed to get reduced-size font for smallcaps!"); 3878 smallCapsFont = this; 3879 } 3880 3881 bool isCJK = gfxTextRun::IsCJKScript(aScript); 3882 3883 enum RunCaseAction { kNoChange, kUppercaseReduce, kUppercase }; 3884 3885 RunCaseAction runAction = kNoChange; 3886 uint32_t runStart = 0; 3887 3888 for (uint32_t i = 0; i <= aLength; ++i) { 3889 uint32_t extraCodeUnits = 0; // Will be set to 1 if we need to consume 3890 // a trailing surrogate as well as the 3891 // current code unit. 3892 RunCaseAction chAction = kNoChange; 3893 // Unless we're at the end, figure out what treatment the current 3894 // character will need. 3895 if (i < aLength) { 3896 uint32_t ch = aText[i]; 3897 if (i < aLength - 1 && NS_IS_SURROGATE_PAIR(ch, aText[i + 1])) { 3898 ch = SURROGATE_TO_UCS4(ch, aText[i + 1]); 3899 extraCodeUnits = 1; 3900 } 3901 // Characters that aren't the start of a cluster are ignored here. 3902 // They get added to whatever lowercase/non-lowercase run we're in. 3903 if (IsClusterExtender(ch)) { 3904 chAction = runAction; 3905 } else { 3906 if (ch != ToUpperCase(ch) || SpecialUpper(ch)) { 3907 // ch is lower case 3908 chAction = (aSyntheticLower ? kUppercaseReduce : kNoChange); 3909 } else if (ch != ToLowerCase(ch)) { 3910 // ch is upper case 3911 chAction = (aSyntheticUpper ? kUppercaseReduce : kNoChange); 3912 if (aLanguage == nsGkAtoms::el) { 3913 // In Greek, check for characters that will be modified by 3914 // the GreekUpperCase mapping - this catches accented 3915 // capitals where the accent is to be removed (bug 307039). 3916 // These are handled by using the full-size font with the 3917 // uppercasing transform. 3918 mozilla::GreekCasing::State state; 3919 bool markEta, updateEta; 3920 uint32_t ch2 = 3921 mozilla::GreekCasing::UpperCase(ch, state, markEta, updateEta); 3922 if ((ch != ch2 || markEta) && !aSyntheticUpper) { 3923 chAction = kUppercase; 3924 } 3925 } 3926 } 3927 } 3928 } 3929 3930 // At the end of the text or when the current character needs different 3931 // casing treatment from the current run, finish the run-in-progress 3932 // and prepare to accumulate a new run. 3933 // Note that we do not look at any source data for offset [i] here, 3934 // as that would be invalid in the case where i==length. 3935 if ((i == aLength || runAction != chAction) && runStart < i) { 3936 uint32_t runLength = i - runStart; 3937 gfxFont* f = this; 3938 switch (runAction) { 3939 case kNoChange: 3940 // just use the current font and the existing string 3941 aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true, 3942 aOrientation, isCJK); 3943 if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun, aText + runStart, 3944 aOffset + runStart, runLength, aScript, 3945 aLanguage, aOrientation)) { 3946 ok = false; 3947 } 3948 break; 3949 3950 case kUppercaseReduce: 3951 // use reduced-size font, then fall through to uppercase the text 3952 f = smallCapsFont; 3953 [[fallthrough]]; 3954 3955 case kUppercase: 3956 // apply uppercase transform to the string 3957 nsDependentSubstring origString(aText + runStart, runLength); 3958 nsAutoString convertedString; 3959 AutoTArray<bool, 50> charsToMergeArray; 3960 AutoTArray<bool, 50> deletedCharsArray; 3961 3962 const auto globalTransform = StyleTextTransform::UPPERCASE; 3963 // No mask needed; we're doing case conversion, not password-hiding. 3964 const char16_t maskChar = 0; 3965 bool mergeNeeded = nsCaseTransformTextRunFactory::TransformString( 3966 origString, convertedString, Some(globalTransform), maskChar, 3967 /* aCaseTransformsOnly = */ false, aLanguage, charsToMergeArray, 3968 deletedCharsArray); 3969 3970 // Check whether the font supports the uppercased characters needed; 3971 // if not, we're not going to be able to simulate small-caps. 3972 bool failed = false; 3973 char16_t highSurrogate = 0; 3974 for (const char16_t* cp = convertedString.BeginReading(); 3975 cp != convertedString.EndReading(); ++cp) { 3976 if (NS_IS_HIGH_SURROGATE(*cp)) { 3977 highSurrogate = *cp; 3978 continue; 3979 } 3980 uint32_t ch = *cp; 3981 if (NS_IS_LOW_SURROGATE(*cp) && highSurrogate) { 3982 ch = SURROGATE_TO_UCS4(highSurrogate, *cp); 3983 } 3984 highSurrogate = 0; 3985 if (!f->HasCharacter(ch)) { 3986 if (IsDefaultIgnorable(ch)) { 3987 continue; 3988 } 3989 failed = true; 3990 break; 3991 } 3992 } 3993 // Required uppercase letter(s) missing from the font. Just use the 3994 // original text with the original font, no fake small caps! 3995 if (failed) { 3996 convertedString = origString; 3997 mergeNeeded = false; 3998 f = this; 3999 } 4000 4001 if (mergeNeeded) { 4002 // This is the hard case: the transformation caused chars 4003 // to be inserted or deleted, so we can't shape directly 4004 // into the destination textrun but have to handle the 4005 // mismatch of character positions. 4006 gfxTextRunFactory::Parameters params = { 4007 aDrawTarget, nullptr, nullptr, 4008 nullptr, 0, aTextRun->GetAppUnitsPerDevUnit()}; 4009 RefPtr<gfxTextRun> tempRun(gfxTextRun::Create( 4010 ¶ms, convertedString.Length(), aTextRun->GetFontGroup(), 4011 gfx::ShapedTextFlags(), nsTextFrameUtils::Flags())); 4012 tempRun->AddGlyphRun(f, aMatchType, 0, true, aOrientation, isCJK); 4013 if (!f->SplitAndInitTextRun(aDrawTarget, tempRun.get(), 4014 convertedString.BeginReading(), 0, 4015 convertedString.Length(), aScript, 4016 aLanguage, aOrientation)) { 4017 ok = false; 4018 } else { 4019 RefPtr<gfxTextRun> mergedRun(gfxTextRun::Create( 4020 ¶ms, runLength, aTextRun->GetFontGroup(), 4021 gfx::ShapedTextFlags(), nsTextFrameUtils::Flags())); 4022 MergeCharactersInTextRun(mergedRun.get(), tempRun.get(), 4023 charsToMergeArray.Elements(), 4024 deletedCharsArray.Elements()); 4025 gfxTextRun::Range runRange(0, runLength); 4026 aTextRun->CopyGlyphDataFrom(mergedRun.get(), runRange, 4027 aOffset + runStart); 4028 } 4029 } else { 4030 aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true, 4031 aOrientation, isCJK); 4032 if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun, 4033 convertedString.BeginReading(), 4034 aOffset + runStart, runLength, aScript, 4035 aLanguage, aOrientation)) { 4036 ok = false; 4037 } 4038 } 4039 break; 4040 } 4041 4042 runStart = i; 4043 } 4044 4045 i += extraCodeUnits; 4046 if (i < aLength) { 4047 runAction = chAction; 4048 } 4049 } 4050 4051 return ok; 4052 } 4053 4054 template <> 4055 bool gfxFont::InitFakeSmallCapsRun( 4056 FontVisibilityProvider* aFontVisibilityProvider, DrawTarget* aDrawTarget, 4057 gfxTextRun* aTextRun, const uint8_t* aText, uint32_t aOffset, 4058 uint32_t aLength, FontMatchType aMatchType, 4059 gfx::ShapedTextFlags aOrientation, Script aScript, nsAtom* aLanguage, 4060 bool aSyntheticLower, bool aSyntheticUpper) { 4061 NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aText), 4062 aLength); 4063 return InitFakeSmallCapsRun(aFontVisibilityProvider, aDrawTarget, aTextRun, 4064 static_cast<const char16_t*>(unicodeString.get()), 4065 aOffset, aLength, aMatchType, aOrientation, 4066 aScript, aLanguage, aSyntheticLower, 4067 aSyntheticUpper); 4068 } 4069 4070 already_AddRefed<gfxFont> gfxFont::GetSmallCapsFont() const { 4071 gfxFontStyle style(*GetStyle()); 4072 style.size *= SMALL_CAPS_SCALE_FACTOR; 4073 style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL; 4074 gfxFontEntry* fe = GetFontEntry(); 4075 return fe->FindOrMakeFont(&style, mUnicodeRangeMap); 4076 } 4077 4078 already_AddRefed<gfxFont> gfxFont::GetSubSuperscriptFont( 4079 int32_t aAppUnitsPerDevPixel) const { 4080 gfxFontStyle style(*GetStyle()); 4081 style.AdjustForSubSuperscript(aAppUnitsPerDevPixel); 4082 gfxFontEntry* fe = GetFontEntry(); 4083 return fe->FindOrMakeFont(&style, mUnicodeRangeMap); 4084 } 4085 4086 gfxGlyphExtents* gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit) { 4087 uint32_t readCount; 4088 { 4089 AutoReadLock lock(mLock); 4090 readCount = mGlyphExtentsArray.Length(); 4091 for (uint32_t i = 0; i < readCount; ++i) { 4092 if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit) 4093 return mGlyphExtentsArray[i].get(); 4094 } 4095 } 4096 AutoWriteLock lock(mLock); 4097 // Re-check in case of race. 4098 uint32_t count = mGlyphExtentsArray.Length(); 4099 for (uint32_t i = readCount; i < count; ++i) { 4100 if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit) 4101 return mGlyphExtentsArray[i].get(); 4102 } 4103 gfxGlyphExtents* glyphExtents = new gfxGlyphExtents(aAppUnitsPerDevUnit); 4104 if (glyphExtents) { 4105 mGlyphExtentsArray.AppendElement(glyphExtents); 4106 // Initialize the extents of a space glyph, assuming that spaces don't 4107 // render anything! 4108 glyphExtents->SetContainedGlyphWidthAppUnits(GetSpaceGlyph(), 0); 4109 } 4110 return glyphExtents; 4111 } 4112 4113 void gfxFont::SetupGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphID, 4114 bool aNeedTight, gfxGlyphExtents* aExtents) { 4115 gfxRect svgBounds; 4116 if (mFontEntry->TryGetSVGData(this) && mFontEntry->HasSVGGlyph(aGlyphID) && 4117 mFontEntry->GetSVGGlyphExtents(aDrawTarget, aGlyphID, GetAdjustedSize(), 4118 &svgBounds)) { 4119 gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit(); 4120 aExtents->SetTightGlyphExtents( 4121 aGlyphID, gfxRect(svgBounds.X() * d2a, svgBounds.Y() * d2a, 4122 svgBounds.Width() * d2a, svgBounds.Height() * d2a)); 4123 return; 4124 } 4125 4126 if (mFontEntry->TryGetColorGlyphs() && mFontEntry->mCOLR && 4127 COLRFonts::GetColrTableVersion(mFontEntry->mCOLR) == 1) { 4128 auto* shaper = GetHarfBuzzShaper(); 4129 if (shaper && shaper->IsInitialized()) { 4130 RefPtr scaledFont = GetScaledFont(aDrawTarget); 4131 Rect r = COLRFonts::GetColorGlyphBounds( 4132 mFontEntry->mCOLR, shaper->GetHBFont(), aGlyphID, aDrawTarget, 4133 scaledFont, mFUnitsConvFactor); 4134 if (!r.IsEmpty()) { 4135 gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit(); 4136 aExtents->SetTightGlyphExtents( 4137 aGlyphID, gfxRect(r.X() * d2a, r.Y() * d2a, r.Width() * d2a, 4138 r.Height() * d2a)); 4139 return; 4140 } 4141 } 4142 } 4143 4144 gfxRect bounds; 4145 GetGlyphBounds(aGlyphID, &bounds, mAntialiasOption == kAntialiasNone); 4146 4147 const Metrics& fontMetrics = GetMetrics(nsFontMetrics::eHorizontal); 4148 int32_t appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit(); 4149 if (!aNeedTight && bounds.x >= 0.0 && bounds.y >= -fontMetrics.maxAscent && 4150 bounds.height + bounds.y <= fontMetrics.maxDescent) { 4151 uint32_t appUnitsWidth = 4152 uint32_t(ceil((bounds.x + bounds.width) * appUnitsPerDevUnit)); 4153 if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) { 4154 aExtents->SetContainedGlyphWidthAppUnits(aGlyphID, 4155 uint16_t(appUnitsWidth)); 4156 return; 4157 } 4158 } 4159 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS 4160 if (!aNeedTight) { 4161 ++gGlyphExtentsSetupFallBackToTight; 4162 } 4163 #endif 4164 4165 gfxFloat d2a = appUnitsPerDevUnit; 4166 aExtents->SetTightGlyphExtents( 4167 aGlyphID, gfxRect(bounds.x * d2a, bounds.y * d2a, bounds.width * d2a, 4168 bounds.height * d2a)); 4169 } 4170 4171 // Try to initialize font metrics by reading sfnt tables directly; 4172 // set mIsValid=TRUE and return TRUE on success. 4173 // Return FALSE if the gfxFontEntry subclass does not 4174 // implement GetFontTable(), or for non-sfnt fonts where tables are 4175 // not available. 4176 // If this returns TRUE without setting the mIsValid flag, then we -did- 4177 // apparently find an sfnt, but it was too broken to be used. 4178 bool gfxFont::InitMetricsFromSfntTables(Metrics& aMetrics) { 4179 mIsValid = false; // font is NOT valid in case of early return 4180 4181 const uint32_t kHheaTableTag = TRUETYPE_TAG('h', 'h', 'e', 'a'); 4182 const uint32_t kOS_2TableTag = TRUETYPE_TAG('O', 'S', '/', '2'); 4183 4184 uint32_t len; 4185 4186 if (mFUnitsConvFactor < 0.0) { 4187 // If the conversion factor from FUnits is not yet set, 4188 // get the unitsPerEm from the 'head' table via the font entry 4189 uint16_t unitsPerEm = GetFontEntry()->UnitsPerEm(); 4190 if (unitsPerEm == gfxFontEntry::kInvalidUPEM) { 4191 return false; 4192 } 4193 mFUnitsConvFactor = GetAdjustedSize() / unitsPerEm; 4194 } 4195 4196 // 'hhea' table is required for the advanceWidthMax field 4197 gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag); 4198 if (!hheaTable) { 4199 return false; // no 'hhea' table -> not an sfnt 4200 } 4201 const MetricsHeader* hhea = 4202 reinterpret_cast<const MetricsHeader*>(hb_blob_get_data(hheaTable, &len)); 4203 if (len < sizeof(MetricsHeader)) { 4204 return false; 4205 } 4206 4207 #define SET_UNSIGNED(field, src) \ 4208 aMetrics.field = uint16_t(src) * mFUnitsConvFactor 4209 #define SET_SIGNED(field, src) aMetrics.field = int16_t(src) * mFUnitsConvFactor 4210 4211 SET_UNSIGNED(maxAdvance, hhea->advanceWidthMax); 4212 4213 // 'OS/2' table is optional, if not found we'll estimate xHeight 4214 // and aveCharWidth by measuring glyphs 4215 gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag); 4216 if (os2Table) { 4217 const OS2Table* os2 = 4218 reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len)); 4219 // this should always be present in any valid OS/2 of any version 4220 if (len >= offsetof(OS2Table, xAvgCharWidth) + sizeof(int16_t)) { 4221 SET_SIGNED(aveCharWidth, os2->xAvgCharWidth); 4222 } 4223 } 4224 4225 #undef SET_SIGNED 4226 #undef SET_UNSIGNED 4227 4228 hb_font_t* hbFont = gfxHarfBuzzShaper::CreateHBFont(this); 4229 hb_position_t position; 4230 4231 auto FixedToFloat = [](hb_position_t f) -> gfxFloat { return f / 65536.0; }; 4232 4233 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER, 4234 &position)) { 4235 aMetrics.maxAscent = FixedToFloat(position); 4236 } 4237 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER, 4238 &position)) { 4239 aMetrics.maxDescent = -FixedToFloat(position); 4240 } 4241 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP, 4242 &position)) { 4243 aMetrics.externalLeading = FixedToFloat(position); 4244 } 4245 4246 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_UNDERLINE_OFFSET, 4247 &position)) { 4248 aMetrics.underlineOffset = FixedToFloat(position); 4249 } 4250 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_UNDERLINE_SIZE, 4251 &position)) { 4252 aMetrics.underlineSize = FixedToFloat(position); 4253 } 4254 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_STRIKEOUT_OFFSET, 4255 &position)) { 4256 aMetrics.strikeoutOffset = FixedToFloat(position); 4257 } 4258 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_STRIKEOUT_SIZE, 4259 &position)) { 4260 aMetrics.strikeoutSize = FixedToFloat(position); 4261 } 4262 4263 // Although sxHeight and sCapHeight are signed fields, we consider 4264 // zero/negative values to be erroneous and just ignore them. 4265 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_X_HEIGHT, 4266 &position) && 4267 position > 0) { 4268 aMetrics.xHeight = FixedToFloat(position); 4269 } 4270 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_CAP_HEIGHT, 4271 &position) && 4272 position > 0) { 4273 aMetrics.capHeight = FixedToFloat(position); 4274 } 4275 hb_font_destroy(hbFont); 4276 4277 mIsValid = true; 4278 4279 return true; 4280 } 4281 4282 static double RoundToNearestMultiple(double aValue, double aFraction) { 4283 return floor(aValue / aFraction + 0.5) * aFraction; 4284 } 4285 4286 void gfxFont::CalculateDerivedMetrics(Metrics& aMetrics) { 4287 aMetrics.maxAscent = 4288 ceil(RoundToNearestMultiple(aMetrics.maxAscent, 1 / 1024.0)); 4289 aMetrics.maxDescent = 4290 ceil(RoundToNearestMultiple(aMetrics.maxDescent, 1 / 1024.0)); 4291 4292 if (aMetrics.xHeight <= 0) { 4293 // only happens if we couldn't find either font metrics 4294 // or a char to measure; 4295 // pick an arbitrary value that's better than zero 4296 aMetrics.xHeight = aMetrics.maxAscent * DEFAULT_XHEIGHT_FACTOR; 4297 } 4298 4299 // If we have a font that doesn't provide a capHeight value, use maxAscent 4300 // as a reasonable fallback. 4301 if (aMetrics.capHeight <= 0) { 4302 aMetrics.capHeight = aMetrics.maxAscent; 4303 } 4304 4305 aMetrics.maxHeight = aMetrics.maxAscent + aMetrics.maxDescent; 4306 aMetrics.internalLeading = 4307 std::max(0.0, aMetrics.maxHeight - aMetrics.emHeight); 4308 4309 aMetrics.emAscent = 4310 aMetrics.maxAscent * aMetrics.emHeight / aMetrics.maxHeight; 4311 aMetrics.emDescent = aMetrics.emHeight - aMetrics.emAscent; 4312 4313 if (GetFontEntry()->IsFixedPitch()) { 4314 // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger 4315 // advance than the average character width... this forces 4316 // those fonts to be recognized like fixed pitch fonts by layout. 4317 aMetrics.maxAdvance = aMetrics.aveCharWidth; 4318 } 4319 4320 if (!aMetrics.strikeoutOffset) { 4321 aMetrics.strikeoutOffset = aMetrics.xHeight * 0.5; 4322 } 4323 if (!aMetrics.strikeoutSize) { 4324 aMetrics.strikeoutSize = aMetrics.underlineSize; 4325 } 4326 } 4327 4328 void gfxFont::SanitizeMetrics(gfxFont::Metrics* aMetrics, 4329 bool aIsBadUnderlineFont) { 4330 // Even if this font size is zero, this font is created with non-zero size. 4331 // However, for layout and others, we should return the metrics of zero size 4332 // font. 4333 if (mStyle.AdjustedSizeMustBeZero()) { 4334 memset(aMetrics, 0, sizeof(gfxFont::Metrics)); 4335 return; 4336 } 4337 4338 // If the font entry has ascent/descent/lineGap-override values, 4339 // replace the metrics from the font with the overrides. 4340 gfxFloat adjustedSize = GetAdjustedSize(); 4341 if (mFontEntry->mAscentOverride >= 0.0) { 4342 aMetrics->maxAscent = mFontEntry->mAscentOverride * adjustedSize; 4343 aMetrics->maxHeight = aMetrics->maxAscent + aMetrics->maxDescent; 4344 aMetrics->internalLeading = 4345 std::max(0.0, aMetrics->maxHeight - aMetrics->emHeight); 4346 } 4347 if (mFontEntry->mDescentOverride >= 0.0) { 4348 aMetrics->maxDescent = mFontEntry->mDescentOverride * adjustedSize; 4349 aMetrics->maxHeight = aMetrics->maxAscent + aMetrics->maxDescent; 4350 aMetrics->internalLeading = 4351 std::max(0.0, aMetrics->maxHeight - aMetrics->emHeight); 4352 } 4353 if (mFontEntry->mLineGapOverride >= 0.0) { 4354 aMetrics->externalLeading = mFontEntry->mLineGapOverride * adjustedSize; 4355 } 4356 4357 aMetrics->underlineSize = std::max(1.0, aMetrics->underlineSize); 4358 aMetrics->strikeoutSize = std::max(1.0, aMetrics->strikeoutSize); 4359 4360 aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -1.0); 4361 4362 if (aMetrics->maxAscent < 1.0) { 4363 // We cannot draw strikeout line and overline in the ascent... 4364 aMetrics->underlineSize = 0; 4365 aMetrics->underlineOffset = 0; 4366 aMetrics->strikeoutSize = 0; 4367 aMetrics->strikeoutOffset = 0; 4368 return; 4369 } 4370 4371 /** 4372 * Some CJK fonts have bad underline offset. Therefore, if this is such font, 4373 * we need to lower the underline offset to bottom of *em* descent. 4374 * However, if this is system font, we should not do this for the rendering 4375 * compatibility with another application's UI on the platform. 4376 * XXX Should not use this hack if the font size is too small? 4377 * Such text cannot be read, this might be used for tight CSS 4378 * rendering? (E.g., Acid2) 4379 */ 4380 if (!mStyle.systemFont && aIsBadUnderlineFont) { 4381 // First, we need 2 pixels between baseline and underline at least. Because 4382 // many CJK characters put their glyphs on the baseline, so, 1 pixel is too 4383 // close for CJK characters. 4384 aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -2.0); 4385 4386 // Next, we put the underline to bottom of below of the descent space. 4387 if (aMetrics->internalLeading + aMetrics->externalLeading > 4388 aMetrics->underlineSize) { 4389 aMetrics->underlineOffset = 4390 std::min(aMetrics->underlineOffset, -aMetrics->emDescent); 4391 } else { 4392 aMetrics->underlineOffset = 4393 std::min(aMetrics->underlineOffset, 4394 aMetrics->underlineSize - aMetrics->emDescent); 4395 } 4396 } 4397 // If underline positioned is too far from the text, descent position is 4398 // preferred so that underline will stay within the boundary. 4399 else if (aMetrics->underlineSize - aMetrics->underlineOffset > 4400 aMetrics->maxDescent) { 4401 if (aMetrics->underlineSize > aMetrics->maxDescent) 4402 aMetrics->underlineSize = std::max(aMetrics->maxDescent, 1.0); 4403 // The max underlineOffset is 1px (the min underlineSize is 1px, and min 4404 // maxDescent is 0px.) 4405 aMetrics->underlineOffset = aMetrics->underlineSize - aMetrics->maxDescent; 4406 } 4407 4408 // If strikeout line is overflowed from the ascent, the line should be resized 4409 // and moved for that being in the ascent space. Note that the strikeoutOffset 4410 // is *middle* of the strikeout line position. 4411 gfxFloat halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5); 4412 if (halfOfStrikeoutSize + aMetrics->strikeoutOffset > aMetrics->maxAscent) { 4413 if (aMetrics->strikeoutSize > aMetrics->maxAscent) { 4414 aMetrics->strikeoutSize = std::max(aMetrics->maxAscent, 1.0); 4415 halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5); 4416 } 4417 gfxFloat ascent = floor(aMetrics->maxAscent + 0.5); 4418 aMetrics->strikeoutOffset = std::max(halfOfStrikeoutSize, ascent / 2.0); 4419 } 4420 4421 // If overline is larger than the ascent, the line should be resized. 4422 if (aMetrics->underlineSize > aMetrics->maxAscent) { 4423 aMetrics->underlineSize = aMetrics->maxAscent; 4424 } 4425 } 4426 4427 gfxFont::Baselines gfxFont::GetBaselines(Orientation aOrientation) { 4428 // Approximated baselines for fonts lacking actual baseline data. These are 4429 // fractions of the em ascent/descent from the alphabetic baseline. 4430 const double kHangingBaselineDefault = 0.8; // fraction of ascent 4431 const double kIdeographicBaselineDefault = -0.5; // fraction of descent 4432 4433 // If no BASE table is present, just return synthetic values immediately. 4434 if (!mFontEntry->HasFontTable(TRUETYPE_TAG('B', 'A', 'S', 'E'))) { 4435 // No baseline table; just synthesize them immediately. 4436 const Metrics& metrics = GetMetrics(aOrientation); 4437 return Baselines{ 4438 0.0, // alphabetic 4439 kHangingBaselineDefault * metrics.emAscent, // hanging 4440 kIdeographicBaselineDefault * metrics.emDescent // ideographic 4441 }; 4442 } 4443 4444 // Use harfbuzz to try to read the font's baseline metrics. 4445 Baselines result{NAN, NAN, NAN}; 4446 hb_font_t* hbFont = gfxHarfBuzzShaper::CreateHBFont(this); 4447 hb_direction_t hbDir = aOrientation == nsFontMetrics::eHorizontal 4448 ? HB_DIRECTION_LTR 4449 : HB_DIRECTION_TTB; 4450 hb_position_t position; 4451 unsigned count = 0; 4452 auto Fix2Float = [](hb_position_t f) -> gfxFloat { return f / 65536.0; }; 4453 if (hb_ot_layout_get_baseline(hbFont, HB_OT_LAYOUT_BASELINE_TAG_ROMAN, hbDir, 4454 HB_OT_TAG_DEFAULT_SCRIPT, 4455 HB_OT_TAG_DEFAULT_LANGUAGE, &position)) { 4456 result.mAlphabetic = Fix2Float(position); 4457 count++; 4458 } 4459 if (hb_ot_layout_get_baseline(hbFont, HB_OT_LAYOUT_BASELINE_TAG_HANGING, 4460 hbDir, HB_OT_TAG_DEFAULT_SCRIPT, 4461 HB_OT_TAG_DEFAULT_LANGUAGE, &position)) { 4462 result.mHanging = Fix2Float(position); 4463 count++; 4464 } 4465 if (hb_ot_layout_get_baseline( 4466 hbFont, HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT, hbDir, 4467 HB_OT_TAG_DEFAULT_SCRIPT, HB_OT_TAG_DEFAULT_LANGUAGE, &position)) { 4468 result.mIdeographic = Fix2Float(position); 4469 count++; 4470 } 4471 hb_font_destroy(hbFont); 4472 // If we successfully read all three, we can return now. 4473 if (count == 3) { 4474 return result; 4475 } 4476 4477 // Synthesize the baselines that we didn't find in the font. 4478 const Metrics& metrics = GetMetrics(aOrientation); 4479 if (std::isnan(result.mAlphabetic)) { 4480 result.mAlphabetic = 0.0; 4481 } 4482 if (std::isnan(result.mHanging)) { 4483 result.mHanging = kHangingBaselineDefault * metrics.emAscent; 4484 } 4485 if (std::isnan(result.mIdeographic)) { 4486 result.mIdeographic = kIdeographicBaselineDefault * metrics.emDescent; 4487 } 4488 4489 return result; 4490 } 4491 4492 // Create a Metrics record to be used for vertical layout. This should never 4493 // fail, as we've already decided this is a valid font. We do not have the 4494 // option of marking it invalid (as can happen if we're unable to read 4495 // horizontal metrics), because that could break a font that we're already 4496 // using for horizontal text. 4497 // So we will synthesize *something* usable here even if there aren't any of the 4498 // usual font tables (which can happen in the case of a legacy bitmap or Type1 4499 // font for which the platform-specific backend used platform APIs instead of 4500 // sfnt tables to create the horizontal metrics). 4501 void gfxFont::CreateVerticalMetrics() { 4502 const uint32_t kHheaTableTag = TRUETYPE_TAG('h', 'h', 'e', 'a'); 4503 const uint32_t kVheaTableTag = TRUETYPE_TAG('v', 'h', 'e', 'a'); 4504 const uint32_t kPostTableTag = TRUETYPE_TAG('p', 'o', 's', 't'); 4505 const uint32_t kOS_2TableTag = TRUETYPE_TAG('O', 'S', '/', '2'); 4506 uint32_t len; 4507 4508 auto* metrics = new Metrics(); 4509 ::memset(metrics, 0, sizeof(Metrics)); 4510 4511 // Some basic defaults, in case the font lacks any real metrics tables. 4512 // TODO: consider what rounding (if any) we should apply to these. 4513 metrics->emHeight = GetAdjustedSize(); 4514 metrics->emAscent = metrics->emHeight / 2; 4515 metrics->emDescent = metrics->emHeight - metrics->emAscent; 4516 4517 metrics->maxAscent = metrics->emAscent; 4518 metrics->maxDescent = metrics->emDescent; 4519 4520 const float UNINITIALIZED_LEADING = -10000.0f; 4521 metrics->externalLeading = UNINITIALIZED_LEADING; 4522 4523 if (mFUnitsConvFactor < 0.0) { 4524 uint16_t upem = GetFontEntry()->UnitsPerEm(); 4525 if (upem != gfxFontEntry::kInvalidUPEM) { 4526 AutoWriteLock lock(mLock); 4527 mFUnitsConvFactor = GetAdjustedSize() / upem; 4528 } 4529 } 4530 4531 #define SET_UNSIGNED(field, src) \ 4532 metrics->field = uint16_t(src) * mFUnitsConvFactor 4533 #define SET_SIGNED(field, src) metrics->field = int16_t(src) * mFUnitsConvFactor 4534 4535 gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag); 4536 if (os2Table && mFUnitsConvFactor >= 0.0) { 4537 const OS2Table* os2 = 4538 reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len)); 4539 // These fields should always be present in any valid OS/2 table 4540 if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) { 4541 SET_SIGNED(strikeoutSize, os2->yStrikeoutSize); 4542 // Use ascent+descent from the horizontal metrics as the default 4543 // advance (aveCharWidth) in vertical mode 4544 gfxFloat ascentDescent = 4545 gfxFloat(mFUnitsConvFactor) * 4546 (int16_t(os2->sTypoAscender) - int16_t(os2->sTypoDescender)); 4547 metrics->aveCharWidth = std::max(metrics->emHeight, ascentDescent); 4548 // Use xAvgCharWidth from horizontal metrics as minimum font extent 4549 // for vertical layout, applying half of it to ascent and half to 4550 // descent (to work with a default centered baseline). 4551 gfxFloat halfCharWidth = 4552 int16_t(os2->xAvgCharWidth) * gfxFloat(mFUnitsConvFactor) / 2; 4553 metrics->maxAscent = std::max(metrics->maxAscent, halfCharWidth); 4554 metrics->maxDescent = std::max(metrics->maxDescent, halfCharWidth); 4555 } 4556 } 4557 4558 // If we didn't set aveCharWidth from OS/2, try to read 'hhea' metrics 4559 // and use the line height from its ascent/descent. 4560 if (!metrics->aveCharWidth) { 4561 gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag); 4562 if (hheaTable && mFUnitsConvFactor >= 0.0) { 4563 const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>( 4564 hb_blob_get_data(hheaTable, &len)); 4565 if (len >= sizeof(MetricsHeader)) { 4566 SET_SIGNED(aveCharWidth, 4567 int16_t(hhea->ascender) - int16_t(hhea->descender)); 4568 metrics->maxAscent = metrics->aveCharWidth / 2; 4569 metrics->maxDescent = metrics->aveCharWidth - metrics->maxAscent; 4570 } 4571 } 4572 } 4573 4574 // Read real vertical metrics if available. 4575 metrics->ideographicWidth = -1.0; 4576 metrics->zeroWidth = -1.0; 4577 gfxFontEntry::AutoTable vheaTable(mFontEntry, kVheaTableTag); 4578 if (vheaTable && mFUnitsConvFactor >= 0.0) { 4579 const MetricsHeader* vhea = reinterpret_cast<const MetricsHeader*>( 4580 hb_blob_get_data(vheaTable, &len)); 4581 if (len >= sizeof(MetricsHeader)) { 4582 SET_UNSIGNED(maxAdvance, vhea->advanceWidthMax); 4583 // Redistribute space between ascent/descent because we want a 4584 // centered vertical baseline by default. 4585 gfxFloat halfExtent = 4586 0.5 * gfxFloat(mFUnitsConvFactor) * 4587 (int16_t(vhea->ascender) + std::abs(int16_t(vhea->descender))); 4588 // Some bogus fonts have ascent and descent set to zero in 'vhea'. 4589 // In that case we just ignore them and keep our synthetic values 4590 // from above. 4591 if (halfExtent > 0) { 4592 metrics->maxAscent = halfExtent; 4593 metrics->maxDescent = halfExtent; 4594 SET_SIGNED(externalLeading, vhea->lineGap); 4595 } 4596 // Call gfxHarfBuzzShaper::GetGlyphVAdvance directly, as GetCharAdvance 4597 // would potentially recurse if no v-advance is available and it attempts 4598 // to fall back to a value from mVerticalMetrics. 4599 if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) { 4600 uint32_t gid = ProvidesGetGlyph() 4601 ? GetGlyph(kWaterIdeograph, 0) 4602 : shaper->GetNominalGlyph(kWaterIdeograph); 4603 if (gid) { 4604 int32_t advance = shaper->GetGlyphVAdvance(gid); 4605 // Convert 16.16 fixed-point advance from the shaper to a float. 4606 metrics->ideographicWidth = 4607 advance < 0 ? metrics->aveCharWidth : advance / 65536.0; 4608 } 4609 gid = ProvidesGetGlyph() ? GetGlyph('0', 0) 4610 : shaper->GetNominalGlyph('0'); 4611 if (gid) { 4612 int32_t advance = shaper->GetGlyphVAdvance(gid); 4613 metrics->zeroWidth = 4614 advance < 0 ? metrics->aveCharWidth : advance / 65536.0; 4615 } 4616 } 4617 } 4618 } 4619 4620 // If we didn't set aveCharWidth above, we must be dealing with a non-sfnt 4621 // font of some kind (Type1, bitmap, vector, ...), so fall back to using 4622 // whatever the platform backend figured out for horizontal layout. 4623 // And if we haven't set externalLeading yet, then copy that from the 4624 // horizontal metrics as well, to help consistency of CSS line-height. 4625 if (!metrics->aveCharWidth || 4626 metrics->externalLeading == UNINITIALIZED_LEADING) { 4627 const Metrics& horizMetrics = GetHorizontalMetrics(); 4628 if (!metrics->aveCharWidth) { 4629 metrics->aveCharWidth = horizMetrics.maxAscent + horizMetrics.maxDescent; 4630 } 4631 if (metrics->externalLeading == UNINITIALIZED_LEADING) { 4632 metrics->externalLeading = horizMetrics.externalLeading; 4633 } 4634 } 4635 4636 // Get underline thickness from the 'post' table if available. 4637 // We also read the underline position, although in vertical-upright mode 4638 // this will not be appropriate to use directly (see nsTextFrame.cpp). 4639 gfxFontEntry::AutoTable postTable(mFontEntry, kPostTableTag); 4640 if (postTable) { 4641 const PostTable* post = 4642 reinterpret_cast<const PostTable*>(hb_blob_get_data(postTable, &len)); 4643 if (len >= offsetof(PostTable, underlineThickness) + sizeof(uint16_t)) { 4644 static_assert(offsetof(PostTable, underlinePosition) < 4645 offsetof(PostTable, underlineThickness), 4646 "broken PostTable struct?"); 4647 SET_SIGNED(underlineOffset, post->underlinePosition); 4648 SET_UNSIGNED(underlineSize, post->underlineThickness); 4649 // Also use for strikeout if we didn't find that in OS/2 above. 4650 if (!metrics->strikeoutSize) { 4651 metrics->strikeoutSize = metrics->underlineSize; 4652 } 4653 } 4654 } 4655 4656 #undef SET_UNSIGNED 4657 #undef SET_SIGNED 4658 4659 // If we didn't read this from a vhea table, it will still be zero. 4660 // In any case, let's make sure it is not less than the value we've 4661 // come up with for aveCharWidth. 4662 metrics->maxAdvance = std::max(metrics->maxAdvance, metrics->aveCharWidth); 4663 4664 // Thickness of underline and strikeout may have been read from tables, 4665 // but in case they were not present, ensure a minimum of 1 pixel. 4666 metrics->underlineSize = std::max(1.0, metrics->underlineSize); 4667 4668 metrics->strikeoutSize = std::max(1.0, metrics->strikeoutSize); 4669 metrics->strikeoutOffset = -0.5 * metrics->strikeoutSize; 4670 4671 // Somewhat arbitrary values for now, subject to future refinement... 4672 metrics->spaceWidth = metrics->aveCharWidth; 4673 metrics->xHeight = metrics->emHeight / 2; 4674 metrics->capHeight = metrics->maxAscent; 4675 4676 metrics->maxHeight = metrics->maxAscent + metrics->maxDescent; 4677 metrics->internalLeading = 4678 std::max(0.0, metrics->maxHeight - metrics->emHeight); 4679 4680 if (metrics->zeroWidth < 0.0) { 4681 metrics->zeroWidth = metrics->aveCharWidth; 4682 } 4683 4684 if (!mVerticalMetrics.compareExchange(nullptr, metrics)) { 4685 delete metrics; 4686 } 4687 } 4688 4689 gfxFloat gfxFont::SynthesizeSpaceWidth(uint32_t aCh) { 4690 // return an appropriate width for various Unicode space characters 4691 // that we "fake" if they're not actually present in the font; 4692 // returns negative value if the char is not a known space. 4693 switch (aCh) { 4694 case 0x2000: // en quad 4695 case 0x2002: 4696 return GetAdjustedSize() / 2; // en space 4697 case 0x2001: // em quad 4698 case 0x2003: 4699 return GetAdjustedSize(); // em space 4700 case 0x2004: 4701 return GetAdjustedSize() / 3; // three-per-em space 4702 case 0x2005: 4703 return GetAdjustedSize() / 4; // four-per-em space 4704 case 0x2006: 4705 return GetAdjustedSize() / 6; // six-per-em space 4706 case 0x2007: 4707 return GetMetrics(nsFontMetrics::eHorizontal) 4708 .ZeroOrAveCharWidth(); // figure space 4709 case 0x2008: 4710 return GetMetrics(nsFontMetrics::eHorizontal) 4711 .spaceWidth; // punctuation space 4712 case 0x2009: 4713 return GetAdjustedSize() / 5; // thin space 4714 case 0x200a: 4715 return GetAdjustedSize() / 10; // hair space 4716 case 0x202f: 4717 return GetAdjustedSize() / 5; // narrow no-break space 4718 case 0x3000: 4719 return GetAdjustedSize(); // ideographic space 4720 default: 4721 return -1.0; 4722 } 4723 } 4724 4725 void gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, 4726 FontCacheSizes* aSizes) const { 4727 AutoReadLock lock(mLock); 4728 for (uint32_t i = 0; i < mGlyphExtentsArray.Length(); ++i) { 4729 aSizes->mFontInstances += 4730 mGlyphExtentsArray[i]->SizeOfIncludingThis(aMallocSizeOf); 4731 } 4732 if (mWordCache) { 4733 aSizes->mShapedWords += 4734 mWordCache->shallowSizeOfIncludingThis(aMallocSizeOf); 4735 for (auto it = mWordCache->iter(); !it.done(); it.next()) { 4736 aSizes->mShapedWords += 4737 it.get().value()->SizeOfIncludingThis(aMallocSizeOf); 4738 } 4739 } 4740 } 4741 4742 void gfxFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, 4743 FontCacheSizes* aSizes) const { 4744 aSizes->mFontInstances += aMallocSizeOf(this); 4745 AddSizeOfExcludingThis(aMallocSizeOf, aSizes); 4746 } 4747 4748 void gfxFont::AddGlyphChangeObserver(GlyphChangeObserver* aObserver) { 4749 AutoWriteLock lock(mLock); 4750 if (!mGlyphChangeObservers) { 4751 mGlyphChangeObservers = MakeUnique<nsTHashSet<GlyphChangeObserver*>>(); 4752 } 4753 mGlyphChangeObservers->Insert(aObserver); 4754 } 4755 4756 void gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver* aObserver) { 4757 AutoWriteLock lock(mLock); 4758 NS_ASSERTION(mGlyphChangeObservers, "No observers registered"); 4759 NS_ASSERTION(mGlyphChangeObservers->Contains(aObserver), 4760 "Observer not registered"); 4761 mGlyphChangeObservers->Remove(aObserver); 4762 } 4763 4764 #define DEFAULT_PIXEL_FONT_SIZE 16.0f 4765 4766 gfxFontStyle::gfxFontStyle() 4767 : size(DEFAULT_PIXEL_FONT_SIZE), 4768 sizeAdjust(0.0f), 4769 baselineOffset(0.0f), 4770 languageOverride{0}, 4771 weight(FontWeight::NORMAL), 4772 stretch(FontStretch::NORMAL), 4773 style(FontSlantStyle::NORMAL), 4774 variantCaps(NS_FONT_VARIANT_CAPS_NORMAL), 4775 variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL), 4776 sizeAdjustBasis(uint8_t(FontSizeAdjust::Tag::None)), 4777 systemFont(true), 4778 printerFont(false), 4779 #ifdef XP_WIN 4780 allowForceGDIClassic(true), 4781 #endif 4782 useGrayscaleAntialiasing(false), 4783 allowSyntheticWeight(true), 4784 synthesisStyle(StyleFontSynthesisStyle::Auto), 4785 allowSyntheticSmallCaps(true), 4786 useSyntheticPosition(true), 4787 noFallbackVariantFeatures(true) { 4788 } 4789 4790 gfxFontStyle::gfxFontStyle(FontSlantStyle aStyle, FontWeight aWeight, 4791 FontStretch aStretch, gfxFloat aSize, 4792 const FontSizeAdjust& aSizeAdjust, bool aSystemFont, 4793 bool aPrinterFont, 4794 #ifdef XP_WIN 4795 bool aAllowForceGDIClassic, 4796 #endif 4797 bool aAllowWeightSynthesis, 4798 StyleFontSynthesisStyle aStyleSynthesis, 4799 bool aAllowSmallCapsSynthesis, 4800 bool aUsePositionSynthesis, 4801 StyleFontLanguageOverride aLanguageOverride) 4802 : size(aSize), 4803 baselineOffset(0.0f), 4804 languageOverride(aLanguageOverride), 4805 weight(aWeight), 4806 stretch(aStretch), 4807 style(aStyle), 4808 variantCaps(NS_FONT_VARIANT_CAPS_NORMAL), 4809 variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL), 4810 systemFont(aSystemFont), 4811 printerFont(aPrinterFont), 4812 #ifdef XP_WIN 4813 allowForceGDIClassic(aAllowForceGDIClassic), 4814 #endif 4815 useGrayscaleAntialiasing(false), 4816 allowSyntheticWeight(aAllowWeightSynthesis), 4817 synthesisStyle(aStyleSynthesis), 4818 allowSyntheticSmallCaps(aAllowSmallCapsSynthesis), 4819 useSyntheticPosition(aUsePositionSynthesis), 4820 noFallbackVariantFeatures(true) { 4821 MOZ_ASSERT(!std::isnan(size)); 4822 4823 sizeAdjustBasis = uint8_t(aSizeAdjust.tag); 4824 // sizeAdjustBasis is currently a small bitfield, so let's assert that the 4825 // tag value was not truncated. 4826 MOZ_ASSERT(FontSizeAdjust::Tag(sizeAdjustBasis) == aSizeAdjust.tag, 4827 "gfxFontStyle.sizeAdjustBasis too small?"); 4828 4829 #define HANDLE_TAG(TAG) \ 4830 case FontSizeAdjust::Tag::TAG: \ 4831 sizeAdjust = aSizeAdjust.As##TAG(); \ 4832 break; 4833 4834 switch (aSizeAdjust.tag) { 4835 case FontSizeAdjust::Tag::None: 4836 sizeAdjust = 0.0f; 4837 break; 4838 HANDLE_TAG(ExHeight) 4839 HANDLE_TAG(CapHeight) 4840 HANDLE_TAG(ChWidth) 4841 HANDLE_TAG(IcWidth) 4842 HANDLE_TAG(IcHeight) 4843 } 4844 4845 #undef HANDLE_TAG 4846 4847 MOZ_ASSERT(!std::isnan(sizeAdjust)); 4848 4849 if (weight > FontWeight::FromInt(1000)) { 4850 weight = FontWeight::FromInt(1000); 4851 } 4852 if (weight < FontWeight::FromInt(1)) { 4853 weight = FontWeight::FromInt(1); 4854 } 4855 4856 if (size >= FONT_MAX_SIZE) { 4857 size = FONT_MAX_SIZE; 4858 sizeAdjust = 0.0f; 4859 sizeAdjustBasis = uint8_t(FontSizeAdjust::Tag::None); 4860 } else if (size < 0.0) { 4861 NS_WARNING("negative font size"); 4862 size = 0.0; 4863 } 4864 } 4865 4866 PLDHashNumber gfxFontStyle::Hash() const { 4867 uint32_t hash = variationSettings.IsEmpty() 4868 ? 0 4869 : mozilla::HashBytes(variationSettings.Elements(), 4870 variationSettings.Length() * 4871 sizeof(gfxFontVariation)); 4872 return mozilla::AddToHash(hash, systemFont, style.Raw(), stretch.Raw(), 4873 weight.Raw(), size, int32_t(sizeAdjust * 1000.0f)); 4874 } 4875 4876 void gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel) { 4877 MOZ_ASSERT( 4878 variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL && baselineOffset == 0, 4879 "can't adjust this style for sub/superscript"); 4880 4881 // calculate the baseline offset (before changing the size) 4882 if (variantSubSuper == NS_FONT_VARIANT_POSITION_SUPER) { 4883 baselineOffset = size * -NS_FONT_SUPERSCRIPT_OFFSET_RATIO; 4884 } else { 4885 baselineOffset = size * NS_FONT_SUBSCRIPT_OFFSET_RATIO; 4886 } 4887 4888 // calculate reduced size, roughly mimicing behavior of font-size: smaller 4889 float cssSize = size * aAppUnitsPerDevPixel / AppUnitsPerCSSPixel(); 4890 if (cssSize < NS_FONT_SUB_SUPER_SMALL_SIZE) { 4891 size *= NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL; 4892 } else if (cssSize >= NS_FONT_SUB_SUPER_LARGE_SIZE) { 4893 size *= NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE; 4894 } else { 4895 gfxFloat t = (cssSize - NS_FONT_SUB_SUPER_SMALL_SIZE) / 4896 (NS_FONT_SUB_SUPER_LARGE_SIZE - NS_FONT_SUB_SUPER_SMALL_SIZE); 4897 size *= (1.0 - t) * NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL + 4898 t * NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE; 4899 } 4900 4901 // clear the variant field 4902 variantSubSuper = NS_FONT_VARIANT_POSITION_NORMAL; 4903 } 4904 4905 bool gfxFont::TryGetMathTable() { 4906 if (mMathInitialized) { 4907 return !!mMathTable; 4908 } 4909 4910 auto face(GetFontEntry()->GetHBFace()); 4911 if (hb_ot_math_has_data(face)) { 4912 auto* mathTable = new gfxMathTable(face, GetAdjustedSize()); 4913 if (!mMathTable.compareExchange(nullptr, mathTable)) { 4914 delete mathTable; 4915 } 4916 } 4917 mMathInitialized = true; 4918 4919 return !!mMathTable; 4920 }