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