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/MathAlgorithms.h"
14 #include "mozilla/StaticPrefs_gfx.h"
15 #include "mozilla/SVGContextPaint.h"
17 #include "mozilla/Logging.h"
21 #include "gfxGlyphExtents.h"
22 #include "gfxPlatform.h"
23 #include "gfxTextRun.h"
24 #include "nsGkAtoms.h"
27 #include "gfxContext.h"
28 #include "gfxFontMissingGlyphs.h"
29 #include "gfxGraphiteShaper.h"
30 #include "gfxHarfBuzzShaper.h"
31 #include "gfxUserFontSet.h"
33 #include "nsSpecialCasingData.h"
34 #include "nsTextRunTransformations.h"
35 #include "nsUGenCategory.h"
36 #include "nsUnicodeProperties.h"
37 #include "nsStyleConsts.h"
38 #include "mozilla/AppUnits.h"
39 #include "mozilla/Likely.h"
40 #include "mozilla/MemoryReporting.h"
41 #include "mozilla/Preferences.h"
42 #include "mozilla/Services.h"
43 #include "mozilla/Telemetry.h"
44 #include "gfxMathTable.h"
45 #include "gfxSVGGlyphs.h"
46 #include "gfx2DGlue.h"
47 #include "TextDrawTarget.h"
49 #include "ThebesRLBox.h"
51 #include "GreekCasing.h"
55 # include "cairo-win32.h"
56 # include "gfxWindowsPlatform.h"
59 #include "harfbuzz/hb.h"
60 #include "harfbuzz/hb-ot.h"
66 using namespace mozilla
;
67 using namespace mozilla::gfx
;
68 using namespace mozilla::unicode
;
69 using mozilla::services::GetObserverService
;
71 gfxFontCache
* gfxFontCache::gGlobalCache
= nullptr;
74 # define DEBUG_TEXT_RUN_STORAGE_METRICS
77 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
78 uint32_t gTextRunStorageHighWaterMark
= 0;
79 uint32_t gTextRunStorage
= 0;
80 uint32_t gFontCount
= 0;
81 uint32_t gGlyphExtentsCount
= 0;
82 uint32_t gGlyphExtentsWidthsTotalSize
= 0;
83 uint32_t gGlyphExtentsSetupEagerSimple
= 0;
84 uint32_t gGlyphExtentsSetupEagerTight
= 0;
85 uint32_t gGlyphExtentsSetupLazyTight
= 0;
86 uint32_t gGlyphExtentsSetupFallBackToTight
= 0;
89 #define LOG_FONTINIT(args) \
90 MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args)
91 #define LOG_FONTINIT_ENABLED() \
92 MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug)
95 * gfxFontCache - global cache of gfxFont instances.
96 * Expires unused fonts after a short interval;
97 * notifies fonts to age their cached shaped-word records;
98 * observes memory-pressure notification and tells fonts to clear their
99 * shaped-word caches to free up memory.
102 MOZ_DEFINE_MALLOC_SIZE_OF(FontCacheMallocSizeOf
)
104 NS_IMPL_ISUPPORTS(gfxFontCache::MemoryReporter
, nsIMemoryReporter
)
107 gfxTextRunFactory::~gfxTextRunFactory() {
108 // Should not be dropped by stylo
109 MOZ_ASSERT(NS_IsMainThread());
113 gfxFontCache::MemoryReporter::CollectReports(
114 nsIHandleReportCallback
* aHandleReport
, nsISupports
* aData
,
116 FontCacheSizes sizes
;
118 gfxFontCache::GetCache()->AddSizeOfIncludingThis(&FontCacheMallocSizeOf
,
121 MOZ_COLLECT_REPORT("explicit/gfx/font-cache", KIND_HEAP
, UNITS_BYTES
,
122 sizes
.mFontInstances
,
123 "Memory used for active font instances.");
125 MOZ_COLLECT_REPORT("explicit/gfx/font-shaped-words", KIND_HEAP
, UNITS_BYTES
,
127 "Memory used to cache shaped glyph data.");
132 NS_IMPL_ISUPPORTS(gfxFontCache::Observer
, nsIObserver
)
135 gfxFontCache::Observer::Observe(nsISupports
* aSubject
, const char* aTopic
,
136 const char16_t
* someData
) {
137 if (!nsCRT::strcmp(aTopic
, "memory-pressure")) {
138 gfxFontCache
* fontCache
= gfxFontCache::GetCache();
140 fontCache
->FlushShapedWordCaches();
143 MOZ_ASSERT_UNREACHABLE("unexpected notification topic");
148 nsresult
gfxFontCache::Init() {
149 NS_ASSERTION(!gGlobalCache
, "Where did this come from?");
150 gGlobalCache
= new gfxFontCache(GetMainThreadSerialEventTarget());
152 return NS_ERROR_OUT_OF_MEMORY
;
154 RegisterStrongMemoryReporter(new MemoryReporter());
158 void gfxFontCache::Shutdown() {
160 gGlobalCache
= nullptr;
162 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
163 printf("Textrun storage high water mark=%d\n", gTextRunStorageHighWaterMark
);
164 printf("Total number of fonts=%d\n", gFontCount
);
165 printf("Total glyph extents allocated=%d (size %d)\n", gGlyphExtentsCount
,
166 int(gGlyphExtentsCount
* sizeof(gfxGlyphExtents
)));
167 printf("Total glyph extents width-storage size allocated=%d\n",
168 gGlyphExtentsWidthsTotalSize
);
169 printf("Number of simple glyph extents eagerly requested=%d\n",
170 gGlyphExtentsSetupEagerSimple
);
171 printf("Number of tight glyph extents eagerly requested=%d\n",
172 gGlyphExtentsSetupEagerTight
);
173 printf("Number of tight glyph extents lazily requested=%d\n",
174 gGlyphExtentsSetupLazyTight
);
175 printf("Number of simple glyph extent setups that fell back to tight=%d\n",
176 gGlyphExtentsSetupFallBackToTight
);
180 gfxFontCache::gfxFontCache(nsIEventTarget
* aEventTarget
)
181 : gfxFontCacheExpirationTracker(aEventTarget
) {
182 nsCOMPtr
<nsIObserverService
> obs
= GetObserverService();
184 obs
->AddObserver(new Observer
, "memory-pressure", false);
187 nsIEventTarget
* target
= nullptr;
188 if (XRE_IsContentProcess() && NS_IsMainThread()) {
189 target
= aEventTarget
;
191 NS_NewTimerWithFuncCallback(getter_AddRefs(mWordCacheExpirationTimer
),
192 WordCacheExpirationTimerCallback
, this,
193 SHAPED_WORD_TIMEOUT_SECONDS
* 1000,
194 nsITimer::TYPE_REPEATING_SLACK
,
195 "gfxFontCache::gfxFontCache", target
);
198 gfxFontCache::~gfxFontCache() {
199 // Ensure the user font cache releases its references to font entries,
200 // so they aren't kept alive after the font instances and font-list
201 // have been shut down.
202 gfxUserFontSet::UserFontCache::Shutdown();
204 if (mWordCacheExpirationTimer
) {
205 mWordCacheExpirationTimer
->Cancel();
206 mWordCacheExpirationTimer
= nullptr;
209 // Expire everything that has a zero refcount, so we don't leak them.
211 // All fonts should be gone.
212 NS_WARNING_ASSERTION(mFonts
.Count() == 0,
213 "Fonts still alive while shutting down gfxFontCache");
214 // Note that we have to delete everything through the expiration
215 // tracker, since there might be fonts not in the hashtable but in
219 bool gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey
) const {
220 const gfxCharacterMap
* fontUnicodeRangeMap
= mFont
->GetUnicodeRangeMap();
221 return aKey
->mFontEntry
== mFont
->GetFontEntry() &&
222 aKey
->mStyle
->Equals(*mFont
->GetStyle()) &&
223 ((!aKey
->mUnicodeRangeMap
&& !fontUnicodeRangeMap
) ||
224 (aKey
->mUnicodeRangeMap
&& fontUnicodeRangeMap
&&
225 aKey
->mUnicodeRangeMap
->Equals(fontUnicodeRangeMap
)));
228 gfxFont
* gfxFontCache::Lookup(const gfxFontEntry
* aFontEntry
,
229 const gfxFontStyle
* aStyle
,
230 const gfxCharacterMap
* aUnicodeRangeMap
) {
231 Key
key(aFontEntry
, aStyle
, aUnicodeRangeMap
);
232 HashEntry
* entry
= mFonts
.GetEntry(key
);
234 Telemetry::Accumulate(Telemetry::FONT_CACHE_HIT
, entry
!= nullptr);
235 if (!entry
) return nullptr;
240 void gfxFontCache::AddNew(gfxFont
* aFont
) {
241 Key
key(aFont
->GetFontEntry(), aFont
->GetStyle(),
242 aFont
->GetUnicodeRangeMap());
243 HashEntry
* entry
= mFonts
.PutEntry(key
);
245 gfxFont
* oldFont
= entry
->mFont
;
246 entry
->mFont
= aFont
;
247 // Assert that we can find the entry we just put in (this fails if the key
248 // has a NaN float value in it, e.g. 'sizeAdjust').
249 MOZ_ASSERT(entry
== mFonts
.GetEntry(key
));
250 // If someone's asked us to replace an existing font entry, then that's a
251 // bit weird, but let it happen, and expire the old font if it's not used.
252 if (oldFont
&& oldFont
->GetExpirationState()->IsTracked()) {
253 // if oldFont == aFont, recount should be > 0,
254 // so we shouldn't be here.
255 NS_ASSERTION(aFont
!= oldFont
, "new font is tracked for expiry!");
256 NotifyExpired(oldFont
);
260 void gfxFontCache::NotifyReleased(gfxFont
* aFont
) {
261 nsresult rv
= AddObject(aFont
);
263 // We couldn't track it for some reason. Kill it now.
266 // Note that we might have fonts that aren't in the hashtable, perhaps because
267 // of OOM adding to the hashtable or because someone did an AddNew where
268 // we already had a font. These fonts are added to the expiration tracker
269 // anyway, even though Lookup can't resurrect them. Eventually they will
270 // expire and be deleted.
273 void gfxFontCache::NotifyExpired(gfxFont
* aFont
) {
274 aFont
->ClearCachedWords();
279 void gfxFontCache::DestroyFont(gfxFont
* aFont
) {
280 Key
key(aFont
->GetFontEntry(), aFont
->GetStyle(),
281 aFont
->GetUnicodeRangeMap());
282 HashEntry
* entry
= mFonts
.GetEntry(key
);
283 if (entry
&& entry
->mFont
== aFont
) {
284 mFonts
.RemoveEntry(entry
);
286 NS_ASSERTION(aFont
->GetRefCount() == 0,
287 "Destroying with non-zero ref count!");
292 void gfxFontCache::WordCacheExpirationTimerCallback(nsITimer
* aTimer
,
294 gfxFontCache
* cache
= static_cast<gfxFontCache
*>(aCache
);
295 for (const auto& entry
: cache
->mFonts
) {
296 entry
.mFont
->AgeCachedWords();
300 void gfxFontCache::FlushShapedWordCaches() {
301 for (const auto& entry
: mFonts
) {
302 entry
.mFont
->ClearCachedWords();
306 void gfxFontCache::NotifyGlyphsChanged() {
307 for (const auto& entry
: mFonts
) {
308 entry
.mFont
->NotifyGlyphsChanged();
312 void gfxFontCache::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf
,
313 FontCacheSizes
* aSizes
) const {
314 // TODO: add the overhead of the expiration tracker (generation arrays)
316 aSizes
->mFontInstances
+= mFonts
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
317 for (const auto& entry
: mFonts
) {
318 entry
.mFont
->AddSizeOfExcludingThis(aMallocSizeOf
, aSizes
);
322 void gfxFontCache::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf
,
323 FontCacheSizes
* aSizes
) const {
324 aSizes
->mFontInstances
+= aMallocSizeOf(this);
325 AddSizeOfExcludingThis(aMallocSizeOf
, aSizes
);
328 #define MAX_SSXX_VALUE 99
329 #define MAX_CVXX_VALUE 99
331 static void LookupAlternateValues(const gfxFontFeatureValueSet
& aFeatureLookup
,
332 const nsACString
& aFamily
,
333 const StyleVariantAlternates
& aAlternates
,
334 nsTArray
<gfxFontFeature
>& aFontFeatures
) {
335 using Tag
= StyleVariantAlternates::Tag
;
337 // historical-forms gets handled in nsFont::AddFontFeaturesToStyle.
338 if (aAlternates
.IsHistoricalForms()) {
342 gfxFontFeature feature
;
343 if (aAlternates
.IsCharacterVariant()) {
344 for (auto& ident
: aAlternates
.AsCharacterVariant().AsSpan()) {
345 Span
<const uint32_t> values
= aFeatureLookup
.GetFontFeatureValuesFor(
346 aFamily
, NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT
,
348 // nothing defined, skip
349 if (values
.IsEmpty()) {
352 NS_ASSERTION(values
.Length() <= 2,
353 "too many values allowed for character-variant");
354 // character-variant(12 3) ==> 'cv12' = 3
355 uint32_t nn
= values
[0];
356 // ignore values greater than 99
357 if (nn
== 0 || nn
> MAX_CVXX_VALUE
) {
360 feature
.mValue
= values
.Length() > 1 ? values
[1] : 1;
361 feature
.mTag
= HB_TAG('c', 'v', ('0' + nn
/ 10), ('0' + nn
% 10));
362 aFontFeatures
.AppendElement(feature
);
367 if (aAlternates
.IsStyleset()) {
368 for (auto& ident
: aAlternates
.AsStyleset().AsSpan()) {
369 Span
<const uint32_t> values
= aFeatureLookup
.GetFontFeatureValuesFor(
370 aFamily
, NS_FONT_VARIANT_ALTERNATES_STYLESET
, ident
.AsAtom());
372 // styleset(1 2 7) ==> 'ss01' = 1, 'ss02' = 1, 'ss07' = 1
374 for (uint32_t nn
: values
) {
375 if (nn
== 0 || nn
> MAX_SSXX_VALUE
) {
378 feature
.mTag
= HB_TAG('s', 's', ('0' + nn
/ 10), ('0' + nn
% 10));
379 aFontFeatures
.AppendElement(feature
);
385 uint32_t constant
= 0;
386 nsAtom
* name
= nullptr;
387 switch (aAlternates
.tag
) {
389 constant
= NS_FONT_VARIANT_ALTERNATES_SWASH
;
390 name
= aAlternates
.AsSwash().AsAtom();
393 constant
= NS_FONT_VARIANT_ALTERNATES_STYLISTIC
;
394 name
= aAlternates
.AsStylistic().AsAtom();
397 constant
= NS_FONT_VARIANT_ALTERNATES_ORNAMENTS
;
398 name
= aAlternates
.AsOrnaments().AsAtom();
400 case Tag::Annotation
:
401 constant
= NS_FONT_VARIANT_ALTERNATES_ANNOTATION
;
402 name
= aAlternates
.AsAnnotation().AsAtom();
405 MOZ_ASSERT_UNREACHABLE("Unknown font-variant-alternates value!");
409 Span
<const uint32_t> values
=
410 aFeatureLookup
.GetFontFeatureValuesFor(aFamily
, constant
, name
);
411 if (values
.IsEmpty()) {
414 MOZ_ASSERT(values
.Length() == 1,
415 "too many values for font-specific font-variant-alternates");
417 feature
.mValue
= values
[0];
418 switch (aAlternates
.tag
) {
419 case Tag::Swash
: // swsh, cswh
420 feature
.mTag
= HB_TAG('s', 'w', 's', 'h');
421 aFontFeatures
.AppendElement(feature
);
422 feature
.mTag
= HB_TAG('c', 's', 'w', 'h');
424 case Tag::Stylistic
: // salt
425 feature
.mTag
= HB_TAG('s', 'a', 'l', 't');
427 case Tag::Ornaments
: // ornm
428 feature
.mTag
= HB_TAG('o', 'r', 'n', 'm');
430 case Tag::Annotation
: // nalt
431 feature
.mTag
= HB_TAG('n', 'a', 'l', 't');
434 MOZ_ASSERT_UNREACHABLE("how?");
437 aFontFeatures
.AppendElement(feature
);
441 void gfxFontShaper::MergeFontFeatures(
442 const gfxFontStyle
* aStyle
, const nsTArray
<gfxFontFeature
>& aFontFeatures
,
443 bool aDisableLigatures
, const nsACString
& aFamilyName
, bool aAddSmallCaps
,
444 void (*aHandleFeature
)(const uint32_t&, uint32_t&, void*),
445 void* aHandleFeatureData
) {
446 const nsTArray
<gfxFontFeature
>& styleRuleFeatures
= aStyle
->featureSettings
;
448 // Bail immediately if nothing to do, which is the common case.
449 if (styleRuleFeatures
.IsEmpty() && aFontFeatures
.IsEmpty() &&
450 !aDisableLigatures
&&
451 aStyle
->variantCaps
== NS_FONT_VARIANT_CAPS_NORMAL
&&
452 aStyle
->variantSubSuper
== NS_FONT_VARIANT_POSITION_NORMAL
&&
453 aStyle
->variantAlternates
.IsEmpty()) {
457 nsTHashMap
<nsUint32HashKey
, uint32_t> mergedFeatures
;
459 // add feature values from font
460 for (const gfxFontFeature
& feature
: aFontFeatures
) {
461 mergedFeatures
.InsertOrUpdate(feature
.mTag
, feature
.mValue
);
464 // font-variant-caps - handled here due to the need for fallback handling
465 // petite caps cases can fallback to appropriate smallcaps
466 uint32_t variantCaps
= aStyle
->variantCaps
;
467 switch (variantCaps
) {
468 case NS_FONT_VARIANT_CAPS_NORMAL
:
471 case NS_FONT_VARIANT_CAPS_ALLSMALL
:
472 mergedFeatures
.InsertOrUpdate(HB_TAG('c', '2', 's', 'c'), 1);
473 // fall through to the small-caps case
476 case NS_FONT_VARIANT_CAPS_SMALLCAPS
:
477 mergedFeatures
.InsertOrUpdate(HB_TAG('s', 'm', 'c', 'p'), 1);
480 case NS_FONT_VARIANT_CAPS_ALLPETITE
:
481 mergedFeatures
.InsertOrUpdate(aAddSmallCaps
? HB_TAG('c', '2', 's', 'c')
482 : HB_TAG('c', '2', 'p', 'c'),
484 // fall through to the petite-caps case
487 case NS_FONT_VARIANT_CAPS_PETITECAPS
:
488 mergedFeatures
.InsertOrUpdate(aAddSmallCaps
? HB_TAG('s', 'm', 'c', 'p')
489 : HB_TAG('p', 'c', 'a', 'p'),
493 case NS_FONT_VARIANT_CAPS_TITLING
:
494 mergedFeatures
.InsertOrUpdate(HB_TAG('t', 'i', 't', 'l'), 1);
497 case NS_FONT_VARIANT_CAPS_UNICASE
:
498 mergedFeatures
.InsertOrUpdate(HB_TAG('u', 'n', 'i', 'c'), 1);
502 MOZ_ASSERT_UNREACHABLE("Unexpected variantCaps");
506 // font-variant-position - handled here due to the need for fallback
507 switch (aStyle
->variantSubSuper
) {
508 case NS_FONT_VARIANT_POSITION_NORMAL
:
510 case NS_FONT_VARIANT_POSITION_SUPER
:
511 mergedFeatures
.InsertOrUpdate(HB_TAG('s', 'u', 'p', 's'), 1);
513 case NS_FONT_VARIANT_POSITION_SUB
:
514 mergedFeatures
.InsertOrUpdate(HB_TAG('s', 'u', 'b', 's'), 1);
517 MOZ_ASSERT_UNREACHABLE("Unexpected variantSubSuper");
521 // add font-specific feature values from style rules
522 if (aStyle
->featureValueLookup
&& !aStyle
->variantAlternates
.IsEmpty()) {
523 AutoTArray
<gfxFontFeature
, 4> featureList
;
525 // insert list of alternate feature settings
526 for (auto& alternate
: aStyle
->variantAlternates
.AsSpan()) {
527 LookupAlternateValues(*aStyle
->featureValueLookup
, aFamilyName
, alternate
,
531 for (const gfxFontFeature
& feature
: featureList
) {
532 mergedFeatures
.InsertOrUpdate(feature
.mTag
, feature
.mValue
);
536 // Add features that are already resolved to tags & values in the style.
537 if (styleRuleFeatures
.IsEmpty()) {
538 // Disable common ligatures if non-zero letter-spacing is in effect.
539 if (aDisableLigatures
) {
540 mergedFeatures
.InsertOrUpdate(HB_TAG('l', 'i', 'g', 'a'), 0);
541 mergedFeatures
.InsertOrUpdate(HB_TAG('c', 'l', 'i', 'g'), 0);
544 for (const gfxFontFeature
& feature
: styleRuleFeatures
) {
545 // A dummy feature (0,0) is used as a sentinel to separate features
546 // originating from font-variant-* or other high-level properties from
547 // those directly specified as font-feature-settings. The high-level
548 // features may be overridden by aDisableLigatures, while low-level
549 // features specified directly as tags will come last and therefore
550 // take precedence over everything else.
552 mergedFeatures
.InsertOrUpdate(feature
.mTag
, feature
.mValue
);
553 } else if (aDisableLigatures
) {
554 // Handle ligature-disabling setting at the boundary between high-
555 // and low-level features.
556 mergedFeatures
.InsertOrUpdate(HB_TAG('l', 'i', 'g', 'a'), 0);
557 mergedFeatures
.InsertOrUpdate(HB_TAG('c', 'l', 'i', 'g'), 0);
562 if (mergedFeatures
.Count() != 0) {
563 for (auto iter
= mergedFeatures
.Iter(); !iter
.Done(); iter
.Next()) {
564 aHandleFeature(iter
.Key(), iter
.Data(), aHandleFeatureData
);
569 void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset
,
570 const char16_t
* aString
,
572 CompressedGlyph
* glyphs
= GetCharacterGlyphs() + aOffset
;
574 CompressedGlyph extendCluster
= CompressedGlyph::MakeComplex(false, true);
576 ClusterIterator
iter(aString
, aLength
);
578 // the ClusterIterator won't be able to tell us if the string
579 // _begins_ with a cluster-extender, so we handle that here
581 uint32_t ch
= *aString
;
582 if (aLength
> 1 && NS_IS_SURROGATE_PAIR(ch
, aString
[1])) {
583 ch
= SURROGATE_TO_UCS4(ch
, aString
[1]);
585 if (IsClusterExtender(ch
)) {
586 *glyphs
= extendCluster
;
590 const char16_t kIdeographicSpace
= 0x3000;
591 while (!iter
.AtEnd()) {
592 if (*iter
== char16_t(' ') || *iter
== kIdeographicSpace
) {
593 glyphs
->SetIsSpace();
595 // advance iter to the next cluster-start (or end of text)
597 // step past the first char of the cluster
600 // mark all the rest as cluster-continuations
601 while (aString
< iter
) {
602 *glyphs
= extendCluster
;
609 void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset
,
610 const uint8_t* aString
,
612 CompressedGlyph
* glyphs
= GetCharacterGlyphs() + aOffset
;
613 const uint8_t* limit
= aString
+ aLength
;
615 while (aString
< limit
) {
616 if (*aString
== uint8_t(' ')) {
617 glyphs
->SetIsSpace();
624 gfxShapedText::DetailedGlyph
* gfxShapedText::AllocateDetailedGlyphs(
625 uint32_t aIndex
, uint32_t aCount
) {
626 NS_ASSERTION(aIndex
< GetLength(), "Index out of range");
628 if (!mDetailedGlyphs
) {
629 mDetailedGlyphs
= MakeUnique
<DetailedGlyphStore
>();
632 return mDetailedGlyphs
->Allocate(aIndex
, aCount
);
635 void gfxShapedText::SetDetailedGlyphs(uint32_t aIndex
, uint32_t aGlyphCount
,
636 const DetailedGlyph
* aGlyphs
) {
637 CompressedGlyph
& g
= GetCharacterGlyphs()[aIndex
];
639 MOZ_ASSERT(aIndex
> 0 || g
.IsLigatureGroupStart(),
640 "First character can't be a ligature continuation!");
642 if (aGlyphCount
> 0) {
643 DetailedGlyph
* details
= AllocateDetailedGlyphs(aIndex
, aGlyphCount
);
644 memcpy(details
, aGlyphs
, sizeof(DetailedGlyph
) * aGlyphCount
);
647 g
.SetGlyphCount(aGlyphCount
);
652 static inline bool IsIgnorable(uint32_t aChar
) {
653 return (IsDefaultIgnorable(aChar
)) || aChar
== ZWNJ
|| aChar
== ZWJ
;
656 void gfxShapedText::SetMissingGlyph(uint32_t aIndex
, uint32_t aChar
,
658 CompressedGlyph
& g
= GetCharacterGlyphs()[aIndex
];
659 uint8_t category
= GetGeneralCategory(aChar
);
660 if (category
>= HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK
&&
661 category
<= HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK
) {
662 g
.SetComplex(false, true);
665 // Leaving advance as zero will prevent drawing the hexbox for ignorables.
667 if (!IsIgnorable(aChar
)) {
669 std::max(aFont
->GetMetrics(nsFontMetrics::eHorizontal
).aveCharWidth
,
670 gfxFloat(gfxFontMissingGlyphs::GetDesiredMinWidth(
671 aChar
, mAppUnitsPerDevUnit
)));
672 advance
= int32_t(width
* mAppUnitsPerDevUnit
);
674 DetailedGlyph detail
= {aChar
, advance
, gfx::Point()};
675 SetDetailedGlyphs(aIndex
, 1, &detail
);
679 bool gfxShapedText::FilterIfIgnorable(uint32_t aIndex
, uint32_t aCh
) {
680 if (IsIgnorable(aCh
)) {
681 // There are a few default-ignorables of Letter category (currently,
682 // just the Hangul filler characters) that we'd better not discard
683 // if they're followed by additional characters in the same cluster.
684 // Some fonts use them to carry the width of a whole cluster of
685 // combining jamos; see bug 1238243.
686 auto* charGlyphs
= GetCharacterGlyphs();
687 if (GetGenCategory(aCh
) == nsUGenCategory::kLetter
&&
688 aIndex
+ 1 < GetLength() && !charGlyphs
[aIndex
+ 1].IsClusterStart()) {
691 // A compressedGlyph that is set to MISSING but has no DetailedGlyphs list
692 // will be zero-width/invisible, which is what we want here.
693 CompressedGlyph
& g
= charGlyphs
[aIndex
];
694 g
.SetComplex(g
.IsClusterStart(), g
.IsLigatureGroupStart()).SetMissing();
700 void gfxShapedText::AdjustAdvancesForSyntheticBold(float aSynBoldOffset
,
703 uint32_t synAppUnitOffset
= aSynBoldOffset
* mAppUnitsPerDevUnit
;
704 CompressedGlyph
* charGlyphs
= GetCharacterGlyphs();
705 for (uint32_t i
= aOffset
; i
< aOffset
+ aLength
; ++i
) {
706 CompressedGlyph
* glyphData
= charGlyphs
+ i
;
707 if (glyphData
->IsSimpleGlyph()) {
708 // simple glyphs ==> just add the advance
709 int32_t advance
= glyphData
->GetSimpleAdvance();
711 advance
+= synAppUnitOffset
;
712 if (CompressedGlyph::IsSimpleAdvance(advance
)) {
713 glyphData
->SetSimpleGlyph(advance
, glyphData
->GetSimpleGlyph());
715 // rare case, tested by making this the default
716 uint32_t glyphIndex
= glyphData
->GetSimpleGlyph();
717 // convert the simple CompressedGlyph to an empty complex record
718 glyphData
->SetComplex(true, true);
719 // then set its details (glyph ID with its new advance)
720 DetailedGlyph detail
= {glyphIndex
, advance
, gfx::Point()};
721 SetDetailedGlyphs(i
, 1, &detail
);
725 // complex glyphs ==> add offset at cluster/ligature boundaries
726 uint32_t detailedLength
= glyphData
->GetGlyphCount();
727 if (detailedLength
) {
728 DetailedGlyph
* details
= GetDetailedGlyphs(i
);
732 if (IsRightToLeft()) {
733 if (details
[0].mAdvance
> 0) {
734 details
[0].mAdvance
+= synAppUnitOffset
;
737 if (details
[detailedLength
- 1].mAdvance
> 0) {
738 details
[detailedLength
- 1].mAdvance
+= synAppUnitOffset
;
746 float gfxFont::AngleForSyntheticOblique() const {
747 // If the style doesn't call for italic/oblique, or if the face already
748 // provides it, no synthetic style should be added.
749 if (mStyle
.style
== FontSlantStyle::Normal() || !mStyle
.allowSyntheticStyle
||
750 !mFontEntry
->IsUpright()) {
754 // If style calls for italic, and face doesn't support it, use default
755 // oblique angle as a simulation.
756 if (mStyle
.style
.IsItalic()) {
757 return mFontEntry
->SupportsItalic() ? 0.0f
: FontSlantStyle::kDefaultAngle
;
760 // Default or custom oblique angle
761 return mStyle
.style
.ObliqueAngle();
764 float gfxFont::SkewForSyntheticOblique() const {
765 // Precomputed value of tan(kDefaultAngle), the default italic/oblique slant;
766 // avoids calling tan() at runtime except for custom oblique values.
767 static const float kTanDefaultAngle
=
768 tan(FontSlantStyle::kDefaultAngle
* (M_PI
/ 180.0));
770 float angle
= AngleForSyntheticOblique();
773 } else if (angle
== FontSlantStyle::kDefaultAngle
) {
774 return kTanDefaultAngle
;
776 return tan(angle
* (M_PI
/ 180.0));
780 void gfxFont::RunMetrics::CombineWith(const RunMetrics
& aOther
,
781 bool aOtherIsOnLeft
) {
782 mAscent
= std::max(mAscent
, aOther
.mAscent
);
783 mDescent
= std::max(mDescent
, aOther
.mDescent
);
784 if (aOtherIsOnLeft
) {
785 mBoundingBox
= (mBoundingBox
+ gfxPoint(aOther
.mAdvanceWidth
, 0))
786 .Union(aOther
.mBoundingBox
);
789 mBoundingBox
.Union(aOther
.mBoundingBox
+ gfxPoint(mAdvanceWidth
, 0));
791 mAdvanceWidth
+= aOther
.mAdvanceWidth
;
794 gfxFont::gfxFont(const RefPtr
<UnscaledFont
>& aUnscaledFont
,
795 gfxFontEntry
* aFontEntry
, const gfxFontStyle
* aFontStyle
,
796 AntialiasOption anAAOption
)
797 : mFontEntry(aFontEntry
),
798 mUnscaledFont(aUnscaledFont
),
800 mAdjustedSize(-1.0), // negative to indicate "not yet initialized"
801 mFUnitsConvFactor(-1.0f
), // negative to indicate "not yet initialized"
802 mAntialiasOption(anAAOption
),
804 mApplySyntheticBold(false),
805 mKerningEnabled(false),
806 mMathInitialized(false) {
807 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
811 if (MOZ_UNLIKELY(StaticPrefs::gfx_text_disable_aa_AtStartup())) {
812 mAntialiasOption
= kAntialiasNone
;
815 // Turn off AA for Ahem for testing purposes when requested.
816 if (MOZ_UNLIKELY(StaticPrefs::gfx_font_rendering_ahem_antialias_none() &&
817 mFontEntry
->FamilyName().EqualsLiteral("Ahem"))) {
818 mAntialiasOption
= kAntialiasNone
;
821 mKerningSet
= HasFeatureSet(HB_TAG('k', 'e', 'r', 'n'), mKerningEnabled
);
824 gfxFont::~gfxFont() {
825 mFontEntry
->NotifyFontDestroyed(this);
827 if (mGlyphChangeObservers
) {
828 for (const auto& key
: *mGlyphChangeObservers
) {
834 // Work out whether cairo will snap inter-glyph spacing to pixels.
836 // Layout does not align text to pixel boundaries, so, with font drawing
837 // backends that snap glyph positions to pixels, it is important that
838 // inter-glyph spacing within words is always an integer number of pixels.
839 // This ensures that the drawing backend snaps all of the word's glyphs in the
840 // same direction and so inter-glyph spacing remains the same.
842 gfxFont::RoundingFlags
gfxFont::GetRoundOffsetsToPixels(
843 DrawTarget
* aDrawTarget
) {
844 // Could do something fancy here for ScaleFactors of
845 // AxisAlignedTransforms, but we leave things simple.
846 // Not much point rounding if a matrix will mess things up anyway.
847 // Also check if the font already knows hint metrics is off...
848 if (aDrawTarget
->GetTransform().HasNonTranslation() || !ShouldHintMetrics()) {
849 return RoundingFlags(0);
852 cairo_t
* cr
= static_cast<cairo_t
*>(
853 aDrawTarget
->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT
));
855 cairo_surface_t
* target
= cairo_get_target(cr
);
857 // Check whether the cairo surface's font options hint metrics.
858 cairo_font_options_t
* fontOptions
= cairo_font_options_create();
859 cairo_surface_get_font_options(target
, fontOptions
);
860 cairo_hint_metrics_t hintMetrics
=
861 cairo_font_options_get_hint_metrics(fontOptions
);
862 cairo_font_options_destroy(fontOptions
);
864 switch (hintMetrics
) {
865 case CAIRO_HINT_METRICS_OFF
:
866 return RoundingFlags(0);
867 case CAIRO_HINT_METRICS_ON
:
868 return RoundingFlags::kRoundX
| RoundingFlags::kRoundY
;
874 if (ShouldRoundXOffset(cr
)) {
875 return RoundingFlags::kRoundX
| RoundingFlags::kRoundY
;
877 return RoundingFlags::kRoundY
;
881 gfxFloat
gfxFont::GetGlyphAdvance(uint16_t aGID
, bool aVertical
) {
882 if (!aVertical
&& ProvidesGlyphWidths()) {
883 return GetGlyphWidth(aGID
) / 65536.0;
885 if (mFUnitsConvFactor
< 0.0f
) {
886 GetMetrics(nsFontMetrics::eHorizontal
);
888 NS_ASSERTION(mFUnitsConvFactor
>= 0.0f
,
889 "missing font unit conversion factor");
890 if (!mHarfBuzzShaper
) {
891 mHarfBuzzShaper
= MakeUnique
<gfxHarfBuzzShaper
>(this);
893 gfxHarfBuzzShaper
* shaper
=
894 static_cast<gfxHarfBuzzShaper
*>(mHarfBuzzShaper
.get());
895 if (!shaper
->Initialize()) {
898 return (aVertical
? shaper
->GetGlyphVAdvance(aGID
)
899 : shaper
->GetGlyphHAdvance(aGID
)) /
903 gfxFloat
gfxFont::GetCharAdvance(uint32_t aUnicode
, bool aVertical
) {
905 if (ProvidesGetGlyph()) {
906 gid
= GetGlyph(aUnicode
, 0);
908 if (!mHarfBuzzShaper
) {
909 mHarfBuzzShaper
= MakeUnique
<gfxHarfBuzzShaper
>(this);
911 gfxHarfBuzzShaper
* shaper
=
912 static_cast<gfxHarfBuzzShaper
*>(mHarfBuzzShaper
.get());
913 if (!shaper
->Initialize()) {
916 gid
= shaper
->GetNominalGlyph(aUnicode
);
921 return GetGlyphAdvance(gid
, aVertical
);
924 static void CollectLookupsByFeature(hb_face_t
* aFace
, hb_tag_t aTableTag
,
925 uint32_t aFeatureIndex
,
926 hb_set_t
* aLookups
) {
927 uint32_t lookups
[32];
928 uint32_t i
, len
, offset
;
932 len
= ArrayLength(lookups
);
933 hb_ot_layout_feature_get_lookups(aFace
, aTableTag
, aFeatureIndex
, offset
,
935 for (i
= 0; i
< len
; i
++) {
936 hb_set_add(aLookups
, lookups
[i
]);
939 } while (len
== ArrayLength(lookups
));
942 static void CollectLookupsByLanguage(
943 hb_face_t
* aFace
, hb_tag_t aTableTag
,
944 const nsTHashSet
<uint32_t>& aSpecificFeatures
, hb_set_t
* aOtherLookups
,
945 hb_set_t
* aSpecificFeatureLookups
, uint32_t aScriptIndex
,
946 uint32_t aLangIndex
) {
947 uint32_t reqFeatureIndex
;
948 if (hb_ot_layout_language_get_required_feature_index(
949 aFace
, aTableTag
, aScriptIndex
, aLangIndex
, &reqFeatureIndex
)) {
950 CollectLookupsByFeature(aFace
, aTableTag
, reqFeatureIndex
, aOtherLookups
);
953 uint32_t featureIndexes
[32];
954 uint32_t i
, len
, offset
;
958 len
= ArrayLength(featureIndexes
);
959 hb_ot_layout_language_get_feature_indexes(aFace
, aTableTag
, aScriptIndex
,
960 aLangIndex
, offset
, &len
,
963 for (i
= 0; i
< len
; i
++) {
964 uint32_t featureIndex
= featureIndexes
[i
];
966 // get the feature tag
969 hb_ot_layout_language_get_feature_tags(aFace
, aTableTag
, aScriptIndex
,
970 aLangIndex
, offset
+ i
, &tagLen
,
974 hb_set_t
* lookups
= aSpecificFeatures
.Contains(featureTag
)
975 ? aSpecificFeatureLookups
977 CollectLookupsByFeature(aFace
, aTableTag
, featureIndex
, lookups
);
980 } while (len
== ArrayLength(featureIndexes
));
983 static bool HasLookupRuleWithGlyphByScript(
984 hb_face_t
* aFace
, hb_tag_t aTableTag
, hb_tag_t aScriptTag
,
985 uint32_t aScriptIndex
, uint16_t aGlyph
,
986 const nsTHashSet
<uint32_t>& aDefaultFeatures
,
987 bool& aHasDefaultFeatureWithGlyph
) {
988 uint32_t numLangs
, lang
;
989 hb_set_t
* defaultFeatureLookups
= hb_set_create();
990 hb_set_t
* nonDefaultFeatureLookups
= hb_set_create();
993 CollectLookupsByLanguage(aFace
, aTableTag
, aDefaultFeatures
,
994 nonDefaultFeatureLookups
, defaultFeatureLookups
,
995 aScriptIndex
, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX
);
997 // iterate over langs
998 numLangs
= hb_ot_layout_script_get_language_tags(
999 aFace
, aTableTag
, aScriptIndex
, 0, nullptr, nullptr);
1000 for (lang
= 0; lang
< numLangs
; lang
++) {
1001 CollectLookupsByLanguage(aFace
, aTableTag
, aDefaultFeatures
,
1002 nonDefaultFeatureLookups
, defaultFeatureLookups
,
1003 aScriptIndex
, lang
);
1006 // look for the glyph among default feature lookups
1007 aHasDefaultFeatureWithGlyph
= false;
1008 hb_set_t
* glyphs
= hb_set_create();
1009 hb_codepoint_t index
= -1;
1010 while (hb_set_next(defaultFeatureLookups
, &index
)) {
1011 hb_ot_layout_lookup_collect_glyphs(aFace
, aTableTag
, index
, glyphs
, glyphs
,
1013 if (hb_set_has(glyphs
, aGlyph
)) {
1014 aHasDefaultFeatureWithGlyph
= true;
1019 // look for the glyph among non-default feature lookups
1020 // if no default feature lookups contained spaces
1021 bool hasNonDefaultFeatureWithGlyph
= false;
1022 if (!aHasDefaultFeatureWithGlyph
) {
1023 hb_set_clear(glyphs
);
1025 while (hb_set_next(nonDefaultFeatureLookups
, &index
)) {
1026 hb_ot_layout_lookup_collect_glyphs(aFace
, aTableTag
, index
, glyphs
,
1027 glyphs
, glyphs
, nullptr);
1028 if (hb_set_has(glyphs
, aGlyph
)) {
1029 hasNonDefaultFeatureWithGlyph
= true;
1035 hb_set_destroy(glyphs
);
1036 hb_set_destroy(defaultFeatureLookups
);
1037 hb_set_destroy(nonDefaultFeatureLookups
);
1039 return aHasDefaultFeatureWithGlyph
|| hasNonDefaultFeatureWithGlyph
;
1042 static void HasLookupRuleWithGlyph(hb_face_t
* aFace
, hb_tag_t aTableTag
,
1043 bool& aHasGlyph
, hb_tag_t aSpecificFeature
,
1044 bool& aHasGlyphSpecific
, uint16_t aGlyph
) {
1045 // iterate over the scripts in the font
1046 uint32_t numScripts
, numLangs
, script
, lang
;
1047 hb_set_t
* otherLookups
= hb_set_create();
1048 hb_set_t
* specificFeatureLookups
= hb_set_create();
1049 nsTHashSet
<uint32_t> specificFeature(1);
1051 specificFeature
.Insert(aSpecificFeature
);
1054 hb_ot_layout_table_get_script_tags(aFace
, aTableTag
, 0, nullptr, nullptr);
1056 for (script
= 0; script
< numScripts
; script
++) {
1058 CollectLookupsByLanguage(aFace
, aTableTag
, specificFeature
, otherLookups
,
1059 specificFeatureLookups
, script
,
1060 HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX
);
1062 // iterate over langs
1063 numLangs
= hb_ot_layout_script_get_language_tags(
1064 aFace
, HB_OT_TAG_GPOS
, script
, 0, nullptr, nullptr);
1065 for (lang
= 0; lang
< numLangs
; lang
++) {
1066 CollectLookupsByLanguage(aFace
, aTableTag
, specificFeature
, otherLookups
,
1067 specificFeatureLookups
, script
, lang
);
1071 // look for the glyph among non-specific feature lookups
1072 hb_set_t
* glyphs
= hb_set_create();
1073 hb_codepoint_t index
= -1;
1074 while (hb_set_next(otherLookups
, &index
)) {
1075 hb_ot_layout_lookup_collect_glyphs(aFace
, aTableTag
, index
, glyphs
, glyphs
,
1077 if (hb_set_has(glyphs
, aGlyph
)) {
1083 // look for the glyph among specific feature lookups
1084 hb_set_clear(glyphs
);
1086 while (hb_set_next(specificFeatureLookups
, &index
)) {
1087 hb_ot_layout_lookup_collect_glyphs(aFace
, aTableTag
, index
, glyphs
, glyphs
,
1089 if (hb_set_has(glyphs
, aGlyph
)) {
1090 aHasGlyphSpecific
= true;
1095 hb_set_destroy(glyphs
);
1096 hb_set_destroy(specificFeatureLookups
);
1097 hb_set_destroy(otherLookups
);
1100 nsTHashMap
<nsUint32HashKey
, Script
>* gfxFont::sScriptTagToCode
= nullptr;
1101 nsTHashSet
<uint32_t>* gfxFont::sDefaultFeatures
= nullptr;
1103 static inline bool HasSubstitution(uint32_t* aBitVector
, Script aScript
) {
1104 return (aBitVector
[static_cast<uint32_t>(aScript
) >> 5] &
1105 (1 << (static_cast<uint32_t>(aScript
) & 0x1f))) != 0;
1108 // union of all default substitution features across scripts
1109 static const hb_tag_t defaultFeatures
[] = {
1110 HB_TAG('a', 'b', 'v', 'f'), HB_TAG('a', 'b', 'v', 's'),
1111 HB_TAG('a', 'k', 'h', 'n'), HB_TAG('b', 'l', 'w', 'f'),
1112 HB_TAG('b', 'l', 'w', 's'), HB_TAG('c', 'a', 'l', 't'),
1113 HB_TAG('c', 'c', 'm', 'p'), HB_TAG('c', 'f', 'a', 'r'),
1114 HB_TAG('c', 'j', 'c', 't'), HB_TAG('c', 'l', 'i', 'g'),
1115 HB_TAG('f', 'i', 'n', '2'), HB_TAG('f', 'i', 'n', '3'),
1116 HB_TAG('f', 'i', 'n', 'a'), HB_TAG('h', 'a', 'l', 'f'),
1117 HB_TAG('h', 'a', 'l', 'n'), HB_TAG('i', 'n', 'i', 't'),
1118 HB_TAG('i', 's', 'o', 'l'), HB_TAG('l', 'i', 'g', 'a'),
1119 HB_TAG('l', 'j', 'm', 'o'), HB_TAG('l', 'o', 'c', 'l'),
1120 HB_TAG('l', 't', 'r', 'a'), HB_TAG('l', 't', 'r', 'm'),
1121 HB_TAG('m', 'e', 'd', '2'), HB_TAG('m', 'e', 'd', 'i'),
1122 HB_TAG('m', 's', 'e', 't'), HB_TAG('n', 'u', 'k', 't'),
1123 HB_TAG('p', 'r', 'e', 'f'), HB_TAG('p', 'r', 'e', 's'),
1124 HB_TAG('p', 's', 't', 'f'), HB_TAG('p', 's', 't', 's'),
1125 HB_TAG('r', 'c', 'l', 't'), HB_TAG('r', 'l', 'i', 'g'),
1126 HB_TAG('r', 'k', 'r', 'f'), HB_TAG('r', 'p', 'h', 'f'),
1127 HB_TAG('r', 't', 'l', 'a'), HB_TAG('r', 't', 'l', 'm'),
1128 HB_TAG('t', 'j', 'm', 'o'), HB_TAG('v', 'a', 't', 'u'),
1129 HB_TAG('v', 'e', 'r', 't'), HB_TAG('v', 'j', 'm', 'o')};
1131 void gfxFont::CheckForFeaturesInvolvingSpace() {
1132 mFontEntry
->mHasSpaceFeaturesInitialized
= true;
1134 bool log
= LOG_FONTINIT_ENABLED();
1136 if (MOZ_UNLIKELY(log
)) {
1137 start
= TimeStamp::Now();
1140 bool result
= false;
1142 uint32_t spaceGlyph
= GetSpaceGlyph();
1147 hb_face_t
* face
= GetFontEntry()->GetHBFace();
1149 // GSUB lookups - examine per script
1150 if (hb_ot_layout_has_substitution(face
)) {
1151 // set up the script ==> code hashtable if needed
1152 if (!sScriptTagToCode
) {
1153 sScriptTagToCode
= new nsTHashMap
<nsUint32HashKey
, Script
>(
1154 size_t(Script::NUM_SCRIPT_CODES
));
1155 sScriptTagToCode
->InsertOrUpdate(HB_TAG('D', 'F', 'L', 'T'),
1157 // Ensure that we don't try to look at script codes beyond what the
1158 // current version of ICU (at runtime -- in case of system ICU)
1160 Script scriptCount
=
1161 Script(std::min
<int>(u_getIntPropertyMaxValue(UCHAR_SCRIPT
) + 1,
1162 int(Script::NUM_SCRIPT_CODES
)));
1163 for (Script s
= Script::ARABIC
; s
< scriptCount
;
1164 s
= Script(static_cast<int>(s
) + 1)) {
1165 hb_script_t script
= hb_script_t(GetScriptTagForCode(s
));
1166 unsigned int scriptCount
= 4;
1167 hb_tag_t scriptTags
[4];
1168 hb_ot_tags_from_script_and_language(script
, HB_LANGUAGE_INVALID
,
1169 &scriptCount
, scriptTags
, nullptr,
1171 for (unsigned int i
= 0; i
< scriptCount
; i
++) {
1172 sScriptTagToCode
->InsertOrUpdate(scriptTags
[i
], s
);
1176 uint32_t numDefaultFeatures
= ArrayLength(defaultFeatures
);
1177 sDefaultFeatures
= new nsTHashSet
<uint32_t>(numDefaultFeatures
);
1178 for (uint32_t i
= 0; i
< numDefaultFeatures
; i
++) {
1179 sDefaultFeatures
->Insert(defaultFeatures
[i
]);
1183 // iterate over the scripts in the font
1184 hb_tag_t scriptTags
[8];
1186 uint32_t len
, offset
= 0;
1188 len
= ArrayLength(scriptTags
);
1189 hb_ot_layout_table_get_script_tags(face
, HB_OT_TAG_GSUB
, offset
, &len
,
1191 for (uint32_t i
= 0; i
< len
; i
++) {
1192 bool isDefaultFeature
= false;
1194 if (!HasLookupRuleWithGlyphByScript(
1195 face
, HB_OT_TAG_GSUB
, scriptTags
[i
], offset
+ i
, spaceGlyph
,
1196 *sDefaultFeatures
, isDefaultFeature
) ||
1197 !sScriptTagToCode
->Get(scriptTags
[i
], &s
)) {
1201 uint32_t index
= static_cast<uint32_t>(s
) >> 5;
1202 uint32_t bit
= static_cast<uint32_t>(s
) & 0x1f;
1203 if (isDefaultFeature
) {
1204 mFontEntry
->mDefaultSubSpaceFeatures
[index
] |= (1 << bit
);
1206 mFontEntry
->mNonDefaultSubSpaceFeatures
[index
] |= (1 << bit
);
1210 } while (len
== ArrayLength(scriptTags
));
1213 // spaces in default features of default script?
1214 // ==> can't use word cache, skip GPOS analysis
1215 bool canUseWordCache
= true;
1216 if (HasSubstitution(mFontEntry
->mDefaultSubSpaceFeatures
, Script::COMMON
)) {
1217 canUseWordCache
= false;
1220 // GPOS lookups - distinguish kerning from non-kerning features
1221 mFontEntry
->mHasSpaceFeaturesKerning
= false;
1222 mFontEntry
->mHasSpaceFeaturesNonKerning
= false;
1224 if (canUseWordCache
&& hb_ot_layout_has_positioning(face
)) {
1225 bool hasKerning
= false, hasNonKerning
= false;
1226 HasLookupRuleWithGlyph(face
, HB_OT_TAG_GPOS
, hasNonKerning
,
1227 HB_TAG('k', 'e', 'r', 'n'), hasKerning
, spaceGlyph
);
1228 if (hasKerning
|| hasNonKerning
) {
1231 mFontEntry
->mHasSpaceFeaturesKerning
= hasKerning
;
1232 mFontEntry
->mHasSpaceFeaturesNonKerning
= hasNonKerning
;
1235 hb_face_destroy(face
);
1236 mFontEntry
->mHasSpaceFeatures
= result
;
1238 if (MOZ_UNLIKELY(log
)) {
1239 TimeDuration elapsed
= TimeStamp::Now() - start
;
1241 ("(fontinit-spacelookups) font: %s - "
1242 "subst default: %8.8x %8.8x %8.8x %8.8x "
1243 "subst non-default: %8.8x %8.8x %8.8x %8.8x "
1244 "kerning: %s non-kerning: %s time: %6.3f\n",
1245 mFontEntry
->Name().get(), mFontEntry
->mDefaultSubSpaceFeatures
[3],
1246 mFontEntry
->mDefaultSubSpaceFeatures
[2],
1247 mFontEntry
->mDefaultSubSpaceFeatures
[1],
1248 mFontEntry
->mDefaultSubSpaceFeatures
[0],
1249 mFontEntry
->mNonDefaultSubSpaceFeatures
[3],
1250 mFontEntry
->mNonDefaultSubSpaceFeatures
[2],
1251 mFontEntry
->mNonDefaultSubSpaceFeatures
[1],
1252 mFontEntry
->mNonDefaultSubSpaceFeatures
[0],
1253 (mFontEntry
->mHasSpaceFeaturesKerning
? "true" : "false"),
1254 (mFontEntry
->mHasSpaceFeaturesNonKerning
? "true" : "false"),
1255 elapsed
.ToMilliseconds()));
1259 bool gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript
) {
1260 NS_ASSERTION(GetFontEntry()->mHasSpaceFeaturesInitialized
,
1261 "need to initialize space lookup flags");
1262 NS_ASSERTION(aRunScript
< Script::NUM_SCRIPT_CODES
, "weird script code");
1263 if (aRunScript
== Script::INVALID
|| aRunScript
>= Script::NUM_SCRIPT_CODES
) {
1267 // default features have space lookups ==> true
1268 if (HasSubstitution(mFontEntry
->mDefaultSubSpaceFeatures
, Script::COMMON
) ||
1269 HasSubstitution(mFontEntry
->mDefaultSubSpaceFeatures
, aRunScript
)) {
1273 // non-default features have space lookups and some type of
1274 // font feature, in font or style is specified ==> true
1275 if ((HasSubstitution(mFontEntry
->mNonDefaultSubSpaceFeatures
,
1277 HasSubstitution(mFontEntry
->mNonDefaultSubSpaceFeatures
, aRunScript
)) &&
1278 (!mStyle
.featureSettings
.IsEmpty() ||
1279 !mFontEntry
->mFeatureSettings
.IsEmpty())) {
1286 tainted_boolean_hint
gfxFont::SpaceMayParticipateInShaping(Script aRunScript
) {
1287 // avoid checking fonts known not to include default space-dependent features
1288 if (MOZ_UNLIKELY(mFontEntry
->mSkipDefaultFeatureSpaceCheck
)) {
1289 if (!mKerningSet
&& mStyle
.featureSettings
.IsEmpty() &&
1290 mFontEntry
->mFeatureSettings
.IsEmpty()) {
1295 if (FontCanSupportGraphite()) {
1296 if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1297 return mFontEntry
->HasGraphiteSpaceContextuals();
1301 // We record the presence of space-dependent features in the font entry
1302 // so that subsequent instantiations for the same font face won't
1303 // require us to re-check the tables; however, the actual check is done
1304 // by gfxFont because not all font entry subclasses know how to create
1305 // a harfbuzz face for introspection.
1306 if (!mFontEntry
->mHasSpaceFeaturesInitialized
) {
1307 CheckForFeaturesInvolvingSpace();
1310 if (!mFontEntry
->mHasSpaceFeatures
) {
1314 // if font has substitution rules or non-kerning positioning rules
1315 // that involve spaces, bypass
1316 if (HasSubstitutionRulesWithSpaceLookups(aRunScript
) ||
1317 mFontEntry
->mHasSpaceFeaturesNonKerning
) {
1321 // if kerning explicitly enabled/disabled via font-feature-settings or
1322 // font-kerning and kerning rules use spaces, only bypass when enabled
1323 if (mKerningSet
&& mFontEntry
->mHasSpaceFeaturesKerning
) {
1324 return mKerningEnabled
;
1330 bool gfxFont::SupportsFeature(Script aScript
, uint32_t aFeatureTag
) {
1331 if (mGraphiteShaper
&& gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1332 return GetFontEntry()->SupportsGraphiteFeature(aFeatureTag
);
1334 return GetFontEntry()->SupportsOpenTypeFeature(aScript
, aFeatureTag
);
1337 bool gfxFont::SupportsVariantCaps(Script aScript
, uint32_t aVariantCaps
,
1338 bool& aFallbackToSmallCaps
,
1339 bool& aSyntheticLowerToSmallCaps
,
1340 bool& aSyntheticUpperToSmallCaps
) {
1341 bool ok
= true; // cases without fallback are fine
1342 aFallbackToSmallCaps
= false;
1343 aSyntheticLowerToSmallCaps
= false;
1344 aSyntheticUpperToSmallCaps
= false;
1345 switch (aVariantCaps
) {
1346 case NS_FONT_VARIANT_CAPS_SMALLCAPS
:
1347 ok
= SupportsFeature(aScript
, HB_TAG('s', 'm', 'c', 'p'));
1349 aSyntheticLowerToSmallCaps
= true;
1352 case NS_FONT_VARIANT_CAPS_ALLSMALL
:
1353 ok
= SupportsFeature(aScript
, HB_TAG('s', 'm', 'c', 'p')) &&
1354 SupportsFeature(aScript
, HB_TAG('c', '2', 's', 'c'));
1356 aSyntheticLowerToSmallCaps
= true;
1357 aSyntheticUpperToSmallCaps
= true;
1360 case NS_FONT_VARIANT_CAPS_PETITECAPS
:
1361 ok
= SupportsFeature(aScript
, HB_TAG('p', 'c', 'a', 'p'));
1363 ok
= SupportsFeature(aScript
, HB_TAG('s', 'm', 'c', 'p'));
1364 aFallbackToSmallCaps
= ok
;
1367 aSyntheticLowerToSmallCaps
= true;
1370 case NS_FONT_VARIANT_CAPS_ALLPETITE
:
1371 ok
= SupportsFeature(aScript
, HB_TAG('p', 'c', 'a', 'p')) &&
1372 SupportsFeature(aScript
, HB_TAG('c', '2', 'p', 'c'));
1374 ok
= SupportsFeature(aScript
, HB_TAG('s', 'm', 'c', 'p')) &&
1375 SupportsFeature(aScript
, HB_TAG('c', '2', 's', 'c'));
1376 aFallbackToSmallCaps
= ok
;
1379 aSyntheticLowerToSmallCaps
= true;
1380 aSyntheticUpperToSmallCaps
= true;
1388 !(ok
&& (aSyntheticLowerToSmallCaps
|| aSyntheticUpperToSmallCaps
)),
1389 "shouldn't use synthetic features if we found real ones");
1391 NS_ASSERTION(!(!ok
&& aFallbackToSmallCaps
),
1392 "if we found a usable fallback, that counts as ok");
1397 bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript
,
1398 const uint8_t* aString
, uint32_t aLength
,
1399 Script aRunScript
) {
1400 NS_ConvertASCIItoUTF16
unicodeString(reinterpret_cast<const char*>(aString
),
1402 return SupportsSubSuperscript(aSubSuperscript
, unicodeString
.get(), aLength
,
1406 bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript
,
1407 const char16_t
* aString
, uint32_t aLength
,
1408 Script aRunScript
) {
1409 NS_ASSERTION(aSubSuperscript
== NS_FONT_VARIANT_POSITION_SUPER
||
1410 aSubSuperscript
== NS_FONT_VARIANT_POSITION_SUB
,
1411 "unknown value of font-variant-position");
1413 uint32_t feature
= aSubSuperscript
== NS_FONT_VARIANT_POSITION_SUPER
1414 ? HB_TAG('s', 'u', 'p', 's')
1415 : HB_TAG('s', 'u', 'b', 's');
1417 if (!SupportsFeature(aRunScript
, feature
)) {
1421 // xxx - for graphite, don't really know how to sniff lookups so bail
1422 if (mGraphiteShaper
&& gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1426 if (!mHarfBuzzShaper
) {
1427 mHarfBuzzShaper
= MakeUnique
<gfxHarfBuzzShaper
>(this);
1429 gfxHarfBuzzShaper
* shaper
=
1430 static_cast<gfxHarfBuzzShaper
*>(mHarfBuzzShaper
.get());
1431 if (!shaper
->Initialize()) {
1435 // get the hbset containing input glyphs for the feature
1436 const hb_set_t
* inputGlyphs
=
1437 mFontEntry
->InputsForOpenTypeFeature(aRunScript
, feature
);
1439 // create an hbset containing default glyphs for the script run
1440 hb_set_t
* defaultGlyphsInRun
= hb_set_create();
1442 // for each character, get the glyph id
1443 for (uint32_t i
= 0; i
< aLength
; i
++) {
1444 uint32_t ch
= aString
[i
];
1446 if (i
+ 1 < aLength
&& NS_IS_SURROGATE_PAIR(ch
, aString
[i
+ 1])) {
1448 ch
= SURROGATE_TO_UCS4(ch
, aString
[i
]);
1451 hb_codepoint_t gid
= shaper
->GetNominalGlyph(ch
);
1452 hb_set_add(defaultGlyphsInRun
, gid
);
1455 // intersect with input glyphs, if size is not the same ==> fallback
1456 uint32_t origSize
= hb_set_get_population(defaultGlyphsInRun
);
1457 hb_set_intersect(defaultGlyphsInRun
, inputGlyphs
);
1458 uint32_t intersectionSize
= hb_set_get_population(defaultGlyphsInRun
);
1459 hb_set_destroy(defaultGlyphsInRun
);
1461 return origSize
== intersectionSize
;
1464 bool gfxFont::FeatureWillHandleChar(Script aRunScript
, uint32_t aFeature
,
1465 uint32_t aUnicode
) {
1466 if (!SupportsFeature(aRunScript
, aFeature
)) {
1470 // xxx - for graphite, don't really know how to sniff lookups so bail
1471 if (mGraphiteShaper
&& gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1475 if (!mHarfBuzzShaper
) {
1476 mHarfBuzzShaper
= MakeUnique
<gfxHarfBuzzShaper
>(this);
1478 gfxHarfBuzzShaper
* shaper
=
1479 static_cast<gfxHarfBuzzShaper
*>(mHarfBuzzShaper
.get());
1480 if (!shaper
->Initialize()) {
1484 // get the hbset containing input glyphs for the feature
1485 const hb_set_t
* inputGlyphs
=
1486 mFontEntry
->InputsForOpenTypeFeature(aRunScript
, aFeature
);
1488 hb_codepoint_t gid
= shaper
->GetNominalGlyph(aUnicode
);
1489 return hb_set_has(inputGlyphs
, gid
);
1492 bool gfxFont::HasFeatureSet(uint32_t aFeature
, bool& aFeatureOn
) {
1495 if (mStyle
.featureSettings
.IsEmpty() &&
1496 GetFontEntry()->mFeatureSettings
.IsEmpty()) {
1500 // add feature values from font
1501 bool featureSet
= false;
1504 nsTArray
<gfxFontFeature
>& fontFeatures
= GetFontEntry()->mFeatureSettings
;
1505 count
= fontFeatures
.Length();
1506 for (i
= 0; i
< count
; i
++) {
1507 const gfxFontFeature
& feature
= fontFeatures
.ElementAt(i
);
1508 if (feature
.mTag
== aFeature
) {
1510 aFeatureOn
= (feature
.mValue
!= 0);
1514 // add feature values from style rules
1515 nsTArray
<gfxFontFeature
>& styleFeatures
= mStyle
.featureSettings
;
1516 count
= styleFeatures
.Length();
1517 for (i
= 0; i
< count
; i
++) {
1518 const gfxFontFeature
& feature
= styleFeatures
.ElementAt(i
);
1519 if (feature
.mTag
== aFeature
) {
1521 aFeatureOn
= (feature
.mValue
!= 0);
1528 void gfxFont::InitializeScaledFont() {
1529 if (!mAzureScaledFont
) {
1533 float angle
= AngleForSyntheticOblique();
1534 if (angle
!= 0.0f
) {
1535 mAzureScaledFont
->SetSyntheticObliqueAngle(angle
);
1540 * A helper function in case we need to do any rounding or other
1543 #define ToDeviceUnits(aAppUnits, aDevUnitsPerAppUnit) \
1544 (double(aAppUnits) * double(aDevUnitsPerAppUnit))
1546 static AntialiasMode
Get2DAAMode(gfxFont::AntialiasOption aAAOption
) {
1547 switch (aAAOption
) {
1548 case gfxFont::kAntialiasSubpixel
:
1549 return AntialiasMode::SUBPIXEL
;
1550 case gfxFont::kAntialiasGrayscale
:
1551 return AntialiasMode::GRAY
;
1552 case gfxFont::kAntialiasNone
:
1553 return AntialiasMode::NONE
;
1555 return AntialiasMode::DEFAULT
;
1559 class GlyphBufferAzure
{
1560 #define AUTO_BUFFER_SIZE (2048 / sizeof(Glyph))
1562 typedef mozilla::image::imgDrawingParams imgDrawingParams
;
1565 GlyphBufferAzure(const TextRunDrawParams
& aRunParams
,
1566 const FontDrawParams
& aFontParams
)
1567 : mRunParams(aRunParams
),
1568 mFontParams(aFontParams
),
1569 mBuffer(*mAutoBuffer
.addr()),
1570 mBufSize(AUTO_BUFFER_SIZE
),
1574 ~GlyphBufferAzure() {
1575 if (mNumGlyphs
> 0) {
1579 if (mBuffer
!= *mAutoBuffer
.addr()) {
1584 // Ensure the buffer has enough space for aGlyphCount glyphs to be added,
1585 // considering the supplied strike multipler aStrikeCount.
1586 // This MUST be called before OutputGlyph is used to actually store glyph
1587 // records in the buffer. It may be called repeated to add further capacity
1588 // in case we don't know up-front exactly what will be needed.
1589 void AddCapacity(uint32_t aGlyphCount
, uint32_t aStrikeCount
) {
1590 // Calculate the new capacity and ensure it will fit within the maximum
1591 // allowed capacity.
1592 static const uint64_t kMaxCapacity
= 64 * 1024;
1593 mCapacity
= uint32_t(std::min(
1595 uint64_t(mCapacity
) + uint64_t(aGlyphCount
) * uint64_t(aStrikeCount
)));
1596 // See if the required capacity fits within the already-allocated space
1597 if (mCapacity
<= mBufSize
) {
1600 // We need to grow the buffer: determine a new size, allocate, and
1601 // copy the existing data over if we didn't use realloc (which would
1602 // do it automatically).
1603 mBufSize
= std::max(mCapacity
, mBufSize
* 2);
1604 if (mBuffer
== *mAutoBuffer
.addr()) {
1605 // switching from autobuffer to malloc, so we need to copy
1606 mBuffer
= reinterpret_cast<Glyph
*>(moz_xmalloc(mBufSize
* sizeof(Glyph
)));
1607 std::memcpy(mBuffer
, *mAutoBuffer
.addr(), mNumGlyphs
* sizeof(Glyph
));
1609 mBuffer
= reinterpret_cast<Glyph
*>(
1610 moz_xrealloc(mBuffer
, mBufSize
* sizeof(Glyph
)));
1614 void OutputGlyph(uint32_t aGlyphID
, const gfx::Point
& aPt
) {
1615 // If the buffer is full, flush to make room for the new glyph.
1616 if (mNumGlyphs
>= mCapacity
) {
1617 // Check that AddCapacity has been used appropriately!
1618 MOZ_ASSERT(mCapacity
> 0 && mNumGlyphs
== mCapacity
);
1621 Glyph
* glyph
= mBuffer
+ mNumGlyphs
++;
1622 glyph
->mIndex
= aGlyphID
;
1623 glyph
->mPosition
= aPt
;
1627 if (mNumGlyphs
> 0) {
1633 const TextRunDrawParams
& mRunParams
;
1634 const FontDrawParams
& mFontParams
;
1637 static DrawMode
GetStrokeMode(DrawMode aMode
) {
1638 return aMode
& (DrawMode::GLYPH_STROKE
| DrawMode::GLYPH_STROKE_UNDERNEATH
);
1641 // Render the buffered glyphs to the draw target.
1642 void FlushGlyphs() {
1643 gfx::GlyphBuffer buf
;
1644 buf
.mGlyphs
= mBuffer
;
1645 buf
.mNumGlyphs
= mNumGlyphs
;
1647 const gfxContext::AzureState
& state
= mRunParams
.context
->CurrentState();
1649 // Draw stroke first if the UNDERNEATH flag is set in drawMode.
1650 if (mRunParams
.strokeOpts
&&
1651 GetStrokeMode(mRunParams
.drawMode
) ==
1652 (DrawMode::GLYPH_STROKE
| DrawMode::GLYPH_STROKE_UNDERNEATH
)) {
1653 DrawStroke(state
, buf
);
1656 if (mRunParams
.drawMode
& DrawMode::GLYPH_FILL
) {
1657 if (state
.pattern
|| mFontParams
.contextPaint
) {
1660 RefPtr
<gfxPattern
> fillPattern
;
1661 if (mFontParams
.contextPaint
) {
1662 imgDrawingParams imgParams
;
1663 fillPattern
= mFontParams
.contextPaint
->GetFillPattern(
1664 mRunParams
.context
->GetDrawTarget(),
1665 mRunParams
.context
->CurrentMatrixDouble(), imgParams
);
1668 if (state
.pattern
) {
1669 RefPtr
<gfxPattern
> statePattern
=
1670 mRunParams
.context
->CurrentState().pattern
;
1671 pat
= statePattern
->GetPattern(mRunParams
.dt
,
1672 state
.patternTransformChanged
1673 ? &state
.patternTransform
1679 pat
= fillPattern
->GetPattern(mRunParams
.dt
);
1683 mRunParams
.dt
->FillGlyphs(mFontParams
.scaledFont
, buf
, *pat
,
1684 mFontParams
.drawOptions
);
1687 mRunParams
.dt
->FillGlyphs(mFontParams
.scaledFont
, buf
,
1688 ColorPattern(state
.color
),
1689 mFontParams
.drawOptions
);
1693 // Draw stroke if the UNDERNEATH flag is not set.
1694 if (mRunParams
.strokeOpts
&&
1695 GetStrokeMode(mRunParams
.drawMode
) == DrawMode::GLYPH_STROKE
) {
1696 DrawStroke(state
, buf
);
1699 if (mRunParams
.drawMode
& DrawMode::GLYPH_PATH
) {
1700 mRunParams
.context
->EnsurePathBuilder();
1701 Matrix mat
= mRunParams
.dt
->GetTransform();
1702 mFontParams
.scaledFont
->CopyGlyphsToBuilder(
1703 buf
, mRunParams
.context
->mPathBuilder
, &mat
);
1707 void DrawStroke(const gfxContext::AzureState
& aState
,
1708 gfx::GlyphBuffer
& aBuffer
) {
1709 if (mRunParams
.textStrokePattern
) {
1710 Pattern
* pat
= mRunParams
.textStrokePattern
->GetPattern(
1712 aState
.patternTransformChanged
? &aState
.patternTransform
: nullptr);
1715 FlushStroke(aBuffer
, *pat
);
1718 FlushStroke(aBuffer
,
1719 ColorPattern(ToDeviceColor(mRunParams
.textStrokeColor
)));
1723 void FlushStroke(gfx::GlyphBuffer
& aBuf
, const Pattern
& aPattern
) {
1724 mRunParams
.dt
->StrokeGlyphs(mFontParams
.scaledFont
, aBuf
, aPattern
,
1725 *mRunParams
.strokeOpts
,
1726 mFontParams
.drawOptions
);
1729 // We use an "inline" buffer automatically allocated (on the stack) as part
1730 // of the GlyphBufferAzure object to hold the glyphs in most cases, falling
1731 // back to a separately-allocated heap buffer if the count of buffered
1732 // glyphs gets too big.
1734 // This is basically a rudimentary AutoTArray; so why not use AutoTArray
1737 // If we used an AutoTArray, we'd want to avoid using SetLength or
1738 // AppendElements to allocate the space we actually need, because those
1739 // methods would default-construct the new elements.
1741 // Could we use SetCapacity to reserve the necessary buffer space without
1742 // default-constructing all the Glyph records? No, because of a failure
1743 // that could occur when we need to grow the buffer, which happens when we
1744 // encounter a DetailedGlyph in the textrun that refers to a sequence of
1745 // several real glyphs. At that point, we need to add some extra capacity
1746 // to the buffer we initially allocated based on the length of the textrun
1747 // range we're rendering.
1749 // This buffer growth would work fine as long as it still fits within the
1750 // array's inline buffer (we just use a bit more of it), or if the buffer
1751 // was already heap-allocated (in which case AutoTArray will use realloc(),
1752 // preserving its contents). But a problem will arise when the initial
1753 // capacity we allocated (based on the length of the run) fits within the
1754 // array's inline buffer, but subsequently we need to extend the buffer
1755 // beyond the inline buffer size, so we reallocate to the heap. Because we
1756 // haven't "officially" filled the array with SetLength or AppendElements,
1757 // its mLength is still zero; as far as it's concerned the buffer is just
1758 // uninitialized space, and when it switches to use a malloc'd buffer it
1759 // won't copy the existing contents.
1761 // Allocate space for a buffer of Glyph records, without initializing them.
1762 AlignedStorage2
<Glyph
[AUTO_BUFFER_SIZE
]> mAutoBuffer
;
1764 // Pointer to the buffer we're currently using -- initially mAutoBuffer,
1765 // but may be changed to a malloc'd buffer, in which case that buffer must
1766 // be free'd on destruction.
1769 uint32_t mBufSize
; // size of allocated buffer; capacity can grow to
1770 // this before reallocation is needed
1771 uint32_t mCapacity
; // amount of buffer size reserved
1772 uint32_t mNumGlyphs
; // number of glyphs actually present in the buffer
1774 #undef AUTO_BUFFER_SIZE
1777 // Bug 674909. When synthetic bolding text by drawing twice, need to
1778 // render using a pixel offset in device pixels, otherwise text
1779 // doesn't appear bolded, it appears as if a bad text shadow exists
1780 // when a non-identity transform exists. Use an offset factor so that
1781 // the second draw occurs at a constant offset in device pixels.
1783 gfx::Float
gfxFont::CalcXScale(DrawTarget
* aDrawTarget
) {
1784 // determine magnitude of a 1px x offset in device space
1785 Size t
= aDrawTarget
->GetTransform().TransformSize(Size(1.0, 0.0));
1786 if (t
.width
== 1.0 && t
.height
== 0.0) {
1787 // short-circuit the most common case to avoid sqrt() and division
1791 gfx::Float m
= sqrtf(t
.width
* t
.width
+ t
.height
* t
.height
);
1793 NS_ASSERTION(m
!= 0.0, "degenerate transform while synthetic bolding");
1795 return 0.0; // effectively disables offset
1798 // scale factor so that offsets are 1px in device pixels
1802 // Draw a run of CharacterGlyph records from the given offset in aShapedText.
1803 // Returns true if glyph paths were actually emitted.
1804 template <gfxFont::FontComplexityT FC
, gfxFont::SpacingT S
>
1805 bool gfxFont::DrawGlyphs(const gfxShapedText
* aShapedText
,
1806 uint32_t aOffset
, // offset in the textrun
1807 uint32_t aCount
, // length of run to draw
1809 const gfx::Matrix
* aOffsetMatrix
, // may be null
1810 GlyphBufferAzure
& aBuffer
) {
1811 float& inlineCoord
= aBuffer
.mFontParams
.isVerticalFont
? aPt
->y
: aPt
->x
;
1813 const gfxShapedText::CompressedGlyph
* glyphData
=
1814 &aShapedText
->GetCharacterGlyphs()[aOffset
];
1816 if (S
== SpacingT::HasSpacing
) {
1817 float space
= aBuffer
.mRunParams
.spacing
[0].mBefore
*
1818 aBuffer
.mFontParams
.advanceDirection
;
1819 inlineCoord
+= space
;
1822 // Allocate buffer space for the run, assuming all simple glyphs.
1823 uint32_t capacityMult
= 1 + aBuffer
.mFontParams
.extraStrikes
;
1824 aBuffer
.AddCapacity(aCount
, capacityMult
);
1826 bool emittedGlyphs
= false;
1828 for (uint32_t i
= 0; i
< aCount
; ++i
, ++glyphData
) {
1829 if (glyphData
->IsSimpleGlyph()) {
1831 glyphData
->GetSimpleAdvance() * aBuffer
.mFontParams
.advanceDirection
;
1832 if (aBuffer
.mRunParams
.isRTL
) {
1833 inlineCoord
+= advance
;
1835 DrawOneGlyph
<FC
>(glyphData
->GetSimpleGlyph(), *aPt
, aBuffer
,
1837 if (!aBuffer
.mRunParams
.isRTL
) {
1838 inlineCoord
+= advance
;
1841 uint32_t glyphCount
= glyphData
->GetGlyphCount();
1842 if (glyphCount
> 0) {
1843 // Add extra buffer capacity to allow for multiple-glyph entry.
1844 aBuffer
.AddCapacity(glyphCount
- 1, capacityMult
);
1845 const gfxShapedText::DetailedGlyph
* details
=
1846 aShapedText
->GetDetailedGlyphs(aOffset
+ i
);
1847 MOZ_ASSERT(details
, "missing DetailedGlyph!");
1848 for (uint32_t j
= 0; j
< glyphCount
; ++j
, ++details
) {
1850 details
->mAdvance
* aBuffer
.mFontParams
.advanceDirection
;
1851 if (aBuffer
.mRunParams
.isRTL
) {
1852 inlineCoord
+= advance
;
1854 if (glyphData
->IsMissing()) {
1855 if (!DrawMissingGlyph(aBuffer
.mRunParams
, aBuffer
.mFontParams
,
1861 *aPt
+ (aOffsetMatrix
1862 ? aOffsetMatrix
->TransformPoint(details
->mOffset
)
1863 : details
->mOffset
));
1864 DrawOneGlyph
<FC
>(details
->mGlyphID
, glyphPt
, aBuffer
,
1867 if (!aBuffer
.mRunParams
.isRTL
) {
1868 inlineCoord
+= advance
;
1874 if (S
== SpacingT::HasSpacing
) {
1875 float space
= aBuffer
.mRunParams
.spacing
[i
].mAfter
;
1876 if (i
+ 1 < aCount
) {
1877 space
+= aBuffer
.mRunParams
.spacing
[i
+ 1].mBefore
;
1879 space
*= aBuffer
.mFontParams
.advanceDirection
;
1880 inlineCoord
+= space
;
1884 return emittedGlyphs
;
1887 // Draw an individual glyph at a specific location.
1888 // *aPt is the glyph position in appUnits; it is converted to device
1889 // coordinates (devPt) here.
1890 template <gfxFont::FontComplexityT FC
>
1891 void gfxFont::DrawOneGlyph(uint32_t aGlyphID
, const gfx::Point
& aPt
,
1892 GlyphBufferAzure
& aBuffer
, bool* aEmittedGlyphs
) {
1893 const TextRunDrawParams
& runParams(aBuffer
.mRunParams
);
1895 gfx::Point
devPt(ToDeviceUnits(aPt
.x
, runParams
.devPerApp
),
1896 ToDeviceUnits(aPt
.y
, runParams
.devPerApp
));
1898 if (FC
== FontComplexityT::ComplexFont
) {
1899 const FontDrawParams
& fontParams(aBuffer
.mFontParams
);
1901 auto* textDrawer
= runParams
.context
->GetTextDrawer();
1903 gfxContextMatrixAutoSaveRestore matrixRestore
;
1905 if (fontParams
.obliqueSkew
!= 0.0f
&& fontParams
.isVerticalFont
&&
1907 // We have to flush each glyph individually when doing
1908 // synthetic-oblique for vertical-upright text, because
1909 // the skew transform needs to be applied to a separate
1910 // origin for each glyph, not once for the whole run.
1912 matrixRestore
.SetContext(runParams
.context
);
1914 devPt
.x
+ GetMetrics(nsFontMetrics::eVertical
).emHeight
/ 2, devPt
.y
);
1916 runParams
.context
->CurrentMatrix()
1917 .PreTranslate(skewPt
)
1918 .PreMultiply(gfx::Matrix(1, fontParams
.obliqueSkew
, 0, 1, 0, 0))
1919 .PreTranslate(-skewPt
);
1920 runParams
.context
->SetMatrix(mat
);
1923 if (fontParams
.haveSVGGlyphs
) {
1924 if (!runParams
.paintSVGGlyphs
) {
1927 NS_WARNING_ASSERTION(
1928 runParams
.drawMode
!= DrawMode::GLYPH_PATH
,
1929 "Rendering SVG glyph despite request for glyph path");
1930 if (RenderSVGGlyph(runParams
.context
, textDrawer
, devPt
, aGlyphID
,
1931 fontParams
.contextPaint
, runParams
.callbacks
,
1937 if (fontParams
.haveColorGlyphs
&&
1938 !gfxPlatform::GetPlatform()->HasNativeColrFontSupport() &&
1939 RenderColorGlyph(runParams
.dt
, runParams
.context
, textDrawer
,
1940 fontParams
.scaledFont
, fontParams
.drawOptions
, devPt
,
1945 aBuffer
.OutputGlyph(aGlyphID
, devPt
);
1947 // Synthetic bolding (if required) by multi-striking.
1948 for (int32_t i
= 0; i
< fontParams
.extraStrikes
; ++i
) {
1949 if (fontParams
.isVerticalFont
) {
1950 devPt
.y
+= fontParams
.synBoldOnePixelOffset
;
1952 devPt
.x
+= fontParams
.synBoldOnePixelOffset
;
1954 aBuffer
.OutputGlyph(aGlyphID
, devPt
);
1957 if (fontParams
.obliqueSkew
!= 0.0f
&& fontParams
.isVerticalFont
&&
1962 aBuffer
.OutputGlyph(aGlyphID
, devPt
);
1965 *aEmittedGlyphs
= true;
1968 bool gfxFont::DrawMissingGlyph(const TextRunDrawParams
& aRunParams
,
1969 const FontDrawParams
& aFontParams
,
1970 const gfxShapedText::DetailedGlyph
* aDetails
,
1971 const gfx::Point
& aPt
) {
1972 // Default-ignorable chars will have zero advance width;
1973 // we don't have to draw the hexbox for them.
1974 float advance
= aDetails
->mAdvance
;
1975 if (aRunParams
.drawMode
!= DrawMode::GLYPH_PATH
&& advance
> 0) {
1976 auto* textDrawer
= aRunParams
.context
->GetTextDrawer();
1977 const Matrix
* matPtr
= nullptr;
1980 // Generate an orientation matrix for the current writing mode
1981 wr::FontInstanceFlags flags
= textDrawer
->GetWRGlyphFlags();
1982 if (flags
& wr::FontInstanceFlags::TRANSPOSE
) {
1983 std::swap(mat
._11
, mat
._12
);
1984 std::swap(mat
._21
, mat
._22
);
1986 mat
.PostScale(flags
& wr::FontInstanceFlags::FLIP_X
? -1.0f
: 1.0f
,
1987 flags
& wr::FontInstanceFlags::FLIP_Y
? -1.0f
: 1.0f
);
1991 Point
pt(Float(ToDeviceUnits(aPt
.x
, aRunParams
.devPerApp
)),
1992 Float(ToDeviceUnits(aPt
.y
, aRunParams
.devPerApp
)));
1993 Float advanceDevUnits
= Float(ToDeviceUnits(advance
, aRunParams
.devPerApp
));
1994 Float height
= GetMetrics(nsFontMetrics::eHorizontal
).maxAscent
;
1995 // Horizontally center if drawing vertically upright with no sideways
1998 aFontParams
.isVerticalFont
&& !mat
.HasNonAxisAlignedTransform()
1999 ? Rect(pt
.x
- height
/ 2, pt
.y
, height
, advanceDevUnits
)
2000 : Rect(pt
.x
, pt
.y
- height
, advanceDevUnits
, height
);
2002 // If there's a fake-italic skew in effect as part
2003 // of the drawTarget's transform, we need to undo
2004 // this before drawing the hexbox. (Bug 983985)
2005 gfxContextMatrixAutoSaveRestore matrixRestore
;
2006 if (aFontParams
.obliqueSkew
!= 0.0f
&& !aFontParams
.isVerticalFont
&&
2008 matrixRestore
.SetContext(aRunParams
.context
);
2010 aRunParams
.context
->CurrentMatrix()
2012 .PreMultiply(gfx::Matrix(1, 0, aFontParams
.obliqueSkew
, 1, 0, 0))
2014 aRunParams
.context
->SetMatrix(mat
);
2017 gfxFontMissingGlyphs::DrawMissingGlyph(aDetails
->mGlyphID
, glyphRect
,
2019 PatternFromState(aRunParams
.context
),
2020 1.0 / aRunParams
.devPerApp
, matPtr
);
2025 // This method is mostly parallel to DrawGlyphs.
2026 void gfxFont::DrawEmphasisMarks(const gfxTextRun
* aShapedText
, gfx::Point
* aPt
,
2027 uint32_t aOffset
, uint32_t aCount
,
2028 const EmphasisMarkDrawParams
& aParams
) {
2029 float& inlineCoord
= aParams
.isVertical
? aPt
->y
: aPt
->x
;
2030 gfxTextRun::Range
markRange(aParams
.mark
);
2031 gfxTextRun::DrawParams
params(aParams
.context
);
2033 float clusterStart
= -std::numeric_limits
<float>::infinity();
2034 bool shouldDrawEmphasisMark
= false;
2035 for (uint32_t i
= 0, idx
= aOffset
; i
< aCount
; ++i
, ++idx
) {
2036 if (aParams
.spacing
) {
2037 inlineCoord
+= aParams
.direction
* aParams
.spacing
[i
].mBefore
;
2039 if (aShapedText
->IsClusterStart(idx
) ||
2040 clusterStart
== -std::numeric_limits
<float>::infinity()) {
2041 clusterStart
= inlineCoord
;
2043 if (aShapedText
->CharMayHaveEmphasisMark(idx
)) {
2044 shouldDrawEmphasisMark
= true;
2046 inlineCoord
+= aParams
.direction
* aShapedText
->GetAdvanceForGlyph(idx
);
2047 if (shouldDrawEmphasisMark
&&
2048 (i
+ 1 == aCount
|| aShapedText
->IsClusterStart(idx
+ 1))) {
2049 float clusterAdvance
= inlineCoord
- clusterStart
;
2050 // Move the coord backward to get the needed start point.
2051 float delta
= (clusterAdvance
+ aParams
.advance
) / 2;
2052 inlineCoord
-= delta
;
2053 aParams
.mark
->Draw(markRange
, *aPt
, params
);
2054 inlineCoord
+= delta
;
2055 shouldDrawEmphasisMark
= false;
2057 if (aParams
.spacing
) {
2058 inlineCoord
+= aParams
.direction
* aParams
.spacing
[i
].mAfter
;
2063 void gfxFont::Draw(const gfxTextRun
* aTextRun
, uint32_t aStart
, uint32_t aEnd
,
2064 gfx::Point
* aPt
, const TextRunDrawParams
& aRunParams
,
2065 gfx::ShapedTextFlags aOrientation
) {
2066 NS_ASSERTION(aRunParams
.drawMode
== DrawMode::GLYPH_PATH
||
2067 !(int(aRunParams
.drawMode
) & int(DrawMode::GLYPH_PATH
)),
2068 "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or "
2069 "GLYPH_STROKE_UNDERNEATH");
2071 if (aStart
>= aEnd
) {
2075 FontDrawParams fontParams
;
2077 if (aRunParams
.drawOpts
) {
2078 fontParams
.drawOptions
= *aRunParams
.drawOpts
;
2081 if (aRunParams
.allowGDI
) {
2082 fontParams
.scaledFont
= GetScaledFont(aRunParams
.dt
);
2084 fontParams
.scaledFont
= GetScaledFontNoGDI(aRunParams
.dt
);
2086 if (!fontParams
.scaledFont
) {
2089 auto* textDrawer
= aRunParams
.context
->GetTextDrawer();
2091 fontParams
.obliqueSkew
= SkewForSyntheticOblique();
2092 fontParams
.haveSVGGlyphs
= GetFontEntry()->TryGetSVGData(this);
2093 fontParams
.haveColorGlyphs
= GetFontEntry()->TryGetColorGlyphs();
2094 fontParams
.contextPaint
= aRunParams
.runContextPaint
;
2097 fontParams
.isVerticalFont
= aRunParams
.isVerticalRun
;
2099 fontParams
.isVerticalFont
=
2100 aOrientation
== gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
;
2103 gfxContextMatrixAutoSaveRestore matrixRestore
;
2104 layout::TextDrawTarget::AutoRestoreWRGlyphFlags glyphFlagsRestore
;
2106 // Save the current baseline offset for restoring later, in case it is
2108 float& baseline
= fontParams
.isVerticalFont
? aPt
->x
: aPt
->y
;
2109 float origBaseline
= baseline
;
2111 // The point may be advanced in local-space, while the resulting point on
2112 // return must be advanced in transformed space. So save the original point so
2113 // we can properly transform the advance later.
2114 gfx::Point origPt
= *aPt
;
2115 const gfx::Matrix
* offsetMatrix
= nullptr;
2117 // Default to advancing along the +X direction (-X if RTL).
2118 fontParams
.advanceDirection
= aRunParams
.isRTL
? -1.0f
: 1.0f
;
2119 // Default to offsetting baseline downward along the +Y direction.
2120 float baselineDir
= 1.0f
;
2121 // The direction of sideways rotation, if applicable.
2122 // -1 for rotating left/counter-clockwise
2123 // 1 for rotating right/clockwise
2124 // 0 for no rotation
2126 (aOrientation
== gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
2129 gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT
2132 // If we're rendering a sideways run, we need to push a rotation transform to
2134 if (sidewaysDir
!= 0.0f
) {
2136 // For WebRender, we can't use a DrawTarget transform and must instead use
2137 // flags that locally transform the glyph, without affecting the glyph
2138 // origin. The glyph origins must thus be offset in the transformed
2139 // directions (instead of local-space directions). Modify the advance and
2140 // baseline directions to account for the indicated transform.
2142 // The default text orientation is down being +Y and right being +X.
2143 // Rotating 90 degrees left/CCW makes down be +X and right be -Y.
2144 // Rotating 90 degrees right/CW makes down be -X and right be +Y.
2145 // Thus the advance direction (moving right) is just sidewaysDir,
2146 // i.e. negative along Y axis if rotated left and positive if
2148 fontParams
.advanceDirection
*= sidewaysDir
;
2149 // The baseline direction (moving down) is negated relative to the
2150 // advance direction for sideways transforms.
2151 baselineDir
*= -sidewaysDir
;
2153 glyphFlagsRestore
.Save(textDrawer
);
2154 // Set the transform flags accordingly. Both sideways rotations transpose
2155 // X and Y, while left rotation flips the resulting Y axis, and right
2156 // rotation flips the resulting X axis.
2157 textDrawer
->SetWRGlyphFlags(
2158 textDrawer
->GetWRGlyphFlags() | wr::FontInstanceFlags::TRANSPOSE
|
2160 gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
2161 ? wr::FontInstanceFlags::FLIP_Y
2162 : wr::FontInstanceFlags::FLIP_X
));
2163 // We also need to set up a transform for the glyph offset vector that
2164 // may be present in DetailedGlyph records.
2165 static const gfx::Matrix kSidewaysLeft
= {0, -1, 1, 0, 0, 0};
2166 static const gfx::Matrix kSidewaysRight
= {0, 1, -1, 0, 0, 0};
2168 (aOrientation
== ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
)
2172 // For non-WebRender targets, just push a rotation transform.
2173 matrixRestore
.SetContext(aRunParams
.context
);
2174 gfxPoint
p(aPt
->x
* aRunParams
.devPerApp
, aPt
->y
* aRunParams
.devPerApp
);
2175 // Get a matrix we can use to draw the (horizontally-shaped) textrun
2176 // with 90-degree CW rotation.
2177 const gfxFloat rotation
= sidewaysDir
* M_PI
/ 2.0f
;
2178 gfxMatrix mat
= aRunParams
.context
->CurrentMatrixDouble()
2180 . // translate origin for rotation
2182 . // turn 90deg CCW (sideways-left) or CW (*-right)
2183 PreTranslate(-p
); // undo the translation
2185 aRunParams
.context
->SetMatrixDouble(mat
);
2188 // If we're drawing rotated horizontal text for an element styled
2189 // text-orientation:mixed, the dominant baseline will be vertical-
2190 // centered. So in this case, we need to adjust the position so that
2191 // the rotated horizontal text (which uses an alphabetic baseline) will
2192 // look OK when juxtaposed with upright glyphs (rendered on a centered
2193 // vertical baseline). The adjustment here is somewhat ad hoc; we
2194 // should eventually look for baseline tables[1] in the fonts and use
2195 // those if available.
2196 // [1] See http://www.microsoft.com/typography/otspec/base.htm
2197 if (aTextRun
->UseCenterBaseline()) {
2198 const Metrics
& metrics
= GetMetrics(nsFontMetrics::eHorizontal
);
2199 float baseAdj
= (metrics
.emAscent
- metrics
.emDescent
) / 2;
2200 baseline
+= baseAdj
* aTextRun
->GetAppUnitsPerDevUnit() * baselineDir
;
2202 } else if (textDrawer
&&
2203 aOrientation
== ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
) {
2204 glyphFlagsRestore
.Save(textDrawer
);
2205 textDrawer
->SetWRGlyphFlags(textDrawer
->GetWRGlyphFlags() |
2206 wr::FontInstanceFlags::VERTICAL
);
2209 if (fontParams
.obliqueSkew
!= 0.0f
&& !fontParams
.isVerticalFont
&&
2211 // Adjust matrix for synthetic-oblique, except if we're doing vertical-
2212 // upright text, in which case this will be handled for each glyph
2213 // individually in DrawOneGlyph.
2214 if (!matrixRestore
.HasMatrix()) {
2215 matrixRestore
.SetContext(aRunParams
.context
);
2217 gfx::Point
p(aPt
->x
* aRunParams
.devPerApp
, aPt
->y
* aRunParams
.devPerApp
);
2219 aRunParams
.context
->CurrentMatrix()
2221 .PreMultiply(gfx::Matrix(1, 0, -fontParams
.obliqueSkew
, 1, 0, 0))
2223 aRunParams
.context
->SetMatrix(mat
);
2226 RefPtr
<SVGContextPaint
> contextPaint
;
2227 if (fontParams
.haveSVGGlyphs
&& !fontParams
.contextPaint
) {
2228 // If no pattern is specified for fill, use the current pattern
2229 NS_ASSERTION((int(aRunParams
.drawMode
) & int(DrawMode::GLYPH_STROKE
)) == 0,
2230 "no pattern supplied for stroking text");
2231 RefPtr
<gfxPattern
> fillPattern
= aRunParams
.context
->GetPattern();
2232 contextPaint
= new SimpleTextContextPaint(
2233 fillPattern
, nullptr, aRunParams
.context
->CurrentMatrixDouble());
2234 fontParams
.contextPaint
= contextPaint
.get();
2237 // Synthetic-bold strikes are each offset one device pixel in run direction.
2238 // (these values are only needed if IsSyntheticBold() is true)
2239 // WebRender handles synthetic bold independently via FontInstanceFlags,
2240 // so just ignore requests in that case.
2241 if (IsSyntheticBold() && !textDrawer
) {
2242 gfx::Float xscale
= CalcXScale(aRunParams
.context
->GetDrawTarget());
2243 fontParams
.synBoldOnePixelOffset
= aRunParams
.direction
* xscale
;
2244 if (xscale
!= 0.0) {
2245 static const int32_t kMaxExtraStrikes
= 128;
2246 gfxFloat extraStrikes
= GetSyntheticBoldOffset() / xscale
;
2247 if (extraStrikes
> kMaxExtraStrikes
) {
2248 // if too many strikes are required, limit them and increase the step
2249 // size to compensate
2250 fontParams
.extraStrikes
= kMaxExtraStrikes
;
2251 fontParams
.synBoldOnePixelOffset
= aRunParams
.direction
*
2252 GetSyntheticBoldOffset() /
2253 fontParams
.extraStrikes
;
2255 // use as many strikes as needed for the increased advance
2256 fontParams
.extraStrikes
= NS_lroundf(std::max(1.0, extraStrikes
));
2260 fontParams
.synBoldOnePixelOffset
= 0;
2261 fontParams
.extraStrikes
= 0;
2264 bool oldSubpixelAA
= aRunParams
.dt
->GetPermitSubpixelAA();
2265 if (!AllowSubpixelAA()) {
2266 aRunParams
.dt
->SetPermitSubpixelAA(false);
2270 Matrix oldMat
= aRunParams
.dt
->GetTransform();
2272 fontParams
.drawOptions
.mAntialiasMode
= Get2DAAMode(mAntialiasOption
);
2274 if (mStyle
.baselineOffset
!= 0.0) {
2276 mStyle
.baselineOffset
* aTextRun
->GetAppUnitsPerDevUnit() * baselineDir
;
2281 // Select appropriate version of the templated DrawGlyphs method
2282 // to output glyphs to the buffer, depending on complexity needed
2283 // for the type of font, and whether added inter-glyph spacing
2285 GlyphBufferAzure
buffer(aRunParams
, fontParams
);
2286 if (fontParams
.haveSVGGlyphs
|| fontParams
.haveColorGlyphs
||
2287 fontParams
.extraStrikes
||
2288 (fontParams
.obliqueSkew
!= 0.0f
&& fontParams
.isVerticalFont
&&
2290 if (aRunParams
.spacing
) {
2292 DrawGlyphs
<FontComplexityT::ComplexFont
, SpacingT::HasSpacing
>(
2293 aTextRun
, aStart
, aEnd
- aStart
, aPt
, offsetMatrix
, buffer
);
2296 DrawGlyphs
<FontComplexityT::ComplexFont
, SpacingT::NoSpacing
>(
2297 aTextRun
, aStart
, aEnd
- aStart
, aPt
, offsetMatrix
, buffer
);
2300 if (aRunParams
.spacing
) {
2302 DrawGlyphs
<FontComplexityT::SimpleFont
, SpacingT::HasSpacing
>(
2303 aTextRun
, aStart
, aEnd
- aStart
, aPt
, offsetMatrix
, buffer
);
2306 DrawGlyphs
<FontComplexityT::SimpleFont
, SpacingT::NoSpacing
>(
2307 aTextRun
, aStart
, aEnd
- aStart
, aPt
, offsetMatrix
, buffer
);
2312 baseline
= origBaseline
;
2314 if (aRunParams
.callbacks
&& emittedGlyphs
) {
2315 aRunParams
.callbacks
->NotifyGlyphPathEmitted();
2318 aRunParams
.dt
->SetTransform(oldMat
);
2319 aRunParams
.dt
->SetPermitSubpixelAA(oldSubpixelAA
);
2321 if (sidewaysDir
!= 0.0f
&& !textDrawer
) {
2322 // Adjust updated aPt to account for the transform we were using.
2323 // The advance happened horizontally in local-space, but the transformed
2324 // sideways advance is actually vertical, with sign depending on the
2325 // direction of rotation.
2326 float advance
= aPt
->x
- origPt
.x
;
2327 *aPt
= gfx::Point(origPt
.x
, origPt
.y
+ advance
* sidewaysDir
);
2331 bool gfxFont::RenderSVGGlyph(gfxContext
* aContext
,
2332 layout::TextDrawTarget
* aTextDrawer
,
2333 gfx::Point aPoint
, uint32_t aGlyphId
,
2334 SVGContextPaint
* aContextPaint
) const {
2335 if (!GetFontEntry()->HasSVGGlyph(aGlyphId
)) {
2340 // WebRender doesn't support SVG Glyphs.
2341 // (pretend to succeed, output doesn't matter, we will emit a blob)
2342 aTextDrawer
->FoundUnsupportedFeature();
2346 const gfxFloat devUnitsPerSVGUnit
=
2347 GetAdjustedSize() / GetFontEntry()->UnitsPerEm();
2348 gfxContextMatrixAutoSaveRestore
matrixRestore(aContext
);
2350 aContext
->SetMatrix(aContext
->CurrentMatrix()
2351 .PreTranslate(aPoint
.x
, aPoint
.y
)
2352 .PreScale(devUnitsPerSVGUnit
, devUnitsPerSVGUnit
));
2354 aContextPaint
->InitStrokeGeometry(aContext
, devUnitsPerSVGUnit
);
2356 GetFontEntry()->RenderSVGGlyph(aContext
, aGlyphId
, aContextPaint
);
2357 aContext
->NewPath();
2361 bool gfxFont::RenderSVGGlyph(gfxContext
* aContext
,
2362 layout::TextDrawTarget
* aTextDrawer
,
2363 gfx::Point aPoint
, uint32_t aGlyphId
,
2364 SVGContextPaint
* aContextPaint
,
2365 gfxTextRunDrawCallbacks
* aCallbacks
,
2366 bool& aEmittedGlyphs
) const {
2367 if (aCallbacks
&& aEmittedGlyphs
) {
2368 aCallbacks
->NotifyGlyphPathEmitted();
2369 aEmittedGlyphs
= false;
2371 return RenderSVGGlyph(aContext
, aTextDrawer
, aPoint
, aGlyphId
, aContextPaint
);
2374 bool gfxFont::RenderColorGlyph(DrawTarget
* aDrawTarget
, gfxContext
* aContext
,
2375 layout::TextDrawTarget
* aTextDrawer
,
2376 mozilla::gfx::ScaledFont
* scaledFont
,
2377 mozilla::gfx::DrawOptions aDrawOptions
,
2378 const mozilla::gfx::Point
& aPoint
,
2379 uint32_t aGlyphId
) const {
2380 AutoTArray
<uint16_t, 8> layerGlyphs
;
2381 AutoTArray
<mozilla::gfx::DeviceColor
, 8> layerColors
;
2383 mozilla::gfx::DeviceColor defaultColor
;
2384 if (!aContext
->GetDeviceColor(defaultColor
)) {
2385 defaultColor
= ToDeviceColor(mozilla::gfx::sRGBColor::OpaqueBlack());
2387 if (!GetFontEntry()->GetColorLayersInfo(aGlyphId
, defaultColor
, layerGlyphs
,
2392 // Default to opaque rendering (non-webrender applies alpha with a layer)
2395 // defaultColor is the one that comes from CSS, so it has transparency info.
2396 bool hasComplexTransparency
= 0.f
< defaultColor
.a
&& defaultColor
.a
< 1.f
;
2397 if (hasComplexTransparency
&& layerGlyphs
.Length() > 1) {
2398 // WebRender doesn't support drawing multi-layer transparent color-glyphs,
2399 // as it requires compositing all the layers before applying transparency.
2400 // (pretend to succeed, output doesn't matter, we will emit a blob)
2401 aTextDrawer
->FoundUnsupportedFeature();
2405 // If we get here, then either alpha is 0 or 1, or there's only one layer
2406 // which shouldn't have composition issues. In all of these cases, applying
2407 // transparency directly to the glyph should work perfectly fine.
2409 // Note that we must still emit completely transparent emoji, because they
2410 // might be wrapped in a shadow that uses the text run's glyphs.
2411 alpha
= defaultColor
.a
;
2414 for (uint32_t layerIndex
= 0; layerIndex
< layerGlyphs
.Length();
2417 glyph
.mIndex
= layerGlyphs
[layerIndex
];
2418 glyph
.mPosition
= aPoint
;
2420 mozilla::gfx::GlyphBuffer buffer
;
2421 buffer
.mGlyphs
= &glyph
;
2422 buffer
.mNumGlyphs
= 1;
2424 mozilla::gfx::DeviceColor layerColor
= layerColors
[layerIndex
];
2425 layerColor
.a
*= alpha
;
2426 aDrawTarget
->FillGlyphs(scaledFont
, buffer
, ColorPattern(layerColor
),
2432 bool gfxFont::HasColorGlyphFor(uint32_t aCh
, uint32_t aNextCh
) {
2433 // Bitmap fonts are assumed to provide "color" glyphs for all supported chars.
2434 gfxFontEntry
* fe
= GetFontEntry();
2435 if (fe
->HasColorBitmapTable()) {
2438 // Use harfbuzz shaper to look up the default glyph ID for the character.
2439 if (!mHarfBuzzShaper
) {
2440 mHarfBuzzShaper
= MakeUnique
<gfxHarfBuzzShaper
>(this);
2442 auto* shaper
= static_cast<gfxHarfBuzzShaper
*>(mHarfBuzzShaper
.get());
2443 if (!shaper
->Initialize()) {
2447 if (gfxFontUtils::IsVarSelector(aNextCh
)) {
2448 gid
= shaper
->GetVariationGlyph(aCh
, aNextCh
);
2451 gid
= shaper
->GetNominalGlyph(aCh
);
2456 // Check if there is a COLR/CPAL or SVG glyph for this ID.
2457 if (fe
->TryGetColorGlyphs() && fe
->HasColorLayersForGlyph(gid
)) {
2460 if (fe
->TryGetSVGData(this) && fe
->HasSVGGlyph(gid
)) {
2466 static void UnionRange(gfxFloat aX
, gfxFloat
* aDestMin
, gfxFloat
* aDestMax
) {
2467 *aDestMin
= std::min(*aDestMin
, aX
);
2468 *aDestMax
= std::max(*aDestMax
, aX
);
2471 // We get precise glyph extents if the textrun creator requested them, or
2472 // if the font is a user font --- in which case the author may be relying
2473 // on overflowing glyphs.
2474 static bool NeedsGlyphExtents(gfxFont
* aFont
, const gfxTextRun
* aTextRun
) {
2475 return (aTextRun
->GetFlags() &
2476 gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX
) ||
2477 aFont
->GetFontEntry()->IsUserFont();
2480 bool gfxFont::IsSpaceGlyphInvisible(DrawTarget
* aRefDrawTarget
,
2481 const gfxTextRun
* aTextRun
) {
2482 if (!mFontEntry
->mSpaceGlyphIsInvisibleInitialized
&&
2483 GetAdjustedSize() >= 1.0) {
2484 gfxGlyphExtents
* extents
=
2485 GetOrCreateGlyphExtents(aTextRun
->GetAppUnitsPerDevUnit());
2486 gfxRect glyphExtents
;
2487 mFontEntry
->mSpaceGlyphIsInvisible
=
2488 extents
->GetTightGlyphExtentsAppUnits(this, aRefDrawTarget
,
2489 GetSpaceGlyph(), &glyphExtents
) &&
2490 glyphExtents
.IsEmpty();
2491 mFontEntry
->mSpaceGlyphIsInvisibleInitialized
= true;
2493 return mFontEntry
->mSpaceGlyphIsInvisible
;
2496 gfxFont::RunMetrics
gfxFont::Measure(const gfxTextRun
* aTextRun
,
2497 uint32_t aStart
, uint32_t aEnd
,
2498 BoundingBoxType aBoundingBoxType
,
2499 DrawTarget
* aRefDrawTarget
,
2501 gfx::ShapedTextFlags aOrientation
) {
2502 // If aBoundingBoxType is TIGHT_HINTED_OUTLINE_EXTENTS
2503 // and the underlying cairo font may be antialiased,
2504 // we need to create a copy in order to avoid getting cached extents.
2505 // This is only used by MathML layout at present.
2506 if (aBoundingBoxType
== TIGHT_HINTED_OUTLINE_EXTENTS
&&
2507 mAntialiasOption
!= kAntialiasNone
) {
2509 mNonAAFont
= CopyWithAntialiasOption(kAntialiasNone
);
2511 // if font subclass doesn't implement CopyWithAntialiasOption(),
2512 // it will return null and we'll proceed to use the existing font
2514 return mNonAAFont
->Measure(aTextRun
, aStart
, aEnd
,
2515 TIGHT_HINTED_OUTLINE_EXTENTS
, aRefDrawTarget
,
2516 aSpacing
, aOrientation
);
2520 const int32_t appUnitsPerDevUnit
= aTextRun
->GetAppUnitsPerDevUnit();
2521 // Current position in appunits
2522 Orientation orientation
=
2523 aOrientation
== gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
2524 ? nsFontMetrics::eVertical
2525 : nsFontMetrics::eHorizontal
;
2526 const gfxFont::Metrics
& fontMetrics
= GetMetrics(orientation
);
2528 gfxFloat baselineOffset
= 0;
2529 if (aTextRun
->UseCenterBaseline() &&
2530 orientation
== nsFontMetrics::eHorizontal
) {
2531 // For a horizontal font being used in vertical writing mode with
2532 // text-orientation:mixed, the overall metrics we're accumulating
2533 // will be aimed at a center baseline. But this font's metrics were
2534 // based on the alphabetic baseline. So we compute a baseline offset
2535 // that will be applied to ascent/descent values and glyph rects
2536 // to effectively shift them relative to the baseline.
2537 // XXX Eventually we should probably use the BASE table, if present.
2538 // But it usually isn't, so we need an ad hoc adjustment for now.
2540 appUnitsPerDevUnit
* (fontMetrics
.emAscent
- fontMetrics
.emDescent
) / 2;
2544 metrics
.mAscent
= fontMetrics
.maxAscent
* appUnitsPerDevUnit
;
2545 metrics
.mDescent
= fontMetrics
.maxDescent
* appUnitsPerDevUnit
;
2547 if (aStart
== aEnd
) {
2548 // exit now before we look at aSpacing[0], which is undefined
2549 metrics
.mAscent
-= baselineOffset
;
2550 metrics
.mDescent
+= baselineOffset
;
2551 metrics
.mBoundingBox
=
2552 gfxRect(0, -metrics
.mAscent
, 0, metrics
.mAscent
+ metrics
.mDescent
);
2556 gfxFloat advanceMin
= 0, advanceMax
= 0;
2557 const gfxTextRun::CompressedGlyph
* charGlyphs
=
2558 aTextRun
->GetCharacterGlyphs();
2559 bool isRTL
= aTextRun
->IsRightToLeft();
2560 bool needsGlyphExtents
= NeedsGlyphExtents(this, aTextRun
);
2561 gfxGlyphExtents
* extents
=
2562 ((aBoundingBoxType
== LOOSE_INK_EXTENTS
&& !needsGlyphExtents
&&
2563 !aTextRun
->HasDetailedGlyphs()) ||
2564 MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero()))
2566 : GetOrCreateGlyphExtents(aTextRun
->GetAppUnitsPerDevUnit());
2569 x
+= aSpacing
[0].mBefore
;
2571 uint32_t spaceGlyph
= GetSpaceGlyph();
2572 bool allGlyphsInvisible
= true;
2574 for (i
= aStart
; i
< aEnd
; ++i
) {
2575 const gfxTextRun::CompressedGlyph
* glyphData
= &charGlyphs
[i
];
2576 if (glyphData
->IsSimpleGlyph()) {
2577 double advance
= glyphData
->GetSimpleAdvance();
2578 uint32_t glyphIndex
= glyphData
->GetSimpleGlyph();
2579 if (glyphIndex
!= spaceGlyph
||
2580 !IsSpaceGlyphInvisible(aRefDrawTarget
, aTextRun
)) {
2581 allGlyphsInvisible
= false;
2583 // Only get the real glyph horizontal extent if we were asked
2584 // for the tight bounding box or we're in quality mode
2585 if ((aBoundingBoxType
!= LOOSE_INK_EXTENTS
|| needsGlyphExtents
) &&
2587 uint16_t extentsWidth
=
2588 extents
->GetContainedGlyphWidthAppUnits(glyphIndex
);
2589 if (extentsWidth
!= gfxGlyphExtents::INVALID_WIDTH
&&
2590 aBoundingBoxType
== LOOSE_INK_EXTENTS
) {
2591 UnionRange(x
, &advanceMin
, &advanceMax
);
2592 UnionRange(x
+ extentsWidth
, &advanceMin
, &advanceMax
);
2595 if (!extents
->GetTightGlyphExtentsAppUnits(this, aRefDrawTarget
,
2596 glyphIndex
, &glyphRect
)) {
2597 glyphRect
= gfxRect(0, metrics
.mBoundingBox
.Y(), advance
,
2598 metrics
.mBoundingBox
.Height());
2601 // In effect, swap left and right sidebearings of the glyph, for
2602 // proper accumulation of potentially-overlapping glyph rects.
2603 glyphRect
.MoveToX(advance
- glyphRect
.XMost());
2605 glyphRect
.MoveByX(x
);
2606 metrics
.mBoundingBox
= metrics
.mBoundingBox
.Union(glyphRect
);
2611 allGlyphsInvisible
= false;
2612 uint32_t glyphCount
= glyphData
->GetGlyphCount();
2613 if (glyphCount
> 0) {
2614 const gfxTextRun::DetailedGlyph
* details
=
2615 aTextRun
->GetDetailedGlyphs(i
);
2616 NS_ASSERTION(details
!= nullptr,
2617 "detailedGlyph record should not be missing!");
2619 for (j
= 0; j
< glyphCount
; ++j
, ++details
) {
2620 uint32_t glyphIndex
= details
->mGlyphID
;
2621 double advance
= details
->mAdvance
;
2623 if (glyphData
->IsMissing() || !extents
||
2624 !extents
->GetTightGlyphExtentsAppUnits(this, aRefDrawTarget
,
2625 glyphIndex
, &glyphRect
)) {
2626 // We might have failed to get glyph extents due to
2628 glyphRect
= gfxRect(0, -metrics
.mAscent
, advance
,
2629 metrics
.mAscent
+ metrics
.mDescent
);
2632 // Swap left/right sidebearings of the glyph, because we're doing
2633 // mirrored measurement.
2634 glyphRect
.MoveToX(advance
- glyphRect
.XMost());
2635 // Move to current x position, mirroring any x-offset amount.
2636 glyphRect
.MoveByX(x
- details
->mOffset
.x
);
2638 glyphRect
.MoveByX(x
+ details
->mOffset
.x
);
2640 glyphRect
.MoveByY(details
->mOffset
.y
);
2641 metrics
.mBoundingBox
= metrics
.mBoundingBox
.Union(glyphRect
);
2646 // Every other glyph type is ignored
2648 double space
= aSpacing
[i
- aStart
].mAfter
;
2650 space
+= aSpacing
[i
+ 1 - aStart
].mBefore
;
2656 if (allGlyphsInvisible
) {
2657 metrics
.mBoundingBox
.SetEmpty();
2659 if (aBoundingBoxType
== LOOSE_INK_EXTENTS
) {
2660 UnionRange(x
, &advanceMin
, &advanceMax
);
2661 gfxRect
fontBox(advanceMin
, -metrics
.mAscent
, advanceMax
- advanceMin
,
2662 metrics
.mAscent
+ metrics
.mDescent
);
2663 metrics
.mBoundingBox
= metrics
.mBoundingBox
.Union(fontBox
);
2668 // Reverse the effect of having swapped each glyph's sidebearings, to get
2669 // the correct sidebearings of the merged bounding box.
2670 metrics
.mBoundingBox
.MoveToX(x
- metrics
.mBoundingBox
.XMost());
2673 // If the font may be rendered with a fake-italic effect, we need to allow
2674 // for the top-right of the glyphs being skewed to the right, and the
2675 // bottom-left being skewed further left.
2676 gfxFloat skew
= SkewForSyntheticOblique();
2678 gfxFloat extendLeftEdge
, extendRightEdge
;
2679 if (orientation
== nsFontMetrics::eVertical
) {
2680 // The glyph will actually be skewed vertically, but "left" and "right"
2681 // here refer to line-left (physical top) and -right (bottom), so these
2682 // are still the directions in which we need to extend the box.
2683 extendLeftEdge
= skew
< 0.0 ? ceil(-skew
* metrics
.mBoundingBox
.XMost())
2684 : ceil(skew
* -metrics
.mBoundingBox
.X());
2685 extendRightEdge
= skew
< 0.0 ? ceil(-skew
* -metrics
.mBoundingBox
.X())
2686 : ceil(skew
* metrics
.mBoundingBox
.XMost());
2688 extendLeftEdge
= skew
< 0.0 ? ceil(-skew
* -metrics
.mBoundingBox
.Y())
2689 : ceil(skew
* metrics
.mBoundingBox
.YMost());
2690 extendRightEdge
= skew
< 0.0 ? ceil(-skew
* metrics
.mBoundingBox
.YMost())
2691 : ceil(skew
* -metrics
.mBoundingBox
.Y());
2693 metrics
.mBoundingBox
.SetWidth(metrics
.mBoundingBox
.Width() +
2694 extendLeftEdge
+ extendRightEdge
);
2695 metrics
.mBoundingBox
.MoveByX(-extendLeftEdge
);
2698 if (baselineOffset
!= 0) {
2699 metrics
.mAscent
-= baselineOffset
;
2700 metrics
.mDescent
+= baselineOffset
;
2701 metrics
.mBoundingBox
.MoveByY(baselineOffset
);
2704 metrics
.mAdvanceWidth
= x
;
2709 void gfxFont::AgeCachedWords() {
2711 for (auto it
= mWordCache
->Iter(); !it
.Done(); it
.Next()) {
2712 CacheHashEntry
* entry
= it
.Get();
2713 if (!entry
->mShapedWord
) {
2714 NS_ASSERTION(entry
->mShapedWord
, "cache entry has no gfxShapedWord!");
2716 } else if (entry
->mShapedWord
->IncrementAge() == kShapedWordCacheMaxAge
) {
2723 void gfxFont::NotifyGlyphsChanged() {
2724 uint32_t i
, count
= mGlyphExtentsArray
.Length();
2725 for (i
= 0; i
< count
; ++i
) {
2726 // Flush cached extents array
2727 mGlyphExtentsArray
[i
]->NotifyGlyphsChanged();
2730 if (mGlyphChangeObservers
) {
2731 for (const auto& key
: *mGlyphChangeObservers
) {
2732 key
->NotifyGlyphsChanged();
2737 // If aChar is a "word boundary" for shaped-word caching purposes, return it;
2739 static char16_t
IsBoundarySpace(char16_t aChar
, char16_t aNextChar
) {
2740 if ((aChar
== ' ' || aChar
== 0x00A0) && !IsClusterExtender(aNextChar
)) {
2747 # define GFX_MAYBE_UNUSED __attribute__((unused))
2749 # define GFX_MAYBE_UNUSED
2752 template <typename T
>
2753 gfxShapedWord
* gfxFont::GetShapedWord(
2754 DrawTarget
* aDrawTarget
, const T
* aText
, uint32_t aLength
, uint32_t aHash
,
2755 Script aRunScript
, nsAtom
* aLanguage
, bool aVertical
,
2756 int32_t aAppUnitsPerDevUnit
, gfx::ShapedTextFlags aFlags
,
2757 RoundingFlags aRounding
, gfxTextPerfMetrics
* aTextPerf GFX_MAYBE_UNUSED
) {
2758 // if the cache is getting too big, flush it and start over
2759 uint32_t wordCacheMaxEntries
=
2760 gfxPlatform::GetPlatform()->WordCacheMaxEntries();
2761 if (mWordCache
->Count() > wordCacheMaxEntries
) {
2762 NS_WARNING("flushing shaped-word cache");
2766 // if there's a cached entry for this word, just return it
2767 CacheHashKey
key(aText
, aLength
, aHash
, aRunScript
, aLanguage
,
2768 aAppUnitsPerDevUnit
, aFlags
, aRounding
);
2770 CacheHashEntry
* entry
= mWordCache
->PutEntry(key
, fallible
);
2772 NS_WARNING("failed to create word cache entry - expect missing text");
2775 gfxShapedWord
* sw
= entry
->mShapedWord
.get();
2779 #ifndef RELEASE_OR_BETA
2781 aTextPerf
->current
.wordCacheHit
++;
2787 #ifndef RELEASE_OR_BETA
2789 aTextPerf
->current
.wordCacheMiss
++;
2793 sw
= gfxShapedWord::Create(aText
, aLength
, aRunScript
, aLanguage
,
2794 aAppUnitsPerDevUnit
, aFlags
, aRounding
);
2795 entry
->mShapedWord
.reset(sw
);
2797 NS_WARNING("failed to create gfxShapedWord - expect missing text");
2801 DebugOnly
<bool> ok
= ShapeText(aDrawTarget
, aText
, 0, aLength
, aRunScript
,
2802 aLanguage
, aVertical
, aRounding
, sw
);
2804 NS_WARNING_ASSERTION(ok
, "failed to shape word - expect garbled text");
2809 template gfxShapedWord
* gfxFont::GetShapedWord(
2810 DrawTarget
* aDrawTarget
, const uint8_t* aText
, uint32_t aLength
,
2811 uint32_t aHash
, Script aRunScript
, nsAtom
* aLanguage
, bool aVertical
,
2812 int32_t aAppUnitsPerDevUnit
, gfx::ShapedTextFlags aFlags
,
2813 RoundingFlags aRounding
, gfxTextPerfMetrics
* aTextPerf
);
2815 bool gfxFont::CacheHashEntry::KeyEquals(const KeyTypePointer aKey
) const {
2816 const gfxShapedWord
* sw
= mShapedWord
.get();
2820 if (sw
->GetLength() != aKey
->mLength
|| sw
->GetFlags() != aKey
->mFlags
||
2821 sw
->GetRounding() != aKey
->mRounding
||
2822 sw
->GetAppUnitsPerDevUnit() != aKey
->mAppUnitsPerDevUnit
||
2823 sw
->GetScript() != aKey
->mScript
||
2824 sw
->GetLanguage() != aKey
->mLanguage
) {
2827 if (sw
->TextIs8Bit()) {
2828 if (aKey
->mTextIs8Bit
) {
2829 return (0 == memcmp(sw
->Text8Bit(), aKey
->mText
.mSingle
,
2830 aKey
->mLength
* sizeof(uint8_t)));
2832 // The key has 16-bit text, even though all the characters are < 256,
2833 // so the TEXT_IS_8BIT flag was set and the cached ShapedWord we're
2834 // comparing with will have 8-bit text.
2835 const uint8_t* s1
= sw
->Text8Bit();
2836 const char16_t
* s2
= aKey
->mText
.mDouble
;
2837 const char16_t
* s2end
= s2
+ aKey
->mLength
;
2838 while (s2
< s2end
) {
2839 if (*s1
++ != *s2
++) {
2845 NS_ASSERTION(!(aKey
->mFlags
& gfx::ShapedTextFlags::TEXT_IS_8BIT
) &&
2847 "didn't expect 8-bit text here");
2848 return (0 == memcmp(sw
->TextUnicode(), aKey
->mText
.mDouble
,
2849 aKey
->mLength
* sizeof(char16_t
)));
2852 bool gfxFont::ShapeText(DrawTarget
* aDrawTarget
, const uint8_t* aText
,
2853 uint32_t aOffset
, uint32_t aLength
, Script aScript
,
2854 nsAtom
* aLanguage
, bool aVertical
,
2855 RoundingFlags aRounding
, gfxShapedText
* aShapedText
) {
2856 nsDependentCSubstring
ascii((const char*)aText
, aLength
);
2858 AppendASCIItoUTF16(ascii
, utf16
);
2859 if (utf16
.Length() != aLength
) {
2862 return ShapeText(aDrawTarget
, utf16
.BeginReading(), aOffset
, aLength
, aScript
,
2863 aLanguage
, aVertical
, aRounding
, aShapedText
);
2866 bool gfxFont::ShapeText(DrawTarget
* aDrawTarget
, const char16_t
* aText
,
2867 uint32_t aOffset
, uint32_t aLength
, Script aScript
,
2868 nsAtom
* aLanguage
, bool aVertical
,
2869 RoundingFlags aRounding
, gfxShapedText
* aShapedText
) {
2870 // XXX Currently, we do all vertical shaping through harfbuzz.
2871 // Vertical graphite support may be wanted as a future enhancement.
2872 if (FontCanSupportGraphite() && !aVertical
) {
2873 if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
2874 if (!mGraphiteShaper
) {
2875 mGraphiteShaper
= MakeUnique
<gfxGraphiteShaper
>(this);
2876 Telemetry::ScalarAdd(Telemetry::ScalarID::BROWSER_USAGE_GRAPHITE
, 1);
2878 if (mGraphiteShaper
->ShapeText(aDrawTarget
, aText
, aOffset
, aLength
,
2879 aScript
, aLanguage
, aVertical
, aRounding
,
2881 PostShapingFixup(aDrawTarget
, aText
, aOffset
, aLength
, aVertical
,
2888 if (!mHarfBuzzShaper
) {
2889 mHarfBuzzShaper
= MakeUnique
<gfxHarfBuzzShaper
>(this);
2891 if (mHarfBuzzShaper
->ShapeText(aDrawTarget
, aText
, aOffset
, aLength
, aScript
,
2892 aLanguage
, aVertical
, aRounding
,
2894 PostShapingFixup(aDrawTarget
, aText
, aOffset
, aLength
, aVertical
,
2896 if (GetFontEntry()->HasTrackingTable()) {
2897 // Convert font size from device pixels back to CSS px
2898 // to use in selecting tracking value
2899 float trackSize
= GetAdjustedSize() *
2900 aShapedText
->GetAppUnitsPerDevUnit() /
2901 AppUnitsPerCSSPixel();
2903 GetFontEntry()->TrackingForCSSPx(trackSize
) * mFUnitsConvFactor
;
2904 // Applying tracking is a lot like the adjustment we do for
2905 // synthetic bold: we want to apply between clusters, not to
2906 // non-spacing glyphs within a cluster. So we can reuse that
2908 aShapedText
->AdjustAdvancesForSyntheticBold(tracking
, aOffset
, aLength
);
2913 NS_WARNING_ASSERTION(false, "shaper failed, expect scrambled/missing text");
2917 void gfxFont::PostShapingFixup(DrawTarget
* aDrawTarget
, const char16_t
* aText
,
2918 uint32_t aOffset
, uint32_t aLength
,
2919 bool aVertical
, gfxShapedText
* aShapedText
) {
2920 if (IsSyntheticBold()) {
2921 const Metrics
& metrics
= GetMetrics(aVertical
? nsFontMetrics::eVertical
2922 : nsFontMetrics::eHorizontal
);
2923 if (metrics
.maxAdvance
> metrics
.aveCharWidth
) {
2924 float synBoldOffset
= GetSyntheticBoldOffset() * CalcXScale(aDrawTarget
);
2925 aShapedText
->AdjustAdvancesForSyntheticBold(synBoldOffset
, aOffset
,
2931 #define MAX_SHAPING_LENGTH \
2932 32760 // slightly less than 32K, trying to avoid
2933 // over-stressing platform shapers
2934 #define BACKTRACK_LIMIT \
2935 16 // backtrack this far looking for a good place
2936 // to split into fragments for separate shaping
2938 template <typename T
>
2939 bool gfxFont::ShapeFragmentWithoutWordCache(DrawTarget
* aDrawTarget
,
2940 const T
* aText
, uint32_t aOffset
,
2941 uint32_t aLength
, Script aScript
,
2942 nsAtom
* aLanguage
, bool aVertical
,
2943 RoundingFlags aRounding
,
2944 gfxTextRun
* aTextRun
) {
2945 aTextRun
->SetupClusterBoundaries(aOffset
, aText
, aLength
);
2949 while (ok
&& aLength
> 0) {
2950 uint32_t fragLen
= aLength
;
2952 // limit the length of text we pass to shapers in a single call
2953 if (fragLen
> MAX_SHAPING_LENGTH
) {
2954 fragLen
= MAX_SHAPING_LENGTH
;
2956 // in the 8-bit case, there are no multi-char clusters,
2957 // so we don't need to do this check
2958 if (sizeof(T
) == sizeof(char16_t
)) {
2960 for (i
= 0; i
< BACKTRACK_LIMIT
; ++i
) {
2961 if (aTextRun
->IsClusterStart(aOffset
+ fragLen
- i
)) {
2966 if (i
== BACKTRACK_LIMIT
) {
2967 // if we didn't find any cluster start while backtracking,
2968 // just check that we're not in the middle of a surrogate
2969 // pair; back up by one code unit if we are.
2970 if (NS_IS_SURROGATE_PAIR(aText
[fragLen
- 1], aText
[fragLen
])) {
2977 ok
= ShapeText(aDrawTarget
, aText
, aOffset
, fragLen
, aScript
, aLanguage
,
2978 aVertical
, aRounding
, aTextRun
);
2988 // Check if aCh is an unhandled control character that should be displayed
2989 // as a hexbox rather than rendered by some random font on the system.
2990 // We exclude \r as stray s are rather common (bug 941940).
2991 // Note that \n and \t don't come through here, as they have specific
2992 // meanings that have already been handled.
2993 static bool IsInvalidControlChar(uint32_t aCh
) {
2994 return aCh
!= '\r' && ((aCh
& 0x7f) < 0x20 || aCh
== 0x7f);
2997 template <typename T
>
2998 bool gfxFont::ShapeTextWithoutWordCache(DrawTarget
* aDrawTarget
, const T
* aText
,
2999 uint32_t aOffset
, uint32_t aLength
,
3000 Script aScript
, nsAtom
* aLanguage
,
3001 bool aVertical
, RoundingFlags aRounding
,
3002 gfxTextRun
* aTextRun
) {
3003 uint32_t fragStart
= 0;
3006 for (uint32_t i
= 0; i
<= aLength
&& ok
; ++i
) {
3007 T ch
= (i
< aLength
) ? aText
[i
] : '\n';
3008 bool invalid
= gfxFontGroup::IsInvalidChar(ch
);
3009 uint32_t length
= i
- fragStart
;
3011 // break into separate fragments when we hit an invalid char
3017 ok
= ShapeFragmentWithoutWordCache(
3018 aDrawTarget
, aText
+ fragStart
, aOffset
+ fragStart
, length
, aScript
,
3019 aLanguage
, aVertical
, aRounding
, aTextRun
);
3026 // fragment was terminated by an invalid char: skip it,
3027 // unless it's a control char that we want to show as a hexbox,
3028 // but record where TAB or NEWLINE occur
3030 aTextRun
->SetIsTab(aOffset
+ i
);
3031 } else if (ch
== '\n') {
3032 aTextRun
->SetIsNewline(aOffset
+ i
);
3033 } else if (GetGeneralCategory(ch
) == HB_UNICODE_GENERAL_CATEGORY_FORMAT
) {
3034 aTextRun
->SetIsFormattingControl(aOffset
+ i
);
3035 } else if (IsInvalidControlChar(ch
) &&
3036 !(aTextRun
->GetFlags() &
3037 gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS
)) {
3038 if (GetFontEntry()->IsUserFont() && HasCharacter(ch
)) {
3039 ShapeFragmentWithoutWordCache(aDrawTarget
, aText
+ i
, aOffset
+ i
, 1,
3040 aScript
, aLanguage
, aVertical
, aRounding
,
3043 aTextRun
->SetMissingGlyph(aOffset
+ i
, ch
, this);
3049 NS_WARNING_ASSERTION(ok
, "failed to shape text - expect garbled text");
3053 #ifndef RELEASE_OR_BETA
3054 # define TEXT_PERF_INCR(tp, m) (tp ? (tp)->current.m++ : 0)
3056 # define TEXT_PERF_INCR(tp, m)
3059 inline static bool IsChar8Bit(uint8_t /*aCh*/) { return true; }
3060 inline static bool IsChar8Bit(char16_t aCh
) { return aCh
< 0x100; }
3062 inline static bool HasSpaces(const uint8_t* aString
, uint32_t aLen
) {
3063 return memchr(aString
, 0x20, aLen
) != nullptr;
3066 inline static bool HasSpaces(const char16_t
* aString
, uint32_t aLen
) {
3067 for (const char16_t
* ch
= aString
; ch
< aString
+ aLen
; ch
++) {
3075 template <typename T
>
3076 bool gfxFont::SplitAndInitTextRun(
3077 DrawTarget
* aDrawTarget
, gfxTextRun
* aTextRun
,
3078 const T
* aString
, // text for this font run
3079 uint32_t aRunStart
, // position in the textrun
3080 uint32_t aRunLength
, Script aRunScript
, nsAtom
* aLanguage
,
3081 ShapedTextFlags aOrientation
) {
3082 if (aRunLength
== 0) {
3086 gfxTextPerfMetrics
* tp
= nullptr;
3087 RoundingFlags rounding
= GetRoundOffsetsToPixels(aDrawTarget
);
3089 #ifndef RELEASE_OR_BETA
3090 tp
= aTextRun
->GetFontGroup()->GetTextPerfMetrics();
3092 if (mStyle
.systemFont
) {
3093 tp
->current
.numChromeTextRuns
++;
3095 tp
->current
.numContentTextRuns
++;
3097 tp
->current
.numChars
+= aRunLength
;
3098 if (aRunLength
> tp
->current
.maxTextRunLen
) {
3099 tp
->current
.maxTextRunLen
= aRunLength
;
3104 uint32_t wordCacheCharLimit
=
3105 gfxPlatform::GetPlatform()->WordCacheCharLimit();
3107 bool vertical
= aOrientation
== ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
;
3109 // If spaces can participate in shaping (e.g. within lookups for automatic
3110 // fractions), need to shape without using the word cache which segments
3111 // textruns on space boundaries. Word cache can be used if the textrun
3112 // is short enough to fit in the word cache and it lacks spaces.
3113 tainted_boolean_hint t_canParticipate
=
3114 SpaceMayParticipateInShaping(aRunScript
);
3115 bool canParticipate
= t_canParticipate
.unverified_safe_because(
3116 "We need to ensure that this function operates safely independent of "
3117 "t_canParticipate. The worst that can happen here is that the decision "
3118 "to use the cache is incorrectly made, resulting in a bad "
3119 "rendering/slowness. However, this would not compromise the memory "
3120 "safety of Firefox in any way, and can thus be permitted");
3122 if (canParticipate
) {
3123 if (aRunLength
> wordCacheCharLimit
|| HasSpaces(aString
, aRunLength
)) {
3124 TEXT_PERF_INCR(tp
, wordCacheSpaceRules
);
3125 return ShapeTextWithoutWordCache(aDrawTarget
, aString
, aRunStart
,
3126 aRunLength
, aRunScript
, aLanguage
,
3127 vertical
, rounding
, aTextRun
);
3133 // the only flags we care about for ShapedWord construction/caching
3134 gfx::ShapedTextFlags flags
= aTextRun
->GetFlags();
3135 flags
&= (gfx::ShapedTextFlags::TEXT_IS_RTL
|
3136 gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES
|
3137 gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT
|
3138 gfx::ShapedTextFlags::TEXT_ORIENT_MASK
);
3139 if (sizeof(T
) == sizeof(uint8_t)) {
3140 flags
|= gfx::ShapedTextFlags::TEXT_IS_8BIT
;
3143 uint32_t wordStart
= 0;
3145 bool wordIs8Bit
= true;
3146 int32_t appUnitsPerDevUnit
= aTextRun
->GetAppUnitsPerDevUnit();
3148 T nextCh
= aString
[0];
3149 for (uint32_t i
= 0; i
<= aRunLength
; ++i
) {
3151 nextCh
= (i
< aRunLength
- 1) ? aString
[i
+ 1] : '\n';
3152 T boundary
= IsBoundarySpace(ch
, nextCh
);
3153 bool invalid
= !boundary
&& gfxFontGroup::IsInvalidChar(ch
);
3154 uint32_t length
= i
- wordStart
;
3156 // break into separate ShapedWords when we hit an invalid char,
3157 // or a boundary space (always handled individually),
3158 // or the first non-space after a space
3159 if (!boundary
&& !invalid
) {
3160 if (!IsChar8Bit(ch
)) {
3163 // include this character in the hash, and move on to next
3164 hash
= gfxShapedWord::HashMix(hash
, ch
);
3168 // We've decided to break here (i.e. we're at the end of a "word");
3169 // shape the word and add it to the textrun.
3170 // For words longer than the limit, we don't use the
3171 // font's word cache but just shape directly into the textrun.
3172 if (length
> wordCacheCharLimit
) {
3173 TEXT_PERF_INCR(tp
, wordCacheLong
);
3174 bool ok
= ShapeFragmentWithoutWordCache(
3175 aDrawTarget
, aString
+ wordStart
, aRunStart
+ wordStart
, length
,
3176 aRunScript
, aLanguage
, vertical
, rounding
, aTextRun
);
3180 } else if (length
> 0) {
3181 gfx::ShapedTextFlags wordFlags
= flags
;
3182 // in the 8-bit version of this method, TEXT_IS_8BIT was
3183 // already set as part of |flags|, so no need for a per-word
3185 if (sizeof(T
) == sizeof(char16_t
)) {
3187 wordFlags
|= gfx::ShapedTextFlags::TEXT_IS_8BIT
;
3190 gfxShapedWord
* sw
= GetShapedWord(
3191 aDrawTarget
, aString
+ wordStart
, length
, hash
, aRunScript
, aLanguage
,
3192 vertical
, appUnitsPerDevUnit
, wordFlags
, rounding
, tp
);
3194 aTextRun
->CopyGlyphDataFrom(sw
, aRunStart
+ wordStart
);
3196 return false; // failed, presumably out of memory?
3201 // word was terminated by a space: add that to the textrun
3202 MOZ_ASSERT(aOrientation
!= ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED
,
3203 "text-orientation:mixed should be resolved earlier");
3204 if (boundary
!= ' ' || !aTextRun
->SetSpaceGlyphIfSimple(
3205 this, aRunStart
+ i
, ch
, aOrientation
)) {
3206 // Currently, the only "boundary" characters we recognize are
3207 // space and no-break space, which are both 8-bit, so we force
3208 // that flag (below). If we ever change IsBoundarySpace, we
3209 // may need to revise this.
3210 // Avoid tautological-constant-out-of-range-compare in 8-bit:
3211 DebugOnly
<char16_t
> boundary16
= boundary
;
3212 NS_ASSERTION(boundary16
< 256, "unexpected boundary!");
3213 gfxShapedWord
* sw
= GetShapedWord(
3214 aDrawTarget
, &boundary
, 1, gfxShapedWord::HashMix(0, boundary
),
3215 aRunScript
, aLanguage
, vertical
, appUnitsPerDevUnit
,
3216 flags
| gfx::ShapedTextFlags::TEXT_IS_8BIT
, rounding
, tp
);
3218 aTextRun
->CopyGlyphDataFrom(sw
, aRunStart
+ i
);
3219 if (boundary
== ' ') {
3220 aTextRun
->GetCharacterGlyphs()[aRunStart
+ i
].SetIsSpace();
3232 if (i
== aRunLength
) {
3236 NS_ASSERTION(invalid
, "how did we get here except via an invalid char?");
3238 // word was terminated by an invalid char: skip it,
3239 // unless it's a control char that we want to show as a hexbox,
3240 // but record where TAB or NEWLINE occur
3242 aTextRun
->SetIsTab(aRunStart
+ i
);
3243 } else if (ch
== '\n') {
3244 aTextRun
->SetIsNewline(aRunStart
+ i
);
3245 } else if (GetGeneralCategory(ch
) == HB_UNICODE_GENERAL_CATEGORY_FORMAT
) {
3246 aTextRun
->SetIsFormattingControl(aRunStart
+ i
);
3247 } else if (IsInvalidControlChar(ch
) &&
3248 !(aTextRun
->GetFlags() &
3249 gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS
)) {
3250 if (GetFontEntry()->IsUserFont() && HasCharacter(ch
)) {
3251 ShapeFragmentWithoutWordCache(aDrawTarget
, aString
+ i
, aRunStart
+ i
,
3252 1, aRunScript
, aLanguage
, vertical
,
3253 rounding
, aTextRun
);
3255 aTextRun
->SetMissingGlyph(aRunStart
+ i
, ch
, this);
3267 // Explicit instantiations of SplitAndInitTextRun, to avoid libxul link failure
3268 template bool gfxFont::SplitAndInitTextRun(
3269 DrawTarget
* aDrawTarget
, gfxTextRun
* aTextRun
, const uint8_t* aString
,
3270 uint32_t aRunStart
, uint32_t aRunLength
, Script aRunScript
,
3271 nsAtom
* aLanguage
, ShapedTextFlags aOrientation
);
3272 template bool gfxFont::SplitAndInitTextRun(
3273 DrawTarget
* aDrawTarget
, gfxTextRun
* aTextRun
, const char16_t
* aString
,
3274 uint32_t aRunStart
, uint32_t aRunLength
, Script aRunScript
,
3275 nsAtom
* aLanguage
, ShapedTextFlags aOrientation
);
3278 bool gfxFont::InitFakeSmallCapsRun(
3279 nsPresContext
* aPresContext
, DrawTarget
* aDrawTarget
, gfxTextRun
* aTextRun
,
3280 const char16_t
* aText
, uint32_t aOffset
, uint32_t aLength
,
3281 FontMatchType aMatchType
, gfx::ShapedTextFlags aOrientation
, Script aScript
,
3282 nsAtom
* aLanguage
, bool aSyntheticLower
, bool aSyntheticUpper
) {
3285 RefPtr
<gfxFont
> smallCapsFont
= GetSmallCapsFont();
3286 if (!smallCapsFont
) {
3287 NS_WARNING("failed to get reduced-size font for smallcaps!");
3288 smallCapsFont
= this;
3291 bool isCJK
= gfxTextRun::IsCJKScript(aScript
);
3293 enum RunCaseAction
{ kNoChange
, kUppercaseReduce
, kUppercase
};
3295 RunCaseAction runAction
= kNoChange
;
3296 uint32_t runStart
= 0;
3298 for (uint32_t i
= 0; i
<= aLength
; ++i
) {
3299 uint32_t extraCodeUnits
= 0; // Will be set to 1 if we need to consume
3300 // a trailing surrogate as well as the
3301 // current code unit.
3302 RunCaseAction chAction
= kNoChange
;
3303 // Unless we're at the end, figure out what treatment the current
3304 // character will need.
3306 uint32_t ch
= aText
[i
];
3307 if (i
< aLength
- 1 && NS_IS_SURROGATE_PAIR(ch
, aText
[i
+ 1])) {
3308 ch
= SURROGATE_TO_UCS4(ch
, aText
[i
+ 1]);
3311 // Characters that aren't the start of a cluster are ignored here.
3312 // They get added to whatever lowercase/non-lowercase run we're in.
3313 if (IsClusterExtender(ch
)) {
3314 chAction
= runAction
;
3316 if (ch
!= ToUpperCase(ch
) || SpecialUpper(ch
)) {
3318 chAction
= (aSyntheticLower
? kUppercaseReduce
: kNoChange
);
3319 } else if (ch
!= ToLowerCase(ch
)) {
3321 chAction
= (aSyntheticUpper
? kUppercaseReduce
: kNoChange
);
3322 if (aLanguage
== nsGkAtoms::el
) {
3323 // In Greek, check for characters that will be modified by
3324 // the GreekUpperCase mapping - this catches accented
3325 // capitals where the accent is to be removed (bug 307039).
3326 // These are handled by using the full-size font with the
3327 // uppercasing transform.
3328 mozilla::GreekCasing::State state
;
3329 bool markEta
, updateEta
;
3331 mozilla::GreekCasing::UpperCase(ch
, state
, markEta
, updateEta
);
3332 if ((ch
!= ch2
|| markEta
) && !aSyntheticUpper
) {
3333 chAction
= kUppercase
;
3340 // At the end of the text or when the current character needs different
3341 // casing treatment from the current run, finish the run-in-progress
3342 // and prepare to accumulate a new run.
3343 // Note that we do not look at any source data for offset [i] here,
3344 // as that would be invalid in the case where i==length.
3345 if ((i
== aLength
|| runAction
!= chAction
) && runStart
< i
) {
3346 uint32_t runLength
= i
- runStart
;
3348 switch (runAction
) {
3350 // just use the current font and the existing string
3351 aTextRun
->AddGlyphRun(f
, aMatchType
, aOffset
+ runStart
, true,
3352 aOrientation
, isCJK
);
3353 if (!f
->SplitAndInitTextRun(aDrawTarget
, aTextRun
, aText
+ runStart
,
3354 aOffset
+ runStart
, runLength
, aScript
,
3355 aLanguage
, aOrientation
)) {
3360 case kUppercaseReduce
:
3361 // use reduced-size font, then fall through to uppercase the text
3366 // apply uppercase transform to the string
3367 nsDependentSubstring
origString(aText
+ runStart
, runLength
);
3368 nsAutoString convertedString
;
3369 AutoTArray
<bool, 50> charsToMergeArray
;
3370 AutoTArray
<bool, 50> deletedCharsArray
;
3372 bool mergeNeeded
= nsCaseTransformTextRunFactory::TransformString(
3373 origString
, convertedString
, /* aAllUppercase = */ true,
3374 /* aCaseTransformsOnly = */ false, aLanguage
, charsToMergeArray
,
3378 // This is the hard case: the transformation caused chars
3379 // to be inserted or deleted, so we can't shape directly
3380 // into the destination textrun but have to handle the
3381 // mismatch of character positions.
3382 gfxTextRunFactory::Parameters params
= {
3383 aDrawTarget
, nullptr, nullptr,
3384 nullptr, 0, aTextRun
->GetAppUnitsPerDevUnit()};
3385 RefPtr
<gfxTextRun
> tempRun(gfxTextRun::Create(
3386 ¶ms
, convertedString
.Length(), aTextRun
->GetFontGroup(),
3387 gfx::ShapedTextFlags(), nsTextFrameUtils::Flags()));
3388 tempRun
->AddGlyphRun(f
, aMatchType
, 0, true, aOrientation
, isCJK
);
3389 if (!f
->SplitAndInitTextRun(aDrawTarget
, tempRun
.get(),
3390 convertedString
.BeginReading(), 0,
3391 convertedString
.Length(), aScript
,
3392 aLanguage
, aOrientation
)) {
3395 RefPtr
<gfxTextRun
> mergedRun(gfxTextRun::Create(
3396 ¶ms
, runLength
, aTextRun
->GetFontGroup(),
3397 gfx::ShapedTextFlags(), nsTextFrameUtils::Flags()));
3398 MergeCharactersInTextRun(mergedRun
.get(), tempRun
.get(),
3399 charsToMergeArray
.Elements(),
3400 deletedCharsArray
.Elements());
3401 gfxTextRun::Range
runRange(0, runLength
);
3402 aTextRun
->CopyGlyphDataFrom(mergedRun
.get(), runRange
,
3403 aOffset
+ runStart
);
3406 aTextRun
->AddGlyphRun(f
, aMatchType
, aOffset
+ runStart
, true,
3407 aOrientation
, isCJK
);
3408 if (!f
->SplitAndInitTextRun(aDrawTarget
, aTextRun
,
3409 convertedString
.BeginReading(),
3410 aOffset
+ runStart
, runLength
, aScript
,
3411 aLanguage
, aOrientation
)) {
3421 i
+= extraCodeUnits
;
3423 runAction
= chAction
;
3431 bool gfxFont::InitFakeSmallCapsRun(
3432 nsPresContext
* aPresContext
, DrawTarget
* aDrawTarget
, gfxTextRun
* aTextRun
,
3433 const uint8_t* aText
, uint32_t aOffset
, uint32_t aLength
,
3434 FontMatchType aMatchType
, gfx::ShapedTextFlags aOrientation
, Script aScript
,
3435 nsAtom
* aLanguage
, bool aSyntheticLower
, bool aSyntheticUpper
) {
3436 NS_ConvertASCIItoUTF16
unicodeString(reinterpret_cast<const char*>(aText
),
3438 return InitFakeSmallCapsRun(aPresContext
, aDrawTarget
, aTextRun
,
3439 static_cast<const char16_t
*>(unicodeString
.get()),
3440 aOffset
, aLength
, aMatchType
, aOrientation
,
3441 aScript
, aLanguage
, aSyntheticLower
,
3445 gfxFont
* gfxFont::GetSmallCapsFont() {
3446 gfxFontStyle
style(*GetStyle());
3447 style
.size
*= SMALL_CAPS_SCALE_FACTOR
;
3448 style
.variantCaps
= NS_FONT_VARIANT_CAPS_NORMAL
;
3449 gfxFontEntry
* fe
= GetFontEntry();
3450 return fe
->FindOrMakeFont(&style
, mUnicodeRangeMap
);
3453 gfxFont
* gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel
) {
3454 gfxFontStyle
style(*GetStyle());
3455 style
.AdjustForSubSuperscript(aAppUnitsPerDevPixel
);
3456 gfxFontEntry
* fe
= GetFontEntry();
3457 return fe
->FindOrMakeFont(&style
, mUnicodeRangeMap
);
3460 gfxGlyphExtents
* gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit
) {
3461 uint32_t i
, count
= mGlyphExtentsArray
.Length();
3462 for (i
= 0; i
< count
; ++i
) {
3463 if (mGlyphExtentsArray
[i
]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit
)
3464 return mGlyphExtentsArray
[i
].get();
3466 gfxGlyphExtents
* glyphExtents
= new gfxGlyphExtents(aAppUnitsPerDevUnit
);
3468 mGlyphExtentsArray
.AppendElement(glyphExtents
);
3469 // Initialize the extents of a space glyph, assuming that spaces don't
3471 glyphExtents
->SetContainedGlyphWidthAppUnits(GetSpaceGlyph(), 0);
3473 return glyphExtents
;
3476 void gfxFont::SetupGlyphExtents(DrawTarget
* aDrawTarget
, uint32_t aGlyphID
,
3477 bool aNeedTight
, gfxGlyphExtents
* aExtents
) {
3479 if (mFontEntry
->TryGetSVGData(this) && mFontEntry
->HasSVGGlyph(aGlyphID
) &&
3480 mFontEntry
->GetSVGGlyphExtents(aDrawTarget
, aGlyphID
, GetAdjustedSize(),
3482 gfxFloat d2a
= aExtents
->GetAppUnitsPerDevUnit();
3483 aExtents
->SetTightGlyphExtents(
3484 aGlyphID
, gfxRect(svgBounds
.X() * d2a
, svgBounds
.Y() * d2a
,
3485 svgBounds
.Width() * d2a
, svgBounds
.Height() * d2a
));
3490 GetGlyphBounds(aGlyphID
, &bounds
, mAntialiasOption
== kAntialiasNone
);
3492 const Metrics
& fontMetrics
= GetMetrics(nsFontMetrics::eHorizontal
);
3493 int32_t appUnitsPerDevUnit
= aExtents
->GetAppUnitsPerDevUnit();
3494 if (!aNeedTight
&& bounds
.x
>= 0.0 && bounds
.y
>= -fontMetrics
.maxAscent
&&
3495 bounds
.height
+ bounds
.y
<= fontMetrics
.maxDescent
) {
3496 uint32_t appUnitsWidth
=
3497 uint32_t(ceil((bounds
.x
+ bounds
.width
) * appUnitsPerDevUnit
));
3498 if (appUnitsWidth
< gfxGlyphExtents::INVALID_WIDTH
) {
3499 aExtents
->SetContainedGlyphWidthAppUnits(aGlyphID
,
3500 uint16_t(appUnitsWidth
));
3504 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
3506 ++gGlyphExtentsSetupFallBackToTight
;
3510 gfxFloat d2a
= appUnitsPerDevUnit
;
3511 aExtents
->SetTightGlyphExtents(
3512 aGlyphID
, gfxRect(bounds
.x
* d2a
, bounds
.y
* d2a
, bounds
.width
* d2a
,
3513 bounds
.height
* d2a
));
3516 // Try to initialize font metrics by reading sfnt tables directly;
3517 // set mIsValid=TRUE and return TRUE on success.
3518 // Return FALSE if the gfxFontEntry subclass does not
3519 // implement GetFontTable(), or for non-sfnt fonts where tables are
3521 // If this returns TRUE without setting the mIsValid flag, then we -did-
3522 // apparently find an sfnt, but it was too broken to be used.
3523 bool gfxFont::InitMetricsFromSfntTables(Metrics
& aMetrics
) {
3524 mIsValid
= false; // font is NOT valid in case of early return
3526 const uint32_t kHheaTableTag
= TRUETYPE_TAG('h', 'h', 'e', 'a');
3527 const uint32_t kOS_2TableTag
= TRUETYPE_TAG('O', 'S', '/', '2');
3531 if (mFUnitsConvFactor
< 0.0) {
3532 // If the conversion factor from FUnits is not yet set,
3533 // get the unitsPerEm from the 'head' table via the font entry
3534 uint16_t unitsPerEm
= GetFontEntry()->UnitsPerEm();
3535 if (unitsPerEm
== gfxFontEntry::kInvalidUPEM
) {
3538 mFUnitsConvFactor
= GetAdjustedSize() / unitsPerEm
;
3541 // 'hhea' table is required for the advanceWidthMax field
3542 gfxFontEntry::AutoTable
hheaTable(mFontEntry
, kHheaTableTag
);
3544 return false; // no 'hhea' table -> not an sfnt
3546 const MetricsHeader
* hhea
=
3547 reinterpret_cast<const MetricsHeader
*>(hb_blob_get_data(hheaTable
, &len
));
3548 if (len
< sizeof(MetricsHeader
)) {
3552 #define SET_UNSIGNED(field, src) \
3553 aMetrics.field = uint16_t(src) * mFUnitsConvFactor
3554 #define SET_SIGNED(field, src) aMetrics.field = int16_t(src) * mFUnitsConvFactor
3556 SET_UNSIGNED(maxAdvance
, hhea
->advanceWidthMax
);
3558 // 'OS/2' table is optional, if not found we'll estimate xHeight
3559 // and aveCharWidth by measuring glyphs
3560 gfxFontEntry::AutoTable
os2Table(mFontEntry
, kOS_2TableTag
);
3562 const OS2Table
* os2
=
3563 reinterpret_cast<const OS2Table
*>(hb_blob_get_data(os2Table
, &len
));
3564 // this should always be present in any valid OS/2 of any version
3565 if (len
>= offsetof(OS2Table
, xAvgCharWidth
) + sizeof(int16_t)) {
3566 SET_SIGNED(aveCharWidth
, os2
->xAvgCharWidth
);
3573 hb_font_t
* hbFont
= gfxHarfBuzzShaper::CreateHBFont(this);
3574 hb_position_t position
;
3576 auto FixedToFloat
= [](hb_position_t f
) -> gfxFloat
{ return f
/ 65536.0; };
3578 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER
,
3580 aMetrics
.maxAscent
= FixedToFloat(position
);
3582 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER
,
3584 aMetrics
.maxDescent
= -FixedToFloat(position
);
3586 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP
,
3588 aMetrics
.externalLeading
= FixedToFloat(position
);
3591 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_UNDERLINE_OFFSET
,
3593 aMetrics
.underlineOffset
= FixedToFloat(position
);
3595 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_UNDERLINE_SIZE
,
3597 aMetrics
.underlineSize
= FixedToFloat(position
);
3599 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_STRIKEOUT_OFFSET
,
3601 aMetrics
.strikeoutOffset
= FixedToFloat(position
);
3603 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_STRIKEOUT_SIZE
,
3605 aMetrics
.strikeoutSize
= FixedToFloat(position
);
3608 // Although sxHeight and sCapHeight are signed fields, we consider
3609 // zero/negative values to be erroneous and just ignore them.
3610 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_X_HEIGHT
,
3613 aMetrics
.xHeight
= FixedToFloat(position
);
3615 if (hb_ot_metrics_get_position(hbFont
, HB_OT_METRICS_TAG_CAP_HEIGHT
,
3618 aMetrics
.capHeight
= FixedToFloat(position
);
3620 hb_font_destroy(hbFont
);
3627 static double RoundToNearestMultiple(double aValue
, double aFraction
) {
3628 return floor(aValue
/ aFraction
+ 0.5) * aFraction
;
3631 void gfxFont::CalculateDerivedMetrics(Metrics
& aMetrics
) {
3632 aMetrics
.maxAscent
=
3633 ceil(RoundToNearestMultiple(aMetrics
.maxAscent
, 1 / 1024.0));
3634 aMetrics
.maxDescent
=
3635 ceil(RoundToNearestMultiple(aMetrics
.maxDescent
, 1 / 1024.0));
3637 if (aMetrics
.xHeight
<= 0) {
3638 // only happens if we couldn't find either font metrics
3639 // or a char to measure;
3640 // pick an arbitrary value that's better than zero
3641 aMetrics
.xHeight
= aMetrics
.maxAscent
* DEFAULT_XHEIGHT_FACTOR
;
3644 // If we have a font that doesn't provide a capHeight value, use maxAscent
3645 // as a reasonable fallback.
3646 if (aMetrics
.capHeight
<= 0) {
3647 aMetrics
.capHeight
= aMetrics
.maxAscent
;
3650 aMetrics
.maxHeight
= aMetrics
.maxAscent
+ aMetrics
.maxDescent
;
3652 if (aMetrics
.maxHeight
- aMetrics
.emHeight
> 0.0) {
3653 aMetrics
.internalLeading
= aMetrics
.maxHeight
- aMetrics
.emHeight
;
3655 aMetrics
.internalLeading
= 0.0;
3659 aMetrics
.maxAscent
* aMetrics
.emHeight
/ aMetrics
.maxHeight
;
3660 aMetrics
.emDescent
= aMetrics
.emHeight
- aMetrics
.emAscent
;
3662 if (GetFontEntry()->IsFixedPitch()) {
3663 // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger
3664 // advance than the average character width... this forces
3665 // those fonts to be recognized like fixed pitch fonts by layout.
3666 aMetrics
.maxAdvance
= aMetrics
.aveCharWidth
;
3669 if (!aMetrics
.strikeoutOffset
) {
3670 aMetrics
.strikeoutOffset
= aMetrics
.xHeight
* 0.5;
3672 if (!aMetrics
.strikeoutSize
) {
3673 aMetrics
.strikeoutSize
= aMetrics
.underlineSize
;
3677 void gfxFont::SanitizeMetrics(gfxFont::Metrics
* aMetrics
,
3678 bool aIsBadUnderlineFont
) {
3679 // Even if this font size is zero, this font is created with non-zero size.
3680 // However, for layout and others, we should return the metrics of zero size
3682 if (mStyle
.AdjustedSizeMustBeZero()) {
3683 memset(aMetrics
, 0, sizeof(gfxFont::Metrics
));
3687 // If the font entry has ascent/descent/lineGap-override values,
3688 // replace the metrics from the font with the overrides.
3689 gfxFloat adjustedSize
= GetAdjustedSize();
3690 if (mFontEntry
->mAscentOverride
>= 0.0) {
3691 aMetrics
->maxAscent
= mFontEntry
->mAscentOverride
* adjustedSize
;
3692 aMetrics
->maxHeight
= aMetrics
->maxAscent
+ aMetrics
->maxDescent
;
3693 aMetrics
->internalLeading
=
3694 std::max(0.0, aMetrics
->maxHeight
- aMetrics
->emHeight
);
3696 if (mFontEntry
->mDescentOverride
>= 0.0) {
3697 aMetrics
->maxDescent
= mFontEntry
->mDescentOverride
* adjustedSize
;
3698 aMetrics
->maxHeight
= aMetrics
->maxAscent
+ aMetrics
->maxDescent
;
3699 aMetrics
->internalLeading
=
3700 std::max(0.0, aMetrics
->maxHeight
- aMetrics
->emHeight
);
3702 if (mFontEntry
->mLineGapOverride
>= 0.0) {
3703 aMetrics
->externalLeading
= mFontEntry
->mLineGapOverride
* adjustedSize
;
3706 aMetrics
->underlineSize
= std::max(1.0, aMetrics
->underlineSize
);
3707 aMetrics
->strikeoutSize
= std::max(1.0, aMetrics
->strikeoutSize
);
3709 aMetrics
->underlineOffset
= std::min(aMetrics
->underlineOffset
, -1.0);
3711 if (aMetrics
->maxAscent
< 1.0) {
3712 // We cannot draw strikeout line and overline in the ascent...
3713 aMetrics
->underlineSize
= 0;
3714 aMetrics
->underlineOffset
= 0;
3715 aMetrics
->strikeoutSize
= 0;
3716 aMetrics
->strikeoutOffset
= 0;
3721 * Some CJK fonts have bad underline offset. Therefore, if this is such font,
3722 * we need to lower the underline offset to bottom of *em* descent.
3723 * However, if this is system font, we should not do this for the rendering
3724 * compatibility with another application's UI on the platform.
3725 * XXX Should not use this hack if the font size is too small?
3726 * Such text cannot be read, this might be used for tight CSS
3727 * rendering? (E.g., Acid2)
3729 if (!mStyle
.systemFont
&& aIsBadUnderlineFont
) {
3730 // First, we need 2 pixels between baseline and underline at least. Because
3731 // many CJK characters put their glyphs on the baseline, so, 1 pixel is too
3732 // close for CJK characters.
3733 aMetrics
->underlineOffset
= std::min(aMetrics
->underlineOffset
, -2.0);
3735 // Next, we put the underline to bottom of below of the descent space.
3736 if (aMetrics
->internalLeading
+ aMetrics
->externalLeading
>
3737 aMetrics
->underlineSize
) {
3738 aMetrics
->underlineOffset
=
3739 std::min(aMetrics
->underlineOffset
, -aMetrics
->emDescent
);
3741 aMetrics
->underlineOffset
=
3742 std::min(aMetrics
->underlineOffset
,
3743 aMetrics
->underlineSize
- aMetrics
->emDescent
);
3746 // If underline positioned is too far from the text, descent position is
3747 // preferred so that underline will stay within the boundary.
3748 else if (aMetrics
->underlineSize
- aMetrics
->underlineOffset
>
3749 aMetrics
->maxDescent
) {
3750 if (aMetrics
->underlineSize
> aMetrics
->maxDescent
)
3751 aMetrics
->underlineSize
= std::max(aMetrics
->maxDescent
, 1.0);
3752 // The max underlineOffset is 1px (the min underlineSize is 1px, and min
3753 // maxDescent is 0px.)
3754 aMetrics
->underlineOffset
= aMetrics
->underlineSize
- aMetrics
->maxDescent
;
3757 // If strikeout line is overflowed from the ascent, the line should be resized
3758 // and moved for that being in the ascent space. Note that the strikeoutOffset
3759 // is *middle* of the strikeout line position.
3760 gfxFloat halfOfStrikeoutSize
= floor(aMetrics
->strikeoutSize
/ 2.0 + 0.5);
3761 if (halfOfStrikeoutSize
+ aMetrics
->strikeoutOffset
> aMetrics
->maxAscent
) {
3762 if (aMetrics
->strikeoutSize
> aMetrics
->maxAscent
) {
3763 aMetrics
->strikeoutSize
= std::max(aMetrics
->maxAscent
, 1.0);
3764 halfOfStrikeoutSize
= floor(aMetrics
->strikeoutSize
/ 2.0 + 0.5);
3766 gfxFloat ascent
= floor(aMetrics
->maxAscent
+ 0.5);
3767 aMetrics
->strikeoutOffset
= std::max(halfOfStrikeoutSize
, ascent
/ 2.0);
3770 // If overline is larger than the ascent, the line should be resized.
3771 if (aMetrics
->underlineSize
> aMetrics
->maxAscent
) {
3772 aMetrics
->underlineSize
= aMetrics
->maxAscent
;
3776 // Create a Metrics record to be used for vertical layout. This should never
3777 // fail, as we've already decided this is a valid font. We do not have the
3778 // option of marking it invalid (as can happen if we're unable to read
3779 // horizontal metrics), because that could break a font that we're already
3780 // using for horizontal text.
3781 // So we will synthesize *something* usable here even if there aren't any of the
3782 // usual font tables (which can happen in the case of a legacy bitmap or Type1
3783 // font for which the platform-specific backend used platform APIs instead of
3784 // sfnt tables to create the horizontal metrics).
3785 UniquePtr
<const gfxFont::Metrics
> gfxFont::CreateVerticalMetrics() {
3786 const uint32_t kHheaTableTag
= TRUETYPE_TAG('h', 'h', 'e', 'a');
3787 const uint32_t kVheaTableTag
= TRUETYPE_TAG('v', 'h', 'e', 'a');
3788 const uint32_t kPostTableTag
= TRUETYPE_TAG('p', 'o', 's', 't');
3789 const uint32_t kOS_2TableTag
= TRUETYPE_TAG('O', 'S', '/', '2');
3792 UniquePtr
<Metrics
> metrics
= MakeUnique
<Metrics
>();
3793 ::memset(metrics
.get(), 0, sizeof(Metrics
));
3795 // Some basic defaults, in case the font lacks any real metrics tables.
3796 // TODO: consider what rounding (if any) we should apply to these.
3797 metrics
->emHeight
= GetAdjustedSize();
3798 metrics
->emAscent
= metrics
->emHeight
/ 2;
3799 metrics
->emDescent
= metrics
->emHeight
- metrics
->emAscent
;
3801 metrics
->maxAscent
= metrics
->emAscent
;
3802 metrics
->maxDescent
= metrics
->emDescent
;
3804 const float UNINITIALIZED_LEADING
= -10000.0f
;
3805 metrics
->externalLeading
= UNINITIALIZED_LEADING
;
3807 if (mFUnitsConvFactor
< 0.0) {
3808 uint16_t upem
= GetFontEntry()->UnitsPerEm();
3809 if (upem
!= gfxFontEntry::kInvalidUPEM
) {
3810 mFUnitsConvFactor
= GetAdjustedSize() / upem
;
3814 #define SET_UNSIGNED(field, src) \
3815 metrics->field = uint16_t(src) * mFUnitsConvFactor
3816 #define SET_SIGNED(field, src) metrics->field = int16_t(src) * mFUnitsConvFactor
3818 gfxFontEntry::AutoTable
os2Table(mFontEntry
, kOS_2TableTag
);
3819 if (os2Table
&& mFUnitsConvFactor
>= 0.0) {
3820 const OS2Table
* os2
=
3821 reinterpret_cast<const OS2Table
*>(hb_blob_get_data(os2Table
, &len
));
3822 // These fields should always be present in any valid OS/2 table
3823 if (len
>= offsetof(OS2Table
, sTypoLineGap
) + sizeof(int16_t)) {
3824 SET_SIGNED(strikeoutSize
, os2
->yStrikeoutSize
);
3825 // Use ascent+descent from the horizontal metrics as the default
3826 // advance (aveCharWidth) in vertical mode
3827 gfxFloat ascentDescent
=
3828 gfxFloat(mFUnitsConvFactor
) *
3829 (int16_t(os2
->sTypoAscender
) - int16_t(os2
->sTypoDescender
));
3830 metrics
->aveCharWidth
= std::max(metrics
->emHeight
, ascentDescent
);
3831 // Use xAvgCharWidth from horizontal metrics as minimum font extent
3832 // for vertical layout, applying half of it to ascent and half to
3833 // descent (to work with a default centered baseline).
3834 gfxFloat halfCharWidth
=
3835 int16_t(os2
->xAvgCharWidth
) * gfxFloat(mFUnitsConvFactor
) / 2;
3836 metrics
->maxAscent
= std::max(metrics
->maxAscent
, halfCharWidth
);
3837 metrics
->maxDescent
= std::max(metrics
->maxDescent
, halfCharWidth
);
3841 // If we didn't set aveCharWidth from OS/2, try to read 'hhea' metrics
3842 // and use the line height from its ascent/descent.
3843 if (!metrics
->aveCharWidth
) {
3844 gfxFontEntry::AutoTable
hheaTable(mFontEntry
, kHheaTableTag
);
3845 if (hheaTable
&& mFUnitsConvFactor
>= 0.0) {
3846 const MetricsHeader
* hhea
= reinterpret_cast<const MetricsHeader
*>(
3847 hb_blob_get_data(hheaTable
, &len
));
3848 if (len
>= sizeof(MetricsHeader
)) {
3849 SET_SIGNED(aveCharWidth
,
3850 int16_t(hhea
->ascender
) - int16_t(hhea
->descender
));
3851 metrics
->maxAscent
= metrics
->aveCharWidth
/ 2;
3852 metrics
->maxDescent
= metrics
->aveCharWidth
- metrics
->maxAscent
;
3857 // Read real vertical metrics if available.
3858 gfxFontEntry::AutoTable
vheaTable(mFontEntry
, kVheaTableTag
);
3859 if (vheaTable
&& mFUnitsConvFactor
>= 0.0) {
3860 const MetricsHeader
* vhea
= reinterpret_cast<const MetricsHeader
*>(
3861 hb_blob_get_data(vheaTable
, &len
));
3862 if (len
>= sizeof(MetricsHeader
)) {
3863 SET_UNSIGNED(maxAdvance
, vhea
->advanceWidthMax
);
3864 // Redistribute space between ascent/descent because we want a
3865 // centered vertical baseline by default.
3866 gfxFloat halfExtent
=
3867 0.5 * gfxFloat(mFUnitsConvFactor
) *
3868 (int16_t(vhea
->ascender
) + std::abs(int16_t(vhea
->descender
)));
3869 // Some bogus fonts have ascent and descent set to zero in 'vhea'.
3870 // In that case we just ignore them and keep our synthetic values
3872 if (halfExtent
> 0) {
3873 metrics
->maxAscent
= halfExtent
;
3874 metrics
->maxDescent
= halfExtent
;
3875 SET_SIGNED(externalLeading
, vhea
->lineGap
);
3880 // If we didn't set aveCharWidth above, we must be dealing with a non-sfnt
3881 // font of some kind (Type1, bitmap, vector, ...), so fall back to using
3882 // whatever the platform backend figured out for horizontal layout.
3883 // And if we haven't set externalLeading yet, then copy that from the
3884 // horizontal metrics as well, to help consistency of CSS line-height.
3885 if (!metrics
->aveCharWidth
||
3886 metrics
->externalLeading
== UNINITIALIZED_LEADING
) {
3887 const Metrics
& horizMetrics
= GetHorizontalMetrics();
3888 if (!metrics
->aveCharWidth
) {
3889 metrics
->aveCharWidth
= horizMetrics
.maxAscent
+ horizMetrics
.maxDescent
;
3891 if (metrics
->externalLeading
== UNINITIALIZED_LEADING
) {
3892 metrics
->externalLeading
= horizMetrics
.externalLeading
;
3896 // Get underline thickness from the 'post' table if available.
3897 // We also read the underline position, although in vertical-upright mode
3898 // this will not be appropriate to use directly (see nsTextFrame.cpp).
3899 gfxFontEntry::AutoTable
postTable(mFontEntry
, kPostTableTag
);
3901 const PostTable
* post
=
3902 reinterpret_cast<const PostTable
*>(hb_blob_get_data(postTable
, &len
));
3903 if (len
>= offsetof(PostTable
, underlineThickness
) + sizeof(uint16_t)) {
3904 static_assert(offsetof(PostTable
, underlinePosition
) <
3905 offsetof(PostTable
, underlineThickness
),
3906 "broken PostTable struct?");
3907 SET_SIGNED(underlineOffset
, post
->underlinePosition
);
3908 SET_UNSIGNED(underlineSize
, post
->underlineThickness
);
3909 // Also use for strikeout if we didn't find that in OS/2 above.
3910 if (!metrics
->strikeoutSize
) {
3911 metrics
->strikeoutSize
= metrics
->underlineSize
;
3919 // If we didn't read this from a vhea table, it will still be zero.
3920 // In any case, let's make sure it is not less than the value we've
3921 // come up with for aveCharWidth.
3922 metrics
->maxAdvance
= std::max(metrics
->maxAdvance
, metrics
->aveCharWidth
);
3924 // Thickness of underline and strikeout may have been read from tables,
3925 // but in case they were not present, ensure a minimum of 1 pixel.
3926 metrics
->underlineSize
= std::max(1.0, metrics
->underlineSize
);
3928 metrics
->strikeoutSize
= std::max(1.0, metrics
->strikeoutSize
);
3929 metrics
->strikeoutOffset
= -0.5 * metrics
->strikeoutSize
;
3931 // Somewhat arbitrary values for now, subject to future refinement...
3932 metrics
->spaceWidth
= metrics
->aveCharWidth
;
3933 metrics
->zeroWidth
= metrics
->aveCharWidth
;
3934 metrics
->maxHeight
= metrics
->maxAscent
+ metrics
->maxDescent
;
3935 metrics
->xHeight
= metrics
->emHeight
/ 2;
3936 metrics
->capHeight
= metrics
->maxAscent
;
3938 return std::move(metrics
);
3941 gfxFloat
gfxFont::SynthesizeSpaceWidth(uint32_t aCh
) {
3942 // return an appropriate width for various Unicode space characters
3943 // that we "fake" if they're not actually present in the font;
3944 // returns negative value if the char is not a known space.
3946 case 0x2000: // en quad
3948 return GetAdjustedSize() / 2; // en space
3949 case 0x2001: // em quad
3951 return GetAdjustedSize(); // em space
3953 return GetAdjustedSize() / 3; // three-per-em space
3955 return GetAdjustedSize() / 4; // four-per-em space
3957 return GetAdjustedSize() / 6; // six-per-em space
3959 return GetMetrics(nsFontMetrics::eHorizontal
)
3960 .ZeroOrAveCharWidth(); // figure space
3962 return GetMetrics(nsFontMetrics::eHorizontal
)
3963 .spaceWidth
; // punctuation space
3965 return GetAdjustedSize() / 5; // thin space
3967 return GetAdjustedSize() / 10; // hair space
3969 return GetAdjustedSize() / 5; // narrow no-break space
3971 return GetAdjustedSize(); // ideographic space
3977 void gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf
,
3978 FontCacheSizes
* aSizes
) const {
3979 for (uint32_t i
= 0; i
< mGlyphExtentsArray
.Length(); ++i
) {
3980 aSizes
->mFontInstances
+=
3981 mGlyphExtentsArray
[i
]->SizeOfIncludingThis(aMallocSizeOf
);
3984 aSizes
->mShapedWords
+= mWordCache
->SizeOfIncludingThis(aMallocSizeOf
);
3988 void gfxFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf
,
3989 FontCacheSizes
* aSizes
) const {
3990 aSizes
->mFontInstances
+= aMallocSizeOf(this);
3991 AddSizeOfExcludingThis(aMallocSizeOf
, aSizes
);
3994 void gfxFont::AddGlyphChangeObserver(GlyphChangeObserver
* aObserver
) {
3995 if (!mGlyphChangeObservers
) {
3996 mGlyphChangeObservers
= MakeUnique
<nsTHashSet
<GlyphChangeObserver
*>>();
3998 mGlyphChangeObservers
->Insert(aObserver
);
4001 void gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver
* aObserver
) {
4002 NS_ASSERTION(mGlyphChangeObservers
, "No observers registered");
4003 NS_ASSERTION(mGlyphChangeObservers
->Contains(aObserver
),
4004 "Observer not registered");
4005 mGlyphChangeObservers
->Remove(aObserver
);
4008 #define DEFAULT_PIXEL_FONT_SIZE 16.0f
4010 gfxFontStyle::gfxFontStyle()
4011 : size(DEFAULT_PIXEL_FONT_SIZE
),
4013 baselineOffset(0.0f
),
4014 languageOverride(NO_FONT_LANGUAGE_OVERRIDE
),
4015 fontSmoothingBackgroundColor(NS_RGBA(0, 0, 0, 0)),
4016 weight(FontWeight::Normal()),
4017 stretch(FontStretch::Normal()),
4018 style(FontSlantStyle::Normal()),
4019 variantCaps(NS_FONT_VARIANT_CAPS_NORMAL
),
4020 variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL
),
4021 sizeAdjustBasis(uint8_t(FontSizeAdjust::Tag::None
)),
4024 useGrayscaleAntialiasing(false),
4025 allowSyntheticWeight(true),
4026 allowSyntheticStyle(true),
4027 allowSyntheticSmallCaps(true),
4028 noFallbackVariantFeatures(true) {}
4030 gfxFontStyle::gfxFontStyle(FontSlantStyle aStyle
, FontWeight aWeight
,
4031 FontStretch aStretch
, gfxFloat aSize
,
4032 const FontSizeAdjust
& aSizeAdjust
, bool aSystemFont
,
4033 bool aPrinterFont
, bool aAllowWeightSynthesis
,
4034 bool aAllowStyleSynthesis
,
4035 bool aAllowSmallCapsSynthesis
,
4036 uint32_t aLanguageOverride
)
4038 baselineOffset(0.0f
),
4039 languageOverride(aLanguageOverride
),
4040 fontSmoothingBackgroundColor(NS_RGBA(0, 0, 0, 0)),
4044 variantCaps(NS_FONT_VARIANT_CAPS_NORMAL
),
4045 variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL
),
4046 systemFont(aSystemFont
),
4047 printerFont(aPrinterFont
),
4048 useGrayscaleAntialiasing(false),
4049 allowSyntheticWeight(aAllowWeightSynthesis
),
4050 allowSyntheticStyle(aAllowStyleSynthesis
),
4051 allowSyntheticSmallCaps(aAllowSmallCapsSynthesis
),
4052 noFallbackVariantFeatures(true) {
4053 MOZ_ASSERT(!mozilla::IsNaN(size
));
4055 switch (aSizeAdjust
.tag
) {
4056 case FontSizeAdjust::Tag::None
:
4059 case FontSizeAdjust::Tag::ExHeight
:
4060 sizeAdjust
= aSizeAdjust
.AsExHeight();
4062 case FontSizeAdjust::Tag::CapHeight
:
4063 sizeAdjust
= aSizeAdjust
.AsCapHeight();
4065 case FontSizeAdjust::Tag::ChWidth
:
4066 sizeAdjust
= aSizeAdjust
.AsChWidth();
4068 case FontSizeAdjust::Tag::IcWidth
:
4069 sizeAdjust
= aSizeAdjust
.AsIcWidth();
4071 case FontSizeAdjust::Tag::IcHeight
:
4072 sizeAdjust
= aSizeAdjust
.AsIcHeight();
4075 MOZ_ASSERT(!mozilla::IsNaN(sizeAdjust
));
4077 sizeAdjustBasis
= uint8_t(aSizeAdjust
.tag
);
4078 // sizeAdjustBasis is currently a small bitfield, so let's assert that the
4079 // tag value was not truncated.
4080 MOZ_ASSERT(FontSizeAdjust::Tag(sizeAdjustBasis
) == aSizeAdjust
.tag
,
4081 "gfxFontStyle.sizeAdjustBasis too small?");
4083 if (weight
> FontWeight(1000)) {
4084 weight
= FontWeight(1000);
4086 if (weight
< FontWeight(1)) {
4087 weight
= FontWeight(1);
4090 if (size
>= FONT_MAX_SIZE
) {
4091 size
= FONT_MAX_SIZE
;
4093 sizeAdjustBasis
= uint8_t(FontSizeAdjust::Tag::None
);
4094 } else if (size
< 0.0) {
4095 NS_WARNING("negative font size");
4100 PLDHashNumber
gfxFontStyle::Hash() const {
4101 uint32_t hash
= variationSettings
.IsEmpty()
4103 : mozilla::HashBytes(variationSettings
.Elements(),
4104 variationSettings
.Length() *
4105 sizeof(gfxFontVariation
));
4106 return mozilla::AddToHash(hash
, systemFont
, style
.ForHash(),
4107 stretch
.ForHash(), weight
.ForHash(), size
,
4108 int32_t(sizeAdjust
* 1000.0f
));
4111 void gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel
) {
4113 variantSubSuper
!= NS_FONT_VARIANT_POSITION_NORMAL
&& baselineOffset
== 0,
4114 "can't adjust this style for sub/superscript");
4116 // calculate the baseline offset (before changing the size)
4117 if (variantSubSuper
== NS_FONT_VARIANT_POSITION_SUPER
) {
4118 baselineOffset
= size
* -NS_FONT_SUPERSCRIPT_OFFSET_RATIO
;
4120 baselineOffset
= size
* NS_FONT_SUBSCRIPT_OFFSET_RATIO
;
4123 // calculate reduced size, roughly mimicing behavior of font-size: smaller
4124 float cssSize
= size
* aAppUnitsPerDevPixel
/ AppUnitsPerCSSPixel();
4125 if (cssSize
< NS_FONT_SUB_SUPER_SMALL_SIZE
) {
4126 size
*= NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL
;
4127 } else if (cssSize
>= NS_FONT_SUB_SUPER_LARGE_SIZE
) {
4128 size
*= NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE
;
4130 gfxFloat t
= (cssSize
- NS_FONT_SUB_SUPER_SMALL_SIZE
) /
4131 (NS_FONT_SUB_SUPER_LARGE_SIZE
- NS_FONT_SUB_SUPER_SMALL_SIZE
);
4132 size
*= (1.0 - t
) * NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL
+
4133 t
* NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE
;
4136 // clear the variant field
4137 variantSubSuper
= NS_FONT_VARIANT_POSITION_NORMAL
;
4140 bool gfxFont::TryGetMathTable() {
4141 if (!mMathInitialized
) {
4142 mMathInitialized
= true;
4144 hb_face_t
* face
= GetFontEntry()->GetHBFace();
4146 if (hb_ot_math_has_data(face
)) {
4147 mMathTable
= MakeUnique
<gfxMathTable
>(face
, GetAdjustedSize());
4149 hb_face_destroy(face
);
4153 return !!mMathTable
;