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