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/. */
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"
23 #include "gfxGlyphExtents.h"
24 #include "gfxPlatform.h"
25 #include "gfxTextRun.h"
26 #include "nsGkAtoms.h"
29 #include "gfxContext.h"
30 #include "gfxFontMissingGlyphs.h"
31 #include "gfxGraphiteShaper.h"
32 #include "gfxHarfBuzzShaper.h"
33 #include "gfxUserFontSet.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"
59 # include "cairo-win32.h"
60 # include "gfxWindowsPlatform.h"
63 #include "harfbuzz/hb.h"
64 #include "harfbuzz/hb-ot.h"
70 using namespace mozilla
;
71 using namespace mozilla::gfx
;
72 using namespace mozilla::unicode
;
73 using mozilla::services::GetObserverService
;
75 gfxFontCache
* gfxFontCache::gGlobalCache
= nullptr;
78 # define DEBUG_TEXT_RUN_STORAGE_METRICS
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;
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
)
111 gfxTextRunFactory::~gfxTextRunFactory() {
112 // Should not be dropped by stylo
113 MOZ_ASSERT(!Servo_IsWorkerThread());
117 gfxFontCache::MemoryReporter::CollectReports(
118 nsIHandleReportCallback
* aHandleReport
, nsISupports
* aData
,
120 FontCacheSizes sizes
;
122 gfxFontCache::GetCache()->AddSizeOfIncludingThis(&FontCacheMallocSizeOf
,
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
,
131 "Memory used to cache shaped glyph data.");
136 NS_IMPL_ISUPPORTS(gfxFontCache::Observer
, nsIObserver
)
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();
144 fontCache
->FlushShapedWordCaches();
147 MOZ_ASSERT_UNREACHABLE("unexpected notification topic");
152 nsresult
gfxFontCache::Init() {
153 NS_ASSERTION(!gGlobalCache
, "Where did this come from?");
154 gGlobalCache
= new gfxFontCache(GetMainThreadSerialEventTarget());
156 return NS_ERROR_OUT_OF_MEMORY
;
158 RegisterStrongMemoryReporter(new MemoryReporter());
162 void gfxFontCache::Shutdown() {
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
);
184 gfxFontCache::gfxFontCache(nsIEventTarget
* aEventTarget
)
185 : ExpirationTrackerImpl
<gfxFont
, 3, Lock
, AutoLock
>(
186 FONT_TIMEOUT_SECONDS
* 1000, "gfxFontCache", aEventTarget
) {
187 nsCOMPtr
<nsIObserverService
> obs
= GetObserverService();
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.
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);
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
) {
253 MutexAutoLock
lock(mMutex
);
255 Key
key(aFont
->GetFontEntry(), aFont
->GetStyle(),
256 aFont
->GetUnicodeRangeMap());
257 HashEntry
* entry
= mFonts
.PutEntry(key
);
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.
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
));
271 MOZ_ASSERT(entry
->mFont
!= aFont
);
273 if (entry
->mFont
->GetExpirationState()->IsTracked()) {
274 RemoveObjectLocked(entry
->mFont
, lock
);
278 return do_AddRef(entry
->mFont
);
281 bool gfxFontCache::MaybeDestroy(gfxFont
* 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) {
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());
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()) {
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
)) {
312 mFonts
.RemoveEntry(entry
);
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?");
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();
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?");
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
370 if (entry
->mFont
->GetExpirationState()->IsTracked()) {
371 RemoveObjectLocked(entry
->mFont
, lock
);
372 discard
.AppendElement(entry
->mFont
);
375 MOZ_ASSERT(!entry
->mFont
->GetExpirationState()->IsTracked());
378 MOZ_ASSERT(IsEmptyLocked(lock
),
379 "Cache tracker still has fonts after flush!");
382 DestroyDiscard(discard
);
386 void gfxFontCache::WordCacheExpirationTimerCallback(nsITimer
* aTimer
,
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
;
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()) {
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
,
459 // nothing defined, skip
460 if (values
.IsEmpty()) {
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
) {
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
);
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
485 for (uint32_t nn
: values
) {
486 if (nn
== 0 || nn
> MAX_SSXX_VALUE
) {
489 feature
.mTag
= HB_TAG('s', 's', ('0' + nn
/ 10), ('0' + nn
% 10));
490 aFontFeatures
.AppendElement(feature
);
496 uint32_t constant
= 0;
497 nsAtom
* name
= nullptr;
498 switch (aAlternates
.tag
) {
500 constant
= NS_FONT_VARIANT_ALTERNATES_SWASH
;
501 name
= aAlternates
.AsSwash().AsAtom();
504 constant
= NS_FONT_VARIANT_ALTERNATES_STYLISTIC
;
505 name
= aAlternates
.AsStylistic().AsAtom();
508 constant
= NS_FONT_VARIANT_ALTERNATES_ORNAMENTS
;
509 name
= aAlternates
.AsOrnaments().AsAtom();
511 case Tag::Annotation
:
512 constant
= NS_FONT_VARIANT_ALTERNATES_ANNOTATION
;
513 name
= aAlternates
.AsAnnotation().AsAtom();
516 MOZ_ASSERT_UNREACHABLE("Unknown font-variant-alternates value!");
520 Span
<const uint32_t> values
=
521 aFeatureLookup
.GetFontFeatureValuesFor(aFamily
, constant
, name
);
522 if (values
.IsEmpty()) {
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');
535 case Tag::Stylistic
: // salt
536 feature
.mTag
= HB_TAG('s', 'a', 'l', 't');
538 case Tag::Ornaments
: // ornm
539 feature
.mTag
= HB_TAG('o', 'r', 'n', 'm');
541 case Tag::Annotation
: // nalt
542 feature
.mTag
= HB_TAG('n', 'a', 'l', 't');
545 MOZ_ASSERT_UNREACHABLE("how?");
548 aFontFeatures
.AppendElement(feature
);
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()) {
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
;
579 auto addOrReplace
= [&](const gfxFontFeature
& aFeature
) {
580 auto index
= mergedFeatures
.BinaryIndexOf(aFeature
, cmp
);
581 if (index
== nsTArray
<gfxFontFeature
>::NoIndex
) {
582 mergedFeatures
.InsertElementSorted(aFeature
, cmp
);
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
:
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
605 case NS_FONT_VARIANT_CAPS_SMALLCAPS
:
606 addOrReplace(gfxFontFeature
{HB_TAG('s', 'm', 'c', 'p'), 1});
609 case NS_FONT_VARIANT_CAPS_ALLPETITE
:
610 addOrReplace(gfxFontFeature
{aAddSmallCaps
? HB_TAG('c', '2', 's', 'c')
611 : HB_TAG('c', '2', 'p', 'c'),
613 // fall through to the petite-caps case
616 case NS_FONT_VARIANT_CAPS_PETITECAPS
:
617 addOrReplace(gfxFontFeature
{aAddSmallCaps
? HB_TAG('s', 'm', 'c', 'p')
618 : HB_TAG('p', 'c', 'a', 'p'),
622 case NS_FONT_VARIANT_CAPS_TITLING
:
623 addOrReplace(gfxFontFeature
{HB_TAG('t', 'i', 't', 'l'), 1});
626 case NS_FONT_VARIANT_CAPS_UNICASE
:
627 addOrReplace(gfxFontFeature
{HB_TAG('u', 'n', 'i', 'c'), 1});
631 MOZ_ASSERT_UNREACHABLE("Unexpected variantCaps");
635 // font-variant-position - handled here due to the need for fallback
636 switch (aStyle
->variantSubSuper
) {
637 case NS_FONT_VARIANT_POSITION_NORMAL
:
639 case NS_FONT_VARIANT_POSITION_SUPER
:
640 addOrReplace(gfxFontFeature
{HB_TAG('s', 'u', 'p', 's'), 1});
642 case NS_FONT_VARIANT_POSITION_SUB
:
643 addOrReplace(gfxFontFeature
{HB_TAG('s', 'u', 'b', 's'), 1});
646 MOZ_ASSERT_UNREACHABLE("Unexpected variantSubSuper");
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
,
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();
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.
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
,
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
));
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
];
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
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
,
767 CompressedGlyph
* glyphs
= GetCharacterGlyphs() + aOffset
;
769 bool prevWasHyphen
= false;
770 while (pos
< aLength
) {
771 uint8_t ch
= aString
[pos
];
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;
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
);
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
,
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.
833 if (!IsIgnorable(aChar
)) {
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
);
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()) {
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();
866 void gfxShapedText::ApplyTrackingToClusters(gfxFloat aTrackingAdjustment
,
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();
878 advance
= std::max(0, advance
+ appUnitAdjustment
);
879 if (CompressedGlyph::IsSimpleAdvance(advance
)) {
880 glyphData
->SetSimpleGlyph(advance
, glyphData
->GetSimpleGlyph());
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
);
892 // complex glyphs ==> add offset at cluster/ligature boundaries
893 uint32_t detailedLength
= glyphData
->GetGlyphCount();
894 if (detailedLength
) {
895 DetailedGlyph
* details
= GetDetailedGlyphs(i
);
899 auto& advance
= IsRightToLeft() ? details
[0].mAdvance
900 : details
[detailedLength
- 1].mAdvance
;
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
);
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()
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();
950 } else if (angle
== FontSlantStyle::DEFAULT_OBLIQUE_DEGREES
) {
951 return kTanDefaultAngle
;
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
);
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
),
978 mAdjustedSize(-1.0), // negative to indicate "not yet initialized"
979 mFUnitsConvFactor(-1.0f
), // negative to indicate "not yet initialized"
980 mAntialiasOption(anAAOption
),
982 mApplySyntheticBold(false),
983 mKerningEnabled(false),
984 mMathInitialized(false) {
985 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
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,
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
) {
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
));
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
;
1068 if (ShouldRoundXOffset(cr
)) {
1069 return RoundingFlags::kRoundX
| RoundingFlags::kRoundY
;
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
)) {
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()) {
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
);
1107 return GetMetrics(nsFontMetrics::eVertical
).aveCharWidth
;
1109 return advance
/ 65536.0;
1111 return shaper
->GetGlyphHAdvance(aGID
) / 65536.0;
1116 gfxFloat
gfxFont::GetCharAdvance(uint32_t aUnicode
, bool aVertical
) {
1118 if (ProvidesGetGlyph()) {
1119 gid
= GetGlyph(aUnicode
, 0);
1121 if (gfxHarfBuzzShaper
* shaper
= GetHarfBuzzShaper()) {
1122 gid
= shaper
->GetNominalGlyph(aUnicode
);
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
;
1139 len
= std::size(lookups
);
1140 hb_ot_layout_feature_get_lookups(aFace
, aTableTag
, aFeatureIndex
, offset
,
1142 for (i
= 0; i
< len
; i
++) {
1143 hb_set_add(aLookups
, lookups
[i
]);
1146 } while (len
== std::size(lookups
));
1149 static void CollectLookupsByLanguage(
1150 hb_face_t
* aFace
, hb_tag_t aTableTag
,
1151 const nsTHashSet
<uint32_t>& aSpecificFeatures
, hb_set_t
* aOtherLookups
,
1152 hb_set_t
* aSpecificFeatureLookups
, uint32_t aScriptIndex
,
1153 uint32_t aLangIndex
) {
1154 uint32_t reqFeatureIndex
;
1155 if (hb_ot_layout_language_get_required_feature_index(
1156 aFace
, aTableTag
, aScriptIndex
, aLangIndex
, &reqFeatureIndex
)) {
1157 CollectLookupsByFeature(aFace
, aTableTag
, reqFeatureIndex
, aOtherLookups
);
1160 uint32_t featureIndexes
[32];
1161 uint32_t i
, len
, offset
;
1165 len
= std::size(featureIndexes
);
1166 hb_ot_layout_language_get_feature_indexes(aFace
, aTableTag
, aScriptIndex
,
1167 aLangIndex
, offset
, &len
,
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
,
1181 hb_set_t
* lookups
= aSpecificFeatures
.Contains(featureTag
)
1182 ? aSpecificFeatureLookups
1184 CollectLookupsByFeature(aFace
, aTableTag
, featureIndex
, lookups
);
1187 } while (len
== std::size(featureIndexes
));
1190 static bool HasLookupRuleWithGlyphByScript(
1191 hb_face_t
* aFace
, hb_tag_t aTableTag
, hb_tag_t aScriptTag
,
1192 uint32_t aScriptIndex
, uint16_t aGlyph
,
1193 const nsTHashSet
<uint32_t>& aDefaultFeatures
,
1194 bool& aHasDefaultFeatureWithGlyph
) {
1195 uint32_t numLangs
, lang
;
1196 hb_set_t
* defaultFeatureLookups
= hb_set_create();
1197 hb_set_t
* nonDefaultFeatureLookups
= hb_set_create();
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
,
1220 if (hb_set_has(glyphs
, aGlyph
)) {
1221 aHasDefaultFeatureWithGlyph
= true;
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
);
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;
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
);
1261 hb_ot_layout_table_get_script_tags(aFace
, aTableTag
, 0, nullptr, nullptr);
1263 for (script
= 0; script
< numScripts
; script
++) {
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
,
1284 if (hb_set_has(glyphs
, aGlyph
)) {
1290 // look for the glyph among specific feature lookups
1291 hb_set_clear(glyphs
);
1293 while (hb_set_next(specificFeatureLookups
, &index
)) {
1294 hb_ot_layout_lookup_collect_glyphs(aFace
, aTableTag
, index
, glyphs
, glyphs
,
1296 if (hb_set_has(glyphs
, aGlyph
)) {
1297 aHasGlyphSpecific
= true;
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
;
1342 MakeScopeExit([&]() { mFontEntry
->mHasSpaceFeatures
= flags
; });
1344 bool log
= LOG_FONTINIT_ENABLED();
1346 if (MOZ_UNLIKELY(log
)) {
1347 start
= TimeStamp::Now();
1350 uint32_t spaceGlyph
= GetSpaceGlyph();
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
;
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)
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,
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.
1386 tagToCode
= sScriptTagToCode
;
1390 // Set up the default-features hashset on first use.
1391 if (!sDefaultFeatures
) {
1392 uint32_t numDefaultFeatures
= std::size(defaultFeatures
);
1393 auto* set
= new nsTHashSet
<uint32_t>(numDefaultFeatures
);
1394 for (uint32_t i
= 0; i
< numDefaultFeatures
; i
++) {
1395 set
->Insert(defaultFeatures
[i
]);
1397 if (!sDefaultFeatures
.compareExchange(nullptr, set
)) {
1402 // iterate over the scripts in the font
1403 hb_tag_t scriptTags
[8];
1405 uint32_t len
, offset
= 0;
1407 len
= std::size(scriptTags
);
1408 hb_ot_layout_table_get_script_tags(face
, HB_OT_TAG_GSUB
, offset
, &len
,
1410 for (uint32_t i
= 0; i
< len
; i
++) {
1411 bool isDefaultFeature
= false;
1413 if (!HasLookupRuleWithGlyphByScript(
1414 face
, HB_OT_TAG_GSUB
, scriptTags
[i
], offset
+ i
, spaceGlyph
,
1415 *sDefaultFeatures
, isDefaultFeature
) ||
1416 !tagToCode
->Get(scriptTags
[i
], &s
)) {
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
);
1425 mFontEntry
->mNonDefaultSubSpaceFeatures
[index
] |= (1 << bit
);
1429 } while (len
== std::size(scriptTags
));
1432 // spaces in default features of default script?
1433 // ==> can't use word cache, skip GPOS analysis
1434 bool canUseWordCache
= true;
1435 if (HasSubstitution(mFontEntry
->mDefaultSubSpaceFeatures
, Script::COMMON
)) {
1436 canUseWordCache
= false;
1439 // GPOS lookups - distinguish kerning from non-kerning features
1440 if (canUseWordCache
&& hb_ot_layout_has_positioning(face
)) {
1441 bool hasKerning
= false, hasNonKerning
= false;
1442 HasLookupRuleWithGlyph(face
, HB_OT_TAG_GPOS
, hasNonKerning
,
1443 HB_TAG('k', 'e', 'r', 'n'), hasKerning
, spaceGlyph
);
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
;
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
1472 (mFontEntry
->mHasSpaceFeatures
& gfxFontEntry::SpaceFeatures::NonKerning
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
) {
1488 // default features have space lookups ==> true
1489 if (HasSubstitution(mFontEntry
->mDefaultSubSpaceFeatures
, Script::COMMON
) ||
1490 HasSubstitution(mFontEntry
->mDefaultSubSpaceFeatures
, aRunScript
)) {
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
,
1498 HasSubstitution(mFontEntry
->mNonDefaultSubSpaceFeatures
, aRunScript
)) &&
1499 (!mStyle
.featureSettings
.IsEmpty() ||
1500 !mFontEntry
->mFeatureSettings
.IsEmpty())) {
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()) {
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
)) {
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
)) {
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
;
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'));
1573 aSyntheticLowerToSmallCaps
= true;
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'));
1580 aSyntheticLowerToSmallCaps
= true;
1581 aSyntheticUpperToSmallCaps
= true;
1584 case NS_FONT_VARIANT_CAPS_PETITECAPS
:
1585 ok
= SupportsFeature(aScript
, HB_TAG('p', 'c', 'a', 'p'));
1587 ok
= SupportsFeature(aScript
, HB_TAG('s', 'm', 'c', 'p'));
1588 aFallbackToSmallCaps
= ok
;
1591 aSyntheticLowerToSmallCaps
= true;
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'));
1598 ok
= SupportsFeature(aScript
, HB_TAG('s', 'm', 'c', 'p')) &&
1599 SupportsFeature(aScript
, HB_TAG('c', '2', 's', 'c'));
1600 aFallbackToSmallCaps
= ok
;
1603 aSyntheticLowerToSmallCaps
= true;
1604 aSyntheticUpperToSmallCaps
= true;
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");
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
),
1626 return SupportsSubSuperscript(aSubSuperscript
, unicodeString
.get(), aLength
,
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
)) {
1645 // xxx - for graphite, don't really know how to sniff lookups so bail
1646 if (mGraphiteShaper
&& gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1650 gfxHarfBuzzShaper
* shaper
= GetHarfBuzzShaper();
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])) {
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
)) {
1690 // xxx - for graphite, don't really know how to sniff lookups so bail
1691 if (mGraphiteShaper
&& gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
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
);
1707 bool gfxFont::HasFeatureSet(uint32_t aFeature
, bool& aFeatureOn
) {
1710 if (mStyle
.featureSettings
.IsEmpty() &&
1711 GetFontEntry()->mFeatureSettings
.IsEmpty()) {
1715 // add feature values from font
1716 bool featureSet
= false;
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
) {
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
) {
1736 aFeatureOn
= (feature
.mValue
!= 0);
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
) {
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
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
;
1778 return AntialiasMode::DEFAULT
;
1782 class GlyphBufferAzure
{
1783 #define AUTO_BUFFER_SIZE (2048 / sizeof(Glyph))
1785 typedef mozilla::image::imgDrawingParams imgDrawingParams
;
1788 GlyphBufferAzure(const TextRunDrawParams
& aRunParams
,
1789 const FontDrawParams
& aFontParams
)
1790 : mRunParams(aRunParams
),
1791 mFontParams(aFontParams
),
1792 mBuffer(*mAutoBuffer
.addr()),
1793 mBufSize(AUTO_BUFFER_SIZE
),
1797 ~GlyphBufferAzure() {
1798 if (mNumGlyphs
> 0) {
1802 if (mBuffer
!= *mAutoBuffer
.addr()) {
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(
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
) {
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
));
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
);
1844 Glyph
* glyph
= mBuffer
+ mNumGlyphs
++;
1845 glyph
->mIndex
= aGlyphID
;
1846 glyph
->mPosition
= aPt
;
1850 if (mNumGlyphs
> 0) {
1856 const TextRunDrawParams
& mRunParams
;
1857 const FontDrawParams
& mFontParams
;
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
) {
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
);
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
1902 pat
= fillPattern
->GetPattern(mRunParams
.dt
);
1906 mRunParams
.dt
->FillGlyphs(mFontParams
.scaledFont
, buf
, *pat
,
1907 mFontParams
.drawOptions
);
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(
1935 aState
.patternTransformChanged
? &aState
.patternTransform
: nullptr);
1938 FlushStroke(aBuffer
, *pat
);
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
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.
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
2014 gfx::Float m
= sqrtf(t
.width
* t
.width
+ t
.height
* t
.height
);
2016 NS_ASSERTION(m
!= 0.0, "degenerate transform while synthetic bolding");
2018 return 0.0; // effectively disables offset
2021 // scale factor so that offsets are 1px in device pixels
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
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()) {
2055 glyphData
->GetSimpleAdvance() * aBuffer
.mFontParams
.advanceDirection
;
2056 if (aBuffer
.mRunParams
.isRTL
) {
2057 inlineCoord
+= advance
;
2059 DrawOneGlyph
<FC
>(glyphData
->GetSimpleGlyph(), *aPt
, aBuffer
,
2061 if (!aBuffer
.mRunParams
.isRTL
) {
2062 inlineCoord
+= advance
;
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
) {
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
,
2085 *aPt
+ (aOffsetMatrix
2086 ? aOffsetMatrix
->TransformPoint(details
->mOffset
)
2087 : details
->mOffset
));
2088 DrawOneGlyph
<FC
>(details
->mGlyphID
, glyphPt
, aBuffer
,
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
;
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
)) {
2136 if (FC
== FontComplexityT::ComplexFont
) {
2137 const FontDrawParams
& fontParams(aBuffer
.mFontParams
);
2139 gfxContextMatrixAutoSaveRestore matrixRestore
;
2141 if (fontParams
.obliqueSkew
!= 0.0f
&& fontParams
.isVerticalFont
&&
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.
2148 matrixRestore
.SetContext(runParams
.context
);
2150 devPt
.x
+ GetMetrics(nsFontMetrics::eVertical
).emHeight
/ 2, devPt
.y
);
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
) {
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
,
2173 if (fontParams
.haveColorGlyphs
&& !UseNativeColrFontSupport() &&
2174 RenderColorGlyph(runParams
.dt
, runParams
.context
, textDrawer
,
2175 fontParams
, devPt
, aGlyphID
)) {
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
;
2186 devPt
.x
+= fontParams
.synBoldOnePixelOffset
;
2188 aBuffer
.OutputGlyph(aGlyphID
, devPt
);
2191 if (fontParams
.obliqueSkew
!= 0.0f
&& fontParams
.isVerticalFont
&&
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;
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
);
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
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
&&
2242 matrixRestore
.SetContext(aRunParams
.context
);
2244 aRunParams
.context
->CurrentMatrix()
2246 .PreMultiply(gfx::Matrix(1, 0, aFontParams
.obliqueSkew
, 1, 0, 0))
2248 aRunParams
.context
->SetMatrix(mat
);
2251 gfxFontMissingGlyphs::DrawMissingGlyph(
2252 aDetails
->mGlyphID
, glyphRect
, *aRunParams
.dt
,
2253 PatternFromState(aRunParams
.context
), matPtr
);
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
) {
2308 FontDrawParams fontParams
;
2310 if (aRunParams
.drawOpts
) {
2311 fontParams
.drawOptions
= *aRunParams
.drawOpts
;
2314 fontParams
.scaledFont
= GetScaledFont(aRunParams
);
2315 if (!fontParams
.scaledFont
) {
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
);
2336 fontParams
.isVerticalFont
= aRunParams
.isVerticalRun
;
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
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
2365 (aOrientation
== gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
2368 gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT
2371 // If we're rendering a sideways run, we need to push a rotation transform to
2373 if (sidewaysDir
!= 0.0f
) {
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
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
|
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};
2407 (aOrientation
== ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
)
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()
2419 . // translate origin for 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
&&
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
);
2458 aRunParams
.context
->CurrentMatrix()
2460 .PreMultiply(gfx::Matrix(1, 0, -fontParams
.obliqueSkew
, 1, 0, 0))
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.
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
;
2509 // use as many strikes as needed for the increased advance
2510 fontParams
.extraStrikes
= NS_lroundf(std::max(1.0, extraStrikes
));
2513 // Degenerate transform?!
2514 fontParams
.extraStrikes
= 0;
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
);
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
;
2542 fontParams
.fontExtents
.width
+=
2543 float(fontParams
.extraStrikes
) * fontParams
.synBoldOnePixelOffset
;
2547 bool oldSubpixelAA
= aRunParams
.dt
->GetPermitSubpixelAA();
2548 if (!AllowSubpixelAA()) {
2549 aRunParams
.dt
->SetPermitSubpixelAA(false);
2553 Matrix oldMat
= aRunParams
.dt
->GetTransform();
2555 fontParams
.drawOptions
.mAntialiasMode
= Get2DAAMode(mAntialiasOption
);
2557 if (mStyle
.baselineOffset
!= 0.0) {
2559 mStyle
.baselineOffset
* aTextRun
->GetAppUnitsPerDevUnit() * baselineDir
;
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
2568 GlyphBufferAzure
buffer(aRunParams
, fontParams
);
2569 if (fontParams
.haveSVGGlyphs
|| fontParams
.haveColorGlyphs
||
2570 fontParams
.extraStrikes
||
2571 (fontParams
.obliqueSkew
!= 0.0f
&& fontParams
.isVerticalFont
&&
2573 if (aRunParams
.spacing
) {
2575 DrawGlyphs
<FontComplexityT::ComplexFont
, SpacingT::HasSpacing
>(
2576 aTextRun
, aStart
, aEnd
- aStart
, aPt
, offsetMatrix
, buffer
);
2579 DrawGlyphs
<FontComplexityT::ComplexFont
, SpacingT::NoSpacing
>(
2580 aTextRun
, aStart
, aEnd
- aStart
, aPt
, offsetMatrix
, buffer
);
2583 if (aRunParams
.spacing
) {
2585 DrawGlyphs
<FontComplexityT::SimpleFont
, SpacingT::HasSpacing
>(
2586 aTextRun
, aStart
, aEnd
- aStart
, aPt
, offsetMatrix
, buffer
);
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
)) {
2623 // WebRender doesn't support SVG Glyphs.
2624 // (pretend to succeed, output doesn't matter, we will emit a blob)
2625 aTextDrawer
->FoundUnsupportedFeature();
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();
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();
2666 auto* colr
= GetFontEntry()->GetCOLR();
2667 const auto* paintGraph
= COLRFonts::GetGlyphPaintGraph(colr
, aGlyphId
);
2668 const gfxHarfBuzzShaper
* hbShaper
= nullptr;
2670 // We need the hbShaper to get color glyph bounds, so check that it's
2672 hbShaper
= GetHarfBuzzShaper();
2673 if (!hbShaper
&& !hbShaper
->IsInitialized()) {
2677 aTextDrawer
->FoundUnsupportedFeature();
2681 const auto* layers
=
2682 paintGraph
? nullptr : COLRFonts::GetGlyphLayers(colr
, aGlyphId
);
2684 if (!paintGraph
&& !layers
) {
2688 // For reasonable font sizes, use a cache of rasterized glyphs.
2689 bool useCache
= GetAdjustedSize() <= 256.0;
2691 // If the composition op is not OVER, rasterize to a temporary surface
2692 // and then composite to the destination, even if we're not caching.
2693 // But we can't do this if the target is a TextDrawTarget, as it doesn't
2694 // support DrawSurface.
2695 RefPtr
<SourceSurface
> snapshot
;
2697 aFontParams
.drawOptions
.mCompositionOp
!= CompositionOp::OP_OVER
) &&
2698 aDrawTarget
->GetBackendType() != BackendType::WEBRENDER_TEXT
) {
2699 AutoWriteLock
lock(mLock
);
2700 if (!mColorGlyphCache
&& useCache
) {
2701 mColorGlyphCache
= MakeUnique
<ColorGlyphCache
>();
2706 bounds
= COLRFonts::GetColorGlyphBounds(
2707 colr
, hbShaper
->GetHBFont(), aGlyphId
, aDrawTarget
,
2708 aFontParams
.scaledFont
, mFUnitsConvFactor
);
2710 bounds
= GetFontEntry()->GetFontExtents(mFUnitsConvFactor
);
2714 // Tell the cache what colors we're using; if they have changed, it
2715 // will discard any currently-cached entries.
2716 HashMap
<uint32_t, RefPtr
<SourceSurface
>>::AddPtr cached
;
2718 mColorGlyphCache
->SetColors(aFontParams
.currentColor
,
2719 aFontParams
.palette
);
2720 cached
= mColorGlyphCache
->mCache
.lookupForAdd(aGlyphId
);
2722 snapshot
= cached
->value();
2727 // Create a temporary DrawTarget and render the glyph to it.
2728 IntSize
size(int(bounds
.width
), int(bounds
.height
));
2729 SurfaceFormat format
= SurfaceFormat::B8G8R8A8
;
2731 Factory::CreateDrawTarget(BackendType::SKIA
, size
, format
);
2733 // Use OP_OVER and opaque alpha to create the glyph snapshot.
2734 DrawOptions
drawOptions(aFontParams
.drawOptions
);
2735 drawOptions
.mCompositionOp
= CompositionOp::OP_OVER
;
2736 drawOptions
.mAlpha
= 1.0f
;
2739 ok
= COLRFonts::PaintGlyphGraph(
2740 colr
, hbShaper
->GetHBFont(), paintGraph
, target
, nullptr,
2741 aFontParams
.scaledFont
, drawOptions
, -bounds
.TopLeft(),
2742 aFontParams
.currentColor
, aFontParams
.palette
->Colors(), aGlyphId
,
2745 auto face(GetFontEntry()->GetHBFace());
2746 ok
= COLRFonts::PaintGlyphLayers(
2747 colr
, face
, layers
, target
, nullptr, aFontParams
.scaledFont
,
2748 drawOptions
, -bounds
.TopLeft(), aFontParams
.currentColor
,
2749 aFontParams
.palette
->Colors());
2752 snapshot
= target
->Snapshot();
2754 // Save a snapshot of the rendering in the cache.
2755 // (We ignore potential failure here, and just paint the snapshot
2756 // without caching it.)
2757 Unused
<< mColorGlyphCache
->mCache
.add(cached
, aGlyphId
, snapshot
);
2763 // Paint the snapshot using the appropriate composition op.
2764 aDrawTarget
->DrawSurface(snapshot
,
2765 Rect(aPoint
+ bounds
.TopLeft(), bounds
.Size()),
2766 Rect(Point(), bounds
.Size()),
2767 DrawSurfaceOptions(), aFontParams
.drawOptions
);
2772 // If we didn't paint from a cached or temporary snapshot, just render
2773 // directly to the destination drawTarget.
2775 return COLRFonts::PaintGlyphGraph(
2776 colr
, hbShaper
->GetHBFont(), paintGraph
, aDrawTarget
, aTextDrawer
,
2777 aFontParams
.scaledFont
, aFontParams
.drawOptions
, aPoint
,
2778 aFontParams
.currentColor
, aFontParams
.palette
->Colors(), aGlyphId
,
2783 auto face(GetFontEntry()->GetHBFace());
2784 return COLRFonts::PaintGlyphLayers(
2785 colr
, face
, layers
, aDrawTarget
, aTextDrawer
, aFontParams
.scaledFont
,
2786 aFontParams
.drawOptions
, aPoint
, aFontParams
.currentColor
,
2787 aFontParams
.palette
->Colors());
2793 void gfxFont::ColorGlyphCache::SetColors(sRGBColor aCurrentColor
,
2794 FontPalette
* aPalette
) {
2795 if (aCurrentColor
!= mCurrentColor
|| aPalette
!= mPalette
) {
2797 mCurrentColor
= aCurrentColor
;
2798 mPalette
= aPalette
;
2802 bool gfxFont::HasColorGlyphFor(uint32_t aCh
, uint32_t aNextCh
) {
2803 // Bitmap fonts are assumed to provide "color" glyphs for all supported chars.
2804 gfxFontEntry
* fe
= GetFontEntry();
2805 if (fe
->HasColorBitmapTable()) {
2808 // Use harfbuzz shaper to look up the default glyph ID for the character.
2809 auto* shaper
= GetHarfBuzzShaper();
2814 if (gfxFontUtils::IsVarSelector(aNextCh
)) {
2815 gid
= shaper
->GetVariationGlyph(aCh
, aNextCh
);
2818 gid
= shaper
->GetNominalGlyph(aCh
);
2824 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1801521:
2825 // Emoji special-case: flag sequences NOT based on Regional Indicator pairs
2826 // use the BLACK FLAG character plus a series of plane-14 TAG LETTERs, e.g.
2827 // England = <black-flag, tag-G, tag-B, tag-E, tag-N, tag-G, tag-cancel>
2828 // Here, we don't check for support of the entire sequence (too much
2829 // expensive lookahead), but we check that the font at least supports the
2830 // first of the tag letter codes, because if it doesn't, we're at risk of
2831 // just getting an undifferentiated black flag glyph.
2832 if (gfxFontUtils::IsEmojiFlagAndTag(aCh
, aNextCh
)) {
2833 if (!shaper
->GetNominalGlyph(aNextCh
)) {
2838 // Check if there is a COLR/CPAL or SVG glyph for this ID.
2839 if (fe
->TryGetColorGlyphs() &&
2840 (COLRFonts::GetGlyphPaintGraph(fe
->GetCOLR(), gid
) ||
2841 COLRFonts::GetGlyphLayers(fe
->GetCOLR(), gid
))) {
2844 if (fe
->TryGetSVGData(this) && fe
->HasSVGGlyph(gid
)) {
2850 static void UnionRange(gfxFloat aX
, gfxFloat
* aDestMin
, gfxFloat
* aDestMax
) {
2851 *aDestMin
= std::min(*aDestMin
, aX
);
2852 *aDestMax
= std::max(*aDestMax
, aX
);
2855 // We get precise glyph extents if the textrun creator requested them, or
2856 // if the font is a user font --- in which case the author may be relying
2857 // on overflowing glyphs.
2858 static bool NeedsGlyphExtents(gfxFont
* aFont
, const gfxTextRun
* aTextRun
) {
2859 return (aTextRun
->GetFlags() &
2860 gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX
) ||
2861 aFont
->GetFontEntry()->IsUserFont();
2864 bool gfxFont::IsSpaceGlyphInvisible(DrawTarget
* aRefDrawTarget
,
2865 const gfxTextRun
* aTextRun
) {
2866 gfxFontEntry::LazyFlag flag
= mFontEntry
->mSpaceGlyphIsInvisible
;
2867 if (flag
== gfxFontEntry::LazyFlag::Uninitialized
&&
2868 GetAdjustedSize() >= 1.0) {
2869 gfxGlyphExtents
* extents
=
2870 GetOrCreateGlyphExtents(aTextRun
->GetAppUnitsPerDevUnit());
2871 gfxRect glyphExtents
;
2872 flag
= extents
->GetTightGlyphExtentsAppUnits(
2873 this, aRefDrawTarget
, GetSpaceGlyph(), &glyphExtents
) &&
2874 glyphExtents
.IsEmpty()
2875 ? gfxFontEntry::LazyFlag::Yes
2876 : gfxFontEntry::LazyFlag::No
;
2877 mFontEntry
->mSpaceGlyphIsInvisible
= flag
;
2879 return flag
== gfxFontEntry::LazyFlag::Yes
;
2882 bool gfxFont::MeasureGlyphs(const gfxTextRun
* aTextRun
, uint32_t aStart
,
2883 uint32_t aEnd
, BoundingBoxType aBoundingBoxType
,
2884 DrawTarget
* aRefDrawTarget
, Spacing
* aSpacing
,
2885 gfxGlyphExtents
* aExtents
, bool aIsRTL
,
2886 bool aNeedsGlyphExtents
, RunMetrics
& aMetrics
,
2887 gfxFloat
* aAdvanceMin
, gfxFloat
* aAdvanceMax
) {
2888 const gfxTextRun::CompressedGlyph
* charGlyphs
=
2889 aTextRun
->GetCharacterGlyphs();
2892 x
+= aSpacing
[0].mBefore
;
2894 uint32_t spaceGlyph
= GetSpaceGlyph();
2895 bool allGlyphsInvisible
= true;
2897 AutoReadLock
lock(aExtents
->mLock
);
2899 for (uint32_t i
= aStart
; i
< aEnd
; ++i
) {
2900 const gfxTextRun::CompressedGlyph
* glyphData
= &charGlyphs
[i
];
2901 if (glyphData
->IsSimpleGlyph()) {
2902 double advance
= glyphData
->GetSimpleAdvance();
2903 uint32_t glyphIndex
= glyphData
->GetSimpleGlyph();
2904 if (allGlyphsInvisible
) {
2905 if (glyphIndex
!= spaceGlyph
) {
2906 allGlyphsInvisible
= false;
2908 gfxFontEntry::LazyFlag flag
= mFontEntry
->mSpaceGlyphIsInvisible
;
2909 if (flag
== gfxFontEntry::LazyFlag::Uninitialized
&&
2910 GetAdjustedSize() >= 1.0) {
2911 gfxRect glyphExtents
;
2912 flag
= aExtents
->GetTightGlyphExtentsAppUnitsLocked(
2913 this, aRefDrawTarget
, spaceGlyph
, &glyphExtents
) &&
2914 glyphExtents
.IsEmpty()
2915 ? gfxFontEntry::LazyFlag::Yes
2916 : gfxFontEntry::LazyFlag::No
;
2917 mFontEntry
->mSpaceGlyphIsInvisible
= flag
;
2919 if (flag
== gfxFontEntry::LazyFlag::No
) {
2920 allGlyphsInvisible
= false;
2924 // Only get the real glyph horizontal extent if we were asked
2925 // for the tight bounding box or we're in quality mode
2926 if (aBoundingBoxType
!= LOOSE_INK_EXTENTS
|| aNeedsGlyphExtents
) {
2927 uint16_t extentsWidth
=
2928 aExtents
->GetContainedGlyphWidthAppUnitsLocked(glyphIndex
);
2929 if (extentsWidth
!= gfxGlyphExtents::INVALID_WIDTH
&&
2930 aBoundingBoxType
== LOOSE_INK_EXTENTS
) {
2931 UnionRange(x
, aAdvanceMin
, aAdvanceMax
);
2932 UnionRange(x
+ extentsWidth
, aAdvanceMin
, aAdvanceMax
);
2935 if (!aExtents
->GetTightGlyphExtentsAppUnitsLocked(
2936 this, aRefDrawTarget
, glyphIndex
, &glyphRect
)) {
2937 glyphRect
= gfxRect(0, aMetrics
.mBoundingBox
.Y(), advance
,
2938 aMetrics
.mBoundingBox
.Height());
2941 // In effect, swap left and right sidebearings of the glyph, for
2942 // proper accumulation of potentially-overlapping glyph rects.
2943 glyphRect
.MoveToX(advance
- glyphRect
.XMost());
2945 glyphRect
.MoveByX(x
);
2946 aMetrics
.mBoundingBox
= aMetrics
.mBoundingBox
.Union(glyphRect
);
2951 allGlyphsInvisible
= false;
2952 uint32_t glyphCount
= glyphData
->GetGlyphCount();
2953 if (glyphCount
> 0) {
2954 const gfxTextRun::DetailedGlyph
* details
=
2955 aTextRun
->GetDetailedGlyphs(i
);
2956 NS_ASSERTION(details
!= nullptr,
2957 "detailedGlyph record should not be missing!");
2959 for (j
= 0; j
< glyphCount
; ++j
, ++details
) {
2960 uint32_t glyphIndex
= details
->mGlyphID
;
2961 double advance
= details
->mAdvance
;
2963 if (glyphData
->IsMissing() ||
2964 !aExtents
->GetTightGlyphExtentsAppUnitsLocked(
2965 this, aRefDrawTarget
, glyphIndex
, &glyphRect
)) {
2966 // We might have failed to get glyph extents due to
2968 glyphRect
= gfxRect(0, -aMetrics
.mAscent
, advance
,
2969 aMetrics
.mAscent
+ aMetrics
.mDescent
);
2972 // Swap left/right sidebearings of the glyph, because we're doing
2973 // mirrored measurement.
2974 glyphRect
.MoveToX(advance
- glyphRect
.XMost());
2975 // Move to current x position, mirroring any x-offset amount.
2976 glyphRect
.MoveByX(x
- details
->mOffset
.x
);
2978 glyphRect
.MoveByX(x
+ details
->mOffset
.x
);
2980 glyphRect
.MoveByY(details
->mOffset
.y
);
2981 aMetrics
.mBoundingBox
= aMetrics
.mBoundingBox
.Union(glyphRect
);
2987 double space
= aSpacing
[i
- aStart
].mAfter
;
2989 space
+= aSpacing
[i
+ 1 - aStart
].mBefore
;
2995 aMetrics
.mAdvanceWidth
= x
;
2996 return allGlyphsInvisible
;
2999 bool gfxFont::MeasureGlyphs(const gfxTextRun
* aTextRun
, uint32_t aStart
,
3000 uint32_t aEnd
, BoundingBoxType aBoundingBoxType
,
3001 DrawTarget
* aRefDrawTarget
, Spacing
* aSpacing
,
3002 bool aIsRTL
, RunMetrics
& aMetrics
) {
3003 const gfxTextRun::CompressedGlyph
* charGlyphs
=
3004 aTextRun
->GetCharacterGlyphs();
3007 x
+= aSpacing
[0].mBefore
;
3009 uint32_t spaceGlyph
= GetSpaceGlyph();
3010 bool allGlyphsInvisible
= true;
3012 for (uint32_t i
= aStart
; i
< aEnd
; ++i
) {
3013 const gfxTextRun::CompressedGlyph
* glyphData
= &charGlyphs
[i
];
3014 if (glyphData
->IsSimpleGlyph()) {
3015 double advance
= glyphData
->GetSimpleAdvance();
3016 uint32_t glyphIndex
= glyphData
->GetSimpleGlyph();
3017 if (allGlyphsInvisible
&&
3018 (glyphIndex
!= spaceGlyph
||
3019 !IsSpaceGlyphInvisible(aRefDrawTarget
, aTextRun
))) {
3020 allGlyphsInvisible
= false;
3024 allGlyphsInvisible
= false;
3025 uint32_t glyphCount
= glyphData
->GetGlyphCount();
3026 if (glyphCount
> 0) {
3027 const gfxTextRun::DetailedGlyph
* details
=
3028 aTextRun
->GetDetailedGlyphs(i
);
3029 NS_ASSERTION(details
!= nullptr,
3030 "detailedGlyph record should not be missing!");
3032 for (j
= 0; j
< glyphCount
; ++j
, ++details
) {
3033 double advance
= details
->mAdvance
;
3034 gfxRect
glyphRect(0, -aMetrics
.mAscent
, advance
,
3035 aMetrics
.mAscent
+ aMetrics
.mDescent
);
3037 // Swap left/right sidebearings of the glyph, because we're doing
3038 // mirrored measurement.
3039 glyphRect
.MoveToX(advance
- glyphRect
.XMost());
3040 // Move to current x position, mirroring any x-offset amount.
3041 glyphRect
.MoveByX(x
- details
->mOffset
.x
);
3043 glyphRect
.MoveByX(x
+ details
->mOffset
.x
);
3045 glyphRect
.MoveByY(details
->mOffset
.y
);
3046 aMetrics
.mBoundingBox
= aMetrics
.mBoundingBox
.Union(glyphRect
);
3052 double space
= aSpacing
[i
- aStart
].mAfter
;
3054 space
+= aSpacing
[i
+ 1 - aStart
].mBefore
;
3060 aMetrics
.mAdvanceWidth
= x
;
3061 return allGlyphsInvisible
;
3064 gfxFont::RunMetrics
gfxFont::Measure(const gfxTextRun
* aTextRun
,
3065 uint32_t aStart
, uint32_t aEnd
,
3066 BoundingBoxType aBoundingBoxType
,
3067 DrawTarget
* aRefDrawTarget
,
3069 gfx::ShapedTextFlags aOrientation
) {
3070 // If aBoundingBoxType is TIGHT_HINTED_OUTLINE_EXTENTS
3071 // and the underlying cairo font may be antialiased,
3072 // we need to create a copy in order to avoid getting cached extents.
3073 // This is only used by MathML layout at present.
3074 if (aBoundingBoxType
== TIGHT_HINTED_OUTLINE_EXTENTS
&&
3075 mAntialiasOption
!= kAntialiasNone
) {
3076 gfxFont
* nonAA
= mNonAAFont
;
3078 nonAA
= CopyWithAntialiasOption(kAntialiasNone
);
3080 if (!mNonAAFont
.compareExchange(nullptr, nonAA
)) {
3086 // if font subclass doesn't implement CopyWithAntialiasOption(),
3087 // it will return null and we'll proceed to use the existing font
3089 return nonAA
->Measure(aTextRun
, aStart
, aEnd
,
3090 TIGHT_HINTED_OUTLINE_EXTENTS
, aRefDrawTarget
,
3091 aSpacing
, aOrientation
);
3095 const int32_t appUnitsPerDevUnit
= aTextRun
->GetAppUnitsPerDevUnit();
3096 // Current position in appunits
3097 Orientation orientation
=
3098 aOrientation
== gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
3099 ? nsFontMetrics::eVertical
3100 : nsFontMetrics::eHorizontal
;
3101 const gfxFont::Metrics
& fontMetrics
= GetMetrics(orientation
);
3103 gfxFloat baselineOffset
= 0;
3104 if (aTextRun
->UseCenterBaseline() &&
3105 orientation
== nsFontMetrics::eHorizontal
) {
3106 // For a horizontal font being used in vertical writing mode with
3107 // text-orientation:mixed, the overall metrics we're accumulating
3108 // will be aimed at a center baseline. But this font's metrics were
3109 // based on the alphabetic baseline. So we compute a baseline offset
3110 // that will be applied to ascent/descent values and glyph rects
3111 // to effectively shift them relative to the baseline.
3112 // XXX Eventually we should probably use the BASE table, if present.
3113 // But it usually isn't, so we need an ad hoc adjustment for now.
3115 appUnitsPerDevUnit
* (fontMetrics
.emAscent
- fontMetrics
.emDescent
) / 2;
3119 metrics
.mAscent
= fontMetrics
.maxAscent
* appUnitsPerDevUnit
;
3120 metrics
.mDescent
= fontMetrics
.maxDescent
* appUnitsPerDevUnit
;
3122 if (aStart
== aEnd
) {
3123 // exit now before we look at aSpacing[0], which is undefined
3124 metrics
.mAscent
-= baselineOffset
;
3125 metrics
.mDescent
+= baselineOffset
;
3126 metrics
.mBoundingBox
=
3127 gfxRect(0, -metrics
.mAscent
, 0, metrics
.mAscent
+ metrics
.mDescent
);
3131 gfxFloat advanceMin
= 0, advanceMax
= 0;
3132 bool isRTL
= aTextRun
->IsRightToLeft();
3133 bool needsGlyphExtents
= NeedsGlyphExtents(this, aTextRun
);
3134 gfxGlyphExtents
* extents
=
3135 ((aBoundingBoxType
== LOOSE_INK_EXTENTS
&& !needsGlyphExtents
&&
3136 !aTextRun
->HasDetailedGlyphs()) ||
3137 MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero()))
3139 : GetOrCreateGlyphExtents(aTextRun
->GetAppUnitsPerDevUnit());
3141 bool allGlyphsInvisible
;
3143 allGlyphsInvisible
= MeasureGlyphs(
3144 aTextRun
, aStart
, aEnd
, aBoundingBoxType
, aRefDrawTarget
, aSpacing
,
3145 extents
, isRTL
, needsGlyphExtents
, metrics
, &advanceMin
, &advanceMax
);
3147 allGlyphsInvisible
=
3148 MeasureGlyphs(aTextRun
, aStart
, aEnd
, aBoundingBoxType
, aRefDrawTarget
,
3149 aSpacing
, isRTL
, metrics
);
3152 if (allGlyphsInvisible
) {
3153 metrics
.mBoundingBox
.SetEmpty();
3154 } else if (aBoundingBoxType
== LOOSE_INK_EXTENTS
) {
3155 UnionRange(metrics
.mAdvanceWidth
, &advanceMin
, &advanceMax
);
3156 gfxRect
fontBox(advanceMin
, -metrics
.mAscent
, advanceMax
- advanceMin
,
3157 metrics
.mAscent
+ metrics
.mDescent
);
3158 metrics
.mBoundingBox
= metrics
.mBoundingBox
.Union(fontBox
);
3162 // Reverse the effect of having swapped each glyph's sidebearings, to get
3163 // the correct sidebearings of the merged bounding box.
3164 metrics
.mBoundingBox
.MoveToX(metrics
.mAdvanceWidth
-
3165 metrics
.mBoundingBox
.XMost());
3168 // If the font may be rendered with a fake-italic effect, we need to allow
3169 // for the top-right of the glyphs being skewed to the right, and the
3170 // bottom-left being skewed further left.
3171 gfxFloat skew
= SkewForSyntheticOblique();
3173 gfxFloat extendLeftEdge
, extendRightEdge
;
3174 if (orientation
== nsFontMetrics::eVertical
) {
3175 // The glyph will actually be skewed vertically, but "left" and "right"
3176 // here refer to line-left (physical top) and -right (bottom), so these
3177 // are still the directions in which we need to extend the box.
3178 extendLeftEdge
= skew
< 0.0 ? ceil(-skew
* metrics
.mBoundingBox
.XMost())
3179 : ceil(skew
* -metrics
.mBoundingBox
.X());
3180 extendRightEdge
= skew
< 0.0 ? ceil(-skew
* -metrics
.mBoundingBox
.X())
3181 : ceil(skew
* metrics
.mBoundingBox
.XMost());
3183 extendLeftEdge
= skew
< 0.0 ? ceil(-skew
* -metrics
.mBoundingBox
.Y())
3184 : ceil(skew
* metrics
.mBoundingBox
.YMost());
3185 extendRightEdge
= skew
< 0.0 ? ceil(-skew
* metrics
.mBoundingBox
.YMost())
3186 : ceil(skew
* -metrics
.mBoundingBox
.Y());
3188 metrics
.mBoundingBox
.SetWidth(metrics
.mBoundingBox
.Width() +
3189 extendLeftEdge
+ extendRightEdge
);
3190 metrics
.mBoundingBox
.MoveByX(-extendLeftEdge
);
3193 if (baselineOffset
!= 0) {
3194 metrics
.mAscent
-= baselineOffset
;
3195 metrics
.mDescent
+= baselineOffset
;
3196 metrics
.mBoundingBox
.MoveByY(baselineOffset
);
3202 bool gfxFont::AgeCachedWords() {
3203 mozilla::AutoWriteLock
lock(mLock
);
3205 for (auto it
= mWordCache
->modIter(); !it
.done(); it
.next()) {
3206 auto& entry
= it
.get().value();
3208 NS_ASSERTION(entry
, "cache entry has no gfxShapedWord!");
3210 } else if (entry
->IncrementAge() == kShapedWordCacheMaxAge
) {
3214 return mWordCache
->empty();
3219 void gfxFont::NotifyGlyphsChanged() const {
3220 AutoReadLock
lock(mLock
);
3221 uint32_t i
, count
= mGlyphExtentsArray
.Length();
3222 for (i
= 0; i
< count
; ++i
) {
3223 // Flush cached extents array
3224 mGlyphExtentsArray
[i
]->NotifyGlyphsChanged();
3227 if (mGlyphChangeObservers
) {
3228 for (const auto& key
: *mGlyphChangeObservers
) {
3229 key
->NotifyGlyphsChanged();
3234 // If aChar is a "word boundary" for shaped-word caching purposes, return it;
3236 static char16_t
IsBoundarySpace(char16_t aChar
, char16_t aNextChar
) {
3237 if ((aChar
== ' ' || aChar
== 0x00A0) && !IsClusterExtender(aNextChar
)) {
3243 // In 8-bit text, there cannot be any cluster-extenders.
3244 static uint8_t IsBoundarySpace(uint8_t aChar
, uint8_t aNextChar
) {
3245 if (aChar
== ' ' || aChar
== 0x00A0) {
3252 # define GFX_MAYBE_UNUSED __attribute__((unused))
3254 # define GFX_MAYBE_UNUSED
3257 template <typename T
, typename Func
>
3258 bool gfxFont::ProcessShapedWordInternal(
3259 DrawTarget
* aDrawTarget
, const T
* aText
, uint32_t aLength
, uint32_t aHash
,
3260 Script aRunScript
, nsAtom
* aLanguage
, bool aVertical
,
3261 int32_t aAppUnitsPerDevUnit
, gfx::ShapedTextFlags aFlags
,
3262 RoundingFlags aRounding
, gfxTextPerfMetrics
* aTextPerf GFX_MAYBE_UNUSED
,
3264 WordCacheKey
key(aText
, aLength
, aHash
, aRunScript
, aLanguage
,
3265 aAppUnitsPerDevUnit
, aFlags
, aRounding
);
3267 // If we have a word cache, attempt to look up the word in it.
3268 AutoReadLock
lock(mLock
);
3270 // if there's a cached entry for this word, just return it
3271 if (auto entry
= mWordCache
->lookup(key
)) {
3272 entry
->value()->ResetAge();
3273 #ifndef RELEASE_OR_BETA
3275 // XXX we should make sure this is atomic
3276 aTextPerf
->current
.wordCacheHit
++;
3279 aCallback(entry
->value().get());
3285 // We didn't find a cached word (or don't even have a cache yet), so create
3286 // a new gfxShapedWord and cache it. We don't have to lock during shaping,
3287 // only when it comes time to cache the new entry.
3289 UniquePtr
<gfxShapedWord
> newShapedWord(
3290 gfxShapedWord::Create(aText
, aLength
, aRunScript
, aLanguage
,
3291 aAppUnitsPerDevUnit
, aFlags
, aRounding
));
3292 if (!newShapedWord
) {
3293 NS_WARNING("failed to create gfxShapedWord - expect missing text");
3296 DebugOnly
<bool> ok
=
3297 ShapeText(aDrawTarget
, aText
, 0, aLength
, aRunScript
, aLanguage
,
3298 aVertical
, aRounding
, newShapedWord
.get());
3299 NS_WARNING_ASSERTION(ok
, "failed to shape word - expect garbled text");
3302 // We're going to cache the new shaped word, so lock for writing now.
3303 AutoWriteLock
lock(mLock
);
3305 mWordCache
= MakeUnique
<HashMap
<WordCacheKey
, UniquePtr
<gfxShapedWord
>,
3306 WordCacheKey::HashPolicy
>>();
3308 // If the cache is getting too big, flush it and start over.
3309 uint32_t wordCacheMaxEntries
=
3310 gfxPlatform::GetPlatform()->WordCacheMaxEntries();
3311 if (mWordCache
->count() > wordCacheMaxEntries
) {
3312 // Flush the cache if it is getting overly big.
3313 NS_WARNING("flushing shaped-word cache");
3314 ClearCachedWordsLocked();
3318 // Update key so that it references the text stored in the newShapedWord,
3319 // which is guaranteed to live as long as the hashtable entry.
3320 if ((key
.mTextIs8Bit
= newShapedWord
->TextIs8Bit())) {
3321 key
.mText
.mSingle
= newShapedWord
->Text8Bit();
3323 key
.mText
.mDouble
= newShapedWord
->TextUnicode();
3325 auto entry
= mWordCache
->lookupForAdd(key
);
3327 // It's unlikely, but maybe another thread got there before us...
3329 // Use the existing entry; the newShapedWord will be discarded.
3330 entry
->value()->ResetAge();
3331 #ifndef RELEASE_OR_BETA
3333 aTextPerf
->current
.wordCacheHit
++;
3336 aCallback(entry
->value().get());
3340 if (!mWordCache
->add(entry
, key
, std::move(newShapedWord
))) {
3341 NS_WARNING("failed to cache gfxShapedWord - expect missing text");
3345 #ifndef RELEASE_OR_BETA
3347 aTextPerf
->current
.wordCacheMiss
++;
3350 aCallback(entry
->value().get());
3353 gfxFontCache::GetCache()->RunWordCacheExpirationTimer();
3357 bool gfxFont::WordCacheKey::HashPolicy::match(const Key
& aKey
,
3358 const Lookup
& aLookup
) {
3359 if (aKey
.mLength
!= aLookup
.mLength
|| aKey
.mFlags
!= aLookup
.mFlags
||
3360 aKey
.mRounding
!= aLookup
.mRounding
||
3361 aKey
.mAppUnitsPerDevUnit
!= aLookup
.mAppUnitsPerDevUnit
||
3362 aKey
.mScript
!= aLookup
.mScript
|| aKey
.mLanguage
!= aLookup
.mLanguage
) {
3366 if (aKey
.mTextIs8Bit
) {
3367 if (aLookup
.mTextIs8Bit
) {
3368 return (0 == memcmp(aKey
.mText
.mSingle
, aLookup
.mText
.mSingle
,
3369 aKey
.mLength
* sizeof(uint8_t)));
3371 // The lookup key has 16-bit text, even though all the characters are < 256,
3372 // so the TEXT_IS_8BIT flag was set and the cached ShapedWord we're
3373 // comparing with will have 8-bit text.
3374 const uint8_t* s1
= aKey
.mText
.mSingle
;
3375 const char16_t
* s2
= aLookup
.mText
.mDouble
;
3376 const char16_t
* s2end
= s2
+ aKey
.mLength
;
3377 while (s2
< s2end
) {
3378 if (*s1
++ != *s2
++) {
3384 NS_ASSERTION(!(aLookup
.mFlags
& gfx::ShapedTextFlags::TEXT_IS_8BIT
) &&
3385 !aLookup
.mTextIs8Bit
,
3386 "didn't expect 8-bit text here");
3387 return (0 == memcmp(aKey
.mText
.mDouble
, aLookup
.mText
.mDouble
,
3388 aKey
.mLength
* sizeof(char16_t
)));
3391 bool gfxFont::ProcessSingleSpaceShapedWord(
3392 DrawTarget
* aDrawTarget
, bool aVertical
, int32_t aAppUnitsPerDevUnit
,
3393 gfx::ShapedTextFlags aFlags
, RoundingFlags aRounding
,
3394 const std::function
<void(gfxShapedWord
*)>& aCallback
) {
3395 static const uint8_t space
= ' ';
3396 return ProcessShapedWordInternal(
3397 aDrawTarget
, &space
, 1, gfxShapedWord::HashMix(0, ' '), Script::LATIN
,
3398 /* aLanguage = */ nullptr, aVertical
, aAppUnitsPerDevUnit
, aFlags
,
3399 aRounding
, nullptr, aCallback
);
3402 bool gfxFont::ShapeText(DrawTarget
* aDrawTarget
, const uint8_t* aText
,
3403 uint32_t aOffset
, uint32_t aLength
, Script aScript
,
3404 nsAtom
* aLanguage
, bool aVertical
,
3405 RoundingFlags aRounding
, gfxShapedText
* aShapedText
) {
3406 nsDependentCSubstring
ascii((const char*)aText
, aLength
);
3408 AppendASCIItoUTF16(ascii
, utf16
);
3409 if (utf16
.Length() != aLength
) {
3412 return ShapeText(aDrawTarget
, utf16
.BeginReading(), aOffset
, aLength
, aScript
,
3413 aLanguage
, aVertical
, aRounding
, aShapedText
);
3416 bool gfxFont::ShapeText(DrawTarget
* aDrawTarget
, const char16_t
* aText
,
3417 uint32_t aOffset
, uint32_t aLength
, Script aScript
,
3418 nsAtom
* aLanguage
, bool aVertical
,
3419 RoundingFlags aRounding
, gfxShapedText
* aShapedText
) {
3420 // XXX Currently, we do all vertical shaping through harfbuzz.
3421 // Vertical graphite support may be wanted as a future enhancement.
3422 // XXX Graphite shaping currently only supported on the main thread!
3423 // Worker-thread shaping (offscreen canvas) will always go via harfbuzz.
3424 if (FontCanSupportGraphite() && !aVertical
&& NS_IsMainThread()) {
3425 if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
3426 gfxGraphiteShaper
* shaper
= mGraphiteShaper
;
3428 shaper
= new gfxGraphiteShaper(this);
3429 if (!mGraphiteShaper
.compareExchange(nullptr, shaper
)) {
3431 shaper
= mGraphiteShaper
;
3434 if (shaper
->ShapeText(aDrawTarget
, aText
, aOffset
, aLength
, aScript
,
3435 aLanguage
, aVertical
, aRounding
, aShapedText
)) {
3436 PostShapingFixup(aDrawTarget
, aText
, aOffset
, aLength
, aVertical
,
3443 gfxHarfBuzzShaper
* shaper
= GetHarfBuzzShaper();
3445 shaper
->ShapeText(aDrawTarget
, aText
, aOffset
, aLength
, aScript
,
3446 aLanguage
, aVertical
, aRounding
, aShapedText
)) {
3447 PostShapingFixup(aDrawTarget
, aText
, aOffset
, aLength
, aVertical
,
3449 if (GetFontEntry()->HasTrackingTable()) {
3450 // Convert font size from device pixels back to CSS px
3451 // to use in selecting tracking value
3452 gfxFloat trackSize
= GetAdjustedSize() *
3453 aShapedText
->GetAppUnitsPerDevUnit() /
3454 AppUnitsPerCSSPixel();
3455 // Usually, a given font will be used with the same appunit scale, so we
3456 // can cache the tracking value rather than recompute it every time.
3458 AutoReadLock
lock(mLock
);
3459 if (trackSize
== mCachedTrackingSize
) {
3460 // Applying tracking is a lot like the adjustment we do for
3461 // synthetic bold: we want to apply between clusters, not to
3462 // non-spacing glyphs within a cluster. So we can reuse that
3464 aShapedText
->ApplyTrackingToClusters(mTracking
, aOffset
, aLength
);
3468 // We didn't have the appropriate tracking value cached yet.
3469 AutoWriteLock
lock(mLock
);
3470 if (trackSize
!= mCachedTrackingSize
) {
3471 mCachedTrackingSize
= trackSize
;
3473 GetFontEntry()->TrackingForCSSPx(trackSize
) * mFUnitsConvFactor
;
3475 aShapedText
->ApplyTrackingToClusters(mTracking
, aOffset
, aLength
);
3480 NS_WARNING_ASSERTION(false, "shaper failed, expect scrambled/missing text");
3484 void gfxFont::PostShapingFixup(DrawTarget
* aDrawTarget
, const char16_t
* aText
,
3485 uint32_t aOffset
, uint32_t aLength
,
3486 bool aVertical
, gfxShapedText
* aShapedText
) {
3487 if (ApplySyntheticBold()) {
3488 const Metrics
& metrics
= GetMetrics(aVertical
? nsFontMetrics::eVertical
3489 : nsFontMetrics::eHorizontal
);
3490 if (metrics
.maxAdvance
> metrics
.aveCharWidth
) {
3491 aShapedText
->ApplyTrackingToClusters(GetSyntheticBoldOffset(), aOffset
,
3497 #define MAX_SHAPING_LENGTH \
3498 32760 // slightly less than 32K, trying to avoid
3499 // over-stressing platform shapers
3500 #define BACKTRACK_LIMIT \
3501 16 // backtrack this far looking for a good place
3502 // to split into fragments for separate shaping
3504 template <typename T
>
3505 bool gfxFont::ShapeFragmentWithoutWordCache(DrawTarget
* aDrawTarget
,
3506 const T
* aText
, uint32_t aOffset
,
3507 uint32_t aLength
, Script aScript
,
3508 nsAtom
* aLanguage
, bool aVertical
,
3509 RoundingFlags aRounding
,
3510 gfxTextRun
* aTextRun
) {
3511 aTextRun
->SetupClusterBoundaries(aOffset
, aText
, aLength
);
3515 while (ok
&& aLength
> 0) {
3516 uint32_t fragLen
= aLength
;
3518 // limit the length of text we pass to shapers in a single call
3519 if (fragLen
> MAX_SHAPING_LENGTH
) {
3520 fragLen
= MAX_SHAPING_LENGTH
;
3522 // in the 8-bit case, there are no multi-char clusters,
3523 // so we don't need to do this check
3524 if constexpr (sizeof(T
) == sizeof(char16_t
)) {
3526 for (i
= 0; i
< BACKTRACK_LIMIT
; ++i
) {
3527 if (aTextRun
->IsClusterStart(aOffset
+ fragLen
- i
)) {
3532 if (i
== BACKTRACK_LIMIT
) {
3533 // if we didn't find any cluster start while backtracking,
3534 // just check that we're not in the middle of a surrogate
3535 // pair; back up by one code unit if we are.
3536 if (NS_IS_SURROGATE_PAIR(aText
[fragLen
- 1], aText
[fragLen
])) {
3543 ok
= ShapeText(aDrawTarget
, aText
, aOffset
, fragLen
, aScript
, aLanguage
,
3544 aVertical
, aRounding
, aTextRun
);
3554 // Check if aCh is an unhandled control character that should be displayed
3555 // as a hexbox rather than rendered by some random font on the system.
3556 // We exclude \r as stray s are rather common (bug 941940).
3557 // Note that \n and \t don't come through here, as they have specific
3558 // meanings that have already been handled.
3559 static bool IsInvalidControlChar(uint32_t aCh
) {
3560 return aCh
!= '\r' && ((aCh
& 0x7f) < 0x20 || aCh
== 0x7f);
3563 template <typename T
>
3564 bool gfxFont::ShapeTextWithoutWordCache(DrawTarget
* aDrawTarget
, const T
* aText
,
3565 uint32_t aOffset
, uint32_t aLength
,
3566 Script aScript
, nsAtom
* aLanguage
,
3567 bool aVertical
, RoundingFlags aRounding
,
3568 gfxTextRun
* aTextRun
) {
3569 uint32_t fragStart
= 0;
3572 for (uint32_t i
= 0; i
<= aLength
&& ok
; ++i
) {
3573 T ch
= (i
< aLength
) ? aText
[i
] : '\n';
3574 bool invalid
= gfxFontGroup::IsInvalidChar(ch
);
3575 uint32_t length
= i
- fragStart
;
3577 // break into separate fragments when we hit an invalid char
3583 ok
= ShapeFragmentWithoutWordCache(
3584 aDrawTarget
, aText
+ fragStart
, aOffset
+ fragStart
, length
, aScript
,
3585 aLanguage
, aVertical
, aRounding
, aTextRun
);
3592 // fragment was terminated by an invalid char: skip it,
3593 // unless it's a control char that we want to show as a hexbox,
3594 // but record where TAB or NEWLINE occur
3596 aTextRun
->SetIsTab(aOffset
+ i
);
3597 } else if (ch
== '\n') {
3598 aTextRun
->SetIsNewline(aOffset
+ i
);
3599 } else if (GetGeneralCategory(ch
) == HB_UNICODE_GENERAL_CATEGORY_FORMAT
) {
3600 aTextRun
->SetIsFormattingControl(aOffset
+ i
);
3601 } else if (IsInvalidControlChar(ch
) &&
3602 !(aTextRun
->GetFlags() &
3603 gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS
)) {
3604 if (GetFontEntry()->IsUserFont() && HasCharacter(ch
)) {
3605 ShapeFragmentWithoutWordCache(aDrawTarget
, aText
+ i
, aOffset
+ i
, 1,
3606 aScript
, aLanguage
, aVertical
, aRounding
,
3609 aTextRun
->SetMissingGlyph(aOffset
+ i
, ch
, this);
3615 NS_WARNING_ASSERTION(ok
, "failed to shape text - expect garbled text");
3619 #ifndef RELEASE_OR_BETA
3620 # define TEXT_PERF_INCR(tp, m) (tp ? (tp)->current.m++ : 0)
3622 # define TEXT_PERF_INCR(tp, m)
3625 inline static bool IsChar8Bit(uint8_t /*aCh*/) { return true; }
3626 inline static bool IsChar8Bit(char16_t aCh
) { return aCh
< 0x100; }
3628 inline static bool HasSpaces(const uint8_t* aString
, uint32_t aLen
) {
3629 return memchr(aString
, 0x20, aLen
) != nullptr;
3632 inline static bool HasSpaces(const char16_t
* aString
, uint32_t aLen
) {
3633 for (const char16_t
* ch
= aString
; ch
< aString
+ aLen
; ch
++) {
3641 template <typename T
>
3642 bool gfxFont::SplitAndInitTextRun(
3643 DrawTarget
* aDrawTarget
, gfxTextRun
* aTextRun
,
3644 const T
* aString
, // text for this font run
3645 uint32_t aRunStart
, // position in the textrun
3646 uint32_t aRunLength
, Script aRunScript
, nsAtom
* aLanguage
,
3647 ShapedTextFlags aOrientation
) {
3648 if (aRunLength
== 0) {
3652 gfxTextPerfMetrics
* tp
= nullptr;
3653 RoundingFlags rounding
= GetRoundOffsetsToPixels(aDrawTarget
);
3655 #ifndef RELEASE_OR_BETA
3656 tp
= aTextRun
->GetFontGroup()->GetTextPerfMetrics();
3658 if (mStyle
.systemFont
) {
3659 tp
->current
.numChromeTextRuns
++;
3661 tp
->current
.numContentTextRuns
++;
3663 tp
->current
.numChars
+= aRunLength
;
3664 if (aRunLength
> tp
->current
.maxTextRunLen
) {
3665 tp
->current
.maxTextRunLen
= aRunLength
;
3670 uint32_t wordCacheCharLimit
=
3671 gfxPlatform::GetPlatform()->WordCacheCharLimit();
3673 bool vertical
= aOrientation
== ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
;
3675 // If spaces can participate in shaping (e.g. within lookups for automatic
3676 // fractions), need to shape without using the word cache which segments
3677 // textruns on space boundaries. Word cache can be used if the textrun
3678 // is short enough to fit in the word cache and it lacks spaces.
3679 tainted_boolean_hint t_canParticipate
=
3680 SpaceMayParticipateInShaping(aRunScript
);
3681 bool canParticipate
= t_canParticipate
.unverified_safe_because(
3682 "We need to ensure that this function operates safely independent of "
3683 "t_canParticipate. The worst that can happen here is that the decision "
3684 "to use the cache is incorrectly made, resulting in a bad "
3685 "rendering/slowness. However, this would not compromise the memory "
3686 "safety of Firefox in any way, and can thus be permitted");
3688 if (canParticipate
) {
3689 if (aRunLength
> wordCacheCharLimit
|| HasSpaces(aString
, aRunLength
)) {
3690 TEXT_PERF_INCR(tp
, wordCacheSpaceRules
);
3691 return ShapeTextWithoutWordCache(aDrawTarget
, aString
, aRunStart
,
3692 aRunLength
, aRunScript
, aLanguage
,
3693 vertical
, rounding
, aTextRun
);
3697 // the only flags we care about for ShapedWord construction/caching
3698 gfx::ShapedTextFlags flags
= aTextRun
->GetFlags();
3699 flags
&= (gfx::ShapedTextFlags::TEXT_IS_RTL
|
3700 gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES
|
3701 gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT
|
3702 gfx::ShapedTextFlags::TEXT_ORIENT_MASK
);
3703 if constexpr (sizeof(T
) == sizeof(uint8_t)) {
3704 flags
|= gfx::ShapedTextFlags::TEXT_IS_8BIT
;
3707 uint32_t wordStart
= 0;
3709 bool wordIs8Bit
= true;
3710 int32_t appUnitsPerDevUnit
= aTextRun
->GetAppUnitsPerDevUnit();
3712 T nextCh
= aString
[0];
3713 for (uint32_t i
= 0; i
<= aRunLength
; ++i
) {
3715 nextCh
= (i
< aRunLength
- 1) ? aString
[i
+ 1] : '\n';
3716 T boundary
= IsBoundarySpace(ch
, nextCh
);
3717 bool invalid
= !boundary
&& gfxFontGroup::IsInvalidChar(ch
);
3718 uint32_t length
= i
- wordStart
;
3720 // break into separate ShapedWords when we hit an invalid char,
3721 // or a boundary space (always handled individually),
3722 // or the first non-space after a space
3723 if (!boundary
&& !invalid
) {
3724 if (!IsChar8Bit(ch
)) {
3727 // include this character in the hash, and move on to next
3728 hash
= gfxShapedWord::HashMix(hash
, ch
);
3732 // We've decided to break here (i.e. we're at the end of a "word");
3733 // shape the word and add it to the textrun.
3734 // For words longer than the limit, we don't use the
3735 // font's word cache but just shape directly into the textrun.
3736 if (length
> wordCacheCharLimit
) {
3737 TEXT_PERF_INCR(tp
, wordCacheLong
);
3738 bool ok
= ShapeFragmentWithoutWordCache(
3739 aDrawTarget
, aString
+ wordStart
, aRunStart
+ wordStart
, length
,
3740 aRunScript
, aLanguage
, vertical
, rounding
, aTextRun
);
3744 } else if (length
> 0) {
3745 gfx::ShapedTextFlags wordFlags
= flags
;
3746 // in the 8-bit version of this method, TEXT_IS_8BIT was
3747 // already set as part of |flags|, so no need for a per-word
3749 if (sizeof(T
) == sizeof(char16_t
)) {
3751 wordFlags
|= gfx::ShapedTextFlags::TEXT_IS_8BIT
;
3754 bool processed
= ProcessShapedWordInternal(
3755 aDrawTarget
, aString
+ wordStart
, length
, hash
, aRunScript
, aLanguage
,
3756 vertical
, appUnitsPerDevUnit
, wordFlags
, rounding
, tp
,
3757 [&](gfxShapedWord
* aShapedWord
) {
3758 aTextRun
->CopyGlyphDataFrom(aShapedWord
, aRunStart
+ wordStart
);
3761 return false; // failed, presumably out of memory?
3766 // word was terminated by a space: add that to the textrun
3767 MOZ_ASSERT(aOrientation
!= ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED
,
3768 "text-orientation:mixed should be resolved earlier");
3769 if (boundary
!= ' ' || !aTextRun
->SetSpaceGlyphIfSimple(
3770 this, aRunStart
+ i
, ch
, aOrientation
)) {
3771 // Currently, the only "boundary" characters we recognize are
3772 // space and no-break space, which are both 8-bit, so we force
3773 // that flag (below). If we ever change IsBoundarySpace, we
3774 // may need to revise this.
3775 // Avoid tautological-constant-out-of-range-compare in 8-bit:
3776 DebugOnly
<char16_t
> boundary16
= boundary
;
3777 NS_ASSERTION(boundary16
< 256, "unexpected boundary!");
3778 bool processed
= ProcessShapedWordInternal(
3779 aDrawTarget
, &boundary
, 1, gfxShapedWord::HashMix(0, boundary
),
3780 aRunScript
, aLanguage
, vertical
, appUnitsPerDevUnit
,
3781 flags
| gfx::ShapedTextFlags::TEXT_IS_8BIT
, rounding
, tp
,
3782 [&](gfxShapedWord
* aShapedWord
) {
3783 aTextRun
->CopyGlyphDataFrom(aShapedWord
, aRunStart
+ i
);
3784 if (boundary
== ' ') {
3785 aTextRun
->GetCharacterGlyphs()[aRunStart
+ i
].SetIsSpace();
3798 if (i
== aRunLength
) {
3802 NS_ASSERTION(invalid
, "how did we get here except via an invalid char?");
3804 // word was terminated by an invalid char: skip it,
3805 // unless it's a control char that we want to show as a hexbox,
3806 // but record where TAB or NEWLINE occur
3808 aTextRun
->SetIsTab(aRunStart
+ i
);
3809 } else if (ch
== '\n') {
3810 aTextRun
->SetIsNewline(aRunStart
+ i
);
3811 } else if (GetGeneralCategory(ch
) == HB_UNICODE_GENERAL_CATEGORY_FORMAT
) {
3812 aTextRun
->SetIsFormattingControl(aRunStart
+ i
);
3813 } else if (IsInvalidControlChar(ch
) &&
3814 !(aTextRun
->GetFlags() &
3815 gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS
)) {
3816 if (GetFontEntry()->IsUserFont() && HasCharacter(ch
)) {
3817 ShapeFragmentWithoutWordCache(aDrawTarget
, aString
+ i
, aRunStart
+ i
,
3818 1, aRunScript
, aLanguage
, vertical
,
3819 rounding
, aTextRun
);
3821 aTextRun
->SetMissingGlyph(aRunStart
+ i
, ch
, this);
3833 // Explicit instantiations of SplitAndInitTextRun, to avoid libxul link failure
3834 template bool gfxFont::SplitAndInitTextRun(
3835 DrawTarget
* aDrawTarget
, gfxTextRun
* aTextRun
, const uint8_t* aString
,
3836 uint32_t aRunStart
, uint32_t aRunLength
, Script aRunScript
,
3837 nsAtom
* aLanguage
, ShapedTextFlags aOrientation
);
3838 template bool gfxFont::SplitAndInitTextRun(
3839 DrawTarget
* aDrawTarget
, gfxTextRun
* aTextRun
, const char16_t
* aString
,
3840 uint32_t aRunStart
, uint32_t aRunLength
, Script aRunScript
,
3841 nsAtom
* aLanguage
, ShapedTextFlags aOrientation
);
3844 bool gfxFont::InitFakeSmallCapsRun(
3845 nsPresContext
* aPresContext
, DrawTarget
* aDrawTarget
, gfxTextRun
* aTextRun
,
3846 const char16_t
* aText
, uint32_t aOffset
, uint32_t aLength
,
3847 FontMatchType aMatchType
, gfx::ShapedTextFlags aOrientation
, Script aScript
,
3848 nsAtom
* aLanguage
, bool aSyntheticLower
, bool aSyntheticUpper
) {
3851 RefPtr
<gfxFont
> smallCapsFont
= GetSmallCapsFont();
3852 if (!smallCapsFont
) {
3853 NS_WARNING("failed to get reduced-size font for smallcaps!");
3854 smallCapsFont
= this;
3857 bool isCJK
= gfxTextRun::IsCJKScript(aScript
);
3859 enum RunCaseAction
{ kNoChange
, kUppercaseReduce
, kUppercase
};
3861 RunCaseAction runAction
= kNoChange
;
3862 uint32_t runStart
= 0;
3864 for (uint32_t i
= 0; i
<= aLength
; ++i
) {
3865 uint32_t extraCodeUnits
= 0; // Will be set to 1 if we need to consume
3866 // a trailing surrogate as well as the
3867 // current code unit.
3868 RunCaseAction chAction
= kNoChange
;
3869 // Unless we're at the end, figure out what treatment the current
3870 // character will need.
3872 uint32_t ch
= aText
[i
];
3873 if (i
< aLength
- 1 && NS_IS_SURROGATE_PAIR(ch
, aText
[i
+ 1])) {
3874 ch
= SURROGATE_TO_UCS4(ch
, aText
[i
+ 1]);
3877 // Characters that aren't the start of a cluster are ignored here.
3878 // They get added to whatever lowercase/non-lowercase run we're in.
3879 if (IsClusterExtender(ch
)) {
3880 chAction
= runAction
;
3882 if (ch
!= ToUpperCase(ch
) || SpecialUpper(ch
)) {
3884 chAction
= (aSyntheticLower
? kUppercaseReduce
: kNoChange
);
3885 } else if (ch
!= ToLowerCase(ch
)) {
3887 chAction
= (aSyntheticUpper
? kUppercaseReduce
: kNoChange
);
3888 if (aLanguage
== nsGkAtoms::el
) {
3889 // In Greek, check for characters that will be modified by
3890 // the GreekUpperCase mapping - this catches accented
3891 // capitals where the accent is to be removed (bug 307039).
3892 // These are handled by using the full-size font with the
3893 // uppercasing transform.
3894 mozilla::GreekCasing::State state
;
3895 bool markEta
, updateEta
;
3897 mozilla::GreekCasing::UpperCase(ch
, state
, markEta
, updateEta
);
3898 if ((ch
!= ch2
|| markEta
) && !aSyntheticUpper
) {
3899 chAction
= kUppercase
;
3906 // At the end of the text or when the current character needs different
3907 // casing treatment from the current run, finish the run-in-progress
3908 // and prepare to accumulate a new run.
3909 // Note that we do not look at any source data for offset [i] here,
3910 // as that would be invalid in the case where i==length.
3911 if ((i
== aLength
|| runAction
!= chAction
) && runStart
< i
) {
3912 uint32_t runLength
= i
- runStart
;
3914 switch (runAction
) {
3916 // just use the current font and the existing string
3917 aTextRun
->AddGlyphRun(f
, aMatchType
, aOffset
+ runStart
, true,
3918 aOrientation
, isCJK
);
3919 if (!f
->SplitAndInitTextRun(aDrawTarget
, aTextRun
, aText
+ runStart
,
3920 aOffset
+ runStart
, runLength
, aScript
,
3921 aLanguage
, aOrientation
)) {
3926 case kUppercaseReduce
:
3927 // use reduced-size font, then fall through to uppercase the text
3932 // apply uppercase transform to the string
3933 nsDependentSubstring
origString(aText
+ runStart
, runLength
);
3934 nsAutoString convertedString
;
3935 AutoTArray
<bool, 50> charsToMergeArray
;
3936 AutoTArray
<bool, 50> deletedCharsArray
;
3938 const auto globalTransform
= StyleTextTransform::UPPERCASE
;
3939 // No mask needed; we're doing case conversion, not password-hiding.
3940 const char16_t maskChar
= 0;
3941 bool mergeNeeded
= nsCaseTransformTextRunFactory::TransformString(
3942 origString
, convertedString
, Some(globalTransform
), maskChar
,
3943 /* aCaseTransformsOnly = */ false, aLanguage
, charsToMergeArray
,
3946 // Check whether the font supports the uppercased characters needed;
3947 // if not, we're not going to be able to simulate small-caps.
3948 bool failed
= false;
3949 char16_t highSurrogate
= 0;
3950 for (const char16_t
* cp
= convertedString
.BeginReading();
3951 cp
!= convertedString
.EndReading(); ++cp
) {
3952 if (NS_IS_HIGH_SURROGATE(*cp
)) {
3953 highSurrogate
= *cp
;
3957 if (NS_IS_LOW_SURROGATE(*cp
) && highSurrogate
) {
3958 ch
= SURROGATE_TO_UCS4(highSurrogate
, *cp
);
3961 if (!f
->HasCharacter(ch
)) {
3962 if (IsDefaultIgnorable(ch
)) {
3969 // Required uppercase letter(s) missing from the font. Just use the
3970 // original text with the original font, no fake small caps!
3972 convertedString
= origString
;
3973 mergeNeeded
= false;
3978 // This is the hard case: the transformation caused chars
3979 // to be inserted or deleted, so we can't shape directly
3980 // into the destination textrun but have to handle the
3981 // mismatch of character positions.
3982 gfxTextRunFactory::Parameters params
= {
3983 aDrawTarget
, nullptr, nullptr,
3984 nullptr, 0, aTextRun
->GetAppUnitsPerDevUnit()};
3985 RefPtr
<gfxTextRun
> tempRun(gfxTextRun::Create(
3986 ¶ms
, convertedString
.Length(), aTextRun
->GetFontGroup(),
3987 gfx::ShapedTextFlags(), nsTextFrameUtils::Flags()));
3988 tempRun
->AddGlyphRun(f
, aMatchType
, 0, true, aOrientation
, isCJK
);
3989 if (!f
->SplitAndInitTextRun(aDrawTarget
, tempRun
.get(),
3990 convertedString
.BeginReading(), 0,
3991 convertedString
.Length(), aScript
,
3992 aLanguage
, aOrientation
)) {
3995 RefPtr
<gfxTextRun
> mergedRun(gfxTextRun::Create(
3996 ¶ms
, runLength
, aTextRun
->GetFontGroup(),
3997 gfx::ShapedTextFlags(), nsTextFrameUtils::Flags()));
3998 MergeCharactersInTextRun(mergedRun
.get(), tempRun
.get(),
3999 charsToMergeArray
.Elements(),
4000 deletedCharsArray
.Elements());
4001 gfxTextRun::Range
runRange(0, runLength
);
4002 aTextRun
->CopyGlyphDataFrom(mergedRun
.get(), runRange
,
4003 aOffset
+ runStart
);
4006 aTextRun
->AddGlyphRun(f
, aMatchType
, aOffset
+ runStart
, true,
4007 aOrientation
, isCJK
);
4008 if (!f
->SplitAndInitTextRun(aDrawTarget
, aTextRun
,
4009 convertedString
.BeginReading(),
4010 aOffset
+ runStart
, runLength
, aScript
,
4011 aLanguage
, aOrientation
)) {
4021 i
+= extraCodeUnits
;
4023 runAction
= chAction
;
4031 bool gfxFont::InitFakeSmallCapsRun(
4032 nsPresContext
* aPresContext
, DrawTarget
* aDrawTarget
, gfxTextRun
* aTextRun
,
4033 const uint8_t* aText
, uint32_t aOffset
, uint32_t aLength
,
4034 FontMatchType aMatchType
, gfx::ShapedTextFlags aOrientation
, Script aScript
,
4035 nsAtom
* aLanguage
, bool aSyntheticLower
, bool aSyntheticUpper
) {
4036 NS_ConvertASCIItoUTF16
unicodeString(reinterpret_cast<const char*>(aText
),
4038 return InitFakeSmallCapsRun(aPresContext
, aDrawTarget
, aTextRun
,
4039 static_cast<const char16_t
*>(unicodeString
.get()),
4040 aOffset
, aLength
, aMatchType
, aOrientation
,
4041 aScript
, aLanguage
, aSyntheticLower
,
4045 already_AddRefed
<gfxFont
> gfxFont::GetSmallCapsFont() const {
4046 gfxFontStyle
style(*GetStyle());
4047 style
.size
*= SMALL_CAPS_SCALE_FACTOR
;
4048 style
.variantCaps
= NS_FONT_VARIANT_CAPS_NORMAL
;
4049 gfxFontEntry
* fe
= GetFontEntry();
4050 return fe
->FindOrMakeFont(&style
, mUnicodeRangeMap
);
4053 already_AddRefed
<gfxFont
> gfxFont::GetSubSuperscriptFont(
4054 int32_t aAppUnitsPerDevPixel
) const {
4055 gfxFontStyle
style(*GetStyle());
4056 style
.AdjustForSubSuperscript(aAppUnitsPerDevPixel
);
4057 gfxFontEntry
* fe
= GetFontEntry();
4058 return fe
->FindOrMakeFont(&style
, mUnicodeRangeMap
);
4061 gfxGlyphExtents
* gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit
) {
4064 AutoReadLock
lock(mLock
);
4065 readCount
= mGlyphExtentsArray
.Length();
4066 for (uint32_t i
= 0; i
< readCount
; ++i
) {
4067 if (mGlyphExtentsArray
[i
]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit
)
4068 return mGlyphExtentsArray
[i
].get();
4071 AutoWriteLock
lock(mLock
);
4072 // Re-check in case of race.
4073 uint32_t count
= mGlyphExtentsArray
.Length();
4074 for (uint32_t i
= readCount
; i
< count
; ++i
) {
4075 if (mGlyphExtentsArray
[i
]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit
)
4076 return mGlyphExtentsArray
[i
].get();
4078 gfxGlyphExtents
* glyphExtents
= new gfxGlyphExtents(aAppUnitsPerDevUnit
);
4080 mGlyphExtentsArray
.AppendElement(glyphExtents
);
4081 // Initialize the extents of a space glyph, assuming that spaces don't
4083 glyphExtents
->SetContainedGlyphWidthAppUnits(GetSpaceGlyph(), 0);
4085 return glyphExtents
;
4088 void gfxFont::SetupGlyphExtents(DrawTarget
* aDrawTarget
, uint32_t aGlyphID
,
4089 bool aNeedTight
, gfxGlyphExtents
* aExtents
) {
4091 if (mFontEntry
->TryGetSVGData(this) && mFontEntry
->HasSVGGlyph(aGlyphID
) &&
4092 mFontEntry
->GetSVGGlyphExtents(aDrawTarget
, aGlyphID
, GetAdjustedSize(),
4094 gfxFloat d2a
= aExtents
->GetAppUnitsPerDevUnit();
4095 aExtents
->SetTightGlyphExtents(
4096 aGlyphID
, gfxRect(svgBounds
.X() * d2a
, svgBounds
.Y() * d2a
,
4097 svgBounds
.Width() * d2a
, svgBounds
.Height() * d2a
));
4101 if (mFontEntry
->TryGetColorGlyphs() && mFontEntry
->mCOLR
&&
4102 COLRFonts::GetColrTableVersion(mFontEntry
->mCOLR
) == 1) {
4103 auto* shaper
= GetHarfBuzzShaper();
4104 if (shaper
&& shaper
->IsInitialized()) {
4105 RefPtr scaledFont
= GetScaledFont(aDrawTarget
);
4106 Rect r
= COLRFonts::GetColorGlyphBounds(
4107 mFontEntry
->mCOLR
, shaper
->GetHBFont(), aGlyphID
, aDrawTarget
,
4108 scaledFont
, mFUnitsConvFactor
);
4110 gfxFloat d2a
= aExtents
->GetAppUnitsPerDevUnit();
4111 aExtents
->SetTightGlyphExtents(
4112 aGlyphID
, gfxRect(r
.X() * d2a
, r
.Y() * d2a
, r
.Width() * d2a
,
4120 GetGlyphBounds(aGlyphID
, &bounds
, mAntialiasOption
== kAntialiasNone
);
4122 const Metrics
& fontMetrics
= GetMetrics(nsFontMetrics::eHorizontal
);
4123 int32_t appUnitsPerDevUnit
= aExtents
->GetAppUnitsPerDevUnit();
4124 if (!aNeedTight
&& bounds
.x
>= 0.0 && bounds
.y
>= -fontMetrics
.maxAscent
&&
4125 bounds
.height
+ bounds
.y
<= fontMetrics
.maxDescent
) {
4126 uint32_t appUnitsWidth
=
4127 uint32_t(ceil((bounds
.x
+ bounds
.width
) * appUnitsPerDevUnit
));
4128 if (appUnitsWidth
< gfxGlyphExtents::INVALID_WIDTH
) {
4129 aExtents
->SetContainedGlyphWidthAppUnits(aGlyphID
,
4130 uint16_t(appUnitsWidth
));
4134 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
4136 ++gGlyphExtentsSetupFallBackToTight
;
4140 gfxFloat d2a
= appUnitsPerDevUnit
;
4141 aExtents
->SetTightGlyphExtents(
4142 aGlyphID
, gfxRect(bounds
.x
* d2a
, bounds
.y
* d2a
, bounds
.width
* d2a
,
4143 bounds
.height
* d2a
));
4146 // Try to initialize font metrics by reading sfnt tables directly;
4147 // set mIsValid=TRUE and return TRUE on success.
4148 // Return FALSE if the gfxFontEntry subclass does not
4149 // implement GetFontTable(), or for non-sfnt fonts where tables are
4151 // If this returns TRUE without setting the mIsValid flag, then we -did-
4152 // apparently find an sfnt, but it was too broken to be used.
4153 bool gfxFont::InitMetricsFromSfntTables(Metrics
& aMetrics
) {
4154 mIsValid
= false; // font is NOT valid in case of early return
4156 const uint32_t kHheaTableTag
= TRUETYPE_TAG('h', 'h', 'e', 'a');
4157 const uint32_t kOS_2TableTag
= TRUETYPE_TAG('O', 'S', '/', '2');
4161 if (mFUnitsConvFactor
< 0.0) {
4162 // If the conversion factor from FUnits is not yet set,
4163 // get the unitsPerEm from the 'head' table via the font entry
4164 uint16_t unitsPerEm
= GetFontEntry()->UnitsPerEm();
4165 if (unitsPerEm
== gfxFontEntry::kInvalidUPEM
) {
4168 mFUnitsConvFactor
= GetAdjustedSize() / unitsPerEm
;
4171 // 'hhea' table is required for the advanceWidthMax field
4172 gfxFontEntry::AutoTable
hheaTable(mFontEntry
, kHheaTableTag
);
4174 return false; // no 'hhea' table -> not an sfnt
4176 const MetricsHeader
* hhea
=
4177 reinterpret_cast<const MetricsHeader
*>(hb_blob_get_data(hheaTable
, &len
));
4178 if (len
< sizeof(MetricsHeader
)) {
4182 #define SET_UNSIGNED(field, src) \
4183 aMetrics.field = uint16_t(src) * mFUnitsConvFactor
4184 #define SET_SIGNED(field, src) aMetrics.field = int16_t(src) * mFUnitsConvFactor
4186 SET_UNSIGNED(maxAdvance
, hhea
->advanceWidthMax
);
4188 // 'OS/2' table is optional, if not found we'll estimate xHeight
4189 // and aveCharWidth by measuring glyphs
4190 gfxFontEntry::AutoTable
os2Table(mFontEntry
, kOS_2TableTag
);
4192 const OS2Table
* os2
=
4193 reinterpret_cast<const OS2Table
*>(hb_blob_get_data(os2Table
, &len
));
4194 // this should always be present in any valid OS/2 of any version
4195 if (len
>= offsetof(OS2Table
, xAvgCharWidth
) + sizeof(int16_t)) {
4196 SET_SIGNED(aveCharWidth
, os2
->xAvgCharWidth
);
4203 hb_font_t
* hbFont
= gfxHarfBuzzShaper::CreateHBFont(this);
4204 hb_position_t position
;
4206 auto FixedToFloat
= [](hb_position_t f
) -> gfxFloat
{ return f
/ 65536.0; };
4208 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER
,
4210 aMetrics
.maxAscent
= FixedToFloat(position
);
4212 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER
,
4214 aMetrics
.maxDescent
= -FixedToFloat(position
);
4216 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP
,
4218 aMetrics
.externalLeading
= FixedToFloat(position
);
4221 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_UNDERLINE_OFFSET
,
4223 aMetrics
.underlineOffset
= FixedToFloat(position
);
4225 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_UNDERLINE_SIZE
,
4227 aMetrics
.underlineSize
= FixedToFloat(position
);
4229 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_STRIKEOUT_OFFSET
,
4231 aMetrics
.strikeoutOffset
= FixedToFloat(position
);
4233 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_STRIKEOUT_SIZE
,
4235 aMetrics
.strikeoutSize
= FixedToFloat(position
);
4238 // Although sxHeight and sCapHeight are signed fields, we consider
4239 // zero/negative values to be erroneous and just ignore them.
4240 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_X_HEIGHT
,
4243 aMetrics
.xHeight
= FixedToFloat(position
);
4245 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_CAP_HEIGHT
,
4248 aMetrics
.capHeight
= FixedToFloat(position
);
4250 hb_font_destroy(hbFont
);
4257 static double RoundToNearestMultiple(double aValue
, double aFraction
) {
4258 return floor(aValue
/ aFraction
+ 0.5) * aFraction
;
4261 void gfxFont::CalculateDerivedMetrics(Metrics
& aMetrics
) {
4262 aMetrics
.maxAscent
=
4263 ceil(RoundToNearestMultiple(aMetrics
.maxAscent
, 1 / 1024.0));
4264 aMetrics
.maxDescent
=
4265 ceil(RoundToNearestMultiple(aMetrics
.maxDescent
, 1 / 1024.0));
4267 if (aMetrics
.xHeight
<= 0) {
4268 // only happens if we couldn't find either font metrics
4269 // or a char to measure;
4270 // pick an arbitrary value that's better than zero
4271 aMetrics
.xHeight
= aMetrics
.maxAscent
* DEFAULT_XHEIGHT_FACTOR
;
4274 // If we have a font that doesn't provide a capHeight value, use maxAscent
4275 // as a reasonable fallback.
4276 if (aMetrics
.capHeight
<= 0) {
4277 aMetrics
.capHeight
= aMetrics
.maxAscent
;
4280 aMetrics
.maxHeight
= aMetrics
.maxAscent
+ aMetrics
.maxDescent
;
4282 if (aMetrics
.maxHeight
- aMetrics
.emHeight
> 0.0) {
4283 aMetrics
.internalLeading
= aMetrics
.maxHeight
- aMetrics
.emHeight
;
4285 aMetrics
.internalLeading
= 0.0;
4289 aMetrics
.maxAscent
* aMetrics
.emHeight
/ aMetrics
.maxHeight
;
4290 aMetrics
.emDescent
= aMetrics
.emHeight
- aMetrics
.emAscent
;
4292 if (GetFontEntry()->IsFixedPitch()) {
4293 // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger
4294 // advance than the average character width... this forces
4295 // those fonts to be recognized like fixed pitch fonts by layout.
4296 aMetrics
.maxAdvance
= aMetrics
.aveCharWidth
;
4299 if (!aMetrics
.strikeoutOffset
) {
4300 aMetrics
.strikeoutOffset
= aMetrics
.xHeight
* 0.5;
4302 if (!aMetrics
.strikeoutSize
) {
4303 aMetrics
.strikeoutSize
= aMetrics
.underlineSize
;
4307 void gfxFont::SanitizeMetrics(gfxFont::Metrics
* aMetrics
,
4308 bool aIsBadUnderlineFont
) {
4309 // Even if this font size is zero, this font is created with non-zero size.
4310 // However, for layout and others, we should return the metrics of zero size
4312 if (mStyle
.AdjustedSizeMustBeZero()) {
4313 memset(aMetrics
, 0, sizeof(gfxFont::Metrics
));
4317 // If the font entry has ascent/descent/lineGap-override values,
4318 // replace the metrics from the font with the overrides.
4319 gfxFloat adjustedSize
= GetAdjustedSize();
4320 if (mFontEntry
->mAscentOverride
>= 0.0) {
4321 aMetrics
->maxAscent
= mFontEntry
->mAscentOverride
* adjustedSize
;
4322 aMetrics
->maxHeight
= aMetrics
->maxAscent
+ aMetrics
->maxDescent
;
4323 aMetrics
->internalLeading
=
4324 std::max(0.0, aMetrics
->maxHeight
- aMetrics
->emHeight
);
4326 if (mFontEntry
->mDescentOverride
>= 0.0) {
4327 aMetrics
->maxDescent
= mFontEntry
->mDescentOverride
* adjustedSize
;
4328 aMetrics
->maxHeight
= aMetrics
->maxAscent
+ aMetrics
->maxDescent
;
4329 aMetrics
->internalLeading
=
4330 std::max(0.0, aMetrics
->maxHeight
- aMetrics
->emHeight
);
4332 if (mFontEntry
->mLineGapOverride
>= 0.0) {
4333 aMetrics
->externalLeading
= mFontEntry
->mLineGapOverride
* adjustedSize
;
4336 aMetrics
->underlineSize
= std::max(1.0, aMetrics
->underlineSize
);
4337 aMetrics
->strikeoutSize
= std::max(1.0, aMetrics
->strikeoutSize
);
4339 aMetrics
->underlineOffset
= std::min(aMetrics
->underlineOffset
, -1.0);
4341 if (aMetrics
->maxAscent
< 1.0) {
4342 // We cannot draw strikeout line and overline in the ascent...
4343 aMetrics
->underlineSize
= 0;
4344 aMetrics
->underlineOffset
= 0;
4345 aMetrics
->strikeoutSize
= 0;
4346 aMetrics
->strikeoutOffset
= 0;
4351 * Some CJK fonts have bad underline offset. Therefore, if this is such font,
4352 * we need to lower the underline offset to bottom of *em* descent.
4353 * However, if this is system font, we should not do this for the rendering
4354 * compatibility with another application's UI on the platform.
4355 * XXX Should not use this hack if the font size is too small?
4356 * Such text cannot be read, this might be used for tight CSS
4357 * rendering? (E.g., Acid2)
4359 if (!mStyle
.systemFont
&& aIsBadUnderlineFont
) {
4360 // First, we need 2 pixels between baseline and underline at least. Because
4361 // many CJK characters put their glyphs on the baseline, so, 1 pixel is too
4362 // close for CJK characters.
4363 aMetrics
->underlineOffset
= std::min(aMetrics
->underlineOffset
, -2.0);
4365 // Next, we put the underline to bottom of below of the descent space.
4366 if (aMetrics
->internalLeading
+ aMetrics
->externalLeading
>
4367 aMetrics
->underlineSize
) {
4368 aMetrics
->underlineOffset
=
4369 std::min(aMetrics
->underlineOffset
, -aMetrics
->emDescent
);
4371 aMetrics
->underlineOffset
=
4372 std::min(aMetrics
->underlineOffset
,
4373 aMetrics
->underlineSize
- aMetrics
->emDescent
);
4376 // If underline positioned is too far from the text, descent position is
4377 // preferred so that underline will stay within the boundary.
4378 else if (aMetrics
->underlineSize
- aMetrics
->underlineOffset
>
4379 aMetrics
->maxDescent
) {
4380 if (aMetrics
->underlineSize
> aMetrics
->maxDescent
)
4381 aMetrics
->underlineSize
= std::max(aMetrics
->maxDescent
, 1.0);
4382 // The max underlineOffset is 1px (the min underlineSize is 1px, and min
4383 // maxDescent is 0px.)
4384 aMetrics
->underlineOffset
= aMetrics
->underlineSize
- aMetrics
->maxDescent
;
4387 // If strikeout line is overflowed from the ascent, the line should be resized
4388 // and moved for that being in the ascent space. Note that the strikeoutOffset
4389 // is *middle* of the strikeout line position.
4390 gfxFloat halfOfStrikeoutSize
= floor(aMetrics
->strikeoutSize
/ 2.0 + 0.5);
4391 if (halfOfStrikeoutSize
+ aMetrics
->strikeoutOffset
> aMetrics
->maxAscent
) {
4392 if (aMetrics
->strikeoutSize
> aMetrics
->maxAscent
) {
4393 aMetrics
->strikeoutSize
= std::max(aMetrics
->maxAscent
, 1.0);
4394 halfOfStrikeoutSize
= floor(aMetrics
->strikeoutSize
/ 2.0 + 0.5);
4396 gfxFloat ascent
= floor(aMetrics
->maxAscent
+ 0.5);
4397 aMetrics
->strikeoutOffset
= std::max(halfOfStrikeoutSize
, ascent
/ 2.0);
4400 // If overline is larger than the ascent, the line should be resized.
4401 if (aMetrics
->underlineSize
> aMetrics
->maxAscent
) {
4402 aMetrics
->underlineSize
= aMetrics
->maxAscent
;
4406 gfxFont::Baselines
gfxFont::GetBaselines(Orientation aOrientation
) {
4407 // Approximated baselines for fonts lacking actual baseline data. These are
4408 // fractions of the em ascent/descent from the alphabetic baseline.
4409 const double kHangingBaselineDefault
= 0.8; // fraction of ascent
4410 const double kIdeographicBaselineDefault
= -0.5; // fraction of descent
4412 // If no BASE table is present, just return synthetic values immediately.
4413 if (!mFontEntry
->HasFontTable(TRUETYPE_TAG('B', 'A', 'S', 'E'))) {
4414 // No baseline table; just synthesize them immediately.
4415 const Metrics
& metrics
= GetMetrics(aOrientation
);
4418 kHangingBaselineDefault
* metrics
.emAscent
, // hanging
4419 kIdeographicBaselineDefault
* metrics
.emDescent
// ideographic
4423 // Use harfbuzz to try to read the font's baseline metrics.
4424 Baselines result
{NAN
, NAN
, NAN
};
4425 hb_font_t
* hbFont
= gfxHarfBuzzShaper::CreateHBFont(this);
4426 hb_direction_t hbDir
= aOrientation
== nsFontMetrics::eHorizontal
4429 hb_position_t position
;
4431 auto Fix2Float
= [](hb_position_t f
) -> gfxFloat
{ return f
/ 65536.0; };
4432 if (hb_ot_layout_get_baseline(hbFont
, HB_OT_LAYOUT_BASELINE_TAG_ROMAN
, hbDir
,
4433 HB_OT_TAG_DEFAULT_SCRIPT
,
4434 HB_OT_TAG_DEFAULT_LANGUAGE
, &position
)) {
4435 result
.mAlphabetic
= Fix2Float(position
);
4438 if (hb_ot_layout_get_baseline(hbFont
, HB_OT_LAYOUT_BASELINE_TAG_HANGING
,
4439 hbDir
, HB_OT_TAG_DEFAULT_SCRIPT
,
4440 HB_OT_TAG_DEFAULT_LANGUAGE
, &position
)) {
4441 result
.mHanging
= Fix2Float(position
);
4444 if (hb_ot_layout_get_baseline(
4445 hbFont
, HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT
, hbDir
,
4446 HB_OT_TAG_DEFAULT_SCRIPT
, HB_OT_TAG_DEFAULT_LANGUAGE
, &position
)) {
4447 result
.mIdeographic
= Fix2Float(position
);
4450 hb_font_destroy(hbFont
);
4451 // If we successfully read all three, we can return now.
4456 // Synthesize the baselines that we didn't find in the font.
4457 const Metrics
& metrics
= GetMetrics(aOrientation
);
4458 if (std::isnan(result
.mAlphabetic
)) {
4459 result
.mAlphabetic
= 0.0;
4461 if (std::isnan(result
.mHanging
)) {
4462 result
.mHanging
= kHangingBaselineDefault
* metrics
.emAscent
;
4464 if (std::isnan(result
.mIdeographic
)) {
4465 result
.mIdeographic
= kIdeographicBaselineDefault
* metrics
.emDescent
;
4471 // Create a Metrics record to be used for vertical layout. This should never
4472 // fail, as we've already decided this is a valid font. We do not have the
4473 // option of marking it invalid (as can happen if we're unable to read
4474 // horizontal metrics), because that could break a font that we're already
4475 // using for horizontal text.
4476 // So we will synthesize *something* usable here even if there aren't any of the
4477 // usual font tables (which can happen in the case of a legacy bitmap or Type1
4478 // font for which the platform-specific backend used platform APIs instead of
4479 // sfnt tables to create the horizontal metrics).
4480 void gfxFont::CreateVerticalMetrics() {
4481 const uint32_t kHheaTableTag
= TRUETYPE_TAG('h', 'h', 'e', 'a');
4482 const uint32_t kVheaTableTag
= TRUETYPE_TAG('v', 'h', 'e', 'a');
4483 const uint32_t kPostTableTag
= TRUETYPE_TAG('p', 'o', 's', 't');
4484 const uint32_t kOS_2TableTag
= TRUETYPE_TAG('O', 'S', '/', '2');
4487 auto* metrics
= new Metrics();
4488 ::memset(metrics
, 0, sizeof(Metrics
));
4490 // Some basic defaults, in case the font lacks any real metrics tables.
4491 // TODO: consider what rounding (if any) we should apply to these.
4492 metrics
->emHeight
= GetAdjustedSize();
4493 metrics
->emAscent
= metrics
->emHeight
/ 2;
4494 metrics
->emDescent
= metrics
->emHeight
- metrics
->emAscent
;
4496 metrics
->maxAscent
= metrics
->emAscent
;
4497 metrics
->maxDescent
= metrics
->emDescent
;
4499 const float UNINITIALIZED_LEADING
= -10000.0f
;
4500 metrics
->externalLeading
= UNINITIALIZED_LEADING
;
4502 if (mFUnitsConvFactor
< 0.0) {
4503 uint16_t upem
= GetFontEntry()->UnitsPerEm();
4504 if (upem
!= gfxFontEntry::kInvalidUPEM
) {
4505 AutoWriteLock
lock(mLock
);
4506 mFUnitsConvFactor
= GetAdjustedSize() / upem
;
4510 #define SET_UNSIGNED(field, src) \
4511 metrics->field = uint16_t(src) * mFUnitsConvFactor
4512 #define SET_SIGNED(field, src) metrics->field = int16_t(src) * mFUnitsConvFactor
4514 gfxFontEntry::AutoTable
os2Table(mFontEntry
, kOS_2TableTag
);
4515 if (os2Table
&& mFUnitsConvFactor
>= 0.0) {
4516 const OS2Table
* os2
=
4517 reinterpret_cast<const OS2Table
*>(hb_blob_get_data(os2Table
, &len
));
4518 // These fields should always be present in any valid OS/2 table
4519 if (len
>= offsetof(OS2Table
, sTypoLineGap
) + sizeof(int16_t)) {
4520 SET_SIGNED(strikeoutSize
, os2
->yStrikeoutSize
);
4521 // Use ascent+descent from the horizontal metrics as the default
4522 // advance (aveCharWidth) in vertical mode
4523 gfxFloat ascentDescent
=
4524 gfxFloat(mFUnitsConvFactor
) *
4525 (int16_t(os2
->sTypoAscender
) - int16_t(os2
->sTypoDescender
));
4526 metrics
->aveCharWidth
= std::max(metrics
->emHeight
, ascentDescent
);
4527 // Use xAvgCharWidth from horizontal metrics as minimum font extent
4528 // for vertical layout, applying half of it to ascent and half to
4529 // descent (to work with a default centered baseline).
4530 gfxFloat halfCharWidth
=
4531 int16_t(os2
->xAvgCharWidth
) * gfxFloat(mFUnitsConvFactor
) / 2;
4532 metrics
->maxAscent
= std::max(metrics
->maxAscent
, halfCharWidth
);
4533 metrics
->maxDescent
= std::max(metrics
->maxDescent
, halfCharWidth
);
4537 // If we didn't set aveCharWidth from OS/2, try to read 'hhea' metrics
4538 // and use the line height from its ascent/descent.
4539 if (!metrics
->aveCharWidth
) {
4540 gfxFontEntry::AutoTable
hheaTable(mFontEntry
, kHheaTableTag
);
4541 if (hheaTable
&& mFUnitsConvFactor
>= 0.0) {
4542 const MetricsHeader
* hhea
= reinterpret_cast<const MetricsHeader
*>(
4543 hb_blob_get_data(hheaTable
, &len
));
4544 if (len
>= sizeof(MetricsHeader
)) {
4545 SET_SIGNED(aveCharWidth
,
4546 int16_t(hhea
->ascender
) - int16_t(hhea
->descender
));
4547 metrics
->maxAscent
= metrics
->aveCharWidth
/ 2;
4548 metrics
->maxDescent
= metrics
->aveCharWidth
- metrics
->maxAscent
;
4553 // Read real vertical metrics if available.
4554 metrics
->ideographicWidth
= -1.0;
4555 metrics
->zeroWidth
= -1.0;
4556 gfxFontEntry::AutoTable
vheaTable(mFontEntry
, kVheaTableTag
);
4557 if (vheaTable
&& mFUnitsConvFactor
>= 0.0) {
4558 const MetricsHeader
* vhea
= reinterpret_cast<const MetricsHeader
*>(
4559 hb_blob_get_data(vheaTable
, &len
));
4560 if (len
>= sizeof(MetricsHeader
)) {
4561 SET_UNSIGNED(maxAdvance
, vhea
->advanceWidthMax
);
4562 // Redistribute space between ascent/descent because we want a
4563 // centered vertical baseline by default.
4564 gfxFloat halfExtent
=
4565 0.5 * gfxFloat(mFUnitsConvFactor
) *
4566 (int16_t(vhea
->ascender
) + std::abs(int16_t(vhea
->descender
)));
4567 // Some bogus fonts have ascent and descent set to zero in 'vhea'.
4568 // In that case we just ignore them and keep our synthetic values
4570 if (halfExtent
> 0) {
4571 metrics
->maxAscent
= halfExtent
;
4572 metrics
->maxDescent
= halfExtent
;
4573 SET_SIGNED(externalLeading
, vhea
->lineGap
);
4575 // Call gfxHarfBuzzShaper::GetGlyphVAdvance directly, as GetCharAdvance
4576 // would potentially recurse if no v-advance is available and it attempts
4577 // to fall back to a value from mVerticalMetrics.
4578 if (gfxHarfBuzzShaper
* shaper
= GetHarfBuzzShaper()) {
4579 uint32_t gid
= ProvidesGetGlyph()
4580 ? GetGlyph(kWaterIdeograph
, 0)
4581 : shaper
->GetNominalGlyph(kWaterIdeograph
);
4583 int32_t advance
= shaper
->GetGlyphVAdvance(gid
);
4584 // Convert 16.16 fixed-point advance from the shaper to a float.
4585 metrics
->ideographicWidth
=
4586 advance
< 0 ? metrics
->aveCharWidth
: advance
/ 65536.0;
4588 gid
= ProvidesGetGlyph() ? GetGlyph('0', 0)
4589 : shaper
->GetNominalGlyph('0');
4591 int32_t advance
= shaper
->GetGlyphVAdvance(gid
);
4592 metrics
->zeroWidth
=
4593 advance
< 0 ? metrics
->aveCharWidth
: advance
/ 65536.0;
4599 // If we didn't set aveCharWidth above, we must be dealing with a non-sfnt
4600 // font of some kind (Type1, bitmap, vector, ...), so fall back to using
4601 // whatever the platform backend figured out for horizontal layout.
4602 // And if we haven't set externalLeading yet, then copy that from the
4603 // horizontal metrics as well, to help consistency of CSS line-height.
4604 if (!metrics
->aveCharWidth
||
4605 metrics
->externalLeading
== UNINITIALIZED_LEADING
) {
4606 const Metrics
& horizMetrics
= GetHorizontalMetrics();
4607 if (!metrics
->aveCharWidth
) {
4608 metrics
->aveCharWidth
= horizMetrics
.maxAscent
+ horizMetrics
.maxDescent
;
4610 if (metrics
->externalLeading
== UNINITIALIZED_LEADING
) {
4611 metrics
->externalLeading
= horizMetrics
.externalLeading
;
4615 // Get underline thickness from the 'post' table if available.
4616 // We also read the underline position, although in vertical-upright mode
4617 // this will not be appropriate to use directly (see nsTextFrame.cpp).
4618 gfxFontEntry::AutoTable
postTable(mFontEntry
, kPostTableTag
);
4620 const PostTable
* post
=
4621 reinterpret_cast<const PostTable
*>(hb_blob_get_data(postTable
, &len
));
4622 if (len
>= offsetof(PostTable
, underlineThickness
) + sizeof(uint16_t)) {
4623 static_assert(offsetof(PostTable
, underlinePosition
) <
4624 offsetof(PostTable
, underlineThickness
),
4625 "broken PostTable struct?");
4626 SET_SIGNED(underlineOffset
, post
->underlinePosition
);
4627 SET_UNSIGNED(underlineSize
, post
->underlineThickness
);
4628 // Also use for strikeout if we didn't find that in OS/2 above.
4629 if (!metrics
->strikeoutSize
) {
4630 metrics
->strikeoutSize
= metrics
->underlineSize
;
4638 // If we didn't read this from a vhea table, it will still be zero.
4639 // In any case, let's make sure it is not less than the value we've
4640 // come up with for aveCharWidth.
4641 metrics
->maxAdvance
= std::max(metrics
->maxAdvance
, metrics
->aveCharWidth
);
4643 // Thickness of underline and strikeout may have been read from tables,
4644 // but in case they were not present, ensure a minimum of 1 pixel.
4645 metrics
->underlineSize
= std::max(1.0, metrics
->underlineSize
);
4647 metrics
->strikeoutSize
= std::max(1.0, metrics
->strikeoutSize
);
4648 metrics
->strikeoutOffset
= -0.5 * metrics
->strikeoutSize
;
4650 // Somewhat arbitrary values for now, subject to future refinement...
4651 metrics
->spaceWidth
= metrics
->aveCharWidth
;
4652 metrics
->maxHeight
= metrics
->maxAscent
+ metrics
->maxDescent
;
4653 metrics
->xHeight
= metrics
->emHeight
/ 2;
4654 metrics
->capHeight
= metrics
->maxAscent
;
4656 if (metrics
->zeroWidth
< 0.0) {
4657 metrics
->zeroWidth
= metrics
->aveCharWidth
;
4660 if (!mVerticalMetrics
.compareExchange(nullptr, metrics
)) {
4665 gfxFloat
gfxFont::SynthesizeSpaceWidth(uint32_t aCh
) {
4666 // return an appropriate width for various Unicode space characters
4667 // that we "fake" if they're not actually present in the font;
4668 // returns negative value if the char is not a known space.
4670 case 0x2000: // en quad
4672 return GetAdjustedSize() / 2; // en space
4673 case 0x2001: // em quad
4675 return GetAdjustedSize(); // em space
4677 return GetAdjustedSize() / 3; // three-per-em space
4679 return GetAdjustedSize() / 4; // four-per-em space
4681 return GetAdjustedSize() / 6; // six-per-em space
4683 return GetMetrics(nsFontMetrics::eHorizontal
)
4684 .ZeroOrAveCharWidth(); // figure space
4686 return GetMetrics(nsFontMetrics::eHorizontal
)
4687 .spaceWidth
; // punctuation space
4689 return GetAdjustedSize() / 5; // thin space
4691 return GetAdjustedSize() / 10; // hair space
4693 return GetAdjustedSize() / 5; // narrow no-break space
4695 return GetAdjustedSize(); // ideographic space
4701 void gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf
,
4702 FontCacheSizes
* aSizes
) const {
4703 AutoReadLock
lock(mLock
);
4704 for (uint32_t i
= 0; i
< mGlyphExtentsArray
.Length(); ++i
) {
4705 aSizes
->mFontInstances
+=
4706 mGlyphExtentsArray
[i
]->SizeOfIncludingThis(aMallocSizeOf
);
4709 aSizes
->mShapedWords
+=
4710 mWordCache
->shallowSizeOfIncludingThis(aMallocSizeOf
);
4711 for (auto it
= mWordCache
->iter(); !it
.done(); it
.next()) {
4712 aSizes
->mShapedWords
+=
4713 it
.get().value()->SizeOfIncludingThis(aMallocSizeOf
);
4718 void gfxFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf
,
4719 FontCacheSizes
* aSizes
) const {
4720 aSizes
->mFontInstances
+= aMallocSizeOf(this);
4721 AddSizeOfExcludingThis(aMallocSizeOf
, aSizes
);
4724 void gfxFont::AddGlyphChangeObserver(GlyphChangeObserver
* aObserver
) {
4725 AutoWriteLock
lock(mLock
);
4726 if (!mGlyphChangeObservers
) {
4727 mGlyphChangeObservers
= MakeUnique
<nsTHashSet
<GlyphChangeObserver
*>>();
4729 mGlyphChangeObservers
->Insert(aObserver
);
4732 void gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver
* aObserver
) {
4733 AutoWriteLock
lock(mLock
);
4734 NS_ASSERTION(mGlyphChangeObservers
, "No observers registered");
4735 NS_ASSERTION(mGlyphChangeObservers
->Contains(aObserver
),
4736 "Observer not registered");
4737 mGlyphChangeObservers
->Remove(aObserver
);
4740 #define DEFAULT_PIXEL_FONT_SIZE 16.0f
4742 gfxFontStyle::gfxFontStyle()
4743 : size(DEFAULT_PIXEL_FONT_SIZE
),
4745 baselineOffset(0.0f
),
4746 languageOverride
{0},
4747 weight(FontWeight::NORMAL
),
4748 stretch(FontStretch::NORMAL
),
4749 style(FontSlantStyle::NORMAL
),
4750 variantCaps(NS_FONT_VARIANT_CAPS_NORMAL
),
4751 variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL
),
4752 sizeAdjustBasis(uint8_t(FontSizeAdjust::Tag::None
)),
4756 allowForceGDIClassic(true),
4758 useGrayscaleAntialiasing(false),
4759 allowSyntheticWeight(true),
4760 allowSyntheticStyle(true),
4761 allowSyntheticSmallCaps(true),
4762 useSyntheticPosition(true),
4763 noFallbackVariantFeatures(true) {
4766 gfxFontStyle::gfxFontStyle(FontSlantStyle aStyle
, FontWeight aWeight
,
4767 FontStretch aStretch
, gfxFloat aSize
,
4768 const FontSizeAdjust
& aSizeAdjust
, bool aSystemFont
,
4771 bool aAllowForceGDIClassic
,
4773 bool aAllowWeightSynthesis
,
4774 bool aAllowStyleSynthesis
,
4775 bool aAllowSmallCapsSynthesis
,
4776 bool aUsePositionSynthesis
,
4777 StyleFontLanguageOverride aLanguageOverride
)
4779 baselineOffset(0.0f
),
4780 languageOverride(aLanguageOverride
),
4784 variantCaps(NS_FONT_VARIANT_CAPS_NORMAL
),
4785 variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL
),
4786 systemFont(aSystemFont
),
4787 printerFont(aPrinterFont
),
4789 allowForceGDIClassic(aAllowForceGDIClassic
),
4791 useGrayscaleAntialiasing(false),
4792 allowSyntheticWeight(aAllowWeightSynthesis
),
4793 allowSyntheticStyle(aAllowStyleSynthesis
),
4794 allowSyntheticSmallCaps(aAllowSmallCapsSynthesis
),
4795 useSyntheticPosition(aUsePositionSynthesis
),
4796 noFallbackVariantFeatures(true) {
4797 MOZ_ASSERT(!std::isnan(size
));
4799 sizeAdjustBasis
= uint8_t(aSizeAdjust
.tag
);
4800 // sizeAdjustBasis is currently a small bitfield, so let's assert that the
4801 // tag value was not truncated.
4802 MOZ_ASSERT(FontSizeAdjust::Tag(sizeAdjustBasis
) == aSizeAdjust
.tag
,
4803 "gfxFontStyle.sizeAdjustBasis too small?");
4805 #define HANDLE_TAG(TAG) \
4806 case FontSizeAdjust::Tag::TAG: \
4807 sizeAdjust = aSizeAdjust.As##TAG(); \
4810 switch (aSizeAdjust
.tag
) {
4811 case FontSizeAdjust::Tag::None
:
4814 HANDLE_TAG(ExHeight
)
4815 HANDLE_TAG(CapHeight
)
4818 HANDLE_TAG(IcHeight
)
4823 MOZ_ASSERT(!std::isnan(sizeAdjust
));
4825 if (weight
> FontWeight::FromInt(1000)) {
4826 weight
= FontWeight::FromInt(1000);
4828 if (weight
< FontWeight::FromInt(1)) {
4829 weight
= FontWeight::FromInt(1);
4832 if (size
>= FONT_MAX_SIZE
) {
4833 size
= FONT_MAX_SIZE
;
4835 sizeAdjustBasis
= uint8_t(FontSizeAdjust::Tag::None
);
4836 } else if (size
< 0.0) {
4837 NS_WARNING("negative font size");
4842 PLDHashNumber
gfxFontStyle::Hash() const {
4843 uint32_t hash
= variationSettings
.IsEmpty()
4845 : mozilla::HashBytes(variationSettings
.Elements(),
4846 variationSettings
.Length() *
4847 sizeof(gfxFontVariation
));
4848 return mozilla::AddToHash(hash
, systemFont
, style
.Raw(), stretch
.Raw(),
4849 weight
.Raw(), size
, int32_t(sizeAdjust
* 1000.0f
));
4852 void gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel
) {
4854 variantSubSuper
!= NS_FONT_VARIANT_POSITION_NORMAL
&& baselineOffset
== 0,
4855 "can't adjust this style for sub/superscript");
4857 // calculate the baseline offset (before changing the size)
4858 if (variantSubSuper
== NS_FONT_VARIANT_POSITION_SUPER
) {
4859 baselineOffset
= size
* -NS_FONT_SUPERSCRIPT_OFFSET_RATIO
;
4861 baselineOffset
= size
* NS_FONT_SUBSCRIPT_OFFSET_RATIO
;
4864 // calculate reduced size, roughly mimicing behavior of font-size: smaller
4865 float cssSize
= size
* aAppUnitsPerDevPixel
/ AppUnitsPerCSSPixel();
4866 if (cssSize
< NS_FONT_SUB_SUPER_SMALL_SIZE
) {
4867 size
*= NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL
;
4868 } else if (cssSize
>= NS_FONT_SUB_SUPER_LARGE_SIZE
) {
4869 size
*= NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE
;
4871 gfxFloat t
= (cssSize
- NS_FONT_SUB_SUPER_SMALL_SIZE
) /
4872 (NS_FONT_SUB_SUPER_LARGE_SIZE
- NS_FONT_SUB_SUPER_SMALL_SIZE
);
4873 size
*= (1.0 - t
) * NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL
+
4874 t
* NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE
;
4877 // clear the variant field
4878 variantSubSuper
= NS_FONT_VARIANT_POSITION_NORMAL
;
4881 bool gfxFont::TryGetMathTable() {
4882 if (mMathInitialized
) {
4883 return !!mMathTable
;
4886 auto face(GetFontEntry()->GetHBFace());
4887 if (hb_ot_math_has_data(face
)) {
4888 auto* mathTable
= new gfxMathTable(face
, GetAdjustedSize());
4889 if (!mMathTable
.compareExchange(nullptr, mathTable
)) {
4893 mMathInitialized
= true;
4895 return !!mMathTable
;