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"
36 # include "gfxWindowsPlatform.h"
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};
49 # define DEBUG_TEXT_RUN_STORAGE_METRICS
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
;
64 void gfxTextRun::GlyphRunIterator::NextRun() {
66 if (mGlyphRun
== mTextRun
->mGlyphRuns
.begin()) {
72 MOZ_DIAGNOSTIC_ASSERT(mGlyphRun
!= mTextRun
->mGlyphRuns
.end());
74 if (mGlyphRun
== mTextRun
->mGlyphRuns
.end()) {
79 if (mGlyphRun
->mCharacterOffset
>= mEndOffset
) {
83 uint32_t glyphRunEndOffset
= mGlyphRun
== mTextRun
->mGlyphRuns
.end() - 1
84 ? mTextRun
->GetLength()
85 : (mGlyphRun
+ 1)->mCharacterOffset
;
86 if (glyphRunEndOffset
< mStartOffset
) {
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
);
111 bool gfxTextRun::NeedsGlyphExtents() const {
112 if (GetFlags() & gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX
) {
115 for (const auto& run
: mGlyphRuns
) {
116 if (run
.mFont
->GetFontEntry()->IsUserFont()) {
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
));
131 NS_WARNING("failed to allocate storage for text run!");
135 // Initialize the glyph storage (beyond aSize) to zero
136 memset(reinterpret_cast<char*>(storage
) + aSize
, 0,
137 aLength
* sizeof(CompressedGlyph
));
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
);
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
),
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();
173 tp
->current
.textrunConst
++;
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);
188 !!(aFlags2
& nsTextFrameUtils::Flags::DontSkipDrawingForPendingUserFonts
);
191 gfxTextRun::~gfxTextRun() {
192 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
193 AccountStorageForTextRun(this, -1);
196 // Make it easy to detect a dead text run
197 mFlags
= ~gfx::ShapedTextFlags();
198 mFlags2
= ~nsTextFrameUtils::Flags();
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
204 if (!mReleasedFontGroup
) {
205 #ifndef RELEASE_OR_BETA
206 gfxTextPerfMetrics
* tp
= mFontGroup
->GetTextPerfMetrics();
208 tp
->current
.textrunDestr
++;
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();
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.
255 changed
|= cg
->SetCanBreakBefore(canBreak
);
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");
269 const CompressedGlyph
* charGlyphs
= mCharacterGlyphs
;
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
290 if (i
== result
.mRange
.start
|| charGlyphs
[i
].IsClusterStart()) {
292 if (i
< aPartRange
.start
) {
294 } else if (i
< aPartRange
.end
) {
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) {
313 result
.mClipBeforePart
= result
.mClipAfterPart
= true;
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
319 result
.mClipBeforePart
= partClusterIndex
> 0;
320 // We need to clip after the part if any cluster is drawn after
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),
331 result
.mPartWidth
+= spacing
.mBefore
;
333 if (aPartRange
.end
== result
.mRange
.end
) {
334 aProvider
->GetSpacing(Range(aPartRange
.end
- 1, aPartRange
.end
),
336 result
.mPartWidth
+= spacing
.mAfter
;
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 {
352 for (auto i
= aRange
.start
; i
< aRange
.end
; ++i
) {
353 advance
+= GetAdvanceForGlyph(i
);
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
) {
366 aProvider
.GetSpacing(aRange
, aSpacing
);
369 // Check to see if we have spacing inside ligatures
371 const gfxTextRun::CompressedGlyph
* charGlyphs
=
372 aTextRun
->GetCharacterGlyphs();
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!");
380 i
- 1 <= aRange
.start
|| aSpacing
[i
- 1 - aRange
.start
].mAfter
== 0,
381 "After-spacing inside a ligature!");
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
)) {
393 if (!aSpacing
->AppendElements(aRange
.Length(), fallible
)) {
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
));
405 bool gfxTextRun::ShrinkToLigatureBoundaries(Range
* aRange
) const {
406 if (aRange
->start
>= aRange
->end
) {
410 const CompressedGlyph
* charGlyphs
= mCharacterGlyphs
;
411 bool adjusted
= false;
412 while (aRange
->start
< aRange
->end
&&
413 !charGlyphs
[aRange
->start
].IsLigatureGroupStart()) {
417 if (aRange
->end
< GetLength()) {
418 while (aRange
->end
> aRange
->start
&&
419 !charGlyphs
[aRange
->end
].IsLigatureGroupStart()) {
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
;
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
);
445 *aStart
= std::max(*aStart
, aOrigin
);
448 if (aLigature
->mClipAfterPart
) {
450 aOrigin
+ aTextRun
->GetDirection() * aLigature
->mPartWidth
;
451 if (aTextRun
->IsRightToLeft()) {
452 *aStart
= std::max(*aStart
, endEdge
);
454 *aEnd
= std::min(*aEnd
, endEdge
);
459 void gfxTextRun::DrawPartialLigature(gfxFont
* aFont
, Range aRange
,
461 const PropertyProvider
* aProvider
,
462 TextRunDrawParams
& aParams
,
463 gfx::ShapedTextFlags aOrientation
) const {
464 if (aRange
.start
>= aRange
.end
) {
468 // Draw partial ligature. We hack this by clipping the ligature.
469 LigatureData data
= ComputeLigatureData(aRange
, aProvider
);
470 gfxRect clipExtents
= aParams
.context
->GetClipExtents();
472 if (aParams
.isVerticalRun
) {
473 start
= clipExtents
.Y() * mAppUnitsPerDevUnit
;
474 end
= clipExtents
.YMost() * mAppUnitsPerDevUnit
;
475 ClipPartialLigature(this, &start
, &end
, aPt
->y
, &data
);
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.
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
);
499 if (aParams
.isVerticalRun
) {
500 pt
= Point(aPt
->x
, aPt
->y
- aParams
.direction
* data
.mPartAdvance
);
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
;
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
518 static bool HasSyntheticBoldOrColor(gfxFont
* aFont
) {
519 if (aFont
->ApplySyntheticBold()) {
522 gfxFontEntry
* fe
= aFont
->GetFontEntry();
523 if (fe
->TryGetSVGData(aFont
) || fe
->TryGetColorGlyphs()) {
526 #if defined(XP_MACOSX) // sbix fonts only supported via Core Text
527 if (fe
->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) {
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
) {
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
);
552 // pop the text, using the color alpha as the opacity
553 mContext
->PopGroupAndBlend();
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");
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 &&
582 gfxFloat direction
= GetDirection();
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
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
) &&
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
;
638 params
.clipRect
= textDrawer
->GeckoClipRect();
640 params
.allowGDI
= aParams
.allowGDI
;
642 gfxFloat advance
= 0.0;
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;
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
,
662 if (IsRightToLeft()) {
663 metrics
.mBoundingBox
.MoveBy(
664 gfxPoint(aPt
.x
- metrics
.mAdvanceWidth
, aPt
.y
));
666 metrics
.mBoundingBox
.MoveBy(gfxPoint(aPt
.x
, aPt
.y
));
670 syntheticBoldBuffer
.PushSolidColor(metrics
.mBoundingBox
, currentColor
,
671 GetAppUnitsPerDevUnit());
674 Range
ligatureRange(runRange
);
675 bool adjusted
= ShrinkToLigatureBoundaries(&ligatureRange
);
679 ((aParams
.drawMode
& (DrawMode::GLYPH_FILL
| DrawMode::GLYPH_STROKE
)) ||
680 (aParams
.drawMode
== DrawMode::GLYPH_PATH
&& aParams
.callbacks
));
681 gfx::Point origPt
= pt
;
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
);
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
;
701 advance
+= (pt
.x
- origPt
.x
) * params
.direction
;
704 // composite result when synthetic bolding used
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
);
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
);
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
);
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
;
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
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
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(
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
);
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
832 AccumulateMetricsForRun(font
, ligatureRange
, aBoundingBoxType
,
833 aRefDrawTarget
, aProvider
, ligatureRange
,
834 iter
.GlyphRun()->mOrientation
, &accumulatedMetrics
);
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
;
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
) {
866 aRange
.end
- aStart
<= aHyphenBuffer
.Length() && aRange
.start
>= aStart
,
867 "Range out of bounds");
868 MOZ_ASSERT(aWordState
->mostRecentBoundary
>= aStart
,
869 "Unexpected aMostRecentWordBoundary!!");
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
,
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");
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
);
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
);
964 haveHyphenation
= false;
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
;
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.
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
,
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
) {
1026 i
= prevMostRecentWordBoundary
- 1;
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
;
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;
1066 // The spec says the breaking opportunity is *after* whitespace.
1067 auto const& g
= mCharacterGlyphs
[i
- 1];
1068 whitespaceWrapping
=
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.
1083 lastBreakTrimmableChars
= trimmableChars
;
1084 lastBreakTrimmableAdvance
= trimmableAdvance
;
1085 lastBreakUsedHyphenation
= atHyphenationBreak
;
1086 aBreakPriority
= (atBreak
|| whitespaceWrapping
)
1087 ? gfxBreakPriority::eNormalBreak
1088 : gfxBreakPriority::eWordWrapBreak
;
1093 if (width
- trimmableAdvance
> aWidth
) {
1094 // No more text fits. Abort
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
) {
1127 gfxFloat charAdvance
;
1128 if (i
>= ligatureRange
.start
&& i
< ligatureRange
.end
) {
1129 charAdvance
= GetAdvanceForGlyphs(Range(i
, i
+ 1));
1131 PropertyProvider::Spacing
* space
=
1132 &spacingBuffer
[i
- bufferRange
.start
];
1133 charAdvance
+= space
->mBefore
+ space
->mAfter
;
1136 charAdvance
= ComputePartialLigatureWidth(Range(i
, i
+ 1), &aProvider
);
1139 advance
+= charAdvance
;
1140 if (aOutTrimmableWhitespace
) {
1141 if (mCharacterGlyphs
[i
].CharIsSpace()) {
1143 trimmableAdvance
+= charAdvance
;
1145 trimmableAdvance
= 0;
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 &&
1159 // 3) none of the text fits before a break opportunity (width > aWidth &&
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
;
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
;
1195 aOutLastBreak
= lastBreak
- aStart
;
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
);
1211 adjusted
? ComputePartialLigatureWidth(
1212 Range(aRange
.start
, ligatureRange
.start
), aProvider
) +
1213 ComputePartialLigatureWidth(
1214 Range(ligatureRange
.end
, aRange
.end
), aProvider
)
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
)) {
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
;
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
);
1251 ? std::max(ComputePartialLigatureWidth(
1252 Range(aRange
.start
, ligatureRange
.start
), nullptr),
1253 ComputePartialLigatureWidth(
1254 Range(ligatureRange
.end
, aRange
.end
), nullptr))
1257 // Compute min advance width by assuming each grapheme cluster takes its own
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.
1266 clusterAdvance
+= GetAdvanceForGlyph(i
);
1267 if (i
+ 1 == ligatureRange
.end
|| IsClusterStart(i
+ 1)) {
1268 result
= std::max(result
, clusterAdvance
);
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;
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
) {
1308 MOZ_ASSERT(start
->mCharacterOffset
<= aOffset
,
1309 "Hmm, something went wrong, aOffset should have been found");
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");
1323 if (mGlyphRuns
.IsEmpty()) {
1324 mGlyphRuns
.AppendElement(
1325 GlyphRun
{aFont
, aUTF16Offset
, aOrientation
, aMatchType
, aIsCJK
});
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
)) {
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);
1353 lastGlyphRun
->SetProperties(aFont
, aOrientation
, aIsCJK
, aMatchType
);
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) {
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
) {
1382 prevOffset
= r
.mCharacterOffset
;
1385 runs
.Sort(GlyphRunOffsetComparator());
1388 // Coalesce adjacent glyph runs that have the same properties, and eliminate
1390 GlyphRun
* prevRun
= nullptr;
1391 const CompressedGlyph
* charGlyphs
= mCharacterGlyphs
;
1393 runs
.RemoveElementsBy([&](GlyphRun
& aRun
) -> bool {
1394 // First run is always retained.
1400 // Merge any run whose properties match its predecessor.
1401 if (prevRun
->Matches(aRun
.mFont
, aRun
.mOrientation
, aRun
.mIsCJK
,
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.
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
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).
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
,
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
;
1461 memcpy(charGlyphs
+ aOffset
, wordGlyphs
, wordLen
* sizeof(CompressedGlyph
));
1465 void gfxTextRun::CopyGlyphDataFrom(gfxTextRun
* aSource
, Range aRange
,
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();
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?");
1492 DetailedGlyph
* dst
= AllocateDetailedGlyphs(i
+ aDest
, count
);
1493 ::memcpy(dst
, src
, count
* sizeof(DetailedGlyph
));
1504 GlyphRun
* prevRun
= nullptr;
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?");
1514 prevRun
= const_cast<GlyphRun
*>(iter
.GlyphRun());
1515 uint32_t end
= iter
.StringEnd();
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() {
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
)) {
1553 gfx::ShapedTextFlags flags
=
1554 gfx::ShapedTextFlags::TEXT_IS_8BIT
| aOrientation
;
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
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
)) {
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
)) {
1592 const GlyphRun
* prevRun
= TrailingGlyphRun();
1593 bool isCJK
= prevRun
&& prevRun
->mFont
== aFont
&&
1594 prevRun
->mOrientation
== aOrientation
1597 AddGlyphRun(aFont
, FontMatchType::Kind::kUnspecified
, aCharIndex
, false,
1598 aOrientation
, isCJK
);
1600 CompressedGlyph::MakeSimpleGlyph(spaceWidthAppUnits
, spaceGlyph
);
1601 if (aSpaceChar
== ' ') {
1604 GetCharacterGlyphs()[aCharIndex
] = g
;
1608 void gfxTextRun::FetchGlyphExtents(DrawTarget
* aRefDrawTarget
) const {
1609 bool needsGlyphExtents
= NeedsGlyphExtents();
1610 if (!needsGlyphExtents
&& !mDetailedGlyphs
) {
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())) {
1624 uint32_t start
= run
.mCharacterOffset
;
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
;
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) {
1652 const gfxTextRun::DetailedGlyph
* details
= GetDetailedGlyphs(j
);
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
;
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
);
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()) {
1718 case ShapedTextFlags::TEXT_ORIENT_HORIZONTAL
:
1720 case ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT
:
1721 flagsString
+= "TEXT_ORIENT_VERTICAL_UPRIGHT";
1723 case ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT
:
1724 flagsString
+= "TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT";
1726 case ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED
:
1727 flagsString
+= "TEXT_ORIENT_VERTICAL_MIXED";
1729 case ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
:
1730 flagsString
+= "TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT";
1733 flagsString
.AppendPrintf("UNKNOWN_TEXT_ORIENT_MASK(0x%0x)", int(orient
));
1737 nsCString flags2String
;
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
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(),
1764 fprintf(out
, " Glyphs:\n");
1765 for (uint32_t i
= 0; i
< mLength
; ++i
) {
1766 auto glyphData
= GetCharacterGlyphs()[i
];
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());
1776 uint32_t count
= glyphData
.GetGlyphCount();
1779 for (uint32_t j
= 0; j
< count
; j
++) {
1780 line
.AppendPrintf(j
? ",%d" : "%d", GetDetailedGlyphs(i
)[j
].mGlyphID
);
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
,
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";
1830 case CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN
:
1831 line
+= " BREAK_TYPE_HYPHEN";
1835 fprintf(out
, "%s\n", line
.get());
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
),
1850 mLanguage(aLanguage
),
1851 mUnderlineOffset(UNDERLINE_OFFSET_NOT_SET
),
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
:
1865 case StyleFontVariantEmoji::Text
:
1866 mEmojiPresentation
= eFontPresentation::Text
;
1868 case StyleFontVariantEmoji::Emoji
:
1869 mEmojiPresentation
= eFontPresentation::EmojiExplicit
;
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();
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
,
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
);
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
,
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
);
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.
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
);
1950 aFamilyList
.AppendElement(std::move(family
));
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
) {
1966 MOZ_ASSERT_UNREACHABLE("don't try to add a null font family!");
1969 AutoTArray
<gfxFontEntry
*, 4> fontEntryList
;
1970 aFamily
->FindAllFontsForStyle(mStyle
, fontEntryList
);
1971 // add these to the fontlist
1972 for (gfxFontEntry
* fe
: fontEntryList
) {
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
));
2000 if (!pfl
->InitializeFamily(aFamily
)) {
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
) {
2024 already_AddRefed
<gfxFont
> gfxFontGroup::GetFontAt(uint32_t i
, uint32_t aCh
,
2026 if (i
>= mFonts
.Length()) {
2030 FamilyFace
& ff
= mFonts
[i
];
2031 if (ff
.IsInvalid() || ff
.IsLoading()) {
2035 RefPtr
<gfxFont
> font
= ff
.Font();
2037 gfxFontEntry
* fe
= ff
.FontEntry();
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
) {
2047 ff
.CheckState(mSkipDrawing
);
2048 *aLoading
= ff
.IsLoading();
2050 fe
= ufe
->GetPlatformFontEntry();
2054 unicodeRangeMap
= ufe
->GetUnicodeRangeMap();
2056 font
= fe
->FindOrMakeFont(&mStyle
, unicodeRangeMap
);
2057 if (!font
|| !font
->Valid()) {
2063 return font
.forget();
2066 void gfxFontGroup::FamilyFace::CheckState(bool& aSkipDrawing
) {
2067 gfxFontEntry
* fe
= FontEntry();
2071 if (fe
->mIsUserFontContainer
) {
2072 gfxUserFontEntry
* ufe
= static_cast<gfxUserFontEntry
*>(fe
);
2073 gfxUserFontEntry::UserFontLoadState state
= ufe
->LoadState();
2075 case gfxUserFontEntry::STATUS_LOAD_PENDING
:
2076 case gfxUserFontEntry::STATUS_LOADING
:
2079 case gfxUserFontEntry::STATUS_FAILED
:
2081 // fall-thru to the default case
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
2097 gfxFontEntry
* pfe
= aUserFont
->GetPlatformFontEntry();
2101 } else if (fe
== aUserFont
) {
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() {
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
);
2134 fe
= pfl
->GetOrCreateFontEntry(face
, fam
);
2137 fe
= family
.mUnshared
->FindFontForStyle(mStyle
);
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.
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
);
2168 fe
= pfl
->GetOrCreateFontEntry(face
, fam
);
2170 mDefaultFont
= fe
->FindOrMakeFont(&mStyle
);
2174 NS_WARNING("FindOrMakeFont failed");
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);
2185 mDefaultFont
= fe
->FindOrMakeFont(&mStyle
);
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();
2214 RefPtr
<gfxFont
> f
= fe
->FindOrMakeFont(&mStyle
);
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
);
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",
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 {
2263 if (aCh
== kCSSFirstAvailableFont
) {
2264 if (const auto* unicodeRange
= aFont
->GetUnicodeRangeMap()) {
2265 return unicodeRange
->test(' ');
2269 return aFont
->HasCharacter(aCh
);
2272 for (uint32_t i
= 0; i
< count
; ++i
) {
2273 FamilyFace
& ff
= mFonts
[i
];
2274 if (ff
.IsInvalid()) {
2278 // already have a font?
2279 RefPtr
<gfxFont
> font
= ff
.Font();
2280 if (isValidForChar(font
, aCh
)) {
2282 *aGeneric
= ff
.Generic();
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
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
);
2300 ufe
->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED
) {
2302 ff
.CheckState(mSkipDrawing
);
2304 if (ff
.IsLoading()) {
2308 if (ufe
->LoadState() != gfxUserFontEntry::STATUS_LOADED
|| !inRange
) {
2313 font
= GetFontAt(i
, aCh
, &loading
);
2314 if (isValidForChar(font
, aCh
)) {
2316 *aGeneric
= ff
.Generic();
2319 *aIsFirst
= (i
== 0);
2321 return font
.forget();
2325 *aGeneric
= StyleGenericFontFamily::None
;
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();
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) {
2353 // No point in sending non-printing control chars through font shaping
2357 // Word-separating format/bidi control characters are not shaped as part
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
);
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);
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
);
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
);
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
);
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());
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
) {
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
);
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
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
));
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
2573 if (sizeof(T
) == sizeof(uint8_t) && !transformedString
) {
2574 if (MOZ_UNLIKELY(MOZ_LOG_TEST(log
, LogLevel::Warning
))) {
2576 mLanguage
->ToUTF8String(lang
);
2577 nsAutoCString
str((const char*)aString
, aLength
);
2578 nsAutoCString styleString
;
2579 mStyle
.style
.ToString(styleString
);
2580 auto defaultLanguageGeneric
= GetDefaultGeneric(mLanguage
);
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
2590 : (defaultLanguageGeneric
== StyleGenericFontFamily::SansSerif
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
,
2603 const char16_t
* textPtr
;
2604 if (transformedString
) {
2605 textPtr
= transformedString
.get();
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
))) {
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
2634 : (defaultLanguageGeneric
==
2635 StyleGenericFontFamily::SansSerif
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
) {
2653 aTextRun
->SetShapingState(gfxTextRun::eShapingState_ForceFallbackFeature
);
2654 aTextRun
->ClearGlyphsAndCharacters();
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
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()) {
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
);
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
,
2789 matchedFont
= nullptr;
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
);
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;
2816 aTextRun
->AddGlyphRun(mainFont
, FontMatchType::Kind::kFontGroup
,
2817 aOffset
+ runStart
, (matchedLength
> 0),
2818 range
.orientation
, isCJK
);
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
,
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
2837 aTextRun
->SetIsNewline(aOffset
+ index
);
2841 aTextRun
->SetIsTab(aOffset
+ index
);
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
);
2853 if (!mSkipDrawing
&& !IsPUA(usv
)) {
2854 missingChars
= true;
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
);
2864 aTextRun
->GetAppUnitsPerDevUnit() * floor(wid
+ 0.5);
2865 if (gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance
)) {
2866 aTextRun
->GetCharacterGlyphs()[aOffset
+ index
].SetSimpleGlyph(
2867 advance
, mainFont
->GetSpaceGlyph());
2869 gfxTextRun::DetailedGlyph detailedGlyph
;
2870 detailedGlyph
.mGlyphID
= mainFont
->GetSpaceGlyph();
2871 detailedGlyph
.mAdvance
= advance
;
2872 aTextRun
->SetDetailedGlyphs(aOffset
+ index
, 1, &detailedGlyph
);
2878 if (IsInvalidChar(ch
)) {
2879 // invalid chars are left as zero-width/invisible
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();
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(), ¶ms
, aFlags
,
2925 nsTextFrameUtils::Flags(), nullptr);
2926 if (!mCachedEllipsisTextRun
) {
2929 // don't let the presence of a cached ellipsis textrun prolong the
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
;
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());
2962 GlobalFontMatch
data(aCh
, aNextCh
, mStyle
, aPresentation
);
2963 aFamily
->SearchAllFontsForChar(list
, &data
);
2964 gfxFontEntry
* fe
= data
.mBestMatch
;
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
,
2978 return FindFallbackFaceForChar(aFamily
.OwnedFamily(), aCh
, aNextCh
,
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();
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
);
3003 font
->GetMetrics(nsFontMetrics::eHorizontal
).underlineOffset
;
3004 RefPtr
<gfxFont
> firstValidFont
= GetFirstValidFont();
3005 gfxFloat first
= firstValidFont
->GetMetrics(nsFontMetrics::eHorizontal
)
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();
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) ? ' '
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
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
);
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
);
3139 *aMatchType
= {FontMatchType::Kind::kFontGroup
, mFonts
[0].Generic()};
3140 return font
.forget();
3143 if (fontListLength
> 0) {
3144 loading
= loading
|| mFonts
[0].IsLoadingFor(aCh
);
3148 // we don't need to check the first font again below
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())) {
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
)) {
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
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()) {
3215 // Otherwise, remember the first potential fallback, but keep searching.
3216 if (!candidateFont
) {
3218 candidateMatchType
= t
;
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
)) {
3233 RefPtr
<gfxFont
> font
= ff
.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();
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
)) {
3256 // Load if not already loaded, unless we've already seen an in-
3257 // progress load that is expected to satisfy this request.
3259 ufe
->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED
) {
3261 ff
.CheckState(mSkipDrawing
);
3264 if (ff
.IsLoading()) {
3268 gfxFontEntry
* pfe
= ufe
->GetPlatformFontEntry();
3269 if (pfe
&& (pfe
->HasCharacter(aCh
) ||
3270 (fallbackChar
&& pfe
->HasCharacter(fallbackChar
)))) {
3271 font
= GetFontAt(i
, aCh
, &loading
);
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
);
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()) {
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");
3309 font
= FindFallbackFaceForChar(ff
, aCh
, aNextCh
, presentation
);
3311 if (CheckCandidate(font
,
3312 {FontMatchType::Kind::kFontGroup
, ff
.Generic()})) {
3313 return font
.forget();
3317 // For platform fonts, but not user fonts, consider intra-family
3318 // fallback to handle styles with reduced character sets (see
3320 gfxFontEntry
* fe
= ff
.FontEntry();
3321 if (fe
&& !fe
->mIsUserFontContainer
&& !fe
->IsUserFont()) {
3322 font
= FindFallbackFaceForChar(ff
, aCh
, aNextCh
, presentation
);
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
);
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
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
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) {
3408 // -- otherwise look for other stuff
3409 font
= WhichSystemFontSupportsChar(aCh
, aNextCh
, aRunScript
, presentation
);
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.
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]);
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()};
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
++;
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
;
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
;
3541 case VERTICAL_ORIENTATION_R
:
3542 orient
= ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT
;
3547 if (lastRangeIndex
== -1) {
3548 // first char ==> make a new range
3549 aRanges
.AppendElement(TextRange(0, 1, font
, matchType
, orient
));
3551 prevFont
= std::move(font
);
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
));
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
);
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
))) {
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>"),
3611 MOZ_LOG(log
, LogLevel::Debug
,
3612 ("(%s-fontmatching) fontgroup: [%s] default: %s lang: %s script: %d"
3614 (mStyle
.systemFont
? "textrunui" : "textrun"),
3615 FamilyListToString(mFamilyList
).get(),
3616 (defaultLanguageGeneric
== StyleGenericFontFamily::Serif
3618 : (defaultLanguageGeneric
== StyleGenericFontFamily::SansSerif
3621 lang
.get(), static_cast<int>(aRunScript
), fontMatches
.get()));
3626 gfxUserFontSet
* gfxFontGroup::GetUserFontSet() { return mUserFontSet
; }
3628 void gfxFontGroup::SetUserFontSet(gfxUserFontSet
* aUserFontSet
) {
3629 if (aUserFontSet
== mUserFontSet
) {
3632 mUserFontSet
= aUserFontSet
;
3633 mCurrGeneration
= GetGeneration() - 1;
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
3653 mCurrGeneration
= GetGeneration();
3654 } else if (mCurrGeneration
!= GetGeneration()) {
3655 // load state change occurred, verify load state and validity of fonts
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()) {
3664 ff
.CheckState(mSkipDrawing
);
3667 mCurrGeneration
= GetGeneration();
3671 bool gfxFontGroup::ContainsUserFont(const gfxUserFontEntry
* aUserFont
) {
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
)) {
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
;
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
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()) {
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
);
3746 fe
= pfl
->GetOrCreateFontEntry(face
, fam
);
3749 fe
= family
.mUnshared
->FindFontForStyle(mStyle
);
3755 // if ch in cmap, create and return a gfxFont
3756 RefPtr
<gfxFont
> prefFont
;
3757 if (fe
->HasCharacter(aCh
)) {
3758 prefFont
= fe
->FindOrMakeFont(&mStyle
);
3762 if (aPresentation
== eFontPresentation::EmojiExplicit
&&
3763 !prefFont
->HasColorGlyphFor(aCh
, aNextCh
)) {
3768 // If the char was not available, see if we can fall back to an
3769 // alternative face in the same family.
3771 prefFont
= family
.mShared
3772 ? FindFallbackFaceForChar(family
.mShared
, aCh
, aNextCh
,
3774 : FindFallbackFaceForChar(family
.mUnshared
, aCh
, aNextCh
,
3778 mLastPrefFamily
= family
;
3779 mLastPrefFont
= prefFont
;
3780 mLastPrefLang
= charLang
;
3781 mLastPrefFirstFont
= (i
== 0 && j
== 0);
3782 return prefFont
.forget();
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
,
3799 gfxFont::Metrics
gfxFontGroup::GetMetricsForCSSUnits(
3800 gfxFont::Orientation aOrientation
) {
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
;
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
]) {
3842 for (uint32_t j
= 0; j
< 32; ++j
) {
3843 if (!(mMissingFonts
[i
] & (1 << j
))) {
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());