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/. */
5 
6 #include "gfxFont.h"
7 
8 #include "mozilla/BinarySearch.h"
9 #include "mozilla/DebugOnly.h"
10 #include "mozilla/FontPropertyTypes.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/IntegerRange.h"
13 #include "mozilla/intl/Segmenter.h"
14 #include "mozilla/MathAlgorithms.h"
15 #include "mozilla/StaticPrefs_gfx.h"
16 #include "mozilla/SVGContextPaint.h"
17 
18 #include "mozilla/Logging.h"
19 
20 #include "nsITimer.h"
21 
22 #include "gfxGlyphExtents.h"
23 #include "gfxPlatform.h"
24 #include "gfxTextRun.h"
25 #include "nsGkAtoms.h"
26 
27 #include "gfxTypes.h"
28 #include "gfxContext.h"
29 #include "gfxFontMissingGlyphs.h"
30 #include "gfxGraphiteShaper.h"
31 #include "gfxHarfBuzzShaper.h"
32 #include "gfxUserFontSet.h"
33 #include "nsCRT.h"
34 #include "nsSpecialCasingData.h"
35 #include "nsTextRunTransformations.h"
36 #include "nsUGenCategory.h"
37 #include "nsUnicodeProperties.h"
38 #include "nsStyleConsts.h"
39 #include "mozilla/AppUnits.h"
40 #include "mozilla/Likely.h"
41 #include "mozilla/MemoryReporting.h"
42 #include "mozilla/Preferences.h"
43 #include "mozilla/Services.h"
44 #include "mozilla/Telemetry.h"
45 #include "gfxMathTable.h"
46 #include "gfxSVGGlyphs.h"
47 #include "gfx2DGlue.h"
48 #include "TextDrawTarget.h"
49 
50 #include "ThebesRLBox.h"
51 
52 #include "GreekCasing.h"
53 
54 #include "cairo.h"
55 #ifdef XP_WIN
56 #  include "cairo-win32.h"
57 #  include "gfxWindowsPlatform.h"
58 #endif
59 
60 #include "harfbuzz/hb.h"
61 #include "harfbuzz/hb-ot.h"
62 
63 #include <algorithm>
64 #include <limits>
65 #include <cmath>
66 
67 using namespace mozilla;
68 using namespace mozilla::gfx;
69 using namespace mozilla::unicode;
70 using mozilla::services::GetObserverService;
71 
72 gfxFontCache* gfxFontCache::gGlobalCache = nullptr;
73 
74 #ifdef DEBUG_roc
75 #  define DEBUG_TEXT_RUN_STORAGE_METRICS
76 #endif
77 
78 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
79 uint32_t gTextRunStorageHighWaterMark = 0;
80 uint32_t gTextRunStorage = 0;
81 uint32_t gFontCount = 0;
82 uint32_t gGlyphExtentsCount = 0;
83 uint32_t gGlyphExtentsWidthsTotalSize = 0;
84 uint32_t gGlyphExtentsSetupEagerSimple = 0;
85 uint32_t gGlyphExtentsSetupEagerTight = 0;
86 uint32_t gGlyphExtentsSetupLazyTight = 0;
87 uint32_t gGlyphExtentsSetupFallBackToTight = 0;
88 #endif
89 
90 #define LOG_FONTINIT(args) \
91   MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args)
92 #define LOG_FONTINIT_ENABLED() \
93   MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug)
94 
95 /*
96  * gfxFontCache - global cache of gfxFont instances.
97  * Expires unused fonts after a short interval;
98  * notifies fonts to age their cached shaped-word records;
99  * observes memory-pressure notification and tells fonts to clear their
100  * shaped-word caches to free up memory.
101  */
102 
103 MOZ_DEFINE_MALLOC_SIZE_OF(FontCacheMallocSizeOf)
104 
NS_IMPL_ISUPPORTS(gfxFontCache::MemoryReporter,nsIMemoryReporter)105 NS_IMPL_ISUPPORTS(gfxFontCache::MemoryReporter, nsIMemoryReporter)
106 
107 /*virtual*/
108 gfxTextRunFactory::~gfxTextRunFactory() {
109   // Should not be dropped by stylo
110   MOZ_ASSERT(NS_IsMainThread());
111 }
112 
113 NS_IMETHODIMP
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)114 gfxFontCache::MemoryReporter::CollectReports(
115     nsIHandleReportCallback* aHandleReport, nsISupports* aData,
116     bool aAnonymize) {
117   FontCacheSizes sizes;
118 
119   gfxFontCache::GetCache()->AddSizeOfIncludingThis(&FontCacheMallocSizeOf,
120                                                    &sizes);
121 
122   MOZ_COLLECT_REPORT("explicit/gfx/font-cache", KIND_HEAP, UNITS_BYTES,
123                      sizes.mFontInstances,
124                      "Memory used for active font instances.");
125 
126   MOZ_COLLECT_REPORT("explicit/gfx/font-shaped-words", KIND_HEAP, UNITS_BYTES,
127                      sizes.mShapedWords,
128                      "Memory used to cache shaped glyph data.");
129 
130   return NS_OK;
131 }
132 
NS_IMPL_ISUPPORTS(gfxFontCache::Observer,nsIObserver)133 NS_IMPL_ISUPPORTS(gfxFontCache::Observer, nsIObserver)
134 
135 NS_IMETHODIMP
136 gfxFontCache::Observer::Observe(nsISupports* aSubject, const char* aTopic,
137                                 const char16_t* someData) {
138   if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
139     gfxFontCache* fontCache = gfxFontCache::GetCache();
140     if (fontCache) {
141       fontCache->FlushShapedWordCaches();
142     }
143   } else {
144     MOZ_ASSERT_UNREACHABLE("unexpected notification topic");
145   }
146   return NS_OK;
147 }
148 
Init()149 nsresult gfxFontCache::Init() {
150   NS_ASSERTION(!gGlobalCache, "Where did this come from?");
151   gGlobalCache = new gfxFontCache(GetMainThreadSerialEventTarget());
152   if (!gGlobalCache) {
153     return NS_ERROR_OUT_OF_MEMORY;
154   }
155   RegisterStrongMemoryReporter(new MemoryReporter());
156   return NS_OK;
157 }
158 
Shutdown()159 void gfxFontCache::Shutdown() {
160   delete gGlobalCache;
161   gGlobalCache = nullptr;
162 
163 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
164   printf("Textrun storage high water mark=%d\n", gTextRunStorageHighWaterMark);
165   printf("Total number of fonts=%d\n", gFontCount);
166   printf("Total glyph extents allocated=%d (size %d)\n", gGlyphExtentsCount,
167          int(gGlyphExtentsCount * sizeof(gfxGlyphExtents)));
168   printf("Total glyph extents width-storage size allocated=%d\n",
169          gGlyphExtentsWidthsTotalSize);
170   printf("Number of simple glyph extents eagerly requested=%d\n",
171          gGlyphExtentsSetupEagerSimple);
172   printf("Number of tight glyph extents eagerly requested=%d\n",
173          gGlyphExtentsSetupEagerTight);
174   printf("Number of tight glyph extents lazily requested=%d\n",
175          gGlyphExtentsSetupLazyTight);
176   printf("Number of simple glyph extent setups that fell back to tight=%d\n",
177          gGlyphExtentsSetupFallBackToTight);
178 #endif
179 }
180 
gfxFontCache(nsIEventTarget * aEventTarget)181 gfxFontCache::gfxFontCache(nsIEventTarget* aEventTarget)
182     : gfxFontCacheExpirationTracker(aEventTarget) {
183   nsCOMPtr<nsIObserverService> obs = GetObserverService();
184   if (obs) {
185     obs->AddObserver(new Observer, "memory-pressure", false);
186   }
187 
188   nsIEventTarget* target = nullptr;
189   if (XRE_IsContentProcess() && NS_IsMainThread()) {
190     target = aEventTarget;
191   }
192 
193   // Create the timer used to expire shaped-word records from each font's
194   // cache after a short period of non-use. We have a single timer in
195   // gfxFontCache that loops over all fonts known to the cache, to avoid
196   // the overhead of individual timers in each font instance.
197   // The timer will be started any time shaped word records are cached
198   // (and pauses itself when all caches become empty).
199   mWordCacheExpirationTimer = NS_NewTimer(target);
200 }
201 
~gfxFontCache()202 gfxFontCache::~gfxFontCache() {
203   // Ensure the user font cache releases its references to font entries,
204   // so they aren't kept alive after the font instances and font-list
205   // have been shut down.
206   gfxUserFontSet::UserFontCache::Shutdown();
207 
208   if (mWordCacheExpirationTimer) {
209     mWordCacheExpirationTimer->Cancel();
210     mWordCacheExpirationTimer = nullptr;
211   }
212 
213   // Expire everything that has a zero refcount, so we don't leak them.
214   AgeAllGenerations();
215   // All fonts should be gone.
216   NS_WARNING_ASSERTION(mFonts.Count() == 0,
217                        "Fonts still alive while shutting down gfxFontCache");
218   // Note that we have to delete everything through the expiration
219   // tracker, since there might be fonts not in the hashtable but in
220   // the tracker.
221 }
222 
KeyEquals(const KeyTypePointer aKey) const223 bool gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey) const {
224   const gfxCharacterMap* fontUnicodeRangeMap = mFont->GetUnicodeRangeMap();
225   return aKey->mFontEntry == mFont->GetFontEntry() &&
226          aKey->mStyle->Equals(*mFont->GetStyle()) &&
227          ((!aKey->mUnicodeRangeMap && !fontUnicodeRangeMap) ||
228           (aKey->mUnicodeRangeMap && fontUnicodeRangeMap &&
229            aKey->mUnicodeRangeMap->Equals(fontUnicodeRangeMap)));
230 }
231 
Lookup(const gfxFontEntry * aFontEntry,const gfxFontStyle * aStyle,const gfxCharacterMap * aUnicodeRangeMap)232 gfxFont* gfxFontCache::Lookup(const gfxFontEntry* aFontEntry,
233                               const gfxFontStyle* aStyle,
234                               const gfxCharacterMap* aUnicodeRangeMap) {
235   Key key(aFontEntry, aStyle, aUnicodeRangeMap);
236   HashEntry* entry = mFonts.GetEntry(key);
237 
238   Telemetry::Accumulate(Telemetry::FONT_CACHE_HIT, entry != nullptr);
239   if (!entry) return nullptr;
240 
241   return entry->mFont;
242 }
243 
AddNew(gfxFont * aFont)244 void gfxFontCache::AddNew(gfxFont* aFont) {
245   Key key(aFont->GetFontEntry(), aFont->GetStyle(),
246           aFont->GetUnicodeRangeMap());
247   HashEntry* entry = mFonts.PutEntry(key);
248   if (!entry) return;
249   gfxFont* oldFont = entry->mFont;
250   entry->mFont = aFont;
251   // Assert that we can find the entry we just put in (this fails if the key
252   // has a NaN float value in it, e.g. 'sizeAdjust').
253   MOZ_ASSERT(entry == mFonts.GetEntry(key));
254   // If someone's asked us to replace an existing font entry, then that's a
255   // bit weird, but let it happen, and expire the old font if it's not used.
256   if (oldFont && oldFont->GetExpirationState()->IsTracked()) {
257     // if oldFont == aFont, recount should be > 0,
258     // so we shouldn't be here.
259     NS_ASSERTION(aFont != oldFont, "new font is tracked for expiry!");
260     NotifyExpired(oldFont);
261   }
262 }
263 
NotifyReleased(gfxFont * aFont)264 void gfxFontCache::NotifyReleased(gfxFont* aFont) {
265   nsresult rv = AddObject(aFont);
266   if (NS_FAILED(rv)) {
267     // We couldn't track it for some reason. Kill it now.
268     DestroyFont(aFont);
269   }
270   // Note that we might have fonts that aren't in the hashtable, perhaps because
271   // of OOM adding to the hashtable or because someone did an AddNew where
272   // we already had a font. These fonts are added to the expiration tracker
273   // anyway, even though Lookup can't resurrect them. Eventually they will
274   // expire and be deleted.
275 }
276 
NotifyExpired(gfxFont * aFont)277 void gfxFontCache::NotifyExpired(gfxFont* aFont) {
278   aFont->ClearCachedWords();
279   RemoveObject(aFont);
280   DestroyFont(aFont);
281 }
282 
DestroyFont(gfxFont * aFont)283 void gfxFontCache::DestroyFont(gfxFont* aFont) {
284   Key key(aFont->GetFontEntry(), aFont->GetStyle(),
285           aFont->GetUnicodeRangeMap());
286   HashEntry* entry = mFonts.GetEntry(key);
287   if (entry && entry->mFont == aFont) {
288     mFonts.RemoveEntry(entry);
289   }
290   NS_ASSERTION(aFont->GetRefCount() == 0,
291                "Destroying with non-zero ref count!");
292   delete aFont;
293 }
294 
295 /*static*/
WordCacheExpirationTimerCallback(nsITimer * aTimer,void * aCache)296 void gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer,
297                                                     void* aCache) {
298   bool allEmpty = true;
299   gfxFontCache* cache = static_cast<gfxFontCache*>(aCache);
300   for (const auto& entry : cache->mFonts) {
301     allEmpty = entry.mFont->AgeCachedWords() && allEmpty;
302   }
303   if (allEmpty) {
304     cache->PauseWordCacheExpirationTimer();
305   }
306 }
307 
FlushShapedWordCaches()308 void gfxFontCache::FlushShapedWordCaches() {
309   for (const auto& entry : mFonts) {
310     entry.mFont->ClearCachedWords();
311   }
312   PauseWordCacheExpirationTimer();
313 }
314 
NotifyGlyphsChanged()315 void gfxFontCache::NotifyGlyphsChanged() {
316   for (const auto& entry : mFonts) {
317     entry.mFont->NotifyGlyphsChanged();
318   }
319 }
320 
AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,FontCacheSizes * aSizes) const321 void gfxFontCache::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
322                                           FontCacheSizes* aSizes) const {
323   // TODO: add the overhead of the expiration tracker (generation arrays)
324 
325   aSizes->mFontInstances += mFonts.ShallowSizeOfExcludingThis(aMallocSizeOf);
326   for (const auto& entry : mFonts) {
327     entry.mFont->AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
328   }
329 }
330 
AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,FontCacheSizes * aSizes) const331 void gfxFontCache::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
332                                           FontCacheSizes* aSizes) const {
333   aSizes->mFontInstances += aMallocSizeOf(this);
334   AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
335 }
336 
337 #define MAX_SSXX_VALUE 99
338 #define MAX_CVXX_VALUE 99
339 
LookupAlternateValues(const gfxFontFeatureValueSet & aFeatureLookup,const nsACString & aFamily,const StyleVariantAlternates & aAlternates,nsTArray<gfxFontFeature> & aFontFeatures)340 static void LookupAlternateValues(const gfxFontFeatureValueSet& aFeatureLookup,
341                                   const nsACString& aFamily,
342                                   const StyleVariantAlternates& aAlternates,
343                                   nsTArray<gfxFontFeature>& aFontFeatures) {
344   using Tag = StyleVariantAlternates::Tag;
345 
346   // historical-forms gets handled in nsFont::AddFontFeaturesToStyle.
347   if (aAlternates.IsHistoricalForms()) {
348     return;
349   }
350 
351   gfxFontFeature feature;
352   if (aAlternates.IsCharacterVariant()) {
353     for (auto& ident : aAlternates.AsCharacterVariant().AsSpan()) {
354       Span<const uint32_t> values = aFeatureLookup.GetFontFeatureValuesFor(
355           aFamily, NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT,
356           ident.AsAtom());
357       // nothing defined, skip
358       if (values.IsEmpty()) {
359         continue;
360       }
361       NS_ASSERTION(values.Length() <= 2,
362                    "too many values allowed for character-variant");
363       // character-variant(12 3) ==> 'cv12' = 3
364       uint32_t nn = values[0];
365       // ignore values greater than 99
366       if (nn == 0 || nn > MAX_CVXX_VALUE) {
367         continue;
368       }
369       feature.mValue = values.Length() > 1 ? values[1] : 1;
370       feature.mTag = HB_TAG('c', 'v', ('0' + nn / 10), ('0' + nn % 10));
371       aFontFeatures.AppendElement(feature);
372     }
373     return;
374   }
375 
376   if (aAlternates.IsStyleset()) {
377     for (auto& ident : aAlternates.AsStyleset().AsSpan()) {
378       Span<const uint32_t> values = aFeatureLookup.GetFontFeatureValuesFor(
379           aFamily, NS_FONT_VARIANT_ALTERNATES_STYLESET, ident.AsAtom());
380 
381       // styleset(1 2 7) ==> 'ss01' = 1, 'ss02' = 1, 'ss07' = 1
382       feature.mValue = 1;
383       for (uint32_t nn : values) {
384         if (nn == 0 || nn > MAX_SSXX_VALUE) {
385           continue;
386         }
387         feature.mTag = HB_TAG('s', 's', ('0' + nn / 10), ('0' + nn % 10));
388         aFontFeatures.AppendElement(feature);
389       }
390     }
391     return;
392   }
393 
394   uint32_t constant = 0;
395   nsAtom* name = nullptr;
396   switch (aAlternates.tag) {
397     case Tag::Swash:
398       constant = NS_FONT_VARIANT_ALTERNATES_SWASH;
399       name = aAlternates.AsSwash().AsAtom();
400       break;
401     case Tag::Stylistic:
402       constant = NS_FONT_VARIANT_ALTERNATES_STYLISTIC;
403       name = aAlternates.AsStylistic().AsAtom();
404       break;
405     case Tag::Ornaments:
406       constant = NS_FONT_VARIANT_ALTERNATES_ORNAMENTS;
407       name = aAlternates.AsOrnaments().AsAtom();
408       break;
409     case Tag::Annotation:
410       constant = NS_FONT_VARIANT_ALTERNATES_ANNOTATION;
411       name = aAlternates.AsAnnotation().AsAtom();
412       break;
413     default:
414       MOZ_ASSERT_UNREACHABLE("Unknown font-variant-alternates value!");
415       return;
416   }
417 
418   Span<const uint32_t> values =
419       aFeatureLookup.GetFontFeatureValuesFor(aFamily, constant, name);
420   if (values.IsEmpty()) {
421     return;
422   }
423   MOZ_ASSERT(values.Length() == 1,
424              "too many values for font-specific font-variant-alternates");
425 
426   feature.mValue = values[0];
427   switch (aAlternates.tag) {
428     case Tag::Swash:  // swsh, cswh
429       feature.mTag = HB_TAG('s', 'w', 's', 'h');
430       aFontFeatures.AppendElement(feature);
431       feature.mTag = HB_TAG('c', 's', 'w', 'h');
432       break;
433     case Tag::Stylistic:  // salt
434       feature.mTag = HB_TAG('s', 'a', 'l', 't');
435       break;
436     case Tag::Ornaments:  // ornm
437       feature.mTag = HB_TAG('o', 'r', 'n', 'm');
438       break;
439     case Tag::Annotation:  // nalt
440       feature.mTag = HB_TAG('n', 'a', 'l', 't');
441       break;
442     default:
443       MOZ_ASSERT_UNREACHABLE("how?");
444       return;
445   }
446   aFontFeatures.AppendElement(feature);
447 }
448 
449 /* static */
MergeFontFeatures(const gfxFontStyle * aStyle,const nsTArray<gfxFontFeature> & aFontFeatures,bool aDisableLigatures,const nsACString & aFamilyName,bool aAddSmallCaps,void (* aHandleFeature)(const uint32_t &,uint32_t &,void *),void * aHandleFeatureData)450 void gfxFontShaper::MergeFontFeatures(
451     const gfxFontStyle* aStyle, const nsTArray<gfxFontFeature>& aFontFeatures,
452     bool aDisableLigatures, const nsACString& aFamilyName, bool aAddSmallCaps,
453     void (*aHandleFeature)(const uint32_t&, uint32_t&, void*),
454     void* aHandleFeatureData) {
455   const nsTArray<gfxFontFeature>& styleRuleFeatures = aStyle->featureSettings;
456 
457   // Bail immediately if nothing to do, which is the common case.
458   if (styleRuleFeatures.IsEmpty() && aFontFeatures.IsEmpty() &&
459       !aDisableLigatures &&
460       aStyle->variantCaps == NS_FONT_VARIANT_CAPS_NORMAL &&
461       aStyle->variantSubSuper == NS_FONT_VARIANT_POSITION_NORMAL &&
462       aStyle->variantAlternates.IsEmpty()) {
463     return;
464   }
465 
466   nsTHashMap<nsUint32HashKey, uint32_t> mergedFeatures;
467 
468   // add feature values from font
469   for (const gfxFontFeature& feature : aFontFeatures) {
470     mergedFeatures.InsertOrUpdate(feature.mTag, feature.mValue);
471   }
472 
473   // font-variant-caps - handled here due to the need for fallback handling
474   // petite caps cases can fallback to appropriate smallcaps
475   uint32_t variantCaps = aStyle->variantCaps;
476   switch (variantCaps) {
477     case NS_FONT_VARIANT_CAPS_NORMAL:
478       break;
479 
480     case NS_FONT_VARIANT_CAPS_ALLSMALL:
481       mergedFeatures.InsertOrUpdate(HB_TAG('c', '2', 's', 'c'), 1);
482       // fall through to the small-caps case
483       [[fallthrough]];
484 
485     case NS_FONT_VARIANT_CAPS_SMALLCAPS:
486       mergedFeatures.InsertOrUpdate(HB_TAG('s', 'm', 'c', 'p'), 1);
487       break;
488 
489     case NS_FONT_VARIANT_CAPS_ALLPETITE:
490       mergedFeatures.InsertOrUpdate(aAddSmallCaps ? HB_TAG('c', '2', 's', 'c')
491                                                   : HB_TAG('c', '2', 'p', 'c'),
492                                     1);
493       // fall through to the petite-caps case
494       [[fallthrough]];
495 
496     case NS_FONT_VARIANT_CAPS_PETITECAPS:
497       mergedFeatures.InsertOrUpdate(aAddSmallCaps ? HB_TAG('s', 'm', 'c', 'p')
498                                                   : HB_TAG('p', 'c', 'a', 'p'),
499                                     1);
500       break;
501 
502     case NS_FONT_VARIANT_CAPS_TITLING:
503       mergedFeatures.InsertOrUpdate(HB_TAG('t', 'i', 't', 'l'), 1);
504       break;
505 
506     case NS_FONT_VARIANT_CAPS_UNICASE:
507       mergedFeatures.InsertOrUpdate(HB_TAG('u', 'n', 'i', 'c'), 1);
508       break;
509 
510     default:
511       MOZ_ASSERT_UNREACHABLE("Unexpected variantCaps");
512       break;
513   }
514 
515   // font-variant-position - handled here due to the need for fallback
516   switch (aStyle->variantSubSuper) {
517     case NS_FONT_VARIANT_POSITION_NORMAL:
518       break;
519     case NS_FONT_VARIANT_POSITION_SUPER:
520       mergedFeatures.InsertOrUpdate(HB_TAG('s', 'u', 'p', 's'), 1);
521       break;
522     case NS_FONT_VARIANT_POSITION_SUB:
523       mergedFeatures.InsertOrUpdate(HB_TAG('s', 'u', 'b', 's'), 1);
524       break;
525     default:
526       MOZ_ASSERT_UNREACHABLE("Unexpected variantSubSuper");
527       break;
528   }
529 
530   // add font-specific feature values from style rules
531   if (aStyle->featureValueLookup && !aStyle->variantAlternates.IsEmpty()) {
532     AutoTArray<gfxFontFeature, 4> featureList;
533 
534     // insert list of alternate feature settings
535     for (auto& alternate : aStyle->variantAlternates.AsSpan()) {
536       LookupAlternateValues(*aStyle->featureValueLookup, aFamilyName, alternate,
537                             featureList);
538     }
539 
540     for (const gfxFontFeature& feature : featureList) {
541       mergedFeatures.InsertOrUpdate(feature.mTag, feature.mValue);
542     }
543   }
544 
545   // Add features that are already resolved to tags & values in the style.
546   if (styleRuleFeatures.IsEmpty()) {
547     // Disable common ligatures if non-zero letter-spacing is in effect.
548     if (aDisableLigatures) {
549       mergedFeatures.InsertOrUpdate(HB_TAG('l', 'i', 'g', 'a'), 0);
550       mergedFeatures.InsertOrUpdate(HB_TAG('c', 'l', 'i', 'g'), 0);
551     }
552   } else {
553     for (const gfxFontFeature& feature : styleRuleFeatures) {
554       // A dummy feature (0,0) is used as a sentinel to separate features
555       // originating from font-variant-* or other high-level properties from
556       // those directly specified as font-feature-settings. The high-level
557       // features may be overridden by aDisableLigatures, while low-level
558       // features specified directly as tags will come last and therefore
559       // take precedence over everything else.
560       if (feature.mTag) {
561         mergedFeatures.InsertOrUpdate(feature.mTag, feature.mValue);
562       } else if (aDisableLigatures) {
563         // Handle ligature-disabling setting at the boundary between high-
564         // and low-level features.
565         mergedFeatures.InsertOrUpdate(HB_TAG('l', 'i', 'g', 'a'), 0);
566         mergedFeatures.InsertOrUpdate(HB_TAG('c', 'l', 'i', 'g'), 0);
567       }
568     }
569   }
570 
571   if (mergedFeatures.Count() != 0) {
572     for (auto iter = mergedFeatures.Iter(); !iter.Done(); iter.Next()) {
573       aHandleFeature(iter.Key(), iter.Data(), aHandleFeatureData);
574     }
575   }
576 }
577 
SetupClusterBoundaries(uint32_t aOffset,const char16_t * aString,uint32_t aLength)578 void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
579                                            const char16_t* aString,
580                                            uint32_t aLength) {
581   if (aLength == 0) {
582     return;
583   }
584 
585   CompressedGlyph* const glyphs = GetCharacterGlyphs() + aOffset;
586   CompressedGlyph extendCluster = CompressedGlyph::MakeComplex(false, true);
587 
588   // GraphemeClusterBreakIteratorUtf16 won't be able to tell us if the string
589   // _begins_ with a cluster-extender, so we handle that here
590   uint32_t ch = aString[0];
591   if (aLength > 1 && NS_IS_SURROGATE_PAIR(ch, aString[1])) {
592     ch = SURROGATE_TO_UCS4(ch, aString[1]);
593   }
594   if (IsClusterExtender(ch)) {
595     glyphs[0] = extendCluster;
596   }
597 
598   intl::GraphemeClusterBreakIteratorUtf16 iter(
599       Span<const char16_t>(aString, aLength));
600   uint32_t pos = 0;
601 
602   const char16_t kIdeographicSpace = 0x3000;
603   // Special case for Bengali: although Virama normally clusters with the
604   // preceding letter, we *also* want to cluster it with a following Ya
605   // so that when the Virama+Ya form ya-phala, this is not separated from the
606   // preceding letter by any letter-spacing or justification.
607   const char16_t kBengaliVirama = 0x09CD;
608   const char16_t kBengaliYa = 0x09AF;
609   while (pos < aLength) {
610     const char16_t ch = aString[pos];
611     if (ch == char16_t(' ') || ch == kIdeographicSpace) {
612       glyphs[pos].SetIsSpace();
613     } else if (ch == kBengaliYa) {
614       // Unless we're at the start, check for a preceding virama.
615       if (pos > 0 && aString[pos - 1] == kBengaliVirama) {
616         glyphs[pos] = extendCluster;
617       }
618     }
619     // advance iter to the next cluster-start (or end of text)
620     const uint32_t nextPos = *iter.Next();
621     // step past the first char of the cluster
622     ++pos;
623     // mark all the rest as cluster-continuations
624     for (; pos < nextPos; ++pos) {
625       glyphs[pos] = extendCluster;
626     }
627   }
628 }
629 
SetupClusterBoundaries(uint32_t aOffset,const uint8_t * aString,uint32_t aLength)630 void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
631                                            const uint8_t* aString,
632                                            uint32_t aLength) {
633   CompressedGlyph* glyphs = GetCharacterGlyphs() + aOffset;
634   const uint8_t* limit = aString + aLength;
635 
636   while (aString < limit) {
637     if (*aString == uint8_t(' ')) {
638       glyphs->SetIsSpace();
639     }
640     aString++;
641     glyphs++;
642   }
643 }
644 
AllocateDetailedGlyphs(uint32_t aIndex,uint32_t aCount)645 gfxShapedText::DetailedGlyph* gfxShapedText::AllocateDetailedGlyphs(
646     uint32_t aIndex, uint32_t aCount) {
647   NS_ASSERTION(aIndex < GetLength(), "Index out of range");
648 
649   if (!mDetailedGlyphs) {
650     mDetailedGlyphs = MakeUnique<DetailedGlyphStore>();
651   }
652 
653   return mDetailedGlyphs->Allocate(aIndex, aCount);
654 }
655 
SetDetailedGlyphs(uint32_t aIndex,uint32_t aGlyphCount,const DetailedGlyph * aGlyphs)656 void gfxShapedText::SetDetailedGlyphs(uint32_t aIndex, uint32_t aGlyphCount,
657                                       const DetailedGlyph* aGlyphs) {
658   CompressedGlyph& g = GetCharacterGlyphs()[aIndex];
659 
660   MOZ_ASSERT(aIndex > 0 || g.IsLigatureGroupStart(),
661              "First character can't be a ligature continuation!");
662 
663   if (aGlyphCount > 0) {
664     DetailedGlyph* details = AllocateDetailedGlyphs(aIndex, aGlyphCount);
665     memcpy(details, aGlyphs, sizeof(DetailedGlyph) * aGlyphCount);
666   }
667 
668   g.SetGlyphCount(aGlyphCount);
669 }
670 
671 #define ZWNJ 0x200C
672 #define ZWJ 0x200D
IsIgnorable(uint32_t aChar)673 static inline bool IsIgnorable(uint32_t aChar) {
674   return (IsDefaultIgnorable(aChar)) || aChar == ZWNJ || aChar == ZWJ;
675 }
676 
SetMissingGlyph(uint32_t aIndex,uint32_t aChar,gfxFont * aFont)677 void gfxShapedText::SetMissingGlyph(uint32_t aIndex, uint32_t aChar,
678                                     gfxFont* aFont) {
679   CompressedGlyph& g = GetCharacterGlyphs()[aIndex];
680   uint8_t category = GetGeneralCategory(aChar);
681   if (category >= HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK &&
682       category <= HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) {
683     g.SetComplex(false, true);
684   }
685 
686   // Leaving advance as zero will prevent drawing the hexbox for ignorables.
687   int32_t advance = 0;
688   if (!IsIgnorable(aChar)) {
689     gfxFloat width =
690         std::max(aFont->GetMetrics(nsFontMetrics::eHorizontal).aveCharWidth,
691                  gfxFloat(gfxFontMissingGlyphs::GetDesiredMinWidth(
692                      aChar, mAppUnitsPerDevUnit)));
693     advance = int32_t(width * mAppUnitsPerDevUnit);
694   }
695   DetailedGlyph detail = {aChar, advance, gfx::Point()};
696   SetDetailedGlyphs(aIndex, 1, &detail);
697   g.SetMissing();
698 }
699 
FilterIfIgnorable(uint32_t aIndex,uint32_t aCh)700 bool gfxShapedText::FilterIfIgnorable(uint32_t aIndex, uint32_t aCh) {
701   if (IsIgnorable(aCh)) {
702     // There are a few default-ignorables of Letter category (currently,
703     // just the Hangul filler characters) that we'd better not discard
704     // if they're followed by additional characters in the same cluster.
705     // Some fonts use them to carry the width of a whole cluster of
706     // combining jamos; see bug 1238243.
707     auto* charGlyphs = GetCharacterGlyphs();
708     if (GetGenCategory(aCh) == nsUGenCategory::kLetter &&
709         aIndex + 1 < GetLength() && !charGlyphs[aIndex + 1].IsClusterStart()) {
710       return false;
711     }
712     // A compressedGlyph that is set to MISSING but has no DetailedGlyphs list
713     // will be zero-width/invisible, which is what we want here.
714     CompressedGlyph& g = charGlyphs[aIndex];
715     g.SetComplex(g.IsClusterStart(), g.IsLigatureGroupStart()).SetMissing();
716     return true;
717   }
718   return false;
719 }
720 
AdjustAdvancesForSyntheticBold(float aSynBoldOffset,uint32_t aOffset,uint32_t aLength)721 void gfxShapedText::AdjustAdvancesForSyntheticBold(float aSynBoldOffset,
722                                                    uint32_t aOffset,
723                                                    uint32_t aLength) {
724   uint32_t synAppUnitOffset = aSynBoldOffset * mAppUnitsPerDevUnit;
725   CompressedGlyph* charGlyphs = GetCharacterGlyphs();
726   for (uint32_t i = aOffset; i < aOffset + aLength; ++i) {
727     CompressedGlyph* glyphData = charGlyphs + i;
728     if (glyphData->IsSimpleGlyph()) {
729       // simple glyphs ==> just add the advance
730       int32_t advance = glyphData->GetSimpleAdvance();
731       if (advance > 0) {
732         advance += synAppUnitOffset;
733         if (CompressedGlyph::IsSimpleAdvance(advance)) {
734           glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph());
735         } else {
736           // rare case, tested by making this the default
737           uint32_t glyphIndex = glyphData->GetSimpleGlyph();
738           // convert the simple CompressedGlyph to an empty complex record
739           glyphData->SetComplex(true, true);
740           // then set its details (glyph ID with its new advance)
741           DetailedGlyph detail = {glyphIndex, advance, gfx::Point()};
742           SetDetailedGlyphs(i, 1, &detail);
743         }
744       }
745     } else {
746       // complex glyphs ==> add offset at cluster/ligature boundaries
747       uint32_t detailedLength = glyphData->GetGlyphCount();
748       if (detailedLength) {
749         DetailedGlyph* details = GetDetailedGlyphs(i);
750         if (!details) {
751           continue;
752         }
753         if (IsRightToLeft()) {
754           if (details[0].mAdvance > 0) {
755             details[0].mAdvance += synAppUnitOffset;
756           }
757         } else {
758           if (details[detailedLength - 1].mAdvance > 0) {
759             details[detailedLength - 1].mAdvance += synAppUnitOffset;
760           }
761         }
762       }
763     }
764   }
765 }
766 
AngleForSyntheticOblique() const767 float gfxFont::AngleForSyntheticOblique() const {
768   // If the style doesn't call for italic/oblique, or if the face already
769   // provides it, no synthetic style should be added.
770   if (mStyle.style == FontSlantStyle::Normal() || !mStyle.allowSyntheticStyle ||
771       !mFontEntry->IsUpright()) {
772     return 0.0f;
773   }
774 
775   // If style calls for italic, and face doesn't support it, use default
776   // oblique angle as a simulation.
777   if (mStyle.style.IsItalic()) {
778     return mFontEntry->SupportsItalic() ? 0.0f : FontSlantStyle::kDefaultAngle;
779   }
780 
781   // Default or custom oblique angle
782   return mStyle.style.ObliqueAngle();
783 }
784 
SkewForSyntheticOblique() const785 float gfxFont::SkewForSyntheticOblique() const {
786   // Precomputed value of tan(kDefaultAngle), the default italic/oblique slant;
787   // avoids calling tan() at runtime except for custom oblique values.
788   static const float kTanDefaultAngle =
789       tan(FontSlantStyle::kDefaultAngle * (M_PI / 180.0));
790 
791   float angle = AngleForSyntheticOblique();
792   if (angle == 0.0f) {
793     return 0.0f;
794   } else if (angle == FontSlantStyle::kDefaultAngle) {
795     return kTanDefaultAngle;
796   } else {
797     return tan(angle * (M_PI / 180.0));
798   }
799 }
800 
CombineWith(const RunMetrics & aOther,bool aOtherIsOnLeft)801 void gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther,
802                                       bool aOtherIsOnLeft) {
803   mAscent = std::max(mAscent, aOther.mAscent);
804   mDescent = std::max(mDescent, aOther.mDescent);
805   if (aOtherIsOnLeft) {
806     mBoundingBox = (mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0))
807                        .Union(aOther.mBoundingBox);
808   } else {
809     mBoundingBox =
810         mBoundingBox.Union(aOther.mBoundingBox + gfxPoint(mAdvanceWidth, 0));
811   }
812   mAdvanceWidth += aOther.mAdvanceWidth;
813 }
814 
gfxFont(const RefPtr<UnscaledFont> & aUnscaledFont,gfxFontEntry * aFontEntry,const gfxFontStyle * aFontStyle,AntialiasOption anAAOption)815 gfxFont::gfxFont(const RefPtr<UnscaledFont>& aUnscaledFont,
816                  gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle,
817                  AntialiasOption anAAOption)
818     : mFontEntry(aFontEntry),
819       mUnscaledFont(aUnscaledFont),
820       mStyle(*aFontStyle),
821       mAdjustedSize(-1.0),       // negative to indicate "not yet initialized"
822       mFUnitsConvFactor(-1.0f),  // negative to indicate "not yet initialized"
823       mAntialiasOption(anAAOption),
824       mIsValid(true),
825       mApplySyntheticBold(false),
826       mKerningEnabled(false),
827       mMathInitialized(false) {
828 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
829   ++gFontCount;
830 #endif
831 
832   if (MOZ_UNLIKELY(StaticPrefs::gfx_text_disable_aa_AtStartup())) {
833     mAntialiasOption = kAntialiasNone;
834   }
835 
836   // Turn off AA for Ahem for testing purposes when requested.
837   if (MOZ_UNLIKELY(StaticPrefs::gfx_font_rendering_ahem_antialias_none() &&
838                    mFontEntry->FamilyName().EqualsLiteral("Ahem"))) {
839     mAntialiasOption = kAntialiasNone;
840   }
841 
842   mKerningSet = HasFeatureSet(HB_TAG('k', 'e', 'r', 'n'), mKerningEnabled);
843 }
844 
~gfxFont()845 gfxFont::~gfxFont() {
846   mFontEntry->NotifyFontDestroyed(this);
847 
848   if (mGlyphChangeObservers) {
849     for (const auto& key : *mGlyphChangeObservers) {
850       key->ForgetFont();
851     }
852   }
853 }
854 
855 // Work out whether cairo will snap inter-glyph spacing to pixels.
856 //
857 // Layout does not align text to pixel boundaries, so, with font drawing
858 // backends that snap glyph positions to pixels, it is important that
859 // inter-glyph spacing within words is always an integer number of pixels.
860 // This ensures that the drawing backend snaps all of the word's glyphs in the
861 // same direction and so inter-glyph spacing remains the same.
862 //
GetRoundOffsetsToPixels(DrawTarget * aDrawTarget)863 gfxFont::RoundingFlags gfxFont::GetRoundOffsetsToPixels(
864     DrawTarget* aDrawTarget) {
865   // Could do something fancy here for ScaleFactors of
866   // AxisAlignedTransforms, but we leave things simple.
867   // Not much point rounding if a matrix will mess things up anyway.
868   // Also check if the font already knows hint metrics is off...
869   if (aDrawTarget->GetTransform().HasNonTranslation() || !ShouldHintMetrics()) {
870     return RoundingFlags(0);
871   }
872 
873   cairo_t* cr = static_cast<cairo_t*>(
874       aDrawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT));
875   if (cr) {
876     cairo_surface_t* target = cairo_get_target(cr);
877 
878     // Check whether the cairo surface's font options hint metrics.
879     cairo_font_options_t* fontOptions = cairo_font_options_create();
880     cairo_surface_get_font_options(target, fontOptions);
881     cairo_hint_metrics_t hintMetrics =
882         cairo_font_options_get_hint_metrics(fontOptions);
883     cairo_font_options_destroy(fontOptions);
884 
885     switch (hintMetrics) {
886       case CAIRO_HINT_METRICS_OFF:
887         return RoundingFlags(0);
888       case CAIRO_HINT_METRICS_ON:
889         return RoundingFlags::kRoundX | RoundingFlags::kRoundY;
890       default:
891         break;
892     }
893   }
894 
895   if (ShouldRoundXOffset(cr)) {
896     return RoundingFlags::kRoundX | RoundingFlags::kRoundY;
897   } else {
898     return RoundingFlags::kRoundY;
899   }
900 }
901 
GetGlyphAdvance(uint16_t aGID,bool aVertical)902 gfxFloat gfxFont::GetGlyphAdvance(uint16_t aGID, bool aVertical) {
903   if (!aVertical && ProvidesGlyphWidths()) {
904     return GetGlyphWidth(aGID) / 65536.0;
905   }
906   if (mFUnitsConvFactor < 0.0f) {
907     GetMetrics(nsFontMetrics::eHorizontal);
908   }
909   NS_ASSERTION(mFUnitsConvFactor >= 0.0f,
910                "missing font unit conversion factor");
911   if (!mHarfBuzzShaper) {
912     mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
913   }
914   gfxHarfBuzzShaper* shaper =
915       static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get());
916   if (!shaper->Initialize()) {
917     return 0;
918   }
919   return (aVertical ? shaper->GetGlyphVAdvance(aGID)
920                     : shaper->GetGlyphHAdvance(aGID)) /
921          65536.0;
922 }
923 
GetCharAdvance(uint32_t aUnicode,bool aVertical)924 gfxFloat gfxFont::GetCharAdvance(uint32_t aUnicode, bool aVertical) {
925   uint32_t gid = 0;
926   if (ProvidesGetGlyph()) {
927     gid = GetGlyph(aUnicode, 0);
928   } else {
929     if (!mHarfBuzzShaper) {
930       mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
931     }
932     gfxHarfBuzzShaper* shaper =
933         static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get());
934     if (!shaper->Initialize()) {
935       return -1.0;
936     }
937     gid = shaper->GetNominalGlyph(aUnicode);
938   }
939   if (!gid) {
940     return -1.0;
941   }
942   return GetGlyphAdvance(gid, aVertical);
943 }
944 
CollectLookupsByFeature(hb_face_t * aFace,hb_tag_t aTableTag,uint32_t aFeatureIndex,hb_set_t * aLookups)945 static void CollectLookupsByFeature(hb_face_t* aFace, hb_tag_t aTableTag,
946                                     uint32_t aFeatureIndex,
947                                     hb_set_t* aLookups) {
948   uint32_t lookups[32];
949   uint32_t i, len, offset;
950 
951   offset = 0;
952   do {
953     len = ArrayLength(lookups);
954     hb_ot_layout_feature_get_lookups(aFace, aTableTag, aFeatureIndex, offset,
955                                      &len, lookups);
956     for (i = 0; i < len; i++) {
957       hb_set_add(aLookups, lookups[i]);
958     }
959     offset += len;
960   } while (len == ArrayLength(lookups));
961 }
962 
CollectLookupsByLanguage(hb_face_t * aFace,hb_tag_t aTableTag,const nsTHashSet<uint32_t> & aSpecificFeatures,hb_set_t * aOtherLookups,hb_set_t * aSpecificFeatureLookups,uint32_t aScriptIndex,uint32_t aLangIndex)963 static void CollectLookupsByLanguage(
964     hb_face_t* aFace, hb_tag_t aTableTag,
965     const nsTHashSet<uint32_t>& aSpecificFeatures, hb_set_t* aOtherLookups,
966     hb_set_t* aSpecificFeatureLookups, uint32_t aScriptIndex,
967     uint32_t aLangIndex) {
968   uint32_t reqFeatureIndex;
969   if (hb_ot_layout_language_get_required_feature_index(
970           aFace, aTableTag, aScriptIndex, aLangIndex, &reqFeatureIndex)) {
971     CollectLookupsByFeature(aFace, aTableTag, reqFeatureIndex, aOtherLookups);
972   }
973 
974   uint32_t featureIndexes[32];
975   uint32_t i, len, offset;
976 
977   offset = 0;
978   do {
979     len = ArrayLength(featureIndexes);
980     hb_ot_layout_language_get_feature_indexes(aFace, aTableTag, aScriptIndex,
981                                               aLangIndex, offset, &len,
982                                               featureIndexes);
983 
984     for (i = 0; i < len; i++) {
985       uint32_t featureIndex = featureIndexes[i];
986 
987       // get the feature tag
988       hb_tag_t featureTag;
989       uint32_t tagLen = 1;
990       hb_ot_layout_language_get_feature_tags(aFace, aTableTag, aScriptIndex,
991                                              aLangIndex, offset + i, &tagLen,
992                                              &featureTag);
993 
994       // collect lookups
995       hb_set_t* lookups = aSpecificFeatures.Contains(featureTag)
996                               ? aSpecificFeatureLookups
997                               : aOtherLookups;
998       CollectLookupsByFeature(aFace, aTableTag, featureIndex, lookups);
999     }
1000     offset += len;
1001   } while (len == ArrayLength(featureIndexes));
1002 }
1003 
HasLookupRuleWithGlyphByScript(hb_face_t * aFace,hb_tag_t aTableTag,hb_tag_t aScriptTag,uint32_t aScriptIndex,uint16_t aGlyph,const nsTHashSet<uint32_t> & aDefaultFeatures,bool & aHasDefaultFeatureWithGlyph)1004 static bool HasLookupRuleWithGlyphByScript(
1005     hb_face_t* aFace, hb_tag_t aTableTag, hb_tag_t aScriptTag,
1006     uint32_t aScriptIndex, uint16_t aGlyph,
1007     const nsTHashSet<uint32_t>& aDefaultFeatures,
1008     bool& aHasDefaultFeatureWithGlyph) {
1009   uint32_t numLangs, lang;
1010   hb_set_t* defaultFeatureLookups = hb_set_create();
1011   hb_set_t* nonDefaultFeatureLookups = hb_set_create();
1012 
1013   // default lang
1014   CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures,
1015                            nonDefaultFeatureLookups, defaultFeatureLookups,
1016                            aScriptIndex, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX);
1017 
1018   // iterate over langs
1019   numLangs = hb_ot_layout_script_get_language_tags(
1020       aFace, aTableTag, aScriptIndex, 0, nullptr, nullptr);
1021   for (lang = 0; lang < numLangs; lang++) {
1022     CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures,
1023                              nonDefaultFeatureLookups, defaultFeatureLookups,
1024                              aScriptIndex, lang);
1025   }
1026 
1027   // look for the glyph among default feature lookups
1028   aHasDefaultFeatureWithGlyph = false;
1029   hb_set_t* glyphs = hb_set_create();
1030   hb_codepoint_t index = -1;
1031   while (hb_set_next(defaultFeatureLookups, &index)) {
1032     hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs,
1033                                        glyphs, nullptr);
1034     if (hb_set_has(glyphs, aGlyph)) {
1035       aHasDefaultFeatureWithGlyph = true;
1036       break;
1037     }
1038   }
1039 
1040   // look for the glyph among non-default feature lookups
1041   // if no default feature lookups contained spaces
1042   bool hasNonDefaultFeatureWithGlyph = false;
1043   if (!aHasDefaultFeatureWithGlyph) {
1044     hb_set_clear(glyphs);
1045     index = -1;
1046     while (hb_set_next(nonDefaultFeatureLookups, &index)) {
1047       hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs,
1048                                          glyphs, glyphs, nullptr);
1049       if (hb_set_has(glyphs, aGlyph)) {
1050         hasNonDefaultFeatureWithGlyph = true;
1051         break;
1052       }
1053     }
1054   }
1055 
1056   hb_set_destroy(glyphs);
1057   hb_set_destroy(defaultFeatureLookups);
1058   hb_set_destroy(nonDefaultFeatureLookups);
1059 
1060   return aHasDefaultFeatureWithGlyph || hasNonDefaultFeatureWithGlyph;
1061 }
1062 
HasLookupRuleWithGlyph(hb_face_t * aFace,hb_tag_t aTableTag,bool & aHasGlyph,hb_tag_t aSpecificFeature,bool & aHasGlyphSpecific,uint16_t aGlyph)1063 static void HasLookupRuleWithGlyph(hb_face_t* aFace, hb_tag_t aTableTag,
1064                                    bool& aHasGlyph, hb_tag_t aSpecificFeature,
1065                                    bool& aHasGlyphSpecific, uint16_t aGlyph) {
1066   // iterate over the scripts in the font
1067   uint32_t numScripts, numLangs, script, lang;
1068   hb_set_t* otherLookups = hb_set_create();
1069   hb_set_t* specificFeatureLookups = hb_set_create();
1070   nsTHashSet<uint32_t> specificFeature(1);
1071 
1072   specificFeature.Insert(aSpecificFeature);
1073 
1074   numScripts =
1075       hb_ot_layout_table_get_script_tags(aFace, aTableTag, 0, nullptr, nullptr);
1076 
1077   for (script = 0; script < numScripts; script++) {
1078     // default lang
1079     CollectLookupsByLanguage(aFace, aTableTag, specificFeature, otherLookups,
1080                              specificFeatureLookups, script,
1081                              HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX);
1082 
1083     // iterate over langs
1084     numLangs = hb_ot_layout_script_get_language_tags(
1085         aFace, HB_OT_TAG_GPOS, script, 0, nullptr, nullptr);
1086     for (lang = 0; lang < numLangs; lang++) {
1087       CollectLookupsByLanguage(aFace, aTableTag, specificFeature, otherLookups,
1088                                specificFeatureLookups, script, lang);
1089     }
1090   }
1091 
1092   // look for the glyph among non-specific feature lookups
1093   hb_set_t* glyphs = hb_set_create();
1094   hb_codepoint_t index = -1;
1095   while (hb_set_next(otherLookups, &index)) {
1096     hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs,
1097                                        glyphs, nullptr);
1098     if (hb_set_has(glyphs, aGlyph)) {
1099       aHasGlyph = true;
1100       break;
1101     }
1102   }
1103 
1104   // look for the glyph among specific feature lookups
1105   hb_set_clear(glyphs);
1106   index = -1;
1107   while (hb_set_next(specificFeatureLookups, &index)) {
1108     hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs,
1109                                        glyphs, nullptr);
1110     if (hb_set_has(glyphs, aGlyph)) {
1111       aHasGlyphSpecific = true;
1112       break;
1113     }
1114   }
1115 
1116   hb_set_destroy(glyphs);
1117   hb_set_destroy(specificFeatureLookups);
1118   hb_set_destroy(otherLookups);
1119 }
1120 
1121 nsTHashMap<nsUint32HashKey, intl::Script>* gfxFont::sScriptTagToCode = nullptr;
1122 nsTHashSet<uint32_t>* gfxFont::sDefaultFeatures = nullptr;
1123 
HasSubstitution(uint32_t * aBitVector,intl::Script aScript)1124 static inline bool HasSubstitution(uint32_t* aBitVector, intl::Script aScript) {
1125   return (aBitVector[static_cast<uint32_t>(aScript) >> 5] &
1126           (1 << (static_cast<uint32_t>(aScript) & 0x1f))) != 0;
1127 }
1128 
1129 // union of all default substitution features across scripts
1130 static const hb_tag_t defaultFeatures[] = {
1131     HB_TAG('a', 'b', 'v', 'f'), HB_TAG('a', 'b', 'v', 's'),
1132     HB_TAG('a', 'k', 'h', 'n'), HB_TAG('b', 'l', 'w', 'f'),
1133     HB_TAG('b', 'l', 'w', 's'), HB_TAG('c', 'a', 'l', 't'),
1134     HB_TAG('c', 'c', 'm', 'p'), HB_TAG('c', 'f', 'a', 'r'),
1135     HB_TAG('c', 'j', 'c', 't'), HB_TAG('c', 'l', 'i', 'g'),
1136     HB_TAG('f', 'i', 'n', '2'), HB_TAG('f', 'i', 'n', '3'),
1137     HB_TAG('f', 'i', 'n', 'a'), HB_TAG('h', 'a', 'l', 'f'),
1138     HB_TAG('h', 'a', 'l', 'n'), HB_TAG('i', 'n', 'i', 't'),
1139     HB_TAG('i', 's', 'o', 'l'), HB_TAG('l', 'i', 'g', 'a'),
1140     HB_TAG('l', 'j', 'm', 'o'), HB_TAG('l', 'o', 'c', 'l'),
1141     HB_TAG('l', 't', 'r', 'a'), HB_TAG('l', 't', 'r', 'm'),
1142     HB_TAG('m', 'e', 'd', '2'), HB_TAG('m', 'e', 'd', 'i'),
1143     HB_TAG('m', 's', 'e', 't'), HB_TAG('n', 'u', 'k', 't'),
1144     HB_TAG('p', 'r', 'e', 'f'), HB_TAG('p', 'r', 'e', 's'),
1145     HB_TAG('p', 's', 't', 'f'), HB_TAG('p', 's', 't', 's'),
1146     HB_TAG('r', 'c', 'l', 't'), HB_TAG('r', 'l', 'i', 'g'),
1147     HB_TAG('r', 'k', 'r', 'f'), HB_TAG('r', 'p', 'h', 'f'),
1148     HB_TAG('r', 't', 'l', 'a'), HB_TAG('r', 't', 'l', 'm'),
1149     HB_TAG('t', 'j', 'm', 'o'), HB_TAG('v', 'a', 't', 'u'),
1150     HB_TAG('v', 'e', 'r', 't'), HB_TAG('v', 'j', 'm', 'o')};
1151 
CheckForFeaturesInvolvingSpace()1152 void gfxFont::CheckForFeaturesInvolvingSpace() {
1153   mFontEntry->mHasSpaceFeaturesInitialized = true;
1154 
1155   bool log = LOG_FONTINIT_ENABLED();
1156   TimeStamp start;
1157   if (MOZ_UNLIKELY(log)) {
1158     start = TimeStamp::Now();
1159   }
1160 
1161   bool result = false;
1162 
1163   uint32_t spaceGlyph = GetSpaceGlyph();
1164   if (!spaceGlyph) {
1165     return;
1166   }
1167 
1168   hb_face_t* face = GetFontEntry()->GetHBFace();
1169 
1170   // GSUB lookups - examine per script
1171   if (hb_ot_layout_has_substitution(face)) {
1172     // set up the script ==> code hashtable if needed
1173     if (!sScriptTagToCode) {
1174       sScriptTagToCode = new nsTHashMap<nsUint32HashKey, Script>(
1175           size_t(Script::NUM_SCRIPT_CODES));
1176       sScriptTagToCode->InsertOrUpdate(HB_TAG('D', 'F', 'L', 'T'),
1177                                        Script::COMMON);
1178       // Ensure that we don't try to look at script codes beyond what the
1179       // current version of ICU (at runtime -- in case of system ICU)
1180       // knows about.
1181       Script scriptCount = Script(
1182           std::min<int>(intl::UnicodeProperties::GetMaxNumberOfScripts() + 1,
1183                         int(Script::NUM_SCRIPT_CODES)));
1184       for (Script s = Script::ARABIC; s < scriptCount;
1185            s = Script(static_cast<int>(s) + 1)) {
1186         hb_script_t script = hb_script_t(GetScriptTagForCode(s));
1187         unsigned int scriptCount = 4;
1188         hb_tag_t scriptTags[4];
1189         hb_ot_tags_from_script_and_language(script, HB_LANGUAGE_INVALID,
1190                                             &scriptCount, scriptTags, nullptr,
1191                                             nullptr);
1192         for (unsigned int i = 0; i < scriptCount; i++) {
1193           sScriptTagToCode->InsertOrUpdate(scriptTags[i], s);
1194         }
1195       }
1196 
1197       uint32_t numDefaultFeatures = ArrayLength(defaultFeatures);
1198       sDefaultFeatures = new nsTHashSet<uint32_t>(numDefaultFeatures);
1199       for (uint32_t i = 0; i < numDefaultFeatures; i++) {
1200         sDefaultFeatures->Insert(defaultFeatures[i]);
1201       }
1202     }
1203 
1204     // iterate over the scripts in the font
1205     hb_tag_t scriptTags[8];
1206 
1207     uint32_t len, offset = 0;
1208     do {
1209       len = ArrayLength(scriptTags);
1210       hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, offset, &len,
1211                                          scriptTags);
1212       for (uint32_t i = 0; i < len; i++) {
1213         bool isDefaultFeature = false;
1214         Script s;
1215         if (!HasLookupRuleWithGlyphByScript(
1216                 face, HB_OT_TAG_GSUB, scriptTags[i], offset + i, spaceGlyph,
1217                 *sDefaultFeatures, isDefaultFeature) ||
1218             !sScriptTagToCode->Get(scriptTags[i], &s)) {
1219           continue;
1220         }
1221         result = true;
1222         uint32_t index = static_cast<uint32_t>(s) >> 5;
1223         uint32_t bit = static_cast<uint32_t>(s) & 0x1f;
1224         if (isDefaultFeature) {
1225           mFontEntry->mDefaultSubSpaceFeatures[index] |= (1 << bit);
1226         } else {
1227           mFontEntry->mNonDefaultSubSpaceFeatures[index] |= (1 << bit);
1228         }
1229       }
1230       offset += len;
1231     } while (len == ArrayLength(scriptTags));
1232   }
1233 
1234   // spaces in default features of default script?
1235   // ==> can't use word cache, skip GPOS analysis
1236   bool canUseWordCache = true;
1237   if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, Script::COMMON)) {
1238     canUseWordCache = false;
1239   }
1240 
1241   // GPOS lookups - distinguish kerning from non-kerning features
1242   mFontEntry->mHasSpaceFeaturesKerning = false;
1243   mFontEntry->mHasSpaceFeaturesNonKerning = false;
1244 
1245   if (canUseWordCache && hb_ot_layout_has_positioning(face)) {
1246     bool hasKerning = false, hasNonKerning = false;
1247     HasLookupRuleWithGlyph(face, HB_OT_TAG_GPOS, hasNonKerning,
1248                            HB_TAG('k', 'e', 'r', 'n'), hasKerning, spaceGlyph);
1249     if (hasKerning || hasNonKerning) {
1250       result = true;
1251     }
1252     mFontEntry->mHasSpaceFeaturesKerning = hasKerning;
1253     mFontEntry->mHasSpaceFeaturesNonKerning = hasNonKerning;
1254   }
1255 
1256   hb_face_destroy(face);
1257   mFontEntry->mHasSpaceFeatures = result;
1258 
1259   if (MOZ_UNLIKELY(log)) {
1260     TimeDuration elapsed = TimeStamp::Now() - start;
1261     LOG_FONTINIT(
1262         ("(fontinit-spacelookups) font: %s - "
1263          "subst default: %8.8x %8.8x %8.8x %8.8x "
1264          "subst non-default: %8.8x %8.8x %8.8x %8.8x "
1265          "kerning: %s non-kerning: %s time: %6.3f\n",
1266          mFontEntry->Name().get(), mFontEntry->mDefaultSubSpaceFeatures[3],
1267          mFontEntry->mDefaultSubSpaceFeatures[2],
1268          mFontEntry->mDefaultSubSpaceFeatures[1],
1269          mFontEntry->mDefaultSubSpaceFeatures[0],
1270          mFontEntry->mNonDefaultSubSpaceFeatures[3],
1271          mFontEntry->mNonDefaultSubSpaceFeatures[2],
1272          mFontEntry->mNonDefaultSubSpaceFeatures[1],
1273          mFontEntry->mNonDefaultSubSpaceFeatures[0],
1274          (mFontEntry->mHasSpaceFeaturesKerning ? "true" : "false"),
1275          (mFontEntry->mHasSpaceFeaturesNonKerning ? "true" : "false"),
1276          elapsed.ToMilliseconds()));
1277   }
1278 }
1279 
HasSubstitutionRulesWithSpaceLookups(Script aRunScript)1280 bool gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript) {
1281   NS_ASSERTION(GetFontEntry()->mHasSpaceFeaturesInitialized,
1282                "need to initialize space lookup flags");
1283   NS_ASSERTION(aRunScript < Script::NUM_SCRIPT_CODES, "weird script code");
1284   if (aRunScript == Script::INVALID || aRunScript >= Script::NUM_SCRIPT_CODES) {
1285     return false;
1286   }
1287 
1288   // default features have space lookups ==> true
1289   if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, Script::COMMON) ||
1290       HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, aRunScript)) {
1291     return true;
1292   }
1293 
1294   // non-default features have space lookups and some type of
1295   // font feature, in font or style is specified ==> true
1296   if ((HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures,
1297                        Script::COMMON) ||
1298        HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures, aRunScript)) &&
1299       (!mStyle.featureSettings.IsEmpty() ||
1300        !mFontEntry->mFeatureSettings.IsEmpty())) {
1301     return true;
1302   }
1303 
1304   return false;
1305 }
1306 
SpaceMayParticipateInShaping(Script aRunScript)1307 tainted_boolean_hint gfxFont::SpaceMayParticipateInShaping(Script aRunScript) {
1308   // avoid checking fonts known not to include default space-dependent features
1309   if (MOZ_UNLIKELY(mFontEntry->mSkipDefaultFeatureSpaceCheck)) {
1310     if (!mKerningSet && mStyle.featureSettings.IsEmpty() &&
1311         mFontEntry->mFeatureSettings.IsEmpty()) {
1312       return false;
1313     }
1314   }
1315 
1316   if (FontCanSupportGraphite()) {
1317     if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1318       return mFontEntry->HasGraphiteSpaceContextuals();
1319     }
1320   }
1321 
1322   // We record the presence of space-dependent features in the font entry
1323   // so that subsequent instantiations for the same font face won't
1324   // require us to re-check the tables; however, the actual check is done
1325   // by gfxFont because not all font entry subclasses know how to create
1326   // a harfbuzz face for introspection.
1327   if (!mFontEntry->mHasSpaceFeaturesInitialized) {
1328     CheckForFeaturesInvolvingSpace();
1329   }
1330 
1331   if (!mFontEntry->mHasSpaceFeatures) {
1332     return false;
1333   }
1334 
1335   // if font has substitution rules or non-kerning positioning rules
1336   // that involve spaces, bypass
1337   if (HasSubstitutionRulesWithSpaceLookups(aRunScript) ||
1338       mFontEntry->mHasSpaceFeaturesNonKerning) {
1339     return true;
1340   }
1341 
1342   // if kerning explicitly enabled/disabled via font-feature-settings or
1343   // font-kerning and kerning rules use spaces, only bypass when enabled
1344   if (mKerningSet && mFontEntry->mHasSpaceFeaturesKerning) {
1345     return mKerningEnabled;
1346   }
1347 
1348   return false;
1349 }
1350 
SupportsFeature(Script aScript,uint32_t aFeatureTag)1351 bool gfxFont::SupportsFeature(Script aScript, uint32_t aFeatureTag) {
1352   if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1353     return GetFontEntry()->SupportsGraphiteFeature(aFeatureTag);
1354   }
1355   return GetFontEntry()->SupportsOpenTypeFeature(aScript, aFeatureTag);
1356 }
1357 
SupportsVariantCaps(Script aScript,uint32_t aVariantCaps,bool & aFallbackToSmallCaps,bool & aSyntheticLowerToSmallCaps,bool & aSyntheticUpperToSmallCaps)1358 bool gfxFont::SupportsVariantCaps(Script aScript, uint32_t aVariantCaps,
1359                                   bool& aFallbackToSmallCaps,
1360                                   bool& aSyntheticLowerToSmallCaps,
1361                                   bool& aSyntheticUpperToSmallCaps) {
1362   bool ok = true;  // cases without fallback are fine
1363   aFallbackToSmallCaps = false;
1364   aSyntheticLowerToSmallCaps = false;
1365   aSyntheticUpperToSmallCaps = false;
1366   switch (aVariantCaps) {
1367     case NS_FONT_VARIANT_CAPS_SMALLCAPS:
1368       ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p'));
1369       if (!ok) {
1370         aSyntheticLowerToSmallCaps = true;
1371       }
1372       break;
1373     case NS_FONT_VARIANT_CAPS_ALLSMALL:
1374       ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')) &&
1375            SupportsFeature(aScript, HB_TAG('c', '2', 's', 'c'));
1376       if (!ok) {
1377         aSyntheticLowerToSmallCaps = true;
1378         aSyntheticUpperToSmallCaps = true;
1379       }
1380       break;
1381     case NS_FONT_VARIANT_CAPS_PETITECAPS:
1382       ok = SupportsFeature(aScript, HB_TAG('p', 'c', 'a', 'p'));
1383       if (!ok) {
1384         ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p'));
1385         aFallbackToSmallCaps = ok;
1386       }
1387       if (!ok) {
1388         aSyntheticLowerToSmallCaps = true;
1389       }
1390       break;
1391     case NS_FONT_VARIANT_CAPS_ALLPETITE:
1392       ok = SupportsFeature(aScript, HB_TAG('p', 'c', 'a', 'p')) &&
1393            SupportsFeature(aScript, HB_TAG('c', '2', 'p', 'c'));
1394       if (!ok) {
1395         ok = SupportsFeature(aScript, HB_TAG('s', 'm', 'c', 'p')) &&
1396              SupportsFeature(aScript, HB_TAG('c', '2', 's', 'c'));
1397         aFallbackToSmallCaps = ok;
1398       }
1399       if (!ok) {
1400         aSyntheticLowerToSmallCaps = true;
1401         aSyntheticUpperToSmallCaps = true;
1402       }
1403       break;
1404     default:
1405       break;
1406   }
1407 
1408   NS_ASSERTION(
1409       !(ok && (aSyntheticLowerToSmallCaps || aSyntheticUpperToSmallCaps)),
1410       "shouldn't use synthetic features if we found real ones");
1411 
1412   NS_ASSERTION(!(!ok && aFallbackToSmallCaps),
1413                "if we found a usable fallback, that counts as ok");
1414 
1415   return ok;
1416 }
1417 
SupportsSubSuperscript(uint32_t aSubSuperscript,const uint8_t * aString,uint32_t aLength,Script aRunScript)1418 bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript,
1419                                      const uint8_t* aString, uint32_t aLength,
1420                                      Script aRunScript) {
1421   NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aString),
1422                                        aLength);
1423   return SupportsSubSuperscript(aSubSuperscript, unicodeString.get(), aLength,
1424                                 aRunScript);
1425 }
1426 
SupportsSubSuperscript(uint32_t aSubSuperscript,const char16_t * aString,uint32_t aLength,Script aRunScript)1427 bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript,
1428                                      const char16_t* aString, uint32_t aLength,
1429                                      Script aRunScript) {
1430   NS_ASSERTION(aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER ||
1431                    aSubSuperscript == NS_FONT_VARIANT_POSITION_SUB,
1432                "unknown value of font-variant-position");
1433 
1434   uint32_t feature = aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER
1435                          ? HB_TAG('s', 'u', 'p', 's')
1436                          : HB_TAG('s', 'u', 'b', 's');
1437 
1438   if (!SupportsFeature(aRunScript, feature)) {
1439     return false;
1440   }
1441 
1442   // xxx - for graphite, don't really know how to sniff lookups so bail
1443   if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1444     return true;
1445   }
1446 
1447   if (!mHarfBuzzShaper) {
1448     mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
1449   }
1450   gfxHarfBuzzShaper* shaper =
1451       static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get());
1452   if (!shaper->Initialize()) {
1453     return false;
1454   }
1455 
1456   // get the hbset containing input glyphs for the feature
1457   const hb_set_t* inputGlyphs =
1458       mFontEntry->InputsForOpenTypeFeature(aRunScript, feature);
1459 
1460   // create an hbset containing default glyphs for the script run
1461   hb_set_t* defaultGlyphsInRun = hb_set_create();
1462 
1463   // for each character, get the glyph id
1464   for (uint32_t i = 0; i < aLength; i++) {
1465     uint32_t ch = aString[i];
1466 
1467     if (i + 1 < aLength && NS_IS_SURROGATE_PAIR(ch, aString[i + 1])) {
1468       i++;
1469       ch = SURROGATE_TO_UCS4(ch, aString[i]);
1470     }
1471 
1472     hb_codepoint_t gid = shaper->GetNominalGlyph(ch);
1473     hb_set_add(defaultGlyphsInRun, gid);
1474   }
1475 
1476   // intersect with input glyphs, if size is not the same ==> fallback
1477   uint32_t origSize = hb_set_get_population(defaultGlyphsInRun);
1478   hb_set_intersect(defaultGlyphsInRun, inputGlyphs);
1479   uint32_t intersectionSize = hb_set_get_population(defaultGlyphsInRun);
1480   hb_set_destroy(defaultGlyphsInRun);
1481 
1482   return origSize == intersectionSize;
1483 }
1484 
FeatureWillHandleChar(Script aRunScript,uint32_t aFeature,uint32_t aUnicode)1485 bool gfxFont::FeatureWillHandleChar(Script aRunScript, uint32_t aFeature,
1486                                     uint32_t aUnicode) {
1487   if (!SupportsFeature(aRunScript, aFeature)) {
1488     return false;
1489   }
1490 
1491   // xxx - for graphite, don't really know how to sniff lookups so bail
1492   if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
1493     return true;
1494   }
1495 
1496   if (!mHarfBuzzShaper) {
1497     mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
1498   }
1499   gfxHarfBuzzShaper* shaper =
1500       static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get());
1501   if (!shaper->Initialize()) {
1502     return false;
1503   }
1504 
1505   // get the hbset containing input glyphs for the feature
1506   const hb_set_t* inputGlyphs =
1507       mFontEntry->InputsForOpenTypeFeature(aRunScript, aFeature);
1508 
1509   hb_codepoint_t gid = shaper->GetNominalGlyph(aUnicode);
1510   return hb_set_has(inputGlyphs, gid);
1511 }
1512 
HasFeatureSet(uint32_t aFeature,bool & aFeatureOn)1513 bool gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn) {
1514   aFeatureOn = false;
1515 
1516   if (mStyle.featureSettings.IsEmpty() &&
1517       GetFontEntry()->mFeatureSettings.IsEmpty()) {
1518     return false;
1519   }
1520 
1521   // add feature values from font
1522   bool featureSet = false;
1523   uint32_t i, count;
1524 
1525   nsTArray<gfxFontFeature>& fontFeatures = GetFontEntry()->mFeatureSettings;
1526   count = fontFeatures.Length();
1527   for (i = 0; i < count; i++) {
1528     const gfxFontFeature& feature = fontFeatures.ElementAt(i);
1529     if (feature.mTag == aFeature) {
1530       featureSet = true;
1531       aFeatureOn = (feature.mValue != 0);
1532     }
1533   }
1534 
1535   // add feature values from style rules
1536   nsTArray<gfxFontFeature>& styleFeatures = mStyle.featureSettings;
1537   count = styleFeatures.Length();
1538   for (i = 0; i < count; i++) {
1539     const gfxFontFeature& feature = styleFeatures.ElementAt(i);
1540     if (feature.mTag == aFeature) {
1541       featureSet = true;
1542       aFeatureOn = (feature.mValue != 0);
1543     }
1544   }
1545 
1546   return featureSet;
1547 }
1548 
GetScaledFont(mozilla::gfx::DrawTarget * aDrawTarget)1549 already_AddRefed<mozilla::gfx::ScaledFont> gfxFont::GetScaledFont(
1550     mozilla::gfx::DrawTarget* aDrawTarget) {
1551   TextRunDrawParams params;
1552   params.dt = aDrawTarget;
1553   return GetScaledFont(params);
1554 }
1555 
InitializeScaledFont(const RefPtr<mozilla::gfx::ScaledFont> & aScaledFont)1556 void gfxFont::InitializeScaledFont(
1557     const RefPtr<mozilla::gfx::ScaledFont>& aScaledFont) {
1558   if (!aScaledFont) {
1559     return;
1560   }
1561 
1562   float angle = AngleForSyntheticOblique();
1563   if (angle != 0.0f) {
1564     aScaledFont->SetSyntheticObliqueAngle(angle);
1565   }
1566 }
1567 
1568 /**
1569  * A helper function in case we need to do any rounding or other
1570  * processing here.
1571  */
1572 #define ToDeviceUnits(aAppUnits, aDevUnitsPerAppUnit) \
1573   (double(aAppUnits) * double(aDevUnitsPerAppUnit))
1574 
Get2DAAMode(gfxFont::AntialiasOption aAAOption)1575 static AntialiasMode Get2DAAMode(gfxFont::AntialiasOption aAAOption) {
1576   switch (aAAOption) {
1577     case gfxFont::kAntialiasSubpixel:
1578       return AntialiasMode::SUBPIXEL;
1579     case gfxFont::kAntialiasGrayscale:
1580       return AntialiasMode::GRAY;
1581     case gfxFont::kAntialiasNone:
1582       return AntialiasMode::NONE;
1583     default:
1584       return AntialiasMode::DEFAULT;
1585   }
1586 }
1587 
1588 class GlyphBufferAzure {
1589 #define AUTO_BUFFER_SIZE (2048 / sizeof(Glyph))
1590 
1591   typedef mozilla::image::imgDrawingParams imgDrawingParams;
1592 
1593  public:
GlyphBufferAzure(const TextRunDrawParams & aRunParams,const FontDrawParams & aFontParams)1594   GlyphBufferAzure(const TextRunDrawParams& aRunParams,
1595                    const FontDrawParams& aFontParams)
1596       : mRunParams(aRunParams),
1597         mFontParams(aFontParams),
1598         mBuffer(*mAutoBuffer.addr()),
1599         mBufSize(AUTO_BUFFER_SIZE),
1600         mCapacity(0),
1601         mNumGlyphs(0) {}
1602 
~GlyphBufferAzure()1603   ~GlyphBufferAzure() {
1604     if (mNumGlyphs > 0) {
1605       FlushGlyphs();
1606     }
1607 
1608     if (mBuffer != *mAutoBuffer.addr()) {
1609       free(mBuffer);
1610     }
1611   }
1612 
1613   // Ensure the buffer has enough space for aGlyphCount glyphs to be added,
1614   // considering the supplied strike multipler aStrikeCount.
1615   // This MUST be called before OutputGlyph is used to actually store glyph
1616   // records in the buffer. It may be called repeated to add further capacity
1617   // in case we don't know up-front exactly what will be needed.
AddCapacity(uint32_t aGlyphCount,uint32_t aStrikeCount)1618   void AddCapacity(uint32_t aGlyphCount, uint32_t aStrikeCount) {
1619     // Calculate the new capacity and ensure it will fit within the maximum
1620     // allowed capacity.
1621     static const uint64_t kMaxCapacity = 64 * 1024;
1622     mCapacity = uint32_t(std::min(
1623         kMaxCapacity,
1624         uint64_t(mCapacity) + uint64_t(aGlyphCount) * uint64_t(aStrikeCount)));
1625     // See if the required capacity fits within the already-allocated space
1626     if (mCapacity <= mBufSize) {
1627       return;
1628     }
1629     // We need to grow the buffer: determine a new size, allocate, and
1630     // copy the existing data over if we didn't use realloc (which would
1631     // do it automatically).
1632     mBufSize = std::max(mCapacity, mBufSize * 2);
1633     if (mBuffer == *mAutoBuffer.addr()) {
1634       // switching from autobuffer to malloc, so we need to copy
1635       mBuffer = reinterpret_cast<Glyph*>(moz_xmalloc(mBufSize * sizeof(Glyph)));
1636       std::memcpy(mBuffer, *mAutoBuffer.addr(), mNumGlyphs * sizeof(Glyph));
1637     } else {
1638       mBuffer = reinterpret_cast<Glyph*>(
1639           moz_xrealloc(mBuffer, mBufSize * sizeof(Glyph)));
1640     }
1641   }
1642 
OutputGlyph(uint32_t aGlyphID,const gfx::Point & aPt)1643   void OutputGlyph(uint32_t aGlyphID, const gfx::Point& aPt) {
1644     // If the buffer is full, flush to make room for the new glyph.
1645     if (mNumGlyphs >= mCapacity) {
1646       // Check that AddCapacity has been used appropriately!
1647       MOZ_ASSERT(mCapacity > 0 && mNumGlyphs == mCapacity);
1648       Flush();
1649     }
1650     Glyph* glyph = mBuffer + mNumGlyphs++;
1651     glyph->mIndex = aGlyphID;
1652     glyph->mPosition = aPt;
1653   }
1654 
Flush()1655   void Flush() {
1656     if (mNumGlyphs > 0) {
1657       FlushGlyphs();
1658       mNumGlyphs = 0;
1659     }
1660   }
1661 
1662   const TextRunDrawParams& mRunParams;
1663   const FontDrawParams& mFontParams;
1664 
1665  private:
GetStrokeMode(DrawMode aMode)1666   static DrawMode GetStrokeMode(DrawMode aMode) {
1667     return aMode & (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH);
1668   }
1669 
1670   // Render the buffered glyphs to the draw target.
FlushGlyphs()1671   void FlushGlyphs() {
1672     gfx::GlyphBuffer buf;
1673     buf.mGlyphs = mBuffer;
1674     buf.mNumGlyphs = mNumGlyphs;
1675 
1676     const gfxContext::AzureState& state = mRunParams.context->CurrentState();
1677 
1678     // Draw stroke first if the UNDERNEATH flag is set in drawMode.
1679     if (mRunParams.strokeOpts &&
1680         GetStrokeMode(mRunParams.drawMode) ==
1681             (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH)) {
1682       DrawStroke(state, buf);
1683     }
1684 
1685     if (mRunParams.drawMode & DrawMode::GLYPH_FILL) {
1686       if (state.pattern || mFontParams.contextPaint) {
1687         Pattern* pat;
1688 
1689         RefPtr<gfxPattern> fillPattern;
1690         if (mFontParams.contextPaint) {
1691           imgDrawingParams imgParams;
1692           fillPattern = mFontParams.contextPaint->GetFillPattern(
1693               mRunParams.context->GetDrawTarget(),
1694               mRunParams.context->CurrentMatrixDouble(), imgParams);
1695         }
1696         if (!fillPattern) {
1697           if (state.pattern) {
1698             RefPtr<gfxPattern> statePattern =
1699                 mRunParams.context->CurrentState().pattern;
1700             pat = statePattern->GetPattern(mRunParams.dt,
1701                                            state.patternTransformChanged
1702                                                ? &state.patternTransform
1703                                                : nullptr);
1704           } else {
1705             pat = nullptr;
1706           }
1707         } else {
1708           pat = fillPattern->GetPattern(mRunParams.dt);
1709         }
1710 
1711         if (pat) {
1712           mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf, *pat,
1713                                     mFontParams.drawOptions);
1714         }
1715       } else {
1716         mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
1717                                   ColorPattern(state.color),
1718                                   mFontParams.drawOptions);
1719       }
1720     }
1721 
1722     // Draw stroke if the UNDERNEATH flag is not set.
1723     if (mRunParams.strokeOpts &&
1724         GetStrokeMode(mRunParams.drawMode) == DrawMode::GLYPH_STROKE) {
1725       DrawStroke(state, buf);
1726     }
1727 
1728     if (mRunParams.drawMode & DrawMode::GLYPH_PATH) {
1729       mRunParams.context->EnsurePathBuilder();
1730       Matrix mat = mRunParams.dt->GetTransform();
1731       mFontParams.scaledFont->CopyGlyphsToBuilder(
1732           buf, mRunParams.context->mPathBuilder, &mat);
1733     }
1734   }
1735 
DrawStroke(const gfxContext::AzureState & aState,gfx::GlyphBuffer & aBuffer)1736   void DrawStroke(const gfxContext::AzureState& aState,
1737                   gfx::GlyphBuffer& aBuffer) {
1738     if (mRunParams.textStrokePattern) {
1739       Pattern* pat = mRunParams.textStrokePattern->GetPattern(
1740           mRunParams.dt,
1741           aState.patternTransformChanged ? &aState.patternTransform : nullptr);
1742 
1743       if (pat) {
1744         FlushStroke(aBuffer, *pat);
1745       }
1746     } else {
1747       FlushStroke(aBuffer,
1748                   ColorPattern(ToDeviceColor(mRunParams.textStrokeColor)));
1749     }
1750   }
1751 
FlushStroke(gfx::GlyphBuffer & aBuf,const Pattern & aPattern)1752   void FlushStroke(gfx::GlyphBuffer& aBuf, const Pattern& aPattern) {
1753     mRunParams.dt->StrokeGlyphs(mFontParams.scaledFont, aBuf, aPattern,
1754                                 *mRunParams.strokeOpts,
1755                                 mFontParams.drawOptions);
1756   }
1757 
1758   // We use an "inline" buffer automatically allocated (on the stack) as part
1759   // of the GlyphBufferAzure object to hold the glyphs in most cases, falling
1760   // back to a separately-allocated heap buffer if the count of buffered
1761   // glyphs gets too big.
1762   //
1763   // This is basically a rudimentary AutoTArray; so why not use AutoTArray
1764   // itself?
1765   //
1766   // If we used an AutoTArray, we'd want to avoid using SetLength or
1767   // AppendElements to allocate the space we actually need, because those
1768   // methods would default-construct the new elements.
1769   //
1770   // Could we use SetCapacity to reserve the necessary buffer space without
1771   // default-constructing all the Glyph records? No, because of a failure
1772   // that could occur when we need to grow the buffer, which happens when we
1773   // encounter a DetailedGlyph in the textrun that refers to a sequence of
1774   // several real glyphs. At that point, we need to add some extra capacity
1775   // to the buffer we initially allocated based on the length of the textrun
1776   // range we're rendering.
1777   //
1778   // This buffer growth would work fine as long as it still fits within the
1779   // array's inline buffer (we just use a bit more of it), or if the buffer
1780   // was already heap-allocated (in which case AutoTArray will use realloc(),
1781   // preserving its contents). But a problem will arise when the initial
1782   // capacity we allocated (based on the length of the run) fits within the
1783   // array's inline buffer, but subsequently we need to extend the buffer
1784   // beyond the inline buffer size, so we reallocate to the heap. Because we
1785   // haven't "officially" filled the array with SetLength or AppendElements,
1786   // its mLength is still zero; as far as it's concerned the buffer is just
1787   // uninitialized space, and when it switches to use a malloc'd buffer it
1788   // won't copy the existing contents.
1789 
1790   // Allocate space for a buffer of Glyph records, without initializing them.
1791   AlignedStorage2<Glyph[AUTO_BUFFER_SIZE]> mAutoBuffer;
1792 
1793   // Pointer to the buffer we're currently using -- initially mAutoBuffer,
1794   // but may be changed to a malloc'd buffer, in which case that buffer must
1795   // be free'd on destruction.
1796   Glyph* mBuffer;
1797 
1798   uint32_t mBufSize;    // size of allocated buffer; capacity can grow to
1799                         // this before reallocation is needed
1800   uint32_t mCapacity;   // amount of buffer size reserved
1801   uint32_t mNumGlyphs;  // number of glyphs actually present in the buffer
1802 
1803 #undef AUTO_BUFFER_SIZE
1804 };
1805 
1806 // Bug 674909. When synthetic bolding text by drawing twice, need to
1807 // render using a pixel offset in device pixels, otherwise text
1808 // doesn't appear bolded, it appears as if a bad text shadow exists
1809 // when a non-identity transform exists.  Use an offset factor so that
1810 // the second draw occurs at a constant offset in device pixels.
1811 
CalcXScale(DrawTarget * aDrawTarget)1812 gfx::Float gfxFont::CalcXScale(DrawTarget* aDrawTarget) {
1813   // determine magnitude of a 1px x offset in device space
1814   Size t = aDrawTarget->GetTransform().TransformSize(Size(1.0, 0.0));
1815   if (t.width == 1.0 && t.height == 0.0) {
1816     // short-circuit the most common case to avoid sqrt() and division
1817     return 1.0;
1818   }
1819 
1820   gfx::Float m = sqrtf(t.width * t.width + t.height * t.height);
1821 
1822   NS_ASSERTION(m != 0.0, "degenerate transform while synthetic bolding");
1823   if (m == 0.0) {
1824     return 0.0;  // effectively disables offset
1825   }
1826 
1827   // scale factor so that offsets are 1px in device pixels
1828   return 1.0 / m;
1829 }
1830 
1831 // Draw a run of CharacterGlyph records from the given offset in aShapedText.
1832 // Returns true if glyph paths were actually emitted.
1833 template <gfxFont::FontComplexityT FC, gfxFont::SpacingT S>
DrawGlyphs(const gfxShapedText * aShapedText,uint32_t aOffset,uint32_t aCount,gfx::Point * aPt,const gfx::Matrix * aOffsetMatrix,GlyphBufferAzure & aBuffer)1834 bool gfxFont::DrawGlyphs(const gfxShapedText* aShapedText,
1835                          uint32_t aOffset,  // offset in the textrun
1836                          uint32_t aCount,   // length of run to draw
1837                          gfx::Point* aPt,
1838                          const gfx::Matrix* aOffsetMatrix,  // may be null
1839                          GlyphBufferAzure& aBuffer) {
1840   float& inlineCoord = aBuffer.mFontParams.isVerticalFont ? aPt->y : aPt->x;
1841 
1842   const gfxShapedText::CompressedGlyph* glyphData =
1843       &aShapedText->GetCharacterGlyphs()[aOffset];
1844 
1845   if (S == SpacingT::HasSpacing) {
1846     float space = aBuffer.mRunParams.spacing[0].mBefore *
1847                   aBuffer.mFontParams.advanceDirection;
1848     inlineCoord += space;
1849   }
1850 
1851   // Allocate buffer space for the run, assuming all simple glyphs.
1852   uint32_t capacityMult = 1 + aBuffer.mFontParams.extraStrikes;
1853   aBuffer.AddCapacity(aCount, capacityMult);
1854 
1855   bool emittedGlyphs = false;
1856 
1857   for (uint32_t i = 0; i < aCount; ++i, ++glyphData) {
1858     if (glyphData->IsSimpleGlyph()) {
1859       float advance =
1860           glyphData->GetSimpleAdvance() * aBuffer.mFontParams.advanceDirection;
1861       if (aBuffer.mRunParams.isRTL) {
1862         inlineCoord += advance;
1863       }
1864       DrawOneGlyph<FC>(glyphData->GetSimpleGlyph(), *aPt, aBuffer,
1865                        &emittedGlyphs);
1866       if (!aBuffer.mRunParams.isRTL) {
1867         inlineCoord += advance;
1868       }
1869     } else {
1870       uint32_t glyphCount = glyphData->GetGlyphCount();
1871       if (glyphCount > 0) {
1872         // Add extra buffer capacity to allow for multiple-glyph entry.
1873         aBuffer.AddCapacity(glyphCount - 1, capacityMult);
1874         const gfxShapedText::DetailedGlyph* details =
1875             aShapedText->GetDetailedGlyphs(aOffset + i);
1876         MOZ_ASSERT(details, "missing DetailedGlyph!");
1877         for (uint32_t j = 0; j < glyphCount; ++j, ++details) {
1878           float advance =
1879               details->mAdvance * aBuffer.mFontParams.advanceDirection;
1880           if (aBuffer.mRunParams.isRTL) {
1881             inlineCoord += advance;
1882           }
1883           if (glyphData->IsMissing()) {
1884             if (!DrawMissingGlyph(aBuffer.mRunParams, aBuffer.mFontParams,
1885                                   details, *aPt)) {
1886               return false;
1887             }
1888           } else {
1889             gfx::Point glyphPt(
1890                 *aPt + (aOffsetMatrix
1891                             ? aOffsetMatrix->TransformPoint(details->mOffset)
1892                             : details->mOffset));
1893             DrawOneGlyph<FC>(details->mGlyphID, glyphPt, aBuffer,
1894                              &emittedGlyphs);
1895           }
1896           if (!aBuffer.mRunParams.isRTL) {
1897             inlineCoord += advance;
1898           }
1899         }
1900       }
1901     }
1902 
1903     if (S == SpacingT::HasSpacing) {
1904       float space = aBuffer.mRunParams.spacing[i].mAfter;
1905       if (i + 1 < aCount) {
1906         space += aBuffer.mRunParams.spacing[i + 1].mBefore;
1907       }
1908       space *= aBuffer.mFontParams.advanceDirection;
1909       inlineCoord += space;
1910     }
1911   }
1912 
1913   return emittedGlyphs;
1914 }
1915 
1916 // Draw an individual glyph at a specific location.
1917 // *aPt is the glyph position in appUnits; it is converted to device
1918 // coordinates (devPt) here.
1919 template <gfxFont::FontComplexityT FC>
DrawOneGlyph(uint32_t aGlyphID,const gfx::Point & aPt,GlyphBufferAzure & aBuffer,bool * aEmittedGlyphs)1920 void gfxFont::DrawOneGlyph(uint32_t aGlyphID, const gfx::Point& aPt,
1921                            GlyphBufferAzure& aBuffer, bool* aEmittedGlyphs) {
1922   const TextRunDrawParams& runParams(aBuffer.mRunParams);
1923 
1924   gfx::Point devPt(ToDeviceUnits(aPt.x, runParams.devPerApp),
1925                    ToDeviceUnits(aPt.y, runParams.devPerApp));
1926 
1927   if (FC == FontComplexityT::ComplexFont) {
1928     const FontDrawParams& fontParams(aBuffer.mFontParams);
1929 
1930     auto* textDrawer = runParams.context->GetTextDrawer();
1931 
1932     gfxContextMatrixAutoSaveRestore matrixRestore;
1933 
1934     if (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont &&
1935         !textDrawer) {
1936       // We have to flush each glyph individually when doing
1937       // synthetic-oblique for vertical-upright text, because
1938       // the skew transform needs to be applied to a separate
1939       // origin for each glyph, not once for the whole run.
1940       aBuffer.Flush();
1941       matrixRestore.SetContext(runParams.context);
1942       gfx::Point skewPt(
1943           devPt.x + GetMetrics(nsFontMetrics::eVertical).emHeight / 2, devPt.y);
1944       gfx::Matrix mat =
1945           runParams.context->CurrentMatrix()
1946               .PreTranslate(skewPt)
1947               .PreMultiply(gfx::Matrix(1, fontParams.obliqueSkew, 0, 1, 0, 0))
1948               .PreTranslate(-skewPt);
1949       runParams.context->SetMatrix(mat);
1950     }
1951 
1952     if (fontParams.haveSVGGlyphs) {
1953       if (!runParams.paintSVGGlyphs) {
1954         return;
1955       }
1956       NS_WARNING_ASSERTION(
1957           runParams.drawMode != DrawMode::GLYPH_PATH,
1958           "Rendering SVG glyph despite request for glyph path");
1959       if (RenderSVGGlyph(runParams.context, textDrawer, devPt, aGlyphID,
1960                          fontParams.contextPaint, runParams.callbacks,
1961                          *aEmittedGlyphs)) {
1962         return;
1963       }
1964     }
1965 
1966     if (fontParams.haveColorGlyphs &&
1967         !gfxPlatform::GetPlatform()->HasNativeColrFontSupport() &&
1968         RenderColorGlyph(runParams.dt, runParams.context, textDrawer,
1969                          fontParams.scaledFont, fontParams.drawOptions, devPt,
1970                          aGlyphID)) {
1971       return;
1972     }
1973 
1974     aBuffer.OutputGlyph(aGlyphID, devPt);
1975 
1976     // Synthetic bolding (if required) by multi-striking.
1977     for (int32_t i = 0; i < fontParams.extraStrikes; ++i) {
1978       if (fontParams.isVerticalFont) {
1979         devPt.y += fontParams.synBoldOnePixelOffset;
1980       } else {
1981         devPt.x += fontParams.synBoldOnePixelOffset;
1982       }
1983       aBuffer.OutputGlyph(aGlyphID, devPt);
1984     }
1985 
1986     if (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont &&
1987         !textDrawer) {
1988       aBuffer.Flush();
1989     }
1990   } else {
1991     aBuffer.OutputGlyph(aGlyphID, devPt);
1992   }
1993 
1994   *aEmittedGlyphs = true;
1995 }
1996 
DrawMissingGlyph(const TextRunDrawParams & aRunParams,const FontDrawParams & aFontParams,const gfxShapedText::DetailedGlyph * aDetails,const gfx::Point & aPt)1997 bool gfxFont::DrawMissingGlyph(const TextRunDrawParams& aRunParams,
1998                                const FontDrawParams& aFontParams,
1999                                const gfxShapedText::DetailedGlyph* aDetails,
2000                                const gfx::Point& aPt) {
2001   // Default-ignorable chars will have zero advance width;
2002   // we don't have to draw the hexbox for them.
2003   float advance = aDetails->mAdvance;
2004   if (aRunParams.drawMode != DrawMode::GLYPH_PATH && advance > 0) {
2005     auto* textDrawer = aRunParams.context->GetTextDrawer();
2006     const Matrix* matPtr = nullptr;
2007     Matrix mat;
2008     if (textDrawer) {
2009       // Generate an orientation matrix for the current writing mode
2010       wr::FontInstanceFlags flags = textDrawer->GetWRGlyphFlags();
2011       if (flags & wr::FontInstanceFlags::TRANSPOSE) {
2012         std::swap(mat._11, mat._12);
2013         std::swap(mat._21, mat._22);
2014       }
2015       mat.PostScale(flags & wr::FontInstanceFlags::FLIP_X ? -1.0f : 1.0f,
2016                     flags & wr::FontInstanceFlags::FLIP_Y ? -1.0f : 1.0f);
2017       matPtr = &mat;
2018     }
2019 
2020     Point pt(Float(ToDeviceUnits(aPt.x, aRunParams.devPerApp)),
2021              Float(ToDeviceUnits(aPt.y, aRunParams.devPerApp)));
2022     Float advanceDevUnits = Float(ToDeviceUnits(advance, aRunParams.devPerApp));
2023     Float height = GetMetrics(nsFontMetrics::eHorizontal).maxAscent;
2024     // Horizontally center if drawing vertically upright with no sideways
2025     // transform.
2026     Rect glyphRect =
2027         aFontParams.isVerticalFont && !mat.HasNonAxisAlignedTransform()
2028             ? Rect(pt.x - height / 2, pt.y, height, advanceDevUnits)
2029             : Rect(pt.x, pt.y - height, advanceDevUnits, height);
2030 
2031     // If there's a fake-italic skew in effect as part
2032     // of the drawTarget's transform, we need to undo
2033     // this before drawing the hexbox. (Bug 983985)
2034     gfxContextMatrixAutoSaveRestore matrixRestore;
2035     if (aFontParams.obliqueSkew != 0.0f && !aFontParams.isVerticalFont &&
2036         !textDrawer) {
2037       matrixRestore.SetContext(aRunParams.context);
2038       gfx::Matrix mat =
2039           aRunParams.context->CurrentMatrix()
2040               .PreTranslate(pt)
2041               .PreMultiply(gfx::Matrix(1, 0, aFontParams.obliqueSkew, 1, 0, 0))
2042               .PreTranslate(-pt);
2043       aRunParams.context->SetMatrix(mat);
2044     }
2045 
2046     gfxFontMissingGlyphs::DrawMissingGlyph(aDetails->mGlyphID, glyphRect,
2047                                            *aRunParams.dt,
2048                                            PatternFromState(aRunParams.context),
2049                                            1.0 / aRunParams.devPerApp, matPtr);
2050   }
2051   return true;
2052 }
2053 
2054 // This method is mostly parallel to DrawGlyphs.
DrawEmphasisMarks(const gfxTextRun * aShapedText,gfx::Point * aPt,uint32_t aOffset,uint32_t aCount,const EmphasisMarkDrawParams & aParams)2055 void gfxFont::DrawEmphasisMarks(const gfxTextRun* aShapedText, gfx::Point* aPt,
2056                                 uint32_t aOffset, uint32_t aCount,
2057                                 const EmphasisMarkDrawParams& aParams) {
2058   float& inlineCoord = aParams.isVertical ? aPt->y : aPt->x;
2059   gfxTextRun::Range markRange(aParams.mark);
2060   gfxTextRun::DrawParams params(aParams.context);
2061 
2062   float clusterStart = -std::numeric_limits<float>::infinity();
2063   bool shouldDrawEmphasisMark = false;
2064   for (uint32_t i = 0, idx = aOffset; i < aCount; ++i, ++idx) {
2065     if (aParams.spacing) {
2066       inlineCoord += aParams.direction * aParams.spacing[i].mBefore;
2067     }
2068     if (aShapedText->IsClusterStart(idx) ||
2069         clusterStart == -std::numeric_limits<float>::infinity()) {
2070       clusterStart = inlineCoord;
2071     }
2072     if (aShapedText->CharMayHaveEmphasisMark(idx)) {
2073       shouldDrawEmphasisMark = true;
2074     }
2075     inlineCoord += aParams.direction * aShapedText->GetAdvanceForGlyph(idx);
2076     if (shouldDrawEmphasisMark &&
2077         (i + 1 == aCount || aShapedText->IsClusterStart(idx + 1))) {
2078       float clusterAdvance = inlineCoord - clusterStart;
2079       // Move the coord backward to get the needed start point.
2080       float delta = (clusterAdvance + aParams.advance) / 2;
2081       inlineCoord -= delta;
2082       aParams.mark->Draw(markRange, *aPt, params);
2083       inlineCoord += delta;
2084       shouldDrawEmphasisMark = false;
2085     }
2086     if (aParams.spacing) {
2087       inlineCoord += aParams.direction * aParams.spacing[i].mAfter;
2088     }
2089   }
2090 }
2091 
Draw(const gfxTextRun * aTextRun,uint32_t aStart,uint32_t aEnd,gfx::Point * aPt,const TextRunDrawParams & aRunParams,gfx::ShapedTextFlags aOrientation)2092 void gfxFont::Draw(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd,
2093                    gfx::Point* aPt, const TextRunDrawParams& aRunParams,
2094                    gfx::ShapedTextFlags aOrientation) {
2095   NS_ASSERTION(aRunParams.drawMode == DrawMode::GLYPH_PATH ||
2096                    !(int(aRunParams.drawMode) & int(DrawMode::GLYPH_PATH)),
2097                "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or "
2098                "GLYPH_STROKE_UNDERNEATH");
2099 
2100   if (aStart >= aEnd) {
2101     return;
2102   }
2103 
2104   FontDrawParams fontParams;
2105 
2106   if (aRunParams.drawOpts) {
2107     fontParams.drawOptions = *aRunParams.drawOpts;
2108   }
2109 
2110   fontParams.scaledFont = GetScaledFont(aRunParams);
2111   if (!fontParams.scaledFont) {
2112     return;
2113   }
2114   auto* textDrawer = aRunParams.context->GetTextDrawer();
2115 
2116   fontParams.obliqueSkew = SkewForSyntheticOblique();
2117   fontParams.haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this);
2118   fontParams.haveColorGlyphs = GetFontEntry()->TryGetColorGlyphs();
2119   fontParams.contextPaint = aRunParams.runContextPaint;
2120 
2121   if (textDrawer) {
2122     fontParams.isVerticalFont = aRunParams.isVerticalRun;
2123   } else {
2124     fontParams.isVerticalFont =
2125         aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
2126   }
2127 
2128   gfxContextMatrixAutoSaveRestore matrixRestore;
2129   layout::TextDrawTarget::AutoRestoreWRGlyphFlags glyphFlagsRestore;
2130 
2131   // Save the current baseline offset for restoring later, in case it is
2132   // modified.
2133   float& baseline = fontParams.isVerticalFont ? aPt->x : aPt->y;
2134   float origBaseline = baseline;
2135 
2136   // The point may be advanced in local-space, while the resulting point on
2137   // return must be advanced in transformed space. So save the original point so
2138   // we can properly transform the advance later.
2139   gfx::Point origPt = *aPt;
2140   const gfx::Matrix* offsetMatrix = nullptr;
2141 
2142   // Default to advancing along the +X direction (-X if RTL).
2143   fontParams.advanceDirection = aRunParams.isRTL ? -1.0f : 1.0f;
2144   // Default to offsetting baseline downward along the +Y direction.
2145   float baselineDir = 1.0f;
2146   // The direction of sideways rotation, if applicable.
2147   // -1 for rotating left/counter-clockwise
2148   // 1 for rotating right/clockwise
2149   // 0 for no rotation
2150   float sidewaysDir =
2151       (aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
2152            ? -1.0f
2153            : (aOrientation ==
2154                       gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT
2155                   ? 1.0f
2156                   : 0.0f));
2157   // If we're rendering a sideways run, we need to push a rotation transform to
2158   // the context.
2159   if (sidewaysDir != 0.0f) {
2160     if (textDrawer) {
2161       // For WebRender, we can't use a DrawTarget transform and must instead use
2162       // flags that locally transform the glyph, without affecting the glyph
2163       // origin. The glyph origins must thus be offset in the transformed
2164       // directions (instead of local-space directions). Modify the advance and
2165       // baseline directions to account for the indicated transform.
2166 
2167       // The default text orientation is down being +Y and right being +X.
2168       // Rotating 90 degrees left/CCW makes down be +X and right be -Y.
2169       // Rotating 90 degrees right/CW makes down be -X and right be +Y.
2170       // Thus the advance direction (moving right) is just sidewaysDir,
2171       // i.e. negative along Y axis if rotated left and positive if
2172       // rotated right.
2173       fontParams.advanceDirection *= sidewaysDir;
2174       // The baseline direction (moving down) is negated relative to the
2175       // advance direction for sideways transforms.
2176       baselineDir *= -sidewaysDir;
2177 
2178       glyphFlagsRestore.Save(textDrawer);
2179       // Set the transform flags accordingly. Both sideways rotations transpose
2180       // X and Y, while left rotation flips the resulting Y axis, and right
2181       // rotation flips the resulting X axis.
2182       textDrawer->SetWRGlyphFlags(
2183           textDrawer->GetWRGlyphFlags() | wr::FontInstanceFlags::TRANSPOSE |
2184           (aOrientation ==
2185                    gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
2186                ? wr::FontInstanceFlags::FLIP_Y
2187                : wr::FontInstanceFlags::FLIP_X));
2188       // We also need to set up a transform for the glyph offset vector that
2189       // may be present in DetailedGlyph records.
2190       static const gfx::Matrix kSidewaysLeft = {0, -1, 1, 0, 0, 0};
2191       static const gfx::Matrix kSidewaysRight = {0, 1, -1, 0, 0, 0};
2192       offsetMatrix =
2193           (aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT)
2194               ? &kSidewaysLeft
2195               : &kSidewaysRight;
2196     } else {
2197       // For non-WebRender targets, just push a rotation transform.
2198       matrixRestore.SetContext(aRunParams.context);
2199       gfxPoint p(aPt->x * aRunParams.devPerApp, aPt->y * aRunParams.devPerApp);
2200       // Get a matrix we can use to draw the (horizontally-shaped) textrun
2201       // with 90-degree CW rotation.
2202       const gfxFloat rotation = sidewaysDir * M_PI / 2.0f;
2203       gfxMatrix mat = aRunParams.context->CurrentMatrixDouble()
2204                           .PreTranslate(p)
2205                           .  // translate origin for rotation
2206                       PreRotate(rotation)
2207                           .  // turn 90deg CCW (sideways-left) or CW (*-right)
2208                       PreTranslate(-p);  // undo the translation
2209 
2210       aRunParams.context->SetMatrixDouble(mat);
2211     }
2212 
2213     // If we're drawing rotated horizontal text for an element styled
2214     // text-orientation:mixed, the dominant baseline will be vertical-
2215     // centered. So in this case, we need to adjust the position so that
2216     // the rotated horizontal text (which uses an alphabetic baseline) will
2217     // look OK when juxtaposed with upright glyphs (rendered on a centered
2218     // vertical baseline). The adjustment here is somewhat ad hoc; we
2219     // should eventually look for baseline tables[1] in the fonts and use
2220     // those if available.
2221     // [1] See http://www.microsoft.com/typography/otspec/base.htm
2222     if (aTextRun->UseCenterBaseline()) {
2223       const Metrics& metrics = GetMetrics(nsFontMetrics::eHorizontal);
2224       float baseAdj = (metrics.emAscent - metrics.emDescent) / 2;
2225       baseline += baseAdj * aTextRun->GetAppUnitsPerDevUnit() * baselineDir;
2226     }
2227   } else if (textDrawer &&
2228              aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) {
2229     glyphFlagsRestore.Save(textDrawer);
2230     textDrawer->SetWRGlyphFlags(textDrawer->GetWRGlyphFlags() |
2231                                 wr::FontInstanceFlags::VERTICAL);
2232   }
2233 
2234   if (fontParams.obliqueSkew != 0.0f && !fontParams.isVerticalFont &&
2235       !textDrawer) {
2236     // Adjust matrix for synthetic-oblique, except if we're doing vertical-
2237     // upright text, in which case this will be handled for each glyph
2238     // individually in DrawOneGlyph.
2239     if (!matrixRestore.HasMatrix()) {
2240       matrixRestore.SetContext(aRunParams.context);
2241     }
2242     gfx::Point p(aPt->x * aRunParams.devPerApp, aPt->y * aRunParams.devPerApp);
2243     gfx::Matrix mat =
2244         aRunParams.context->CurrentMatrix()
2245             .PreTranslate(p)
2246             .PreMultiply(gfx::Matrix(1, 0, -fontParams.obliqueSkew, 1, 0, 0))
2247             .PreTranslate(-p);
2248     aRunParams.context->SetMatrix(mat);
2249   }
2250 
2251   RefPtr<SVGContextPaint> contextPaint;
2252   if (fontParams.haveSVGGlyphs && !fontParams.contextPaint) {
2253     // If no pattern is specified for fill, use the current pattern
2254     NS_ASSERTION((int(aRunParams.drawMode) & int(DrawMode::GLYPH_STROKE)) == 0,
2255                  "no pattern supplied for stroking text");
2256     RefPtr<gfxPattern> fillPattern = aRunParams.context->GetPattern();
2257     contextPaint = new SimpleTextContextPaint(
2258         fillPattern, nullptr, aRunParams.context->CurrentMatrixDouble());
2259     fontParams.contextPaint = contextPaint.get();
2260   }
2261 
2262   // Synthetic-bold strikes are each offset one device pixel in run direction
2263   // (these values are only needed if ApplySyntheticBold() is true).
2264   // If drawing via webrender, it will do multistrike internally so we don't
2265   // need to handle it here.
2266   bool doMultistrikeBold = ApplySyntheticBold() && !textDrawer;
2267   if (doMultistrikeBold) {
2268     gfx::Float xscale = CalcXScale(aRunParams.context->GetDrawTarget());
2269     fontParams.synBoldOnePixelOffset = aRunParams.direction * xscale;
2270     if (xscale != 0.0) {
2271       static const int32_t kMaxExtraStrikes = 128;
2272       gfxFloat extraStrikes = GetSyntheticBoldOffset() / xscale;
2273       if (extraStrikes > kMaxExtraStrikes) {
2274         // if too many strikes are required, limit them and increase the step
2275         // size to compensate
2276         fontParams.extraStrikes = kMaxExtraStrikes;
2277         fontParams.synBoldOnePixelOffset = aRunParams.direction *
2278                                            GetSyntheticBoldOffset() /
2279                                            fontParams.extraStrikes;
2280       } else {
2281         // use as many strikes as needed for the increased advance
2282         fontParams.extraStrikes = NS_lroundf(std::max(1.0, extraStrikes));
2283       }
2284     }
2285   } else {
2286     fontParams.synBoldOnePixelOffset = 0;
2287     fontParams.extraStrikes = 0;
2288   }
2289 
2290   bool oldSubpixelAA = aRunParams.dt->GetPermitSubpixelAA();
2291   if (!AllowSubpixelAA()) {
2292     aRunParams.dt->SetPermitSubpixelAA(false);
2293   }
2294 
2295   Matrix mat;
2296   Matrix oldMat = aRunParams.dt->GetTransform();
2297 
2298   fontParams.drawOptions.mAntialiasMode = Get2DAAMode(mAntialiasOption);
2299 
2300   if (mStyle.baselineOffset != 0.0) {
2301     baseline +=
2302         mStyle.baselineOffset * aTextRun->GetAppUnitsPerDevUnit() * baselineDir;
2303   }
2304 
2305   bool emittedGlyphs;
2306   {
2307     // Select appropriate version of the templated DrawGlyphs method
2308     // to output glyphs to the buffer, depending on complexity needed
2309     // for the type of font, and whether added inter-glyph spacing
2310     // is specified.
2311     GlyphBufferAzure buffer(aRunParams, fontParams);
2312     if (fontParams.haveSVGGlyphs || fontParams.haveColorGlyphs ||
2313         fontParams.extraStrikes ||
2314         (fontParams.obliqueSkew != 0.0f && fontParams.isVerticalFont &&
2315          !textDrawer)) {
2316       if (aRunParams.spacing) {
2317         emittedGlyphs =
2318             DrawGlyphs<FontComplexityT::ComplexFont, SpacingT::HasSpacing>(
2319                 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
2320       } else {
2321         emittedGlyphs =
2322             DrawGlyphs<FontComplexityT::ComplexFont, SpacingT::NoSpacing>(
2323                 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
2324       }
2325     } else {
2326       if (aRunParams.spacing) {
2327         emittedGlyphs =
2328             DrawGlyphs<FontComplexityT::SimpleFont, SpacingT::HasSpacing>(
2329                 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
2330       } else {
2331         emittedGlyphs =
2332             DrawGlyphs<FontComplexityT::SimpleFont, SpacingT::NoSpacing>(
2333                 aTextRun, aStart, aEnd - aStart, aPt, offsetMatrix, buffer);
2334       }
2335     }
2336   }
2337 
2338   baseline = origBaseline;
2339 
2340   if (aRunParams.callbacks && emittedGlyphs) {
2341     aRunParams.callbacks->NotifyGlyphPathEmitted();
2342   }
2343 
2344   aRunParams.dt->SetTransform(oldMat);
2345   aRunParams.dt->SetPermitSubpixelAA(oldSubpixelAA);
2346 
2347   if (sidewaysDir != 0.0f && !textDrawer) {
2348     // Adjust updated aPt to account for the transform we were using.
2349     // The advance happened horizontally in local-space, but the transformed
2350     // sideways advance is actually vertical, with sign depending on the
2351     // direction of rotation.
2352     float advance = aPt->x - origPt.x;
2353     *aPt = gfx::Point(origPt.x, origPt.y + advance * sidewaysDir);
2354   }
2355 }
2356 
RenderSVGGlyph(gfxContext * aContext,layout::TextDrawTarget * aTextDrawer,gfx::Point aPoint,uint32_t aGlyphId,SVGContextPaint * aContextPaint) const2357 bool gfxFont::RenderSVGGlyph(gfxContext* aContext,
2358                              layout::TextDrawTarget* aTextDrawer,
2359                              gfx::Point aPoint, uint32_t aGlyphId,
2360                              SVGContextPaint* aContextPaint) const {
2361   if (!GetFontEntry()->HasSVGGlyph(aGlyphId)) {
2362     return false;
2363   }
2364 
2365   if (aTextDrawer) {
2366     // WebRender doesn't support SVG Glyphs.
2367     // (pretend to succeed, output doesn't matter, we will emit a blob)
2368     aTextDrawer->FoundUnsupportedFeature();
2369     return true;
2370   }
2371 
2372   const gfxFloat devUnitsPerSVGUnit =
2373       GetAdjustedSize() / GetFontEntry()->UnitsPerEm();
2374   gfxContextMatrixAutoSaveRestore matrixRestore(aContext);
2375 
2376   aContext->SetMatrix(aContext->CurrentMatrix()
2377                           .PreTranslate(aPoint.x, aPoint.y)
2378                           .PreScale(devUnitsPerSVGUnit, devUnitsPerSVGUnit));
2379 
2380   aContextPaint->InitStrokeGeometry(aContext, devUnitsPerSVGUnit);
2381 
2382   GetFontEntry()->RenderSVGGlyph(aContext, aGlyphId, aContextPaint);
2383   aContext->NewPath();
2384   return true;
2385 }
2386 
RenderSVGGlyph(gfxContext * aContext,layout::TextDrawTarget * aTextDrawer,gfx::Point aPoint,uint32_t aGlyphId,SVGContextPaint * aContextPaint,gfxTextRunDrawCallbacks * aCallbacks,bool & aEmittedGlyphs) const2387 bool gfxFont::RenderSVGGlyph(gfxContext* aContext,
2388                              layout::TextDrawTarget* aTextDrawer,
2389                              gfx::Point aPoint, uint32_t aGlyphId,
2390                              SVGContextPaint* aContextPaint,
2391                              gfxTextRunDrawCallbacks* aCallbacks,
2392                              bool& aEmittedGlyphs) const {
2393   if (aCallbacks && aEmittedGlyphs) {
2394     aCallbacks->NotifyGlyphPathEmitted();
2395     aEmittedGlyphs = false;
2396   }
2397   return RenderSVGGlyph(aContext, aTextDrawer, aPoint, aGlyphId, aContextPaint);
2398 }
2399 
RenderColorGlyph(DrawTarget * aDrawTarget,gfxContext * aContext,layout::TextDrawTarget * aTextDrawer,mozilla::gfx::ScaledFont * scaledFont,mozilla::gfx::DrawOptions aDrawOptions,const mozilla::gfx::Point & aPoint,uint32_t aGlyphId) const2400 bool gfxFont::RenderColorGlyph(DrawTarget* aDrawTarget, gfxContext* aContext,
2401                                layout::TextDrawTarget* aTextDrawer,
2402                                mozilla::gfx::ScaledFont* scaledFont,
2403                                mozilla::gfx::DrawOptions aDrawOptions,
2404                                const mozilla::gfx::Point& aPoint,
2405                                uint32_t aGlyphId) const {
2406   AutoTArray<uint16_t, 8> layerGlyphs;
2407   AutoTArray<mozilla::gfx::DeviceColor, 8> layerColors;
2408 
2409   mozilla::gfx::DeviceColor defaultColor;
2410   if (!aContext->GetDeviceColor(defaultColor)) {
2411     defaultColor = ToDeviceColor(mozilla::gfx::sRGBColor::OpaqueBlack());
2412   }
2413   if (!GetFontEntry()->GetColorLayersInfo(aGlyphId, defaultColor, layerGlyphs,
2414                                           layerColors)) {
2415     return false;
2416   }
2417 
2418   // Default to opaque rendering (non-webrender applies alpha with a layer)
2419   float alpha = 1.0;
2420   if (aTextDrawer) {
2421     // defaultColor is the one that comes from CSS, so it has transparency info.
2422     bool hasComplexTransparency = 0.f < defaultColor.a && defaultColor.a < 1.f;
2423     if (hasComplexTransparency && layerGlyphs.Length() > 1) {
2424       // WebRender doesn't support drawing multi-layer transparent color-glyphs,
2425       // as it requires compositing all the layers before applying transparency.
2426       // (pretend to succeed, output doesn't matter, we will emit a blob)
2427       aTextDrawer->FoundUnsupportedFeature();
2428       return true;
2429     }
2430 
2431     // If we get here, then either alpha is 0 or 1, or there's only one layer
2432     // which shouldn't have composition issues. In all of these cases, applying
2433     // transparency directly to the glyph should work perfectly fine.
2434     //
2435     // Note that we must still emit completely transparent emoji, because they
2436     // might be wrapped in a shadow that uses the text run's glyphs.
2437     alpha = defaultColor.a;
2438   }
2439 
2440   for (uint32_t layerIndex = 0; layerIndex < layerGlyphs.Length();
2441        layerIndex++) {
2442     Glyph glyph;
2443     glyph.mIndex = layerGlyphs[layerIndex];
2444     glyph.mPosition = aPoint;
2445 
2446     mozilla::gfx::GlyphBuffer buffer;
2447     buffer.mGlyphs = &glyph;
2448     buffer.mNumGlyphs = 1;
2449 
2450     mozilla::gfx::DeviceColor layerColor = layerColors[layerIndex];
2451     layerColor.a *= alpha;
2452     aDrawTarget->FillGlyphs(scaledFont, buffer, ColorPattern(layerColor),
2453                             aDrawOptions);
2454   }
2455   return true;
2456 }
2457 
HasColorGlyphFor(uint32_t aCh,uint32_t aNextCh)2458 bool gfxFont::HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh) {
2459   // Bitmap fonts are assumed to provide "color" glyphs for all supported chars.
2460   gfxFontEntry* fe = GetFontEntry();
2461   if (fe->HasColorBitmapTable()) {
2462     return true;
2463   }
2464   // Use harfbuzz shaper to look up the default glyph ID for the character.
2465   if (!mHarfBuzzShaper) {
2466     mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
2467   }
2468   auto* shaper = static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get());
2469   if (!shaper->Initialize()) {
2470     return false;
2471   }
2472   uint32_t gid = 0;
2473   if (gfxFontUtils::IsVarSelector(aNextCh)) {
2474     gid = shaper->GetVariationGlyph(aCh, aNextCh);
2475   }
2476   if (!gid) {
2477     gid = shaper->GetNominalGlyph(aCh);
2478   }
2479   if (!gid) {
2480     return false;
2481   }
2482   // Check if there is a COLR/CPAL or SVG glyph for this ID.
2483   if (fe->TryGetColorGlyphs() && fe->HasColorLayersForGlyph(gid)) {
2484     return true;
2485   }
2486   if (fe->TryGetSVGData(this) && fe->HasSVGGlyph(gid)) {
2487     return true;
2488   }
2489   return false;
2490 }
2491 
UnionRange(gfxFloat aX,gfxFloat * aDestMin,gfxFloat * aDestMax)2492 static void UnionRange(gfxFloat aX, gfxFloat* aDestMin, gfxFloat* aDestMax) {
2493   *aDestMin = std::min(*aDestMin, aX);
2494   *aDestMax = std::max(*aDestMax, aX);
2495 }
2496 
2497 // We get precise glyph extents if the textrun creator requested them, or
2498 // if the font is a user font --- in which case the author may be relying
2499 // on overflowing glyphs.
NeedsGlyphExtents(gfxFont * aFont,const gfxTextRun * aTextRun)2500 static bool NeedsGlyphExtents(gfxFont* aFont, const gfxTextRun* aTextRun) {
2501   return (aTextRun->GetFlags() &
2502           gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX) ||
2503          aFont->GetFontEntry()->IsUserFont();
2504 }
2505 
IsSpaceGlyphInvisible(DrawTarget * aRefDrawTarget,const gfxTextRun * aTextRun)2506 bool gfxFont::IsSpaceGlyphInvisible(DrawTarget* aRefDrawTarget,
2507                                     const gfxTextRun* aTextRun) {
2508   if (!mFontEntry->mSpaceGlyphIsInvisibleInitialized &&
2509       GetAdjustedSize() >= 1.0) {
2510     gfxGlyphExtents* extents =
2511         GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit());
2512     gfxRect glyphExtents;
2513     mFontEntry->mSpaceGlyphIsInvisible =
2514         extents->GetTightGlyphExtentsAppUnits(this, aRefDrawTarget,
2515                                               GetSpaceGlyph(), &glyphExtents) &&
2516         glyphExtents.IsEmpty();
2517     mFontEntry->mSpaceGlyphIsInvisibleInitialized = true;
2518   }
2519   return mFontEntry->mSpaceGlyphIsInvisible;
2520 }
2521 
Measure(const gfxTextRun * aTextRun,uint32_t aStart,uint32_t aEnd,BoundingBoxType aBoundingBoxType,DrawTarget * aRefDrawTarget,Spacing * aSpacing,gfx::ShapedTextFlags aOrientation)2522 gfxFont::RunMetrics gfxFont::Measure(const gfxTextRun* aTextRun,
2523                                      uint32_t aStart, uint32_t aEnd,
2524                                      BoundingBoxType aBoundingBoxType,
2525                                      DrawTarget* aRefDrawTarget,
2526                                      Spacing* aSpacing,
2527                                      gfx::ShapedTextFlags aOrientation) {
2528   // If aBoundingBoxType is TIGHT_HINTED_OUTLINE_EXTENTS
2529   // and the underlying cairo font may be antialiased,
2530   // we need to create a copy in order to avoid getting cached extents.
2531   // This is only used by MathML layout at present.
2532   if (aBoundingBoxType == TIGHT_HINTED_OUTLINE_EXTENTS &&
2533       mAntialiasOption != kAntialiasNone) {
2534     if (!mNonAAFont) {
2535       mNonAAFont = CopyWithAntialiasOption(kAntialiasNone);
2536     }
2537     // if font subclass doesn't implement CopyWithAntialiasOption(),
2538     // it will return null and we'll proceed to use the existing font
2539     if (mNonAAFont) {
2540       return mNonAAFont->Measure(aTextRun, aStart, aEnd,
2541                                  TIGHT_HINTED_OUTLINE_EXTENTS, aRefDrawTarget,
2542                                  aSpacing, aOrientation);
2543     }
2544   }
2545 
2546   const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
2547   // Current position in appunits
2548   Orientation orientation =
2549       aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
2550           ? nsFontMetrics::eVertical
2551           : nsFontMetrics::eHorizontal;
2552   const gfxFont::Metrics& fontMetrics = GetMetrics(orientation);
2553 
2554   gfxFloat baselineOffset = 0;
2555   if (aTextRun->UseCenterBaseline() &&
2556       orientation == nsFontMetrics::eHorizontal) {
2557     // For a horizontal font being used in vertical writing mode with
2558     // text-orientation:mixed, the overall metrics we're accumulating
2559     // will be aimed at a center baseline. But this font's metrics were
2560     // based on the alphabetic baseline. So we compute a baseline offset
2561     // that will be applied to ascent/descent values and glyph rects
2562     // to effectively shift them relative to the baseline.
2563     // XXX Eventually we should probably use the BASE table, if present.
2564     // But it usually isn't, so we need an ad hoc adjustment for now.
2565     baselineOffset =
2566         appUnitsPerDevUnit * (fontMetrics.emAscent - fontMetrics.emDescent) / 2;
2567   }
2568 
2569   RunMetrics metrics;
2570   metrics.mAscent = fontMetrics.maxAscent * appUnitsPerDevUnit;
2571   metrics.mDescent = fontMetrics.maxDescent * appUnitsPerDevUnit;
2572 
2573   if (aStart == aEnd) {
2574     // exit now before we look at aSpacing[0], which is undefined
2575     metrics.mAscent -= baselineOffset;
2576     metrics.mDescent += baselineOffset;
2577     metrics.mBoundingBox =
2578         gfxRect(0, -metrics.mAscent, 0, metrics.mAscent + metrics.mDescent);
2579     return metrics;
2580   }
2581 
2582   gfxFloat advanceMin = 0, advanceMax = 0;
2583   const gfxTextRun::CompressedGlyph* charGlyphs =
2584       aTextRun->GetCharacterGlyphs();
2585   bool isRTL = aTextRun->IsRightToLeft();
2586   bool needsGlyphExtents = NeedsGlyphExtents(this, aTextRun);
2587   gfxGlyphExtents* extents =
2588       ((aBoundingBoxType == LOOSE_INK_EXTENTS && !needsGlyphExtents &&
2589         !aTextRun->HasDetailedGlyphs()) ||
2590        MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero()))
2591           ? nullptr
2592           : GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit());
2593   double x = 0;
2594   if (aSpacing) {
2595     x += aSpacing[0].mBefore;
2596   }
2597   uint32_t spaceGlyph = GetSpaceGlyph();
2598   bool allGlyphsInvisible = true;
2599   uint32_t i;
2600   for (i = aStart; i < aEnd; ++i) {
2601     const gfxTextRun::CompressedGlyph* glyphData = &charGlyphs[i];
2602     if (glyphData->IsSimpleGlyph()) {
2603       double advance = glyphData->GetSimpleAdvance();
2604       uint32_t glyphIndex = glyphData->GetSimpleGlyph();
2605       if (glyphIndex != spaceGlyph ||
2606           !IsSpaceGlyphInvisible(aRefDrawTarget, aTextRun)) {
2607         allGlyphsInvisible = false;
2608       }
2609       // Only get the real glyph horizontal extent if we were asked
2610       // for the tight bounding box or we're in quality mode
2611       if ((aBoundingBoxType != LOOSE_INK_EXTENTS || needsGlyphExtents) &&
2612           extents) {
2613         uint16_t extentsWidth =
2614             extents->GetContainedGlyphWidthAppUnits(glyphIndex);
2615         if (extentsWidth != gfxGlyphExtents::INVALID_WIDTH &&
2616             aBoundingBoxType == LOOSE_INK_EXTENTS) {
2617           UnionRange(x, &advanceMin, &advanceMax);
2618           UnionRange(x + extentsWidth, &advanceMin, &advanceMax);
2619         } else {
2620           gfxRect glyphRect;
2621           if (!extents->GetTightGlyphExtentsAppUnits(this, aRefDrawTarget,
2622                                                      glyphIndex, &glyphRect)) {
2623             glyphRect = gfxRect(0, metrics.mBoundingBox.Y(), advance,
2624                                 metrics.mBoundingBox.Height());
2625           }
2626           if (isRTL) {
2627             // In effect, swap left and right sidebearings of the glyph, for
2628             // proper accumulation of potentially-overlapping glyph rects.
2629             glyphRect.MoveToX(advance - glyphRect.XMost());
2630           }
2631           glyphRect.MoveByX(x);
2632           metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect);
2633         }
2634       }
2635       x += advance;
2636     } else {
2637       allGlyphsInvisible = false;
2638       uint32_t glyphCount = glyphData->GetGlyphCount();
2639       if (glyphCount > 0) {
2640         const gfxTextRun::DetailedGlyph* details =
2641             aTextRun->GetDetailedGlyphs(i);
2642         NS_ASSERTION(details != nullptr,
2643                      "detailedGlyph record should not be missing!");
2644         uint32_t j;
2645         for (j = 0; j < glyphCount; ++j, ++details) {
2646           uint32_t glyphIndex = details->mGlyphID;
2647           double advance = details->mAdvance;
2648           gfxRect glyphRect;
2649           if (glyphData->IsMissing() || !extents ||
2650               !extents->GetTightGlyphExtentsAppUnits(this, aRefDrawTarget,
2651                                                      glyphIndex, &glyphRect)) {
2652             // We might have failed to get glyph extents due to
2653             // OOM or something
2654             glyphRect = gfxRect(0, -metrics.mAscent, advance,
2655                                 metrics.mAscent + metrics.mDescent);
2656           }
2657           if (isRTL) {
2658             // Swap left/right sidebearings of the glyph, because we're doing
2659             // mirrored measurement.
2660             glyphRect.MoveToX(advance - glyphRect.XMost());
2661             // Move to current x position, mirroring any x-offset amount.
2662             glyphRect.MoveByX(x - details->mOffset.x);
2663           } else {
2664             glyphRect.MoveByX(x + details->mOffset.x);
2665           }
2666           glyphRect.MoveByY(details->mOffset.y);
2667           metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect);
2668           x += advance;
2669         }
2670       }
2671     }
2672     // Every other glyph type is ignored
2673     if (aSpacing) {
2674       double space = aSpacing[i - aStart].mAfter;
2675       if (i + 1 < aEnd) {
2676         space += aSpacing[i + 1 - aStart].mBefore;
2677       }
2678       x += space;
2679     }
2680   }
2681 
2682   if (allGlyphsInvisible) {
2683     metrics.mBoundingBox.SetEmpty();
2684   } else {
2685     if (aBoundingBoxType == LOOSE_INK_EXTENTS) {
2686       UnionRange(x, &advanceMin, &advanceMax);
2687       gfxRect fontBox(advanceMin, -metrics.mAscent, advanceMax - advanceMin,
2688                       metrics.mAscent + metrics.mDescent);
2689       metrics.mBoundingBox = metrics.mBoundingBox.Union(fontBox);
2690     }
2691   }
2692 
2693   if (isRTL) {
2694     // Reverse the effect of having swapped each glyph's sidebearings, to get
2695     // the correct sidebearings of the merged bounding box.
2696     metrics.mBoundingBox.MoveToX(x - metrics.mBoundingBox.XMost());
2697   }
2698 
2699   // If the font may be rendered with a fake-italic effect, we need to allow
2700   // for the top-right of the glyphs being skewed to the right, and the
2701   // bottom-left being skewed further left.
2702   gfxFloat skew = SkewForSyntheticOblique();
2703   if (skew != 0.0) {
2704     gfxFloat extendLeftEdge, extendRightEdge;
2705     if (orientation == nsFontMetrics::eVertical) {
2706       // The glyph will actually be skewed vertically, but "left" and "right"
2707       // here refer to line-left (physical top) and -right (bottom), so these
2708       // are still the directions in which we need to extend the box.
2709       extendLeftEdge = skew < 0.0 ? ceil(-skew * metrics.mBoundingBox.XMost())
2710                                   : ceil(skew * -metrics.mBoundingBox.X());
2711       extendRightEdge = skew < 0.0 ? ceil(-skew * -metrics.mBoundingBox.X())
2712                                    : ceil(skew * metrics.mBoundingBox.XMost());
2713     } else {
2714       extendLeftEdge = skew < 0.0 ? ceil(-skew * -metrics.mBoundingBox.Y())
2715                                   : ceil(skew * metrics.mBoundingBox.YMost());
2716       extendRightEdge = skew < 0.0 ? ceil(-skew * metrics.mBoundingBox.YMost())
2717                                    : ceil(skew * -metrics.mBoundingBox.Y());
2718     }
2719     metrics.mBoundingBox.SetWidth(metrics.mBoundingBox.Width() +
2720                                   extendLeftEdge + extendRightEdge);
2721     metrics.mBoundingBox.MoveByX(-extendLeftEdge);
2722   }
2723 
2724   if (baselineOffset != 0) {
2725     metrics.mAscent -= baselineOffset;
2726     metrics.mDescent += baselineOffset;
2727     metrics.mBoundingBox.MoveByY(baselineOffset);
2728   }
2729 
2730   metrics.mAdvanceWidth = x;
2731 
2732   return metrics;
2733 }
2734 
AgeCachedWords()2735 bool gfxFont::AgeCachedWords() {
2736   if (mWordCache) {
2737     for (auto it = mWordCache->Iter(); !it.Done(); it.Next()) {
2738       CacheHashEntry* entry = it.Get();
2739       if (!entry->mShapedWord) {
2740         NS_ASSERTION(entry->mShapedWord, "cache entry has no gfxShapedWord!");
2741         it.Remove();
2742       } else if (entry->mShapedWord->IncrementAge() == kShapedWordCacheMaxAge) {
2743         it.Remove();
2744       }
2745     }
2746     return mWordCache->IsEmpty();
2747   }
2748   return true;
2749 }
2750 
NotifyGlyphsChanged()2751 void gfxFont::NotifyGlyphsChanged() {
2752   uint32_t i, count = mGlyphExtentsArray.Length();
2753   for (i = 0; i < count; ++i) {
2754     // Flush cached extents array
2755     mGlyphExtentsArray[i]->NotifyGlyphsChanged();
2756   }
2757 
2758   if (mGlyphChangeObservers) {
2759     for (const auto& key : *mGlyphChangeObservers) {
2760       key->NotifyGlyphsChanged();
2761     }
2762   }
2763 }
2764 
2765 // If aChar is a "word boundary" for shaped-word caching purposes, return it;
2766 // else return 0.
IsBoundarySpace(char16_t aChar,char16_t aNextChar)2767 static char16_t IsBoundarySpace(char16_t aChar, char16_t aNextChar) {
2768   if ((aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar)) {
2769     return aChar;
2770   }
2771   return 0;
2772 }
2773 
2774 #ifdef __GNUC__
2775 #  define GFX_MAYBE_UNUSED __attribute__((unused))
2776 #else
2777 #  define GFX_MAYBE_UNUSED
2778 #endif
2779 
2780 template <typename T>
GetShapedWord(DrawTarget * aDrawTarget,const T * aText,uint32_t aLength,uint32_t aHash,Script aRunScript,nsAtom * aLanguage,bool aVertical,int32_t aAppUnitsPerDevUnit,gfx::ShapedTextFlags aFlags,RoundingFlags aRounding,gfxTextPerfMetrics * aTextPerf GFX_MAYBE_UNUSED)2781 gfxShapedWord* gfxFont::GetShapedWord(
2782     DrawTarget* aDrawTarget, const T* aText, uint32_t aLength, uint32_t aHash,
2783     Script aRunScript, nsAtom* aLanguage, bool aVertical,
2784     int32_t aAppUnitsPerDevUnit, gfx::ShapedTextFlags aFlags,
2785     RoundingFlags aRounding, gfxTextPerfMetrics* aTextPerf GFX_MAYBE_UNUSED) {
2786   // if the cache is getting too big, flush it and start over
2787   uint32_t wordCacheMaxEntries =
2788       gfxPlatform::GetPlatform()->WordCacheMaxEntries();
2789   if (mWordCache->Count() > wordCacheMaxEntries) {
2790     NS_WARNING("flushing shaped-word cache");
2791     ClearCachedWords();
2792   }
2793 
2794   // if there's a cached entry for this word, just return it
2795   CacheHashKey key(aText, aLength, aHash, aRunScript, aLanguage,
2796                    aAppUnitsPerDevUnit, aFlags, aRounding);
2797 
2798   CacheHashEntry* entry = mWordCache->PutEntry(key, fallible);
2799   if (!entry) {
2800     NS_WARNING("failed to create word cache entry - expect missing text");
2801     return nullptr;
2802   }
2803   gfxShapedWord* sw = entry->mShapedWord.get();
2804 
2805   if (sw) {
2806     sw->ResetAge();
2807 #ifndef RELEASE_OR_BETA
2808     if (aTextPerf) {
2809       aTextPerf->current.wordCacheHit++;
2810     }
2811 #endif
2812     return sw;
2813   }
2814 
2815 #ifndef RELEASE_OR_BETA
2816   if (aTextPerf) {
2817     aTextPerf->current.wordCacheMiss++;
2818   }
2819 #endif
2820 
2821   sw = gfxShapedWord::Create(aText, aLength, aRunScript, aLanguage,
2822                              aAppUnitsPerDevUnit, aFlags, aRounding);
2823   entry->mShapedWord.reset(sw);
2824   if (!sw) {
2825     NS_WARNING("failed to create gfxShapedWord - expect missing text");
2826     return nullptr;
2827   }
2828 
2829   DebugOnly<bool> ok = ShapeText(aDrawTarget, aText, 0, aLength, aRunScript,
2830                                  aLanguage, aVertical, aRounding, sw);
2831 
2832   NS_WARNING_ASSERTION(ok, "failed to shape word - expect garbled text");
2833 
2834   gfxFontCache::GetCache()->RunWordCacheExpirationTimer();
2835 
2836   return sw;
2837 }
2838 
2839 template gfxShapedWord* gfxFont::GetShapedWord(
2840     DrawTarget* aDrawTarget, const uint8_t* aText, uint32_t aLength,
2841     uint32_t aHash, Script aRunScript, nsAtom* aLanguage, bool aVertical,
2842     int32_t aAppUnitsPerDevUnit, gfx::ShapedTextFlags aFlags,
2843     RoundingFlags aRounding, gfxTextPerfMetrics* aTextPerf);
2844 
KeyEquals(const KeyTypePointer aKey) const2845 bool gfxFont::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const {
2846   const gfxShapedWord* sw = mShapedWord.get();
2847   if (!sw) {
2848     return false;
2849   }
2850   if (sw->GetLength() != aKey->mLength || sw->GetFlags() != aKey->mFlags ||
2851       sw->GetRounding() != aKey->mRounding ||
2852       sw->GetAppUnitsPerDevUnit() != aKey->mAppUnitsPerDevUnit ||
2853       sw->GetScript() != aKey->mScript ||
2854       sw->GetLanguage() != aKey->mLanguage) {
2855     return false;
2856   }
2857   if (sw->TextIs8Bit()) {
2858     if (aKey->mTextIs8Bit) {
2859       return (0 == memcmp(sw->Text8Bit(), aKey->mText.mSingle,
2860                           aKey->mLength * sizeof(uint8_t)));
2861     }
2862     // The key has 16-bit text, even though all the characters are < 256,
2863     // so the TEXT_IS_8BIT flag was set and the cached ShapedWord we're
2864     // comparing with will have 8-bit text.
2865     const uint8_t* s1 = sw->Text8Bit();
2866     const char16_t* s2 = aKey->mText.mDouble;
2867     const char16_t* s2end = s2 + aKey->mLength;
2868     while (s2 < s2end) {
2869       if (*s1++ != *s2++) {
2870         return false;
2871       }
2872     }
2873     return true;
2874   }
2875   NS_ASSERTION(!(aKey->mFlags & gfx::ShapedTextFlags::TEXT_IS_8BIT) &&
2876                    !aKey->mTextIs8Bit,
2877                "didn't expect 8-bit text here");
2878   return (0 == memcmp(sw->TextUnicode(), aKey->mText.mDouble,
2879                       aKey->mLength * sizeof(char16_t)));
2880 }
2881 
ShapeText(DrawTarget * aDrawTarget,const uint8_t * aText,uint32_t aOffset,uint32_t aLength,Script aScript,nsAtom * aLanguage,bool aVertical,RoundingFlags aRounding,gfxShapedText * aShapedText)2882 bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const uint8_t* aText,
2883                         uint32_t aOffset, uint32_t aLength, Script aScript,
2884                         nsAtom* aLanguage, bool aVertical,
2885                         RoundingFlags aRounding, gfxShapedText* aShapedText) {
2886   nsDependentCSubstring ascii((const char*)aText, aLength);
2887   nsAutoString utf16;
2888   AppendASCIItoUTF16(ascii, utf16);
2889   if (utf16.Length() != aLength) {
2890     return false;
2891   }
2892   return ShapeText(aDrawTarget, utf16.BeginReading(), aOffset, aLength, aScript,
2893                    aLanguage, aVertical, aRounding, aShapedText);
2894 }
2895 
ShapeText(DrawTarget * aDrawTarget,const char16_t * aText,uint32_t aOffset,uint32_t aLength,Script aScript,nsAtom * aLanguage,bool aVertical,RoundingFlags aRounding,gfxShapedText * aShapedText)2896 bool gfxFont::ShapeText(DrawTarget* aDrawTarget, const char16_t* aText,
2897                         uint32_t aOffset, uint32_t aLength, Script aScript,
2898                         nsAtom* aLanguage, bool aVertical,
2899                         RoundingFlags aRounding, gfxShapedText* aShapedText) {
2900   // XXX Currently, we do all vertical shaping through harfbuzz.
2901   // Vertical graphite support may be wanted as a future enhancement.
2902   if (FontCanSupportGraphite() && !aVertical) {
2903     if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
2904       if (!mGraphiteShaper) {
2905         mGraphiteShaper = MakeUnique<gfxGraphiteShaper>(this);
2906         Telemetry::ScalarAdd(Telemetry::ScalarID::BROWSER_USAGE_GRAPHITE, 1);
2907       }
2908       if (mGraphiteShaper->ShapeText(aDrawTarget, aText, aOffset, aLength,
2909                                      aScript, aLanguage, aVertical, aRounding,
2910                                      aShapedText)) {
2911         PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical,
2912                          aShapedText);
2913         return true;
2914       }
2915     }
2916   }
2917 
2918   if (!mHarfBuzzShaper) {
2919     mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
2920   }
2921   if (mHarfBuzzShaper->ShapeText(aDrawTarget, aText, aOffset, aLength, aScript,
2922                                  aLanguage, aVertical, aRounding,
2923                                  aShapedText)) {
2924     PostShapingFixup(aDrawTarget, aText, aOffset, aLength, aVertical,
2925                      aShapedText);
2926     if (GetFontEntry()->HasTrackingTable()) {
2927       // Convert font size from device pixels back to CSS px
2928       // to use in selecting tracking value
2929       float trackSize = GetAdjustedSize() *
2930                         aShapedText->GetAppUnitsPerDevUnit() /
2931                         AppUnitsPerCSSPixel();
2932       float tracking =
2933           GetFontEntry()->TrackingForCSSPx(trackSize) * mFUnitsConvFactor;
2934       // Applying tracking is a lot like the adjustment we do for
2935       // synthetic bold: we want to apply between clusters, not to
2936       // non-spacing glyphs within a cluster. So we can reuse that
2937       // helper here.
2938       aShapedText->AdjustAdvancesForSyntheticBold(tracking, aOffset, aLength);
2939     }
2940     return true;
2941   }
2942 
2943   NS_WARNING_ASSERTION(false, "shaper failed, expect scrambled/missing text");
2944   return false;
2945 }
2946 
PostShapingFixup(DrawTarget * aDrawTarget,const char16_t * aText,uint32_t aOffset,uint32_t aLength,bool aVertical,gfxShapedText * aShapedText)2947 void gfxFont::PostShapingFixup(DrawTarget* aDrawTarget, const char16_t* aText,
2948                                uint32_t aOffset, uint32_t aLength,
2949                                bool aVertical, gfxShapedText* aShapedText) {
2950   if (ApplySyntheticBold()) {
2951     const Metrics& metrics = GetMetrics(aVertical ? nsFontMetrics::eVertical
2952                                                   : nsFontMetrics::eHorizontal);
2953     if (metrics.maxAdvance > metrics.aveCharWidth) {
2954       float synBoldOffset = GetSyntheticBoldOffset() * CalcXScale(aDrawTarget);
2955       aShapedText->AdjustAdvancesForSyntheticBold(synBoldOffset, aOffset,
2956                                                   aLength);
2957     }
2958   }
2959 }
2960 
2961 #define MAX_SHAPING_LENGTH \
2962   32760  // slightly less than 32K, trying to avoid
2963          // over-stressing platform shapers
2964 #define BACKTRACK_LIMIT \
2965   16  // backtrack this far looking for a good place
2966       // to split into fragments for separate shaping
2967 
2968 template <typename T>
ShapeFragmentWithoutWordCache(DrawTarget * aDrawTarget,const T * aText,uint32_t aOffset,uint32_t aLength,Script aScript,nsAtom * aLanguage,bool aVertical,RoundingFlags aRounding,gfxTextRun * aTextRun)2969 bool gfxFont::ShapeFragmentWithoutWordCache(DrawTarget* aDrawTarget,
2970                                             const T* aText, uint32_t aOffset,
2971                                             uint32_t aLength, Script aScript,
2972                                             nsAtom* aLanguage, bool aVertical,
2973                                             RoundingFlags aRounding,
2974                                             gfxTextRun* aTextRun) {
2975   aTextRun->SetupClusterBoundaries(aOffset, aText, aLength);
2976 
2977   bool ok = true;
2978 
2979   while (ok && aLength > 0) {
2980     uint32_t fragLen = aLength;
2981 
2982     // limit the length of text we pass to shapers in a single call
2983     if (fragLen > MAX_SHAPING_LENGTH) {
2984       fragLen = MAX_SHAPING_LENGTH;
2985 
2986       // in the 8-bit case, there are no multi-char clusters,
2987       // so we don't need to do this check
2988       if constexpr (sizeof(T) == sizeof(char16_t)) {
2989         uint32_t i;
2990         for (i = 0; i < BACKTRACK_LIMIT; ++i) {
2991           if (aTextRun->IsClusterStart(aOffset + fragLen - i)) {
2992             fragLen -= i;
2993             break;
2994           }
2995         }
2996         if (i == BACKTRACK_LIMIT) {
2997           // if we didn't find any cluster start while backtracking,
2998           // just check that we're not in the middle of a surrogate
2999           // pair; back up by one code unit if we are.
3000           if (NS_IS_SURROGATE_PAIR(aText[fragLen - 1], aText[fragLen])) {
3001             --fragLen;
3002           }
3003         }
3004       }
3005     }
3006 
3007     ok = ShapeText(aDrawTarget, aText, aOffset, fragLen, aScript, aLanguage,
3008                    aVertical, aRounding, aTextRun);
3009 
3010     aText += fragLen;
3011     aOffset += fragLen;
3012     aLength -= fragLen;
3013   }
3014 
3015   return ok;
3016 }
3017 
3018 // Check if aCh is an unhandled control character that should be displayed
3019 // as a hexbox rather than rendered by some random font on the system.
3020 // We exclude \r as stray &#13;s are rather common (bug 941940).
3021 // Note that \n and \t don't come through here, as they have specific
3022 // meanings that have already been handled.
IsInvalidControlChar(uint32_t aCh)3023 static bool IsInvalidControlChar(uint32_t aCh) {
3024   return aCh != '\r' && ((aCh & 0x7f) < 0x20 || aCh == 0x7f);
3025 }
3026 
3027 template <typename T>
ShapeTextWithoutWordCache(DrawTarget * aDrawTarget,const T * aText,uint32_t aOffset,uint32_t aLength,Script aScript,nsAtom * aLanguage,bool aVertical,RoundingFlags aRounding,gfxTextRun * aTextRun)3028 bool gfxFont::ShapeTextWithoutWordCache(DrawTarget* aDrawTarget, const T* aText,
3029                                         uint32_t aOffset, uint32_t aLength,
3030                                         Script aScript, nsAtom* aLanguage,
3031                                         bool aVertical, RoundingFlags aRounding,
3032                                         gfxTextRun* aTextRun) {
3033   uint32_t fragStart = 0;
3034   bool ok = true;
3035 
3036   for (uint32_t i = 0; i <= aLength && ok; ++i) {
3037     T ch = (i < aLength) ? aText[i] : '\n';
3038     bool invalid = gfxFontGroup::IsInvalidChar(ch);
3039     uint32_t length = i - fragStart;
3040 
3041     // break into separate fragments when we hit an invalid char
3042     if (!invalid) {
3043       continue;
3044     }
3045 
3046     if (length > 0) {
3047       ok = ShapeFragmentWithoutWordCache(
3048           aDrawTarget, aText + fragStart, aOffset + fragStart, length, aScript,
3049           aLanguage, aVertical, aRounding, aTextRun);
3050     }
3051 
3052     if (i == aLength) {
3053       break;
3054     }
3055 
3056     // fragment was terminated by an invalid char: skip it,
3057     // unless it's a control char that we want to show as a hexbox,
3058     // but record where TAB or NEWLINE occur
3059     if (ch == '\t') {
3060       aTextRun->SetIsTab(aOffset + i);
3061     } else if (ch == '\n') {
3062       aTextRun->SetIsNewline(aOffset + i);
3063     } else if (GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_FORMAT) {
3064       aTextRun->SetIsFormattingControl(aOffset + i);
3065     } else if (IsInvalidControlChar(ch) &&
3066                !(aTextRun->GetFlags() &
3067                  gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS)) {
3068       if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) {
3069         ShapeFragmentWithoutWordCache(aDrawTarget, aText + i, aOffset + i, 1,
3070                                       aScript, aLanguage, aVertical, aRounding,
3071                                       aTextRun);
3072       } else {
3073         aTextRun->SetMissingGlyph(aOffset + i, ch, this);
3074       }
3075     }
3076     fragStart = i + 1;
3077   }
3078 
3079   NS_WARNING_ASSERTION(ok, "failed to shape text - expect garbled text");
3080   return ok;
3081 }
3082 
3083 #ifndef RELEASE_OR_BETA
3084 #  define TEXT_PERF_INCR(tp, m) (tp ? (tp)->current.m++ : 0)
3085 #else
3086 #  define TEXT_PERF_INCR(tp, m)
3087 #endif
3088 
IsChar8Bit(uint8_t)3089 inline static bool IsChar8Bit(uint8_t /*aCh*/) { return true; }
IsChar8Bit(char16_t aCh)3090 inline static bool IsChar8Bit(char16_t aCh) { return aCh < 0x100; }
3091 
HasSpaces(const uint8_t * aString,uint32_t aLen)3092 inline static bool HasSpaces(const uint8_t* aString, uint32_t aLen) {
3093   return memchr(aString, 0x20, aLen) != nullptr;
3094 }
3095 
HasSpaces(const char16_t * aString,uint32_t aLen)3096 inline static bool HasSpaces(const char16_t* aString, uint32_t aLen) {
3097   for (const char16_t* ch = aString; ch < aString + aLen; ch++) {
3098     if (*ch == 0x20) {
3099       return true;
3100     }
3101   }
3102   return false;
3103 }
3104 
3105 template <typename T>
SplitAndInitTextRun(DrawTarget * aDrawTarget,gfxTextRun * aTextRun,const T * aString,uint32_t aRunStart,uint32_t aRunLength,Script aRunScript,nsAtom * aLanguage,ShapedTextFlags aOrientation)3106 bool gfxFont::SplitAndInitTextRun(
3107     DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
3108     const T* aString,    // text for this font run
3109     uint32_t aRunStart,  // position in the textrun
3110     uint32_t aRunLength, Script aRunScript, nsAtom* aLanguage,
3111     ShapedTextFlags aOrientation) {
3112   if (aRunLength == 0) {
3113     return true;
3114   }
3115 
3116   gfxTextPerfMetrics* tp = nullptr;
3117   RoundingFlags rounding = GetRoundOffsetsToPixels(aDrawTarget);
3118 
3119 #ifndef RELEASE_OR_BETA
3120   tp = aTextRun->GetFontGroup()->GetTextPerfMetrics();
3121   if (tp) {
3122     if (mStyle.systemFont) {
3123       tp->current.numChromeTextRuns++;
3124     } else {
3125       tp->current.numContentTextRuns++;
3126     }
3127     tp->current.numChars += aRunLength;
3128     if (aRunLength > tp->current.maxTextRunLen) {
3129       tp->current.maxTextRunLen = aRunLength;
3130     }
3131   }
3132 #endif
3133 
3134   uint32_t wordCacheCharLimit =
3135       gfxPlatform::GetPlatform()->WordCacheCharLimit();
3136 
3137   bool vertical = aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
3138 
3139   // If spaces can participate in shaping (e.g. within lookups for automatic
3140   // fractions), need to shape without using the word cache which segments
3141   // textruns on space boundaries. Word cache can be used if the textrun
3142   // is short enough to fit in the word cache and it lacks spaces.
3143   tainted_boolean_hint t_canParticipate =
3144       SpaceMayParticipateInShaping(aRunScript);
3145   bool canParticipate = t_canParticipate.unverified_safe_because(
3146       "We need to ensure that this function operates safely independent of "
3147       "t_canParticipate. The worst that can happen here is that the decision "
3148       "to use the cache is incorrectly made, resulting in a bad "
3149       "rendering/slowness. However, this  would not compromise the memory "
3150       "safety of Firefox in any way, and can thus be permitted");
3151 
3152   if (canParticipate) {
3153     if (aRunLength > wordCacheCharLimit || HasSpaces(aString, aRunLength)) {
3154       TEXT_PERF_INCR(tp, wordCacheSpaceRules);
3155       return ShapeTextWithoutWordCache(aDrawTarget, aString, aRunStart,
3156                                        aRunLength, aRunScript, aLanguage,
3157                                        vertical, rounding, aTextRun);
3158     }
3159   }
3160 
3161   InitWordCache();
3162 
3163   // the only flags we care about for ShapedWord construction/caching
3164   gfx::ShapedTextFlags flags = aTextRun->GetFlags();
3165   flags &= (gfx::ShapedTextFlags::TEXT_IS_RTL |
3166             gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES |
3167             gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT |
3168             gfx::ShapedTextFlags::TEXT_ORIENT_MASK);
3169   if constexpr (sizeof(T) == sizeof(uint8_t)) {
3170     flags |= gfx::ShapedTextFlags::TEXT_IS_8BIT;
3171   }
3172 
3173   uint32_t wordStart = 0;
3174   uint32_t hash = 0;
3175   bool wordIs8Bit = true;
3176   int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
3177 
3178   T nextCh = aString[0];
3179   for (uint32_t i = 0; i <= aRunLength; ++i) {
3180     T ch = nextCh;
3181     nextCh = (i < aRunLength - 1) ? aString[i + 1] : '\n';
3182     T boundary = IsBoundarySpace(ch, nextCh);
3183     bool invalid = !boundary && gfxFontGroup::IsInvalidChar(ch);
3184     uint32_t length = i - wordStart;
3185 
3186     // break into separate ShapedWords when we hit an invalid char,
3187     // or a boundary space (always handled individually),
3188     // or the first non-space after a space
3189     if (!boundary && !invalid) {
3190       if (!IsChar8Bit(ch)) {
3191         wordIs8Bit = false;
3192       }
3193       // include this character in the hash, and move on to next
3194       hash = gfxShapedWord::HashMix(hash, ch);
3195       continue;
3196     }
3197 
3198     // We've decided to break here (i.e. we're at the end of a "word");
3199     // shape the word and add it to the textrun.
3200     // For words longer than the limit, we don't use the
3201     // font's word cache but just shape directly into the textrun.
3202     if (length > wordCacheCharLimit) {
3203       TEXT_PERF_INCR(tp, wordCacheLong);
3204       bool ok = ShapeFragmentWithoutWordCache(
3205           aDrawTarget, aString + wordStart, aRunStart + wordStart, length,
3206           aRunScript, aLanguage, vertical, rounding, aTextRun);
3207       if (!ok) {
3208         return false;
3209       }
3210     } else if (length > 0) {
3211       gfx::ShapedTextFlags wordFlags = flags;
3212       // in the 8-bit version of this method, TEXT_IS_8BIT was
3213       // already set as part of |flags|, so no need for a per-word
3214       // adjustment here
3215       if (sizeof(T) == sizeof(char16_t)) {
3216         if (wordIs8Bit) {
3217           wordFlags |= gfx::ShapedTextFlags::TEXT_IS_8BIT;
3218         }
3219       }
3220       gfxShapedWord* sw = GetShapedWord(
3221           aDrawTarget, aString + wordStart, length, hash, aRunScript, aLanguage,
3222           vertical, appUnitsPerDevUnit, wordFlags, rounding, tp);
3223       if (sw) {
3224         aTextRun->CopyGlyphDataFrom(sw, aRunStart + wordStart);
3225       } else {
3226         return false;  // failed, presumably out of memory?
3227       }
3228     }
3229 
3230     if (boundary) {
3231       // word was terminated by a space: add that to the textrun
3232       MOZ_ASSERT(aOrientation != ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED,
3233                  "text-orientation:mixed should be resolved earlier");
3234       if (boundary != ' ' || !aTextRun->SetSpaceGlyphIfSimple(
3235                                  this, aRunStart + i, ch, aOrientation)) {
3236         // Currently, the only "boundary" characters we recognize are
3237         // space and no-break space, which are both 8-bit, so we force
3238         // that flag (below). If we ever change IsBoundarySpace, we
3239         // may need to revise this.
3240         // Avoid tautological-constant-out-of-range-compare in 8-bit:
3241         DebugOnly<char16_t> boundary16 = boundary;
3242         NS_ASSERTION(boundary16 < 256, "unexpected boundary!");
3243         gfxShapedWord* sw = GetShapedWord(
3244             aDrawTarget, &boundary, 1, gfxShapedWord::HashMix(0, boundary),
3245             aRunScript, aLanguage, vertical, appUnitsPerDevUnit,
3246             flags | gfx::ShapedTextFlags::TEXT_IS_8BIT, rounding, tp);
3247         if (sw) {
3248           aTextRun->CopyGlyphDataFrom(sw, aRunStart + i);
3249           if (boundary == ' ') {
3250             aTextRun->GetCharacterGlyphs()[aRunStart + i].SetIsSpace();
3251           }
3252         } else {
3253           return false;
3254         }
3255       }
3256       hash = 0;
3257       wordStart = i + 1;
3258       wordIs8Bit = true;
3259       continue;
3260     }
3261 
3262     if (i == aRunLength) {
3263       break;
3264     }
3265 
3266     NS_ASSERTION(invalid, "how did we get here except via an invalid char?");
3267 
3268     // word was terminated by an invalid char: skip it,
3269     // unless it's a control char that we want to show as a hexbox,
3270     // but record where TAB or NEWLINE occur
3271     if (ch == '\t') {
3272       aTextRun->SetIsTab(aRunStart + i);
3273     } else if (ch == '\n') {
3274       aTextRun->SetIsNewline(aRunStart + i);
3275     } else if (GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_FORMAT) {
3276       aTextRun->SetIsFormattingControl(aRunStart + i);
3277     } else if (IsInvalidControlChar(ch) &&
3278                !(aTextRun->GetFlags() &
3279                  gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS)) {
3280       if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) {
3281         ShapeFragmentWithoutWordCache(aDrawTarget, aString + i, aRunStart + i,
3282                                       1, aRunScript, aLanguage, vertical,
3283                                       rounding, aTextRun);
3284       } else {
3285         aTextRun->SetMissingGlyph(aRunStart + i, ch, this);
3286       }
3287     }
3288 
3289     hash = 0;
3290     wordStart = i + 1;
3291     wordIs8Bit = true;
3292   }
3293 
3294   return true;
3295 }
3296 
3297 // Explicit instantiations of SplitAndInitTextRun, to avoid libxul link failure
3298 template bool gfxFont::SplitAndInitTextRun(
3299     DrawTarget* aDrawTarget, gfxTextRun* aTextRun, const uint8_t* aString,
3300     uint32_t aRunStart, uint32_t aRunLength, Script aRunScript,
3301     nsAtom* aLanguage, ShapedTextFlags aOrientation);
3302 template bool gfxFont::SplitAndInitTextRun(
3303     DrawTarget* aDrawTarget, gfxTextRun* aTextRun, const char16_t* aString,
3304     uint32_t aRunStart, uint32_t aRunLength, Script aRunScript,
3305     nsAtom* aLanguage, ShapedTextFlags aOrientation);
3306 
3307 template <>
InitFakeSmallCapsRun(nsPresContext * aPresContext,DrawTarget * aDrawTarget,gfxTextRun * aTextRun,const char16_t * aText,uint32_t aOffset,uint32_t aLength,FontMatchType aMatchType,gfx::ShapedTextFlags aOrientation,Script aScript,nsAtom * aLanguage,bool aSyntheticLower,bool aSyntheticUpper)3308 bool gfxFont::InitFakeSmallCapsRun(
3309     nsPresContext* aPresContext, DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
3310     const char16_t* aText, uint32_t aOffset, uint32_t aLength,
3311     FontMatchType aMatchType, gfx::ShapedTextFlags aOrientation, Script aScript,
3312     nsAtom* aLanguage, bool aSyntheticLower, bool aSyntheticUpper) {
3313   bool ok = true;
3314 
3315   RefPtr<gfxFont> smallCapsFont = GetSmallCapsFont();
3316   if (!smallCapsFont) {
3317     NS_WARNING("failed to get reduced-size font for smallcaps!");
3318     smallCapsFont = this;
3319   }
3320 
3321   bool isCJK = gfxTextRun::IsCJKScript(aScript);
3322 
3323   enum RunCaseAction { kNoChange, kUppercaseReduce, kUppercase };
3324 
3325   RunCaseAction runAction = kNoChange;
3326   uint32_t runStart = 0;
3327 
3328   for (uint32_t i = 0; i <= aLength; ++i) {
3329     uint32_t extraCodeUnits = 0;  // Will be set to 1 if we need to consume
3330                                   // a trailing surrogate as well as the
3331                                   // current code unit.
3332     RunCaseAction chAction = kNoChange;
3333     // Unless we're at the end, figure out what treatment the current
3334     // character will need.
3335     if (i < aLength) {
3336       uint32_t ch = aText[i];
3337       if (i < aLength - 1 && NS_IS_SURROGATE_PAIR(ch, aText[i + 1])) {
3338         ch = SURROGATE_TO_UCS4(ch, aText[i + 1]);
3339         extraCodeUnits = 1;
3340       }
3341       // Characters that aren't the start of a cluster are ignored here.
3342       // They get added to whatever lowercase/non-lowercase run we're in.
3343       if (IsClusterExtender(ch)) {
3344         chAction = runAction;
3345       } else {
3346         if (ch != ToUpperCase(ch) || SpecialUpper(ch)) {
3347           // ch is lower case
3348           chAction = (aSyntheticLower ? kUppercaseReduce : kNoChange);
3349         } else if (ch != ToLowerCase(ch)) {
3350           // ch is upper case
3351           chAction = (aSyntheticUpper ? kUppercaseReduce : kNoChange);
3352           if (aLanguage == nsGkAtoms::el) {
3353             // In Greek, check for characters that will be modified by
3354             // the GreekUpperCase mapping - this catches accented
3355             // capitals where the accent is to be removed (bug 307039).
3356             // These are handled by using the full-size font with the
3357             // uppercasing transform.
3358             mozilla::GreekCasing::State state;
3359             bool markEta, updateEta;
3360             uint32_t ch2 =
3361                 mozilla::GreekCasing::UpperCase(ch, state, markEta, updateEta);
3362             if ((ch != ch2 || markEta) && !aSyntheticUpper) {
3363               chAction = kUppercase;
3364             }
3365           }
3366         }
3367       }
3368     }
3369 
3370     // At the end of the text or when the current character needs different
3371     // casing treatment from the current run, finish the run-in-progress
3372     // and prepare to accumulate a new run.
3373     // Note that we do not look at any source data for offset [i] here,
3374     // as that would be invalid in the case where i==length.
3375     if ((i == aLength || runAction != chAction) && runStart < i) {
3376       uint32_t runLength = i - runStart;
3377       gfxFont* f = this;
3378       switch (runAction) {
3379         case kNoChange:
3380           // just use the current font and the existing string
3381           aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true,
3382                                 aOrientation, isCJK);
3383           if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun, aText + runStart,
3384                                       aOffset + runStart, runLength, aScript,
3385                                       aLanguage, aOrientation)) {
3386             ok = false;
3387           }
3388           break;
3389 
3390         case kUppercaseReduce:
3391           // use reduced-size font, then fall through to uppercase the text
3392           f = smallCapsFont;
3393           [[fallthrough]];
3394 
3395         case kUppercase:
3396           // apply uppercase transform to the string
3397           nsDependentSubstring origString(aText + runStart, runLength);
3398           nsAutoString convertedString;
3399           AutoTArray<bool, 50> charsToMergeArray;
3400           AutoTArray<bool, 50> deletedCharsArray;
3401 
3402           StyleTextTransform globalTransform{StyleTextTransformCase::Uppercase,
3403                                              {}};
3404           bool mergeNeeded = nsCaseTransformTextRunFactory::TransformString(
3405               origString, convertedString, Some(globalTransform),
3406               /* aCaseTransformsOnly = */ false, aLanguage, charsToMergeArray,
3407               deletedCharsArray);
3408 
3409           if (mergeNeeded) {
3410             // This is the hard case: the transformation caused chars
3411             // to be inserted or deleted, so we can't shape directly
3412             // into the destination textrun but have to handle the
3413             // mismatch of character positions.
3414             gfxTextRunFactory::Parameters params = {
3415                 aDrawTarget, nullptr, nullptr,
3416                 nullptr,     0,       aTextRun->GetAppUnitsPerDevUnit()};
3417             RefPtr<gfxTextRun> tempRun(gfxTextRun::Create(
3418                 &params, convertedString.Length(), aTextRun->GetFontGroup(),
3419                 gfx::ShapedTextFlags(), nsTextFrameUtils::Flags()));
3420             tempRun->AddGlyphRun(f, aMatchType, 0, true, aOrientation, isCJK);
3421             if (!f->SplitAndInitTextRun(aDrawTarget, tempRun.get(),
3422                                         convertedString.BeginReading(), 0,
3423                                         convertedString.Length(), aScript,
3424                                         aLanguage, aOrientation)) {
3425               ok = false;
3426             } else {
3427               RefPtr<gfxTextRun> mergedRun(gfxTextRun::Create(
3428                   &params, runLength, aTextRun->GetFontGroup(),
3429                   gfx::ShapedTextFlags(), nsTextFrameUtils::Flags()));
3430               MergeCharactersInTextRun(mergedRun.get(), tempRun.get(),
3431                                        charsToMergeArray.Elements(),
3432                                        deletedCharsArray.Elements());
3433               gfxTextRun::Range runRange(0, runLength);
3434               aTextRun->CopyGlyphDataFrom(mergedRun.get(), runRange,
3435                                           aOffset + runStart);
3436             }
3437           } else {
3438             aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true,
3439                                   aOrientation, isCJK);
3440             if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun,
3441                                         convertedString.BeginReading(),
3442                                         aOffset + runStart, runLength, aScript,
3443                                         aLanguage, aOrientation)) {
3444               ok = false;
3445             }
3446           }
3447           break;
3448       }
3449 
3450       runStart = i;
3451     }
3452 
3453     i += extraCodeUnits;
3454     if (i < aLength) {
3455       runAction = chAction;
3456     }
3457   }
3458 
3459   return ok;
3460 }
3461 
3462 template <>
InitFakeSmallCapsRun(nsPresContext * aPresContext,DrawTarget * aDrawTarget,gfxTextRun * aTextRun,const uint8_t * aText,uint32_t aOffset,uint32_t aLength,FontMatchType aMatchType,gfx::ShapedTextFlags aOrientation,Script aScript,nsAtom * aLanguage,bool aSyntheticLower,bool aSyntheticUpper)3463 bool gfxFont::InitFakeSmallCapsRun(
3464     nsPresContext* aPresContext, DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
3465     const uint8_t* aText, uint32_t aOffset, uint32_t aLength,
3466     FontMatchType aMatchType, gfx::ShapedTextFlags aOrientation, Script aScript,
3467     nsAtom* aLanguage, bool aSyntheticLower, bool aSyntheticUpper) {
3468   NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aText),
3469                                        aLength);
3470   return InitFakeSmallCapsRun(aPresContext, aDrawTarget, aTextRun,
3471                               static_cast<const char16_t*>(unicodeString.get()),
3472                               aOffset, aLength, aMatchType, aOrientation,
3473                               aScript, aLanguage, aSyntheticLower,
3474                               aSyntheticUpper);
3475 }
3476 
GetSmallCapsFont()3477 gfxFont* gfxFont::GetSmallCapsFont() {
3478   gfxFontStyle style(*GetStyle());
3479   style.size *= SMALL_CAPS_SCALE_FACTOR;
3480   style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
3481   gfxFontEntry* fe = GetFontEntry();
3482   return fe->FindOrMakeFont(&style, mUnicodeRangeMap);
3483 }
3484 
GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel)3485 gfxFont* gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel) {
3486   gfxFontStyle style(*GetStyle());
3487   style.AdjustForSubSuperscript(aAppUnitsPerDevPixel);
3488   gfxFontEntry* fe = GetFontEntry();
3489   return fe->FindOrMakeFont(&style, mUnicodeRangeMap);
3490 }
3491 
GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit)3492 gfxGlyphExtents* gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit) {
3493   uint32_t i, count = mGlyphExtentsArray.Length();
3494   for (i = 0; i < count; ++i) {
3495     if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit)
3496       return mGlyphExtentsArray[i].get();
3497   }
3498   gfxGlyphExtents* glyphExtents = new gfxGlyphExtents(aAppUnitsPerDevUnit);
3499   if (glyphExtents) {
3500     mGlyphExtentsArray.AppendElement(glyphExtents);
3501     // Initialize the extents of a space glyph, assuming that spaces don't
3502     // render anything!
3503     glyphExtents->SetContainedGlyphWidthAppUnits(GetSpaceGlyph(), 0);
3504   }
3505   return glyphExtents;
3506 }
3507 
SetupGlyphExtents(DrawTarget * aDrawTarget,uint32_t aGlyphID,bool aNeedTight,gfxGlyphExtents * aExtents)3508 void gfxFont::SetupGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphID,
3509                                 bool aNeedTight, gfxGlyphExtents* aExtents) {
3510   gfxRect svgBounds;
3511   if (mFontEntry->TryGetSVGData(this) && mFontEntry->HasSVGGlyph(aGlyphID) &&
3512       mFontEntry->GetSVGGlyphExtents(aDrawTarget, aGlyphID, GetAdjustedSize(),
3513                                      &svgBounds)) {
3514     gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit();
3515     aExtents->SetTightGlyphExtents(
3516         aGlyphID, gfxRect(svgBounds.X() * d2a, svgBounds.Y() * d2a,
3517                           svgBounds.Width() * d2a, svgBounds.Height() * d2a));
3518     return;
3519   }
3520 
3521   gfxRect bounds;
3522   GetGlyphBounds(aGlyphID, &bounds, mAntialiasOption == kAntialiasNone);
3523 
3524   const Metrics& fontMetrics = GetMetrics(nsFontMetrics::eHorizontal);
3525   int32_t appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit();
3526   if (!aNeedTight && bounds.x >= 0.0 && bounds.y >= -fontMetrics.maxAscent &&
3527       bounds.height + bounds.y <= fontMetrics.maxDescent) {
3528     uint32_t appUnitsWidth =
3529         uint32_t(ceil((bounds.x + bounds.width) * appUnitsPerDevUnit));
3530     if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) {
3531       aExtents->SetContainedGlyphWidthAppUnits(aGlyphID,
3532                                                uint16_t(appUnitsWidth));
3533       return;
3534     }
3535   }
3536 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
3537   if (!aNeedTight) {
3538     ++gGlyphExtentsSetupFallBackToTight;
3539   }
3540 #endif
3541 
3542   gfxFloat d2a = appUnitsPerDevUnit;
3543   aExtents->SetTightGlyphExtents(
3544       aGlyphID, gfxRect(bounds.x * d2a, bounds.y * d2a, bounds.width * d2a,
3545                         bounds.height * d2a));
3546 }
3547 
3548 // Try to initialize font metrics by reading sfnt tables directly;
3549 // set mIsValid=TRUE and return TRUE on success.
3550 // Return FALSE if the gfxFontEntry subclass does not
3551 // implement GetFontTable(), or for non-sfnt fonts where tables are
3552 // not available.
3553 // If this returns TRUE without setting the mIsValid flag, then we -did-
3554 // apparently find an sfnt, but it was too broken to be used.
InitMetricsFromSfntTables(Metrics & aMetrics)3555 bool gfxFont::InitMetricsFromSfntTables(Metrics& aMetrics) {
3556   mIsValid = false;  // font is NOT valid in case of early return
3557 
3558   const uint32_t kHheaTableTag = TRUETYPE_TAG('h', 'h', 'e', 'a');
3559   const uint32_t kOS_2TableTag = TRUETYPE_TAG('O', 'S', '/', '2');
3560 
3561   uint32_t len;
3562 
3563   if (mFUnitsConvFactor < 0.0) {
3564     // If the conversion factor from FUnits is not yet set,
3565     // get the unitsPerEm from the 'head' table via the font entry
3566     uint16_t unitsPerEm = GetFontEntry()->UnitsPerEm();
3567     if (unitsPerEm == gfxFontEntry::kInvalidUPEM) {
3568       return false;
3569     }
3570     mFUnitsConvFactor = GetAdjustedSize() / unitsPerEm;
3571   }
3572 
3573   // 'hhea' table is required for the advanceWidthMax field
3574   gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag);
3575   if (!hheaTable) {
3576     return false;  // no 'hhea' table -> not an sfnt
3577   }
3578   const MetricsHeader* hhea =
3579       reinterpret_cast<const MetricsHeader*>(hb_blob_get_data(hheaTable, &len));
3580   if (len < sizeof(MetricsHeader)) {
3581     return false;
3582   }
3583 
3584 #define SET_UNSIGNED(field, src) \
3585   aMetrics.field = uint16_t(src) * mFUnitsConvFactor
3586 #define SET_SIGNED(field, src) aMetrics.field = int16_t(src) * mFUnitsConvFactor
3587 
3588   SET_UNSIGNED(maxAdvance, hhea->advanceWidthMax);
3589 
3590   // 'OS/2' table is optional, if not found we'll estimate xHeight
3591   // and aveCharWidth by measuring glyphs
3592   gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag);
3593   if (os2Table) {
3594     const OS2Table* os2 =
3595         reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
3596     // this should always be present in any valid OS/2 of any version
3597     if (len >= offsetof(OS2Table, xAvgCharWidth) + sizeof(int16_t)) {
3598       SET_SIGNED(aveCharWidth, os2->xAvgCharWidth);
3599     }
3600   }
3601 
3602 #undef SET_SIGNED
3603 #undef SET_UNSIGNED
3604 
3605   hb_font_t* hbFont = gfxHarfBuzzShaper::CreateHBFont(this);
3606   hb_position_t position;
3607 
3608   auto FixedToFloat = [](hb_position_t f) -> gfxFloat { return f / 65536.0; };
3609 
3610   if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_ASCENDER,
3611                                  &position)) {
3612     aMetrics.maxAscent = FixedToFloat(position);
3613   }
3614   if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER,
3615                                  &position)) {
3616     aMetrics.maxDescent = -FixedToFloat(position);
3617   }
3618   if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_HORIZONTAL_LINE_GAP,
3619                                  &position)) {
3620     aMetrics.externalLeading = FixedToFloat(position);
3621   }
3622 
3623   if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_UNDERLINE_OFFSET,
3624                                  &position)) {
3625     aMetrics.underlineOffset = FixedToFloat(position);
3626   }
3627   if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_UNDERLINE_SIZE,
3628                                  &position)) {
3629     aMetrics.underlineSize = FixedToFloat(position);
3630   }
3631   if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_STRIKEOUT_OFFSET,
3632                                  &position)) {
3633     aMetrics.strikeoutOffset = FixedToFloat(position);
3634   }
3635   if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_STRIKEOUT_SIZE,
3636                                  &position)) {
3637     aMetrics.strikeoutSize = FixedToFloat(position);
3638   }
3639 
3640   // Although sxHeight and sCapHeight are signed fields, we consider
3641   // zero/negative values to be erroneous and just ignore them.
3642   if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_X_HEIGHT,
3643                                  &position) &&
3644       position > 0) {
3645     aMetrics.xHeight = FixedToFloat(position);
3646   }
3647   if (hb_ot_metrics_get_position(hbFont, HB_OT_METRICS_TAG_CAP_HEIGHT,
3648                                  &position) &&
3649       position > 0) {
3650     aMetrics.capHeight = FixedToFloat(position);
3651   }
3652   hb_font_destroy(hbFont);
3653 
3654   mIsValid = true;
3655 
3656   return true;
3657 }
3658 
RoundToNearestMultiple(double aValue,double aFraction)3659 static double RoundToNearestMultiple(double aValue, double aFraction) {
3660   return floor(aValue / aFraction + 0.5) * aFraction;
3661 }
3662 
CalculateDerivedMetrics(Metrics & aMetrics)3663 void gfxFont::CalculateDerivedMetrics(Metrics& aMetrics) {
3664   aMetrics.maxAscent =
3665       ceil(RoundToNearestMultiple(aMetrics.maxAscent, 1 / 1024.0));
3666   aMetrics.maxDescent =
3667       ceil(RoundToNearestMultiple(aMetrics.maxDescent, 1 / 1024.0));
3668 
3669   if (aMetrics.xHeight <= 0) {
3670     // only happens if we couldn't find either font metrics
3671     // or a char to measure;
3672     // pick an arbitrary value that's better than zero
3673     aMetrics.xHeight = aMetrics.maxAscent * DEFAULT_XHEIGHT_FACTOR;
3674   }
3675 
3676   // If we have a font that doesn't provide a capHeight value, use maxAscent
3677   // as a reasonable fallback.
3678   if (aMetrics.capHeight <= 0) {
3679     aMetrics.capHeight = aMetrics.maxAscent;
3680   }
3681 
3682   aMetrics.maxHeight = aMetrics.maxAscent + aMetrics.maxDescent;
3683 
3684   if (aMetrics.maxHeight - aMetrics.emHeight > 0.0) {
3685     aMetrics.internalLeading = aMetrics.maxHeight - aMetrics.emHeight;
3686   } else {
3687     aMetrics.internalLeading = 0.0;
3688   }
3689 
3690   aMetrics.emAscent =
3691       aMetrics.maxAscent * aMetrics.emHeight / aMetrics.maxHeight;
3692   aMetrics.emDescent = aMetrics.emHeight - aMetrics.emAscent;
3693 
3694   if (GetFontEntry()->IsFixedPitch()) {
3695     // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger
3696     // advance than the average character width... this forces
3697     // those fonts to be recognized like fixed pitch fonts by layout.
3698     aMetrics.maxAdvance = aMetrics.aveCharWidth;
3699   }
3700 
3701   if (!aMetrics.strikeoutOffset) {
3702     aMetrics.strikeoutOffset = aMetrics.xHeight * 0.5;
3703   }
3704   if (!aMetrics.strikeoutSize) {
3705     aMetrics.strikeoutSize = aMetrics.underlineSize;
3706   }
3707 }
3708 
SanitizeMetrics(gfxFont::Metrics * aMetrics,bool aIsBadUnderlineFont)3709 void gfxFont::SanitizeMetrics(gfxFont::Metrics* aMetrics,
3710                               bool aIsBadUnderlineFont) {
3711   // Even if this font size is zero, this font is created with non-zero size.
3712   // However, for layout and others, we should return the metrics of zero size
3713   // font.
3714   if (mStyle.AdjustedSizeMustBeZero()) {
3715     memset(aMetrics, 0, sizeof(gfxFont::Metrics));
3716     return;
3717   }
3718 
3719   // If the font entry has ascent/descent/lineGap-override values,
3720   // replace the metrics from the font with the overrides.
3721   gfxFloat adjustedSize = GetAdjustedSize();
3722   if (mFontEntry->mAscentOverride >= 0.0) {
3723     aMetrics->maxAscent = mFontEntry->mAscentOverride * adjustedSize;
3724     aMetrics->maxHeight = aMetrics->maxAscent + aMetrics->maxDescent;
3725     aMetrics->internalLeading =
3726         std::max(0.0, aMetrics->maxHeight - aMetrics->emHeight);
3727   }
3728   if (mFontEntry->mDescentOverride >= 0.0) {
3729     aMetrics->maxDescent = mFontEntry->mDescentOverride * adjustedSize;
3730     aMetrics->maxHeight = aMetrics->maxAscent + aMetrics->maxDescent;
3731     aMetrics->internalLeading =
3732         std::max(0.0, aMetrics->maxHeight - aMetrics->emHeight);
3733   }
3734   if (mFontEntry->mLineGapOverride >= 0.0) {
3735     aMetrics->externalLeading = mFontEntry->mLineGapOverride * adjustedSize;
3736   }
3737 
3738   aMetrics->underlineSize = std::max(1.0, aMetrics->underlineSize);
3739   aMetrics->strikeoutSize = std::max(1.0, aMetrics->strikeoutSize);
3740 
3741   aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -1.0);
3742 
3743   if (aMetrics->maxAscent < 1.0) {
3744     // We cannot draw strikeout line and overline in the ascent...
3745     aMetrics->underlineSize = 0;
3746     aMetrics->underlineOffset = 0;
3747     aMetrics->strikeoutSize = 0;
3748     aMetrics->strikeoutOffset = 0;
3749     return;
3750   }
3751 
3752   /**
3753    * Some CJK fonts have bad underline offset. Therefore, if this is such font,
3754    * we need to lower the underline offset to bottom of *em* descent.
3755    * However, if this is system font, we should not do this for the rendering
3756    * compatibility with another application's UI on the platform.
3757    * XXX Should not use this hack if the font size is too small?
3758    *     Such text cannot be read, this might be used for tight CSS
3759    *     rendering? (E.g., Acid2)
3760    */
3761   if (!mStyle.systemFont && aIsBadUnderlineFont) {
3762     // First, we need 2 pixels between baseline and underline at least. Because
3763     // many CJK characters put their glyphs on the baseline, so, 1 pixel is too
3764     // close for CJK characters.
3765     aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -2.0);
3766 
3767     // Next, we put the underline to bottom of below of the descent space.
3768     if (aMetrics->internalLeading + aMetrics->externalLeading >
3769         aMetrics->underlineSize) {
3770       aMetrics->underlineOffset =
3771           std::min(aMetrics->underlineOffset, -aMetrics->emDescent);
3772     } else {
3773       aMetrics->underlineOffset =
3774           std::min(aMetrics->underlineOffset,
3775                    aMetrics->underlineSize - aMetrics->emDescent);
3776     }
3777   }
3778   // If underline positioned is too far from the text, descent position is
3779   // preferred so that underline will stay within the boundary.
3780   else if (aMetrics->underlineSize - aMetrics->underlineOffset >
3781            aMetrics->maxDescent) {
3782     if (aMetrics->underlineSize > aMetrics->maxDescent)
3783       aMetrics->underlineSize = std::max(aMetrics->maxDescent, 1.0);
3784     // The max underlineOffset is 1px (the min underlineSize is 1px, and min
3785     // maxDescent is 0px.)
3786     aMetrics->underlineOffset = aMetrics->underlineSize - aMetrics->maxDescent;
3787   }
3788 
3789   // If strikeout line is overflowed from the ascent, the line should be resized
3790   // and moved for that being in the ascent space. Note that the strikeoutOffset
3791   // is *middle* of the strikeout line position.
3792   gfxFloat halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5);
3793   if (halfOfStrikeoutSize + aMetrics->strikeoutOffset > aMetrics->maxAscent) {
3794     if (aMetrics->strikeoutSize > aMetrics->maxAscent) {
3795       aMetrics->strikeoutSize = std::max(aMetrics->maxAscent, 1.0);
3796       halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5);
3797     }
3798     gfxFloat ascent = floor(aMetrics->maxAscent + 0.5);
3799     aMetrics->strikeoutOffset = std::max(halfOfStrikeoutSize, ascent / 2.0);
3800   }
3801 
3802   // If overline is larger than the ascent, the line should be resized.
3803   if (aMetrics->underlineSize > aMetrics->maxAscent) {
3804     aMetrics->underlineSize = aMetrics->maxAscent;
3805   }
3806 }
3807 
3808 // Create a Metrics record to be used for vertical layout. This should never
3809 // fail, as we've already decided this is a valid font. We do not have the
3810 // option of marking it invalid (as can happen if we're unable to read
3811 // horizontal metrics), because that could break a font that we're already
3812 // using for horizontal text.
3813 // So we will synthesize *something* usable here even if there aren't any of the
3814 // usual font tables (which can happen in the case of a legacy bitmap or Type1
3815 // font for which the platform-specific backend used platform APIs instead of
3816 // sfnt tables to create the horizontal metrics).
CreateVerticalMetrics()3817 void gfxFont::CreateVerticalMetrics() {
3818   const uint32_t kHheaTableTag = TRUETYPE_TAG('h', 'h', 'e', 'a');
3819   const uint32_t kVheaTableTag = TRUETYPE_TAG('v', 'h', 'e', 'a');
3820   const uint32_t kPostTableTag = TRUETYPE_TAG('p', 'o', 's', 't');
3821   const uint32_t kOS_2TableTag = TRUETYPE_TAG('O', 'S', '/', '2');
3822   uint32_t len;
3823 
3824   mVerticalMetrics = MakeUnique<Metrics>();
3825   auto* metrics = mVerticalMetrics.get();
3826   ::memset(metrics, 0, sizeof(Metrics));
3827 
3828   // Some basic defaults, in case the font lacks any real metrics tables.
3829   // TODO: consider what rounding (if any) we should apply to these.
3830   metrics->emHeight = GetAdjustedSize();
3831   metrics->emAscent = metrics->emHeight / 2;
3832   metrics->emDescent = metrics->emHeight - metrics->emAscent;
3833 
3834   metrics->maxAscent = metrics->emAscent;
3835   metrics->maxDescent = metrics->emDescent;
3836 
3837   const float UNINITIALIZED_LEADING = -10000.0f;
3838   metrics->externalLeading = UNINITIALIZED_LEADING;
3839 
3840   if (mFUnitsConvFactor < 0.0) {
3841     uint16_t upem = GetFontEntry()->UnitsPerEm();
3842     if (upem != gfxFontEntry::kInvalidUPEM) {
3843       mFUnitsConvFactor = GetAdjustedSize() / upem;
3844     }
3845   }
3846 
3847 #define SET_UNSIGNED(field, src) \
3848   metrics->field = uint16_t(src) * mFUnitsConvFactor
3849 #define SET_SIGNED(field, src) metrics->field = int16_t(src) * mFUnitsConvFactor
3850 
3851   gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag);
3852   if (os2Table && mFUnitsConvFactor >= 0.0) {
3853     const OS2Table* os2 =
3854         reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
3855     // These fields should always be present in any valid OS/2 table
3856     if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) {
3857       SET_SIGNED(strikeoutSize, os2->yStrikeoutSize);
3858       // Use ascent+descent from the horizontal metrics as the default
3859       // advance (aveCharWidth) in vertical mode
3860       gfxFloat ascentDescent =
3861           gfxFloat(mFUnitsConvFactor) *
3862           (int16_t(os2->sTypoAscender) - int16_t(os2->sTypoDescender));
3863       metrics->aveCharWidth = std::max(metrics->emHeight, ascentDescent);
3864       // Use xAvgCharWidth from horizontal metrics as minimum font extent
3865       // for vertical layout, applying half of it to ascent and half to
3866       // descent (to work with a default centered baseline).
3867       gfxFloat halfCharWidth =
3868           int16_t(os2->xAvgCharWidth) * gfxFloat(mFUnitsConvFactor) / 2;
3869       metrics->maxAscent = std::max(metrics->maxAscent, halfCharWidth);
3870       metrics->maxDescent = std::max(metrics->maxDescent, halfCharWidth);
3871     }
3872   }
3873 
3874   // If we didn't set aveCharWidth from OS/2, try to read 'hhea' metrics
3875   // and use the line height from its ascent/descent.
3876   if (!metrics->aveCharWidth) {
3877     gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag);
3878     if (hheaTable && mFUnitsConvFactor >= 0.0) {
3879       const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>(
3880           hb_blob_get_data(hheaTable, &len));
3881       if (len >= sizeof(MetricsHeader)) {
3882         SET_SIGNED(aveCharWidth,
3883                    int16_t(hhea->ascender) - int16_t(hhea->descender));
3884         metrics->maxAscent = metrics->aveCharWidth / 2;
3885         metrics->maxDescent = metrics->aveCharWidth - metrics->maxAscent;
3886       }
3887     }
3888   }
3889 
3890   // Read real vertical metrics if available.
3891   metrics->ideographicWidth = -1.0;
3892   gfxFontEntry::AutoTable vheaTable(mFontEntry, kVheaTableTag);
3893   if (vheaTable && mFUnitsConvFactor >= 0.0) {
3894     const MetricsHeader* vhea = reinterpret_cast<const MetricsHeader*>(
3895         hb_blob_get_data(vheaTable, &len));
3896     if (len >= sizeof(MetricsHeader)) {
3897       SET_UNSIGNED(maxAdvance, vhea->advanceWidthMax);
3898       // Redistribute space between ascent/descent because we want a
3899       // centered vertical baseline by default.
3900       gfxFloat halfExtent =
3901           0.5 * gfxFloat(mFUnitsConvFactor) *
3902           (int16_t(vhea->ascender) + std::abs(int16_t(vhea->descender)));
3903       // Some bogus fonts have ascent and descent set to zero in 'vhea'.
3904       // In that case we just ignore them and keep our synthetic values
3905       // from above.
3906       if (halfExtent > 0) {
3907         metrics->maxAscent = halfExtent;
3908         metrics->maxDescent = halfExtent;
3909         SET_SIGNED(externalLeading, vhea->lineGap);
3910       }
3911       metrics->ideographicWidth = GetCharAdvance(kWaterIdeograph, true);
3912     }
3913   }
3914 
3915   // If we didn't set aveCharWidth above, we must be dealing with a non-sfnt
3916   // font of some kind (Type1, bitmap, vector, ...), so fall back to using
3917   // whatever the platform backend figured out for horizontal layout.
3918   // And if we haven't set externalLeading yet, then copy that from the
3919   // horizontal metrics as well, to help consistency of CSS line-height.
3920   if (!metrics->aveCharWidth ||
3921       metrics->externalLeading == UNINITIALIZED_LEADING) {
3922     const Metrics& horizMetrics = GetHorizontalMetrics();
3923     if (!metrics->aveCharWidth) {
3924       metrics->aveCharWidth = horizMetrics.maxAscent + horizMetrics.maxDescent;
3925     }
3926     if (metrics->externalLeading == UNINITIALIZED_LEADING) {
3927       metrics->externalLeading = horizMetrics.externalLeading;
3928     }
3929   }
3930 
3931   // Get underline thickness from the 'post' table if available.
3932   // We also read the underline position, although in vertical-upright mode
3933   // this will not be appropriate to use directly (see nsTextFrame.cpp).
3934   gfxFontEntry::AutoTable postTable(mFontEntry, kPostTableTag);
3935   if (postTable) {
3936     const PostTable* post =
3937         reinterpret_cast<const PostTable*>(hb_blob_get_data(postTable, &len));
3938     if (len >= offsetof(PostTable, underlineThickness) + sizeof(uint16_t)) {
3939       static_assert(offsetof(PostTable, underlinePosition) <
3940                         offsetof(PostTable, underlineThickness),
3941                     "broken PostTable struct?");
3942       SET_SIGNED(underlineOffset, post->underlinePosition);
3943       SET_UNSIGNED(underlineSize, post->underlineThickness);
3944       // Also use for strikeout if we didn't find that in OS/2 above.
3945       if (!metrics->strikeoutSize) {
3946         metrics->strikeoutSize = metrics->underlineSize;
3947       }
3948     }
3949   }
3950 
3951 #undef SET_UNSIGNED
3952 #undef SET_SIGNED
3953 
3954   // If we didn't read this from a vhea table, it will still be zero.
3955   // In any case, let's make sure it is not less than the value we've
3956   // come up with for aveCharWidth.
3957   metrics->maxAdvance = std::max(metrics->maxAdvance, metrics->aveCharWidth);
3958 
3959   // Thickness of underline and strikeout may have been read from tables,
3960   // but in case they were not present, ensure a minimum of 1 pixel.
3961   metrics->underlineSize = std::max(1.0, metrics->underlineSize);
3962 
3963   metrics->strikeoutSize = std::max(1.0, metrics->strikeoutSize);
3964   metrics->strikeoutOffset = -0.5 * metrics->strikeoutSize;
3965 
3966   // Somewhat arbitrary values for now, subject to future refinement...
3967   metrics->spaceWidth = metrics->aveCharWidth;
3968   metrics->zeroWidth = metrics->aveCharWidth;
3969   metrics->maxHeight = metrics->maxAscent + metrics->maxDescent;
3970   metrics->xHeight = metrics->emHeight / 2;
3971   metrics->capHeight = metrics->maxAscent;
3972 }
3973 
SynthesizeSpaceWidth(uint32_t aCh)3974 gfxFloat gfxFont::SynthesizeSpaceWidth(uint32_t aCh) {
3975   // return an appropriate width for various Unicode space characters
3976   // that we "fake" if they're not actually present in the font;
3977   // returns negative value if the char is not a known space.
3978   switch (aCh) {
3979     case 0x2000:  // en quad
3980     case 0x2002:
3981       return GetAdjustedSize() / 2;  // en space
3982     case 0x2001:                     // em quad
3983     case 0x2003:
3984       return GetAdjustedSize();  // em space
3985     case 0x2004:
3986       return GetAdjustedSize() / 3;  // three-per-em space
3987     case 0x2005:
3988       return GetAdjustedSize() / 4;  // four-per-em space
3989     case 0x2006:
3990       return GetAdjustedSize() / 6;  // six-per-em space
3991     case 0x2007:
3992       return GetMetrics(nsFontMetrics::eHorizontal)
3993           .ZeroOrAveCharWidth();  // figure space
3994     case 0x2008:
3995       return GetMetrics(nsFontMetrics::eHorizontal)
3996           .spaceWidth;  // punctuation space
3997     case 0x2009:
3998       return GetAdjustedSize() / 5;  // thin space
3999     case 0x200a:
4000       return GetAdjustedSize() / 10;  // hair space
4001     case 0x202f:
4002       return GetAdjustedSize() / 5;  // narrow no-break space
4003     case 0x3000:
4004       return GetAdjustedSize();  // ideographic space
4005     default:
4006       return -1.0;
4007   }
4008 }
4009 
AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,FontCacheSizes * aSizes) const4010 void gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
4011                                      FontCacheSizes* aSizes) const {
4012   for (uint32_t i = 0; i < mGlyphExtentsArray.Length(); ++i) {
4013     aSizes->mFontInstances +=
4014         mGlyphExtentsArray[i]->SizeOfIncludingThis(aMallocSizeOf);
4015   }
4016   if (mWordCache) {
4017     aSizes->mShapedWords += mWordCache->SizeOfIncludingThis(aMallocSizeOf);
4018   }
4019 }
4020 
AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,FontCacheSizes * aSizes) const4021 void gfxFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
4022                                      FontCacheSizes* aSizes) const {
4023   aSizes->mFontInstances += aMallocSizeOf(this);
4024   AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
4025 }
4026 
AddGlyphChangeObserver(GlyphChangeObserver * aObserver)4027 void gfxFont::AddGlyphChangeObserver(GlyphChangeObserver* aObserver) {
4028   if (!mGlyphChangeObservers) {
4029     mGlyphChangeObservers = MakeUnique<nsTHashSet<GlyphChangeObserver*>>();
4030   }
4031   mGlyphChangeObservers->Insert(aObserver);
4032 }
4033 
RemoveGlyphChangeObserver(GlyphChangeObserver * aObserver)4034 void gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver* aObserver) {
4035   NS_ASSERTION(mGlyphChangeObservers, "No observers registered");
4036   NS_ASSERTION(mGlyphChangeObservers->Contains(aObserver),
4037                "Observer not registered");
4038   mGlyphChangeObservers->Remove(aObserver);
4039 }
4040 
4041 #define DEFAULT_PIXEL_FONT_SIZE 16.0f
4042 
gfxFontStyle()4043 gfxFontStyle::gfxFontStyle()
4044     : size(DEFAULT_PIXEL_FONT_SIZE),
4045       sizeAdjust(0.0f),
4046       baselineOffset(0.0f),
4047       languageOverride(NO_FONT_LANGUAGE_OVERRIDE),
4048       fontSmoothingBackgroundColor(NS_RGBA(0, 0, 0, 0)),
4049       weight(FontWeight::Normal()),
4050       stretch(FontStretch::Normal()),
4051       style(FontSlantStyle::Normal()),
4052       variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
4053       variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL),
4054       sizeAdjustBasis(uint8_t(FontSizeAdjust::Tag::None)),
4055       systemFont(true),
4056       printerFont(false),
4057       useGrayscaleAntialiasing(false),
4058       allowSyntheticWeight(true),
4059       allowSyntheticStyle(true),
4060       allowSyntheticSmallCaps(true),
4061       noFallbackVariantFeatures(true) {}
4062 
gfxFontStyle(FontSlantStyle aStyle,FontWeight aWeight,FontStretch aStretch,gfxFloat aSize,const FontSizeAdjust & aSizeAdjust,bool aSystemFont,bool aPrinterFont,bool aAllowWeightSynthesis,bool aAllowStyleSynthesis,bool aAllowSmallCapsSynthesis,uint32_t aLanguageOverride)4063 gfxFontStyle::gfxFontStyle(FontSlantStyle aStyle, FontWeight aWeight,
4064                            FontStretch aStretch, gfxFloat aSize,
4065                            const FontSizeAdjust& aSizeAdjust, bool aSystemFont,
4066                            bool aPrinterFont, bool aAllowWeightSynthesis,
4067                            bool aAllowStyleSynthesis,
4068                            bool aAllowSmallCapsSynthesis,
4069                            uint32_t aLanguageOverride)
4070     : size(aSize),
4071       baselineOffset(0.0f),
4072       languageOverride(aLanguageOverride),
4073       fontSmoothingBackgroundColor(NS_RGBA(0, 0, 0, 0)),
4074       weight(aWeight),
4075       stretch(aStretch),
4076       style(aStyle),
4077       variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
4078       variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL),
4079       systemFont(aSystemFont),
4080       printerFont(aPrinterFont),
4081       useGrayscaleAntialiasing(false),
4082       allowSyntheticWeight(aAllowWeightSynthesis),
4083       allowSyntheticStyle(aAllowStyleSynthesis),
4084       allowSyntheticSmallCaps(aAllowSmallCapsSynthesis),
4085       noFallbackVariantFeatures(true) {
4086   MOZ_ASSERT(!mozilla::IsNaN(size));
4087 
4088   switch (aSizeAdjust.tag) {
4089     case FontSizeAdjust::Tag::None:
4090       sizeAdjust = 0.0f;
4091       break;
4092     case FontSizeAdjust::Tag::ExHeight:
4093       sizeAdjust = aSizeAdjust.AsExHeight();
4094       break;
4095     case FontSizeAdjust::Tag::CapHeight:
4096       sizeAdjust = aSizeAdjust.AsCapHeight();
4097       break;
4098     case FontSizeAdjust::Tag::ChWidth:
4099       sizeAdjust = aSizeAdjust.AsChWidth();
4100       break;
4101     case FontSizeAdjust::Tag::IcWidth:
4102       sizeAdjust = aSizeAdjust.AsIcWidth();
4103       break;
4104     case FontSizeAdjust::Tag::IcHeight:
4105       sizeAdjust = aSizeAdjust.AsIcHeight();
4106       break;
4107   }
4108   MOZ_ASSERT(!mozilla::IsNaN(sizeAdjust));
4109 
4110   sizeAdjustBasis = uint8_t(aSizeAdjust.tag);
4111   // sizeAdjustBasis is currently a small bitfield, so let's assert that the
4112   // tag value was not truncated.
4113   MOZ_ASSERT(FontSizeAdjust::Tag(sizeAdjustBasis) == aSizeAdjust.tag,
4114              "gfxFontStyle.sizeAdjustBasis too small?");
4115 
4116   if (weight > FontWeight(1000)) {
4117     weight = FontWeight(1000);
4118   }
4119   if (weight < FontWeight(1)) {
4120     weight = FontWeight(1);
4121   }
4122 
4123   if (size >= FONT_MAX_SIZE) {
4124     size = FONT_MAX_SIZE;
4125     sizeAdjust = 0.0f;
4126     sizeAdjustBasis = uint8_t(FontSizeAdjust::Tag::None);
4127   } else if (size < 0.0) {
4128     NS_WARNING("negative font size");
4129     size = 0.0;
4130   }
4131 }
4132 
Hash() const4133 PLDHashNumber gfxFontStyle::Hash() const {
4134   uint32_t hash = variationSettings.IsEmpty()
4135                       ? 0
4136                       : mozilla::HashBytes(variationSettings.Elements(),
4137                                            variationSettings.Length() *
4138                                                sizeof(gfxFontVariation));
4139   return mozilla::AddToHash(hash, systemFont, style.ForHash(),
4140                             stretch.ForHash(), weight.ForHash(), size,
4141                             int32_t(sizeAdjust * 1000.0f));
4142 }
4143 
AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel)4144 void gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel) {
4145   MOZ_ASSERT(
4146       variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL && baselineOffset == 0,
4147       "can't adjust this style for sub/superscript");
4148 
4149   // calculate the baseline offset (before changing the size)
4150   if (variantSubSuper == NS_FONT_VARIANT_POSITION_SUPER) {
4151     baselineOffset = size * -NS_FONT_SUPERSCRIPT_OFFSET_RATIO;
4152   } else {
4153     baselineOffset = size * NS_FONT_SUBSCRIPT_OFFSET_RATIO;
4154   }
4155 
4156   // calculate reduced size, roughly mimicing behavior of font-size: smaller
4157   float cssSize = size * aAppUnitsPerDevPixel / AppUnitsPerCSSPixel();
4158   if (cssSize < NS_FONT_SUB_SUPER_SMALL_SIZE) {
4159     size *= NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL;
4160   } else if (cssSize >= NS_FONT_SUB_SUPER_LARGE_SIZE) {
4161     size *= NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE;
4162   } else {
4163     gfxFloat t = (cssSize - NS_FONT_SUB_SUPER_SMALL_SIZE) /
4164                  (NS_FONT_SUB_SUPER_LARGE_SIZE - NS_FONT_SUB_SUPER_SMALL_SIZE);
4165     size *= (1.0 - t) * NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL +
4166             t * NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE;
4167   }
4168 
4169   // clear the variant field
4170   variantSubSuper = NS_FONT_VARIANT_POSITION_NORMAL;
4171 }
4172 
TryGetMathTable()4173 bool gfxFont::TryGetMathTable() {
4174   if (!mMathInitialized) {
4175     mMathInitialized = true;
4176 
4177     hb_face_t* face = GetFontEntry()->GetHBFace();
4178     if (face) {
4179       if (hb_ot_math_has_data(face)) {
4180         mMathTable = MakeUnique<gfxMathTable>(face, GetAdjustedSize());
4181       }
4182       hb_face_destroy(face);
4183     }
4184   }
4185 
4186   return !!mMathTable;
4187 }
4188