Bug 1732219 - Add API for fetching the preview image. r=geckoview-reviewers,agi,mconley
[gecko.git] / gfx / thebes / gfxFont.cpp
blob36275d809061ca123edbd0671e2b91f57d075b50
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "gfxFont.h"
8 #include "mozilla/BinarySearch.h"
9 #include "mozilla/DebugOnly.h"
10 #include "mozilla/FontPropertyTypes.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/IntegerRange.h"
13 #include "mozilla/MathAlgorithms.h"
14 #include "mozilla/StaticPrefs_gfx.h"
15 #include "mozilla/SVGContextPaint.h"
17 #include "mozilla/Logging.h"
19 #include "nsITimer.h"
21 #include "gfxGlyphExtents.h"
22 #include "gfxPlatform.h"
23 #include "gfxTextRun.h"
24 #include "nsGkAtoms.h"
26 #include "gfxTypes.h"
27 #include "gfxContext.h"
28 #include "gfxFontMissingGlyphs.h"
29 #include "gfxGraphiteShaper.h"
30 #include "gfxHarfBuzzShaper.h"
31 #include "gfxUserFontSet.h"
32 #include "nsCRT.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"
53 #include "cairo.h"
54 #ifdef XP_WIN
55 # include "cairo-win32.h"
56 # include "gfxWindowsPlatform.h"
57 #endif
59 #include "harfbuzz/hb.h"
60 #include "harfbuzz/hb-ot.h"
62 #include <algorithm>
63 #include <limits>
64 #include <cmath>
66 using namespace mozilla;
67 using namespace mozilla::gfx;
68 using namespace mozilla::unicode;
69 using mozilla::services::GetObserverService;
71 gfxFontCache* gfxFontCache::gGlobalCache = nullptr;
73 #ifdef DEBUG_roc
74 # define DEBUG_TEXT_RUN_STORAGE_METRICS
75 #endif
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;
87 #endif
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)
106 /*virtual*/
107 gfxTextRunFactory::~gfxTextRunFactory() {
108 // Should not be dropped by stylo
109 MOZ_ASSERT(NS_IsMainThread());
112 NS_IMETHODIMP
113 gfxFontCache::MemoryReporter::CollectReports(
114 nsIHandleReportCallback* aHandleReport, nsISupports* aData,
115 bool aAnonymize) {
116 FontCacheSizes sizes;
118 gfxFontCache::GetCache()->AddSizeOfIncludingThis(&FontCacheMallocSizeOf,
119 &sizes);
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,
126 sizes.mShapedWords,
127 "Memory used to cache shaped glyph data.");
129 return NS_OK;
132 NS_IMPL_ISUPPORTS(gfxFontCache::Observer, nsIObserver)
134 NS_IMETHODIMP
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();
139 if (fontCache) {
140 fontCache->FlushShapedWordCaches();
142 } else {
143 MOZ_ASSERT_UNREACHABLE("unexpected notification topic");
145 return NS_OK;
148 nsresult gfxFontCache::Init() {
149 NS_ASSERTION(!gGlobalCache, "Where did this come from?");
150 gGlobalCache = new gfxFontCache(GetMainThreadSerialEventTarget());
151 if (!gGlobalCache) {
152 return NS_ERROR_OUT_OF_MEMORY;
154 RegisterStrongMemoryReporter(new MemoryReporter());
155 return NS_OK;
158 void gfxFontCache::Shutdown() {
159 delete gGlobalCache;
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);
177 #endif
180 gfxFontCache::gfxFontCache(nsIEventTarget* aEventTarget)
181 : gfxFontCacheExpirationTracker(aEventTarget) {
182 nsCOMPtr<nsIObserverService> obs = GetObserverService();
183 if (obs) {
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.
210 AgeAllGenerations();
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
216 // the tracker.
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;
237 return entry->mFont;
240 void gfxFontCache::AddNew(gfxFont* aFont) {
241 Key key(aFont->GetFontEntry(), aFont->GetStyle(),
242 aFont->GetUnicodeRangeMap());
243 HashEntry* entry = mFonts.PutEntry(key);
244 if (!entry) return;
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);
262 if (NS_FAILED(rv)) {
263 // We couldn't track it for some reason. Kill it now.
264 DestroyFont(aFont);
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();
275 RemoveObject(aFont);
276 DestroyFont(aFont);
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!");
288 delete aFont;
291 /*static*/
292 void gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer,
293 void* aCache) {
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()) {
339 return;
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,
347 ident.AsAtom());
348 // nothing defined, skip
349 if (values.IsEmpty()) {
350 continue;
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) {
358 continue;
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);
364 return;
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
373 feature.mValue = 1;
374 for (uint32_t nn : values) {
375 if (nn == 0 || nn > MAX_SSXX_VALUE) {
376 continue;
378 feature.mTag = HB_TAG('s', 's', ('0' + nn / 10), ('0' + nn % 10));
379 aFontFeatures.AppendElement(feature);
382 return;
385 uint32_t constant = 0;
386 nsAtom* name = nullptr;
387 switch (aAlternates.tag) {
388 case Tag::Swash:
389 constant = NS_FONT_VARIANT_ALTERNATES_SWASH;
390 name = aAlternates.AsSwash().AsAtom();
391 break;
392 case Tag::Stylistic:
393 constant = NS_FONT_VARIANT_ALTERNATES_STYLISTIC;
394 name = aAlternates.AsStylistic().AsAtom();
395 break;
396 case Tag::Ornaments:
397 constant = NS_FONT_VARIANT_ALTERNATES_ORNAMENTS;
398 name = aAlternates.AsOrnaments().AsAtom();
399 break;
400 case Tag::Annotation:
401 constant = NS_FONT_VARIANT_ALTERNATES_ANNOTATION;
402 name = aAlternates.AsAnnotation().AsAtom();
403 break;
404 default:
405 MOZ_ASSERT_UNREACHABLE("Unknown font-variant-alternates value!");
406 return;
409 Span<const uint32_t> values =
410 aFeatureLookup.GetFontFeatureValuesFor(aFamily, constant, name);
411 if (values.IsEmpty()) {
412 return;
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');
423 break;
424 case Tag::Stylistic: // salt
425 feature.mTag = HB_TAG('s', 'a', 'l', 't');
426 break;
427 case Tag::Ornaments: // ornm
428 feature.mTag = HB_TAG('o', 'r', 'n', 'm');
429 break;
430 case Tag::Annotation: // nalt
431 feature.mTag = HB_TAG('n', 'a', 'l', 't');
432 break;
433 default:
434 MOZ_ASSERT_UNREACHABLE("how?");
435 return;
437 aFontFeatures.AppendElement(feature);
440 /* static */
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()) {
454 return;
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:
469 break;
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
474 [[fallthrough]];
476 case NS_FONT_VARIANT_CAPS_SMALLCAPS:
477 mergedFeatures.InsertOrUpdate(HB_TAG('s', 'm', 'c', 'p'), 1);
478 break;
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
485 [[fallthrough]];
487 case NS_FONT_VARIANT_CAPS_PETITECAPS:
488 mergedFeatures.InsertOrUpdate(aAddSmallCaps ? HB_TAG('s', 'm', 'c', 'p')
489 : HB_TAG('p', 'c', 'a', 'p'),
491 break;
493 case NS_FONT_VARIANT_CAPS_TITLING:
494 mergedFeatures.InsertOrUpdate(HB_TAG('t', 'i', 't', 'l'), 1);
495 break;
497 case NS_FONT_VARIANT_CAPS_UNICASE:
498 mergedFeatures.InsertOrUpdate(HB_TAG('u', 'n', 'i', 'c'), 1);
499 break;
501 default:
502 MOZ_ASSERT_UNREACHABLE("Unexpected variantCaps");
503 break;
506 // font-variant-position - handled here due to the need for fallback
507 switch (aStyle->variantSubSuper) {
508 case NS_FONT_VARIANT_POSITION_NORMAL:
509 break;
510 case NS_FONT_VARIANT_POSITION_SUPER:
511 mergedFeatures.InsertOrUpdate(HB_TAG('s', 'u', 'p', 's'), 1);
512 break;
513 case NS_FONT_VARIANT_POSITION_SUB:
514 mergedFeatures.InsertOrUpdate(HB_TAG('s', 'u', 'b', 's'), 1);
515 break;
516 default:
517 MOZ_ASSERT_UNREACHABLE("Unexpected variantSubSuper");
518 break;
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,
528 featureList);
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);
543 } else {
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.
551 if (feature.mTag) {
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,
571 uint32_t aLength) {
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
580 if (aLength) {
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)
596 iter.Next();
597 // step past the first char of the cluster
598 aString++;
599 glyphs++;
600 // mark all the rest as cluster-continuations
601 while (aString < iter) {
602 *glyphs = extendCluster;
603 glyphs++;
604 aString++;
609 void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
610 const uint8_t* aString,
611 uint32_t aLength) {
612 CompressedGlyph* glyphs = GetCharacterGlyphs() + aOffset;
613 const uint8_t* limit = aString + aLength;
615 while (aString < limit) {
616 if (*aString == uint8_t(' ')) {
617 glyphs->SetIsSpace();
619 aString++;
620 glyphs++;
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);
650 #define ZWNJ 0x200C
651 #define ZWJ 0x200D
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,
657 gfxFont* aFont) {
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.
666 int32_t advance = 0;
667 if (!IsIgnorable(aChar)) {
668 gfxFloat width =
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);
676 g.SetMissing();
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()) {
689 return false;
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();
695 return true;
697 return false;
700 void gfxShapedText::AdjustAdvancesForSyntheticBold(float aSynBoldOffset,
701 uint32_t aOffset,
702 uint32_t aLength) {
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();
710 if (advance > 0) {
711 advance += synAppUnitOffset;
712 if (CompressedGlyph::IsSimpleAdvance(advance)) {
713 glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph());
714 } else {
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);
724 } else {
725 // complex glyphs ==> add offset at cluster/ligature boundaries
726 uint32_t detailedLength = glyphData->GetGlyphCount();
727 if (detailedLength) {
728 DetailedGlyph* details = GetDetailedGlyphs(i);
729 if (!details) {
730 continue;
732 if (IsRightToLeft()) {
733 if (details[0].mAdvance > 0) {
734 details[0].mAdvance += synAppUnitOffset;
736 } else {
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()) {
751 return 0.0f;
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();
771 if (angle == 0.0f) {
772 return 0.0f;
773 } else if (angle == FontSlantStyle::kDefaultAngle) {
774 return kTanDefaultAngle;
775 } else {
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);
787 } else {
788 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),
799 mStyle(*aFontStyle),
800 mAdjustedSize(-1.0), // negative to indicate "not yet initialized"
801 mFUnitsConvFactor(-1.0f), // negative to indicate "not yet initialized"
802 mAntialiasOption(anAAOption),
803 mIsValid(true),
804 mApplySyntheticBold(false),
805 mKerningEnabled(false),
806 mMathInitialized(false) {
807 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
808 ++gFontCount;
809 #endif
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) {
829 key->ForgetFont();
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));
854 if (cr) {
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;
869 default:
870 break;
874 if (ShouldRoundXOffset(cr)) {
875 return RoundingFlags::kRoundX | RoundingFlags::kRoundY;
876 } else {
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()) {
896 return 0;
898 return (aVertical ? shaper->GetGlyphVAdvance(aGID)
899 : shaper->GetGlyphHAdvance(aGID)) /
900 65536.0;
903 gfxFloat gfxFont::GetCharAdvance(uint32_t aUnicode, bool aVertical) {
904 uint32_t gid = 0;
905 if (ProvidesGetGlyph()) {
906 gid = GetGlyph(aUnicode, 0);
907 } else {
908 if (!mHarfBuzzShaper) {
909 mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
911 gfxHarfBuzzShaper* shaper =
912 static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get());
913 if (!shaper->Initialize()) {
914 return -1.0;
916 gid = shaper->GetNominalGlyph(aUnicode);
918 if (!gid) {
919 return -1.0;
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;
930 offset = 0;
931 do {
932 len = ArrayLength(lookups);
933 hb_ot_layout_feature_get_lookups(aFace, aTableTag, aFeatureIndex, offset,
934 &len, lookups);
935 for (i = 0; i < len; i++) {
936 hb_set_add(aLookups, lookups[i]);
938 offset += len;
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;
956 offset = 0;
957 do {
958 len = ArrayLength(featureIndexes);
959 hb_ot_layout_language_get_feature_indexes(aFace, aTableTag, aScriptIndex,
960 aLangIndex, offset, &len,
961 featureIndexes);
963 for (i = 0; i < len; i++) {
964 uint32_t featureIndex = featureIndexes[i];
966 // get the feature tag
967 hb_tag_t featureTag;
968 uint32_t tagLen = 1;
969 hb_ot_layout_language_get_feature_tags(aFace, aTableTag, aScriptIndex,
970 aLangIndex, offset + i, &tagLen,
971 &featureTag);
973 // collect lookups
974 hb_set_t* lookups = aSpecificFeatures.Contains(featureTag)
975 ? aSpecificFeatureLookups
976 : aOtherLookups;
977 CollectLookupsByFeature(aFace, aTableTag, featureIndex, lookups);
979 offset += len;
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();
992 // default lang
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,
1012 glyphs, nullptr);
1013 if (hb_set_has(glyphs, aGlyph)) {
1014 aHasDefaultFeatureWithGlyph = true;
1015 break;
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);
1024 index = -1;
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;
1030 break;
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);
1053 numScripts =
1054 hb_ot_layout_table_get_script_tags(aFace, aTableTag, 0, nullptr, nullptr);
1056 for (script = 0; script < numScripts; script++) {
1057 // default lang
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,
1076 glyphs, nullptr);
1077 if (hb_set_has(glyphs, aGlyph)) {
1078 aHasGlyph = true;
1079 break;
1083 // look for the glyph among specific feature lookups
1084 hb_set_clear(glyphs);
1085 index = -1;
1086 while (hb_set_next(specificFeatureLookups, &index)) {
1087 hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs,
1088 glyphs, nullptr);
1089 if (hb_set_has(glyphs, aGlyph)) {
1090 aHasGlyphSpecific = true;
1091 break;
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();
1135 TimeStamp start;
1136 if (MOZ_UNLIKELY(log)) {
1137 start = TimeStamp::Now();
1140 bool result = false;
1142 uint32_t spaceGlyph = GetSpaceGlyph();
1143 if (!spaceGlyph) {
1144 return;
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'),
1156 Script::COMMON);
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)
1159 // knows about.
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,
1170 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;
1187 do {
1188 len = ArrayLength(scriptTags);
1189 hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, offset, &len,
1190 scriptTags);
1191 for (uint32_t i = 0; i < len; i++) {
1192 bool isDefaultFeature = false;
1193 Script s;
1194 if (!HasLookupRuleWithGlyphByScript(
1195 face, HB_OT_TAG_GSUB, scriptTags[i], offset + i, spaceGlyph,
1196 *sDefaultFeatures, isDefaultFeature) ||
1197 !sScriptTagToCode->Get(scriptTags[i], &s)) {
1198 continue;
1200 result = true;
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);
1205 } else {
1206 mFontEntry->mNonDefaultSubSpaceFeatures[index] |= (1 << bit);
1209 offset += len;
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) {
1229 result = true;
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;
1240 LOG_FONTINIT(
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) {
1264 return false;
1267 // default features have space lookups ==> true
1268 if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, Script::COMMON) ||
1269 HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, aRunScript)) {
1270 return true;
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,
1276 Script::COMMON) ||
1277 HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures, aRunScript)) &&
1278 (!mStyle.featureSettings.IsEmpty() ||
1279 !mFontEntry->mFeatureSettings.IsEmpty())) {
1280 return true;
1283 return false;
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()) {
1291 return false;
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) {
1311 return false;
1314 // if font has substitution rules or non-kerning positioning rules
1315 // that involve spaces, bypass
1316 if (HasSubstitutionRulesWithSpaceLookups(aRunScript) ||
1317 mFontEntry->mHasSpaceFeaturesNonKerning) {
1318 return true;
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;
1327 return false;
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'));
1348 if (!ok) {
1349 aSyntheticLowerToSmallCaps = true;
1351 break;
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'));
1355 if (!ok) {
1356 aSyntheticLowerToSmallCaps = true;
1357 aSyntheticUpperToSmallCaps = true;
1359 break;
1360 case NS_FONT_VARIANT_CAPS_PETITECAPS:
1361 ok = SupportsFeature(aScript, HB_TAG('p', 'c', 'a', 'p'));
1362 if (!ok) {
1363 ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p'));
1364 aFallbackToSmallCaps = ok;
1366 if (!ok) {
1367 aSyntheticLowerToSmallCaps = true;
1369 break;
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'));
1373 if (!ok) {
1374 ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')) &&
1375 SupportsFeature(aScript, HB_TAG('c', '2', 's', 'c'));
1376 aFallbackToSmallCaps = ok;
1378 if (!ok) {
1379 aSyntheticLowerToSmallCaps = true;
1380 aSyntheticUpperToSmallCaps = true;
1382 break;
1383 default:
1384 break;
1387 NS_ASSERTION(
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");
1394 return 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),
1401 aLength);
1402 return SupportsSubSuperscript(aSubSuperscript, unicodeString.get(), aLength,
1403 aRunScript);
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)) {
1418 return false;
1421 // xxx - for graphite, don't really know how to sniff lookups so bail
1422 if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1423 return true;
1426 if (!mHarfBuzzShaper) {
1427 mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
1429 gfxHarfBuzzShaper* shaper =
1430 static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get());
1431 if (!shaper->Initialize()) {
1432 return false;
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])) {
1447 i++;
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)) {
1467 return false;
1470 // xxx - for graphite, don't really know how to sniff lookups so bail
1471 if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1472 return true;
1475 if (!mHarfBuzzShaper) {
1476 mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
1478 gfxHarfBuzzShaper* shaper =
1479 static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get());
1480 if (!shaper->Initialize()) {
1481 return false;
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) {
1493 aFeatureOn = false;
1495 if (mStyle.featureSettings.IsEmpty() &&
1496 GetFontEntry()->mFeatureSettings.IsEmpty()) {
1497 return false;
1500 // add feature values from font
1501 bool featureSet = false;
1502 uint32_t i, count;
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) {
1509 featureSet = true;
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) {
1520 featureSet = true;
1521 aFeatureOn = (feature.mValue != 0);
1525 return featureSet;
1528 void gfxFont::InitializeScaledFont() {
1529 if (!mAzureScaledFont) {
1530 return;
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
1541 * processing here.
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;
1554 default:
1555 return AntialiasMode::DEFAULT;
1559 class GlyphBufferAzure {
1560 #define AUTO_BUFFER_SIZE (2048 / sizeof(Glyph))
1562 typedef mozilla::image::imgDrawingParams imgDrawingParams;
1564 public:
1565 GlyphBufferAzure(const TextRunDrawParams& aRunParams,
1566 const FontDrawParams& aFontParams)
1567 : mRunParams(aRunParams),
1568 mFontParams(aFontParams),
1569 mBuffer(*mAutoBuffer.addr()),
1570 mBufSize(AUTO_BUFFER_SIZE),
1571 mCapacity(0),
1572 mNumGlyphs(0) {}
1574 ~GlyphBufferAzure() {
1575 if (mNumGlyphs > 0) {
1576 FlushGlyphs();
1579 if (mBuffer != *mAutoBuffer.addr()) {
1580 free(mBuffer);
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(
1594 kMaxCapacity,
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) {
1598 return;
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));
1608 } else {
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);
1619 Flush();
1621 Glyph* glyph = mBuffer + mNumGlyphs++;
1622 glyph->mIndex = aGlyphID;
1623 glyph->mPosition = aPt;
1626 void Flush() {
1627 if (mNumGlyphs > 0) {
1628 FlushGlyphs();
1629 mNumGlyphs = 0;
1633 const TextRunDrawParams& mRunParams;
1634 const FontDrawParams& mFontParams;
1636 private:
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) {
1658 Pattern* pat;
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);
1667 if (!fillPattern) {
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
1674 : nullptr);
1675 } else {
1676 pat = nullptr;
1678 } else {
1679 pat = fillPattern->GetPattern(mRunParams.dt);
1682 if (pat) {
1683 mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf, *pat,
1684 mFontParams.drawOptions);
1686 } else {
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(
1711 mRunParams.dt,
1712 aState.patternTransformChanged ? &aState.patternTransform : nullptr);
1714 if (pat) {
1715 FlushStroke(aBuffer, *pat);
1717 } else {
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
1735 // itself?
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.
1767 Glyph* mBuffer;
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
1788 return 1.0;
1791 gfx::Float m = sqrtf(t.width * t.width + t.height * t.height);
1793 NS_ASSERTION(m != 0.0, "degenerate transform while synthetic bolding");
1794 if (m == 0.0) {
1795 return 0.0; // effectively disables offset
1798 // scale factor so that offsets are 1px in device pixels
1799 return 1.0 / m;
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
1808 gfx::Point* aPt,
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()) {
1830 float advance =
1831 glyphData->GetSimpleAdvance() * aBuffer.mFontParams.advanceDirection;
1832 if (aBuffer.mRunParams.isRTL) {
1833 inlineCoord += advance;
1835 DrawOneGlyph<FC>(glyphData->GetSimpleGlyph(), *aPt, aBuffer,
1836 &emittedGlyphs);
1837 if (!aBuffer.mRunParams.isRTL) {
1838 inlineCoord += advance;
1840 } else {
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) {
1849 float advance =
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,
1856 details, *aPt)) {
1857 return false;
1859 } else {
1860 gfx::Point glyphPt(
1861 *aPt + (aOffsetMatrix
1862 ? aOffsetMatrix->TransformPoint(details->mOffset)
1863 : details->mOffset));
1864 DrawOneGlyph<FC>(details->mGlyphID, glyphPt, aBuffer,
1865 &emittedGlyphs);
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 &&
1906 !textDrawer) {
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.
1911 aBuffer.Flush();
1912 matrixRestore.SetContext(runParams.context);
1913 gfx::Point skewPt(
1914 devPt.x + GetMetrics(nsFontMetrics::eVertical).emHeight / 2, devPt.y);
1915 gfx::Matrix mat =
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) {
1925 return;
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,
1932 *aEmittedGlyphs)) {
1933 return;
1937 if (fontParams.haveColorGlyphs &&
1938 !gfxPlatform::GetPlatform()->HasNativeColrFontSupport() &&
1939 RenderColorGlyph(runParams.dt, runParams.context, textDrawer,
1940 fontParams.scaledFont, fontParams.drawOptions, devPt,
1941 aGlyphID)) {
1942 return;
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;
1951 } else {
1952 devPt.x += fontParams.synBoldOnePixelOffset;
1954 aBuffer.OutputGlyph(aGlyphID, devPt);
1957 if (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont &&
1958 !textDrawer) {
1959 aBuffer.Flush();
1961 } else {
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;
1978 Matrix mat;
1979 if (textDrawer) {
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);
1988 matPtr = &mat;
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
1996 // transform.
1997 Rect glyphRect =
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 &&
2007 !textDrawer) {
2008 matrixRestore.SetContext(aRunParams.context);
2009 gfx::Matrix mat =
2010 aRunParams.context->CurrentMatrix()
2011 .PreTranslate(pt)
2012 .PreMultiply(gfx::Matrix(1, 0, aFontParams.obliqueSkew, 1, 0, 0))
2013 .PreTranslate(-pt);
2014 aRunParams.context->SetMatrix(mat);
2017 gfxFontMissingGlyphs::DrawMissingGlyph(aDetails->mGlyphID, glyphRect,
2018 *aRunParams.dt,
2019 PatternFromState(aRunParams.context),
2020 1.0 / aRunParams.devPerApp, matPtr);
2022 return true;
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) {
2072 return;
2075 FontDrawParams fontParams;
2077 if (aRunParams.drawOpts) {
2078 fontParams.drawOptions = *aRunParams.drawOpts;
2081 if (aRunParams.allowGDI) {
2082 fontParams.scaledFont = GetScaledFont(aRunParams.dt);
2083 } else {
2084 fontParams.scaledFont = GetScaledFontNoGDI(aRunParams.dt);
2086 if (!fontParams.scaledFont) {
2087 return;
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;
2096 if (textDrawer) {
2097 fontParams.isVerticalFont = aRunParams.isVerticalRun;
2098 } else {
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
2107 // modified.
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
2125 float sidewaysDir =
2126 (aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
2127 ? -1.0f
2128 : (aOrientation ==
2129 gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT
2130 ? 1.0f
2131 : 0.0f));
2132 // If we're rendering a sideways run, we need to push a rotation transform to
2133 // the context.
2134 if (sidewaysDir != 0.0f) {
2135 if (textDrawer) {
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
2147 // rotated right.
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 |
2159 (aOrientation ==
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};
2167 offsetMatrix =
2168 (aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT)
2169 ? &kSidewaysLeft
2170 : &kSidewaysRight;
2171 } else {
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()
2179 .PreTranslate(p)
2180 . // translate origin for rotation
2181 PreRotate(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 &&
2210 !textDrawer) {
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);
2218 gfx::Matrix mat =
2219 aRunParams.context->CurrentMatrix()
2220 .PreTranslate(p)
2221 .PreMultiply(gfx::Matrix(1, 0, -fontParams.obliqueSkew, 1, 0, 0))
2222 .PreTranslate(-p);
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;
2254 } else {
2255 // use as many strikes as needed for the increased advance
2256 fontParams.extraStrikes = NS_lroundf(std::max(1.0, extraStrikes));
2259 } else {
2260 fontParams.synBoldOnePixelOffset = 0;
2261 fontParams.extraStrikes = 0;
2264 bool oldSubpixelAA = aRunParams.dt->GetPermitSubpixelAA();
2265 if (!AllowSubpixelAA()) {
2266 aRunParams.dt->SetPermitSubpixelAA(false);
2269 Matrix mat;
2270 Matrix oldMat = aRunParams.dt->GetTransform();
2272 fontParams.drawOptions.mAntialiasMode = Get2DAAMode(mAntialiasOption);
2274 if (mStyle.baselineOffset != 0.0) {
2275 baseline +=
2276 mStyle.baselineOffset * aTextRun->GetAppUnitsPerDevUnit() * baselineDir;
2279 bool emittedGlyphs;
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
2284 // is specified.
2285 GlyphBufferAzure buffer(aRunParams, fontParams);
2286 if (fontParams.haveSVGGlyphs || fontParams.haveColorGlyphs ||
2287 fontParams.extraStrikes ||
2288 (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont &&
2289 !textDrawer)) {
2290 if (aRunParams.spacing) {
2291 emittedGlyphs =
2292 DrawGlyphs<FontComplexityT::ComplexFont, SpacingT::HasSpacing>(
2293 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
2294 } else {
2295 emittedGlyphs =
2296 DrawGlyphs<FontComplexityT::ComplexFont, SpacingT::NoSpacing>(
2297 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
2299 } else {
2300 if (aRunParams.spacing) {
2301 emittedGlyphs =
2302 DrawGlyphs<FontComplexityT::SimpleFont, SpacingT::HasSpacing>(
2303 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
2304 } else {
2305 emittedGlyphs =
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)) {
2336 return false;
2339 if (aTextDrawer) {
2340 // WebRender doesn't support SVG Glyphs.
2341 // (pretend to succeed, output doesn't matter, we will emit a blob)
2342 aTextDrawer->FoundUnsupportedFeature();
2343 return true;
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();
2358 return true;
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,
2388 layerColors)) {
2389 return false;
2392 // Default to opaque rendering (non-webrender applies alpha with a layer)
2393 float alpha = 1.0;
2394 if (aTextDrawer) {
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();
2402 return true;
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();
2415 layerIndex++) {
2416 Glyph glyph;
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),
2427 aDrawOptions);
2429 return true;
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()) {
2436 return true;
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()) {
2444 return false;
2446 uint32_t gid = 0;
2447 if (gfxFontUtils::IsVarSelector(aNextCh)) {
2448 gid = shaper->GetVariationGlyph(aCh, aNextCh);
2450 if (!gid) {
2451 gid = shaper->GetNominalGlyph(aCh);
2453 if (!gid) {
2454 return false;
2456 // Check if there is a COLR/CPAL or SVG glyph for this ID.
2457 if (fe->TryGetColorGlyphs() && fe->HasColorLayersForGlyph(gid)) {
2458 return true;
2460 if (fe->TryGetSVGData(this) && fe->HasSVGGlyph(gid)) {
2461 return true;
2463 return false;
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,
2500 Spacing* aSpacing,
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) {
2508 if (!mNonAAFont) {
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
2513 if (mNonAAFont) {
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.
2539 baselineOffset =
2540 appUnitsPerDevUnit * (fontMetrics.emAscent - fontMetrics.emDescent) / 2;
2543 RunMetrics metrics;
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);
2553 return metrics;
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()))
2565 ? nullptr
2566 : GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit());
2567 double x = 0;
2568 if (aSpacing) {
2569 x += aSpacing[0].mBefore;
2571 uint32_t spaceGlyph = GetSpaceGlyph();
2572 bool allGlyphsInvisible = true;
2573 uint32_t i;
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) &&
2586 extents) {
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);
2593 } else {
2594 gfxRect glyphRect;
2595 if (!extents->GetTightGlyphExtentsAppUnits(this, aRefDrawTarget,
2596 glyphIndex, &glyphRect)) {
2597 glyphRect = gfxRect(0, metrics.mBoundingBox.Y(), advance,
2598 metrics.mBoundingBox.Height());
2600 if (isRTL) {
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);
2609 x += advance;
2610 } else {
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!");
2618 uint32_t j;
2619 for (j = 0; j < glyphCount; ++j, ++details) {
2620 uint32_t glyphIndex = details->mGlyphID;
2621 double advance = details->mAdvance;
2622 gfxRect glyphRect;
2623 if (glyphData->IsMissing() || !extents ||
2624 !extents->GetTightGlyphExtentsAppUnits(this, aRefDrawTarget,
2625 glyphIndex, &glyphRect)) {
2626 // We might have failed to get glyph extents due to
2627 // OOM or something
2628 glyphRect = gfxRect(0, -metrics.mAscent, advance,
2629 metrics.mAscent + metrics.mDescent);
2631 if (isRTL) {
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);
2637 } else {
2638 glyphRect.MoveByX(x + details->mOffset.x);
2640 glyphRect.MoveByY(details->mOffset.y);
2641 metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect);
2642 x += advance;
2646 // Every other glyph type is ignored
2647 if (aSpacing) {
2648 double space = aSpacing[i - aStart].mAfter;
2649 if (i + 1 < aEnd) {
2650 space += aSpacing[i + 1 - aStart].mBefore;
2652 x += space;
2656 if (allGlyphsInvisible) {
2657 metrics.mBoundingBox.SetEmpty();
2658 } else {
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);
2667 if (isRTL) {
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();
2677 if (skew != 0.0) {
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());
2687 } else {
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;
2706 return metrics;
2709 void gfxFont::AgeCachedWords() {
2710 if (mWordCache) {
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!");
2715 it.Remove();
2716 } else if (entry->mShapedWord->IncrementAge() == kShapedWordCacheMaxAge) {
2717 it.Remove();
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;
2738 // else return 0.
2739 static char16_t IsBoundarySpace(char16_t aChar, char16_t aNextChar) {
2740 if ((aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar)) {
2741 return aChar;
2743 return 0;
2746 #ifdef __GNUC__
2747 # define GFX_MAYBE_UNUSED __attribute__((unused))
2748 #else
2749 # define GFX_MAYBE_UNUSED
2750 #endif
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");
2763 ClearCachedWords();
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);
2771 if (!entry) {
2772 NS_WARNING("failed to create word cache entry - expect missing text");
2773 return nullptr;
2775 gfxShapedWord* sw = entry->mShapedWord.get();
2777 if (sw) {
2778 sw->ResetAge();
2779 #ifndef RELEASE_OR_BETA
2780 if (aTextPerf) {
2781 aTextPerf->current.wordCacheHit++;
2783 #endif
2784 return sw;
2787 #ifndef RELEASE_OR_BETA
2788 if (aTextPerf) {
2789 aTextPerf->current.wordCacheMiss++;
2791 #endif
2793 sw = gfxShapedWord::Create(aText, aLength, aRunScript, aLanguage,
2794 aAppUnitsPerDevUnit, aFlags, aRounding);
2795 entry->mShapedWord.reset(sw);
2796 if (!sw) {
2797 NS_WARNING("failed to create gfxShapedWord - expect missing text");
2798 return nullptr;
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");
2806 return sw;
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();
2817 if (!sw) {
2818 return false;
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) {
2825 return false;
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++) {
2840 return false;
2843 return true;
2845 NS_ASSERTION(!(aKey->mFlags & gfx::ShapedTextFlags::TEXT_IS_8BIT) &&
2846 !aKey->mTextIs8Bit,
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);
2857 nsAutoString utf16;
2858 AppendASCIItoUTF16(ascii, utf16);
2859 if (utf16.Length() != aLength) {
2860 return false;
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,
2880 aShapedText)) {
2881 PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical,
2882 aShapedText);
2883 return true;
2888 if (!mHarfBuzzShaper) {
2889 mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
2891 if (mHarfBuzzShaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript,
2892 aLanguage, aVertical, aRounding,
2893 aShapedText)) {
2894 PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical,
2895 aShapedText);
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();
2902 float tracking =
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
2907 // helper here.
2908 aShapedText->AdjustAdvancesForSyntheticBold(tracking, aOffset, aLength);
2910 return true;
2913 NS_WARNING_ASSERTION(false, "shaper failed, expect scrambled/missing text");
2914 return false;
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,
2926 aLength);
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);
2947 bool ok = true;
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)) {
2959 uint32_t i;
2960 for (i = 0; i < BACKTRACK_LIMIT; ++i) {
2961 if (aTextRun->IsClusterStart(aOffset + fragLen - i)) {
2962 fragLen -= i;
2963 break;
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])) {
2971 --fragLen;
2977 ok = ShapeText(aDrawTarget, aText, aOffset, fragLen, aScript, aLanguage,
2978 aVertical, aRounding, aTextRun);
2980 aText += fragLen;
2981 aOffset += fragLen;
2982 aLength -= fragLen;
2985 return ok;
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 &#13;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;
3004 bool ok = true;
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
3012 if (!invalid) {
3013 continue;
3016 if (length > 0) {
3017 ok = ShapeFragmentWithoutWordCache(
3018 aDrawTarget, aText + fragStart, aOffset + fragStart, length, aScript,
3019 aLanguage, aVertical, aRounding, aTextRun);
3022 if (i == aLength) {
3023 break;
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
3029 if (ch == '\t') {
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,
3041 aTextRun);
3042 } else {
3043 aTextRun->SetMissingGlyph(aOffset + i, ch, this);
3046 fragStart = i + 1;
3049 NS_WARNING_ASSERTION(ok, "failed to shape text - expect garbled text");
3050 return ok;
3053 #ifndef RELEASE_OR_BETA
3054 # define TEXT_PERF_INCR(tp, m) (tp ? (tp)->current.m++ : 0)
3055 #else
3056 # define TEXT_PERF_INCR(tp, m)
3057 #endif
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++) {
3068 if (*ch == 0x20) {
3069 return true;
3072 return false;
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) {
3083 return true;
3086 gfxTextPerfMetrics* tp = nullptr;
3087 RoundingFlags rounding = GetRoundOffsetsToPixels(aDrawTarget);
3089 #ifndef RELEASE_OR_BETA
3090 tp = aTextRun->GetFontGroup()->GetTextPerfMetrics();
3091 if (tp) {
3092 if (mStyle.systemFont) {
3093 tp->current.numChromeTextRuns++;
3094 } else {
3095 tp->current.numContentTextRuns++;
3097 tp->current.numChars += aRunLength;
3098 if (aRunLength > tp->current.maxTextRunLen) {
3099 tp->current.maxTextRunLen = aRunLength;
3102 #endif
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);
3131 InitWordCache();
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;
3144 uint32_t hash = 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) {
3150 T ch = nextCh;
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)) {
3161 wordIs8Bit = false;
3163 // include this character in the hash, and move on to next
3164 hash = gfxShapedWord::HashMix(hash, ch);
3165 continue;
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);
3177 if (!ok) {
3178 return false;
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
3184 // adjustment here
3185 if (sizeof(T) == sizeof(char16_t)) {
3186 if (wordIs8Bit) {
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);
3193 if (sw) {
3194 aTextRun->CopyGlyphDataFrom(sw, aRunStart + wordStart);
3195 } else {
3196 return false; // failed, presumably out of memory?
3200 if (boundary) {
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);
3217 if (sw) {
3218 aTextRun->CopyGlyphDataFrom(sw, aRunStart + i);
3219 if (boundary == ' ') {
3220 aTextRun->GetCharacterGlyphs()[aRunStart + i].SetIsSpace();
3222 } else {
3223 return false;
3226 hash = 0;
3227 wordStart = i + 1;
3228 wordIs8Bit = true;
3229 continue;
3232 if (i == aRunLength) {
3233 break;
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
3241 if (ch == '\t') {
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);
3254 } else {
3255 aTextRun->SetMissingGlyph(aRunStart + i, ch, this);
3259 hash = 0;
3260 wordStart = i + 1;
3261 wordIs8Bit = true;
3264 return true;
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);
3277 template <>
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) {
3283 bool ok = true;
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.
3305 if (i < aLength) {
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]);
3309 extraCodeUnits = 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;
3315 } else {
3316 if (ch != ToUpperCase(ch) || SpecialUpper(ch)) {
3317 // ch is lower case
3318 chAction = (aSyntheticLower ? kUppercaseReduce : kNoChange);
3319 } else if (ch != ToLowerCase(ch)) {
3320 // ch is upper case
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;
3330 uint32_t ch2 =
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;
3347 gfxFont* f = this;
3348 switch (runAction) {
3349 case kNoChange:
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)) {
3356 ok = false;
3358 break;
3360 case kUppercaseReduce:
3361 // use reduced-size font, then fall through to uppercase the text
3362 f = smallCapsFont;
3363 [[fallthrough]];
3365 case kUppercase:
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,
3375 deletedCharsArray);
3377 if (mergeNeeded) {
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 &params, 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)) {
3393 ok = false;
3394 } else {
3395 RefPtr<gfxTextRun> mergedRun(gfxTextRun::Create(
3396 &params, 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);
3405 } else {
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)) {
3412 ok = false;
3415 break;
3418 runStart = i;
3421 i += extraCodeUnits;
3422 if (i < aLength) {
3423 runAction = chAction;
3427 return ok;
3430 template <>
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),
3437 aLength);
3438 return InitFakeSmallCapsRun(aPresContext, aDrawTarget, aTextRun,
3439 static_cast<const char16_t*>(unicodeString.get()),
3440 aOffset, aLength, aMatchType, aOrientation,
3441 aScript, aLanguage, aSyntheticLower,
3442 aSyntheticUpper);
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);
3467 if (glyphExtents) {
3468 mGlyphExtentsArray.AppendElement(glyphExtents);
3469 // Initialize the extents of a space glyph, assuming that spaces don't
3470 // render anything!
3471 glyphExtents->SetContainedGlyphWidthAppUnits(GetSpaceGlyph(), 0);
3473 return glyphExtents;
3476 void gfxFont::SetupGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphID,
3477 bool aNeedTight, gfxGlyphExtents* aExtents) {
3478 gfxRect svgBounds;
3479 if (mFontEntry->TryGetSVGData(this) && mFontEntry->HasSVGGlyph(aGlyphID) &&
3480 mFontEntry->GetSVGGlyphExtents(aDrawTarget, aGlyphID, GetAdjustedSize(),
3481 &svgBounds)) {
3482 gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit();
3483 aExtents->SetTightGlyphExtents(
3484 aGlyphID, gfxRect(svgBounds.X() * d2a, svgBounds.Y() * d2a,
3485 svgBounds.Width() * d2a, svgBounds.Height() * d2a));
3486 return;
3489 gfxRect bounds;
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));
3501 return;
3504 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
3505 if (!aNeedTight) {
3506 ++gGlyphExtentsSetupFallBackToTight;
3508 #endif
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
3520 // not available.
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');
3529 uint32_t len;
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) {
3536 return false;
3538 mFUnitsConvFactor = GetAdjustedSize() / unitsPerEm;
3541 // 'hhea' table is required for the advanceWidthMax field
3542 gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag);
3543 if (!hheaTable) {
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)) {
3549 return false;
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);
3561 if (os2Table) {
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);
3570 #undef SET_SIGNED
3571 #undef SET_UNSIGNED
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,
3579 &position)) {
3580 aMetrics.maxAscent = FixedToFloat(position);
3582 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER,
3583 &position)) {
3584 aMetrics.maxDescent = -FixedToFloat(position);
3586 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP,
3587 &position)) {
3588 aMetrics.externalLeading = FixedToFloat(position);
3591 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_UNDERLINE_OFFSET,
3592 &position)) {
3593 aMetrics.underlineOffset = FixedToFloat(position);
3595 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_UNDERLINE_SIZE,
3596 &position)) {
3597 aMetrics.underlineSize = FixedToFloat(position);
3599 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_STRIKEOUT_OFFSET,
3600 &position)) {
3601 aMetrics.strikeoutOffset = FixedToFloat(position);
3603 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_STRIKEOUT_SIZE,
3604 &position)) {
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,
3611 &position) &&
3612 position > 0) {
3613 aMetrics.xHeight = FixedToFloat(position);
3615 if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_CAP_HEIGHT,
3616 &position) &&
3617 position > 0) {
3618 aMetrics.capHeight = FixedToFloat(position);
3620 hb_font_destroy(hbFont);
3622 mIsValid = true;
3624 return true;
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;
3654 } else {
3655 aMetrics.internalLeading = 0.0;
3658 aMetrics.emAscent =
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
3681 // font.
3682 if (mStyle.AdjustedSizeMustBeZero()) {
3683 memset(aMetrics, 0, sizeof(gfxFont::Metrics));
3684 return;
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;
3717 return;
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);
3740 } else {
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');
3790 uint32_t len;
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
3871 // from above.
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);
3900 if (postTable) {
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;
3916 #undef SET_UNSIGNED
3917 #undef SET_SIGNED
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.
3945 switch (aCh) {
3946 case 0x2000: // en quad
3947 case 0x2002:
3948 return GetAdjustedSize() / 2; // en space
3949 case 0x2001: // em quad
3950 case 0x2003:
3951 return GetAdjustedSize(); // em space
3952 case 0x2004:
3953 return GetAdjustedSize() / 3; // three-per-em space
3954 case 0x2005:
3955 return GetAdjustedSize() / 4; // four-per-em space
3956 case 0x2006:
3957 return GetAdjustedSize() / 6; // six-per-em space
3958 case 0x2007:
3959 return GetMetrics(nsFontMetrics::eHorizontal)
3960 .ZeroOrAveCharWidth(); // figure space
3961 case 0x2008:
3962 return GetMetrics(nsFontMetrics::eHorizontal)
3963 .spaceWidth; // punctuation space
3964 case 0x2009:
3965 return GetAdjustedSize() / 5; // thin space
3966 case 0x200a:
3967 return GetAdjustedSize() / 10; // hair space
3968 case 0x202f:
3969 return GetAdjustedSize() / 5; // narrow no-break space
3970 case 0x3000:
3971 return GetAdjustedSize(); // ideographic space
3972 default:
3973 return -1.0;
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);
3983 if (mWordCache) {
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),
4012 sizeAdjust(0.0f),
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)),
4022 systemFont(true),
4023 printerFont(false),
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)
4037 : size(aSize),
4038 baselineOffset(0.0f),
4039 languageOverride(aLanguageOverride),
4040 fontSmoothingBackgroundColor(NS_RGBA(0, 0, 0, 0)),
4041 weight(aWeight),
4042 stretch(aStretch),
4043 style(aStyle),
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:
4057 sizeAdjust = 0.0f;
4058 break;
4059 case FontSizeAdjust::Tag::ExHeight:
4060 sizeAdjust = aSizeAdjust.AsExHeight();
4061 break;
4062 case FontSizeAdjust::Tag::CapHeight:
4063 sizeAdjust = aSizeAdjust.AsCapHeight();
4064 break;
4065 case FontSizeAdjust::Tag::ChWidth:
4066 sizeAdjust = aSizeAdjust.AsChWidth();
4067 break;
4068 case FontSizeAdjust::Tag::IcWidth:
4069 sizeAdjust = aSizeAdjust.AsIcWidth();
4070 break;
4071 case FontSizeAdjust::Tag::IcHeight:
4072 sizeAdjust = aSizeAdjust.AsIcHeight();
4073 break;
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;
4092 sizeAdjust = 0.0f;
4093 sizeAdjustBasis = uint8_t(FontSizeAdjust::Tag::None);
4094 } else if (size < 0.0) {
4095 NS_WARNING("negative font size");
4096 size = 0.0;
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) {
4112 MOZ_ASSERT(
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;
4119 } else {
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;
4129 } else {
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();
4145 if (face) {
4146 if (hb_ot_math_has_data(face)) {
4147 mMathTable = MakeUnique<gfxMathTable>(face, GetAdjustedSize());
4149 hb_face_destroy(face);
4153 return !!mMathTable;