diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /gfx/thebes/gfxTextRun.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/thebes/gfxTextRun.cpp')
-rw-r--r-- | gfx/thebes/gfxTextRun.cpp | 3852 |
1 files changed, 3852 insertions, 0 deletions
diff --git a/gfx/thebes/gfxTextRun.cpp b/gfx/thebes/gfxTextRun.cpp new file mode 100644 index 0000000000..447594ba66 --- /dev/null +++ b/gfx/thebes/gfxTextRun.cpp @@ -0,0 +1,3852 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=4 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxTextRun.h" +#include "gfxGlyphExtents.h" +#include "gfxHarfBuzzShaper.h" +#include "gfxPlatformFontList.h" +#include "gfxUserFontSet.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/ServoStyleSet.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPresData.h" + +#include "gfxContext.h" +#include "gfxFontConstants.h" +#include "gfxFontMissingGlyphs.h" +#include "gfxScriptItemizer.h" +#include "nsUnicodeProperties.h" +#include "nsStyleConsts.h" +#include "nsStyleUtil.h" +#include "mozilla/Likely.h" +#include "gfx2DGlue.h" +#include "mozilla/gfx/Logging.h" // for gfxCriticalError +#include "mozilla/intl/String.h" +#include "mozilla/intl/UnicodeProperties.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "SharedFontList-impl.h" +#include "TextDrawTarget.h" + +#ifdef XP_WIN +# include "gfxWindowsPlatform.h" +#endif + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::intl; +using namespace mozilla::unicode; +using mozilla::services::GetObserverService; + +static const char16_t kEllipsisChar[] = {0x2026, 0x0}; +static const char16_t kASCIIPeriodsChar[] = {'.', '.', '.', 0x0}; + +#ifdef DEBUG_roc +# define DEBUG_TEXT_RUN_STORAGE_METRICS +#endif + +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS +extern uint32_t gTextRunStorageHighWaterMark; +extern uint32_t gTextRunStorage; +extern uint32_t gFontCount; +extern uint32_t gGlyphExtentsCount; +extern uint32_t gGlyphExtentsWidthsTotalSize; +extern uint32_t gGlyphExtentsSetupEagerSimple; +extern uint32_t gGlyphExtentsSetupEagerTight; +extern uint32_t gGlyphExtentsSetupLazyTight; +extern uint32_t gGlyphExtentsSetupFallBackToTight; +#endif + +void gfxTextRun::GlyphRunIterator::NextRun() { + if (mReverse) { + if (mGlyphRun == mTextRun->mGlyphRuns.begin()) { + mGlyphRun = nullptr; + return; + } + --mGlyphRun; + } else { + MOZ_DIAGNOSTIC_ASSERT(mGlyphRun != mTextRun->mGlyphRuns.end()); + ++mGlyphRun; + if (mGlyphRun == mTextRun->mGlyphRuns.end()) { + mGlyphRun = nullptr; + return; + } + } + if (mGlyphRun->mCharacterOffset >= mEndOffset) { + mGlyphRun = nullptr; + return; + } + uint32_t glyphRunEndOffset = mGlyphRun == mTextRun->mGlyphRuns.end() - 1 + ? mTextRun->GetLength() + : (mGlyphRun + 1)->mCharacterOffset; + if (glyphRunEndOffset < mStartOffset) { + mGlyphRun = nullptr; + return; + } + mStringEnd = std::min(mEndOffset, glyphRunEndOffset); + mStringStart = std::max(mStartOffset, mGlyphRun->mCharacterOffset); +} + +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS +static void AccountStorageForTextRun(gfxTextRun* aTextRun, int32_t aSign) { + // Ignores detailed glyphs... we don't know when those have been constructed + // Also ignores gfxSkipChars dynamic storage (which won't be anything + // for preformatted text) + // Also ignores GlyphRun array, again because it hasn't been constructed + // by the time this gets called. If there's only one glyphrun that's stored + // directly in the textrun anyway so no additional overhead. + uint32_t length = aTextRun->GetLength(); + int32_t bytes = length * sizeof(gfxTextRun::CompressedGlyph); + bytes += sizeof(gfxTextRun); + gTextRunStorage += bytes * aSign; + gTextRunStorageHighWaterMark = + std::max(gTextRunStorageHighWaterMark, gTextRunStorage); +} +#endif + +bool gfxTextRun::NeedsGlyphExtents() const { + if (GetFlags() & gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX) { + return true; + } + for (const auto& run : mGlyphRuns) { + if (run.mFont->GetFontEntry()->IsUserFont()) { + return true; + } + } + return false; +} + +// Helper for textRun creation to preallocate storage for glyph records; +// this function returns a pointer to the newly-allocated glyph storage. +// Returns nullptr if allocation fails. +void* gfxTextRun::AllocateStorageForTextRun(size_t aSize, uint32_t aLength) { + // Allocate the storage we need, returning nullptr on failure rather than + // throwing an exception (because web content can create huge runs). + void* storage = malloc(aSize + aLength * sizeof(CompressedGlyph)); + if (!storage) { + NS_WARNING("failed to allocate storage for text run!"); + return nullptr; + } + + // Initialize the glyph storage (beyond aSize) to zero + memset(reinterpret_cast<char*>(storage) + aSize, 0, + aLength * sizeof(CompressedGlyph)); + + return storage; +} + +already_AddRefed<gfxTextRun> gfxTextRun::Create( + const gfxTextRunFactory::Parameters* aParams, uint32_t aLength, + gfxFontGroup* aFontGroup, gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2) { + void* storage = AllocateStorageForTextRun(sizeof(gfxTextRun), aLength); + if (!storage) { + return nullptr; + } + + RefPtr<gfxTextRun> result = + new (storage) gfxTextRun(aParams, aLength, aFontGroup, aFlags, aFlags2); + return result.forget(); +} + +gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters* aParams, + uint32_t aLength, gfxFontGroup* aFontGroup, + gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2) + : gfxShapedText(aLength, aFlags, aParams->mAppUnitsPerDevUnit), + mUserData(aParams->mUserData), + mFontGroup(aFontGroup), + mFlags2(aFlags2), + mReleasedFontGroup(false), + mReleasedFontGroupSkippedDrawing(false), + mShapingState(eShapingState_Normal) { + NS_ASSERTION(mAppUnitsPerDevUnit > 0, "Invalid app unit scale"); + NS_ADDREF(mFontGroup); + +#ifndef RELEASE_OR_BETA + gfxTextPerfMetrics* tp = aFontGroup->GetTextPerfMetrics(); + if (tp) { + tp->current.textrunConst++; + } +#endif + + mCharacterGlyphs = reinterpret_cast<CompressedGlyph*>(this + 1); + + if (aParams->mSkipChars) { + mSkipChars.TakeFrom(aParams->mSkipChars); + } + +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + AccountStorageForTextRun(this, 1); +#endif + + mDontSkipDrawing = + !!(aFlags2 & nsTextFrameUtils::Flags::DontSkipDrawingForPendingUserFonts); +} + +gfxTextRun::~gfxTextRun() { +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + AccountStorageForTextRun(this, -1); +#endif +#ifdef DEBUG + // Make it easy to detect a dead text run + mFlags = ~gfx::ShapedTextFlags(); + mFlags2 = ~nsTextFrameUtils::Flags(); +#endif + + // The cached ellipsis textrun (if any) in a fontgroup will have already + // been told to release its reference to the group, so we mustn't do that + // again here. + if (!mReleasedFontGroup) { +#ifndef RELEASE_OR_BETA + gfxTextPerfMetrics* tp = mFontGroup->GetTextPerfMetrics(); + if (tp) { + tp->current.textrunDestr++; + } +#endif + NS_RELEASE(mFontGroup); + } +} + +void gfxTextRun::ReleaseFontGroup() { + NS_ASSERTION(!mReleasedFontGroup, "doubly released!"); + + // After dropping our reference to the font group, we'll no longer be able + // to get up-to-date results for ShouldSkipDrawing(). Store the current + // value in mReleasedFontGroupSkippedDrawing. + // + // (It doesn't actually matter that we can't get up-to-date results for + // ShouldSkipDrawing(), since the only text runs that we call + // ReleaseFontGroup() for are ellipsis text runs, and we ask the font + // group for a new ellipsis text run each time we want to draw one, + // and ensure that the cached one is cleared in ClearCachedData() when + // font loading status changes.) + mReleasedFontGroupSkippedDrawing = mFontGroup->ShouldSkipDrawing(); + + NS_RELEASE(mFontGroup); + mReleasedFontGroup = true; +} + +bool gfxTextRun::SetPotentialLineBreaks(Range aRange, + const uint8_t* aBreakBefore) { + NS_ASSERTION(aRange.end <= GetLength(), "Overflow"); + + uint32_t changed = 0; + CompressedGlyph* cg = mCharacterGlyphs + aRange.start; + const CompressedGlyph* const end = cg + aRange.Length(); + while (cg < end) { + uint8_t canBreak = *aBreakBefore++; + if (canBreak && !cg->IsClusterStart()) { + // XXX If we replace the line-breaker with one based more closely + // on UAX#14 (e.g. using ICU), this may not be needed any more. + // Avoid possible breaks inside a cluster, EXCEPT when the previous + // character was a space (compare UAX#14 rules LB9, LB10). + if (cg == mCharacterGlyphs || !(cg - 1)->CharIsSpace()) { + canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE; + } + } + changed |= cg->SetCanBreakBefore(canBreak); + ++cg; + } + return changed != 0; +} + +gfxTextRun::LigatureData gfxTextRun::ComputeLigatureData( + Range aPartRange, const PropertyProvider* aProvider) const { + NS_ASSERTION(aPartRange.start < aPartRange.end, + "Computing ligature data for empty range"); + NS_ASSERTION(aPartRange.end <= GetLength(), "Character length overflow"); + + LigatureData result; + const CompressedGlyph* charGlyphs = mCharacterGlyphs; + + uint32_t i; + for (i = aPartRange.start; !charGlyphs[i].IsLigatureGroupStart(); --i) { + NS_ASSERTION(i > 0, "Ligature at the start of the run??"); + } + result.mRange.start = i; + for (i = aPartRange.start + 1; + i < GetLength() && !charGlyphs[i].IsLigatureGroupStart(); ++i) { + } + result.mRange.end = i; + + int32_t ligatureWidth = GetAdvanceForGlyphs(result.mRange); + // Count the number of started clusters we have seen + uint32_t totalClusterCount = 0; + uint32_t partClusterIndex = 0; + uint32_t partClusterCount = 0; + for (i = result.mRange.start; i < result.mRange.end; ++i) { + // Treat the first character of the ligature as the start of a + // cluster for our purposes of allocating ligature width to its + // characters. + if (i == result.mRange.start || charGlyphs[i].IsClusterStart()) { + ++totalClusterCount; + if (i < aPartRange.start) { + ++partClusterIndex; + } else if (i < aPartRange.end) { + ++partClusterCount; + } + } + } + NS_ASSERTION(totalClusterCount > 0, "Ligature involving no clusters??"); + result.mPartAdvance = partClusterIndex * (ligatureWidth / totalClusterCount); + result.mPartWidth = partClusterCount * (ligatureWidth / totalClusterCount); + + // Any rounding errors are apportioned to the final part of the ligature, + // so that measuring all parts of a ligature and summing them is equal to + // the ligature width. + if (aPartRange.end == result.mRange.end) { + gfxFloat allParts = totalClusterCount * (ligatureWidth / totalClusterCount); + result.mPartWidth += ligatureWidth - allParts; + } + + if (partClusterCount == 0) { + // nothing to draw + result.mClipBeforePart = result.mClipAfterPart = true; + } else { + // Determine whether we should clip before or after this part when + // drawing its slice of the ligature. + // We need to clip before the part if any cluster is drawn before + // this part. + result.mClipBeforePart = partClusterIndex > 0; + // We need to clip after the part if any cluster is drawn after + // this part. + result.mClipAfterPart = + partClusterIndex + partClusterCount < totalClusterCount; + } + + if (aProvider && (mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) { + gfxFont::Spacing spacing; + if (aPartRange.start == result.mRange.start) { + aProvider->GetSpacing(Range(aPartRange.start, aPartRange.start + 1), + &spacing); + result.mPartWidth += spacing.mBefore; + } + if (aPartRange.end == result.mRange.end) { + aProvider->GetSpacing(Range(aPartRange.end - 1, aPartRange.end), + &spacing); + result.mPartWidth += spacing.mAfter; + } + } + + return result; +} + +gfxFloat gfxTextRun::ComputePartialLigatureWidth( + Range aPartRange, const PropertyProvider* aProvider) const { + if (aPartRange.start >= aPartRange.end) return 0; + LigatureData data = ComputeLigatureData(aPartRange, aProvider); + return data.mPartWidth; +} + +int32_t gfxTextRun::GetAdvanceForGlyphs(Range aRange) const { + int32_t advance = 0; + for (auto i = aRange.start; i < aRange.end; ++i) { + advance += GetAdvanceForGlyph(i); + } + return advance; +} + +static void GetAdjustedSpacing( + const gfxTextRun* aTextRun, gfxTextRun::Range aRange, + const gfxTextRun::PropertyProvider& aProvider, + gfxTextRun::PropertyProvider::Spacing* aSpacing) { + if (aRange.start >= aRange.end) { + return; + } + + aProvider.GetSpacing(aRange, aSpacing); + +#ifdef DEBUG + // Check to see if we have spacing inside ligatures + + const gfxTextRun::CompressedGlyph* charGlyphs = + aTextRun->GetCharacterGlyphs(); + uint32_t i; + + for (i = aRange.start; i < aRange.end; ++i) { + if (!charGlyphs[i].IsLigatureGroupStart()) { + NS_ASSERTION(i == aRange.start || aSpacing[i - aRange.start].mBefore == 0, + "Before-spacing inside a ligature!"); + NS_ASSERTION( + i - 1 <= aRange.start || aSpacing[i - 1 - aRange.start].mAfter == 0, + "After-spacing inside a ligature!"); + } + } +#endif +} + +bool gfxTextRun::GetAdjustedSpacingArray( + Range aRange, const PropertyProvider* aProvider, Range aSpacingRange, + nsTArray<PropertyProvider::Spacing>* aSpacing) const { + if (!aProvider || !(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) { + return false; + } + if (!aSpacing->AppendElements(aRange.Length(), fallible)) { + return false; + } + auto spacingOffset = aSpacingRange.start - aRange.start; + memset(aSpacing->Elements(), 0, sizeof(gfxFont::Spacing) * spacingOffset); + GetAdjustedSpacing(this, aSpacingRange, *aProvider, + aSpacing->Elements() + spacingOffset); + memset(aSpacing->Elements() + spacingOffset + aSpacingRange.Length(), 0, + sizeof(gfxFont::Spacing) * (aRange.end - aSpacingRange.end)); + return true; +} + +bool gfxTextRun::ShrinkToLigatureBoundaries(Range* aRange) const { + if (aRange->start >= aRange->end) { + return false; + } + + const CompressedGlyph* charGlyphs = mCharacterGlyphs; + bool adjusted = false; + while (aRange->start < aRange->end && + !charGlyphs[aRange->start].IsLigatureGroupStart()) { + ++aRange->start; + adjusted = true; + } + if (aRange->end < GetLength()) { + while (aRange->end > aRange->start && + !charGlyphs[aRange->end].IsLigatureGroupStart()) { + --aRange->end; + adjusted = true; + } + } + return adjusted; +} + +void gfxTextRun::DrawGlyphs(gfxFont* aFont, Range aRange, gfx::Point* aPt, + const PropertyProvider* aProvider, + Range aSpacingRange, TextRunDrawParams& aParams, + gfx::ShapedTextFlags aOrientation) const { + AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer; + bool haveSpacing = + GetAdjustedSpacingArray(aRange, aProvider, aSpacingRange, &spacingBuffer); + aParams.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr; + aFont->Draw(this, aRange.start, aRange.end, aPt, aParams, aOrientation); +} + +static void ClipPartialLigature(const gfxTextRun* aTextRun, gfxFloat* aStart, + gfxFloat* aEnd, gfxFloat aOrigin, + gfxTextRun::LigatureData* aLigature) { + if (aLigature->mClipBeforePart) { + if (aTextRun->IsRightToLeft()) { + *aEnd = std::min(*aEnd, aOrigin); + } else { + *aStart = std::max(*aStart, aOrigin); + } + } + if (aLigature->mClipAfterPart) { + gfxFloat endEdge = + aOrigin + aTextRun->GetDirection() * aLigature->mPartWidth; + if (aTextRun->IsRightToLeft()) { + *aStart = std::max(*aStart, endEdge); + } else { + *aEnd = std::min(*aEnd, endEdge); + } + } +} + +void gfxTextRun::DrawPartialLigature(gfxFont* aFont, Range aRange, + gfx::Point* aPt, + const PropertyProvider* aProvider, + TextRunDrawParams& aParams, + gfx::ShapedTextFlags aOrientation) const { + if (aRange.start >= aRange.end) { + return; + } + + // Draw partial ligature. We hack this by clipping the ligature. + LigatureData data = ComputeLigatureData(aRange, aProvider); + gfxRect clipExtents = aParams.context->GetClipExtents(); + gfxFloat start, end; + if (aParams.isVerticalRun) { + start = clipExtents.Y() * mAppUnitsPerDevUnit; + end = clipExtents.YMost() * mAppUnitsPerDevUnit; + ClipPartialLigature(this, &start, &end, aPt->y, &data); + } else { + start = clipExtents.X() * mAppUnitsPerDevUnit; + end = clipExtents.XMost() * mAppUnitsPerDevUnit; + ClipPartialLigature(this, &start, &end, aPt->x, &data); + } + + gfxClipAutoSaveRestore autoSaveClip(aParams.context); + { + // use division here to ensure that when the rect is aligned on multiples + // of mAppUnitsPerDevUnit, we clip to true device unit boundaries. + // Also, make sure we snap the rectangle to device pixels. + Rect clipRect = + aParams.isVerticalRun + ? Rect(clipExtents.X(), start / mAppUnitsPerDevUnit, + clipExtents.Width(), (end - start) / mAppUnitsPerDevUnit) + : Rect(start / mAppUnitsPerDevUnit, clipExtents.Y(), + (end - start) / mAppUnitsPerDevUnit, clipExtents.Height()); + MaybeSnapToDevicePixels(clipRect, *aParams.dt, true); + + autoSaveClip.Clip(clipRect); + } + + gfx::Point pt; + if (aParams.isVerticalRun) { + pt = Point(aPt->x, aPt->y - aParams.direction * data.mPartAdvance); + } else { + pt = Point(aPt->x - aParams.direction * data.mPartAdvance, aPt->y); + } + + DrawGlyphs(aFont, data.mRange, &pt, aProvider, aRange, aParams, aOrientation); + + if (aParams.isVerticalRun) { + aPt->y += aParams.direction * data.mPartWidth; + } else { + aPt->x += aParams.direction * data.mPartWidth; + } +} + +// Returns true if the font has synthetic bolding enabled, +// or is a color font (COLR/SVG/sbix/CBDT), false otherwise. This is used to +// check whether the text run needs to be explicitly composited in order to +// support opacity. +static bool HasSyntheticBoldOrColor(gfxFont* aFont) { + if (aFont->ApplySyntheticBold()) { + return true; + } + gfxFontEntry* fe = aFont->GetFontEntry(); + if (fe->TryGetSVGData(aFont) || fe->TryGetColorGlyphs()) { + return true; + } +#if defined(XP_MACOSX) // sbix fonts only supported via Core Text + if (fe->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) { + return true; + } +#endif + return false; +} + +// helper class for double-buffering drawing with non-opaque color +struct MOZ_STACK_CLASS BufferAlphaColor { + explicit BufferAlphaColor(gfxContext* aContext) : mContext(aContext) {} + + ~BufferAlphaColor() = default; + + void PushSolidColor(const gfxRect& aBounds, const DeviceColor& aAlphaColor, + uint32_t appsPerDevUnit) { + mContext->Save(); + mContext->SnappedClip(gfxRect( + aBounds.X() / appsPerDevUnit, aBounds.Y() / appsPerDevUnit, + aBounds.Width() / appsPerDevUnit, aBounds.Height() / appsPerDevUnit)); + mContext->SetDeviceColor( + DeviceColor(aAlphaColor.r, aAlphaColor.g, aAlphaColor.b)); + mContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, aAlphaColor.a); + } + + void PopAlpha() { + // pop the text, using the color alpha as the opacity + mContext->PopGroupAndBlend(); + mContext->Restore(); + } + + gfxContext* mContext; +}; + +void gfxTextRun::Draw(const Range aRange, const gfx::Point aPt, + const DrawParams& aParams) const { + NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range"); + NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH || + !(aParams.drawMode & DrawMode::GLYPH_PATH), + "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or " + "GLYPH_STROKE_UNDERNEATH"); + NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH || !aParams.callbacks, + "callback must not be specified unless using GLYPH_PATH"); + + bool skipDrawing = + !mDontSkipDrawing && (mFontGroup ? mFontGroup->ShouldSkipDrawing() + : mReleasedFontGroupSkippedDrawing); + auto* textDrawer = aParams.context->GetTextDrawer(); + if (aParams.drawMode & DrawMode::GLYPH_FILL) { + DeviceColor currentColor; + if (aParams.context->GetDeviceColor(currentColor) && currentColor.a == 0 && + !textDrawer) { + skipDrawing = true; + } + } + + gfxFloat direction = GetDirection(); + + if (skipDrawing) { + // We don't need to draw anything; + // but if the caller wants advance width, we need to compute it here + if (aParams.advanceWidth) { + gfxTextRun::Metrics metrics = + MeasureText(aRange, gfxFont::LOOSE_INK_EXTENTS, + aParams.context->GetDrawTarget(), aParams.provider); + *aParams.advanceWidth = metrics.mAdvanceWidth * direction; + } + + // return without drawing + return; + } + + // synthetic bolding draws glyphs twice ==> colors with opacity won't draw + // correctly unless first drawn without alpha + BufferAlphaColor syntheticBoldBuffer(aParams.context); + DeviceColor currentColor; + bool mayNeedBuffering = + aParams.drawMode & DrawMode::GLYPH_FILL && + aParams.context->HasNonOpaqueNonTransparentColor(currentColor) && + !textDrawer; + + // If we need to double-buffer, we'll need to measure the text first to + // get the bounds of the area of interest. Ideally we'd do that just for + // the specific glyph run(s) that need buffering, but because of bug + // 1612610 we currently use the extent of the entire range even when + // just buffering a subrange. So we'll measure the full range once and + // keep the metrics on hand for any subsequent subranges. + gfxTextRun::Metrics metrics; + bool gotMetrics = false; + + // Set up parameters that will be constant across all glyph runs we need + // to draw, regardless of the font used. + TextRunDrawParams params; + params.context = aParams.context; + params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit()); + params.isVerticalRun = IsVertical(); + params.isRTL = IsRightToLeft(); + params.direction = direction; + params.strokeOpts = aParams.strokeOpts; + params.textStrokeColor = aParams.textStrokeColor; + params.fontPalette = aParams.fontPalette; + params.paletteValueSet = aParams.paletteValueSet; + params.textStrokePattern = aParams.textStrokePattern; + params.drawOpts = aParams.drawOpts; + params.drawMode = aParams.drawMode; + params.callbacks = aParams.callbacks; + params.runContextPaint = aParams.contextPaint; + params.paintSVGGlyphs = + !aParams.callbacks || aParams.callbacks->mShouldPaintSVGGlyphs; + params.dt = aParams.context->GetDrawTarget(); + params.textDrawer = textDrawer; + if (textDrawer) { + params.clipRect = textDrawer->GeckoClipRect(); + } + params.allowGDI = aParams.allowGDI; + + gfxFloat advance = 0.0; + gfx::Point pt = aPt; + + for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) { + gfxFont* font = iter.GlyphRun()->mFont; + Range runRange(iter.StringStart(), iter.StringEnd()); + + bool needToRestore = false; + if (mayNeedBuffering && HasSyntheticBoldOrColor(font)) { + needToRestore = true; + if (!gotMetrics) { + // Measure text; use the bounding box to determine the area we need + // to buffer. We measure the entire range, rather than just the glyph + // run that we're actually handling, because of bug 1612610: if the + // bounding box passed to PushSolidColor does not intersect the + // drawTarget's current clip, the skia backend fails to clip properly. + // This means we may use a larger buffer than actually needed, but is + // otherwise harmless. + metrics = MeasureText(aRange, gfxFont::LOOSE_INK_EXTENTS, params.dt, + aParams.provider); + if (IsRightToLeft()) { + metrics.mBoundingBox.MoveBy( + gfxPoint(aPt.x - metrics.mAdvanceWidth, aPt.y)); + } else { + metrics.mBoundingBox.MoveBy(gfxPoint(aPt.x, aPt.y)); + } + gotMetrics = true; + } + syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor, + GetAppUnitsPerDevUnit()); + } + + Range ligatureRange(runRange); + bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange); + + bool drawPartial = + adjusted && + ((aParams.drawMode & (DrawMode::GLYPH_FILL | DrawMode::GLYPH_STROKE)) || + (aParams.drawMode == DrawMode::GLYPH_PATH && aParams.callbacks)); + gfx::Point origPt = pt; + + if (drawPartial) { + DrawPartialLigature(font, Range(runRange.start, ligatureRange.start), &pt, + aParams.provider, params, + iter.GlyphRun()->mOrientation); + } + + DrawGlyphs(font, ligatureRange, &pt, aParams.provider, ligatureRange, + params, iter.GlyphRun()->mOrientation); + + if (drawPartial) { + DrawPartialLigature(font, Range(ligatureRange.end, runRange.end), &pt, + aParams.provider, params, + iter.GlyphRun()->mOrientation); + } + + if (params.isVerticalRun) { + advance += (pt.y - origPt.y) * params.direction; + } else { + advance += (pt.x - origPt.x) * params.direction; + } + + // composite result when synthetic bolding used + if (needToRestore) { + syntheticBoldBuffer.PopAlpha(); + } + } + + if (aParams.advanceWidth) { + *aParams.advanceWidth = advance; + } +} + +// This method is mostly parallel to Draw(). +void gfxTextRun::DrawEmphasisMarks(gfxContext* aContext, gfxTextRun* aMark, + gfxFloat aMarkAdvance, gfx::Point aPt, + Range aRange, + const PropertyProvider* aProvider) const { + MOZ_ASSERT(aRange.end <= GetLength()); + + EmphasisMarkDrawParams params; + params.context = aContext; + params.mark = aMark; + params.advance = aMarkAdvance; + params.direction = GetDirection(); + params.isVertical = IsVertical(); + + float& inlineCoord = params.isVertical ? aPt.y.value : aPt.x.value; + float direction = params.direction; + + for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) { + gfxFont* font = iter.GlyphRun()->mFont; + uint32_t start = iter.StringStart(); + uint32_t end = iter.StringEnd(); + Range ligatureRange(start, end); + bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange); + + if (adjusted) { + inlineCoord += + direction * ComputePartialLigatureWidth( + Range(start, ligatureRange.start), aProvider); + } + + AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer; + bool haveSpacing = GetAdjustedSpacingArray(ligatureRange, aProvider, + ligatureRange, &spacingBuffer); + params.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr; + font->DrawEmphasisMarks(this, &aPt, ligatureRange.start, + ligatureRange.Length(), params); + + if (adjusted) { + inlineCoord += direction * ComputePartialLigatureWidth( + Range(ligatureRange.end, end), aProvider); + } + } +} + +void gfxTextRun::AccumulateMetricsForRun( + gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, const PropertyProvider* aProvider, + Range aSpacingRange, gfx::ShapedTextFlags aOrientation, + Metrics* aMetrics) const { + AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer; + bool haveSpacing = + GetAdjustedSpacingArray(aRange, aProvider, aSpacingRange, &spacingBuffer); + Metrics metrics = aFont->Measure( + this, aRange.start, aRange.end, aBoundingBoxType, aRefDrawTarget, + haveSpacing ? spacingBuffer.Elements() : nullptr, aOrientation); + aMetrics->CombineWith(metrics, IsRightToLeft()); +} + +void gfxTextRun::AccumulatePartialLigatureMetrics( + gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, const PropertyProvider* aProvider, + gfx::ShapedTextFlags aOrientation, Metrics* aMetrics) const { + if (aRange.start >= aRange.end) return; + + // Measure partial ligature. We hack this by clipping the metrics in the + // same way we clip the drawing. + LigatureData data = ComputeLigatureData(aRange, aProvider); + + // First measure the complete ligature + Metrics metrics; + AccumulateMetricsForRun(aFont, data.mRange, aBoundingBoxType, aRefDrawTarget, + aProvider, aRange, aOrientation, &metrics); + + // Clip the bounding box to the ligature part + gfxFloat bboxLeft = metrics.mBoundingBox.X(); + gfxFloat bboxRight = metrics.mBoundingBox.XMost(); + // Where we are going to start "drawing" relative to our left baseline origin + gfxFloat origin = + IsRightToLeft() ? metrics.mAdvanceWidth - data.mPartAdvance : 0; + ClipPartialLigature(this, &bboxLeft, &bboxRight, origin, &data); + metrics.mBoundingBox.SetBoxX(bboxLeft, bboxRight); + + // mBoundingBox is now relative to the left baseline origin for the entire + // ligature. Shift it left. + metrics.mBoundingBox.MoveByX( + -(IsRightToLeft() + ? metrics.mAdvanceWidth - (data.mPartAdvance + data.mPartWidth) + : data.mPartAdvance)); + metrics.mAdvanceWidth = data.mPartWidth; + + aMetrics->CombineWith(metrics, IsRightToLeft()); +} + +gfxTextRun::Metrics gfxTextRun::MeasureText( + Range aRange, gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, const PropertyProvider* aProvider) const { + NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range"); + + Metrics accumulatedMetrics; + for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) { + gfxFont* font = iter.GlyphRun()->mFont; + uint32_t start = iter.StringStart(); + uint32_t end = iter.StringEnd(); + Range ligatureRange(start, end); + bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange); + + if (adjusted) { + AccumulatePartialLigatureMetrics(font, Range(start, ligatureRange.start), + aBoundingBoxType, aRefDrawTarget, + aProvider, iter.GlyphRun()->mOrientation, + &accumulatedMetrics); + } + + // XXX This sucks. We have to get glyph extents just so we can detect + // glyphs outside the font box, even when aBoundingBoxType is LOOSE, + // even though in almost all cases we could get correct results just + // by getting some ascent/descent from the font and using our stored + // advance widths. + AccumulateMetricsForRun(font, ligatureRange, aBoundingBoxType, + aRefDrawTarget, aProvider, ligatureRange, + iter.GlyphRun()->mOrientation, &accumulatedMetrics); + + if (adjusted) { + AccumulatePartialLigatureMetrics( + font, Range(ligatureRange.end, end), aBoundingBoxType, aRefDrawTarget, + aProvider, iter.GlyphRun()->mOrientation, &accumulatedMetrics); + } + } + + return accumulatedMetrics; +} + +void gfxTextRun::GetLineHeightMetrics(Range aRange, gfxFloat& aAscent, + gfxFloat& aDescent) const { + Metrics accumulatedMetrics; + for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) { + gfxFont* font = iter.GlyphRun()->mFont; + auto metrics = + font->Measure(this, 0, 0, gfxFont::LOOSE_INK_EXTENTS, nullptr, nullptr, + iter.GlyphRun()->mOrientation); + accumulatedMetrics.CombineWith(metrics, false); + } + aAscent = accumulatedMetrics.mAscent; + aDescent = accumulatedMetrics.mDescent; +} + +#define MEASUREMENT_BUFFER_SIZE 100 + +void gfxTextRun::ClassifyAutoHyphenations(uint32_t aStart, Range aRange, + nsTArray<HyphenType>& aHyphenBuffer, + HyphenationState* aWordState) { + MOZ_ASSERT( + aRange.end - aStart <= aHyphenBuffer.Length() && aRange.start >= aStart, + "Range out of bounds"); + MOZ_ASSERT(aWordState->mostRecentBoundary >= aStart, + "Unexpected aMostRecentWordBoundary!!"); + + uint32_t start = + std::min<uint32_t>(aRange.start, aWordState->mostRecentBoundary); + + for (uint32_t i = start; i < aRange.end; ++i) { + if (aHyphenBuffer[i - aStart] == HyphenType::Explicit && + !aWordState->hasExplicitHyphen) { + aWordState->hasExplicitHyphen = true; + } + if (!aWordState->hasManualHyphen && + (aHyphenBuffer[i - aStart] == HyphenType::Soft || + aHyphenBuffer[i - aStart] == HyphenType::Explicit)) { + aWordState->hasManualHyphen = true; + // This is the first manual hyphen in the current word. We can only + // know if the current word has a manual hyphen until now. So, we need + // to run a sub loop to update the auto hyphens between the start of + // the current word and this manual hyphen. + if (aWordState->hasAutoHyphen) { + for (uint32_t j = aWordState->mostRecentBoundary; j < i; j++) { + if (aHyphenBuffer[j - aStart] == + HyphenType::AutoWithoutManualInSameWord) { + aHyphenBuffer[j - aStart] = HyphenType::AutoWithManualInSameWord; + } + } + } + } + if (aHyphenBuffer[i - aStart] == HyphenType::AutoWithoutManualInSameWord) { + if (!aWordState->hasAutoHyphen) { + aWordState->hasAutoHyphen = true; + } + if (aWordState->hasManualHyphen) { + aHyphenBuffer[i - aStart] = HyphenType::AutoWithManualInSameWord; + } + } + + // If we're at the word boundary, clear/reset couple states. + if (mCharacterGlyphs[i].CharIsSpace() || mCharacterGlyphs[i].CharIsTab() || + mCharacterGlyphs[i].CharIsNewline() || + // Since we will not have a boundary in the end of the string, let's + // call the end of the string a special case for word boundary. + i == GetLength() - 1) { + // We can only get to know whether we should raise/clear an explicit + // manual hyphen until we get to the end of a word, because this depends + // on whether there exists at least one auto hyphen in the same word. + if (!aWordState->hasAutoHyphen && aWordState->hasExplicitHyphen) { + for (uint32_t j = aWordState->mostRecentBoundary; j <= i; j++) { + if (aHyphenBuffer[j - aStart] == HyphenType::Explicit) { + aHyphenBuffer[j - aStart] = HyphenType::None; + } + } + } + aWordState->mostRecentBoundary = i; + aWordState->hasManualHyphen = false; + aWordState->hasAutoHyphen = false; + aWordState->hasExplicitHyphen = false; + } + } +} + +uint32_t gfxTextRun::BreakAndMeasureText( + uint32_t aStart, uint32_t aMaxLength, bool aLineBreakBefore, + gfxFloat aWidth, const PropertyProvider& aProvider, + SuppressBreak aSuppressBreak, gfxFont::BoundingBoxType aBoundingBoxType, + DrawTarget* aRefDrawTarget, bool aCanWordWrap, bool aCanWhitespaceWrap, + TrimmableWS* aOutTrimmableWhitespace, Metrics& aOutMetrics, + bool& aOutUsedHyphenation, uint32_t& aOutLastBreak, + gfxBreakPriority& aBreakPriority) { + aMaxLength = std::min(aMaxLength, GetLength() - aStart); + + NS_ASSERTION(aStart + aMaxLength <= GetLength(), "Substring out of range"); + + Range bufferRange( + aStart, aStart + std::min<uint32_t>(aMaxLength, MEASUREMENT_BUFFER_SIZE)); + PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE]; + bool haveSpacing = !!(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING); + if (haveSpacing) { + GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer); + } + AutoTArray<HyphenType, 4096> hyphenBuffer; + HyphenationState wordState; + wordState.mostRecentBoundary = aStart; + bool haveHyphenation = + (aProvider.GetHyphensOption() == StyleHyphens::Auto || + (aProvider.GetHyphensOption() == StyleHyphens::Manual && + !!(mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS))); + if (haveHyphenation) { + if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) { + aProvider.GetHyphenationBreaks(bufferRange, hyphenBuffer.Elements()); + if (aProvider.GetHyphensOption() == StyleHyphens::Auto) { + ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer, &wordState); + } + } else { + haveHyphenation = false; + } + } + + gfxFloat width = 0; + gfxFloat advance = 0; + // The number of space characters that can be trimmed or hang at a soft-wrap + uint32_t trimmableChars = 0; + // The amount of space removed by ignoring trimmableChars + gfxFloat trimmableAdvance = 0; + int32_t lastBreak = -1; + int32_t lastBreakTrimmableChars = -1; + gfxFloat lastBreakTrimmableAdvance = -1; + // Cache the last candidate break + int32_t lastCandidateBreak = -1; + int32_t lastCandidateBreakTrimmableChars = -1; + gfxFloat lastCandidateBreakTrimmableAdvance = -1; + bool lastCandidateBreakUsedHyphenation = false; + gfxBreakPriority lastCandidateBreakPriority = gfxBreakPriority::eNoBreak; + bool aborted = false; + uint32_t end = aStart + aMaxLength; + bool lastBreakUsedHyphenation = false; + Range ligatureRange(aStart, end); + ShrinkToLigatureBoundaries(&ligatureRange); + + // We may need to move `i` backwards in the following loop, and re-scan + // part of the textrun; we'll use `rescanLimit` so we can tell when that + // is happening: if `i < rescanLimit` then we're rescanning. + uint32_t rescanLimit = aStart; + for (uint32_t i = aStart; i < end; ++i) { + if (i >= bufferRange.end) { + // Fetch more spacing and hyphenation data + uint32_t oldHyphenBufferLength = hyphenBuffer.Length(); + bufferRange.start = i; + bufferRange.end = + std::min(aStart + aMaxLength, i + MEASUREMENT_BUFFER_SIZE); + // For spacing, we always overwrite the old data with the newly + // fetched one. However, for hyphenation, hyphenation data sometimes + // depends on the context in every word (if "hyphens: auto" is set). + // To ensure we get enough information between neighboring buffers, + // we grow the hyphenBuffer instead of overwrite it. + // NOTE that this means bufferRange does not correspond to the + // entire hyphenBuffer, but only to the most recently added portion. + // Therefore, we need to add the old length to hyphenBuffer.Elements() + // when getting more data. + if (haveSpacing) { + GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer); + } + if (haveHyphenation) { + if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) { + aProvider.GetHyphenationBreaks( + bufferRange, hyphenBuffer.Elements() + oldHyphenBufferLength); + if (aProvider.GetHyphensOption() == StyleHyphens::Auto) { + uint32_t prevMostRecentWordBoundary = wordState.mostRecentBoundary; + ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer, + &wordState); + // If the buffer boundary is in the middle of a word, + // we need to go back to the start of the current word. + // So, we can correct the wrong candidates that we set + // in the previous runs of the loop. + if (prevMostRecentWordBoundary < oldHyphenBufferLength) { + rescanLimit = i; + i = prevMostRecentWordBoundary - 1; + continue; + } + } + } else { + haveHyphenation = false; + } + } + } + + // There can't be a word-wrap break opportunity at the beginning of the + // line: if the width is too small for even one character to fit, it + // could be the first and last break opportunity on the line, and that + // would trigger an infinite loop. + if (aSuppressBreak != eSuppressAllBreaks && + (aSuppressBreak != eSuppressInitialBreak || i > aStart)) { + bool atNaturalBreak = mCharacterGlyphs[i].CanBreakBefore() == 1; + // atHyphenationBreak indicates we're at a "soft" hyphen, where an extra + // hyphen glyph will need to be painted. It is NOT set for breaks at an + // explicit hyphen present in the text. + // + // NOTE(emilio): If you change this condition you also need to change + // nsTextFrame::AddInlineMinISizeForFlow to match. + bool atHyphenationBreak = !atNaturalBreak && haveHyphenation && + IsOptionalHyphenBreak(hyphenBuffer[i - aStart]); + bool atAutoHyphenWithManualHyphenInSameWord = + atHyphenationBreak && + hyphenBuffer[i - aStart] == HyphenType::AutoWithManualInSameWord; + bool atBreak = atNaturalBreak || atHyphenationBreak; + bool wordWrapping = aCanWordWrap && + mCharacterGlyphs[i].IsClusterStart() && + aBreakPriority <= gfxBreakPriority::eWordWrapBreak; + + bool whitespaceWrapping = false; + if (i > aStart) { + // The spec says the breaking opportunity is *after* whitespace. + auto const& g = mCharacterGlyphs[i - 1]; + whitespaceWrapping = + aCanWhitespaceWrap && + (g.CharIsSpace() || g.CharIsTab() || g.CharIsNewline()); + } + + if (atBreak || wordWrapping || whitespaceWrapping) { + gfxFloat hyphenatedAdvance = advance; + if (atHyphenationBreak) { + hyphenatedAdvance += aProvider.GetHyphenWidth(); + } + + if (lastBreak < 0 || + width + hyphenatedAdvance - trimmableAdvance <= aWidth) { + // We can break here. + lastBreak = i; + lastBreakTrimmableChars = trimmableChars; + lastBreakTrimmableAdvance = trimmableAdvance; + lastBreakUsedHyphenation = atHyphenationBreak; + aBreakPriority = (atBreak || whitespaceWrapping) + ? gfxBreakPriority::eNormalBreak + : gfxBreakPriority::eWordWrapBreak; + } + + width += advance; + advance = 0; + if (width - trimmableAdvance > aWidth) { + // No more text fits. Abort + aborted = true; + break; + } + // There are various kinds of break opportunities: + // 1. word wrap break, + // 2. natural break, + // 3. manual hyphenation break, + // 4. auto hyphenation break without any manual hyphenation + // in the same word, + // 5. auto hyphenation break with another manual hyphenation + // in the same word. + // Allow all of them except the last one to be a candidate. + // So, we can ensure that we don't use an automatic + // hyphenation opportunity within a word that contains another + // manual hyphenation, unless it is the only choice. + if (wordWrapping || !atAutoHyphenWithManualHyphenInSameWord) { + lastCandidateBreak = lastBreak; + lastCandidateBreakTrimmableChars = lastBreakTrimmableChars; + lastCandidateBreakTrimmableAdvance = lastBreakTrimmableAdvance; + lastCandidateBreakUsedHyphenation = lastBreakUsedHyphenation; + lastCandidateBreakPriority = aBreakPriority; + } + } + } + + // If we're re-scanning part of a word (to re-process potential + // hyphenation types) then we don't want to accumulate widths again + // for the characters that were already added to `advance`. + if (i < rescanLimit) { + continue; + } + + gfxFloat charAdvance; + if (i >= ligatureRange.start && i < ligatureRange.end) { + charAdvance = GetAdvanceForGlyphs(Range(i, i + 1)); + if (haveSpacing) { + PropertyProvider::Spacing* space = + &spacingBuffer[i - bufferRange.start]; + charAdvance += space->mBefore + space->mAfter; + } + } else { + charAdvance = ComputePartialLigatureWidth(Range(i, i + 1), &aProvider); + } + + advance += charAdvance; + if (aOutTrimmableWhitespace) { + if (mCharacterGlyphs[i].CharIsSpace()) { + ++trimmableChars; + trimmableAdvance += charAdvance; + } else { + trimmableAdvance = 0; + trimmableChars = 0; + } + } + } + + if (!aborted) { + width += advance; + } + + // There are three possibilities: + // 1) all the text fit (width <= aWidth) + // 2) some of the text fit up to a break opportunity (width > aWidth && + // lastBreak >= 0) + // 3) none of the text fits before a break opportunity (width > aWidth && + // lastBreak < 0) + uint32_t charsFit; + aOutUsedHyphenation = false; + if (width - trimmableAdvance <= aWidth) { + charsFit = aMaxLength; + } else if (lastBreak >= 0) { + if (lastCandidateBreak >= 0 && lastCandidateBreak != lastBreak) { + lastBreak = lastCandidateBreak; + lastBreakTrimmableChars = lastCandidateBreakTrimmableChars; + lastBreakTrimmableAdvance = lastCandidateBreakTrimmableAdvance; + lastBreakUsedHyphenation = lastCandidateBreakUsedHyphenation; + aBreakPriority = lastCandidateBreakPriority; + } + charsFit = lastBreak - aStart; + trimmableChars = lastBreakTrimmableChars; + trimmableAdvance = lastBreakTrimmableAdvance; + aOutUsedHyphenation = lastBreakUsedHyphenation; + } else { + charsFit = aMaxLength; + } + + // Get the overall metrics of the range that fit (including any potentially + // trimmable or hanging whitespace). + aOutMetrics = MeasureText(Range(aStart, aStart + charsFit), aBoundingBoxType, + aRefDrawTarget, &aProvider); + + if (aOutTrimmableWhitespace) { + aOutTrimmableWhitespace->mAdvance = trimmableAdvance; + aOutTrimmableWhitespace->mCount = trimmableChars; + } + + if (charsFit == aMaxLength) { + if (lastBreak < 0) { + aOutLastBreak = UINT32_MAX; + } else { + aOutLastBreak = lastBreak - aStart; + } + } + + return charsFit; +} + +gfxFloat gfxTextRun::GetAdvanceWidth( + Range aRange, const PropertyProvider* aProvider, + PropertyProvider::Spacing* aSpacing) const { + NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range"); + + Range ligatureRange = aRange; + bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange); + + gfxFloat result = + adjusted ? ComputePartialLigatureWidth( + Range(aRange.start, ligatureRange.start), aProvider) + + ComputePartialLigatureWidth( + Range(ligatureRange.end, aRange.end), aProvider) + : 0.0; + + if (aSpacing) { + aSpacing->mBefore = aSpacing->mAfter = 0; + } + + // Account for all remaining spacing here. This is more efficient than + // processing it along with the glyphs. + if (aProvider && (mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) { + uint32_t i; + AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer; + if (spacingBuffer.AppendElements(aRange.Length(), fallible)) { + GetAdjustedSpacing(this, ligatureRange, *aProvider, + spacingBuffer.Elements()); + for (i = 0; i < ligatureRange.Length(); ++i) { + PropertyProvider::Spacing* space = &spacingBuffer[i]; + result += space->mBefore + space->mAfter; + } + if (aSpacing) { + aSpacing->mBefore = spacingBuffer[0].mBefore; + aSpacing->mAfter = spacingBuffer.LastElement().mAfter; + } + } + } + + return result + GetAdvanceForGlyphs(ligatureRange); +} + +gfxFloat gfxTextRun::GetMinAdvanceWidth(Range aRange) { + MOZ_ASSERT(aRange.end <= GetLength(), "Substring out of range"); + + Range ligatureRange = aRange; + bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange); + + gfxFloat result = + adjusted + ? std::max(ComputePartialLigatureWidth( + Range(aRange.start, ligatureRange.start), nullptr), + ComputePartialLigatureWidth( + Range(ligatureRange.end, aRange.end), nullptr)) + : 0.0; + + // Compute min advance width by assuming each grapheme cluster takes its own + // line. + gfxFloat clusterAdvance = 0; + for (uint32_t i = ligatureRange.start; i < ligatureRange.end; ++i) { + if (mCharacterGlyphs[i].CharIsSpace()) { + // Skip space char to prevent its advance width contributing to the + // result. That is, don't consider a space can be in its own line. + continue; + } + clusterAdvance += GetAdvanceForGlyph(i); + if (i + 1 == ligatureRange.end || IsClusterStart(i + 1)) { + result = std::max(result, clusterAdvance); + clusterAdvance = 0; + } + } + + return result; +} + +bool gfxTextRun::SetLineBreaks(Range aRange, bool aLineBreakBefore, + bool aLineBreakAfter, + gfxFloat* aAdvanceWidthDelta) { + // Do nothing because our shaping does not currently take linebreaks into + // account. There is no change in advance width. + if (aAdvanceWidthDelta) { + *aAdvanceWidthDelta = 0; + } + return false; +} + +const gfxTextRun::GlyphRun* gfxTextRun::FindFirstGlyphRunContaining( + uint32_t aOffset) const { + MOZ_ASSERT(aOffset <= GetLength(), "Bad offset looking for glyphrun"); + MOZ_ASSERT(GetLength() == 0 || !mGlyphRuns.IsEmpty(), + "non-empty text but no glyph runs present!"); + if (mGlyphRuns.Length() <= 1) { + return mGlyphRuns.begin(); + } + if (aOffset == GetLength()) { + return mGlyphRuns.end() - 1; + } + const auto* start = mGlyphRuns.begin(); + const auto* limit = mGlyphRuns.end(); + while (limit - start > 1) { + const auto* mid = start + (limit - start) / 2; + if (mid->mCharacterOffset <= aOffset) { + start = mid; + } else { + limit = mid; + } + } + MOZ_ASSERT(start->mCharacterOffset <= aOffset, + "Hmm, something went wrong, aOffset should have been found"); + return start; +} + +void gfxTextRun::AddGlyphRun(gfxFont* aFont, FontMatchType aMatchType, + uint32_t aUTF16Offset, bool aForceNewRun, + gfx::ShapedTextFlags aOrientation, bool aIsCJK) { + MOZ_ASSERT(aFont, "adding glyph run for null font!"); + MOZ_ASSERT(aOrientation != gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED, + "mixed orientation should have been resolved"); + if (!aFont) { + return; + } + + if (mGlyphRuns.IsEmpty()) { + mGlyphRuns.AppendElement( + GlyphRun{aFont, aUTF16Offset, aOrientation, aMatchType, aIsCJK}); + return; + } + + uint32_t numGlyphRuns = mGlyphRuns.Length(); + if (!aForceNewRun) { + GlyphRun* lastGlyphRun = &mGlyphRuns.LastElement(); + + MOZ_ASSERT(lastGlyphRun->mCharacterOffset <= aUTF16Offset, + "Glyph runs out of order (and run not forced)"); + + // Don't append a run if the font is already the one we want + if (lastGlyphRun->Matches(aFont, aOrientation, aIsCJK, aMatchType)) { + return; + } + + // If the offset has not changed, avoid leaving a zero-length run + // by overwriting the last entry instead of appending... + if (lastGlyphRun->mCharacterOffset == aUTF16Offset) { + // ...except that if the run before the last entry had the same + // font as the new one wants, merge with it instead of creating + // adjacent runs with the same font + if (numGlyphRuns > 1 && mGlyphRuns[numGlyphRuns - 2].Matches( + aFont, aOrientation, aIsCJK, aMatchType)) { + mGlyphRuns.TruncateLength(numGlyphRuns - 1); + return; + } + + lastGlyphRun->SetProperties(aFont, aOrientation, aIsCJK, aMatchType); + return; + } + } + + MOZ_ASSERT( + aForceNewRun || numGlyphRuns > 0 || aUTF16Offset == 0, + "First run doesn't cover the first character (and run not forced)?"); + + mGlyphRuns.AppendElement( + GlyphRun{aFont, aUTF16Offset, aOrientation, aMatchType, aIsCJK}); +} + +void gfxTextRun::SanitizeGlyphRuns() { + if (mGlyphRuns.Length() < 2) { + return; + } + + auto& runs = mGlyphRuns.Array(); + + // The runs are almost certain to be already sorted, so it's worth avoiding + // the Sort() call if possible. + bool isSorted = true; + uint32_t prevOffset = 0; + for (const auto& r : runs) { + if (r.mCharacterOffset < prevOffset) { + isSorted = false; + break; + } + prevOffset = r.mCharacterOffset; + } + if (!isSorted) { + runs.Sort(GlyphRunOffsetComparator()); + } + + // Coalesce adjacent glyph runs that have the same properties, and eliminate + // any empty runs. + GlyphRun* prevRun = nullptr; + const CompressedGlyph* charGlyphs = mCharacterGlyphs; + + runs.RemoveElementsBy([&](GlyphRun& aRun) -> bool { + // First run is always retained. + if (!prevRun) { + prevRun = &aRun; + return false; + } + + // Merge any run whose properties match its predecessor. + if (prevRun->Matches(aRun.mFont, aRun.mOrientation, aRun.mIsCJK, + aRun.mMatchType)) { + return true; + } + + if (prevRun->mCharacterOffset >= aRun.mCharacterOffset) { + // Preceding run is empty (or has become so due to the adjusting for + // ligature boundaries), so we will overwrite it with this one, which + // will then be discarded. + *prevRun = aRun; + return true; + } + + // If any glyph run starts with ligature-continuation characters, we need to + // advance it to the first "real" character to avoid drawing partial + // ligature glyphs from wrong font (seen with U+FEFF in reftest 474417-1, as + // Core Text eliminates the glyph, which makes it appear as if a ligature + // has been formed) + while (charGlyphs[aRun.mCharacterOffset].IsLigatureContinuation() && + aRun.mCharacterOffset < GetLength()) { + aRun.mCharacterOffset++; + } + + // We're keeping another run, so update prevRun pointer to refer to it (in + // its new position). + ++prevRun; + return false; + }); + + MOZ_ASSERT(prevRun == &runs.LastElement(), "lost track of prevRun!"); + + // Drop any trailing empty run. + if (runs.Length() > 1 && prevRun->mCharacterOffset == GetLength()) { + runs.RemoveLastElement(); + } + + MOZ_ASSERT(!runs.IsEmpty()); + if (runs.Length() == 1) { + mGlyphRuns.ConvertToElement(); + } +} + +void gfxTextRun::CopyGlyphDataFrom(gfxShapedWord* aShapedWord, + uint32_t aOffset) { + uint32_t wordLen = aShapedWord->GetLength(); + MOZ_ASSERT(aOffset + wordLen <= GetLength(), "word overruns end of textrun"); + + CompressedGlyph* charGlyphs = GetCharacterGlyphs(); + const CompressedGlyph* wordGlyphs = aShapedWord->GetCharacterGlyphs(); + if (aShapedWord->HasDetailedGlyphs()) { + for (uint32_t i = 0; i < wordLen; ++i, ++aOffset) { + const CompressedGlyph& g = wordGlyphs[i]; + if (!g.IsSimpleGlyph()) { + const DetailedGlyph* details = + g.GetGlyphCount() > 0 ? aShapedWord->GetDetailedGlyphs(i) : nullptr; + SetDetailedGlyphs(aOffset, g.GetGlyphCount(), details); + } + charGlyphs[aOffset] = g; + } + } else { + memcpy(charGlyphs + aOffset, wordGlyphs, wordLen * sizeof(CompressedGlyph)); + } +} + +void gfxTextRun::CopyGlyphDataFrom(gfxTextRun* aSource, Range aRange, + uint32_t aDest) { + MOZ_ASSERT(aRange.end <= aSource->GetLength(), + "Source substring out of range"); + MOZ_ASSERT(aDest + aRange.Length() <= GetLength(), + "Destination substring out of range"); + + if (aSource->mDontSkipDrawing) { + mDontSkipDrawing = true; + } + + // Copy base glyph data, and DetailedGlyph data where present + const CompressedGlyph* srcGlyphs = aSource->mCharacterGlyphs + aRange.start; + CompressedGlyph* dstGlyphs = mCharacterGlyphs + aDest; + for (uint32_t i = 0; i < aRange.Length(); ++i) { + CompressedGlyph g = srcGlyphs[i]; + g.SetCanBreakBefore(!g.IsClusterStart() + ? CompressedGlyph::FLAG_BREAK_TYPE_NONE + : dstGlyphs[i].CanBreakBefore()); + if (!g.IsSimpleGlyph()) { + uint32_t count = g.GetGlyphCount(); + if (count > 0) { + // DetailedGlyphs allocation is infallible, so this should never be + // null unless the source textrun is somehow broken. + DetailedGlyph* src = aSource->GetDetailedGlyphs(i + aRange.start); + MOZ_ASSERT(src, "missing DetailedGlyphs?"); + if (src) { + DetailedGlyph* dst = AllocateDetailedGlyphs(i + aDest, count); + ::memcpy(dst, src, count * sizeof(DetailedGlyph)); + } else { + g.SetMissing(); + } + } + } + dstGlyphs[i] = g; + } + + // Copy glyph runs +#ifdef DEBUG + GlyphRun* prevRun = nullptr; +#endif + for (GlyphRunIterator iter(aSource, aRange); !iter.AtEnd(); iter.NextRun()) { + gfxFont* font = iter.GlyphRun()->mFont; + MOZ_ASSERT(!prevRun || !prevRun->Matches(iter.GlyphRun()->mFont, + iter.GlyphRun()->mOrientation, + iter.GlyphRun()->mIsCJK, + FontMatchType::Kind::kUnspecified), + "Glyphruns not coalesced?"); +#ifdef DEBUG + prevRun = const_cast<GlyphRun*>(iter.GlyphRun()); + uint32_t end = iter.StringEnd(); +#endif + uint32_t start = iter.StringStart(); + + // These used to be NS_ASSERTION()s, but WARNING is more appropriate. + // Although it's unusual (and not desirable), it's possible for us to assign + // different fonts to a base character and a following diacritic. + // Example on OSX 10.5/10.6 with default fonts installed: + // data:text/html,<p style="font-family:helvetica, arial, sans-serif;"> + // &%23x043E;&%23x0486;&%23x20;&%23x043E;&%23x0486; + // This means the rendering of the cluster will probably not be very good, + // but it's the best we can do for now if the specified font only covered + // the initial base character and not its applied marks. + NS_WARNING_ASSERTION(aSource->IsClusterStart(start), + "Started font run in the middle of a cluster"); + NS_WARNING_ASSERTION( + end == aSource->GetLength() || aSource->IsClusterStart(end), + "Ended font run in the middle of a cluster"); + + AddGlyphRun(font, iter.GlyphRun()->mMatchType, start - aRange.start + aDest, + false, iter.GlyphRun()->mOrientation, iter.GlyphRun()->mIsCJK); + } +} + +void gfxTextRun::ClearGlyphsAndCharacters() { + ResetGlyphRuns(); + memset(reinterpret_cast<char*>(mCharacterGlyphs), 0, + mLength * sizeof(CompressedGlyph)); + mDetailedGlyphs = nullptr; +} + +void gfxTextRun::SetSpaceGlyph(gfxFont* aFont, DrawTarget* aDrawTarget, + uint32_t aCharIndex, + gfx::ShapedTextFlags aOrientation) { + if (SetSpaceGlyphIfSimple(aFont, aCharIndex, ' ', aOrientation)) { + return; + } + + gfx::ShapedTextFlags flags = + gfx::ShapedTextFlags::TEXT_IS_8BIT | aOrientation; + bool vertical = + !!(GetFlags() & gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT); + gfxFontShaper::RoundingFlags roundingFlags = + aFont->GetRoundOffsetsToPixels(aDrawTarget); + aFont->ProcessSingleSpaceShapedWord( + aDrawTarget, vertical, mAppUnitsPerDevUnit, flags, roundingFlags, + [&](gfxShapedWord* aShapedWord) { + const GlyphRun* prevRun = TrailingGlyphRun(); + bool isCJK = prevRun && prevRun->mFont == aFont && + prevRun->mOrientation == aOrientation + ? prevRun->mIsCJK + : false; + AddGlyphRun(aFont, FontMatchType::Kind::kUnspecified, aCharIndex, false, + aOrientation, isCJK); + CopyGlyphDataFrom(aShapedWord, aCharIndex); + GetCharacterGlyphs()[aCharIndex].SetIsSpace(); + }); +} + +bool gfxTextRun::SetSpaceGlyphIfSimple(gfxFont* aFont, uint32_t aCharIndex, + char16_t aSpaceChar, + gfx::ShapedTextFlags aOrientation) { + uint32_t spaceGlyph = aFont->GetSpaceGlyph(); + if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) { + return false; + } + + gfxFont::Orientation fontOrientation = + (aOrientation & gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) + ? nsFontMetrics::eVertical + : nsFontMetrics::eHorizontal; + uint32_t spaceWidthAppUnits = NS_lroundf( + aFont->GetMetrics(fontOrientation).spaceWidth * mAppUnitsPerDevUnit); + if (!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) { + return false; + } + + const GlyphRun* prevRun = TrailingGlyphRun(); + bool isCJK = prevRun && prevRun->mFont == aFont && + prevRun->mOrientation == aOrientation + ? prevRun->mIsCJK + : false; + AddGlyphRun(aFont, FontMatchType::Kind::kUnspecified, aCharIndex, false, + aOrientation, isCJK); + CompressedGlyph g = + CompressedGlyph::MakeSimpleGlyph(spaceWidthAppUnits, spaceGlyph); + if (aSpaceChar == ' ') { + g.SetIsSpace(); + } + GetCharacterGlyphs()[aCharIndex] = g; + return true; +} + +void gfxTextRun::FetchGlyphExtents(DrawTarget* aRefDrawTarget) const { + bool needsGlyphExtents = NeedsGlyphExtents(); + if (!needsGlyphExtents && !mDetailedGlyphs) { + return; + } + + uint32_t runCount; + const GlyphRun* glyphRuns = GetGlyphRuns(&runCount); + CompressedGlyph* charGlyphs = mCharacterGlyphs; + for (uint32_t i = 0; i < runCount; ++i) { + const GlyphRun& run = glyphRuns[i]; + gfxFont* font = run.mFont; + if (MOZ_UNLIKELY(font->GetStyle()->AdjustedSizeMustBeZero())) { + continue; + } + + uint32_t start = run.mCharacterOffset; + uint32_t end = + i + 1 < runCount ? glyphRuns[i + 1].mCharacterOffset : GetLength(); + gfxGlyphExtents* extents = + font->GetOrCreateGlyphExtents(mAppUnitsPerDevUnit); + + AutoReadLock lock(extents->mLock); + for (uint32_t j = start; j < end; ++j) { + const gfxTextRun::CompressedGlyph* glyphData = &charGlyphs[j]; + if (glyphData->IsSimpleGlyph()) { + // If we're in speed mode, don't set up glyph extents here; we'll + // just return "optimistic" glyph bounds later + if (needsGlyphExtents) { + uint32_t glyphIndex = glyphData->GetSimpleGlyph(); + if (!extents->IsGlyphKnownLocked(glyphIndex)) { +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + ++gGlyphExtentsSetupEagerSimple; +#endif + extents->mLock.ReadUnlock(); + font->SetupGlyphExtents(aRefDrawTarget, glyphIndex, false, extents); + extents->mLock.ReadLock(); + } + } + } else if (!glyphData->IsMissing()) { + uint32_t glyphCount = glyphData->GetGlyphCount(); + if (glyphCount == 0) { + continue; + } + const gfxTextRun::DetailedGlyph* details = GetDetailedGlyphs(j); + if (!details) { + continue; + } + for (uint32_t k = 0; k < glyphCount; ++k, ++details) { + uint32_t glyphIndex = details->mGlyphID; + if (!extents->IsGlyphKnownWithTightExtentsLocked(glyphIndex)) { +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + ++gGlyphExtentsSetupEagerTight; +#endif + extents->mLock.ReadUnlock(); + font->SetupGlyphExtents(aRefDrawTarget, glyphIndex, true, extents); + extents->mLock.ReadLock(); + } + } + } + } + } +} + +size_t gfxTextRun::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) { + size_t total = mGlyphRuns.ShallowSizeOfExcludingThis(aMallocSizeOf); + + if (mDetailedGlyphs) { + total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf); + } + + return total; +} + +size_t gfxTextRun::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +#ifdef DEBUG_FRAME_DUMP +void gfxTextRun::Dump(FILE* out) { +# define APPEND_FLAG(string_, enum_, field_, flag_) \ + if (field_ & enum_::flag_) { \ + string_.AppendPrintf(remaining != field_ ? " %s" : "%s", #flag_); \ + remaining &= ~enum_::flag_; \ + } +# define APPEND_FLAGS(string_, enum_, field_, flags_) \ + { \ + auto remaining = field_; \ + MOZ_FOR_EACH(APPEND_FLAG, (string_, enum_, field_, ), flags_) \ + if (int(remaining)) { \ + string_.AppendPrintf(" %s(0x%0x)", #enum_, int(remaining)); \ + } \ + } + + nsCString flagsString; + ShapedTextFlags orient = mFlags & ShapedTextFlags::TEXT_ORIENT_MASK; + ShapedTextFlags otherFlags = mFlags & ~ShapedTextFlags::TEXT_ORIENT_MASK; + APPEND_FLAGS(flagsString, ShapedTextFlags, otherFlags, + (TEXT_IS_RTL, TEXT_ENABLE_SPACING, TEXT_IS_8BIT, + TEXT_ENABLE_HYPHEN_BREAKS, TEXT_NEED_BOUNDING_BOX, + TEXT_DISABLE_OPTIONAL_LIGATURES, TEXT_OPTIMIZE_SPEED, + TEXT_HIDE_CONTROL_CHARACTERS, TEXT_TRAILING_ARABICCHAR, + TEXT_INCOMING_ARABICCHAR, TEXT_USE_MATH_SCRIPT)) + + if (orient != ShapedTextFlags::TEXT_ORIENT_HORIZONTAL && + !flagsString.IsEmpty()) { + flagsString += ' '; + } + + switch (orient) { + case ShapedTextFlags::TEXT_ORIENT_HORIZONTAL: + break; + case ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT: + flagsString += "TEXT_ORIENT_VERTICAL_UPRIGHT"; + break; + case ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT: + flagsString += "TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT"; + break; + case ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED: + flagsString += "TEXT_ORIENT_VERTICAL_MIXED"; + break; + case ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT: + flagsString += "TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT"; + break; + default: + flagsString.AppendPrintf("UNKNOWN_TEXT_ORIENT_MASK(0x%0x)", int(orient)); + break; + } + + nsCString flags2String; + APPEND_FLAGS( + flags2String, nsTextFrameUtils::Flags, mFlags2, + (HasTab, HasShy, DontSkipDrawingForPendingUserFonts, IsSimpleFlow, + IncomingWhitespace, TrailingWhitespace, CompressedLeadingWhitespace, + NoBreaks, IsTransformed, HasTrailingBreak, IsSingleCharMi, + MightHaveGlyphChanges, RunSizeAccounted)) + +# undef APPEND_FLAGS +# undef APPEND_FLAG + + nsAutoCString lang; + mFontGroup->Language()->ToUTF8String(lang); + fprintf(out, "gfxTextRun@%p (length %u) [%s] [%s] [%s]\n", this, mLength, + flagsString.get(), flags2String.get(), lang.get()); + + fprintf(out, " Glyph runs:\n"); + for (const auto& run : mGlyphRuns) { + gfxFont* font = run.mFont; + const gfxFontStyle* style = font->GetStyle(); + nsAutoCString styleString; + style->style.ToString(styleString); + fprintf(out, " offset=%d %s %f/%g/%s\n", run.mCharacterOffset, + font->GetName().get(), style->size, style->weight.ToFloat(), + styleString.get()); + } + + fprintf(out, " Glyphs:\n"); + for (uint32_t i = 0; i < mLength; ++i) { + auto glyphData = GetCharacterGlyphs()[i]; + + nsCString line; + line.AppendPrintf(" [%d] 0x%p %s", i, GetCharacterGlyphs() + i, + glyphData.IsSimpleGlyph() ? "simple" : "detailed"); + + if (glyphData.IsSimpleGlyph()) { + line.AppendPrintf(" id=%d adv=%d", glyphData.GetSimpleGlyph(), + glyphData.GetSimpleAdvance()); + } else { + uint32_t count = glyphData.GetGlyphCount(); + if (count) { + line += " ids="; + for (uint32_t j = 0; j < count; j++) { + line.AppendPrintf(j ? ",%d" : "%d", GetDetailedGlyphs(i)[j].mGlyphID); + } + line += " advs="; + for (uint32_t j = 0; j < count; j++) { + line.AppendPrintf(j ? ",%d" : "%d", GetDetailedGlyphs(i)[j].mAdvance); + } + line += " offsets="; + for (uint32_t j = 0; j < count; j++) { + auto offset = GetDetailedGlyphs(i)[j].mOffset; + line.AppendPrintf(j ? ",(%g,%g)" : "(%g,%g)", offset.x.value, + offset.y.value); + } + } else { + line += " (no glyphs)"; + } + } + + if (glyphData.CharIsSpace()) { + line += " CHAR_IS_SPACE"; + } + if (glyphData.CharIsTab()) { + line += " CHAR_IS_TAB"; + } + if (glyphData.CharIsNewline()) { + line += " CHAR_IS_NEWLINE"; + } + if (glyphData.CharIsFormattingControl()) { + line += " CHAR_IS_FORMATTING_CONTROL"; + } + if (glyphData.CharTypeFlags() & + CompressedGlyph::FLAG_CHAR_NO_EMPHASIS_MARK) { + line += " CHAR_NO_EMPHASIS_MARK"; + } + + if (!glyphData.IsSimpleGlyph()) { + if (!glyphData.IsMissing()) { + line += " NOT_MISSING"; + } + if (!glyphData.IsClusterStart()) { + line += " NOT_IS_CLUSTER_START"; + } + if (!glyphData.IsLigatureGroupStart()) { + line += " NOT_LIGATURE_GROUP_START"; + } + } + + switch (glyphData.CanBreakBefore()) { + case CompressedGlyph::FLAG_BREAK_TYPE_NORMAL: + line += " BREAK_TYPE_NORMAL"; + break; + case CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN: + line += " BREAK_TYPE_HYPHEN"; + break; + } + + fprintf(out, "%s\n", line.get()); + } +} +#endif + +gfxFontGroup::gfxFontGroup(nsPresContext* aPresContext, + const StyleFontFamilyList& aFontFamilyList, + const gfxFontStyle* aStyle, nsAtom* aLanguage, + bool aExplicitLanguage, + gfxTextPerfMetrics* aTextPerf, + gfxUserFontSet* aUserFontSet, gfxFloat aDevToCssSize, + StyleFontVariantEmoji aVariantEmoji) + : mPresContext(aPresContext), // Note that aPresContext may be null! + mFamilyList(aFontFamilyList), + mStyle(*aStyle), + mLanguage(aLanguage), + mUnderlineOffset(UNDERLINE_OFFSET_NOT_SET), + mHyphenWidth(-1), + mDevToCssSize(aDevToCssSize), + mUserFontSet(aUserFontSet), + mTextPerf(aTextPerf), + mLastPrefLang(eFontPrefLang_Western), + mPageLang(gfxPlatformFontList::GetFontPrefLangFor(aLanguage)), + mLastPrefFirstFont(false), + mSkipDrawing(false), + mExplicitLanguage(aExplicitLanguage) { + switch (aVariantEmoji) { + case StyleFontVariantEmoji::Normal: + case StyleFontVariantEmoji::Unicode: + break; + case StyleFontVariantEmoji::Text: + mEmojiPresentation = eFontPresentation::Text; + break; + case StyleFontVariantEmoji::Emoji: + mEmojiPresentation = eFontPresentation::EmojiExplicit; + break; + } + // We don't use SetUserFontSet() here, as we want to unconditionally call + // BuildFontList() rather than only do UpdateUserFonts() if it changed. + mCurrGeneration = GetGeneration(); + BuildFontList(); +} + +gfxFontGroup::~gfxFontGroup() { + // Should not be dropped by stylo + MOZ_ASSERT(!Servo_IsWorkerThread()); +} + +static StyleGenericFontFamily GetDefaultGeneric(nsAtom* aLanguage) { + return StaticPresData::Get() + ->GetFontPrefsForLang(aLanguage) + ->GetDefaultGeneric(); +} + +void gfxFontGroup::BuildFontList() { + // initialize fonts in the font family list + AutoTArray<FamilyAndGeneric, 10> fonts; + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + mFontListGeneration = pfl->GetGeneration(); + + // lookup fonts in the fontlist + for (const StyleSingleFontFamily& name : mFamilyList.list.AsSpan()) { + if (name.IsFamilyName()) { + const auto& familyName = name.AsFamilyName(); + AddPlatformFont(nsAtomCString(familyName.name.AsAtom()), + familyName.syntax == StyleFontFamilyNameSyntax::Quoted, + fonts); + } else { + MOZ_ASSERT(name.IsGeneric()); + const StyleGenericFontFamily generic = name.AsGeneric(); + // system-ui is usually a single family, so it doesn't work great as + // fallback. Prefer the following generic or the language default instead. + if (mFallbackGeneric == StyleGenericFontFamily::None && + generic != StyleGenericFontFamily::SystemUi) { + mFallbackGeneric = generic; + } + pfl->AddGenericFonts(mPresContext, generic, mLanguage, fonts); + if (mTextPerf) { + mTextPerf->current.genericLookups++; + } + } + } + + // If necessary, append default language generic onto the end. + if (mFallbackGeneric == StyleGenericFontFamily::None && !mStyle.systemFont) { + auto defaultLanguageGeneric = GetDefaultGeneric(mLanguage); + + pfl->AddGenericFonts(mPresContext, defaultLanguageGeneric, mLanguage, + fonts); + if (mTextPerf) { + mTextPerf->current.genericLookups++; + } + } + + // build the fontlist from the specified families + for (const auto& f : fonts) { + if (f.mFamily.mShared) { + AddFamilyToFontList(f.mFamily.mShared, f.mGeneric); + } else { + AddFamilyToFontList(f.mFamily.mUnshared, f.mGeneric); + } + } +} + +void gfxFontGroup::AddPlatformFont(const nsACString& aName, bool aQuotedName, + nsTArray<FamilyAndGeneric>& aFamilyList) { + // First, look up in the user font set... + // If the fontSet matches the family, we must not look for a platform + // font of the same name, even if we fail to actually get a fontEntry + // here; we'll fall back to the next name in the CSS font-family list. + if (mUserFontSet) { + // Add userfonts to the fontlist whether already loaded + // or not. Loading is initiated during font matching. + RefPtr<gfxFontFamily> family = mUserFontSet->LookupFamily(aName); + if (family) { + aFamilyList.AppendElement(std::move(family)); + return; + } + } + + // Not known in the user font set ==> check system fonts + gfxPlatformFontList::PlatformFontList()->FindAndAddFamilies( + mPresContext, StyleGenericFontFamily::None, aName, &aFamilyList, + aQuotedName ? gfxPlatformFontList::FindFamiliesFlags::eQuotedFamilyName + : gfxPlatformFontList::FindFamiliesFlags(0), + &mStyle, mLanguage.get(), mDevToCssSize); +} + +void gfxFontGroup::AddFamilyToFontList(gfxFontFamily* aFamily, + StyleGenericFontFamily aGeneric) { + if (!aFamily) { + MOZ_ASSERT_UNREACHABLE("don't try to add a null font family!"); + return; + } + AutoTArray<gfxFontEntry*, 4> fontEntryList; + aFamily->FindAllFontsForStyle(mStyle, fontEntryList); + // add these to the fontlist + for (gfxFontEntry* fe : fontEntryList) { + if (!HasFont(fe)) { + FamilyFace ff(aFamily, fe, aGeneric); + if (fe->mIsUserFontContainer) { + ff.CheckState(mSkipDrawing); + } + mFonts.AppendElement(ff); + } + } + // for a family marked as "check fallback faces", only mark the last + // entry so that fallbacks for a family are only checked once + if (aFamily->CheckForFallbackFaces() && !fontEntryList.IsEmpty() && + !mFonts.IsEmpty()) { + mFonts.LastElement().SetCheckForFallbackFaces(); + } +} + +void gfxFontGroup::AddFamilyToFontList(fontlist::Family* aFamily, + StyleGenericFontFamily aGeneric) { + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + if (!aFamily->IsInitialized()) { + if (ServoStyleSet* set = gfxFontUtils::CurrentServoStyleSet()) { + // If we need to initialize a Family record, but we're on a style + // worker thread, we have to defer it. + set->AppendTask(PostTraversalTask::InitializeFamily(aFamily)); + set->AppendTask(PostTraversalTask::FontInfoUpdate(set)); + return; + } + if (!pfl->InitializeFamily(aFamily)) { + return; + } + } + AutoTArray<fontlist::Face*, 4> faceList; + aFamily->FindAllFacesForStyle(pfl->SharedFontList(), mStyle, faceList); + for (auto* face : faceList) { + gfxFontEntry* fe = pfl->GetOrCreateFontEntry(face, aFamily); + if (fe && !HasFont(fe)) { + FamilyFace ff(aFamily, fe, aGeneric); + mFonts.AppendElement(ff); + } + } +} + +bool gfxFontGroup::HasFont(const gfxFontEntry* aFontEntry) { + for (auto& f : mFonts) { + if (f.FontEntry() == aFontEntry) { + return true; + } + } + return false; +} + +already_AddRefed<gfxFont> gfxFontGroup::GetFontAt(int32_t i, uint32_t aCh, + bool* aLoading) { + if (uint32_t(i) >= mFonts.Length()) { + return nullptr; + } + + FamilyFace& ff = mFonts[i]; + if (ff.IsInvalid() || ff.IsLoading()) { + return nullptr; + } + + RefPtr<gfxFont> font = ff.Font(); + if (!font) { + gfxFontEntry* fe = ff.FontEntry(); + if (!fe) { + return nullptr; + } + gfxCharacterMap* unicodeRangeMap = nullptr; + if (fe->mIsUserFontContainer) { + gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe); + if (ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED && + ufe->CharacterInUnicodeRange(aCh) && !*aLoading) { + ufe->Load(); + ff.CheckState(mSkipDrawing); + *aLoading = ff.IsLoading(); + } + fe = ufe->GetPlatformFontEntry(); + if (!fe) { + return nullptr; + } + unicodeRangeMap = ufe->GetUnicodeRangeMap(); + } + font = fe->FindOrMakeFont(&mStyle, unicodeRangeMap); + if (!font || !font->Valid()) { + ff.SetInvalid(); + return nullptr; + } + ff.SetFont(font); + } + return font.forget(); +} + +void gfxFontGroup::FamilyFace::CheckState(bool& aSkipDrawing) { + gfxFontEntry* fe = FontEntry(); + if (!fe) { + return; + } + if (fe->mIsUserFontContainer) { + gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe); + gfxUserFontEntry::UserFontLoadState state = ufe->LoadState(); + switch (state) { + case gfxUserFontEntry::STATUS_LOAD_PENDING: + case gfxUserFontEntry::STATUS_LOADING: + SetLoading(true); + break; + case gfxUserFontEntry::STATUS_FAILED: + SetInvalid(); + // fall-thru to the default case + [[fallthrough]]; + default: + SetLoading(false); + } + if (ufe->WaitForUserFont()) { + aSkipDrawing = true; + } + } +} + +bool gfxFontGroup::FamilyFace::EqualsUserFont( + const gfxUserFontEntry* aUserFont) const { + gfxFontEntry* fe = FontEntry(); + // if there's a font, the entry is the underlying platform font + if (mFontCreated) { + gfxFontEntry* pfe = aUserFont->GetPlatformFontEntry(); + if (pfe == fe) { + return true; + } + } else if (fe == aUserFont) { + return true; + } + return false; +} + +static nsAutoCString FamilyListToString( + const StyleFontFamilyList& aFamilyList) { + return StringJoin(","_ns, aFamilyList.list.AsSpan(), + [](nsACString& dst, const StyleSingleFontFamily& name) { + name.AppendToString(dst); + }); +} + +already_AddRefed<gfxFont> gfxFontGroup::GetDefaultFont() { + if (mDefaultFont) { + return do_AddRef(mDefaultFont); + } + + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + FontFamily family = pfl->GetDefaultFont(mPresContext, &mStyle); + MOZ_ASSERT(!family.IsNull(), + "invalid default font returned by GetDefaultFont"); + + gfxFontEntry* fe = nullptr; + if (family.mShared) { + fontlist::Family* fam = family.mShared; + if (!fam->IsInitialized()) { + // If this fails, FindFaceForStyle will just safely return nullptr + Unused << pfl->InitializeFamily(fam); + } + fontlist::Face* face = fam->FindFaceForStyle(pfl->SharedFontList(), mStyle); + if (face) { + fe = pfl->GetOrCreateFontEntry(face, fam); + } + } else { + fe = family.mUnshared->FindFontForStyle(mStyle); + } + if (fe) { + mDefaultFont = fe->FindOrMakeFont(&mStyle); + } + + uint32_t numInits, loaderState; + pfl->GetFontlistInitInfo(numInits, loaderState); + + MOZ_ASSERT(numInits != 0, + "must initialize system fontlist before getting default font!"); + + uint32_t numFonts = 0; + if (!mDefaultFont) { + // Try for a "font of last resort...." + // Because an empty font list would be Really Bad for later code + // that assumes it will be able to get valid metrics for layout, + // just look for the first usable font and put in the list. + // (see bug 554544) + if (pfl->SharedFontList()) { + fontlist::FontList* list = pfl->SharedFontList(); + numFonts = list->NumFamilies(); + fontlist::Family* families = list->Families(); + for (uint32_t i = 0; i < numFonts; ++i) { + fontlist::Family* fam = &families[i]; + if (!fam->IsInitialized()) { + Unused << pfl->InitializeFamily(fam); + } + fontlist::Face* face = + fam->FindFaceForStyle(pfl->SharedFontList(), mStyle); + if (face) { + fe = pfl->GetOrCreateFontEntry(face, fam); + if (fe) { + mDefaultFont = fe->FindOrMakeFont(&mStyle); + if (mDefaultFont) { + break; + } + NS_WARNING("FindOrMakeFont failed"); + } + } + } + } else { + AutoTArray<RefPtr<gfxFontFamily>, 200> familyList; + pfl->GetFontFamilyList(familyList); + numFonts = familyList.Length(); + for (uint32_t i = 0; i < numFonts; ++i) { + gfxFontEntry* fe = familyList[i]->FindFontForStyle(mStyle, true); + if (fe) { + mDefaultFont = fe->FindOrMakeFont(&mStyle); + if (mDefaultFont) { + break; + } + } + } + } + } + + if (!mDefaultFont && pfl->SharedFontList() && !XRE_IsParentProcess()) { + // If we're a content process, it's possible this is failing because the + // chrome process has just updated the shared font list and we haven't yet + // refreshed our reference to it. If that's the case, update and retry. + // But if we're not on the main thread, we can't do this, so just use + // the platform default font directly. + if (NS_IsMainThread()) { + uint32_t oldGeneration = pfl->SharedFontList()->GetGeneration(); + pfl->UpdateFontList(); + if (pfl->SharedFontList()->GetGeneration() != oldGeneration) { + return GetDefaultFont(); + } + } + } + + if (!mDefaultFont) { + // We must have failed to find anything usable in our font-family list, + // or it's badly broken. One more last-ditch effort to make a font: + gfxFontEntry* fe = pfl->GetDefaultFontEntry(); + if (fe) { + RefPtr<gfxFont> f = fe->FindOrMakeFont(&mStyle); + if (f) { + return f.forget(); + } + } + } + + if (!mDefaultFont) { + // an empty font list at this point is fatal; we're not going to + // be able to do even the most basic layout operations + + // annotate crash report with fontlist info + nsAutoCString fontInitInfo; + fontInitInfo.AppendPrintf("no fonts - init: %d fonts: %d loader: %d", + numInits, numFonts, loaderState); +#ifdef XP_WIN + bool dwriteEnabled = gfxWindowsPlatform::GetPlatform()->DWriteEnabled(); + double upTime = (double)GetTickCount(); + fontInitInfo.AppendPrintf(" backend: %s system-uptime: %9.3f sec", + dwriteEnabled ? "directwrite" : "gdi", + upTime / 1000); +#endif + gfxCriticalError() << fontInitInfo.get(); + + char msg[256]; // CHECK buffer length if revising message below + SprintfLiteral(msg, "unable to find a usable font (%.220s)", + FamilyListToString(mFamilyList).get()); + MOZ_CRASH_UNSAFE(msg); + } + + return do_AddRef(mDefaultFont); +} + +already_AddRefed<gfxFont> gfxFontGroup::GetFirstValidFont( + uint32_t aCh, StyleGenericFontFamily* aGeneric, bool* aIsFirst) { + // Ensure cached font instances are valid. + CheckForUpdatedPlatformList(); + + uint32_t count = mFonts.Length(); + bool loading = false; + + // Check whether the font supports the given character, unless the char is + // SPACE, in which case it is not required to be present in the font, but + // we must still check if it was excluded by a unicode-range descriptor. + auto isValidForChar = [](gfxFont* aFont, uint32_t aCh) -> bool { + if (!aFont) { + return false; + } + if (aCh == 0x20) { + if (const auto* unicodeRange = aFont->GetUnicodeRangeMap()) { + return unicodeRange->test(aCh); + } + return true; + } + return aFont->HasCharacter(aCh); + }; + + for (uint32_t i = 0; i < count; ++i) { + FamilyFace& ff = mFonts[i]; + if (ff.IsInvalid()) { + continue; + } + + // already have a font? + RefPtr<gfxFont> font = ff.Font(); + if (isValidForChar(font, aCh)) { + if (aGeneric) { + *aGeneric = ff.Generic(); + } + if (aIsFirst) { + *aIsFirst = (i == 0); + } + return font.forget(); + } + + // Need to build a font, loading userfont if not loaded. In + // cases where unicode range might apply, use the character + // provided. + gfxFontEntry* fe = ff.FontEntry(); + if (fe && fe->mIsUserFontContainer) { + gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe); + bool inRange = ufe->CharacterInUnicodeRange(aCh); + if (inRange) { + if (!loading && + ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED) { + ufe->Load(); + ff.CheckState(mSkipDrawing); + } + if (ff.IsLoading()) { + loading = true; + } + } + if (ufe->LoadState() != gfxUserFontEntry::STATUS_LOADED || !inRange) { + continue; + } + } + + font = GetFontAt(i, aCh, &loading); + if (isValidForChar(font, aCh)) { + if (aGeneric) { + *aGeneric = ff.Generic(); + } + if (aIsFirst) { + *aIsFirst = (i == 0); + } + return font.forget(); + } + } + if (aGeneric) { + *aGeneric = StyleGenericFontFamily::None; + } + if (aIsFirst) { + *aIsFirst = false; + } + return GetDefaultFont(); +} + +already_AddRefed<gfxFont> gfxFontGroup::GetFirstMathFont() { + uint32_t count = mFonts.Length(); + for (uint32_t i = 0; i < count; ++i) { + RefPtr<gfxFont> font = GetFontAt(i); + if (font && font->TryGetMathTable()) { + return font.forget(); + } + } + return nullptr; +} + +bool gfxFontGroup::IsInvalidChar(uint8_t ch) { + return ((ch & 0x7f) < 0x20 || ch == 0x7f); +} + +bool gfxFontGroup::IsInvalidChar(char16_t ch) { + // All printable 7-bit ASCII values are OK + if (ch >= ' ' && ch < 0x7f) { + return false; + } + // No point in sending non-printing control chars through font shaping + if (ch <= 0x9f) { + return true; + } + // Word-separating format/bidi control characters are not shaped as part + // of words. + return (((ch & 0xFF00) == 0x2000 /* Unicode control character */ && + (ch == 0x200B /*ZWSP*/ || ch == 0x2028 /*LSEP*/ || + ch == 0x2029 /*PSEP*/ || ch == 0x2060 /*WJ*/)) || + ch == 0xfeff /*ZWNBSP*/ || IsBidiControl(ch)); +} + +already_AddRefed<gfxTextRun> gfxFontGroup::MakeEmptyTextRun( + const Parameters* aParams, gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2) { + aFlags |= ShapedTextFlags::TEXT_IS_8BIT; + return gfxTextRun::Create(aParams, 0, this, aFlags, aFlags2); +} + +already_AddRefed<gfxTextRun> gfxFontGroup::MakeSpaceTextRun( + const Parameters* aParams, gfx::ShapedTextFlags aFlags, + nsTextFrameUtils::Flags aFlags2) { + aFlags |= ShapedTextFlags::TEXT_IS_8BIT; + + RefPtr<gfxTextRun> textRun = + gfxTextRun::Create(aParams, 1, this, aFlags, aFlags2); + if (!textRun) { + return nullptr; + } + + gfx::ShapedTextFlags orientation = aFlags & ShapedTextFlags::TEXT_ORIENT_MASK; + if (orientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) { + orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + } + + RefPtr<gfxFont> font = GetFirstValidFont(); + if (MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero())) { + // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle + // them, and always create at least size 1 fonts, i.e. they still + // render something for size 0 fonts. + textRun->AddGlyphRun(font, FontMatchType::Kind::kUnspecified, 0, false, + orientation, false); + } else { + if (font->GetSpaceGlyph()) { + // Normally, the font has a cached space glyph, so we can avoid + // the cost of calling FindFontForChar. + textRun->SetSpaceGlyph(font, aParams->mDrawTarget, 0, orientation); + } else { + // In case the primary font doesn't have <space> (bug 970891), + // find one that does. + FontMatchType matchType; + RefPtr<gfxFont> spaceFont = + FindFontForChar(' ', 0, 0, Script::LATIN, nullptr, &matchType); + if (spaceFont) { + textRun->SetSpaceGlyph(spaceFont, aParams->mDrawTarget, 0, orientation); + } + } + } + + // Note that the gfxGlyphExtents glyph bounds storage for the font will + // always contain an entry for the font's space glyph, so we don't have + // to call FetchGlyphExtents here. + return textRun.forget(); +} + +template <typename T> +already_AddRefed<gfxTextRun> gfxFontGroup::MakeBlankTextRun( + const T* aString, uint32_t aLength, const Parameters* aParams, + gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2) { + RefPtr<gfxTextRun> textRun = + gfxTextRun::Create(aParams, aLength, this, aFlags, aFlags2); + if (!textRun) { + return nullptr; + } + + gfx::ShapedTextFlags orientation = aFlags & ShapedTextFlags::TEXT_ORIENT_MASK; + if (orientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) { + orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; + } + RefPtr<gfxFont> font = GetFirstValidFont(); + textRun->AddGlyphRun(font, FontMatchType::Kind::kUnspecified, 0, false, + orientation, false); + + textRun->SetupClusterBoundaries(0, aString, aLength); + + for (uint32_t i = 0; i < aLength; i++) { + if (aString[i] == '\n') { + textRun->SetIsNewline(i); + } else if (aString[i] == '\t') { + textRun->SetIsTab(i); + } + } + + return textRun.forget(); +} + +already_AddRefed<gfxTextRun> gfxFontGroup::MakeHyphenTextRun( + DrawTarget* aDrawTarget, gfx::ShapedTextFlags aFlags, + uint32_t aAppUnitsPerDevUnit) { + // only use U+2010 if it is supported by the first font in the group; + // it's better to use ASCII '-' from the primary font than to fall back to + // U+2010 from some other, possibly poorly-matching face + static const char16_t hyphen = 0x2010; + RefPtr<gfxFont> font = GetFirstValidFont(uint32_t(hyphen)); + if (font->HasCharacter(hyphen)) { + return MakeTextRun(&hyphen, 1, aDrawTarget, aAppUnitsPerDevUnit, aFlags, + nsTextFrameUtils::Flags(), nullptr); + } + + static const uint8_t dash = '-'; + return MakeTextRun(&dash, 1, aDrawTarget, aAppUnitsPerDevUnit, aFlags, + nsTextFrameUtils::Flags(), nullptr); +} + +gfxFloat gfxFontGroup::GetHyphenWidth( + const gfxTextRun::PropertyProvider* aProvider) { + if (mHyphenWidth < 0) { + RefPtr<DrawTarget> dt(aProvider->GetDrawTarget()); + if (dt) { + RefPtr<gfxTextRun> hyphRun( + MakeHyphenTextRun(dt, aProvider->GetShapedTextFlags(), + aProvider->GetAppUnitsPerDevUnit())); + mHyphenWidth = hyphRun.get() ? hyphRun->GetAdvanceWidth() : 0; + } + } + return mHyphenWidth; +} + +template <typename T> +already_AddRefed<gfxTextRun> gfxFontGroup::MakeTextRun( + const T* aString, uint32_t aLength, const Parameters* aParams, + gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2, + gfxMissingFontRecorder* aMFR) { + if (aLength == 0) { + return MakeEmptyTextRun(aParams, aFlags, aFlags2); + } + if (aLength == 1 && aString[0] == ' ') { + return MakeSpaceTextRun(aParams, aFlags, aFlags2); + } + + if (sizeof(T) == 1) { + aFlags |= ShapedTextFlags::TEXT_IS_8BIT; + } + + if (MOZ_UNLIKELY(GetStyle()->AdjustedSizeMustBeZero())) { + // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle + // them, and always create at least size 1 fonts, i.e. they still + // render something for size 0 fonts. + return MakeBlankTextRun(aString, aLength, aParams, aFlags, aFlags2); + } + + RefPtr<gfxTextRun> textRun = + gfxTextRun::Create(aParams, aLength, this, aFlags, aFlags2); + if (!textRun) { + return nullptr; + } + + InitTextRun(aParams->mDrawTarget, textRun.get(), aString, aLength, aMFR); + + textRun->FetchGlyphExtents(aParams->mDrawTarget); + + return textRun.forget(); +} + +// MakeTextRun instantiations (needed by Linux64 base-toolchain build). +template already_AddRefed<gfxTextRun> gfxFontGroup::MakeTextRun( + const uint8_t* aString, uint32_t aLength, const Parameters* aParams, + gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2, + gfxMissingFontRecorder* aMFR); +template already_AddRefed<gfxTextRun> gfxFontGroup::MakeTextRun( + const char16_t* aString, uint32_t aLength, const Parameters* aParams, + gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2, + gfxMissingFontRecorder* aMFR); + +template <typename T> +void gfxFontGroup::InitTextRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const T* aString, uint32_t aLength, + gfxMissingFontRecorder* aMFR) { + NS_ASSERTION(aLength > 0, "don't call InitTextRun for a zero-length run"); + + // we need to do numeral processing even on 8-bit text, + // in case we're converting Western to Hindi/Arabic digits + uint32_t numOption = gfxPlatform::GetPlatform()->GetBidiNumeralOption(); + UniquePtr<char16_t[]> transformedString; + if (numOption != IBMBIDI_NUMERAL_NOMINAL) { + // scan the string for numerals that may need to be transformed; + // if we find any, we'll make a local copy here and use that for + // font matching and glyph generation/shaping + bool prevIsArabic = + !!(aTextRun->GetFlags() & ShapedTextFlags::TEXT_INCOMING_ARABICCHAR); + for (uint32_t i = 0; i < aLength; ++i) { + char16_t origCh = aString[i]; + char16_t newCh = HandleNumberInChar(origCh, prevIsArabic, numOption); + if (newCh != origCh) { + if (!transformedString) { + transformedString = MakeUnique<char16_t[]>(aLength); + if constexpr (sizeof(T) == sizeof(char16_t)) { + memcpy(transformedString.get(), aString, i * sizeof(char16_t)); + } else { + for (uint32_t j = 0; j < i; ++j) { + transformedString[j] = aString[j]; + } + } + } + } + if (transformedString) { + transformedString[i] = newCh; + } + prevIsArabic = IS_ARABIC_CHAR(newCh); + } + } + + LogModule* log = mStyle.systemFont ? gfxPlatform::GetLog(eGfxLog_textrunui) + : gfxPlatform::GetLog(eGfxLog_textrun); + + // variant fallback handling may end up passing through this twice + bool redo; + do { + redo = false; + + if (sizeof(T) == sizeof(uint8_t) && !transformedString) { + if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) { + nsAutoCString lang; + mLanguage->ToUTF8String(lang); + nsAutoCString str((const char*)aString, aLength); + nsAutoCString styleString; + mStyle.style.ToString(styleString); + auto defaultLanguageGeneric = GetDefaultGeneric(mLanguage); + MOZ_LOG( + log, LogLevel::Warning, + ("(%s) fontgroup: [%s] default: %s lang: %s script: %d " + "len %d weight: %g stretch: %g%% style: %s size: %6.2f %zu-byte " + "TEXTRUN [%s] ENDTEXTRUN\n", + (mStyle.systemFont ? "textrunui" : "textrun"), + FamilyListToString(mFamilyList).get(), + (defaultLanguageGeneric == StyleGenericFontFamily::Serif + ? "serif" + : (defaultLanguageGeneric == StyleGenericFontFamily::SansSerif + ? "sans-serif" + : "none")), + lang.get(), static_cast<int>(Script::LATIN), aLength, + mStyle.weight.ToFloat(), mStyle.stretch.ToFloat(), + styleString.get(), mStyle.size, sizeof(T), str.get())); + } + + // the text is still purely 8-bit; bypass the script-run itemizer + // and treat it as a single Latin run + InitScriptRun(aDrawTarget, aTextRun, aString, 0, aLength, Script::LATIN, + aMFR); + } else { + const char16_t* textPtr; + if (transformedString) { + textPtr = transformedString.get(); + } else { + // typecast to avoid compilation error for the 8-bit version, + // even though this is dead code in that case + textPtr = reinterpret_cast<const char16_t*>(aString); + } + + // split into script runs so that script can potentially influence + // the font matching process below + gfxScriptItemizer scriptRuns(textPtr, aLength); + + uint32_t runStart = 0, runLimit = aLength; + Script runScript = Script::LATIN; + while (scriptRuns.Next(runStart, runLimit, runScript)) { + if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Warning))) { + nsAutoCString lang; + mLanguage->ToUTF8String(lang); + nsAutoCString styleString; + mStyle.style.ToString(styleString); + auto defaultLanguageGeneric = GetDefaultGeneric(mLanguage); + uint32_t runLen = runLimit - runStart; + MOZ_LOG(log, LogLevel::Warning, + ("(%s) fontgroup: [%s] default: %s lang: %s script: %d " + "len %d weight: %g stretch: %g%% style: %s size: %6.2f " + "%zu-byte TEXTRUN [%s] ENDTEXTRUN\n", + (mStyle.systemFont ? "textrunui" : "textrun"), + FamilyListToString(mFamilyList).get(), + (defaultLanguageGeneric == StyleGenericFontFamily::Serif + ? "serif" + : (defaultLanguageGeneric == + StyleGenericFontFamily::SansSerif + ? "sans-serif" + : "none")), + lang.get(), static_cast<int>(runScript), runLen, + mStyle.weight.ToFloat(), mStyle.stretch.ToFloat(), + styleString.get(), mStyle.size, sizeof(T), + NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get())); + } + + InitScriptRun(aDrawTarget, aTextRun, textPtr + runStart, runStart, + runLimit - runStart, runScript, aMFR); + } + } + + // if shaping was aborted due to lack of feature support, clear out + // glyph runs and redo shaping with fallback forced on + if (aTextRun->GetShapingState() == gfxTextRun::eShapingState_Aborted) { + redo = true; + aTextRun->SetShapingState(gfxTextRun::eShapingState_ForceFallbackFeature); + aTextRun->ClearGlyphsAndCharacters(); + } + + } while (redo); + + if (sizeof(T) == sizeof(char16_t) && aLength > 0) { + gfxTextRun::CompressedGlyph* glyph = aTextRun->GetCharacterGlyphs(); + if (!glyph->IsSimpleGlyph()) { + glyph->SetClusterStart(true); + } + } + + // It's possible for CoreText to omit glyph runs if it decides they contain + // only invisibles (e.g., U+FEFF, see reftest 474417-1). In this case, we + // need to eliminate them from the glyph run array to avoid drawing "partial + // ligatures" with the wrong font. + // We don't do this during InitScriptRun (or gfxFont::InitTextRun) because + // it will iterate back over all glyphruns in the textrun, which leads to + // pathologically-bad perf in the case where a textrun contains many script + // changes (see bug 680402) - we'd end up re-sanitizing all the earlier runs + // every time a new script subrun is processed. + aTextRun->SanitizeGlyphRuns(); +} + +static inline bool IsPUA(uint32_t aUSV) { + // We could look up the General Category of the codepoint here, + // but it's simpler to check PUA codepoint ranges. + return (aUSV >= 0xE000 && aUSV <= 0xF8FF) || (aUSV >= 0xF0000); +} + +template <typename T> +void gfxFontGroup::InitScriptRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun, + const T* aString, // text for this script run, + // not the entire textrun + uint32_t aOffset, // position of the script + // run within the textrun + uint32_t aLength, // length of the script run + Script aRunScript, + gfxMissingFontRecorder* aMFR) { + NS_ASSERTION(aLength > 0, "don't call InitScriptRun for a 0-length run"); + NS_ASSERTION(aTextRun->GetShapingState() != gfxTextRun::eShapingState_Aborted, + "don't call InitScriptRun with aborted shaping state"); + + // confirm the load state of userfonts in the list + if (mUserFontSet && mCurrGeneration != mUserFontSet->GetGeneration()) { + UpdateUserFonts(); + } + + RefPtr<gfxFont> mainFont = GetFirstValidFont(); + + ShapedTextFlags orientation = + aTextRun->GetFlags() & ShapedTextFlags::TEXT_ORIENT_MASK; + + if (orientation != ShapedTextFlags::TEXT_ORIENT_HORIZONTAL && + (aRunScript == Script::MONGOLIAN || aRunScript == Script::PHAGS_PA)) { + // Mongolian and Phags-pa text should ignore text-orientation and + // always render in its "native" vertical mode, implemented by fonts + // as sideways-right (i.e as if shaped horizontally, and then the + // entire line is rotated to render vertically). Therefore, we ignore + // the aOrientation value from the textrun's flags, and make all + // vertical Mongolian/Phags-pa use sideways-right. + orientation = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + } + + uint32_t runStart = 0; + AutoTArray<TextRange, 3> fontRanges; + ComputeRanges(fontRanges, aString, aLength, aRunScript, orientation); + uint32_t numRanges = fontRanges.Length(); + bool missingChars = false; + bool isCJK = gfxTextRun::IsCJKScript(aRunScript); + + for (uint32_t r = 0; r < numRanges; r++) { + const TextRange& range = fontRanges[r]; + uint32_t matchedLength = range.Length(); + RefPtr<gfxFont> matchedFont = range.font; + // create the glyph run for this range + if (matchedFont && mStyle.noFallbackVariantFeatures) { + // common case - just do glyph layout and record the + // resulting positioned glyphs + aTextRun->AddGlyphRun(matchedFont, range.matchType, aOffset + runStart, + (matchedLength > 0), range.orientation, isCJK); + if (!matchedFont->SplitAndInitTextRun( + aDrawTarget, aTextRun, aString + runStart, aOffset + runStart, + matchedLength, aRunScript, mLanguage, range.orientation)) { + // glyph layout failed! treat as missing glyphs + matchedFont = nullptr; + } + } else if (matchedFont) { + // shape with some variant feature that requires fallback handling + bool petiteToSmallCaps = false; + bool syntheticLower = false; + bool syntheticUpper = false; + + if (mStyle.variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL && + (aTextRun->GetShapingState() == + gfxTextRun::eShapingState_ForceFallbackFeature || + !matchedFont->SupportsSubSuperscript(mStyle.variantSubSuper, aString, + aLength, aRunScript))) { + // fallback for subscript/superscript variant glyphs + + // if the feature was already used, abort and force + // fallback across the entire textrun + gfxTextRun::ShapingState ss = aTextRun->GetShapingState(); + + if (ss == gfxTextRun::eShapingState_Normal) { + aTextRun->SetShapingState( + gfxTextRun::eShapingState_ShapingWithFallback); + } else if (ss == gfxTextRun::eShapingState_ShapingWithFeature) { + aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted); + return; + } + + RefPtr<gfxFont> subSuperFont = matchedFont->GetSubSuperscriptFont( + aTextRun->GetAppUnitsPerDevUnit()); + aTextRun->AddGlyphRun(subSuperFont, range.matchType, aOffset + runStart, + (matchedLength > 0), range.orientation, isCJK); + if (!subSuperFont->SplitAndInitTextRun( + aDrawTarget, aTextRun, aString + runStart, aOffset + runStart, + matchedLength, aRunScript, mLanguage, range.orientation)) { + // glyph layout failed! treat as missing glyphs + matchedFont = nullptr; + } + } else if (mStyle.variantCaps != NS_FONT_VARIANT_CAPS_NORMAL && + mStyle.allowSyntheticSmallCaps && + !matchedFont->SupportsVariantCaps( + aRunScript, mStyle.variantCaps, petiteToSmallCaps, + syntheticLower, syntheticUpper)) { + // fallback for small-caps variant glyphs + if (!matchedFont->InitFakeSmallCapsRun( + mPresContext, aDrawTarget, aTextRun, aString + runStart, + aOffset + runStart, matchedLength, range.matchType, + range.orientation, aRunScript, + mExplicitLanguage ? mLanguage.get() : nullptr, syntheticLower, + syntheticUpper)) { + matchedFont = nullptr; + } + } else { + // shape normally with variant feature enabled + gfxTextRun::ShapingState ss = aTextRun->GetShapingState(); + + // adjust the shaping state if necessary + if (ss == gfxTextRun::eShapingState_Normal) { + aTextRun->SetShapingState( + gfxTextRun::eShapingState_ShapingWithFeature); + } else if (ss == gfxTextRun::eShapingState_ShapingWithFallback) { + // already have shaping results using fallback, need to redo + aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted); + return; + } + + // do glyph layout and record the resulting positioned glyphs + aTextRun->AddGlyphRun(matchedFont, range.matchType, aOffset + runStart, + (matchedLength > 0), range.orientation, isCJK); + if (!matchedFont->SplitAndInitTextRun( + aDrawTarget, aTextRun, aString + runStart, aOffset + runStart, + matchedLength, aRunScript, mLanguage, range.orientation)) { + // glyph layout failed! treat as missing glyphs + matchedFont = nullptr; + } + } + } else { + aTextRun->AddGlyphRun(mainFont, FontMatchType::Kind::kFontGroup, + aOffset + runStart, (matchedLength > 0), + range.orientation, isCJK); + } + + if (!matchedFont) { + // We need to set cluster boundaries (and mark spaces) so that + // surrogate pairs, combining characters, etc behave properly, + // even if we don't have glyphs for them + aTextRun->SetupClusterBoundaries(aOffset + runStart, aString + runStart, + matchedLength); + + // various "missing" characters may need special handling, + // so we check for them here + uint32_t runLimit = runStart + matchedLength; + for (uint32_t index = runStart; index < runLimit; index++) { + T ch = aString[index]; + + // tab and newline are not to be displayed as hexboxes, + // but do need to be recorded in the textrun + if (ch == '\n') { + aTextRun->SetIsNewline(aOffset + index); + continue; + } + if (ch == '\t') { + aTextRun->SetIsTab(aOffset + index); + continue; + } + + // for 16-bit textruns only, check for surrogate pairs and + // special Unicode spaces; omit these checks in 8-bit runs + if constexpr (sizeof(T) == sizeof(char16_t)) { + if (index + 1 < aLength && + NS_IS_SURROGATE_PAIR(ch, aString[index + 1])) { + uint32_t usv = SURROGATE_TO_UCS4(ch, aString[index + 1]); + aTextRun->SetMissingGlyph(aOffset + index, usv, mainFont); + index++; + if (!mSkipDrawing && !IsPUA(usv)) { + missingChars = true; + } + continue; + } + + // check if this is a known Unicode whitespace character that + // we can render using the space glyph with a custom width + gfxFloat wid = mainFont->SynthesizeSpaceWidth(ch); + if (wid >= 0.0) { + nscoord advance = + aTextRun->GetAppUnitsPerDevUnit() * floor(wid + 0.5); + if (gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance)) { + aTextRun->GetCharacterGlyphs()[aOffset + index].SetSimpleGlyph( + advance, mainFont->GetSpaceGlyph()); + } else { + gfxTextRun::DetailedGlyph detailedGlyph; + detailedGlyph.mGlyphID = mainFont->GetSpaceGlyph(); + detailedGlyph.mAdvance = advance; + aTextRun->SetDetailedGlyphs(aOffset + index, 1, &detailedGlyph); + } + continue; + } + } + + if (IsInvalidChar(ch)) { + // invalid chars are left as zero-width/invisible + continue; + } + + // record char code so we can draw a box with the Unicode value + aTextRun->SetMissingGlyph(aOffset + index, ch, mainFont); + if (!mSkipDrawing && !IsPUA(ch)) { + missingChars = true; + } + } + } + + runStart += matchedLength; + } + + if (aMFR && missingChars) { + aMFR->RecordScript(aRunScript); + } +} + +gfxTextRun* gfxFontGroup::GetEllipsisTextRun( + int32_t aAppUnitsPerDevPixel, gfx::ShapedTextFlags aFlags, + LazyReferenceDrawTargetGetter& aRefDrawTargetGetter) { + MOZ_ASSERT(!(aFlags & ~ShapedTextFlags::TEXT_ORIENT_MASK), + "flags here should only be used to specify orientation"); + if (mCachedEllipsisTextRun && + (mCachedEllipsisTextRun->GetFlags() & + ShapedTextFlags::TEXT_ORIENT_MASK) == aFlags && + mCachedEllipsisTextRun->GetAppUnitsPerDevUnit() == aAppUnitsPerDevPixel) { + return mCachedEllipsisTextRun.get(); + } + + // Use a Unicode ellipsis if the font supports it, + // otherwise use three ASCII periods as fallback. + RefPtr<gfxFont> firstFont = GetFirstValidFont(); + nsString ellipsis = + firstFont->HasCharacter(kEllipsisChar[0]) + ? nsDependentString(kEllipsisChar, ArrayLength(kEllipsisChar) - 1) + : nsDependentString(kASCIIPeriodsChar, + ArrayLength(kASCIIPeriodsChar) - 1); + + RefPtr<DrawTarget> refDT = aRefDrawTargetGetter.GetRefDrawTarget(); + Parameters params = {refDT, nullptr, nullptr, + nullptr, 0, aAppUnitsPerDevPixel}; + mCachedEllipsisTextRun = + MakeTextRun(ellipsis.BeginReading(), ellipsis.Length(), ¶ms, aFlags, + nsTextFrameUtils::Flags(), nullptr); + if (!mCachedEllipsisTextRun) { + return nullptr; + } + // don't let the presence of a cached ellipsis textrun prolong the + // fontgroup's life + mCachedEllipsisTextRun->ReleaseFontGroup(); + return mCachedEllipsisTextRun.get(); +} + +already_AddRefed<gfxFont> gfxFontGroup::FindFallbackFaceForChar( + gfxFontFamily* aFamily, uint32_t aCh, uint32_t aNextCh, + eFontPresentation aPresentation) { + GlobalFontMatch data(aCh, aNextCh, mStyle, aPresentation); + aFamily->SearchAllFontsForChar(&data); + gfxFontEntry* fe = data.mBestMatch; + if (!fe) { + return nullptr; + } + return fe->FindOrMakeFont(&mStyle); +} + +already_AddRefed<gfxFont> gfxFontGroup::FindFallbackFaceForChar( + fontlist::Family* aFamily, uint32_t aCh, uint32_t aNextCh, + eFontPresentation aPresentation) { + auto* pfl = gfxPlatformFontList::PlatformFontList(); + auto* list = pfl->SharedFontList(); + + // If async fallback is enabled, and the family isn't fully initialized yet, + // just start the async cmap loading and return. + if (!aFamily->IsFullyInitialized() && + StaticPrefs::gfx_font_rendering_fallback_async() && + !XRE_IsParentProcess()) { + pfl->StartCmapLoadingFromFamily(aFamily - list->Families()); + return nullptr; + } + + GlobalFontMatch data(aCh, aNextCh, mStyle, aPresentation); + aFamily->SearchAllFontsForChar(list, &data); + gfxFontEntry* fe = data.mBestMatch; + if (!fe) { + return nullptr; + } + return fe->FindOrMakeFont(&mStyle); +} + +already_AddRefed<gfxFont> gfxFontGroup::FindFallbackFaceForChar( + const FamilyFace& aFamily, uint32_t aCh, uint32_t aNextCh, + eFontPresentation aPresentation) { + if (aFamily.IsSharedFamily()) { + return FindFallbackFaceForChar(aFamily.SharedFamily(), aCh, aNextCh, + aPresentation); + } + return FindFallbackFaceForChar(aFamily.OwnedFamily(), aCh, aNextCh, + aPresentation); +} + +gfxFloat gfxFontGroup::GetUnderlineOffset() { + if (mUnderlineOffset == UNDERLINE_OFFSET_NOT_SET) { + // if the fontlist contains a bad underline font, make the underline + // offset the min of the first valid font and bad font underline offsets + uint32_t len = mFonts.Length(); + for (uint32_t i = 0; i < len; i++) { + FamilyFace& ff = mFonts[i]; + gfxFontEntry* fe = ff.FontEntry(); + if (!fe) { + continue; + } + if (!fe->mIsUserFontContainer && !fe->IsUserFont() && + ((ff.IsSharedFamily() && ff.SharedFamily() && + ff.SharedFamily()->IsBadUnderlineFamily()) || + (!ff.IsSharedFamily() && ff.OwnedFamily() && + ff.OwnedFamily()->IsBadUnderlineFamily()))) { + RefPtr<gfxFont> font = GetFontAt(i); + if (!font) { + continue; + } + gfxFloat bad = + font->GetMetrics(nsFontMetrics::eHorizontal).underlineOffset; + RefPtr<gfxFont> firstValidFont = GetFirstValidFont(); + gfxFloat first = firstValidFont->GetMetrics(nsFontMetrics::eHorizontal) + .underlineOffset; + mUnderlineOffset = std::min(first, bad); + return mUnderlineOffset; + } + } + + // no bad underline fonts, use the first valid font's metric + RefPtr<gfxFont> firstValidFont = GetFirstValidFont(); + mUnderlineOffset = + firstValidFont->GetMetrics(nsFontMetrics::eHorizontal).underlineOffset; + } + + return mUnderlineOffset; +} + +#define NARROW_NO_BREAK_SPACE 0x202fu + +already_AddRefed<gfxFont> gfxFontGroup::FindFontForChar( + uint32_t aCh, uint32_t aPrevCh, uint32_t aNextCh, Script aRunScript, + gfxFont* aPrevMatchedFont, FontMatchType* aMatchType) { + // If the char is a cluster extender, we want to use the same font as the + // preceding character if possible. This is preferable to using the font + // group because it avoids breaks in shaping within a cluster. + if (aPrevMatchedFont && IsClusterExtender(aCh)) { + if (aPrevMatchedFont->HasCharacter(aCh) || IsDefaultIgnorable(aCh)) { + return do_AddRef(aPrevMatchedFont); + } + // Check if this char and preceding char can compose; if so, is the + // combination supported by the current font. + uint32_t composed = intl::String::ComposePairNFC(aPrevCh, aCh); + if (composed > 0 && aPrevMatchedFont->HasCharacter(composed)) { + return do_AddRef(aPrevMatchedFont); + } + } + + // Special cases for NNBSP (as used in Mongolian): + if (aCh == NARROW_NO_BREAK_SPACE) { + // If there is no preceding character, try the font that we'd use + // for the next char (unless it's just another NNBSP; we don't try + // to look ahead through a whole run of them). + if (!aPrevCh && aNextCh && aNextCh != NARROW_NO_BREAK_SPACE) { + RefPtr<gfxFont> nextFont = FindFontForChar(aNextCh, 0, 0, aRunScript, + aPrevMatchedFont, aMatchType); + if (nextFont && nextFont->HasCharacter(aCh)) { + return nextFont.forget(); + } + } + // Otherwise, treat NNBSP like a cluster extender (as above) and try + // to continue the preceding font run. + if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) { + return do_AddRef(aPrevMatchedFont); + } + } + + // To optimize common cases, try the first font in the font-group + // before going into the more detailed checks below + uint32_t fontListLength = mFonts.Length(); + uint32_t nextIndex = 0; + bool isJoinControl = gfxFontUtils::IsJoinControl(aCh); + bool wasJoinCauser = gfxFontUtils::IsJoinCauser(aPrevCh); + bool isVarSelector = gfxFontUtils::IsVarSelector(aCh); + bool nextIsVarSelector = gfxFontUtils::IsVarSelector(aNextCh); + + // For Unicode hyphens, if not supported in the font then we'll try for + // the ASCII hyphen-minus as a fallback. + // Similarly, for NBSP we try normal <space> as a fallback. + uint32_t fallbackChar = (aCh == 0x2010 || aCh == 0x2011) ? '-' + : (aCh == 0x00A0) ? ' ' + : 0; + + // Whether we've seen a font that is currently loading a resource that may + // provide this character (so we should not start a new load). + bool loading = false; + + // Do we need to explicitly look for a font that does or does not provide a + // color glyph for the given character? + // For characters with no `EMOJI` property, we'll use whatever the family + // list calls for; but if it's a potential emoji codepoint, we need to check + // if there's a variation selector specifically asking for Text-style or + // Emoji-style rendering and look for a suitable font. + eFontPresentation presentation = eFontPresentation::Any; + EmojiPresentation emojiPresentation = GetEmojiPresentation(aCh); + if (emojiPresentation != TextOnly) { + // Default presentation from the font-variant-emoji property. + presentation = mEmojiPresentation; + // If the prefer-emoji selector is present, or if it's a default-emoji + // char and the prefer-text selector is NOT present, or if there's a + // skin-tone modifier, we specifically look for a font with a color + // glyph. + // If the prefer-text selector is present, we specifically look for a + // font that will provide a monochrome glyph. + // Otherwise, we'll accept either color or monochrome font-family + // entries, so that a color font can be explicitly applied via font- + // family even to characters that are not inherently emoji-style. + if (aNextCh == kVariationSelector16 || + (aNextCh >= kEmojiSkinToneFirst && aNextCh <= kEmojiSkinToneLast) || + gfxFontUtils::IsEmojiFlagAndTag(aCh, aNextCh)) { + // Emoji presentation is explicitly requested by a variation selector + // or the presence of a skin-tone codepoint. + presentation = eFontPresentation::EmojiExplicit; + } else if (emojiPresentation == EmojiPresentation::EmojiDefault && + aNextCh != kVariationSelector15) { + // Emoji presentation is the default for this Unicode character. but we + // will allow an explicitly-specified webfont to apply to it, + // regardless of its glyph type. + presentation = eFontPresentation::EmojiDefault; + } else if (aNextCh == kVariationSelector15) { + // Text presentation is explicitly requested. + presentation = eFontPresentation::Text; + } + } + + if (!isJoinControl && !wasJoinCauser && !isVarSelector && + !nextIsVarSelector && presentation == eFontPresentation::Any) { + RefPtr<gfxFont> firstFont = GetFontAt(0, aCh, &loading); + if (firstFont) { + if (firstFont->HasCharacter(aCh) || + (fallbackChar && firstFont->HasCharacter(fallbackChar))) { + *aMatchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()}; + return firstFont.forget(); + } + + RefPtr<gfxFont> font; + if (mFonts[0].CheckForFallbackFaces()) { + font = FindFallbackFaceForChar(mFonts[0], aCh, aNextCh, presentation); + } else if (!firstFont->GetFontEntry()->IsUserFont()) { + // For platform fonts (but not userfonts), we may need to do + // fallback within the family to handle cases where some faces + // such as Italic or Black have reduced character sets compared + // to the family's Regular face. + font = FindFallbackFaceForChar(mFonts[0], aCh, aNextCh, presentation); + } + if (font) { + *aMatchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()}; + return font.forget(); + } + } else { + if (fontListLength > 0) { + loading = loading || mFonts[0].IsLoadingFor(aCh); + } + } + + // we don't need to check the first font again below + ++nextIndex; + } + + if (aPrevMatchedFont) { + // Don't switch fonts for control characters, regardless of + // whether they are present in the current font, as they won't + // actually be rendered (see bug 716229) + if (isJoinControl || + GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_CONTROL) { + return do_AddRef(aPrevMatchedFont); + } + + // if previous character was a join-causer (ZWJ), + // use the same font as the previous range if we can + if (wasJoinCauser) { + if (aPrevMatchedFont->HasCharacter(aCh)) { + return do_AddRef(aPrevMatchedFont); + } + } + } + + // If this character is a variation selector or default-ignorable, use the + // previous font regardless of whether it supports the codepoint or not. + // (We don't want to unnecessarily split glyph runs, and the character will + // not be visibly rendered.) + if (isVarSelector || IsDefaultIgnorable(aCh)) { + return do_AddRef(aPrevMatchedFont); + } + + // Used to remember the first "candidate" font that would provide a fallback + // text-style rendering if no color glyph can be found. + // If we decide NOT to return this font, we must AddRef/Release it to ensure + // that it goes into the global font cache as a candidate for deletion. + // This is done for us by CheckCandidate, but any code path that returns + // WITHOUT calling CheckCandidate needs to handle it explicitly. + RefPtr<gfxFont> candidateFont; + FontMatchType candidateMatchType; + + // Handle a candidate font that could support the character, returning true + // if we should go ahead and return |f|, false to continue searching. + // If there is already a saved candidate font, and the new candidate is + // accepted, we AddRef/Release the existing candidate so it won't leak. + auto CheckCandidate = [&](gfxFont* f, FontMatchType t) -> bool { + // If no preference, then just accept the font. + if (presentation == eFontPresentation::Any || + (presentation == eFontPresentation::EmojiDefault && + f->GetFontEntry()->IsUserFont())) { + *aMatchType = t; + return true; + } + // Does the candidate font provide a color glyph for the current character? + bool hasColorGlyph = f->HasColorGlyphFor(aCh, aNextCh); + // If the provided glyph matches the preference, accept the font. + if (hasColorGlyph == PrefersColor(presentation)) { + *aMatchType = t; + return true; + } + // If the character was a TextDefault char, but the next char is VS16, + // and the font is a COLR font that supports both these codepoints, then + // we'll assume it knows what it is doing (eg Twemoji Mozilla keycap + // sequences). + // TODO: reconsider all this as part of any fix for bug 543200. + if (aNextCh == kVariationSelector16 && emojiPresentation == TextDefault && + f->HasCharacter(aNextCh) && f->GetFontEntry()->TryGetColorGlyphs()) { + return true; + } + // Otherwise, remember the first potential fallback, but keep searching. + if (!candidateFont) { + candidateFont = f; + candidateMatchType = t; + } + return false; + }; + + // 1. check remaining fonts in the font group + for (uint32_t i = nextIndex; i < fontListLength; i++) { + FamilyFace& ff = mFonts[i]; + if (ff.IsInvalid() || ff.IsLoading()) { + if (ff.IsLoadingFor(aCh)) { + loading = true; + } + continue; + } + + RefPtr<gfxFont> font = ff.Font(); + if (font) { + // if available, use already-made gfxFont and check for character + if (font->HasCharacter(aCh) || + (fallbackChar && font->HasCharacter(fallbackChar))) { + if (CheckCandidate(font, + {FontMatchType::Kind::kFontGroup, ff.Generic()})) { + return font.forget(); + } + } + } else { + // don't have a gfxFont yet, test charmap before instantiating + gfxFontEntry* fe = ff.FontEntry(); + if (fe && fe->mIsUserFontContainer) { + // for userfonts, need to test both the unicode range map and + // the cmap of the platform font entry + gfxUserFontEntry* ufe = static_cast<gfxUserFontEntry*>(fe); + + // never match a character outside the defined unicode range + if (!ufe->CharacterInUnicodeRange(aCh)) { + continue; + } + + // Load if not already loaded, unless we've already seen an in- + // progress load that is expected to satisfy this request. + if (!loading && + ufe->LoadState() == gfxUserFontEntry::STATUS_NOT_LOADED) { + ufe->Load(); + ff.CheckState(mSkipDrawing); + } + + if (ff.IsLoading()) { + loading = true; + } + + gfxFontEntry* pfe = ufe->GetPlatformFontEntry(); + if (pfe && (pfe->HasCharacter(aCh) || + (fallbackChar && pfe->HasCharacter(fallbackChar)))) { + font = GetFontAt(i, aCh, &loading); + if (font) { + if (CheckCandidate(font, {FontMatchType::Kind::kFontGroup, + mFonts[i].Generic()})) { + return font.forget(); + } + } + } + } else if (fe && (fe->HasCharacter(aCh) || + (fallbackChar && fe->HasCharacter(fallbackChar)))) { + // for normal platform fonts, after checking the cmap + // build the font via GetFontAt + font = GetFontAt(i, aCh, &loading); + if (font) { + if (CheckCandidate(font, {FontMatchType::Kind::kFontGroup, + mFonts[i].Generic()})) { + return font.forget(); + } + } + } + } + + // check other family faces if needed + if (ff.CheckForFallbackFaces()) { +#ifdef DEBUG + if (i > 0) { + fontlist::FontList* list = + gfxPlatformFontList::PlatformFontList()->SharedFontList(); + nsCString s1 = mFonts[i - 1].IsSharedFamily() + ? mFonts[i - 1].SharedFamily()->Key().AsString(list) + : mFonts[i - 1].OwnedFamily()->Name(); + nsCString s2 = ff.IsSharedFamily() + ? ff.SharedFamily()->Key().AsString(list) + : ff.OwnedFamily()->Name(); + MOZ_ASSERT(!mFonts[i - 1].CheckForFallbackFaces() || !s1.Equals(s2), + "should only do fallback once per font family"); + } +#endif + font = FindFallbackFaceForChar(ff, aCh, aNextCh, presentation); + if (font) { + if (CheckCandidate(font, + {FontMatchType::Kind::kFontGroup, ff.Generic()})) { + return font.forget(); + } + } + } else { + // For platform fonts, but not user fonts, consider intra-family + // fallback to handle styles with reduced character sets (see + // also above). + gfxFontEntry* fe = ff.FontEntry(); + if (fe && !fe->mIsUserFontContainer && !fe->IsUserFont()) { + font = FindFallbackFaceForChar(ff, aCh, aNextCh, presentation); + if (font) { + if (CheckCandidate(font, + {FontMatchType::Kind::kFontGroup, ff.Generic()})) { + return font.forget(); + } + } + } + } + } + + if (fontListLength == 0) { + RefPtr<gfxFont> defaultFont = GetDefaultFont(); + if (defaultFont->HasCharacter(aCh) || + (fallbackChar && defaultFont->HasCharacter(fallbackChar))) { + if (CheckCandidate(defaultFont, FontMatchType::Kind::kFontGroup)) { + return defaultFont.forget(); + } + } + } + + // If character is in Private Use Area, or is unassigned in Unicode, don't do + // matching against pref or system fonts. We only support such codepoints + // when used with an explicitly-specified font, as they have no standard/ + // interoperable meaning. + // Also don't attempt any fallback for control characters or noncharacters, + // where we won't be rendering a glyph anyhow, or for codepoints where global + // fallback has already noted a failure. + FontVisibility level = + mPresContext ? mPresContext->GetFontVisibility() : FontVisibility::User; + auto* pfl = gfxPlatformFontList::PlatformFontList(); + if (pfl->SkipFontFallbackForChar(level, aCh) || + (!StaticPrefs::gfx_font_rendering_fallback_unassigned_chars() && + GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED)) { + if (candidateFont) { + *aMatchType = candidateMatchType; + } + return candidateFont.forget(); + } + + // 2. search pref fonts + RefPtr<gfxFont> font = WhichPrefFontSupportsChar(aCh, aNextCh, presentation); + if (font) { + if (PrefersColor(presentation) && pfl->EmojiPrefHasUserValue()) { + // For emoji, always accept the font from preferences if it's explicitly + // user-set, even if it isn't actually a color-emoji font, as some users + // may want to set their emoji font preference to a monochrome font like + // Symbola. + // So a user-provided font.name-list.emoji preference takes precedence + // over the Unicode presentation style here. + RefPtr<gfxFont> autoRefDeref(candidateFont); + *aMatchType = FontMatchType::Kind::kPrefsFallback; + return font.forget(); + } + if (CheckCandidate(font, FontMatchType::Kind::kPrefsFallback)) { + return font.forget(); + } + } + + // For fallback searches, we don't want to use a color-emoji font unless + // emoji-style presentation is specifically required, so we map Any to + // Text here. + if (presentation == eFontPresentation::Any) { + presentation = eFontPresentation::Text; + } + + // 3. use fallback fonts + // -- before searching for something else check the font used for the + // previous character + if (aPrevMatchedFont && + (aPrevMatchedFont->HasCharacter(aCh) || + (fallbackChar && aPrevMatchedFont->HasCharacter(fallbackChar)))) { + if (CheckCandidate(aPrevMatchedFont, + FontMatchType::Kind::kSystemFallback)) { + return do_AddRef(aPrevMatchedFont); + } + } + + // for known "space" characters, don't do a full system-fallback search; + // we'll synthesize appropriate-width spaces instead of missing-glyph boxes + font = GetFirstValidFont(); + if (GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR && + font->SynthesizeSpaceWidth(aCh) >= 0.0) { + return nullptr; + } + + // -- otherwise look for other stuff + font = WhichSystemFontSupportsChar(aCh, aNextCh, aRunScript, presentation); + if (font) { + if (CheckCandidate(font, FontMatchType::Kind::kSystemFallback)) { + return font.forget(); + } + } + if (candidateFont) { + *aMatchType = candidateMatchType; + } + return candidateFont.forget(); +} + +template <typename T> +void gfxFontGroup::ComputeRanges(nsTArray<TextRange>& aRanges, const T* aString, + uint32_t aLength, Script aRunScript, + gfx::ShapedTextFlags aOrientation) { + NS_ASSERTION(aRanges.Length() == 0, "aRanges must be initially empty"); + NS_ASSERTION(aLength > 0, "don't call ComputeRanges for zero-length text"); + + uint32_t prevCh = 0; + uint32_t nextCh = aString[0]; + if constexpr (sizeof(T) == sizeof(char16_t)) { + if (aLength > 1 && NS_IS_SURROGATE_PAIR(nextCh, aString[1])) { + nextCh = SURROGATE_TO_UCS4(nextCh, aString[1]); + } + } + int32_t lastRangeIndex = -1; + + // initialize prevFont to the group's primary font, so that this will be + // used for string-initial control chars, etc rather than risk hitting font + // fallback for these (bug 716229) + StyleGenericFontFamily generic = StyleGenericFontFamily::None; + RefPtr<gfxFont> prevFont = GetFirstValidFont(' ', &generic); + + // if we use the initial value of prevFont, we treat this as a match from + // the font group; fixes bug 978313 + FontMatchType matchType = {FontMatchType::Kind::kFontGroup, generic}; + + for (uint32_t i = 0; i < aLength; i++) { + const uint32_t origI = i; // save off in case we increase for surrogate + + // set up current ch + uint32_t ch = nextCh; + + // Get next char (if any) so that FindFontForChar can look ahead + // for a possible variation selector. + + if constexpr (sizeof(T) == sizeof(char16_t)) { + // In 16-bit case only, check for surrogate pairs. + if (ch > 0xffffu) { + i++; + } + if (i < aLength - 1) { + nextCh = aString[i + 1]; + if (i + 2 < aLength && NS_IS_SURROGATE_PAIR(nextCh, aString[i + 2])) { + nextCh = SURROGATE_TO_UCS4(nextCh, aString[i + 2]); + } + } else { + nextCh = 0; + } + } else { + // 8-bit case is trivial. + nextCh = i < aLength - 1 ? aString[i + 1] : 0; + } + + RefPtr<gfxFont> font; + + // Find the font for this char; but try to avoid calling the expensive + // FindFontForChar method for the most common case, where the first + // font in the list supports the current char, and it is not one of + // the special cases where FindFontForChar will attempt to propagate + // the font selected for an adjacent character. + if ((font = GetFontAt(0, ch)) != nullptr && font->HasCharacter(ch) && + (sizeof(T) == sizeof(uint8_t) || + (!IsClusterExtender(ch) && ch != NARROW_NO_BREAK_SPACE && + !gfxFontUtils::IsJoinControl(ch) && + !gfxFontUtils::IsJoinCauser(prevCh) && + !gfxFontUtils::IsVarSelector(ch) && + GetEmojiPresentation(ch) == TextOnly))) { + matchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()}; + } else { + font = + FindFontForChar(ch, prevCh, nextCh, aRunScript, prevFont, &matchType); + } + +#ifndef RELEASE_OR_BETA + if (MOZ_UNLIKELY(mTextPerf)) { + if (matchType.kind == FontMatchType::Kind::kPrefsFallback) { + mTextPerf->current.fallbackPrefs++; + } else if (matchType.kind == FontMatchType::Kind::kSystemFallback) { + mTextPerf->current.fallbackSystem++; + } + } +#endif + + prevCh = ch; + + ShapedTextFlags orient = aOrientation; + if (aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) { + // For CSS text-orientation:mixed, we need to resolve orientation + // on a per-character basis using the UTR50 orientation property. + switch (GetVerticalOrientation(ch)) { + case VERTICAL_ORIENTATION_U: + case VERTICAL_ORIENTATION_Tu: + orient = ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; + break; + case VERTICAL_ORIENTATION_Tr: { + // We check for a vertical presentation form first as that's + // likely to be cheaper than inspecting lookups to see if the + // 'vert' feature is going to handle this character, and if the + // presentation form is available then it will be used as + // fallback if needed, so it's OK if the feature is missing. + // + // Because "common" CJK punctuation characters in isolation will be + // resolved to Bopomofo script (as the first script listed in their + // ScriptExtensions property), but this is not always well supported + // by fonts' OpenType tables, we also try Han script; harfbuzz will + // apply a 'vert' feature from any available script (see + // https://github.com/harfbuzz/harfbuzz/issues/63) when shaping, + // so this is OK. It's not quite as general as what harfbuzz does + // (it will find the feature in *any* script), but should be enough + // for likely real-world examples. + uint32_t v = gfxHarfBuzzShaper::GetVerticalPresentationForm(ch); + const uint32_t kVert = HB_TAG('v', 'e', 'r', 't'); + orient = (!font || (v && font->HasCharacter(v)) || + font->FeatureWillHandleChar(aRunScript, kVert, ch) || + (aRunScript == Script::BOPOMOFO && + font->FeatureWillHandleChar(Script::HAN, kVert, ch))) + ? ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT + : ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + break; + } + case VERTICAL_ORIENTATION_R: + orient = ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + break; + } + } + + if (lastRangeIndex == -1) { + // first char ==> make a new range + aRanges.AppendElement(TextRange(0, 1, font, matchType, orient)); + lastRangeIndex++; + prevFont = std::move(font); + } else { + // if font or orientation has changed, make a new range... + // unless ch is a variation selector (bug 1248248) + TextRange& prevRange = aRanges[lastRangeIndex]; + if (prevRange.font != font || + (prevRange.orientation != orient && !IsClusterExtender(ch))) { + // close out the previous range + prevRange.end = origI; + aRanges.AppendElement(TextRange(origI, i + 1, font, matchType, orient)); + lastRangeIndex++; + + // update prevFont for the next match, *unless* we switched + // fonts on a ZWJ, in which case propagating the changed font + // is probably not a good idea (see bug 619511) + if (sizeof(T) == sizeof(uint8_t) || !gfxFontUtils::IsJoinCauser(ch)) { + prevFont = std::move(font); + } + } else { + prevRange.matchType |= matchType; + } + } + } + + aRanges[lastRangeIndex].end = aLength; + +#ifndef RELEASE_OR_BETA + LogModule* log = mStyle.systemFont ? gfxPlatform::GetLog(eGfxLog_textrunui) + : gfxPlatform::GetLog(eGfxLog_textrun); + + if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Debug))) { + nsAutoCString lang; + mLanguage->ToUTF8String(lang); + auto defaultLanguageGeneric = GetDefaultGeneric(mLanguage); + + // collect the font matched for each range + nsAutoCString fontMatches; + for (size_t i = 0, i_end = aRanges.Length(); i < i_end; i++) { + const TextRange& r = aRanges[i]; + nsAutoCString matchTypes; + if (r.matchType.kind & FontMatchType::Kind::kFontGroup) { + matchTypes.AppendLiteral("list"); + } + if (r.matchType.kind & FontMatchType::Kind::kPrefsFallback) { + if (!matchTypes.IsEmpty()) { + matchTypes.AppendLiteral(","); + } + matchTypes.AppendLiteral("prefs"); + } + if (r.matchType.kind & FontMatchType::Kind::kSystemFallback) { + if (!matchTypes.IsEmpty()) { + matchTypes.AppendLiteral(","); + } + matchTypes.AppendLiteral("sys"); + } + fontMatches.AppendPrintf( + " [%u:%u] %.200s (%s)", r.start, r.end, + (r.font.get() ? r.font->GetName().get() : "<null>"), + matchTypes.get()); + } + MOZ_LOG(log, LogLevel::Debug, + ("(%s-fontmatching) fontgroup: [%s] default: %s lang: %s script: %d" + "%s\n", + (mStyle.systemFont ? "textrunui" : "textrun"), + FamilyListToString(mFamilyList).get(), + (defaultLanguageGeneric == StyleGenericFontFamily::Serif + ? "serif" + : (defaultLanguageGeneric == StyleGenericFontFamily::SansSerif + ? "sans-serif" + : "none")), + lang.get(), static_cast<int>(aRunScript), fontMatches.get())); + } +#endif +} + +gfxUserFontSet* gfxFontGroup::GetUserFontSet() { return mUserFontSet; } + +void gfxFontGroup::SetUserFontSet(gfxUserFontSet* aUserFontSet) { + if (aUserFontSet == mUserFontSet) { + return; + } + mUserFontSet = aUserFontSet; + mCurrGeneration = GetGeneration() - 1; + UpdateUserFonts(); +} + +uint64_t gfxFontGroup::GetGeneration() { + if (!mUserFontSet) return 0; + return mUserFontSet->GetGeneration(); +} + +uint64_t gfxFontGroup::GetRebuildGeneration() { + if (!mUserFontSet) return 0; + return mUserFontSet->GetRebuildGeneration(); +} + +void gfxFontGroup::UpdateUserFonts() { + if (mCurrGeneration < GetRebuildGeneration()) { + // fonts in userfont set changed, need to redo the fontlist + mFonts.Clear(); + ClearCachedData(); + BuildFontList(); + mCurrGeneration = GetGeneration(); + } else if (mCurrGeneration != GetGeneration()) { + // load state change occurred, verify load state and validity of fonts + ClearCachedData(); + + uint32_t len = mFonts.Length(); + for (uint32_t i = 0; i < len; i++) { + FamilyFace& ff = mFonts[i]; + if (ff.Font() || !ff.IsUserFontContainer()) { + continue; + } + ff.CheckState(mSkipDrawing); + } + + mCurrGeneration = GetGeneration(); + } +} + +bool gfxFontGroup::ContainsUserFont(const gfxUserFontEntry* aUserFont) { + UpdateUserFonts(); + // search through the fonts list for a specific user font + uint32_t len = mFonts.Length(); + for (uint32_t i = 0; i < len; i++) { + FamilyFace& ff = mFonts[i]; + if (ff.EqualsUserFont(aUserFont)) { + return true; + } + } + return false; +} + +already_AddRefed<gfxFont> gfxFontGroup::WhichPrefFontSupportsChar( + uint32_t aCh, uint32_t aNextCh, eFontPresentation aPresentation) { + eFontPrefLang charLang; + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + + if (PrefersColor(aPresentation)) { + charLang = eFontPrefLang_Emoji; + } else { + // get the pref font list if it hasn't been set up already + charLang = pfl->GetFontPrefLangFor(aCh); + } + + // if the last pref font was the first family in the pref list, no need to + // recheck through a list of families + if (mLastPrefFont && charLang == mLastPrefLang && mLastPrefFirstFont && + mLastPrefFont->HasCharacter(aCh)) { + return do_AddRef(mLastPrefFont); + } + + // based on char lang and page lang, set up list of pref lang fonts to check + eFontPrefLang prefLangs[kMaxLenPrefLangList]; + uint32_t i, numLangs = 0; + + pfl->GetLangPrefs(prefLangs, numLangs, charLang, mPageLang); + + for (i = 0; i < numLangs; i++) { + eFontPrefLang currentLang = prefLangs[i]; + StyleGenericFontFamily generic = + mFallbackGeneric != StyleGenericFontFamily::None + ? mFallbackGeneric + : pfl->GetDefaultGeneric(currentLang); + gfxPlatformFontList::PrefFontList* families = + pfl->GetPrefFontsLangGroup(mPresContext, generic, currentLang); + NS_ASSERTION(families, "no pref font families found"); + + // find the first pref font that includes the character + uint32_t j, numPrefs; + numPrefs = families->Length(); + for (j = 0; j < numPrefs; j++) { + // look up the appropriate face + FontFamily family = (*families)[j]; + if (family.IsNull()) { + continue; + } + + // if a pref font is used, it's likely to be used again in the same text + // run. the style doesn't change so the face lookup can be cached rather + // than calling FindOrMakeFont repeatedly. speeds up FindFontForChar + // lookup times for subsequent pref font lookups + if (family == mLastPrefFamily && mLastPrefFont->HasCharacter(aCh)) { + return do_AddRef(mLastPrefFont); + } + + gfxFontEntry* fe = nullptr; + if (family.mShared) { + fontlist::Family* fam = family.mShared; + if (!fam->IsInitialized()) { + Unused << pfl->InitializeFamily(fam); + } + fontlist::Face* face = + fam->FindFaceForStyle(pfl->SharedFontList(), mStyle); + if (face) { + fe = pfl->GetOrCreateFontEntry(face, fam); + } + } else { + fe = family.mUnshared->FindFontForStyle(mStyle); + } + if (!fe) { + continue; + } + + // if ch in cmap, create and return a gfxFont + RefPtr<gfxFont> prefFont; + if (fe->HasCharacter(aCh)) { + prefFont = fe->FindOrMakeFont(&mStyle); + if (!prefFont) { + continue; + } + if (aPresentation == eFontPresentation::EmojiExplicit && + !prefFont->HasColorGlyphFor(aCh, aNextCh)) { + continue; + } + } + + // If the char was not available, see if we can fall back to an + // alternative face in the same family. + if (!prefFont) { + prefFont = family.mShared + ? FindFallbackFaceForChar(family.mShared, aCh, aNextCh, + aPresentation) + : FindFallbackFaceForChar(family.mUnshared, aCh, aNextCh, + aPresentation); + } + if (prefFont) { + mLastPrefFamily = family; + mLastPrefFont = prefFont; + mLastPrefLang = charLang; + mLastPrefFirstFont = (i == 0 && j == 0); + return prefFont.forget(); + } + } + } + + return nullptr; +} + +already_AddRefed<gfxFont> gfxFontGroup::WhichSystemFontSupportsChar( + uint32_t aCh, uint32_t aNextCh, Script aRunScript, + eFontPresentation aPresentation) { + FontVisibility visibility; + return gfxPlatformFontList::PlatformFontList()->SystemFindFontForChar( + mPresContext, aCh, aNextCh, aRunScript, aPresentation, &mStyle, + &visibility); +} + +gfxFont::Metrics gfxFontGroup::GetMetricsForCSSUnits( + gfxFont::Orientation aOrientation) { + bool isFirst; + RefPtr<gfxFont> font = GetFirstValidFont(0x20, nullptr, &isFirst); + auto metrics = font->GetMetrics(aOrientation); + + // If the font we used to get metrics was not the first in the list, + // or if it doesn't support the ZERO character, check for the font that + // does support ZERO and use its metrics for the 'ch' unit. + if (!isFirst || !font->HasCharacter('0')) { + RefPtr<gfxFont> zeroFont = GetFirstValidFont('0'); + if (zeroFont != font) { + const auto& zeroMetrics = zeroFont->GetMetrics(aOrientation); + metrics.zeroWidth = zeroMetrics.zeroWidth; + } + } + + // Likewise for the WATER ideograph character used as the basis for 'ic'. + if (!isFirst || !font->HasCharacter(0x6C34)) { + RefPtr<gfxFont> icFont = GetFirstValidFont(0x6C34); + if (icFont != font) { + const auto& icMetrics = icFont->GetMetrics(aOrientation); + metrics.ideographicWidth = icMetrics.ideographicWidth; + } + } + + return metrics; +} + +void gfxMissingFontRecorder::Flush() { + static bool mNotifiedFontsInitialized = false; + static uint32_t mNotifiedFonts[gfxMissingFontRecorder::kNumScriptBitsWords]; + if (!mNotifiedFontsInitialized) { + memset(&mNotifiedFonts, 0, sizeof(mNotifiedFonts)); + mNotifiedFontsInitialized = true; + } + + nsAutoString fontNeeded; + for (uint32_t i = 0; i < kNumScriptBitsWords; ++i) { + mMissingFonts[i] &= ~mNotifiedFonts[i]; + if (!mMissingFonts[i]) { + continue; + } + for (uint32_t j = 0; j < 32; ++j) { + if (!(mMissingFonts[i] & (1 << j))) { + continue; + } + mNotifiedFonts[i] |= (1 << j); + if (!fontNeeded.IsEmpty()) { + fontNeeded.Append(char16_t(',')); + } + uint32_t sc = i * 32 + j; + MOZ_ASSERT(sc < static_cast<uint32_t>(Script::NUM_SCRIPT_CODES), + "how did we set the bit for an invalid script code?"); + uint32_t tag = GetScriptTagForCode(static_cast<Script>(sc)); + fontNeeded.Append(char16_t(tag >> 24)); + fontNeeded.Append(char16_t((tag >> 16) & 0xff)); + fontNeeded.Append(char16_t((tag >> 8) & 0xff)); + fontNeeded.Append(char16_t(tag & 0xff)); + } + mMissingFonts[i] = 0; + } + if (!fontNeeded.IsEmpty()) { + nsCOMPtr<nsIObserverService> service = GetObserverService(); + service->NotifyObservers(nullptr, "font-needed", fontNeeded.get()); + } +} |