Bug 1932613 - temporarily disable browser_ml_end_to_end.js for permanent failures...
[gecko.git] / gfx / thebes / gfxFont.cpp
blob4661bc986bfad756f51b6ac6bb4aef524b820a9e
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/. */
6 #include "gfxFont.h"
8 #include "mozilla/BinarySearch.h"
9 #include "mozilla/DebugOnly.h"
10 #include "mozilla/FontPropertyTypes.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/IntegerRange.h"
13 #include "mozilla/intl/Segmenter.h"
14 #include "mozilla/MathAlgorithms.h"
15 #include "mozilla/StaticPrefs_gfx.h"
16 #include "mozilla/ScopeExit.h"
17 #include "mozilla/SVGContextPaint.h"
19 #include "mozilla/Logging.h"
21 #include "nsITimer.h"
23 #include "gfxGlyphExtents.h"
24 #include "gfxPlatform.h"
25 #include "gfxTextRun.h"
26 #include "nsGkAtoms.h"
28 #include "gfxTypes.h"
29 #include "gfxContext.h"
30 #include "gfxFontMissingGlyphs.h"
31 #include "gfxGraphiteShaper.h"
32 #include "gfxHarfBuzzShaper.h"
33 #include "gfxUserFontSet.h"
34 #include "nsCRT.h"
35 #include "nsContentUtils.h"
36 #include "nsSpecialCasingData.h"
37 #include "nsTextRunTransformations.h"
38 #include "nsUGenCategory.h"
39 #include "nsUnicodeProperties.h"
40 #include "nsStyleConsts.h"
41 #include "mozilla/AppUnits.h"
42 #include "mozilla/HashTable.h"
43 #include "mozilla/Likely.h"
44 #include "mozilla/MemoryReporting.h"
45 #include "mozilla/Preferences.h"
46 #include "mozilla/Services.h"
47 #include "mozilla/Telemetry.h"
48 #include "gfxMathTable.h"
49 #include "gfxSVGGlyphs.h"
50 #include "gfx2DGlue.h"
51 #include "TextDrawTarget.h"
53 #include "ThebesRLBox.h"
55 #include "GreekCasing.h"
57 #include "cairo.h"
58 #ifdef XP_WIN
59 # include "cairo-win32.h"
60 # include "gfxWindowsPlatform.h"
61 #endif
63 #include "harfbuzz/hb.h"
64 #include "harfbuzz/hb-ot.h"
66 #include <algorithm>
67 #include <limits>
68 #include <cmath>
70 using namespace mozilla;
71 using namespace mozilla::gfx;
72 using namespace mozilla::unicode;
73 using mozilla::services::GetObserverService;
75 gfxFontCache* gfxFontCache::gGlobalCache = nullptr;
77 #ifdef DEBUG_roc
78 # define DEBUG_TEXT_RUN_STORAGE_METRICS
79 #endif
81 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
82 uint32_t gTextRunStorageHighWaterMark = 0;
83 uint32_t gTextRunStorage = 0;
84 uint32_t gFontCount = 0;
85 uint32_t gGlyphExtentsCount = 0;
86 uint32_t gGlyphExtentsWidthsTotalSize = 0;
87 uint32_t gGlyphExtentsSetupEagerSimple = 0;
88 uint32_t gGlyphExtentsSetupEagerTight = 0;
89 uint32_t gGlyphExtentsSetupLazyTight = 0;
90 uint32_t gGlyphExtentsSetupFallBackToTight = 0;
91 #endif
93 #define LOG_FONTINIT(args) \
94 MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args)
95 #define LOG_FONTINIT_ENABLED() \
96 MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug)
99 * gfxFontCache - global cache of gfxFont instances.
100 * Expires unused fonts after a short interval;
101 * notifies fonts to age their cached shaped-word records;
102 * observes memory-pressure notification and tells fonts to clear their
103 * shaped-word caches to free up memory.
106 MOZ_DEFINE_MALLOC_SIZE_OF(FontCacheMallocSizeOf)
108 NS_IMPL_ISUPPORTS(gfxFontCache::MemoryReporter, nsIMemoryReporter)
110 /*virtual*/
111 gfxTextRunFactory::~gfxTextRunFactory() {
112 // Should not be dropped by stylo
113 MOZ_ASSERT(!Servo_IsWorkerThread());
116 NS_IMETHODIMP
117 gfxFontCache::MemoryReporter::CollectReports(
118 nsIHandleReportCallback* aHandleReport, nsISupports* aData,
119 bool aAnonymize) {
120 FontCacheSizes sizes;
122 gfxFontCache::GetCache()->AddSizeOfIncludingThis(&FontCacheMallocSizeOf,
123 &sizes);
125 MOZ_COLLECT_REPORT("explicit/gfx/font-cache", KIND_HEAP, UNITS_BYTES,
126 sizes.mFontInstances,
127 "Memory used for active font instances.");
129 MOZ_COLLECT_REPORT("explicit/gfx/font-shaped-words", KIND_HEAP, UNITS_BYTES,
130 sizes.mShapedWords,
131 "Memory used to cache shaped glyph data.");
133 return NS_OK;
136 NS_IMPL_ISUPPORTS(gfxFontCache::Observer, nsIObserver)
138 NS_IMETHODIMP
139 gfxFontCache::Observer::Observe(nsISupports* aSubject, const char* aTopic,
140 const char16_t* someData) {
141 if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
142 gfxFontCache* fontCache = gfxFontCache::GetCache();
143 if (fontCache) {
144 fontCache->FlushShapedWordCaches();
146 } else {
147 MOZ_ASSERT_UNREACHABLE("unexpected notification topic");
149 return NS_OK;
152 nsresult gfxFontCache::Init() {
153 NS_ASSERTION(!gGlobalCache, "Where did this come from?");
154 gGlobalCache = new gfxFontCache(GetMainThreadSerialEventTarget());
155 if (!gGlobalCache) {
156 return NS_ERROR_OUT_OF_MEMORY;
158 RegisterStrongMemoryReporter(new MemoryReporter());
159 return NS_OK;
162 void gfxFontCache::Shutdown() {
163 delete gGlobalCache;
164 gGlobalCache = nullptr;
166 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
167 printf("Textrun storage high water mark=%d\n", gTextRunStorageHighWaterMark);
168 printf("Total number of fonts=%d\n", gFontCount);
169 printf("Total glyph extents allocated=%d (size %d)\n", gGlyphExtentsCount,
170 int(gGlyphExtentsCount * sizeof(gfxGlyphExtents)));
171 printf("Total glyph extents width-storage size allocated=%d\n",
172 gGlyphExtentsWidthsTotalSize);
173 printf("Number of simple glyph extents eagerly requested=%d\n",
174 gGlyphExtentsSetupEagerSimple);
175 printf("Number of tight glyph extents eagerly requested=%d\n",
176 gGlyphExtentsSetupEagerTight);
177 printf("Number of tight glyph extents lazily requested=%d\n",
178 gGlyphExtentsSetupLazyTight);
179 printf("Number of simple glyph extent setups that fell back to tight=%d\n",
180 gGlyphExtentsSetupFallBackToTight);
181 #endif
184 gfxFontCache::gfxFontCache(nsIEventTarget* aEventTarget)
185 : ExpirationTrackerImpl<gfxFont, 3, Lock, AutoLock>(
186 FONT_TIMEOUT_SECONDS * 1000, "gfxFontCache", aEventTarget) {
187 nsCOMPtr<nsIObserverService> obs = GetObserverService();
188 if (obs) {
189 obs->AddObserver(new Observer, "memory-pressure", false);
192 nsIEventTarget* target = nullptr;
193 if (XRE_IsContentProcess() && NS_IsMainThread()) {
194 target = aEventTarget;
197 // Create the timer used to expire shaped-word records from each font's
198 // cache after a short period of non-use. We have a single timer in
199 // gfxFontCache that loops over all fonts known to the cache, to avoid
200 // the overhead of individual timers in each font instance.
201 // The timer will be started any time shaped word records are cached
202 // (and pauses itself when all caches become empty).
203 mWordCacheExpirationTimer = NS_NewTimer(target);
206 gfxFontCache::~gfxFontCache() {
207 // Ensure the user font cache releases its references to font entries,
208 // so they aren't kept alive after the font instances and font-list
209 // have been shut down.
210 gfxUserFontSet::UserFontCache::Shutdown();
212 if (mWordCacheExpirationTimer) {
213 mWordCacheExpirationTimer->Cancel();
214 mWordCacheExpirationTimer = nullptr;
217 // Expire everything manually so we don't leak them.
218 Flush();
221 bool gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey) const {
222 const gfxCharacterMap* fontUnicodeRangeMap = mFont->GetUnicodeRangeMap();
223 return aKey->mFontEntry == mFont->GetFontEntry() &&
224 aKey->mStyle->Equals(*mFont->GetStyle()) &&
225 ((!aKey->mUnicodeRangeMap && !fontUnicodeRangeMap) ||
226 (aKey->mUnicodeRangeMap && fontUnicodeRangeMap &&
227 aKey->mUnicodeRangeMap->Equals(fontUnicodeRangeMap)));
230 already_AddRefed<gfxFont> gfxFontCache::Lookup(
231 const gfxFontEntry* aFontEntry, const gfxFontStyle* aStyle,
232 const gfxCharacterMap* aUnicodeRangeMap) {
233 MutexAutoLock lock(mMutex);
235 Key key(aFontEntry, aStyle, aUnicodeRangeMap);
236 HashEntry* entry = mFonts.GetEntry(key);
238 Telemetry::Accumulate(Telemetry::FONT_CACHE_HIT, entry != nullptr);
240 if (!entry) {
241 return nullptr;
244 RefPtr<gfxFont> font = entry->mFont;
245 if (font->GetExpirationState()->IsTracked()) {
246 RemoveObjectLocked(font, lock);
248 return font.forget();
251 already_AddRefed<gfxFont> gfxFontCache::MaybeInsert(gfxFont* aFont) {
252 MOZ_ASSERT(aFont);
253 MutexAutoLock lock(mMutex);
255 Key key(aFont->GetFontEntry(), aFont->GetStyle(),
256 aFont->GetUnicodeRangeMap());
257 HashEntry* entry = mFonts.PutEntry(key);
258 if (!entry) {
259 return do_AddRef(aFont);
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);
278 return do_AddRef(entry->mFont);
281 bool gfxFontCache::MaybeDestroy(gfxFont* aFont) {
282 MOZ_ASSERT(aFont);
283 MutexAutoLock lock(mMutex);
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;
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;
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;
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;
312 mFonts.RemoveEntry(entry);
313 return true;
316 void gfxFontCache::NotifyExpiredLocked(gfxFont* aFont, const AutoLock& aLock) {
317 MOZ_ASSERT(aFont->GetRefCount() == 0);
319 RemoveObjectLocked(aFont, aLock);
320 mTrackerDiscard.AppendElement(aFont);
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;
330 mFonts.RemoveEntry(entry);
333 void gfxFontCache::NotifyHandlerEnd() {
334 nsTArray<gfxFont*> discard;
336 MutexAutoLock lock(mMutex);
337 discard = std::move(mTrackerDiscard);
339 DestroyDiscard(discard);
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();
349 aDiscard.Clear();
352 void gfxFontCache::Flush() {
353 nsTArray<gfxFont*> discard;
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;
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);
374 } else {
375 MOZ_ASSERT(!entry->mFont->GetExpirationState()->IsTracked());
378 MOZ_ASSERT(IsEmptyLocked(lock),
379 "Cache tracker still has fonts after flush!");
380 mFonts.Clear();
382 DestroyDiscard(discard);
385 /*static*/
386 void gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer,
387 void* aCache) {
388 gfxFontCache* cache = static_cast<gfxFontCache*>(aCache);
389 cache->AgeCachedWords();
392 void gfxFontCache::AgeCachedWords() {
393 bool allEmpty = true;
395 MutexAutoLock lock(mMutex);
396 for (const auto& entry : mFonts) {
397 allEmpty = entry.mFont->AgeCachedWords() && allEmpty;
400 if (allEmpty) {
401 PauseWordCacheExpirationTimer();
405 void gfxFontCache::FlushShapedWordCaches() {
407 MutexAutoLock lock(mMutex);
408 for (const auto& entry : mFonts) {
409 entry.mFont->ClearCachedWords();
412 PauseWordCacheExpirationTimer();
415 void gfxFontCache::NotifyGlyphsChanged() {
416 MutexAutoLock lock(mMutex);
417 for (const auto& entry : mFonts) {
418 entry.mFont->NotifyGlyphsChanged();
422 void gfxFontCache::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
423 FontCacheSizes* aSizes) const {
424 // TODO: add the overhead of the expiration tracker (generation arrays)
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);
433 void gfxFontCache::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
434 FontCacheSizes* aSizes) const {
435 aSizes->mFontInstances += aMallocSizeOf(this);
436 AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
439 #define MAX_SSXX_VALUE 99
440 #define MAX_CVXX_VALUE 99
442 static void LookupAlternateValues(const gfxFontFeatureValueSet& aFeatureLookup,
443 const nsACString& aFamily,
444 const StyleVariantAlternates& aAlternates,
445 nsTArray<gfxFontFeature>& aFontFeatures) {
446 using Tag = StyleVariantAlternates::Tag;
448 // historical-forms gets handled in nsFont::AddFontFeaturesToStyle.
449 if (aAlternates.IsHistoricalForms()) {
450 return;
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;
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;
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);
475 return;
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());
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;
489 feature.mTag = HB_TAG('s', 's', ('0' + nn / 10), ('0' + nn % 10));
490 aFontFeatures.AppendElement(feature);
493 return;
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;
520 Span<const uint32_t> values =
521 aFeatureLookup.GetFontFeatureValuesFor(aFamily, constant, name);
522 if (values.IsEmpty()) {
523 return;
525 MOZ_ASSERT(values.Length() == 1,
526 "too many values for font-specific font-variant-alternates");
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;
548 aFontFeatures.AppendElement(feature);
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;
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;
568 AutoTArray<gfxFontFeature, 32> mergedFeatures;
570 struct FeatureTagCmp {
571 bool Equals(const gfxFontFeature& a, const gfxFontFeature& b) const {
572 return a.mTag == b.mTag;
574 bool LessThan(const gfxFontFeature& a, const gfxFontFeature& b) const {
575 return a.mTag < b.mTag;
577 } cmp;
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;
588 // add feature values from font
589 for (const gfxFontFeature& feature : aFontFeatures) {
590 addOrReplace(feature);
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;
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]];
605 case NS_FONT_VARIANT_CAPS_SMALLCAPS:
606 addOrReplace(gfxFontFeature{HB_TAG('s', 'm', 'c', 'p'), 1});
607 break;
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]];
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;
622 case NS_FONT_VARIANT_CAPS_TITLING:
623 addOrReplace(gfxFontFeature{HB_TAG('t', 'i', 't', 'l'), 1});
624 break;
626 case NS_FONT_VARIANT_CAPS_UNICASE:
627 addOrReplace(gfxFontFeature{HB_TAG('u', 'n', 'i', 'c'), 1});
628 break;
630 default:
631 MOZ_ASSERT_UNREACHABLE("Unexpected variantCaps");
632 break;
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;
650 // add font-specific feature values from style rules
651 if (aStyle->featureValueLookup && !aStyle->variantAlternates.IsEmpty()) {
652 AutoTArray<gfxFontFeature, 4> featureList;
654 // insert list of alternate feature settings
655 for (auto& alternate : aStyle->variantAlternates.AsSpan()) {
656 LookupAlternateValues(*aStyle->featureValueLookup, aFamilyName, alternate,
657 featureList);
660 for (const gfxFontFeature& feature : featureList) {
661 addOrReplace(gfxFontFeature{feature.mTag, feature.mValue});
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});
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();
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();
696 for (const auto& f : mergedFeatures) {
697 aHandleFeature(f.mTag, f.mValue, aHandleFeatureData);
701 void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
702 const char16_t* aString,
703 uint32_t aLength) {
704 if (aLength == 0) {
705 return;
708 CompressedGlyph* const glyphs = GetCharacterGlyphs() + aOffset;
709 CompressedGlyph extendCluster = CompressedGlyph::MakeComplex(false, true);
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]);
717 if (IsClusterExtender(ch)) {
718 glyphs[0] = extendCluster;
721 intl::GraphemeClusterBreakIteratorUtf16 iter(
722 Span<const char16_t>(aString, aLength));
723 uint32_t pos = 0;
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);
740 prevWasHyphen = false;
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;
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;
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);
777 prevWasHyphen = false;
779 if (ch == uint8_t(' ')) {
780 glyphs->SetIsSpace();
781 } else if (ch == uint8_t('-') && pos &&
782 nsContentUtils::IsAlphanumeric(aString[pos - 1])) {
783 prevWasHyphen = true;
785 ++pos;
786 ++glyphs;
790 gfxShapedText::DetailedGlyph* gfxShapedText::AllocateDetailedGlyphs(
791 uint32_t aIndex, uint32_t aCount) {
792 NS_ASSERTION(aIndex < GetLength(), "Index out of range");
794 if (!mDetailedGlyphs) {
795 mDetailedGlyphs = MakeUnique<DetailedGlyphStore>();
798 return mDetailedGlyphs->Allocate(aIndex, aCount);
801 void gfxShapedText::SetDetailedGlyphs(uint32_t aIndex, uint32_t aGlyphCount,
802 const DetailedGlyph* aGlyphs) {
803 CompressedGlyph& g = GetCharacterGlyphs()[aIndex];
805 MOZ_ASSERT(aIndex > 0 || g.IsLigatureGroupStart(),
806 "First character can't be a ligature continuation!");
808 if (aGlyphCount > 0) {
809 DetailedGlyph* details = AllocateDetailedGlyphs(aIndex, aGlyphCount);
810 memcpy(details, aGlyphs, sizeof(DetailedGlyph) * aGlyphCount);
813 g.SetGlyphCount(aGlyphCount);
816 #define ZWNJ 0x200C
817 #define ZWJ 0x200D
818 static inline bool IsIgnorable(uint32_t aChar) {
819 return (IsDefaultIgnorable(aChar)) || aChar == ZWNJ || aChar == ZWJ;
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);
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);
840 DetailedGlyph detail = {aChar, advance, gfx::Point()};
841 SetDetailedGlyphs(aIndex, 1, &detail);
842 g.SetMissing();
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;
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;
863 return false;
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);
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;
899 auto& advance = IsRightToLeft() ? details[0].mAdvance
900 : details[detailedLength - 1].mAdvance;
901 if (advance > 0) {
902 advance = std::max(0, advance + appUnitAdjustment);
909 size_t gfxShapedWord::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
910 size_t total = aMallocSizeOf(this);
911 if (mDetailedGlyphs) {
912 total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf);
914 return total;
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'.
922 if (!mStyle.allowSyntheticStyle) {
923 return 0.0f; // Synthetic obliquing is disabled.
925 if (!mFontEntry->MayUseSyntheticSlant()) {
926 return 0.0f; // The resource supports "real" slant, so don't synthesize.
929 // If style calls for italic, and face doesn't support it, use default
930 // oblique angle as a simulation.
931 if (mStyle.style.IsItalic()) {
932 return mFontEntry->SupportsItalic()
933 ? 0.0f
934 : FontSlantStyle::DEFAULT_OBLIQUE_DEGREES;
937 // OK, we're going to use synthetic oblique: return the requested angle.
938 return mStyle.style.ObliqueAngle();
941 float gfxFont::SkewForSyntheticOblique() const {
942 // Precomputed value of tan(kDefaultAngle), the default italic/oblique slant;
943 // avoids calling tan() at runtime except for custom oblique values.
944 static const float kTanDefaultAngle =
945 tan(FontSlantStyle::DEFAULT_OBLIQUE_DEGREES * (M_PI / 180.0));
947 float angle = AngleForSyntheticOblique();
948 if (angle == 0.0f) {
949 return 0.0f;
950 } else if (angle == FontSlantStyle::DEFAULT_OBLIQUE_DEGREES) {
951 return kTanDefaultAngle;
952 } else {
953 return tan(angle * (M_PI / 180.0));
957 void gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther,
958 bool aOtherIsOnLeft) {
959 mAscent = std::max(mAscent, aOther.mAscent);
960 mDescent = std::max(mDescent, aOther.mDescent);
961 if (aOtherIsOnLeft) {
962 mBoundingBox = (mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0))
963 .Union(aOther.mBoundingBox);
964 } else {
965 mBoundingBox =
966 mBoundingBox.Union(aOther.mBoundingBox + gfxPoint(mAdvanceWidth, 0));
968 mAdvanceWidth += aOther.mAdvanceWidth;
971 gfxFont::gfxFont(const RefPtr<UnscaledFont>& aUnscaledFont,
972 gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle,
973 AntialiasOption anAAOption)
974 : mFontEntry(aFontEntry),
975 mLock("gfxFont lock"),
976 mUnscaledFont(aUnscaledFont),
977 mStyle(*aFontStyle),
978 mAdjustedSize(-1.0), // negative to indicate "not yet initialized"
979 mFUnitsConvFactor(-1.0f), // negative to indicate "not yet initialized"
980 mAntialiasOption(anAAOption),
981 mIsValid(true),
982 mApplySyntheticBold(false),
983 mKerningEnabled(false),
984 mMathInitialized(false) {
985 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
986 ++gFontCount;
987 #endif
989 if (MOZ_UNLIKELY(StaticPrefs::gfx_text_disable_aa_AtStartup())) {
990 mAntialiasOption = kAntialiasNone;
993 // Turn off AA for Ahem for testing purposes when requested.
994 if (MOZ_UNLIKELY(StaticPrefs::gfx_font_rendering_ahem_antialias_none() &&
995 mFontEntry->FamilyName().EqualsLiteral("Ahem"))) {
996 mAntialiasOption = kAntialiasNone;
999 mKerningSet = HasFeatureSet(HB_TAG('k', 'e', 'r', 'n'), mKerningEnabled);
1001 // Ensure the gfxFontEntry's unitsPerEm and extents fields are initialized,
1002 // so that GetFontExtents can use them without risk of races.
1003 Unused << mFontEntry->UnitsPerEm();
1006 gfxFont::~gfxFont() {
1007 mFontEntry->NotifyFontDestroyed(this);
1009 // Delete objects owned through atomic pointers. (Some of these may be null,
1010 // but that's OK.)
1011 delete mVerticalMetrics.exchange(nullptr);
1012 delete mHarfBuzzShaper.exchange(nullptr);
1013 delete mGraphiteShaper.exchange(nullptr);
1014 delete mMathTable.exchange(nullptr);
1015 delete mNonAAFont.exchange(nullptr);
1017 if (auto* scaledFont = mAzureScaledFont.exchange(nullptr)) {
1018 scaledFont->Release();
1021 if (mGlyphChangeObservers) {
1022 for (const auto& key : *mGlyphChangeObservers) {
1023 key->ForgetFont();
1028 // Work out whether cairo will snap inter-glyph spacing to pixels.
1030 // Layout does not align text to pixel boundaries, so, with font drawing
1031 // backends that snap glyph positions to pixels, it is important that
1032 // inter-glyph spacing within words is always an integer number of pixels.
1033 // This ensures that the drawing backend snaps all of the word's glyphs in the
1034 // same direction and so inter-glyph spacing remains the same.
1036 gfxFont::RoundingFlags gfxFont::GetRoundOffsetsToPixels(
1037 DrawTarget* aDrawTarget) {
1038 // Could do something fancy here for ScaleFactors of
1039 // AxisAlignedTransforms, but we leave things simple.
1040 // Not much point rounding if a matrix will mess things up anyway.
1041 // Also check if the font already knows hint metrics is off...
1042 if (aDrawTarget->GetTransform().HasNonTranslation() || !ShouldHintMetrics()) {
1043 return RoundingFlags(0);
1046 cairo_t* cr = static_cast<cairo_t*>(
1047 aDrawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT));
1048 if (cr) {
1049 cairo_surface_t* target = cairo_get_target(cr);
1051 // Check whether the cairo surface's font options hint metrics.
1052 cairo_font_options_t* fontOptions = cairo_font_options_create();
1053 cairo_surface_get_font_options(target, fontOptions);
1054 cairo_hint_metrics_t hintMetrics =
1055 cairo_font_options_get_hint_metrics(fontOptions);
1056 cairo_font_options_destroy(fontOptions);
1058 switch (hintMetrics) {
1059 case CAIRO_HINT_METRICS_OFF:
1060 return RoundingFlags(0);
1061 case CAIRO_HINT_METRICS_ON:
1062 return RoundingFlags::kRoundX | RoundingFlags::kRoundY;
1063 default:
1064 break;
1068 if (ShouldRoundXOffset(cr)) {
1069 return RoundingFlags::kRoundX | RoundingFlags::kRoundY;
1070 } else {
1071 return RoundingFlags::kRoundY;
1075 gfxHarfBuzzShaper* gfxFont::GetHarfBuzzShaper() {
1076 if (!mHarfBuzzShaper) {
1077 auto* shaper = new gfxHarfBuzzShaper(this);
1078 shaper->Initialize();
1079 if (!mHarfBuzzShaper.compareExchange(nullptr, shaper)) {
1080 delete shaper;
1083 gfxHarfBuzzShaper* shaper = mHarfBuzzShaper;
1084 return shaper->IsInitialized() ? shaper : nullptr;
1087 gfxFloat gfxFont::GetGlyphAdvance(uint16_t aGID, bool aVertical) {
1088 if (!aVertical && ProvidesGlyphWidths()) {
1089 return GetGlyphWidth(aGID) / 65536.0;
1091 if (mFUnitsConvFactor < 0.0f) {
1092 // Metrics haven't been initialized; lock while we do that.
1093 AutoWriteLock lock(mLock);
1094 if (mFUnitsConvFactor < 0.0f) {
1095 GetMetrics(nsFontMetrics::eHorizontal);
1098 NS_ASSERTION(mFUnitsConvFactor >= 0.0f,
1099 "missing font unit conversion factor");
1100 if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) {
1101 if (aVertical) {
1102 // Note that GetGlyphVAdvance may return -1 to indicate it was unable
1103 // to retrieve vertical metrics; in that case we fall back to the
1104 // aveCharWidth value as a default advance.
1105 int32_t advance = shaper->GetGlyphVAdvance(aGID);
1106 if (advance < 0) {
1107 return GetMetrics(nsFontMetrics::eVertical).aveCharWidth;
1109 return advance / 65536.0;
1111 return shaper->GetGlyphHAdvance(aGID) / 65536.0;
1113 return 0.0;
1116 gfxFloat gfxFont::GetCharAdvance(uint32_t aUnicode, bool aVertical) {
1117 uint32_t gid = 0;
1118 if (ProvidesGetGlyph()) {
1119 gid = GetGlyph(aUnicode, 0);
1120 } else {
1121 if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) {
1122 gid = shaper->GetNominalGlyph(aUnicode);
1125 if (!gid) {
1126 return -1.0;
1128 return GetGlyphAdvance(gid, aVertical);
1131 static void CollectLookupsByFeature(hb_face_t* aFace, hb_tag_t aTableTag,
1132 uint32_t aFeatureIndex,
1133 hb_set_t* aLookups) {
1134 uint32_t lookups[32];
1135 uint32_t i, len, offset;
1137 offset = 0;
1138 do {
1139 len = std::size(lookups);
1140 hb_ot_layout_feature_get_lookups(aFace, aTableTag, aFeatureIndex, offset,
1141 &len, lookups);
1142 for (i = 0; i < len; i++) {
1143 hb_set_add(aLookups, lookups[i]);
1145 offset += len;
1146 } while (len == std::size(lookups));
1149 static void CollectLookupsByLanguage(
1150 hb_face_t* aFace, hb_tag_t aTableTag,
1151 const nsTHashSet<uint32_t>& aSpecificFeatures, hb_set_t* aOtherLookups,
1152 hb_set_t* aSpecificFeatureLookups, uint32_t aScriptIndex,
1153 uint32_t aLangIndex) {
1154 uint32_t reqFeatureIndex;
1155 if (hb_ot_layout_language_get_required_feature_index(
1156 aFace, aTableTag, aScriptIndex, aLangIndex, &reqFeatureIndex)) {
1157 CollectLookupsByFeature(aFace, aTableTag, reqFeatureIndex, aOtherLookups);
1160 uint32_t featureIndexes[32];
1161 uint32_t i, len, offset;
1163 offset = 0;
1164 do {
1165 len = std::size(featureIndexes);
1166 hb_ot_layout_language_get_feature_indexes(aFace, aTableTag, aScriptIndex,
1167 aLangIndex, offset, &len,
1168 featureIndexes);
1170 for (i = 0; i < len; i++) {
1171 uint32_t featureIndex = featureIndexes[i];
1173 // get the feature tag
1174 hb_tag_t featureTag;
1175 uint32_t tagLen = 1;
1176 hb_ot_layout_language_get_feature_tags(aFace, aTableTag, aScriptIndex,
1177 aLangIndex, offset + i, &tagLen,
1178 &featureTag);
1180 // collect lookups
1181 hb_set_t* lookups = aSpecificFeatures.Contains(featureTag)
1182 ? aSpecificFeatureLookups
1183 : aOtherLookups;
1184 CollectLookupsByFeature(aFace, aTableTag, featureIndex, lookups);
1186 offset += len;
1187 } while (len == std::size(featureIndexes));
1190 static bool HasLookupRuleWithGlyphByScript(
1191 hb_face_t* aFace, hb_tag_t aTableTag, hb_tag_t aScriptTag,
1192 uint32_t aScriptIndex, uint16_t aGlyph,
1193 const nsTHashSet<uint32_t>& aDefaultFeatures,
1194 bool& aHasDefaultFeatureWithGlyph) {
1195 uint32_t numLangs, lang;
1196 hb_set_t* defaultFeatureLookups = hb_set_create();
1197 hb_set_t* nonDefaultFeatureLookups = hb_set_create();
1199 // default lang
1200 CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures,
1201 nonDefaultFeatureLookups, defaultFeatureLookups,
1202 aScriptIndex, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX);
1204 // iterate over langs
1205 numLangs = hb_ot_layout_script_get_language_tags(
1206 aFace, aTableTag, aScriptIndex, 0, nullptr, nullptr);
1207 for (lang = 0; lang < numLangs; lang++) {
1208 CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures,
1209 nonDefaultFeatureLookups, defaultFeatureLookups,
1210 aScriptIndex, lang);
1213 // look for the glyph among default feature lookups
1214 aHasDefaultFeatureWithGlyph = false;
1215 hb_set_t* glyphs = hb_set_create();
1216 hb_codepoint_t index = -1;
1217 while (hb_set_next(defaultFeatureLookups, &index)) {
1218 hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs,
1219 glyphs, nullptr);
1220 if (hb_set_has(glyphs, aGlyph)) {
1221 aHasDefaultFeatureWithGlyph = true;
1222 break;
1226 // look for the glyph among non-default feature lookups
1227 // if no default feature lookups contained spaces
1228 bool hasNonDefaultFeatureWithGlyph = false;
1229 if (!aHasDefaultFeatureWithGlyph) {
1230 hb_set_clear(glyphs);
1231 index = -1;
1232 while (hb_set_next(nonDefaultFeatureLookups, &index)) {
1233 hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs,
1234 glyphs, glyphs, nullptr);
1235 if (hb_set_has(glyphs, aGlyph)) {
1236 hasNonDefaultFeatureWithGlyph = true;
1237 break;
1242 hb_set_destroy(glyphs);
1243 hb_set_destroy(defaultFeatureLookups);
1244 hb_set_destroy(nonDefaultFeatureLookups);
1246 return aHasDefaultFeatureWithGlyph || hasNonDefaultFeatureWithGlyph;
1249 static void HasLookupRuleWithGlyph(hb_face_t* aFace, hb_tag_t aTableTag,
1250 bool& aHasGlyph, hb_tag_t aSpecificFeature,
1251 bool& aHasGlyphSpecific, uint16_t aGlyph) {
1252 // iterate over the scripts in the font
1253 uint32_t numScripts, numLangs, script, lang;
1254 hb_set_t* otherLookups = hb_set_create();
1255 hb_set_t* specificFeatureLookups = hb_set_create();
1256 nsTHashSet<uint32_t> specificFeature(1);
1258 specificFeature.Insert(aSpecificFeature);
1260 numScripts =
1261 hb_ot_layout_table_get_script_tags(aFace, aTableTag, 0, nullptr, nullptr);
1263 for (script = 0; script < numScripts; script++) {
1264 // default lang
1265 CollectLookupsByLanguage(aFace, aTableTag, specificFeature, otherLookups,
1266 specificFeatureLookups, script,
1267 HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX);
1269 // iterate over langs
1270 numLangs = hb_ot_layout_script_get_language_tags(
1271 aFace, HB_OT_TAG_GPOS, script, 0, nullptr, nullptr);
1272 for (lang = 0; lang < numLangs; lang++) {
1273 CollectLookupsByLanguage(aFace, aTableTag, specificFeature, otherLookups,
1274 specificFeatureLookups, script, lang);
1278 // look for the glyph among non-specific feature lookups
1279 hb_set_t* glyphs = hb_set_create();
1280 hb_codepoint_t index = -1;
1281 while (hb_set_next(otherLookups, &index)) {
1282 hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs,
1283 glyphs, nullptr);
1284 if (hb_set_has(glyphs, aGlyph)) {
1285 aHasGlyph = true;
1286 break;
1290 // look for the glyph among specific feature lookups
1291 hb_set_clear(glyphs);
1292 index = -1;
1293 while (hb_set_next(specificFeatureLookups, &index)) {
1294 hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs,
1295 glyphs, nullptr);
1296 if (hb_set_has(glyphs, aGlyph)) {
1297 aHasGlyphSpecific = true;
1298 break;
1302 hb_set_destroy(glyphs);
1303 hb_set_destroy(specificFeatureLookups);
1304 hb_set_destroy(otherLookups);
1307 Atomic<nsTHashMap<nsUint32HashKey, intl::Script>*> gfxFont::sScriptTagToCode;
1308 Atomic<nsTHashSet<uint32_t>*> gfxFont::sDefaultFeatures;
1310 static inline bool HasSubstitution(uint32_t* aBitVector, intl::Script aScript) {
1311 return (aBitVector[static_cast<uint32_t>(aScript) >> 5] &
1312 (1 << (static_cast<uint32_t>(aScript) & 0x1f))) != 0;
1315 // union of all default substitution features across scripts
1316 static const hb_tag_t defaultFeatures[] = {
1317 HB_TAG('a', 'b', 'v', 'f'), HB_TAG('a', 'b', 'v', 's'),
1318 HB_TAG('a', 'k', 'h', 'n'), HB_TAG('b', 'l', 'w', 'f'),
1319 HB_TAG('b', 'l', 'w', 's'), HB_TAG('c', 'a', 'l', 't'),
1320 HB_TAG('c', 'c', 'm', 'p'), HB_TAG('c', 'f', 'a', 'r'),
1321 HB_TAG('c', 'j', 'c', 't'), HB_TAG('c', 'l', 'i', 'g'),
1322 HB_TAG('f', 'i', 'n', '2'), HB_TAG('f', 'i', 'n', '3'),
1323 HB_TAG('f', 'i', 'n', 'a'), HB_TAG('h', 'a', 'l', 'f'),
1324 HB_TAG('h', 'a', 'l', 'n'), HB_TAG('i', 'n', 'i', 't'),
1325 HB_TAG('i', 's', 'o', 'l'), HB_TAG('l', 'i', 'g', 'a'),
1326 HB_TAG('l', 'j', 'm', 'o'), HB_TAG('l', 'o', 'c', 'l'),
1327 HB_TAG('l', 't', 'r', 'a'), HB_TAG('l', 't', 'r', 'm'),
1328 HB_TAG('m', 'e', 'd', '2'), HB_TAG('m', 'e', 'd', 'i'),
1329 HB_TAG('m', 's', 'e', 't'), HB_TAG('n', 'u', 'k', 't'),
1330 HB_TAG('p', 'r', 'e', 'f'), HB_TAG('p', 'r', 'e', 's'),
1331 HB_TAG('p', 's', 't', 'f'), HB_TAG('p', 's', 't', 's'),
1332 HB_TAG('r', 'c', 'l', 't'), HB_TAG('r', 'l', 'i', 'g'),
1333 HB_TAG('r', 'k', 'r', 'f'), HB_TAG('r', 'p', 'h', 'f'),
1334 HB_TAG('r', 't', 'l', 'a'), HB_TAG('r', 't', 'l', 'm'),
1335 HB_TAG('t', 'j', 'm', 'o'), HB_TAG('v', 'a', 't', 'u'),
1336 HB_TAG('v', 'e', 'r', 't'), HB_TAG('v', 'j', 'm', 'o')};
1338 void gfxFont::CheckForFeaturesInvolvingSpace() const {
1339 gfxFontEntry::SpaceFeatures flags = gfxFontEntry::SpaceFeatures::None;
1341 auto setFlags =
1342 MakeScopeExit([&]() { mFontEntry->mHasSpaceFeatures = flags; });
1344 bool log = LOG_FONTINIT_ENABLED();
1345 TimeStamp start;
1346 if (MOZ_UNLIKELY(log)) {
1347 start = TimeStamp::Now();
1350 uint32_t spaceGlyph = GetSpaceGlyph();
1351 if (!spaceGlyph) {
1352 return;
1355 auto face(GetFontEntry()->GetHBFace());
1357 // GSUB lookups - examine per script
1358 if (hb_ot_layout_has_substitution(face)) {
1359 // Get the script ==> code hashtable, creating it on first use.
1360 nsTHashMap<nsUint32HashKey, Script>* tagToCode = sScriptTagToCode;
1361 if (!tagToCode) {
1362 tagToCode = new nsTHashMap<nsUint32HashKey, Script>(
1363 size_t(Script::NUM_SCRIPT_CODES));
1364 tagToCode->InsertOrUpdate(HB_TAG('D', 'F', 'L', 'T'), Script::COMMON);
1365 // Ensure that we don't try to look at script codes beyond what the
1366 // current version of ICU (at runtime -- in case of system ICU)
1367 // knows about.
1368 Script scriptCount = Script(
1369 std::min<int>(intl::UnicodeProperties::GetMaxNumberOfScripts() + 1,
1370 int(Script::NUM_SCRIPT_CODES)));
1371 for (Script s = Script::ARABIC; s < scriptCount;
1372 s = Script(static_cast<int>(s) + 1)) {
1373 hb_script_t script = hb_script_t(GetScriptTagForCode(s));
1374 unsigned int scriptCount = 4;
1375 hb_tag_t scriptTags[4];
1376 hb_ot_tags_from_script_and_language(script, HB_LANGUAGE_INVALID,
1377 &scriptCount, scriptTags, nullptr,
1378 nullptr);
1379 for (unsigned int i = 0; i < scriptCount; i++) {
1380 tagToCode->InsertOrUpdate(scriptTags[i], s);
1383 if (!sScriptTagToCode.compareExchange(nullptr, tagToCode)) {
1384 // We lost a race! Discard our new table and use the winner.
1385 delete tagToCode;
1386 tagToCode = sScriptTagToCode;
1390 // Set up the default-features hashset on first use.
1391 if (!sDefaultFeatures) {
1392 uint32_t numDefaultFeatures = std::size(defaultFeatures);
1393 auto* set = new nsTHashSet<uint32_t>(numDefaultFeatures);
1394 for (uint32_t i = 0; i < numDefaultFeatures; i++) {
1395 set->Insert(defaultFeatures[i]);
1397 if (!sDefaultFeatures.compareExchange(nullptr, set)) {
1398 delete set;
1402 // iterate over the scripts in the font
1403 hb_tag_t scriptTags[8];
1405 uint32_t len, offset = 0;
1406 do {
1407 len = std::size(scriptTags);
1408 hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, offset, &len,
1409 scriptTags);
1410 for (uint32_t i = 0; i < len; i++) {
1411 bool isDefaultFeature = false;
1412 Script s;
1413 if (!HasLookupRuleWithGlyphByScript(
1414 face, HB_OT_TAG_GSUB, scriptTags[i], offset + i, spaceGlyph,
1415 *sDefaultFeatures, isDefaultFeature) ||
1416 !tagToCode->Get(scriptTags[i], &s)) {
1417 continue;
1419 flags = flags | gfxFontEntry::SpaceFeatures::HasFeatures;
1420 uint32_t index = static_cast<uint32_t>(s) >> 5;
1421 uint32_t bit = static_cast<uint32_t>(s) & 0x1f;
1422 if (isDefaultFeature) {
1423 mFontEntry->mDefaultSubSpaceFeatures[index] |= (1 << bit);
1424 } else {
1425 mFontEntry->mNonDefaultSubSpaceFeatures[index] |= (1 << bit);
1428 offset += len;
1429 } while (len == std::size(scriptTags));
1432 // spaces in default features of default script?
1433 // ==> can't use word cache, skip GPOS analysis
1434 bool canUseWordCache = true;
1435 if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, Script::COMMON)) {
1436 canUseWordCache = false;
1439 // GPOS lookups - distinguish kerning from non-kerning features
1440 if (canUseWordCache && hb_ot_layout_has_positioning(face)) {
1441 bool hasKerning = false, hasNonKerning = false;
1442 HasLookupRuleWithGlyph(face, HB_OT_TAG_GPOS, hasNonKerning,
1443 HB_TAG('k', 'e', 'r', 'n'), hasKerning, spaceGlyph);
1444 if (hasKerning) {
1445 flags |= gfxFontEntry::SpaceFeatures::HasFeatures |
1446 gfxFontEntry::SpaceFeatures::Kerning;
1448 if (hasNonKerning) {
1449 flags |= gfxFontEntry::SpaceFeatures::HasFeatures |
1450 gfxFontEntry::SpaceFeatures::NonKerning;
1454 if (MOZ_UNLIKELY(log)) {
1455 TimeDuration elapsed = TimeStamp::Now() - start;
1456 LOG_FONTINIT((
1457 "(fontinit-spacelookups) font: %s - "
1458 "subst default: %8.8x %8.8x %8.8x %8.8x "
1459 "subst non-default: %8.8x %8.8x %8.8x %8.8x "
1460 "kerning: %s non-kerning: %s time: %6.3f\n",
1461 mFontEntry->Name().get(), mFontEntry->mDefaultSubSpaceFeatures[3],
1462 mFontEntry->mDefaultSubSpaceFeatures[2],
1463 mFontEntry->mDefaultSubSpaceFeatures[1],
1464 mFontEntry->mDefaultSubSpaceFeatures[0],
1465 mFontEntry->mNonDefaultSubSpaceFeatures[3],
1466 mFontEntry->mNonDefaultSubSpaceFeatures[2],
1467 mFontEntry->mNonDefaultSubSpaceFeatures[1],
1468 mFontEntry->mNonDefaultSubSpaceFeatures[0],
1469 (mFontEntry->mHasSpaceFeatures & gfxFontEntry::SpaceFeatures::Kerning
1470 ? "true"
1471 : "false"),
1472 (mFontEntry->mHasSpaceFeatures & gfxFontEntry::SpaceFeatures::NonKerning
1473 ? "true"
1474 : "false"),
1475 elapsed.ToMilliseconds()));
1479 bool gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript) const {
1480 NS_ASSERTION(GetFontEntry()->mHasSpaceFeatures !=
1481 gfxFontEntry::SpaceFeatures::Uninitialized,
1482 "need to initialize space lookup flags");
1483 NS_ASSERTION(aRunScript < Script::NUM_SCRIPT_CODES, "weird script code");
1484 if (aRunScript == Script::INVALID || aRunScript >= Script::NUM_SCRIPT_CODES) {
1485 return false;
1488 // default features have space lookups ==> true
1489 if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, Script::COMMON) ||
1490 HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, aRunScript)) {
1491 return true;
1494 // non-default features have space lookups and some type of
1495 // font feature, in font or style is specified ==> true
1496 if ((HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures,
1497 Script::COMMON) ||
1498 HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures, aRunScript)) &&
1499 (!mStyle.featureSettings.IsEmpty() ||
1500 !mFontEntry->mFeatureSettings.IsEmpty())) {
1501 return true;
1504 return false;
1507 tainted_boolean_hint gfxFont::SpaceMayParticipateInShaping(
1508 Script aRunScript) const {
1509 // avoid checking fonts known not to include default space-dependent features
1510 if (MOZ_UNLIKELY(mFontEntry->mSkipDefaultFeatureSpaceCheck)) {
1511 if (!mKerningSet && mStyle.featureSettings.IsEmpty() &&
1512 mFontEntry->mFeatureSettings.IsEmpty()) {
1513 return false;
1517 if (FontCanSupportGraphite()) {
1518 if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1519 return mFontEntry->HasGraphiteSpaceContextuals();
1523 // We record the presence of space-dependent features in the font entry
1524 // so that subsequent instantiations for the same font face won't
1525 // require us to re-check the tables; however, the actual check is done
1526 // by gfxFont because not all font entry subclasses know how to create
1527 // a harfbuzz face for introspection.
1528 gfxFontEntry::SpaceFeatures flags = mFontEntry->mHasSpaceFeatures;
1529 if (flags == gfxFontEntry::SpaceFeatures::Uninitialized) {
1530 CheckForFeaturesInvolvingSpace();
1531 flags = mFontEntry->mHasSpaceFeatures;
1534 if (!(flags & gfxFontEntry::SpaceFeatures::HasFeatures)) {
1535 return false;
1538 // if font has substitution rules or non-kerning positioning rules
1539 // that involve spaces, bypass
1540 if (HasSubstitutionRulesWithSpaceLookups(aRunScript) ||
1541 (flags & gfxFontEntry::SpaceFeatures::NonKerning)) {
1542 return true;
1545 // if kerning explicitly enabled/disabled via font-feature-settings or
1546 // font-kerning and kerning rules use spaces, only bypass when enabled
1547 if (mKerningSet && (flags & gfxFontEntry::SpaceFeatures::Kerning)) {
1548 return mKerningEnabled;
1551 return false;
1554 bool gfxFont::SupportsFeature(Script aScript, uint32_t aFeatureTag) {
1555 if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1556 return GetFontEntry()->SupportsGraphiteFeature(aFeatureTag);
1558 return GetFontEntry()->SupportsOpenTypeFeature(aScript, aFeatureTag);
1561 bool gfxFont::SupportsVariantCaps(Script aScript, uint32_t aVariantCaps,
1562 bool& aFallbackToSmallCaps,
1563 bool& aSyntheticLowerToSmallCaps,
1564 bool& aSyntheticUpperToSmallCaps) {
1565 bool ok = true; // cases without fallback are fine
1566 aFallbackToSmallCaps = false;
1567 aSyntheticLowerToSmallCaps = false;
1568 aSyntheticUpperToSmallCaps = false;
1569 switch (aVariantCaps) {
1570 case NS_FONT_VARIANT_CAPS_SMALLCAPS:
1571 ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p'));
1572 if (!ok) {
1573 aSyntheticLowerToSmallCaps = true;
1575 break;
1576 case NS_FONT_VARIANT_CAPS_ALLSMALL:
1577 ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')) &&
1578 SupportsFeature(aScript, HB_TAG('c', '2', 's', 'c'));
1579 if (!ok) {
1580 aSyntheticLowerToSmallCaps = true;
1581 aSyntheticUpperToSmallCaps = true;
1583 break;
1584 case NS_FONT_VARIANT_CAPS_PETITECAPS:
1585 ok = SupportsFeature(aScript, HB_TAG('p', 'c', 'a', 'p'));
1586 if (!ok) {
1587 ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p'));
1588 aFallbackToSmallCaps = ok;
1590 if (!ok) {
1591 aSyntheticLowerToSmallCaps = true;
1593 break;
1594 case NS_FONT_VARIANT_CAPS_ALLPETITE:
1595 ok = SupportsFeature(aScript, HB_TAG('p', 'c', 'a', 'p')) &&
1596 SupportsFeature(aScript, HB_TAG('c', '2', 'p', 'c'));
1597 if (!ok) {
1598 ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')) &&
1599 SupportsFeature(aScript, HB_TAG('c', '2', 's', 'c'));
1600 aFallbackToSmallCaps = ok;
1602 if (!ok) {
1603 aSyntheticLowerToSmallCaps = true;
1604 aSyntheticUpperToSmallCaps = true;
1606 break;
1607 default:
1608 break;
1611 NS_ASSERTION(
1612 !(ok && (aSyntheticLowerToSmallCaps || aSyntheticUpperToSmallCaps)),
1613 "shouldn't use synthetic features if we found real ones");
1615 NS_ASSERTION(!(!ok && aFallbackToSmallCaps),
1616 "if we found a usable fallback, that counts as ok");
1618 return ok;
1621 bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript,
1622 const uint8_t* aString, uint32_t aLength,
1623 Script aRunScript) {
1624 NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aString),
1625 aLength);
1626 return SupportsSubSuperscript(aSubSuperscript, unicodeString.get(), aLength,
1627 aRunScript);
1630 bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript,
1631 const char16_t* aString, uint32_t aLength,
1632 Script aRunScript) {
1633 NS_ASSERTION(aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER ||
1634 aSubSuperscript == NS_FONT_VARIANT_POSITION_SUB,
1635 "unknown value of font-variant-position");
1637 uint32_t feature = aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER
1638 ? HB_TAG('s', 'u', 'p', 's')
1639 : HB_TAG('s', 'u', 'b', 's');
1641 if (!SupportsFeature(aRunScript, feature)) {
1642 return false;
1645 // xxx - for graphite, don't really know how to sniff lookups so bail
1646 if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1647 return true;
1650 gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper();
1651 if (!shaper) {
1652 return false;
1655 // get the hbset containing input glyphs for the feature
1656 const hb_set_t* inputGlyphs =
1657 mFontEntry->InputsForOpenTypeFeature(aRunScript, feature);
1659 // create an hbset containing default glyphs for the script run
1660 hb_set_t* defaultGlyphsInRun = hb_set_create();
1662 // for each character, get the glyph id
1663 for (uint32_t i = 0; i < aLength; i++) {
1664 uint32_t ch = aString[i];
1666 if (i + 1 < aLength && NS_IS_SURROGATE_PAIR(ch, aString[i + 1])) {
1667 i++;
1668 ch = SURROGATE_TO_UCS4(ch, aString[i]);
1671 hb_codepoint_t gid = shaper->GetNominalGlyph(ch);
1672 hb_set_add(defaultGlyphsInRun, gid);
1675 // intersect with input glyphs, if size is not the same ==> fallback
1676 uint32_t origSize = hb_set_get_population(defaultGlyphsInRun);
1677 hb_set_intersect(defaultGlyphsInRun, inputGlyphs);
1678 uint32_t intersectionSize = hb_set_get_population(defaultGlyphsInRun);
1679 hb_set_destroy(defaultGlyphsInRun);
1681 return origSize == intersectionSize;
1684 bool gfxFont::FeatureWillHandleChar(Script aRunScript, uint32_t aFeature,
1685 uint32_t aUnicode) {
1686 if (!SupportsFeature(aRunScript, aFeature)) {
1687 return false;
1690 // xxx - for graphite, don't really know how to sniff lookups so bail
1691 if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1692 return true;
1695 if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) {
1696 // get the hbset containing input glyphs for the feature
1697 const hb_set_t* inputGlyphs =
1698 mFontEntry->InputsForOpenTypeFeature(aRunScript, aFeature);
1700 hb_codepoint_t gid = shaper->GetNominalGlyph(aUnicode);
1701 return hb_set_has(inputGlyphs, gid);
1704 return false;
1707 bool gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn) {
1708 aFeatureOn = false;
1710 if (mStyle.featureSettings.IsEmpty() &&
1711 GetFontEntry()->mFeatureSettings.IsEmpty()) {
1712 return false;
1715 // add feature values from font
1716 bool featureSet = false;
1717 uint32_t i, count;
1719 nsTArray<gfxFontFeature>& fontFeatures = GetFontEntry()->mFeatureSettings;
1720 count = fontFeatures.Length();
1721 for (i = 0; i < count; i++) {
1722 const gfxFontFeature& feature = fontFeatures.ElementAt(i);
1723 if (feature.mTag == aFeature) {
1724 featureSet = true;
1725 aFeatureOn = (feature.mValue != 0);
1729 // add feature values from style rules
1730 nsTArray<gfxFontFeature>& styleFeatures = mStyle.featureSettings;
1731 count = styleFeatures.Length();
1732 for (i = 0; i < count; i++) {
1733 const gfxFontFeature& feature = styleFeatures.ElementAt(i);
1734 if (feature.mTag == aFeature) {
1735 featureSet = true;
1736 aFeatureOn = (feature.mValue != 0);
1740 return featureSet;
1743 already_AddRefed<mozilla::gfx::ScaledFont> gfxFont::GetScaledFont(
1744 mozilla::gfx::DrawTarget* aDrawTarget) {
1745 mozilla::gfx::PaletteCache dummy;
1746 TextRunDrawParams params(dummy);
1747 return GetScaledFont(params);
1750 void gfxFont::InitializeScaledFont(
1751 const RefPtr<mozilla::gfx::ScaledFont>& aScaledFont) {
1752 if (!aScaledFont) {
1753 return;
1756 float angle = AngleForSyntheticOblique();
1757 if (angle != 0.0f) {
1758 aScaledFont->SetSyntheticObliqueAngle(angle);
1763 * A helper function in case we need to do any rounding or other
1764 * processing here.
1766 #define ToDeviceUnits(aAppUnits, aDevUnitsPerAppUnit) \
1767 (double(aAppUnits) * double(aDevUnitsPerAppUnit))
1769 static AntialiasMode Get2DAAMode(gfxFont::AntialiasOption aAAOption) {
1770 switch (aAAOption) {
1771 case gfxFont::kAntialiasSubpixel:
1772 return AntialiasMode::SUBPIXEL;
1773 case gfxFont::kAntialiasGrayscale:
1774 return AntialiasMode::GRAY;
1775 case gfxFont::kAntialiasNone:
1776 return AntialiasMode::NONE;
1777 default:
1778 return AntialiasMode::DEFAULT;
1782 class GlyphBufferAzure {
1783 #define AUTO_BUFFER_SIZE (2048 / sizeof(Glyph))
1785 typedef mozilla::image::imgDrawingParams imgDrawingParams;
1787 public:
1788 GlyphBufferAzure(const TextRunDrawParams& aRunParams,
1789 const FontDrawParams& aFontParams)
1790 : mRunParams(aRunParams),
1791 mFontParams(aFontParams),
1792 mBuffer(*mAutoBuffer.addr()),
1793 mBufSize(AUTO_BUFFER_SIZE),
1794 mCapacity(0),
1795 mNumGlyphs(0) {}
1797 ~GlyphBufferAzure() {
1798 if (mNumGlyphs > 0) {
1799 FlushGlyphs();
1802 if (mBuffer != *mAutoBuffer.addr()) {
1803 free(mBuffer);
1807 // Ensure the buffer has enough space for aGlyphCount glyphs to be added,
1808 // considering the supplied strike multipler aStrikeCount.
1809 // This MUST be called before OutputGlyph is used to actually store glyph
1810 // records in the buffer. It may be called repeated to add further capacity
1811 // in case we don't know up-front exactly what will be needed.
1812 void AddCapacity(uint32_t aGlyphCount, uint32_t aStrikeCount) {
1813 // Calculate the new capacity and ensure it will fit within the maximum
1814 // allowed capacity.
1815 static const uint64_t kMaxCapacity = 64 * 1024;
1816 mCapacity = uint32_t(std::min(
1817 kMaxCapacity,
1818 uint64_t(mCapacity) + uint64_t(aGlyphCount) * uint64_t(aStrikeCount)));
1819 // See if the required capacity fits within the already-allocated space
1820 if (mCapacity <= mBufSize) {
1821 return;
1823 // We need to grow the buffer: determine a new size, allocate, and
1824 // copy the existing data over if we didn't use realloc (which would
1825 // do it automatically).
1826 mBufSize = std::max(mCapacity, mBufSize * 2);
1827 if (mBuffer == *mAutoBuffer.addr()) {
1828 // switching from autobuffer to malloc, so we need to copy
1829 mBuffer = reinterpret_cast<Glyph*>(moz_xmalloc(mBufSize * sizeof(Glyph)));
1830 std::memcpy(mBuffer, *mAutoBuffer.addr(), mNumGlyphs * sizeof(Glyph));
1831 } else {
1832 mBuffer = reinterpret_cast<Glyph*>(
1833 moz_xrealloc(mBuffer, mBufSize * sizeof(Glyph)));
1837 void OutputGlyph(uint32_t aGlyphID, const gfx::Point& aPt) {
1838 // If the buffer is full, flush to make room for the new glyph.
1839 if (mNumGlyphs >= mCapacity) {
1840 // Check that AddCapacity has been used appropriately!
1841 MOZ_ASSERT(mCapacity > 0 && mNumGlyphs == mCapacity);
1842 Flush();
1844 Glyph* glyph = mBuffer + mNumGlyphs++;
1845 glyph->mIndex = aGlyphID;
1846 glyph->mPosition = aPt;
1849 void Flush() {
1850 if (mNumGlyphs > 0) {
1851 FlushGlyphs();
1852 mNumGlyphs = 0;
1856 const TextRunDrawParams& mRunParams;
1857 const FontDrawParams& mFontParams;
1859 private:
1860 static DrawMode GetStrokeMode(DrawMode aMode) {
1861 return aMode & (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH);
1864 // Render the buffered glyphs to the draw target.
1865 void FlushGlyphs() {
1866 gfx::GlyphBuffer buf;
1867 buf.mGlyphs = mBuffer;
1868 buf.mNumGlyphs = mNumGlyphs;
1870 const gfxContext::AzureState& state = mRunParams.context->CurrentState();
1872 // Draw stroke first if the UNDERNEATH flag is set in drawMode.
1873 if (mRunParams.strokeOpts &&
1874 GetStrokeMode(mRunParams.drawMode) ==
1875 (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH)) {
1876 DrawStroke(state, buf);
1879 if (mRunParams.drawMode & DrawMode::GLYPH_FILL) {
1880 if (state.pattern || mFontParams.contextPaint) {
1881 Pattern* pat;
1883 RefPtr<gfxPattern> fillPattern;
1884 if (mFontParams.contextPaint) {
1885 imgDrawingParams imgParams;
1886 fillPattern = mFontParams.contextPaint->GetFillPattern(
1887 mRunParams.context->GetDrawTarget(),
1888 mRunParams.context->CurrentMatrixDouble(), imgParams);
1890 if (!fillPattern) {
1891 if (state.pattern) {
1892 RefPtr<gfxPattern> statePattern =
1893 mRunParams.context->CurrentState().pattern;
1894 pat = statePattern->GetPattern(mRunParams.dt,
1895 state.patternTransformChanged
1896 ? &state.patternTransform
1897 : nullptr);
1898 } else {
1899 pat = nullptr;
1901 } else {
1902 pat = fillPattern->GetPattern(mRunParams.dt);
1905 if (pat) {
1906 mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf, *pat,
1907 mFontParams.drawOptions);
1909 } else {
1910 mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
1911 ColorPattern(state.color),
1912 mFontParams.drawOptions);
1916 // Draw stroke if the UNDERNEATH flag is not set.
1917 if (mRunParams.strokeOpts &&
1918 GetStrokeMode(mRunParams.drawMode) == DrawMode::GLYPH_STROKE) {
1919 DrawStroke(state, buf);
1922 if (mRunParams.drawMode & DrawMode::GLYPH_PATH) {
1923 mRunParams.context->EnsurePathBuilder();
1924 Matrix mat = mRunParams.dt->GetTransform();
1925 mFontParams.scaledFont->CopyGlyphsToBuilder(
1926 buf, mRunParams.context->mPathBuilder, &mat);
1930 void DrawStroke(const gfxContext::AzureState& aState,
1931 gfx::GlyphBuffer& aBuffer) {
1932 if (mRunParams.textStrokePattern) {
1933 Pattern* pat = mRunParams.textStrokePattern->GetPattern(
1934 mRunParams.dt,
1935 aState.patternTransformChanged ? &aState.patternTransform : nullptr);
1937 if (pat) {
1938 FlushStroke(aBuffer, *pat);
1940 } else {
1941 FlushStroke(aBuffer,
1942 ColorPattern(ToDeviceColor(mRunParams.textStrokeColor)));
1946 void FlushStroke(gfx::GlyphBuffer& aBuf, const Pattern& aPattern) {
1947 mRunParams.dt->StrokeGlyphs(mFontParams.scaledFont, aBuf, aPattern,
1948 *mRunParams.strokeOpts,
1949 mFontParams.drawOptions);
1952 // We use an "inline" buffer automatically allocated (on the stack) as part
1953 // of the GlyphBufferAzure object to hold the glyphs in most cases, falling
1954 // back to a separately-allocated heap buffer if the count of buffered
1955 // glyphs gets too big.
1957 // This is basically a rudimentary AutoTArray; so why not use AutoTArray
1958 // itself?
1960 // If we used an AutoTArray, we'd want to avoid using SetLength or
1961 // AppendElements to allocate the space we actually need, because those
1962 // methods would default-construct the new elements.
1964 // Could we use SetCapacity to reserve the necessary buffer space without
1965 // default-constructing all the Glyph records? No, because of a failure
1966 // that could occur when we need to grow the buffer, which happens when we
1967 // encounter a DetailedGlyph in the textrun that refers to a sequence of
1968 // several real glyphs. At that point, we need to add some extra capacity
1969 // to the buffer we initially allocated based on the length of the textrun
1970 // range we're rendering.
1972 // This buffer growth would work fine as long as it still fits within the
1973 // array's inline buffer (we just use a bit more of it), or if the buffer
1974 // was already heap-allocated (in which case AutoTArray will use realloc(),
1975 // preserving its contents). But a problem will arise when the initial
1976 // capacity we allocated (based on the length of the run) fits within the
1977 // array's inline buffer, but subsequently we need to extend the buffer
1978 // beyond the inline buffer size, so we reallocate to the heap. Because we
1979 // haven't "officially" filled the array with SetLength or AppendElements,
1980 // its mLength is still zero; as far as it's concerned the buffer is just
1981 // uninitialized space, and when it switches to use a malloc'd buffer it
1982 // won't copy the existing contents.
1984 // Allocate space for a buffer of Glyph records, without initializing them.
1985 AlignedStorage2<Glyph[AUTO_BUFFER_SIZE]> mAutoBuffer;
1987 // Pointer to the buffer we're currently using -- initially mAutoBuffer,
1988 // but may be changed to a malloc'd buffer, in which case that buffer must
1989 // be free'd on destruction.
1990 Glyph* mBuffer;
1992 uint32_t mBufSize; // size of allocated buffer; capacity can grow to
1993 // this before reallocation is needed
1994 uint32_t mCapacity; // amount of buffer size reserved
1995 uint32_t mNumGlyphs; // number of glyphs actually present in the buffer
1997 #undef AUTO_BUFFER_SIZE
2000 // Bug 674909. When synthetic bolding text by drawing twice, need to
2001 // render using a pixel offset in device pixels, otherwise text
2002 // doesn't appear bolded, it appears as if a bad text shadow exists
2003 // when a non-identity transform exists. Use an offset factor so that
2004 // the second draw occurs at a constant offset in device pixels.
2006 gfx::Float gfxFont::CalcXScale(DrawTarget* aDrawTarget) {
2007 // determine magnitude of a 1px x offset in device space
2008 Size t = aDrawTarget->GetTransform().TransformSize(Size(1.0, 0.0));
2009 if (t.width == 1.0 && t.height == 0.0) {
2010 // short-circuit the most common case to avoid sqrt() and division
2011 return 1.0;
2014 gfx::Float m = sqrtf(t.width * t.width + t.height * t.height);
2016 NS_ASSERTION(m != 0.0, "degenerate transform while synthetic bolding");
2017 if (m == 0.0) {
2018 return 0.0; // effectively disables offset
2021 // scale factor so that offsets are 1px in device pixels
2022 return 1.0 / m;
2025 // Draw a run of CharacterGlyph records from the given offset in aShapedText.
2026 // Returns true if glyph paths were actually emitted.
2027 template <gfxFont::FontComplexityT FC, gfxFont::SpacingT S>
2028 bool gfxFont::DrawGlyphs(const gfxShapedText* aShapedText,
2029 uint32_t aOffset, // offset in the textrun
2030 uint32_t aCount, // length of run to draw
2031 gfx::Point* aPt,
2032 const gfx::Matrix* aOffsetMatrix, // may be null
2033 GlyphBufferAzure& aBuffer) {
2034 float& inlineCoord =
2035 aBuffer.mFontParams.isVerticalFont ? aPt->y.value : aPt->x.value;
2037 const gfxShapedText::CompressedGlyph* glyphData =
2038 &aShapedText->GetCharacterGlyphs()[aOffset];
2040 if (S == SpacingT::HasSpacing) {
2041 float space = aBuffer.mRunParams.spacing[0].mBefore *
2042 aBuffer.mFontParams.advanceDirection;
2043 inlineCoord += space;
2046 // Allocate buffer space for the run, assuming all simple glyphs.
2047 uint32_t capacityMult = 1 + aBuffer.mFontParams.extraStrikes;
2048 aBuffer.AddCapacity(aCount, capacityMult);
2050 bool emittedGlyphs = false;
2052 for (uint32_t i = 0; i < aCount; ++i, ++glyphData) {
2053 if (glyphData->IsSimpleGlyph()) {
2054 float advance =
2055 glyphData->GetSimpleAdvance() * aBuffer.mFontParams.advanceDirection;
2056 if (aBuffer.mRunParams.isRTL) {
2057 inlineCoord += advance;
2059 DrawOneGlyph<FC>(glyphData->GetSimpleGlyph(), *aPt, aBuffer,
2060 &emittedGlyphs);
2061 if (!aBuffer.mRunParams.isRTL) {
2062 inlineCoord += advance;
2064 } else {
2065 uint32_t glyphCount = glyphData->GetGlyphCount();
2066 if (glyphCount > 0) {
2067 // Add extra buffer capacity to allow for multiple-glyph entry.
2068 aBuffer.AddCapacity(glyphCount - 1, capacityMult);
2069 const gfxShapedText::DetailedGlyph* details =
2070 aShapedText->GetDetailedGlyphs(aOffset + i);
2071 MOZ_ASSERT(details, "missing DetailedGlyph!");
2072 for (uint32_t j = 0; j < glyphCount; ++j, ++details) {
2073 float advance =
2074 details->mAdvance * aBuffer.mFontParams.advanceDirection;
2075 if (aBuffer.mRunParams.isRTL) {
2076 inlineCoord += advance;
2078 if (glyphData->IsMissing()) {
2079 if (!DrawMissingGlyph(aBuffer.mRunParams, aBuffer.mFontParams,
2080 details, *aPt)) {
2081 return false;
2083 } else {
2084 gfx::Point glyphPt(
2085 *aPt + (aOffsetMatrix
2086 ? aOffsetMatrix->TransformPoint(details->mOffset)
2087 : details->mOffset));
2088 DrawOneGlyph<FC>(details->mGlyphID, glyphPt, aBuffer,
2089 &emittedGlyphs);
2091 if (!aBuffer.mRunParams.isRTL) {
2092 inlineCoord += advance;
2098 if (S == SpacingT::HasSpacing) {
2099 float space = aBuffer.mRunParams.spacing[i].mAfter;
2100 if (i + 1 < aCount) {
2101 space += aBuffer.mRunParams.spacing[i + 1].mBefore;
2103 space *= aBuffer.mFontParams.advanceDirection;
2104 inlineCoord += space;
2108 return emittedGlyphs;
2111 // Draw an individual glyph at a specific location.
2112 // *aPt is the glyph position in appUnits; it is converted to device
2113 // coordinates (devPt) here.
2114 template <gfxFont::FontComplexityT FC>
2115 void gfxFont::DrawOneGlyph(uint32_t aGlyphID, const gfx::Point& aPt,
2116 GlyphBufferAzure& aBuffer, bool* aEmittedGlyphs) {
2117 const TextRunDrawParams& runParams(aBuffer.mRunParams);
2119 gfx::Point devPt(ToDeviceUnits(aPt.x, runParams.devPerApp),
2120 ToDeviceUnits(aPt.y, runParams.devPerApp));
2122 auto* textDrawer = runParams.textDrawer;
2123 if (textDrawer) {
2124 // If the glyph is entirely outside the clip rect, we don't need to draw it
2125 // at all. (We check the font extents here rather than the individual glyph
2126 // bounds because that's cheaper to look up, and provides a conservative
2127 // "worst case" for where this glyph might want to draw.)
2128 LayoutDeviceRect extents =
2129 LayoutDeviceRect::FromUnknownRect(aBuffer.mFontParams.fontExtents);
2130 extents.MoveBy(LayoutDevicePoint::FromUnknownPoint(devPt));
2131 if (!extents.Intersects(runParams.clipRect)) {
2132 return;
2136 if (FC == FontComplexityT::ComplexFont) {
2137 const FontDrawParams& fontParams(aBuffer.mFontParams);
2139 gfxContextMatrixAutoSaveRestore matrixRestore;
2141 if (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont &&
2142 !textDrawer) {
2143 // We have to flush each glyph individually when doing
2144 // synthetic-oblique for vertical-upright text, because
2145 // the skew transform needs to be applied to a separate
2146 // origin for each glyph, not once for the whole run.
2147 aBuffer.Flush();
2148 matrixRestore.SetContext(runParams.context);
2149 gfx::Point skewPt(
2150 devPt.x + GetMetrics(nsFontMetrics::eVertical).emHeight / 2, devPt.y);
2151 gfx::Matrix mat =
2152 runParams.context->CurrentMatrix()
2153 .PreTranslate(skewPt)
2154 .PreMultiply(gfx::Matrix(1, fontParams.obliqueSkew, 0, 1, 0, 0))
2155 .PreTranslate(-skewPt);
2156 runParams.context->SetMatrix(mat);
2159 if (fontParams.haveSVGGlyphs) {
2160 if (!runParams.paintSVGGlyphs) {
2161 return;
2163 NS_WARNING_ASSERTION(
2164 runParams.drawMode != DrawMode::GLYPH_PATH,
2165 "Rendering SVG glyph despite request for glyph path");
2166 if (RenderSVGGlyph(runParams.context, textDrawer, devPt, aGlyphID,
2167 fontParams.contextPaint, runParams.callbacks,
2168 *aEmittedGlyphs)) {
2169 return;
2173 if (fontParams.haveColorGlyphs && !UseNativeColrFontSupport() &&
2174 RenderColorGlyph(runParams.dt, runParams.context, textDrawer,
2175 fontParams, devPt, aGlyphID)) {
2176 return;
2179 aBuffer.OutputGlyph(aGlyphID, devPt);
2181 // Synthetic bolding (if required) by multi-striking.
2182 for (int32_t i = 0; i < fontParams.extraStrikes; ++i) {
2183 if (fontParams.isVerticalFont) {
2184 devPt.y += fontParams.synBoldOnePixelOffset;
2185 } else {
2186 devPt.x += fontParams.synBoldOnePixelOffset;
2188 aBuffer.OutputGlyph(aGlyphID, devPt);
2191 if (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont &&
2192 !textDrawer) {
2193 aBuffer.Flush();
2195 } else {
2196 aBuffer.OutputGlyph(aGlyphID, devPt);
2199 *aEmittedGlyphs = true;
2202 bool gfxFont::DrawMissingGlyph(const TextRunDrawParams& aRunParams,
2203 const FontDrawParams& aFontParams,
2204 const gfxShapedText::DetailedGlyph* aDetails,
2205 const gfx::Point& aPt) {
2206 // Default-ignorable chars will have zero advance width;
2207 // we don't have to draw the hexbox for them.
2208 float advance = aDetails->mAdvance;
2209 if (aRunParams.drawMode != DrawMode::GLYPH_PATH && advance > 0) {
2210 auto* textDrawer = aRunParams.textDrawer;
2211 const Matrix* matPtr = nullptr;
2212 Matrix mat;
2213 if (textDrawer) {
2214 // Generate an orientation matrix for the current writing mode
2215 wr::FontInstanceFlags flags = textDrawer->GetWRGlyphFlags();
2216 if (flags & wr::FontInstanceFlags::TRANSPOSE) {
2217 std::swap(mat._11, mat._12);
2218 std::swap(mat._21, mat._22);
2220 mat.PostScale(flags & wr::FontInstanceFlags::FLIP_X ? -1.0f : 1.0f,
2221 flags & wr::FontInstanceFlags::FLIP_Y ? -1.0f : 1.0f);
2222 matPtr = &mat;
2225 Point pt(Float(ToDeviceUnits(aPt.x, aRunParams.devPerApp)),
2226 Float(ToDeviceUnits(aPt.y, aRunParams.devPerApp)));
2227 Float advanceDevUnits = Float(ToDeviceUnits(advance, aRunParams.devPerApp));
2228 Float height = GetMetrics(nsFontMetrics::eHorizontal).maxAscent;
2229 // Horizontally center if drawing vertically upright with no sideways
2230 // transform.
2231 Rect glyphRect =
2232 aFontParams.isVerticalFont && !mat.HasNonAxisAlignedTransform()
2233 ? Rect(pt.x - height / 2, pt.y, height, advanceDevUnits)
2234 : Rect(pt.x, pt.y - height, advanceDevUnits, height);
2236 // If there's a fake-italic skew in effect as part
2237 // of the drawTarget's transform, we need to undo
2238 // this before drawing the hexbox. (Bug 983985)
2239 gfxContextMatrixAutoSaveRestore matrixRestore;
2240 if (aFontParams.obliqueSkew != 0.0f && !aFontParams.isVerticalFont &&
2241 !textDrawer) {
2242 matrixRestore.SetContext(aRunParams.context);
2243 gfx::Matrix mat =
2244 aRunParams.context->CurrentMatrix()
2245 .PreTranslate(pt)
2246 .PreMultiply(gfx::Matrix(1, 0, aFontParams.obliqueSkew, 1, 0, 0))
2247 .PreTranslate(-pt);
2248 aRunParams.context->SetMatrix(mat);
2251 gfxFontMissingGlyphs::DrawMissingGlyph(
2252 aDetails->mGlyphID, glyphRect, *aRunParams.dt,
2253 PatternFromState(aRunParams.context), matPtr);
2255 return true;
2258 // This method is mostly parallel to DrawGlyphs.
2259 void gfxFont::DrawEmphasisMarks(const gfxTextRun* aShapedText, gfx::Point* aPt,
2260 uint32_t aOffset, uint32_t aCount,
2261 const EmphasisMarkDrawParams& aParams) {
2262 float& inlineCoord = aParams.isVertical ? aPt->y.value : aPt->x.value;
2263 gfxTextRun::Range markRange(aParams.mark);
2264 gfxTextRun::DrawParams params(aParams.context, aParams.paletteCache);
2266 float clusterStart = -std::numeric_limits<float>::infinity();
2267 bool shouldDrawEmphasisMark = false;
2268 for (uint32_t i = 0, idx = aOffset; i < aCount; ++i, ++idx) {
2269 if (aParams.spacing) {
2270 inlineCoord += aParams.direction * aParams.spacing[i].mBefore;
2272 if (aShapedText->IsClusterStart(idx) ||
2273 clusterStart == -std::numeric_limits<float>::infinity()) {
2274 clusterStart = inlineCoord;
2276 if (aShapedText->CharMayHaveEmphasisMark(idx)) {
2277 shouldDrawEmphasisMark = true;
2279 inlineCoord += aParams.direction * aShapedText->GetAdvanceForGlyph(idx);
2280 if (shouldDrawEmphasisMark &&
2281 (i + 1 == aCount || aShapedText->IsClusterStart(idx + 1))) {
2282 float clusterAdvance = inlineCoord - clusterStart;
2283 // Move the coord backward to get the needed start point.
2284 float delta = (clusterAdvance + aParams.advance) / 2;
2285 inlineCoord -= delta;
2286 aParams.mark->Draw(markRange, *aPt, params);
2287 inlineCoord += delta;
2288 shouldDrawEmphasisMark = false;
2290 if (aParams.spacing) {
2291 inlineCoord += aParams.direction * aParams.spacing[i].mAfter;
2296 void gfxFont::Draw(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd,
2297 gfx::Point* aPt, TextRunDrawParams& aRunParams,
2298 gfx::ShapedTextFlags aOrientation) {
2299 NS_ASSERTION(aRunParams.drawMode == DrawMode::GLYPH_PATH ||
2300 !(int(aRunParams.drawMode) & int(DrawMode::GLYPH_PATH)),
2301 "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or "
2302 "GLYPH_STROKE_UNDERNEATH");
2304 if (aStart >= aEnd) {
2305 return;
2308 FontDrawParams fontParams;
2310 if (aRunParams.drawOpts) {
2311 fontParams.drawOptions = *aRunParams.drawOpts;
2314 fontParams.scaledFont = GetScaledFont(aRunParams);
2315 if (!fontParams.scaledFont) {
2316 return;
2318 auto* textDrawer = aRunParams.textDrawer;
2320 fontParams.obliqueSkew = SkewForSyntheticOblique();
2321 fontParams.haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this);
2322 fontParams.haveColorGlyphs = GetFontEntry()->TryGetColorGlyphs();
2323 fontParams.hasTextShadow = aRunParams.hasTextShadow;
2324 fontParams.contextPaint = aRunParams.runContextPaint;
2326 if (fontParams.haveColorGlyphs && !UseNativeColrFontSupport()) {
2327 DeviceColor ctxColor;
2328 fontParams.currentColor = aRunParams.context->GetDeviceColor(ctxColor)
2329 ? sRGBColor::FromABGR(ctxColor.ToABGR())
2330 : sRGBColor::OpaqueBlack();
2331 fontParams.palette = aRunParams.paletteCache.GetPaletteFor(
2332 GetFontEntry(), aRunParams.fontPalette);
2335 if (textDrawer) {
2336 fontParams.isVerticalFont = aRunParams.isVerticalRun;
2337 } else {
2338 fontParams.isVerticalFont =
2339 aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
2342 gfxContextMatrixAutoSaveRestore matrixRestore;
2343 layout::TextDrawTarget::AutoRestoreWRGlyphFlags glyphFlagsRestore;
2345 // Save the current baseline offset for restoring later, in case it is
2346 // modified.
2347 float& baseline = fontParams.isVerticalFont ? aPt->x.value : aPt->y.value;
2348 float origBaseline = baseline;
2350 // The point may be advanced in local-space, while the resulting point on
2351 // return must be advanced in transformed space. So save the original point so
2352 // we can properly transform the advance later.
2353 gfx::Point origPt = *aPt;
2354 const gfx::Matrix* offsetMatrix = nullptr;
2356 // Default to advancing along the +X direction (-X if RTL).
2357 fontParams.advanceDirection = aRunParams.isRTL ? -1.0f : 1.0f;
2358 // Default to offsetting baseline downward along the +Y direction.
2359 float baselineDir = 1.0f;
2360 // The direction of sideways rotation, if applicable.
2361 // -1 for rotating left/counter-clockwise
2362 // 1 for rotating right/clockwise
2363 // 0 for no rotation
2364 float sidewaysDir =
2365 (aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
2366 ? -1.0f
2367 : (aOrientation ==
2368 gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT
2369 ? 1.0f
2370 : 0.0f));
2371 // If we're rendering a sideways run, we need to push a rotation transform to
2372 // the context.
2373 if (sidewaysDir != 0.0f) {
2374 if (textDrawer) {
2375 // For WebRender, we can't use a DrawTarget transform and must instead use
2376 // flags that locally transform the glyph, without affecting the glyph
2377 // origin. The glyph origins must thus be offset in the transformed
2378 // directions (instead of local-space directions). Modify the advance and
2379 // baseline directions to account for the indicated transform.
2381 // The default text orientation is down being +Y and right being +X.
2382 // Rotating 90 degrees left/CCW makes down be +X and right be -Y.
2383 // Rotating 90 degrees right/CW makes down be -X and right be +Y.
2384 // Thus the advance direction (moving right) is just sidewaysDir,
2385 // i.e. negative along Y axis if rotated left and positive if
2386 // rotated right.
2387 fontParams.advanceDirection *= sidewaysDir;
2388 // The baseline direction (moving down) is negated relative to the
2389 // advance direction for sideways transforms.
2390 baselineDir *= -sidewaysDir;
2392 glyphFlagsRestore.Save(textDrawer);
2393 // Set the transform flags accordingly. Both sideways rotations transpose
2394 // X and Y, while left rotation flips the resulting Y axis, and right
2395 // rotation flips the resulting X axis.
2396 textDrawer->SetWRGlyphFlags(
2397 textDrawer->GetWRGlyphFlags() | wr::FontInstanceFlags::TRANSPOSE |
2398 (aOrientation ==
2399 gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
2400 ? wr::FontInstanceFlags::FLIP_Y
2401 : wr::FontInstanceFlags::FLIP_X));
2402 // We also need to set up a transform for the glyph offset vector that
2403 // may be present in DetailedGlyph records.
2404 static const gfx::Matrix kSidewaysLeft = {0, -1, 1, 0, 0, 0};
2405 static const gfx::Matrix kSidewaysRight = {0, 1, -1, 0, 0, 0};
2406 offsetMatrix =
2407 (aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT)
2408 ? &kSidewaysLeft
2409 : &kSidewaysRight;
2410 } else {
2411 // For non-WebRender targets, just push a rotation transform.
2412 matrixRestore.SetContext(aRunParams.context);
2413 gfxPoint p(aPt->x * aRunParams.devPerApp, aPt->y * aRunParams.devPerApp);
2414 // Get a matrix we can use to draw the (horizontally-shaped) textrun
2415 // with 90-degree CW rotation.
2416 const gfxFloat rotation = sidewaysDir * M_PI / 2.0f;
2417 gfxMatrix mat = aRunParams.context->CurrentMatrixDouble()
2418 .PreTranslate(p)
2419 . // translate origin for rotation
2420 PreRotate(rotation)
2421 . // turn 90deg CCW (sideways-left) or CW (*-right)
2422 PreTranslate(-p); // undo the translation
2424 aRunParams.context->SetMatrixDouble(mat);
2427 // If we're drawing rotated horizontal text for an element styled
2428 // text-orientation:mixed, the dominant baseline will be vertical-
2429 // centered. So in this case, we need to adjust the position so that
2430 // the rotated horizontal text (which uses an alphabetic baseline) will
2431 // look OK when juxtaposed with upright glyphs (rendered on a centered
2432 // vertical baseline). The adjustment here is somewhat ad hoc; we
2433 // should eventually look for baseline tables[1] in the fonts and use
2434 // those if available.
2435 // [1] See http://www.microsoft.com/typography/otspec/base.htm
2436 if (aTextRun->UseCenterBaseline()) {
2437 const Metrics& metrics = GetMetrics(nsFontMetrics::eHorizontal);
2438 float baseAdj = (metrics.emAscent - metrics.emDescent) / 2;
2439 baseline += baseAdj * aTextRun->GetAppUnitsPerDevUnit() * baselineDir;
2441 } else if (textDrawer &&
2442 aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) {
2443 glyphFlagsRestore.Save(textDrawer);
2444 textDrawer->SetWRGlyphFlags(textDrawer->GetWRGlyphFlags() |
2445 wr::FontInstanceFlags::VERTICAL);
2448 if (fontParams.obliqueSkew != 0.0f && !fontParams.isVerticalFont &&
2449 !textDrawer) {
2450 // Adjust matrix for synthetic-oblique, except if we're doing vertical-
2451 // upright text, in which case this will be handled for each glyph
2452 // individually in DrawOneGlyph.
2453 if (!matrixRestore.HasMatrix()) {
2454 matrixRestore.SetContext(aRunParams.context);
2456 gfx::Point p(aPt->x * aRunParams.devPerApp, aPt->y * aRunParams.devPerApp);
2457 gfx::Matrix mat =
2458 aRunParams.context->CurrentMatrix()
2459 .PreTranslate(p)
2460 .PreMultiply(gfx::Matrix(1, 0, -fontParams.obliqueSkew, 1, 0, 0))
2461 .PreTranslate(-p);
2462 aRunParams.context->SetMatrix(mat);
2465 RefPtr<SVGContextPaint> contextPaint;
2466 if (fontParams.haveSVGGlyphs && !fontParams.contextPaint) {
2467 // If no pattern is specified for fill, use the current pattern
2468 NS_ASSERTION((int(aRunParams.drawMode) & int(DrawMode::GLYPH_STROKE)) == 0,
2469 "no pattern supplied for stroking text");
2470 RefPtr<gfxPattern> fillPattern = aRunParams.context->GetPattern();
2471 contextPaint = new SimpleTextContextPaint(
2472 fillPattern, nullptr, aRunParams.context->CurrentMatrixDouble());
2473 fontParams.contextPaint = contextPaint.get();
2476 // Synthetic-bold strikes are each offset one device pixel in run direction
2477 // (these values are only needed if ApplySyntheticBold() is true).
2478 // If drawing via webrender, it will do multistrike internally so we don't
2479 // need to handle it here.
2480 bool doMultistrikeBold = ApplySyntheticBold() && !textDrawer;
2481 if (doMultistrikeBold) {
2482 // For screen display, we want to try and repeat strikes with an offset of
2483 // one device pixel, accounting for zoom or other transforms that may be
2484 // in effect, so compute x-axis scale factor from the drawtarget.
2485 // However, when generating PDF output the drawtarget's transform does not
2486 // really bear any relation to "device pixels", and may result in an
2487 // excessively large offset relative to the font size (bug 1823888), so
2488 // we limit it based on the used font size to avoid this.
2489 // The constant 48.0 reflects the threshold where the calculation in
2490 // gfxFont::GetSyntheticBoldOffset() switches to a simple origin-based
2491 // slope, though the exact value is somewhat arbitrary; it's selected to
2492 // allow a visible amount of boldness while preventing the offset from
2493 // becoming "large" in relation to the glyphs.
2494 Float xscale =
2495 std::min<Float>(GetAdjustedSize() / 48.0,
2496 CalcXScale(aRunParams.context->GetDrawTarget()));
2497 fontParams.synBoldOnePixelOffset = aRunParams.direction * xscale;
2498 if (xscale != 0.0) {
2499 static const int32_t kMaxExtraStrikes = 128;
2500 gfxFloat extraStrikes = GetSyntheticBoldOffset() / xscale;
2501 if (extraStrikes > kMaxExtraStrikes) {
2502 // if too many strikes are required, limit them and increase the step
2503 // size to compensate
2504 fontParams.extraStrikes = kMaxExtraStrikes;
2505 fontParams.synBoldOnePixelOffset = aRunParams.direction *
2506 GetSyntheticBoldOffset() /
2507 fontParams.extraStrikes;
2508 } else {
2509 // use as many strikes as needed for the increased advance
2510 fontParams.extraStrikes = NS_lroundf(std::max(1.0, extraStrikes));
2512 } else {
2513 // Degenerate transform?!
2514 fontParams.extraStrikes = 0;
2516 } else {
2517 fontParams.synBoldOnePixelOffset = 0;
2518 fontParams.extraStrikes = 0;
2521 // Figure out the maximum extents for the font, accounting for synthetic
2522 // oblique and bold.
2523 if (mFUnitsConvFactor > 0.0) {
2524 fontParams.fontExtents = GetFontEntry()->GetFontExtents(mFUnitsConvFactor);
2525 } else {
2526 // Was it not an sfnt? Maybe on Linux... use arbitrary huge extents, so we
2527 // don't inadvertently clip stuff. A bit less efficient than true extents,
2528 // but this should be extremely rare.
2529 auto size = GetAdjustedSize();
2530 fontParams.fontExtents = Rect(-2 * size, -2 * size, 5 * size, 5 * size);
2532 if (fontParams.obliqueSkew != 0.0f) {
2533 gfx::Point p(fontParams.fontExtents.x, fontParams.fontExtents.y);
2534 gfx::Matrix skew(1, 0, fontParams.obliqueSkew, 1, 0, 0);
2535 fontParams.fontExtents = skew.TransformBounds(fontParams.fontExtents);
2537 if (fontParams.extraStrikes) {
2538 if (fontParams.isVerticalFont) {
2539 fontParams.fontExtents.height +=
2540 float(fontParams.extraStrikes) * fontParams.synBoldOnePixelOffset;
2541 } else {
2542 fontParams.fontExtents.width +=
2543 float(fontParams.extraStrikes) * fontParams.synBoldOnePixelOffset;
2547 bool oldSubpixelAA = aRunParams.dt->GetPermitSubpixelAA();
2548 if (!AllowSubpixelAA()) {
2549 aRunParams.dt->SetPermitSubpixelAA(false);
2552 Matrix mat;
2553 Matrix oldMat = aRunParams.dt->GetTransform();
2555 fontParams.drawOptions.mAntialiasMode = Get2DAAMode(mAntialiasOption);
2557 if (mStyle.baselineOffset != 0.0) {
2558 baseline +=
2559 mStyle.baselineOffset * aTextRun->GetAppUnitsPerDevUnit() * baselineDir;
2562 bool emittedGlyphs;
2564 // Select appropriate version of the templated DrawGlyphs method
2565 // to output glyphs to the buffer, depending on complexity needed
2566 // for the type of font, and whether added inter-glyph spacing
2567 // is specified.
2568 GlyphBufferAzure buffer(aRunParams, fontParams);
2569 if (fontParams.haveSVGGlyphs || fontParams.haveColorGlyphs ||
2570 fontParams.extraStrikes ||
2571 (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont &&
2572 !textDrawer)) {
2573 if (aRunParams.spacing) {
2574 emittedGlyphs =
2575 DrawGlyphs<FontComplexityT::ComplexFont, SpacingT::HasSpacing>(
2576 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
2577 } else {
2578 emittedGlyphs =
2579 DrawGlyphs<FontComplexityT::ComplexFont, SpacingT::NoSpacing>(
2580 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
2582 } else {
2583 if (aRunParams.spacing) {
2584 emittedGlyphs =
2585 DrawGlyphs<FontComplexityT::SimpleFont, SpacingT::HasSpacing>(
2586 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
2587 } else {
2588 emittedGlyphs =
2589 DrawGlyphs<FontComplexityT::SimpleFont, SpacingT::NoSpacing>(
2590 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
2595 baseline = origBaseline;
2597 if (aRunParams.callbacks && emittedGlyphs) {
2598 aRunParams.callbacks->NotifyGlyphPathEmitted();
2601 aRunParams.dt->SetTransform(oldMat);
2602 aRunParams.dt->SetPermitSubpixelAA(oldSubpixelAA);
2604 if (sidewaysDir != 0.0f && !textDrawer) {
2605 // Adjust updated aPt to account for the transform we were using.
2606 // The advance happened horizontally in local-space, but the transformed
2607 // sideways advance is actually vertical, with sign depending on the
2608 // direction of rotation.
2609 float advance = aPt->x - origPt.x;
2610 *aPt = gfx::Point(origPt.x, origPt.y + advance * sidewaysDir);
2614 bool gfxFont::RenderSVGGlyph(gfxContext* aContext,
2615 layout::TextDrawTarget* aTextDrawer,
2616 gfx::Point aPoint, uint32_t aGlyphId,
2617 SVGContextPaint* aContextPaint) const {
2618 if (!GetFontEntry()->HasSVGGlyph(aGlyphId)) {
2619 return false;
2622 if (aTextDrawer) {
2623 // WebRender doesn't support SVG Glyphs.
2624 // (pretend to succeed, output doesn't matter, we will emit a blob)
2625 aTextDrawer->FoundUnsupportedFeature();
2626 return true;
2629 const gfxFloat devUnitsPerSVGUnit =
2630 GetAdjustedSize() / GetFontEntry()->UnitsPerEm();
2631 gfxContextMatrixAutoSaveRestore matrixRestore(aContext);
2633 aContext->SetMatrix(aContext->CurrentMatrix()
2634 .PreTranslate(aPoint.x, aPoint.y)
2635 .PreScale(devUnitsPerSVGUnit, devUnitsPerSVGUnit));
2637 aContextPaint->InitStrokeGeometry(aContext, devUnitsPerSVGUnit);
2639 GetFontEntry()->RenderSVGGlyph(aContext, aGlyphId, aContextPaint);
2640 aContext->NewPath();
2641 return true;
2644 bool gfxFont::RenderSVGGlyph(gfxContext* aContext,
2645 layout::TextDrawTarget* aTextDrawer,
2646 gfx::Point aPoint, uint32_t aGlyphId,
2647 SVGContextPaint* aContextPaint,
2648 gfxTextRunDrawCallbacks* aCallbacks,
2649 bool& aEmittedGlyphs) const {
2650 if (aCallbacks && aEmittedGlyphs) {
2651 aCallbacks->NotifyGlyphPathEmitted();
2652 aEmittedGlyphs = false;
2654 return RenderSVGGlyph(aContext, aTextDrawer, aPoint, aGlyphId, aContextPaint);
2657 bool gfxFont::RenderColorGlyph(DrawTarget* aDrawTarget, gfxContext* aContext,
2658 layout::TextDrawTarget* aTextDrawer,
2659 const FontDrawParams& aFontParams,
2660 const Point& aPoint, uint32_t aGlyphId) {
2661 if (aTextDrawer && aFontParams.hasTextShadow) {
2662 aTextDrawer->FoundUnsupportedFeature();
2663 return true;
2666 auto* colr = GetFontEntry()->GetCOLR();
2667 const auto* paintGraph = COLRFonts::GetGlyphPaintGraph(colr, aGlyphId);
2668 const gfxHarfBuzzShaper* hbShaper = nullptr;
2669 if (paintGraph) {
2670 // We need the hbShaper to get color glyph bounds, so check that it's
2671 // usable.
2672 hbShaper = GetHarfBuzzShaper();
2673 if (!hbShaper && !hbShaper->IsInitialized()) {
2674 return false;
2676 if (aTextDrawer) {
2677 aTextDrawer->FoundUnsupportedFeature();
2678 return true;
2681 const auto* layers =
2682 paintGraph ? nullptr : COLRFonts::GetGlyphLayers(colr, aGlyphId);
2684 if (!paintGraph && !layers) {
2685 return false;
2688 // For reasonable font sizes, use a cache of rasterized glyphs.
2689 bool useCache = GetAdjustedSize() <= 256.0;
2691 // If the composition op is not OVER, rasterize to a temporary surface
2692 // and then composite to the destination, even if we're not caching.
2693 // But we can't do this if the target is a TextDrawTarget, as it doesn't
2694 // support DrawSurface.
2695 RefPtr<SourceSurface> snapshot;
2696 if ((useCache ||
2697 aFontParams.drawOptions.mCompositionOp != CompositionOp::OP_OVER) &&
2698 aDrawTarget->GetBackendType() != BackendType::WEBRENDER_TEXT) {
2699 AutoWriteLock lock(mLock);
2700 if (!mColorGlyphCache && useCache) {
2701 mColorGlyphCache = MakeUnique<ColorGlyphCache>();
2704 Rect bounds;
2705 if (paintGraph) {
2706 bounds = COLRFonts::GetColorGlyphBounds(
2707 colr, hbShaper->GetHBFont(), aGlyphId, aDrawTarget,
2708 aFontParams.scaledFont, mFUnitsConvFactor);
2709 } else {
2710 bounds = GetFontEntry()->GetFontExtents(mFUnitsConvFactor);
2712 bounds.RoundOut();
2714 // Tell the cache what colors we're using; if they have changed, it
2715 // will discard any currently-cached entries.
2716 HashMap<uint32_t, RefPtr<SourceSurface>>::AddPtr cached;
2717 if (useCache) {
2718 mColorGlyphCache->SetColors(aFontParams.currentColor,
2719 aFontParams.palette);
2720 cached = mColorGlyphCache->mCache.lookupForAdd(aGlyphId);
2721 if (cached) {
2722 snapshot = cached->value();
2726 if (!snapshot) {
2727 // Create a temporary DrawTarget and render the glyph to it.
2728 IntSize size(int(bounds.width), int(bounds.height));
2729 SurfaceFormat format = SurfaceFormat::B8G8R8A8;
2730 RefPtr target =
2731 Factory::CreateDrawTarget(BackendType::SKIA, size, format);
2732 if (target) {
2733 // Use OP_OVER and opaque alpha to create the glyph snapshot.
2734 DrawOptions drawOptions(aFontParams.drawOptions);
2735 drawOptions.mCompositionOp = CompositionOp::OP_OVER;
2736 drawOptions.mAlpha = 1.0f;
2737 bool ok = false;
2738 if (paintGraph) {
2739 ok = COLRFonts::PaintGlyphGraph(
2740 colr, hbShaper->GetHBFont(), paintGraph, target, nullptr,
2741 aFontParams.scaledFont, drawOptions, -bounds.TopLeft(),
2742 aFontParams.currentColor, aFontParams.palette->Colors(), aGlyphId,
2743 mFUnitsConvFactor);
2744 } else {
2745 auto face(GetFontEntry()->GetHBFace());
2746 ok = COLRFonts::PaintGlyphLayers(
2747 colr, face, layers, target, nullptr, aFontParams.scaledFont,
2748 drawOptions, -bounds.TopLeft(), aFontParams.currentColor,
2749 aFontParams.palette->Colors());
2751 if (ok) {
2752 snapshot = target->Snapshot();
2753 if (useCache) {
2754 // Save a snapshot of the rendering in the cache.
2755 // (We ignore potential failure here, and just paint the snapshot
2756 // without caching it.)
2757 Unused << mColorGlyphCache->mCache.add(cached, aGlyphId, snapshot);
2762 if (snapshot) {
2763 // Paint the snapshot using the appropriate composition op.
2764 aDrawTarget->DrawSurface(snapshot,
2765 Rect(aPoint + bounds.TopLeft(), bounds.Size()),
2766 Rect(Point(), bounds.Size()),
2767 DrawSurfaceOptions(), aFontParams.drawOptions);
2768 return true;
2772 // If we didn't paint from a cached or temporary snapshot, just render
2773 // directly to the destination drawTarget.
2774 if (paintGraph) {
2775 return COLRFonts::PaintGlyphGraph(
2776 colr, hbShaper->GetHBFont(), paintGraph, aDrawTarget, aTextDrawer,
2777 aFontParams.scaledFont, aFontParams.drawOptions, aPoint,
2778 aFontParams.currentColor, aFontParams.palette->Colors(), aGlyphId,
2779 mFUnitsConvFactor);
2782 if (layers) {
2783 auto face(GetFontEntry()->GetHBFace());
2784 return COLRFonts::PaintGlyphLayers(
2785 colr, face, layers, aDrawTarget, aTextDrawer, aFontParams.scaledFont,
2786 aFontParams.drawOptions, aPoint, aFontParams.currentColor,
2787 aFontParams.palette->Colors());
2790 return false;
2793 void gfxFont::ColorGlyphCache::SetColors(sRGBColor aCurrentColor,
2794 FontPalette* aPalette) {
2795 if (aCurrentColor != mCurrentColor || aPalette != mPalette) {
2796 mCache.clear();
2797 mCurrentColor = aCurrentColor;
2798 mPalette = aPalette;
2802 bool gfxFont::HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh) {
2803 // Bitmap fonts are assumed to provide "color" glyphs for all supported chars.
2804 gfxFontEntry* fe = GetFontEntry();
2805 if (fe->HasColorBitmapTable()) {
2806 return true;
2808 // Use harfbuzz shaper to look up the default glyph ID for the character.
2809 auto* shaper = GetHarfBuzzShaper();
2810 if (!shaper) {
2811 return false;
2813 uint32_t gid = 0;
2814 if (gfxFontUtils::IsVarSelector(aNextCh)) {
2815 gid = shaper->GetVariationGlyph(aCh, aNextCh);
2817 if (!gid) {
2818 gid = shaper->GetNominalGlyph(aCh);
2820 if (!gid) {
2821 return false;
2824 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1801521:
2825 // Emoji special-case: flag sequences NOT based on Regional Indicator pairs
2826 // use the BLACK FLAG character plus a series of plane-14 TAG LETTERs, e.g.
2827 // England = <black-flag, tag-G, tag-B, tag-E, tag-N, tag-G, tag-cancel>
2828 // Here, we don't check for support of the entire sequence (too much
2829 // expensive lookahead), but we check that the font at least supports the
2830 // first of the tag letter codes, because if it doesn't, we're at risk of
2831 // just getting an undifferentiated black flag glyph.
2832 if (gfxFontUtils::IsEmojiFlagAndTag(aCh, aNextCh)) {
2833 if (!shaper->GetNominalGlyph(aNextCh)) {
2834 return false;
2838 // Check if there is a COLR/CPAL or SVG glyph for this ID.
2839 if (fe->TryGetColorGlyphs() &&
2840 (COLRFonts::GetGlyphPaintGraph(fe->GetCOLR(), gid) ||
2841 COLRFonts::GetGlyphLayers(fe->GetCOLR(), gid))) {
2842 return true;
2844 if (fe->TryGetSVGData(this) && fe->HasSVGGlyph(gid)) {
2845 return true;
2847 return false;
2850 static void UnionRange(gfxFloat aX, gfxFloat* aDestMin, gfxFloat* aDestMax) {
2851 *aDestMin = std::min(*aDestMin, aX);
2852 *aDestMax = std::max(*aDestMax, aX);
2855 // We get precise glyph extents if the textrun creator requested them, or
2856 // if the font is a user font --- in which case the author may be relying
2857 // on overflowing glyphs.
2858 static bool NeedsGlyphExtents(gfxFont* aFont, const gfxTextRun* aTextRun) {
2859 return (aTextRun->GetFlags() &
2860 gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX) ||
2861 aFont->GetFontEntry()->IsUserFont();
2864 bool gfxFont::IsSpaceGlyphInvisible(DrawTarget* aRefDrawTarget,
2865 const gfxTextRun* aTextRun) {
2866 gfxFontEntry::LazyFlag flag = mFontEntry->mSpaceGlyphIsInvisible;
2867 if (flag == gfxFontEntry::LazyFlag::Uninitialized &&
2868 GetAdjustedSize() >= 1.0) {
2869 gfxGlyphExtents* extents =
2870 GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit());
2871 gfxRect glyphExtents;
2872 flag = extents->GetTightGlyphExtentsAppUnits(
2873 this, aRefDrawTarget, GetSpaceGlyph(), &glyphExtents) &&
2874 glyphExtents.IsEmpty()
2875 ? gfxFontEntry::LazyFlag::Yes
2876 : gfxFontEntry::LazyFlag::No;
2877 mFontEntry->mSpaceGlyphIsInvisible = flag;
2879 return flag == gfxFontEntry::LazyFlag::Yes;
2882 bool gfxFont::MeasureGlyphs(const gfxTextRun* aTextRun, uint32_t aStart,
2883 uint32_t aEnd, BoundingBoxType aBoundingBoxType,
2884 DrawTarget* aRefDrawTarget, Spacing* aSpacing,
2885 gfxGlyphExtents* aExtents, bool aIsRTL,
2886 bool aNeedsGlyphExtents, RunMetrics& aMetrics,
2887 gfxFloat* aAdvanceMin, gfxFloat* aAdvanceMax) {
2888 const gfxTextRun::CompressedGlyph* charGlyphs =
2889 aTextRun->GetCharacterGlyphs();
2890 double x = 0;
2891 if (aSpacing) {
2892 x += aSpacing[0].mBefore;
2894 uint32_t spaceGlyph = GetSpaceGlyph();
2895 bool allGlyphsInvisible = true;
2897 AutoReadLock lock(aExtents->mLock);
2899 for (uint32_t i = aStart; i < aEnd; ++i) {
2900 const gfxTextRun::CompressedGlyph* glyphData = &charGlyphs[i];
2901 if (glyphData->IsSimpleGlyph()) {
2902 double advance = glyphData->GetSimpleAdvance();
2903 uint32_t glyphIndex = glyphData->GetSimpleGlyph();
2904 if (allGlyphsInvisible) {
2905 if (glyphIndex != spaceGlyph) {
2906 allGlyphsInvisible = false;
2907 } else {
2908 gfxFontEntry::LazyFlag flag = mFontEntry->mSpaceGlyphIsInvisible;
2909 if (flag == gfxFontEntry::LazyFlag::Uninitialized &&
2910 GetAdjustedSize() >= 1.0) {
2911 gfxRect glyphExtents;
2912 flag = aExtents->GetTightGlyphExtentsAppUnitsLocked(
2913 this, aRefDrawTarget, spaceGlyph, &glyphExtents) &&
2914 glyphExtents.IsEmpty()
2915 ? gfxFontEntry::LazyFlag::Yes
2916 : gfxFontEntry::LazyFlag::No;
2917 mFontEntry->mSpaceGlyphIsInvisible = flag;
2919 if (flag == gfxFontEntry::LazyFlag::No) {
2920 allGlyphsInvisible = false;
2924 // Only get the real glyph horizontal extent if we were asked
2925 // for the tight bounding box or we're in quality mode
2926 if (aBoundingBoxType != LOOSE_INK_EXTENTS || aNeedsGlyphExtents) {
2927 uint16_t extentsWidth =
2928 aExtents->GetContainedGlyphWidthAppUnitsLocked(glyphIndex);
2929 if (extentsWidth != gfxGlyphExtents::INVALID_WIDTH &&
2930 aBoundingBoxType == LOOSE_INK_EXTENTS) {
2931 UnionRange(x, aAdvanceMin, aAdvanceMax);
2932 UnionRange(x + extentsWidth, aAdvanceMin, aAdvanceMax);
2933 } else {
2934 gfxRect glyphRect;
2935 if (!aExtents->GetTightGlyphExtentsAppUnitsLocked(
2936 this, aRefDrawTarget, glyphIndex, &glyphRect)) {
2937 glyphRect = gfxRect(0, aMetrics.mBoundingBox.Y(), advance,
2938 aMetrics.mBoundingBox.Height());
2940 if (aIsRTL) {
2941 // In effect, swap left and right sidebearings of the glyph, for
2942 // proper accumulation of potentially-overlapping glyph rects.
2943 glyphRect.MoveToX(advance - glyphRect.XMost());
2945 glyphRect.MoveByX(x);
2946 aMetrics.mBoundingBox = aMetrics.mBoundingBox.Union(glyphRect);
2949 x += advance;
2950 } else {
2951 allGlyphsInvisible = false;
2952 uint32_t glyphCount = glyphData->GetGlyphCount();
2953 if (glyphCount > 0) {
2954 const gfxTextRun::DetailedGlyph* details =
2955 aTextRun->GetDetailedGlyphs(i);
2956 NS_ASSERTION(details != nullptr,
2957 "detailedGlyph record should not be missing!");
2958 uint32_t j;
2959 for (j = 0; j < glyphCount; ++j, ++details) {
2960 uint32_t glyphIndex = details->mGlyphID;
2961 double advance = details->mAdvance;
2962 gfxRect glyphRect;
2963 if (glyphData->IsMissing() ||
2964 !aExtents->GetTightGlyphExtentsAppUnitsLocked(
2965 this, aRefDrawTarget, glyphIndex, &glyphRect)) {
2966 // We might have failed to get glyph extents due to
2967 // OOM or something
2968 glyphRect = gfxRect(0, -aMetrics.mAscent, advance,
2969 aMetrics.mAscent + aMetrics.mDescent);
2971 if (aIsRTL) {
2972 // Swap left/right sidebearings of the glyph, because we're doing
2973 // mirrored measurement.
2974 glyphRect.MoveToX(advance - glyphRect.XMost());
2975 // Move to current x position, mirroring any x-offset amount.
2976 glyphRect.MoveByX(x - details->mOffset.x);
2977 } else {
2978 glyphRect.MoveByX(x + details->mOffset.x);
2980 glyphRect.MoveByY(details->mOffset.y);
2981 aMetrics.mBoundingBox = aMetrics.mBoundingBox.Union(glyphRect);
2982 x += advance;
2986 if (aSpacing) {
2987 double space = aSpacing[i - aStart].mAfter;
2988 if (i + 1 < aEnd) {
2989 space += aSpacing[i + 1 - aStart].mBefore;
2991 x += space;
2995 aMetrics.mAdvanceWidth = x;
2996 return allGlyphsInvisible;
2999 bool gfxFont::MeasureGlyphs(const gfxTextRun* aTextRun, uint32_t aStart,
3000 uint32_t aEnd, BoundingBoxType aBoundingBoxType,
3001 DrawTarget* aRefDrawTarget, Spacing* aSpacing,
3002 bool aIsRTL, RunMetrics& aMetrics) {
3003 const gfxTextRun::CompressedGlyph* charGlyphs =
3004 aTextRun->GetCharacterGlyphs();
3005 double x = 0;
3006 if (aSpacing) {
3007 x += aSpacing[0].mBefore;
3009 uint32_t spaceGlyph = GetSpaceGlyph();
3010 bool allGlyphsInvisible = true;
3012 for (uint32_t i = aStart; i < aEnd; ++i) {
3013 const gfxTextRun::CompressedGlyph* glyphData = &charGlyphs[i];
3014 if (glyphData->IsSimpleGlyph()) {
3015 double advance = glyphData->GetSimpleAdvance();
3016 uint32_t glyphIndex = glyphData->GetSimpleGlyph();
3017 if (allGlyphsInvisible &&
3018 (glyphIndex != spaceGlyph ||
3019 !IsSpaceGlyphInvisible(aRefDrawTarget, aTextRun))) {
3020 allGlyphsInvisible = false;
3022 x += advance;
3023 } else {
3024 allGlyphsInvisible = false;
3025 uint32_t glyphCount = glyphData->GetGlyphCount();
3026 if (glyphCount > 0) {
3027 const gfxTextRun::DetailedGlyph* details =
3028 aTextRun->GetDetailedGlyphs(i);
3029 NS_ASSERTION(details != nullptr,
3030 "detailedGlyph record should not be missing!");
3031 uint32_t j;
3032 for (j = 0; j < glyphCount; ++j, ++details) {
3033 double advance = details->mAdvance;
3034 gfxRect glyphRect(0, -aMetrics.mAscent, advance,
3035 aMetrics.mAscent + aMetrics.mDescent);
3036 if (aIsRTL) {
3037 // Swap left/right sidebearings of the glyph, because we're doing
3038 // mirrored measurement.
3039 glyphRect.MoveToX(advance - glyphRect.XMost());
3040 // Move to current x position, mirroring any x-offset amount.
3041 glyphRect.MoveByX(x - details->mOffset.x);
3042 } else {
3043 glyphRect.MoveByX(x + details->mOffset.x);
3045 glyphRect.MoveByY(details->mOffset.y);
3046 aMetrics.mBoundingBox = aMetrics.mBoundingBox.Union(glyphRect);
3047 x += advance;
3051 if (aSpacing) {
3052 double space = aSpacing[i - aStart].mAfter;
3053 if (i + 1 < aEnd) {
3054 space += aSpacing[i + 1 - aStart].mBefore;
3056 x += space;
3060 aMetrics.mAdvanceWidth = x;
3061 return allGlyphsInvisible;
3064 gfxFont::RunMetrics gfxFont::Measure(const gfxTextRun* aTextRun,
3065 uint32_t aStart, uint32_t aEnd,
3066 BoundingBoxType aBoundingBoxType,
3067 DrawTarget* aRefDrawTarget,
3068 Spacing* aSpacing,
3069 gfx::ShapedTextFlags aOrientation) {
3070 // If aBoundingBoxType is TIGHT_HINTED_OUTLINE_EXTENTS
3071 // and the underlying cairo font may be antialiased,
3072 // we need to create a copy in order to avoid getting cached extents.
3073 // This is only used by MathML layout at present.
3074 if (aBoundingBoxType == TIGHT_HINTED_OUTLINE_EXTENTS &&
3075 mAntialiasOption != kAntialiasNone) {
3076 gfxFont* nonAA = mNonAAFont;
3077 if (!nonAA) {
3078 nonAA = CopyWithAntialiasOption(kAntialiasNone);
3079 if (nonAA) {
3080 if (!mNonAAFont.compareExchange(nullptr, nonAA)) {
3081 delete nonAA;
3082 nonAA = mNonAAFont;
3086 // if font subclass doesn't implement CopyWithAntialiasOption(),
3087 // it will return null and we'll proceed to use the existing font
3088 if (nonAA) {
3089 return nonAA->Measure(aTextRun, aStart, aEnd,
3090 TIGHT_HINTED_OUTLINE_EXTENTS, aRefDrawTarget,
3091 aSpacing, aOrientation);
3095 const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
3096 // Current position in appunits
3097 Orientation orientation =
3098 aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
3099 ? nsFontMetrics::eVertical
3100 : nsFontMetrics::eHorizontal;
3101 const gfxFont::Metrics& fontMetrics = GetMetrics(orientation);
3103 gfxFloat baselineOffset = 0;
3104 if (aTextRun->UseCenterBaseline() &&
3105 orientation == nsFontMetrics::eHorizontal) {
3106 // For a horizontal font being used in vertical writing mode with
3107 // text-orientation:mixed, the overall metrics we're accumulating
3108 // will be aimed at a center baseline. But this font's metrics were
3109 // based on the alphabetic baseline. So we compute a baseline offset
3110 // that will be applied to ascent/descent values and glyph rects
3111 // to effectively shift them relative to the baseline.
3112 // XXX Eventually we should probably use the BASE table, if present.
3113 // But it usually isn't, so we need an ad hoc adjustment for now.
3114 baselineOffset =
3115 appUnitsPerDevUnit * (fontMetrics.emAscent - fontMetrics.emDescent) / 2;
3118 RunMetrics metrics;
3119 metrics.mAscent = fontMetrics.maxAscent * appUnitsPerDevUnit;
3120 metrics.mDescent = fontMetrics.maxDescent * appUnitsPerDevUnit;
3122 if (aStart == aEnd) {
3123 // exit now before we look at aSpacing[0], which is undefined
3124 metrics.mAscent -= baselineOffset;
3125 metrics.mDescent += baselineOffset;
3126 metrics.mBoundingBox =
3127 gfxRect(0, -metrics.mAscent, 0, metrics.mAscent + metrics.mDescent);
3128 return metrics;
3131 gfxFloat advanceMin = 0, advanceMax = 0;
3132 bool isRTL = aTextRun->IsRightToLeft();
3133 bool needsGlyphExtents = NeedsGlyphExtents(this, aTextRun);
3134 gfxGlyphExtents* extents =
3135 ((aBoundingBoxType == LOOSE_INK_EXTENTS && !needsGlyphExtents &&
3136 !aTextRun->HasDetailedGlyphs()) ||
3137 MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero()))
3138 ? nullptr
3139 : GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit());
3141 bool allGlyphsInvisible;
3142 if (extents) {
3143 allGlyphsInvisible = MeasureGlyphs(
3144 aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget, aSpacing,
3145 extents, isRTL, needsGlyphExtents, metrics, &advanceMin, &advanceMax);
3146 } else {
3147 allGlyphsInvisible =
3148 MeasureGlyphs(aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget,
3149 aSpacing, isRTL, metrics);
3152 if (allGlyphsInvisible) {
3153 metrics.mBoundingBox.SetEmpty();
3154 } else if (aBoundingBoxType == LOOSE_INK_EXTENTS) {
3155 UnionRange(metrics.mAdvanceWidth, &advanceMin, &advanceMax);
3156 gfxRect fontBox(advanceMin, -metrics.mAscent, advanceMax - advanceMin,
3157 metrics.mAscent + metrics.mDescent);
3158 metrics.mBoundingBox = metrics.mBoundingBox.Union(fontBox);
3161 if (isRTL) {
3162 // Reverse the effect of having swapped each glyph's sidebearings, to get
3163 // the correct sidebearings of the merged bounding box.
3164 metrics.mBoundingBox.MoveToX(metrics.mAdvanceWidth -
3165 metrics.mBoundingBox.XMost());
3168 // If the font may be rendered with a fake-italic effect, we need to allow
3169 // for the top-right of the glyphs being skewed to the right, and the
3170 // bottom-left being skewed further left.
3171 gfxFloat skew = SkewForSyntheticOblique();
3172 if (skew != 0.0) {
3173 gfxFloat extendLeftEdge, extendRightEdge;
3174 if (orientation == nsFontMetrics::eVertical) {
3175 // The glyph will actually be skewed vertically, but "left" and "right"
3176 // here refer to line-left (physical top) and -right (bottom), so these
3177 // are still the directions in which we need to extend the box.
3178 extendLeftEdge = skew < 0.0 ? ceil(-skew * metrics.mBoundingBox.XMost())
3179 : ceil(skew * -metrics.mBoundingBox.X());
3180 extendRightEdge = skew < 0.0 ? ceil(-skew * -metrics.mBoundingBox.X())
3181 : ceil(skew * metrics.mBoundingBox.XMost());
3182 } else {
3183 extendLeftEdge = skew < 0.0 ? ceil(-skew * -metrics.mBoundingBox.Y())
3184 : ceil(skew * metrics.mBoundingBox.YMost());
3185 extendRightEdge = skew < 0.0 ? ceil(-skew * metrics.mBoundingBox.YMost())
3186 : ceil(skew * -metrics.mBoundingBox.Y());
3188 metrics.mBoundingBox.SetWidth(metrics.mBoundingBox.Width() +
3189 extendLeftEdge + extendRightEdge);
3190 metrics.mBoundingBox.MoveByX(-extendLeftEdge);
3193 if (baselineOffset != 0) {
3194 metrics.mAscent -= baselineOffset;
3195 metrics.mDescent += baselineOffset;
3196 metrics.mBoundingBox.MoveByY(baselineOffset);
3199 return metrics;
3202 bool gfxFont::AgeCachedWords() {
3203 mozilla::AutoWriteLock lock(mLock);
3204 if (mWordCache) {
3205 for (auto it = mWordCache->modIter(); !it.done(); it.next()) {
3206 auto& entry = it.get().value();
3207 if (!entry) {
3208 NS_ASSERTION(entry, "cache entry has no gfxShapedWord!");
3209 it.remove();
3210 } else if (entry->IncrementAge() == kShapedWordCacheMaxAge) {
3211 it.remove();
3214 return mWordCache->empty();
3216 return true;
3219 void gfxFont::NotifyGlyphsChanged() const {
3220 AutoReadLock lock(mLock);
3221 uint32_t i, count = mGlyphExtentsArray.Length();
3222 for (i = 0; i < count; ++i) {
3223 // Flush cached extents array
3224 mGlyphExtentsArray[i]->NotifyGlyphsChanged();
3227 if (mGlyphChangeObservers) {
3228 for (const auto& key : *mGlyphChangeObservers) {
3229 key->NotifyGlyphsChanged();
3234 // If aChar is a "word boundary" for shaped-word caching purposes, return it;
3235 // else return 0.
3236 static char16_t IsBoundarySpace(char16_t aChar, char16_t aNextChar) {
3237 if ((aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar)) {
3238 return aChar;
3240 return 0;
3243 // In 8-bit text, there cannot be any cluster-extenders.
3244 static uint8_t IsBoundarySpace(uint8_t aChar, uint8_t aNextChar) {
3245 if (aChar == ' ' || aChar == 0x00A0) {
3246 return aChar;
3248 return 0;
3251 #ifdef __GNUC__
3252 # define GFX_MAYBE_UNUSED __attribute__((unused))
3253 #else
3254 # define GFX_MAYBE_UNUSED
3255 #endif
3257 template <typename T, typename Func>
3258 bool gfxFont::ProcessShapedWordInternal(
3259 DrawTarget* aDrawTarget, const T* aText, uint32_t aLength, uint32_t aHash,
3260 Script aRunScript, nsAtom* aLanguage, bool aVertical,
3261 int32_t aAppUnitsPerDevUnit, gfx::ShapedTextFlags aFlags,
3262 RoundingFlags aRounding, gfxTextPerfMetrics* aTextPerf GFX_MAYBE_UNUSED,
3263 Func aCallback) {
3264 WordCacheKey key(aText, aLength, aHash, aRunScript, aLanguage,
3265 aAppUnitsPerDevUnit, aFlags, aRounding);
3267 // If we have a word cache, attempt to look up the word in it.
3268 AutoReadLock lock(mLock);
3269 if (mWordCache) {
3270 // if there's a cached entry for this word, just return it
3271 if (auto entry = mWordCache->lookup(key)) {
3272 entry->value()->ResetAge();
3273 #ifndef RELEASE_OR_BETA
3274 if (aTextPerf) {
3275 // XXX we should make sure this is atomic
3276 aTextPerf->current.wordCacheHit++;
3278 #endif
3279 aCallback(entry->value().get());
3280 return true;
3285 // We didn't find a cached word (or don't even have a cache yet), so create
3286 // a new gfxShapedWord and cache it. We don't have to lock during shaping,
3287 // only when it comes time to cache the new entry.
3289 UniquePtr<gfxShapedWord> newShapedWord(
3290 gfxShapedWord::Create(aText, aLength, aRunScript, aLanguage,
3291 aAppUnitsPerDevUnit, aFlags, aRounding));
3292 if (!newShapedWord) {
3293 NS_WARNING("failed to create gfxShapedWord - expect missing text");
3294 return false;
3296 DebugOnly<bool> ok =
3297 ShapeText(aDrawTarget, aText, 0, aLength, aRunScript, aLanguage,
3298 aVertical, aRounding, newShapedWord.get());
3299 NS_WARNING_ASSERTION(ok, "failed to shape word - expect garbled text");
3302 // We're going to cache the new shaped word, so lock for writing now.
3303 AutoWriteLock lock(mLock);
3304 if (!mWordCache) {
3305 mWordCache = MakeUnique<HashMap<WordCacheKey, UniquePtr<gfxShapedWord>,
3306 WordCacheKey::HashPolicy>>();
3307 } else {
3308 // If the cache is getting too big, flush it and start over.
3309 uint32_t wordCacheMaxEntries =
3310 gfxPlatform::GetPlatform()->WordCacheMaxEntries();
3311 if (mWordCache->count() > wordCacheMaxEntries) {
3312 // Flush the cache if it is getting overly big.
3313 NS_WARNING("flushing shaped-word cache");
3314 ClearCachedWordsLocked();
3318 // Update key so that it references the text stored in the newShapedWord,
3319 // which is guaranteed to live as long as the hashtable entry.
3320 if ((key.mTextIs8Bit = newShapedWord->TextIs8Bit())) {
3321 key.mText.mSingle = newShapedWord->Text8Bit();
3322 } else {
3323 key.mText.mDouble = newShapedWord->TextUnicode();
3325 auto entry = mWordCache->lookupForAdd(key);
3327 // It's unlikely, but maybe another thread got there before us...
3328 if (entry) {
3329 // Use the existing entry; the newShapedWord will be discarded.
3330 entry->value()->ResetAge();
3331 #ifndef RELEASE_OR_BETA
3332 if (aTextPerf) {
3333 aTextPerf->current.wordCacheHit++;
3335 #endif
3336 aCallback(entry->value().get());
3337 return true;
3340 if (!mWordCache->add(entry, key, std::move(newShapedWord))) {
3341 NS_WARNING("failed to cache gfxShapedWord - expect missing text");
3342 return false;
3345 #ifndef RELEASE_OR_BETA
3346 if (aTextPerf) {
3347 aTextPerf->current.wordCacheMiss++;
3349 #endif
3350 aCallback(entry->value().get());
3353 gfxFontCache::GetCache()->RunWordCacheExpirationTimer();
3354 return true;
3357 bool gfxFont::WordCacheKey::HashPolicy::match(const Key& aKey,
3358 const Lookup& aLookup) {
3359 if (aKey.mLength != aLookup.mLength || aKey.mFlags != aLookup.mFlags ||
3360 aKey.mRounding != aLookup.mRounding ||
3361 aKey.mAppUnitsPerDevUnit != aLookup.mAppUnitsPerDevUnit ||
3362 aKey.mScript != aLookup.mScript || aKey.mLanguage != aLookup.mLanguage) {
3363 return false;
3366 if (aKey.mTextIs8Bit) {
3367 if (aLookup.mTextIs8Bit) {
3368 return (0 == memcmp(aKey.mText.mSingle, aLookup.mText.mSingle,
3369 aKey.mLength * sizeof(uint8_t)));
3371 // The lookup key has 16-bit text, even though all the characters are < 256,
3372 // so the TEXT_IS_8BIT flag was set and the cached ShapedWord we're
3373 // comparing with will have 8-bit text.
3374 const uint8_t* s1 = aKey.mText.mSingle;
3375 const char16_t* s2 = aLookup.mText.mDouble;
3376 const char16_t* s2end = s2 + aKey.mLength;
3377 while (s2 < s2end) {
3378 if (*s1++ != *s2++) {
3379 return false;
3382 return true;
3384 NS_ASSERTION(!(aLookup.mFlags & gfx::ShapedTextFlags::TEXT_IS_8BIT) &&
3385 !aLookup.mTextIs8Bit,
3386 "didn't expect 8-bit text here");
3387 return (0 == memcmp(aKey.mText.mDouble, aLookup.mText.mDouble,
3388 aKey.mLength * sizeof(char16_t)));
3391 bool gfxFont::ProcessSingleSpaceShapedWord(
3392 DrawTarget* aDrawTarget, bool aVertical, int32_t aAppUnitsPerDevUnit,
3393 gfx::ShapedTextFlags aFlags, RoundingFlags aRounding,
3394 const std::function<void(gfxShapedWord*)>& aCallback) {
3395 static const uint8_t space = ' ';
3396 return ProcessShapedWordInternal(
3397 aDrawTarget, &space, 1, gfxShapedWord::HashMix(0, ' '), Script::LATIN,
3398 /* aLanguage = */ nullptr, aVertical, aAppUnitsPerDevUnit, aFlags,
3399 aRounding, nullptr, aCallback);
3402 bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const uint8_t* aText,
3403 uint32_t aOffset, uint32_t aLength, Script aScript,
3404 nsAtom* aLanguage, bool aVertical,
3405 RoundingFlags aRounding, gfxShapedText* aShapedText) {
3406 nsDependentCSubstring ascii((const char*)aText, aLength);
3407 nsAutoString utf16;
3408 AppendASCIItoUTF16(ascii, utf16);
3409 if (utf16.Length() != aLength) {
3410 return false;
3412 return ShapeText(aDrawTarget, utf16.BeginReading(), aOffset, aLength, aScript,
3413 aLanguage, aVertical, aRounding, aShapedText);
3416 bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText,
3417 uint32_t aOffset, uint32_t aLength, Script aScript,
3418 nsAtom* aLanguage, bool aVertical,
3419 RoundingFlags aRounding, gfxShapedText* aShapedText) {
3420 // XXX Currently, we do all vertical shaping through harfbuzz.
3421 // Vertical graphite support may be wanted as a future enhancement.
3422 // XXX Graphite shaping currently only supported on the main thread!
3423 // Worker-thread shaping (offscreen canvas) will always go via harfbuzz.
3424 if (FontCanSupportGraphite() && !aVertical && NS_IsMainThread()) {
3425 if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
3426 gfxGraphiteShaper* shaper = mGraphiteShaper;
3427 if (!shaper) {
3428 shaper = new gfxGraphiteShaper(this);
3429 if (!mGraphiteShaper.compareExchange(nullptr, shaper)) {
3430 delete shaper;
3431 shaper = mGraphiteShaper;
3434 if (shaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript,
3435 aLanguage, aVertical, aRounding, aShapedText)) {
3436 PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical,
3437 aShapedText);
3438 return true;
3443 gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper();
3444 if (shaper &&
3445 shaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript,
3446 aLanguage, aVertical, aRounding, aShapedText)) {
3447 PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical,
3448 aShapedText);
3449 if (GetFontEntry()->HasTrackingTable()) {
3450 // Convert font size from device pixels back to CSS px
3451 // to use in selecting tracking value
3452 gfxFloat trackSize = GetAdjustedSize() *
3453 aShapedText->GetAppUnitsPerDevUnit() /
3454 AppUnitsPerCSSPixel();
3455 // Usually, a given font will be used with the same appunit scale, so we
3456 // can cache the tracking value rather than recompute it every time.
3458 AutoReadLock lock(mLock);
3459 if (trackSize == mCachedTrackingSize) {
3460 // Applying tracking is a lot like the adjustment we do for
3461 // synthetic bold: we want to apply between clusters, not to
3462 // non-spacing glyphs within a cluster. So we can reuse that
3463 // helper here.
3464 aShapedText->ApplyTrackingToClusters(mTracking, aOffset, aLength);
3465 return true;
3468 // We didn't have the appropriate tracking value cached yet.
3469 AutoWriteLock lock(mLock);
3470 if (trackSize != mCachedTrackingSize) {
3471 mCachedTrackingSize = trackSize;
3472 mTracking =
3473 GetFontEntry()->TrackingForCSSPx(trackSize) * mFUnitsConvFactor;
3475 aShapedText->ApplyTrackingToClusters(mTracking, aOffset, aLength);
3477 return true;
3480 NS_WARNING_ASSERTION(false, "shaper failed, expect scrambled/missing text");
3481 return false;
3484 void gfxFont::PostShapingFixup(DrawTarget* aDrawTarget, const char16_t* aText,
3485 uint32_t aOffset, uint32_t aLength,
3486 bool aVertical, gfxShapedText* aShapedText) {
3487 if (ApplySyntheticBold()) {
3488 const Metrics& metrics = GetMetrics(aVertical ? nsFontMetrics::eVertical
3489 : nsFontMetrics::eHorizontal);
3490 if (metrics.maxAdvance > metrics.aveCharWidth) {
3491 aShapedText->ApplyTrackingToClusters(GetSyntheticBoldOffset(), aOffset,
3492 aLength);
3497 #define MAX_SHAPING_LENGTH \
3498 32760 // slightly less than 32K, trying to avoid
3499 // over-stressing platform shapers
3500 #define BACKTRACK_LIMIT \
3501 16 // backtrack this far looking for a good place
3502 // to split into fragments for separate shaping
3504 template <typename T>
3505 bool gfxFont::ShapeFragmentWithoutWordCache(DrawTarget* aDrawTarget,
3506 const T* aText, uint32_t aOffset,
3507 uint32_t aLength, Script aScript,
3508 nsAtom* aLanguage, bool aVertical,
3509 RoundingFlags aRounding,
3510 gfxTextRun* aTextRun) {
3511 aTextRun->SetupClusterBoundaries(aOffset, aText, aLength);
3513 bool ok = true;
3515 while (ok && aLength > 0) {
3516 uint32_t fragLen = aLength;
3518 // limit the length of text we pass to shapers in a single call
3519 if (fragLen > MAX_SHAPING_LENGTH) {
3520 fragLen = MAX_SHAPING_LENGTH;
3522 // in the 8-bit case, there are no multi-char clusters,
3523 // so we don't need to do this check
3524 if constexpr (sizeof(T) == sizeof(char16_t)) {
3525 uint32_t i;
3526 for (i = 0; i < BACKTRACK_LIMIT; ++i) {
3527 if (aTextRun->IsClusterStart(aOffset + fragLen - i)) {
3528 fragLen -= i;
3529 break;
3532 if (i == BACKTRACK_LIMIT) {
3533 // if we didn't find any cluster start while backtracking,
3534 // just check that we're not in the middle of a surrogate
3535 // pair; back up by one code unit if we are.
3536 if (NS_IS_SURROGATE_PAIR(aText[fragLen - 1], aText[fragLen])) {
3537 --fragLen;
3543 ok = ShapeText(aDrawTarget, aText, aOffset, fragLen, aScript, aLanguage,
3544 aVertical, aRounding, aTextRun);
3546 aText += fragLen;
3547 aOffset += fragLen;
3548 aLength -= fragLen;
3551 return ok;
3554 // Check if aCh is an unhandled control character that should be displayed
3555 // as a hexbox rather than rendered by some random font on the system.
3556 // We exclude \r as stray &#13;s are rather common (bug 941940).
3557 // Note that \n and \t don't come through here, as they have specific
3558 // meanings that have already been handled.
3559 static bool IsInvalidControlChar(uint32_t aCh) {
3560 return aCh != '\r' && ((aCh & 0x7f) < 0x20 || aCh == 0x7f);
3563 template <typename T>
3564 bool gfxFont::ShapeTextWithoutWordCache(DrawTarget* aDrawTarget, const T* aText,
3565 uint32_t aOffset, uint32_t aLength,
3566 Script aScript, nsAtom* aLanguage,
3567 bool aVertical, RoundingFlags aRounding,
3568 gfxTextRun* aTextRun) {
3569 uint32_t fragStart = 0;
3570 bool ok = true;
3572 for (uint32_t i = 0; i <= aLength && ok; ++i) {
3573 T ch = (i < aLength) ? aText[i] : '\n';
3574 bool invalid = gfxFontGroup::IsInvalidChar(ch);
3575 uint32_t length = i - fragStart;
3577 // break into separate fragments when we hit an invalid char
3578 if (!invalid) {
3579 continue;
3582 if (length > 0) {
3583 ok = ShapeFragmentWithoutWordCache(
3584 aDrawTarget, aText + fragStart, aOffset + fragStart, length, aScript,
3585 aLanguage, aVertical, aRounding, aTextRun);
3588 if (i == aLength) {
3589 break;
3592 // fragment was terminated by an invalid char: skip it,
3593 // unless it's a control char that we want to show as a hexbox,
3594 // but record where TAB or NEWLINE occur
3595 if (ch == '\t') {
3596 aTextRun->SetIsTab(aOffset + i);
3597 } else if (ch == '\n') {
3598 aTextRun->SetIsNewline(aOffset + i);
3599 } else if (GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_FORMAT) {
3600 aTextRun->SetIsFormattingControl(aOffset + i);
3601 } else if (IsInvalidControlChar(ch) &&
3602 !(aTextRun->GetFlags() &
3603 gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS)) {
3604 if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) {
3605 ShapeFragmentWithoutWordCache(aDrawTarget, aText + i, aOffset + i, 1,
3606 aScript, aLanguage, aVertical, aRounding,
3607 aTextRun);
3608 } else {
3609 aTextRun->SetMissingGlyph(aOffset + i, ch, this);
3612 fragStart = i + 1;
3615 NS_WARNING_ASSERTION(ok, "failed to shape text - expect garbled text");
3616 return ok;
3619 #ifndef RELEASE_OR_BETA
3620 # define TEXT_PERF_INCR(tp, m) (tp ? (tp)->current.m++ : 0)
3621 #else
3622 # define TEXT_PERF_INCR(tp, m)
3623 #endif
3625 inline static bool IsChar8Bit(uint8_t /*aCh*/) { return true; }
3626 inline static bool IsChar8Bit(char16_t aCh) { return aCh < 0x100; }
3628 inline static bool HasSpaces(const uint8_t* aString, uint32_t aLen) {
3629 return memchr(aString, 0x20, aLen) != nullptr;
3632 inline static bool HasSpaces(const char16_t* aString, uint32_t aLen) {
3633 for (const char16_t* ch = aString; ch < aString + aLen; ch++) {
3634 if (*ch == 0x20) {
3635 return true;
3638 return false;
3641 template <typename T>
3642 bool gfxFont::SplitAndInitTextRun(
3643 DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
3644 const T* aString, // text for this font run
3645 uint32_t aRunStart, // position in the textrun
3646 uint32_t aRunLength, Script aRunScript, nsAtom* aLanguage,
3647 ShapedTextFlags aOrientation) {
3648 if (aRunLength == 0) {
3649 return true;
3652 gfxTextPerfMetrics* tp = nullptr;
3653 RoundingFlags rounding = GetRoundOffsetsToPixels(aDrawTarget);
3655 #ifndef RELEASE_OR_BETA
3656 tp = aTextRun->GetFontGroup()->GetTextPerfMetrics();
3657 if (tp) {
3658 if (mStyle.systemFont) {
3659 tp->current.numChromeTextRuns++;
3660 } else {
3661 tp->current.numContentTextRuns++;
3663 tp->current.numChars += aRunLength;
3664 if (aRunLength > tp->current.maxTextRunLen) {
3665 tp->current.maxTextRunLen = aRunLength;
3668 #endif
3670 uint32_t wordCacheCharLimit =
3671 gfxPlatform::GetPlatform()->WordCacheCharLimit();
3673 bool vertical = aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
3675 // If spaces can participate in shaping (e.g. within lookups for automatic
3676 // fractions), need to shape without using the word cache which segments
3677 // textruns on space boundaries. Word cache can be used if the textrun
3678 // is short enough to fit in the word cache and it lacks spaces.
3679 tainted_boolean_hint t_canParticipate =
3680 SpaceMayParticipateInShaping(aRunScript);
3681 bool canParticipate = t_canParticipate.unverified_safe_because(
3682 "We need to ensure that this function operates safely independent of "
3683 "t_canParticipate. The worst that can happen here is that the decision "
3684 "to use the cache is incorrectly made, resulting in a bad "
3685 "rendering/slowness. However, this would not compromise the memory "
3686 "safety of Firefox in any way, and can thus be permitted");
3688 if (canParticipate) {
3689 if (aRunLength > wordCacheCharLimit || HasSpaces(aString, aRunLength)) {
3690 TEXT_PERF_INCR(tp, wordCacheSpaceRules);
3691 return ShapeTextWithoutWordCache(aDrawTarget, aString, aRunStart,
3692 aRunLength, aRunScript, aLanguage,
3693 vertical, rounding, aTextRun);
3697 // the only flags we care about for ShapedWord construction/caching
3698 gfx::ShapedTextFlags flags = aTextRun->GetFlags();
3699 flags &= (gfx::ShapedTextFlags::TEXT_IS_RTL |
3700 gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES |
3701 gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT |
3702 gfx::ShapedTextFlags::TEXT_ORIENT_MASK);
3703 if constexpr (sizeof(T) == sizeof(uint8_t)) {
3704 flags |= gfx::ShapedTextFlags::TEXT_IS_8BIT;
3707 uint32_t wordStart = 0;
3708 uint32_t hash = 0;
3709 bool wordIs8Bit = true;
3710 int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
3712 T nextCh = aString[0];
3713 for (uint32_t i = 0; i <= aRunLength; ++i) {
3714 T ch = nextCh;
3715 nextCh = (i < aRunLength - 1) ? aString[i + 1] : '\n';
3716 T boundary = IsBoundarySpace(ch, nextCh);
3717 bool invalid = !boundary && gfxFontGroup::IsInvalidChar(ch);
3718 uint32_t length = i - wordStart;
3720 // break into separate ShapedWords when we hit an invalid char,
3721 // or a boundary space (always handled individually),
3722 // or the first non-space after a space
3723 if (!boundary && !invalid) {
3724 if (!IsChar8Bit(ch)) {
3725 wordIs8Bit = false;
3727 // include this character in the hash, and move on to next
3728 hash = gfxShapedWord::HashMix(hash, ch);
3729 continue;
3732 // We've decided to break here (i.e. we're at the end of a "word");
3733 // shape the word and add it to the textrun.
3734 // For words longer than the limit, we don't use the
3735 // font's word cache but just shape directly into the textrun.
3736 if (length > wordCacheCharLimit) {
3737 TEXT_PERF_INCR(tp, wordCacheLong);
3738 bool ok = ShapeFragmentWithoutWordCache(
3739 aDrawTarget, aString + wordStart, aRunStart + wordStart, length,
3740 aRunScript, aLanguage, vertical, rounding, aTextRun);
3741 if (!ok) {
3742 return false;
3744 } else if (length > 0) {
3745 gfx::ShapedTextFlags wordFlags = flags;
3746 // in the 8-bit version of this method, TEXT_IS_8BIT was
3747 // already set as part of |flags|, so no need for a per-word
3748 // adjustment here
3749 if (sizeof(T) == sizeof(char16_t)) {
3750 if (wordIs8Bit) {
3751 wordFlags |= gfx::ShapedTextFlags::TEXT_IS_8BIT;
3754 bool processed = ProcessShapedWordInternal(
3755 aDrawTarget, aString + wordStart, length, hash, aRunScript, aLanguage,
3756 vertical, appUnitsPerDevUnit, wordFlags, rounding, tp,
3757 [&](gfxShapedWord* aShapedWord) {
3758 aTextRun->CopyGlyphDataFrom(aShapedWord, aRunStart + wordStart);
3760 if (!processed) {
3761 return false; // failed, presumably out of memory?
3765 if (boundary) {
3766 // word was terminated by a space: add that to the textrun
3767 MOZ_ASSERT(aOrientation != ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED,
3768 "text-orientation:mixed should be resolved earlier");
3769 if (boundary != ' ' || !aTextRun->SetSpaceGlyphIfSimple(
3770 this, aRunStart + i, ch, aOrientation)) {
3771 // Currently, the only "boundary" characters we recognize are
3772 // space and no-break space, which are both 8-bit, so we force
3773 // that flag (below). If we ever change IsBoundarySpace, we
3774 // may need to revise this.
3775 // Avoid tautological-constant-out-of-range-compare in 8-bit:
3776 DebugOnly<char16_t> boundary16 = boundary;
3777 NS_ASSERTION(boundary16 < 256, "unexpected boundary!");
3778 bool processed = ProcessShapedWordInternal(
3779 aDrawTarget, &boundary, 1, gfxShapedWord::HashMix(0, boundary),
3780 aRunScript, aLanguage, vertical, appUnitsPerDevUnit,
3781 flags | gfx::ShapedTextFlags::TEXT_IS_8BIT, rounding, tp,
3782 [&](gfxShapedWord* aShapedWord) {
3783 aTextRun->CopyGlyphDataFrom(aShapedWord, aRunStart + i);
3784 if (boundary == ' ') {
3785 aTextRun->GetCharacterGlyphs()[aRunStart + i].SetIsSpace();
3788 if (!processed) {
3789 return false;
3792 hash = 0;
3793 wordStart = i + 1;
3794 wordIs8Bit = true;
3795 continue;
3798 if (i == aRunLength) {
3799 break;
3802 NS_ASSERTION(invalid, "how did we get here except via an invalid char?");
3804 // word was terminated by an invalid char: skip it,
3805 // unless it's a control char that we want to show as a hexbox,
3806 // but record where TAB or NEWLINE occur
3807 if (ch == '\t') {
3808 aTextRun->SetIsTab(aRunStart + i);
3809 } else if (ch == '\n') {
3810 aTextRun->SetIsNewline(aRunStart + i);
3811 } else if (GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_FORMAT) {
3812 aTextRun->SetIsFormattingControl(aRunStart + i);
3813 } else if (IsInvalidControlChar(ch) &&
3814 !(aTextRun->GetFlags() &
3815 gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS)) {
3816 if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) {
3817 ShapeFragmentWithoutWordCache(aDrawTarget, aString + i, aRunStart + i,
3818 1, aRunScript, aLanguage, vertical,
3819 rounding, aTextRun);
3820 } else {
3821 aTextRun->SetMissingGlyph(aRunStart + i, ch, this);
3825 hash = 0;
3826 wordStart = i + 1;
3827 wordIs8Bit = true;
3830 return true;
3833 // Explicit instantiations of SplitAndInitTextRun, to avoid libxul link failure
3834 template bool gfxFont::SplitAndInitTextRun(
3835 DrawTarget* aDrawTarget, gfxTextRun* aTextRun, const uint8_t* aString,
3836 uint32_t aRunStart, uint32_t aRunLength, Script aRunScript,
3837 nsAtom* aLanguage, ShapedTextFlags aOrientation);
3838 template bool gfxFont::SplitAndInitTextRun(
3839 DrawTarget* aDrawTarget, gfxTextRun* aTextRun, const char16_t* aString,
3840 uint32_t aRunStart, uint32_t aRunLength, Script aRunScript,
3841 nsAtom* aLanguage, ShapedTextFlags aOrientation);
3843 template <>
3844 bool gfxFont::InitFakeSmallCapsRun(
3845 nsPresContext* aPresContext, DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
3846 const char16_t* aText, uint32_t aOffset, uint32_t aLength,
3847 FontMatchType aMatchType, gfx::ShapedTextFlags aOrientation, Script aScript,
3848 nsAtom* aLanguage, bool aSyntheticLower, bool aSyntheticUpper) {
3849 bool ok = true;
3851 RefPtr<gfxFont> smallCapsFont = GetSmallCapsFont();
3852 if (!smallCapsFont) {
3853 NS_WARNING("failed to get reduced-size font for smallcaps!");
3854 smallCapsFont = this;
3857 bool isCJK = gfxTextRun::IsCJKScript(aScript);
3859 enum RunCaseAction { kNoChange, kUppercaseReduce, kUppercase };
3861 RunCaseAction runAction = kNoChange;
3862 uint32_t runStart = 0;
3864 for (uint32_t i = 0; i <= aLength; ++i) {
3865 uint32_t extraCodeUnits = 0; // Will be set to 1 if we need to consume
3866 // a trailing surrogate as well as the
3867 // current code unit.
3868 RunCaseAction chAction = kNoChange;
3869 // Unless we're at the end, figure out what treatment the current
3870 // character will need.
3871 if (i < aLength) {
3872 uint32_t ch = aText[i];
3873 if (i < aLength - 1 && NS_IS_SURROGATE_PAIR(ch, aText[i + 1])) {
3874 ch = SURROGATE_TO_UCS4(ch, aText[i + 1]);
3875 extraCodeUnits = 1;
3877 // Characters that aren't the start of a cluster are ignored here.
3878 // They get added to whatever lowercase/non-lowercase run we're in.
3879 if (IsClusterExtender(ch)) {
3880 chAction = runAction;
3881 } else {
3882 if (ch != ToUpperCase(ch) || SpecialUpper(ch)) {
3883 // ch is lower case
3884 chAction = (aSyntheticLower ? kUppercaseReduce : kNoChange);
3885 } else if (ch != ToLowerCase(ch)) {
3886 // ch is upper case
3887 chAction = (aSyntheticUpper ? kUppercaseReduce : kNoChange);
3888 if (aLanguage == nsGkAtoms::el) {
3889 // In Greek, check for characters that will be modified by
3890 // the GreekUpperCase mapping - this catches accented
3891 // capitals where the accent is to be removed (bug 307039).
3892 // These are handled by using the full-size font with the
3893 // uppercasing transform.
3894 mozilla::GreekCasing::State state;
3895 bool markEta, updateEta;
3896 uint32_t ch2 =
3897 mozilla::GreekCasing::UpperCase(ch, state, markEta, updateEta);
3898 if ((ch != ch2 || markEta) && !aSyntheticUpper) {
3899 chAction = kUppercase;
3906 // At the end of the text or when the current character needs different
3907 // casing treatment from the current run, finish the run-in-progress
3908 // and prepare to accumulate a new run.
3909 // Note that we do not look at any source data for offset [i] here,
3910 // as that would be invalid in the case where i==length.
3911 if ((i == aLength || runAction != chAction) && runStart < i) {
3912 uint32_t runLength = i - runStart;
3913 gfxFont* f = this;
3914 switch (runAction) {
3915 case kNoChange:
3916 // just use the current font and the existing string
3917 aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true,
3918 aOrientation, isCJK);
3919 if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun, aText + runStart,
3920 aOffset + runStart, runLength, aScript,
3921 aLanguage, aOrientation)) {
3922 ok = false;
3924 break;
3926 case kUppercaseReduce:
3927 // use reduced-size font, then fall through to uppercase the text
3928 f = smallCapsFont;
3929 [[fallthrough]];
3931 case kUppercase:
3932 // apply uppercase transform to the string
3933 nsDependentSubstring origString(aText + runStart, runLength);
3934 nsAutoString convertedString;
3935 AutoTArray<bool, 50> charsToMergeArray;
3936 AutoTArray<bool, 50> deletedCharsArray;
3938 const auto globalTransform = StyleTextTransform::UPPERCASE;
3939 // No mask needed; we're doing case conversion, not password-hiding.
3940 const char16_t maskChar = 0;
3941 bool mergeNeeded = nsCaseTransformTextRunFactory::TransformString(
3942 origString, convertedString, Some(globalTransform), maskChar,
3943 /* aCaseTransformsOnly = */ false, aLanguage, charsToMergeArray,
3944 deletedCharsArray);
3946 // Check whether the font supports the uppercased characters needed;
3947 // if not, we're not going to be able to simulate small-caps.
3948 bool failed = false;
3949 char16_t highSurrogate = 0;
3950 for (const char16_t* cp = convertedString.BeginReading();
3951 cp != convertedString.EndReading(); ++cp) {
3952 if (NS_IS_HIGH_SURROGATE(*cp)) {
3953 highSurrogate = *cp;
3954 continue;
3956 uint32_t ch = *cp;
3957 if (NS_IS_LOW_SURROGATE(*cp) && highSurrogate) {
3958 ch = SURROGATE_TO_UCS4(highSurrogate, *cp);
3960 highSurrogate = 0;
3961 if (!f->HasCharacter(ch)) {
3962 if (IsDefaultIgnorable(ch)) {
3963 continue;
3965 failed = true;
3966 break;
3969 // Required uppercase letter(s) missing from the font. Just use the
3970 // original text with the original font, no fake small caps!
3971 if (failed) {
3972 convertedString = origString;
3973 mergeNeeded = false;
3974 f = this;
3977 if (mergeNeeded) {
3978 // This is the hard case: the transformation caused chars
3979 // to be inserted or deleted, so we can't shape directly
3980 // into the destination textrun but have to handle the
3981 // mismatch of character positions.
3982 gfxTextRunFactory::Parameters params = {
3983 aDrawTarget, nullptr, nullptr,
3984 nullptr, 0, aTextRun->GetAppUnitsPerDevUnit()};
3985 RefPtr<gfxTextRun> tempRun(gfxTextRun::Create(
3986 &params, convertedString.Length(), aTextRun->GetFontGroup(),
3987 gfx::ShapedTextFlags(), nsTextFrameUtils::Flags()));
3988 tempRun->AddGlyphRun(f, aMatchType, 0, true, aOrientation, isCJK);
3989 if (!f->SplitAndInitTextRun(aDrawTarget, tempRun.get(),
3990 convertedString.BeginReading(), 0,
3991 convertedString.Length(), aScript,
3992 aLanguage, aOrientation)) {
3993 ok = false;
3994 } else {
3995 RefPtr<gfxTextRun> mergedRun(gfxTextRun::Create(
3996 &params, runLength, aTextRun->GetFontGroup(),
3997 gfx::ShapedTextFlags(), nsTextFrameUtils::Flags()));
3998 MergeCharactersInTextRun(mergedRun.get(), tempRun.get(),
3999 charsToMergeArray.Elements(),
4000 deletedCharsArray.Elements());
4001 gfxTextRun::Range runRange(0, runLength);
4002 aTextRun->CopyGlyphDataFrom(mergedRun.get(), runRange,
4003 aOffset + runStart);
4005 } else {
4006 aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true,
4007 aOrientation, isCJK);
4008 if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun,
4009 convertedString.BeginReading(),
4010 aOffset + runStart, runLength, aScript,
4011 aLanguage, aOrientation)) {
4012 ok = false;
4015 break;
4018 runStart = i;
4021 i += extraCodeUnits;
4022 if (i < aLength) {
4023 runAction = chAction;
4027 return ok;
4030 template <>
4031 bool gfxFont::InitFakeSmallCapsRun(
4032 nsPresContext* aPresContext, DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
4033 const uint8_t* aText, uint32_t aOffset, uint32_t aLength,
4034 FontMatchType aMatchType, gfx::ShapedTextFlags aOrientation, Script aScript,
4035 nsAtom* aLanguage, bool aSyntheticLower, bool aSyntheticUpper) {
4036 NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aText),
4037 aLength);
4038 return InitFakeSmallCapsRun(aPresContext, aDrawTarget, aTextRun,
4039 static_cast<const char16_t*>(unicodeString.get()),
4040 aOffset, aLength, aMatchType, aOrientation,
4041 aScript, aLanguage, aSyntheticLower,
4042 aSyntheticUpper);
4045 already_AddRefed<gfxFont> gfxFont::GetSmallCapsFont() const {
4046 gfxFontStyle style(*GetStyle());
4047 style.size *= SMALL_CAPS_SCALE_FACTOR;
4048 style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
4049 gfxFontEntry* fe = GetFontEntry();
4050 return fe->FindOrMakeFont(&style, mUnicodeRangeMap);
4053 already_AddRefed<gfxFont> gfxFont::GetSubSuperscriptFont(
4054 int32_t aAppUnitsPerDevPixel) const {
4055 gfxFontStyle style(*GetStyle());
4056 style.AdjustForSubSuperscript(aAppUnitsPerDevPixel);
4057 gfxFontEntry* fe = GetFontEntry();
4058 return fe->FindOrMakeFont(&style, mUnicodeRangeMap);
4061 gfxGlyphExtents* gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit) {
4062 uint32_t readCount;
4064 AutoReadLock lock(mLock);
4065 readCount = mGlyphExtentsArray.Length();
4066 for (uint32_t i = 0; i < readCount; ++i) {
4067 if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit)
4068 return mGlyphExtentsArray[i].get();
4071 AutoWriteLock lock(mLock);
4072 // Re-check in case of race.
4073 uint32_t count = mGlyphExtentsArray.Length();
4074 for (uint32_t i = readCount; i < count; ++i) {
4075 if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit)
4076 return mGlyphExtentsArray[i].get();
4078 gfxGlyphExtents* glyphExtents = new gfxGlyphExtents(aAppUnitsPerDevUnit);
4079 if (glyphExtents) {
4080 mGlyphExtentsArray.AppendElement(glyphExtents);
4081 // Initialize the extents of a space glyph, assuming that spaces don't
4082 // render anything!
4083 glyphExtents->SetContainedGlyphWidthAppUnits(GetSpaceGlyph(), 0);
4085 return glyphExtents;
4088 void gfxFont::SetupGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphID,
4089 bool aNeedTight, gfxGlyphExtents* aExtents) {
4090 gfxRect svgBounds;
4091 if (mFontEntry->TryGetSVGData(this) && mFontEntry->HasSVGGlyph(aGlyphID) &&
4092 mFontEntry->GetSVGGlyphExtents(aDrawTarget, aGlyphID, GetAdjustedSize(),
4093 &svgBounds)) {
4094 gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit();
4095 aExtents->SetTightGlyphExtents(
4096 aGlyphID, gfxRect(svgBounds.X() * d2a, svgBounds.Y() * d2a,
4097 svgBounds.Width() * d2a, svgBounds.Height() * d2a));
4098 return;
4101 if (mFontEntry->TryGetColorGlyphs() && mFontEntry->mCOLR &&
4102 COLRFonts::GetColrTableVersion(mFontEntry->mCOLR) == 1) {
4103 auto* shaper = GetHarfBuzzShaper();
4104 if (shaper && shaper->IsInitialized()) {
4105 RefPtr scaledFont = GetScaledFont(aDrawTarget);
4106 Rect r = COLRFonts::GetColorGlyphBounds(
4107 mFontEntry->mCOLR, shaper->GetHBFont(), aGlyphID, aDrawTarget,
4108 scaledFont, mFUnitsConvFactor);
4109 if (!r.IsEmpty()) {
4110 gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit();
4111 aExtents->SetTightGlyphExtents(
4112 aGlyphID, gfxRect(r.X() * d2a, r.Y() * d2a, r.Width() * d2a,
4113 r.Height() * d2a));
4114 return;
4119 gfxRect bounds;
4120 GetGlyphBounds(aGlyphID, &bounds, mAntialiasOption == kAntialiasNone);
4122 const Metrics& fontMetrics = GetMetrics(nsFontMetrics::eHorizontal);
4123 int32_t appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit();
4124 if (!aNeedTight && bounds.x >= 0.0 && bounds.y >= -fontMetrics.maxAscent &&
4125 bounds.height + bounds.y <= fontMetrics.maxDescent) {
4126 uint32_t appUnitsWidth =
4127 uint32_t(ceil((bounds.x + bounds.width) * appUnitsPerDevUnit));
4128 if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) {
4129 aExtents->SetContainedGlyphWidthAppUnits(aGlyphID,
4130 uint16_t(appUnitsWidth));
4131 return;
4134 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
4135 if (!aNeedTight) {
4136 ++gGlyphExtentsSetupFallBackToTight;
4138 #endif
4140 gfxFloat d2a = appUnitsPerDevUnit;
4141 aExtents->SetTightGlyphExtents(
4142 aGlyphID, gfxRect(bounds.x * d2a, bounds.y * d2a, bounds.width * d2a,
4143 bounds.height * d2a));
4146 // Try to initialize font metrics by reading sfnt tables directly;
4147 // set mIsValid=TRUE and return TRUE on success.
4148 // Return FALSE if the gfxFontEntry subclass does not
4149 // implement GetFontTable(), or for non-sfnt fonts where tables are
4150 // not available.
4151 // If this returns TRUE without setting the mIsValid flag, then we -did-
4152 // apparently find an sfnt, but it was too broken to be used.
4153 bool gfxFont::InitMetricsFromSfntTables(Metrics& aMetrics) {
4154 mIsValid = false; // font is NOT valid in case of early return
4156 const uint32_t kHheaTableTag = TRUETYPE_TAG('h', 'h', 'e', 'a');
4157 const uint32_t kOS_2TableTag = TRUETYPE_TAG('O', 'S', '/', '2');
4159 uint32_t len;
4161 if (mFUnitsConvFactor < 0.0) {
4162 // If the conversion factor from FUnits is not yet set,
4163 // get the unitsPerEm from the 'head' table via the font entry
4164 uint16_t unitsPerEm = GetFontEntry()->UnitsPerEm();
4165 if (unitsPerEm == gfxFontEntry::kInvalidUPEM) {
4166 return false;
4168 mFUnitsConvFactor = GetAdjustedSize() / unitsPerEm;
4171 // 'hhea' table is required for the advanceWidthMax field
4172 gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag);
4173 if (!hheaTable) {
4174 return false; // no 'hhea' table -> not an sfnt
4176 const MetricsHeader* hhea =
4177 reinterpret_cast<const MetricsHeader*>(hb_blob_get_data(hheaTable, &len));
4178 if (len < sizeof(MetricsHeader)) {
4179 return false;
4182 #define SET_UNSIGNED(field, src) \
4183 aMetrics.field = uint16_t(src) * mFUnitsConvFactor
4184 #define SET_SIGNED(field, src) aMetrics.field = int16_t(src) * mFUnitsConvFactor
4186 SET_UNSIGNED(maxAdvance, hhea->advanceWidthMax);
4188 // 'OS/2' table is optional, if not found we'll estimate xHeight
4189 // and aveCharWidth by measuring glyphs
4190 gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag);
4191 if (os2Table) {
4192 const OS2Table* os2 =
4193 reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
4194 // this should always be present in any valid OS/2 of any version
4195 if (len >= offsetof(OS2Table, xAvgCharWidth) + sizeof(int16_t)) {
4196 SET_SIGNED(aveCharWidth, os2->xAvgCharWidth);
4200 #undef SET_SIGNED
4201 #undef SET_UNSIGNED
4203 hb_font_t* hbFont = gfxHarfBuzzShaper::CreateHBFont(this);
4204 hb_position_t position;
4206 auto FixedToFloat = [](hb_position_t f) -> gfxFloat { return f / 65536.0; };
4208 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER,
4209 &position)) {
4210 aMetrics.maxAscent = FixedToFloat(position);
4212 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER,
4213 &position)) {
4214 aMetrics.maxDescent = -FixedToFloat(position);
4216 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP,
4217 &position)) {
4218 aMetrics.externalLeading = FixedToFloat(position);
4221 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_UNDERLINE_OFFSET,
4222 &position)) {
4223 aMetrics.underlineOffset = FixedToFloat(position);
4225 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_UNDERLINE_SIZE,
4226 &position)) {
4227 aMetrics.underlineSize = FixedToFloat(position);
4229 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_STRIKEOUT_OFFSET,
4230 &position)) {
4231 aMetrics.strikeoutOffset = FixedToFloat(position);
4233 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_STRIKEOUT_SIZE,
4234 &position)) {
4235 aMetrics.strikeoutSize = FixedToFloat(position);
4238 // Although sxHeight and sCapHeight are signed fields, we consider
4239 // zero/negative values to be erroneous and just ignore them.
4240 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_X_HEIGHT,
4241 &position) &&
4242 position > 0) {
4243 aMetrics.xHeight = FixedToFloat(position);
4245 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_CAP_HEIGHT,
4246 &position) &&
4247 position > 0) {
4248 aMetrics.capHeight = FixedToFloat(position);
4250 hb_font_destroy(hbFont);
4252 mIsValid = true;
4254 return true;
4257 static double RoundToNearestMultiple(double aValue, double aFraction) {
4258 return floor(aValue / aFraction + 0.5) * aFraction;
4261 void gfxFont::CalculateDerivedMetrics(Metrics& aMetrics) {
4262 aMetrics.maxAscent =
4263 ceil(RoundToNearestMultiple(aMetrics.maxAscent, 1 / 1024.0));
4264 aMetrics.maxDescent =
4265 ceil(RoundToNearestMultiple(aMetrics.maxDescent, 1 / 1024.0));
4267 if (aMetrics.xHeight <= 0) {
4268 // only happens if we couldn't find either font metrics
4269 // or a char to measure;
4270 // pick an arbitrary value that's better than zero
4271 aMetrics.xHeight = aMetrics.maxAscent * DEFAULT_XHEIGHT_FACTOR;
4274 // If we have a font that doesn't provide a capHeight value, use maxAscent
4275 // as a reasonable fallback.
4276 if (aMetrics.capHeight <= 0) {
4277 aMetrics.capHeight = aMetrics.maxAscent;
4280 aMetrics.maxHeight = aMetrics.maxAscent + aMetrics.maxDescent;
4282 if (aMetrics.maxHeight - aMetrics.emHeight > 0.0) {
4283 aMetrics.internalLeading = aMetrics.maxHeight - aMetrics.emHeight;
4284 } else {
4285 aMetrics.internalLeading = 0.0;
4288 aMetrics.emAscent =
4289 aMetrics.maxAscent * aMetrics.emHeight / aMetrics.maxHeight;
4290 aMetrics.emDescent = aMetrics.emHeight - aMetrics.emAscent;
4292 if (GetFontEntry()->IsFixedPitch()) {
4293 // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger
4294 // advance than the average character width... this forces
4295 // those fonts to be recognized like fixed pitch fonts by layout.
4296 aMetrics.maxAdvance = aMetrics.aveCharWidth;
4299 if (!aMetrics.strikeoutOffset) {
4300 aMetrics.strikeoutOffset = aMetrics.xHeight * 0.5;
4302 if (!aMetrics.strikeoutSize) {
4303 aMetrics.strikeoutSize = aMetrics.underlineSize;
4307 void gfxFont::SanitizeMetrics(gfxFont::Metrics* aMetrics,
4308 bool aIsBadUnderlineFont) {
4309 // Even if this font size is zero, this font is created with non-zero size.
4310 // However, for layout and others, we should return the metrics of zero size
4311 // font.
4312 if (mStyle.AdjustedSizeMustBeZero()) {
4313 memset(aMetrics, 0, sizeof(gfxFont::Metrics));
4314 return;
4317 // If the font entry has ascent/descent/lineGap-override values,
4318 // replace the metrics from the font with the overrides.
4319 gfxFloat adjustedSize = GetAdjustedSize();
4320 if (mFontEntry->mAscentOverride >= 0.0) {
4321 aMetrics->maxAscent = mFontEntry->mAscentOverride * adjustedSize;
4322 aMetrics->maxHeight = aMetrics->maxAscent + aMetrics->maxDescent;
4323 aMetrics->internalLeading =
4324 std::max(0.0, aMetrics->maxHeight - aMetrics->emHeight);
4326 if (mFontEntry->mDescentOverride >= 0.0) {
4327 aMetrics->maxDescent = mFontEntry->mDescentOverride * adjustedSize;
4328 aMetrics->maxHeight = aMetrics->maxAscent + aMetrics->maxDescent;
4329 aMetrics->internalLeading =
4330 std::max(0.0, aMetrics->maxHeight - aMetrics->emHeight);
4332 if (mFontEntry->mLineGapOverride >= 0.0) {
4333 aMetrics->externalLeading = mFontEntry->mLineGapOverride * adjustedSize;
4336 aMetrics->underlineSize = std::max(1.0, aMetrics->underlineSize);
4337 aMetrics->strikeoutSize = std::max(1.0, aMetrics->strikeoutSize);
4339 aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -1.0);
4341 if (aMetrics->maxAscent < 1.0) {
4342 // We cannot draw strikeout line and overline in the ascent...
4343 aMetrics->underlineSize = 0;
4344 aMetrics->underlineOffset = 0;
4345 aMetrics->strikeoutSize = 0;
4346 aMetrics->strikeoutOffset = 0;
4347 return;
4351 * Some CJK fonts have bad underline offset. Therefore, if this is such font,
4352 * we need to lower the underline offset to bottom of *em* descent.
4353 * However, if this is system font, we should not do this for the rendering
4354 * compatibility with another application's UI on the platform.
4355 * XXX Should not use this hack if the font size is too small?
4356 * Such text cannot be read, this might be used for tight CSS
4357 * rendering? (E.g., Acid2)
4359 if (!mStyle.systemFont && aIsBadUnderlineFont) {
4360 // First, we need 2 pixels between baseline and underline at least. Because
4361 // many CJK characters put their glyphs on the baseline, so, 1 pixel is too
4362 // close for CJK characters.
4363 aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -2.0);
4365 // Next, we put the underline to bottom of below of the descent space.
4366 if (aMetrics->internalLeading + aMetrics->externalLeading >
4367 aMetrics->underlineSize) {
4368 aMetrics->underlineOffset =
4369 std::min(aMetrics->underlineOffset, -aMetrics->emDescent);
4370 } else {
4371 aMetrics->underlineOffset =
4372 std::min(aMetrics->underlineOffset,
4373 aMetrics->underlineSize - aMetrics->emDescent);
4376 // If underline positioned is too far from the text, descent position is
4377 // preferred so that underline will stay within the boundary.
4378 else if (aMetrics->underlineSize - aMetrics->underlineOffset >
4379 aMetrics->maxDescent) {
4380 if (aMetrics->underlineSize > aMetrics->maxDescent)
4381 aMetrics->underlineSize = std::max(aMetrics->maxDescent, 1.0);
4382 // The max underlineOffset is 1px (the min underlineSize is 1px, and min
4383 // maxDescent is 0px.)
4384 aMetrics->underlineOffset = aMetrics->underlineSize - aMetrics->maxDescent;
4387 // If strikeout line is overflowed from the ascent, the line should be resized
4388 // and moved for that being in the ascent space. Note that the strikeoutOffset
4389 // is *middle* of the strikeout line position.
4390 gfxFloat halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5);
4391 if (halfOfStrikeoutSize + aMetrics->strikeoutOffset > aMetrics->maxAscent) {
4392 if (aMetrics->strikeoutSize > aMetrics->maxAscent) {
4393 aMetrics->strikeoutSize = std::max(aMetrics->maxAscent, 1.0);
4394 halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5);
4396 gfxFloat ascent = floor(aMetrics->maxAscent + 0.5);
4397 aMetrics->strikeoutOffset = std::max(halfOfStrikeoutSize, ascent / 2.0);
4400 // If overline is larger than the ascent, the line should be resized.
4401 if (aMetrics->underlineSize > aMetrics->maxAscent) {
4402 aMetrics->underlineSize = aMetrics->maxAscent;
4406 gfxFont::Baselines gfxFont::GetBaselines(Orientation aOrientation) {
4407 // Approximated baselines for fonts lacking actual baseline data. These are
4408 // fractions of the em ascent/descent from the alphabetic baseline.
4409 const double kHangingBaselineDefault = 0.8; // fraction of ascent
4410 const double kIdeographicBaselineDefault = -0.5; // fraction of descent
4412 // If no BASE table is present, just return synthetic values immediately.
4413 if (!mFontEntry->HasFontTable(TRUETYPE_TAG('B', 'A', 'S', 'E'))) {
4414 // No baseline table; just synthesize them immediately.
4415 const Metrics& metrics = GetMetrics(aOrientation);
4416 return Baselines{
4417 0.0, // alphabetic
4418 kHangingBaselineDefault * metrics.emAscent, // hanging
4419 kIdeographicBaselineDefault * metrics.emDescent // ideographic
4423 // Use harfbuzz to try to read the font's baseline metrics.
4424 Baselines result{NAN, NAN, NAN};
4425 hb_font_t* hbFont = gfxHarfBuzzShaper::CreateHBFont(this);
4426 hb_direction_t hbDir = aOrientation == nsFontMetrics::eHorizontal
4427 ? HB_DIRECTION_LTR
4428 : HB_DIRECTION_TTB;
4429 hb_position_t position;
4430 unsigned count = 0;
4431 auto Fix2Float = [](hb_position_t f) -> gfxFloat { return f / 65536.0; };
4432 if (hb_ot_layout_get_baseline(hbFont, HB_OT_LAYOUT_BASELINE_TAG_ROMAN, hbDir,
4433 HB_OT_TAG_DEFAULT_SCRIPT,
4434 HB_OT_TAG_DEFAULT_LANGUAGE, &position)) {
4435 result.mAlphabetic = Fix2Float(position);
4436 count++;
4438 if (hb_ot_layout_get_baseline(hbFont, HB_OT_LAYOUT_BASELINE_TAG_HANGING,
4439 hbDir, HB_OT_TAG_DEFAULT_SCRIPT,
4440 HB_OT_TAG_DEFAULT_LANGUAGE, &position)) {
4441 result.mHanging = Fix2Float(position);
4442 count++;
4444 if (hb_ot_layout_get_baseline(
4445 hbFont, HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT, hbDir,
4446 HB_OT_TAG_DEFAULT_SCRIPT, HB_OT_TAG_DEFAULT_LANGUAGE, &position)) {
4447 result.mIdeographic = Fix2Float(position);
4448 count++;
4450 hb_font_destroy(hbFont);
4451 // If we successfully read all three, we can return now.
4452 if (count == 3) {
4453 return result;
4456 // Synthesize the baselines that we didn't find in the font.
4457 const Metrics& metrics = GetMetrics(aOrientation);
4458 if (std::isnan(result.mAlphabetic)) {
4459 result.mAlphabetic = 0.0;
4461 if (std::isnan(result.mHanging)) {
4462 result.mHanging = kHangingBaselineDefault * metrics.emAscent;
4464 if (std::isnan(result.mIdeographic)) {
4465 result.mIdeographic = kIdeographicBaselineDefault * metrics.emDescent;
4468 return result;
4471 // Create a Metrics record to be used for vertical layout. This should never
4472 // fail, as we've already decided this is a valid font. We do not have the
4473 // option of marking it invalid (as can happen if we're unable to read
4474 // horizontal metrics), because that could break a font that we're already
4475 // using for horizontal text.
4476 // So we will synthesize *something* usable here even if there aren't any of the
4477 // usual font tables (which can happen in the case of a legacy bitmap or Type1
4478 // font for which the platform-specific backend used platform APIs instead of
4479 // sfnt tables to create the horizontal metrics).
4480 void gfxFont::CreateVerticalMetrics() {
4481 const uint32_t kHheaTableTag = TRUETYPE_TAG('h', 'h', 'e', 'a');
4482 const uint32_t kVheaTableTag = TRUETYPE_TAG('v', 'h', 'e', 'a');
4483 const uint32_t kPostTableTag = TRUETYPE_TAG('p', 'o', 's', 't');
4484 const uint32_t kOS_2TableTag = TRUETYPE_TAG('O', 'S', '/', '2');
4485 uint32_t len;
4487 auto* metrics = new Metrics();
4488 ::memset(metrics, 0, sizeof(Metrics));
4490 // Some basic defaults, in case the font lacks any real metrics tables.
4491 // TODO: consider what rounding (if any) we should apply to these.
4492 metrics->emHeight = GetAdjustedSize();
4493 metrics->emAscent = metrics->emHeight / 2;
4494 metrics->emDescent = metrics->emHeight - metrics->emAscent;
4496 metrics->maxAscent = metrics->emAscent;
4497 metrics->maxDescent = metrics->emDescent;
4499 const float UNINITIALIZED_LEADING = -10000.0f;
4500 metrics->externalLeading = UNINITIALIZED_LEADING;
4502 if (mFUnitsConvFactor < 0.0) {
4503 uint16_t upem = GetFontEntry()->UnitsPerEm();
4504 if (upem != gfxFontEntry::kInvalidUPEM) {
4505 AutoWriteLock lock(mLock);
4506 mFUnitsConvFactor = GetAdjustedSize() / upem;
4510 #define SET_UNSIGNED(field, src) \
4511 metrics->field = uint16_t(src) * mFUnitsConvFactor
4512 #define SET_SIGNED(field, src) metrics->field = int16_t(src) * mFUnitsConvFactor
4514 gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag);
4515 if (os2Table && mFUnitsConvFactor >= 0.0) {
4516 const OS2Table* os2 =
4517 reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
4518 // These fields should always be present in any valid OS/2 table
4519 if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) {
4520 SET_SIGNED(strikeoutSize, os2->yStrikeoutSize);
4521 // Use ascent+descent from the horizontal metrics as the default
4522 // advance (aveCharWidth) in vertical mode
4523 gfxFloat ascentDescent =
4524 gfxFloat(mFUnitsConvFactor) *
4525 (int16_t(os2->sTypoAscender) - int16_t(os2->sTypoDescender));
4526 metrics->aveCharWidth = std::max(metrics->emHeight, ascentDescent);
4527 // Use xAvgCharWidth from horizontal metrics as minimum font extent
4528 // for vertical layout, applying half of it to ascent and half to
4529 // descent (to work with a default centered baseline).
4530 gfxFloat halfCharWidth =
4531 int16_t(os2->xAvgCharWidth) * gfxFloat(mFUnitsConvFactor) / 2;
4532 metrics->maxAscent = std::max(metrics->maxAscent, halfCharWidth);
4533 metrics->maxDescent = std::max(metrics->maxDescent, halfCharWidth);
4537 // If we didn't set aveCharWidth from OS/2, try to read 'hhea' metrics
4538 // and use the line height from its ascent/descent.
4539 if (!metrics->aveCharWidth) {
4540 gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag);
4541 if (hheaTable && mFUnitsConvFactor >= 0.0) {
4542 const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>(
4543 hb_blob_get_data(hheaTable, &len));
4544 if (len >= sizeof(MetricsHeader)) {
4545 SET_SIGNED(aveCharWidth,
4546 int16_t(hhea->ascender) - int16_t(hhea->descender));
4547 metrics->maxAscent = metrics->aveCharWidth / 2;
4548 metrics->maxDescent = metrics->aveCharWidth - metrics->maxAscent;
4553 // Read real vertical metrics if available.
4554 metrics->ideographicWidth = -1.0;
4555 metrics->zeroWidth = -1.0;
4556 gfxFontEntry::AutoTable vheaTable(mFontEntry, kVheaTableTag);
4557 if (vheaTable && mFUnitsConvFactor >= 0.0) {
4558 const MetricsHeader* vhea = reinterpret_cast<const MetricsHeader*>(
4559 hb_blob_get_data(vheaTable, &len));
4560 if (len >= sizeof(MetricsHeader)) {
4561 SET_UNSIGNED(maxAdvance, vhea->advanceWidthMax);
4562 // Redistribute space between ascent/descent because we want a
4563 // centered vertical baseline by default.
4564 gfxFloat halfExtent =
4565 0.5 * gfxFloat(mFUnitsConvFactor) *
4566 (int16_t(vhea->ascender) + std::abs(int16_t(vhea->descender)));
4567 // Some bogus fonts have ascent and descent set to zero in 'vhea'.
4568 // In that case we just ignore them and keep our synthetic values
4569 // from above.
4570 if (halfExtent > 0) {
4571 metrics->maxAscent = halfExtent;
4572 metrics->maxDescent = halfExtent;
4573 SET_SIGNED(externalLeading, vhea->lineGap);
4575 // Call gfxHarfBuzzShaper::GetGlyphVAdvance directly, as GetCharAdvance
4576 // would potentially recurse if no v-advance is available and it attempts
4577 // to fall back to a value from mVerticalMetrics.
4578 if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) {
4579 uint32_t gid = ProvidesGetGlyph()
4580 ? GetGlyph(kWaterIdeograph, 0)
4581 : shaper->GetNominalGlyph(kWaterIdeograph);
4582 if (gid) {
4583 int32_t advance = shaper->GetGlyphVAdvance(gid);
4584 // Convert 16.16 fixed-point advance from the shaper to a float.
4585 metrics->ideographicWidth =
4586 advance < 0 ? metrics->aveCharWidth : advance / 65536.0;
4588 gid = ProvidesGetGlyph() ? GetGlyph('0', 0)
4589 : shaper->GetNominalGlyph('0');
4590 if (gid) {
4591 int32_t advance = shaper->GetGlyphVAdvance(gid);
4592 metrics->zeroWidth =
4593 advance < 0 ? metrics->aveCharWidth : advance / 65536.0;
4599 // If we didn't set aveCharWidth above, we must be dealing with a non-sfnt
4600 // font of some kind (Type1, bitmap, vector, ...), so fall back to using
4601 // whatever the platform backend figured out for horizontal layout.
4602 // And if we haven't set externalLeading yet, then copy that from the
4603 // horizontal metrics as well, to help consistency of CSS line-height.
4604 if (!metrics->aveCharWidth ||
4605 metrics->externalLeading == UNINITIALIZED_LEADING) {
4606 const Metrics& horizMetrics = GetHorizontalMetrics();
4607 if (!metrics->aveCharWidth) {
4608 metrics->aveCharWidth = horizMetrics.maxAscent + horizMetrics.maxDescent;
4610 if (metrics->externalLeading == UNINITIALIZED_LEADING) {
4611 metrics->externalLeading = horizMetrics.externalLeading;
4615 // Get underline thickness from the 'post' table if available.
4616 // We also read the underline position, although in vertical-upright mode
4617 // this will not be appropriate to use directly (see nsTextFrame.cpp).
4618 gfxFontEntry::AutoTable postTable(mFontEntry, kPostTableTag);
4619 if (postTable) {
4620 const PostTable* post =
4621 reinterpret_cast<const PostTable*>(hb_blob_get_data(postTable, &len));
4622 if (len >= offsetof(PostTable, underlineThickness) + sizeof(uint16_t)) {
4623 static_assert(offsetof(PostTable, underlinePosition) <
4624 offsetof(PostTable, underlineThickness),
4625 "broken PostTable struct?");
4626 SET_SIGNED(underlineOffset, post->underlinePosition);
4627 SET_UNSIGNED(underlineSize, post->underlineThickness);
4628 // Also use for strikeout if we didn't find that in OS/2 above.
4629 if (!metrics->strikeoutSize) {
4630 metrics->strikeoutSize = metrics->underlineSize;
4635 #undef SET_UNSIGNED
4636 #undef SET_SIGNED
4638 // If we didn't read this from a vhea table, it will still be zero.
4639 // In any case, let's make sure it is not less than the value we've
4640 // come up with for aveCharWidth.
4641 metrics->maxAdvance = std::max(metrics->maxAdvance, metrics->aveCharWidth);
4643 // Thickness of underline and strikeout may have been read from tables,
4644 // but in case they were not present, ensure a minimum of 1 pixel.
4645 metrics->underlineSize = std::max(1.0, metrics->underlineSize);
4647 metrics->strikeoutSize = std::max(1.0, metrics->strikeoutSize);
4648 metrics->strikeoutOffset = -0.5 * metrics->strikeoutSize;
4650 // Somewhat arbitrary values for now, subject to future refinement...
4651 metrics->spaceWidth = metrics->aveCharWidth;
4652 metrics->maxHeight = metrics->maxAscent + metrics->maxDescent;
4653 metrics->xHeight = metrics->emHeight / 2;
4654 metrics->capHeight = metrics->maxAscent;
4656 if (metrics->zeroWidth < 0.0) {
4657 metrics->zeroWidth = metrics->aveCharWidth;
4660 if (!mVerticalMetrics.compareExchange(nullptr, metrics)) {
4661 delete metrics;
4665 gfxFloat gfxFont::SynthesizeSpaceWidth(uint32_t aCh) {
4666 // return an appropriate width for various Unicode space characters
4667 // that we "fake" if they're not actually present in the font;
4668 // returns negative value if the char is not a known space.
4669 switch (aCh) {
4670 case 0x2000: // en quad
4671 case 0x2002:
4672 return GetAdjustedSize() / 2; // en space
4673 case 0x2001: // em quad
4674 case 0x2003:
4675 return GetAdjustedSize(); // em space
4676 case 0x2004:
4677 return GetAdjustedSize() / 3; // three-per-em space
4678 case 0x2005:
4679 return GetAdjustedSize() / 4; // four-per-em space
4680 case 0x2006:
4681 return GetAdjustedSize() / 6; // six-per-em space
4682 case 0x2007:
4683 return GetMetrics(nsFontMetrics::eHorizontal)
4684 .ZeroOrAveCharWidth(); // figure space
4685 case 0x2008:
4686 return GetMetrics(nsFontMetrics::eHorizontal)
4687 .spaceWidth; // punctuation space
4688 case 0x2009:
4689 return GetAdjustedSize() / 5; // thin space
4690 case 0x200a:
4691 return GetAdjustedSize() / 10; // hair space
4692 case 0x202f:
4693 return GetAdjustedSize() / 5; // narrow no-break space
4694 case 0x3000:
4695 return GetAdjustedSize(); // ideographic space
4696 default:
4697 return -1.0;
4701 void gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
4702 FontCacheSizes* aSizes) const {
4703 AutoReadLock lock(mLock);
4704 for (uint32_t i = 0; i < mGlyphExtentsArray.Length(); ++i) {
4705 aSizes->mFontInstances +=
4706 mGlyphExtentsArray[i]->SizeOfIncludingThis(aMallocSizeOf);
4708 if (mWordCache) {
4709 aSizes->mShapedWords +=
4710 mWordCache->shallowSizeOfIncludingThis(aMallocSizeOf);
4711 for (auto it = mWordCache->iter(); !it.done(); it.next()) {
4712 aSizes->mShapedWords +=
4713 it.get().value()->SizeOfIncludingThis(aMallocSizeOf);
4718 void gfxFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
4719 FontCacheSizes* aSizes) const {
4720 aSizes->mFontInstances += aMallocSizeOf(this);
4721 AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
4724 void gfxFont::AddGlyphChangeObserver(GlyphChangeObserver* aObserver) {
4725 AutoWriteLock lock(mLock);
4726 if (!mGlyphChangeObservers) {
4727 mGlyphChangeObservers = MakeUnique<nsTHashSet<GlyphChangeObserver*>>();
4729 mGlyphChangeObservers->Insert(aObserver);
4732 void gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver* aObserver) {
4733 AutoWriteLock lock(mLock);
4734 NS_ASSERTION(mGlyphChangeObservers, "No observers registered");
4735 NS_ASSERTION(mGlyphChangeObservers->Contains(aObserver),
4736 "Observer not registered");
4737 mGlyphChangeObservers->Remove(aObserver);
4740 #define DEFAULT_PIXEL_FONT_SIZE 16.0f
4742 gfxFontStyle::gfxFontStyle()
4743 : size(DEFAULT_PIXEL_FONT_SIZE),
4744 sizeAdjust(0.0f),
4745 baselineOffset(0.0f),
4746 languageOverride{0},
4747 weight(FontWeight::NORMAL),
4748 stretch(FontStretch::NORMAL),
4749 style(FontSlantStyle::NORMAL),
4750 variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
4751 variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL),
4752 sizeAdjustBasis(uint8_t(FontSizeAdjust::Tag::None)),
4753 systemFont(true),
4754 printerFont(false),
4755 #ifdef XP_WIN
4756 allowForceGDIClassic(true),
4757 #endif
4758 useGrayscaleAntialiasing(false),
4759 allowSyntheticWeight(true),
4760 allowSyntheticStyle(true),
4761 allowSyntheticSmallCaps(true),
4762 useSyntheticPosition(true),
4763 noFallbackVariantFeatures(true) {
4766 gfxFontStyle::gfxFontStyle(FontSlantStyle aStyle, FontWeight aWeight,
4767 FontStretch aStretch, gfxFloat aSize,
4768 const FontSizeAdjust& aSizeAdjust, bool aSystemFont,
4769 bool aPrinterFont,
4770 #ifdef XP_WIN
4771 bool aAllowForceGDIClassic,
4772 #endif
4773 bool aAllowWeightSynthesis,
4774 bool aAllowStyleSynthesis,
4775 bool aAllowSmallCapsSynthesis,
4776 bool aUsePositionSynthesis,
4777 StyleFontLanguageOverride aLanguageOverride)
4778 : size(aSize),
4779 baselineOffset(0.0f),
4780 languageOverride(aLanguageOverride),
4781 weight(aWeight),
4782 stretch(aStretch),
4783 style(aStyle),
4784 variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
4785 variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL),
4786 systemFont(aSystemFont),
4787 printerFont(aPrinterFont),
4788 #ifdef XP_WIN
4789 allowForceGDIClassic(aAllowForceGDIClassic),
4790 #endif
4791 useGrayscaleAntialiasing(false),
4792 allowSyntheticWeight(aAllowWeightSynthesis),
4793 allowSyntheticStyle(aAllowStyleSynthesis),
4794 allowSyntheticSmallCaps(aAllowSmallCapsSynthesis),
4795 useSyntheticPosition(aUsePositionSynthesis),
4796 noFallbackVariantFeatures(true) {
4797 MOZ_ASSERT(!std::isnan(size));
4799 sizeAdjustBasis = uint8_t(aSizeAdjust.tag);
4800 // sizeAdjustBasis is currently a small bitfield, so let's assert that the
4801 // tag value was not truncated.
4802 MOZ_ASSERT(FontSizeAdjust::Tag(sizeAdjustBasis) == aSizeAdjust.tag,
4803 "gfxFontStyle.sizeAdjustBasis too small?");
4805 #define HANDLE_TAG(TAG) \
4806 case FontSizeAdjust::Tag::TAG: \
4807 sizeAdjust = aSizeAdjust.As##TAG(); \
4808 break;
4810 switch (aSizeAdjust.tag) {
4811 case FontSizeAdjust::Tag::None:
4812 sizeAdjust = 0.0f;
4813 break;
4814 HANDLE_TAG(ExHeight)
4815 HANDLE_TAG(CapHeight)
4816 HANDLE_TAG(ChWidth)
4817 HANDLE_TAG(IcWidth)
4818 HANDLE_TAG(IcHeight)
4821 #undef HANDLE_TAG
4823 MOZ_ASSERT(!std::isnan(sizeAdjust));
4825 if (weight > FontWeight::FromInt(1000)) {
4826 weight = FontWeight::FromInt(1000);
4828 if (weight < FontWeight::FromInt(1)) {
4829 weight = FontWeight::FromInt(1);
4832 if (size >= FONT_MAX_SIZE) {
4833 size = FONT_MAX_SIZE;
4834 sizeAdjust = 0.0f;
4835 sizeAdjustBasis = uint8_t(FontSizeAdjust::Tag::None);
4836 } else if (size < 0.0) {
4837 NS_WARNING("negative font size");
4838 size = 0.0;
4842 PLDHashNumber gfxFontStyle::Hash() const {
4843 uint32_t hash = variationSettings.IsEmpty()
4845 : mozilla::HashBytes(variationSettings.Elements(),
4846 variationSettings.Length() *
4847 sizeof(gfxFontVariation));
4848 return mozilla::AddToHash(hash, systemFont, style.Raw(), stretch.Raw(),
4849 weight.Raw(), size, int32_t(sizeAdjust * 1000.0f));
4852 void gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel) {
4853 MOZ_ASSERT(
4854 variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL && baselineOffset == 0,
4855 "can't adjust this style for sub/superscript");
4857 // calculate the baseline offset (before changing the size)
4858 if (variantSubSuper == NS_FONT_VARIANT_POSITION_SUPER) {
4859 baselineOffset = size * -NS_FONT_SUPERSCRIPT_OFFSET_RATIO;
4860 } else {
4861 baselineOffset = size * NS_FONT_SUBSCRIPT_OFFSET_RATIO;
4864 // calculate reduced size, roughly mimicing behavior of font-size: smaller
4865 float cssSize = size * aAppUnitsPerDevPixel / AppUnitsPerCSSPixel();
4866 if (cssSize < NS_FONT_SUB_SUPER_SMALL_SIZE) {
4867 size *= NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL;
4868 } else if (cssSize >= NS_FONT_SUB_SUPER_LARGE_SIZE) {
4869 size *= NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE;
4870 } else {
4871 gfxFloat t = (cssSize - NS_FONT_SUB_SUPER_SMALL_SIZE) /
4872 (NS_FONT_SUB_SUPER_LARGE_SIZE - NS_FONT_SUB_SUPER_SMALL_SIZE);
4873 size *= (1.0 - t) * NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL +
4874 t * NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE;
4877 // clear the variant field
4878 variantSubSuper = NS_FONT_VARIANT_POSITION_NORMAL;
4881 bool gfxFont::TryGetMathTable() {
4882 if (mMathInitialized) {
4883 return !!mMathTable;
4886 auto face(GetFontEntry()->GetHBFace());
4887 if (hb_ot_math_has_data(face)) {
4888 auto* mathTable = new gfxMathTable(face, GetAdjustedSize());
4889 if (!mMathTable.compareExchange(nullptr, mathTable)) {
4890 delete mathTable;
4893 mMathInitialized = true;
4895 return !!mMathTable;