no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / gfx / thebes / gfxTextRun.cpp
blob0b1b5ed83f35945697441dbd65d70d03eb8875f1
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/ServoStyleSet.h"
15 #include "mozilla/Sprintf.h"
16 #include "mozilla/StaticPresData.h"
18 #include "gfxContext.h"
19 #include "gfxFontConstants.h"
20 #include "gfxFontMissingGlyphs.h"
21 #include "gfxScriptItemizer.h"
22 #include "nsUnicodeProperties.h"
23 #include "nsStyleConsts.h"
24 #include "nsStyleUtil.h"
25 #include "mozilla/Likely.h"
26 #include "gfx2DGlue.h"
27 #include "mozilla/gfx/Logging.h" // for gfxCriticalError
28 #include "mozilla/intl/String.h"
29 #include "mozilla/intl/UnicodeProperties.h"
30 #include "mozilla/UniquePtr.h"
31 #include "mozilla/Unused.h"
32 #include "SharedFontList-impl.h"
33 #include "TextDrawTarget.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 void gfxTextRun::GlyphRunIterator::NextRun() {
65 if (mReverse) {
66 if (mGlyphRun == mTextRun->mGlyphRuns.begin()) {
67 mGlyphRun = nullptr;
68 return;
70 --mGlyphRun;
71 } else {
72 MOZ_DIAGNOSTIC_ASSERT(mGlyphRun != mTextRun->mGlyphRuns.end());
73 ++mGlyphRun;
74 if (mGlyphRun == mTextRun->mGlyphRuns.end()) {
75 mGlyphRun = nullptr;
76 return;
79 if (mGlyphRun->mCharacterOffset >= mEndOffset) {
80 mGlyphRun = nullptr;
81 return;
83 uint32_t glyphRunEndOffset = mGlyphRun == mTextRun->mGlyphRuns.end() - 1
84 ? mTextRun->GetLength()
85 : (mGlyphRun + 1)->mCharacterOffset;
86 if (glyphRunEndOffset < mStartOffset) {
87 mGlyphRun = nullptr;
88 return;
90 mStringEnd = std::min(mEndOffset, glyphRunEndOffset);
91 mStringStart = std::max(mStartOffset, mGlyphRun->mCharacterOffset);
94 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
95 static void AccountStorageForTextRun(gfxTextRun* aTextRun, int32_t aSign) {
96 // Ignores detailed glyphs... we don't know when those have been constructed
97 // Also ignores gfxSkipChars dynamic storage (which won't be anything
98 // for preformatted text)
99 // Also ignores GlyphRun array, again because it hasn't been constructed
100 // by the time this gets called. If there's only one glyphrun that's stored
101 // directly in the textrun anyway so no additional overhead.
102 uint32_t length = aTextRun->GetLength();
103 int32_t bytes = length * sizeof(gfxTextRun::CompressedGlyph);
104 bytes += sizeof(gfxTextRun);
105 gTextRunStorage += bytes * aSign;
106 gTextRunStorageHighWaterMark =
107 std::max(gTextRunStorageHighWaterMark, gTextRunStorage);
109 #endif
111 bool gfxTextRun::NeedsGlyphExtents() const {
112 if (GetFlags() & gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX) {
113 return true;
115 for (const auto& run : mGlyphRuns) {
116 if (run.mFont->GetFontEntry()->IsUserFont()) {
117 return true;
120 return false;
123 // Helper for textRun creation to preallocate storage for glyph records;
124 // this function returns a pointer to the newly-allocated glyph storage.
125 // Returns nullptr if allocation fails.
126 void* gfxTextRun::AllocateStorageForTextRun(size_t aSize, uint32_t aLength) {
127 // Allocate the storage we need, returning nullptr on failure rather than
128 // throwing an exception (because web content can create huge runs).
129 void* storage = malloc(aSize + aLength * sizeof(CompressedGlyph));
130 if (!storage) {
131 NS_WARNING("failed to allocate storage for text run!");
132 return nullptr;
135 // Initialize the glyph storage (beyond aSize) to zero
136 memset(reinterpret_cast<char*>(storage) + aSize, 0,
137 aLength * sizeof(CompressedGlyph));
139 return storage;
142 already_AddRefed<gfxTextRun> gfxTextRun::Create(
143 const gfxTextRunFactory::Parameters* aParams, uint32_t aLength,
144 gfxFontGroup* aFontGroup, gfx::ShapedTextFlags aFlags,
145 nsTextFrameUtils::Flags aFlags2) {
146 void* storage = AllocateStorageForTextRun(sizeof(gfxTextRun), aLength);
147 if (!storage) {
148 return nullptr;
151 RefPtr<gfxTextRun> result =
152 new (storage) gfxTextRun(aParams, aLength, aFontGroup, aFlags, aFlags2);
153 return result.forget();
156 gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters* aParams,
157 uint32_t aLength, gfxFontGroup* aFontGroup,
158 gfx::ShapedTextFlags aFlags,
159 nsTextFrameUtils::Flags aFlags2)
160 : gfxShapedText(aLength, aFlags, aParams->mAppUnitsPerDevUnit),
161 mUserData(aParams->mUserData),
162 mFontGroup(aFontGroup),
163 mFlags2(aFlags2),
164 mReleasedFontGroup(false),
165 mReleasedFontGroupSkippedDrawing(false),
166 mShapingState(eShapingState_Normal) {
167 NS_ASSERTION(mAppUnitsPerDevUnit > 0, "Invalid app unit scale");
168 NS_ADDREF(mFontGroup);
170 #ifndef RELEASE_OR_BETA
171 gfxTextPerfMetrics* tp = aFontGroup->GetTextPerfMetrics();
172 if (tp) {
173 tp->current.textrunConst++;
175 #endif
177 mCharacterGlyphs = reinterpret_cast<CompressedGlyph*>(this + 1);
179 if (aParams->mSkipChars) {
180 mSkipChars.TakeFrom(aParams->mSkipChars);
183 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
184 AccountStorageForTextRun(this, 1);
185 #endif
187 mDontSkipDrawing =
188 !!(aFlags2 & nsTextFrameUtils::Flags::DontSkipDrawingForPendingUserFonts);
191 gfxTextRun::~gfxTextRun() {
192 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
193 AccountStorageForTextRun(this, -1);
194 #endif
195 #ifdef DEBUG
196 // Make it easy to detect a dead text run
197 mFlags = ~gfx::ShapedTextFlags();
198 mFlags2 = ~nsTextFrameUtils::Flags();
199 #endif
201 // The cached ellipsis textrun (if any) in a fontgroup will have already
202 // been told to release its reference to the group, so we mustn't do that
203 // again here.
204 if (!mReleasedFontGroup) {
205 #ifndef RELEASE_OR_BETA
206 gfxTextPerfMetrics* tp = mFontGroup->GetTextPerfMetrics();
207 if (tp) {
208 tp->current.textrunDestr++;
210 #endif
211 NS_RELEASE(mFontGroup);
215 void gfxTextRun::ReleaseFontGroup() {
216 NS_ASSERTION(!mReleasedFontGroup, "doubly released!");
218 // After dropping our reference to the font group, we'll no longer be able
219 // to get up-to-date results for ShouldSkipDrawing(). Store the current
220 // value in mReleasedFontGroupSkippedDrawing.
222 // (It doesn't actually matter that we can't get up-to-date results for
223 // ShouldSkipDrawing(), since the only text runs that we call
224 // ReleaseFontGroup() for are ellipsis text runs, and we ask the font
225 // group for a new ellipsis text run each time we want to draw one,
226 // and ensure that the cached one is cleared in ClearCachedData() when
227 // font loading status changes.)
228 mReleasedFontGroupSkippedDrawing = mFontGroup->ShouldSkipDrawing();
230 NS_RELEASE(mFontGroup);
231 mReleasedFontGroup = true;
234 bool gfxTextRun::SetPotentialLineBreaks(Range aRange,
235 const uint8_t* aBreakBefore) {
236 NS_ASSERTION(aRange.end <= GetLength(), "Overflow");
238 uint32_t changed = 0;
239 CompressedGlyph* cg = mCharacterGlyphs + aRange.start;
240 const CompressedGlyph* const end = cg + aRange.Length();
241 while (cg < end) {
242 uint8_t canBreak = *aBreakBefore++;
243 if (canBreak && !cg->IsClusterStart()) {
244 // XXX If we replace the line-breaker with one based more closely
245 // on UAX#14 (e.g. using ICU), this may not be needed any more.
246 // Avoid possible breaks inside a cluster, EXCEPT when the previous
247 // character was a space (compare UAX#14 rules LB9, LB10).
248 if (cg == mCharacterGlyphs || !(cg - 1)->CharIsSpace()) {
249 canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE;
252 // If a break is allowed here, set the break flag, but don't clear a
253 // possible pre-existing emergency-break flag already in the run.
254 if (canBreak) {
255 changed |= cg->SetCanBreakBefore(canBreak);
257 ++cg;
259 return changed != 0;
262 gfxTextRun::LigatureData gfxTextRun::ComputeLigatureData(
263 Range aPartRange, const PropertyProvider* aProvider) const {
264 NS_ASSERTION(aPartRange.start < aPartRange.end,
265 "Computing ligature data for empty range");
266 NS_ASSERTION(aPartRange.end <= GetLength(), "Character length overflow");
268 LigatureData result;
269 const CompressedGlyph* charGlyphs = mCharacterGlyphs;
271 uint32_t i;
272 for (i = aPartRange.start; !charGlyphs[i].IsLigatureGroupStart(); --i) {
273 NS_ASSERTION(i > 0, "Ligature at the start of the run??");
275 result.mRange.start = i;
276 for (i = aPartRange.start + 1;
277 i < GetLength() && !charGlyphs[i].IsLigatureGroupStart(); ++i) {
279 result.mRange.end = i;
281 int32_t ligatureWidth = GetAdvanceForGlyphs(result.mRange);
282 // Count the number of started clusters we have seen
283 uint32_t totalClusterCount = 0;
284 uint32_t partClusterIndex = 0;
285 uint32_t partClusterCount = 0;
286 for (i = result.mRange.start; i < result.mRange.end; ++i) {
287 // Treat the first character of the ligature as the start of a
288 // cluster for our purposes of allocating ligature width to its
289 // characters.
290 if (i == result.mRange.start || charGlyphs[i].IsClusterStart()) {
291 ++totalClusterCount;
292 if (i < aPartRange.start) {
293 ++partClusterIndex;
294 } else if (i < aPartRange.end) {
295 ++partClusterCount;
299 NS_ASSERTION(totalClusterCount > 0, "Ligature involving no clusters??");
300 result.mPartAdvance = partClusterIndex * (ligatureWidth / totalClusterCount);
301 result.mPartWidth = partClusterCount * (ligatureWidth / totalClusterCount);
303 // Any rounding errors are apportioned to the final part of the ligature,
304 // so that measuring all parts of a ligature and summing them is equal to
305 // the ligature width.
306 if (aPartRange.end == result.mRange.end) {
307 gfxFloat allParts = totalClusterCount * (ligatureWidth / totalClusterCount);
308 result.mPartWidth += ligatureWidth - allParts;
311 if (partClusterCount == 0) {
312 // nothing to draw
313 result.mClipBeforePart = result.mClipAfterPart = true;
314 } else {
315 // Determine whether we should clip before or after this part when
316 // drawing its slice of the ligature.
317 // We need to clip before the part if any cluster is drawn before
318 // this part.
319 result.mClipBeforePart = partClusterIndex > 0;
320 // We need to clip after the part if any cluster is drawn after
321 // this part.
322 result.mClipAfterPart =
323 partClusterIndex + partClusterCount < totalClusterCount;
326 if (aProvider && (mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) {
327 gfxFont::Spacing spacing;
328 if (aPartRange.start == result.mRange.start) {
329 aProvider->GetSpacing(Range(aPartRange.start, aPartRange.start + 1),
330 &spacing);
331 result.mPartWidth += spacing.mBefore;
333 if (aPartRange.end == result.mRange.end) {
334 aProvider->GetSpacing(Range(aPartRange.end - 1, aPartRange.end),
335 &spacing);
336 result.mPartWidth += spacing.mAfter;
340 return result;
343 gfxFloat gfxTextRun::ComputePartialLigatureWidth(
344 Range aPartRange, const PropertyProvider* aProvider) const {
345 if (aPartRange.start >= aPartRange.end) return 0;
346 LigatureData data = ComputeLigatureData(aPartRange, aProvider);
347 return data.mPartWidth;
350 int32_t gfxTextRun::GetAdvanceForGlyphs(Range aRange) const {
351 int32_t advance = 0;
352 for (auto i = aRange.start; i < aRange.end; ++i) {
353 advance += GetAdvanceForGlyph(i);
355 return advance;
358 static void GetAdjustedSpacing(
359 const gfxTextRun* aTextRun, gfxTextRun::Range aRange,
360 const gfxTextRun::PropertyProvider& aProvider,
361 gfxTextRun::PropertyProvider::Spacing* aSpacing) {
362 if (aRange.start >= aRange.end) {
363 return;
366 aProvider.GetSpacing(aRange, aSpacing);
368 #ifdef DEBUG
369 // Check to see if we have spacing inside ligatures
371 const gfxTextRun::CompressedGlyph* charGlyphs =
372 aTextRun->GetCharacterGlyphs();
373 uint32_t i;
375 for (i = aRange.start; i < aRange.end; ++i) {
376 if (!charGlyphs[i].IsLigatureGroupStart()) {
377 NS_ASSERTION(i == aRange.start || aSpacing[i - aRange.start].mBefore == 0,
378 "Before-spacing inside a ligature!");
379 NS_ASSERTION(
380 i - 1 <= aRange.start || aSpacing[i - 1 - aRange.start].mAfter == 0,
381 "After-spacing inside a ligature!");
384 #endif
387 bool gfxTextRun::GetAdjustedSpacingArray(
388 Range aRange, const PropertyProvider* aProvider, Range aSpacingRange,
389 nsTArray<PropertyProvider::Spacing>* aSpacing) const {
390 if (!aProvider || !(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) {
391 return false;
393 if (!aSpacing->AppendElements(aRange.Length(), fallible)) {
394 return false;
396 auto spacingOffset = aSpacingRange.start - aRange.start;
397 memset(aSpacing->Elements(), 0, sizeof(gfxFont::Spacing) * spacingOffset);
398 GetAdjustedSpacing(this, aSpacingRange, *aProvider,
399 aSpacing->Elements() + spacingOffset);
400 memset(aSpacing->Elements() + spacingOffset + aSpacingRange.Length(), 0,
401 sizeof(gfxFont::Spacing) * (aRange.end - aSpacingRange.end));
402 return true;
405 bool gfxTextRun::ShrinkToLigatureBoundaries(Range* aRange) const {
406 if (aRange->start >= aRange->end) {
407 return false;
410 const CompressedGlyph* charGlyphs = mCharacterGlyphs;
411 bool adjusted = false;
412 while (aRange->start < aRange->end &&
413 !charGlyphs[aRange->start].IsLigatureGroupStart()) {
414 ++aRange->start;
415 adjusted = true;
417 if (aRange->end < GetLength()) {
418 while (aRange->end > aRange->start &&
419 !charGlyphs[aRange->end].IsLigatureGroupStart()) {
420 --aRange->end;
421 adjusted = true;
424 return adjusted;
427 void gfxTextRun::DrawGlyphs(gfxFont* aFont, Range aRange, gfx::Point* aPt,
428 const PropertyProvider* aProvider,
429 Range aSpacingRange, TextRunDrawParams& aParams,
430 gfx::ShapedTextFlags aOrientation) const {
431 AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer;
432 bool haveSpacing =
433 GetAdjustedSpacingArray(aRange, aProvider, aSpacingRange, &spacingBuffer);
434 aParams.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr;
435 aFont->Draw(this, aRange.start, aRange.end, aPt, aParams, aOrientation);
438 static void ClipPartialLigature(const gfxTextRun* aTextRun, gfxFloat* aStart,
439 gfxFloat* aEnd, gfxFloat aOrigin,
440 gfxTextRun::LigatureData* aLigature) {
441 if (aLigature->mClipBeforePart) {
442 if (aTextRun->IsRightToLeft()) {
443 *aEnd = std::min(*aEnd, aOrigin);
444 } else {
445 *aStart = std::max(*aStart, aOrigin);
448 if (aLigature->mClipAfterPart) {
449 gfxFloat endEdge =
450 aOrigin + aTextRun->GetDirection() * aLigature->mPartWidth;
451 if (aTextRun->IsRightToLeft()) {
452 *aStart = std::max(*aStart, endEdge);
453 } else {
454 *aEnd = std::min(*aEnd, endEdge);
459 void gfxTextRun::DrawPartialLigature(gfxFont* aFont, Range aRange,
460 gfx::Point* aPt,
461 const PropertyProvider* aProvider,
462 TextRunDrawParams& aParams,
463 gfx::ShapedTextFlags aOrientation) const {
464 if (aRange.start >= aRange.end) {
465 return;
468 // Draw partial ligature. We hack this by clipping the ligature.
469 LigatureData data = ComputeLigatureData(aRange, aProvider);
470 gfxRect clipExtents = aParams.context->GetClipExtents();
471 gfxFloat start, end;
472 if (aParams.isVerticalRun) {
473 start = clipExtents.Y() * mAppUnitsPerDevUnit;
474 end = clipExtents.YMost() * mAppUnitsPerDevUnit;
475 ClipPartialLigature(this, &start, &end, aPt->y, &data);
476 } else {
477 start = clipExtents.X() * mAppUnitsPerDevUnit;
478 end = clipExtents.XMost() * mAppUnitsPerDevUnit;
479 ClipPartialLigature(this, &start, &end, aPt->x, &data);
482 gfxClipAutoSaveRestore autoSaveClip(aParams.context);
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 autoSaveClip.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);
507 if (aParams.isVerticalRun) {
508 aPt->y += aParams.direction * data.mPartWidth;
509 } else {
510 aPt->x += aParams.direction * data.mPartWidth;
514 // Returns true if the font has synthetic bolding enabled,
515 // or is a color font (COLR/SVG/sbix/CBDT), false otherwise. This is used to
516 // check whether the text run needs to be explicitly composited in order to
517 // support opacity.
518 static bool HasSyntheticBoldOrColor(gfxFont* aFont) {
519 if (aFont->ApplySyntheticBold()) {
520 return true;
522 gfxFontEntry* fe = aFont->GetFontEntry();
523 if (fe->TryGetSVGData(aFont) || fe->TryGetColorGlyphs()) {
524 return true;
526 #if defined(XP_MACOSX) // sbix fonts only supported via Core Text
527 if (fe->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) {
528 return true;
530 #endif
531 return false;
534 // helper class for double-buffering drawing with non-opaque color
535 struct MOZ_STACK_CLASS BufferAlphaColor {
536 explicit BufferAlphaColor(gfxContext* aContext) : mContext(aContext) {}
538 ~BufferAlphaColor() = default;
540 void PushSolidColor(const gfxRect& aBounds, const DeviceColor& aAlphaColor,
541 uint32_t appsPerDevUnit) {
542 mContext->Save();
543 mContext->SnappedClip(gfxRect(
544 aBounds.X() / appsPerDevUnit, aBounds.Y() / appsPerDevUnit,
545 aBounds.Width() / appsPerDevUnit, aBounds.Height() / appsPerDevUnit));
546 mContext->SetDeviceColor(
547 DeviceColor(aAlphaColor.r, aAlphaColor.g, aAlphaColor.b));
548 mContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, aAlphaColor.a);
551 void PopAlpha() {
552 // pop the text, using the color alpha as the opacity
553 mContext->PopGroupAndBlend();
554 mContext->Restore();
557 gfxContext* mContext;
560 void gfxTextRun::Draw(const Range aRange, const gfx::Point aPt,
561 const DrawParams& aParams) const {
562 NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range");
563 NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH ||
564 !(aParams.drawMode & DrawMode::GLYPH_PATH),
565 "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or "
566 "GLYPH_STROKE_UNDERNEATH");
567 NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH || !aParams.callbacks,
568 "callback must not be specified unless using GLYPH_PATH");
570 bool skipDrawing =
571 !mDontSkipDrawing && (mFontGroup ? mFontGroup->ShouldSkipDrawing()
572 : mReleasedFontGroupSkippedDrawing);
573 auto* textDrawer = aParams.context->GetTextDrawer();
574 if (aParams.drawMode & DrawMode::GLYPH_FILL) {
575 DeviceColor currentColor;
576 if (aParams.context->GetDeviceColor(currentColor) && currentColor.a == 0 &&
577 !textDrawer) {
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 !textDrawer;
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(aParams.paletteCache);
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.fontPalette = aParams.fontPalette;
627 params.textStrokePattern = aParams.textStrokePattern;
628 params.drawOpts = aParams.drawOpts;
629 params.drawMode = aParams.drawMode;
630 params.hasTextShadow = aParams.hasTextShadow;
631 params.callbacks = aParams.callbacks;
632 params.runContextPaint = aParams.contextPaint;
633 params.paintSVGGlyphs =
634 !aParams.callbacks || aParams.callbacks->mShouldPaintSVGGlyphs;
635 params.dt = aParams.context->GetDrawTarget();
636 params.textDrawer = textDrawer;
637 if (textDrawer) {
638 params.clipRect = textDrawer->GeckoClipRect();
640 params.allowGDI = aParams.allowGDI;
642 gfxFloat advance = 0.0;
643 gfx::Point pt = aPt;
645 for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) {
646 gfxFont* font = iter.GlyphRun()->mFont;
647 Range runRange(iter.StringStart(), iter.StringEnd());
649 bool needToRestore = false;
650 if (mayNeedBuffering && HasSyntheticBoldOrColor(font)) {
651 needToRestore = true;
652 if (!gotMetrics) {
653 // Measure text; use the bounding box to determine the area we need
654 // to buffer. We measure the entire range, rather than just the glyph
655 // run that we're actually handling, because of bug 1612610: if the
656 // bounding box passed to PushSolidColor does not intersect the
657 // drawTarget's current clip, the skia backend fails to clip properly.
658 // This means we may use a larger buffer than actually needed, but is
659 // otherwise harmless.
660 metrics = MeasureText(aRange, gfxFont::LOOSE_INK_EXTENTS, params.dt,
661 aParams.provider);
662 if (IsRightToLeft()) {
663 metrics.mBoundingBox.MoveBy(
664 gfxPoint(aPt.x - metrics.mAdvanceWidth, aPt.y));
665 } else {
666 metrics.mBoundingBox.MoveBy(gfxPoint(aPt.x, aPt.y));
668 gotMetrics = true;
670 syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor,
671 GetAppUnitsPerDevUnit());
674 Range ligatureRange(runRange);
675 bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange);
677 bool drawPartial =
678 adjusted &&
679 ((aParams.drawMode & (DrawMode::GLYPH_FILL | DrawMode::GLYPH_STROKE)) ||
680 (aParams.drawMode == DrawMode::GLYPH_PATH && aParams.callbacks));
681 gfx::Point origPt = pt;
683 if (drawPartial) {
684 DrawPartialLigature(font, Range(runRange.start, ligatureRange.start), &pt,
685 aParams.provider, params,
686 iter.GlyphRun()->mOrientation);
689 DrawGlyphs(font, ligatureRange, &pt, aParams.provider, ligatureRange,
690 params, iter.GlyphRun()->mOrientation);
692 if (drawPartial) {
693 DrawPartialLigature(font, Range(ligatureRange.end, runRange.end), &pt,
694 aParams.provider, params,
695 iter.GlyphRun()->mOrientation);
698 if (params.isVerticalRun) {
699 advance += (pt.y - origPt.y) * params.direction;
700 } else {
701 advance += (pt.x - origPt.x) * params.direction;
704 // composite result when synthetic bolding used
705 if (needToRestore) {
706 syntheticBoldBuffer.PopAlpha();
710 if (aParams.advanceWidth) {
711 *aParams.advanceWidth = advance;
715 // This method is mostly parallel to Draw().
716 void gfxTextRun::DrawEmphasisMarks(
717 gfxContext* aContext, gfxTextRun* aMark, gfxFloat aMarkAdvance,
718 gfx::Point aPt, Range aRange, const PropertyProvider* aProvider,
719 mozilla::gfx::PaletteCache& aPaletteCache) const {
720 MOZ_ASSERT(aRange.end <= GetLength());
722 EmphasisMarkDrawParams params(aContext, aPaletteCache);
723 params.mark = aMark;
724 params.advance = aMarkAdvance;
725 params.direction = GetDirection();
726 params.isVertical = IsVertical();
728 float& inlineCoord = params.isVertical ? aPt.y.value : aPt.x.value;
729 float direction = params.direction;
731 for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) {
732 gfxFont* font = iter.GlyphRun()->mFont;
733 uint32_t start = iter.StringStart();
734 uint32_t end = iter.StringEnd();
735 Range ligatureRange(start, end);
736 bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange);
738 if (adjusted) {
739 inlineCoord +=
740 direction * ComputePartialLigatureWidth(
741 Range(start, ligatureRange.start), aProvider);
744 AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer;
745 bool haveSpacing = GetAdjustedSpacingArray(ligatureRange, aProvider,
746 ligatureRange, &spacingBuffer);
747 params.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr;
748 font->DrawEmphasisMarks(this, &aPt, ligatureRange.start,
749 ligatureRange.Length(), params);
751 if (adjusted) {
752 inlineCoord += direction * ComputePartialLigatureWidth(
753 Range(ligatureRange.end, end), aProvider);
758 void gfxTextRun::AccumulateMetricsForRun(
759 gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType,
760 DrawTarget* aRefDrawTarget, const PropertyProvider* aProvider,
761 Range aSpacingRange, gfx::ShapedTextFlags aOrientation,
762 Metrics* aMetrics) const {
763 AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer;
764 bool haveSpacing =
765 GetAdjustedSpacingArray(aRange, aProvider, aSpacingRange, &spacingBuffer);
766 Metrics metrics = aFont->Measure(
767 this, aRange.start, aRange.end, aBoundingBoxType, aRefDrawTarget,
768 haveSpacing ? spacingBuffer.Elements() : nullptr, aOrientation);
769 aMetrics->CombineWith(metrics, IsRightToLeft());
772 void gfxTextRun::AccumulatePartialLigatureMetrics(
773 gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType,
774 DrawTarget* aRefDrawTarget, const PropertyProvider* aProvider,
775 gfx::ShapedTextFlags aOrientation, Metrics* aMetrics) const {
776 if (aRange.start >= aRange.end) return;
778 // Measure partial ligature. We hack this by clipping the metrics in the
779 // same way we clip the drawing.
780 LigatureData data = ComputeLigatureData(aRange, aProvider);
782 // First measure the complete ligature
783 Metrics metrics;
784 AccumulateMetricsForRun(aFont, data.mRange, aBoundingBoxType, aRefDrawTarget,
785 aProvider, aRange, aOrientation, &metrics);
787 // Clip the bounding box to the ligature part
788 gfxFloat bboxLeft = metrics.mBoundingBox.X();
789 gfxFloat bboxRight = metrics.mBoundingBox.XMost();
790 // Where we are going to start "drawing" relative to our left baseline origin
791 gfxFloat origin =
792 IsRightToLeft() ? metrics.mAdvanceWidth - data.mPartAdvance : 0;
793 ClipPartialLigature(this, &bboxLeft, &bboxRight, origin, &data);
794 metrics.mBoundingBox.SetBoxX(bboxLeft, bboxRight);
796 // mBoundingBox is now relative to the left baseline origin for the entire
797 // ligature. Shift it left.
798 metrics.mBoundingBox.MoveByX(
799 -(IsRightToLeft()
800 ? metrics.mAdvanceWidth - (data.mPartAdvance + data.mPartWidth)
801 : data.mPartAdvance));
802 metrics.mAdvanceWidth = data.mPartWidth;
804 aMetrics->CombineWith(metrics, IsRightToLeft());
807 gfxTextRun::Metrics gfxTextRun::MeasureText(
808 Range aRange, gfxFont::BoundingBoxType aBoundingBoxType,
809 DrawTarget* aRefDrawTarget, const PropertyProvider* aProvider) const {
810 NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range");
812 Metrics accumulatedMetrics;
813 for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) {
814 gfxFont* font = iter.GlyphRun()->mFont;
815 uint32_t start = iter.StringStart();
816 uint32_t end = iter.StringEnd();
817 Range ligatureRange(start, end);
818 bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange);
820 if (adjusted) {
821 AccumulatePartialLigatureMetrics(font, Range(start, ligatureRange.start),
822 aBoundingBoxType, aRefDrawTarget,
823 aProvider, iter.GlyphRun()->mOrientation,
824 &accumulatedMetrics);
827 // XXX This sucks. We have to get glyph extents just so we can detect
828 // glyphs outside the font box, even when aBoundingBoxType is LOOSE,
829 // even though in almost all cases we could get correct results just
830 // by getting some ascent/descent from the font and using our stored
831 // advance widths.
832 AccumulateMetricsForRun(font, ligatureRange, aBoundingBoxType,
833 aRefDrawTarget, aProvider, ligatureRange,
834 iter.GlyphRun()->mOrientation, &accumulatedMetrics);
836 if (adjusted) {
837 AccumulatePartialLigatureMetrics(
838 font, Range(ligatureRange.end, end), aBoundingBoxType, aRefDrawTarget,
839 aProvider, iter.GlyphRun()->mOrientation, &accumulatedMetrics);
843 return accumulatedMetrics;
846 void gfxTextRun::GetLineHeightMetrics(Range aRange, gfxFloat& aAscent,
847 gfxFloat& aDescent) const {
848 Metrics accumulatedMetrics;
849 for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) {
850 gfxFont* font = iter.GlyphRun()->mFont;
851 auto metrics =
852 font->Measure(this, 0, 0, gfxFont::LOOSE_INK_EXTENTS, nullptr, nullptr,
853 iter.GlyphRun()->mOrientation);
854 accumulatedMetrics.CombineWith(metrics, false);
856 aAscent = accumulatedMetrics.mAscent;
857 aDescent = accumulatedMetrics.mDescent;
860 #define MEASUREMENT_BUFFER_SIZE 100
862 void gfxTextRun::ClassifyAutoHyphenations(uint32_t aStart, Range aRange,
863 nsTArray<HyphenType>& aHyphenBuffer,
864 HyphenationState* aWordState) {
865 MOZ_ASSERT(
866 aRange.end - aStart <= aHyphenBuffer.Length() && aRange.start >= aStart,
867 "Range out of bounds");
868 MOZ_ASSERT(aWordState->mostRecentBoundary >= aStart,
869 "Unexpected aMostRecentWordBoundary!!");
871 uint32_t start =
872 std::min<uint32_t>(aRange.start, aWordState->mostRecentBoundary);
874 for (uint32_t i = start; i < aRange.end; ++i) {
875 if (aHyphenBuffer[i - aStart] == HyphenType::Explicit &&
876 !aWordState->hasExplicitHyphen) {
877 aWordState->hasExplicitHyphen = true;
879 if (!aWordState->hasManualHyphen &&
880 (aHyphenBuffer[i - aStart] == HyphenType::Soft ||
881 aHyphenBuffer[i - aStart] == HyphenType::Explicit)) {
882 aWordState->hasManualHyphen = true;
883 // This is the first manual hyphen in the current word. We can only
884 // know if the current word has a manual hyphen until now. So, we need
885 // to run a sub loop to update the auto hyphens between the start of
886 // the current word and this manual hyphen.
887 if (aWordState->hasAutoHyphen) {
888 for (uint32_t j = aWordState->mostRecentBoundary; j < i; j++) {
889 if (aHyphenBuffer[j - aStart] ==
890 HyphenType::AutoWithoutManualInSameWord) {
891 aHyphenBuffer[j - aStart] = HyphenType::AutoWithManualInSameWord;
896 if (aHyphenBuffer[i - aStart] == HyphenType::AutoWithoutManualInSameWord) {
897 if (!aWordState->hasAutoHyphen) {
898 aWordState->hasAutoHyphen = true;
900 if (aWordState->hasManualHyphen) {
901 aHyphenBuffer[i - aStart] = HyphenType::AutoWithManualInSameWord;
905 // If we're at the word boundary, clear/reset couple states.
906 if (mCharacterGlyphs[i].CharIsSpace() || mCharacterGlyphs[i].CharIsTab() ||
907 mCharacterGlyphs[i].CharIsNewline() ||
908 // Since we will not have a boundary in the end of the string, let's
909 // call the end of the string a special case for word boundary.
910 i == GetLength() - 1) {
911 // We can only get to know whether we should raise/clear an explicit
912 // manual hyphen until we get to the end of a word, because this depends
913 // on whether there exists at least one auto hyphen in the same word.
914 if (!aWordState->hasAutoHyphen && aWordState->hasExplicitHyphen) {
915 for (uint32_t j = aWordState->mostRecentBoundary; j <= i; j++) {
916 if (aHyphenBuffer[j - aStart] == HyphenType::Explicit) {
917 aHyphenBuffer[j - aStart] = HyphenType::None;
921 aWordState->mostRecentBoundary = i;
922 aWordState->hasManualHyphen = false;
923 aWordState->hasAutoHyphen = false;
924 aWordState->hasExplicitHyphen = false;
929 uint32_t gfxTextRun::BreakAndMeasureText(
930 uint32_t aStart, uint32_t aMaxLength, bool aLineBreakBefore,
931 gfxFloat aWidth, const PropertyProvider& aProvider,
932 SuppressBreak aSuppressBreak, gfxFont::BoundingBoxType aBoundingBoxType,
933 DrawTarget* aRefDrawTarget, bool aCanWordWrap, bool aCanWhitespaceWrap,
934 bool aIsBreakSpaces,
935 // output params:
936 TrimmableWS* aOutTrimmableWhitespace, Metrics& aOutMetrics,
937 bool& aOutUsedHyphenation, uint32_t& aOutLastBreak,
938 gfxBreakPriority& aBreakPriority) {
939 aMaxLength = std::min(aMaxLength, GetLength() - aStart);
941 NS_ASSERTION(aStart + aMaxLength <= GetLength(), "Substring out of range");
943 Range bufferRange(
944 aStart, aStart + std::min<uint32_t>(aMaxLength, MEASUREMENT_BUFFER_SIZE));
945 PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE];
946 bool haveSpacing = !!(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING);
947 if (haveSpacing) {
948 GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer);
950 AutoTArray<HyphenType, 4096> hyphenBuffer;
951 HyphenationState wordState;
952 wordState.mostRecentBoundary = aStart;
953 bool haveHyphenation =
954 (aProvider.GetHyphensOption() == StyleHyphens::Auto ||
955 (aProvider.GetHyphensOption() == StyleHyphens::Manual &&
956 !!(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS)));
957 if (haveHyphenation) {
958 if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) {
959 aProvider.GetHyphenationBreaks(bufferRange, hyphenBuffer.Elements());
960 if (aProvider.GetHyphensOption() == StyleHyphens::Auto) {
961 ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer, &wordState);
963 } else {
964 haveHyphenation = false;
968 gfxFloat width = 0;
969 gfxFloat advance = 0;
970 // The number of space characters that can be trimmed or hang at a soft-wrap
971 uint32_t trimmableChars = 0;
972 // The amount of space removed by ignoring trimmableChars
973 gfxFloat trimmableAdvance = 0;
974 int32_t lastBreak = -1;
975 int32_t lastBreakTrimmableChars = -1;
976 gfxFloat lastBreakTrimmableAdvance = -1;
977 // Cache the last candidate break
978 int32_t lastCandidateBreak = -1;
979 int32_t lastCandidateBreakTrimmableChars = -1;
980 gfxFloat lastCandidateBreakTrimmableAdvance = -1;
981 bool lastCandidateBreakUsedHyphenation = false;
982 gfxBreakPriority lastCandidateBreakPriority = gfxBreakPriority::eNoBreak;
983 bool aborted = false;
984 uint32_t end = aStart + aMaxLength;
985 bool lastBreakUsedHyphenation = false;
986 Range ligatureRange(aStart, end);
987 ShrinkToLigatureBoundaries(&ligatureRange);
989 // We may need to move `i` backwards in the following loop, and re-scan
990 // part of the textrun; we'll use `rescanLimit` so we can tell when that
991 // is happening: if `i < rescanLimit` then we're rescanning.
992 uint32_t rescanLimit = aStart;
993 for (uint32_t i = aStart; i < end; ++i) {
994 if (i >= bufferRange.end) {
995 // Fetch more spacing and hyphenation data
996 uint32_t oldHyphenBufferLength = hyphenBuffer.Length();
997 bufferRange.start = i;
998 bufferRange.end =
999 std::min(aStart + aMaxLength, i + MEASUREMENT_BUFFER_SIZE);
1000 // For spacing, we always overwrite the old data with the newly
1001 // fetched one. However, for hyphenation, hyphenation data sometimes
1002 // depends on the context in every word (if "hyphens: auto" is set).
1003 // To ensure we get enough information between neighboring buffers,
1004 // we grow the hyphenBuffer instead of overwrite it.
1005 // NOTE that this means bufferRange does not correspond to the
1006 // entire hyphenBuffer, but only to the most recently added portion.
1007 // Therefore, we need to add the old length to hyphenBuffer.Elements()
1008 // when getting more data.
1009 if (haveSpacing) {
1010 GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer);
1012 if (haveHyphenation) {
1013 if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) {
1014 aProvider.GetHyphenationBreaks(
1015 bufferRange, hyphenBuffer.Elements() + oldHyphenBufferLength);
1016 if (aProvider.GetHyphensOption() == StyleHyphens::Auto) {
1017 uint32_t prevMostRecentWordBoundary = wordState.mostRecentBoundary;
1018 ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer,
1019 &wordState);
1020 // If the buffer boundary is in the middle of a word,
1021 // we need to go back to the start of the current word.
1022 // So, we can correct the wrong candidates that we set
1023 // in the previous runs of the loop.
1024 if (prevMostRecentWordBoundary < oldHyphenBufferLength) {
1025 rescanLimit = i;
1026 i = prevMostRecentWordBoundary - 1;
1027 continue;
1030 } else {
1031 haveHyphenation = false;
1036 // There can't be a word-wrap break opportunity at the beginning of the
1037 // line: if the width is too small for even one character to fit, it
1038 // could be the first and last break opportunity on the line, and that
1039 // would trigger an infinite loop.
1040 if (aSuppressBreak != eSuppressAllBreaks &&
1041 (aSuppressBreak != eSuppressInitialBreak || i > aStart)) {
1042 bool atNaturalBreak = mCharacterGlyphs[i].CanBreakBefore() ==
1043 CompressedGlyph::FLAG_BREAK_TYPE_NORMAL;
1044 // atHyphenationBreak indicates we're at a "soft" hyphen, where an extra
1045 // hyphen glyph will need to be painted. It is NOT set for breaks at an
1046 // explicit hyphen present in the text.
1048 // NOTE(emilio): If you change this condition you also need to change
1049 // nsTextFrame::AddInlineMinISizeForFlow to match.
1050 bool atHyphenationBreak = !atNaturalBreak && haveHyphenation &&
1051 IsOptionalHyphenBreak(hyphenBuffer[i - aStart]);
1052 bool atAutoHyphenWithManualHyphenInSameWord =
1053 atHyphenationBreak &&
1054 hyphenBuffer[i - aStart] == HyphenType::AutoWithManualInSameWord;
1055 bool atBreak = atNaturalBreak || atHyphenationBreak;
1056 bool wordWrapping =
1057 (aCanWordWrap ||
1058 (aCanWhitespaceWrap &&
1059 mCharacterGlyphs[i].CanBreakBefore() ==
1060 CompressedGlyph::FLAG_BREAK_TYPE_EMERGENCY_WRAP)) &&
1061 mCharacterGlyphs[i].IsClusterStart() &&
1062 aBreakPriority <= gfxBreakPriority::eWordWrapBreak;
1064 bool whitespaceWrapping = false;
1065 if (i > aStart) {
1066 // The spec says the breaking opportunity is *after* whitespace.
1067 auto const& g = mCharacterGlyphs[i - 1];
1068 whitespaceWrapping =
1069 aIsBreakSpaces &&
1070 (g.CharIsSpace() || g.CharIsTab() || g.CharIsNewline());
1073 if (atBreak || wordWrapping || whitespaceWrapping) {
1074 gfxFloat hyphenatedAdvance = advance;
1075 if (atHyphenationBreak) {
1076 hyphenatedAdvance += aProvider.GetHyphenWidth();
1079 if (lastBreak < 0 ||
1080 width + hyphenatedAdvance - trimmableAdvance <= aWidth) {
1081 // We can break here.
1082 lastBreak = i;
1083 lastBreakTrimmableChars = trimmableChars;
1084 lastBreakTrimmableAdvance = trimmableAdvance;
1085 lastBreakUsedHyphenation = atHyphenationBreak;
1086 aBreakPriority = (atBreak || whitespaceWrapping)
1087 ? gfxBreakPriority::eNormalBreak
1088 : gfxBreakPriority::eWordWrapBreak;
1091 width += advance;
1092 advance = 0;
1093 if (width - trimmableAdvance > aWidth) {
1094 // No more text fits. Abort
1095 aborted = true;
1096 break;
1098 // There are various kinds of break opportunities:
1099 // 1. word wrap break,
1100 // 2. natural break,
1101 // 3. manual hyphenation break,
1102 // 4. auto hyphenation break without any manual hyphenation
1103 // in the same word,
1104 // 5. auto hyphenation break with another manual hyphenation
1105 // in the same word.
1106 // Allow all of them except the last one to be a candidate.
1107 // So, we can ensure that we don't use an automatic
1108 // hyphenation opportunity within a word that contains another
1109 // manual hyphenation, unless it is the only choice.
1110 if (wordWrapping || !atAutoHyphenWithManualHyphenInSameWord) {
1111 lastCandidateBreak = lastBreak;
1112 lastCandidateBreakTrimmableChars = lastBreakTrimmableChars;
1113 lastCandidateBreakTrimmableAdvance = lastBreakTrimmableAdvance;
1114 lastCandidateBreakUsedHyphenation = lastBreakUsedHyphenation;
1115 lastCandidateBreakPriority = aBreakPriority;
1120 // If we're re-scanning part of a word (to re-process potential
1121 // hyphenation types) then we don't want to accumulate widths again
1122 // for the characters that were already added to `advance`.
1123 if (i < rescanLimit) {
1124 continue;
1127 gfxFloat charAdvance;
1128 if (i >= ligatureRange.start && i < ligatureRange.end) {
1129 charAdvance = GetAdvanceForGlyphs(Range(i, i + 1));
1130 if (haveSpacing) {
1131 PropertyProvider::Spacing* space =
1132 &spacingBuffer[i - bufferRange.start];
1133 charAdvance += space->mBefore + space->mAfter;
1135 } else {
1136 charAdvance = ComputePartialLigatureWidth(Range(i, i + 1), &aProvider);
1139 advance += charAdvance;
1140 if (aOutTrimmableWhitespace) {
1141 if (mCharacterGlyphs[i].CharIsSpace()) {
1142 ++trimmableChars;
1143 trimmableAdvance += charAdvance;
1144 } else {
1145 trimmableAdvance = 0;
1146 trimmableChars = 0;
1151 if (!aborted) {
1152 width += advance;
1155 // There are three possibilities:
1156 // 1) all the text fit (width <= aWidth)
1157 // 2) some of the text fit up to a break opportunity (width > aWidth &&
1158 // lastBreak >= 0)
1159 // 3) none of the text fits before a break opportunity (width > aWidth &&
1160 // lastBreak < 0)
1161 uint32_t charsFit;
1162 aOutUsedHyphenation = false;
1163 if (width - trimmableAdvance <= aWidth) {
1164 charsFit = aMaxLength;
1165 } else if (lastBreak >= 0) {
1166 if (lastCandidateBreak >= 0 && lastCandidateBreak != lastBreak) {
1167 lastBreak = lastCandidateBreak;
1168 lastBreakTrimmableChars = lastCandidateBreakTrimmableChars;
1169 lastBreakTrimmableAdvance = lastCandidateBreakTrimmableAdvance;
1170 lastBreakUsedHyphenation = lastCandidateBreakUsedHyphenation;
1171 aBreakPriority = lastCandidateBreakPriority;
1173 charsFit = lastBreak - aStart;
1174 trimmableChars = lastBreakTrimmableChars;
1175 trimmableAdvance = lastBreakTrimmableAdvance;
1176 aOutUsedHyphenation = lastBreakUsedHyphenation;
1177 } else {
1178 charsFit = aMaxLength;
1181 // Get the overall metrics of the range that fit (including any potentially
1182 // trimmable or hanging whitespace).
1183 aOutMetrics = MeasureText(Range(aStart, aStart + charsFit), aBoundingBoxType,
1184 aRefDrawTarget, &aProvider);
1186 if (aOutTrimmableWhitespace) {
1187 aOutTrimmableWhitespace->mAdvance = trimmableAdvance;
1188 aOutTrimmableWhitespace->mCount = trimmableChars;
1191 if (charsFit == aMaxLength) {
1192 if (lastBreak < 0) {
1193 aOutLastBreak = UINT32_MAX;
1194 } else {
1195 aOutLastBreak = lastBreak - aStart;
1199 return charsFit;
1202 gfxFloat gfxTextRun::GetAdvanceWidth(
1203 Range aRange, const PropertyProvider* aProvider,
1204 PropertyProvider::Spacing* aSpacing) const {
1205 NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range");
1207 Range ligatureRange = aRange;
1208 bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange);
1210 gfxFloat result =
1211 adjusted ? ComputePartialLigatureWidth(
1212 Range(aRange.start, ligatureRange.start), aProvider) +
1213 ComputePartialLigatureWidth(
1214 Range(ligatureRange.end, aRange.end), aProvider)
1215 : 0.0;
1217 if (aSpacing) {
1218 aSpacing->mBefore = aSpacing->mAfter = 0;
1221 // Account for all remaining spacing here. This is more efficient than
1222 // processing it along with the glyphs.
1223 if (aProvider && (mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) {
1224 uint32_t i;
1225 AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer;
1226 if (spacingBuffer.AppendElements(aRange.Length(), fallible)) {
1227 GetAdjustedSpacing(this, ligatureRange, *aProvider,
1228 spacingBuffer.Elements());
1229 for (i = 0; i < ligatureRange.Length(); ++i) {
1230 PropertyProvider::Spacing* space = &spacingBuffer[i];
1231 result += space->mBefore + space->mAfter;
1233 if (aSpacing) {
1234 aSpacing->mBefore = spacingBuffer[0].mBefore;
1235 aSpacing->mAfter = spacingBuffer.LastElement().mAfter;
1240 return result + GetAdvanceForGlyphs(ligatureRange);
1243 gfxFloat gfxTextRun::GetMinAdvanceWidth(Range aRange) {
1244 MOZ_ASSERT(aRange.end <= GetLength(), "Substring out of range");
1246 Range ligatureRange = aRange;
1247 bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange);
1249 gfxFloat result =
1250 adjusted
1251 ? std::max(ComputePartialLigatureWidth(
1252 Range(aRange.start, ligatureRange.start), nullptr),
1253 ComputePartialLigatureWidth(
1254 Range(ligatureRange.end, aRange.end), nullptr))
1255 : 0.0;
1257 // Compute min advance width by assuming each grapheme cluster takes its own
1258 // line.
1259 gfxFloat clusterAdvance = 0;
1260 for (uint32_t i = ligatureRange.start; i < ligatureRange.end; ++i) {
1261 if (mCharacterGlyphs[i].CharIsSpace()) {
1262 // Skip space char to prevent its advance width contributing to the
1263 // result. That is, don't consider a space can be in its own line.
1264 continue;
1266 clusterAdvance += GetAdvanceForGlyph(i);
1267 if (i + 1 == ligatureRange.end || IsClusterStart(i + 1)) {
1268 result = std::max(result, clusterAdvance);
1269 clusterAdvance = 0;
1273 return result;
1276 bool gfxTextRun::SetLineBreaks(Range aRange, bool aLineBreakBefore,
1277 bool aLineBreakAfter,
1278 gfxFloat* aAdvanceWidthDelta) {
1279 // Do nothing because our shaping does not currently take linebreaks into
1280 // account. There is no change in advance width.
1281 if (aAdvanceWidthDelta) {
1282 *aAdvanceWidthDelta = 0;
1284 return false;
1287 const gfxTextRun::GlyphRun* gfxTextRun::FindFirstGlyphRunContaining(
1288 uint32_t aOffset) const {
1289 MOZ_ASSERT(aOffset <= GetLength(), "Bad offset looking for glyphrun");
1290 MOZ_ASSERT(GetLength() == 0 || !mGlyphRuns.IsEmpty(),
1291 "non-empty text but no glyph runs present!");
1292 if (mGlyphRuns.Length() <= 1) {
1293 return mGlyphRuns.begin();
1295 if (aOffset == GetLength()) {
1296 return mGlyphRuns.end() - 1;
1298 const auto* start = mGlyphRuns.begin();
1299 const auto* limit = mGlyphRuns.end();
1300 while (limit - start > 1) {
1301 const auto* mid = start + (limit - start) / 2;
1302 if (mid->mCharacterOffset <= aOffset) {
1303 start = mid;
1304 } else {
1305 limit = mid;
1308 MOZ_ASSERT(start->mCharacterOffset <= aOffset,
1309 "Hmm, something went wrong, aOffset should have been found");
1310 return start;
1313 void gfxTextRun::AddGlyphRun(gfxFont* aFont, FontMatchType aMatchType,
1314 uint32_t aUTF16Offset, bool aForceNewRun,
1315 gfx::ShapedTextFlags aOrientation, bool aIsCJK) {
1316 MOZ_ASSERT(aFont, "adding glyph run for null font!");
1317 MOZ_ASSERT(aOrientation != gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED,
1318 "mixed orientation should have been resolved");
1319 if (!aFont) {
1320 return;
1323 if (mGlyphRuns.IsEmpty()) {
1324 mGlyphRuns.AppendElement(
1325 GlyphRun{aFont, aUTF16Offset, aOrientation, aMatchType, aIsCJK});
1326 return;
1329 uint32_t numGlyphRuns = mGlyphRuns.Length();
1330 if (!aForceNewRun) {
1331 GlyphRun* lastGlyphRun = &mGlyphRuns.LastElement();
1333 MOZ_ASSERT(lastGlyphRun->mCharacterOffset <= aUTF16Offset,
1334 "Glyph runs out of order (and run not forced)");
1336 // Don't append a run if the font is already the one we want
1337 if (lastGlyphRun->Matches(aFont, aOrientation, aIsCJK, aMatchType)) {
1338 return;
1341 // If the offset has not changed, avoid leaving a zero-length run
1342 // by overwriting the last entry instead of appending...
1343 if (lastGlyphRun->mCharacterOffset == aUTF16Offset) {
1344 // ...except that if the run before the last entry had the same
1345 // font as the new one wants, merge with it instead of creating
1346 // adjacent runs with the same font
1347 if (numGlyphRuns > 1 && mGlyphRuns[numGlyphRuns - 2].Matches(
1348 aFont, aOrientation, aIsCJK, aMatchType)) {
1349 mGlyphRuns.TruncateLength(numGlyphRuns - 1);
1350 return;
1353 lastGlyphRun->SetProperties(aFont, aOrientation, aIsCJK, aMatchType);
1354 return;
1358 MOZ_ASSERT(
1359 aForceNewRun || numGlyphRuns > 0 || aUTF16Offset == 0,
1360 "First run doesn't cover the first character (and run not forced)?");
1362 mGlyphRuns.AppendElement(
1363 GlyphRun{aFont, aUTF16Offset, aOrientation, aMatchType, aIsCJK});
1366 void gfxTextRun::SanitizeGlyphRuns() {
1367 if (mGlyphRuns.Length() < 2) {
1368 return;
1371 auto& runs = mGlyphRuns.Array();
1373 // The runs are almost certain to be already sorted, so it's worth avoiding
1374 // the Sort() call if possible.
1375 bool isSorted = true;
1376 uint32_t prevOffset = 0;
1377 for (const auto& r : runs) {
1378 if (r.mCharacterOffset < prevOffset) {
1379 isSorted = false;
1380 break;
1382 prevOffset = r.mCharacterOffset;
1384 if (!isSorted) {
1385 runs.Sort(GlyphRunOffsetComparator());
1388 // Coalesce adjacent glyph runs that have the same properties, and eliminate
1389 // any empty runs.
1390 GlyphRun* prevRun = nullptr;
1391 const CompressedGlyph* charGlyphs = mCharacterGlyphs;
1393 runs.RemoveElementsBy([&](GlyphRun& aRun) -> bool {
1394 // First run is always retained.
1395 if (!prevRun) {
1396 prevRun = &aRun;
1397 return false;
1400 // Merge any run whose properties match its predecessor.
1401 if (prevRun->Matches(aRun.mFont, aRun.mOrientation, aRun.mIsCJK,
1402 aRun.mMatchType)) {
1403 return true;
1406 if (prevRun->mCharacterOffset >= aRun.mCharacterOffset) {
1407 // Preceding run is empty (or has become so due to the adjusting for
1408 // ligature boundaries), so we will overwrite it with this one, which
1409 // will then be discarded.
1410 *prevRun = aRun;
1411 return true;
1414 // If any glyph run starts with ligature-continuation characters, we need to
1415 // advance it to the first "real" character to avoid drawing partial
1416 // ligature glyphs from wrong font (seen with U+FEFF in reftest 474417-1, as
1417 // Core Text eliminates the glyph, which makes it appear as if a ligature
1418 // has been formed)
1419 while (charGlyphs[aRun.mCharacterOffset].IsLigatureContinuation() &&
1420 aRun.mCharacterOffset < GetLength()) {
1421 aRun.mCharacterOffset++;
1424 // We're keeping another run, so update prevRun pointer to refer to it (in
1425 // its new position).
1426 ++prevRun;
1427 return false;
1430 MOZ_ASSERT(prevRun == &runs.LastElement(), "lost track of prevRun!");
1432 // Drop any trailing empty run.
1433 if (runs.Length() > 1 && prevRun->mCharacterOffset == GetLength()) {
1434 runs.RemoveLastElement();
1437 MOZ_ASSERT(!runs.IsEmpty());
1438 if (runs.Length() == 1) {
1439 mGlyphRuns.ConvertToElement();
1443 void gfxTextRun::CopyGlyphDataFrom(gfxShapedWord* aShapedWord,
1444 uint32_t aOffset) {
1445 uint32_t wordLen = aShapedWord->GetLength();
1446 MOZ_ASSERT(aOffset + wordLen <= GetLength(), "word overruns end of textrun");
1448 CompressedGlyph* charGlyphs = GetCharacterGlyphs();
1449 const CompressedGlyph* wordGlyphs = aShapedWord->GetCharacterGlyphs();
1450 if (aShapedWord->HasDetailedGlyphs()) {
1451 for (uint32_t i = 0; i < wordLen; ++i, ++aOffset) {
1452 const CompressedGlyph& g = wordGlyphs[i];
1453 if (!g.IsSimpleGlyph()) {
1454 const DetailedGlyph* details =
1455 g.GetGlyphCount() > 0 ? aShapedWord->GetDetailedGlyphs(i) : nullptr;
1456 SetDetailedGlyphs(aOffset, g.GetGlyphCount(), details);
1458 charGlyphs[aOffset] = g;
1460 } else {
1461 memcpy(charGlyphs + aOffset, wordGlyphs, wordLen * sizeof(CompressedGlyph));
1465 void gfxTextRun::CopyGlyphDataFrom(gfxTextRun* aSource, Range aRange,
1466 uint32_t aDest) {
1467 MOZ_ASSERT(aRange.end <= aSource->GetLength(),
1468 "Source substring out of range");
1469 MOZ_ASSERT(aDest + aRange.Length() <= GetLength(),
1470 "Destination substring out of range");
1472 if (aSource->mDontSkipDrawing) {
1473 mDontSkipDrawing = true;
1476 // Copy base glyph data, and DetailedGlyph data where present
1477 const CompressedGlyph* srcGlyphs = aSource->mCharacterGlyphs + aRange.start;
1478 CompressedGlyph* dstGlyphs = mCharacterGlyphs + aDest;
1479 for (uint32_t i = 0; i < aRange.Length(); ++i) {
1480 CompressedGlyph g = srcGlyphs[i];
1481 g.SetCanBreakBefore(!g.IsClusterStart()
1482 ? CompressedGlyph::FLAG_BREAK_TYPE_NONE
1483 : dstGlyphs[i].CanBreakBefore());
1484 if (!g.IsSimpleGlyph()) {
1485 uint32_t count = g.GetGlyphCount();
1486 if (count > 0) {
1487 // DetailedGlyphs allocation is infallible, so this should never be
1488 // null unless the source textrun is somehow broken.
1489 DetailedGlyph* src = aSource->GetDetailedGlyphs(i + aRange.start);
1490 MOZ_ASSERT(src, "missing DetailedGlyphs?");
1491 if (src) {
1492 DetailedGlyph* dst = AllocateDetailedGlyphs(i + aDest, count);
1493 ::memcpy(dst, src, count * sizeof(DetailedGlyph));
1494 } else {
1495 g.SetMissing();
1499 dstGlyphs[i] = g;
1502 // Copy glyph runs
1503 #ifdef DEBUG
1504 GlyphRun* prevRun = nullptr;
1505 #endif
1506 for (GlyphRunIterator iter(aSource, aRange); !iter.AtEnd(); iter.NextRun()) {
1507 gfxFont* font = iter.GlyphRun()->mFont;
1508 MOZ_ASSERT(!prevRun || !prevRun->Matches(iter.GlyphRun()->mFont,
1509 iter.GlyphRun()->mOrientation,
1510 iter.GlyphRun()->mIsCJK,
1511 FontMatchType::Kind::kUnspecified),
1512 "Glyphruns not coalesced?");
1513 #ifdef DEBUG
1514 prevRun = const_cast<GlyphRun*>(iter.GlyphRun());
1515 uint32_t end = iter.StringEnd();
1516 #endif
1517 uint32_t start = iter.StringStart();
1519 // These used to be NS_ASSERTION()s, but WARNING is more appropriate.
1520 // Although it's unusual (and not desirable), it's possible for us to assign
1521 // different fonts to a base character and a following diacritic.
1522 // Example on OSX 10.5/10.6 with default fonts installed:
1523 // data:text/html,<p style="font-family:helvetica, arial, sans-serif;">
1524 // &%23x043E;&%23x0486;&%23x20;&%23x043E;&%23x0486;
1525 // This means the rendering of the cluster will probably not be very good,
1526 // but it's the best we can do for now if the specified font only covered
1527 // the initial base character and not its applied marks.
1528 NS_WARNING_ASSERTION(aSource->IsClusterStart(start),
1529 "Started font run in the middle of a cluster");
1530 NS_WARNING_ASSERTION(
1531 end == aSource->GetLength() || aSource->IsClusterStart(end),
1532 "Ended font run in the middle of a cluster");
1534 AddGlyphRun(font, iter.GlyphRun()->mMatchType, start - aRange.start + aDest,
1535 false, iter.GlyphRun()->mOrientation, iter.GlyphRun()->mIsCJK);
1539 void gfxTextRun::ClearGlyphsAndCharacters() {
1540 ResetGlyphRuns();
1541 memset(reinterpret_cast<char*>(mCharacterGlyphs), 0,
1542 mLength * sizeof(CompressedGlyph));
1543 mDetailedGlyphs = nullptr;
1546 void gfxTextRun::SetSpaceGlyph(gfxFont* aFont, DrawTarget* aDrawTarget,
1547 uint32_t aCharIndex,
1548 gfx::ShapedTextFlags aOrientation) {
1549 if (SetSpaceGlyphIfSimple(aFont, aCharIndex, ' ', aOrientation)) {
1550 return;
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 aFont->ProcessSingleSpaceShapedWord(
1560 aDrawTarget, vertical, mAppUnitsPerDevUnit, flags, roundingFlags,
1561 [&](gfxShapedWord* aShapedWord) {
1562 const GlyphRun* prevRun = TrailingGlyphRun();
1563 bool isCJK = prevRun && prevRun->mFont == aFont &&
1564 prevRun->mOrientation == aOrientation
1565 ? prevRun->mIsCJK
1566 : false;
1567 AddGlyphRun(aFont, FontMatchType::Kind::kUnspecified, aCharIndex, false,
1568 aOrientation, isCJK);
1569 CopyGlyphDataFrom(aShapedWord, aCharIndex);
1570 GetCharacterGlyphs()[aCharIndex].SetIsSpace();
1574 bool gfxTextRun::SetSpaceGlyphIfSimple(gfxFont* aFont, uint32_t aCharIndex,
1575 char16_t aSpaceChar,
1576 gfx::ShapedTextFlags aOrientation) {
1577 uint32_t spaceGlyph = aFont->GetSpaceGlyph();
1578 if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) {
1579 return false;
1582 gfxFont::Orientation fontOrientation =
1583 (aOrientation & gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT)
1584 ? nsFontMetrics::eVertical
1585 : nsFontMetrics::eHorizontal;
1586 uint32_t spaceWidthAppUnits = NS_lroundf(
1587 aFont->GetMetrics(fontOrientation).spaceWidth * mAppUnitsPerDevUnit);
1588 if (!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) {
1589 return false;
1592 const GlyphRun* prevRun = TrailingGlyphRun();
1593 bool isCJK = prevRun && prevRun->mFont == aFont &&
1594 prevRun->mOrientation == aOrientation
1595 ? prevRun->mIsCJK
1596 : false;
1597 AddGlyphRun(aFont, FontMatchType::Kind::kUnspecified, aCharIndex, false,
1598 aOrientation, isCJK);
1599 CompressedGlyph g =
1600 CompressedGlyph::MakeSimpleGlyph(spaceWidthAppUnits, spaceGlyph);
1601 if (aSpaceChar == ' ') {
1602 g.SetIsSpace();
1604 GetCharacterGlyphs()[aCharIndex] = g;
1605 return true;
1608 void gfxTextRun::FetchGlyphExtents(DrawTarget* aRefDrawTarget) const {
1609 bool needsGlyphExtents = NeedsGlyphExtents();
1610 if (!needsGlyphExtents && !mDetailedGlyphs) {
1611 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 gfxGlyphExtents* extents =
1628 font->GetOrCreateGlyphExtents(mAppUnitsPerDevUnit);
1630 AutoReadLock lock(extents->mLock);
1631 for (uint32_t 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->IsGlyphKnownLocked(glyphIndex)) {
1639 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
1640 ++gGlyphExtentsSetupEagerSimple;
1641 #endif
1642 extents->mLock.ReadUnlock();
1643 font->SetupGlyphExtents(aRefDrawTarget, glyphIndex, false, extents);
1644 extents->mLock.ReadLock();
1647 } else if (!glyphData->IsMissing()) {
1648 uint32_t glyphCount = glyphData->GetGlyphCount();
1649 if (glyphCount == 0) {
1650 continue;
1652 const gfxTextRun::DetailedGlyph* details = GetDetailedGlyphs(j);
1653 if (!details) {
1654 continue;
1656 for (uint32_t k = 0; k < glyphCount; ++k, ++details) {
1657 uint32_t glyphIndex = details->mGlyphID;
1658 if (!extents->IsGlyphKnownWithTightExtentsLocked(glyphIndex)) {
1659 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
1660 ++gGlyphExtentsSetupEagerTight;
1661 #endif
1662 extents->mLock.ReadUnlock();
1663 font->SetupGlyphExtents(aRefDrawTarget, glyphIndex, true, extents);
1664 extents->mLock.ReadLock();
1672 size_t gfxTextRun::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) {
1673 size_t total = mGlyphRuns.ShallowSizeOfExcludingThis(aMallocSizeOf);
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, HasNewline, DontSkipDrawingForPendingUserFonts,
1741 IsSimpleFlow, IncomingWhitespace, TrailingWhitespace,
1742 CompressedLeadingWhitespace, NoBreaks, IsTransformed, HasTrailingBreak,
1743 IsSingleCharMi, 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 fprintf(out, " Glyph runs:\n");
1754 for (const auto& run : mGlyphRuns) {
1755 gfxFont* font = run.mFont;
1756 const gfxFontStyle* style = font->GetStyle();
1757 nsAutoCString styleString;
1758 style->style.ToString(styleString);
1759 fprintf(out, " offset=%d %s %f/%g/%s\n", run.mCharacterOffset,
1760 font->GetName().get(), style->size, style->weight.ToFloat(),
1761 styleString.get());
1764 fprintf(out, " Glyphs:\n");
1765 for (uint32_t i = 0; i < mLength; ++i) {
1766 auto glyphData = GetCharacterGlyphs()[i];
1768 nsCString line;
1769 line.AppendPrintf(" [%d] 0x%p %s", i, GetCharacterGlyphs() + i,
1770 glyphData.IsSimpleGlyph() ? "simple" : "detailed");
1772 if (glyphData.IsSimpleGlyph()) {
1773 line.AppendPrintf(" id=%d adv=%d", glyphData.GetSimpleGlyph(),
1774 glyphData.GetSimpleAdvance());
1775 } else {
1776 uint32_t count = glyphData.GetGlyphCount();
1777 if (count) {
1778 line += " ids=";
1779 for (uint32_t j = 0; j < count; j++) {
1780 line.AppendPrintf(j ? ",%d" : "%d", GetDetailedGlyphs(i)[j].mGlyphID);
1782 line += " advs=";
1783 for (uint32_t j = 0; j < count; j++) {
1784 line.AppendPrintf(j ? ",%d" : "%d", GetDetailedGlyphs(i)[j].mAdvance);
1786 line += " offsets=";
1787 for (uint32_t j = 0; j < count; j++) {
1788 auto offset = GetDetailedGlyphs(i)[j].mOffset;
1789 line.AppendPrintf(j ? ",(%g,%g)" : "(%g,%g)", offset.x.value,
1790 offset.y.value);
1792 } else {
1793 line += " (no glyphs)";
1797 if (glyphData.CharIsSpace()) {
1798 line += " CHAR_IS_SPACE";
1800 if (glyphData.CharIsTab()) {
1801 line += " CHAR_IS_TAB";
1803 if (glyphData.CharIsNewline()) {
1804 line += " CHAR_IS_NEWLINE";
1806 if (glyphData.CharIsFormattingControl()) {
1807 line += " CHAR_IS_FORMATTING_CONTROL";
1809 if (glyphData.CharTypeFlags() &
1810 CompressedGlyph::FLAG_CHAR_NO_EMPHASIS_MARK) {
1811 line += " CHAR_NO_EMPHASIS_MARK";
1814 if (!glyphData.IsSimpleGlyph()) {
1815 if (!glyphData.IsMissing()) {
1816 line += " NOT_MISSING";
1818 if (!glyphData.IsClusterStart()) {
1819 line += " NOT_IS_CLUSTER_START";
1821 if (!glyphData.IsLigatureGroupStart()) {
1822 line += " NOT_LIGATURE_GROUP_START";
1826 switch (glyphData.CanBreakBefore()) {
1827 case CompressedGlyph::FLAG_BREAK_TYPE_NORMAL:
1828 line += " BREAK_TYPE_NORMAL";
1829 break;
1830 case CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN:
1831 line += " BREAK_TYPE_HYPHEN";
1832 break;
1835 fprintf(out, "%s\n", line.get());
1838 #endif
1840 gfxFontGroup::gfxFontGroup(nsPresContext* aPresContext,
1841 const StyleFontFamilyList& aFontFamilyList,
1842 const gfxFontStyle* aStyle, nsAtom* aLanguage,
1843 bool aExplicitLanguage,
1844 gfxTextPerfMetrics* aTextPerf,
1845 gfxUserFontSet* aUserFontSet, gfxFloat aDevToCssSize,
1846 StyleFontVariantEmoji aVariantEmoji)
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 switch (aVariantEmoji) {
1862 case StyleFontVariantEmoji::Normal:
1863 case StyleFontVariantEmoji::Unicode:
1864 break;
1865 case StyleFontVariantEmoji::Text:
1866 mEmojiPresentation = eFontPresentation::Text;
1867 break;
1868 case StyleFontVariantEmoji::Emoji:
1869 mEmojiPresentation = eFontPresentation::EmojiExplicit;
1870 break;
1872 // We don't use SetUserFontSet() here, as we want to unconditionally call
1873 // BuildFontList() rather than only do UpdateUserFonts() if it changed.
1874 mCurrGeneration = GetGeneration();
1875 BuildFontList();
1878 gfxFontGroup::~gfxFontGroup() {
1879 // Should not be dropped by stylo
1880 MOZ_ASSERT(!Servo_IsWorkerThread());
1883 static StyleGenericFontFamily GetDefaultGeneric(nsAtom* aLanguage) {
1884 return StaticPresData::Get()
1885 ->GetFontPrefsForLang(aLanguage)
1886 ->GetDefaultGeneric();
1889 void gfxFontGroup::BuildFontList() {
1890 // initialize fonts in the font family list
1891 AutoTArray<FamilyAndGeneric, 10> fonts;
1892 gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
1893 mFontListGeneration = pfl->GetGeneration();
1895 // lookup fonts in the fontlist
1896 for (const StyleSingleFontFamily& name : mFamilyList.list.AsSpan()) {
1897 if (name.IsFamilyName()) {
1898 const auto& familyName = name.AsFamilyName();
1899 AddPlatformFont(nsAtomCString(familyName.name.AsAtom()),
1900 familyName.syntax == StyleFontFamilyNameSyntax::Quoted,
1901 fonts);
1902 } else {
1903 MOZ_ASSERT(name.IsGeneric());
1904 const StyleGenericFontFamily generic = name.AsGeneric();
1905 // system-ui is usually a single family, so it doesn't work great as
1906 // fallback. Prefer the following generic or the language default instead.
1907 if (mFallbackGeneric == StyleGenericFontFamily::None &&
1908 generic != StyleGenericFontFamily::SystemUi) {
1909 mFallbackGeneric = generic;
1911 pfl->AddGenericFonts(mPresContext, generic, mLanguage, fonts);
1912 if (mTextPerf) {
1913 mTextPerf->current.genericLookups++;
1918 // If necessary, append default language generic onto the end.
1919 if (mFallbackGeneric == StyleGenericFontFamily::None && !mStyle.systemFont) {
1920 auto defaultLanguageGeneric = GetDefaultGeneric(mLanguage);
1922 pfl->AddGenericFonts(mPresContext, defaultLanguageGeneric, mLanguage,
1923 fonts);
1924 if (mTextPerf) {
1925 mTextPerf->current.genericLookups++;
1929 // build the fontlist from the specified families
1930 for (const auto& f : fonts) {
1931 if (f.mFamily.mShared) {
1932 AddFamilyToFontList(f.mFamily.mShared, f.mGeneric);
1933 } else {
1934 AddFamilyToFontList(f.mFamily.mUnshared, f.mGeneric);
1939 void gfxFontGroup::AddPlatformFont(const nsACString& aName, bool aQuotedName,
1940 nsTArray<FamilyAndGeneric>& aFamilyList) {
1941 // First, look up in the user font set...
1942 // If the fontSet matches the family, we must not look for a platform
1943 // font of the same name, even if we fail to actually get a fontEntry
1944 // here; we'll fall back to the next name in the CSS font-family list.
1945 if (mUserFontSet) {
1946 // Add userfonts to the fontlist whether already loaded
1947 // or not. Loading is initiated during font matching.
1948 RefPtr<gfxFontFamily> family = mUserFontSet->LookupFamily(aName);
1949 if (family) {
1950 aFamilyList.AppendElement(std::move(family));
1951 return;
1955 // Not known in the user font set ==> check system fonts
1956 gfxPlatformFontList::PlatformFontList()->FindAndAddFamilies(
1957 mPresContext, StyleGenericFontFamily::None, aName, &aFamilyList,
1958 aQuotedName ? gfxPlatformFontList::FindFamiliesFlags::eQuotedFamilyName
1959 : gfxPlatformFontList::FindFamiliesFlags(0),
1960 &mStyle, mLanguage.get(), mDevToCssSize);
1963 void gfxFontGroup::AddFamilyToFontList(gfxFontFamily* aFamily,
1964 StyleGenericFontFamily aGeneric) {
1965 if (!aFamily) {
1966 MOZ_ASSERT_UNREACHABLE("don't try to add a null font family!");
1967 return;
1969 AutoTArray<gfxFontEntry*, 4> fontEntryList;
1970 aFamily->FindAllFontsForStyle(mStyle, fontEntryList);
1971 // add these to the fontlist
1972 for (gfxFontEntry* fe : fontEntryList) {
1973 if (!HasFont(fe)) {
1974 FamilyFace ff(aFamily, fe, aGeneric);
1975 if (fe->mIsUserFontContainer) {
1976 ff.CheckState(mSkipDrawing);
1978 mFonts.AppendElement(ff);
1981 // for a family marked as "check fallback faces", only mark the last
1982 // entry so that fallbacks for a family are only checked once
1983 if (aFamily->CheckForFallbackFaces() && !fontEntryList.IsEmpty() &&
1984 !mFonts.IsEmpty()) {
1985 mFonts.LastElement().SetCheckForFallbackFaces();
1989 void gfxFontGroup::AddFamilyToFontList(fontlist::Family* aFamily,
1990 StyleGenericFontFamily aGeneric) {
1991 gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
1992 if (!aFamily->IsInitialized()) {
1993 if (ServoStyleSet* set = gfxFontUtils::CurrentServoStyleSet()) {
1994 // If we need to initialize a Family record, but we're on a style
1995 // worker thread, we have to defer it.
1996 set->AppendTask(PostTraversalTask::InitializeFamily(aFamily));
1997 set->AppendTask(PostTraversalTask::FontInfoUpdate(set));
1998 return;
2000 if (!pfl->InitializeFamily(aFamily)) {
2001 return;
2004 AutoTArray<fontlist::Face*, 4> faceList;
2005 aFamily->FindAllFacesForStyle(pfl->SharedFontList(), mStyle, faceList);
2006 for (auto* face : faceList) {
2007 gfxFontEntry* fe = pfl->GetOrCreateFontEntry(face, aFamily);
2008 if (fe && !HasFont(fe)) {
2009 FamilyFace ff(aFamily, fe, aGeneric);
2010 mFonts.AppendElement(ff);
2015 bool gfxFontGroup::HasFont(const gfxFontEntry* aFontEntry) {
2016 for (auto& f : mFonts) {
2017 if (f.FontEntry() == aFontEntry) {
2018 return true;
2021 return false;
2024 already_AddRefed<gfxFont> gfxFontGroup::GetFontAt(uint32_t i, uint32_t aCh,
2025 bool* aLoading) {
2026 if (i >= mFonts.Length()) {
2027 return nullptr;
2030 FamilyFace& ff = mFonts[i];
2031 if (ff.IsInvalid() || ff.IsLoading()) {
2032 return nullptr;
2035 RefPtr<gfxFont> font = ff.Font();
2036 if (!font) {
2037 gfxFontEntry* fe = ff.FontEntry();
2038 if (!fe) {
2039 return nullptr;
2041 gfxCharacterMap* unicodeRangeMap = nullptr;
2042 if (fe->mIsUserFontContainer) {
2043 gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe);
2044 if (ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED &&
2045 ufe->CharacterInUnicodeRange(aCh) && !*aLoading) {
2046 ufe->Load();
2047 ff.CheckState(mSkipDrawing);
2048 *aLoading = ff.IsLoading();
2050 fe = ufe->GetPlatformFontEntry();
2051 if (!fe) {
2052 return nullptr;
2054 unicodeRangeMap = ufe->GetUnicodeRangeMap();
2056 font = fe->FindOrMakeFont(&mStyle, unicodeRangeMap);
2057 if (!font || !font->Valid()) {
2058 ff.SetInvalid();
2059 return nullptr;
2061 ff.SetFont(font);
2063 return font.forget();
2066 void gfxFontGroup::FamilyFace::CheckState(bool& aSkipDrawing) {
2067 gfxFontEntry* fe = FontEntry();
2068 if (!fe) {
2069 return;
2071 if (fe->mIsUserFontContainer) {
2072 gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe);
2073 gfxUserFontEntry::UserFontLoadState state = ufe->LoadState();
2074 switch (state) {
2075 case gfxUserFontEntry::STATUS_LOAD_PENDING:
2076 case gfxUserFontEntry::STATUS_LOADING:
2077 SetLoading(true);
2078 break;
2079 case gfxUserFontEntry::STATUS_FAILED:
2080 SetInvalid();
2081 // fall-thru to the default case
2082 [[fallthrough]];
2083 default:
2084 SetLoading(false);
2086 if (ufe->WaitForUserFont()) {
2087 aSkipDrawing = true;
2092 bool gfxFontGroup::FamilyFace::EqualsUserFont(
2093 const gfxUserFontEntry* aUserFont) const {
2094 gfxFontEntry* fe = FontEntry();
2095 // if there's a font, the entry is the underlying platform font
2096 if (mFontCreated) {
2097 gfxFontEntry* pfe = aUserFont->GetPlatformFontEntry();
2098 if (pfe == fe) {
2099 return true;
2101 } else if (fe == aUserFont) {
2102 return true;
2104 return false;
2107 static nsAutoCString FamilyListToString(
2108 const StyleFontFamilyList& aFamilyList) {
2109 return StringJoin(","_ns, aFamilyList.list.AsSpan(),
2110 [](nsACString& dst, const StyleSingleFontFamily& name) {
2111 name.AppendToString(dst);
2115 already_AddRefed<gfxFont> gfxFontGroup::GetDefaultFont() {
2116 if (mDefaultFont) {
2117 return do_AddRef(mDefaultFont);
2120 gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
2121 FontFamily family = pfl->GetDefaultFont(mPresContext, &mStyle);
2122 MOZ_ASSERT(!family.IsNull(),
2123 "invalid default font returned by GetDefaultFont");
2125 gfxFontEntry* fe = nullptr;
2126 if (family.mShared) {
2127 fontlist::Family* fam = family.mShared;
2128 if (!fam->IsInitialized()) {
2129 // If this fails, FindFaceForStyle will just safely return nullptr
2130 Unused << pfl->InitializeFamily(fam);
2132 fontlist::Face* face = fam->FindFaceForStyle(pfl->SharedFontList(), mStyle);
2133 if (face) {
2134 fe = pfl->GetOrCreateFontEntry(face, fam);
2136 } else {
2137 fe = family.mUnshared->FindFontForStyle(mStyle);
2139 if (fe) {
2140 mDefaultFont = fe->FindOrMakeFont(&mStyle);
2143 uint32_t numInits, loaderState;
2144 pfl->GetFontlistInitInfo(numInits, loaderState);
2146 MOZ_ASSERT(numInits != 0,
2147 "must initialize system fontlist before getting default font!");
2149 uint32_t numFonts = 0;
2150 if (!mDefaultFont) {
2151 // Try for a "font of last resort...."
2152 // Because an empty font list would be Really Bad for later code
2153 // that assumes it will be able to get valid metrics for layout,
2154 // just look for the first usable font and put in the list.
2155 // (see bug 554544)
2156 if (pfl->SharedFontList()) {
2157 fontlist::FontList* list = pfl->SharedFontList();
2158 numFonts = list->NumFamilies();
2159 fontlist::Family* families = list->Families();
2160 for (uint32_t i = 0; i < numFonts; ++i) {
2161 fontlist::Family* fam = &families[i];
2162 if (!fam->IsInitialized()) {
2163 Unused << pfl->InitializeFamily(fam);
2165 fontlist::Face* face =
2166 fam->FindFaceForStyle(pfl->SharedFontList(), mStyle);
2167 if (face) {
2168 fe = pfl->GetOrCreateFontEntry(face, fam);
2169 if (fe) {
2170 mDefaultFont = fe->FindOrMakeFont(&mStyle);
2171 if (mDefaultFont) {
2172 break;
2174 NS_WARNING("FindOrMakeFont failed");
2178 } else {
2179 AutoTArray<RefPtr<gfxFontFamily>, 200> familyList;
2180 pfl->GetFontFamilyList(familyList);
2181 numFonts = familyList.Length();
2182 for (uint32_t i = 0; i < numFonts; ++i) {
2183 gfxFontEntry* fe = familyList[i]->FindFontForStyle(mStyle, true);
2184 if (fe) {
2185 mDefaultFont = fe->FindOrMakeFont(&mStyle);
2186 if (mDefaultFont) {
2187 break;
2194 if (!mDefaultFont && pfl->SharedFontList() && !XRE_IsParentProcess()) {
2195 // If we're a content process, it's possible this is failing because the
2196 // chrome process has just updated the shared font list and we haven't yet
2197 // refreshed our reference to it. If that's the case, update and retry.
2198 // But if we're not on the main thread, we can't do this, so just use
2199 // the platform default font directly.
2200 if (NS_IsMainThread()) {
2201 uint32_t oldGeneration = pfl->SharedFontList()->GetGeneration();
2202 pfl->UpdateFontList();
2203 if (pfl->SharedFontList()->GetGeneration() != oldGeneration) {
2204 return GetDefaultFont();
2209 if (!mDefaultFont) {
2210 // We must have failed to find anything usable in our font-family list,
2211 // or it's badly broken. One more last-ditch effort to make a font:
2212 gfxFontEntry* fe = pfl->GetDefaultFontEntry();
2213 if (fe) {
2214 RefPtr<gfxFont> f = fe->FindOrMakeFont(&mStyle);
2215 if (f) {
2216 return f.forget();
2221 if (!mDefaultFont) {
2222 // an empty font list at this point is fatal; we're not going to
2223 // be able to do even the most basic layout operations
2225 // annotate crash report with fontlist info
2226 nsAutoCString fontInitInfo;
2227 fontInitInfo.AppendPrintf("no fonts - init: %d fonts: %d loader: %d",
2228 numInits, numFonts, loaderState);
2229 #ifdef XP_WIN
2230 bool dwriteEnabled = gfxWindowsPlatform::GetPlatform()->DWriteEnabled();
2231 double upTime = (double)GetTickCount();
2232 fontInitInfo.AppendPrintf(" backend: %s system-uptime: %9.3f sec",
2233 dwriteEnabled ? "directwrite" : "gdi",
2234 upTime / 1000);
2235 #endif
2236 gfxCriticalError() << fontInitInfo.get();
2238 char msg[256]; // CHECK buffer length if revising message below
2239 SprintfLiteral(msg, "unable to find a usable font (%.220s)",
2240 FamilyListToString(mFamilyList).get());
2241 MOZ_CRASH_UNSAFE(msg);
2244 return do_AddRef(mDefaultFont);
2247 already_AddRefed<gfxFont> gfxFontGroup::GetFirstValidFont(
2248 uint32_t aCh, StyleGenericFontFamily* aGeneric, bool* aIsFirst) {
2249 // Ensure cached font instances are valid.
2250 CheckForUpdatedPlatformList();
2252 uint32_t count = mFonts.Length();
2253 bool loading = false;
2255 // Check whether the font supports the given character, unless aCh is the
2256 // kCSSFirstAvailableFont constant, in which case (as per CSS Fonts spec)
2257 // we want the first font whose unicode-range does not exclude <space>,
2258 // regardless of whether it in fact supports the <space> character.
2259 auto isValidForChar = [](gfxFont* aFont, uint32_t aCh) -> bool {
2260 if (!aFont) {
2261 return false;
2263 if (aCh == kCSSFirstAvailableFont) {
2264 if (const auto* unicodeRange = aFont->GetUnicodeRangeMap()) {
2265 return unicodeRange->test(' ');
2267 return true;
2269 return aFont->HasCharacter(aCh);
2272 for (uint32_t i = 0; i < count; ++i) {
2273 FamilyFace& ff = mFonts[i];
2274 if (ff.IsInvalid()) {
2275 continue;
2278 // already have a font?
2279 RefPtr<gfxFont> font = ff.Font();
2280 if (isValidForChar(font, aCh)) {
2281 if (aGeneric) {
2282 *aGeneric = ff.Generic();
2284 if (aIsFirst) {
2285 *aIsFirst = (i == 0);
2287 return font.forget();
2290 // Need to build a font, loading userfont if not loaded. In
2291 // cases where unicode range might apply, use the character
2292 // provided.
2293 gfxFontEntry* fe = ff.FontEntry();
2294 if (fe && fe->mIsUserFontContainer) {
2295 gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe);
2296 bool inRange = ufe->CharacterInUnicodeRange(
2297 aCh == kCSSFirstAvailableFont ? ' ' : aCh);
2298 if (inRange) {
2299 if (!loading &&
2300 ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED) {
2301 ufe->Load();
2302 ff.CheckState(mSkipDrawing);
2304 if (ff.IsLoading()) {
2305 loading = true;
2308 if (ufe->LoadState() != gfxUserFontEntry::STATUS_LOADED || !inRange) {
2309 continue;
2313 font = GetFontAt(i, aCh, &loading);
2314 if (isValidForChar(font, aCh)) {
2315 if (aGeneric) {
2316 *aGeneric = ff.Generic();
2318 if (aIsFirst) {
2319 *aIsFirst = (i == 0);
2321 return font.forget();
2324 if (aGeneric) {
2325 *aGeneric = StyleGenericFontFamily::None;
2327 if (aIsFirst) {
2328 *aIsFirst = false;
2330 return GetDefaultFont();
2333 already_AddRefed<gfxFont> gfxFontGroup::GetFirstMathFont() {
2334 uint32_t count = mFonts.Length();
2335 for (uint32_t i = 0; i < count; ++i) {
2336 RefPtr<gfxFont> font = GetFontAt(i);
2337 if (font && font->TryGetMathTable()) {
2338 return font.forget();
2341 return nullptr;
2344 bool gfxFontGroup::IsInvalidChar(uint8_t ch) {
2345 return ((ch & 0x7f) < 0x20 || ch == 0x7f);
2348 bool gfxFontGroup::IsInvalidChar(char16_t ch) {
2349 // All printable 7-bit ASCII values are OK
2350 if (ch >= ' ' && ch < 0x7f) {
2351 return false;
2353 // No point in sending non-printing control chars through font shaping
2354 if (ch <= 0x9f) {
2355 return true;
2357 // Word-separating format/bidi control characters are not shaped as part
2358 // of words.
2359 return (((ch & 0xFF00) == 0x2000 /* Unicode control character */ &&
2360 (ch == 0x200B /*ZWSP*/ || ch == 0x2028 /*LSEP*/ ||
2361 ch == 0x2029 /*PSEP*/ || ch == 0x2060 /*WJ*/)) ||
2362 ch == 0xfeff /*ZWNBSP*/ || IsBidiControl(ch));
2365 already_AddRefed<gfxTextRun> gfxFontGroup::MakeEmptyTextRun(
2366 const Parameters* aParams, gfx::ShapedTextFlags aFlags,
2367 nsTextFrameUtils::Flags aFlags2) {
2368 aFlags |= ShapedTextFlags::TEXT_IS_8BIT;
2369 return gfxTextRun::Create(aParams, 0, this, aFlags, aFlags2);
2372 already_AddRefed<gfxTextRun> gfxFontGroup::MakeSpaceTextRun(
2373 const Parameters* aParams, gfx::ShapedTextFlags aFlags,
2374 nsTextFrameUtils::Flags aFlags2) {
2375 aFlags |= ShapedTextFlags::TEXT_IS_8BIT;
2377 RefPtr<gfxTextRun> textRun =
2378 gfxTextRun::Create(aParams, 1, this, aFlags, aFlags2);
2379 if (!textRun) {
2380 return nullptr;
2383 gfx::ShapedTextFlags orientation = aFlags & ShapedTextFlags::TEXT_ORIENT_MASK;
2384 if (orientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
2385 orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
2388 RefPtr<gfxFont> font = GetFirstValidFont();
2389 if (MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero())) {
2390 // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle
2391 // them, and always create at least size 1 fonts, i.e. they still
2392 // render something for size 0 fonts.
2393 textRun->AddGlyphRun(font, FontMatchType::Kind::kUnspecified, 0, false,
2394 orientation, false);
2395 } else {
2396 if (font->GetSpaceGlyph()) {
2397 // Normally, the font has a cached space glyph, so we can avoid
2398 // the cost of calling FindFontForChar.
2399 textRun->SetSpaceGlyph(font, aParams->mDrawTarget, 0, orientation);
2400 } else {
2401 // In case the primary font doesn't have <space> (bug 970891),
2402 // find one that does.
2403 FontMatchType matchType;
2404 RefPtr<gfxFont> spaceFont =
2405 FindFontForChar(' ', 0, 0, Script::LATIN, nullptr, &matchType);
2406 if (spaceFont) {
2407 textRun->SetSpaceGlyph(spaceFont, aParams->mDrawTarget, 0, orientation);
2412 // Note that the gfxGlyphExtents glyph bounds storage for the font will
2413 // always contain an entry for the font's space glyph, so we don't have
2414 // to call FetchGlyphExtents here.
2415 return textRun.forget();
2418 template <typename T>
2419 already_AddRefed<gfxTextRun> gfxFontGroup::MakeBlankTextRun(
2420 const T* aString, uint32_t aLength, const Parameters* aParams,
2421 gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2) {
2422 RefPtr<gfxTextRun> textRun =
2423 gfxTextRun::Create(aParams, aLength, this, aFlags, aFlags2);
2424 if (!textRun) {
2425 return nullptr;
2428 gfx::ShapedTextFlags orientation = aFlags & ShapedTextFlags::TEXT_ORIENT_MASK;
2429 if (orientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
2430 orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
2432 RefPtr<gfxFont> font = GetFirstValidFont();
2433 textRun->AddGlyphRun(font, FontMatchType::Kind::kUnspecified, 0, false,
2434 orientation, false);
2436 textRun->SetupClusterBoundaries(0, aString, aLength);
2438 for (uint32_t i = 0; i < aLength; i++) {
2439 if (aString[i] == '\n') {
2440 textRun->SetIsNewline(i);
2441 } else if (aString[i] == '\t') {
2442 textRun->SetIsTab(i);
2446 return textRun.forget();
2449 already_AddRefed<gfxTextRun> gfxFontGroup::MakeHyphenTextRun(
2450 DrawTarget* aDrawTarget, gfx::ShapedTextFlags aFlags,
2451 uint32_t aAppUnitsPerDevUnit) {
2452 // only use U+2010 if it is supported by the first font in the group;
2453 // it's better to use ASCII '-' from the primary font than to fall back to
2454 // U+2010 from some other, possibly poorly-matching face
2455 static const char16_t hyphen = 0x2010;
2456 RefPtr<gfxFont> font = GetFirstValidFont(uint32_t(hyphen));
2457 if (font->HasCharacter(hyphen)) {
2458 return MakeTextRun(&hyphen, 1, aDrawTarget, aAppUnitsPerDevUnit, aFlags,
2459 nsTextFrameUtils::Flags(), nullptr);
2462 static const uint8_t dash = '-';
2463 return MakeTextRun(&dash, 1, aDrawTarget, aAppUnitsPerDevUnit, aFlags,
2464 nsTextFrameUtils::Flags(), nullptr);
2467 gfxFloat gfxFontGroup::GetHyphenWidth(
2468 const gfxTextRun::PropertyProvider* aProvider) {
2469 if (mHyphenWidth < 0) {
2470 RefPtr<DrawTarget> dt(aProvider->GetDrawTarget());
2471 if (dt) {
2472 RefPtr<gfxTextRun> hyphRun(
2473 MakeHyphenTextRun(dt, aProvider->GetShapedTextFlags(),
2474 aProvider->GetAppUnitsPerDevUnit()));
2475 mHyphenWidth = hyphRun.get() ? hyphRun->GetAdvanceWidth() : 0;
2478 return mHyphenWidth;
2481 template <typename T>
2482 already_AddRefed<gfxTextRun> gfxFontGroup::MakeTextRun(
2483 const T* aString, uint32_t aLength, const Parameters* aParams,
2484 gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2,
2485 gfxMissingFontRecorder* aMFR) {
2486 if (aLength == 0) {
2487 return MakeEmptyTextRun(aParams, aFlags, aFlags2);
2489 if (aLength == 1 && aString[0] == ' ') {
2490 return MakeSpaceTextRun(aParams, aFlags, aFlags2);
2493 if (sizeof(T) == 1) {
2494 aFlags |= ShapedTextFlags::TEXT_IS_8BIT;
2497 if (MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero())) {
2498 // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle
2499 // them, and always create at least size 1 fonts, i.e. they still
2500 // render something for size 0 fonts.
2501 return MakeBlankTextRun(aString, aLength, aParams, aFlags, aFlags2);
2504 RefPtr<gfxTextRun> textRun =
2505 gfxTextRun::Create(aParams, aLength, this, aFlags, aFlags2);
2506 if (!textRun) {
2507 return nullptr;
2510 InitTextRun(aParams->mDrawTarget, textRun.get(), aString, aLength, aMFR);
2512 textRun->FetchGlyphExtents(aParams->mDrawTarget);
2514 return textRun.forget();
2517 // MakeTextRun instantiations (needed by Linux64 base-toolchain build).
2518 template already_AddRefed<gfxTextRun> gfxFontGroup::MakeTextRun(
2519 const uint8_t* aString, uint32_t aLength, const Parameters* aParams,
2520 gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2,
2521 gfxMissingFontRecorder* aMFR);
2522 template already_AddRefed<gfxTextRun> gfxFontGroup::MakeTextRun(
2523 const char16_t* aString, uint32_t aLength, const Parameters* aParams,
2524 gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2,
2525 gfxMissingFontRecorder* aMFR);
2527 template <typename T>
2528 void gfxFontGroup::InitTextRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
2529 const T* aString, uint32_t aLength,
2530 gfxMissingFontRecorder* aMFR) {
2531 NS_ASSERTION(aLength > 0, "don't call InitTextRun for a zero-length run");
2533 // we need to do numeral processing even on 8-bit text,
2534 // in case we're converting Western to Hindi/Arabic digits
2535 uint32_t numOption = gfxPlatform::GetPlatform()->GetBidiNumeralOption();
2536 UniquePtr<char16_t[]> transformedString;
2537 if (numOption != IBMBIDI_NUMERAL_NOMINAL) {
2538 // scan the string for numerals that may need to be transformed;
2539 // if we find any, we'll make a local copy here and use that for
2540 // font matching and glyph generation/shaping
2541 bool prevIsArabic =
2542 !!(aTextRun->GetFlags() & ShapedTextFlags::TEXT_INCOMING_ARABICCHAR);
2543 for (uint32_t i = 0; i < aLength; ++i) {
2544 char16_t origCh = aString[i];
2545 char16_t newCh = HandleNumberInChar(origCh, prevIsArabic, numOption);
2546 if (newCh != origCh) {
2547 if (!transformedString) {
2548 transformedString = MakeUnique<char16_t[]>(aLength);
2549 if constexpr (sizeof(T) == sizeof(char16_t)) {
2550 memcpy(transformedString.get(), aString, i * sizeof(char16_t));
2551 } else {
2552 for (uint32_t j = 0; j < i; ++j) {
2553 transformedString[j] = aString[j];
2558 if (transformedString) {
2559 transformedString[i] = newCh;
2561 prevIsArabic = IS_ARABIC_CHAR(newCh);
2565 LogModule* log = mStyle.systemFont ? gfxPlatform::GetLog(eGfxLog_textrunui)
2566 : gfxPlatform::GetLog(eGfxLog_textrun);
2568 // variant fallback handling may end up passing through this twice
2569 bool redo;
2570 do {
2571 redo = false;
2573 if (sizeof(T) == sizeof(uint8_t) && !transformedString) {
2574 if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) {
2575 nsAutoCString lang;
2576 mLanguage->ToUTF8String(lang);
2577 nsAutoCString str((const char*)aString, aLength);
2578 nsAutoCString styleString;
2579 mStyle.style.ToString(styleString);
2580 auto defaultLanguageGeneric = GetDefaultGeneric(mLanguage);
2581 MOZ_LOG(
2582 log, LogLevel::Warning,
2583 ("(%s) fontgroup: [%s] default: %s lang: %s script: %d "
2584 "len %d weight: %g stretch: %g%% style: %s size: %6.2f %zu-byte "
2585 "TEXTRUN [%s] ENDTEXTRUN\n",
2586 (mStyle.systemFont ? "textrunui" : "textrun"),
2587 FamilyListToString(mFamilyList).get(),
2588 (defaultLanguageGeneric == StyleGenericFontFamily::Serif
2589 ? "serif"
2590 : (defaultLanguageGeneric == StyleGenericFontFamily::SansSerif
2591 ? "sans-serif"
2592 : "none")),
2593 lang.get(), static_cast<int>(Script::LATIN), aLength,
2594 mStyle.weight.ToFloat(), mStyle.stretch.ToFloat(),
2595 styleString.get(), mStyle.size, sizeof(T), str.get()));
2598 // the text is still purely 8-bit; bypass the script-run itemizer
2599 // and treat it as a single Latin run
2600 InitScriptRun(aDrawTarget, aTextRun, aString, 0, aLength, Script::LATIN,
2601 aMFR);
2602 } else {
2603 const char16_t* textPtr;
2604 if (transformedString) {
2605 textPtr = transformedString.get();
2606 } else {
2607 // typecast to avoid compilation error for the 8-bit version,
2608 // even though this is dead code in that case
2609 textPtr = reinterpret_cast<const char16_t*>(aString);
2612 // split into script runs so that script can potentially influence
2613 // the font matching process below
2614 gfxScriptItemizer scriptRuns(textPtr, aLength);
2616 uint32_t runStart = 0, runLimit = aLength;
2617 Script runScript = Script::LATIN;
2618 while (scriptRuns.Next(runStart, runLimit, runScript)) {
2619 if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) {
2620 nsAutoCString lang;
2621 mLanguage->ToUTF8String(lang);
2622 nsAutoCString styleString;
2623 mStyle.style.ToString(styleString);
2624 auto defaultLanguageGeneric = GetDefaultGeneric(mLanguage);
2625 uint32_t runLen = runLimit - runStart;
2626 MOZ_LOG(log, LogLevel::Warning,
2627 ("(%s) fontgroup: [%s] default: %s lang: %s script: %d "
2628 "len %d weight: %g stretch: %g%% style: %s size: %6.2f "
2629 "%zu-byte TEXTRUN [%s] ENDTEXTRUN\n",
2630 (mStyle.systemFont ? "textrunui" : "textrun"),
2631 FamilyListToString(mFamilyList).get(),
2632 (defaultLanguageGeneric == StyleGenericFontFamily::Serif
2633 ? "serif"
2634 : (defaultLanguageGeneric ==
2635 StyleGenericFontFamily::SansSerif
2636 ? "sans-serif"
2637 : "none")),
2638 lang.get(), static_cast<int>(runScript), runLen,
2639 mStyle.weight.ToFloat(), mStyle.stretch.ToFloat(),
2640 styleString.get(), mStyle.size, sizeof(T),
2641 NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get()));
2644 InitScriptRun(aDrawTarget, aTextRun, textPtr + runStart, runStart,
2645 runLimit - runStart, runScript, aMFR);
2649 // if shaping was aborted due to lack of feature support, clear out
2650 // glyph runs and redo shaping with fallback forced on
2651 if (aTextRun->GetShapingState() == gfxTextRun::eShapingState_Aborted) {
2652 redo = true;
2653 aTextRun->SetShapingState(gfxTextRun::eShapingState_ForceFallbackFeature);
2654 aTextRun->ClearGlyphsAndCharacters();
2657 } while (redo);
2659 if (sizeof(T) == sizeof(char16_t) && aLength > 0) {
2660 gfxTextRun::CompressedGlyph* glyph = aTextRun->GetCharacterGlyphs();
2661 if (!glyph->IsSimpleGlyph()) {
2662 glyph->SetClusterStart(true);
2666 // It's possible for CoreText to omit glyph runs if it decides they contain
2667 // only invisibles (e.g., U+FEFF, see reftest 474417-1). In this case, we
2668 // need to eliminate them from the glyph run array to avoid drawing "partial
2669 // ligatures" with the wrong font.
2670 // We don't do this during InitScriptRun (or gfxFont::InitTextRun) because
2671 // it will iterate back over all glyphruns in the textrun, which leads to
2672 // pathologically-bad perf in the case where a textrun contains many script
2673 // changes (see bug 680402) - we'd end up re-sanitizing all the earlier runs
2674 // every time a new script subrun is processed.
2675 aTextRun->SanitizeGlyphRuns();
2678 static inline bool IsPUA(uint32_t aUSV) {
2679 // We could look up the General Category of the codepoint here,
2680 // but it's simpler to check PUA codepoint ranges.
2681 return (aUSV >= 0xE000 && aUSV <= 0xF8FF) || (aUSV >= 0xF0000);
2684 template <typename T>
2685 void gfxFontGroup::InitScriptRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
2686 const T* aString, // text for this script run,
2687 // not the entire textrun
2688 uint32_t aOffset, // position of the script
2689 // run within the textrun
2690 uint32_t aLength, // length of the script run
2691 Script aRunScript,
2692 gfxMissingFontRecorder* aMFR) {
2693 NS_ASSERTION(aLength > 0, "don't call InitScriptRun for a 0-length run");
2694 NS_ASSERTION(aTextRun->GetShapingState() != gfxTextRun::eShapingState_Aborted,
2695 "don't call InitScriptRun with aborted shaping state");
2697 // confirm the load state of userfonts in the list
2698 if (mUserFontSet && mCurrGeneration != mUserFontSet->GetGeneration()) {
2699 UpdateUserFonts();
2702 RefPtr<gfxFont> mainFont = GetFirstValidFont();
2704 ShapedTextFlags orientation =
2705 aTextRun->GetFlags() & ShapedTextFlags::TEXT_ORIENT_MASK;
2707 if (orientation != ShapedTextFlags::TEXT_ORIENT_HORIZONTAL &&
2708 (aRunScript == Script::MONGOLIAN || aRunScript == Script::PHAGS_PA)) {
2709 // Mongolian and Phags-pa text should ignore text-orientation and
2710 // always render in its "native" vertical mode, implemented by fonts
2711 // as sideways-right (i.e as if shaped horizontally, and then the
2712 // entire line is rotated to render vertically). Therefore, we ignore
2713 // the aOrientation value from the textrun's flags, and make all
2714 // vertical Mongolian/Phags-pa use sideways-right.
2715 orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
2718 uint32_t runStart = 0;
2719 AutoTArray<TextRange, 3> fontRanges;
2720 ComputeRanges(fontRanges, aString, aLength, aRunScript, orientation);
2721 uint32_t numRanges = fontRanges.Length();
2722 bool missingChars = false;
2723 bool isCJK = gfxTextRun::IsCJKScript(aRunScript);
2725 for (uint32_t r = 0; r < numRanges; r++) {
2726 const TextRange& range = fontRanges[r];
2727 uint32_t matchedLength = range.Length();
2728 RefPtr<gfxFont> matchedFont = range.font;
2729 // create the glyph run for this range
2730 if (matchedFont && mStyle.noFallbackVariantFeatures) {
2731 // common case - just do glyph layout and record the
2732 // resulting positioned glyphs
2733 aTextRun->AddGlyphRun(matchedFont, range.matchType, aOffset + runStart,
2734 (matchedLength > 0), range.orientation, isCJK);
2735 if (!matchedFont->SplitAndInitTextRun(
2736 aDrawTarget, aTextRun, aString + runStart, aOffset + runStart,
2737 matchedLength, aRunScript, mLanguage, range.orientation)) {
2738 // glyph layout failed! treat as missing glyphs
2739 matchedFont = nullptr;
2741 } else if (matchedFont) {
2742 // shape with some variant feature that requires fallback handling
2743 bool petiteToSmallCaps = false;
2744 bool syntheticLower = false;
2745 bool syntheticUpper = false;
2747 if (mStyle.variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL &&
2748 mStyle.useSyntheticPosition &&
2749 (aTextRun->GetShapingState() ==
2750 gfxTextRun::eShapingState_ForceFallbackFeature ||
2751 !matchedFont->SupportsSubSuperscript(mStyle.variantSubSuper, aString,
2752 aLength, aRunScript))) {
2753 // fallback for subscript/superscript variant glyphs
2755 // if the feature was already used, abort and force
2756 // fallback across the entire textrun
2757 gfxTextRun::ShapingState ss = aTextRun->GetShapingState();
2759 if (ss == gfxTextRun::eShapingState_Normal) {
2760 aTextRun->SetShapingState(
2761 gfxTextRun::eShapingState_ShapingWithFallback);
2762 } else if (ss == gfxTextRun::eShapingState_ShapingWithFeature) {
2763 aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted);
2764 return;
2767 RefPtr<gfxFont> subSuperFont = matchedFont->GetSubSuperscriptFont(
2768 aTextRun->GetAppUnitsPerDevUnit());
2769 aTextRun->AddGlyphRun(subSuperFont, range.matchType, aOffset + runStart,
2770 (matchedLength > 0), range.orientation, isCJK);
2771 if (!subSuperFont->SplitAndInitTextRun(
2772 aDrawTarget, aTextRun, aString + runStart, aOffset + runStart,
2773 matchedLength, aRunScript, mLanguage, range.orientation)) {
2774 // glyph layout failed! treat as missing glyphs
2775 matchedFont = nullptr;
2777 } else if (mStyle.variantCaps != NS_FONT_VARIANT_CAPS_NORMAL &&
2778 mStyle.allowSyntheticSmallCaps &&
2779 !matchedFont->SupportsVariantCaps(
2780 aRunScript, mStyle.variantCaps, petiteToSmallCaps,
2781 syntheticLower, syntheticUpper)) {
2782 // fallback for small-caps variant glyphs
2783 if (!matchedFont->InitFakeSmallCapsRun(
2784 mPresContext, aDrawTarget, aTextRun, aString + runStart,
2785 aOffset + runStart, matchedLength, range.matchType,
2786 range.orientation, aRunScript,
2787 mExplicitLanguage ? mLanguage.get() : nullptr, syntheticLower,
2788 syntheticUpper)) {
2789 matchedFont = nullptr;
2791 } else {
2792 // shape normally with variant feature enabled
2793 gfxTextRun::ShapingState ss = aTextRun->GetShapingState();
2795 // adjust the shaping state if necessary
2796 if (ss == gfxTextRun::eShapingState_Normal) {
2797 aTextRun->SetShapingState(
2798 gfxTextRun::eShapingState_ShapingWithFeature);
2799 } else if (ss == gfxTextRun::eShapingState_ShapingWithFallback) {
2800 // already have shaping results using fallback, need to redo
2801 aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted);
2802 return;
2805 // do glyph layout and record the resulting positioned glyphs
2806 aTextRun->AddGlyphRun(matchedFont, range.matchType, aOffset + runStart,
2807 (matchedLength > 0), range.orientation, isCJK);
2808 if (!matchedFont->SplitAndInitTextRun(
2809 aDrawTarget, aTextRun, aString + runStart, aOffset + runStart,
2810 matchedLength, aRunScript, mLanguage, range.orientation)) {
2811 // glyph layout failed! treat as missing glyphs
2812 matchedFont = nullptr;
2815 } else {
2816 aTextRun->AddGlyphRun(mainFont, FontMatchType::Kind::kFontGroup,
2817 aOffset + runStart, (matchedLength > 0),
2818 range.orientation, isCJK);
2821 if (!matchedFont) {
2822 // We need to set cluster boundaries (and mark spaces) so that
2823 // surrogate pairs, combining characters, etc behave properly,
2824 // even if we don't have glyphs for them
2825 aTextRun->SetupClusterBoundaries(aOffset + runStart, aString + runStart,
2826 matchedLength);
2828 // various "missing" characters may need special handling,
2829 // so we check for them here
2830 uint32_t runLimit = runStart + matchedLength;
2831 for (uint32_t index = runStart; index < runLimit; index++) {
2832 T ch = aString[index];
2834 // tab and newline are not to be displayed as hexboxes,
2835 // but do need to be recorded in the textrun
2836 if (ch == '\n') {
2837 aTextRun->SetIsNewline(aOffset + index);
2838 continue;
2840 if (ch == '\t') {
2841 aTextRun->SetIsTab(aOffset + index);
2842 continue;
2845 // for 16-bit textruns only, check for surrogate pairs and
2846 // special Unicode spaces; omit these checks in 8-bit runs
2847 if constexpr (sizeof(T) == sizeof(char16_t)) {
2848 if (index + 1 < aLength &&
2849 NS_IS_SURROGATE_PAIR(ch, aString[index + 1])) {
2850 uint32_t usv = SURROGATE_TO_UCS4(ch, aString[index + 1]);
2851 aTextRun->SetMissingGlyph(aOffset + index, usv, mainFont);
2852 index++;
2853 if (!mSkipDrawing && !IsPUA(usv)) {
2854 missingChars = true;
2856 continue;
2859 // check if this is a known Unicode whitespace character that
2860 // we can render using the space glyph with a custom width
2861 gfxFloat wid = mainFont->SynthesizeSpaceWidth(ch);
2862 if (wid >= 0.0) {
2863 nscoord advance =
2864 aTextRun->GetAppUnitsPerDevUnit() * floor(wid + 0.5);
2865 if (gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance)) {
2866 aTextRun->GetCharacterGlyphs()[aOffset + index].SetSimpleGlyph(
2867 advance, mainFont->GetSpaceGlyph());
2868 } else {
2869 gfxTextRun::DetailedGlyph detailedGlyph;
2870 detailedGlyph.mGlyphID = mainFont->GetSpaceGlyph();
2871 detailedGlyph.mAdvance = advance;
2872 aTextRun->SetDetailedGlyphs(aOffset + index, 1, &detailedGlyph);
2874 continue;
2878 if (IsInvalidChar(ch)) {
2879 // invalid chars are left as zero-width/invisible
2880 continue;
2883 // record char code so we can draw a box with the Unicode value
2884 aTextRun->SetMissingGlyph(aOffset + index, ch, mainFont);
2885 if (!mSkipDrawing && !IsPUA(ch)) {
2886 missingChars = true;
2891 runStart += matchedLength;
2894 if (aMFR && missingChars) {
2895 aMFR->RecordScript(aRunScript);
2899 gfxTextRun* gfxFontGroup::GetEllipsisTextRun(
2900 int32_t aAppUnitsPerDevPixel, gfx::ShapedTextFlags aFlags,
2901 LazyReferenceDrawTargetGetter& aRefDrawTargetGetter) {
2902 MOZ_ASSERT(!(aFlags & ~ShapedTextFlags::TEXT_ORIENT_MASK),
2903 "flags here should only be used to specify orientation");
2904 if (mCachedEllipsisTextRun &&
2905 (mCachedEllipsisTextRun->GetFlags() &
2906 ShapedTextFlags::TEXT_ORIENT_MASK) == aFlags &&
2907 mCachedEllipsisTextRun->GetAppUnitsPerDevUnit() == aAppUnitsPerDevPixel) {
2908 return mCachedEllipsisTextRun.get();
2911 // Use a Unicode ellipsis if the font supports it,
2912 // otherwise use three ASCII periods as fallback.
2913 RefPtr<gfxFont> firstFont = GetFirstValidFont();
2914 nsString ellipsis =
2915 firstFont->HasCharacter(kEllipsisChar[0])
2916 ? nsDependentString(kEllipsisChar, ArrayLength(kEllipsisChar) - 1)
2917 : nsDependentString(kASCIIPeriodsChar,
2918 ArrayLength(kASCIIPeriodsChar) - 1);
2920 RefPtr<DrawTarget> refDT = aRefDrawTargetGetter.GetRefDrawTarget();
2921 Parameters params = {refDT, nullptr, nullptr,
2922 nullptr, 0, aAppUnitsPerDevPixel};
2923 mCachedEllipsisTextRun =
2924 MakeTextRun(ellipsis.BeginReading(), ellipsis.Length(), &params, aFlags,
2925 nsTextFrameUtils::Flags(), nullptr);
2926 if (!mCachedEllipsisTextRun) {
2927 return nullptr;
2929 // don't let the presence of a cached ellipsis textrun prolong the
2930 // fontgroup's life
2931 mCachedEllipsisTextRun->ReleaseFontGroup();
2932 return mCachedEllipsisTextRun.get();
2935 already_AddRefed<gfxFont> gfxFontGroup::FindFallbackFaceForChar(
2936 gfxFontFamily* aFamily, uint32_t aCh, uint32_t aNextCh,
2937 eFontPresentation aPresentation) {
2938 GlobalFontMatch data(aCh, aNextCh, mStyle, aPresentation);
2939 aFamily->SearchAllFontsForChar(&data);
2940 gfxFontEntry* fe = data.mBestMatch;
2941 if (!fe) {
2942 return nullptr;
2944 return fe->FindOrMakeFont(&mStyle);
2947 already_AddRefed<gfxFont> gfxFontGroup::FindFallbackFaceForChar(
2948 fontlist::Family* aFamily, uint32_t aCh, uint32_t aNextCh,
2949 eFontPresentation aPresentation) {
2950 auto* pfl = gfxPlatformFontList::PlatformFontList();
2951 auto* list = pfl->SharedFontList();
2953 // If async fallback is enabled, and the family isn't fully initialized yet,
2954 // just start the async cmap loading and return.
2955 if (!aFamily->IsFullyInitialized() &&
2956 StaticPrefs::gfx_font_rendering_fallback_async() &&
2957 !XRE_IsParentProcess()) {
2958 pfl->StartCmapLoadingFromFamily(aFamily - list->Families());
2959 return nullptr;
2962 GlobalFontMatch data(aCh, aNextCh, mStyle, aPresentation);
2963 aFamily->SearchAllFontsForChar(list, &data);
2964 gfxFontEntry* fe = data.mBestMatch;
2965 if (!fe) {
2966 return nullptr;
2968 return fe->FindOrMakeFont(&mStyle);
2971 already_AddRefed<gfxFont> gfxFontGroup::FindFallbackFaceForChar(
2972 const FamilyFace& aFamily, uint32_t aCh, uint32_t aNextCh,
2973 eFontPresentation aPresentation) {
2974 if (aFamily.IsSharedFamily()) {
2975 return FindFallbackFaceForChar(aFamily.SharedFamily(), aCh, aNextCh,
2976 aPresentation);
2978 return FindFallbackFaceForChar(aFamily.OwnedFamily(), aCh, aNextCh,
2979 aPresentation);
2982 gfxFloat gfxFontGroup::GetUnderlineOffset() {
2983 if (mUnderlineOffset == UNDERLINE_OFFSET_NOT_SET) {
2984 // if the fontlist contains a bad underline font, make the underline
2985 // offset the min of the first valid font and bad font underline offsets
2986 uint32_t len = mFonts.Length();
2987 for (uint32_t i = 0; i < len; i++) {
2988 FamilyFace& ff = mFonts[i];
2989 gfxFontEntry* fe = ff.FontEntry();
2990 if (!fe) {
2991 continue;
2993 if (!fe->mIsUserFontContainer && !fe->IsUserFont() &&
2994 ((ff.IsSharedFamily() && ff.SharedFamily() &&
2995 ff.SharedFamily()->IsBadUnderlineFamily()) ||
2996 (!ff.IsSharedFamily() && ff.OwnedFamily() &&
2997 ff.OwnedFamily()->IsBadUnderlineFamily()))) {
2998 RefPtr<gfxFont> font = GetFontAt(i);
2999 if (!font) {
3000 continue;
3002 gfxFloat bad =
3003 font->GetMetrics(nsFontMetrics::eHorizontal).underlineOffset;
3004 RefPtr<gfxFont> firstValidFont = GetFirstValidFont();
3005 gfxFloat first = firstValidFont->GetMetrics(nsFontMetrics::eHorizontal)
3006 .underlineOffset;
3007 mUnderlineOffset = std::min(first, bad);
3008 return mUnderlineOffset;
3012 // no bad underline fonts, use the first valid font's metric
3013 RefPtr<gfxFont> firstValidFont = GetFirstValidFont();
3014 mUnderlineOffset =
3015 firstValidFont->GetMetrics(nsFontMetrics::eHorizontal).underlineOffset;
3018 return mUnderlineOffset;
3021 #define NARROW_NO_BREAK_SPACE 0x202fu
3023 already_AddRefed<gfxFont> gfxFontGroup::FindFontForChar(
3024 uint32_t aCh, uint32_t aPrevCh, uint32_t aNextCh, Script aRunScript,
3025 gfxFont* aPrevMatchedFont, FontMatchType* aMatchType) {
3026 // If the char is a cluster extender, we want to use the same font as the
3027 // preceding character if possible. This is preferable to using the font
3028 // group because it avoids breaks in shaping within a cluster.
3029 if (aPrevMatchedFont && IsClusterExtender(aCh)) {
3030 if (aPrevMatchedFont->HasCharacter(aCh) || IsDefaultIgnorable(aCh)) {
3031 return do_AddRef(aPrevMatchedFont);
3033 // Check if this char and preceding char can compose; if so, is the
3034 // combination supported by the current font.
3035 uint32_t composed = intl::String::ComposePairNFC(aPrevCh, aCh);
3036 if (composed > 0 && aPrevMatchedFont->HasCharacter(composed)) {
3037 return do_AddRef(aPrevMatchedFont);
3041 // Special cases for NNBSP (as used in Mongolian):
3042 if (aCh == NARROW_NO_BREAK_SPACE) {
3043 // If there is no preceding character, try the font that we'd use
3044 // for the next char (unless it's just another NNBSP; we don't try
3045 // to look ahead through a whole run of them).
3046 if (!aPrevCh && aNextCh && aNextCh != NARROW_NO_BREAK_SPACE) {
3047 RefPtr<gfxFont> nextFont = FindFontForChar(aNextCh, 0, 0, aRunScript,
3048 aPrevMatchedFont, aMatchType);
3049 if (nextFont && nextFont->HasCharacter(aCh)) {
3050 return nextFont.forget();
3053 // Otherwise, treat NNBSP like a cluster extender (as above) and try
3054 // to continue the preceding font run.
3055 if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) {
3056 return do_AddRef(aPrevMatchedFont);
3060 // To optimize common cases, try the first font in the font-group
3061 // before going into the more detailed checks below
3062 uint32_t fontListLength = mFonts.Length();
3063 uint32_t nextIndex = 0;
3064 bool isJoinControl = gfxFontUtils::IsJoinControl(aCh);
3065 bool wasJoinCauser = gfxFontUtils::IsJoinCauser(aPrevCh);
3066 bool isVarSelector = gfxFontUtils::IsVarSelector(aCh);
3067 bool nextIsVarSelector = gfxFontUtils::IsVarSelector(aNextCh);
3069 // For Unicode hyphens, if not supported in the font then we'll try for
3070 // the ASCII hyphen-minus as a fallback.
3071 // Similarly, for NBSP we try normal <space> as a fallback.
3072 uint32_t fallbackChar = (aCh == 0x2010 || aCh == 0x2011) ? '-'
3073 : (aCh == 0x00A0) ? ' '
3074 : 0;
3076 // Whether we've seen a font that is currently loading a resource that may
3077 // provide this character (so we should not start a new load).
3078 bool loading = false;
3080 // Do we need to explicitly look for a font that does or does not provide a
3081 // color glyph for the given character?
3082 // For characters with no `EMOJI` property, we'll use whatever the family
3083 // list calls for; but if it's a potential emoji codepoint, we need to check
3084 // if there's a variation selector specifically asking for Text-style or
3085 // Emoji-style rendering and look for a suitable font.
3086 eFontPresentation presentation = eFontPresentation::Any;
3087 EmojiPresentation emojiPresentation = GetEmojiPresentation(aCh);
3088 if (emojiPresentation != TextOnly) {
3089 // Default presentation from the font-variant-emoji property.
3090 presentation = mEmojiPresentation;
3091 // If the prefer-emoji selector is present, or if it's a default-emoji
3092 // char and the prefer-text selector is NOT present, or if there's a
3093 // skin-tone modifier, we specifically look for a font with a color
3094 // glyph.
3095 // If the prefer-text selector is present, we specifically look for a
3096 // font that will provide a monochrome glyph.
3097 // Otherwise, we'll accept either color or monochrome font-family
3098 // entries, so that a color font can be explicitly applied via font-
3099 // family even to characters that are not inherently emoji-style.
3100 if (aNextCh == kVariationSelector16 ||
3101 (aNextCh >= kEmojiSkinToneFirst && aNextCh <= kEmojiSkinToneLast) ||
3102 gfxFontUtils::IsEmojiFlagAndTag(aCh, aNextCh)) {
3103 // Emoji presentation is explicitly requested by a variation selector
3104 // or the presence of a skin-tone codepoint.
3105 presentation = eFontPresentation::EmojiExplicit;
3106 } else if (emojiPresentation == EmojiPresentation::EmojiDefault &&
3107 aNextCh != kVariationSelector15) {
3108 // Emoji presentation is the default for this Unicode character. but we
3109 // will allow an explicitly-specified webfont to apply to it,
3110 // regardless of its glyph type.
3111 presentation = eFontPresentation::EmojiDefault;
3112 } else if (aNextCh == kVariationSelector15) {
3113 // Text presentation is explicitly requested.
3114 presentation = eFontPresentation::Text;
3118 if (!isJoinControl && !wasJoinCauser && !isVarSelector &&
3119 !nextIsVarSelector && presentation == eFontPresentation::Any) {
3120 RefPtr<gfxFont> firstFont = GetFontAt(0, aCh, &loading);
3121 if (firstFont) {
3122 if (firstFont->HasCharacter(aCh) ||
3123 (fallbackChar && firstFont->HasCharacter(fallbackChar))) {
3124 *aMatchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()};
3125 return firstFont.forget();
3128 RefPtr<gfxFont> font;
3129 if (mFonts[0].CheckForFallbackFaces()) {
3130 font = FindFallbackFaceForChar(mFonts[0], aCh, aNextCh, presentation);
3131 } else if (!firstFont->GetFontEntry()->IsUserFont()) {
3132 // For platform fonts (but not userfonts), we may need to do
3133 // fallback within the family to handle cases where some faces
3134 // such as Italic or Black have reduced character sets compared
3135 // to the family's Regular face.
3136 font = FindFallbackFaceForChar(mFonts[0], aCh, aNextCh, presentation);
3138 if (font) {
3139 *aMatchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()};
3140 return font.forget();
3142 } else {
3143 if (fontListLength > 0) {
3144 loading = loading || mFonts[0].IsLoadingFor(aCh);
3148 // we don't need to check the first font again below
3149 ++nextIndex;
3152 if (aPrevMatchedFont) {
3153 // Don't switch fonts for control characters, regardless of
3154 // whether they are present in the current font, as they won't
3155 // actually be rendered (see bug 716229)
3156 if (isJoinControl ||
3157 GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_CONTROL) {
3158 return do_AddRef(aPrevMatchedFont);
3161 // if previous character was a join-causer (ZWJ),
3162 // use the same font as the previous range if we can
3163 if (wasJoinCauser) {
3164 if (aPrevMatchedFont->HasCharacter(aCh)) {
3165 return do_AddRef(aPrevMatchedFont);
3170 // If this character is a variation selector or default-ignorable, use the
3171 // previous font regardless of whether it supports the codepoint or not.
3172 // (We don't want to unnecessarily split glyph runs, and the character will
3173 // not be visibly rendered.)
3174 if (isVarSelector || IsDefaultIgnorable(aCh)) {
3175 return do_AddRef(aPrevMatchedFont);
3178 // Used to remember the first "candidate" font that would provide a fallback
3179 // text-style rendering if no color glyph can be found.
3180 // If we decide NOT to return this font, we must AddRef/Release it to ensure
3181 // that it goes into the global font cache as a candidate for deletion.
3182 // This is done for us by CheckCandidate, but any code path that returns
3183 // WITHOUT calling CheckCandidate needs to handle it explicitly.
3184 RefPtr<gfxFont> candidateFont;
3185 FontMatchType candidateMatchType;
3187 // Handle a candidate font that could support the character, returning true
3188 // if we should go ahead and return |f|, false to continue searching.
3189 // If there is already a saved candidate font, and the new candidate is
3190 // accepted, we AddRef/Release the existing candidate so it won't leak.
3191 auto CheckCandidate = [&](gfxFont* f, FontMatchType t) -> bool {
3192 // If no preference, then just accept the font.
3193 if (presentation == eFontPresentation::Any ||
3194 (presentation == eFontPresentation::EmojiDefault &&
3195 f->GetFontEntry()->IsUserFont())) {
3196 *aMatchType = t;
3197 return true;
3199 // Does the candidate font provide a color glyph for the current character?
3200 bool hasColorGlyph = f->HasColorGlyphFor(aCh, aNextCh);
3201 // If the provided glyph matches the preference, accept the font.
3202 if (hasColorGlyph == PrefersColor(presentation)) {
3203 *aMatchType = t;
3204 return true;
3206 // If the character was a TextDefault char, but the next char is VS16,
3207 // and the font is a COLR font that supports both these codepoints, then
3208 // we'll assume it knows what it is doing (eg Twemoji Mozilla keycap
3209 // sequences).
3210 // TODO: reconsider all this as part of any fix for bug 543200.
3211 if (aNextCh == kVariationSelector16 && emojiPresentation == TextDefault &&
3212 f->HasCharacter(aNextCh) && f->GetFontEntry()->TryGetColorGlyphs()) {
3213 return true;
3215 // Otherwise, remember the first potential fallback, but keep searching.
3216 if (!candidateFont) {
3217 candidateFont = f;
3218 candidateMatchType = t;
3220 return false;
3223 // 1. check remaining fonts in the font group
3224 for (uint32_t i = nextIndex; i < fontListLength; i++) {
3225 FamilyFace& ff = mFonts[i];
3226 if (ff.IsInvalid() || ff.IsLoading()) {
3227 if (ff.IsLoadingFor(aCh)) {
3228 loading = true;
3230 continue;
3233 RefPtr<gfxFont> font = ff.Font();
3234 if (font) {
3235 // if available, use already-made gfxFont and check for character
3236 if (font->HasCharacter(aCh) ||
3237 (fallbackChar && font->HasCharacter(fallbackChar))) {
3238 if (CheckCandidate(font,
3239 {FontMatchType::Kind::kFontGroup, ff.Generic()})) {
3240 return font.forget();
3243 } else {
3244 // don't have a gfxFont yet, test charmap before instantiating
3245 gfxFontEntry* fe = ff.FontEntry();
3246 if (fe && fe->mIsUserFontContainer) {
3247 // for userfonts, need to test both the unicode range map and
3248 // the cmap of the platform font entry
3249 gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe);
3251 // never match a character outside the defined unicode range
3252 if (!ufe->CharacterInUnicodeRange(aCh)) {
3253 continue;
3256 // Load if not already loaded, unless we've already seen an in-
3257 // progress load that is expected to satisfy this request.
3258 if (!loading &&
3259 ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED) {
3260 ufe->Load();
3261 ff.CheckState(mSkipDrawing);
3264 if (ff.IsLoading()) {
3265 loading = true;
3268 gfxFontEntry* pfe = ufe->GetPlatformFontEntry();
3269 if (pfe && (pfe->HasCharacter(aCh) ||
3270 (fallbackChar && pfe->HasCharacter(fallbackChar)))) {
3271 font = GetFontAt(i, aCh, &loading);
3272 if (font) {
3273 if (CheckCandidate(font, {FontMatchType::Kind::kFontGroup,
3274 mFonts[i].Generic()})) {
3275 return font.forget();
3279 } else if (fe && (fe->HasCharacter(aCh) ||
3280 (fallbackChar && fe->HasCharacter(fallbackChar)))) {
3281 // for normal platform fonts, after checking the cmap
3282 // build the font via GetFontAt
3283 font = GetFontAt(i, aCh, &loading);
3284 if (font) {
3285 if (CheckCandidate(font, {FontMatchType::Kind::kFontGroup,
3286 mFonts[i].Generic()})) {
3287 return font.forget();
3293 // check other family faces if needed
3294 if (ff.CheckForFallbackFaces()) {
3295 #ifdef DEBUG
3296 if (i > 0) {
3297 fontlist::FontList* list =
3298 gfxPlatformFontList::PlatformFontList()->SharedFontList();
3299 nsCString s1 = mFonts[i - 1].IsSharedFamily()
3300 ? mFonts[i - 1].SharedFamily()->Key().AsString(list)
3301 : mFonts[i - 1].OwnedFamily()->Name();
3302 nsCString s2 = ff.IsSharedFamily()
3303 ? ff.SharedFamily()->Key().AsString(list)
3304 : ff.OwnedFamily()->Name();
3305 MOZ_ASSERT(!mFonts[i - 1].CheckForFallbackFaces() || !s1.Equals(s2),
3306 "should only do fallback once per font family");
3308 #endif
3309 font = FindFallbackFaceForChar(ff, aCh, aNextCh, presentation);
3310 if (font) {
3311 if (CheckCandidate(font,
3312 {FontMatchType::Kind::kFontGroup, ff.Generic()})) {
3313 return font.forget();
3316 } else {
3317 // For platform fonts, but not user fonts, consider intra-family
3318 // fallback to handle styles with reduced character sets (see
3319 // also above).
3320 gfxFontEntry* fe = ff.FontEntry();
3321 if (fe && !fe->mIsUserFontContainer && !fe->IsUserFont()) {
3322 font = FindFallbackFaceForChar(ff, aCh, aNextCh, presentation);
3323 if (font) {
3324 if (CheckCandidate(font,
3325 {FontMatchType::Kind::kFontGroup, ff.Generic()})) {
3326 return font.forget();
3333 if (fontListLength == 0) {
3334 RefPtr<gfxFont> defaultFont = GetDefaultFont();
3335 if (defaultFont->HasCharacter(aCh) ||
3336 (fallbackChar && defaultFont->HasCharacter(fallbackChar))) {
3337 if (CheckCandidate(defaultFont, FontMatchType::Kind::kFontGroup)) {
3338 return defaultFont.forget();
3343 // If character is in Private Use Area, or is unassigned in Unicode, don't do
3344 // matching against pref or system fonts. We only support such codepoints
3345 // when used with an explicitly-specified font, as they have no standard/
3346 // interoperable meaning.
3347 // Also don't attempt any fallback for control characters or noncharacters,
3348 // where we won't be rendering a glyph anyhow, or for codepoints where global
3349 // fallback has already noted a failure.
3350 FontVisibility level =
3351 mPresContext ? mPresContext->GetFontVisibility() : FontVisibility::User;
3352 auto* pfl = gfxPlatformFontList::PlatformFontList();
3353 if (pfl->SkipFontFallbackForChar(level, aCh) ||
3354 (!StaticPrefs::gfx_font_rendering_fallback_unassigned_chars() &&
3355 GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED)) {
3356 if (candidateFont) {
3357 *aMatchType = candidateMatchType;
3359 return candidateFont.forget();
3362 // 2. search pref fonts
3363 RefPtr<gfxFont> font = WhichPrefFontSupportsChar(aCh, aNextCh, presentation);
3364 if (font) {
3365 if (PrefersColor(presentation) && pfl->EmojiPrefHasUserValue()) {
3366 // For emoji, always accept the font from preferences if it's explicitly
3367 // user-set, even if it isn't actually a color-emoji font, as some users
3368 // may want to set their emoji font preference to a monochrome font like
3369 // Symbola.
3370 // So a user-provided font.name-list.emoji preference takes precedence
3371 // over the Unicode presentation style here.
3372 RefPtr<gfxFont> autoRefDeref(candidateFont);
3373 *aMatchType = FontMatchType::Kind::kPrefsFallback;
3374 return font.forget();
3376 if (CheckCandidate(font, FontMatchType::Kind::kPrefsFallback)) {
3377 return font.forget();
3381 // For fallback searches, we don't want to use a color-emoji font unless
3382 // emoji-style presentation is specifically required, so we map Any to
3383 // Text here.
3384 if (presentation == eFontPresentation::Any) {
3385 presentation = eFontPresentation::Text;
3388 // 3. use fallback fonts
3389 // -- before searching for something else check the font used for the
3390 // previous character
3391 if (aPrevMatchedFont &&
3392 (aPrevMatchedFont->HasCharacter(aCh) ||
3393 (fallbackChar && aPrevMatchedFont->HasCharacter(fallbackChar)))) {
3394 if (CheckCandidate(aPrevMatchedFont,
3395 FontMatchType::Kind::kSystemFallback)) {
3396 return do_AddRef(aPrevMatchedFont);
3400 // for known "space" characters, don't do a full system-fallback search;
3401 // we'll synthesize appropriate-width spaces instead of missing-glyph boxes
3402 font = GetFirstValidFont();
3403 if (GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR &&
3404 font->SynthesizeSpaceWidth(aCh) >= 0.0) {
3405 return nullptr;
3408 // -- otherwise look for other stuff
3409 font = WhichSystemFontSupportsChar(aCh, aNextCh, aRunScript, presentation);
3410 if (font) {
3411 if (CheckCandidate(font, FontMatchType::Kind::kSystemFallback)) {
3412 return font.forget();
3415 if (candidateFont) {
3416 *aMatchType = candidateMatchType;
3418 return candidateFont.forget();
3421 template <typename T>
3422 void gfxFontGroup::ComputeRanges(nsTArray<TextRange>& aRanges, const T* aString,
3423 uint32_t aLength, Script aRunScript,
3424 gfx::ShapedTextFlags aOrientation) {
3425 NS_ASSERTION(aRanges.Length() == 0, "aRanges must be initially empty");
3426 NS_ASSERTION(aLength > 0, "don't call ComputeRanges for zero-length text");
3428 uint32_t prevCh = 0;
3429 uint32_t nextCh = aString[0];
3430 if constexpr (sizeof(T) == sizeof(char16_t)) {
3431 if (aLength > 1 && NS_IS_SURROGATE_PAIR(nextCh, aString[1])) {
3432 nextCh = SURROGATE_TO_UCS4(nextCh, aString[1]);
3435 int32_t lastRangeIndex = -1;
3437 // initialize prevFont to the group's primary font, so that this will be
3438 // used for string-initial control chars, etc rather than risk hitting font
3439 // fallback for these (bug 716229)
3440 StyleGenericFontFamily generic = StyleGenericFontFamily::None;
3441 RefPtr<gfxFont> prevFont = GetFirstValidFont(' ', &generic);
3443 // if we use the initial value of prevFont, we treat this as a match from
3444 // the font group; fixes bug 978313
3445 FontMatchType matchType = {FontMatchType::Kind::kFontGroup, generic};
3447 for (uint32_t i = 0; i < aLength; i++) {
3448 const uint32_t origI = i; // save off in case we increase for surrogate
3450 // set up current ch
3451 uint32_t ch = nextCh;
3453 // Get next char (if any) so that FindFontForChar can look ahead
3454 // for a possible variation selector.
3456 if constexpr (sizeof(T) == sizeof(char16_t)) {
3457 // In 16-bit case only, check for surrogate pairs.
3458 if (ch > 0xffffu) {
3459 i++;
3461 if (i < aLength - 1) {
3462 nextCh = aString[i + 1];
3463 if (i + 2 < aLength && NS_IS_SURROGATE_PAIR(nextCh, aString[i + 2])) {
3464 nextCh = SURROGATE_TO_UCS4(nextCh, aString[i + 2]);
3466 } else {
3467 nextCh = 0;
3469 } else {
3470 // 8-bit case is trivial.
3471 nextCh = i < aLength - 1 ? aString[i + 1] : 0;
3474 RefPtr<gfxFont> font;
3476 // Find the font for this char; but try to avoid calling the expensive
3477 // FindFontForChar method for the most common case, where the first
3478 // font in the list supports the current char, and it is not one of
3479 // the special cases where FindFontForChar will attempt to propagate
3480 // the font selected for an adjacent character.
3481 if ((font = GetFontAt(0, ch)) != nullptr && font->HasCharacter(ch) &&
3482 (sizeof(T) == sizeof(uint8_t) ||
3483 (!IsClusterExtender(ch) && ch != NARROW_NO_BREAK_SPACE &&
3484 !gfxFontUtils::IsJoinControl(ch) &&
3485 !gfxFontUtils::IsJoinCauser(prevCh) &&
3486 !gfxFontUtils::IsVarSelector(ch) &&
3487 GetEmojiPresentation(ch) == TextOnly))) {
3488 matchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()};
3489 } else {
3490 font =
3491 FindFontForChar(ch, prevCh, nextCh, aRunScript, prevFont, &matchType);
3494 #ifndef RELEASE_OR_BETA
3495 if (MOZ_UNLIKELY(mTextPerf)) {
3496 if (matchType.kind == FontMatchType::Kind::kPrefsFallback) {
3497 mTextPerf->current.fallbackPrefs++;
3498 } else if (matchType.kind == FontMatchType::Kind::kSystemFallback) {
3499 mTextPerf->current.fallbackSystem++;
3502 #endif
3504 prevCh = ch;
3506 ShapedTextFlags orient = aOrientation;
3507 if (aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
3508 // For CSS text-orientation:mixed, we need to resolve orientation
3509 // on a per-character basis using the UTR50 orientation property.
3510 switch (GetVerticalOrientation(ch)) {
3511 case VERTICAL_ORIENTATION_U:
3512 case VERTICAL_ORIENTATION_Tu:
3513 orient = ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
3514 break;
3515 case VERTICAL_ORIENTATION_Tr: {
3516 // We check for a vertical presentation form first as that's
3517 // likely to be cheaper than inspecting lookups to see if the
3518 // 'vert' feature is going to handle this character, and if the
3519 // presentation form is available then it will be used as
3520 // fallback if needed, so it's OK if the feature is missing.
3522 // Because "common" CJK punctuation characters in isolation will be
3523 // resolved to Bopomofo script (as the first script listed in their
3524 // ScriptExtensions property), but this is not always well supported
3525 // by fonts' OpenType tables, we also try Han script; harfbuzz will
3526 // apply a 'vert' feature from any available script (see
3527 // https://github.com/harfbuzz/harfbuzz/issues/63) when shaping,
3528 // so this is OK. It's not quite as general as what harfbuzz does
3529 // (it will find the feature in *any* script), but should be enough
3530 // for likely real-world examples.
3531 uint32_t v = gfxHarfBuzzShaper::GetVerticalPresentationForm(ch);
3532 const uint32_t kVert = HB_TAG('v', 'e', 'r', 't');
3533 orient = (!font || (v && font->HasCharacter(v)) ||
3534 font->FeatureWillHandleChar(aRunScript, kVert, ch) ||
3535 (aRunScript == Script::BOPOMOFO &&
3536 font->FeatureWillHandleChar(Script::HAN, kVert, ch)))
3537 ? ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
3538 : ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
3539 break;
3541 case VERTICAL_ORIENTATION_R:
3542 orient = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
3543 break;
3547 if (lastRangeIndex == -1) {
3548 // first char ==> make a new range
3549 aRanges.AppendElement(TextRange(0, 1, font, matchType, orient));
3550 lastRangeIndex++;
3551 prevFont = std::move(font);
3552 } else {
3553 // if font or orientation has changed, make a new range...
3554 // unless ch is a variation selector (bug 1248248)
3555 TextRange& prevRange = aRanges[lastRangeIndex];
3556 if (prevRange.font != font ||
3557 (prevRange.orientation != orient && !IsClusterExtender(ch))) {
3558 // close out the previous range
3559 prevRange.end = origI;
3560 aRanges.AppendElement(TextRange(origI, i + 1, font, matchType, orient));
3561 lastRangeIndex++;
3563 // update prevFont for the next match, *unless* we switched
3564 // fonts on a ZWJ, in which case propagating the changed font
3565 // is probably not a good idea (see bug 619511)
3566 if (sizeof(T) == sizeof(uint8_t) || !gfxFontUtils::IsJoinCauser(ch)) {
3567 prevFont = std::move(font);
3569 } else {
3570 prevRange.matchType |= matchType;
3575 aRanges[lastRangeIndex].end = aLength;
3577 #ifndef RELEASE_OR_BETA
3578 LogModule* log = mStyle.systemFont ? gfxPlatform::GetLog(eGfxLog_textrunui)
3579 : gfxPlatform::GetLog(eGfxLog_textrun);
3581 if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Debug))) {
3582 nsAutoCString lang;
3583 mLanguage->ToUTF8String(lang);
3584 auto defaultLanguageGeneric = GetDefaultGeneric(mLanguage);
3586 // collect the font matched for each range
3587 nsAutoCString fontMatches;
3588 for (size_t i = 0, i_end = aRanges.Length(); i < i_end; i++) {
3589 const TextRange& r = aRanges[i];
3590 nsAutoCString matchTypes;
3591 if (r.matchType.kind & FontMatchType::Kind::kFontGroup) {
3592 matchTypes.AppendLiteral("list");
3594 if (r.matchType.kind & FontMatchType::Kind::kPrefsFallback) {
3595 if (!matchTypes.IsEmpty()) {
3596 matchTypes.AppendLiteral(",");
3598 matchTypes.AppendLiteral("prefs");
3600 if (r.matchType.kind & FontMatchType::Kind::kSystemFallback) {
3601 if (!matchTypes.IsEmpty()) {
3602 matchTypes.AppendLiteral(",");
3604 matchTypes.AppendLiteral("sys");
3606 fontMatches.AppendPrintf(
3607 " [%u:%u] %.200s (%s)", r.start, r.end,
3608 (r.font.get() ? r.font->GetName().get() : "<null>"),
3609 matchTypes.get());
3611 MOZ_LOG(log, LogLevel::Debug,
3612 ("(%s-fontmatching) fontgroup: [%s] default: %s lang: %s script: %d"
3613 "%s\n",
3614 (mStyle.systemFont ? "textrunui" : "textrun"),
3615 FamilyListToString(mFamilyList).get(),
3616 (defaultLanguageGeneric == StyleGenericFontFamily::Serif
3617 ? "serif"
3618 : (defaultLanguageGeneric == StyleGenericFontFamily::SansSerif
3619 ? "sans-serif"
3620 : "none")),
3621 lang.get(), static_cast<int>(aRunScript), fontMatches.get()));
3623 #endif
3626 gfxUserFontSet* gfxFontGroup::GetUserFontSet() { return mUserFontSet; }
3628 void gfxFontGroup::SetUserFontSet(gfxUserFontSet* aUserFontSet) {
3629 if (aUserFontSet == mUserFontSet) {
3630 return;
3632 mUserFontSet = aUserFontSet;
3633 mCurrGeneration = GetGeneration() - 1;
3634 UpdateUserFonts();
3637 uint64_t gfxFontGroup::GetGeneration() {
3638 if (!mUserFontSet) return 0;
3639 return mUserFontSet->GetGeneration();
3642 uint64_t gfxFontGroup::GetRebuildGeneration() {
3643 if (!mUserFontSet) return 0;
3644 return mUserFontSet->GetRebuildGeneration();
3647 void gfxFontGroup::UpdateUserFonts() {
3648 if (mCurrGeneration < GetRebuildGeneration()) {
3649 // fonts in userfont set changed, need to redo the fontlist
3650 mFonts.Clear();
3651 ClearCachedData();
3652 BuildFontList();
3653 mCurrGeneration = GetGeneration();
3654 } else if (mCurrGeneration != GetGeneration()) {
3655 // load state change occurred, verify load state and validity of fonts
3656 ClearCachedData();
3658 uint32_t len = mFonts.Length();
3659 for (uint32_t i = 0; i < len; i++) {
3660 FamilyFace& ff = mFonts[i];
3661 if (ff.Font() || !ff.IsUserFontContainer()) {
3662 continue;
3664 ff.CheckState(mSkipDrawing);
3667 mCurrGeneration = GetGeneration();
3671 bool gfxFontGroup::ContainsUserFont(const gfxUserFontEntry* aUserFont) {
3672 UpdateUserFonts();
3673 // search through the fonts list for a specific user font
3674 uint32_t len = mFonts.Length();
3675 for (uint32_t i = 0; i < len; i++) {
3676 FamilyFace& ff = mFonts[i];
3677 if (ff.EqualsUserFont(aUserFont)) {
3678 return true;
3681 return false;
3684 already_AddRefed<gfxFont> gfxFontGroup::WhichPrefFontSupportsChar(
3685 uint32_t aCh, uint32_t aNextCh, eFontPresentation aPresentation) {
3686 eFontPrefLang charLang;
3687 gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
3689 if (PrefersColor(aPresentation)) {
3690 charLang = eFontPrefLang_Emoji;
3691 } else {
3692 // get the pref font list if it hasn't been set up already
3693 charLang = pfl->GetFontPrefLangFor(aCh);
3696 // if the last pref font was the first family in the pref list, no need to
3697 // recheck through a list of families
3698 if (mLastPrefFont && charLang == mLastPrefLang && mLastPrefFirstFont &&
3699 mLastPrefFont->HasCharacter(aCh)) {
3700 return do_AddRef(mLastPrefFont);
3703 // based on char lang and page lang, set up list of pref lang fonts to check
3704 eFontPrefLang prefLangs[kMaxLenPrefLangList];
3705 uint32_t i, numLangs = 0;
3707 pfl->GetLangPrefs(prefLangs, numLangs, charLang, mPageLang);
3709 for (i = 0; i < numLangs; i++) {
3710 eFontPrefLang currentLang = prefLangs[i];
3711 StyleGenericFontFamily generic =
3712 mFallbackGeneric != StyleGenericFontFamily::None
3713 ? mFallbackGeneric
3714 : pfl->GetDefaultGeneric(currentLang);
3715 gfxPlatformFontList::PrefFontList* families =
3716 pfl->GetPrefFontsLangGroup(mPresContext, generic, currentLang);
3717 NS_ASSERTION(families, "no pref font families found");
3719 // find the first pref font that includes the character
3720 uint32_t j, numPrefs;
3721 numPrefs = families->Length();
3722 for (j = 0; j < numPrefs; j++) {
3723 // look up the appropriate face
3724 FontFamily family = (*families)[j];
3725 if (family.IsNull()) {
3726 continue;
3729 // if a pref font is used, it's likely to be used again in the same text
3730 // run. the style doesn't change so the face lookup can be cached rather
3731 // than calling FindOrMakeFont repeatedly. speeds up FindFontForChar
3732 // lookup times for subsequent pref font lookups
3733 if (family == mLastPrefFamily && mLastPrefFont->HasCharacter(aCh)) {
3734 return do_AddRef(mLastPrefFont);
3737 gfxFontEntry* fe = nullptr;
3738 if (family.mShared) {
3739 fontlist::Family* fam = family.mShared;
3740 if (!fam->IsInitialized()) {
3741 Unused << pfl->InitializeFamily(fam);
3743 fontlist::Face* face =
3744 fam->FindFaceForStyle(pfl->SharedFontList(), mStyle);
3745 if (face) {
3746 fe = pfl->GetOrCreateFontEntry(face, fam);
3748 } else {
3749 fe = family.mUnshared->FindFontForStyle(mStyle);
3751 if (!fe) {
3752 continue;
3755 // if ch in cmap, create and return a gfxFont
3756 RefPtr<gfxFont> prefFont;
3757 if (fe->HasCharacter(aCh)) {
3758 prefFont = fe->FindOrMakeFont(&mStyle);
3759 if (!prefFont) {
3760 continue;
3762 if (aPresentation == eFontPresentation::EmojiExplicit &&
3763 !prefFont->HasColorGlyphFor(aCh, aNextCh)) {
3764 continue;
3768 // If the char was not available, see if we can fall back to an
3769 // alternative face in the same family.
3770 if (!prefFont) {
3771 prefFont = family.mShared
3772 ? FindFallbackFaceForChar(family.mShared, aCh, aNextCh,
3773 aPresentation)
3774 : FindFallbackFaceForChar(family.mUnshared, aCh, aNextCh,
3775 aPresentation);
3777 if (prefFont) {
3778 mLastPrefFamily = family;
3779 mLastPrefFont = prefFont;
3780 mLastPrefLang = charLang;
3781 mLastPrefFirstFont = (i == 0 && j == 0);
3782 return prefFont.forget();
3787 return nullptr;
3790 already_AddRefed<gfxFont> gfxFontGroup::WhichSystemFontSupportsChar(
3791 uint32_t aCh, uint32_t aNextCh, Script aRunScript,
3792 eFontPresentation aPresentation) {
3793 FontVisibility visibility;
3794 return gfxPlatformFontList::PlatformFontList()->SystemFindFontForChar(
3795 mPresContext, aCh, aNextCh, aRunScript, aPresentation, &mStyle,
3796 &visibility);
3799 gfxFont::Metrics gfxFontGroup::GetMetricsForCSSUnits(
3800 gfxFont::Orientation aOrientation) {
3801 bool isFirst;
3802 RefPtr<gfxFont> font = GetFirstValidFont(0x20, nullptr, &isFirst);
3803 auto metrics = font->GetMetrics(aOrientation);
3805 // If the font we used to get metrics was not the first in the list,
3806 // or if it doesn't support the ZERO character, check for the font that
3807 // does support ZERO and use its metrics for the 'ch' unit.
3808 if (!isFirst || !font->HasCharacter('0')) {
3809 RefPtr<gfxFont> zeroFont = GetFirstValidFont('0');
3810 if (zeroFont != font) {
3811 const auto& zeroMetrics = zeroFont->GetMetrics(aOrientation);
3812 metrics.zeroWidth = zeroMetrics.zeroWidth;
3816 // Likewise for the WATER ideograph character used as the basis for 'ic'.
3817 if (!isFirst || !font->HasCharacter(0x6C34)) {
3818 RefPtr<gfxFont> icFont = GetFirstValidFont(0x6C34);
3819 if (icFont != font) {
3820 const auto& icMetrics = icFont->GetMetrics(aOrientation);
3821 metrics.ideographicWidth = icMetrics.ideographicWidth;
3825 return metrics;
3828 void gfxMissingFontRecorder::Flush() {
3829 static bool mNotifiedFontsInitialized = false;
3830 static uint32_t mNotifiedFonts[gfxMissingFontRecorder::kNumScriptBitsWords];
3831 if (!mNotifiedFontsInitialized) {
3832 memset(&mNotifiedFonts, 0, sizeof(mNotifiedFonts));
3833 mNotifiedFontsInitialized = true;
3836 nsAutoString fontNeeded;
3837 for (uint32_t i = 0; i < kNumScriptBitsWords; ++i) {
3838 mMissingFonts[i] &= ~mNotifiedFonts[i];
3839 if (!mMissingFonts[i]) {
3840 continue;
3842 for (uint32_t j = 0; j < 32; ++j) {
3843 if (!(mMissingFonts[i] & (1 << j))) {
3844 continue;
3846 mNotifiedFonts[i] |= (1 << j);
3847 if (!fontNeeded.IsEmpty()) {
3848 fontNeeded.Append(char16_t(','));
3850 uint32_t sc = i * 32 + j;
3851 MOZ_ASSERT(sc < static_cast<uint32_t>(Script::NUM_SCRIPT_CODES),
3852 "how did we set the bit for an invalid script code?");
3853 uint32_t tag = GetScriptTagForCode(static_cast<Script>(sc));
3854 fontNeeded.Append(char16_t(tag >> 24));
3855 fontNeeded.Append(char16_t((tag >> 16) & 0xff));
3856 fontNeeded.Append(char16_t((tag >> 8) & 0xff));
3857 fontNeeded.Append(char16_t(tag & 0xff));
3859 mMissingFonts[i] = 0;
3861 if (!fontNeeded.IsEmpty()) {
3862 nsCOMPtr<nsIObserverService> service = GetObserverService();
3863 service->NotifyObservers(nullptr, "font-needed", fontNeeded.get());