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