1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=4 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "gfxTextRun.h"
8 #include "gfxGlyphExtents.h"
9 #include "gfxHarfBuzzShaper.h"
10 #include "gfxPlatformFontList.h"
11 #include "gfxUserFontSet.h"
12 #include "mozilla/gfx/2D.h"
13 #include "mozilla/gfx/PathHelpers.h"
14 #include "mozilla/Sprintf.h"
15
16 #include "gfxContext.h"
17 #include "gfxFontConstants.h"
18 #include "gfxFontMissingGlyphs.h"
19 #include "gfxScriptItemizer.h"
20 #include "nsUnicodeProperties.h"
21 #include "nsStyleConsts.h"
22 #include "nsStyleUtil.h"
23 #include "mozilla/Likely.h"
24 #include "gfx2DGlue.h"
25 #include "mozilla/gfx/Logging.h" // for gfxCriticalError
26 #include "mozilla/UniquePtr.h"
27 #include "mozilla/Unused.h"
28 #include "SharedFontList-impl.h"
29 #include "TextDrawTarget.h"
30
31 #include <unicode/unorm2.h>
32
33 #ifdef XP_WIN
34 # include "gfxWindowsPlatform.h"
35 #endif
36
37 using namespace mozilla;
38 using namespace mozilla::gfx;
39 using namespace mozilla::unicode;
40 using mozilla::services::GetObserverService;
41
42 static const char16_t kEllipsisChar[] = {0x2026, 0x0};
43 static const char16_t kASCIIPeriodsChar[] = {'.', '.', '.', 0x0};
44
45 #ifdef DEBUG_roc
46 # define DEBUG_TEXT_RUN_STORAGE_METRICS
47 #endif
48
49 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
50 extern uint32_t gTextRunStorageHighWaterMark;
51 extern uint32_t gTextRunStorage;
52 extern uint32_t gFontCount;
53 extern uint32_t gGlyphExtentsCount;
54 extern uint32_t gGlyphExtentsWidthsTotalSize;
55 extern uint32_t gGlyphExtentsSetupEagerSimple;
56 extern uint32_t gGlyphExtentsSetupEagerTight;
57 extern uint32_t gGlyphExtentsSetupLazyTight;
58 extern uint32_t gGlyphExtentsSetupFallBackToTight;
59 #endif
60
NextRun()61 bool gfxTextRun::GlyphRunIterator::NextRun() {
62 int32_t glyphRunCount;
63 if (mTextRun->mHasGlyphRunArray) {
64 glyphRunCount = mTextRun->mGlyphRunArray.Length();
65 if (mNextIndex >= glyphRunCount || mNextIndex < 0) {
66 return false;
67 }
68 mGlyphRun = &mTextRun->mGlyphRunArray[mNextIndex];
69 } else {
70 if (mNextIndex != 0 || !mTextRun->mSingleGlyphRun.mFont) {
71 return false;
72 }
73 glyphRunCount = 1;
74 mGlyphRun = &mTextRun->mSingleGlyphRun;
75 }
76
77 if (mGlyphRun->mCharacterOffset >= mEndOffset) {
78 return false;
79 }
80
81 uint32_t glyphRunEndOffset =
82 mNextIndex + 1 < (int32_t)glyphRunCount
83 ? mTextRun->mGlyphRunArray[mNextIndex + 1].mCharacterOffset
84 : mTextRun->GetLength();
85
86 if (glyphRunEndOffset <= mStartOffset) {
87 return false;
88 }
89
90 mStringEnd = std::min(mEndOffset, glyphRunEndOffset);
91 mStringStart = std::max(mStartOffset, mGlyphRun->mCharacterOffset);
92 mNextIndex += mDirection;
93 return true;
94 }
95
96 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
AccountStorageForTextRun(gfxTextRun * aTextRun,int32_t aSign)97 static void AccountStorageForTextRun(gfxTextRun* aTextRun, int32_t aSign) {
98 // Ignores detailed glyphs... we don't know when those have been constructed
99 // Also ignores gfxSkipChars dynamic storage (which won't be anything
100 // for preformatted text)
101 // Also ignores GlyphRun array, again because it hasn't been constructed
102 // by the time this gets called. If there's only one glyphrun that's stored
103 // directly in the textrun anyway so no additional overhead.
104 uint32_t length = aTextRun->GetLength();
105 int32_t bytes = length * sizeof(gfxTextRun::CompressedGlyph);
106 bytes += sizeof(gfxTextRun);
107 gTextRunStorage += bytes * aSign;
108 gTextRunStorageHighWaterMark =
109 std::max(gTextRunStorageHighWaterMark, gTextRunStorage);
110 }
111 #endif
112
NeedsGlyphExtents(gfxTextRun * aTextRun)113 static bool NeedsGlyphExtents(gfxTextRun* aTextRun) {
114 if (aTextRun->GetFlags() & gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX)
115 return true;
116 uint32_t numRuns;
117 const gfxTextRun::GlyphRun* glyphRuns = aTextRun->GetGlyphRuns(&numRuns);
118 for (uint32_t i = 0; i < numRuns; ++i) {
119 if (glyphRuns[i].mFont->GetFontEntry()->IsUserFont()) return true;
120 }
121 return false;
122 }
123
124 // Helper for textRun creation to preallocate storage for glyph records;
125 // this function returns a pointer to the newly-allocated glyph storage.
126 // Returns nullptr if allocation fails.
AllocateStorageForTextRun(size_t aSize,uint32_t aLength)127 void* gfxTextRun::AllocateStorageForTextRun(size_t aSize, uint32_t aLength) {
128 // Allocate the storage we need, returning nullptr on failure rather than
129 // throwing an exception (because web content can create huge runs).
130 void* storage = malloc(aSize + aLength * sizeof(CompressedGlyph));
131 if (!storage) {
132 NS_WARNING("failed to allocate storage for text run!");
133 return nullptr;
134 }
135
136 // Initialize the glyph storage (beyond aSize) to zero
137 memset(reinterpret_cast<char*>(storage) + aSize, 0,
138 aLength * sizeof(CompressedGlyph));
139
140 return storage;
141 }
142
Create(const gfxTextRunFactory::Parameters * aParams,uint32_t aLength,gfxFontGroup * aFontGroup,gfx::ShapedTextFlags aFlags,nsTextFrameUtils::Flags aFlags2)143 already_AddRefed<gfxTextRun> gfxTextRun::Create(
144 const gfxTextRunFactory::Parameters* aParams, uint32_t aLength,
145 gfxFontGroup* aFontGroup, gfx::ShapedTextFlags aFlags,
146 nsTextFrameUtils::Flags aFlags2) {
147 void* storage = AllocateStorageForTextRun(sizeof(gfxTextRun), aLength);
148 if (!storage) {
149 return nullptr;
150 }
151
152 RefPtr<gfxTextRun> result =
153 new (storage) gfxTextRun(aParams, aLength, aFontGroup, aFlags, aFlags2);
154 return result.forget();
155 }
156
gfxTextRun(const gfxTextRunFactory::Parameters * aParams,uint32_t aLength,gfxFontGroup * aFontGroup,gfx::ShapedTextFlags aFlags,nsTextFrameUtils::Flags aFlags2)157 gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters* aParams,
158 uint32_t aLength, gfxFontGroup* aFontGroup,
159 gfx::ShapedTextFlags aFlags,
160 nsTextFrameUtils::Flags aFlags2)
161 : gfxShapedText(aLength, aFlags, aParams->mAppUnitsPerDevUnit),
162 mSingleGlyphRun(),
163 mUserData(aParams->mUserData),
164 mFontGroup(aFontGroup),
165 mFlags2(aFlags2),
166 mReleasedFontGroup(false),
167 mReleasedFontGroupSkippedDrawing(false),
168 mHasGlyphRunArray(false),
169 mShapingState(eShapingState_Normal) {
170 NS_ASSERTION(mAppUnitsPerDevUnit > 0, "Invalid app unit scale");
171 NS_ADDREF(mFontGroup);
172
173 #ifndef RELEASE_OR_BETA
174 gfxTextPerfMetrics* tp = aFontGroup->GetTextPerfMetrics();
175 if (tp) {
176 tp->current.textrunConst++;
177 }
178 #endif
179
180 mCharacterGlyphs = reinterpret_cast<CompressedGlyph*>(this + 1);
181
182 if (aParams->mSkipChars) {
183 mSkipChars.TakeFrom(aParams->mSkipChars);
184 }
185
186 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
187 AccountStorageForTextRun(this, 1);
188 #endif
189
190 mDontSkipDrawing =
191 !!(aFlags2 & nsTextFrameUtils::Flags::DontSkipDrawingForPendingUserFonts);
192 }
193
~gfxTextRun()194 gfxTextRun::~gfxTextRun() {
195 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
196 AccountStorageForTextRun(this, -1);
197 #endif
198 #ifdef DEBUG
199 // Make it easy to detect a dead text run
200 mFlags = ~gfx::ShapedTextFlags();
201 mFlags2 = ~nsTextFrameUtils::Flags();
202 #endif
203
204 if (mHasGlyphRunArray) {
205 mGlyphRunArray.~nsTArray<GlyphRun>();
206 } else {
207 mSingleGlyphRun.mFont = nullptr;
208 }
209
210 // The cached ellipsis textrun (if any) in a fontgroup will have already
211 // been told to release its reference to the group, so we mustn't do that
212 // again here.
213 if (!mReleasedFontGroup) {
214 #ifndef RELEASE_OR_BETA
215 gfxTextPerfMetrics* tp = mFontGroup->GetTextPerfMetrics();
216 if (tp) {
217 tp->current.textrunDestr++;
218 }
219 #endif
220 NS_RELEASE(mFontGroup);
221 }
222 }
223
ReleaseFontGroup()224 void gfxTextRun::ReleaseFontGroup() {
225 NS_ASSERTION(!mReleasedFontGroup, "doubly released!");
226
227 // After dropping our reference to the font group, we'll no longer be able
228 // to get up-to-date results for ShouldSkipDrawing(). Store the current
229 // value in mReleasedFontGroupSkippedDrawing.
230 //
231 // (It doesn't actually matter that we can't get up-to-date results for
232 // ShouldSkipDrawing(), since the only text runs that we call
233 // ReleaseFontGroup() for are ellipsis text runs, and we ask the font
234 // group for a new ellipsis text run each time we want to draw one,
235 // and ensure that the cached one is cleared in ClearCachedData() when
236 // font loading status changes.)
237 mReleasedFontGroupSkippedDrawing = mFontGroup->ShouldSkipDrawing();
238
239 NS_RELEASE(mFontGroup);
240 mReleasedFontGroup = true;
241 }
242
SetPotentialLineBreaks(Range aRange,const uint8_t * aBreakBefore)243 bool gfxTextRun::SetPotentialLineBreaks(Range aRange,
244 const uint8_t* aBreakBefore) {
245 NS_ASSERTION(aRange.end <= GetLength(), "Overflow");
246
247 uint32_t changed = 0;
248 CompressedGlyph* cg = mCharacterGlyphs + aRange.start;
249 const CompressedGlyph* const end = cg + aRange.Length();
250 while (cg < end) {
251 uint8_t canBreak = *aBreakBefore++;
252 if (canBreak && !cg->IsClusterStart()) {
253 // XXX If we replace the line-breaker with one based more closely
254 // on UAX#14 (e.g. using ICU), this may not be needed any more.
255 // Avoid possible breaks inside a cluster, EXCEPT when the previous
256 // character was a space (compare UAX#14 rules LB9, LB10).
257 if (cg == mCharacterGlyphs || !(cg - 1)->CharIsSpace()) {
258 canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE;
259 }
260 }
261 changed |= cg->SetCanBreakBefore(canBreak);
262 ++cg;
263 }
264 return changed != 0;
265 }
266
ComputeLigatureData(Range aPartRange,PropertyProvider * aProvider) const267 gfxTextRun::LigatureData gfxTextRun::ComputeLigatureData(
268 Range aPartRange, PropertyProvider* aProvider) const {
269 NS_ASSERTION(aPartRange.start < aPartRange.end,
270 "Computing ligature data for empty range");
271 NS_ASSERTION(aPartRange.end <= GetLength(), "Character length overflow");
272
273 LigatureData result;
274 const CompressedGlyph* charGlyphs = mCharacterGlyphs;
275
276 uint32_t i;
277 for (i = aPartRange.start; !charGlyphs[i].IsLigatureGroupStart(); --i) {
278 NS_ASSERTION(i > 0, "Ligature at the start of the run??");
279 }
280 result.mRange.start = i;
281 for (i = aPartRange.start + 1;
282 i < GetLength() && !charGlyphs[i].IsLigatureGroupStart(); ++i) {
283 }
284 result.mRange.end = i;
285
286 int32_t ligatureWidth = GetAdvanceForGlyphs(result.mRange);
287 // Count the number of started clusters we have seen
288 uint32_t totalClusterCount = 0;
289 uint32_t partClusterIndex = 0;
290 uint32_t partClusterCount = 0;
291 for (i = result.mRange.start; i < result.mRange.end; ++i) {
292 // Treat the first character of the ligature as the start of a
293 // cluster for our purposes of allocating ligature width to its
294 // characters.
295 if (i == result.mRange.start || charGlyphs[i].IsClusterStart()) {
296 ++totalClusterCount;
297 if (i < aPartRange.start) {
298 ++partClusterIndex;
299 } else if (i < aPartRange.end) {
300 ++partClusterCount;
301 }
302 }
303 }
304 NS_ASSERTION(totalClusterCount > 0, "Ligature involving no clusters??");
305 result.mPartAdvance = partClusterIndex * (ligatureWidth / totalClusterCount);
306 result.mPartWidth = partClusterCount * (ligatureWidth / totalClusterCount);
307
308 // Any rounding errors are apportioned to the final part of the ligature,
309 // so that measuring all parts of a ligature and summing them is equal to
310 // the ligature width.
311 if (aPartRange.end == result.mRange.end) {
312 gfxFloat allParts = totalClusterCount * (ligatureWidth / totalClusterCount);
313 result.mPartWidth += ligatureWidth - allParts;
314 }
315
316 if (partClusterCount == 0) {
317 // nothing to draw
318 result.mClipBeforePart = result.mClipAfterPart = true;
319 } else {
320 // Determine whether we should clip before or after this part when
321 // drawing its slice of the ligature.
322 // We need to clip before the part if any cluster is drawn before
323 // this part.
324 result.mClipBeforePart = partClusterIndex > 0;
325 // We need to clip after the part if any cluster is drawn after
326 // this part.
327 result.mClipAfterPart =
328 partClusterIndex + partClusterCount < totalClusterCount;
329 }
330
331 if (aProvider && (mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) {
332 gfxFont::Spacing spacing;
333 if (aPartRange.start == result.mRange.start) {
334 aProvider->GetSpacing(Range(aPartRange.start, aPartRange.start + 1),
335 &spacing);
336 result.mPartWidth += spacing.mBefore;
337 }
338 if (aPartRange.end == result.mRange.end) {
339 aProvider->GetSpacing(Range(aPartRange.end - 1, aPartRange.end),
340 &spacing);
341 result.mPartWidth += spacing.mAfter;
342 }
343 }
344
345 return result;
346 }
347
ComputePartialLigatureWidth(Range aPartRange,PropertyProvider * aProvider) const348 gfxFloat gfxTextRun::ComputePartialLigatureWidth(
349 Range aPartRange, PropertyProvider* aProvider) const {
350 if (aPartRange.start >= aPartRange.end) return 0;
351 LigatureData data = ComputeLigatureData(aPartRange, aProvider);
352 return data.mPartWidth;
353 }
354
GetAdvanceForGlyphs(Range aRange) const355 int32_t gfxTextRun::GetAdvanceForGlyphs(Range aRange) const {
356 int32_t advance = 0;
357 for (auto i = aRange.start; i < aRange.end; ++i) {
358 advance += GetAdvanceForGlyph(i);
359 }
360 return advance;
361 }
362
GetAdjustedSpacing(const gfxTextRun * aTextRun,gfxTextRun::Range aRange,gfxTextRun::PropertyProvider * aProvider,gfxTextRun::PropertyProvider::Spacing * aSpacing)363 static void GetAdjustedSpacing(
364 const gfxTextRun* aTextRun, gfxTextRun::Range aRange,
365 gfxTextRun::PropertyProvider* aProvider,
366 gfxTextRun::PropertyProvider::Spacing* aSpacing) {
367 if (aRange.start >= aRange.end) return;
368
369 aProvider->GetSpacing(aRange, aSpacing);
370
371 #ifdef DEBUG
372 // Check to see if we have spacing inside ligatures
373
374 const gfxTextRun::CompressedGlyph* charGlyphs =
375 aTextRun->GetCharacterGlyphs();
376 uint32_t i;
377
378 for (i = aRange.start; i < aRange.end; ++i) {
379 if (!charGlyphs[i].IsLigatureGroupStart()) {
380 NS_ASSERTION(i == aRange.start || aSpacing[i - aRange.start].mBefore == 0,
381 "Before-spacing inside a ligature!");
382 NS_ASSERTION(
383 i - 1 <= aRange.start || aSpacing[i - 1 - aRange.start].mAfter == 0,
384 "After-spacing inside a ligature!");
385 }
386 }
387 #endif
388 }
389
GetAdjustedSpacingArray(Range aRange,PropertyProvider * aProvider,Range aSpacingRange,nsTArray<PropertyProvider::Spacing> * aSpacing) const390 bool gfxTextRun::GetAdjustedSpacingArray(
391 Range aRange, PropertyProvider* aProvider, Range aSpacingRange,
392 nsTArray<PropertyProvider::Spacing>* aSpacing) const {
393 if (!aProvider || !(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) {
394 return false;
395 }
396 if (!aSpacing->AppendElements(aRange.Length(), fallible)) {
397 return false;
398 }
399 auto spacingOffset = aSpacingRange.start - aRange.start;
400 memset(aSpacing->Elements(), 0, sizeof(gfxFont::Spacing) * spacingOffset);
401 GetAdjustedSpacing(this, aSpacingRange, aProvider,
402 aSpacing->Elements() + spacingOffset);
403 memset(aSpacing->Elements() + aSpacingRange.end - aRange.start, 0,
404 sizeof(gfxFont::Spacing) * (aRange.end - aSpacingRange.end));
405 return true;
406 }
407
ShrinkToLigatureBoundaries(Range * aRange) const408 void gfxTextRun::ShrinkToLigatureBoundaries(Range* aRange) const {
409 if (aRange->start >= aRange->end) return;
410
411 const CompressedGlyph* charGlyphs = mCharacterGlyphs;
412
413 while (aRange->start < aRange->end &&
414 !charGlyphs[aRange->start].IsLigatureGroupStart()) {
415 ++aRange->start;
416 }
417 if (aRange->end < GetLength()) {
418 while (aRange->end > aRange->start &&
419 !charGlyphs[aRange->end].IsLigatureGroupStart()) {
420 --aRange->end;
421 }
422 }
423 }
424
DrawGlyphs(gfxFont * aFont,Range aRange,gfx::Point * aPt,PropertyProvider * aProvider,Range aSpacingRange,TextRunDrawParams & aParams,gfx::ShapedTextFlags aOrientation) const425 void gfxTextRun::DrawGlyphs(gfxFont* aFont, Range aRange, gfx::Point* aPt,
426 PropertyProvider* aProvider, Range aSpacingRange,
427 TextRunDrawParams& aParams,
428 gfx::ShapedTextFlags aOrientation) const {
429 AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer;
430 bool haveSpacing =
431 GetAdjustedSpacingArray(aRange, aProvider, aSpacingRange, &spacingBuffer);
432 aParams.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr;
433 aFont->Draw(this, aRange.start, aRange.end, aPt, aParams, aOrientation);
434 }
435
ClipPartialLigature(const gfxTextRun * aTextRun,gfxFloat * aStart,gfxFloat * aEnd,gfxFloat aOrigin,gfxTextRun::LigatureData * aLigature)436 static void ClipPartialLigature(const gfxTextRun* aTextRun, gfxFloat* aStart,
437 gfxFloat* aEnd, gfxFloat aOrigin,
438 gfxTextRun::LigatureData* aLigature) {
439 if (aLigature->mClipBeforePart) {
440 if (aTextRun->IsRightToLeft()) {
441 *aEnd = std::min(*aEnd, aOrigin);
442 } else {
443 *aStart = std::max(*aStart, aOrigin);
444 }
445 }
446 if (aLigature->mClipAfterPart) {
447 gfxFloat endEdge =
448 aOrigin + aTextRun->GetDirection() * aLigature->mPartWidth;
449 if (aTextRun->IsRightToLeft()) {
450 *aStart = std::max(*aStart, endEdge);
451 } else {
452 *aEnd = std::min(*aEnd, endEdge);
453 }
454 }
455 }
456
DrawPartialLigature(gfxFont * aFont,Range aRange,gfx::Point * aPt,PropertyProvider * aProvider,TextRunDrawParams & aParams,gfx::ShapedTextFlags aOrientation) const457 void gfxTextRun::DrawPartialLigature(gfxFont* aFont, Range aRange,
458 gfx::Point* aPt,
459 PropertyProvider* aProvider,
460 TextRunDrawParams& aParams,
461 gfx::ShapedTextFlags aOrientation) const {
462 if (aRange.start >= aRange.end) {
463 return;
464 }
465
466 // Draw partial ligature. We hack this by clipping the ligature.
467 LigatureData data = ComputeLigatureData(aRange, aProvider);
468 gfxRect clipExtents = aParams.context->GetClipExtents();
469 gfxFloat start, end;
470 if (aParams.isVerticalRun) {
471 start = clipExtents.Y() * mAppUnitsPerDevUnit;
472 end = clipExtents.YMost() * mAppUnitsPerDevUnit;
473 ClipPartialLigature(this, &start, &end, aPt->y, &data);
474 } else {
475 start = clipExtents.X() * mAppUnitsPerDevUnit;
476 end = clipExtents.XMost() * mAppUnitsPerDevUnit;
477 ClipPartialLigature(this, &start, &end, aPt->x, &data);
478 }
479
480 {
481 // use division here to ensure that when the rect is aligned on multiples
482 // of mAppUnitsPerDevUnit, we clip to true device unit boundaries.
483 // Also, make sure we snap the rectangle to device pixels.
484 Rect clipRect =
485 aParams.isVerticalRun
486 ? Rect(clipExtents.X(), start / mAppUnitsPerDevUnit,
487 clipExtents.Width(), (end - start) / mAppUnitsPerDevUnit)
488 : Rect(start / mAppUnitsPerDevUnit, clipExtents.Y(),
489 (end - start) / mAppUnitsPerDevUnit, clipExtents.Height());
490 MaybeSnapToDevicePixels(clipRect, *aParams.dt, true);
491
492 aParams.context->Clip(clipRect);
493 }
494
495 gfx::Point pt;
496 if (aParams.isVerticalRun) {
497 pt = Point(aPt->x, aPt->y - aParams.direction * data.mPartAdvance);
498 } else {
499 pt = Point(aPt->x - aParams.direction * data.mPartAdvance, aPt->y);
500 }
501
502 DrawGlyphs(aFont, data.mRange, &pt, aProvider, aRange, aParams, aOrientation);
503 aParams.context->PopClip();
504
505 if (aParams.isVerticalRun) {
506 aPt->y += aParams.direction * data.mPartWidth;
507 } else {
508 aPt->x += aParams.direction * data.mPartWidth;
509 }
510 }
511
512 // Returns true if the font has synthetic bolding enabled,
513 // or is a color font (COLR/SVG/sbix/CBDT), false otherwise. This is used to
514 // check whether the text run needs to be explicitly composited in order to
515 // support opacity.
HasSyntheticBoldOrColor(gfxFont * aFont)516 static bool HasSyntheticBoldOrColor(gfxFont* aFont) {
517 if (aFont->IsSyntheticBold()) {
518 return true;
519 }
520 gfxFontEntry* fe = aFont->GetFontEntry();
521 if (fe->TryGetSVGData(aFont) || fe->TryGetColorGlyphs()) {
522 return true;
523 }
524 #if defined(XP_MACOSX) // sbix fonts only supported via Core Text
525 if (fe->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) {
526 return true;
527 }
528 #endif
529 return false;
530 }
531
532 // helper class for double-buffering drawing with non-opaque color
533 struct MOZ_STACK_CLASS BufferAlphaColor {
BufferAlphaColorBufferAlphaColor534 explicit BufferAlphaColor(gfxContext* aContext) : mContext(aContext) {}
535
536 ~BufferAlphaColor() = default;
537
PushSolidColorBufferAlphaColor538 void PushSolidColor(const gfxRect& aBounds, const DeviceColor& aAlphaColor,
539 uint32_t appsPerDevUnit) {
540 mContext->Save();
541 mContext->SnappedClip(gfxRect(
542 aBounds.X() / appsPerDevUnit, aBounds.Y() / appsPerDevUnit,
543 aBounds.Width() / appsPerDevUnit, aBounds.Height() / appsPerDevUnit));
544 mContext->SetDeviceColor(
545 DeviceColor(aAlphaColor.r, aAlphaColor.g, aAlphaColor.b));
546 mContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, aAlphaColor.a);
547 }
548
PopAlphaBufferAlphaColor549 void PopAlpha() {
550 // pop the text, using the color alpha as the opacity
551 mContext->PopGroupAndBlend();
552 mContext->Restore();
553 }
554
555 gfxContext* mContext;
556 };
557
Draw(const Range aRange,const gfx::Point aPt,const DrawParams & aParams) const558 void gfxTextRun::Draw(const Range aRange, const gfx::Point aPt,
559 const DrawParams& aParams) const {
560 NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range");
561 NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH ||
562 !(aParams.drawMode & DrawMode::GLYPH_PATH),
563 "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or "
564 "GLYPH_STROKE_UNDERNEATH");
565 NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH || !aParams.callbacks,
566 "callback must not be specified unless using GLYPH_PATH");
567
568 bool skipDrawing =
569 !mDontSkipDrawing && (mFontGroup ? mFontGroup->ShouldSkipDrawing()
570 : mReleasedFontGroupSkippedDrawing);
571 if (aParams.drawMode & DrawMode::GLYPH_FILL) {
572 DeviceColor currentColor;
573 if (aParams.context->GetDeviceColor(currentColor) && currentColor.a == 0 &&
574 !aParams.context->GetTextDrawer()) {
575 skipDrawing = true;
576 }
577 }
578
579 gfxFloat direction = GetDirection();
580
581 if (skipDrawing) {
582 // We don't need to draw anything;
583 // but if the caller wants advance width, we need to compute it here
584 if (aParams.advanceWidth) {
585 gfxTextRun::Metrics metrics =
586 MeasureText(aRange, gfxFont::LOOSE_INK_EXTENTS,
587 aParams.context->GetDrawTarget(), aParams.provider);
588 *aParams.advanceWidth = metrics.mAdvanceWidth * direction;
589 }
590
591 // return without drawing
592 return;
593 }
594
595 // synthetic bolding draws glyphs twice ==> colors with opacity won't draw
596 // correctly unless first drawn without alpha
597 BufferAlphaColor syntheticBoldBuffer(aParams.context);
598 DeviceColor currentColor;
599 bool mayNeedBuffering =
600 aParams.drawMode & DrawMode::GLYPH_FILL &&
601 aParams.context->HasNonOpaqueNonTransparentColor(currentColor) &&
602 !aParams.context->GetTextDrawer();
603
604 // If we need to double-buffer, we'll need to measure the text first to
605 // get the bounds of the area of interest. Ideally we'd do that just for
606 // the specific glyph run(s) that need buffering, but because of bug
607 // 1612610 we currently use the extent of the entire range even when
608 // just buffering a subrange. So we'll measure the full range once and
609 // keep the metrics on hand for any subsequent subranges.
610 gfxTextRun::Metrics metrics;
611 bool gotMetrics = false;
612
613 // Set up parameters that will be constant across all glyph runs we need
614 // to draw, regardless of the font used.
615 TextRunDrawParams params;
616 params.context = aParams.context;
617 params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit());
618 params.isVerticalRun = IsVertical();
619 params.isRTL = IsRightToLeft();
620 params.direction = direction;
621 params.strokeOpts = aParams.strokeOpts;
622 params.textStrokeColor = aParams.textStrokeColor;
623 params.textStrokePattern = aParams.textStrokePattern;
624 params.drawOpts = aParams.drawOpts;
625 params.drawMode = aParams.drawMode;
626 params.callbacks = aParams.callbacks;
627 params.runContextPaint = aParams.contextPaint;
628 params.paintSVGGlyphs =
629 !aParams.callbacks || aParams.callbacks->mShouldPaintSVGGlyphs;
630 params.dt = aParams.context->GetDrawTarget();
631
632 GlyphRunIterator iter(this, aRange);
633 gfxFloat advance = 0.0;
634 gfx::Point pt = aPt;
635
636 while (iter.NextRun()) {
637 gfxFont* font = iter.GetGlyphRun()->mFont;
638 Range runRange(iter.GetStringStart(), iter.GetStringEnd());
639
640 bool needToRestore = false;
641 if (mayNeedBuffering && HasSyntheticBoldOrColor(font)) {
642 needToRestore = true;
643 if (!gotMetrics) {
644 // Measure text; use the bounding box to determine the area we need
645 // to buffer. We measure the entire range, rather than just the glyph
646 // run that we're actually handling, because of bug 1612610: if the
647 // bounding box passed to PushSolidColor does not intersect the
648 // drawTarget's current clip, the skia backend fails to clip properly.
649 // This means we may use a larger buffer than actually needed, but is
650 // otherwise harmless.
651 metrics =
652 MeasureText(aRange, gfxFont::LOOSE_INK_EXTENTS,
653 aParams.context->GetDrawTarget(), aParams.provider);
654 if (IsRightToLeft()) {
655 metrics.mBoundingBox.MoveBy(
656 gfxPoint(aPt.x - metrics.mAdvanceWidth, aPt.y));
657 } else {
658 metrics.mBoundingBox.MoveBy(gfxPoint(aPt.x, aPt.y));
659 }
660 gotMetrics = true;
661 }
662 syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor,
663 GetAppUnitsPerDevUnit());
664 }
665
666 Range ligatureRange(runRange);
667 ShrinkToLigatureBoundaries(&ligatureRange);
668
669 bool drawPartial =
670 (aParams.drawMode & (DrawMode::GLYPH_FILL | DrawMode::GLYPH_STROKE)) ||
671 (aParams.drawMode == DrawMode::GLYPH_PATH && aParams.callbacks);
672 gfx::Point origPt = pt;
673
674 if (drawPartial) {
675 DrawPartialLigature(font, Range(runRange.start, ligatureRange.start), &pt,
676 aParams.provider, params,
677 iter.GetGlyphRun()->mOrientation);
678 }
679
680 DrawGlyphs(font, ligatureRange, &pt, aParams.provider, ligatureRange,
681 params, iter.GetGlyphRun()->mOrientation);
682
683 if (drawPartial) {
684 DrawPartialLigature(font, Range(ligatureRange.end, runRange.end), &pt,
685 aParams.provider, params,
686 iter.GetGlyphRun()->mOrientation);
687 }
688
689 if (params.isVerticalRun) {
690 advance += (pt.y - origPt.y) * params.direction;
691 } else {
692 advance += (pt.x - origPt.x) * params.direction;
693 }
694
695 // composite result when synthetic bolding used
696 if (needToRestore) {
697 syntheticBoldBuffer.PopAlpha();
698 }
699 }
700
701 if (aParams.advanceWidth) {
702 *aParams.advanceWidth = advance;
703 }
704 }
705
706 // This method is mostly parallel to Draw().
DrawEmphasisMarks(gfxContext * aContext,gfxTextRun * aMark,gfxFloat aMarkAdvance,gfx::Point aPt,Range aRange,PropertyProvider * aProvider) const707 void gfxTextRun::DrawEmphasisMarks(gfxContext* aContext, gfxTextRun* aMark,
708 gfxFloat aMarkAdvance, gfx::Point aPt,
709 Range aRange,
710 PropertyProvider* aProvider) const {
711 MOZ_ASSERT(aRange.end <= GetLength());
712
713 EmphasisMarkDrawParams params;
714 params.context = aContext;
715 params.mark = aMark;
716 params.advance = aMarkAdvance;
717 params.direction = GetDirection();
718 params.isVertical = IsVertical();
719
720 float& inlineCoord = params.isVertical ? aPt.y : aPt.x;
721 float direction = params.direction;
722
723 GlyphRunIterator iter(this, aRange);
724 while (iter.NextRun()) {
725 gfxFont* font = iter.GetGlyphRun()->mFont;
726 uint32_t start = iter.GetStringStart();
727 uint32_t end = iter.GetStringEnd();
728 Range ligatureRange(start, end);
729 ShrinkToLigatureBoundaries(&ligatureRange);
730
731 inlineCoord +=
732 direction * ComputePartialLigatureWidth(
733 Range(start, ligatureRange.start), aProvider);
734
735 AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer;
736 bool haveSpacing = GetAdjustedSpacingArray(ligatureRange, aProvider,
737 ligatureRange, &spacingBuffer);
738 params.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr;
739 font->DrawEmphasisMarks(this, &aPt, ligatureRange.start,
740 ligatureRange.Length(), params);
741
742 inlineCoord += direction * ComputePartialLigatureWidth(
743 Range(ligatureRange.end, end), aProvider);
744 }
745 }
746
AccumulateMetricsForRun(gfxFont * aFont,Range aRange,gfxFont::BoundingBoxType aBoundingBoxType,DrawTarget * aRefDrawTarget,PropertyProvider * aProvider,Range aSpacingRange,gfx::ShapedTextFlags aOrientation,Metrics * aMetrics) const747 void gfxTextRun::AccumulateMetricsForRun(
748 gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType,
749 DrawTarget* aRefDrawTarget, PropertyProvider* aProvider,
750 Range aSpacingRange, gfx::ShapedTextFlags aOrientation,
751 Metrics* aMetrics) const {
752 AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer;
753 bool haveSpacing =
754 GetAdjustedSpacingArray(aRange, aProvider, aSpacingRange, &spacingBuffer);
755 Metrics metrics = aFont->Measure(
756 this, aRange.start, aRange.end, aBoundingBoxType, aRefDrawTarget,
757 haveSpacing ? spacingBuffer.Elements() : nullptr, aOrientation);
758 aMetrics->CombineWith(metrics, IsRightToLeft());
759 }
760
AccumulatePartialLigatureMetrics(gfxFont * aFont,Range aRange,gfxFont::BoundingBoxType aBoundingBoxType,DrawTarget * aRefDrawTarget,PropertyProvider * aProvider,gfx::ShapedTextFlags aOrientation,Metrics * aMetrics) const761 void gfxTextRun::AccumulatePartialLigatureMetrics(
762 gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType,
763 DrawTarget* aRefDrawTarget, PropertyProvider* aProvider,
764 gfx::ShapedTextFlags aOrientation, Metrics* aMetrics) const {
765 if (aRange.start >= aRange.end) return;
766
767 // Measure partial ligature. We hack this by clipping the metrics in the
768 // same way we clip the drawing.
769 LigatureData data = ComputeLigatureData(aRange, aProvider);
770
771 // First measure the complete ligature
772 Metrics metrics;
773 AccumulateMetricsForRun(aFont, data.mRange, aBoundingBoxType, aRefDrawTarget,
774 aProvider, aRange, aOrientation, &metrics);
775
776 // Clip the bounding box to the ligature part
777 gfxFloat bboxLeft = metrics.mBoundingBox.X();
778 gfxFloat bboxRight = metrics.mBoundingBox.XMost();
779 // Where we are going to start "drawing" relative to our left baseline origin
780 gfxFloat origin =
781 IsRightToLeft() ? metrics.mAdvanceWidth - data.mPartAdvance : 0;
782 ClipPartialLigature(this, &bboxLeft, &bboxRight, origin, &data);
783 metrics.mBoundingBox.SetBoxX(bboxLeft, bboxRight);
784
785 // mBoundingBox is now relative to the left baseline origin for the entire
786 // ligature. Shift it left.
787 metrics.mBoundingBox.MoveByX(
788 -(IsRightToLeft()
789 ? metrics.mAdvanceWidth - (data.mPartAdvance + data.mPartWidth)
790 : data.mPartAdvance));
791 metrics.mAdvanceWidth = data.mPartWidth;
792
793 aMetrics->CombineWith(metrics, IsRightToLeft());
794 }
795
MeasureText(Range aRange,gfxFont::BoundingBoxType aBoundingBoxType,DrawTarget * aRefDrawTarget,PropertyProvider * aProvider) const796 gfxTextRun::Metrics gfxTextRun::MeasureText(
797 Range aRange, gfxFont::BoundingBoxType aBoundingBoxType,
798 DrawTarget* aRefDrawTarget, PropertyProvider* aProvider) const {
799 NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range");
800
801 Metrics accumulatedMetrics;
802 GlyphRunIterator iter(this, aRange);
803 while (iter.NextRun()) {
804 gfxFont* font = iter.GetGlyphRun()->mFont;
805 uint32_t start = iter.GetStringStart();
806 uint32_t end = iter.GetStringEnd();
807 Range ligatureRange(start, end);
808 ShrinkToLigatureBoundaries(&ligatureRange);
809
810 AccumulatePartialLigatureMetrics(
811 font, Range(start, ligatureRange.start), aBoundingBoxType,
812 aRefDrawTarget, aProvider, iter.GetGlyphRun()->mOrientation,
813 &accumulatedMetrics);
814
815 // XXX This sucks. We have to get glyph extents just so we can detect
816 // glyphs outside the font box, even when aBoundingBoxType is LOOSE,
817 // even though in almost all cases we could get correct results just
818 // by getting some ascent/descent from the font and using our stored
819 // advance widths.
820 AccumulateMetricsForRun(
821 font, ligatureRange, aBoundingBoxType, aRefDrawTarget, aProvider,
822 ligatureRange, iter.GetGlyphRun()->mOrientation, &accumulatedMetrics);
823
824 AccumulatePartialLigatureMetrics(
825 font, Range(ligatureRange.end, end), aBoundingBoxType, aRefDrawTarget,
826 aProvider, iter.GetGlyphRun()->mOrientation, &accumulatedMetrics);
827 }
828
829 return accumulatedMetrics;
830 }
831
832 #define MEASUREMENT_BUFFER_SIZE 100
833
ClassifyAutoHyphenations(uint32_t aStart,Range aRange,nsTArray<HyphenType> & aHyphenBuffer,HyphenationState * aWordState)834 void gfxTextRun::ClassifyAutoHyphenations(uint32_t aStart, Range aRange,
835 nsTArray<HyphenType>& aHyphenBuffer,
836 HyphenationState* aWordState) {
837 MOZ_ASSERT(
838 aRange.end - aStart <= aHyphenBuffer.Length() && aRange.start >= aStart,
839 "Range out of bounds");
840 MOZ_ASSERT(aWordState->mostRecentBoundary >= aStart,
841 "Unexpected aMostRecentWordBoundary!!");
842
843 uint32_t start =
844 std::min<uint32_t>(aRange.start, aWordState->mostRecentBoundary);
845
846 for (uint32_t i = start; i < aRange.end; ++i) {
847 if (aHyphenBuffer[i - aStart] == HyphenType::Explicit &&
848 !aWordState->hasExplicitHyphen) {
849 aWordState->hasExplicitHyphen = true;
850 }
851 if (!aWordState->hasManualHyphen &&
852 (aHyphenBuffer[i - aStart] == HyphenType::Soft ||
853 aHyphenBuffer[i - aStart] == HyphenType::Explicit)) {
854 aWordState->hasManualHyphen = true;
855 // This is the first manual hyphen in the current word. We can only
856 // know if the current word has a manual hyphen until now. So, we need
857 // to run a sub loop to update the auto hyphens between the start of
858 // the current word and this manual hyphen.
859 if (aWordState->hasAutoHyphen) {
860 for (uint32_t j = aWordState->mostRecentBoundary; j < i; j++) {
861 if (aHyphenBuffer[j - aStart] ==
862 HyphenType::AutoWithoutManualInSameWord) {
863 aHyphenBuffer[j - aStart] = HyphenType::AutoWithManualInSameWord;
864 }
865 }
866 }
867 }
868 if (aHyphenBuffer[i - aStart] == HyphenType::AutoWithoutManualInSameWord) {
869 if (!aWordState->hasAutoHyphen) {
870 aWordState->hasAutoHyphen = true;
871 }
872 if (aWordState->hasManualHyphen) {
873 aHyphenBuffer[i - aStart] = HyphenType::AutoWithManualInSameWord;
874 }
875 }
876
877 // If we're at the word boundary, clear/reset couple states.
878 if (mCharacterGlyphs[i].CharIsSpace() || mCharacterGlyphs[i].CharIsTab() ||
879 mCharacterGlyphs[i].CharIsNewline() ||
880 // Since we will not have a boundary in the end of the string, let's
881 // call the end of the string a special case for word boundary.
882 i == GetLength() - 1) {
883 // We can only get to know whether we should raise/clear an explicit
884 // manual hyphen until we get to the end of a word, because this depends
885 // on whether there exists at least one auto hyphen in the same word.
886 if (!aWordState->hasAutoHyphen && aWordState->hasExplicitHyphen) {
887 for (uint32_t j = aWordState->mostRecentBoundary; j <= i; j++) {
888 if (aHyphenBuffer[j - aStart] == HyphenType::Explicit) {
889 aHyphenBuffer[j - aStart] = HyphenType::None;
890 }
891 }
892 }
893 aWordState->mostRecentBoundary = i;
894 aWordState->hasManualHyphen = false;
895 aWordState->hasAutoHyphen = false;
896 aWordState->hasExplicitHyphen = false;
897 }
898 }
899 }
900
BreakAndMeasureText(uint32_t aStart,uint32_t aMaxLength,bool aLineBreakBefore,gfxFloat aWidth,PropertyProvider * aProvider,SuppressBreak aSuppressBreak,gfxFloat * aTrimWhitespace,bool aWhitespaceCanHang,Metrics * aMetrics,gfxFont::BoundingBoxType aBoundingBoxType,DrawTarget * aRefDrawTarget,bool * aUsedHyphenation,uint32_t * aLastBreak,bool aCanWordWrap,bool aCanWhitespaceWrap,gfxBreakPriority * aBreakPriority)901 uint32_t gfxTextRun::BreakAndMeasureText(
902 uint32_t aStart, uint32_t aMaxLength, bool aLineBreakBefore,
903 gfxFloat aWidth, PropertyProvider* aProvider, SuppressBreak aSuppressBreak,
904 gfxFloat* aTrimWhitespace, bool aWhitespaceCanHang, Metrics* aMetrics,
905 gfxFont::BoundingBoxType aBoundingBoxType, DrawTarget* aRefDrawTarget,
906 bool* aUsedHyphenation, uint32_t* aLastBreak, bool aCanWordWrap,
907 bool aCanWhitespaceWrap, gfxBreakPriority* aBreakPriority) {
908 aMaxLength = std::min(aMaxLength, GetLength() - aStart);
909
910 NS_ASSERTION(aStart + aMaxLength <= GetLength(), "Substring out of range");
911
912 Range bufferRange(
913 aStart, aStart + std::min<uint32_t>(aMaxLength, MEASUREMENT_BUFFER_SIZE));
914 PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE];
915 bool haveSpacing =
916 aProvider && !!(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING);
917 if (haveSpacing) {
918 GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer);
919 }
920 AutoTArray<HyphenType, 4096> hyphenBuffer;
921 HyphenationState wordState;
922 wordState.mostRecentBoundary = aStart;
923 bool haveHyphenation =
924 aProvider &&
925 (aProvider->GetHyphensOption() == StyleHyphens::Auto ||
926 (aProvider->GetHyphensOption() == StyleHyphens::Manual &&
927 !!(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS)));
928 if (haveHyphenation) {
929 if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) {
930 aProvider->GetHyphenationBreaks(bufferRange, hyphenBuffer.Elements());
931 if (aProvider->GetHyphensOption() == StyleHyphens::Auto) {
932 ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer, &wordState);
933 }
934 } else {
935 haveHyphenation = false;
936 }
937 }
938
939 gfxFloat width = 0;
940 gfxFloat advance = 0;
941 // The number of space characters that can be trimmed or hang at a soft-wrap
942 uint32_t trimmableChars = 0;
943 // The amount of space removed by ignoring trimmableChars
944 gfxFloat trimmableAdvance = 0;
945 int32_t lastBreak = -1;
946 int32_t lastBreakTrimmableChars = -1;
947 gfxFloat lastBreakTrimmableAdvance = -1;
948 // Cache the last candidate break
949 int32_t lastCandidateBreak = -1;
950 int32_t lastCandidateBreakTrimmableChars = -1;
951 gfxFloat lastCandidateBreakTrimmableAdvance = -1;
952 bool lastCandidateBreakUsedHyphenation = false;
953 gfxBreakPriority lastCandidateBreakPriority = gfxBreakPriority::eNoBreak;
954 bool aborted = false;
955 uint32_t end = aStart + aMaxLength;
956 bool lastBreakUsedHyphenation = false;
957 Range ligatureRange(aStart, end);
958 ShrinkToLigatureBoundaries(&ligatureRange);
959
960 // We may need to move `i` backwards in the following loop, and re-scan
961 // part of the textrun; we'll use `rescanLimit` so we can tell when that
962 // is happening: if `i < rescanLimit` then we're rescanning.
963 uint32_t rescanLimit = aStart;
964 for (uint32_t i = aStart; i < end; ++i) {
965 if (i >= bufferRange.end) {
966 // Fetch more spacing and hyphenation data
967 uint32_t oldHyphenBufferLength = hyphenBuffer.Length();
968 bufferRange.start = i;
969 bufferRange.end =
970 std::min(aStart + aMaxLength, i + MEASUREMENT_BUFFER_SIZE);
971 // For spacing, we always overwrite the old data with the newly
972 // fetched one. However, for hyphenation, hyphenation data sometimes
973 // depends on the context in every word (if "hyphens: auto" is set).
974 // To ensure we get enough information between neighboring buffers,
975 // we grow the hyphenBuffer instead of overwrite it.
976 // NOTE that this means bufferRange does not correspond to the
977 // entire hyphenBuffer, but only to the most recently added portion.
978 // Therefore, we need to add the old length to hyphenBuffer.Elements()
979 // when getting more data.
980 if (haveSpacing) {
981 GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer);
982 }
983 if (haveHyphenation) {
984 if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) {
985 aProvider->GetHyphenationBreaks(
986 bufferRange, hyphenBuffer.Elements() + oldHyphenBufferLength);
987 if (aProvider->GetHyphensOption() == StyleHyphens::Auto) {
988 uint32_t prevMostRecentWordBoundary = wordState.mostRecentBoundary;
989 ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer,
990 &wordState);
991 // If the buffer boundary is in the middle of a word,
992 // we need to go back to the start of the current word.
993 // So, we can correct the wrong candidates that we set
994 // in the previous runs of the loop.
995 if (prevMostRecentWordBoundary < oldHyphenBufferLength) {
996 rescanLimit = i;
997 i = prevMostRecentWordBoundary - 1;
998 continue;
999 }
1000 }
1001 } else {
1002 haveHyphenation = false;
1003 }
1004 }
1005 }
1006
1007 // There can't be a word-wrap break opportunity at the beginning of the
1008 // line: if the width is too small for even one character to fit, it
1009 // could be the first and last break opportunity on the line, and that
1010 // would trigger an infinite loop.
1011 if (aSuppressBreak != eSuppressAllBreaks &&
1012 (aSuppressBreak != eSuppressInitialBreak || i > aStart)) {
1013 bool atNaturalBreak = mCharacterGlyphs[i].CanBreakBefore() == 1;
1014 // atHyphenationBreak indicates we're at a "soft" hyphen, where an extra
1015 // hyphen glyph will need to be painted. It is NOT set for breaks at an
1016 // explicit hyphen present in the text.
1017 bool atHyphenationBreak = !atNaturalBreak && haveHyphenation &&
1018 hyphenBuffer[i - aStart] > HyphenType::Explicit;
1019 bool atAutoHyphenWithManualHyphenInSameWord =
1020 atHyphenationBreak &&
1021 hyphenBuffer[i - aStart] == HyphenType::AutoWithManualInSameWord;
1022 bool atBreak = atNaturalBreak || atHyphenationBreak;
1023 bool wordWrapping = aCanWordWrap &&
1024 mCharacterGlyphs[i].IsClusterStart() &&
1025 *aBreakPriority <= gfxBreakPriority::eWordWrapBreak;
1026
1027 bool whitespaceWrapping = false;
1028 if (i > aStart) {
1029 // The spec says the breaking opportunity is *after* whitespace.
1030 auto const& g = mCharacterGlyphs[i - 1];
1031 whitespaceWrapping =
1032 aCanWhitespaceWrap &&
1033 (g.CharIsSpace() || g.CharIsTab() || g.CharIsNewline());
1034 }
1035
1036 if (atBreak || wordWrapping || whitespaceWrapping) {
1037 gfxFloat hyphenatedAdvance = advance;
1038 if (atHyphenationBreak) {
1039 hyphenatedAdvance += aProvider->GetHyphenWidth();
1040 }
1041
1042 if (lastBreak < 0 ||
1043 width + hyphenatedAdvance - trimmableAdvance <= aWidth) {
1044 // We can break here.
1045 lastBreak = i;
1046 lastBreakTrimmableChars = trimmableChars;
1047 lastBreakTrimmableAdvance = trimmableAdvance;
1048 lastBreakUsedHyphenation = atHyphenationBreak;
1049 *aBreakPriority = (atBreak || whitespaceWrapping)
1050 ? gfxBreakPriority::eNormalBreak
1051 : gfxBreakPriority::eWordWrapBreak;
1052 }
1053
1054 width += advance;
1055 advance = 0;
1056 if (width - trimmableAdvance > aWidth) {
1057 // No more text fits. Abort
1058 aborted = true;
1059 break;
1060 }
1061 // There are various kinds of break opportunities:
1062 // 1. word wrap break,
1063 // 2. natural break,
1064 // 3. manual hyphenation break,
1065 // 4. auto hyphenation break without any manual hyphenation
1066 // in the same word,
1067 // 5. auto hyphenation break with another manual hyphenation
1068 // in the same word.
1069 // Allow all of them except the last one to be a candidate.
1070 // So, we can ensure that we don't use an automatic
1071 // hyphenation opportunity within a word that contains another
1072 // manual hyphenation, unless it is the only choice.
1073 if (wordWrapping || !atAutoHyphenWithManualHyphenInSameWord) {
1074 lastCandidateBreak = lastBreak;
1075 lastCandidateBreakTrimmableChars = lastBreakTrimmableChars;
1076 lastCandidateBreakTrimmableAdvance = lastBreakTrimmableAdvance;
1077 lastCandidateBreakUsedHyphenation = lastBreakUsedHyphenation;
1078 lastCandidateBreakPriority = *aBreakPriority;
1079 }
1080 }
1081 }
1082
1083 // If we're re-scanning part of a word (to re-process potential
1084 // hyphenation types) then we don't want to accumulate widths again
1085 // for the characters that were already added to `advance`.
1086 if (i < rescanLimit) {
1087 continue;
1088 }
1089
1090 gfxFloat charAdvance;
1091 if (i >= ligatureRange.start && i < ligatureRange.end) {
1092 charAdvance = GetAdvanceForGlyphs(Range(i, i + 1));
1093 if (haveSpacing) {
1094 PropertyProvider::Spacing* space =
1095 &spacingBuffer[i - bufferRange.start];
1096 charAdvance += space->mBefore + space->mAfter;
1097 }
1098 } else {
1099 charAdvance = ComputePartialLigatureWidth(Range(i, i + 1), aProvider);
1100 }
1101
1102 advance += charAdvance;
1103 if (aTrimWhitespace || aWhitespaceCanHang) {
1104 if (mCharacterGlyphs[i].CharIsSpace()) {
1105 ++trimmableChars;
1106 trimmableAdvance += charAdvance;
1107 } else {
1108 trimmableAdvance = 0;
1109 trimmableChars = 0;
1110 }
1111 }
1112 }
1113
1114 if (!aborted) {
1115 width += advance;
1116 }
1117
1118 // There are three possibilities:
1119 // 1) all the text fit (width <= aWidth)
1120 // 2) some of the text fit up to a break opportunity (width > aWidth &&
1121 // lastBreak >= 0)
1122 // 3) none of the text fits before a break opportunity (width > aWidth &&
1123 // lastBreak < 0)
1124 uint32_t charsFit;
1125 bool usedHyphenation = false;
1126 if (width - trimmableAdvance <= aWidth) {
1127 charsFit = aMaxLength;
1128 } else if (lastBreak >= 0) {
1129 if (lastCandidateBreak >= 0 && lastCandidateBreak != lastBreak) {
1130 lastBreak = lastCandidateBreak;
1131 lastBreakTrimmableChars = lastCandidateBreakTrimmableChars;
1132 lastBreakTrimmableAdvance = lastCandidateBreakTrimmableAdvance;
1133 lastBreakUsedHyphenation = lastCandidateBreakUsedHyphenation;
1134 *aBreakPriority = lastCandidateBreakPriority;
1135 }
1136 charsFit = lastBreak - aStart;
1137 trimmableChars = lastBreakTrimmableChars;
1138 trimmableAdvance = lastBreakTrimmableAdvance;
1139 usedHyphenation = lastBreakUsedHyphenation;
1140 } else {
1141 charsFit = aMaxLength;
1142 }
1143
1144 if (aMetrics) {
1145 auto fitEnd = aStart + charsFit;
1146 // Initially, measure everything, so that our bounding box includes
1147 // any trimmable or hanging whitespace.
1148 *aMetrics = MeasureText(Range(aStart, fitEnd), aBoundingBoxType,
1149 aRefDrawTarget, aProvider);
1150 if (aTrimWhitespace || aWhitespaceCanHang) {
1151 // Measure trailing whitespace that is to be trimmed/hung.
1152 Metrics trimOrHangMetrics =
1153 MeasureText(Range(fitEnd - trimmableChars, fitEnd), aBoundingBoxType,
1154 aRefDrawTarget, aProvider);
1155 if (aTrimWhitespace) {
1156 aMetrics->mAdvanceWidth -= trimOrHangMetrics.mAdvanceWidth;
1157 } else if (aMetrics->mAdvanceWidth > aWidth) {
1158 // Restrict width of hanging whitespace so it doesn't overflow.
1159 aMetrics->mAdvanceWidth = std::max(
1160 aWidth, aMetrics->mAdvanceWidth - trimOrHangMetrics.mAdvanceWidth);
1161 }
1162 }
1163 }
1164 if (aTrimWhitespace) {
1165 *aTrimWhitespace = trimmableAdvance;
1166 }
1167 if (aUsedHyphenation) {
1168 *aUsedHyphenation = usedHyphenation;
1169 }
1170 if (aLastBreak && charsFit == aMaxLength) {
1171 if (lastBreak < 0) {
1172 *aLastBreak = UINT32_MAX;
1173 } else {
1174 *aLastBreak = lastBreak - aStart;
1175 }
1176 }
1177
1178 return charsFit;
1179 }
1180
GetAdvanceWidth(Range aRange,PropertyProvider * aProvider,PropertyProvider::Spacing * aSpacing) const1181 gfxFloat gfxTextRun::GetAdvanceWidth(
1182 Range aRange, PropertyProvider* aProvider,
1183 PropertyProvider::Spacing* aSpacing) const {
1184 NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range");
1185
1186 Range ligatureRange = aRange;
1187 ShrinkToLigatureBoundaries(&ligatureRange);
1188
1189 gfxFloat result = ComputePartialLigatureWidth(
1190 Range(aRange.start, ligatureRange.start), aProvider) +
1191 ComputePartialLigatureWidth(
1192 Range(ligatureRange.end, aRange.end), aProvider);
1193
1194 if (aSpacing) {
1195 aSpacing->mBefore = aSpacing->mAfter = 0;
1196 }
1197
1198 // Account for all remaining spacing here. This is more efficient than
1199 // processing it along with the glyphs.
1200 if (aProvider && (mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) {
1201 uint32_t i;
1202 AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer;
1203 if (spacingBuffer.AppendElements(aRange.Length(), fallible)) {
1204 GetAdjustedSpacing(this, ligatureRange, aProvider,
1205 spacingBuffer.Elements());
1206 for (i = 0; i < ligatureRange.Length(); ++i) {
1207 PropertyProvider::Spacing* space = &spacingBuffer[i];
1208 result += space->mBefore + space->mAfter;
1209 }
1210 if (aSpacing) {
1211 aSpacing->mBefore = spacingBuffer[0].mBefore;
1212 aSpacing->mAfter = spacingBuffer.LastElement().mAfter;
1213 }
1214 }
1215 }
1216
1217 return result + GetAdvanceForGlyphs(ligatureRange);
1218 }
1219
GetMinAdvanceWidth(Range aRange)1220 gfxFloat gfxTextRun::GetMinAdvanceWidth(Range aRange) {
1221 MOZ_ASSERT(aRange.end <= GetLength(), "Substring out of range");
1222
1223 Range ligatureRange = aRange;
1224 ShrinkToLigatureBoundaries(&ligatureRange);
1225
1226 gfxFloat result =
1227 std::max(ComputePartialLigatureWidth(
1228 Range(aRange.start, ligatureRange.start), nullptr),
1229 ComputePartialLigatureWidth(Range(ligatureRange.end, aRange.end),
1230 nullptr));
1231
1232 // Compute min advance width by assuming each grapheme cluster takes its own
1233 // line.
1234 gfxFloat clusterAdvance = 0;
1235 for (uint32_t i = ligatureRange.start; i < ligatureRange.end; ++i) {
1236 if (mCharacterGlyphs[i].CharIsSpace()) {
1237 // Skip space char to prevent its advance width contributing to the
1238 // result. That is, don't consider a space can be in its own line.
1239 continue;
1240 }
1241 clusterAdvance += GetAdvanceForGlyph(i);
1242 if (i + 1 == ligatureRange.end || IsClusterStart(i + 1)) {
1243 result = std::max(result, clusterAdvance);
1244 clusterAdvance = 0;
1245 }
1246 }
1247
1248 return result;
1249 }
1250
SetLineBreaks(Range aRange,bool aLineBreakBefore,bool aLineBreakAfter,gfxFloat * aAdvanceWidthDelta)1251 bool gfxTextRun::SetLineBreaks(Range aRange, bool aLineBreakBefore,
1252 bool aLineBreakAfter,
1253 gfxFloat* aAdvanceWidthDelta) {
1254 // Do nothing because our shaping does not currently take linebreaks into
1255 // account. There is no change in advance width.
1256 if (aAdvanceWidthDelta) {
1257 *aAdvanceWidthDelta = 0;
1258 }
1259 return false;
1260 }
1261
FindFirstGlyphRunContaining(uint32_t aOffset) const1262 uint32_t gfxTextRun::FindFirstGlyphRunContaining(uint32_t aOffset) const {
1263 NS_ASSERTION(aOffset <= GetLength(), "Bad offset looking for glyphrun");
1264 NS_ASSERTION(GetLength() == 0 ||
1265 (!mHasGlyphRunArray && mSingleGlyphRun.mFont) ||
1266 (mHasGlyphRunArray && mGlyphRunArray.Length() > 0),
1267 "non-empty text but no glyph runs present!");
1268 if (!mHasGlyphRunArray) {
1269 return 0;
1270 }
1271 if (aOffset == GetLength()) {
1272 return mGlyphRunArray.Length();
1273 }
1274 uint32_t start = 0;
1275 uint32_t end = mGlyphRunArray.Length();
1276 while (end - start > 1) {
1277 uint32_t mid = (start + end) / 2;
1278 if (mGlyphRunArray[mid].mCharacterOffset <= aOffset) {
1279 start = mid;
1280 } else {
1281 end = mid;
1282 }
1283 }
1284 NS_ASSERTION(mGlyphRunArray[start].mCharacterOffset <= aOffset,
1285 "Hmm, something went wrong, aOffset should have been found");
1286 return start;
1287 }
1288
AddGlyphRun(gfxFont * aFont,FontMatchType aMatchType,uint32_t aUTF16Offset,bool aForceNewRun,gfx::ShapedTextFlags aOrientation,bool aIsCJK)1289 void gfxTextRun::AddGlyphRun(gfxFont* aFont, FontMatchType aMatchType,
1290 uint32_t aUTF16Offset, bool aForceNewRun,
1291 gfx::ShapedTextFlags aOrientation, bool aIsCJK) {
1292 NS_ASSERTION(aFont, "adding glyph run for null font!");
1293 NS_ASSERTION(aOrientation != gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED,
1294 "mixed orientation should have been resolved");
1295 if (!aFont) {
1296 return;
1297 }
1298 if (!mHasGlyphRunArray) {
1299 // We don't currently have an array.
1300 if (!mSingleGlyphRun.mFont) {
1301 // This is the first glyph run: just store it directly.
1302 mSingleGlyphRun.SetProperties(aFont, aOrientation, aIsCJK, aMatchType);
1303 mSingleGlyphRun.mCharacterOffset = aUTF16Offset;
1304 return;
1305 }
1306 }
1307 uint32_t numGlyphRuns = mHasGlyphRunArray ? mGlyphRunArray.Length() : 1;
1308 if (!aForceNewRun && numGlyphRuns > 0) {
1309 GlyphRun* lastGlyphRun = mHasGlyphRunArray
1310 ? &mGlyphRunArray[numGlyphRuns - 1]
1311 : &mSingleGlyphRun;
1312
1313 NS_ASSERTION(lastGlyphRun->mCharacterOffset <= aUTF16Offset,
1314 "Glyph runs out of order (and run not forced)");
1315
1316 // Don't append a run if the font is already the one we want
1317 if (lastGlyphRun->Matches(aFont, aOrientation, aIsCJK, aMatchType)) {
1318 return;
1319 }
1320
1321 // If the offset has not changed, avoid leaving a zero-length run
1322 // by overwriting the last entry instead of appending...
1323 if (lastGlyphRun->mCharacterOffset == aUTF16Offset) {
1324 // ...except that if the run before the last entry had the same
1325 // font as the new one wants, merge with it instead of creating
1326 // adjacent runs with the same font
1327 if (numGlyphRuns > 1 && mGlyphRunArray[numGlyphRuns - 2].Matches(
1328 aFont, aOrientation, aIsCJK, aMatchType)) {
1329 mGlyphRunArray.TruncateLength(numGlyphRuns - 1);
1330 if (mGlyphRunArray.Length() == 1) {
1331 ConvertFromGlyphRunArray();
1332 }
1333 return;
1334 }
1335
1336 lastGlyphRun->SetProperties(aFont, aOrientation, aIsCJK, aMatchType);
1337 return;
1338 }
1339 }
1340
1341 NS_ASSERTION(
1342 aForceNewRun || numGlyphRuns > 0 || aUTF16Offset == 0,
1343 "First run doesn't cover the first character (and run not forced)?");
1344
1345 if (!mHasGlyphRunArray) {
1346 ConvertToGlyphRunArray();
1347 }
1348
1349 GlyphRun* glyphRun = mGlyphRunArray.AppendElement();
1350 glyphRun->SetProperties(aFont, aOrientation, aIsCJK, aMatchType);
1351 glyphRun->mCharacterOffset = aUTF16Offset;
1352 }
1353
SortGlyphRuns()1354 void gfxTextRun::SortGlyphRuns() {
1355 if (!mHasGlyphRunArray) {
1356 return;
1357 }
1358
1359 // We should never have an empty or one-element array here; if there's only
1360 // one glyphrun, it should be stored directly in the textrun without using
1361 // an array at all.
1362 MOZ_ASSERT(mGlyphRunArray.Length() > 1);
1363
1364 AutoTArray<GlyphRun, 16> runs(std::move(mGlyphRunArray));
1365 GlyphRunOffsetComparator comp;
1366 runs.Sort(comp);
1367
1368 // Now copy back, coalescing adjacent glyph runs that have the same
1369 // properties.
1370 mGlyphRunArray.Clear();
1371 GlyphRun* prevRun = nullptr;
1372 for (auto& run : runs) {
1373 // A GlyphRun with the same font and orientation as the previous can
1374 // just be skipped; the last GlyphRun will cover its character range.
1375 MOZ_ASSERT(run.mFont != nullptr);
1376 if (!prevRun || !prevRun->Matches(run.mFont, run.mOrientation, run.mIsCJK,
1377 run.mMatchType)) {
1378 // If two font runs have the same character offset, Sort() will have
1379 // randomized their order!
1380 MOZ_ASSERT(prevRun == nullptr ||
1381 prevRun->mCharacterOffset < run.mCharacterOffset,
1382 "Two fonts for the same run, glyph indices unreliable");
1383 prevRun = mGlyphRunArray.AppendElement(std::move(run));
1384 }
1385 }
1386
1387 MOZ_ASSERT(mGlyphRunArray.Length() > 0);
1388 if (mGlyphRunArray.Length() == 1) {
1389 ConvertFromGlyphRunArray();
1390 }
1391 }
1392
1393 // Note that SanitizeGlyphRuns scans all glyph runs in the textrun;
1394 // therefore we only call it once, at the end of textrun construction,
1395 // NOT incrementally as each glyph run is added (bug 680402).
SanitizeGlyphRuns()1396 void gfxTextRun::SanitizeGlyphRuns() {
1397 if (!mHasGlyphRunArray) {
1398 return;
1399 }
1400
1401 MOZ_ASSERT(mGlyphRunArray.Length() > 1);
1402
1403 // If any glyph run starts with ligature-continuation characters, we need to
1404 // advance it to the first "real" character to avoid drawing partial ligature
1405 // glyphs from wrong font (seen with U+FEFF in reftest 474417-1, as Core Text
1406 // eliminates the glyph, which makes it appear as if a ligature has been
1407 // formed)
1408 int32_t i, lastRunIndex = mGlyphRunArray.Length() - 1;
1409 const CompressedGlyph* charGlyphs = mCharacterGlyphs;
1410 for (i = lastRunIndex; i >= 0; --i) {
1411 GlyphRun& run = mGlyphRunArray[i];
1412 while (charGlyphs[run.mCharacterOffset].IsLigatureContinuation() &&
1413 run.mCharacterOffset < GetLength()) {
1414 run.mCharacterOffset++;
1415 }
1416 // if the run has become empty, eliminate it
1417 if ((i < lastRunIndex &&
1418 run.mCharacterOffset >= mGlyphRunArray[i + 1].mCharacterOffset) ||
1419 (i == lastRunIndex && run.mCharacterOffset == GetLength())) {
1420 mGlyphRunArray.RemoveElementAt(i);
1421 --lastRunIndex;
1422 }
1423 }
1424
1425 MOZ_ASSERT(mGlyphRunArray.Length() > 0);
1426 if (mGlyphRunArray.Length() == 1) {
1427 ConvertFromGlyphRunArray();
1428 }
1429 }
1430
CopyGlyphDataFrom(gfxShapedWord * aShapedWord,uint32_t aOffset)1431 void gfxTextRun::CopyGlyphDataFrom(gfxShapedWord* aShapedWord,
1432 uint32_t aOffset) {
1433 uint32_t wordLen = aShapedWord->GetLength();
1434 NS_ASSERTION(aOffset + wordLen <= GetLength(),
1435 "word overruns end of textrun!");
1436
1437 CompressedGlyph* charGlyphs = GetCharacterGlyphs();
1438 const CompressedGlyph* wordGlyphs = aShapedWord->GetCharacterGlyphs();
1439 if (aShapedWord->HasDetailedGlyphs()) {
1440 for (uint32_t i = 0; i < wordLen; ++i, ++aOffset) {
1441 const CompressedGlyph& g = wordGlyphs[i];
1442 if (!g.IsSimpleGlyph()) {
1443 const DetailedGlyph* details =
1444 g.GetGlyphCount() > 0 ? aShapedWord->GetDetailedGlyphs(i) : nullptr;
1445 SetDetailedGlyphs(aOffset, g.GetGlyphCount(), details);
1446 }
1447 charGlyphs[aOffset] = g;
1448 }
1449 } else {
1450 memcpy(charGlyphs + aOffset, wordGlyphs, wordLen * sizeof(CompressedGlyph));
1451 }
1452 }
1453
CopyGlyphDataFrom(gfxTextRun * aSource,Range aRange,uint32_t aDest)1454 void gfxTextRun::CopyGlyphDataFrom(gfxTextRun* aSource, Range aRange,
1455 uint32_t aDest) {
1456 NS_ASSERTION(aRange.end <= aSource->GetLength(),
1457 "Source substring out of range");
1458 NS_ASSERTION(aDest + aRange.Length() <= GetLength(),
1459 "Destination substring out of range");
1460
1461 if (aSource->mDontSkipDrawing) {
1462 mDontSkipDrawing = true;
1463 }
1464
1465 // Copy base glyph data, and DetailedGlyph data where present
1466 const CompressedGlyph* srcGlyphs = aSource->mCharacterGlyphs + aRange.start;
1467 CompressedGlyph* dstGlyphs = mCharacterGlyphs + aDest;
1468 for (uint32_t i = 0; i < aRange.Length(); ++i) {
1469 CompressedGlyph g = srcGlyphs[i];
1470 g.SetCanBreakBefore(!g.IsClusterStart()
1471 ? CompressedGlyph::FLAG_BREAK_TYPE_NONE
1472 : dstGlyphs[i].CanBreakBefore());
1473 if (!g.IsSimpleGlyph()) {
1474 uint32_t count = g.GetGlyphCount();
1475 if (count > 0) {
1476 // DetailedGlyphs allocation is infallible, so this should never be
1477 // null unless the source textrun is somehow broken.
1478 DetailedGlyph* src = aSource->GetDetailedGlyphs(i + aRange.start);
1479 MOZ_ASSERT(src, "missing DetailedGlyphs?");
1480 if (src) {
1481 DetailedGlyph* dst = AllocateDetailedGlyphs(i + aDest, count);
1482 ::memcpy(dst, src, count * sizeof(DetailedGlyph));
1483 } else {
1484 g.SetMissing();
1485 }
1486 }
1487 }
1488 dstGlyphs[i] = g;
1489 }
1490
1491 // Copy glyph runs
1492 GlyphRunIterator iter(aSource, aRange);
1493 #ifdef DEBUG
1494 GlyphRun* prevRun = nullptr;
1495 #endif
1496 while (iter.NextRun()) {
1497 gfxFont* font = iter.GetGlyphRun()->mFont;
1498 MOZ_ASSERT(!prevRun || !prevRun->Matches(iter.GetGlyphRun()->mFont,
1499 iter.GetGlyphRun()->mOrientation,
1500 iter.GetGlyphRun()->mIsCJK,
1501 FontMatchType::Kind::kUnspecified),
1502 "Glyphruns not coalesced?");
1503 #ifdef DEBUG
1504 prevRun = const_cast<GlyphRun*>(iter.GetGlyphRun());
1505 uint32_t end = iter.GetStringEnd();
1506 #endif
1507 uint32_t start = iter.GetStringStart();
1508
1509 // These used to be NS_ASSERTION()s, but WARNING is more appropriate.
1510 // Although it's unusual (and not desirable), it's possible for us to assign
1511 // different fonts to a base character and a following diacritic.
1512 // Example on OSX 10.5/10.6 with default fonts installed:
1513 // data:text/html,<p style="font-family:helvetica, arial, sans-serif;">
1514 // &%23x043E;&%23x0486;&%23x20;&%23x043E;&%23x0486;
1515 // This means the rendering of the cluster will probably not be very good,
1516 // but it's the best we can do for now if the specified font only covered
1517 // the initial base character and not its applied marks.
1518 NS_WARNING_ASSERTION(aSource->IsClusterStart(start),
1519 "Started font run in the middle of a cluster");
1520 NS_WARNING_ASSERTION(
1521 end == aSource->GetLength() || aSource->IsClusterStart(end),
1522 "Ended font run in the middle of a cluster");
1523
1524 AddGlyphRun(font, iter.GetGlyphRun()->mMatchType,
1525 start - aRange.start + aDest, false,
1526 iter.GetGlyphRun()->mOrientation, iter.GetGlyphRun()->mIsCJK);
1527 }
1528 }
1529
ClearGlyphsAndCharacters()1530 void gfxTextRun::ClearGlyphsAndCharacters() {
1531 ResetGlyphRuns();
1532 memset(reinterpret_cast<char*>(mCharacterGlyphs), 0,
1533 mLength * sizeof(CompressedGlyph));
1534 mDetailedGlyphs = nullptr;
1535 }
1536
SetSpaceGlyph(gfxFont * aFont,DrawTarget * aDrawTarget,uint32_t aCharIndex,gfx::ShapedTextFlags aOrientation)1537 void gfxTextRun::SetSpaceGlyph(gfxFont* aFont, DrawTarget* aDrawTarget,
1538 uint32_t aCharIndex,
1539 gfx::ShapedTextFlags aOrientation) {
1540 if (SetSpaceGlyphIfSimple(aFont, aCharIndex, ' ', aOrientation)) {
1541 return;
1542 }
1543
1544 aFont->InitWordCache();
1545 static const uint8_t space = ' ';
1546 gfx::ShapedTextFlags flags =
1547 gfx::ShapedTextFlags::TEXT_IS_8BIT | aOrientation;
1548 bool vertical =
1549 !!(GetFlags() & gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT);
1550 gfxFontShaper::RoundingFlags roundingFlags =
1551 aFont->GetRoundOffsetsToPixels(aDrawTarget);
1552 gfxShapedWord* sw = aFont->GetShapedWord(
1553 aDrawTarget, &space, 1, gfxShapedWord::HashMix(0, ' '), Script::LATIN,
1554 /* aLanguage = */ nullptr, vertical, mAppUnitsPerDevUnit, flags,
1555 roundingFlags, nullptr);
1556 if (sw) {
1557 const GlyphRun* prevRun = TrailingGlyphRun();
1558 bool isCJK = prevRun && prevRun->mFont == aFont &&
1559 prevRun->mOrientation == aOrientation
1560 ? prevRun->mIsCJK
1561 : false;
1562 AddGlyphRun(aFont, FontMatchType::Kind::kUnspecified, aCharIndex, false,
1563 aOrientation, isCJK);
1564 CopyGlyphDataFrom(sw, aCharIndex);
1565 GetCharacterGlyphs()[aCharIndex].SetIsSpace();
1566 }
1567 }
1568
SetSpaceGlyphIfSimple(gfxFont * aFont,uint32_t aCharIndex,char16_t aSpaceChar,gfx::ShapedTextFlags aOrientation)1569 bool gfxTextRun::SetSpaceGlyphIfSimple(gfxFont* aFont, uint32_t aCharIndex,
1570 char16_t aSpaceChar,
1571 gfx::ShapedTextFlags aOrientation) {
1572 uint32_t spaceGlyph = aFont->GetSpaceGlyph();
1573 if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) {
1574 return false;
1575 }
1576
1577 gfxFont::Orientation fontOrientation =
1578 (aOrientation & gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT)
1579 ? nsFontMetrics::eVertical
1580 : nsFontMetrics::eHorizontal;
1581 uint32_t spaceWidthAppUnits = NS_lroundf(
1582 aFont->GetMetrics(fontOrientation).spaceWidth * mAppUnitsPerDevUnit);
1583 if (!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) {
1584 return false;
1585 }
1586
1587 const GlyphRun* prevRun = TrailingGlyphRun();
1588 bool isCJK = prevRun && prevRun->mFont == aFont &&
1589 prevRun->mOrientation == aOrientation
1590 ? prevRun->mIsCJK
1591 : false;
1592 AddGlyphRun(aFont, FontMatchType::Kind::kUnspecified, aCharIndex, false,
1593 aOrientation, isCJK);
1594 CompressedGlyph g =
1595 CompressedGlyph::MakeSimpleGlyph(spaceWidthAppUnits, spaceGlyph);
1596 if (aSpaceChar == ' ') {
1597 g.SetIsSpace();
1598 }
1599 GetCharacterGlyphs()[aCharIndex] = g;
1600 return true;
1601 }
1602
FetchGlyphExtents(DrawTarget * aRefDrawTarget)1603 void gfxTextRun::FetchGlyphExtents(DrawTarget* aRefDrawTarget) {
1604 bool needsGlyphExtents = NeedsGlyphExtents(this);
1605 if (!needsGlyphExtents && !mDetailedGlyphs) return;
1606
1607 uint32_t runCount;
1608 const GlyphRun* glyphRuns = GetGlyphRuns(&runCount);
1609 CompressedGlyph* charGlyphs = mCharacterGlyphs;
1610 for (uint32_t i = 0; i < runCount; ++i) {
1611 const GlyphRun& run = glyphRuns[i];
1612 gfxFont* font = run.mFont;
1613 if (MOZ_UNLIKELY(font->GetStyle()->AdjustedSizeMustBeZero())) {
1614 continue;
1615 }
1616
1617 uint32_t start = run.mCharacterOffset;
1618 uint32_t end =
1619 i + 1 < runCount ? glyphRuns[i + 1].mCharacterOffset : GetLength();
1620 uint32_t j;
1621 gfxGlyphExtents* extents =
1622 font->GetOrCreateGlyphExtents(mAppUnitsPerDevUnit);
1623
1624 for (j = start; j < end; ++j) {
1625 const gfxTextRun::CompressedGlyph* glyphData = &charGlyphs[j];
1626 if (glyphData->IsSimpleGlyph()) {
1627 // If we're in speed mode, don't set up glyph extents here; we'll
1628 // just return "optimistic" glyph bounds later
1629 if (needsGlyphExtents) {
1630 uint32_t glyphIndex = glyphData->GetSimpleGlyph();
1631 if (!extents->IsGlyphKnown(glyphIndex)) {
1632 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
1633 ++gGlyphExtentsSetupEagerSimple;
1634 #endif
1635 font->SetupGlyphExtents(aRefDrawTarget, glyphIndex, false, extents);
1636 }
1637 }
1638 } else if (!glyphData->IsMissing()) {
1639 uint32_t glyphCount = glyphData->GetGlyphCount();
1640 if (glyphCount == 0) {
1641 continue;
1642 }
1643 const gfxTextRun::DetailedGlyph* details = GetDetailedGlyphs(j);
1644 if (!details) {
1645 continue;
1646 }
1647 for (uint32_t k = 0; k < glyphCount; ++k, ++details) {
1648 uint32_t glyphIndex = details->mGlyphID;
1649 if (!extents->IsGlyphKnownWithTightExtents(glyphIndex)) {
1650 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
1651 ++gGlyphExtentsSetupEagerTight;
1652 #endif
1653 font->SetupGlyphExtents(aRefDrawTarget, glyphIndex, true, extents);
1654 }
1655 }
1656 }
1657 }
1658 }
1659 }
1660
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)1661 size_t gfxTextRun::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) {
1662 // The second arg is how much gfxTextRun::AllocateStorage would have
1663 // allocated.
1664 size_t total = mHasGlyphRunArray
1665 ? mGlyphRunArray.ShallowSizeOfExcludingThis(aMallocSizeOf)
1666 : 0;
1667
1668 if (mDetailedGlyphs) {
1669 total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf);
1670 }
1671
1672 return total;
1673 }
1674
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)1675 size_t gfxTextRun::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
1676 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
1677 }
1678
1679 #ifdef DEBUG_FRAME_DUMP
Dump(FILE * out)1680 void gfxTextRun::Dump(FILE* out) {
1681 # define APPEND_FLAG(string_, enum_, field_, flag_) \
1682 if (field_ & enum_::flag_) { \
1683 string_.AppendPrintf(remaining != field_ ? " %s" : "%s", #flag_); \
1684 remaining &= ~enum_::flag_; \
1685 }
1686 # define APPEND_FLAGS(string_, enum_, field_, flags_) \
1687 { \
1688 auto remaining = field_; \
1689 MOZ_FOR_EACH(APPEND_FLAG, (string_, enum_, field_, ), flags_) \
1690 if (int(remaining)) { \
1691 string_.AppendPrintf(" %s(0x%0x)", #enum_, int(remaining)); \
1692 } \
1693 }
1694
1695 nsCString flagsString;
1696 ShapedTextFlags orient = mFlags & ShapedTextFlags::TEXT_ORIENT_MASK;
1697 ShapedTextFlags otherFlags = mFlags & ~ShapedTextFlags::TEXT_ORIENT_MASK;
1698 APPEND_FLAGS(flagsString, ShapedTextFlags, otherFlags,
1699 (TEXT_IS_RTL, TEXT_ENABLE_SPACING, TEXT_IS_8BIT,
1700 TEXT_ENABLE_HYPHEN_BREAKS, TEXT_NEED_BOUNDING_BOX,
1701 TEXT_DISABLE_OPTIONAL_LIGATURES, TEXT_OPTIMIZE_SPEED,
1702 TEXT_HIDE_CONTROL_CHARACTERS, TEXT_TRAILING_ARABICCHAR,
1703 TEXT_INCOMING_ARABICCHAR, TEXT_USE_MATH_SCRIPT))
1704
1705 if (orient != ShapedTextFlags::TEXT_ORIENT_HORIZONTAL &&
1706 !flagsString.IsEmpty()) {
1707 flagsString += ' ';
1708 }
1709
1710 switch (orient) {
1711 case ShapedTextFlags::TEXT_ORIENT_HORIZONTAL:
1712 break;
1713 case ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT:
1714 flagsString += "TEXT_ORIENT_VERTICAL_UPRIGHT";
1715 break;
1716 case ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT:
1717 flagsString += "TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT";
1718 break;
1719 case ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED:
1720 flagsString += "TEXT_ORIENT_VERTICAL_MIXED";
1721 break;
1722 case ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT:
1723 flagsString += "TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT";
1724 break;
1725 default:
1726 flagsString.AppendPrintf("UNKNOWN_TEXT_ORIENT_MASK(0x%0x)", int(orient));
1727 break;
1728 }
1729
1730 nsCString flags2String;
1731 APPEND_FLAGS(
1732 flags2String, nsTextFrameUtils::Flags, mFlags2,
1733 (HasTab, HasShy, DontSkipDrawingForPendingUserFonts, IsSimpleFlow,
1734 IncomingWhitespace, TrailingWhitespace, CompressedLeadingWhitespace,
1735 NoBreaks, IsTransformed, HasTrailingBreak, IsSingleCharMi,
1736 MightHaveGlyphChanges, RunSizeAccounted))
1737
1738 # undef APPEND_FLAGS
1739 # undef APPEND_FLAG
1740
1741 nsAutoCString lang;
1742 mFontGroup->Language()->ToUTF8String(lang);
1743 fprintf(out, "gfxTextRun@%p (length %u) [%s] [%s] [%s]\n", this, mLength,
1744 flagsString.get(), flags2String.get(), lang.get());
1745
1746 uint32_t numGlyphRuns;
1747 const GlyphRun* glyphRuns = GetGlyphRuns(&numGlyphRuns);
1748 fprintf(out, " Glyph runs:\n");
1749 for (uint32_t i = 0; i < numGlyphRuns; ++i) {
1750 gfxFont* font = glyphRuns[i].mFont;
1751 const gfxFontStyle* style = font->GetStyle();
1752 nsAutoString styleString;
1753 nsStyleUtil::AppendFontSlantStyle(style->style, styleString);
1754 fprintf(out, " [%d] offset=%d %s %f/%g/%s\n", i,
1755 glyphRuns[i].mCharacterOffset, font->GetName().get(), style->size,
1756 style->weight.ToFloat(), NS_ConvertUTF16toUTF8(styleString).get());
1757 }
1758
1759 fprintf(out, " Glyphs:\n");
1760 for (uint32_t i = 0; i < mLength; ++i) {
1761 auto glyphData = GetCharacterGlyphs()[i];
1762
1763 nsCString line;
1764 line.AppendPrintf(" [%d] 0x%p %s", i, GetCharacterGlyphs() + i,
1765 glyphData.IsSimpleGlyph() ? "simple" : "detailed");
1766
1767 if (glyphData.IsSimpleGlyph()) {
1768 line.AppendPrintf(" id=%d adv=%d", glyphData.GetSimpleGlyph(),
1769 glyphData.GetSimpleAdvance());
1770 } else {
1771 uint32_t count = glyphData.GetGlyphCount();
1772 if (count) {
1773 line += " ids=";
1774 for (uint32_t j = 0; j < count; j++) {
1775 line.AppendPrintf(j ? ",%d" : "%d", GetDetailedGlyphs(i)[j].mGlyphID);
1776 }
1777 line += " advs=";
1778 for (uint32_t j = 0; j < count; j++) {
1779 line.AppendPrintf(j ? ",%d" : "%d", GetDetailedGlyphs(i)[j].mAdvance);
1780 }
1781 line += " offsets=";
1782 for (uint32_t j = 0; j < count; j++) {
1783 auto offset = GetDetailedGlyphs(i)[j].mOffset;
1784 line.AppendPrintf(j ? ",(%g,%g)" : "(%g,%g)", offset.x, offset.y);
1785 }
1786 } else {
1787 line += " (no glyphs)";
1788 }
1789 }
1790
1791 if (glyphData.CharIsSpace()) {
1792 line += " CHAR_IS_SPACE";
1793 }
1794 if (glyphData.CharIsTab()) {
1795 line += " CHAR_IS_TAB";
1796 }
1797 if (glyphData.CharIsNewline()) {
1798 line += " CHAR_IS_NEWLINE";
1799 }
1800 if (glyphData.CharIsFormattingControl()) {
1801 line += " CHAR_IS_FORMATTING_CONTROL";
1802 }
1803 if (glyphData.CharTypeFlags() &
1804 CompressedGlyph::FLAG_CHAR_NO_EMPHASIS_MARK) {
1805 line += " CHAR_NO_EMPHASIS_MARK";
1806 }
1807
1808 if (!glyphData.IsSimpleGlyph()) {
1809 if (!glyphData.IsMissing()) {
1810 line += " NOT_MISSING";
1811 }
1812 if (!glyphData.IsClusterStart()) {
1813 line += " NOT_IS_CLUSTER_START";
1814 }
1815 if (!glyphData.IsLigatureGroupStart()) {
1816 line += " NOT_LIGATURE_GROUP_START";
1817 }
1818 }
1819
1820 switch (glyphData.CanBreakBefore()) {
1821 case CompressedGlyph::FLAG_BREAK_TYPE_NORMAL:
1822 line += " BREAK_TYPE_NORMAL";
1823 break;
1824 case CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN:
1825 line += " BREAK_TYPE_HYPHEN";
1826 break;
1827 }
1828
1829 fprintf(out, "%s\n", line.get());
1830 }
1831 }
1832 #endif
1833
gfxFontGroup(const StyleFontFamilyList & aFontFamilyList,const gfxFontStyle * aStyle,nsAtom * aLanguage,bool aExplicitLanguage,gfxTextPerfMetrics * aTextPerf,FontMatchingStats * aFontMatchingStats,gfxUserFontSet * aUserFontSet,gfxFloat aDevToCssSize)1834 gfxFontGroup::gfxFontGroup(const StyleFontFamilyList& aFontFamilyList,
1835 const gfxFontStyle* aStyle, nsAtom* aLanguage,
1836 bool aExplicitLanguage,
1837 gfxTextPerfMetrics* aTextPerf,
1838 FontMatchingStats* aFontMatchingStats,
1839 gfxUserFontSet* aUserFontSet, gfxFloat aDevToCssSize)
1840 : mFamilyList(aFontFamilyList),
1841 mStyle(*aStyle),
1842 mLanguage(aLanguage),
1843 mUnderlineOffset(UNDERLINE_OFFSET_NOT_SET),
1844 mHyphenWidth(-1),
1845 mDevToCssSize(aDevToCssSize),
1846 mUserFontSet(aUserFontSet),
1847 mTextPerf(aTextPerf),
1848 mFontMatchingStats(aFontMatchingStats),
1849 mLastPrefLang(eFontPrefLang_Western),
1850 mPageLang(gfxPlatformFontList::GetFontPrefLangFor(aLanguage)),
1851 mLastPrefFirstFont(false),
1852 mSkipDrawing(false),
1853 mExplicitLanguage(aExplicitLanguage) {
1854 // We don't use SetUserFontSet() here, as we want to unconditionally call
1855 // BuildFontList() rather than only do UpdateUserFonts() if it changed.
1856 mCurrGeneration = GetGeneration();
1857 BuildFontList();
1858 }
1859
~gfxFontGroup()1860 gfxFontGroup::~gfxFontGroup() {
1861 // Should not be dropped by stylo
1862 MOZ_ASSERT(NS_IsMainThread());
1863 }
1864
BuildFontList()1865 void gfxFontGroup::BuildFontList() {
1866 // initialize fonts in the font family list
1867 AutoTArray<FamilyAndGeneric, 10> fonts;
1868 gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
1869
1870 // lookup fonts in the fontlist
1871 for (const StyleSingleFontFamily& name : mFamilyList.list.AsSpan()) {
1872 if (name.IsFamilyName()) {
1873 const auto& familyName = name.AsFamilyName();
1874 AddPlatformFont(nsAtomCString(familyName.name.AsAtom()),
1875 familyName.syntax == StyleFontFamilyNameSyntax::Quoted,
1876 fonts);
1877 } else {
1878 MOZ_ASSERT(name.IsGeneric());
1879 if (mFirstGeneric == StyleGenericFontFamily::None) {
1880 mFirstGeneric = name.AsGeneric();
1881 }
1882 pfl->AddGenericFonts(name.AsGeneric(), mLanguage, fonts);
1883 if (mTextPerf) {
1884 mTextPerf->current.genericLookups++;
1885 }
1886 }
1887 }
1888
1889 if (mFontMatchingStats) {
1890 for (const auto& f : fonts) {
1891 nsCString key; // not nsAutoCString, as the assignment to it won't copy
1892 // characters, it'll just share the string buffer
1893 FontVisibility visibility;
1894 if (f.mFamily.mIsShared) {
1895 key = f.mFamily.mShared->Key().AsString(pfl->SharedFontList());
1896 visibility = f.mFamily.mShared->Visibility();
1897 } else {
1898 key = f.mFamily.mUnshared->Name();
1899 ToLowerCase(key);
1900 visibility = f.mFamily.mUnshared->Visibility();
1901 }
1902 if (mFontMatchingStats->mFamilyNames.EnsureInserted(key)) {
1903 switch (visibility) {
1904 case FontVisibility::Base:
1905 mFontMatchingStats->mBaseFonts++;
1906 break;
1907 case FontVisibility::LangPack:
1908 mFontMatchingStats->mLangPackFonts++;
1909 break;
1910 case FontVisibility::User:
1911 mFontMatchingStats->mUserFonts++;
1912 break;
1913 case FontVisibility::Webfont:
1914 mFontMatchingStats->mWebFonts++;
1915 break;
1916 default:
1917 break;
1918 }
1919 }
1920 }
1921 }
1922
1923 // if necessary, append fallback generic onto the end
1924 if (mFamilyList.fallback != StyleGenericFontFamily::None &&
1925 !mFamilyList.ContainsFallback()) {
1926 pfl->AddGenericFonts(mFamilyList.fallback, mLanguage, fonts);
1927 if (mTextPerf) {
1928 mTextPerf->current.genericLookups++;
1929 }
1930 }
1931
1932 // build the fontlist from the specified families
1933 for (const auto& f : fonts) {
1934 if (f.mFamily.mIsShared) {
1935 AddFamilyToFontList(f.mFamily.mShared, f.mGeneric);
1936 } else {
1937 AddFamilyToFontList(f.mFamily.mUnshared, f.mGeneric);
1938 }
1939 }
1940
1941 mFontListGeneration = pfl->GetGeneration();
1942 }
1943
AddPlatformFont(const nsACString & aName,bool aQuotedName,nsTArray<FamilyAndGeneric> & aFamilyList)1944 void gfxFontGroup::AddPlatformFont(const nsACString& aName, bool aQuotedName,
1945 nsTArray<FamilyAndGeneric>& aFamilyList) {
1946 // First, look up in the user font set...
1947 // If the fontSet matches the family, we must not look for a platform
1948 // font of the same name, even if we fail to actually get a fontEntry
1949 // here; we'll fall back to the next name in the CSS font-family list.
1950 if (mUserFontSet) {
1951 // Add userfonts to the fontlist whether already loaded
1952 // or not. Loading is initiated during font matching.
1953 gfxFontFamily* family = mUserFontSet->LookupFamily(aName);
1954 if (family) {
1955 aFamilyList.AppendElement(family);
1956 return;
1957 }
1958 }
1959
1960 // Not known in the user font set ==> check system fonts
1961 gfxPlatformFontList::PlatformFontList()->FindAndAddFamilies(
1962 StyleGenericFontFamily::None, aName, &aFamilyList,
1963 aQuotedName ? gfxPlatformFontList::FindFamiliesFlags::eQuotedFamilyName
1964 : gfxPlatformFontList::FindFamiliesFlags(0),
1965 &mStyle, mLanguage.get(), mDevToCssSize);
1966 }
1967
AddFamilyToFontList(gfxFontFamily * aFamily,StyleGenericFontFamily aGeneric)1968 void gfxFontGroup::AddFamilyToFontList(gfxFontFamily* aFamily,
1969 StyleGenericFontFamily aGeneric) {
1970 if (!aFamily) {
1971 MOZ_ASSERT_UNREACHABLE("don't try to add a null font family!");
1972 return;
1973 }
1974 AutoTArray<gfxFontEntry*, 4> fontEntryList;
1975 aFamily->FindAllFontsForStyle(mStyle, fontEntryList);
1976 // add these to the fontlist
1977 for (gfxFontEntry* fe : fontEntryList) {
1978 if (!HasFont(fe)) {
1979 FamilyFace ff(aFamily, fe, aGeneric);
1980 if (fe->mIsUserFontContainer) {
1981 ff.CheckState(mSkipDrawing);
1982 }
1983 mFonts.AppendElement(ff);
1984 }
1985 }
1986 // for a family marked as "check fallback faces", only mark the last
1987 // entry so that fallbacks for a family are only checked once
1988 if (aFamily->CheckForFallbackFaces() && !fontEntryList.IsEmpty() &&
1989 !mFonts.IsEmpty()) {
1990 mFonts.LastElement().SetCheckForFallbackFaces();
1991 }
1992 }
1993
AddFamilyToFontList(fontlist::Family * aFamily,StyleGenericFontFamily aGeneric)1994 void gfxFontGroup::AddFamilyToFontList(fontlist::Family* aFamily,
1995 StyleGenericFontFamily aGeneric) {
1996 gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
1997 if (!aFamily->IsInitialized()) {
1998 if (!NS_IsMainThread()) {
1999 // If we need to initialize a Family record, but we're on a style
2000 // worker thread, we have to defer it.
2001 ServoStyleSet* set = ServoStyleSet::Current();
2002 MOZ_ASSERT(set);
2003 set->AppendTask(PostTraversalTask::InitializeFamily(aFamily));
2004 set->AppendTask(PostTraversalTask::FontInfoUpdate(set));
2005 return;
2006 }
2007 if (!pfl->InitializeFamily(aFamily)) {
2008 return;
2009 }
2010 }
2011 AutoTArray<fontlist::Face*, 4> faceList;
2012 aFamily->FindAllFacesForStyle(pfl->SharedFontList(), mStyle, faceList);
2013 for (auto face : faceList) {
2014 gfxFontEntry* fe = pfl->GetOrCreateFontEntry(face, aFamily);
2015 if (fe && !HasFont(fe)) {
2016 FamilyFace ff(aFamily, fe, aGeneric);
2017 mFonts.AppendElement(ff);
2018 }
2019 }
2020 }
2021
HasFont(const gfxFontEntry * aFontEntry)2022 bool gfxFontGroup::HasFont(const gfxFontEntry* aFontEntry) {
2023 for (auto& f : mFonts) {
2024 if (f.FontEntry() == aFontEntry) {
2025 return true;
2026 }
2027 }
2028 return false;
2029 }
2030
GetFontAt(int32_t i,uint32_t aCh,bool * aLoading)2031 gfxFont* gfxFontGroup::GetFontAt(int32_t i, uint32_t aCh, bool* aLoading) {
2032 if (uint32_t(i) >= mFonts.Length()) {
2033 return nullptr;
2034 }
2035
2036 FamilyFace& ff = mFonts[i];
2037 if (ff.IsInvalid() || ff.IsLoading()) {
2038 return nullptr;
2039 }
2040
2041 gfxFont* font = ff.Font();
2042 if (!font) {
2043 gfxFontEntry* fe = ff.FontEntry();
2044 if (!fe) {
2045 return nullptr;
2046 }
2047 gfxCharacterMap* unicodeRangeMap = nullptr;
2048 if (fe->mIsUserFontContainer) {
2049 gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe);
2050 if (ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED &&
2051 ufe->CharacterInUnicodeRange(aCh) && !*aLoading) {
2052 ufe->Load();
2053 ff.CheckState(mSkipDrawing);
2054 *aLoading = ff.IsLoading();
2055 }
2056 fe = ufe->GetPlatformFontEntry();
2057 if (!fe) {
2058 return nullptr;
2059 }
2060 unicodeRangeMap = ufe->GetUnicodeRangeMap();
2061 }
2062 font = fe->FindOrMakeFont(&mStyle, unicodeRangeMap);
2063 if (!font || !font->Valid()) {
2064 ff.SetInvalid();
2065 // We can't just |delete font| here, in case there are other
2066 // references to the object FindOrMakeFont returned.
2067 RefPtr<gfxFont> ref(font);
2068 return nullptr;
2069 }
2070 ff.SetFont(font);
2071 }
2072 return font;
2073 }
2074
CheckState(bool & aSkipDrawing)2075 void gfxFontGroup::FamilyFace::CheckState(bool& aSkipDrawing) {
2076 gfxFontEntry* fe = FontEntry();
2077 if (!fe) {
2078 return;
2079 }
2080 if (fe->mIsUserFontContainer) {
2081 gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe);
2082 gfxUserFontEntry::UserFontLoadState state = ufe->LoadState();
2083 switch (state) {
2084 case gfxUserFontEntry::STATUS_LOAD_PENDING:
2085 case gfxUserFontEntry::STATUS_LOADING:
2086 SetLoading(true);
2087 break;
2088 case gfxUserFontEntry::STATUS_FAILED:
2089 SetInvalid();
2090 // fall-thru to the default case
2091 [[fallthrough]];
2092 default:
2093 SetLoading(false);
2094 }
2095 if (ufe->WaitForUserFont()) {
2096 aSkipDrawing = true;
2097 }
2098 }
2099 }
2100
EqualsUserFont(const gfxUserFontEntry * aUserFont) const2101 bool gfxFontGroup::FamilyFace::EqualsUserFont(
2102 const gfxUserFontEntry* aUserFont) const {
2103 gfxFontEntry* fe = FontEntry();
2104 // if there's a font, the entry is the underlying platform font
2105 if (mFontCreated) {
2106 gfxFontEntry* pfe = aUserFont->GetPlatformFontEntry();
2107 if (pfe == fe) {
2108 return true;
2109 }
2110 } else if (fe == aUserFont) {
2111 return true;
2112 }
2113 return false;
2114 }
2115
FamilyListToString(const StyleFontFamilyList & aFamilyList)2116 static nsAutoCString FamilyListToString(
2117 const StyleFontFamilyList& aFamilyList) {
2118 return StringJoin(","_ns, aFamilyList.list.AsSpan(),
2119 [](nsACString& dst, const StyleSingleFontFamily& name) {
2120 name.AppendToString(dst);
2121 });
2122 }
2123
GetDefaultFont()2124 gfxFont* gfxFontGroup::GetDefaultFont() {
2125 if (mDefaultFont) {
2126 return mDefaultFont.get();
2127 }
2128
2129 gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
2130 FontFamily family = pfl->GetDefaultFont(&mStyle);
2131 MOZ_ASSERT(!family.IsNull(),
2132 "invalid default font returned by GetDefaultFont");
2133
2134 gfxFontEntry* fe = nullptr;
2135 if (family.mIsShared) {
2136 fontlist::Family* fam = family.mShared;
2137 if (!fam->IsInitialized()) {
2138 // If this fails, FindFaceForStyle will just safely return nullptr
2139 Unused << pfl->InitializeFamily(fam);
2140 }
2141 fontlist::Face* face = fam->FindFaceForStyle(pfl->SharedFontList(), mStyle);
2142 if (face) {
2143 fe = pfl->GetOrCreateFontEntry(face, fam);
2144 }
2145 } else {
2146 fe = family.mUnshared->FindFontForStyle(mStyle);
2147 }
2148 if (fe) {
2149 mDefaultFont = fe->FindOrMakeFont(&mStyle);
2150 }
2151
2152 uint32_t numInits, loaderState;
2153 pfl->GetFontlistInitInfo(numInits, loaderState);
2154
2155 MOZ_ASSERT(numInits != 0,
2156 "must initialize system fontlist before getting default font!");
2157
2158 uint32_t numFonts = 0;
2159 if (!mDefaultFont) {
2160 // Try for a "font of last resort...."
2161 // Because an empty font list would be Really Bad for later code
2162 // that assumes it will be able to get valid metrics for layout,
2163 // just look for the first usable font and put in the list.
2164 // (see bug 554544)
2165 if (pfl->SharedFontList()) {
2166 fontlist::FontList* list = pfl->SharedFontList();
2167 numFonts = list->NumFamilies();
2168 fontlist::Family* families = list->Families();
2169 for (uint32_t i = 0; i < numFonts; ++i) {
2170 fontlist::Family* fam = &families[i];
2171 if (!fam->IsInitialized()) {
2172 Unused << pfl->InitializeFamily(fam);
2173 }
2174 fontlist::Face* face =
2175 fam->FindFaceForStyle(pfl->SharedFontList(), mStyle);
2176 if (face) {
2177 fe = pfl->GetOrCreateFontEntry(face, fam);
2178 if (fe) {
2179 mDefaultFont = fe->FindOrMakeFont(&mStyle);
2180 if (mDefaultFont) {
2181 break;
2182 }
2183 NS_WARNING("FindOrMakeFont failed");
2184 }
2185 }
2186 }
2187 } else {
2188 AutoTArray<RefPtr<gfxFontFamily>, 200> familyList;
2189 pfl->GetFontFamilyList(familyList);
2190 numFonts = familyList.Length();
2191 for (uint32_t i = 0; i < numFonts; ++i) {
2192 gfxFontEntry* fe = familyList[i]->FindFontForStyle(mStyle, true);
2193 if (fe) {
2194 mDefaultFont = fe->FindOrMakeFont(&mStyle);
2195 if (mDefaultFont) {
2196 break;
2197 }
2198 }
2199 }
2200 }
2201 }
2202
2203 if (!mDefaultFont && pfl->SharedFontList() && !XRE_IsParentProcess()) {
2204 // If we're a content process, it's possible this is failing because the
2205 // chrome process has just updated the shared font list and we haven't yet
2206 // refreshed our reference to it. If that's the case, update and retry.
2207 // But if we're not on the main thread, we can't do this, so just use
2208 // the platform default font directly.
2209 if (NS_IsMainThread()) {
2210 uint32_t oldGeneration = pfl->SharedFontList()->GetGeneration();
2211 pfl->UpdateFontList();
2212 if (pfl->SharedFontList()->GetGeneration() != oldGeneration) {
2213 return GetDefaultFont();
2214 }
2215 } else {
2216 gfxFontEntry* fe = pfl->GetDefaultFontEntry();
2217 if (fe) {
2218 return fe->FindOrMakeFont(&mStyle);
2219 }
2220 }
2221 }
2222
2223 if (!mDefaultFont) {
2224 // an empty font list at this point is fatal; we're not going to
2225 // be able to do even the most basic layout operations
2226
2227 // annotate crash report with fontlist info
2228 nsAutoCString fontInitInfo;
2229 fontInitInfo.AppendPrintf("no fonts - init: %d fonts: %d loader: %d",
2230 numInits, numFonts, loaderState);
2231 #ifdef XP_WIN
2232 bool dwriteEnabled = gfxWindowsPlatform::GetPlatform()->DWriteEnabled();
2233 double upTime = (double)GetTickCount();
2234 fontInitInfo.AppendPrintf(" backend: %s system-uptime: %9.3f sec",
2235 dwriteEnabled ? "directwrite" : "gdi",
2236 upTime / 1000);
2237 #endif
2238 gfxCriticalError() << fontInitInfo.get();
2239
2240 char msg[256]; // CHECK buffer length if revising message below
2241 SprintfLiteral(msg, "unable to find a usable font (%.220s)",
2242 FamilyListToString(mFamilyList).get());
2243 MOZ_CRASH_UNSAFE(msg);
2244 }
2245
2246 return mDefaultFont.get();
2247 }
2248
GetFirstValidFont(uint32_t aCh,StyleGenericFontFamily * aGeneric)2249 gfxFont* gfxFontGroup::GetFirstValidFont(uint32_t aCh,
2250 StyleGenericFontFamily* aGeneric) {
2251 // Ensure cached font instances are valid.
2252 CheckForUpdatedPlatformList();
2253
2254 uint32_t count = mFonts.Length();
2255 bool loading = false;
2256 for (uint32_t i = 0; i < count; ++i) {
2257 FamilyFace& ff = mFonts[i];
2258 if (ff.IsInvalid()) {
2259 continue;
2260 }
2261
2262 // already have a font?
2263 gfxFont* font = ff.Font();
2264 if (font) {
2265 if (aGeneric) {
2266 *aGeneric = ff.Generic();
2267 }
2268 return font;
2269 }
2270
2271 // Need to build a font, loading userfont if not loaded. In
2272 // cases where unicode range might apply, use the character
2273 // provided.
2274 gfxFontEntry* fe = ff.FontEntry();
2275 if (fe && fe->mIsUserFontContainer) {
2276 gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe);
2277 bool inRange = ufe->CharacterInUnicodeRange(aCh);
2278 if (inRange) {
2279 if (!loading &&
2280 ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED) {
2281 ufe->Load();
2282 ff.CheckState(mSkipDrawing);
2283 }
2284 if (ff.IsLoading()) {
2285 loading = true;
2286 }
2287 }
2288 if (ufe->LoadState() != gfxUserFontEntry::STATUS_LOADED || !inRange) {
2289 continue;
2290 }
2291 }
2292
2293 font = GetFontAt(i, aCh, &loading);
2294 if (font) {
2295 if (aGeneric) {
2296 *aGeneric = ff.Generic();
2297 }
2298 return font;
2299 }
2300 }
2301 if (aGeneric) {
2302 *aGeneric = StyleGenericFontFamily::None;
2303 }
2304 return GetDefaultFont();
2305 }
2306
GetFirstMathFont()2307 gfxFont* gfxFontGroup::GetFirstMathFont() {
2308 uint32_t count = mFonts.Length();
2309 for (uint32_t i = 0; i < count; ++i) {
2310 gfxFont* font = GetFontAt(i);
2311 if (font && font->TryGetMathTable()) {
2312 return font;
2313 }
2314 }
2315 return nullptr;
2316 }
2317
IsInvalidChar(uint8_t ch)2318 bool gfxFontGroup::IsInvalidChar(uint8_t ch) {
2319 return ((ch & 0x7f) < 0x20 || ch == 0x7f);
2320 }
2321
IsInvalidChar(char16_t ch)2322 bool gfxFontGroup::IsInvalidChar(char16_t ch) {
2323 // All printable 7-bit ASCII values are OK
2324 if (ch >= ' ' && ch < 0x7f) {
2325 return false;
2326 }
2327 // No point in sending non-printing control chars through font shaping
2328 if (ch <= 0x9f) {
2329 return true;
2330 }
2331 // Word-separating format/bidi control characters are not shaped as part
2332 // of words.
2333 return (((ch & 0xFF00) == 0x2000 /* Unicode control character */ &&
2334 (ch == 0x200B /*ZWSP*/ || ch == 0x2028 /*LSEP*/ ||
2335 ch == 0x2029 /*PSEP*/ || ch == 0x2060 /*WJ*/)) ||
2336 ch == 0xfeff /*ZWNBSP*/ || IsBidiControl(ch));
2337 }
2338
MakeEmptyTextRun(const Parameters * aParams,gfx::ShapedTextFlags aFlags,nsTextFrameUtils::Flags aFlags2)2339 already_AddRefed<gfxTextRun> gfxFontGroup::MakeEmptyTextRun(
2340 const Parameters* aParams, gfx::ShapedTextFlags aFlags,
2341 nsTextFrameUtils::Flags aFlags2) {
2342 aFlags |= ShapedTextFlags::TEXT_IS_8BIT;
2343 return gfxTextRun::Create(aParams, 0, this, aFlags, aFlags2);
2344 }
2345
MakeSpaceTextRun(const Parameters * aParams,gfx::ShapedTextFlags aFlags,nsTextFrameUtils::Flags aFlags2)2346 already_AddRefed<gfxTextRun> gfxFontGroup::MakeSpaceTextRun(
2347 const Parameters* aParams, gfx::ShapedTextFlags aFlags,
2348 nsTextFrameUtils::Flags aFlags2) {
2349 aFlags |= ShapedTextFlags::TEXT_IS_8BIT;
2350
2351 RefPtr<gfxTextRun> textRun =
2352 gfxTextRun::Create(aParams, 1, this, aFlags, aFlags2);
2353 if (!textRun) {
2354 return nullptr;
2355 }
2356
2357 gfx::ShapedTextFlags orientation = aFlags & ShapedTextFlags::TEXT_ORIENT_MASK;
2358 if (orientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
2359 orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
2360 }
2361
2362 gfxFont* font = GetFirstValidFont();
2363 if (MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero())) {
2364 // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle
2365 // them, and always create at least size 1 fonts, i.e. they still
2366 // render something for size 0 fonts.
2367 textRun->AddGlyphRun(font, FontMatchType::Kind::kUnspecified, 0, false,
2368 orientation, false);
2369 } else {
2370 if (font->GetSpaceGlyph()) {
2371 // Normally, the font has a cached space glyph, so we can avoid
2372 // the cost of calling FindFontForChar.
2373 textRun->SetSpaceGlyph(font, aParams->mDrawTarget, 0, orientation);
2374 } else {
2375 // In case the primary font doesn't have <space> (bug 970891),
2376 // find one that does.
2377 FontMatchType matchType;
2378 gfxFont* spaceFont =
2379 FindFontForChar(' ', 0, 0, Script::LATIN, nullptr, &matchType);
2380 if (spaceFont) {
2381 textRun->SetSpaceGlyph(spaceFont, aParams->mDrawTarget, 0, orientation);
2382 }
2383 }
2384 }
2385
2386 // Note that the gfxGlyphExtents glyph bounds storage for the font will
2387 // always contain an entry for the font's space glyph, so we don't have
2388 // to call FetchGlyphExtents here.
2389 return textRun.forget();
2390 }
2391
2392 template <typename T>
MakeBlankTextRun(const T * aString,uint32_t aLength,const Parameters * aParams,gfx::ShapedTextFlags aFlags,nsTextFrameUtils::Flags aFlags2)2393 already_AddRefed<gfxTextRun> gfxFontGroup::MakeBlankTextRun(
2394 const T* aString, uint32_t aLength, const Parameters* aParams,
2395 gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2) {
2396 RefPtr<gfxTextRun> textRun =
2397 gfxTextRun::Create(aParams, aLength, this, aFlags, aFlags2);
2398 if (!textRun) {
2399 return nullptr;
2400 }
2401
2402 gfx::ShapedTextFlags orientation = aFlags & ShapedTextFlags::TEXT_ORIENT_MASK;
2403 if (orientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
2404 orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
2405 }
2406 textRun->AddGlyphRun(GetFirstValidFont(), FontMatchType::Kind::kUnspecified,
2407 0, false, orientation, false);
2408
2409 for (uint32_t i = 0; i < aLength; i++) {
2410 if (aString[i] == '\n') {
2411 textRun->SetIsNewline(i);
2412 } else if (aString[i] == '\t') {
2413 textRun->SetIsTab(i);
2414 }
2415 }
2416
2417 return textRun.forget();
2418 }
2419
MakeHyphenTextRun(DrawTarget * aDrawTarget,uint32_t aAppUnitsPerDevUnit)2420 already_AddRefed<gfxTextRun> gfxFontGroup::MakeHyphenTextRun(
2421 DrawTarget* aDrawTarget, uint32_t aAppUnitsPerDevUnit) {
2422 // only use U+2010 if it is supported by the first font in the group;
2423 // it's better to use ASCII '-' from the primary font than to fall back to
2424 // U+2010 from some other, possibly poorly-matching face
2425 static const char16_t hyphen = 0x2010;
2426 gfxFont* font = GetFirstValidFont(uint32_t(hyphen));
2427 if (font->HasCharacter(hyphen)) {
2428 return MakeTextRun(&hyphen, 1, aDrawTarget, aAppUnitsPerDevUnit,
2429 ShapedTextFlags(), nsTextFrameUtils::Flags(), nullptr);
2430 }
2431
2432 static const uint8_t dash = '-';
2433 return MakeTextRun(&dash, 1, aDrawTarget, aAppUnitsPerDevUnit,
2434 ShapedTextFlags(), nsTextFrameUtils::Flags(), nullptr);
2435 }
2436
GetHyphenWidth(const gfxTextRun::PropertyProvider * aProvider)2437 gfxFloat gfxFontGroup::GetHyphenWidth(
2438 const gfxTextRun::PropertyProvider* aProvider) {
2439 if (mHyphenWidth < 0) {
2440 RefPtr<DrawTarget> dt(aProvider->GetDrawTarget());
2441 if (dt) {
2442 RefPtr<gfxTextRun> hyphRun(
2443 MakeHyphenTextRun(dt, aProvider->GetAppUnitsPerDevUnit()));
2444 mHyphenWidth = hyphRun.get() ? hyphRun->GetAdvanceWidth() : 0;
2445 }
2446 }
2447 return mHyphenWidth;
2448 }
2449
MakeTextRun(const uint8_t * aString,uint32_t aLength,const Parameters * aParams,gfx::ShapedTextFlags aFlags,nsTextFrameUtils::Flags aFlags2,gfxMissingFontRecorder * aMFR)2450 already_AddRefed<gfxTextRun> gfxFontGroup::MakeTextRun(
2451 const uint8_t* aString, uint32_t aLength, const Parameters* aParams,
2452 gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2,
2453 gfxMissingFontRecorder* aMFR) {
2454 if (aLength == 0) {
2455 return MakeEmptyTextRun(aParams, aFlags, aFlags2);
2456 }
2457 if (aLength == 1 && aString[0] == ' ') {
2458 return MakeSpaceTextRun(aParams, aFlags, aFlags2);
2459 }
2460
2461 aFlags |= ShapedTextFlags::TEXT_IS_8BIT;
2462
2463 if (MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero())) {
2464 // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle
2465 // them, and always create at least size 1 fonts, i.e. they still
2466 // render something for size 0 fonts.
2467 return MakeBlankTextRun(aString, aLength, aParams, aFlags, aFlags2);
2468 }
2469
2470 RefPtr<gfxTextRun> textRun =
2471 gfxTextRun::Create(aParams, aLength, this, aFlags, aFlags2);
2472 if (!textRun) {
2473 return nullptr;
2474 }
2475
2476 InitTextRun(aParams->mDrawTarget, textRun.get(), aString, aLength, aMFR);
2477
2478 textRun->FetchGlyphExtents(aParams->mDrawTarget);
2479
2480 return textRun.forget();
2481 }
2482
MakeTextRun(const char16_t * aString,uint32_t aLength,const Parameters * aParams,gfx::ShapedTextFlags aFlags,nsTextFrameUtils::Flags aFlags2,gfxMissingFontRecorder * aMFR)2483 already_AddRefed<gfxTextRun> gfxFontGroup::MakeTextRun(
2484 const char16_t* aString, uint32_t aLength, const Parameters* aParams,
2485 gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2,
2486 gfxMissingFontRecorder* aMFR) {
2487 if (aLength == 0) {
2488 return MakeEmptyTextRun(aParams, aFlags, aFlags2);
2489 }
2490 if (aLength == 1 && aString[0] == ' ') {
2491 return MakeSpaceTextRun(aParams, aFlags, aFlags2);
2492 }
2493 if (MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero())) {
2494 return MakeBlankTextRun(aString, aLength, aParams, aFlags, aFlags2);
2495 }
2496
2497 RefPtr<gfxTextRun> textRun =
2498 gfxTextRun::Create(aParams, aLength, this, aFlags, aFlags2);
2499 if (!textRun) {
2500 return nullptr;
2501 }
2502
2503 InitTextRun(aParams->mDrawTarget, textRun.get(), aString, aLength, aMFR);
2504
2505 textRun->FetchGlyphExtents(aParams->mDrawTarget);
2506
2507 return textRun.forget();
2508 }
2509
2510 template <typename T>
InitTextRun(DrawTarget * aDrawTarget,gfxTextRun * aTextRun,const T * aString,uint32_t aLength,gfxMissingFontRecorder * aMFR)2511 void gfxFontGroup::InitTextRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
2512 const T* aString, uint32_t aLength,
2513 gfxMissingFontRecorder* aMFR) {
2514 NS_ASSERTION(aLength > 0, "don't call InitTextRun for a zero-length run");
2515
2516 // we need to do numeral processing even on 8-bit text,
2517 // in case we're converting Western to Hindi/Arabic digits
2518 int32_t numOption = gfxPlatform::GetPlatform()->GetBidiNumeralOption();
2519 UniquePtr<char16_t[]> transformedString;
2520 if (numOption != IBMBIDI_NUMERAL_NOMINAL) {
2521 // scan the string for numerals that may need to be transformed;
2522 // if we find any, we'll make a local copy here and use that for
2523 // font matching and glyph generation/shaping
2524 bool prevIsArabic =
2525 !!(aTextRun->GetFlags() & ShapedTextFlags::TEXT_INCOMING_ARABICCHAR);
2526 for (uint32_t i = 0; i < aLength; ++i) {
2527 char16_t origCh = aString[i];
2528 char16_t newCh = HandleNumberInChar(origCh, prevIsArabic, numOption);
2529 if (newCh != origCh) {
2530 if (!transformedString) {
2531 transformedString = MakeUnique<char16_t[]>(aLength);
2532 if (sizeof(T) == sizeof(char16_t)) {
2533 memcpy(transformedString.get(), aString, i * sizeof(char16_t));
2534 } else {
2535 for (uint32_t j = 0; j < i; ++j) {
2536 transformedString[j] = aString[j];
2537 }
2538 }
2539 }
2540 }
2541 if (transformedString) {
2542 transformedString[i] = newCh;
2543 }
2544 prevIsArabic = IS_ARABIC_CHAR(newCh);
2545 }
2546 }
2547
2548 LogModule* log = mStyle.systemFont ? gfxPlatform::GetLog(eGfxLog_textrunui)
2549 : gfxPlatform::GetLog(eGfxLog_textrun);
2550
2551 // variant fallback handling may end up passing through this twice
2552 bool redo;
2553 do {
2554 redo = false;
2555
2556 if (sizeof(T) == sizeof(uint8_t) && !transformedString) {
2557 if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) {
2558 nsAutoCString lang;
2559 mLanguage->ToUTF8String(lang);
2560 nsAutoCString str((const char*)aString, aLength);
2561 nsAutoString styleString;
2562 nsStyleUtil::AppendFontSlantStyle(mStyle.style, styleString);
2563 MOZ_LOG(
2564 log, LogLevel::Warning,
2565 ("(%s) fontgroup: [%s] default: %s lang: %s script: %d "
2566 "len %d weight: %g stretch: %g%% style: %s size: %6.2f %zu-byte "
2567 "TEXTRUN [%s] ENDTEXTRUN\n",
2568 (mStyle.systemFont ? "textrunui" : "textrun"),
2569 FamilyListToString(mFamilyList).get(),
2570 (mFamilyList.fallback == StyleGenericFontFamily::Serif
2571 ? "serif"
2572 : (mFamilyList.fallback == StyleGenericFontFamily::SansSerif
2573 ? "sans-serif"
2574 : "none")),
2575 lang.get(), static_cast<int>(Script::LATIN), aLength,
2576 mStyle.weight.ToFloat(), mStyle.stretch.Percentage(),
2577 NS_ConvertUTF16toUTF8(styleString).get(), mStyle.size, sizeof(T),
2578 str.get()));
2579 }
2580
2581 // the text is still purely 8-bit; bypass the script-run itemizer
2582 // and treat it as a single Latin run
2583 InitScriptRun(aDrawTarget, aTextRun, aString, 0, aLength, Script::LATIN,
2584 aMFR);
2585 } else {
2586 const char16_t* textPtr;
2587 if (transformedString) {
2588 textPtr = transformedString.get();
2589 } else {
2590 // typecast to avoid compilation error for the 8-bit version,
2591 // even though this is dead code in that case
2592 textPtr = reinterpret_cast<const char16_t*>(aString);
2593 }
2594
2595 // split into script runs so that script can potentially influence
2596 // the font matching process below
2597 gfxScriptItemizer scriptRuns(textPtr, aLength);
2598
2599 uint32_t runStart = 0, runLimit = aLength;
2600 Script runScript = Script::LATIN;
2601 while (scriptRuns.Next(runStart, runLimit, runScript)) {
2602 if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) {
2603 nsAutoCString lang;
2604 mLanguage->ToUTF8String(lang);
2605 nsAutoString styleString;
2606 nsStyleUtil::AppendFontSlantStyle(mStyle.style, styleString);
2607 uint32_t runLen = runLimit - runStart;
2608 MOZ_LOG(
2609 log, LogLevel::Warning,
2610 ("(%s) fontgroup: [%s] default: %s lang: %s script: %d "
2611 "len %d weight: %g stretch: %g%% style: %s size: %6.2f "
2612 "%zu-byte TEXTRUN [%s] ENDTEXTRUN\n",
2613 (mStyle.systemFont ? "textrunui" : "textrun"),
2614 FamilyListToString(mFamilyList).get(),
2615 (mFamilyList.fallback == StyleGenericFontFamily::Serif
2616 ? "serif"
2617 : (mFamilyList.fallback == StyleGenericFontFamily::SansSerif
2618 ? "sans-serif"
2619 : "none")),
2620 lang.get(), static_cast<int>(runScript), runLen,
2621 mStyle.weight.ToFloat(), mStyle.stretch.Percentage(),
2622 NS_ConvertUTF16toUTF8(styleString).get(), mStyle.size, sizeof(T),
2623 NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get()));
2624 }
2625
2626 InitScriptRun(aDrawTarget, aTextRun, textPtr + runStart, runStart,
2627 runLimit - runStart, runScript, aMFR);
2628 }
2629 }
2630
2631 // if shaping was aborted due to lack of feature support, clear out
2632 // glyph runs and redo shaping with fallback forced on
2633 if (aTextRun->GetShapingState() == gfxTextRun::eShapingState_Aborted) {
2634 redo = true;
2635 aTextRun->SetShapingState(gfxTextRun::eShapingState_ForceFallbackFeature);
2636 aTextRun->ClearGlyphsAndCharacters();
2637 }
2638
2639 } while (redo);
2640
2641 if (sizeof(T) == sizeof(char16_t) && aLength > 0) {
2642 gfxTextRun::CompressedGlyph* glyph = aTextRun->GetCharacterGlyphs();
2643 if (!glyph->IsSimpleGlyph()) {
2644 glyph->SetClusterStart(true);
2645 }
2646 }
2647
2648 // It's possible for CoreText to omit glyph runs if it decides they contain
2649 // only invisibles (e.g., U+FEFF, see reftest 474417-1). In this case, we
2650 // need to eliminate them from the glyph run array to avoid drawing "partial
2651 // ligatures" with the wrong font.
2652 // We don't do this during InitScriptRun (or gfxFont::InitTextRun) because
2653 // it will iterate back over all glyphruns in the textrun, which leads to
2654 // pathologically-bad perf in the case where a textrun contains many script
2655 // changes (see bug 680402) - we'd end up re-sanitizing all the earlier runs
2656 // every time a new script subrun is processed.
2657 aTextRun->SanitizeGlyphRuns();
2658
2659 aTextRun->SortGlyphRuns();
2660 }
2661
IsPUA(uint32_t aUSV)2662 static inline bool IsPUA(uint32_t aUSV) {
2663 // We could look up the General Category of the codepoint here,
2664 // but it's simpler to check PUA codepoint ranges.
2665 return (aUSV >= 0xE000 && aUSV <= 0xF8FF) || (aUSV >= 0xF0000);
2666 }
2667
2668 template <typename T>
InitScriptRun(DrawTarget * aDrawTarget,gfxTextRun * aTextRun,const T * aString,uint32_t aOffset,uint32_t aLength,Script aRunScript,gfxMissingFontRecorder * aMFR)2669 void gfxFontGroup::InitScriptRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
2670 const T* aString, // text for this script run,
2671 // not the entire textrun
2672 uint32_t aOffset, // position of the script
2673 // run within the textrun
2674 uint32_t aLength, // length of the script run
2675 Script aRunScript,
2676 gfxMissingFontRecorder* aMFR) {
2677 NS_ASSERTION(aLength > 0, "don't call InitScriptRun for a 0-length run");
2678 NS_ASSERTION(aTextRun->GetShapingState() != gfxTextRun::eShapingState_Aborted,
2679 "don't call InitScriptRun with aborted shaping state");
2680
2681 // confirm the load state of userfonts in the list
2682 if (mUserFontSet && mCurrGeneration != mUserFontSet->GetGeneration()) {
2683 UpdateUserFonts();
2684 }
2685
2686 gfxFont* mainFont = GetFirstValidFont();
2687
2688 ShapedTextFlags orientation =
2689 aTextRun->GetFlags() & ShapedTextFlags::TEXT_ORIENT_MASK;
2690
2691 if (orientation != ShapedTextFlags::TEXT_ORIENT_HORIZONTAL &&
2692 (aRunScript == Script::MONGOLIAN || aRunScript == Script::PHAGS_PA)) {
2693 // Mongolian and Phags-pa text should ignore text-orientation and
2694 // always render in its "native" vertical mode, implemented by fonts
2695 // as sideways-right (i.e as if shaped horizontally, and then the
2696 // entire line is rotated to render vertically). Therefore, we ignore
2697 // the aOrientation value from the textrun's flags, and make all
2698 // vertical Mongolian/Phags-pa use sideways-right.
2699 orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
2700 }
2701
2702 uint32_t runStart = 0;
2703 AutoTArray<TextRange, 3> fontRanges;
2704 ComputeRanges(fontRanges, aString, aLength, aRunScript, orientation);
2705 uint32_t numRanges = fontRanges.Length();
2706 bool missingChars = false;
2707 bool isCJK = gfxTextRun::IsCJKScript(aRunScript);
2708
2709 for (uint32_t r = 0; r < numRanges; r++) {
2710 const TextRange& range = fontRanges[r];
2711 uint32_t matchedLength = range.Length();
2712 gfxFont* matchedFont = range.font;
2713 // create the glyph run for this range
2714 if (matchedFont && mStyle.noFallbackVariantFeatures) {
2715 // common case - just do glyph layout and record the
2716 // resulting positioned glyphs
2717 aTextRun->AddGlyphRun(matchedFont, range.matchType, aOffset + runStart,
2718 (matchedLength > 0), range.orientation, isCJK);
2719 if (!matchedFont->SplitAndInitTextRun(
2720 aDrawTarget, aTextRun, aString + runStart, aOffset + runStart,
2721 matchedLength, aRunScript, mLanguage, range.orientation)) {
2722 // glyph layout failed! treat as missing glyphs
2723 matchedFont = nullptr;
2724 }
2725 } else if (matchedFont) {
2726 // shape with some variant feature that requires fallback handling
2727 bool petiteToSmallCaps = false;
2728 bool syntheticLower = false;
2729 bool syntheticUpper = false;
2730
2731 if (mStyle.variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL &&
2732 (aTextRun->GetShapingState() ==
2733 gfxTextRun::eShapingState_ForceFallbackFeature ||
2734 !matchedFont->SupportsSubSuperscript(mStyle.variantSubSuper, aString,
2735 aLength, aRunScript))) {
2736 // fallback for subscript/superscript variant glyphs
2737
2738 // if the feature was already used, abort and force
2739 // fallback across the entire textrun
2740 gfxTextRun::ShapingState ss = aTextRun->GetShapingState();
2741
2742 if (ss == gfxTextRun::eShapingState_Normal) {
2743 aTextRun->SetShapingState(
2744 gfxTextRun::eShapingState_ShapingWithFallback);
2745 } else if (ss == gfxTextRun::eShapingState_ShapingWithFeature) {
2746 aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted);
2747 return;
2748 }
2749
2750 RefPtr<gfxFont> subSuperFont = matchedFont->GetSubSuperscriptFont(
2751 aTextRun->GetAppUnitsPerDevUnit());
2752 aTextRun->AddGlyphRun(subSuperFont, range.matchType, aOffset + runStart,
2753 (matchedLength > 0), range.orientation, isCJK);
2754 if (!subSuperFont->SplitAndInitTextRun(
2755 aDrawTarget, aTextRun, aString + runStart, aOffset + runStart,
2756 matchedLength, aRunScript, mLanguage, range.orientation)) {
2757 // glyph layout failed! treat as missing glyphs
2758 matchedFont = nullptr;
2759 }
2760 } else if (mStyle.variantCaps != NS_FONT_VARIANT_CAPS_NORMAL &&
2761 !matchedFont->SupportsVariantCaps(
2762 aRunScript, mStyle.variantCaps, petiteToSmallCaps,
2763 syntheticLower, syntheticUpper)) {
2764 // fallback for small-caps variant glyphs
2765 if (!matchedFont->InitFakeSmallCapsRun(
2766 aDrawTarget, aTextRun, aString + runStart, aOffset + runStart,
2767 matchedLength, range.matchType, range.orientation, aRunScript,
2768 mExplicitLanguage ? mLanguage.get() : nullptr, syntheticLower,
2769 syntheticUpper)) {
2770 matchedFont = nullptr;
2771 }
2772 } else {
2773 // shape normally with variant feature enabled
2774 gfxTextRun::ShapingState ss = aTextRun->GetShapingState();
2775
2776 // adjust the shaping state if necessary
2777 if (ss == gfxTextRun::eShapingState_Normal) {
2778 aTextRun->SetShapingState(
2779 gfxTextRun::eShapingState_ShapingWithFeature);
2780 } else if (ss == gfxTextRun::eShapingState_ShapingWithFallback) {
2781 // already have shaping results using fallback, need to redo
2782 aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted);
2783 return;
2784 }
2785
2786 // do glyph layout and record the resulting positioned glyphs
2787 aTextRun->AddGlyphRun(matchedFont, range.matchType, aOffset + runStart,
2788 (matchedLength > 0), range.orientation, isCJK);
2789 if (!matchedFont->SplitAndInitTextRun(
2790 aDrawTarget, aTextRun, aString + runStart, aOffset + runStart,
2791 matchedLength, aRunScript, mLanguage, range.orientation)) {
2792 // glyph layout failed! treat as missing glyphs
2793 matchedFont = nullptr;
2794 }
2795 }
2796 } else {
2797 aTextRun->AddGlyphRun(mainFont, FontMatchType::Kind::kFontGroup,
2798 aOffset + runStart, (matchedLength > 0),
2799 range.orientation, isCJK);
2800 }
2801
2802 if (!matchedFont) {
2803 // We need to set cluster boundaries (and mark spaces) so that
2804 // surrogate pairs, combining characters, etc behave properly,
2805 // even if we don't have glyphs for them
2806 aTextRun->SetupClusterBoundaries(aOffset + runStart, aString + runStart,
2807 matchedLength);
2808
2809 // various "missing" characters may need special handling,
2810 // so we check for them here
2811 uint32_t runLimit = runStart + matchedLength;
2812 for (uint32_t index = runStart; index < runLimit; index++) {
2813 T ch = aString[index];
2814
2815 // tab and newline are not to be displayed as hexboxes,
2816 // but do need to be recorded in the textrun
2817 if (ch == '\n') {
2818 aTextRun->SetIsNewline(aOffset + index);
2819 continue;
2820 }
2821 if (ch == '\t') {
2822 aTextRun->SetIsTab(aOffset + index);
2823 continue;
2824 }
2825
2826 // for 16-bit textruns only, check for surrogate pairs and
2827 // special Unicode spaces; omit these checks in 8-bit runs
2828 if (sizeof(T) == sizeof(char16_t)) {
2829 if (index + 1 < aLength &&
2830 NS_IS_SURROGATE_PAIR(ch, aString[index + 1])) {
2831 uint32_t usv = SURROGATE_TO_UCS4(ch, aString[index + 1]);
2832 aTextRun->SetMissingGlyph(aOffset + index, usv, mainFont);
2833 index++;
2834 if (!mSkipDrawing && !IsPUA(usv)) {
2835 missingChars = true;
2836 if (mFontMatchingStats) {
2837 mFontMatchingStats->mFallbacks |= FallbackTypes::MissingFont;
2838 }
2839 }
2840 continue;
2841 }
2842
2843 // check if this is a known Unicode whitespace character that
2844 // we can render using the space glyph with a custom width
2845 gfxFloat wid = mainFont->SynthesizeSpaceWidth(ch);
2846 if (wid >= 0.0) {
2847 nscoord advance =
2848 aTextRun->GetAppUnitsPerDevUnit() * floor(wid + 0.5);
2849 if (gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance)) {
2850 aTextRun->GetCharacterGlyphs()[aOffset + index].SetSimpleGlyph(
2851 advance, mainFont->GetSpaceGlyph());
2852 } else {
2853 gfxTextRun::DetailedGlyph detailedGlyph;
2854 detailedGlyph.mGlyphID = mainFont->GetSpaceGlyph();
2855 detailedGlyph.mAdvance = advance;
2856 aTextRun->SetDetailedGlyphs(aOffset + index, 1, &detailedGlyph);
2857 }
2858 continue;
2859 }
2860 }
2861
2862 if (IsInvalidChar(ch)) {
2863 // invalid chars are left as zero-width/invisible
2864 continue;
2865 }
2866
2867 // record char code so we can draw a box with the Unicode value
2868 aTextRun->SetMissingGlyph(aOffset + index, ch, mainFont);
2869 if (!mSkipDrawing && !IsPUA(ch)) {
2870 missingChars = true;
2871 if (mFontMatchingStats) {
2872 mFontMatchingStats->mFallbacks |= FallbackTypes::MissingFont;
2873 }
2874 }
2875 }
2876 }
2877
2878 runStart += matchedLength;
2879 }
2880
2881 if (aMFR && missingChars) {
2882 aMFR->RecordScript(aRunScript);
2883 }
2884 }
2885
GetEllipsisTextRun(int32_t aAppUnitsPerDevPixel,gfx::ShapedTextFlags aFlags,LazyReferenceDrawTargetGetter & aRefDrawTargetGetter)2886 gfxTextRun* gfxFontGroup::GetEllipsisTextRun(
2887 int32_t aAppUnitsPerDevPixel, gfx::ShapedTextFlags aFlags,
2888 LazyReferenceDrawTargetGetter& aRefDrawTargetGetter) {
2889 MOZ_ASSERT(!(aFlags & ~ShapedTextFlags::TEXT_ORIENT_MASK),
2890 "flags here should only be used to specify orientation");
2891 if (mCachedEllipsisTextRun &&
2892 (mCachedEllipsisTextRun->GetFlags() &
2893 ShapedTextFlags::TEXT_ORIENT_MASK) == aFlags &&
2894 mCachedEllipsisTextRun->GetAppUnitsPerDevUnit() == aAppUnitsPerDevPixel) {
2895 return mCachedEllipsisTextRun.get();
2896 }
2897
2898 // Use a Unicode ellipsis if the font supports it,
2899 // otherwise use three ASCII periods as fallback.
2900 gfxFont* firstFont = GetFirstValidFont(uint32_t(kEllipsisChar[0]));
2901 nsString ellipsis =
2902 firstFont->HasCharacter(kEllipsisChar[0])
2903 ? nsDependentString(kEllipsisChar, ArrayLength(kEllipsisChar) - 1)
2904 : nsDependentString(kASCIIPeriodsChar,
2905 ArrayLength(kASCIIPeriodsChar) - 1);
2906
2907 RefPtr<DrawTarget> refDT = aRefDrawTargetGetter.GetRefDrawTarget();
2908 Parameters params = {refDT, nullptr, nullptr,
2909 nullptr, 0, aAppUnitsPerDevPixel};
2910 mCachedEllipsisTextRun =
2911 MakeTextRun(ellipsis.get(), ellipsis.Length(), ¶ms, aFlags,
2912 nsTextFrameUtils::Flags(), nullptr);
2913 if (!mCachedEllipsisTextRun) {
2914 return nullptr;
2915 }
2916 // don't let the presence of a cached ellipsis textrun prolong the
2917 // fontgroup's life
2918 mCachedEllipsisTextRun->ReleaseFontGroup();
2919 return mCachedEllipsisTextRun.get();
2920 }
2921
FindFallbackFaceForChar(gfxFontFamily * aFamily,uint32_t aCh,uint32_t aNextCh,eFontPresentation aPresentation)2922 gfxFont* gfxFontGroup::FindFallbackFaceForChar(
2923 gfxFontFamily* aFamily, uint32_t aCh, uint32_t aNextCh,
2924 eFontPresentation aPresentation) {
2925 GlobalFontMatch data(aCh, aNextCh, mStyle, aPresentation);
2926 aFamily->SearchAllFontsForChar(&data);
2927 gfxFontEntry* fe = data.mBestMatch;
2928 if (!fe) {
2929 return nullptr;
2930 }
2931 return fe->FindOrMakeFont(&mStyle);
2932 }
2933
FindFallbackFaceForChar(fontlist::Family * aFamily,uint32_t aCh,uint32_t aNextCh,eFontPresentation aPresentation)2934 gfxFont* gfxFontGroup::FindFallbackFaceForChar(
2935 fontlist::Family* aFamily, uint32_t aCh, uint32_t aNextCh,
2936 eFontPresentation aPresentation) {
2937 auto* pfl = gfxPlatformFontList::PlatformFontList();
2938 auto* list = pfl->SharedFontList();
2939
2940 // If async fallback is enabled, and the family isn't fully initialized yet,
2941 // just start the async cmap loading and return.
2942 if (!aFamily->IsFullyInitialized() &&
2943 StaticPrefs::gfx_font_rendering_fallback_async() &&
2944 !XRE_IsParentProcess()) {
2945 pfl->StartCmapLoadingFromFamily(aFamily - list->Families());
2946 return nullptr;
2947 }
2948
2949 GlobalFontMatch data(aCh, aNextCh, mStyle, aPresentation);
2950 aFamily->SearchAllFontsForChar(list, &data);
2951 gfxFontEntry* fe = data.mBestMatch;
2952 if (!fe) {
2953 return nullptr;
2954 }
2955 return fe->FindOrMakeFont(&mStyle);
2956 }
2957
FindFallbackFaceForChar(const FamilyFace & aFamily,uint32_t aCh,uint32_t aNextCh,eFontPresentation aPresentation)2958 gfxFont* gfxFontGroup::FindFallbackFaceForChar(
2959 const FamilyFace& aFamily, uint32_t aCh, uint32_t aNextCh,
2960 eFontPresentation aPresentation) {
2961 if (aFamily.IsSharedFamily()) {
2962 return FindFallbackFaceForChar(aFamily.SharedFamily(), aCh, aNextCh,
2963 aPresentation);
2964 }
2965 return FindFallbackFaceForChar(aFamily.OwnedFamily(), aCh, aNextCh,
2966 aPresentation);
2967 }
2968
GetUnderlineOffset()2969 gfxFloat gfxFontGroup::GetUnderlineOffset() {
2970 if (mUnderlineOffset == UNDERLINE_OFFSET_NOT_SET) {
2971 // if the fontlist contains a bad underline font, make the underline
2972 // offset the min of the first valid font and bad font underline offsets
2973 uint32_t len = mFonts.Length();
2974 for (uint32_t i = 0; i < len; i++) {
2975 FamilyFace& ff = mFonts[i];
2976 gfxFontEntry* fe = ff.FontEntry();
2977 if (!fe) {
2978 continue;
2979 }
2980 if (!fe->mIsUserFontContainer && !fe->IsUserFont() &&
2981 ((ff.IsSharedFamily() && ff.SharedFamily() &&
2982 ff.SharedFamily()->IsBadUnderlineFamily()) ||
2983 (!ff.IsSharedFamily() && ff.OwnedFamily() &&
2984 ff.OwnedFamily()->IsBadUnderlineFamily()))) {
2985 gfxFont* font = GetFontAt(i);
2986 if (!font) {
2987 continue;
2988 }
2989 gfxFloat bad =
2990 font->GetMetrics(nsFontMetrics::eHorizontal).underlineOffset;
2991 gfxFloat first = GetFirstValidFont()
2992 ->GetMetrics(nsFontMetrics::eHorizontal)
2993 .underlineOffset;
2994 mUnderlineOffset = std::min(first, bad);
2995 return mUnderlineOffset;
2996 }
2997 }
2998
2999 // no bad underline fonts, use the first valid font's metric
3000 mUnderlineOffset = GetFirstValidFont()
3001 ->GetMetrics(nsFontMetrics::eHorizontal)
3002 .underlineOffset;
3003 }
3004
3005 return mUnderlineOffset;
3006 }
3007
3008 #define NARROW_NO_BREAK_SPACE 0x202fu
3009
FindFontForChar(uint32_t aCh,uint32_t aPrevCh,uint32_t aNextCh,Script aRunScript,gfxFont * aPrevMatchedFont,FontMatchType * aMatchType)3010 gfxFont* gfxFontGroup::FindFontForChar(uint32_t aCh, uint32_t aPrevCh,
3011 uint32_t aNextCh, Script aRunScript,
3012 gfxFont* aPrevMatchedFont,
3013 FontMatchType* aMatchType) {
3014 // If the char is a cluster extender, we want to use the same font as the
3015 // preceding character if possible. This is preferable to using the font
3016 // group because it avoids breaks in shaping within a cluster.
3017 if (aPrevMatchedFont && IsClusterExtender(aCh)) {
3018 if (aPrevMatchedFont->HasCharacter(aCh) || IsDefaultIgnorable(aCh)) {
3019 return aPrevMatchedFont;
3020 }
3021 // Get the singleton NFC normalizer; this does not need to be deleted.
3022 static UErrorCode err = U_ZERO_ERROR;
3023 static const UNormalizer2* nfc = unorm2_getNFCInstance(&err);
3024 // Check if this char and preceding char can compose; if so, is the
3025 // combination supported by the current font.
3026 int32_t composed = unorm2_composePair(nfc, aPrevCh, aCh);
3027 if (composed > 0 && aPrevMatchedFont->HasCharacter(composed)) {
3028 return aPrevMatchedFont;
3029 }
3030 }
3031
3032 // Special cases for NNBSP (as used in Mongolian):
3033 if (aCh == NARROW_NO_BREAK_SPACE) {
3034 // If there is no preceding character, try the font that we'd use
3035 // for the next char (unless it's just another NNBSP; we don't try
3036 // to look ahead through a whole run of them).
3037 if (!aPrevCh && aNextCh && aNextCh != NARROW_NO_BREAK_SPACE) {
3038 gfxFont* nextFont = FindFontForChar(aNextCh, 0, 0, aRunScript,
3039 aPrevMatchedFont, aMatchType);
3040 if (nextFont && nextFont->HasCharacter(aCh)) {
3041 return nextFont;
3042 }
3043 }
3044 // Otherwise, treat NNBSP like a cluster extender (as above) and try
3045 // to continue the preceding font run.
3046 if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) {
3047 return aPrevMatchedFont;
3048 }
3049 }
3050
3051 // To optimize common cases, try the first font in the font-group
3052 // before going into the more detailed checks below
3053 uint32_t fontListLength = mFonts.Length();
3054 uint32_t nextIndex = 0;
3055 bool isJoinControl = gfxFontUtils::IsJoinControl(aCh);
3056 bool wasJoinCauser = gfxFontUtils::IsJoinCauser(aPrevCh);
3057 bool isVarSelector = gfxFontUtils::IsVarSelector(aCh);
3058 bool nextIsVarSelector = gfxFontUtils::IsVarSelector(aNextCh);
3059
3060 // For Unicode hyphens, if not supported in the font then we'll try for
3061 // the ASCII hyphen-minus as a fallback.
3062 uint32_t fallbackChar = (aCh == 0x2010 || aCh == 0x2011) ? '-' : 0;
3063
3064 // Whether we've seen a font that is currently loading a resource that may
3065 // provide this character (so we should not start a new load).
3066 bool loading = false;
3067
3068 // Do we need to explicitly look for a font that does or does not provide a
3069 // color glyph for the given character?
3070 // For characters with no `EMOJI` property, we'll use whatever the family
3071 // list calls for; but if it's a potential emoji codepoint, we need to check
3072 // if there's a variation selector specifically asking for Text-style or
3073 // Emoji-style rendering and look for a suitable font.
3074 eFontPresentation presentation = eFontPresentation::Any;
3075 EmojiPresentation emojiPresentation = GetEmojiPresentation(aCh);
3076 if (emojiPresentation != TextOnly) {
3077 // If the prefer-emoji selector is present, or if it's a default-emoji char
3078 // and the prefer-text selector is NOT present, or if there's a skin-tone
3079 // modifier, we specifically look for a font with a color glyph.
3080 // If the prefer-text selector is present, we specifically look for a font
3081 // that will provide a monochrome glyph.
3082 // Otherwise, we'll accept either color or monochrome font-family entries,
3083 // so that a color font can be explicitly applied via font-family even to
3084 // characters that are not inherently emoji-style.
3085 if (aNextCh == kVariationSelector16 ||
3086 (aNextCh >= kEmojiSkinToneFirst && aNextCh <= kEmojiSkinToneLast)) {
3087 // Emoji presentation is explicitly requested by a variation selector or
3088 // the presence of a skin-tone codepoint.
3089 presentation = eFontPresentation::EmojiExplicit;
3090 } else if (emojiPresentation == EmojiPresentation::EmojiDefault &&
3091 aNextCh != kVariationSelector15) {
3092 // Emoji presentation is the default for this Unicode character. but we
3093 // will allow an explicitly-specified webfont to apply to it, regardless
3094 // of its glyph type.
3095 presentation = eFontPresentation::EmojiDefault;
3096 } else if (aNextCh == kVariationSelector15) {
3097 // Text presentation is explicitly requested.
3098 presentation = eFontPresentation::Text;
3099 }
3100 }
3101
3102 if (!isJoinControl && !wasJoinCauser && !isVarSelector &&
3103 !nextIsVarSelector && presentation == eFontPresentation::Any) {
3104 gfxFont* firstFont = GetFontAt(0, aCh, &loading);
3105 if (firstFont) {
3106 if (firstFont->HasCharacter(aCh) ||
3107 (fallbackChar && firstFont->HasCharacter(fallbackChar))) {
3108 *aMatchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()};
3109 return firstFont;
3110 }
3111
3112 gfxFont* font = nullptr;
3113 if (mFonts[0].CheckForFallbackFaces()) {
3114 font = FindFallbackFaceForChar(mFonts[0], aCh, aNextCh, presentation);
3115 } else if (!firstFont->GetFontEntry()->IsUserFont()) {
3116 // For platform fonts (but not userfonts), we may need to do
3117 // fallback within the family to handle cases where some faces
3118 // such as Italic or Black have reduced character sets compared
3119 // to the family's Regular face.
3120 font = FindFallbackFaceForChar(mFonts[0], aCh, aNextCh, presentation);
3121 }
3122 if (font) {
3123 *aMatchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()};
3124 return font;
3125 }
3126 } else {
3127 if (fontListLength > 0) {
3128 loading = loading || mFonts[0].IsLoadingFor(aCh);
3129 }
3130 }
3131
3132 // we don't need to check the first font again below
3133 ++nextIndex;
3134 }
3135
3136 if (aPrevMatchedFont) {
3137 // Don't switch fonts for control characters, regardless of
3138 // whether they are present in the current font, as they won't
3139 // actually be rendered (see bug 716229)
3140 if (isJoinControl ||
3141 GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_CONTROL) {
3142 return aPrevMatchedFont;
3143 }
3144
3145 // if previous character was a join-causer (ZWJ),
3146 // use the same font as the previous range if we can
3147 if (wasJoinCauser) {
3148 if (aPrevMatchedFont->HasCharacter(aCh)) {
3149 return aPrevMatchedFont;
3150 }
3151 }
3152 }
3153
3154 // If this character is a variation selector or default-ignorable, use the
3155 // previous font regardless of whether it supports the codepoint or not.
3156 // (We don't want to unnecessarily split glyph runs, and the character will
3157 // not be visibly rendered.)
3158 if (isVarSelector || IsDefaultIgnorable(aCh)) {
3159 return aPrevMatchedFont;
3160 }
3161
3162 // Used to remember the first "candidate" font that would provide a fallback
3163 // text-style rendering if no color glyph can be found.
3164 // If we decide NOT to return this font, we must AddRef/Release it to ensure
3165 // that it goes into the global font cache as a candidate for deletion.
3166 // This is done for us by CheckCandidate, but any code path that returns
3167 // WITHOUT calling CheckCandidate needs to handle it explicitly.
3168 gfxFont* candidateFont = nullptr;
3169 FontMatchType candidateMatchType;
3170
3171 // Handle a candidate font that could support the character, returning true
3172 // if we should go ahead and return |f|, false to continue searching.
3173 // If there is already a saved candidate font, and the new candidate is
3174 // accepted, we AddRef/Release the existing candidate so it won't leak.
3175 auto CheckCandidate = [&](gfxFont* f, FontMatchType t) -> bool {
3176 // If no preference, then just accept the font.
3177 if (presentation == eFontPresentation::Any ||
3178 (presentation == eFontPresentation::EmojiDefault &&
3179 f->GetFontEntry()->IsUserFont())) {
3180 RefPtr<gfxFont> autoRefDeref(candidateFont);
3181 *aMatchType = t;
3182 return true;
3183 }
3184 // Does the candidate font provide a color glyph for the current character?
3185 bool hasColorGlyph = f->HasColorGlyphFor(aCh, aNextCh);
3186 // If the provided glyph matches the preference, accept the font.
3187 if (hasColorGlyph == PrefersColor(presentation)) {
3188 RefPtr<gfxFont> autoRefDeref(candidateFont);
3189 *aMatchType = t;
3190 return true;
3191 }
3192 // Otherwise, remember the first potential fallback, but keep searching.
3193 if (!candidateFont) {
3194 candidateFont = f;
3195 candidateMatchType = t;
3196 }
3197 return false;
3198 };
3199
3200 // 1. check remaining fonts in the font group
3201 for (uint32_t i = nextIndex; i < fontListLength; i++) {
3202 FamilyFace& ff = mFonts[i];
3203 if (ff.IsInvalid() || ff.IsLoading()) {
3204 if (ff.IsLoadingFor(aCh)) {
3205 loading = true;
3206 }
3207 continue;
3208 }
3209
3210 gfxFont* font = ff.Font();
3211 if (font) {
3212 // if available, use already-made gfxFont and check for character
3213 if (font->HasCharacter(aCh) ||
3214 (fallbackChar && font->HasCharacter(fallbackChar))) {
3215 if (CheckCandidate(font,
3216 {FontMatchType::Kind::kFontGroup, ff.Generic()})) {
3217 return font;
3218 }
3219 }
3220 } else {
3221 // don't have a gfxFont yet, test charmap before instantiating
3222 gfxFontEntry* fe = ff.FontEntry();
3223 if (fe && fe->mIsUserFontContainer) {
3224 // for userfonts, need to test both the unicode range map and
3225 // the cmap of the platform font entry
3226 gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe);
3227
3228 // never match a character outside the defined unicode range
3229 if (!ufe->CharacterInUnicodeRange(aCh)) {
3230 continue;
3231 }
3232
3233 // Load if not already loaded, unless we've already seen an in-
3234 // progress load that is expected to satisfy this request.
3235 if (!loading &&
3236 ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED) {
3237 ufe->Load();
3238 ff.CheckState(mSkipDrawing);
3239 }
3240
3241 if (ff.IsLoading()) {
3242 loading = true;
3243 }
3244
3245 gfxFontEntry* pfe = ufe->GetPlatformFontEntry();
3246 if (pfe && (pfe->HasCharacter(aCh) ||
3247 (fallbackChar && pfe->HasCharacter(fallbackChar)))) {
3248 font = GetFontAt(i, aCh, &loading);
3249 if (font) {
3250 if (CheckCandidate(font, {FontMatchType::Kind::kFontGroup,
3251 mFonts[i].Generic()})) {
3252 return font;
3253 }
3254 }
3255 }
3256 } else if (fe && (fe->HasCharacter(aCh) ||
3257 (fallbackChar && fe->HasCharacter(fallbackChar)))) {
3258 // for normal platform fonts, after checking the cmap
3259 // build the font via GetFontAt
3260 font = GetFontAt(i, aCh, &loading);
3261 if (font) {
3262 if (CheckCandidate(font, {FontMatchType::Kind::kFontGroup,
3263 mFonts[i].Generic()})) {
3264 return font;
3265 }
3266 }
3267 }
3268 }
3269
3270 // check other family faces if needed
3271 if (ff.CheckForFallbackFaces()) {
3272 #ifdef DEBUG
3273 if (i > 0) {
3274 fontlist::FontList* list =
3275 gfxPlatformFontList::PlatformFontList()->SharedFontList();
3276 nsCString s1 = mFonts[i - 1].IsSharedFamily()
3277 ? mFonts[i - 1].SharedFamily()->Key().AsString(list)
3278 : mFonts[i - 1].OwnedFamily()->Name();
3279 nsCString s2 = ff.IsSharedFamily()
3280 ? ff.SharedFamily()->Key().AsString(list)
3281 : ff.OwnedFamily()->Name();
3282 MOZ_ASSERT(!mFonts[i - 1].CheckForFallbackFaces() || !s1.Equals(s2),
3283 "should only do fallback once per font family");
3284 }
3285 #endif
3286 font = FindFallbackFaceForChar(ff, aCh, aNextCh, presentation);
3287 if (font) {
3288 if (CheckCandidate(font,
3289 {FontMatchType::Kind::kFontGroup, ff.Generic()})) {
3290 return font;
3291 }
3292 }
3293 } else {
3294 // For platform fonts, but not user fonts, consider intra-family
3295 // fallback to handle styles with reduced character sets (see
3296 // also above).
3297 gfxFontEntry* fe = ff.FontEntry();
3298 if (fe && !fe->mIsUserFontContainer && !fe->IsUserFont()) {
3299 font = FindFallbackFaceForChar(ff, aCh, aNextCh, presentation);
3300 if (font) {
3301 if (CheckCandidate(font,
3302 {FontMatchType::Kind::kFontGroup, ff.Generic()})) {
3303 return font;
3304 }
3305 }
3306 }
3307 }
3308 }
3309
3310 if (fontListLength == 0) {
3311 gfxFont* defaultFont = GetDefaultFont();
3312 if (defaultFont->HasCharacter(aCh) ||
3313 (fallbackChar && defaultFont->HasCharacter(fallbackChar))) {
3314 if (CheckCandidate(defaultFont, FontMatchType::Kind::kFontGroup)) {
3315 return defaultFont;
3316 }
3317 }
3318 }
3319
3320 // If character is in Private Use Area, or is unassigned in Unicode, don't do
3321 // matching against pref or system fonts. We only support such codepoints
3322 // when used with an explicitly-specified font, as they have no standard/
3323 // interoperable meaning.
3324 // Also don't attempt any fallback for control characters or noncharacters,
3325 // where we won't be rendering a glyph anyhow, or for codepoints where global
3326 // fallback has already noted a failure.
3327 if (gfxPlatformFontList::PlatformFontList()->SkipFontFallbackForChar(aCh) ||
3328 GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED) {
3329 if (candidateFont) {
3330 *aMatchType = candidateMatchType;
3331 }
3332 return candidateFont;
3333 }
3334
3335 // 2. search pref fonts
3336 gfxFont* font = WhichPrefFontSupportsChar(aCh, aNextCh, presentation);
3337 if (font) {
3338 if (PrefersColor(presentation) &&
3339 Preferences::HasUserValue("font.name-list.emoji")) {
3340 // For emoji, always accept the font from preferences if it's explicitly
3341 // user-set, even if it isn't actually a color-emoji font, as some users
3342 // may want to set their emoji font preference to a monochrome font like
3343 // Symbola.
3344 // So a user-provided font.name-list.emoji preference takes precedence
3345 // over the Unicode presentation style here.
3346 RefPtr<gfxFont> autoRefDeref(candidateFont);
3347 *aMatchType = FontMatchType::Kind::kPrefsFallback;
3348 return font;
3349 }
3350 if (CheckCandidate(font, FontMatchType::Kind::kPrefsFallback)) {
3351 return font;
3352 }
3353 // Don't leak `font` if we decided not to return it.
3354 RefPtr<gfxFont> autoRefDeref(font);
3355 }
3356
3357 // For fallback searches, we don't want to use a color-emoji font unless
3358 // emoji-style presentation is specifically required, so we map Any to
3359 // Text here.
3360 if (presentation == eFontPresentation::Any) {
3361 presentation = eFontPresentation::Text;
3362 }
3363
3364 // 3. use fallback fonts
3365 // -- before searching for something else check the font used for the
3366 // previous character
3367 if (aPrevMatchedFont &&
3368 (aPrevMatchedFont->HasCharacter(aCh) ||
3369 (fallbackChar && aPrevMatchedFont->HasCharacter(fallbackChar)))) {
3370 if (CheckCandidate(aPrevMatchedFont,
3371 FontMatchType::Kind::kSystemFallback)) {
3372 return aPrevMatchedFont;
3373 }
3374 }
3375
3376 // for known "space" characters, don't do a full system-fallback search;
3377 // we'll synthesize appropriate-width spaces instead of missing-glyph boxes
3378 if (GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR &&
3379 GetFirstValidFont()->SynthesizeSpaceWidth(aCh) >= 0.0) {
3380 RefPtr<gfxFont> autoRefDeref(candidateFont);
3381 return nullptr;
3382 }
3383
3384 // -- otherwise look for other stuff
3385 font = WhichSystemFontSupportsChar(aCh, aNextCh, aRunScript, presentation);
3386 if (font) {
3387 if (CheckCandidate(font, FontMatchType::Kind::kSystemFallback)) {
3388 return font;
3389 }
3390 RefPtr<gfxFont> autoRefDeref(font);
3391 }
3392 if (candidateFont) {
3393 *aMatchType = candidateMatchType;
3394 }
3395 return candidateFont;
3396 }
3397
3398 template <typename T>
ComputeRanges(nsTArray<TextRange> & aRanges,const T * aString,uint32_t aLength,Script aRunScript,gfx::ShapedTextFlags aOrientation)3399 void gfxFontGroup::ComputeRanges(nsTArray<TextRange>& aRanges, const T* aString,
3400 uint32_t aLength, Script aRunScript,
3401 gfx::ShapedTextFlags aOrientation) {
3402 NS_ASSERTION(aRanges.Length() == 0, "aRanges must be initially empty");
3403 NS_ASSERTION(aLength > 0, "don't call ComputeRanges for zero-length text");
3404
3405 uint32_t prevCh = 0;
3406 uint32_t nextCh = aString[0];
3407 if (sizeof(T) == sizeof(char16_t)) {
3408 if (aLength > 1 && NS_IS_SURROGATE_PAIR(nextCh, aString[1])) {
3409 nextCh = SURROGATE_TO_UCS4(nextCh, aString[1]);
3410 }
3411 }
3412 int32_t lastRangeIndex = -1;
3413
3414 // initialize prevFont to the group's primary font, so that this will be
3415 // used for string-initial control chars, etc rather than risk hitting font
3416 // fallback for these (bug 716229)
3417 StyleGenericFontFamily generic = StyleGenericFontFamily::None;
3418 gfxFont* prevFont = GetFirstValidFont(' ', &generic);
3419
3420 // if we use the initial value of prevFont, we treat this as a match from
3421 // the font group; fixes bug 978313
3422 FontMatchType matchType = {FontMatchType::Kind::kFontGroup, generic};
3423
3424 for (uint32_t i = 0; i < aLength; i++) {
3425 const uint32_t origI = i; // save off in case we increase for surrogate
3426
3427 // set up current ch
3428 uint32_t ch = nextCh;
3429
3430 // Get next char (if any) so that FindFontForChar can look ahead
3431 // for a possible variation selector.
3432
3433 if (sizeof(T) == sizeof(char16_t)) {
3434 // In 16-bit case only, check for surrogate pairs.
3435 if (ch > 0xffffu) {
3436 i++;
3437 }
3438 if (i < aLength - 1) {
3439 nextCh = aString[i + 1];
3440 if (i + 2 < aLength && NS_IS_SURROGATE_PAIR(nextCh, aString[i + 2])) {
3441 nextCh = SURROGATE_TO_UCS4(nextCh, aString[i + 2]);
3442 }
3443 } else {
3444 nextCh = 0;
3445 }
3446 } else {
3447 // 8-bit case is trivial.
3448 nextCh = i < aLength - 1 ? aString[i + 1] : 0;
3449 }
3450
3451 if (ch == 0xa0) {
3452 ch = ' ';
3453 }
3454
3455 gfxFont* font;
3456
3457 // Find the font for this char; but try to avoid calling the expensive
3458 // FindFontForChar method for the most common case, where the first
3459 // font in the list supports the current char, and it is not one of
3460 // the special cases where FindFontForChar will attempt to propagate
3461 // the font selected for an adjacent character.
3462 if ((font = GetFontAt(0, ch)) != nullptr && font->HasCharacter(ch) &&
3463 (sizeof(T) == sizeof(uint8_t) ||
3464 (!IsClusterExtender(ch) && ch != NARROW_NO_BREAK_SPACE &&
3465 !gfxFontUtils::IsJoinControl(ch) &&
3466 !gfxFontUtils::IsJoinCauser(prevCh) &&
3467 !gfxFontUtils::IsVarSelector(ch) &&
3468 GetEmojiPresentation(ch) == TextOnly))) {
3469 matchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()};
3470 } else {
3471 font =
3472 FindFontForChar(ch, prevCh, nextCh, aRunScript, prevFont, &matchType);
3473 }
3474
3475 #ifndef RELEASE_OR_BETA
3476 if (MOZ_UNLIKELY(mTextPerf)) {
3477 if (matchType.kind == FontMatchType::Kind::kPrefsFallback) {
3478 mTextPerf->current.fallbackPrefs++;
3479 } else if (matchType.kind == FontMatchType::Kind::kSystemFallback) {
3480 mTextPerf->current.fallbackSystem++;
3481 }
3482 }
3483 #endif
3484
3485 prevCh = ch;
3486
3487 ShapedTextFlags orient = aOrientation;
3488 if (aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
3489 // For CSS text-orientation:mixed, we need to resolve orientation
3490 // on a per-character basis using the UTR50 orientation property.
3491 switch (GetVerticalOrientation(ch)) {
3492 case VERTICAL_ORIENTATION_U:
3493 case VERTICAL_ORIENTATION_Tu:
3494 orient = ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
3495 break;
3496 case VERTICAL_ORIENTATION_Tr: {
3497 // We check for a vertical presentation form first as that's
3498 // likely to be cheaper than inspecting lookups to see if the
3499 // 'vert' feature is going to handle this character, and if the
3500 // presentation form is available then it will be used as
3501 // fallback if needed, so it's OK if the feature is missing.
3502 //
3503 // Because "common" CJK punctuation characters in isolation will be
3504 // resolved to Bopomofo script (as the first script listed in their
3505 // ScriptExtensions property), but this is not always well supported
3506 // by fonts' OpenType tables, we also try Han script; harfbuzz will
3507 // apply a 'vert' feature from any available script (see
3508 // https://github.com/harfbuzz/harfbuzz/issues/63) when shaping,
3509 // so this is OK. It's not quite as general as what harfbuzz does
3510 // (it will find the feature in *any* script), but should be enough
3511 // for likely real-world examples.
3512 uint32_t v = gfxHarfBuzzShaper::GetVerticalPresentationForm(ch);
3513 const uint32_t kVert = HB_TAG('v', 'e', 'r', 't');
3514 orient = (!font || (v && font->HasCharacter(v)) ||
3515 font->FeatureWillHandleChar(aRunScript, kVert, ch) ||
3516 (aRunScript == Script::BOPOMOFO &&
3517 font->FeatureWillHandleChar(Script::HAN, kVert, ch)))
3518 ? ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
3519 : ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
3520 break;
3521 }
3522 case VERTICAL_ORIENTATION_R:
3523 orient = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
3524 break;
3525 }
3526 }
3527
3528 if (lastRangeIndex == -1) {
3529 // first char ==> make a new range
3530 aRanges.AppendElement(TextRange(0, 1, font, matchType, orient));
3531 lastRangeIndex++;
3532 prevFont = font;
3533 } else {
3534 // if font or orientation has changed, make a new range...
3535 // unless ch is a variation selector (bug 1248248)
3536 TextRange& prevRange = aRanges[lastRangeIndex];
3537 if (prevRange.font != font ||
3538 (prevRange.orientation != orient && !IsClusterExtender(ch))) {
3539 // close out the previous range
3540 prevRange.end = origI;
3541 aRanges.AppendElement(TextRange(origI, i + 1, font, matchType, orient));
3542 lastRangeIndex++;
3543
3544 // update prevFont for the next match, *unless* we switched
3545 // fonts on a ZWJ, in which case propagating the changed font
3546 // is probably not a good idea (see bug 619511)
3547 if (sizeof(T) == sizeof(uint8_t) || !gfxFontUtils::IsJoinCauser(ch)) {
3548 prevFont = font;
3549 }
3550 } else {
3551 prevRange.matchType |= matchType;
3552 }
3553 }
3554 }
3555
3556 aRanges[lastRangeIndex].end = aLength;
3557
3558 #ifndef RELEASE_OR_BETA
3559 LogModule* log = mStyle.systemFont ? gfxPlatform::GetLog(eGfxLog_textrunui)
3560 : gfxPlatform::GetLog(eGfxLog_textrun);
3561
3562 if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Debug))) {
3563 nsAutoCString lang;
3564 mLanguage->ToUTF8String(lang);
3565
3566 // collect the font matched for each range
3567 nsAutoCString fontMatches;
3568 for (size_t i = 0, i_end = aRanges.Length(); i < i_end; i++) {
3569 const TextRange& r = aRanges[i];
3570 nsAutoCString matchTypes;
3571 if (r.matchType.kind & FontMatchType::Kind::kFontGroup) {
3572 matchTypes.AppendLiteral("list");
3573 }
3574 if (r.matchType.kind & FontMatchType::Kind::kPrefsFallback) {
3575 if (!matchTypes.IsEmpty()) {
3576 matchTypes.AppendLiteral(",");
3577 }
3578 matchTypes.AppendLiteral("prefs");
3579 }
3580 if (r.matchType.kind & FontMatchType::Kind::kPrefsFallback) {
3581 if (!matchTypes.IsEmpty()) {
3582 matchTypes.AppendLiteral(",");
3583 }
3584 matchTypes.AppendLiteral("sys");
3585 }
3586 fontMatches.AppendPrintf(
3587 " [%u:%u] %.200s (%s)", r.start, r.end,
3588 (r.font.get() ? r.font->GetName().get() : "<null>"),
3589 matchTypes.get());
3590 }
3591 MOZ_LOG(log, LogLevel::Debug,
3592 ("(%s-fontmatching) fontgroup: [%s] default: %s lang: %s script: %d"
3593 "%s\n",
3594 (mStyle.systemFont ? "textrunui" : "textrun"),
3595 FamilyListToString(mFamilyList).get(),
3596 (mFamilyList.fallback == StyleGenericFontFamily::Serif
3597 ? "serif"
3598 : (mFamilyList.fallback == StyleGenericFontFamily::SansSerif
3599 ? "sans-serif"
3600 : "none")),
3601 lang.get(), static_cast<int>(aRunScript), fontMatches.get()));
3602 }
3603 #endif
3604 }
3605
GetUserFontSet()3606 gfxUserFontSet* gfxFontGroup::GetUserFontSet() { return mUserFontSet; }
3607
SetUserFontSet(gfxUserFontSet * aUserFontSet)3608 void gfxFontGroup::SetUserFontSet(gfxUserFontSet* aUserFontSet) {
3609 if (aUserFontSet == mUserFontSet) {
3610 return;
3611 }
3612 mUserFontSet = aUserFontSet;
3613 mCurrGeneration = GetGeneration() - 1;
3614 UpdateUserFonts();
3615 }
3616
GetGeneration()3617 uint64_t gfxFontGroup::GetGeneration() {
3618 if (!mUserFontSet) return 0;
3619 return mUserFontSet->GetGeneration();
3620 }
3621
GetRebuildGeneration()3622 uint64_t gfxFontGroup::GetRebuildGeneration() {
3623 if (!mUserFontSet) return 0;
3624 return mUserFontSet->GetRebuildGeneration();
3625 }
3626
UpdateUserFonts()3627 void gfxFontGroup::UpdateUserFonts() {
3628 if (mCurrGeneration < GetRebuildGeneration()) {
3629 // fonts in userfont set changed, need to redo the fontlist
3630 mFonts.Clear();
3631 ClearCachedData();
3632 BuildFontList();
3633 mCurrGeneration = GetGeneration();
3634 } else if (mCurrGeneration != GetGeneration()) {
3635 // load state change occurred, verify load state and validity of fonts
3636 ClearCachedData();
3637
3638 uint32_t len = mFonts.Length();
3639 for (uint32_t i = 0; i < len; i++) {
3640 FamilyFace& ff = mFonts[i];
3641 if (ff.Font() || !ff.IsUserFontContainer()) {
3642 continue;
3643 }
3644 ff.CheckState(mSkipDrawing);
3645 }
3646
3647 mCurrGeneration = GetGeneration();
3648 }
3649 }
3650
ContainsUserFont(const gfxUserFontEntry * aUserFont)3651 bool gfxFontGroup::ContainsUserFont(const gfxUserFontEntry* aUserFont) {
3652 UpdateUserFonts();
3653 // search through the fonts list for a specific user font
3654 uint32_t len = mFonts.Length();
3655 for (uint32_t i = 0; i < len; i++) {
3656 FamilyFace& ff = mFonts[i];
3657 if (ff.EqualsUserFont(aUserFont)) {
3658 return true;
3659 }
3660 }
3661 return false;
3662 }
3663
WhichPrefFontSupportsChar(uint32_t aCh,uint32_t aNextCh,eFontPresentation aPresentation)3664 gfxFont* gfxFontGroup::WhichPrefFontSupportsChar(
3665 uint32_t aCh, uint32_t aNextCh, eFontPresentation aPresentation) {
3666 eFontPrefLang charLang;
3667 gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
3668
3669 if (PrefersColor(aPresentation)) {
3670 charLang = eFontPrefLang_Emoji;
3671 } else {
3672 // get the pref font list if it hasn't been set up already
3673 charLang = pfl->GetFontPrefLangFor(aCh);
3674 }
3675
3676 // if the last pref font was the first family in the pref list, no need to
3677 // recheck through a list of families
3678 if (mLastPrefFont && charLang == mLastPrefLang && mLastPrefFirstFont &&
3679 mLastPrefFont->HasCharacter(aCh)) {
3680 return mLastPrefFont;
3681 }
3682
3683 // based on char lang and page lang, set up list of pref lang fonts to check
3684 eFontPrefLang prefLangs[kMaxLenPrefLangList];
3685 uint32_t i, numLangs = 0;
3686
3687 pfl->GetLangPrefs(prefLangs, numLangs, charLang, mPageLang);
3688
3689 for (i = 0; i < numLangs; i++) {
3690 eFontPrefLang currentLang = prefLangs[i];
3691 StyleGenericFontFamily generic =
3692 mFirstGeneric != StyleGenericFontFamily::None
3693 ? mFirstGeneric
3694 : pfl->GetDefaultGeneric(currentLang);
3695 gfxPlatformFontList::PrefFontList* families =
3696 pfl->GetPrefFontsLangGroup(generic, currentLang);
3697 NS_ASSERTION(families, "no pref font families found");
3698
3699 // find the first pref font that includes the character
3700 uint32_t j, numPrefs;
3701 numPrefs = families->Length();
3702 for (j = 0; j < numPrefs; j++) {
3703 // look up the appropriate face
3704 FontFamily family = (*families)[j];
3705 if (family.IsNull()) {
3706 continue;
3707 }
3708
3709 // if a pref font is used, it's likely to be used again in the same text
3710 // run. the style doesn't change so the face lookup can be cached rather
3711 // than calling FindOrMakeFont repeatedly. speeds up FindFontForChar
3712 // lookup times for subsequent pref font lookups
3713 if (family == mLastPrefFamily && mLastPrefFont->HasCharacter(aCh)) {
3714 return mLastPrefFont;
3715 }
3716
3717 gfxFontEntry* fe = nullptr;
3718 if (family.mIsShared) {
3719 fontlist::Family* fam = family.mShared;
3720 if (!fam->IsInitialized()) {
3721 Unused << pfl->InitializeFamily(fam);
3722 }
3723 fontlist::Face* face =
3724 fam->FindFaceForStyle(pfl->SharedFontList(), mStyle);
3725 if (face) {
3726 fe = pfl->GetOrCreateFontEntry(face, fam);
3727 }
3728 } else {
3729 fe = family.mUnshared->FindFontForStyle(mStyle);
3730 }
3731 if (!fe) {
3732 continue;
3733 }
3734
3735 // if ch in cmap, create and return a gfxFont
3736 gfxFont* prefFont = nullptr;
3737 if (fe->HasCharacter(aCh)) {
3738 prefFont = fe->FindOrMakeFont(&mStyle);
3739 if (!prefFont) {
3740 continue;
3741 }
3742 }
3743
3744 // If the char was not available, see if we can fall back to an
3745 // alternative face in the same family.
3746 if (!prefFont) {
3747 prefFont = family.mIsShared
3748 ? FindFallbackFaceForChar(family.mShared, aCh, aNextCh,
3749 aPresentation)
3750 : FindFallbackFaceForChar(family.mUnshared, aCh, aNextCh,
3751 aPresentation);
3752 }
3753 if (prefFont) {
3754 mLastPrefFamily = family;
3755 mLastPrefFont = prefFont;
3756 mLastPrefLang = charLang;
3757 mLastPrefFirstFont = (i == 0 && j == 0);
3758 if (mFontMatchingStats) {
3759 mFontMatchingStats->mFallbacks |= FallbackTypes::FallbackToPrefsFont;
3760 }
3761 return prefFont;
3762 }
3763 }
3764 }
3765
3766 return nullptr;
3767 }
3768
WhichSystemFontSupportsChar(uint32_t aCh,uint32_t aNextCh,Script aRunScript,eFontPresentation aPresentation)3769 gfxFont* gfxFontGroup::WhichSystemFontSupportsChar(
3770 uint32_t aCh, uint32_t aNextCh, Script aRunScript,
3771 eFontPresentation aPresentation) {
3772 FontVisibility visibility;
3773 gfxFont* font =
3774 gfxPlatformFontList::PlatformFontList()->SystemFindFontForChar(
3775 aCh, aNextCh, aRunScript, aPresentation, &mStyle, &visibility,
3776 mFontMatchingStats);
3777 if (font) {
3778 if (mFontMatchingStats) {
3779 switch (visibility) {
3780 case FontVisibility::Unknown:
3781 // We don't currently track stats for systems lacking font visibility
3782 // categories, so just ignore this.
3783 break;
3784 case FontVisibility::Base:
3785 mFontMatchingStats->mFallbacks |= FallbackTypes::FallbackToBaseFont;
3786 break;
3787 case FontVisibility::LangPack:
3788 mFontMatchingStats->mFallbacks |=
3789 FallbackTypes::FallbackToLangPackFont;
3790 break;
3791 case FontVisibility::User:
3792 mFontMatchingStats->mFallbacks |= FallbackTypes::FallbackToUserFont;
3793 break;
3794 case FontVisibility::Hidden:
3795 // If macOS font fallback uses the system font, we consider this a
3796 // base OS font even though it wouldn't be exposed via font-family.
3797 mFontMatchingStats->mFallbacks |= FallbackTypes::FallbackToBaseFont;
3798 break;
3799 case FontVisibility::Webfont:
3800 default:
3801 MOZ_ASSERT_UNREACHABLE("this can't happen!");
3802 break;
3803 }
3804 }
3805 return font;
3806 }
3807
3808 return nullptr;
3809 }
3810
Flush()3811 void gfxMissingFontRecorder::Flush() {
3812 static bool mNotifiedFontsInitialized = false;
3813 static uint32_t mNotifiedFonts[gfxMissingFontRecorder::kNumScriptBitsWords];
3814 if (!mNotifiedFontsInitialized) {
3815 memset(&mNotifiedFonts, 0, sizeof(mNotifiedFonts));
3816 mNotifiedFontsInitialized = true;
3817 }
3818
3819 nsAutoString fontNeeded;
3820 for (uint32_t i = 0; i < kNumScriptBitsWords; ++i) {
3821 mMissingFonts[i] &= ~mNotifiedFonts[i];
3822 if (!mMissingFonts[i]) {
3823 continue;
3824 }
3825 for (uint32_t j = 0; j < 32; ++j) {
3826 if (!(mMissingFonts[i] & (1 << j))) {
3827 continue;
3828 }
3829 mNotifiedFonts[i] |= (1 << j);
3830 if (!fontNeeded.IsEmpty()) {
3831 fontNeeded.Append(char16_t(','));
3832 }
3833 uint32_t sc = i * 32 + j;
3834 MOZ_ASSERT(sc < static_cast<uint32_t>(Script::NUM_SCRIPT_CODES),
3835 "how did we set the bit for an invalid script code?");
3836 uint32_t tag = GetScriptTagForCode(static_cast<Script>(sc));
3837 fontNeeded.Append(char16_t(tag >> 24));
3838 fontNeeded.Append(char16_t((tag >> 16) & 0xff));
3839 fontNeeded.Append(char16_t((tag >> 8) & 0xff));
3840 fontNeeded.Append(char16_t(tag & 0xff));
3841 }
3842 mMissingFonts[i] = 0;
3843 }
3844 if (!fontNeeded.IsEmpty()) {
3845 nsCOMPtr<nsIObserverService> service = GetObserverService();
3846 service->NotifyObservers(nullptr, "font-needed", fontNeeded.get());
3847 }
3848 }
3849