Bug 1876335 - use GRADLE_MAVEN_REPOSITORIES in more places. r=owlish,geckoview-review...
[gecko.git] / gfx / thebes / gfxFont.cpp
blob618eb49455c86597786988795e8f900028b0fa97
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 // Characters treated as hyphens for the purpose of "emergency" breaking
733 // when the content would otherwise overflow.
734 auto isHyphen = [](char16_t c) {
735 return c == char16_t('-') || // HYPHEN-MINUS
736 c == 0x2010 || // HYPHEN
737 c == 0x2012 || // FIGURE DASH
738 c == 0x2013 || // EN DASH
739 c == 0x058A; // ARMENIAN HYPHEN
741 bool prevWasHyphen = false;
742 while (pos < aLength) {
743 const char16_t ch = aString[pos];
744 if (prevWasHyphen) {
745 if (nsContentUtils::IsAlphanumeric(ch)) {
746 glyphs[pos].SetCanBreakBefore(
747 CompressedGlyph::FLAG_BREAK_TYPE_EMERGENCY_WRAP);
749 prevWasHyphen = false;
751 if (ch == char16_t(' ') || ch == kIdeographicSpace) {
752 glyphs[pos].SetIsSpace();
753 } else if (isHyphen(ch) && pos &&
754 nsContentUtils::IsAlphanumeric(aString[pos - 1])) {
755 prevWasHyphen = true;
756 } else if (ch == kBengaliYa) {
757 // Unless we're at the start, check for a preceding virama.
758 if (pos > 0 && aString[pos - 1] == kBengaliVirama) {
759 glyphs[pos] = extendCluster;
762 // advance iter to the next cluster-start (or end of text)
763 const uint32_t nextPos = *iter.Next();
764 // step past the first char of the cluster
765 ++pos;
766 // mark all the rest as cluster-continuations
767 for (; pos < nextPos; ++pos) {
768 glyphs[pos] = extendCluster;
773 void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
774 const uint8_t* aString,
775 uint32_t aLength) {
776 CompressedGlyph* glyphs = GetCharacterGlyphs() + aOffset;
777 uint32_t pos = 0;
778 bool prevWasHyphen = false;
779 while (pos < aLength) {
780 uint8_t ch = aString[pos];
781 if (prevWasHyphen) {
782 if (nsContentUtils::IsAlphanumeric(ch)) {
783 glyphs->SetCanBreakBefore(
784 CompressedGlyph::FLAG_BREAK_TYPE_EMERGENCY_WRAP);
786 prevWasHyphen = false;
788 if (ch == uint8_t(' ')) {
789 glyphs->SetIsSpace();
790 } else if (ch == uint8_t('-') && pos &&
791 nsContentUtils::IsAlphanumeric(aString[pos - 1])) {
792 prevWasHyphen = true;
794 ++pos;
795 ++glyphs;
799 gfxShapedText::DetailedGlyph* gfxShapedText::AllocateDetailedGlyphs(
800 uint32_t aIndex, uint32_t aCount) {
801 NS_ASSERTION(aIndex < GetLength(), "Index out of range");
803 if (!mDetailedGlyphs) {
804 mDetailedGlyphs = MakeUnique<DetailedGlyphStore>();
807 return mDetailedGlyphs->Allocate(aIndex, aCount);
810 void gfxShapedText::SetDetailedGlyphs(uint32_t aIndex, uint32_t aGlyphCount,
811 const DetailedGlyph* aGlyphs) {
812 CompressedGlyph& g = GetCharacterGlyphs()[aIndex];
814 MOZ_ASSERT(aIndex > 0 || g.IsLigatureGroupStart(),
815 "First character can't be a ligature continuation!");
817 if (aGlyphCount > 0) {
818 DetailedGlyph* details = AllocateDetailedGlyphs(aIndex, aGlyphCount);
819 memcpy(details, aGlyphs, sizeof(DetailedGlyph) * aGlyphCount);
822 g.SetGlyphCount(aGlyphCount);
825 #define ZWNJ 0x200C
826 #define ZWJ 0x200D
827 static inline bool IsIgnorable(uint32_t aChar) {
828 return (IsDefaultIgnorable(aChar)) || aChar == ZWNJ || aChar == ZWJ;
831 void gfxShapedText::SetMissingGlyph(uint32_t aIndex, uint32_t aChar,
832 gfxFont* aFont) {
833 CompressedGlyph& g = GetCharacterGlyphs()[aIndex];
834 uint8_t category = GetGeneralCategory(aChar);
835 if (category >= HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK &&
836 category <= HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) {
837 g.SetComplex(false, true);
840 // Leaving advance as zero will prevent drawing the hexbox for ignorables.
841 int32_t advance = 0;
842 if (!IsIgnorable(aChar)) {
843 gfxFloat width =
844 std::max(aFont->GetMetrics(nsFontMetrics::eHorizontal).aveCharWidth,
845 gfxFloat(gfxFontMissingGlyphs::GetDesiredMinWidth(
846 aChar, mAppUnitsPerDevUnit)));
847 advance = int32_t(width * mAppUnitsPerDevUnit);
849 DetailedGlyph detail = {aChar, advance, gfx::Point()};
850 SetDetailedGlyphs(aIndex, 1, &detail);
851 g.SetMissing();
854 bool gfxShapedText::FilterIfIgnorable(uint32_t aIndex, uint32_t aCh) {
855 if (IsIgnorable(aCh)) {
856 // There are a few default-ignorables of Letter category (currently,
857 // just the Hangul filler characters) that we'd better not discard
858 // if they're followed by additional characters in the same cluster.
859 // Some fonts use them to carry the width of a whole cluster of
860 // combining jamos; see bug 1238243.
861 auto* charGlyphs = GetCharacterGlyphs();
862 if (GetGenCategory(aCh) == nsUGenCategory::kLetter &&
863 aIndex + 1 < GetLength() && !charGlyphs[aIndex + 1].IsClusterStart()) {
864 return false;
866 // A compressedGlyph that is set to MISSING but has no DetailedGlyphs list
867 // will be zero-width/invisible, which is what we want here.
868 CompressedGlyph& g = charGlyphs[aIndex];
869 g.SetComplex(g.IsClusterStart(), g.IsLigatureGroupStart()).SetMissing();
870 return true;
872 return false;
875 void gfxShapedText::ApplyTrackingToClusters(gfxFloat aTrackingAdjustment,
876 uint32_t aOffset,
877 uint32_t aLength) {
878 int32_t appUnitAdjustment =
879 NS_round(aTrackingAdjustment * gfxFloat(mAppUnitsPerDevUnit));
880 CompressedGlyph* charGlyphs = GetCharacterGlyphs();
881 for (uint32_t i = aOffset; i < aOffset + aLength; ++i) {
882 CompressedGlyph* glyphData = charGlyphs + i;
883 if (glyphData->IsSimpleGlyph()) {
884 // simple glyphs ==> just add the advance
885 int32_t advance = glyphData->GetSimpleAdvance();
886 if (advance > 0) {
887 advance = std::max(0, advance + appUnitAdjustment);
888 if (CompressedGlyph::IsSimpleAdvance(advance)) {
889 glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph());
890 } else {
891 // rare case, tested by making this the default
892 uint32_t glyphIndex = glyphData->GetSimpleGlyph();
893 // convert the simple CompressedGlyph to an empty complex record
894 glyphData->SetComplex(true, true);
895 // then set its details (glyph ID with its new advance)
896 DetailedGlyph detail = {glyphIndex, advance, gfx::Point()};
897 SetDetailedGlyphs(i, 1, &detail);
900 } else {
901 // complex glyphs ==> add offset at cluster/ligature boundaries
902 uint32_t detailedLength = glyphData->GetGlyphCount();
903 if (detailedLength) {
904 DetailedGlyph* details = GetDetailedGlyphs(i);
905 if (!details) {
906 continue;
908 auto& advance = IsRightToLeft() ? details[0].mAdvance
909 : details[detailedLength - 1].mAdvance;
910 if (advance > 0) {
911 advance = std::max(0, advance + appUnitAdjustment);
918 size_t gfxShapedWord::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
919 size_t total = aMallocSizeOf(this);
920 if (mDetailedGlyphs) {
921 total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf);
923 return total;
926 float gfxFont::AngleForSyntheticOblique() const {
927 // First check conditions that mean no synthetic slant should be used:
928 if (mStyle.style == FontSlantStyle::NORMAL) {
929 return 0.0f; // Requested style is 'normal'.
931 if (!mStyle.allowSyntheticStyle) {
932 return 0.0f; // Synthetic obliquing is disabled.
934 if (!mFontEntry->MayUseSyntheticSlant()) {
935 return 0.0f; // The resource supports "real" slant, so don't synthesize.
938 // If style calls for italic, and face doesn't support it, use default
939 // oblique angle as a simulation.
940 if (mStyle.style.IsItalic()) {
941 return mFontEntry->SupportsItalic()
942 ? 0.0f
943 : FontSlantStyle::DEFAULT_OBLIQUE_DEGREES;
946 // OK, we're going to use synthetic oblique: return the requested angle.
947 return mStyle.style.ObliqueAngle();
950 float gfxFont::SkewForSyntheticOblique() const {
951 // Precomputed value of tan(kDefaultAngle), the default italic/oblique slant;
952 // avoids calling tan() at runtime except for custom oblique values.
953 static const float kTanDefaultAngle =
954 tan(FontSlantStyle::DEFAULT_OBLIQUE_DEGREES * (M_PI / 180.0));
956 float angle = AngleForSyntheticOblique();
957 if (angle == 0.0f) {
958 return 0.0f;
959 } else if (angle == FontSlantStyle::DEFAULT_OBLIQUE_DEGREES) {
960 return kTanDefaultAngle;
961 } else {
962 return tan(angle * (M_PI / 180.0));
966 void gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther,
967 bool aOtherIsOnLeft) {
968 mAscent = std::max(mAscent, aOther.mAscent);
969 mDescent = std::max(mDescent, aOther.mDescent);
970 if (aOtherIsOnLeft) {
971 mBoundingBox = (mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0))
972 .Union(aOther.mBoundingBox);
973 } else {
974 mBoundingBox =
975 mBoundingBox.Union(aOther.mBoundingBox + gfxPoint(mAdvanceWidth, 0));
977 mAdvanceWidth += aOther.mAdvanceWidth;
980 gfxFont::gfxFont(const RefPtr<UnscaledFont>& aUnscaledFont,
981 gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle,
982 AntialiasOption anAAOption)
983 : mFontEntry(aFontEntry),
984 mLock("gfxFont lock"),
985 mUnscaledFont(aUnscaledFont),
986 mStyle(*aFontStyle),
987 mAdjustedSize(-1.0), // negative to indicate "not yet initialized"
988 mFUnitsConvFactor(-1.0f), // negative to indicate "not yet initialized"
989 mAntialiasOption(anAAOption),
990 mIsValid(true),
991 mApplySyntheticBold(false),
992 mKerningEnabled(false),
993 mMathInitialized(false) {
994 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
995 ++gFontCount;
996 #endif
998 if (MOZ_UNLIKELY(StaticPrefs::gfx_text_disable_aa_AtStartup())) {
999 mAntialiasOption = kAntialiasNone;
1002 // Turn off AA for Ahem for testing purposes when requested.
1003 if (MOZ_UNLIKELY(StaticPrefs::gfx_font_rendering_ahem_antialias_none() &&
1004 mFontEntry->FamilyName().EqualsLiteral("Ahem"))) {
1005 mAntialiasOption = kAntialiasNone;
1008 mKerningSet = HasFeatureSet(HB_TAG('k', 'e', 'r', 'n'), mKerningEnabled);
1011 gfxFont::~gfxFont() {
1012 mFontEntry->NotifyFontDestroyed(this);
1014 // Delete objects owned through atomic pointers. (Some of these may be null,
1015 // but that's OK.)
1016 delete mVerticalMetrics.exchange(nullptr);
1017 delete mHarfBuzzShaper.exchange(nullptr);
1018 delete mGraphiteShaper.exchange(nullptr);
1019 delete mMathTable.exchange(nullptr);
1020 delete mNonAAFont.exchange(nullptr);
1022 if (auto* scaledFont = mAzureScaledFont.exchange(nullptr)) {
1023 scaledFont->Release();
1026 if (mGlyphChangeObservers) {
1027 for (const auto& key : *mGlyphChangeObservers) {
1028 key->ForgetFont();
1033 // Work out whether cairo will snap inter-glyph spacing to pixels.
1035 // Layout does not align text to pixel boundaries, so, with font drawing
1036 // backends that snap glyph positions to pixels, it is important that
1037 // inter-glyph spacing within words is always an integer number of pixels.
1038 // This ensures that the drawing backend snaps all of the word's glyphs in the
1039 // same direction and so inter-glyph spacing remains the same.
1041 gfxFont::RoundingFlags gfxFont::GetRoundOffsetsToPixels(
1042 DrawTarget* aDrawTarget) {
1043 // Could do something fancy here for ScaleFactors of
1044 // AxisAlignedTransforms, but we leave things simple.
1045 // Not much point rounding if a matrix will mess things up anyway.
1046 // Also check if the font already knows hint metrics is off...
1047 if (aDrawTarget->GetTransform().HasNonTranslation() || !ShouldHintMetrics()) {
1048 return RoundingFlags(0);
1051 cairo_t* cr = static_cast<cairo_t*>(
1052 aDrawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT));
1053 if (cr) {
1054 cairo_surface_t* target = cairo_get_target(cr);
1056 // Check whether the cairo surface's font options hint metrics.
1057 cairo_font_options_t* fontOptions = cairo_font_options_create();
1058 cairo_surface_get_font_options(target, fontOptions);
1059 cairo_hint_metrics_t hintMetrics =
1060 cairo_font_options_get_hint_metrics(fontOptions);
1061 cairo_font_options_destroy(fontOptions);
1063 switch (hintMetrics) {
1064 case CAIRO_HINT_METRICS_OFF:
1065 return RoundingFlags(0);
1066 case CAIRO_HINT_METRICS_ON:
1067 return RoundingFlags::kRoundX | RoundingFlags::kRoundY;
1068 default:
1069 break;
1073 if (ShouldRoundXOffset(cr)) {
1074 return RoundingFlags::kRoundX | RoundingFlags::kRoundY;
1075 } else {
1076 return RoundingFlags::kRoundY;
1080 gfxHarfBuzzShaper* gfxFont::GetHarfBuzzShaper() {
1081 if (!mHarfBuzzShaper) {
1082 auto* shaper = new gfxHarfBuzzShaper(this);
1083 shaper->Initialize();
1084 if (!mHarfBuzzShaper.compareExchange(nullptr, shaper)) {
1085 delete shaper;
1088 gfxHarfBuzzShaper* shaper = mHarfBuzzShaper;
1089 return shaper->IsInitialized() ? shaper : nullptr;
1092 gfxFloat gfxFont::GetGlyphAdvance(uint16_t aGID, bool aVertical) {
1093 if (!aVertical && ProvidesGlyphWidths()) {
1094 return GetGlyphWidth(aGID) / 65536.0;
1096 if (mFUnitsConvFactor < 0.0f) {
1097 // Metrics haven't been initialized; lock while we do that.
1098 AutoWriteLock lock(mLock);
1099 if (mFUnitsConvFactor < 0.0f) {
1100 GetMetrics(nsFontMetrics::eHorizontal);
1103 NS_ASSERTION(mFUnitsConvFactor >= 0.0f,
1104 "missing font unit conversion factor");
1105 if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) {
1106 if (aVertical) {
1107 // Note that GetGlyphVAdvance may return -1 to indicate it was unable
1108 // to retrieve vertical metrics; in that case we fall back to the
1109 // aveCharWidth value as a default advance.
1110 int32_t advance = shaper->GetGlyphVAdvance(aGID);
1111 if (advance < 0) {
1112 return GetMetrics(nsFontMetrics::eVertical).aveCharWidth;
1114 return advance / 65536.0;
1116 return shaper->GetGlyphHAdvance(aGID) / 65536.0;
1118 return 0.0;
1121 gfxFloat gfxFont::GetCharAdvance(uint32_t aUnicode, bool aVertical) {
1122 uint32_t gid = 0;
1123 if (ProvidesGetGlyph()) {
1124 gid = GetGlyph(aUnicode, 0);
1125 } else {
1126 if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) {
1127 gid = shaper->GetNominalGlyph(aUnicode);
1130 if (!gid) {
1131 return -1.0;
1133 return GetGlyphAdvance(gid, aVertical);
1136 static void CollectLookupsByFeature(hb_face_t* aFace, hb_tag_t aTableTag,
1137 uint32_t aFeatureIndex,
1138 hb_set_t* aLookups) {
1139 uint32_t lookups[32];
1140 uint32_t i, len, offset;
1142 offset = 0;
1143 do {
1144 len = ArrayLength(lookups);
1145 hb_ot_layout_feature_get_lookups(aFace, aTableTag, aFeatureIndex, offset,
1146 &len, lookups);
1147 for (i = 0; i < len; i++) {
1148 hb_set_add(aLookups, lookups[i]);
1150 offset += len;
1151 } while (len == ArrayLength(lookups));
1154 static void CollectLookupsByLanguage(
1155 hb_face_t* aFace, hb_tag_t aTableTag,
1156 const nsTHashSet<uint32_t>& aSpecificFeatures, hb_set_t* aOtherLookups,
1157 hb_set_t* aSpecificFeatureLookups, uint32_t aScriptIndex,
1158 uint32_t aLangIndex) {
1159 uint32_t reqFeatureIndex;
1160 if (hb_ot_layout_language_get_required_feature_index(
1161 aFace, aTableTag, aScriptIndex, aLangIndex, &reqFeatureIndex)) {
1162 CollectLookupsByFeature(aFace, aTableTag, reqFeatureIndex, aOtherLookups);
1165 uint32_t featureIndexes[32];
1166 uint32_t i, len, offset;
1168 offset = 0;
1169 do {
1170 len = ArrayLength(featureIndexes);
1171 hb_ot_layout_language_get_feature_indexes(aFace, aTableTag, aScriptIndex,
1172 aLangIndex, offset, &len,
1173 featureIndexes);
1175 for (i = 0; i < len; i++) {
1176 uint32_t featureIndex = featureIndexes[i];
1178 // get the feature tag
1179 hb_tag_t featureTag;
1180 uint32_t tagLen = 1;
1181 hb_ot_layout_language_get_feature_tags(aFace, aTableTag, aScriptIndex,
1182 aLangIndex, offset + i, &tagLen,
1183 &featureTag);
1185 // collect lookups
1186 hb_set_t* lookups = aSpecificFeatures.Contains(featureTag)
1187 ? aSpecificFeatureLookups
1188 : aOtherLookups;
1189 CollectLookupsByFeature(aFace, aTableTag, featureIndex, lookups);
1191 offset += len;
1192 } while (len == ArrayLength(featureIndexes));
1195 static bool HasLookupRuleWithGlyphByScript(
1196 hb_face_t* aFace, hb_tag_t aTableTag, hb_tag_t aScriptTag,
1197 uint32_t aScriptIndex, uint16_t aGlyph,
1198 const nsTHashSet<uint32_t>& aDefaultFeatures,
1199 bool& aHasDefaultFeatureWithGlyph) {
1200 uint32_t numLangs, lang;
1201 hb_set_t* defaultFeatureLookups = hb_set_create();
1202 hb_set_t* nonDefaultFeatureLookups = hb_set_create();
1204 // default lang
1205 CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures,
1206 nonDefaultFeatureLookups, defaultFeatureLookups,
1207 aScriptIndex, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX);
1209 // iterate over langs
1210 numLangs = hb_ot_layout_script_get_language_tags(
1211 aFace, aTableTag, aScriptIndex, 0, nullptr, nullptr);
1212 for (lang = 0; lang < numLangs; lang++) {
1213 CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures,
1214 nonDefaultFeatureLookups, defaultFeatureLookups,
1215 aScriptIndex, lang);
1218 // look for the glyph among default feature lookups
1219 aHasDefaultFeatureWithGlyph = false;
1220 hb_set_t* glyphs = hb_set_create();
1221 hb_codepoint_t index = -1;
1222 while (hb_set_next(defaultFeatureLookups, &index)) {
1223 hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs,
1224 glyphs, nullptr);
1225 if (hb_set_has(glyphs, aGlyph)) {
1226 aHasDefaultFeatureWithGlyph = true;
1227 break;
1231 // look for the glyph among non-default feature lookups
1232 // if no default feature lookups contained spaces
1233 bool hasNonDefaultFeatureWithGlyph = false;
1234 if (!aHasDefaultFeatureWithGlyph) {
1235 hb_set_clear(glyphs);
1236 index = -1;
1237 while (hb_set_next(nonDefaultFeatureLookups, &index)) {
1238 hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs,
1239 glyphs, glyphs, nullptr);
1240 if (hb_set_has(glyphs, aGlyph)) {
1241 hasNonDefaultFeatureWithGlyph = true;
1242 break;
1247 hb_set_destroy(glyphs);
1248 hb_set_destroy(defaultFeatureLookups);
1249 hb_set_destroy(nonDefaultFeatureLookups);
1251 return aHasDefaultFeatureWithGlyph || hasNonDefaultFeatureWithGlyph;
1254 static void HasLookupRuleWithGlyph(hb_face_t* aFace, hb_tag_t aTableTag,
1255 bool& aHasGlyph, hb_tag_t aSpecificFeature,
1256 bool& aHasGlyphSpecific, uint16_t aGlyph) {
1257 // iterate over the scripts in the font
1258 uint32_t numScripts, numLangs, script, lang;
1259 hb_set_t* otherLookups = hb_set_create();
1260 hb_set_t* specificFeatureLookups = hb_set_create();
1261 nsTHashSet<uint32_t> specificFeature(1);
1263 specificFeature.Insert(aSpecificFeature);
1265 numScripts =
1266 hb_ot_layout_table_get_script_tags(aFace, aTableTag, 0, nullptr, nullptr);
1268 for (script = 0; script < numScripts; script++) {
1269 // default lang
1270 CollectLookupsByLanguage(aFace, aTableTag, specificFeature, otherLookups,
1271 specificFeatureLookups, script,
1272 HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX);
1274 // iterate over langs
1275 numLangs = hb_ot_layout_script_get_language_tags(
1276 aFace, HB_OT_TAG_GPOS, script, 0, nullptr, nullptr);
1277 for (lang = 0; lang < numLangs; lang++) {
1278 CollectLookupsByLanguage(aFace, aTableTag, specificFeature, otherLookups,
1279 specificFeatureLookups, script, lang);
1283 // look for the glyph among non-specific feature lookups
1284 hb_set_t* glyphs = hb_set_create();
1285 hb_codepoint_t index = -1;
1286 while (hb_set_next(otherLookups, &index)) {
1287 hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs,
1288 glyphs, nullptr);
1289 if (hb_set_has(glyphs, aGlyph)) {
1290 aHasGlyph = true;
1291 break;
1295 // look for the glyph among specific feature lookups
1296 hb_set_clear(glyphs);
1297 index = -1;
1298 while (hb_set_next(specificFeatureLookups, &index)) {
1299 hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs,
1300 glyphs, nullptr);
1301 if (hb_set_has(glyphs, aGlyph)) {
1302 aHasGlyphSpecific = true;
1303 break;
1307 hb_set_destroy(glyphs);
1308 hb_set_destroy(specificFeatureLookups);
1309 hb_set_destroy(otherLookups);
1312 Atomic<nsTHashMap<nsUint32HashKey, intl::Script>*> gfxFont::sScriptTagToCode;
1313 Atomic<nsTHashSet<uint32_t>*> gfxFont::sDefaultFeatures;
1315 static inline bool HasSubstitution(uint32_t* aBitVector, intl::Script aScript) {
1316 return (aBitVector[static_cast<uint32_t>(aScript) >> 5] &
1317 (1 << (static_cast<uint32_t>(aScript) & 0x1f))) != 0;
1320 // union of all default substitution features across scripts
1321 static const hb_tag_t defaultFeatures[] = {
1322 HB_TAG('a', 'b', 'v', 'f'), HB_TAG('a', 'b', 'v', 's'),
1323 HB_TAG('a', 'k', 'h', 'n'), HB_TAG('b', 'l', 'w', 'f'),
1324 HB_TAG('b', 'l', 'w', 's'), HB_TAG('c', 'a', 'l', 't'),
1325 HB_TAG('c', 'c', 'm', 'p'), HB_TAG('c', 'f', 'a', 'r'),
1326 HB_TAG('c', 'j', 'c', 't'), HB_TAG('c', 'l', 'i', 'g'),
1327 HB_TAG('f', 'i', 'n', '2'), HB_TAG('f', 'i', 'n', '3'),
1328 HB_TAG('f', 'i', 'n', 'a'), HB_TAG('h', 'a', 'l', 'f'),
1329 HB_TAG('h', 'a', 'l', 'n'), HB_TAG('i', 'n', 'i', 't'),
1330 HB_TAG('i', 's', 'o', 'l'), HB_TAG('l', 'i', 'g', 'a'),
1331 HB_TAG('l', 'j', 'm', 'o'), HB_TAG('l', 'o', 'c', 'l'),
1332 HB_TAG('l', 't', 'r', 'a'), HB_TAG('l', 't', 'r', 'm'),
1333 HB_TAG('m', 'e', 'd', '2'), HB_TAG('m', 'e', 'd', 'i'),
1334 HB_TAG('m', 's', 'e', 't'), HB_TAG('n', 'u', 'k', 't'),
1335 HB_TAG('p', 'r', 'e', 'f'), HB_TAG('p', 'r', 'e', 's'),
1336 HB_TAG('p', 's', 't', 'f'), HB_TAG('p', 's', 't', 's'),
1337 HB_TAG('r', 'c', 'l', 't'), HB_TAG('r', 'l', 'i', 'g'),
1338 HB_TAG('r', 'k', 'r', 'f'), HB_TAG('r', 'p', 'h', 'f'),
1339 HB_TAG('r', 't', 'l', 'a'), HB_TAG('r', 't', 'l', 'm'),
1340 HB_TAG('t', 'j', 'm', 'o'), HB_TAG('v', 'a', 't', 'u'),
1341 HB_TAG('v', 'e', 'r', 't'), HB_TAG('v', 'j', 'm', 'o')};
1343 void gfxFont::CheckForFeaturesInvolvingSpace() const {
1344 gfxFontEntry::SpaceFeatures flags = gfxFontEntry::SpaceFeatures::None;
1346 auto setFlags =
1347 MakeScopeExit([&]() { mFontEntry->mHasSpaceFeatures = flags; });
1349 bool log = LOG_FONTINIT_ENABLED();
1350 TimeStamp start;
1351 if (MOZ_UNLIKELY(log)) {
1352 start = TimeStamp::Now();
1355 uint32_t spaceGlyph = GetSpaceGlyph();
1356 if (!spaceGlyph) {
1357 return;
1360 auto face(GetFontEntry()->GetHBFace());
1362 // GSUB lookups - examine per script
1363 if (hb_ot_layout_has_substitution(face)) {
1364 // Get the script ==> code hashtable, creating it on first use.
1365 nsTHashMap<nsUint32HashKey, Script>* tagToCode = sScriptTagToCode;
1366 if (!tagToCode) {
1367 tagToCode = new nsTHashMap<nsUint32HashKey, Script>(
1368 size_t(Script::NUM_SCRIPT_CODES));
1369 tagToCode->InsertOrUpdate(HB_TAG('D', 'F', 'L', 'T'), Script::COMMON);
1370 // Ensure that we don't try to look at script codes beyond what the
1371 // current version of ICU (at runtime -- in case of system ICU)
1372 // knows about.
1373 Script scriptCount = Script(
1374 std::min<int>(intl::UnicodeProperties::GetMaxNumberOfScripts() + 1,
1375 int(Script::NUM_SCRIPT_CODES)));
1376 for (Script s = Script::ARABIC; s < scriptCount;
1377 s = Script(static_cast<int>(s) + 1)) {
1378 hb_script_t script = hb_script_t(GetScriptTagForCode(s));
1379 unsigned int scriptCount = 4;
1380 hb_tag_t scriptTags[4];
1381 hb_ot_tags_from_script_and_language(script, HB_LANGUAGE_INVALID,
1382 &scriptCount, scriptTags, nullptr,
1383 nullptr);
1384 for (unsigned int i = 0; i < scriptCount; i++) {
1385 tagToCode->InsertOrUpdate(scriptTags[i], s);
1388 if (!sScriptTagToCode.compareExchange(nullptr, tagToCode)) {
1389 // We lost a race! Discard our new table and use the winner.
1390 delete tagToCode;
1391 tagToCode = sScriptTagToCode;
1395 // Set up the default-features hashset on first use.
1396 if (!sDefaultFeatures) {
1397 uint32_t numDefaultFeatures = ArrayLength(defaultFeatures);
1398 auto* set = new nsTHashSet<uint32_t>(numDefaultFeatures);
1399 for (uint32_t i = 0; i < numDefaultFeatures; i++) {
1400 set->Insert(defaultFeatures[i]);
1402 if (!sDefaultFeatures.compareExchange(nullptr, set)) {
1403 delete set;
1407 // iterate over the scripts in the font
1408 hb_tag_t scriptTags[8];
1410 uint32_t len, offset = 0;
1411 do {
1412 len = ArrayLength(scriptTags);
1413 hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, offset, &len,
1414 scriptTags);
1415 for (uint32_t i = 0; i < len; i++) {
1416 bool isDefaultFeature = false;
1417 Script s;
1418 if (!HasLookupRuleWithGlyphByScript(
1419 face, HB_OT_TAG_GSUB, scriptTags[i], offset + i, spaceGlyph,
1420 *sDefaultFeatures, isDefaultFeature) ||
1421 !tagToCode->Get(scriptTags[i], &s)) {
1422 continue;
1424 flags = flags | gfxFontEntry::SpaceFeatures::HasFeatures;
1425 uint32_t index = static_cast<uint32_t>(s) >> 5;
1426 uint32_t bit = static_cast<uint32_t>(s) & 0x1f;
1427 if (isDefaultFeature) {
1428 mFontEntry->mDefaultSubSpaceFeatures[index] |= (1 << bit);
1429 } else {
1430 mFontEntry->mNonDefaultSubSpaceFeatures[index] |= (1 << bit);
1433 offset += len;
1434 } while (len == ArrayLength(scriptTags));
1437 // spaces in default features of default script?
1438 // ==> can't use word cache, skip GPOS analysis
1439 bool canUseWordCache = true;
1440 if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, Script::COMMON)) {
1441 canUseWordCache = false;
1444 // GPOS lookups - distinguish kerning from non-kerning features
1445 if (canUseWordCache && hb_ot_layout_has_positioning(face)) {
1446 bool hasKerning = false, hasNonKerning = false;
1447 HasLookupRuleWithGlyph(face, HB_OT_TAG_GPOS, hasNonKerning,
1448 HB_TAG('k', 'e', 'r', 'n'), hasKerning, spaceGlyph);
1449 if (hasKerning) {
1450 flags |= gfxFontEntry::SpaceFeatures::HasFeatures |
1451 gfxFontEntry::SpaceFeatures::Kerning;
1453 if (hasNonKerning) {
1454 flags |= gfxFontEntry::SpaceFeatures::HasFeatures |
1455 gfxFontEntry::SpaceFeatures::NonKerning;
1459 if (MOZ_UNLIKELY(log)) {
1460 TimeDuration elapsed = TimeStamp::Now() - start;
1461 LOG_FONTINIT((
1462 "(fontinit-spacelookups) font: %s - "
1463 "subst default: %8.8x %8.8x %8.8x %8.8x "
1464 "subst non-default: %8.8x %8.8x %8.8x %8.8x "
1465 "kerning: %s non-kerning: %s time: %6.3f\n",
1466 mFontEntry->Name().get(), mFontEntry->mDefaultSubSpaceFeatures[3],
1467 mFontEntry->mDefaultSubSpaceFeatures[2],
1468 mFontEntry->mDefaultSubSpaceFeatures[1],
1469 mFontEntry->mDefaultSubSpaceFeatures[0],
1470 mFontEntry->mNonDefaultSubSpaceFeatures[3],
1471 mFontEntry->mNonDefaultSubSpaceFeatures[2],
1472 mFontEntry->mNonDefaultSubSpaceFeatures[1],
1473 mFontEntry->mNonDefaultSubSpaceFeatures[0],
1474 (mFontEntry->mHasSpaceFeatures & gfxFontEntry::SpaceFeatures::Kerning
1475 ? "true"
1476 : "false"),
1477 (mFontEntry->mHasSpaceFeatures & gfxFontEntry::SpaceFeatures::NonKerning
1478 ? "true"
1479 : "false"),
1480 elapsed.ToMilliseconds()));
1484 bool gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript) const {
1485 NS_ASSERTION(GetFontEntry()->mHasSpaceFeatures !=
1486 gfxFontEntry::SpaceFeatures::Uninitialized,
1487 "need to initialize space lookup flags");
1488 NS_ASSERTION(aRunScript < Script::NUM_SCRIPT_CODES, "weird script code");
1489 if (aRunScript == Script::INVALID || aRunScript >= Script::NUM_SCRIPT_CODES) {
1490 return false;
1493 // default features have space lookups ==> true
1494 if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, Script::COMMON) ||
1495 HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, aRunScript)) {
1496 return true;
1499 // non-default features have space lookups and some type of
1500 // font feature, in font or style is specified ==> true
1501 if ((HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures,
1502 Script::COMMON) ||
1503 HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures, aRunScript)) &&
1504 (!mStyle.featureSettings.IsEmpty() ||
1505 !mFontEntry->mFeatureSettings.IsEmpty())) {
1506 return true;
1509 return false;
1512 tainted_boolean_hint gfxFont::SpaceMayParticipateInShaping(
1513 Script aRunScript) const {
1514 // avoid checking fonts known not to include default space-dependent features
1515 if (MOZ_UNLIKELY(mFontEntry->mSkipDefaultFeatureSpaceCheck)) {
1516 if (!mKerningSet && mStyle.featureSettings.IsEmpty() &&
1517 mFontEntry->mFeatureSettings.IsEmpty()) {
1518 return false;
1522 if (FontCanSupportGraphite()) {
1523 if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1524 return mFontEntry->HasGraphiteSpaceContextuals();
1528 // We record the presence of space-dependent features in the font entry
1529 // so that subsequent instantiations for the same font face won't
1530 // require us to re-check the tables; however, the actual check is done
1531 // by gfxFont because not all font entry subclasses know how to create
1532 // a harfbuzz face for introspection.
1533 gfxFontEntry::SpaceFeatures flags = mFontEntry->mHasSpaceFeatures;
1534 if (flags == gfxFontEntry::SpaceFeatures::Uninitialized) {
1535 CheckForFeaturesInvolvingSpace();
1536 flags = mFontEntry->mHasSpaceFeatures;
1539 if (!(flags & gfxFontEntry::SpaceFeatures::HasFeatures)) {
1540 return false;
1543 // if font has substitution rules or non-kerning positioning rules
1544 // that involve spaces, bypass
1545 if (HasSubstitutionRulesWithSpaceLookups(aRunScript) ||
1546 (flags & gfxFontEntry::SpaceFeatures::NonKerning)) {
1547 return true;
1550 // if kerning explicitly enabled/disabled via font-feature-settings or
1551 // font-kerning and kerning rules use spaces, only bypass when enabled
1552 if (mKerningSet && (flags & gfxFontEntry::SpaceFeatures::Kerning)) {
1553 return mKerningEnabled;
1556 return false;
1559 bool gfxFont::SupportsFeature(Script aScript, uint32_t aFeatureTag) {
1560 if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1561 return GetFontEntry()->SupportsGraphiteFeature(aFeatureTag);
1563 return GetFontEntry()->SupportsOpenTypeFeature(aScript, aFeatureTag);
1566 bool gfxFont::SupportsVariantCaps(Script aScript, uint32_t aVariantCaps,
1567 bool& aFallbackToSmallCaps,
1568 bool& aSyntheticLowerToSmallCaps,
1569 bool& aSyntheticUpperToSmallCaps) {
1570 bool ok = true; // cases without fallback are fine
1571 aFallbackToSmallCaps = false;
1572 aSyntheticLowerToSmallCaps = false;
1573 aSyntheticUpperToSmallCaps = false;
1574 switch (aVariantCaps) {
1575 case NS_FONT_VARIANT_CAPS_SMALLCAPS:
1576 ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p'));
1577 if (!ok) {
1578 aSyntheticLowerToSmallCaps = true;
1580 break;
1581 case NS_FONT_VARIANT_CAPS_ALLSMALL:
1582 ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')) &&
1583 SupportsFeature(aScript, HB_TAG('c', '2', 's', 'c'));
1584 if (!ok) {
1585 aSyntheticLowerToSmallCaps = true;
1586 aSyntheticUpperToSmallCaps = true;
1588 break;
1589 case NS_FONT_VARIANT_CAPS_PETITECAPS:
1590 ok = SupportsFeature(aScript, HB_TAG('p', 'c', 'a', 'p'));
1591 if (!ok) {
1592 ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p'));
1593 aFallbackToSmallCaps = ok;
1595 if (!ok) {
1596 aSyntheticLowerToSmallCaps = true;
1598 break;
1599 case NS_FONT_VARIANT_CAPS_ALLPETITE:
1600 ok = SupportsFeature(aScript, HB_TAG('p', 'c', 'a', 'p')) &&
1601 SupportsFeature(aScript, HB_TAG('c', '2', 'p', 'c'));
1602 if (!ok) {
1603 ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')) &&
1604 SupportsFeature(aScript, HB_TAG('c', '2', 's', 'c'));
1605 aFallbackToSmallCaps = ok;
1607 if (!ok) {
1608 aSyntheticLowerToSmallCaps = true;
1609 aSyntheticUpperToSmallCaps = true;
1611 break;
1612 default:
1613 break;
1616 NS_ASSERTION(
1617 !(ok && (aSyntheticLowerToSmallCaps || aSyntheticUpperToSmallCaps)),
1618 "shouldn't use synthetic features if we found real ones");
1620 NS_ASSERTION(!(!ok && aFallbackToSmallCaps),
1621 "if we found a usable fallback, that counts as ok");
1623 return ok;
1626 bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript,
1627 const uint8_t* aString, uint32_t aLength,
1628 Script aRunScript) {
1629 NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aString),
1630 aLength);
1631 return SupportsSubSuperscript(aSubSuperscript, unicodeString.get(), aLength,
1632 aRunScript);
1635 bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript,
1636 const char16_t* aString, uint32_t aLength,
1637 Script aRunScript) {
1638 NS_ASSERTION(aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER ||
1639 aSubSuperscript == NS_FONT_VARIANT_POSITION_SUB,
1640 "unknown value of font-variant-position");
1642 uint32_t feature = aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER
1643 ? HB_TAG('s', 'u', 'p', 's')
1644 : HB_TAG('s', 'u', 'b', 's');
1646 if (!SupportsFeature(aRunScript, feature)) {
1647 return false;
1650 // xxx - for graphite, don't really know how to sniff lookups so bail
1651 if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1652 return true;
1655 gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper();
1656 if (!shaper) {
1657 return false;
1660 // get the hbset containing input glyphs for the feature
1661 const hb_set_t* inputGlyphs =
1662 mFontEntry->InputsForOpenTypeFeature(aRunScript, feature);
1664 // create an hbset containing default glyphs for the script run
1665 hb_set_t* defaultGlyphsInRun = hb_set_create();
1667 // for each character, get the glyph id
1668 for (uint32_t i = 0; i < aLength; i++) {
1669 uint32_t ch = aString[i];
1671 if (i + 1 < aLength && NS_IS_SURROGATE_PAIR(ch, aString[i + 1])) {
1672 i++;
1673 ch = SURROGATE_TO_UCS4(ch, aString[i]);
1676 hb_codepoint_t gid = shaper->GetNominalGlyph(ch);
1677 hb_set_add(defaultGlyphsInRun, gid);
1680 // intersect with input glyphs, if size is not the same ==> fallback
1681 uint32_t origSize = hb_set_get_population(defaultGlyphsInRun);
1682 hb_set_intersect(defaultGlyphsInRun, inputGlyphs);
1683 uint32_t intersectionSize = hb_set_get_population(defaultGlyphsInRun);
1684 hb_set_destroy(defaultGlyphsInRun);
1686 return origSize == intersectionSize;
1689 bool gfxFont::FeatureWillHandleChar(Script aRunScript, uint32_t aFeature,
1690 uint32_t aUnicode) {
1691 if (!SupportsFeature(aRunScript, aFeature)) {
1692 return false;
1695 // xxx - for graphite, don't really know how to sniff lookups so bail
1696 if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1697 return true;
1700 if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) {
1701 // get the hbset containing input glyphs for the feature
1702 const hb_set_t* inputGlyphs =
1703 mFontEntry->InputsForOpenTypeFeature(aRunScript, aFeature);
1705 hb_codepoint_t gid = shaper->GetNominalGlyph(aUnicode);
1706 return hb_set_has(inputGlyphs, gid);
1709 return false;
1712 bool gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn) {
1713 aFeatureOn = false;
1715 if (mStyle.featureSettings.IsEmpty() &&
1716 GetFontEntry()->mFeatureSettings.IsEmpty()) {
1717 return false;
1720 // add feature values from font
1721 bool featureSet = false;
1722 uint32_t i, count;
1724 nsTArray<gfxFontFeature>& fontFeatures = GetFontEntry()->mFeatureSettings;
1725 count = fontFeatures.Length();
1726 for (i = 0; i < count; i++) {
1727 const gfxFontFeature& feature = fontFeatures.ElementAt(i);
1728 if (feature.mTag == aFeature) {
1729 featureSet = true;
1730 aFeatureOn = (feature.mValue != 0);
1734 // add feature values from style rules
1735 nsTArray<gfxFontFeature>& styleFeatures = mStyle.featureSettings;
1736 count = styleFeatures.Length();
1737 for (i = 0; i < count; i++) {
1738 const gfxFontFeature& feature = styleFeatures.ElementAt(i);
1739 if (feature.mTag == aFeature) {
1740 featureSet = true;
1741 aFeatureOn = (feature.mValue != 0);
1745 return featureSet;
1748 already_AddRefed<mozilla::gfx::ScaledFont> gfxFont::GetScaledFont(
1749 mozilla::gfx::DrawTarget* aDrawTarget) {
1750 mozilla::gfx::PaletteCache dummy;
1751 TextRunDrawParams params(dummy);
1752 return GetScaledFont(params);
1755 void gfxFont::InitializeScaledFont(
1756 const RefPtr<mozilla::gfx::ScaledFont>& aScaledFont) {
1757 if (!aScaledFont) {
1758 return;
1761 float angle = AngleForSyntheticOblique();
1762 if (angle != 0.0f) {
1763 aScaledFont->SetSyntheticObliqueAngle(angle);
1768 * A helper function in case we need to do any rounding or other
1769 * processing here.
1771 #define ToDeviceUnits(aAppUnits, aDevUnitsPerAppUnit) \
1772 (double(aAppUnits) * double(aDevUnitsPerAppUnit))
1774 static AntialiasMode Get2DAAMode(gfxFont::AntialiasOption aAAOption) {
1775 switch (aAAOption) {
1776 case gfxFont::kAntialiasSubpixel:
1777 return AntialiasMode::SUBPIXEL;
1778 case gfxFont::kAntialiasGrayscale:
1779 return AntialiasMode::GRAY;
1780 case gfxFont::kAntialiasNone:
1781 return AntialiasMode::NONE;
1782 default:
1783 return AntialiasMode::DEFAULT;
1787 class GlyphBufferAzure {
1788 #define AUTO_BUFFER_SIZE (2048 / sizeof(Glyph))
1790 typedef mozilla::image::imgDrawingParams imgDrawingParams;
1792 public:
1793 GlyphBufferAzure(const TextRunDrawParams& aRunParams,
1794 const FontDrawParams& aFontParams)
1795 : mRunParams(aRunParams),
1796 mFontParams(aFontParams),
1797 mBuffer(*mAutoBuffer.addr()),
1798 mBufSize(AUTO_BUFFER_SIZE),
1799 mCapacity(0),
1800 mNumGlyphs(0) {}
1802 ~GlyphBufferAzure() {
1803 if (mNumGlyphs > 0) {
1804 FlushGlyphs();
1807 if (mBuffer != *mAutoBuffer.addr()) {
1808 free(mBuffer);
1812 // Ensure the buffer has enough space for aGlyphCount glyphs to be added,
1813 // considering the supplied strike multipler aStrikeCount.
1814 // This MUST be called before OutputGlyph is used to actually store glyph
1815 // records in the buffer. It may be called repeated to add further capacity
1816 // in case we don't know up-front exactly what will be needed.
1817 void AddCapacity(uint32_t aGlyphCount, uint32_t aStrikeCount) {
1818 // Calculate the new capacity and ensure it will fit within the maximum
1819 // allowed capacity.
1820 static const uint64_t kMaxCapacity = 64 * 1024;
1821 mCapacity = uint32_t(std::min(
1822 kMaxCapacity,
1823 uint64_t(mCapacity) + uint64_t(aGlyphCount) * uint64_t(aStrikeCount)));
1824 // See if the required capacity fits within the already-allocated space
1825 if (mCapacity <= mBufSize) {
1826 return;
1828 // We need to grow the buffer: determine a new size, allocate, and
1829 // copy the existing data over if we didn't use realloc (which would
1830 // do it automatically).
1831 mBufSize = std::max(mCapacity, mBufSize * 2);
1832 if (mBuffer == *mAutoBuffer.addr()) {
1833 // switching from autobuffer to malloc, so we need to copy
1834 mBuffer = reinterpret_cast<Glyph*>(moz_xmalloc(mBufSize * sizeof(Glyph)));
1835 std::memcpy(mBuffer, *mAutoBuffer.addr(), mNumGlyphs * sizeof(Glyph));
1836 } else {
1837 mBuffer = reinterpret_cast<Glyph*>(
1838 moz_xrealloc(mBuffer, mBufSize * sizeof(Glyph)));
1842 void OutputGlyph(uint32_t aGlyphID, const gfx::Point& aPt) {
1843 // If the buffer is full, flush to make room for the new glyph.
1844 if (mNumGlyphs >= mCapacity) {
1845 // Check that AddCapacity has been used appropriately!
1846 MOZ_ASSERT(mCapacity > 0 && mNumGlyphs == mCapacity);
1847 Flush();
1849 Glyph* glyph = mBuffer + mNumGlyphs++;
1850 glyph->mIndex = aGlyphID;
1851 glyph->mPosition = aPt;
1854 void Flush() {
1855 if (mNumGlyphs > 0) {
1856 FlushGlyphs();
1857 mNumGlyphs = 0;
1861 const TextRunDrawParams& mRunParams;
1862 const FontDrawParams& mFontParams;
1864 private:
1865 static DrawMode GetStrokeMode(DrawMode aMode) {
1866 return aMode & (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH);
1869 // Render the buffered glyphs to the draw target.
1870 void FlushGlyphs() {
1871 gfx::GlyphBuffer buf;
1872 buf.mGlyphs = mBuffer;
1873 buf.mNumGlyphs = mNumGlyphs;
1875 const gfxContext::AzureState& state = mRunParams.context->CurrentState();
1877 // Draw stroke first if the UNDERNEATH flag is set in drawMode.
1878 if (mRunParams.strokeOpts &&
1879 GetStrokeMode(mRunParams.drawMode) ==
1880 (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH)) {
1881 DrawStroke(state, buf);
1884 if (mRunParams.drawMode & DrawMode::GLYPH_FILL) {
1885 if (state.pattern || mFontParams.contextPaint) {
1886 Pattern* pat;
1888 RefPtr<gfxPattern> fillPattern;
1889 if (mFontParams.contextPaint) {
1890 imgDrawingParams imgParams;
1891 fillPattern = mFontParams.contextPaint->GetFillPattern(
1892 mRunParams.context->GetDrawTarget(),
1893 mRunParams.context->CurrentMatrixDouble(), imgParams);
1895 if (!fillPattern) {
1896 if (state.pattern) {
1897 RefPtr<gfxPattern> statePattern =
1898 mRunParams.context->CurrentState().pattern;
1899 pat = statePattern->GetPattern(mRunParams.dt,
1900 state.patternTransformChanged
1901 ? &state.patternTransform
1902 : nullptr);
1903 } else {
1904 pat = nullptr;
1906 } else {
1907 pat = fillPattern->GetPattern(mRunParams.dt);
1910 if (pat) {
1911 mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf, *pat,
1912 mFontParams.drawOptions);
1914 } else {
1915 mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
1916 ColorPattern(state.color),
1917 mFontParams.drawOptions);
1921 // Draw stroke if the UNDERNEATH flag is not set.
1922 if (mRunParams.strokeOpts &&
1923 GetStrokeMode(mRunParams.drawMode) == DrawMode::GLYPH_STROKE) {
1924 DrawStroke(state, buf);
1927 if (mRunParams.drawMode & DrawMode::GLYPH_PATH) {
1928 mRunParams.context->EnsurePathBuilder();
1929 Matrix mat = mRunParams.dt->GetTransform();
1930 mFontParams.scaledFont->CopyGlyphsToBuilder(
1931 buf, mRunParams.context->mPathBuilder, &mat);
1935 void DrawStroke(const gfxContext::AzureState& aState,
1936 gfx::GlyphBuffer& aBuffer) {
1937 if (mRunParams.textStrokePattern) {
1938 Pattern* pat = mRunParams.textStrokePattern->GetPattern(
1939 mRunParams.dt,
1940 aState.patternTransformChanged ? &aState.patternTransform : nullptr);
1942 if (pat) {
1943 FlushStroke(aBuffer, *pat);
1945 } else {
1946 FlushStroke(aBuffer,
1947 ColorPattern(ToDeviceColor(mRunParams.textStrokeColor)));
1951 void FlushStroke(gfx::GlyphBuffer& aBuf, const Pattern& aPattern) {
1952 mRunParams.dt->StrokeGlyphs(mFontParams.scaledFont, aBuf, aPattern,
1953 *mRunParams.strokeOpts,
1954 mFontParams.drawOptions);
1957 // We use an "inline" buffer automatically allocated (on the stack) as part
1958 // of the GlyphBufferAzure object to hold the glyphs in most cases, falling
1959 // back to a separately-allocated heap buffer if the count of buffered
1960 // glyphs gets too big.
1962 // This is basically a rudimentary AutoTArray; so why not use AutoTArray
1963 // itself?
1965 // If we used an AutoTArray, we'd want to avoid using SetLength or
1966 // AppendElements to allocate the space we actually need, because those
1967 // methods would default-construct the new elements.
1969 // Could we use SetCapacity to reserve the necessary buffer space without
1970 // default-constructing all the Glyph records? No, because of a failure
1971 // that could occur when we need to grow the buffer, which happens when we
1972 // encounter a DetailedGlyph in the textrun that refers to a sequence of
1973 // several real glyphs. At that point, we need to add some extra capacity
1974 // to the buffer we initially allocated based on the length of the textrun
1975 // range we're rendering.
1977 // This buffer growth would work fine as long as it still fits within the
1978 // array's inline buffer (we just use a bit more of it), or if the buffer
1979 // was already heap-allocated (in which case AutoTArray will use realloc(),
1980 // preserving its contents). But a problem will arise when the initial
1981 // capacity we allocated (based on the length of the run) fits within the
1982 // array's inline buffer, but subsequently we need to extend the buffer
1983 // beyond the inline buffer size, so we reallocate to the heap. Because we
1984 // haven't "officially" filled the array with SetLength or AppendElements,
1985 // its mLength is still zero; as far as it's concerned the buffer is just
1986 // uninitialized space, and when it switches to use a malloc'd buffer it
1987 // won't copy the existing contents.
1989 // Allocate space for a buffer of Glyph records, without initializing them.
1990 AlignedStorage2<Glyph[AUTO_BUFFER_SIZE]> mAutoBuffer;
1992 // Pointer to the buffer we're currently using -- initially mAutoBuffer,
1993 // but may be changed to a malloc'd buffer, in which case that buffer must
1994 // be free'd on destruction.
1995 Glyph* mBuffer;
1997 uint32_t mBufSize; // size of allocated buffer; capacity can grow to
1998 // this before reallocation is needed
1999 uint32_t mCapacity; // amount of buffer size reserved
2000 uint32_t mNumGlyphs; // number of glyphs actually present in the buffer
2002 #undef AUTO_BUFFER_SIZE
2005 // Bug 674909. When synthetic bolding text by drawing twice, need to
2006 // render using a pixel offset in device pixels, otherwise text
2007 // doesn't appear bolded, it appears as if a bad text shadow exists
2008 // when a non-identity transform exists. Use an offset factor so that
2009 // the second draw occurs at a constant offset in device pixels.
2011 gfx::Float gfxFont::CalcXScale(DrawTarget* aDrawTarget) {
2012 // determine magnitude of a 1px x offset in device space
2013 Size t = aDrawTarget->GetTransform().TransformSize(Size(1.0, 0.0));
2014 if (t.width == 1.0 && t.height == 0.0) {
2015 // short-circuit the most common case to avoid sqrt() and division
2016 return 1.0;
2019 gfx::Float m = sqrtf(t.width * t.width + t.height * t.height);
2021 NS_ASSERTION(m != 0.0, "degenerate transform while synthetic bolding");
2022 if (m == 0.0) {
2023 return 0.0; // effectively disables offset
2026 // scale factor so that offsets are 1px in device pixels
2027 return 1.0 / m;
2030 // Draw a run of CharacterGlyph records from the given offset in aShapedText.
2031 // Returns true if glyph paths were actually emitted.
2032 template <gfxFont::FontComplexityT FC, gfxFont::SpacingT S>
2033 bool gfxFont::DrawGlyphs(const gfxShapedText* aShapedText,
2034 uint32_t aOffset, // offset in the textrun
2035 uint32_t aCount, // length of run to draw
2036 gfx::Point* aPt,
2037 const gfx::Matrix* aOffsetMatrix, // may be null
2038 GlyphBufferAzure& aBuffer) {
2039 float& inlineCoord =
2040 aBuffer.mFontParams.isVerticalFont ? aPt->y.value : aPt->x.value;
2042 const gfxShapedText::CompressedGlyph* glyphData =
2043 &aShapedText->GetCharacterGlyphs()[aOffset];
2045 if (S == SpacingT::HasSpacing) {
2046 float space = aBuffer.mRunParams.spacing[0].mBefore *
2047 aBuffer.mFontParams.advanceDirection;
2048 inlineCoord += space;
2051 // Allocate buffer space for the run, assuming all simple glyphs.
2052 uint32_t capacityMult = 1 + aBuffer.mFontParams.extraStrikes;
2053 aBuffer.AddCapacity(aCount, capacityMult);
2055 bool emittedGlyphs = false;
2057 for (uint32_t i = 0; i < aCount; ++i, ++glyphData) {
2058 if (glyphData->IsSimpleGlyph()) {
2059 float advance =
2060 glyphData->GetSimpleAdvance() * aBuffer.mFontParams.advanceDirection;
2061 if (aBuffer.mRunParams.isRTL) {
2062 inlineCoord += advance;
2064 DrawOneGlyph<FC>(glyphData->GetSimpleGlyph(), *aPt, aBuffer,
2065 &emittedGlyphs);
2066 if (!aBuffer.mRunParams.isRTL) {
2067 inlineCoord += advance;
2069 } else {
2070 uint32_t glyphCount = glyphData->GetGlyphCount();
2071 if (glyphCount > 0) {
2072 // Add extra buffer capacity to allow for multiple-glyph entry.
2073 aBuffer.AddCapacity(glyphCount - 1, capacityMult);
2074 const gfxShapedText::DetailedGlyph* details =
2075 aShapedText->GetDetailedGlyphs(aOffset + i);
2076 MOZ_ASSERT(details, "missing DetailedGlyph!");
2077 for (uint32_t j = 0; j < glyphCount; ++j, ++details) {
2078 float advance =
2079 details->mAdvance * aBuffer.mFontParams.advanceDirection;
2080 if (aBuffer.mRunParams.isRTL) {
2081 inlineCoord += advance;
2083 if (glyphData->IsMissing()) {
2084 if (!DrawMissingGlyph(aBuffer.mRunParams, aBuffer.mFontParams,
2085 details, *aPt)) {
2086 return false;
2088 } else {
2089 gfx::Point glyphPt(
2090 *aPt + (aOffsetMatrix
2091 ? aOffsetMatrix->TransformPoint(details->mOffset)
2092 : details->mOffset));
2093 DrawOneGlyph<FC>(details->mGlyphID, glyphPt, aBuffer,
2094 &emittedGlyphs);
2096 if (!aBuffer.mRunParams.isRTL) {
2097 inlineCoord += advance;
2103 if (S == SpacingT::HasSpacing) {
2104 float space = aBuffer.mRunParams.spacing[i].mAfter;
2105 if (i + 1 < aCount) {
2106 space += aBuffer.mRunParams.spacing[i + 1].mBefore;
2108 space *= aBuffer.mFontParams.advanceDirection;
2109 inlineCoord += space;
2113 return emittedGlyphs;
2116 // Draw an individual glyph at a specific location.
2117 // *aPt is the glyph position in appUnits; it is converted to device
2118 // coordinates (devPt) here.
2119 template <gfxFont::FontComplexityT FC>
2120 void gfxFont::DrawOneGlyph(uint32_t aGlyphID, const gfx::Point& aPt,
2121 GlyphBufferAzure& aBuffer, bool* aEmittedGlyphs) {
2122 const TextRunDrawParams& runParams(aBuffer.mRunParams);
2124 gfx::Point devPt(ToDeviceUnits(aPt.x, runParams.devPerApp),
2125 ToDeviceUnits(aPt.y, runParams.devPerApp));
2127 auto* textDrawer = runParams.textDrawer;
2128 if (textDrawer) {
2129 // If the glyph is entirely outside the clip rect, we don't need to draw it
2130 // at all. (We check the font extents here rather than the individual glyph
2131 // bounds because that's cheaper to look up, and provides a conservative
2132 // "worst case" for where this glyph might want to draw.)
2133 LayoutDeviceRect extents =
2134 LayoutDeviceRect::FromUnknownRect(aBuffer.mFontParams.fontExtents);
2135 extents.MoveBy(LayoutDevicePoint::FromUnknownPoint(devPt));
2136 if (!extents.Intersects(runParams.clipRect)) {
2137 return;
2141 if (FC == FontComplexityT::ComplexFont) {
2142 const FontDrawParams& fontParams(aBuffer.mFontParams);
2144 gfxContextMatrixAutoSaveRestore matrixRestore;
2146 if (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont &&
2147 !textDrawer) {
2148 // We have to flush each glyph individually when doing
2149 // synthetic-oblique for vertical-upright text, because
2150 // the skew transform needs to be applied to a separate
2151 // origin for each glyph, not once for the whole run.
2152 aBuffer.Flush();
2153 matrixRestore.SetContext(runParams.context);
2154 gfx::Point skewPt(
2155 devPt.x + GetMetrics(nsFontMetrics::eVertical).emHeight / 2, devPt.y);
2156 gfx::Matrix mat =
2157 runParams.context->CurrentMatrix()
2158 .PreTranslate(skewPt)
2159 .PreMultiply(gfx::Matrix(1, fontParams.obliqueSkew, 0, 1, 0, 0))
2160 .PreTranslate(-skewPt);
2161 runParams.context->SetMatrix(mat);
2164 if (fontParams.haveSVGGlyphs) {
2165 if (!runParams.paintSVGGlyphs) {
2166 return;
2168 NS_WARNING_ASSERTION(
2169 runParams.drawMode != DrawMode::GLYPH_PATH,
2170 "Rendering SVG glyph despite request for glyph path");
2171 if (RenderSVGGlyph(runParams.context, textDrawer, devPt, aGlyphID,
2172 fontParams.contextPaint, runParams.callbacks,
2173 *aEmittedGlyphs)) {
2174 return;
2178 if (fontParams.haveColorGlyphs && !UseNativeColrFontSupport() &&
2179 RenderColorGlyph(runParams.dt, runParams.context, textDrawer,
2180 fontParams, devPt, aGlyphID)) {
2181 return;
2184 aBuffer.OutputGlyph(aGlyphID, devPt);
2186 // Synthetic bolding (if required) by multi-striking.
2187 for (int32_t i = 0; i < fontParams.extraStrikes; ++i) {
2188 if (fontParams.isVerticalFont) {
2189 devPt.y += fontParams.synBoldOnePixelOffset;
2190 } else {
2191 devPt.x += fontParams.synBoldOnePixelOffset;
2193 aBuffer.OutputGlyph(aGlyphID, devPt);
2196 if (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont &&
2197 !textDrawer) {
2198 aBuffer.Flush();
2200 } else {
2201 aBuffer.OutputGlyph(aGlyphID, devPt);
2204 *aEmittedGlyphs = true;
2207 bool gfxFont::DrawMissingGlyph(const TextRunDrawParams& aRunParams,
2208 const FontDrawParams& aFontParams,
2209 const gfxShapedText::DetailedGlyph* aDetails,
2210 const gfx::Point& aPt) {
2211 // Default-ignorable chars will have zero advance width;
2212 // we don't have to draw the hexbox for them.
2213 float advance = aDetails->mAdvance;
2214 if (aRunParams.drawMode != DrawMode::GLYPH_PATH && advance > 0) {
2215 auto* textDrawer = aRunParams.textDrawer;
2216 const Matrix* matPtr = nullptr;
2217 Matrix mat;
2218 if (textDrawer) {
2219 // Generate an orientation matrix for the current writing mode
2220 wr::FontInstanceFlags flags = textDrawer->GetWRGlyphFlags();
2221 if (flags & wr::FontInstanceFlags::TRANSPOSE) {
2222 std::swap(mat._11, mat._12);
2223 std::swap(mat._21, mat._22);
2225 mat.PostScale(flags & wr::FontInstanceFlags::FLIP_X ? -1.0f : 1.0f,
2226 flags & wr::FontInstanceFlags::FLIP_Y ? -1.0f : 1.0f);
2227 matPtr = &mat;
2230 Point pt(Float(ToDeviceUnits(aPt.x, aRunParams.devPerApp)),
2231 Float(ToDeviceUnits(aPt.y, aRunParams.devPerApp)));
2232 Float advanceDevUnits = Float(ToDeviceUnits(advance, aRunParams.devPerApp));
2233 Float height = GetMetrics(nsFontMetrics::eHorizontal).maxAscent;
2234 // Horizontally center if drawing vertically upright with no sideways
2235 // transform.
2236 Rect glyphRect =
2237 aFontParams.isVerticalFont && !mat.HasNonAxisAlignedTransform()
2238 ? Rect(pt.x - height / 2, pt.y, height, advanceDevUnits)
2239 : Rect(pt.x, pt.y - height, advanceDevUnits, height);
2241 // If there's a fake-italic skew in effect as part
2242 // of the drawTarget's transform, we need to undo
2243 // this before drawing the hexbox. (Bug 983985)
2244 gfxContextMatrixAutoSaveRestore matrixRestore;
2245 if (aFontParams.obliqueSkew != 0.0f && !aFontParams.isVerticalFont &&
2246 !textDrawer) {
2247 matrixRestore.SetContext(aRunParams.context);
2248 gfx::Matrix mat =
2249 aRunParams.context->CurrentMatrix()
2250 .PreTranslate(pt)
2251 .PreMultiply(gfx::Matrix(1, 0, aFontParams.obliqueSkew, 1, 0, 0))
2252 .PreTranslate(-pt);
2253 aRunParams.context->SetMatrix(mat);
2256 gfxFontMissingGlyphs::DrawMissingGlyph(
2257 aDetails->mGlyphID, glyphRect, *aRunParams.dt,
2258 PatternFromState(aRunParams.context), matPtr);
2260 return true;
2263 // This method is mostly parallel to DrawGlyphs.
2264 void gfxFont::DrawEmphasisMarks(const gfxTextRun* aShapedText, gfx::Point* aPt,
2265 uint32_t aOffset, uint32_t aCount,
2266 const EmphasisMarkDrawParams& aParams) {
2267 float& inlineCoord = aParams.isVertical ? aPt->y.value : aPt->x.value;
2268 gfxTextRun::Range markRange(aParams.mark);
2269 gfxTextRun::DrawParams params(aParams.context, aParams.paletteCache);
2271 float clusterStart = -std::numeric_limits<float>::infinity();
2272 bool shouldDrawEmphasisMark = false;
2273 for (uint32_t i = 0, idx = aOffset; i < aCount; ++i, ++idx) {
2274 if (aParams.spacing) {
2275 inlineCoord += aParams.direction * aParams.spacing[i].mBefore;
2277 if (aShapedText->IsClusterStart(idx) ||
2278 clusterStart == -std::numeric_limits<float>::infinity()) {
2279 clusterStart = inlineCoord;
2281 if (aShapedText->CharMayHaveEmphasisMark(idx)) {
2282 shouldDrawEmphasisMark = true;
2284 inlineCoord += aParams.direction * aShapedText->GetAdvanceForGlyph(idx);
2285 if (shouldDrawEmphasisMark &&
2286 (i + 1 == aCount || aShapedText->IsClusterStart(idx + 1))) {
2287 float clusterAdvance = inlineCoord - clusterStart;
2288 // Move the coord backward to get the needed start point.
2289 float delta = (clusterAdvance + aParams.advance) / 2;
2290 inlineCoord -= delta;
2291 aParams.mark->Draw(markRange, *aPt, params);
2292 inlineCoord += delta;
2293 shouldDrawEmphasisMark = false;
2295 if (aParams.spacing) {
2296 inlineCoord += aParams.direction * aParams.spacing[i].mAfter;
2301 void gfxFont::Draw(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd,
2302 gfx::Point* aPt, TextRunDrawParams& aRunParams,
2303 gfx::ShapedTextFlags aOrientation) {
2304 NS_ASSERTION(aRunParams.drawMode == DrawMode::GLYPH_PATH ||
2305 !(int(aRunParams.drawMode) & int(DrawMode::GLYPH_PATH)),
2306 "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or "
2307 "GLYPH_STROKE_UNDERNEATH");
2309 if (aStart >= aEnd) {
2310 return;
2313 FontDrawParams fontParams;
2315 if (aRunParams.drawOpts) {
2316 fontParams.drawOptions = *aRunParams.drawOpts;
2319 fontParams.scaledFont = GetScaledFont(aRunParams);
2320 if (!fontParams.scaledFont) {
2321 return;
2323 auto* textDrawer = aRunParams.textDrawer;
2325 fontParams.obliqueSkew = SkewForSyntheticOblique();
2326 fontParams.haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this);
2327 fontParams.haveColorGlyphs = GetFontEntry()->TryGetColorGlyphs();
2328 fontParams.hasTextShadow = aRunParams.hasTextShadow;
2329 fontParams.contextPaint = aRunParams.runContextPaint;
2331 if (fontParams.haveColorGlyphs && !UseNativeColrFontSupport()) {
2332 DeviceColor ctxColor;
2333 fontParams.currentColor = aRunParams.context->GetDeviceColor(ctxColor)
2334 ? sRGBColor::FromABGR(ctxColor.ToABGR())
2335 : sRGBColor::OpaqueBlack();
2336 fontParams.palette = aRunParams.paletteCache.GetPaletteFor(
2337 GetFontEntry(), aRunParams.fontPalette);
2340 if (textDrawer) {
2341 fontParams.isVerticalFont = aRunParams.isVerticalRun;
2342 } else {
2343 fontParams.isVerticalFont =
2344 aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
2347 gfxContextMatrixAutoSaveRestore matrixRestore;
2348 layout::TextDrawTarget::AutoRestoreWRGlyphFlags glyphFlagsRestore;
2350 // Save the current baseline offset for restoring later, in case it is
2351 // modified.
2352 float& baseline = fontParams.isVerticalFont ? aPt->x.value : aPt->y.value;
2353 float origBaseline = baseline;
2355 // The point may be advanced in local-space, while the resulting point on
2356 // return must be advanced in transformed space. So save the original point so
2357 // we can properly transform the advance later.
2358 gfx::Point origPt = *aPt;
2359 const gfx::Matrix* offsetMatrix = nullptr;
2361 // Default to advancing along the +X direction (-X if RTL).
2362 fontParams.advanceDirection = aRunParams.isRTL ? -1.0f : 1.0f;
2363 // Default to offsetting baseline downward along the +Y direction.
2364 float baselineDir = 1.0f;
2365 // The direction of sideways rotation, if applicable.
2366 // -1 for rotating left/counter-clockwise
2367 // 1 for rotating right/clockwise
2368 // 0 for no rotation
2369 float sidewaysDir =
2370 (aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
2371 ? -1.0f
2372 : (aOrientation ==
2373 gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT
2374 ? 1.0f
2375 : 0.0f));
2376 // If we're rendering a sideways run, we need to push a rotation transform to
2377 // the context.
2378 if (sidewaysDir != 0.0f) {
2379 if (textDrawer) {
2380 // For WebRender, we can't use a DrawTarget transform and must instead use
2381 // flags that locally transform the glyph, without affecting the glyph
2382 // origin. The glyph origins must thus be offset in the transformed
2383 // directions (instead of local-space directions). Modify the advance and
2384 // baseline directions to account for the indicated transform.
2386 // The default text orientation is down being +Y and right being +X.
2387 // Rotating 90 degrees left/CCW makes down be +X and right be -Y.
2388 // Rotating 90 degrees right/CW makes down be -X and right be +Y.
2389 // Thus the advance direction (moving right) is just sidewaysDir,
2390 // i.e. negative along Y axis if rotated left and positive if
2391 // rotated right.
2392 fontParams.advanceDirection *= sidewaysDir;
2393 // The baseline direction (moving down) is negated relative to the
2394 // advance direction for sideways transforms.
2395 baselineDir *= -sidewaysDir;
2397 glyphFlagsRestore.Save(textDrawer);
2398 // Set the transform flags accordingly. Both sideways rotations transpose
2399 // X and Y, while left rotation flips the resulting Y axis, and right
2400 // rotation flips the resulting X axis.
2401 textDrawer->SetWRGlyphFlags(
2402 textDrawer->GetWRGlyphFlags() | wr::FontInstanceFlags::TRANSPOSE |
2403 (aOrientation ==
2404 gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
2405 ? wr::FontInstanceFlags::FLIP_Y
2406 : wr::FontInstanceFlags::FLIP_X));
2407 // We also need to set up a transform for the glyph offset vector that
2408 // may be present in DetailedGlyph records.
2409 static const gfx::Matrix kSidewaysLeft = {0, -1, 1, 0, 0, 0};
2410 static const gfx::Matrix kSidewaysRight = {0, 1, -1, 0, 0, 0};
2411 offsetMatrix =
2412 (aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT)
2413 ? &kSidewaysLeft
2414 : &kSidewaysRight;
2415 } else {
2416 // For non-WebRender targets, just push a rotation transform.
2417 matrixRestore.SetContext(aRunParams.context);
2418 gfxPoint p(aPt->x * aRunParams.devPerApp, aPt->y * aRunParams.devPerApp);
2419 // Get a matrix we can use to draw the (horizontally-shaped) textrun
2420 // with 90-degree CW rotation.
2421 const gfxFloat rotation = sidewaysDir * M_PI / 2.0f;
2422 gfxMatrix mat = aRunParams.context->CurrentMatrixDouble()
2423 .PreTranslate(p)
2424 . // translate origin for rotation
2425 PreRotate(rotation)
2426 . // turn 90deg CCW (sideways-left) or CW (*-right)
2427 PreTranslate(-p); // undo the translation
2429 aRunParams.context->SetMatrixDouble(mat);
2432 // If we're drawing rotated horizontal text for an element styled
2433 // text-orientation:mixed, the dominant baseline will be vertical-
2434 // centered. So in this case, we need to adjust the position so that
2435 // the rotated horizontal text (which uses an alphabetic baseline) will
2436 // look OK when juxtaposed with upright glyphs (rendered on a centered
2437 // vertical baseline). The adjustment here is somewhat ad hoc; we
2438 // should eventually look for baseline tables[1] in the fonts and use
2439 // those if available.
2440 // [1] See http://www.microsoft.com/typography/otspec/base.htm
2441 if (aTextRun->UseCenterBaseline()) {
2442 const Metrics& metrics = GetMetrics(nsFontMetrics::eHorizontal);
2443 float baseAdj = (metrics.emAscent - metrics.emDescent) / 2;
2444 baseline += baseAdj * aTextRun->GetAppUnitsPerDevUnit() * baselineDir;
2446 } else if (textDrawer &&
2447 aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) {
2448 glyphFlagsRestore.Save(textDrawer);
2449 textDrawer->SetWRGlyphFlags(textDrawer->GetWRGlyphFlags() |
2450 wr::FontInstanceFlags::VERTICAL);
2453 if (fontParams.obliqueSkew != 0.0f && !fontParams.isVerticalFont &&
2454 !textDrawer) {
2455 // Adjust matrix for synthetic-oblique, except if we're doing vertical-
2456 // upright text, in which case this will be handled for each glyph
2457 // individually in DrawOneGlyph.
2458 if (!matrixRestore.HasMatrix()) {
2459 matrixRestore.SetContext(aRunParams.context);
2461 gfx::Point p(aPt->x * aRunParams.devPerApp, aPt->y * aRunParams.devPerApp);
2462 gfx::Matrix mat =
2463 aRunParams.context->CurrentMatrix()
2464 .PreTranslate(p)
2465 .PreMultiply(gfx::Matrix(1, 0, -fontParams.obliqueSkew, 1, 0, 0))
2466 .PreTranslate(-p);
2467 aRunParams.context->SetMatrix(mat);
2470 RefPtr<SVGContextPaint> contextPaint;
2471 if (fontParams.haveSVGGlyphs && !fontParams.contextPaint) {
2472 // If no pattern is specified for fill, use the current pattern
2473 NS_ASSERTION((int(aRunParams.drawMode) & int(DrawMode::GLYPH_STROKE)) == 0,
2474 "no pattern supplied for stroking text");
2475 RefPtr<gfxPattern> fillPattern = aRunParams.context->GetPattern();
2476 contextPaint = new SimpleTextContextPaint(
2477 fillPattern, nullptr, aRunParams.context->CurrentMatrixDouble());
2478 fontParams.contextPaint = contextPaint.get();
2481 // Synthetic-bold strikes are each offset one device pixel in run direction
2482 // (these values are only needed if ApplySyntheticBold() is true).
2483 // If drawing via webrender, it will do multistrike internally so we don't
2484 // need to handle it here.
2485 bool doMultistrikeBold = ApplySyntheticBold() && !textDrawer;
2486 if (doMultistrikeBold) {
2487 // For screen display, we want to try and repeat strikes with an offset of
2488 // one device pixel, accounting for zoom or other transforms that may be
2489 // in effect, so compute x-axis scale factor from the drawtarget.
2490 // However, when generating PDF output the drawtarget's transform does not
2491 // really bear any relation to "device pixels", and may result in an
2492 // excessively large offset relative to the font size (bug 1823888), so
2493 // we limit it based on the used font size to avoid this.
2494 // The constant 48.0 reflects the threshold where the calculation in
2495 // gfxFont::GetSyntheticBoldOffset() switches to a simple origin-based
2496 // slope, though the exact value is somewhat arbitrary; it's selected to
2497 // allow a visible amount of boldness while preventing the offset from
2498 // becoming "large" in relation to the glyphs.
2499 Float xscale =
2500 std::min<Float>(GetAdjustedSize() / 48.0,
2501 CalcXScale(aRunParams.context->GetDrawTarget()));
2502 fontParams.synBoldOnePixelOffset = aRunParams.direction * xscale;
2503 if (xscale != 0.0) {
2504 static const int32_t kMaxExtraStrikes = 128;
2505 gfxFloat extraStrikes = GetSyntheticBoldOffset() / xscale;
2506 if (extraStrikes > kMaxExtraStrikes) {
2507 // if too many strikes are required, limit them and increase the step
2508 // size to compensate
2509 fontParams.extraStrikes = kMaxExtraStrikes;
2510 fontParams.synBoldOnePixelOffset = aRunParams.direction *
2511 GetSyntheticBoldOffset() /
2512 fontParams.extraStrikes;
2513 } else {
2514 // use as many strikes as needed for the increased advance
2515 fontParams.extraStrikes = NS_lroundf(std::max(1.0, extraStrikes));
2517 } else {
2518 // Degenerate transform?!
2519 fontParams.extraStrikes = 0;
2521 } else {
2522 fontParams.synBoldOnePixelOffset = 0;
2523 fontParams.extraStrikes = 0;
2526 // Figure out the maximum extents for the font, accounting for synthetic
2527 // oblique and bold.
2528 if (mFUnitsConvFactor > 0.0) {
2529 fontParams.fontExtents = GetFontEntry()->GetFontExtents(mFUnitsConvFactor);
2530 } else {
2531 // Was it not an sfnt? Maybe on Linux... use arbitrary huge extents, so we
2532 // don't inadvertently clip stuff. A bit less efficient than true extents,
2533 // but this should be extremely rare.
2534 auto size = GetAdjustedSize();
2535 fontParams.fontExtents = Rect(-2 * size, -2 * size, 5 * size, 5 * size);
2537 if (fontParams.obliqueSkew != 0.0f) {
2538 gfx::Point p(fontParams.fontExtents.x, fontParams.fontExtents.y);
2539 gfx::Matrix skew(1, 0, fontParams.obliqueSkew, 1, 0, 0);
2540 fontParams.fontExtents = skew.TransformBounds(fontParams.fontExtents);
2542 if (fontParams.extraStrikes) {
2543 if (fontParams.isVerticalFont) {
2544 fontParams.fontExtents.height +=
2545 float(fontParams.extraStrikes) * fontParams.synBoldOnePixelOffset;
2546 } else {
2547 fontParams.fontExtents.width +=
2548 float(fontParams.extraStrikes) * fontParams.synBoldOnePixelOffset;
2552 bool oldSubpixelAA = aRunParams.dt->GetPermitSubpixelAA();
2553 if (!AllowSubpixelAA()) {
2554 aRunParams.dt->SetPermitSubpixelAA(false);
2557 Matrix mat;
2558 Matrix oldMat = aRunParams.dt->GetTransform();
2560 fontParams.drawOptions.mAntialiasMode = Get2DAAMode(mAntialiasOption);
2562 if (mStyle.baselineOffset != 0.0) {
2563 baseline +=
2564 mStyle.baselineOffset * aTextRun->GetAppUnitsPerDevUnit() * baselineDir;
2567 bool emittedGlyphs;
2569 // Select appropriate version of the templated DrawGlyphs method
2570 // to output glyphs to the buffer, depending on complexity needed
2571 // for the type of font, and whether added inter-glyph spacing
2572 // is specified.
2573 GlyphBufferAzure buffer(aRunParams, fontParams);
2574 if (fontParams.haveSVGGlyphs || fontParams.haveColorGlyphs ||
2575 fontParams.extraStrikes ||
2576 (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont &&
2577 !textDrawer)) {
2578 if (aRunParams.spacing) {
2579 emittedGlyphs =
2580 DrawGlyphs<FontComplexityT::ComplexFont, SpacingT::HasSpacing>(
2581 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
2582 } else {
2583 emittedGlyphs =
2584 DrawGlyphs<FontComplexityT::ComplexFont, SpacingT::NoSpacing>(
2585 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
2587 } else {
2588 if (aRunParams.spacing) {
2589 emittedGlyphs =
2590 DrawGlyphs<FontComplexityT::SimpleFont, SpacingT::HasSpacing>(
2591 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
2592 } else {
2593 emittedGlyphs =
2594 DrawGlyphs<FontComplexityT::SimpleFont, SpacingT::NoSpacing>(
2595 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
2600 baseline = origBaseline;
2602 if (aRunParams.callbacks && emittedGlyphs) {
2603 aRunParams.callbacks->NotifyGlyphPathEmitted();
2606 aRunParams.dt->SetTransform(oldMat);
2607 aRunParams.dt->SetPermitSubpixelAA(oldSubpixelAA);
2609 if (sidewaysDir != 0.0f && !textDrawer) {
2610 // Adjust updated aPt to account for the transform we were using.
2611 // The advance happened horizontally in local-space, but the transformed
2612 // sideways advance is actually vertical, with sign depending on the
2613 // direction of rotation.
2614 float advance = aPt->x - origPt.x;
2615 *aPt = gfx::Point(origPt.x, origPt.y + advance * sidewaysDir);
2619 bool gfxFont::RenderSVGGlyph(gfxContext* aContext,
2620 layout::TextDrawTarget* aTextDrawer,
2621 gfx::Point aPoint, uint32_t aGlyphId,
2622 SVGContextPaint* aContextPaint) const {
2623 if (!GetFontEntry()->HasSVGGlyph(aGlyphId)) {
2624 return false;
2627 if (aTextDrawer) {
2628 // WebRender doesn't support SVG Glyphs.
2629 // (pretend to succeed, output doesn't matter, we will emit a blob)
2630 aTextDrawer->FoundUnsupportedFeature();
2631 return true;
2634 const gfxFloat devUnitsPerSVGUnit =
2635 GetAdjustedSize() / GetFontEntry()->UnitsPerEm();
2636 gfxContextMatrixAutoSaveRestore matrixRestore(aContext);
2638 aContext->SetMatrix(aContext->CurrentMatrix()
2639 .PreTranslate(aPoint.x, aPoint.y)
2640 .PreScale(devUnitsPerSVGUnit, devUnitsPerSVGUnit));
2642 aContextPaint->InitStrokeGeometry(aContext, devUnitsPerSVGUnit);
2644 GetFontEntry()->RenderSVGGlyph(aContext, aGlyphId, aContextPaint);
2645 aContext->NewPath();
2646 return true;
2649 bool gfxFont::RenderSVGGlyph(gfxContext* aContext,
2650 layout::TextDrawTarget* aTextDrawer,
2651 gfx::Point aPoint, uint32_t aGlyphId,
2652 SVGContextPaint* aContextPaint,
2653 gfxTextRunDrawCallbacks* aCallbacks,
2654 bool& aEmittedGlyphs) const {
2655 if (aCallbacks && aEmittedGlyphs) {
2656 aCallbacks->NotifyGlyphPathEmitted();
2657 aEmittedGlyphs = false;
2659 return RenderSVGGlyph(aContext, aTextDrawer, aPoint, aGlyphId, aContextPaint);
2662 bool gfxFont::RenderColorGlyph(DrawTarget* aDrawTarget, gfxContext* aContext,
2663 layout::TextDrawTarget* aTextDrawer,
2664 const FontDrawParams& aFontParams,
2665 const Point& aPoint, uint32_t aGlyphId) {
2666 if (aTextDrawer && aFontParams.hasTextShadow) {
2667 aTextDrawer->FoundUnsupportedFeature();
2668 return true;
2671 auto* colr = GetFontEntry()->GetCOLR();
2672 if (const auto* paintGraph = COLRFonts::GetGlyphPaintGraph(colr, aGlyphId)) {
2673 const auto* hbShaper = GetHarfBuzzShaper();
2674 if (hbShaper && hbShaper->IsInitialized()) {
2675 if (aTextDrawer) {
2676 aTextDrawer->FoundUnsupportedFeature();
2677 return true;
2680 // For reasonable font sizes, use a cache of rasterized glyphs.
2681 if (GetAdjustedSize() <= 256.0) {
2682 AutoWriteLock lock(mLock);
2683 if (!mColorGlyphCache) {
2684 mColorGlyphCache = MakeUnique<ColorGlyphCache>();
2687 // Tell the cache what colors we're using; if they have changed, it will
2688 // discard any currently-cached entries.
2689 mColorGlyphCache->SetColors(aFontParams.currentColor,
2690 aFontParams.palette);
2692 bool ok = false;
2693 auto cached = mColorGlyphCache->mCache.lookupForAdd(aGlyphId);
2694 Rect bounds = COLRFonts::GetColorGlyphBounds(
2695 colr, hbShaper->GetHBFont(), aGlyphId, aDrawTarget,
2696 aFontParams.scaledFont, mFUnitsConvFactor);
2697 bounds.RoundOut();
2699 if (cached) {
2700 ok = true;
2701 } else {
2702 // Create a temporary DrawTarget, render the glyph, and save a
2703 // snapshot of the rendering in the cache.
2704 IntSize size(int(bounds.width), int(bounds.height));
2705 SurfaceFormat format = SurfaceFormat::B8G8R8A8;
2706 RefPtr target =
2707 Factory::CreateDrawTarget(BackendType::SKIA, size, format);
2708 if (target) {
2709 ok = COLRFonts::PaintGlyphGraph(
2710 GetFontEntry()->GetCOLR(), hbShaper->GetHBFont(), paintGraph,
2711 target, nullptr, aFontParams.scaledFont,
2712 aFontParams.drawOptions, -bounds.TopLeft(),
2713 aFontParams.currentColor, aFontParams.palette->Colors(),
2714 aGlyphId, mFUnitsConvFactor);
2715 if (ok) {
2716 RefPtr snapshot = target->Snapshot();
2717 ok = mColorGlyphCache->mCache.add(cached, aGlyphId, snapshot);
2721 if (ok) {
2722 // Paint the snapshot from cached->value(), and return.
2723 aDrawTarget->DrawSurface(
2724 cached->value(), Rect(aPoint + bounds.TopLeft(), bounds.Size()),
2725 Rect(Point(), bounds.Size()));
2726 return true;
2730 // If we failed to cache the glyph, or it was too large to even try,
2731 // just paint directly to the target.
2732 return COLRFonts::PaintGlyphGraph(
2733 colr, hbShaper->GetHBFont(), paintGraph, aDrawTarget, aTextDrawer,
2734 aFontParams.scaledFont, aFontParams.drawOptions, aPoint,
2735 aFontParams.currentColor, aFontParams.palette->Colors(), aGlyphId,
2736 mFUnitsConvFactor);
2740 if (const auto* layers =
2741 COLRFonts::GetGlyphLayers(GetFontEntry()->GetCOLR(), aGlyphId)) {
2742 auto face(GetFontEntry()->GetHBFace());
2743 bool ok = COLRFonts::PaintGlyphLayers(
2744 colr, face, layers, aDrawTarget, aTextDrawer, aFontParams.scaledFont,
2745 aFontParams.drawOptions, aPoint, aFontParams.currentColor,
2746 aFontParams.palette->Colors());
2747 return ok;
2750 return false;
2753 void gfxFont::ColorGlyphCache::SetColors(sRGBColor aCurrentColor,
2754 FontPalette* aPalette) {
2755 if (aCurrentColor != mCurrentColor || aPalette != mPalette) {
2756 mCache.clear();
2757 mCurrentColor = aCurrentColor;
2758 mPalette = aPalette;
2762 bool gfxFont::HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh) {
2763 // Bitmap fonts are assumed to provide "color" glyphs for all supported chars.
2764 gfxFontEntry* fe = GetFontEntry();
2765 if (fe->HasColorBitmapTable()) {
2766 return true;
2768 // Use harfbuzz shaper to look up the default glyph ID for the character.
2769 auto* shaper = GetHarfBuzzShaper();
2770 if (!shaper) {
2771 return false;
2773 uint32_t gid = 0;
2774 if (gfxFontUtils::IsVarSelector(aNextCh)) {
2775 gid = shaper->GetVariationGlyph(aCh, aNextCh);
2777 if (!gid) {
2778 gid = shaper->GetNominalGlyph(aCh);
2780 if (!gid) {
2781 return false;
2784 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1801521:
2785 // Emoji special-case: flag sequences NOT based on Regional Indicator pairs
2786 // use the BLACK FLAG character plus a series of plane-14 TAG LETTERs, e.g.
2787 // England = <black-flag, tag-G, tag-B, tag-E, tag-N, tag-G, tag-cancel>
2788 // Here, we don't check for support of the entire sequence (too much
2789 // expensive lookahead), but we check that the font at least supports the
2790 // first of the tag letter codes, because if it doesn't, we're at risk of
2791 // just getting an undifferentiated black flag glyph.
2792 if (gfxFontUtils::IsEmojiFlagAndTag(aCh, aNextCh)) {
2793 if (!shaper->GetNominalGlyph(aNextCh)) {
2794 return false;
2798 // Check if there is a COLR/CPAL or SVG glyph for this ID.
2799 if (fe->TryGetColorGlyphs() &&
2800 (COLRFonts::GetGlyphPaintGraph(fe->GetCOLR(), gid) ||
2801 COLRFonts::GetGlyphLayers(fe->GetCOLR(), gid))) {
2802 return true;
2804 if (fe->TryGetSVGData(this) && fe->HasSVGGlyph(gid)) {
2805 return true;
2807 return false;
2810 static void UnionRange(gfxFloat aX, gfxFloat* aDestMin, gfxFloat* aDestMax) {
2811 *aDestMin = std::min(*aDestMin, aX);
2812 *aDestMax = std::max(*aDestMax, aX);
2815 // We get precise glyph extents if the textrun creator requested them, or
2816 // if the font is a user font --- in which case the author may be relying
2817 // on overflowing glyphs.
2818 static bool NeedsGlyphExtents(gfxFont* aFont, const gfxTextRun* aTextRun) {
2819 return (aTextRun->GetFlags() &
2820 gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX) ||
2821 aFont->GetFontEntry()->IsUserFont();
2824 bool gfxFont::IsSpaceGlyphInvisible(DrawTarget* aRefDrawTarget,
2825 const gfxTextRun* aTextRun) {
2826 gfxFontEntry::LazyFlag flag = mFontEntry->mSpaceGlyphIsInvisible;
2827 if (flag == gfxFontEntry::LazyFlag::Uninitialized &&
2828 GetAdjustedSize() >= 1.0) {
2829 gfxGlyphExtents* extents =
2830 GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit());
2831 gfxRect glyphExtents;
2832 flag = extents->GetTightGlyphExtentsAppUnits(
2833 this, aRefDrawTarget, GetSpaceGlyph(), &glyphExtents) &&
2834 glyphExtents.IsEmpty()
2835 ? gfxFontEntry::LazyFlag::Yes
2836 : gfxFontEntry::LazyFlag::No;
2837 mFontEntry->mSpaceGlyphIsInvisible = flag;
2839 return flag == gfxFontEntry::LazyFlag::Yes;
2842 bool gfxFont::MeasureGlyphs(const gfxTextRun* aTextRun, uint32_t aStart,
2843 uint32_t aEnd, BoundingBoxType aBoundingBoxType,
2844 DrawTarget* aRefDrawTarget, Spacing* aSpacing,
2845 gfxGlyphExtents* aExtents, bool aIsRTL,
2846 bool aNeedsGlyphExtents, RunMetrics& aMetrics,
2847 gfxFloat* aAdvanceMin, gfxFloat* aAdvanceMax) {
2848 const gfxTextRun::CompressedGlyph* charGlyphs =
2849 aTextRun->GetCharacterGlyphs();
2850 double x = 0;
2851 if (aSpacing) {
2852 x += aSpacing[0].mBefore;
2854 uint32_t spaceGlyph = GetSpaceGlyph();
2855 bool allGlyphsInvisible = true;
2857 AutoReadLock lock(aExtents->mLock);
2859 for (uint32_t i = aStart; i < aEnd; ++i) {
2860 const gfxTextRun::CompressedGlyph* glyphData = &charGlyphs[i];
2861 if (glyphData->IsSimpleGlyph()) {
2862 double advance = glyphData->GetSimpleAdvance();
2863 uint32_t glyphIndex = glyphData->GetSimpleGlyph();
2864 if (allGlyphsInvisible) {
2865 if (glyphIndex != spaceGlyph) {
2866 allGlyphsInvisible = false;
2867 } else {
2868 gfxFontEntry::LazyFlag flag = mFontEntry->mSpaceGlyphIsInvisible;
2869 if (flag == gfxFontEntry::LazyFlag::Uninitialized &&
2870 GetAdjustedSize() >= 1.0) {
2871 gfxRect glyphExtents;
2872 flag = aExtents->GetTightGlyphExtentsAppUnitsLocked(
2873 this, aRefDrawTarget, spaceGlyph, &glyphExtents) &&
2874 glyphExtents.IsEmpty()
2875 ? gfxFontEntry::LazyFlag::Yes
2876 : gfxFontEntry::LazyFlag::No;
2877 mFontEntry->mSpaceGlyphIsInvisible = flag;
2879 if (flag == gfxFontEntry::LazyFlag::No) {
2880 allGlyphsInvisible = false;
2884 // Only get the real glyph horizontal extent if we were asked
2885 // for the tight bounding box or we're in quality mode
2886 if (aBoundingBoxType != LOOSE_INK_EXTENTS || aNeedsGlyphExtents) {
2887 uint16_t extentsWidth =
2888 aExtents->GetContainedGlyphWidthAppUnitsLocked(glyphIndex);
2889 if (extentsWidth != gfxGlyphExtents::INVALID_WIDTH &&
2890 aBoundingBoxType == LOOSE_INK_EXTENTS) {
2891 UnionRange(x, aAdvanceMin, aAdvanceMax);
2892 UnionRange(x + extentsWidth, aAdvanceMin, aAdvanceMax);
2893 } else {
2894 gfxRect glyphRect;
2895 if (!aExtents->GetTightGlyphExtentsAppUnitsLocked(
2896 this, aRefDrawTarget, glyphIndex, &glyphRect)) {
2897 glyphRect = gfxRect(0, aMetrics.mBoundingBox.Y(), advance,
2898 aMetrics.mBoundingBox.Height());
2900 if (aIsRTL) {
2901 // In effect, swap left and right sidebearings of the glyph, for
2902 // proper accumulation of potentially-overlapping glyph rects.
2903 glyphRect.MoveToX(advance - glyphRect.XMost());
2905 glyphRect.MoveByX(x);
2906 aMetrics.mBoundingBox = aMetrics.mBoundingBox.Union(glyphRect);
2909 x += advance;
2910 } else {
2911 allGlyphsInvisible = false;
2912 uint32_t glyphCount = glyphData->GetGlyphCount();
2913 if (glyphCount > 0) {
2914 const gfxTextRun::DetailedGlyph* details =
2915 aTextRun->GetDetailedGlyphs(i);
2916 NS_ASSERTION(details != nullptr,
2917 "detailedGlyph record should not be missing!");
2918 uint32_t j;
2919 for (j = 0; j < glyphCount; ++j, ++details) {
2920 uint32_t glyphIndex = details->mGlyphID;
2921 double advance = details->mAdvance;
2922 gfxRect glyphRect;
2923 if (glyphData->IsMissing() ||
2924 !aExtents->GetTightGlyphExtentsAppUnitsLocked(
2925 this, aRefDrawTarget, glyphIndex, &glyphRect)) {
2926 // We might have failed to get glyph extents due to
2927 // OOM or something
2928 glyphRect = gfxRect(0, -aMetrics.mAscent, advance,
2929 aMetrics.mAscent + aMetrics.mDescent);
2931 if (aIsRTL) {
2932 // Swap left/right sidebearings of the glyph, because we're doing
2933 // mirrored measurement.
2934 glyphRect.MoveToX(advance - glyphRect.XMost());
2935 // Move to current x position, mirroring any x-offset amount.
2936 glyphRect.MoveByX(x - details->mOffset.x);
2937 } else {
2938 glyphRect.MoveByX(x + details->mOffset.x);
2940 glyphRect.MoveByY(details->mOffset.y);
2941 aMetrics.mBoundingBox = aMetrics.mBoundingBox.Union(glyphRect);
2942 x += advance;
2946 if (aSpacing) {
2947 double space = aSpacing[i - aStart].mAfter;
2948 if (i + 1 < aEnd) {
2949 space += aSpacing[i + 1 - aStart].mBefore;
2951 x += space;
2955 aMetrics.mAdvanceWidth = x;
2956 return allGlyphsInvisible;
2959 bool gfxFont::MeasureGlyphs(const gfxTextRun* aTextRun, uint32_t aStart,
2960 uint32_t aEnd, BoundingBoxType aBoundingBoxType,
2961 DrawTarget* aRefDrawTarget, Spacing* aSpacing,
2962 bool aIsRTL, RunMetrics& aMetrics) {
2963 const gfxTextRun::CompressedGlyph* charGlyphs =
2964 aTextRun->GetCharacterGlyphs();
2965 double x = 0;
2966 if (aSpacing) {
2967 x += aSpacing[0].mBefore;
2969 uint32_t spaceGlyph = GetSpaceGlyph();
2970 bool allGlyphsInvisible = true;
2972 for (uint32_t i = aStart; i < aEnd; ++i) {
2973 const gfxTextRun::CompressedGlyph* glyphData = &charGlyphs[i];
2974 if (glyphData->IsSimpleGlyph()) {
2975 double advance = glyphData->GetSimpleAdvance();
2976 uint32_t glyphIndex = glyphData->GetSimpleGlyph();
2977 if (allGlyphsInvisible &&
2978 (glyphIndex != spaceGlyph ||
2979 !IsSpaceGlyphInvisible(aRefDrawTarget, aTextRun))) {
2980 allGlyphsInvisible = false;
2982 x += advance;
2983 } else {
2984 allGlyphsInvisible = false;
2985 uint32_t glyphCount = glyphData->GetGlyphCount();
2986 if (glyphCount > 0) {
2987 const gfxTextRun::DetailedGlyph* details =
2988 aTextRun->GetDetailedGlyphs(i);
2989 NS_ASSERTION(details != nullptr,
2990 "detailedGlyph record should not be missing!");
2991 uint32_t j;
2992 for (j = 0; j < glyphCount; ++j, ++details) {
2993 double advance = details->mAdvance;
2994 gfxRect glyphRect(0, -aMetrics.mAscent, advance,
2995 aMetrics.mAscent + aMetrics.mDescent);
2996 if (aIsRTL) {
2997 // Swap left/right sidebearings of the glyph, because we're doing
2998 // mirrored measurement.
2999 glyphRect.MoveToX(advance - glyphRect.XMost());
3000 // Move to current x position, mirroring any x-offset amount.
3001 glyphRect.MoveByX(x - details->mOffset.x);
3002 } else {
3003 glyphRect.MoveByX(x + details->mOffset.x);
3005 glyphRect.MoveByY(details->mOffset.y);
3006 aMetrics.mBoundingBox = aMetrics.mBoundingBox.Union(glyphRect);
3007 x += advance;
3011 if (aSpacing) {
3012 double space = aSpacing[i - aStart].mAfter;
3013 if (i + 1 < aEnd) {
3014 space += aSpacing[i + 1 - aStart].mBefore;
3016 x += space;
3020 aMetrics.mAdvanceWidth = x;
3021 return allGlyphsInvisible;
3024 gfxFont::RunMetrics gfxFont::Measure(const gfxTextRun* aTextRun,
3025 uint32_t aStart, uint32_t aEnd,
3026 BoundingBoxType aBoundingBoxType,
3027 DrawTarget* aRefDrawTarget,
3028 Spacing* aSpacing,
3029 gfx::ShapedTextFlags aOrientation) {
3030 // If aBoundingBoxType is TIGHT_HINTED_OUTLINE_EXTENTS
3031 // and the underlying cairo font may be antialiased,
3032 // we need to create a copy in order to avoid getting cached extents.
3033 // This is only used by MathML layout at present.
3034 if (aBoundingBoxType == TIGHT_HINTED_OUTLINE_EXTENTS &&
3035 mAntialiasOption != kAntialiasNone) {
3036 gfxFont* nonAA = mNonAAFont;
3037 if (!nonAA) {
3038 nonAA = CopyWithAntialiasOption(kAntialiasNone);
3039 if (nonAA) {
3040 if (!mNonAAFont.compareExchange(nullptr, nonAA)) {
3041 delete nonAA;
3042 nonAA = mNonAAFont;
3046 // if font subclass doesn't implement CopyWithAntialiasOption(),
3047 // it will return null and we'll proceed to use the existing font
3048 if (nonAA) {
3049 return nonAA->Measure(aTextRun, aStart, aEnd,
3050 TIGHT_HINTED_OUTLINE_EXTENTS, aRefDrawTarget,
3051 aSpacing, aOrientation);
3055 const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
3056 // Current position in appunits
3057 Orientation orientation =
3058 aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
3059 ? nsFontMetrics::eVertical
3060 : nsFontMetrics::eHorizontal;
3061 const gfxFont::Metrics& fontMetrics = GetMetrics(orientation);
3063 gfxFloat baselineOffset = 0;
3064 if (aTextRun->UseCenterBaseline() &&
3065 orientation == nsFontMetrics::eHorizontal) {
3066 // For a horizontal font being used in vertical writing mode with
3067 // text-orientation:mixed, the overall metrics we're accumulating
3068 // will be aimed at a center baseline. But this font's metrics were
3069 // based on the alphabetic baseline. So we compute a baseline offset
3070 // that will be applied to ascent/descent values and glyph rects
3071 // to effectively shift them relative to the baseline.
3072 // XXX Eventually we should probably use the BASE table, if present.
3073 // But it usually isn't, so we need an ad hoc adjustment for now.
3074 baselineOffset =
3075 appUnitsPerDevUnit * (fontMetrics.emAscent - fontMetrics.emDescent) / 2;
3078 RunMetrics metrics;
3079 metrics.mAscent = fontMetrics.maxAscent * appUnitsPerDevUnit;
3080 metrics.mDescent = fontMetrics.maxDescent * appUnitsPerDevUnit;
3082 if (aStart == aEnd) {
3083 // exit now before we look at aSpacing[0], which is undefined
3084 metrics.mAscent -= baselineOffset;
3085 metrics.mDescent += baselineOffset;
3086 metrics.mBoundingBox =
3087 gfxRect(0, -metrics.mAscent, 0, metrics.mAscent + metrics.mDescent);
3088 return metrics;
3091 gfxFloat advanceMin = 0, advanceMax = 0;
3092 bool isRTL = aTextRun->IsRightToLeft();
3093 bool needsGlyphExtents = NeedsGlyphExtents(this, aTextRun);
3094 gfxGlyphExtents* extents =
3095 ((aBoundingBoxType == LOOSE_INK_EXTENTS && !needsGlyphExtents &&
3096 !aTextRun->HasDetailedGlyphs()) ||
3097 MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero()))
3098 ? nullptr
3099 : GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit());
3101 bool allGlyphsInvisible;
3102 if (extents) {
3103 allGlyphsInvisible = MeasureGlyphs(
3104 aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget, aSpacing,
3105 extents, isRTL, needsGlyphExtents, metrics, &advanceMin, &advanceMax);
3106 } else {
3107 allGlyphsInvisible =
3108 MeasureGlyphs(aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget,
3109 aSpacing, isRTL, metrics);
3112 if (allGlyphsInvisible) {
3113 metrics.mBoundingBox.SetEmpty();
3114 } else if (aBoundingBoxType == LOOSE_INK_EXTENTS) {
3115 UnionRange(metrics.mAdvanceWidth, &advanceMin, &advanceMax);
3116 gfxRect fontBox(advanceMin, -metrics.mAscent, advanceMax - advanceMin,
3117 metrics.mAscent + metrics.mDescent);
3118 metrics.mBoundingBox = metrics.mBoundingBox.Union(fontBox);
3121 if (isRTL) {
3122 // Reverse the effect of having swapped each glyph's sidebearings, to get
3123 // the correct sidebearings of the merged bounding box.
3124 metrics.mBoundingBox.MoveToX(metrics.mAdvanceWidth -
3125 metrics.mBoundingBox.XMost());
3128 // If the font may be rendered with a fake-italic effect, we need to allow
3129 // for the top-right of the glyphs being skewed to the right, and the
3130 // bottom-left being skewed further left.
3131 gfxFloat skew = SkewForSyntheticOblique();
3132 if (skew != 0.0) {
3133 gfxFloat extendLeftEdge, extendRightEdge;
3134 if (orientation == nsFontMetrics::eVertical) {
3135 // The glyph will actually be skewed vertically, but "left" and "right"
3136 // here refer to line-left (physical top) and -right (bottom), so these
3137 // are still the directions in which we need to extend the box.
3138 extendLeftEdge = skew < 0.0 ? ceil(-skew * metrics.mBoundingBox.XMost())
3139 : ceil(skew * -metrics.mBoundingBox.X());
3140 extendRightEdge = skew < 0.0 ? ceil(-skew * -metrics.mBoundingBox.X())
3141 : ceil(skew * metrics.mBoundingBox.XMost());
3142 } else {
3143 extendLeftEdge = skew < 0.0 ? ceil(-skew * -metrics.mBoundingBox.Y())
3144 : ceil(skew * metrics.mBoundingBox.YMost());
3145 extendRightEdge = skew < 0.0 ? ceil(-skew * metrics.mBoundingBox.YMost())
3146 : ceil(skew * -metrics.mBoundingBox.Y());
3148 metrics.mBoundingBox.SetWidth(metrics.mBoundingBox.Width() +
3149 extendLeftEdge + extendRightEdge);
3150 metrics.mBoundingBox.MoveByX(-extendLeftEdge);
3153 if (baselineOffset != 0) {
3154 metrics.mAscent -= baselineOffset;
3155 metrics.mDescent += baselineOffset;
3156 metrics.mBoundingBox.MoveByY(baselineOffset);
3159 return metrics;
3162 bool gfxFont::AgeCachedWords() {
3163 mozilla::AutoWriteLock lock(mLock);
3164 if (mWordCache) {
3165 for (auto it = mWordCache->modIter(); !it.done(); it.next()) {
3166 auto& entry = it.get().value();
3167 if (!entry) {
3168 NS_ASSERTION(entry, "cache entry has no gfxShapedWord!");
3169 it.remove();
3170 } else if (entry->IncrementAge() == kShapedWordCacheMaxAge) {
3171 it.remove();
3174 return mWordCache->empty();
3176 return true;
3179 void gfxFont::NotifyGlyphsChanged() const {
3180 AutoReadLock lock(mLock);
3181 uint32_t i, count = mGlyphExtentsArray.Length();
3182 for (i = 0; i < count; ++i) {
3183 // Flush cached extents array
3184 mGlyphExtentsArray[i]->NotifyGlyphsChanged();
3187 if (mGlyphChangeObservers) {
3188 for (const auto& key : *mGlyphChangeObservers) {
3189 key->NotifyGlyphsChanged();
3194 // If aChar is a "word boundary" for shaped-word caching purposes, return it;
3195 // else return 0.
3196 static char16_t IsBoundarySpace(char16_t aChar, char16_t aNextChar) {
3197 if ((aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar)) {
3198 return aChar;
3200 return 0;
3203 // In 8-bit text, there cannot be any cluster-extenders.
3204 static uint8_t IsBoundarySpace(uint8_t aChar, uint8_t aNextChar) {
3205 if (aChar == ' ' || aChar == 0x00A0) {
3206 return aChar;
3208 return 0;
3211 #ifdef __GNUC__
3212 # define GFX_MAYBE_UNUSED __attribute__((unused))
3213 #else
3214 # define GFX_MAYBE_UNUSED
3215 #endif
3217 template <typename T, typename Func>
3218 bool gfxFont::ProcessShapedWordInternal(
3219 DrawTarget* aDrawTarget, const T* aText, uint32_t aLength, uint32_t aHash,
3220 Script aRunScript, nsAtom* aLanguage, bool aVertical,
3221 int32_t aAppUnitsPerDevUnit, gfx::ShapedTextFlags aFlags,
3222 RoundingFlags aRounding, gfxTextPerfMetrics* aTextPerf GFX_MAYBE_UNUSED,
3223 Func aCallback) {
3224 WordCacheKey key(aText, aLength, aHash, aRunScript, aLanguage,
3225 aAppUnitsPerDevUnit, aFlags, aRounding);
3227 // If we have a word cache, attempt to look up the word in it.
3228 AutoReadLock lock(mLock);
3229 if (mWordCache) {
3230 // if there's a cached entry for this word, just return it
3231 if (auto entry = mWordCache->lookup(key)) {
3232 entry->value()->ResetAge();
3233 #ifndef RELEASE_OR_BETA
3234 if (aTextPerf) {
3235 // XXX we should make sure this is atomic
3236 aTextPerf->current.wordCacheHit++;
3238 #endif
3239 aCallback(entry->value().get());
3240 return true;
3245 // We didn't find a cached word (or don't even have a cache yet), so create
3246 // a new gfxShapedWord and cache it. We don't have to lock during shaping,
3247 // only when it comes time to cache the new entry.
3249 UniquePtr<gfxShapedWord> newShapedWord(
3250 gfxShapedWord::Create(aText, aLength, aRunScript, aLanguage,
3251 aAppUnitsPerDevUnit, aFlags, aRounding));
3252 if (!newShapedWord) {
3253 NS_WARNING("failed to create gfxShapedWord - expect missing text");
3254 return false;
3256 DebugOnly<bool> ok =
3257 ShapeText(aDrawTarget, aText, 0, aLength, aRunScript, aLanguage,
3258 aVertical, aRounding, newShapedWord.get());
3259 NS_WARNING_ASSERTION(ok, "failed to shape word - expect garbled text");
3262 // We're going to cache the new shaped word, so lock for writing now.
3263 AutoWriteLock lock(mLock);
3264 if (!mWordCache) {
3265 mWordCache = MakeUnique<HashMap<WordCacheKey, UniquePtr<gfxShapedWord>,
3266 WordCacheKey::HashPolicy>>();
3267 } else {
3268 // If the cache is getting too big, flush it and start over.
3269 uint32_t wordCacheMaxEntries =
3270 gfxPlatform::GetPlatform()->WordCacheMaxEntries();
3271 if (mWordCache->count() > wordCacheMaxEntries) {
3272 // Flush the cache if it is getting overly big.
3273 NS_WARNING("flushing shaped-word cache");
3274 ClearCachedWordsLocked();
3278 // Update key so that it references the text stored in the newShapedWord,
3279 // which is guaranteed to live as long as the hashtable entry.
3280 if ((key.mTextIs8Bit = newShapedWord->TextIs8Bit())) {
3281 key.mText.mSingle = newShapedWord->Text8Bit();
3282 } else {
3283 key.mText.mDouble = newShapedWord->TextUnicode();
3285 auto entry = mWordCache->lookupForAdd(key);
3287 // It's unlikely, but maybe another thread got there before us...
3288 if (entry) {
3289 // Use the existing entry; the newShapedWord will be discarded.
3290 entry->value()->ResetAge();
3291 #ifndef RELEASE_OR_BETA
3292 if (aTextPerf) {
3293 aTextPerf->current.wordCacheHit++;
3295 #endif
3296 aCallback(entry->value().get());
3297 return true;
3300 if (!mWordCache->add(entry, key, std::move(newShapedWord))) {
3301 NS_WARNING("failed to cache gfxShapedWord - expect missing text");
3302 return false;
3305 #ifndef RELEASE_OR_BETA
3306 if (aTextPerf) {
3307 aTextPerf->current.wordCacheMiss++;
3309 #endif
3310 aCallback(entry->value().get());
3313 gfxFontCache::GetCache()->RunWordCacheExpirationTimer();
3314 return true;
3317 bool gfxFont::WordCacheKey::HashPolicy::match(const Key& aKey,
3318 const Lookup& aLookup) {
3319 if (aKey.mLength != aLookup.mLength || aKey.mFlags != aLookup.mFlags ||
3320 aKey.mRounding != aLookup.mRounding ||
3321 aKey.mAppUnitsPerDevUnit != aLookup.mAppUnitsPerDevUnit ||
3322 aKey.mScript != aLookup.mScript || aKey.mLanguage != aLookup.mLanguage) {
3323 return false;
3326 if (aKey.mTextIs8Bit) {
3327 if (aLookup.mTextIs8Bit) {
3328 return (0 == memcmp(aKey.mText.mSingle, aLookup.mText.mSingle,
3329 aKey.mLength * sizeof(uint8_t)));
3331 // The lookup key has 16-bit text, even though all the characters are < 256,
3332 // so the TEXT_IS_8BIT flag was set and the cached ShapedWord we're
3333 // comparing with will have 8-bit text.
3334 const uint8_t* s1 = aKey.mText.mSingle;
3335 const char16_t* s2 = aLookup.mText.mDouble;
3336 const char16_t* s2end = s2 + aKey.mLength;
3337 while (s2 < s2end) {
3338 if (*s1++ != *s2++) {
3339 return false;
3342 return true;
3344 NS_ASSERTION(!(aLookup.mFlags & gfx::ShapedTextFlags::TEXT_IS_8BIT) &&
3345 !aLookup.mTextIs8Bit,
3346 "didn't expect 8-bit text here");
3347 return (0 == memcmp(aKey.mText.mDouble, aLookup.mText.mDouble,
3348 aKey.mLength * sizeof(char16_t)));
3351 bool gfxFont::ProcessSingleSpaceShapedWord(
3352 DrawTarget* aDrawTarget, bool aVertical, int32_t aAppUnitsPerDevUnit,
3353 gfx::ShapedTextFlags aFlags, RoundingFlags aRounding,
3354 const std::function<void(gfxShapedWord*)>& aCallback) {
3355 static const uint8_t space = ' ';
3356 return ProcessShapedWordInternal(
3357 aDrawTarget, &space, 1, gfxShapedWord::HashMix(0, ' '), Script::LATIN,
3358 /* aLanguage = */ nullptr, aVertical, aAppUnitsPerDevUnit, aFlags,
3359 aRounding, nullptr, aCallback);
3362 bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const uint8_t* aText,
3363 uint32_t aOffset, uint32_t aLength, Script aScript,
3364 nsAtom* aLanguage, bool aVertical,
3365 RoundingFlags aRounding, gfxShapedText* aShapedText) {
3366 nsDependentCSubstring ascii((const char*)aText, aLength);
3367 nsAutoString utf16;
3368 AppendASCIItoUTF16(ascii, utf16);
3369 if (utf16.Length() != aLength) {
3370 return false;
3372 return ShapeText(aDrawTarget, utf16.BeginReading(), aOffset, aLength, aScript,
3373 aLanguage, aVertical, aRounding, aShapedText);
3376 bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText,
3377 uint32_t aOffset, uint32_t aLength, Script aScript,
3378 nsAtom* aLanguage, bool aVertical,
3379 RoundingFlags aRounding, gfxShapedText* aShapedText) {
3380 // XXX Currently, we do all vertical shaping through harfbuzz.
3381 // Vertical graphite support may be wanted as a future enhancement.
3382 // XXX Graphite shaping currently only supported on the main thread!
3383 // Worker-thread shaping (offscreen canvas) will always go via harfbuzz.
3384 if (FontCanSupportGraphite() && !aVertical && NS_IsMainThread()) {
3385 if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
3386 gfxGraphiteShaper* shaper = mGraphiteShaper;
3387 if (!shaper) {
3388 shaper = new gfxGraphiteShaper(this);
3389 if (mGraphiteShaper.compareExchange(nullptr, shaper)) {
3390 Telemetry::ScalarAdd(Telemetry::ScalarID::BROWSER_USAGE_GRAPHITE, 1);
3391 } else {
3392 delete shaper;
3393 shaper = mGraphiteShaper;
3396 if (shaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript,
3397 aLanguage, aVertical, aRounding, aShapedText)) {
3398 PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical,
3399 aShapedText);
3400 return true;
3405 gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper();
3406 if (shaper &&
3407 shaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript,
3408 aLanguage, aVertical, aRounding, aShapedText)) {
3409 PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical,
3410 aShapedText);
3411 if (GetFontEntry()->HasTrackingTable()) {
3412 // Convert font size from device pixels back to CSS px
3413 // to use in selecting tracking value
3414 gfxFloat trackSize = GetAdjustedSize() *
3415 aShapedText->GetAppUnitsPerDevUnit() /
3416 AppUnitsPerCSSPixel();
3417 // Usually, a given font will be used with the same appunit scale, so we
3418 // can cache the tracking value rather than recompute it every time.
3420 AutoReadLock lock(mLock);
3421 if (trackSize == mCachedTrackingSize) {
3422 // Applying tracking is a lot like the adjustment we do for
3423 // synthetic bold: we want to apply between clusters, not to
3424 // non-spacing glyphs within a cluster. So we can reuse that
3425 // helper here.
3426 aShapedText->ApplyTrackingToClusters(mTracking, aOffset, aLength);
3427 return true;
3430 // We didn't have the appropriate tracking value cached yet.
3431 AutoWriteLock lock(mLock);
3432 if (trackSize != mCachedTrackingSize) {
3433 mCachedTrackingSize = trackSize;
3434 mTracking =
3435 GetFontEntry()->TrackingForCSSPx(trackSize) * mFUnitsConvFactor;
3437 aShapedText->ApplyTrackingToClusters(mTracking, aOffset, aLength);
3439 return true;
3442 NS_WARNING_ASSERTION(false, "shaper failed, expect scrambled/missing text");
3443 return false;
3446 void gfxFont::PostShapingFixup(DrawTarget* aDrawTarget, const char16_t* aText,
3447 uint32_t aOffset, uint32_t aLength,
3448 bool aVertical, gfxShapedText* aShapedText) {
3449 if (ApplySyntheticBold()) {
3450 const Metrics& metrics = GetMetrics(aVertical ? nsFontMetrics::eVertical
3451 : nsFontMetrics::eHorizontal);
3452 if (metrics.maxAdvance > metrics.aveCharWidth) {
3453 aShapedText->ApplyTrackingToClusters(GetSyntheticBoldOffset(), aOffset,
3454 aLength);
3459 #define MAX_SHAPING_LENGTH \
3460 32760 // slightly less than 32K, trying to avoid
3461 // over-stressing platform shapers
3462 #define BACKTRACK_LIMIT \
3463 16 // backtrack this far looking for a good place
3464 // to split into fragments for separate shaping
3466 template <typename T>
3467 bool gfxFont::ShapeFragmentWithoutWordCache(DrawTarget* aDrawTarget,
3468 const T* aText, uint32_t aOffset,
3469 uint32_t aLength, Script aScript,
3470 nsAtom* aLanguage, bool aVertical,
3471 RoundingFlags aRounding,
3472 gfxTextRun* aTextRun) {
3473 aTextRun->SetupClusterBoundaries(aOffset, aText, aLength);
3475 bool ok = true;
3477 while (ok && aLength > 0) {
3478 uint32_t fragLen = aLength;
3480 // limit the length of text we pass to shapers in a single call
3481 if (fragLen > MAX_SHAPING_LENGTH) {
3482 fragLen = MAX_SHAPING_LENGTH;
3484 // in the 8-bit case, there are no multi-char clusters,
3485 // so we don't need to do this check
3486 if constexpr (sizeof(T) == sizeof(char16_t)) {
3487 uint32_t i;
3488 for (i = 0; i < BACKTRACK_LIMIT; ++i) {
3489 if (aTextRun->IsClusterStart(aOffset + fragLen - i)) {
3490 fragLen -= i;
3491 break;
3494 if (i == BACKTRACK_LIMIT) {
3495 // if we didn't find any cluster start while backtracking,
3496 // just check that we're not in the middle of a surrogate
3497 // pair; back up by one code unit if we are.
3498 if (NS_IS_SURROGATE_PAIR(aText[fragLen - 1], aText[fragLen])) {
3499 --fragLen;
3505 ok = ShapeText(aDrawTarget, aText, aOffset, fragLen, aScript, aLanguage,
3506 aVertical, aRounding, aTextRun);
3508 aText += fragLen;
3509 aOffset += fragLen;
3510 aLength -= fragLen;
3513 return ok;
3516 // Check if aCh is an unhandled control character that should be displayed
3517 // as a hexbox rather than rendered by some random font on the system.
3518 // We exclude \r as stray &#13;s are rather common (bug 941940).
3519 // Note that \n and \t don't come through here, as they have specific
3520 // meanings that have already been handled.
3521 static bool IsInvalidControlChar(uint32_t aCh) {
3522 return aCh != '\r' && ((aCh & 0x7f) < 0x20 || aCh == 0x7f);
3525 template <typename T>
3526 bool gfxFont::ShapeTextWithoutWordCache(DrawTarget* aDrawTarget, const T* aText,
3527 uint32_t aOffset, uint32_t aLength,
3528 Script aScript, nsAtom* aLanguage,
3529 bool aVertical, RoundingFlags aRounding,
3530 gfxTextRun* aTextRun) {
3531 uint32_t fragStart = 0;
3532 bool ok = true;
3534 for (uint32_t i = 0; i <= aLength && ok; ++i) {
3535 T ch = (i < aLength) ? aText[i] : '\n';
3536 bool invalid = gfxFontGroup::IsInvalidChar(ch);
3537 uint32_t length = i - fragStart;
3539 // break into separate fragments when we hit an invalid char
3540 if (!invalid) {
3541 continue;
3544 if (length > 0) {
3545 ok = ShapeFragmentWithoutWordCache(
3546 aDrawTarget, aText + fragStart, aOffset + fragStart, length, aScript,
3547 aLanguage, aVertical, aRounding, aTextRun);
3550 if (i == aLength) {
3551 break;
3554 // fragment was terminated by an invalid char: skip it,
3555 // unless it's a control char that we want to show as a hexbox,
3556 // but record where TAB or NEWLINE occur
3557 if (ch == '\t') {
3558 aTextRun->SetIsTab(aOffset + i);
3559 } else if (ch == '\n') {
3560 aTextRun->SetIsNewline(aOffset + i);
3561 } else if (GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_FORMAT) {
3562 aTextRun->SetIsFormattingControl(aOffset + i);
3563 } else if (IsInvalidControlChar(ch) &&
3564 !(aTextRun->GetFlags() &
3565 gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS)) {
3566 if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) {
3567 ShapeFragmentWithoutWordCache(aDrawTarget, aText + i, aOffset + i, 1,
3568 aScript, aLanguage, aVertical, aRounding,
3569 aTextRun);
3570 } else {
3571 aTextRun->SetMissingGlyph(aOffset + i, ch, this);
3574 fragStart = i + 1;
3577 NS_WARNING_ASSERTION(ok, "failed to shape text - expect garbled text");
3578 return ok;
3581 #ifndef RELEASE_OR_BETA
3582 # define TEXT_PERF_INCR(tp, m) (tp ? (tp)->current.m++ : 0)
3583 #else
3584 # define TEXT_PERF_INCR(tp, m)
3585 #endif
3587 inline static bool IsChar8Bit(uint8_t /*aCh*/) { return true; }
3588 inline static bool IsChar8Bit(char16_t aCh) { return aCh < 0x100; }
3590 inline static bool HasSpaces(const uint8_t* aString, uint32_t aLen) {
3591 return memchr(aString, 0x20, aLen) != nullptr;
3594 inline static bool HasSpaces(const char16_t* aString, uint32_t aLen) {
3595 for (const char16_t* ch = aString; ch < aString + aLen; ch++) {
3596 if (*ch == 0x20) {
3597 return true;
3600 return false;
3603 template <typename T>
3604 bool gfxFont::SplitAndInitTextRun(
3605 DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
3606 const T* aString, // text for this font run
3607 uint32_t aRunStart, // position in the textrun
3608 uint32_t aRunLength, Script aRunScript, nsAtom* aLanguage,
3609 ShapedTextFlags aOrientation) {
3610 if (aRunLength == 0) {
3611 return true;
3614 gfxTextPerfMetrics* tp = nullptr;
3615 RoundingFlags rounding = GetRoundOffsetsToPixels(aDrawTarget);
3617 #ifndef RELEASE_OR_BETA
3618 tp = aTextRun->GetFontGroup()->GetTextPerfMetrics();
3619 if (tp) {
3620 if (mStyle.systemFont) {
3621 tp->current.numChromeTextRuns++;
3622 } else {
3623 tp->current.numContentTextRuns++;
3625 tp->current.numChars += aRunLength;
3626 if (aRunLength > tp->current.maxTextRunLen) {
3627 tp->current.maxTextRunLen = aRunLength;
3630 #endif
3632 uint32_t wordCacheCharLimit =
3633 gfxPlatform::GetPlatform()->WordCacheCharLimit();
3635 bool vertical = aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
3637 // If spaces can participate in shaping (e.g. within lookups for automatic
3638 // fractions), need to shape without using the word cache which segments
3639 // textruns on space boundaries. Word cache can be used if the textrun
3640 // is short enough to fit in the word cache and it lacks spaces.
3641 tainted_boolean_hint t_canParticipate =
3642 SpaceMayParticipateInShaping(aRunScript);
3643 bool canParticipate = t_canParticipate.unverified_safe_because(
3644 "We need to ensure that this function operates safely independent of "
3645 "t_canParticipate. The worst that can happen here is that the decision "
3646 "to use the cache is incorrectly made, resulting in a bad "
3647 "rendering/slowness. However, this would not compromise the memory "
3648 "safety of Firefox in any way, and can thus be permitted");
3650 if (canParticipate) {
3651 if (aRunLength > wordCacheCharLimit || HasSpaces(aString, aRunLength)) {
3652 TEXT_PERF_INCR(tp, wordCacheSpaceRules);
3653 return ShapeTextWithoutWordCache(aDrawTarget, aString, aRunStart,
3654 aRunLength, aRunScript, aLanguage,
3655 vertical, rounding, aTextRun);
3659 // the only flags we care about for ShapedWord construction/caching
3660 gfx::ShapedTextFlags flags = aTextRun->GetFlags();
3661 flags &= (gfx::ShapedTextFlags::TEXT_IS_RTL |
3662 gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES |
3663 gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT |
3664 gfx::ShapedTextFlags::TEXT_ORIENT_MASK);
3665 if constexpr (sizeof(T) == sizeof(uint8_t)) {
3666 flags |= gfx::ShapedTextFlags::TEXT_IS_8BIT;
3669 uint32_t wordStart = 0;
3670 uint32_t hash = 0;
3671 bool wordIs8Bit = true;
3672 int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
3674 T nextCh = aString[0];
3675 for (uint32_t i = 0; i <= aRunLength; ++i) {
3676 T ch = nextCh;
3677 nextCh = (i < aRunLength - 1) ? aString[i + 1] : '\n';
3678 T boundary = IsBoundarySpace(ch, nextCh);
3679 bool invalid = !boundary && gfxFontGroup::IsInvalidChar(ch);
3680 uint32_t length = i - wordStart;
3682 // break into separate ShapedWords when we hit an invalid char,
3683 // or a boundary space (always handled individually),
3684 // or the first non-space after a space
3685 if (!boundary && !invalid) {
3686 if (!IsChar8Bit(ch)) {
3687 wordIs8Bit = false;
3689 // include this character in the hash, and move on to next
3690 hash = gfxShapedWord::HashMix(hash, ch);
3691 continue;
3694 // We've decided to break here (i.e. we're at the end of a "word");
3695 // shape the word and add it to the textrun.
3696 // For words longer than the limit, we don't use the
3697 // font's word cache but just shape directly into the textrun.
3698 if (length > wordCacheCharLimit) {
3699 TEXT_PERF_INCR(tp, wordCacheLong);
3700 bool ok = ShapeFragmentWithoutWordCache(
3701 aDrawTarget, aString + wordStart, aRunStart + wordStart, length,
3702 aRunScript, aLanguage, vertical, rounding, aTextRun);
3703 if (!ok) {
3704 return false;
3706 } else if (length > 0) {
3707 gfx::ShapedTextFlags wordFlags = flags;
3708 // in the 8-bit version of this method, TEXT_IS_8BIT was
3709 // already set as part of |flags|, so no need for a per-word
3710 // adjustment here
3711 if (sizeof(T) == sizeof(char16_t)) {
3712 if (wordIs8Bit) {
3713 wordFlags |= gfx::ShapedTextFlags::TEXT_IS_8BIT;
3716 bool processed = ProcessShapedWordInternal(
3717 aDrawTarget, aString + wordStart, length, hash, aRunScript, aLanguage,
3718 vertical, appUnitsPerDevUnit, wordFlags, rounding, tp,
3719 [&](gfxShapedWord* aShapedWord) {
3720 aTextRun->CopyGlyphDataFrom(aShapedWord, aRunStart + wordStart);
3722 if (!processed) {
3723 return false; // failed, presumably out of memory?
3727 if (boundary) {
3728 // word was terminated by a space: add that to the textrun
3729 MOZ_ASSERT(aOrientation != ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED,
3730 "text-orientation:mixed should be resolved earlier");
3731 if (boundary != ' ' || !aTextRun->SetSpaceGlyphIfSimple(
3732 this, aRunStart + i, ch, aOrientation)) {
3733 // Currently, the only "boundary" characters we recognize are
3734 // space and no-break space, which are both 8-bit, so we force
3735 // that flag (below). If we ever change IsBoundarySpace, we
3736 // may need to revise this.
3737 // Avoid tautological-constant-out-of-range-compare in 8-bit:
3738 DebugOnly<char16_t> boundary16 = boundary;
3739 NS_ASSERTION(boundary16 < 256, "unexpected boundary!");
3740 bool processed = ProcessShapedWordInternal(
3741 aDrawTarget, &boundary, 1, gfxShapedWord::HashMix(0, boundary),
3742 aRunScript, aLanguage, vertical, appUnitsPerDevUnit,
3743 flags | gfx::ShapedTextFlags::TEXT_IS_8BIT, rounding, tp,
3744 [&](gfxShapedWord* aShapedWord) {
3745 aTextRun->CopyGlyphDataFrom(aShapedWord, aRunStart + i);
3746 if (boundary == ' ') {
3747 aTextRun->GetCharacterGlyphs()[aRunStart + i].SetIsSpace();
3750 if (!processed) {
3751 return false;
3754 hash = 0;
3755 wordStart = i + 1;
3756 wordIs8Bit = true;
3757 continue;
3760 if (i == aRunLength) {
3761 break;
3764 NS_ASSERTION(invalid, "how did we get here except via an invalid char?");
3766 // word was terminated by an invalid char: skip it,
3767 // unless it's a control char that we want to show as a hexbox,
3768 // but record where TAB or NEWLINE occur
3769 if (ch == '\t') {
3770 aTextRun->SetIsTab(aRunStart + i);
3771 } else if (ch == '\n') {
3772 aTextRun->SetIsNewline(aRunStart + i);
3773 } else if (GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_FORMAT) {
3774 aTextRun->SetIsFormattingControl(aRunStart + i);
3775 } else if (IsInvalidControlChar(ch) &&
3776 !(aTextRun->GetFlags() &
3777 gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS)) {
3778 if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) {
3779 ShapeFragmentWithoutWordCache(aDrawTarget, aString + i, aRunStart + i,
3780 1, aRunScript, aLanguage, vertical,
3781 rounding, aTextRun);
3782 } else {
3783 aTextRun->SetMissingGlyph(aRunStart + i, ch, this);
3787 hash = 0;
3788 wordStart = i + 1;
3789 wordIs8Bit = true;
3792 return true;
3795 // Explicit instantiations of SplitAndInitTextRun, to avoid libxul link failure
3796 template bool gfxFont::SplitAndInitTextRun(
3797 DrawTarget* aDrawTarget, gfxTextRun* aTextRun, const uint8_t* aString,
3798 uint32_t aRunStart, uint32_t aRunLength, Script aRunScript,
3799 nsAtom* aLanguage, ShapedTextFlags aOrientation);
3800 template bool gfxFont::SplitAndInitTextRun(
3801 DrawTarget* aDrawTarget, gfxTextRun* aTextRun, const char16_t* aString,
3802 uint32_t aRunStart, uint32_t aRunLength, Script aRunScript,
3803 nsAtom* aLanguage, ShapedTextFlags aOrientation);
3805 template <>
3806 bool gfxFont::InitFakeSmallCapsRun(
3807 nsPresContext* aPresContext, DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
3808 const char16_t* aText, uint32_t aOffset, uint32_t aLength,
3809 FontMatchType aMatchType, gfx::ShapedTextFlags aOrientation, Script aScript,
3810 nsAtom* aLanguage, bool aSyntheticLower, bool aSyntheticUpper) {
3811 bool ok = true;
3813 RefPtr<gfxFont> smallCapsFont = GetSmallCapsFont();
3814 if (!smallCapsFont) {
3815 NS_WARNING("failed to get reduced-size font for smallcaps!");
3816 smallCapsFont = this;
3819 bool isCJK = gfxTextRun::IsCJKScript(aScript);
3821 enum RunCaseAction { kNoChange, kUppercaseReduce, kUppercase };
3823 RunCaseAction runAction = kNoChange;
3824 uint32_t runStart = 0;
3826 for (uint32_t i = 0; i <= aLength; ++i) {
3827 uint32_t extraCodeUnits = 0; // Will be set to 1 if we need to consume
3828 // a trailing surrogate as well as the
3829 // current code unit.
3830 RunCaseAction chAction = kNoChange;
3831 // Unless we're at the end, figure out what treatment the current
3832 // character will need.
3833 if (i < aLength) {
3834 uint32_t ch = aText[i];
3835 if (i < aLength - 1 && NS_IS_SURROGATE_PAIR(ch, aText[i + 1])) {
3836 ch = SURROGATE_TO_UCS4(ch, aText[i + 1]);
3837 extraCodeUnits = 1;
3839 // Characters that aren't the start of a cluster are ignored here.
3840 // They get added to whatever lowercase/non-lowercase run we're in.
3841 if (IsClusterExtender(ch)) {
3842 chAction = runAction;
3843 } else {
3844 if (ch != ToUpperCase(ch) || SpecialUpper(ch)) {
3845 // ch is lower case
3846 chAction = (aSyntheticLower ? kUppercaseReduce : kNoChange);
3847 } else if (ch != ToLowerCase(ch)) {
3848 // ch is upper case
3849 chAction = (aSyntheticUpper ? kUppercaseReduce : kNoChange);
3850 if (aLanguage == nsGkAtoms::el) {
3851 // In Greek, check for characters that will be modified by
3852 // the GreekUpperCase mapping - this catches accented
3853 // capitals where the accent is to be removed (bug 307039).
3854 // These are handled by using the full-size font with the
3855 // uppercasing transform.
3856 mozilla::GreekCasing::State state;
3857 bool markEta, updateEta;
3858 uint32_t ch2 =
3859 mozilla::GreekCasing::UpperCase(ch, state, markEta, updateEta);
3860 if ((ch != ch2 || markEta) && !aSyntheticUpper) {
3861 chAction = kUppercase;
3868 // At the end of the text or when the current character needs different
3869 // casing treatment from the current run, finish the run-in-progress
3870 // and prepare to accumulate a new run.
3871 // Note that we do not look at any source data for offset [i] here,
3872 // as that would be invalid in the case where i==length.
3873 if ((i == aLength || runAction != chAction) && runStart < i) {
3874 uint32_t runLength = i - runStart;
3875 gfxFont* f = this;
3876 switch (runAction) {
3877 case kNoChange:
3878 // just use the current font and the existing string
3879 aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true,
3880 aOrientation, isCJK);
3881 if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun, aText + runStart,
3882 aOffset + runStart, runLength, aScript,
3883 aLanguage, aOrientation)) {
3884 ok = false;
3886 break;
3888 case kUppercaseReduce:
3889 // use reduced-size font, then fall through to uppercase the text
3890 f = smallCapsFont;
3891 [[fallthrough]];
3893 case kUppercase:
3894 // apply uppercase transform to the string
3895 nsDependentSubstring origString(aText + runStart, runLength);
3896 nsAutoString convertedString;
3897 AutoTArray<bool, 50> charsToMergeArray;
3898 AutoTArray<bool, 50> deletedCharsArray;
3900 StyleTextTransform globalTransform{StyleTextTransformCase::Uppercase,
3901 {}};
3902 // No mask needed; we're doing case conversion, not password-hiding.
3903 const char16_t maskChar = 0;
3904 bool mergeNeeded = nsCaseTransformTextRunFactory::TransformString(
3905 origString, convertedString, Some(globalTransform), maskChar,
3906 /* aCaseTransformsOnly = */ false, aLanguage, charsToMergeArray,
3907 deletedCharsArray);
3909 // Check whether the font supports the uppercased characters needed;
3910 // if not, we're not going to be able to simulate small-caps.
3911 bool failed = false;
3912 char16_t highSurrogate = 0;
3913 for (const char16_t* cp = convertedString.BeginReading();
3914 cp != convertedString.EndReading(); ++cp) {
3915 if (NS_IS_HIGH_SURROGATE(*cp)) {
3916 highSurrogate = *cp;
3917 continue;
3919 uint32_t ch = *cp;
3920 if (NS_IS_LOW_SURROGATE(*cp) && highSurrogate) {
3921 ch = SURROGATE_TO_UCS4(highSurrogate, *cp);
3923 highSurrogate = 0;
3924 if (!f->HasCharacter(ch)) {
3925 if (IsDefaultIgnorable(ch)) {
3926 continue;
3928 failed = true;
3929 break;
3932 // Required uppercase letter(s) missing from the font. Just use the
3933 // original text with the original font, no fake small caps!
3934 if (failed) {
3935 convertedString = origString;
3936 mergeNeeded = false;
3937 f = this;
3940 if (mergeNeeded) {
3941 // This is the hard case: the transformation caused chars
3942 // to be inserted or deleted, so we can't shape directly
3943 // into the destination textrun but have to handle the
3944 // mismatch of character positions.
3945 gfxTextRunFactory::Parameters params = {
3946 aDrawTarget, nullptr, nullptr,
3947 nullptr, 0, aTextRun->GetAppUnitsPerDevUnit()};
3948 RefPtr<gfxTextRun> tempRun(gfxTextRun::Create(
3949 &params, convertedString.Length(), aTextRun->GetFontGroup(),
3950 gfx::ShapedTextFlags(), nsTextFrameUtils::Flags()));
3951 tempRun->AddGlyphRun(f, aMatchType, 0, true, aOrientation, isCJK);
3952 if (!f->SplitAndInitTextRun(aDrawTarget, tempRun.get(),
3953 convertedString.BeginReading(), 0,
3954 convertedString.Length(), aScript,
3955 aLanguage, aOrientation)) {
3956 ok = false;
3957 } else {
3958 RefPtr<gfxTextRun> mergedRun(gfxTextRun::Create(
3959 &params, runLength, aTextRun->GetFontGroup(),
3960 gfx::ShapedTextFlags(), nsTextFrameUtils::Flags()));
3961 MergeCharactersInTextRun(mergedRun.get(), tempRun.get(),
3962 charsToMergeArray.Elements(),
3963 deletedCharsArray.Elements());
3964 gfxTextRun::Range runRange(0, runLength);
3965 aTextRun->CopyGlyphDataFrom(mergedRun.get(), runRange,
3966 aOffset + runStart);
3968 } else {
3969 aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true,
3970 aOrientation, isCJK);
3971 if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun,
3972 convertedString.BeginReading(),
3973 aOffset + runStart, runLength, aScript,
3974 aLanguage, aOrientation)) {
3975 ok = false;
3978 break;
3981 runStart = i;
3984 i += extraCodeUnits;
3985 if (i < aLength) {
3986 runAction = chAction;
3990 return ok;
3993 template <>
3994 bool gfxFont::InitFakeSmallCapsRun(
3995 nsPresContext* aPresContext, DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
3996 const uint8_t* aText, uint32_t aOffset, uint32_t aLength,
3997 FontMatchType aMatchType, gfx::ShapedTextFlags aOrientation, Script aScript,
3998 nsAtom* aLanguage, bool aSyntheticLower, bool aSyntheticUpper) {
3999 NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aText),
4000 aLength);
4001 return InitFakeSmallCapsRun(aPresContext, aDrawTarget, aTextRun,
4002 static_cast<const char16_t*>(unicodeString.get()),
4003 aOffset, aLength, aMatchType, aOrientation,
4004 aScript, aLanguage, aSyntheticLower,
4005 aSyntheticUpper);
4008 already_AddRefed<gfxFont> gfxFont::GetSmallCapsFont() const {
4009 gfxFontStyle style(*GetStyle());
4010 style.size *= SMALL_CAPS_SCALE_FACTOR;
4011 style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
4012 gfxFontEntry* fe = GetFontEntry();
4013 return fe->FindOrMakeFont(&style, mUnicodeRangeMap);
4016 already_AddRefed<gfxFont> gfxFont::GetSubSuperscriptFont(
4017 int32_t aAppUnitsPerDevPixel) const {
4018 gfxFontStyle style(*GetStyle());
4019 style.AdjustForSubSuperscript(aAppUnitsPerDevPixel);
4020 gfxFontEntry* fe = GetFontEntry();
4021 return fe->FindOrMakeFont(&style, mUnicodeRangeMap);
4024 gfxGlyphExtents* gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit) {
4025 uint32_t readCount;
4027 AutoReadLock lock(mLock);
4028 readCount = mGlyphExtentsArray.Length();
4029 for (uint32_t i = 0; i < readCount; ++i) {
4030 if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit)
4031 return mGlyphExtentsArray[i].get();
4034 AutoWriteLock lock(mLock);
4035 // Re-check in case of race.
4036 uint32_t count = mGlyphExtentsArray.Length();
4037 for (uint32_t i = readCount; i < count; ++i) {
4038 if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit)
4039 return mGlyphExtentsArray[i].get();
4041 gfxGlyphExtents* glyphExtents = new gfxGlyphExtents(aAppUnitsPerDevUnit);
4042 if (glyphExtents) {
4043 mGlyphExtentsArray.AppendElement(glyphExtents);
4044 // Initialize the extents of a space glyph, assuming that spaces don't
4045 // render anything!
4046 glyphExtents->SetContainedGlyphWidthAppUnits(GetSpaceGlyph(), 0);
4048 return glyphExtents;
4051 void gfxFont::SetupGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphID,
4052 bool aNeedTight, gfxGlyphExtents* aExtents) {
4053 gfxRect svgBounds;
4054 if (mFontEntry->TryGetSVGData(this) && mFontEntry->HasSVGGlyph(aGlyphID) &&
4055 mFontEntry->GetSVGGlyphExtents(aDrawTarget, aGlyphID, GetAdjustedSize(),
4056 &svgBounds)) {
4057 gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit();
4058 aExtents->SetTightGlyphExtents(
4059 aGlyphID, gfxRect(svgBounds.X() * d2a, svgBounds.Y() * d2a,
4060 svgBounds.Width() * d2a, svgBounds.Height() * d2a));
4061 return;
4064 if (mFontEntry->TryGetColorGlyphs() && mFontEntry->mCOLR &&
4065 COLRFonts::GetColrTableVersion(mFontEntry->mCOLR) == 1) {
4066 auto* shaper = GetHarfBuzzShaper();
4067 if (shaper && shaper->IsInitialized()) {
4068 RefPtr scaledFont = GetScaledFont(aDrawTarget);
4069 Rect r = COLRFonts::GetColorGlyphBounds(
4070 mFontEntry->mCOLR, shaper->GetHBFont(), aGlyphID, aDrawTarget,
4071 scaledFont, mFUnitsConvFactor);
4072 if (!r.IsEmpty()) {
4073 gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit();
4074 aExtents->SetTightGlyphExtents(
4075 aGlyphID, gfxRect(r.X() * d2a, r.Y() * d2a, r.Width() * d2a,
4076 r.Height() * d2a));
4077 return;
4082 gfxRect bounds;
4083 GetGlyphBounds(aGlyphID, &bounds, mAntialiasOption == kAntialiasNone);
4085 const Metrics& fontMetrics = GetMetrics(nsFontMetrics::eHorizontal);
4086 int32_t appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit();
4087 if (!aNeedTight && bounds.x >= 0.0 && bounds.y >= -fontMetrics.maxAscent &&
4088 bounds.height + bounds.y <= fontMetrics.maxDescent) {
4089 uint32_t appUnitsWidth =
4090 uint32_t(ceil((bounds.x + bounds.width) * appUnitsPerDevUnit));
4091 if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) {
4092 aExtents->SetContainedGlyphWidthAppUnits(aGlyphID,
4093 uint16_t(appUnitsWidth));
4094 return;
4097 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
4098 if (!aNeedTight) {
4099 ++gGlyphExtentsSetupFallBackToTight;
4101 #endif
4103 gfxFloat d2a = appUnitsPerDevUnit;
4104 aExtents->SetTightGlyphExtents(
4105 aGlyphID, gfxRect(bounds.x * d2a, bounds.y * d2a, bounds.width * d2a,
4106 bounds.height * d2a));
4109 // Try to initialize font metrics by reading sfnt tables directly;
4110 // set mIsValid=TRUE and return TRUE on success.
4111 // Return FALSE if the gfxFontEntry subclass does not
4112 // implement GetFontTable(), or for non-sfnt fonts where tables are
4113 // not available.
4114 // If this returns TRUE without setting the mIsValid flag, then we -did-
4115 // apparently find an sfnt, but it was too broken to be used.
4116 bool gfxFont::InitMetricsFromSfntTables(Metrics& aMetrics) {
4117 mIsValid = false; // font is NOT valid in case of early return
4119 const uint32_t kHheaTableTag = TRUETYPE_TAG('h', 'h', 'e', 'a');
4120 const uint32_t kOS_2TableTag = TRUETYPE_TAG('O', 'S', '/', '2');
4122 uint32_t len;
4124 if (mFUnitsConvFactor < 0.0) {
4125 // If the conversion factor from FUnits is not yet set,
4126 // get the unitsPerEm from the 'head' table via the font entry
4127 uint16_t unitsPerEm = GetFontEntry()->UnitsPerEm();
4128 if (unitsPerEm == gfxFontEntry::kInvalidUPEM) {
4129 return false;
4131 mFUnitsConvFactor = GetAdjustedSize() / unitsPerEm;
4134 // 'hhea' table is required for the advanceWidthMax field
4135 gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag);
4136 if (!hheaTable) {
4137 return false; // no 'hhea' table -> not an sfnt
4139 const MetricsHeader* hhea =
4140 reinterpret_cast<const MetricsHeader*>(hb_blob_get_data(hheaTable, &len));
4141 if (len < sizeof(MetricsHeader)) {
4142 return false;
4145 #define SET_UNSIGNED(field, src) \
4146 aMetrics.field = uint16_t(src) * mFUnitsConvFactor
4147 #define SET_SIGNED(field, src) aMetrics.field = int16_t(src) * mFUnitsConvFactor
4149 SET_UNSIGNED(maxAdvance, hhea->advanceWidthMax);
4151 // 'OS/2' table is optional, if not found we'll estimate xHeight
4152 // and aveCharWidth by measuring glyphs
4153 gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag);
4154 if (os2Table) {
4155 const OS2Table* os2 =
4156 reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
4157 // this should always be present in any valid OS/2 of any version
4158 if (len >= offsetof(OS2Table, xAvgCharWidth) + sizeof(int16_t)) {
4159 SET_SIGNED(aveCharWidth, os2->xAvgCharWidth);
4163 #undef SET_SIGNED
4164 #undef SET_UNSIGNED
4166 hb_font_t* hbFont = gfxHarfBuzzShaper::CreateHBFont(this);
4167 hb_position_t position;
4169 auto FixedToFloat = [](hb_position_t f) -> gfxFloat { return f / 65536.0; };
4171 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER,
4172 &position)) {
4173 aMetrics.maxAscent = FixedToFloat(position);
4175 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER,
4176 &position)) {
4177 aMetrics.maxDescent = -FixedToFloat(position);
4179 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP,
4180 &position)) {
4181 aMetrics.externalLeading = FixedToFloat(position);
4184 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_UNDERLINE_OFFSET,
4185 &position)) {
4186 aMetrics.underlineOffset = FixedToFloat(position);
4188 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_UNDERLINE_SIZE,
4189 &position)) {
4190 aMetrics.underlineSize = FixedToFloat(position);
4192 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_STRIKEOUT_OFFSET,
4193 &position)) {
4194 aMetrics.strikeoutOffset = FixedToFloat(position);
4196 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_STRIKEOUT_SIZE,
4197 &position)) {
4198 aMetrics.strikeoutSize = FixedToFloat(position);
4201 // Although sxHeight and sCapHeight are signed fields, we consider
4202 // zero/negative values to be erroneous and just ignore them.
4203 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_X_HEIGHT,
4204 &position) &&
4205 position > 0) {
4206 aMetrics.xHeight = FixedToFloat(position);
4208 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_CAP_HEIGHT,
4209 &position) &&
4210 position > 0) {
4211 aMetrics.capHeight = FixedToFloat(position);
4213 hb_font_destroy(hbFont);
4215 mIsValid = true;
4217 return true;
4220 static double RoundToNearestMultiple(double aValue, double aFraction) {
4221 return floor(aValue / aFraction + 0.5) * aFraction;
4224 void gfxFont::CalculateDerivedMetrics(Metrics& aMetrics) {
4225 aMetrics.maxAscent =
4226 ceil(RoundToNearestMultiple(aMetrics.maxAscent, 1 / 1024.0));
4227 aMetrics.maxDescent =
4228 ceil(RoundToNearestMultiple(aMetrics.maxDescent, 1 / 1024.0));
4230 if (aMetrics.xHeight <= 0) {
4231 // only happens if we couldn't find either font metrics
4232 // or a char to measure;
4233 // pick an arbitrary value that's better than zero
4234 aMetrics.xHeight = aMetrics.maxAscent * DEFAULT_XHEIGHT_FACTOR;
4237 // If we have a font that doesn't provide a capHeight value, use maxAscent
4238 // as a reasonable fallback.
4239 if (aMetrics.capHeight <= 0) {
4240 aMetrics.capHeight = aMetrics.maxAscent;
4243 aMetrics.maxHeight = aMetrics.maxAscent + aMetrics.maxDescent;
4245 if (aMetrics.maxHeight - aMetrics.emHeight > 0.0) {
4246 aMetrics.internalLeading = aMetrics.maxHeight - aMetrics.emHeight;
4247 } else {
4248 aMetrics.internalLeading = 0.0;
4251 aMetrics.emAscent =
4252 aMetrics.maxAscent * aMetrics.emHeight / aMetrics.maxHeight;
4253 aMetrics.emDescent = aMetrics.emHeight - aMetrics.emAscent;
4255 if (GetFontEntry()->IsFixedPitch()) {
4256 // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger
4257 // advance than the average character width... this forces
4258 // those fonts to be recognized like fixed pitch fonts by layout.
4259 aMetrics.maxAdvance = aMetrics.aveCharWidth;
4262 if (!aMetrics.strikeoutOffset) {
4263 aMetrics.strikeoutOffset = aMetrics.xHeight * 0.5;
4265 if (!aMetrics.strikeoutSize) {
4266 aMetrics.strikeoutSize = aMetrics.underlineSize;
4270 void gfxFont::SanitizeMetrics(gfxFont::Metrics* aMetrics,
4271 bool aIsBadUnderlineFont) {
4272 // Even if this font size is zero, this font is created with non-zero size.
4273 // However, for layout and others, we should return the metrics of zero size
4274 // font.
4275 if (mStyle.AdjustedSizeMustBeZero()) {
4276 memset(aMetrics, 0, sizeof(gfxFont::Metrics));
4277 return;
4280 // If the font entry has ascent/descent/lineGap-override values,
4281 // replace the metrics from the font with the overrides.
4282 gfxFloat adjustedSize = GetAdjustedSize();
4283 if (mFontEntry->mAscentOverride >= 0.0) {
4284 aMetrics->maxAscent = mFontEntry->mAscentOverride * adjustedSize;
4285 aMetrics->maxHeight = aMetrics->maxAscent + aMetrics->maxDescent;
4286 aMetrics->internalLeading =
4287 std::max(0.0, aMetrics->maxHeight - aMetrics->emHeight);
4289 if (mFontEntry->mDescentOverride >= 0.0) {
4290 aMetrics->maxDescent = mFontEntry->mDescentOverride * adjustedSize;
4291 aMetrics->maxHeight = aMetrics->maxAscent + aMetrics->maxDescent;
4292 aMetrics->internalLeading =
4293 std::max(0.0, aMetrics->maxHeight - aMetrics->emHeight);
4295 if (mFontEntry->mLineGapOverride >= 0.0) {
4296 aMetrics->externalLeading = mFontEntry->mLineGapOverride * adjustedSize;
4299 aMetrics->underlineSize = std::max(1.0, aMetrics->underlineSize);
4300 aMetrics->strikeoutSize = std::max(1.0, aMetrics->strikeoutSize);
4302 aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -1.0);
4304 if (aMetrics->maxAscent < 1.0) {
4305 // We cannot draw strikeout line and overline in the ascent...
4306 aMetrics->underlineSize = 0;
4307 aMetrics->underlineOffset = 0;
4308 aMetrics->strikeoutSize = 0;
4309 aMetrics->strikeoutOffset = 0;
4310 return;
4314 * Some CJK fonts have bad underline offset. Therefore, if this is such font,
4315 * we need to lower the underline offset to bottom of *em* descent.
4316 * However, if this is system font, we should not do this for the rendering
4317 * compatibility with another application's UI on the platform.
4318 * XXX Should not use this hack if the font size is too small?
4319 * Such text cannot be read, this might be used for tight CSS
4320 * rendering? (E.g., Acid2)
4322 if (!mStyle.systemFont && aIsBadUnderlineFont) {
4323 // First, we need 2 pixels between baseline and underline at least. Because
4324 // many CJK characters put their glyphs on the baseline, so, 1 pixel is too
4325 // close for CJK characters.
4326 aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -2.0);
4328 // Next, we put the underline to bottom of below of the descent space.
4329 if (aMetrics->internalLeading + aMetrics->externalLeading >
4330 aMetrics->underlineSize) {
4331 aMetrics->underlineOffset =
4332 std::min(aMetrics->underlineOffset, -aMetrics->emDescent);
4333 } else {
4334 aMetrics->underlineOffset =
4335 std::min(aMetrics->underlineOffset,
4336 aMetrics->underlineSize - aMetrics->emDescent);
4339 // If underline positioned is too far from the text, descent position is
4340 // preferred so that underline will stay within the boundary.
4341 else if (aMetrics->underlineSize - aMetrics->underlineOffset >
4342 aMetrics->maxDescent) {
4343 if (aMetrics->underlineSize > aMetrics->maxDescent)
4344 aMetrics->underlineSize = std::max(aMetrics->maxDescent, 1.0);
4345 // The max underlineOffset is 1px (the min underlineSize is 1px, and min
4346 // maxDescent is 0px.)
4347 aMetrics->underlineOffset = aMetrics->underlineSize - aMetrics->maxDescent;
4350 // If strikeout line is overflowed from the ascent, the line should be resized
4351 // and moved for that being in the ascent space. Note that the strikeoutOffset
4352 // is *middle* of the strikeout line position.
4353 gfxFloat halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5);
4354 if (halfOfStrikeoutSize + aMetrics->strikeoutOffset > aMetrics->maxAscent) {
4355 if (aMetrics->strikeoutSize > aMetrics->maxAscent) {
4356 aMetrics->strikeoutSize = std::max(aMetrics->maxAscent, 1.0);
4357 halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5);
4359 gfxFloat ascent = floor(aMetrics->maxAscent + 0.5);
4360 aMetrics->strikeoutOffset = std::max(halfOfStrikeoutSize, ascent / 2.0);
4363 // If overline is larger than the ascent, the line should be resized.
4364 if (aMetrics->underlineSize > aMetrics->maxAscent) {
4365 aMetrics->underlineSize = aMetrics->maxAscent;
4369 gfxFont::Baselines gfxFont::GetBaselines(Orientation aOrientation) {
4370 // Approximated baselines for fonts lacking actual baseline data. These are
4371 // fractions of the em ascent/descent from the alphabetic baseline.
4372 const double kHangingBaselineDefault = 0.8; // fraction of ascent
4373 const double kIdeographicBaselineDefault = -0.5; // fraction of descent
4375 // If no BASE table is present, just return synthetic values immediately.
4376 if (!mFontEntry->HasFontTable(TRUETYPE_TAG('B', 'A', 'S', 'E'))) {
4377 // No baseline table; just synthesize them immediately.
4378 const Metrics& metrics = GetMetrics(aOrientation);
4379 return Baselines{
4380 0.0, // alphabetic
4381 kHangingBaselineDefault * metrics.emAscent, // hanging
4382 kIdeographicBaselineDefault * metrics.emDescent // ideographic
4386 // Use harfbuzz to try to read the font's baseline metrics.
4387 Baselines result{NAN, NAN, NAN};
4388 hb_font_t* hbFont = gfxHarfBuzzShaper::CreateHBFont(this);
4389 hb_direction_t hbDir = aOrientation == nsFontMetrics::eHorizontal
4390 ? HB_DIRECTION_LTR
4391 : HB_DIRECTION_TTB;
4392 hb_position_t position;
4393 unsigned count = 0;
4394 auto Fix2Float = [](hb_position_t f) -> gfxFloat { return f / 65536.0; };
4395 if (hb_ot_layout_get_baseline(hbFont, HB_OT_LAYOUT_BASELINE_TAG_ROMAN, hbDir,
4396 HB_OT_TAG_DEFAULT_SCRIPT,
4397 HB_OT_TAG_DEFAULT_LANGUAGE, &position)) {
4398 result.mAlphabetic = Fix2Float(position);
4399 count++;
4401 if (hb_ot_layout_get_baseline(hbFont, HB_OT_LAYOUT_BASELINE_TAG_HANGING,
4402 hbDir, HB_OT_TAG_DEFAULT_SCRIPT,
4403 HB_OT_TAG_DEFAULT_LANGUAGE, &position)) {
4404 result.mHanging = Fix2Float(position);
4405 count++;
4407 if (hb_ot_layout_get_baseline(
4408 hbFont, HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT, hbDir,
4409 HB_OT_TAG_DEFAULT_SCRIPT, HB_OT_TAG_DEFAULT_LANGUAGE, &position)) {
4410 result.mIdeographic = Fix2Float(position);
4411 count++;
4413 hb_font_destroy(hbFont);
4414 // If we successfully read all three, we can return now.
4415 if (count == 3) {
4416 return result;
4419 // Synthesize the baselines that we didn't find in the font.
4420 const Metrics& metrics = GetMetrics(aOrientation);
4421 if (std::isnan(result.mAlphabetic)) {
4422 result.mAlphabetic = 0.0;
4424 if (std::isnan(result.mHanging)) {
4425 result.mHanging = kHangingBaselineDefault * metrics.emAscent;
4427 if (std::isnan(result.mIdeographic)) {
4428 result.mIdeographic = kIdeographicBaselineDefault * metrics.emDescent;
4431 return result;
4434 // Create a Metrics record to be used for vertical layout. This should never
4435 // fail, as we've already decided this is a valid font. We do not have the
4436 // option of marking it invalid (as can happen if we're unable to read
4437 // horizontal metrics), because that could break a font that we're already
4438 // using for horizontal text.
4439 // So we will synthesize *something* usable here even if there aren't any of the
4440 // usual font tables (which can happen in the case of a legacy bitmap or Type1
4441 // font for which the platform-specific backend used platform APIs instead of
4442 // sfnt tables to create the horizontal metrics).
4443 void gfxFont::CreateVerticalMetrics() {
4444 const uint32_t kHheaTableTag = TRUETYPE_TAG('h', 'h', 'e', 'a');
4445 const uint32_t kVheaTableTag = TRUETYPE_TAG('v', 'h', 'e', 'a');
4446 const uint32_t kPostTableTag = TRUETYPE_TAG('p', 'o', 's', 't');
4447 const uint32_t kOS_2TableTag = TRUETYPE_TAG('O', 'S', '/', '2');
4448 uint32_t len;
4450 auto* metrics = new Metrics();
4451 ::memset(metrics, 0, sizeof(Metrics));
4453 // Some basic defaults, in case the font lacks any real metrics tables.
4454 // TODO: consider what rounding (if any) we should apply to these.
4455 metrics->emHeight = GetAdjustedSize();
4456 metrics->emAscent = metrics->emHeight / 2;
4457 metrics->emDescent = metrics->emHeight - metrics->emAscent;
4459 metrics->maxAscent = metrics->emAscent;
4460 metrics->maxDescent = metrics->emDescent;
4462 const float UNINITIALIZED_LEADING = -10000.0f;
4463 metrics->externalLeading = UNINITIALIZED_LEADING;
4465 if (mFUnitsConvFactor < 0.0) {
4466 uint16_t upem = GetFontEntry()->UnitsPerEm();
4467 if (upem != gfxFontEntry::kInvalidUPEM) {
4468 AutoWriteLock lock(mLock);
4469 mFUnitsConvFactor = GetAdjustedSize() / upem;
4473 #define SET_UNSIGNED(field, src) \
4474 metrics->field = uint16_t(src) * mFUnitsConvFactor
4475 #define SET_SIGNED(field, src) metrics->field = int16_t(src) * mFUnitsConvFactor
4477 gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag);
4478 if (os2Table && mFUnitsConvFactor >= 0.0) {
4479 const OS2Table* os2 =
4480 reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
4481 // These fields should always be present in any valid OS/2 table
4482 if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) {
4483 SET_SIGNED(strikeoutSize, os2->yStrikeoutSize);
4484 // Use ascent+descent from the horizontal metrics as the default
4485 // advance (aveCharWidth) in vertical mode
4486 gfxFloat ascentDescent =
4487 gfxFloat(mFUnitsConvFactor) *
4488 (int16_t(os2->sTypoAscender) - int16_t(os2->sTypoDescender));
4489 metrics->aveCharWidth = std::max(metrics->emHeight, ascentDescent);
4490 // Use xAvgCharWidth from horizontal metrics as minimum font extent
4491 // for vertical layout, applying half of it to ascent and half to
4492 // descent (to work with a default centered baseline).
4493 gfxFloat halfCharWidth =
4494 int16_t(os2->xAvgCharWidth) * gfxFloat(mFUnitsConvFactor) / 2;
4495 metrics->maxAscent = std::max(metrics->maxAscent, halfCharWidth);
4496 metrics->maxDescent = std::max(metrics->maxDescent, halfCharWidth);
4500 // If we didn't set aveCharWidth from OS/2, try to read 'hhea' metrics
4501 // and use the line height from its ascent/descent.
4502 if (!metrics->aveCharWidth) {
4503 gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag);
4504 if (hheaTable && mFUnitsConvFactor >= 0.0) {
4505 const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>(
4506 hb_blob_get_data(hheaTable, &len));
4507 if (len >= sizeof(MetricsHeader)) {
4508 SET_SIGNED(aveCharWidth,
4509 int16_t(hhea->ascender) - int16_t(hhea->descender));
4510 metrics->maxAscent = metrics->aveCharWidth / 2;
4511 metrics->maxDescent = metrics->aveCharWidth - metrics->maxAscent;
4516 // Read real vertical metrics if available.
4517 metrics->ideographicWidth = -1.0;
4518 metrics->zeroWidth = -1.0;
4519 gfxFontEntry::AutoTable vheaTable(mFontEntry, kVheaTableTag);
4520 if (vheaTable && mFUnitsConvFactor >= 0.0) {
4521 const MetricsHeader* vhea = reinterpret_cast<const MetricsHeader*>(
4522 hb_blob_get_data(vheaTable, &len));
4523 if (len >= sizeof(MetricsHeader)) {
4524 SET_UNSIGNED(maxAdvance, vhea->advanceWidthMax);
4525 // Redistribute space between ascent/descent because we want a
4526 // centered vertical baseline by default.
4527 gfxFloat halfExtent =
4528 0.5 * gfxFloat(mFUnitsConvFactor) *
4529 (int16_t(vhea->ascender) + std::abs(int16_t(vhea->descender)));
4530 // Some bogus fonts have ascent and descent set to zero in 'vhea'.
4531 // In that case we just ignore them and keep our synthetic values
4532 // from above.
4533 if (halfExtent > 0) {
4534 metrics->maxAscent = halfExtent;
4535 metrics->maxDescent = halfExtent;
4536 SET_SIGNED(externalLeading, vhea->lineGap);
4538 // Call gfxHarfBuzzShaper::GetGlyphVAdvance directly, as GetCharAdvance
4539 // would potentially recurse if no v-advance is available and it attempts
4540 // to fall back to a value from mVerticalMetrics.
4541 if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) {
4542 uint32_t gid = ProvidesGetGlyph()
4543 ? GetGlyph(kWaterIdeograph, 0)
4544 : shaper->GetNominalGlyph(kWaterIdeograph);
4545 if (gid) {
4546 int32_t advance = shaper->GetGlyphVAdvance(gid);
4547 // Convert 16.16 fixed-point advance from the shaper to a float.
4548 metrics->ideographicWidth =
4549 advance < 0 ? metrics->aveCharWidth : advance / 65536.0;
4551 gid = ProvidesGetGlyph() ? GetGlyph('0', 0)
4552 : shaper->GetNominalGlyph('0');
4553 if (gid) {
4554 int32_t advance = shaper->GetGlyphVAdvance(gid);
4555 metrics->zeroWidth =
4556 advance < 0 ? metrics->aveCharWidth : advance / 65536.0;
4562 // If we didn't set aveCharWidth above, we must be dealing with a non-sfnt
4563 // font of some kind (Type1, bitmap, vector, ...), so fall back to using
4564 // whatever the platform backend figured out for horizontal layout.
4565 // And if we haven't set externalLeading yet, then copy that from the
4566 // horizontal metrics as well, to help consistency of CSS line-height.
4567 if (!metrics->aveCharWidth ||
4568 metrics->externalLeading == UNINITIALIZED_LEADING) {
4569 const Metrics& horizMetrics = GetHorizontalMetrics();
4570 if (!metrics->aveCharWidth) {
4571 metrics->aveCharWidth = horizMetrics.maxAscent + horizMetrics.maxDescent;
4573 if (metrics->externalLeading == UNINITIALIZED_LEADING) {
4574 metrics->externalLeading = horizMetrics.externalLeading;
4578 // Get underline thickness from the 'post' table if available.
4579 // We also read the underline position, although in vertical-upright mode
4580 // this will not be appropriate to use directly (see nsTextFrame.cpp).
4581 gfxFontEntry::AutoTable postTable(mFontEntry, kPostTableTag);
4582 if (postTable) {
4583 const PostTable* post =
4584 reinterpret_cast<const PostTable*>(hb_blob_get_data(postTable, &len));
4585 if (len >= offsetof(PostTable, underlineThickness) + sizeof(uint16_t)) {
4586 static_assert(offsetof(PostTable, underlinePosition) <
4587 offsetof(PostTable, underlineThickness),
4588 "broken PostTable struct?");
4589 SET_SIGNED(underlineOffset, post->underlinePosition);
4590 SET_UNSIGNED(underlineSize, post->underlineThickness);
4591 // Also use for strikeout if we didn't find that in OS/2 above.
4592 if (!metrics->strikeoutSize) {
4593 metrics->strikeoutSize = metrics->underlineSize;
4598 #undef SET_UNSIGNED
4599 #undef SET_SIGNED
4601 // If we didn't read this from a vhea table, it will still be zero.
4602 // In any case, let's make sure it is not less than the value we've
4603 // come up with for aveCharWidth.
4604 metrics->maxAdvance = std::max(metrics->maxAdvance, metrics->aveCharWidth);
4606 // Thickness of underline and strikeout may have been read from tables,
4607 // but in case they were not present, ensure a minimum of 1 pixel.
4608 metrics->underlineSize = std::max(1.0, metrics->underlineSize);
4610 metrics->strikeoutSize = std::max(1.0, metrics->strikeoutSize);
4611 metrics->strikeoutOffset = -0.5 * metrics->strikeoutSize;
4613 // Somewhat arbitrary values for now, subject to future refinement...
4614 metrics->spaceWidth = metrics->aveCharWidth;
4615 metrics->maxHeight = metrics->maxAscent + metrics->maxDescent;
4616 metrics->xHeight = metrics->emHeight / 2;
4617 metrics->capHeight = metrics->maxAscent;
4619 if (metrics->zeroWidth < 0.0) {
4620 metrics->zeroWidth = metrics->aveCharWidth;
4623 if (!mVerticalMetrics.compareExchange(nullptr, metrics)) {
4624 delete metrics;
4628 gfxFloat gfxFont::SynthesizeSpaceWidth(uint32_t aCh) {
4629 // return an appropriate width for various Unicode space characters
4630 // that we "fake" if they're not actually present in the font;
4631 // returns negative value if the char is not a known space.
4632 switch (aCh) {
4633 case 0x2000: // en quad
4634 case 0x2002:
4635 return GetAdjustedSize() / 2; // en space
4636 case 0x2001: // em quad
4637 case 0x2003:
4638 return GetAdjustedSize(); // em space
4639 case 0x2004:
4640 return GetAdjustedSize() / 3; // three-per-em space
4641 case 0x2005:
4642 return GetAdjustedSize() / 4; // four-per-em space
4643 case 0x2006:
4644 return GetAdjustedSize() / 6; // six-per-em space
4645 case 0x2007:
4646 return GetMetrics(nsFontMetrics::eHorizontal)
4647 .ZeroOrAveCharWidth(); // figure space
4648 case 0x2008:
4649 return GetMetrics(nsFontMetrics::eHorizontal)
4650 .spaceWidth; // punctuation space
4651 case 0x2009:
4652 return GetAdjustedSize() / 5; // thin space
4653 case 0x200a:
4654 return GetAdjustedSize() / 10; // hair space
4655 case 0x202f:
4656 return GetAdjustedSize() / 5; // narrow no-break space
4657 case 0x3000:
4658 return GetAdjustedSize(); // ideographic space
4659 default:
4660 return -1.0;
4664 void gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
4665 FontCacheSizes* aSizes) const {
4666 AutoReadLock lock(mLock);
4667 for (uint32_t i = 0; i < mGlyphExtentsArray.Length(); ++i) {
4668 aSizes->mFontInstances +=
4669 mGlyphExtentsArray[i]->SizeOfIncludingThis(aMallocSizeOf);
4671 if (mWordCache) {
4672 aSizes->mShapedWords +=
4673 mWordCache->shallowSizeOfIncludingThis(aMallocSizeOf);
4674 for (auto it = mWordCache->iter(); !it.done(); it.next()) {
4675 aSizes->mShapedWords +=
4676 it.get().value()->SizeOfIncludingThis(aMallocSizeOf);
4681 void gfxFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
4682 FontCacheSizes* aSizes) const {
4683 aSizes->mFontInstances += aMallocSizeOf(this);
4684 AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
4687 void gfxFont::AddGlyphChangeObserver(GlyphChangeObserver* aObserver) {
4688 AutoWriteLock lock(mLock);
4689 if (!mGlyphChangeObservers) {
4690 mGlyphChangeObservers = MakeUnique<nsTHashSet<GlyphChangeObserver*>>();
4692 mGlyphChangeObservers->Insert(aObserver);
4695 void gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver* aObserver) {
4696 AutoWriteLock lock(mLock);
4697 NS_ASSERTION(mGlyphChangeObservers, "No observers registered");
4698 NS_ASSERTION(mGlyphChangeObservers->Contains(aObserver),
4699 "Observer not registered");
4700 mGlyphChangeObservers->Remove(aObserver);
4703 #define DEFAULT_PIXEL_FONT_SIZE 16.0f
4705 gfxFontStyle::gfxFontStyle()
4706 : size(DEFAULT_PIXEL_FONT_SIZE),
4707 sizeAdjust(0.0f),
4708 baselineOffset(0.0f),
4709 languageOverride(NO_FONT_LANGUAGE_OVERRIDE),
4710 weight(FontWeight::NORMAL),
4711 stretch(FontStretch::NORMAL),
4712 style(FontSlantStyle::NORMAL),
4713 variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
4714 variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL),
4715 sizeAdjustBasis(uint8_t(FontSizeAdjust::Tag::None)),
4716 systemFont(true),
4717 printerFont(false),
4718 useGrayscaleAntialiasing(false),
4719 allowSyntheticWeight(true),
4720 allowSyntheticStyle(true),
4721 allowSyntheticSmallCaps(true),
4722 useSyntheticPosition(true),
4723 noFallbackVariantFeatures(true) {}
4725 gfxFontStyle::gfxFontStyle(FontSlantStyle aStyle, FontWeight aWeight,
4726 FontStretch aStretch, gfxFloat aSize,
4727 const FontSizeAdjust& aSizeAdjust, bool aSystemFont,
4728 bool aPrinterFont, bool aAllowWeightSynthesis,
4729 bool aAllowStyleSynthesis,
4730 bool aAllowSmallCapsSynthesis,
4731 bool aUsePositionSynthesis,
4732 uint32_t aLanguageOverride)
4733 : size(aSize),
4734 baselineOffset(0.0f),
4735 languageOverride(aLanguageOverride),
4736 weight(aWeight),
4737 stretch(aStretch),
4738 style(aStyle),
4739 variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
4740 variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL),
4741 systemFont(aSystemFont),
4742 printerFont(aPrinterFont),
4743 useGrayscaleAntialiasing(false),
4744 allowSyntheticWeight(aAllowWeightSynthesis),
4745 allowSyntheticStyle(aAllowStyleSynthesis),
4746 allowSyntheticSmallCaps(aAllowSmallCapsSynthesis),
4747 useSyntheticPosition(aUsePositionSynthesis),
4748 noFallbackVariantFeatures(true) {
4749 MOZ_ASSERT(!std::isnan(size));
4751 sizeAdjustBasis = uint8_t(aSizeAdjust.tag);
4752 // sizeAdjustBasis is currently a small bitfield, so let's assert that the
4753 // tag value was not truncated.
4754 MOZ_ASSERT(FontSizeAdjust::Tag(sizeAdjustBasis) == aSizeAdjust.tag,
4755 "gfxFontStyle.sizeAdjustBasis too small?");
4757 #define HANDLE_TAG(TAG) \
4758 case FontSizeAdjust::Tag::TAG: \
4759 sizeAdjust = aSizeAdjust.As##TAG(); \
4760 break;
4762 switch (aSizeAdjust.tag) {
4763 case FontSizeAdjust::Tag::None:
4764 sizeAdjust = 0.0f;
4765 break;
4766 HANDLE_TAG(ExHeight)
4767 HANDLE_TAG(CapHeight)
4768 HANDLE_TAG(ChWidth)
4769 HANDLE_TAG(IcWidth)
4770 HANDLE_TAG(IcHeight)
4773 #undef HANDLE_TAG
4775 MOZ_ASSERT(!std::isnan(sizeAdjust));
4777 if (weight > FontWeight::FromInt(1000)) {
4778 weight = FontWeight::FromInt(1000);
4780 if (weight < FontWeight::FromInt(1)) {
4781 weight = FontWeight::FromInt(1);
4784 if (size >= FONT_MAX_SIZE) {
4785 size = FONT_MAX_SIZE;
4786 sizeAdjust = 0.0f;
4787 sizeAdjustBasis = uint8_t(FontSizeAdjust::Tag::None);
4788 } else if (size < 0.0) {
4789 NS_WARNING("negative font size");
4790 size = 0.0;
4794 PLDHashNumber gfxFontStyle::Hash() const {
4795 uint32_t hash = variationSettings.IsEmpty()
4797 : mozilla::HashBytes(variationSettings.Elements(),
4798 variationSettings.Length() *
4799 sizeof(gfxFontVariation));
4800 return mozilla::AddToHash(hash, systemFont, style.Raw(), stretch.Raw(),
4801 weight.Raw(), size, int32_t(sizeAdjust * 1000.0f));
4804 void gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel) {
4805 MOZ_ASSERT(
4806 variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL && baselineOffset == 0,
4807 "can't adjust this style for sub/superscript");
4809 // calculate the baseline offset (before changing the size)
4810 if (variantSubSuper == NS_FONT_VARIANT_POSITION_SUPER) {
4811 baselineOffset = size * -NS_FONT_SUPERSCRIPT_OFFSET_RATIO;
4812 } else {
4813 baselineOffset = size * NS_FONT_SUBSCRIPT_OFFSET_RATIO;
4816 // calculate reduced size, roughly mimicing behavior of font-size: smaller
4817 float cssSize = size * aAppUnitsPerDevPixel / AppUnitsPerCSSPixel();
4818 if (cssSize < NS_FONT_SUB_SUPER_SMALL_SIZE) {
4819 size *= NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL;
4820 } else if (cssSize >= NS_FONT_SUB_SUPER_LARGE_SIZE) {
4821 size *= NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE;
4822 } else {
4823 gfxFloat t = (cssSize - NS_FONT_SUB_SUPER_SMALL_SIZE) /
4824 (NS_FONT_SUB_SUPER_LARGE_SIZE - NS_FONT_SUB_SUPER_SMALL_SIZE);
4825 size *= (1.0 - t) * NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL +
4826 t * NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE;
4829 // clear the variant field
4830 variantSubSuper = NS_FONT_VARIANT_POSITION_NORMAL;
4833 bool gfxFont::TryGetMathTable() {
4834 if (mMathInitialized) {
4835 return !!mMathTable;
4838 auto face(GetFontEntry()->GetHBFace());
4839 if (hb_ot_math_has_data(face)) {
4840 auto* mathTable = new gfxMathTable(face, GetAdjustedSize());
4841 if (!mMathTable.compareExchange(nullptr, mathTable)) {
4842 delete mathTable;
4845 mMathInitialized = true;
4847 return !!mMathTable;