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