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