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