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
= ArrayLength(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
== ArrayLength(lookups
));
1149 static void CollectLookupsByLanguage(
1150 hb_face_t
* aFace
, hb_tag_t aTableTag
,
1151 const nsTHashSet
<uint32_t>& aSpecificFeatures
, hb_set_t
* aOtherLookups
,
1152 hb_set_t
* aSpecificFeatureLookups
, uint32_t aScriptIndex
,
1153 uint32_t aLangIndex
) {
1154 uint32_t reqFeatureIndex
;
1155 if (hb_ot_layout_language_get_required_feature_index(
1156 aFace
, aTableTag
, aScriptIndex
, aLangIndex
, &reqFeatureIndex
)) {
1157 CollectLookupsByFeature(aFace
, aTableTag
, reqFeatureIndex
, aOtherLookups
);
1160 uint32_t featureIndexes
[32];
1161 uint32_t i
, len
, offset
;
1165 len
= ArrayLength(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
== ArrayLength(featureIndexes
));
1190 static bool HasLookupRuleWithGlyphByScript(
1191 hb_face_t
* aFace
, hb_tag_t aTableTag
, hb_tag_t aScriptTag
,
1192 uint32_t aScriptIndex
, uint16_t aGlyph
,
1193 const nsTHashSet
<uint32_t>& aDefaultFeatures
,
1194 bool& aHasDefaultFeatureWithGlyph
) {
1195 uint32_t numLangs
, lang
;
1196 hb_set_t
* defaultFeatureLookups
= hb_set_create();
1197 hb_set_t
* nonDefaultFeatureLookups
= hb_set_create();
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
= ArrayLength(defaultFeatures
);
1393 auto* set
= new nsTHashSet
<uint32_t>(numDefaultFeatures
);
1394 for (uint32_t i
= 0; i
< numDefaultFeatures
; i
++) {
1395 set
->Insert(defaultFeatures
[i
]);
1397 if (!sDefaultFeatures
.compareExchange(nullptr, set
)) {
1402 // iterate over the scripts in the font
1403 hb_tag_t scriptTags
[8];
1405 uint32_t len
, offset
= 0;
1407 len
= ArrayLength(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
== ArrayLength(scriptTags
));
1432 // spaces in default features of default script?
1433 // ==> can't use word cache, skip GPOS analysis
1434 bool canUseWordCache
= true;
1435 if (HasSubstitution(mFontEntry
->mDefaultSubSpaceFeatures
, Script::COMMON
)) {
1436 canUseWordCache
= false;
1439 // GPOS lookups - distinguish kerning from non-kerning features
1440 if (canUseWordCache
&& hb_ot_layout_has_positioning(face
)) {
1441 bool hasKerning
= false, hasNonKerning
= false;
1442 HasLookupRuleWithGlyph(face
, HB_OT_TAG_GPOS
, hasNonKerning
,
1443 HB_TAG('k', 'e', 'r', 'n'), hasKerning
, spaceGlyph
);
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 if (const auto* paintGraph
= COLRFonts::GetGlyphPaintGraph(colr
, aGlyphId
)) {
2668 const auto* hbShaper
= GetHarfBuzzShaper();
2669 if (hbShaper
&& hbShaper
->IsInitialized()) {
2671 aTextDrawer
->FoundUnsupportedFeature();
2675 // For reasonable font sizes, use a cache of rasterized glyphs.
2676 if (GetAdjustedSize() <= 256.0) {
2677 AutoWriteLock
lock(mLock
);
2678 if (!mColorGlyphCache
) {
2679 mColorGlyphCache
= MakeUnique
<ColorGlyphCache
>();
2682 // Tell the cache what colors we're using; if they have changed, it will
2683 // discard any currently-cached entries.
2684 mColorGlyphCache
->SetColors(aFontParams
.currentColor
,
2685 aFontParams
.palette
);
2688 auto cached
= mColorGlyphCache
->mCache
.lookupForAdd(aGlyphId
);
2689 Rect bounds
= COLRFonts::GetColorGlyphBounds(
2690 colr
, hbShaper
->GetHBFont(), aGlyphId
, aDrawTarget
,
2691 aFontParams
.scaledFont
, mFUnitsConvFactor
);
2697 // Create a temporary DrawTarget, render the glyph, and save a
2698 // snapshot of the rendering in the cache.
2699 IntSize
size(int(bounds
.width
), int(bounds
.height
));
2700 SurfaceFormat format
= SurfaceFormat::B8G8R8A8
;
2702 Factory::CreateDrawTarget(BackendType::SKIA
, size
, format
);
2704 ok
= COLRFonts::PaintGlyphGraph(
2705 GetFontEntry()->GetCOLR(), hbShaper
->GetHBFont(), paintGraph
,
2706 target
, nullptr, aFontParams
.scaledFont
,
2707 aFontParams
.drawOptions
, -bounds
.TopLeft(),
2708 aFontParams
.currentColor
, aFontParams
.palette
->Colors(),
2709 aGlyphId
, mFUnitsConvFactor
);
2711 RefPtr snapshot
= target
->Snapshot();
2712 ok
= mColorGlyphCache
->mCache
.add(cached
, aGlyphId
, snapshot
);
2717 // Paint the snapshot from cached->value(), and return.
2718 aDrawTarget
->DrawSurface(
2719 cached
->value(), Rect(aPoint
+ bounds
.TopLeft(), bounds
.Size()),
2720 Rect(Point(), bounds
.Size()));
2725 // If we failed to cache the glyph, or it was too large to even try,
2726 // just paint directly to the target.
2727 return COLRFonts::PaintGlyphGraph(
2728 colr
, hbShaper
->GetHBFont(), paintGraph
, aDrawTarget
, aTextDrawer
,
2729 aFontParams
.scaledFont
, aFontParams
.drawOptions
, aPoint
,
2730 aFontParams
.currentColor
, aFontParams
.palette
->Colors(), aGlyphId
,
2735 if (const auto* layers
=
2736 COLRFonts::GetGlyphLayers(GetFontEntry()->GetCOLR(), aGlyphId
)) {
2737 auto face(GetFontEntry()->GetHBFace());
2738 bool ok
= COLRFonts::PaintGlyphLayers(
2739 colr
, face
, layers
, aDrawTarget
, aTextDrawer
, aFontParams
.scaledFont
,
2740 aFontParams
.drawOptions
, aPoint
, aFontParams
.currentColor
,
2741 aFontParams
.palette
->Colors());
2748 void gfxFont::ColorGlyphCache::SetColors(sRGBColor aCurrentColor
,
2749 FontPalette
* aPalette
) {
2750 if (aCurrentColor
!= mCurrentColor
|| aPalette
!= mPalette
) {
2752 mCurrentColor
= aCurrentColor
;
2753 mPalette
= aPalette
;
2757 bool gfxFont::HasColorGlyphFor(uint32_t aCh
, uint32_t aNextCh
) {
2758 // Bitmap fonts are assumed to provide "color" glyphs for all supported chars.
2759 gfxFontEntry
* fe
= GetFontEntry();
2760 if (fe
->HasColorBitmapTable()) {
2763 // Use harfbuzz shaper to look up the default glyph ID for the character.
2764 auto* shaper
= GetHarfBuzzShaper();
2769 if (gfxFontUtils::IsVarSelector(aNextCh
)) {
2770 gid
= shaper
->GetVariationGlyph(aCh
, aNextCh
);
2773 gid
= shaper
->GetNominalGlyph(aCh
);
2779 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1801521:
2780 // Emoji special-case: flag sequences NOT based on Regional Indicator pairs
2781 // use the BLACK FLAG character plus a series of plane-14 TAG LETTERs, e.g.
2782 // England = <black-flag, tag-G, tag-B, tag-E, tag-N, tag-G, tag-cancel>
2783 // Here, we don't check for support of the entire sequence (too much
2784 // expensive lookahead), but we check that the font at least supports the
2785 // first of the tag letter codes, because if it doesn't, we're at risk of
2786 // just getting an undifferentiated black flag glyph.
2787 if (gfxFontUtils::IsEmojiFlagAndTag(aCh
, aNextCh
)) {
2788 if (!shaper
->GetNominalGlyph(aNextCh
)) {
2793 // Check if there is a COLR/CPAL or SVG glyph for this ID.
2794 if (fe
->TryGetColorGlyphs() &&
2795 (COLRFonts::GetGlyphPaintGraph(fe
->GetCOLR(), gid
) ||
2796 COLRFonts::GetGlyphLayers(fe
->GetCOLR(), gid
))) {
2799 if (fe
->TryGetSVGData(this) && fe
->HasSVGGlyph(gid
)) {
2805 static void UnionRange(gfxFloat aX
, gfxFloat
* aDestMin
, gfxFloat
* aDestMax
) {
2806 *aDestMin
= std::min(*aDestMin
, aX
);
2807 *aDestMax
= std::max(*aDestMax
, aX
);
2810 // We get precise glyph extents if the textrun creator requested them, or
2811 // if the font is a user font --- in which case the author may be relying
2812 // on overflowing glyphs.
2813 static bool NeedsGlyphExtents(gfxFont
* aFont
, const gfxTextRun
* aTextRun
) {
2814 return (aTextRun
->GetFlags() &
2815 gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX
) ||
2816 aFont
->GetFontEntry()->IsUserFont();
2819 bool gfxFont::IsSpaceGlyphInvisible(DrawTarget
* aRefDrawTarget
,
2820 const gfxTextRun
* aTextRun
) {
2821 gfxFontEntry::LazyFlag flag
= mFontEntry
->mSpaceGlyphIsInvisible
;
2822 if (flag
== gfxFontEntry::LazyFlag::Uninitialized
&&
2823 GetAdjustedSize() >= 1.0) {
2824 gfxGlyphExtents
* extents
=
2825 GetOrCreateGlyphExtents(aTextRun
->GetAppUnitsPerDevUnit());
2826 gfxRect glyphExtents
;
2827 flag
= extents
->GetTightGlyphExtentsAppUnits(
2828 this, aRefDrawTarget
, GetSpaceGlyph(), &glyphExtents
) &&
2829 glyphExtents
.IsEmpty()
2830 ? gfxFontEntry::LazyFlag::Yes
2831 : gfxFontEntry::LazyFlag::No
;
2832 mFontEntry
->mSpaceGlyphIsInvisible
= flag
;
2834 return flag
== gfxFontEntry::LazyFlag::Yes
;
2837 bool gfxFont::MeasureGlyphs(const gfxTextRun
* aTextRun
, uint32_t aStart
,
2838 uint32_t aEnd
, BoundingBoxType aBoundingBoxType
,
2839 DrawTarget
* aRefDrawTarget
, Spacing
* aSpacing
,
2840 gfxGlyphExtents
* aExtents
, bool aIsRTL
,
2841 bool aNeedsGlyphExtents
, RunMetrics
& aMetrics
,
2842 gfxFloat
* aAdvanceMin
, gfxFloat
* aAdvanceMax
) {
2843 const gfxTextRun::CompressedGlyph
* charGlyphs
=
2844 aTextRun
->GetCharacterGlyphs();
2847 x
+= aSpacing
[0].mBefore
;
2849 uint32_t spaceGlyph
= GetSpaceGlyph();
2850 bool allGlyphsInvisible
= true;
2852 AutoReadLock
lock(aExtents
->mLock
);
2854 for (uint32_t i
= aStart
; i
< aEnd
; ++i
) {
2855 const gfxTextRun::CompressedGlyph
* glyphData
= &charGlyphs
[i
];
2856 if (glyphData
->IsSimpleGlyph()) {
2857 double advance
= glyphData
->GetSimpleAdvance();
2858 uint32_t glyphIndex
= glyphData
->GetSimpleGlyph();
2859 if (allGlyphsInvisible
) {
2860 if (glyphIndex
!= spaceGlyph
) {
2861 allGlyphsInvisible
= false;
2863 gfxFontEntry::LazyFlag flag
= mFontEntry
->mSpaceGlyphIsInvisible
;
2864 if (flag
== gfxFontEntry::LazyFlag::Uninitialized
&&
2865 GetAdjustedSize() >= 1.0) {
2866 gfxRect glyphExtents
;
2867 flag
= aExtents
->GetTightGlyphExtentsAppUnitsLocked(
2868 this, aRefDrawTarget
, spaceGlyph
, &glyphExtents
) &&
2869 glyphExtents
.IsEmpty()
2870 ? gfxFontEntry::LazyFlag::Yes
2871 : gfxFontEntry::LazyFlag::No
;
2872 mFontEntry
->mSpaceGlyphIsInvisible
= flag
;
2874 if (flag
== gfxFontEntry::LazyFlag::No
) {
2875 allGlyphsInvisible
= false;
2879 // Only get the real glyph horizontal extent if we were asked
2880 // for the tight bounding box or we're in quality mode
2881 if (aBoundingBoxType
!= LOOSE_INK_EXTENTS
|| aNeedsGlyphExtents
) {
2882 uint16_t extentsWidth
=
2883 aExtents
->GetContainedGlyphWidthAppUnitsLocked(glyphIndex
);
2884 if (extentsWidth
!= gfxGlyphExtents::INVALID_WIDTH
&&
2885 aBoundingBoxType
== LOOSE_INK_EXTENTS
) {
2886 UnionRange(x
, aAdvanceMin
, aAdvanceMax
);
2887 UnionRange(x
+ extentsWidth
, aAdvanceMin
, aAdvanceMax
);
2890 if (!aExtents
->GetTightGlyphExtentsAppUnitsLocked(
2891 this, aRefDrawTarget
, glyphIndex
, &glyphRect
)) {
2892 glyphRect
= gfxRect(0, aMetrics
.mBoundingBox
.Y(), advance
,
2893 aMetrics
.mBoundingBox
.Height());
2896 // In effect, swap left and right sidebearings of the glyph, for
2897 // proper accumulation of potentially-overlapping glyph rects.
2898 glyphRect
.MoveToX(advance
- glyphRect
.XMost());
2900 glyphRect
.MoveByX(x
);
2901 aMetrics
.mBoundingBox
= aMetrics
.mBoundingBox
.Union(glyphRect
);
2906 allGlyphsInvisible
= false;
2907 uint32_t glyphCount
= glyphData
->GetGlyphCount();
2908 if (glyphCount
> 0) {
2909 const gfxTextRun::DetailedGlyph
* details
=
2910 aTextRun
->GetDetailedGlyphs(i
);
2911 NS_ASSERTION(details
!= nullptr,
2912 "detailedGlyph record should not be missing!");
2914 for (j
= 0; j
< glyphCount
; ++j
, ++details
) {
2915 uint32_t glyphIndex
= details
->mGlyphID
;
2916 double advance
= details
->mAdvance
;
2918 if (glyphData
->IsMissing() ||
2919 !aExtents
->GetTightGlyphExtentsAppUnitsLocked(
2920 this, aRefDrawTarget
, glyphIndex
, &glyphRect
)) {
2921 // We might have failed to get glyph extents due to
2923 glyphRect
= gfxRect(0, -aMetrics
.mAscent
, advance
,
2924 aMetrics
.mAscent
+ aMetrics
.mDescent
);
2927 // Swap left/right sidebearings of the glyph, because we're doing
2928 // mirrored measurement.
2929 glyphRect
.MoveToX(advance
- glyphRect
.XMost());
2930 // Move to current x position, mirroring any x-offset amount.
2931 glyphRect
.MoveByX(x
- details
->mOffset
.x
);
2933 glyphRect
.MoveByX(x
+ details
->mOffset
.x
);
2935 glyphRect
.MoveByY(details
->mOffset
.y
);
2936 aMetrics
.mBoundingBox
= aMetrics
.mBoundingBox
.Union(glyphRect
);
2942 double space
= aSpacing
[i
- aStart
].mAfter
;
2944 space
+= aSpacing
[i
+ 1 - aStart
].mBefore
;
2950 aMetrics
.mAdvanceWidth
= x
;
2951 return allGlyphsInvisible
;
2954 bool gfxFont::MeasureGlyphs(const gfxTextRun
* aTextRun
, uint32_t aStart
,
2955 uint32_t aEnd
, BoundingBoxType aBoundingBoxType
,
2956 DrawTarget
* aRefDrawTarget
, Spacing
* aSpacing
,
2957 bool aIsRTL
, RunMetrics
& aMetrics
) {
2958 const gfxTextRun::CompressedGlyph
* charGlyphs
=
2959 aTextRun
->GetCharacterGlyphs();
2962 x
+= aSpacing
[0].mBefore
;
2964 uint32_t spaceGlyph
= GetSpaceGlyph();
2965 bool allGlyphsInvisible
= true;
2967 for (uint32_t i
= aStart
; i
< aEnd
; ++i
) {
2968 const gfxTextRun::CompressedGlyph
* glyphData
= &charGlyphs
[i
];
2969 if (glyphData
->IsSimpleGlyph()) {
2970 double advance
= glyphData
->GetSimpleAdvance();
2971 uint32_t glyphIndex
= glyphData
->GetSimpleGlyph();
2972 if (allGlyphsInvisible
&&
2973 (glyphIndex
!= spaceGlyph
||
2974 !IsSpaceGlyphInvisible(aRefDrawTarget
, aTextRun
))) {
2975 allGlyphsInvisible
= false;
2979 allGlyphsInvisible
= false;
2980 uint32_t glyphCount
= glyphData
->GetGlyphCount();
2981 if (glyphCount
> 0) {
2982 const gfxTextRun::DetailedGlyph
* details
=
2983 aTextRun
->GetDetailedGlyphs(i
);
2984 NS_ASSERTION(details
!= nullptr,
2985 "detailedGlyph record should not be missing!");
2987 for (j
= 0; j
< glyphCount
; ++j
, ++details
) {
2988 double advance
= details
->mAdvance
;
2989 gfxRect
glyphRect(0, -aMetrics
.mAscent
, advance
,
2990 aMetrics
.mAscent
+ aMetrics
.mDescent
);
2992 // Swap left/right sidebearings of the glyph, because we're doing
2993 // mirrored measurement.
2994 glyphRect
.MoveToX(advance
- glyphRect
.XMost());
2995 // Move to current x position, mirroring any x-offset amount.
2996 glyphRect
.MoveByX(x
- details
->mOffset
.x
);
2998 glyphRect
.MoveByX(x
+ details
->mOffset
.x
);
3000 glyphRect
.MoveByY(details
->mOffset
.y
);
3001 aMetrics
.mBoundingBox
= aMetrics
.mBoundingBox
.Union(glyphRect
);
3007 double space
= aSpacing
[i
- aStart
].mAfter
;
3009 space
+= aSpacing
[i
+ 1 - aStart
].mBefore
;
3015 aMetrics
.mAdvanceWidth
= x
;
3016 return allGlyphsInvisible
;
3019 gfxFont::RunMetrics
gfxFont::Measure(const gfxTextRun
* aTextRun
,
3020 uint32_t aStart
, uint32_t aEnd
,
3021 BoundingBoxType aBoundingBoxType
,
3022 DrawTarget
* aRefDrawTarget
,
3024 gfx::ShapedTextFlags aOrientation
) {
3025 // If aBoundingBoxType is TIGHT_HINTED_OUTLINE_EXTENTS
3026 // and the underlying cairo font may be antialiased,
3027 // we need to create a copy in order to avoid getting cached extents.
3028 // This is only used by MathML layout at present.
3029 if (aBoundingBoxType
== TIGHT_HINTED_OUTLINE_EXTENTS
&&
3030 mAntialiasOption
!= kAntialiasNone
) {
3031 gfxFont
* nonAA
= mNonAAFont
;
3033 nonAA
= CopyWithAntialiasOption(kAntialiasNone
);
3035 if (!mNonAAFont
.compareExchange(nullptr, nonAA
)) {
3041 // if font subclass doesn't implement CopyWithAntialiasOption(),
3042 // it will return null and we'll proceed to use the existing font
3044 return nonAA
->Measure(aTextRun
, aStart
, aEnd
,
3045 TIGHT_HINTED_OUTLINE_EXTENTS
, aRefDrawTarget
,
3046 aSpacing
, aOrientation
);
3050 const int32_t appUnitsPerDevUnit
= aTextRun
->GetAppUnitsPerDevUnit();
3051 // Current position in appunits
3052 Orientation orientation
=
3053 aOrientation
== gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
3054 ? nsFontMetrics::eVertical
3055 : nsFontMetrics::eHorizontal
;
3056 const gfxFont::Metrics
& fontMetrics
= GetMetrics(orientation
);
3058 gfxFloat baselineOffset
= 0;
3059 if (aTextRun
->UseCenterBaseline() &&
3060 orientation
== nsFontMetrics::eHorizontal
) {
3061 // For a horizontal font being used in vertical writing mode with
3062 // text-orientation:mixed, the overall metrics we're accumulating
3063 // will be aimed at a center baseline. But this font's metrics were
3064 // based on the alphabetic baseline. So we compute a baseline offset
3065 // that will be applied to ascent/descent values and glyph rects
3066 // to effectively shift them relative to the baseline.
3067 // XXX Eventually we should probably use the BASE table, if present.
3068 // But it usually isn't, so we need an ad hoc adjustment for now.
3070 appUnitsPerDevUnit
* (fontMetrics
.emAscent
- fontMetrics
.emDescent
) / 2;
3074 metrics
.mAscent
= fontMetrics
.maxAscent
* appUnitsPerDevUnit
;
3075 metrics
.mDescent
= fontMetrics
.maxDescent
* appUnitsPerDevUnit
;
3077 if (aStart
== aEnd
) {
3078 // exit now before we look at aSpacing[0], which is undefined
3079 metrics
.mAscent
-= baselineOffset
;
3080 metrics
.mDescent
+= baselineOffset
;
3081 metrics
.mBoundingBox
=
3082 gfxRect(0, -metrics
.mAscent
, 0, metrics
.mAscent
+ metrics
.mDescent
);
3086 gfxFloat advanceMin
= 0, advanceMax
= 0;
3087 bool isRTL
= aTextRun
->IsRightToLeft();
3088 bool needsGlyphExtents
= NeedsGlyphExtents(this, aTextRun
);
3089 gfxGlyphExtents
* extents
=
3090 ((aBoundingBoxType
== LOOSE_INK_EXTENTS
&& !needsGlyphExtents
&&
3091 !aTextRun
->HasDetailedGlyphs()) ||
3092 MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero()))
3094 : GetOrCreateGlyphExtents(aTextRun
->GetAppUnitsPerDevUnit());
3096 bool allGlyphsInvisible
;
3098 allGlyphsInvisible
= MeasureGlyphs(
3099 aTextRun
, aStart
, aEnd
, aBoundingBoxType
, aRefDrawTarget
, aSpacing
,
3100 extents
, isRTL
, needsGlyphExtents
, metrics
, &advanceMin
, &advanceMax
);
3102 allGlyphsInvisible
=
3103 MeasureGlyphs(aTextRun
, aStart
, aEnd
, aBoundingBoxType
, aRefDrawTarget
,
3104 aSpacing
, isRTL
, metrics
);
3107 if (allGlyphsInvisible
) {
3108 metrics
.mBoundingBox
.SetEmpty();
3109 } else if (aBoundingBoxType
== LOOSE_INK_EXTENTS
) {
3110 UnionRange(metrics
.mAdvanceWidth
, &advanceMin
, &advanceMax
);
3111 gfxRect
fontBox(advanceMin
, -metrics
.mAscent
, advanceMax
- advanceMin
,
3112 metrics
.mAscent
+ metrics
.mDescent
);
3113 metrics
.mBoundingBox
= metrics
.mBoundingBox
.Union(fontBox
);
3117 // Reverse the effect of having swapped each glyph's sidebearings, to get
3118 // the correct sidebearings of the merged bounding box.
3119 metrics
.mBoundingBox
.MoveToX(metrics
.mAdvanceWidth
-
3120 metrics
.mBoundingBox
.XMost());
3123 // If the font may be rendered with a fake-italic effect, we need to allow
3124 // for the top-right of the glyphs being skewed to the right, and the
3125 // bottom-left being skewed further left.
3126 gfxFloat skew
= SkewForSyntheticOblique();
3128 gfxFloat extendLeftEdge
, extendRightEdge
;
3129 if (orientation
== nsFontMetrics::eVertical
) {
3130 // The glyph will actually be skewed vertically, but "left" and "right"
3131 // here refer to line-left (physical top) and -right (bottom), so these
3132 // are still the directions in which we need to extend the box.
3133 extendLeftEdge
= skew
< 0.0 ? ceil(-skew
* metrics
.mBoundingBox
.XMost())
3134 : ceil(skew
* -metrics
.mBoundingBox
.X());
3135 extendRightEdge
= skew
< 0.0 ? ceil(-skew
* -metrics
.mBoundingBox
.X())
3136 : ceil(skew
* metrics
.mBoundingBox
.XMost());
3138 extendLeftEdge
= skew
< 0.0 ? ceil(-skew
* -metrics
.mBoundingBox
.Y())
3139 : ceil(skew
* metrics
.mBoundingBox
.YMost());
3140 extendRightEdge
= skew
< 0.0 ? ceil(-skew
* metrics
.mBoundingBox
.YMost())
3141 : ceil(skew
* -metrics
.mBoundingBox
.Y());
3143 metrics
.mBoundingBox
.SetWidth(metrics
.mBoundingBox
.Width() +
3144 extendLeftEdge
+ extendRightEdge
);
3145 metrics
.mBoundingBox
.MoveByX(-extendLeftEdge
);
3148 if (baselineOffset
!= 0) {
3149 metrics
.mAscent
-= baselineOffset
;
3150 metrics
.mDescent
+= baselineOffset
;
3151 metrics
.mBoundingBox
.MoveByY(baselineOffset
);
3157 bool gfxFont::AgeCachedWords() {
3158 mozilla::AutoWriteLock
lock(mLock
);
3160 for (auto it
= mWordCache
->modIter(); !it
.done(); it
.next()) {
3161 auto& entry
= it
.get().value();
3163 NS_ASSERTION(entry
, "cache entry has no gfxShapedWord!");
3165 } else if (entry
->IncrementAge() == kShapedWordCacheMaxAge
) {
3169 return mWordCache
->empty();
3174 void gfxFont::NotifyGlyphsChanged() const {
3175 AutoReadLock
lock(mLock
);
3176 uint32_t i
, count
= mGlyphExtentsArray
.Length();
3177 for (i
= 0; i
< count
; ++i
) {
3178 // Flush cached extents array
3179 mGlyphExtentsArray
[i
]->NotifyGlyphsChanged();
3182 if (mGlyphChangeObservers
) {
3183 for (const auto& key
: *mGlyphChangeObservers
) {
3184 key
->NotifyGlyphsChanged();
3189 // If aChar is a "word boundary" for shaped-word caching purposes, return it;
3191 static char16_t
IsBoundarySpace(char16_t aChar
, char16_t aNextChar
) {
3192 if ((aChar
== ' ' || aChar
== 0x00A0) && !IsClusterExtender(aNextChar
)) {
3198 // In 8-bit text, there cannot be any cluster-extenders.
3199 static uint8_t IsBoundarySpace(uint8_t aChar
, uint8_t aNextChar
) {
3200 if (aChar
== ' ' || aChar
== 0x00A0) {
3207 # define GFX_MAYBE_UNUSED __attribute__((unused))
3209 # define GFX_MAYBE_UNUSED
3212 template <typename T
, typename Func
>
3213 bool gfxFont::ProcessShapedWordInternal(
3214 DrawTarget
* aDrawTarget
, const T
* aText
, uint32_t aLength
, uint32_t aHash
,
3215 Script aRunScript
, nsAtom
* aLanguage
, bool aVertical
,
3216 int32_t aAppUnitsPerDevUnit
, gfx::ShapedTextFlags aFlags
,
3217 RoundingFlags aRounding
, gfxTextPerfMetrics
* aTextPerf GFX_MAYBE_UNUSED
,
3219 WordCacheKey
key(aText
, aLength
, aHash
, aRunScript
, aLanguage
,
3220 aAppUnitsPerDevUnit
, aFlags
, aRounding
);
3222 // If we have a word cache, attempt to look up the word in it.
3223 AutoReadLock
lock(mLock
);
3225 // if there's a cached entry for this word, just return it
3226 if (auto entry
= mWordCache
->lookup(key
)) {
3227 entry
->value()->ResetAge();
3228 #ifndef RELEASE_OR_BETA
3230 // XXX we should make sure this is atomic
3231 aTextPerf
->current
.wordCacheHit
++;
3234 aCallback(entry
->value().get());
3240 // We didn't find a cached word (or don't even have a cache yet), so create
3241 // a new gfxShapedWord and cache it. We don't have to lock during shaping,
3242 // only when it comes time to cache the new entry.
3244 UniquePtr
<gfxShapedWord
> newShapedWord(
3245 gfxShapedWord::Create(aText
, aLength
, aRunScript
, aLanguage
,
3246 aAppUnitsPerDevUnit
, aFlags
, aRounding
));
3247 if (!newShapedWord
) {
3248 NS_WARNING("failed to create gfxShapedWord - expect missing text");
3251 DebugOnly
<bool> ok
=
3252 ShapeText(aDrawTarget
, aText
, 0, aLength
, aRunScript
, aLanguage
,
3253 aVertical
, aRounding
, newShapedWord
.get());
3254 NS_WARNING_ASSERTION(ok
, "failed to shape word - expect garbled text");
3257 // We're going to cache the new shaped word, so lock for writing now.
3258 AutoWriteLock
lock(mLock
);
3260 mWordCache
= MakeUnique
<HashMap
<WordCacheKey
, UniquePtr
<gfxShapedWord
>,
3261 WordCacheKey::HashPolicy
>>();
3263 // If the cache is getting too big, flush it and start over.
3264 uint32_t wordCacheMaxEntries
=
3265 gfxPlatform::GetPlatform()->WordCacheMaxEntries();
3266 if (mWordCache
->count() > wordCacheMaxEntries
) {
3267 // Flush the cache if it is getting overly big.
3268 NS_WARNING("flushing shaped-word cache");
3269 ClearCachedWordsLocked();
3273 // Update key so that it references the text stored in the newShapedWord,
3274 // which is guaranteed to live as long as the hashtable entry.
3275 if ((key
.mTextIs8Bit
= newShapedWord
->TextIs8Bit())) {
3276 key
.mText
.mSingle
= newShapedWord
->Text8Bit();
3278 key
.mText
.mDouble
= newShapedWord
->TextUnicode();
3280 auto entry
= mWordCache
->lookupForAdd(key
);
3282 // It's unlikely, but maybe another thread got there before us...
3284 // Use the existing entry; the newShapedWord will be discarded.
3285 entry
->value()->ResetAge();
3286 #ifndef RELEASE_OR_BETA
3288 aTextPerf
->current
.wordCacheHit
++;
3291 aCallback(entry
->value().get());
3295 if (!mWordCache
->add(entry
, key
, std::move(newShapedWord
))) {
3296 NS_WARNING("failed to cache gfxShapedWord - expect missing text");
3300 #ifndef RELEASE_OR_BETA
3302 aTextPerf
->current
.wordCacheMiss
++;
3305 aCallback(entry
->value().get());
3308 gfxFontCache::GetCache()->RunWordCacheExpirationTimer();
3312 bool gfxFont::WordCacheKey::HashPolicy::match(const Key
& aKey
,
3313 const Lookup
& aLookup
) {
3314 if (aKey
.mLength
!= aLookup
.mLength
|| aKey
.mFlags
!= aLookup
.mFlags
||
3315 aKey
.mRounding
!= aLookup
.mRounding
||
3316 aKey
.mAppUnitsPerDevUnit
!= aLookup
.mAppUnitsPerDevUnit
||
3317 aKey
.mScript
!= aLookup
.mScript
|| aKey
.mLanguage
!= aLookup
.mLanguage
) {
3321 if (aKey
.mTextIs8Bit
) {
3322 if (aLookup
.mTextIs8Bit
) {
3323 return (0 == memcmp(aKey
.mText
.mSingle
, aLookup
.mText
.mSingle
,
3324 aKey
.mLength
* sizeof(uint8_t)));
3326 // The lookup key has 16-bit text, even though all the characters are < 256,
3327 // so the TEXT_IS_8BIT flag was set and the cached ShapedWord we're
3328 // comparing with will have 8-bit text.
3329 const uint8_t* s1
= aKey
.mText
.mSingle
;
3330 const char16_t
* s2
= aLookup
.mText
.mDouble
;
3331 const char16_t
* s2end
= s2
+ aKey
.mLength
;
3332 while (s2
< s2end
) {
3333 if (*s1
++ != *s2
++) {
3339 NS_ASSERTION(!(aLookup
.mFlags
& gfx::ShapedTextFlags::TEXT_IS_8BIT
) &&
3340 !aLookup
.mTextIs8Bit
,
3341 "didn't expect 8-bit text here");
3342 return (0 == memcmp(aKey
.mText
.mDouble
, aLookup
.mText
.mDouble
,
3343 aKey
.mLength
* sizeof(char16_t
)));
3346 bool gfxFont::ProcessSingleSpaceShapedWord(
3347 DrawTarget
* aDrawTarget
, bool aVertical
, int32_t aAppUnitsPerDevUnit
,
3348 gfx::ShapedTextFlags aFlags
, RoundingFlags aRounding
,
3349 const std::function
<void(gfxShapedWord
*)>& aCallback
) {
3350 static const uint8_t space
= ' ';
3351 return ProcessShapedWordInternal(
3352 aDrawTarget
, &space
, 1, gfxShapedWord::HashMix(0, ' '), Script::LATIN
,
3353 /* aLanguage = */ nullptr, aVertical
, aAppUnitsPerDevUnit
, aFlags
,
3354 aRounding
, nullptr, aCallback
);
3357 bool gfxFont::ShapeText(DrawTarget
* aDrawTarget
, const uint8_t* aText
,
3358 uint32_t aOffset
, uint32_t aLength
, Script aScript
,
3359 nsAtom
* aLanguage
, bool aVertical
,
3360 RoundingFlags aRounding
, gfxShapedText
* aShapedText
) {
3361 nsDependentCSubstring
ascii((const char*)aText
, aLength
);
3363 AppendASCIItoUTF16(ascii
, utf16
);
3364 if (utf16
.Length() != aLength
) {
3367 return ShapeText(aDrawTarget
, utf16
.BeginReading(), aOffset
, aLength
, aScript
,
3368 aLanguage
, aVertical
, aRounding
, aShapedText
);
3371 bool gfxFont::ShapeText(DrawTarget
* aDrawTarget
, const char16_t
* aText
,
3372 uint32_t aOffset
, uint32_t aLength
, Script aScript
,
3373 nsAtom
* aLanguage
, bool aVertical
,
3374 RoundingFlags aRounding
, gfxShapedText
* aShapedText
) {
3375 // XXX Currently, we do all vertical shaping through harfbuzz.
3376 // Vertical graphite support may be wanted as a future enhancement.
3377 // XXX Graphite shaping currently only supported on the main thread!
3378 // Worker-thread shaping (offscreen canvas) will always go via harfbuzz.
3379 if (FontCanSupportGraphite() && !aVertical
&& NS_IsMainThread()) {
3380 if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
3381 gfxGraphiteShaper
* shaper
= mGraphiteShaper
;
3383 shaper
= new gfxGraphiteShaper(this);
3384 if (mGraphiteShaper
.compareExchange(nullptr, shaper
)) {
3385 Telemetry::ScalarAdd(Telemetry::ScalarID::BROWSER_USAGE_GRAPHITE
, 1);
3388 shaper
= mGraphiteShaper
;
3391 if (shaper
->ShapeText(aDrawTarget
, aText
, aOffset
, aLength
, aScript
,
3392 aLanguage
, aVertical
, aRounding
, aShapedText
)) {
3393 PostShapingFixup(aDrawTarget
, aText
, aOffset
, aLength
, aVertical
,
3400 gfxHarfBuzzShaper
* shaper
= GetHarfBuzzShaper();
3402 shaper
->ShapeText(aDrawTarget
, aText
, aOffset
, aLength
, aScript
,
3403 aLanguage
, aVertical
, aRounding
, aShapedText
)) {
3404 PostShapingFixup(aDrawTarget
, aText
, aOffset
, aLength
, aVertical
,
3406 if (GetFontEntry()->HasTrackingTable()) {
3407 // Convert font size from device pixels back to CSS px
3408 // to use in selecting tracking value
3409 gfxFloat trackSize
= GetAdjustedSize() *
3410 aShapedText
->GetAppUnitsPerDevUnit() /
3411 AppUnitsPerCSSPixel();
3412 // Usually, a given font will be used with the same appunit scale, so we
3413 // can cache the tracking value rather than recompute it every time.
3415 AutoReadLock
lock(mLock
);
3416 if (trackSize
== mCachedTrackingSize
) {
3417 // Applying tracking is a lot like the adjustment we do for
3418 // synthetic bold: we want to apply between clusters, not to
3419 // non-spacing glyphs within a cluster. So we can reuse that
3421 aShapedText
->ApplyTrackingToClusters(mTracking
, aOffset
, aLength
);
3425 // We didn't have the appropriate tracking value cached yet.
3426 AutoWriteLock
lock(mLock
);
3427 if (trackSize
!= mCachedTrackingSize
) {
3428 mCachedTrackingSize
= trackSize
;
3430 GetFontEntry()->TrackingForCSSPx(trackSize
) * mFUnitsConvFactor
;
3432 aShapedText
->ApplyTrackingToClusters(mTracking
, aOffset
, aLength
);
3437 NS_WARNING_ASSERTION(false, "shaper failed, expect scrambled/missing text");
3441 void gfxFont::PostShapingFixup(DrawTarget
* aDrawTarget
, const char16_t
* aText
,
3442 uint32_t aOffset
, uint32_t aLength
,
3443 bool aVertical
, gfxShapedText
* aShapedText
) {
3444 if (ApplySyntheticBold()) {
3445 const Metrics
& metrics
= GetMetrics(aVertical
? nsFontMetrics::eVertical
3446 : nsFontMetrics::eHorizontal
);
3447 if (metrics
.maxAdvance
> metrics
.aveCharWidth
) {
3448 aShapedText
->ApplyTrackingToClusters(GetSyntheticBoldOffset(), aOffset
,
3454 #define MAX_SHAPING_LENGTH \
3455 32760 // slightly less than 32K, trying to avoid
3456 // over-stressing platform shapers
3457 #define BACKTRACK_LIMIT \
3458 16 // backtrack this far looking for a good place
3459 // to split into fragments for separate shaping
3461 template <typename T
>
3462 bool gfxFont::ShapeFragmentWithoutWordCache(DrawTarget
* aDrawTarget
,
3463 const T
* aText
, uint32_t aOffset
,
3464 uint32_t aLength
, Script aScript
,
3465 nsAtom
* aLanguage
, bool aVertical
,
3466 RoundingFlags aRounding
,
3467 gfxTextRun
* aTextRun
) {
3468 aTextRun
->SetupClusterBoundaries(aOffset
, aText
, aLength
);
3472 while (ok
&& aLength
> 0) {
3473 uint32_t fragLen
= aLength
;
3475 // limit the length of text we pass to shapers in a single call
3476 if (fragLen
> MAX_SHAPING_LENGTH
) {
3477 fragLen
= MAX_SHAPING_LENGTH
;
3479 // in the 8-bit case, there are no multi-char clusters,
3480 // so we don't need to do this check
3481 if constexpr (sizeof(T
) == sizeof(char16_t
)) {
3483 for (i
= 0; i
< BACKTRACK_LIMIT
; ++i
) {
3484 if (aTextRun
->IsClusterStart(aOffset
+ fragLen
- i
)) {
3489 if (i
== BACKTRACK_LIMIT
) {
3490 // if we didn't find any cluster start while backtracking,
3491 // just check that we're not in the middle of a surrogate
3492 // pair; back up by one code unit if we are.
3493 if (NS_IS_SURROGATE_PAIR(aText
[fragLen
- 1], aText
[fragLen
])) {
3500 ok
= ShapeText(aDrawTarget
, aText
, aOffset
, fragLen
, aScript
, aLanguage
,
3501 aVertical
, aRounding
, aTextRun
);
3511 // Check if aCh is an unhandled control character that should be displayed
3512 // as a hexbox rather than rendered by some random font on the system.
3513 // We exclude \r as stray s are rather common (bug 941940).
3514 // Note that \n and \t don't come through here, as they have specific
3515 // meanings that have already been handled.
3516 static bool IsInvalidControlChar(uint32_t aCh
) {
3517 return aCh
!= '\r' && ((aCh
& 0x7f) < 0x20 || aCh
== 0x7f);
3520 template <typename T
>
3521 bool gfxFont::ShapeTextWithoutWordCache(DrawTarget
* aDrawTarget
, const T
* aText
,
3522 uint32_t aOffset
, uint32_t aLength
,
3523 Script aScript
, nsAtom
* aLanguage
,
3524 bool aVertical
, RoundingFlags aRounding
,
3525 gfxTextRun
* aTextRun
) {
3526 uint32_t fragStart
= 0;
3529 for (uint32_t i
= 0; i
<= aLength
&& ok
; ++i
) {
3530 T ch
= (i
< aLength
) ? aText
[i
] : '\n';
3531 bool invalid
= gfxFontGroup::IsInvalidChar(ch
);
3532 uint32_t length
= i
- fragStart
;
3534 // break into separate fragments when we hit an invalid char
3540 ok
= ShapeFragmentWithoutWordCache(
3541 aDrawTarget
, aText
+ fragStart
, aOffset
+ fragStart
, length
, aScript
,
3542 aLanguage
, aVertical
, aRounding
, aTextRun
);
3549 // fragment was terminated by an invalid char: skip it,
3550 // unless it's a control char that we want to show as a hexbox,
3551 // but record where TAB or NEWLINE occur
3553 aTextRun
->SetIsTab(aOffset
+ i
);
3554 } else if (ch
== '\n') {
3555 aTextRun
->SetIsNewline(aOffset
+ i
);
3556 } else if (GetGeneralCategory(ch
) == HB_UNICODE_GENERAL_CATEGORY_FORMAT
) {
3557 aTextRun
->SetIsFormattingControl(aOffset
+ i
);
3558 } else if (IsInvalidControlChar(ch
) &&
3559 !(aTextRun
->GetFlags() &
3560 gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS
)) {
3561 if (GetFontEntry()->IsUserFont() && HasCharacter(ch
)) {
3562 ShapeFragmentWithoutWordCache(aDrawTarget
, aText
+ i
, aOffset
+ i
, 1,
3563 aScript
, aLanguage
, aVertical
, aRounding
,
3566 aTextRun
->SetMissingGlyph(aOffset
+ i
, ch
, this);
3572 NS_WARNING_ASSERTION(ok
, "failed to shape text - expect garbled text");
3576 #ifndef RELEASE_OR_BETA
3577 # define TEXT_PERF_INCR(tp, m) (tp ? (tp)->current.m++ : 0)
3579 # define TEXT_PERF_INCR(tp, m)
3582 inline static bool IsChar8Bit(uint8_t /*aCh*/) { return true; }
3583 inline static bool IsChar8Bit(char16_t aCh
) { return aCh
< 0x100; }
3585 inline static bool HasSpaces(const uint8_t* aString
, uint32_t aLen
) {
3586 return memchr(aString
, 0x20, aLen
) != nullptr;
3589 inline static bool HasSpaces(const char16_t
* aString
, uint32_t aLen
) {
3590 for (const char16_t
* ch
= aString
; ch
< aString
+ aLen
; ch
++) {
3598 template <typename T
>
3599 bool gfxFont::SplitAndInitTextRun(
3600 DrawTarget
* aDrawTarget
, gfxTextRun
* aTextRun
,
3601 const T
* aString
, // text for this font run
3602 uint32_t aRunStart
, // position in the textrun
3603 uint32_t aRunLength
, Script aRunScript
, nsAtom
* aLanguage
,
3604 ShapedTextFlags aOrientation
) {
3605 if (aRunLength
== 0) {
3609 gfxTextPerfMetrics
* tp
= nullptr;
3610 RoundingFlags rounding
= GetRoundOffsetsToPixels(aDrawTarget
);
3612 #ifndef RELEASE_OR_BETA
3613 tp
= aTextRun
->GetFontGroup()->GetTextPerfMetrics();
3615 if (mStyle
.systemFont
) {
3616 tp
->current
.numChromeTextRuns
++;
3618 tp
->current
.numContentTextRuns
++;
3620 tp
->current
.numChars
+= aRunLength
;
3621 if (aRunLength
> tp
->current
.maxTextRunLen
) {
3622 tp
->current
.maxTextRunLen
= aRunLength
;
3627 uint32_t wordCacheCharLimit
=
3628 gfxPlatform::GetPlatform()->WordCacheCharLimit();
3630 bool vertical
= aOrientation
== ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
;
3632 // If spaces can participate in shaping (e.g. within lookups for automatic
3633 // fractions), need to shape without using the word cache which segments
3634 // textruns on space boundaries. Word cache can be used if the textrun
3635 // is short enough to fit in the word cache and it lacks spaces.
3636 tainted_boolean_hint t_canParticipate
=
3637 SpaceMayParticipateInShaping(aRunScript
);
3638 bool canParticipate
= t_canParticipate
.unverified_safe_because(
3639 "We need to ensure that this function operates safely independent of "
3640 "t_canParticipate. The worst that can happen here is that the decision "
3641 "to use the cache is incorrectly made, resulting in a bad "
3642 "rendering/slowness. However, this would not compromise the memory "
3643 "safety of Firefox in any way, and can thus be permitted");
3645 if (canParticipate
) {
3646 if (aRunLength
> wordCacheCharLimit
|| HasSpaces(aString
, aRunLength
)) {
3647 TEXT_PERF_INCR(tp
, wordCacheSpaceRules
);
3648 return ShapeTextWithoutWordCache(aDrawTarget
, aString
, aRunStart
,
3649 aRunLength
, aRunScript
, aLanguage
,
3650 vertical
, rounding
, aTextRun
);
3654 // the only flags we care about for ShapedWord construction/caching
3655 gfx::ShapedTextFlags flags
= aTextRun
->GetFlags();
3656 flags
&= (gfx::ShapedTextFlags::TEXT_IS_RTL
|
3657 gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES
|
3658 gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT
|
3659 gfx::ShapedTextFlags::TEXT_ORIENT_MASK
);
3660 if constexpr (sizeof(T
) == sizeof(uint8_t)) {
3661 flags
|= gfx::ShapedTextFlags::TEXT_IS_8BIT
;
3664 uint32_t wordStart
= 0;
3666 bool wordIs8Bit
= true;
3667 int32_t appUnitsPerDevUnit
= aTextRun
->GetAppUnitsPerDevUnit();
3669 T nextCh
= aString
[0];
3670 for (uint32_t i
= 0; i
<= aRunLength
; ++i
) {
3672 nextCh
= (i
< aRunLength
- 1) ? aString
[i
+ 1] : '\n';
3673 T boundary
= IsBoundarySpace(ch
, nextCh
);
3674 bool invalid
= !boundary
&& gfxFontGroup::IsInvalidChar(ch
);
3675 uint32_t length
= i
- wordStart
;
3677 // break into separate ShapedWords when we hit an invalid char,
3678 // or a boundary space (always handled individually),
3679 // or the first non-space after a space
3680 if (!boundary
&& !invalid
) {
3681 if (!IsChar8Bit(ch
)) {
3684 // include this character in the hash, and move on to next
3685 hash
= gfxShapedWord::HashMix(hash
, ch
);
3689 // We've decided to break here (i.e. we're at the end of a "word");
3690 // shape the word and add it to the textrun.
3691 // For words longer than the limit, we don't use the
3692 // font's word cache but just shape directly into the textrun.
3693 if (length
> wordCacheCharLimit
) {
3694 TEXT_PERF_INCR(tp
, wordCacheLong
);
3695 bool ok
= ShapeFragmentWithoutWordCache(
3696 aDrawTarget
, aString
+ wordStart
, aRunStart
+ wordStart
, length
,
3697 aRunScript
, aLanguage
, vertical
, rounding
, aTextRun
);
3701 } else if (length
> 0) {
3702 gfx::ShapedTextFlags wordFlags
= flags
;
3703 // in the 8-bit version of this method, TEXT_IS_8BIT was
3704 // already set as part of |flags|, so no need for a per-word
3706 if (sizeof(T
) == sizeof(char16_t
)) {
3708 wordFlags
|= gfx::ShapedTextFlags::TEXT_IS_8BIT
;
3711 bool processed
= ProcessShapedWordInternal(
3712 aDrawTarget
, aString
+ wordStart
, length
, hash
, aRunScript
, aLanguage
,
3713 vertical
, appUnitsPerDevUnit
, wordFlags
, rounding
, tp
,
3714 [&](gfxShapedWord
* aShapedWord
) {
3715 aTextRun
->CopyGlyphDataFrom(aShapedWord
, aRunStart
+ wordStart
);
3718 return false; // failed, presumably out of memory?
3723 // word was terminated by a space: add that to the textrun
3724 MOZ_ASSERT(aOrientation
!= ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED
,
3725 "text-orientation:mixed should be resolved earlier");
3726 if (boundary
!= ' ' || !aTextRun
->SetSpaceGlyphIfSimple(
3727 this, aRunStart
+ i
, ch
, aOrientation
)) {
3728 // Currently, the only "boundary" characters we recognize are
3729 // space and no-break space, which are both 8-bit, so we force
3730 // that flag (below). If we ever change IsBoundarySpace, we
3731 // may need to revise this.
3732 // Avoid tautological-constant-out-of-range-compare in 8-bit:
3733 DebugOnly
<char16_t
> boundary16
= boundary
;
3734 NS_ASSERTION(boundary16
< 256, "unexpected boundary!");
3735 bool processed
= ProcessShapedWordInternal(
3736 aDrawTarget
, &boundary
, 1, gfxShapedWord::HashMix(0, boundary
),
3737 aRunScript
, aLanguage
, vertical
, appUnitsPerDevUnit
,
3738 flags
| gfx::ShapedTextFlags::TEXT_IS_8BIT
, rounding
, tp
,
3739 [&](gfxShapedWord
* aShapedWord
) {
3740 aTextRun
->CopyGlyphDataFrom(aShapedWord
, aRunStart
+ i
);
3741 if (boundary
== ' ') {
3742 aTextRun
->GetCharacterGlyphs()[aRunStart
+ i
].SetIsSpace();
3755 if (i
== aRunLength
) {
3759 NS_ASSERTION(invalid
, "how did we get here except via an invalid char?");
3761 // word was terminated by an invalid char: skip it,
3762 // unless it's a control char that we want to show as a hexbox,
3763 // but record where TAB or NEWLINE occur
3765 aTextRun
->SetIsTab(aRunStart
+ i
);
3766 } else if (ch
== '\n') {
3767 aTextRun
->SetIsNewline(aRunStart
+ i
);
3768 } else if (GetGeneralCategory(ch
) == HB_UNICODE_GENERAL_CATEGORY_FORMAT
) {
3769 aTextRun
->SetIsFormattingControl(aRunStart
+ i
);
3770 } else if (IsInvalidControlChar(ch
) &&
3771 !(aTextRun
->GetFlags() &
3772 gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS
)) {
3773 if (GetFontEntry()->IsUserFont() && HasCharacter(ch
)) {
3774 ShapeFragmentWithoutWordCache(aDrawTarget
, aString
+ i
, aRunStart
+ i
,
3775 1, aRunScript
, aLanguage
, vertical
,
3776 rounding
, aTextRun
);
3778 aTextRun
->SetMissingGlyph(aRunStart
+ i
, ch
, this);
3790 // Explicit instantiations of SplitAndInitTextRun, to avoid libxul link failure
3791 template bool gfxFont::SplitAndInitTextRun(
3792 DrawTarget
* aDrawTarget
, gfxTextRun
* aTextRun
, const uint8_t* aString
,
3793 uint32_t aRunStart
, uint32_t aRunLength
, Script aRunScript
,
3794 nsAtom
* aLanguage
, ShapedTextFlags aOrientation
);
3795 template bool gfxFont::SplitAndInitTextRun(
3796 DrawTarget
* aDrawTarget
, gfxTextRun
* aTextRun
, const char16_t
* aString
,
3797 uint32_t aRunStart
, uint32_t aRunLength
, Script aRunScript
,
3798 nsAtom
* aLanguage
, ShapedTextFlags aOrientation
);
3801 bool gfxFont::InitFakeSmallCapsRun(
3802 nsPresContext
* aPresContext
, DrawTarget
* aDrawTarget
, gfxTextRun
* aTextRun
,
3803 const char16_t
* aText
, uint32_t aOffset
, uint32_t aLength
,
3804 FontMatchType aMatchType
, gfx::ShapedTextFlags aOrientation
, Script aScript
,
3805 nsAtom
* aLanguage
, bool aSyntheticLower
, bool aSyntheticUpper
) {
3808 RefPtr
<gfxFont
> smallCapsFont
= GetSmallCapsFont();
3809 if (!smallCapsFont
) {
3810 NS_WARNING("failed to get reduced-size font for smallcaps!");
3811 smallCapsFont
= this;
3814 bool isCJK
= gfxTextRun::IsCJKScript(aScript
);
3816 enum RunCaseAction
{ kNoChange
, kUppercaseReduce
, kUppercase
};
3818 RunCaseAction runAction
= kNoChange
;
3819 uint32_t runStart
= 0;
3821 for (uint32_t i
= 0; i
<= aLength
; ++i
) {
3822 uint32_t extraCodeUnits
= 0; // Will be set to 1 if we need to consume
3823 // a trailing surrogate as well as the
3824 // current code unit.
3825 RunCaseAction chAction
= kNoChange
;
3826 // Unless we're at the end, figure out what treatment the current
3827 // character will need.
3829 uint32_t ch
= aText
[i
];
3830 if (i
< aLength
- 1 && NS_IS_SURROGATE_PAIR(ch
, aText
[i
+ 1])) {
3831 ch
= SURROGATE_TO_UCS4(ch
, aText
[i
+ 1]);
3834 // Characters that aren't the start of a cluster are ignored here.
3835 // They get added to whatever lowercase/non-lowercase run we're in.
3836 if (IsClusterExtender(ch
)) {
3837 chAction
= runAction
;
3839 if (ch
!= ToUpperCase(ch
) || SpecialUpper(ch
)) {
3841 chAction
= (aSyntheticLower
? kUppercaseReduce
: kNoChange
);
3842 } else if (ch
!= ToLowerCase(ch
)) {
3844 chAction
= (aSyntheticUpper
? kUppercaseReduce
: kNoChange
);
3845 if (aLanguage
== nsGkAtoms::el
) {
3846 // In Greek, check for characters that will be modified by
3847 // the GreekUpperCase mapping - this catches accented
3848 // capitals where the accent is to be removed (bug 307039).
3849 // These are handled by using the full-size font with the
3850 // uppercasing transform.
3851 mozilla::GreekCasing::State state
;
3852 bool markEta
, updateEta
;
3854 mozilla::GreekCasing::UpperCase(ch
, state
, markEta
, updateEta
);
3855 if ((ch
!= ch2
|| markEta
) && !aSyntheticUpper
) {
3856 chAction
= kUppercase
;
3863 // At the end of the text or when the current character needs different
3864 // casing treatment from the current run, finish the run-in-progress
3865 // and prepare to accumulate a new run.
3866 // Note that we do not look at any source data for offset [i] here,
3867 // as that would be invalid in the case where i==length.
3868 if ((i
== aLength
|| runAction
!= chAction
) && runStart
< i
) {
3869 uint32_t runLength
= i
- runStart
;
3871 switch (runAction
) {
3873 // just use the current font and the existing string
3874 aTextRun
->AddGlyphRun(f
, aMatchType
, aOffset
+ runStart
, true,
3875 aOrientation
, isCJK
);
3876 if (!f
->SplitAndInitTextRun(aDrawTarget
, aTextRun
, aText
+ runStart
,
3877 aOffset
+ runStart
, runLength
, aScript
,
3878 aLanguage
, aOrientation
)) {
3883 case kUppercaseReduce
:
3884 // use reduced-size font, then fall through to uppercase the text
3889 // apply uppercase transform to the string
3890 nsDependentSubstring
origString(aText
+ runStart
, runLength
);
3891 nsAutoString convertedString
;
3892 AutoTArray
<bool, 50> charsToMergeArray
;
3893 AutoTArray
<bool, 50> deletedCharsArray
;
3895 StyleTextTransform globalTransform
{StyleTextTransformCase::Uppercase
,
3897 // No mask needed; we're doing case conversion, not password-hiding.
3898 const char16_t maskChar
= 0;
3899 bool mergeNeeded
= nsCaseTransformTextRunFactory::TransformString(
3900 origString
, convertedString
, Some(globalTransform
), maskChar
,
3901 /* aCaseTransformsOnly = */ false, aLanguage
, charsToMergeArray
,
3904 // Check whether the font supports the uppercased characters needed;
3905 // if not, we're not going to be able to simulate small-caps.
3906 bool failed
= false;
3907 char16_t highSurrogate
= 0;
3908 for (const char16_t
* cp
= convertedString
.BeginReading();
3909 cp
!= convertedString
.EndReading(); ++cp
) {
3910 if (NS_IS_HIGH_SURROGATE(*cp
)) {
3911 highSurrogate
= *cp
;
3915 if (NS_IS_LOW_SURROGATE(*cp
) && highSurrogate
) {
3916 ch
= SURROGATE_TO_UCS4(highSurrogate
, *cp
);
3919 if (!f
->HasCharacter(ch
)) {
3920 if (IsDefaultIgnorable(ch
)) {
3927 // Required uppercase letter(s) missing from the font. Just use the
3928 // original text with the original font, no fake small caps!
3930 convertedString
= origString
;
3931 mergeNeeded
= false;
3936 // This is the hard case: the transformation caused chars
3937 // to be inserted or deleted, so we can't shape directly
3938 // into the destination textrun but have to handle the
3939 // mismatch of character positions.
3940 gfxTextRunFactory::Parameters params
= {
3941 aDrawTarget
, nullptr, nullptr,
3942 nullptr, 0, aTextRun
->GetAppUnitsPerDevUnit()};
3943 RefPtr
<gfxTextRun
> tempRun(gfxTextRun::Create(
3944 ¶ms
, convertedString
.Length(), aTextRun
->GetFontGroup(),
3945 gfx::ShapedTextFlags(), nsTextFrameUtils::Flags()));
3946 tempRun
->AddGlyphRun(f
, aMatchType
, 0, true, aOrientation
, isCJK
);
3947 if (!f
->SplitAndInitTextRun(aDrawTarget
, tempRun
.get(),
3948 convertedString
.BeginReading(), 0,
3949 convertedString
.Length(), aScript
,
3950 aLanguage
, aOrientation
)) {
3953 RefPtr
<gfxTextRun
> mergedRun(gfxTextRun::Create(
3954 ¶ms
, runLength
, aTextRun
->GetFontGroup(),
3955 gfx::ShapedTextFlags(), nsTextFrameUtils::Flags()));
3956 MergeCharactersInTextRun(mergedRun
.get(), tempRun
.get(),
3957 charsToMergeArray
.Elements(),
3958 deletedCharsArray
.Elements());
3959 gfxTextRun::Range
runRange(0, runLength
);
3960 aTextRun
->CopyGlyphDataFrom(mergedRun
.get(), runRange
,
3961 aOffset
+ runStart
);
3964 aTextRun
->AddGlyphRun(f
, aMatchType
, aOffset
+ runStart
, true,
3965 aOrientation
, isCJK
);
3966 if (!f
->SplitAndInitTextRun(aDrawTarget
, aTextRun
,
3967 convertedString
.BeginReading(),
3968 aOffset
+ runStart
, runLength
, aScript
,
3969 aLanguage
, aOrientation
)) {
3979 i
+= extraCodeUnits
;
3981 runAction
= chAction
;
3989 bool gfxFont::InitFakeSmallCapsRun(
3990 nsPresContext
* aPresContext
, DrawTarget
* aDrawTarget
, gfxTextRun
* aTextRun
,
3991 const uint8_t* aText
, uint32_t aOffset
, uint32_t aLength
,
3992 FontMatchType aMatchType
, gfx::ShapedTextFlags aOrientation
, Script aScript
,
3993 nsAtom
* aLanguage
, bool aSyntheticLower
, bool aSyntheticUpper
) {
3994 NS_ConvertASCIItoUTF16
unicodeString(reinterpret_cast<const char*>(aText
),
3996 return InitFakeSmallCapsRun(aPresContext
, aDrawTarget
, aTextRun
,
3997 static_cast<const char16_t
*>(unicodeString
.get()),
3998 aOffset
, aLength
, aMatchType
, aOrientation
,
3999 aScript
, aLanguage
, aSyntheticLower
,
4003 already_AddRefed
<gfxFont
> gfxFont::GetSmallCapsFont() const {
4004 gfxFontStyle
style(*GetStyle());
4005 style
.size
*= SMALL_CAPS_SCALE_FACTOR
;
4006 style
.variantCaps
= NS_FONT_VARIANT_CAPS_NORMAL
;
4007 gfxFontEntry
* fe
= GetFontEntry();
4008 return fe
->FindOrMakeFont(&style
, mUnicodeRangeMap
);
4011 already_AddRefed
<gfxFont
> gfxFont::GetSubSuperscriptFont(
4012 int32_t aAppUnitsPerDevPixel
) const {
4013 gfxFontStyle
style(*GetStyle());
4014 style
.AdjustForSubSuperscript(aAppUnitsPerDevPixel
);
4015 gfxFontEntry
* fe
= GetFontEntry();
4016 return fe
->FindOrMakeFont(&style
, mUnicodeRangeMap
);
4019 gfxGlyphExtents
* gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit
) {
4022 AutoReadLock
lock(mLock
);
4023 readCount
= mGlyphExtentsArray
.Length();
4024 for (uint32_t i
= 0; i
< readCount
; ++i
) {
4025 if (mGlyphExtentsArray
[i
]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit
)
4026 return mGlyphExtentsArray
[i
].get();
4029 AutoWriteLock
lock(mLock
);
4030 // Re-check in case of race.
4031 uint32_t count
= mGlyphExtentsArray
.Length();
4032 for (uint32_t i
= readCount
; i
< count
; ++i
) {
4033 if (mGlyphExtentsArray
[i
]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit
)
4034 return mGlyphExtentsArray
[i
].get();
4036 gfxGlyphExtents
* glyphExtents
= new gfxGlyphExtents(aAppUnitsPerDevUnit
);
4038 mGlyphExtentsArray
.AppendElement(glyphExtents
);
4039 // Initialize the extents of a space glyph, assuming that spaces don't
4041 glyphExtents
->SetContainedGlyphWidthAppUnits(GetSpaceGlyph(), 0);
4043 return glyphExtents
;
4046 void gfxFont::SetupGlyphExtents(DrawTarget
* aDrawTarget
, uint32_t aGlyphID
,
4047 bool aNeedTight
, gfxGlyphExtents
* aExtents
) {
4049 if (mFontEntry
->TryGetSVGData(this) && mFontEntry
->HasSVGGlyph(aGlyphID
) &&
4050 mFontEntry
->GetSVGGlyphExtents(aDrawTarget
, aGlyphID
, GetAdjustedSize(),
4052 gfxFloat d2a
= aExtents
->GetAppUnitsPerDevUnit();
4053 aExtents
->SetTightGlyphExtents(
4054 aGlyphID
, gfxRect(svgBounds
.X() * d2a
, svgBounds
.Y() * d2a
,
4055 svgBounds
.Width() * d2a
, svgBounds
.Height() * d2a
));
4059 if (mFontEntry
->TryGetColorGlyphs() && mFontEntry
->mCOLR
&&
4060 COLRFonts::GetColrTableVersion(mFontEntry
->mCOLR
) == 1) {
4061 auto* shaper
= GetHarfBuzzShaper();
4062 if (shaper
&& shaper
->IsInitialized()) {
4063 RefPtr scaledFont
= GetScaledFont(aDrawTarget
);
4064 Rect r
= COLRFonts::GetColorGlyphBounds(
4065 mFontEntry
->mCOLR
, shaper
->GetHBFont(), aGlyphID
, aDrawTarget
,
4066 scaledFont
, mFUnitsConvFactor
);
4068 gfxFloat d2a
= aExtents
->GetAppUnitsPerDevUnit();
4069 aExtents
->SetTightGlyphExtents(
4070 aGlyphID
, gfxRect(r
.X() * d2a
, r
.Y() * d2a
, r
.Width() * d2a
,
4078 GetGlyphBounds(aGlyphID
, &bounds
, mAntialiasOption
== kAntialiasNone
);
4080 const Metrics
& fontMetrics
= GetMetrics(nsFontMetrics::eHorizontal
);
4081 int32_t appUnitsPerDevUnit
= aExtents
->GetAppUnitsPerDevUnit();
4082 if (!aNeedTight
&& bounds
.x
>= 0.0 && bounds
.y
>= -fontMetrics
.maxAscent
&&
4083 bounds
.height
+ bounds
.y
<= fontMetrics
.maxDescent
) {
4084 uint32_t appUnitsWidth
=
4085 uint32_t(ceil((bounds
.x
+ bounds
.width
) * appUnitsPerDevUnit
));
4086 if (appUnitsWidth
< gfxGlyphExtents::INVALID_WIDTH
) {
4087 aExtents
->SetContainedGlyphWidthAppUnits(aGlyphID
,
4088 uint16_t(appUnitsWidth
));
4092 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
4094 ++gGlyphExtentsSetupFallBackToTight
;
4098 gfxFloat d2a
= appUnitsPerDevUnit
;
4099 aExtents
->SetTightGlyphExtents(
4100 aGlyphID
, gfxRect(bounds
.x
* d2a
, bounds
.y
* d2a
, bounds
.width
* d2a
,
4101 bounds
.height
* d2a
));
4104 // Try to initialize font metrics by reading sfnt tables directly;
4105 // set mIsValid=TRUE and return TRUE on success.
4106 // Return FALSE if the gfxFontEntry subclass does not
4107 // implement GetFontTable(), or for non-sfnt fonts where tables are
4109 // If this returns TRUE without setting the mIsValid flag, then we -did-
4110 // apparently find an sfnt, but it was too broken to be used.
4111 bool gfxFont::InitMetricsFromSfntTables(Metrics
& aMetrics
) {
4112 mIsValid
= false; // font is NOT valid in case of early return
4114 const uint32_t kHheaTableTag
= TRUETYPE_TAG('h', 'h', 'e', 'a');
4115 const uint32_t kOS_2TableTag
= TRUETYPE_TAG('O', 'S', '/', '2');
4119 if (mFUnitsConvFactor
< 0.0) {
4120 // If the conversion factor from FUnits is not yet set,
4121 // get the unitsPerEm from the 'head' table via the font entry
4122 uint16_t unitsPerEm
= GetFontEntry()->UnitsPerEm();
4123 if (unitsPerEm
== gfxFontEntry::kInvalidUPEM
) {
4126 mFUnitsConvFactor
= GetAdjustedSize() / unitsPerEm
;
4129 // 'hhea' table is required for the advanceWidthMax field
4130 gfxFontEntry::AutoTable
hheaTable(mFontEntry
, kHheaTableTag
);
4132 return false; // no 'hhea' table -> not an sfnt
4134 const MetricsHeader
* hhea
=
4135 reinterpret_cast<const MetricsHeader
*>(hb_blob_get_data(hheaTable
, &len
));
4136 if (len
< sizeof(MetricsHeader
)) {
4140 #define SET_UNSIGNED(field, src) \
4141 aMetrics.field = uint16_t(src) * mFUnitsConvFactor
4142 #define SET_SIGNED(field, src) aMetrics.field = int16_t(src) * mFUnitsConvFactor
4144 SET_UNSIGNED(maxAdvance
, hhea
->advanceWidthMax
);
4146 // 'OS/2' table is optional, if not found we'll estimate xHeight
4147 // and aveCharWidth by measuring glyphs
4148 gfxFontEntry::AutoTable
os2Table(mFontEntry
, kOS_2TableTag
);
4150 const OS2Table
* os2
=
4151 reinterpret_cast<const OS2Table
*>(hb_blob_get_data(os2Table
, &len
));
4152 // this should always be present in any valid OS/2 of any version
4153 if (len
>= offsetof(OS2Table
, xAvgCharWidth
) + sizeof(int16_t)) {
4154 SET_SIGNED(aveCharWidth
, os2
->xAvgCharWidth
);
4161 hb_font_t
* hbFont
= gfxHarfBuzzShaper::CreateHBFont(this);
4162 hb_position_t position
;
4164 auto FixedToFloat
= [](hb_position_t f
) -> gfxFloat
{ return f
/ 65536.0; };
4166 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER
,
4168 aMetrics
.maxAscent
= FixedToFloat(position
);
4170 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER
,
4172 aMetrics
.maxDescent
= -FixedToFloat(position
);
4174 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP
,
4176 aMetrics
.externalLeading
= FixedToFloat(position
);
4179 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_UNDERLINE_OFFSET
,
4181 aMetrics
.underlineOffset
= FixedToFloat(position
);
4183 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_UNDERLINE_SIZE
,
4185 aMetrics
.underlineSize
= FixedToFloat(position
);
4187 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_STRIKEOUT_OFFSET
,
4189 aMetrics
.strikeoutOffset
= FixedToFloat(position
);
4191 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_STRIKEOUT_SIZE
,
4193 aMetrics
.strikeoutSize
= FixedToFloat(position
);
4196 // Although sxHeight and sCapHeight are signed fields, we consider
4197 // zero/negative values to be erroneous and just ignore them.
4198 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_X_HEIGHT
,
4201 aMetrics
.xHeight
= FixedToFloat(position
);
4203 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_CAP_HEIGHT
,
4206 aMetrics
.capHeight
= FixedToFloat(position
);
4208 hb_font_destroy(hbFont
);
4215 static double RoundToNearestMultiple(double aValue
, double aFraction
) {
4216 return floor(aValue
/ aFraction
+ 0.5) * aFraction
;
4219 void gfxFont::CalculateDerivedMetrics(Metrics
& aMetrics
) {
4220 aMetrics
.maxAscent
=
4221 ceil(RoundToNearestMultiple(aMetrics
.maxAscent
, 1 / 1024.0));
4222 aMetrics
.maxDescent
=
4223 ceil(RoundToNearestMultiple(aMetrics
.maxDescent
, 1 / 1024.0));
4225 if (aMetrics
.xHeight
<= 0) {
4226 // only happens if we couldn't find either font metrics
4227 // or a char to measure;
4228 // pick an arbitrary value that's better than zero
4229 aMetrics
.xHeight
= aMetrics
.maxAscent
* DEFAULT_XHEIGHT_FACTOR
;
4232 // If we have a font that doesn't provide a capHeight value, use maxAscent
4233 // as a reasonable fallback.
4234 if (aMetrics
.capHeight
<= 0) {
4235 aMetrics
.capHeight
= aMetrics
.maxAscent
;
4238 aMetrics
.maxHeight
= aMetrics
.maxAscent
+ aMetrics
.maxDescent
;
4240 if (aMetrics
.maxHeight
- aMetrics
.emHeight
> 0.0) {
4241 aMetrics
.internalLeading
= aMetrics
.maxHeight
- aMetrics
.emHeight
;
4243 aMetrics
.internalLeading
= 0.0;
4247 aMetrics
.maxAscent
* aMetrics
.emHeight
/ aMetrics
.maxHeight
;
4248 aMetrics
.emDescent
= aMetrics
.emHeight
- aMetrics
.emAscent
;
4250 if (GetFontEntry()->IsFixedPitch()) {
4251 // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger
4252 // advance than the average character width... this forces
4253 // those fonts to be recognized like fixed pitch fonts by layout.
4254 aMetrics
.maxAdvance
= aMetrics
.aveCharWidth
;
4257 if (!aMetrics
.strikeoutOffset
) {
4258 aMetrics
.strikeoutOffset
= aMetrics
.xHeight
* 0.5;
4260 if (!aMetrics
.strikeoutSize
) {
4261 aMetrics
.strikeoutSize
= aMetrics
.underlineSize
;
4265 void gfxFont::SanitizeMetrics(gfxFont::Metrics
* aMetrics
,
4266 bool aIsBadUnderlineFont
) {
4267 // Even if this font size is zero, this font is created with non-zero size.
4268 // However, for layout and others, we should return the metrics of zero size
4270 if (mStyle
.AdjustedSizeMustBeZero()) {
4271 memset(aMetrics
, 0, sizeof(gfxFont::Metrics
));
4275 // If the font entry has ascent/descent/lineGap-override values,
4276 // replace the metrics from the font with the overrides.
4277 gfxFloat adjustedSize
= GetAdjustedSize();
4278 if (mFontEntry
->mAscentOverride
>= 0.0) {
4279 aMetrics
->maxAscent
= mFontEntry
->mAscentOverride
* adjustedSize
;
4280 aMetrics
->maxHeight
= aMetrics
->maxAscent
+ aMetrics
->maxDescent
;
4281 aMetrics
->internalLeading
=
4282 std::max(0.0, aMetrics
->maxHeight
- aMetrics
->emHeight
);
4284 if (mFontEntry
->mDescentOverride
>= 0.0) {
4285 aMetrics
->maxDescent
= mFontEntry
->mDescentOverride
* adjustedSize
;
4286 aMetrics
->maxHeight
= aMetrics
->maxAscent
+ aMetrics
->maxDescent
;
4287 aMetrics
->internalLeading
=
4288 std::max(0.0, aMetrics
->maxHeight
- aMetrics
->emHeight
);
4290 if (mFontEntry
->mLineGapOverride
>= 0.0) {
4291 aMetrics
->externalLeading
= mFontEntry
->mLineGapOverride
* adjustedSize
;
4294 aMetrics
->underlineSize
= std::max(1.0, aMetrics
->underlineSize
);
4295 aMetrics
->strikeoutSize
= std::max(1.0, aMetrics
->strikeoutSize
);
4297 aMetrics
->underlineOffset
= std::min(aMetrics
->underlineOffset
, -1.0);
4299 if (aMetrics
->maxAscent
< 1.0) {
4300 // We cannot draw strikeout line and overline in the ascent...
4301 aMetrics
->underlineSize
= 0;
4302 aMetrics
->underlineOffset
= 0;
4303 aMetrics
->strikeoutSize
= 0;
4304 aMetrics
->strikeoutOffset
= 0;
4309 * Some CJK fonts have bad underline offset. Therefore, if this is such font,
4310 * we need to lower the underline offset to bottom of *em* descent.
4311 * However, if this is system font, we should not do this for the rendering
4312 * compatibility with another application's UI on the platform.
4313 * XXX Should not use this hack if the font size is too small?
4314 * Such text cannot be read, this might be used for tight CSS
4315 * rendering? (E.g., Acid2)
4317 if (!mStyle
.systemFont
&& aIsBadUnderlineFont
) {
4318 // First, we need 2 pixels between baseline and underline at least. Because
4319 // many CJK characters put their glyphs on the baseline, so, 1 pixel is too
4320 // close for CJK characters.
4321 aMetrics
->underlineOffset
= std::min(aMetrics
->underlineOffset
, -2.0);
4323 // Next, we put the underline to bottom of below of the descent space.
4324 if (aMetrics
->internalLeading
+ aMetrics
->externalLeading
>
4325 aMetrics
->underlineSize
) {
4326 aMetrics
->underlineOffset
=
4327 std::min(aMetrics
->underlineOffset
, -aMetrics
->emDescent
);
4329 aMetrics
->underlineOffset
=
4330 std::min(aMetrics
->underlineOffset
,
4331 aMetrics
->underlineSize
- aMetrics
->emDescent
);
4334 // If underline positioned is too far from the text, descent position is
4335 // preferred so that underline will stay within the boundary.
4336 else if (aMetrics
->underlineSize
- aMetrics
->underlineOffset
>
4337 aMetrics
->maxDescent
) {
4338 if (aMetrics
->underlineSize
> aMetrics
->maxDescent
)
4339 aMetrics
->underlineSize
= std::max(aMetrics
->maxDescent
, 1.0);
4340 // The max underlineOffset is 1px (the min underlineSize is 1px, and min
4341 // maxDescent is 0px.)
4342 aMetrics
->underlineOffset
= aMetrics
->underlineSize
- aMetrics
->maxDescent
;
4345 // If strikeout line is overflowed from the ascent, the line should be resized
4346 // and moved for that being in the ascent space. Note that the strikeoutOffset
4347 // is *middle* of the strikeout line position.
4348 gfxFloat halfOfStrikeoutSize
= floor(aMetrics
->strikeoutSize
/ 2.0 + 0.5);
4349 if (halfOfStrikeoutSize
+ aMetrics
->strikeoutOffset
> aMetrics
->maxAscent
) {
4350 if (aMetrics
->strikeoutSize
> aMetrics
->maxAscent
) {
4351 aMetrics
->strikeoutSize
= std::max(aMetrics
->maxAscent
, 1.0);
4352 halfOfStrikeoutSize
= floor(aMetrics
->strikeoutSize
/ 2.0 + 0.5);
4354 gfxFloat ascent
= floor(aMetrics
->maxAscent
+ 0.5);
4355 aMetrics
->strikeoutOffset
= std::max(halfOfStrikeoutSize
, ascent
/ 2.0);
4358 // If overline is larger than the ascent, the line should be resized.
4359 if (aMetrics
->underlineSize
> aMetrics
->maxAscent
) {
4360 aMetrics
->underlineSize
= aMetrics
->maxAscent
;
4364 gfxFont::Baselines
gfxFont::GetBaselines(Orientation aOrientation
) {
4365 // Approximated baselines for fonts lacking actual baseline data. These are
4366 // fractions of the em ascent/descent from the alphabetic baseline.
4367 const double kHangingBaselineDefault
= 0.8; // fraction of ascent
4368 const double kIdeographicBaselineDefault
= -0.5; // fraction of descent
4370 // If no BASE table is present, just return synthetic values immediately.
4371 if (!mFontEntry
->HasFontTable(TRUETYPE_TAG('B', 'A', 'S', 'E'))) {
4372 // No baseline table; just synthesize them immediately.
4373 const Metrics
& metrics
= GetMetrics(aOrientation
);
4376 kHangingBaselineDefault
* metrics
.emAscent
, // hanging
4377 kIdeographicBaselineDefault
* metrics
.emDescent
// ideographic
4381 // Use harfbuzz to try to read the font's baseline metrics.
4382 Baselines result
{NAN
, NAN
, NAN
};
4383 hb_font_t
* hbFont
= gfxHarfBuzzShaper::CreateHBFont(this);
4384 hb_direction_t hbDir
= aOrientation
== nsFontMetrics::eHorizontal
4387 hb_position_t position
;
4389 auto Fix2Float
= [](hb_position_t f
) -> gfxFloat
{ return f
/ 65536.0; };
4390 if (hb_ot_layout_get_baseline(hbFont
, HB_OT_LAYOUT_BASELINE_TAG_ROMAN
, hbDir
,
4391 HB_OT_TAG_DEFAULT_SCRIPT
,
4392 HB_OT_TAG_DEFAULT_LANGUAGE
, &position
)) {
4393 result
.mAlphabetic
= Fix2Float(position
);
4396 if (hb_ot_layout_get_baseline(hbFont
, HB_OT_LAYOUT_BASELINE_TAG_HANGING
,
4397 hbDir
, HB_OT_TAG_DEFAULT_SCRIPT
,
4398 HB_OT_TAG_DEFAULT_LANGUAGE
, &position
)) {
4399 result
.mHanging
= Fix2Float(position
);
4402 if (hb_ot_layout_get_baseline(
4403 hbFont
, HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT
, hbDir
,
4404 HB_OT_TAG_DEFAULT_SCRIPT
, HB_OT_TAG_DEFAULT_LANGUAGE
, &position
)) {
4405 result
.mIdeographic
= Fix2Float(position
);
4408 hb_font_destroy(hbFont
);
4409 // If we successfully read all three, we can return now.
4414 // Synthesize the baselines that we didn't find in the font.
4415 const Metrics
& metrics
= GetMetrics(aOrientation
);
4416 if (std::isnan(result
.mAlphabetic
)) {
4417 result
.mAlphabetic
= 0.0;
4419 if (std::isnan(result
.mHanging
)) {
4420 result
.mHanging
= kHangingBaselineDefault
* metrics
.emAscent
;
4422 if (std::isnan(result
.mIdeographic
)) {
4423 result
.mIdeographic
= kIdeographicBaselineDefault
* metrics
.emDescent
;
4429 // Create a Metrics record to be used for vertical layout. This should never
4430 // fail, as we've already decided this is a valid font. We do not have the
4431 // option of marking it invalid (as can happen if we're unable to read
4432 // horizontal metrics), because that could break a font that we're already
4433 // using for horizontal text.
4434 // So we will synthesize *something* usable here even if there aren't any of the
4435 // usual font tables (which can happen in the case of a legacy bitmap or Type1
4436 // font for which the platform-specific backend used platform APIs instead of
4437 // sfnt tables to create the horizontal metrics).
4438 void gfxFont::CreateVerticalMetrics() {
4439 const uint32_t kHheaTableTag
= TRUETYPE_TAG('h', 'h', 'e', 'a');
4440 const uint32_t kVheaTableTag
= TRUETYPE_TAG('v', 'h', 'e', 'a');
4441 const uint32_t kPostTableTag
= TRUETYPE_TAG('p', 'o', 's', 't');
4442 const uint32_t kOS_2TableTag
= TRUETYPE_TAG('O', 'S', '/', '2');
4445 auto* metrics
= new Metrics();
4446 ::memset(metrics
, 0, sizeof(Metrics
));
4448 // Some basic defaults, in case the font lacks any real metrics tables.
4449 // TODO: consider what rounding (if any) we should apply to these.
4450 metrics
->emHeight
= GetAdjustedSize();
4451 metrics
->emAscent
= metrics
->emHeight
/ 2;
4452 metrics
->emDescent
= metrics
->emHeight
- metrics
->emAscent
;
4454 metrics
->maxAscent
= metrics
->emAscent
;
4455 metrics
->maxDescent
= metrics
->emDescent
;
4457 const float UNINITIALIZED_LEADING
= -10000.0f
;
4458 metrics
->externalLeading
= UNINITIALIZED_LEADING
;
4460 if (mFUnitsConvFactor
< 0.0) {
4461 uint16_t upem
= GetFontEntry()->UnitsPerEm();
4462 if (upem
!= gfxFontEntry::kInvalidUPEM
) {
4463 AutoWriteLock
lock(mLock
);
4464 mFUnitsConvFactor
= GetAdjustedSize() / upem
;
4468 #define SET_UNSIGNED(field, src) \
4469 metrics->field = uint16_t(src) * mFUnitsConvFactor
4470 #define SET_SIGNED(field, src) metrics->field = int16_t(src) * mFUnitsConvFactor
4472 gfxFontEntry::AutoTable
os2Table(mFontEntry
, kOS_2TableTag
);
4473 if (os2Table
&& mFUnitsConvFactor
>= 0.0) {
4474 const OS2Table
* os2
=
4475 reinterpret_cast<const OS2Table
*>(hb_blob_get_data(os2Table
, &len
));
4476 // These fields should always be present in any valid OS/2 table
4477 if (len
>= offsetof(OS2Table
, sTypoLineGap
) + sizeof(int16_t)) {
4478 SET_SIGNED(strikeoutSize
, os2
->yStrikeoutSize
);
4479 // Use ascent+descent from the horizontal metrics as the default
4480 // advance (aveCharWidth) in vertical mode
4481 gfxFloat ascentDescent
=
4482 gfxFloat(mFUnitsConvFactor
) *
4483 (int16_t(os2
->sTypoAscender
) - int16_t(os2
->sTypoDescender
));
4484 metrics
->aveCharWidth
= std::max(metrics
->emHeight
, ascentDescent
);
4485 // Use xAvgCharWidth from horizontal metrics as minimum font extent
4486 // for vertical layout, applying half of it to ascent and half to
4487 // descent (to work with a default centered baseline).
4488 gfxFloat halfCharWidth
=
4489 int16_t(os2
->xAvgCharWidth
) * gfxFloat(mFUnitsConvFactor
) / 2;
4490 metrics
->maxAscent
= std::max(metrics
->maxAscent
, halfCharWidth
);
4491 metrics
->maxDescent
= std::max(metrics
->maxDescent
, halfCharWidth
);
4495 // If we didn't set aveCharWidth from OS/2, try to read 'hhea' metrics
4496 // and use the line height from its ascent/descent.
4497 if (!metrics
->aveCharWidth
) {
4498 gfxFontEntry::AutoTable
hheaTable(mFontEntry
, kHheaTableTag
);
4499 if (hheaTable
&& mFUnitsConvFactor
>= 0.0) {
4500 const MetricsHeader
* hhea
= reinterpret_cast<const MetricsHeader
*>(
4501 hb_blob_get_data(hheaTable
, &len
));
4502 if (len
>= sizeof(MetricsHeader
)) {
4503 SET_SIGNED(aveCharWidth
,
4504 int16_t(hhea
->ascender
) - int16_t(hhea
->descender
));
4505 metrics
->maxAscent
= metrics
->aveCharWidth
/ 2;
4506 metrics
->maxDescent
= metrics
->aveCharWidth
- metrics
->maxAscent
;
4511 // Read real vertical metrics if available.
4512 metrics
->ideographicWidth
= -1.0;
4513 metrics
->zeroWidth
= -1.0;
4514 gfxFontEntry::AutoTable
vheaTable(mFontEntry
, kVheaTableTag
);
4515 if (vheaTable
&& mFUnitsConvFactor
>= 0.0) {
4516 const MetricsHeader
* vhea
= reinterpret_cast<const MetricsHeader
*>(
4517 hb_blob_get_data(vheaTable
, &len
));
4518 if (len
>= sizeof(MetricsHeader
)) {
4519 SET_UNSIGNED(maxAdvance
, vhea
->advanceWidthMax
);
4520 // Redistribute space between ascent/descent because we want a
4521 // centered vertical baseline by default.
4522 gfxFloat halfExtent
=
4523 0.5 * gfxFloat(mFUnitsConvFactor
) *
4524 (int16_t(vhea
->ascender
) + std::abs(int16_t(vhea
->descender
)));
4525 // Some bogus fonts have ascent and descent set to zero in 'vhea'.
4526 // In that case we just ignore them and keep our synthetic values
4528 if (halfExtent
> 0) {
4529 metrics
->maxAscent
= halfExtent
;
4530 metrics
->maxDescent
= halfExtent
;
4531 SET_SIGNED(externalLeading
, vhea
->lineGap
);
4533 // Call gfxHarfBuzzShaper::GetGlyphVAdvance directly, as GetCharAdvance
4534 // would potentially recurse if no v-advance is available and it attempts
4535 // to fall back to a value from mVerticalMetrics.
4536 if (gfxHarfBuzzShaper
* shaper
= GetHarfBuzzShaper()) {
4537 uint32_t gid
= ProvidesGetGlyph()
4538 ? GetGlyph(kWaterIdeograph
, 0)
4539 : shaper
->GetNominalGlyph(kWaterIdeograph
);
4541 int32_t advance
= shaper
->GetGlyphVAdvance(gid
);
4542 // Convert 16.16 fixed-point advance from the shaper to a float.
4543 metrics
->ideographicWidth
=
4544 advance
< 0 ? metrics
->aveCharWidth
: advance
/ 65536.0;
4546 gid
= ProvidesGetGlyph() ? GetGlyph('0', 0)
4547 : shaper
->GetNominalGlyph('0');
4549 int32_t advance
= shaper
->GetGlyphVAdvance(gid
);
4550 metrics
->zeroWidth
=
4551 advance
< 0 ? metrics
->aveCharWidth
: advance
/ 65536.0;
4557 // If we didn't set aveCharWidth above, we must be dealing with a non-sfnt
4558 // font of some kind (Type1, bitmap, vector, ...), so fall back to using
4559 // whatever the platform backend figured out for horizontal layout.
4560 // And if we haven't set externalLeading yet, then copy that from the
4561 // horizontal metrics as well, to help consistency of CSS line-height.
4562 if (!metrics
->aveCharWidth
||
4563 metrics
->externalLeading
== UNINITIALIZED_LEADING
) {
4564 const Metrics
& horizMetrics
= GetHorizontalMetrics();
4565 if (!metrics
->aveCharWidth
) {
4566 metrics
->aveCharWidth
= horizMetrics
.maxAscent
+ horizMetrics
.maxDescent
;
4568 if (metrics
->externalLeading
== UNINITIALIZED_LEADING
) {
4569 metrics
->externalLeading
= horizMetrics
.externalLeading
;
4573 // Get underline thickness from the 'post' table if available.
4574 // We also read the underline position, although in vertical-upright mode
4575 // this will not be appropriate to use directly (see nsTextFrame.cpp).
4576 gfxFontEntry::AutoTable
postTable(mFontEntry
, kPostTableTag
);
4578 const PostTable
* post
=
4579 reinterpret_cast<const PostTable
*>(hb_blob_get_data(postTable
, &len
));
4580 if (len
>= offsetof(PostTable
, underlineThickness
) + sizeof(uint16_t)) {
4581 static_assert(offsetof(PostTable
, underlinePosition
) <
4582 offsetof(PostTable
, underlineThickness
),
4583 "broken PostTable struct?");
4584 SET_SIGNED(underlineOffset
, post
->underlinePosition
);
4585 SET_UNSIGNED(underlineSize
, post
->underlineThickness
);
4586 // Also use for strikeout if we didn't find that in OS/2 above.
4587 if (!metrics
->strikeoutSize
) {
4588 metrics
->strikeoutSize
= metrics
->underlineSize
;
4596 // If we didn't read this from a vhea table, it will still be zero.
4597 // In any case, let's make sure it is not less than the value we've
4598 // come up with for aveCharWidth.
4599 metrics
->maxAdvance
= std::max(metrics
->maxAdvance
, metrics
->aveCharWidth
);
4601 // Thickness of underline and strikeout may have been read from tables,
4602 // but in case they were not present, ensure a minimum of 1 pixel.
4603 metrics
->underlineSize
= std::max(1.0, metrics
->underlineSize
);
4605 metrics
->strikeoutSize
= std::max(1.0, metrics
->strikeoutSize
);
4606 metrics
->strikeoutOffset
= -0.5 * metrics
->strikeoutSize
;
4608 // Somewhat arbitrary values for now, subject to future refinement...
4609 metrics
->spaceWidth
= metrics
->aveCharWidth
;
4610 metrics
->maxHeight
= metrics
->maxAscent
+ metrics
->maxDescent
;
4611 metrics
->xHeight
= metrics
->emHeight
/ 2;
4612 metrics
->capHeight
= metrics
->maxAscent
;
4614 if (metrics
->zeroWidth
< 0.0) {
4615 metrics
->zeroWidth
= metrics
->aveCharWidth
;
4618 if (!mVerticalMetrics
.compareExchange(nullptr, metrics
)) {
4623 gfxFloat
gfxFont::SynthesizeSpaceWidth(uint32_t aCh
) {
4624 // return an appropriate width for various Unicode space characters
4625 // that we "fake" if they're not actually present in the font;
4626 // returns negative value if the char is not a known space.
4628 case 0x2000: // en quad
4630 return GetAdjustedSize() / 2; // en space
4631 case 0x2001: // em quad
4633 return GetAdjustedSize(); // em space
4635 return GetAdjustedSize() / 3; // three-per-em space
4637 return GetAdjustedSize() / 4; // four-per-em space
4639 return GetAdjustedSize() / 6; // six-per-em space
4641 return GetMetrics(nsFontMetrics::eHorizontal
)
4642 .ZeroOrAveCharWidth(); // figure space
4644 return GetMetrics(nsFontMetrics::eHorizontal
)
4645 .spaceWidth
; // punctuation space
4647 return GetAdjustedSize() / 5; // thin space
4649 return GetAdjustedSize() / 10; // hair space
4651 return GetAdjustedSize() / 5; // narrow no-break space
4653 return GetAdjustedSize(); // ideographic space
4659 void gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf
,
4660 FontCacheSizes
* aSizes
) const {
4661 AutoReadLock
lock(mLock
);
4662 for (uint32_t i
= 0; i
< mGlyphExtentsArray
.Length(); ++i
) {
4663 aSizes
->mFontInstances
+=
4664 mGlyphExtentsArray
[i
]->SizeOfIncludingThis(aMallocSizeOf
);
4667 aSizes
->mShapedWords
+=
4668 mWordCache
->shallowSizeOfIncludingThis(aMallocSizeOf
);
4669 for (auto it
= mWordCache
->iter(); !it
.done(); it
.next()) {
4670 aSizes
->mShapedWords
+=
4671 it
.get().value()->SizeOfIncludingThis(aMallocSizeOf
);
4676 void gfxFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf
,
4677 FontCacheSizes
* aSizes
) const {
4678 aSizes
->mFontInstances
+= aMallocSizeOf(this);
4679 AddSizeOfExcludingThis(aMallocSizeOf
, aSizes
);
4682 void gfxFont::AddGlyphChangeObserver(GlyphChangeObserver
* aObserver
) {
4683 AutoWriteLock
lock(mLock
);
4684 if (!mGlyphChangeObservers
) {
4685 mGlyphChangeObservers
= MakeUnique
<nsTHashSet
<GlyphChangeObserver
*>>();
4687 mGlyphChangeObservers
->Insert(aObserver
);
4690 void gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver
* aObserver
) {
4691 AutoWriteLock
lock(mLock
);
4692 NS_ASSERTION(mGlyphChangeObservers
, "No observers registered");
4693 NS_ASSERTION(mGlyphChangeObservers
->Contains(aObserver
),
4694 "Observer not registered");
4695 mGlyphChangeObservers
->Remove(aObserver
);
4698 #define DEFAULT_PIXEL_FONT_SIZE 16.0f
4700 gfxFontStyle::gfxFontStyle()
4701 : size(DEFAULT_PIXEL_FONT_SIZE
),
4703 baselineOffset(0.0f
),
4704 languageOverride(NO_FONT_LANGUAGE_OVERRIDE
),
4705 weight(FontWeight::NORMAL
),
4706 stretch(FontStretch::NORMAL
),
4707 style(FontSlantStyle::NORMAL
),
4708 variantCaps(NS_FONT_VARIANT_CAPS_NORMAL
),
4709 variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL
),
4710 sizeAdjustBasis(uint8_t(FontSizeAdjust::Tag::None
)),
4713 useGrayscaleAntialiasing(false),
4714 allowSyntheticWeight(true),
4715 allowSyntheticStyle(true),
4716 allowSyntheticSmallCaps(true),
4717 useSyntheticPosition(true),
4718 noFallbackVariantFeatures(true) {}
4720 gfxFontStyle::gfxFontStyle(FontSlantStyle aStyle
, FontWeight aWeight
,
4721 FontStretch aStretch
, gfxFloat aSize
,
4722 const FontSizeAdjust
& aSizeAdjust
, bool aSystemFont
,
4723 bool aPrinterFont
, bool aAllowWeightSynthesis
,
4724 bool aAllowStyleSynthesis
,
4725 bool aAllowSmallCapsSynthesis
,
4726 bool aUsePositionSynthesis
,
4727 uint32_t aLanguageOverride
)
4729 baselineOffset(0.0f
),
4730 languageOverride(aLanguageOverride
),
4734 variantCaps(NS_FONT_VARIANT_CAPS_NORMAL
),
4735 variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL
),
4736 systemFont(aSystemFont
),
4737 printerFont(aPrinterFont
),
4738 useGrayscaleAntialiasing(false),
4739 allowSyntheticWeight(aAllowWeightSynthesis
),
4740 allowSyntheticStyle(aAllowStyleSynthesis
),
4741 allowSyntheticSmallCaps(aAllowSmallCapsSynthesis
),
4742 useSyntheticPosition(aUsePositionSynthesis
),
4743 noFallbackVariantFeatures(true) {
4744 MOZ_ASSERT(!std::isnan(size
));
4746 sizeAdjustBasis
= uint8_t(aSizeAdjust
.tag
);
4747 // sizeAdjustBasis is currently a small bitfield, so let's assert that the
4748 // tag value was not truncated.
4749 MOZ_ASSERT(FontSizeAdjust::Tag(sizeAdjustBasis
) == aSizeAdjust
.tag
,
4750 "gfxFontStyle.sizeAdjustBasis too small?");
4752 #define HANDLE_TAG(TAG) \
4753 case FontSizeAdjust::Tag::TAG: \
4754 sizeAdjust = aSizeAdjust.As##TAG(); \
4757 switch (aSizeAdjust
.tag
) {
4758 case FontSizeAdjust::Tag::None
:
4761 HANDLE_TAG(ExHeight
)
4762 HANDLE_TAG(CapHeight
)
4765 HANDLE_TAG(IcHeight
)
4770 MOZ_ASSERT(!std::isnan(sizeAdjust
));
4772 if (weight
> FontWeight::FromInt(1000)) {
4773 weight
= FontWeight::FromInt(1000);
4775 if (weight
< FontWeight::FromInt(1)) {
4776 weight
= FontWeight::FromInt(1);
4779 if (size
>= FONT_MAX_SIZE
) {
4780 size
= FONT_MAX_SIZE
;
4782 sizeAdjustBasis
= uint8_t(FontSizeAdjust::Tag::None
);
4783 } else if (size
< 0.0) {
4784 NS_WARNING("negative font size");
4789 PLDHashNumber
gfxFontStyle::Hash() const {
4790 uint32_t hash
= variationSettings
.IsEmpty()
4792 : mozilla::HashBytes(variationSettings
.Elements(),
4793 variationSettings
.Length() *
4794 sizeof(gfxFontVariation
));
4795 return mozilla::AddToHash(hash
, systemFont
, style
.Raw(), stretch
.Raw(),
4796 weight
.Raw(), size
, int32_t(sizeAdjust
* 1000.0f
));
4799 void gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel
) {
4801 variantSubSuper
!= NS_FONT_VARIANT_POSITION_NORMAL
&& baselineOffset
== 0,
4802 "can't adjust this style for sub/superscript");
4804 // calculate the baseline offset (before changing the size)
4805 if (variantSubSuper
== NS_FONT_VARIANT_POSITION_SUPER
) {
4806 baselineOffset
= size
* -NS_FONT_SUPERSCRIPT_OFFSET_RATIO
;
4808 baselineOffset
= size
* NS_FONT_SUBSCRIPT_OFFSET_RATIO
;
4811 // calculate reduced size, roughly mimicing behavior of font-size: smaller
4812 float cssSize
= size
* aAppUnitsPerDevPixel
/ AppUnitsPerCSSPixel();
4813 if (cssSize
< NS_FONT_SUB_SUPER_SMALL_SIZE
) {
4814 size
*= NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL
;
4815 } else if (cssSize
>= NS_FONT_SUB_SUPER_LARGE_SIZE
) {
4816 size
*= NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE
;
4818 gfxFloat t
= (cssSize
- NS_FONT_SUB_SUPER_SMALL_SIZE
) /
4819 (NS_FONT_SUB_SUPER_LARGE_SIZE
- NS_FONT_SUB_SUPER_SMALL_SIZE
);
4820 size
*= (1.0 - t
) * NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL
+
4821 t
* NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE
;
4824 // clear the variant field
4825 variantSubSuper
= NS_FONT_VARIANT_POSITION_NORMAL
;
4828 bool gfxFont::TryGetMathTable() {
4829 if (mMathInitialized
) {
4830 return !!mMathTable
;
4833 auto face(GetFontEntry()->GetHBFace());
4834 if (hb_ot_math_has_data(face
)) {
4835 auto* mathTable
= new gfxMathTable(face
, GetAdjustedSize());
4836 if (!mMathTable
.compareExchange(nullptr, mathTable
)) {
4840 mMathInitialized
= true;
4842 return !!mMathTable
;