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 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 ¶ms, 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 ¶ms, 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