diff --git a/content/base/src/nsLineBreaker.cpp b/content/base/src/nsLineBreaker.cpp index 61478462a815..c074b3624a76 100644 --- a/content/base/src/nsLineBreaker.cpp +++ b/content/base/src/nsLineBreaker.cpp @@ -6,7 +6,7 @@ #include "nsLineBreaker.h" #include "nsContentUtils.h" #include "nsILineBreaker.h" -#include "gfxFont.h" // for the gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_* values +#include "gfxTextRun.h" // for the gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_* values #include "nsHyphenationManager.h" #include "nsHyphenator.h" #include "mozilla/gfx/2D.h" diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h index 5d675bc7c32b..9bbb2af34b02 100644 --- a/dom/canvas/CanvasRenderingContext2D.h +++ b/dom/canvas/CanvasRenderingContext2D.h @@ -14,7 +14,7 @@ #include "mozilla/dom/HTMLCanvasElement.h" #include "mozilla/dom/HTMLVideoElement.h" #include "CanvasUtils.h" -#include "gfxFont.h" +#include "gfxTextRun.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/CanvasGradient.h" #include "mozilla/dom/CanvasRenderingContext2DBinding.h" diff --git a/gfx/src/nsFontMetrics.h b/gfx/src/nsFontMetrics.h index 27e58acc698c..0642c4889253 100644 --- a/gfx/src/nsFontMetrics.h +++ b/gfx/src/nsFontMetrics.h @@ -8,7 +8,7 @@ #include // for uint32_t #include // for int32_t -#include "gfxFont.h" // for gfxFont, gfxFontGroup +#include "gfxTextRun.h" // for gfxFont, gfxFontGroup #include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2 #include "nsAutoPtr.h" // for nsRefPtr #include "nsCOMPtr.h" // for nsCOMPtr diff --git a/gfx/thebes/gfxAndroidPlatform.cpp b/gfx/thebes/gfxAndroidPlatform.cpp index cc542f5f7e73..9190915a5abe 100644 --- a/gfx/thebes/gfxAndroidPlatform.cpp +++ b/gfx/thebes/gfxAndroidPlatform.cpp @@ -14,6 +14,7 @@ #include "gfx2DGlue.h" #include "gfxFT2FontList.h" #include "gfxImageSurface.h" +#include "gfxTextRun.h" #include "mozilla/dom/ContentChild.h" #include "nsXULAppAPI.h" #include "nsIScreen.h" diff --git a/gfx/thebes/gfxCoreTextShaper.cpp b/gfx/thebes/gfxCoreTextShaper.cpp index 8ad2a171a4e8..3abbb6330a75 100644 --- a/gfx/thebes/gfxCoreTextShaper.cpp +++ b/gfx/thebes/gfxCoreTextShaper.cpp @@ -7,6 +7,7 @@ #include "gfxCoreTextShaper.h" #include "gfxMacFont.h" #include "gfxFontUtils.h" +#include "gfxTextRun.h" #include "mozilla/gfx/2D.h" #include diff --git a/gfx/thebes/gfxDWriteFonts.cpp b/gfx/thebes/gfxDWriteFonts.cpp index b1619abe432e..117c36afcddb 100644 --- a/gfx/thebes/gfxDWriteFonts.cpp +++ b/gfx/thebes/gfxDWriteFonts.cpp @@ -10,6 +10,7 @@ #include #include "gfxDWriteFontList.h" #include "gfxContext.h" +#include "gfxTextRun.h" #include #include "harfbuzz/hb.h" diff --git a/gfx/thebes/gfxFT2Fonts.cpp b/gfx/thebes/gfxFT2Fonts.cpp index deb055d8af4e..f6e875efddff 100644 --- a/gfx/thebes/gfxFT2Fonts.cpp +++ b/gfx/thebes/gfxFT2Fonts.cpp @@ -23,6 +23,7 @@ #include "gfxFT2FontBase.h" #include "gfxFT2Utils.h" #include "gfxFT2FontList.h" +#include "gfxTextRun.h" #include #include "nsGkAtoms.h" #include "nsTArray.h" diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp index a2ce0e57a096..d161272a698b 100644 --- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -11,13 +11,13 @@ #endif #include "prlog.h" -#include "nsServiceManagerUtils.h" #include "nsExpirationTracker.h" -#include "nsILanguageAtomService.h" #include "nsITimer.h" #include "gfxFont.h" +#include "gfxGlyphExtents.h" #include "gfxPlatform.h" +#include "gfxTextRun.h" #include "nsGkAtoms.h" #include "gfxTypes.h" @@ -26,43 +26,27 @@ #include "gfxGraphiteShaper.h" #include "gfxHarfBuzzShaper.h" #include "gfxUserFontSet.h" -#include "gfxPlatformFontList.h" -#include "gfxScriptItemizer.h" #include "nsSpecialCasingData.h" #include "nsTextRunTransformations.h" #include "nsUnicodeProperties.h" -#include "nsMathUtils.h" -#include "nsBidiUtils.h" -#include "nsUnicodeRange.h" #include "nsStyleConsts.h" #include "mozilla/AppUnits.h" -#include "mozilla/FloatingPoint.h" #include "mozilla/Likely.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/Telemetry.h" #include "gfxSVGGlyphs.h" -#include "gfxMathTable.h" #include "gfx2DGlue.h" #include "GreekCasing.h" -#if defined(XP_MACOSX) -#include "nsCocoaFeatures.h" -#endif - #include "cairo.h" -#include "gfxFontTest.h" #include "harfbuzz/hb.h" #include "harfbuzz/hb-ot.h" #include "graphite2/Font.h" -#include "nsCRT.h" -#include "GeckoProfiler.h" -#include "gfxFontConstants.h" - #include using namespace mozilla; @@ -72,23 +56,20 @@ using mozilla::services::GetObserverService; gfxFontCache *gfxFontCache::gGlobalCache = nullptr; -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 -static uint32_t gTextRunStorageHighWaterMark = 0; -static uint32_t gTextRunStorage = 0; -static uint32_t gFontCount = 0; -static uint32_t gGlyphExtentsCount = 0; -static uint32_t gGlyphExtentsWidthsTotalSize = 0; -static uint32_t gGlyphExtentsSetupEagerSimple = 0; -static uint32_t gGlyphExtentsSetupEagerTight = 0; -static uint32_t gGlyphExtentsSetupLazyTight = 0; -static uint32_t gGlyphExtentsSetupFallBackToTight = 0; +uint32_t gTextRunStorageHighWaterMark = 0; +uint32_t gTextRunStorage = 0; +uint32_t gFontCount = 0; +uint32_t gGlyphExtentsCount = 0; +uint32_t gGlyphExtentsWidthsTotalSize = 0; +uint32_t gGlyphExtentsSetupEagerSimple = 0; +uint32_t gGlyphExtentsSetupEagerTight = 0; +uint32_t gGlyphExtentsSetupLazyTight = 0; +uint32_t gGlyphExtentsSetupFallBackToTight = 0; #endif #ifdef PR_LOGGING @@ -99,1764 +80,6 @@ static uint32_t gGlyphExtentsSetupFallBackToTight = 0; PR_LOG_DEBUG) #endif // PR_LOGGING -void -gfxCharacterMap::NotifyReleased() -{ - gfxPlatformFontList *fontlist = gfxPlatformFontList::PlatformFontList(); - if (mShared) { - fontlist->RemoveCmap(this); - } - delete this; -} - -gfxFontEntry::gfxFontEntry() : - mItalic(false), mFixedPitch(false), - mIsValid(true), - mIsBadUnderlineFont(false), - mIsUserFontContainer(false), - mIsDataUserFont(false), - mIsLocalUserFont(false), - mStandardFace(false), - mSymbolFont(false), - mIgnoreGDEF(false), - mIgnoreGSUB(false), - mSVGInitialized(false), - mMathInitialized(false), - mHasSpaceFeaturesInitialized(false), - mHasSpaceFeatures(false), - mHasSpaceFeaturesKerning(false), - mHasSpaceFeaturesNonKerning(false), - mSkipDefaultFeatureSpaceCheck(false), - mCheckedForGraphiteTables(false), - mHasCmapTable(false), - mGrFaceInitialized(false), - mCheckedForColorGlyph(false), - mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL), - mUVSOffset(0), mUVSData(nullptr), - mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE), - mCOLR(nullptr), - mCPAL(nullptr), - mUnitsPerEm(0), - mHBFace(nullptr), - mGrFace(nullptr), - mGrFaceRefCnt(0) -{ - memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures)); - memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures)); -} - -gfxFontEntry::gfxFontEntry(const nsAString& aName, bool aIsStandardFace) : - mName(aName), mItalic(false), mFixedPitch(false), - mIsValid(true), - mIsBadUnderlineFont(false), - mIsUserFontContainer(false), - mIsDataUserFont(false), - mIsLocalUserFont(false), mStandardFace(aIsStandardFace), - mSymbolFont(false), - mIgnoreGDEF(false), - mIgnoreGSUB(false), - mSVGInitialized(false), - mMathInitialized(false), - mHasSpaceFeaturesInitialized(false), - mHasSpaceFeatures(false), - mHasSpaceFeaturesKerning(false), - mHasSpaceFeaturesNonKerning(false), - mSkipDefaultFeatureSpaceCheck(false), - mCheckedForGraphiteTables(false), - mHasCmapTable(false), - mGrFaceInitialized(false), - mCheckedForColorGlyph(false), - mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL), - mUVSOffset(0), mUVSData(nullptr), - mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE), - mCOLR(nullptr), - mCPAL(nullptr), - mUnitsPerEm(0), - mHBFace(nullptr), - mGrFace(nullptr), - mGrFaceRefCnt(0) -{ - memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures)); - memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures)); -} - -static PLDHashOperator -DestroyHBSet(const uint32_t& aTag, hb_set_t*& aSet, void *aUserArg) -{ - hb_set_destroy(aSet); - return PL_DHASH_NEXT; -} - -gfxFontEntry::~gfxFontEntry() -{ - if (mCOLR) { - hb_blob_destroy(mCOLR); - } - - if (mCPAL) { - hb_blob_destroy(mCPAL); - } - - // For downloaded fonts, we need to tell the user font cache that this - // entry is being deleted. - if (mIsDataUserFont) { - gfxUserFontSet::UserFontCache::ForgetFont(this); - } - - if (mFeatureInputs) { - mFeatureInputs->Enumerate(DestroyHBSet, nullptr); - } - - // By the time the entry is destroyed, all font instances that were - // using it should already have been deleted, and so the HB and/or Gr - // face objects should have been released. - MOZ_ASSERT(!mHBFace); - MOZ_ASSERT(!mGrFaceInitialized); -} - -bool gfxFontEntry::IsSymbolFont() -{ - return mSymbolFont; -} - -bool gfxFontEntry::TestCharacterMap(uint32_t aCh) -{ - if (!mCharacterMap) { - ReadCMAP(); - NS_ASSERTION(mCharacterMap, "failed to initialize character map"); - } - return mCharacterMap->test(aCh); -} - -nsresult gfxFontEntry::InitializeUVSMap() -{ - // mUVSOffset will not be initialized - // until cmap is initialized. - if (!mCharacterMap) { - ReadCMAP(); - NS_ASSERTION(mCharacterMap, "failed to initialize character map"); - } - - if (!mUVSOffset) { - return NS_ERROR_FAILURE; - } - - if (!mUVSData) { - const uint32_t kCmapTag = TRUETYPE_TAG('c','m','a','p'); - AutoTable cmapTable(this, kCmapTag); - if (!cmapTable) { - mUVSOffset = 0; // don't bother to read the table again - return NS_ERROR_FAILURE; - } - - uint8_t* uvsData; - unsigned int cmapLen; - const char* cmapData = hb_blob_get_data(cmapTable, &cmapLen); - nsresult rv = gfxFontUtils::ReadCMAPTableFormat14( - (const uint8_t*)cmapData + mUVSOffset, - cmapLen - mUVSOffset, uvsData); - - if (NS_FAILED(rv)) { - mUVSOffset = 0; // don't bother to read the table again - return rv; - } - - mUVSData = uvsData; - } - - return NS_OK; -} - -uint16_t gfxFontEntry::GetUVSGlyph(uint32_t aCh, uint32_t aVS) -{ - InitializeUVSMap(); - - if (mUVSData) { - return gfxFontUtils::MapUVSToGlyphFormat14(mUVSData, aCh, aVS); - } - - return 0; -} - -bool gfxFontEntry::SupportsScriptInGSUB(const hb_tag_t* aScriptTags) -{ - hb_face_t *face = GetHBFace(); - if (!face) { - return false; - } - - unsigned int index; - hb_tag_t chosenScript; - bool found = - hb_ot_layout_table_choose_script(face, TRUETYPE_TAG('G','S','U','B'), - aScriptTags, &index, &chosenScript); - hb_face_destroy(face); - - return found && chosenScript != TRUETYPE_TAG('D','F','L','T'); -} - -nsresult gfxFontEntry::ReadCMAP(FontInfoData *aFontInfoData) -{ - NS_ASSERTION(false, "using default no-op implementation of ReadCMAP"); - mCharacterMap = new gfxCharacterMap(); - return NS_OK; -} - -nsString -gfxFontEntry::RealFaceName() -{ - AutoTable nameTable(this, TRUETYPE_TAG('n','a','m','e')); - if (nameTable) { - nsAutoString name; - nsresult rv = gfxFontUtils::GetFullNameFromTable(nameTable, name); - if (NS_SUCCEEDED(rv)) { - return name; - } - } - return Name(); -} - -already_AddRefed -gfxFontEntry::FindOrMakeFont(const gfxFontStyle *aStyle, bool aNeedsBold) -{ - // the font entry name is the psname, not the family name - nsRefPtr font = gfxFontCache::GetCache()->Lookup(this, aStyle); - - if (!font) { - gfxFont *newFont = CreateFontInstance(aStyle, aNeedsBold); - if (!newFont) - return nullptr; - if (!newFont->Valid()) { - delete newFont; - return nullptr; - } - font = newFont; - gfxFontCache::GetCache()->AddNew(font); - } - return font.forget(); -} - -uint16_t -gfxFontEntry::UnitsPerEm() -{ - if (!mUnitsPerEm) { - AutoTable headTable(this, TRUETYPE_TAG('h','e','a','d')); - if (headTable) { - uint32_t len; - const HeadTable* head = - reinterpret_cast(hb_blob_get_data(headTable, - &len)); - if (len >= sizeof(HeadTable)) { - mUnitsPerEm = head->unitsPerEm; - } - } - - // if we didn't find a usable 'head' table, or if the value was - // outside the valid range, record it as invalid - if (mUnitsPerEm < kMinUPEM || mUnitsPerEm > kMaxUPEM) { - mUnitsPerEm = kInvalidUPEM; - } - } - return mUnitsPerEm; -} - -bool -gfxFontEntry::HasSVGGlyph(uint32_t aGlyphId) -{ - NS_ASSERTION(mSVGInitialized, "SVG data has not yet been loaded. TryGetSVGData() first."); - return mSVGGlyphs->HasSVGGlyph(aGlyphId); -} - -bool -gfxFontEntry::GetSVGGlyphExtents(gfxContext *aContext, uint32_t aGlyphId, - gfxRect *aResult) -{ - NS_ABORT_IF_FALSE(mSVGInitialized, - "SVG data has not yet been loaded. TryGetSVGData() first."); - NS_ABORT_IF_FALSE(mUnitsPerEm >= kMinUPEM && mUnitsPerEm <= kMaxUPEM, - "font has invalid unitsPerEm"); - - gfxContextAutoSaveRestore matrixRestore(aContext); - cairo_matrix_t fontMatrix; - cairo_get_font_matrix(aContext->GetCairo(), &fontMatrix); - - gfxMatrix svgToAppSpace = *reinterpret_cast(&fontMatrix); - svgToAppSpace.Scale(1.0f / mUnitsPerEm, 1.0f / mUnitsPerEm); - - return mSVGGlyphs->GetGlyphExtents(aGlyphId, svgToAppSpace, aResult); -} - -bool -gfxFontEntry::RenderSVGGlyph(gfxContext *aContext, uint32_t aGlyphId, - int aDrawMode, gfxTextContextPaint *aContextPaint) -{ - NS_ASSERTION(mSVGInitialized, "SVG data has not yet been loaded. TryGetSVGData() first."); - return mSVGGlyphs->RenderGlyph(aContext, aGlyphId, DrawMode(aDrawMode), - aContextPaint); -} - -bool -gfxFontEntry::TryGetSVGData(gfxFont* aFont) -{ - if (!gfxPlatform::GetPlatform()->OpenTypeSVGEnabled()) { - return false; - } - - if (!mSVGInitialized) { - mSVGInitialized = true; - - // If UnitsPerEm is not known/valid, we can't use SVG glyphs - if (UnitsPerEm() == kInvalidUPEM) { - return false; - } - - // We don't use AutoTable here because we'll pass ownership of this - // blob to the gfxSVGGlyphs, once we've confirmed the table exists - hb_blob_t *svgTable = GetFontTable(TRUETYPE_TAG('S','V','G',' ')); - if (!svgTable) { - return false; - } - - // gfxSVGGlyphs will hb_blob_destroy() the table when it is finished - // with it. - mSVGGlyphs = new gfxSVGGlyphs(svgTable, this); - } - - if (!mFontsUsingSVGGlyphs.Contains(aFont)) { - mFontsUsingSVGGlyphs.AppendElement(aFont); - } - - return !!mSVGGlyphs; -} - -void -gfxFontEntry::NotifyFontDestroyed(gfxFont* aFont) -{ - mFontsUsingSVGGlyphs.RemoveElement(aFont); -} - -void -gfxFontEntry::NotifyGlyphsChanged() -{ - for (uint32_t i = 0, count = mFontsUsingSVGGlyphs.Length(); i < count; ++i) { - gfxFont* font = mFontsUsingSVGGlyphs[i]; - font->NotifyGlyphsChanged(); - } -} - -bool -gfxFontEntry::TryGetMathTable() -{ - if (!mMathInitialized) { - mMathInitialized = true; - - // If UnitsPerEm is not known/valid, we can't use MATH table - if (UnitsPerEm() == kInvalidUPEM) { - return false; - } - - // We don't use AutoTable here because we'll pass ownership of this - // blob to the gfxMathTable, once we've confirmed the table exists - hb_blob_t *mathTable = GetFontTable(TRUETYPE_TAG('M','A','T','H')); - if (!mathTable) { - return false; - } - - // gfxMathTable will hb_blob_destroy() the table when it is finished - // with it. - mMathTable = new gfxMathTable(mathTable); - if (!mMathTable->HasValidHeaders()) { - mMathTable = nullptr; - return false; - } - } - - return !!mMathTable; -} - -gfxFloat -gfxFontEntry::GetMathConstant(gfxFontEntry::MathConstant aConstant) -{ - NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first."); - gfxFloat value = mMathTable->GetMathConstant(aConstant); - if (aConstant == gfxFontEntry::ScriptPercentScaleDown || - aConstant == gfxFontEntry::ScriptScriptPercentScaleDown || - aConstant == gfxFontEntry::RadicalDegreeBottomRaisePercent) { - return value / 100.0; - } - return value / mUnitsPerEm; -} - -bool -gfxFontEntry::GetMathItalicsCorrection(uint32_t aGlyphID, - gfxFloat* aItalicCorrection) -{ - NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first."); - int16_t italicCorrection; - if (!mMathTable->GetMathItalicsCorrection(aGlyphID, &italicCorrection)) { - return false; - } - *aItalicCorrection = gfxFloat(italicCorrection) / mUnitsPerEm; - return true; -} - -uint32_t -gfxFontEntry::GetMathVariantsSize(uint32_t aGlyphID, bool aVertical, - uint16_t aSize) -{ - NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first."); - return mMathTable->GetMathVariantsSize(aGlyphID, aVertical, aSize); -} - -bool -gfxFontEntry::GetMathVariantsParts(uint32_t aGlyphID, bool aVertical, - uint32_t aGlyphs[4]) -{ - NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first."); - return mMathTable->GetMathVariantsParts(aGlyphID, aVertical, aGlyphs); -} - -bool -gfxFontEntry::TryGetColorGlyphs() -{ - if (mCheckedForColorGlyph) { - return (mCOLR && mCPAL); - } - - mCheckedForColorGlyph = true; - - mCOLR = GetFontTable(TRUETYPE_TAG('C', 'O', 'L', 'R')); - if (!mCOLR) { - return false; - } - - mCPAL = GetFontTable(TRUETYPE_TAG('C', 'P', 'A', 'L')); - if (!mCPAL) { - hb_blob_destroy(mCOLR); - mCOLR = nullptr; - return false; - } - - // validation COLR and CPAL table - if (gfxFontUtils::ValidateColorGlyphs(mCOLR, mCPAL)) { - return true; - } - - hb_blob_destroy(mCOLR); - hb_blob_destroy(mCPAL); - mCOLR = nullptr; - mCPAL = nullptr; - return false; -} - -/** - * FontTableBlobData - * - * See FontTableHashEntry for the general strategy. - */ - -class gfxFontEntry::FontTableBlobData { -public: - // Adopts the content of aBuffer. - explicit FontTableBlobData(FallibleTArray& aBuffer) - : mHashtable(nullptr), mHashKey(0) - { - MOZ_COUNT_CTOR(FontTableBlobData); - mTableData.SwapElements(aBuffer); - } - - ~FontTableBlobData() { - MOZ_COUNT_DTOR(FontTableBlobData); - if (mHashtable && mHashKey) { - mHashtable->RemoveEntry(mHashKey); - } - } - - // Useful for creating blobs - const char *GetTable() const - { - return reinterpret_cast(mTableData.Elements()); - } - uint32_t GetTableLength() const { return mTableData.Length(); } - - // Tell this FontTableBlobData to remove the HashEntry when this is - // destroyed. - void ManageHashEntry(nsTHashtable *aHashtable, - uint32_t aHashKey) - { - mHashtable = aHashtable; - mHashKey = aHashKey; - } - - // Disconnect from the HashEntry (because the blob has already been - // removed from the hashtable). - void ForgetHashEntry() - { - mHashtable = nullptr; - mHashKey = 0; - } - - size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { - return mTableData.SizeOfExcludingThis(aMallocSizeOf); - } - size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { - return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); - } - -private: - // The font table data block, owned (via adoption) - FallibleTArray mTableData; - - // The blob destroy function needs to know the owning hashtable - // and the hashtable key, so that it can remove the entry. - nsTHashtable *mHashtable; - uint32_t mHashKey; - - // not implemented - FontTableBlobData(const FontTableBlobData&); -}; - -hb_blob_t * -gfxFontEntry::FontTableHashEntry:: -ShareTableAndGetBlob(FallibleTArray& aTable, - nsTHashtable *aHashtable) -{ - Clear(); - // adopts elements of aTable - mSharedBlobData = new FontTableBlobData(aTable); - mBlob = hb_blob_create(mSharedBlobData->GetTable(), - mSharedBlobData->GetTableLength(), - HB_MEMORY_MODE_READONLY, - mSharedBlobData, DeleteFontTableBlobData); - if (!mSharedBlobData) { - // The FontTableBlobData was destroyed during hb_blob_create(). - // The (empty) blob is still be held in the hashtable with a strong - // reference. - return hb_blob_reference(mBlob); - } - - // Tell the FontTableBlobData to remove this hash entry when destroyed. - // The hashtable does not keep a strong reference. - mSharedBlobData->ManageHashEntry(aHashtable, GetKey()); - return mBlob; -} - -void -gfxFontEntry::FontTableHashEntry::Clear() -{ - // If the FontTableBlobData is managing the hash entry, then the blob is - // not owned by this HashEntry; otherwise there is strong reference to the - // blob that must be removed. - if (mSharedBlobData) { - mSharedBlobData->ForgetHashEntry(); - mSharedBlobData = nullptr; - } else if (mBlob) { - hb_blob_destroy(mBlob); - } - mBlob = nullptr; -} - -// a hb_destroy_func for hb_blob_create - -/* static */ void -gfxFontEntry::FontTableHashEntry::DeleteFontTableBlobData(void *aBlobData) -{ - delete static_cast(aBlobData); -} - -hb_blob_t * -gfxFontEntry::FontTableHashEntry::GetBlob() const -{ - return hb_blob_reference(mBlob); -} - -bool -gfxFontEntry::GetExistingFontTable(uint32_t aTag, hb_blob_t **aBlob) -{ - if (!mFontTableCache) { - // we do this here rather than on fontEntry construction - // because not all shapers will access the table cache at all - mFontTableCache = new nsTHashtable(8); - } - - FontTableHashEntry *entry = mFontTableCache->GetEntry(aTag); - if (!entry) { - return false; - } - - *aBlob = entry->GetBlob(); - return true; -} - -hb_blob_t * -gfxFontEntry::ShareFontTableAndGetBlob(uint32_t aTag, - FallibleTArray* aBuffer) -{ - if (MOZ_UNLIKELY(!mFontTableCache)) { - // we do this here rather than on fontEntry construction - // because not all shapers will access the table cache at all - mFontTableCache = new nsTHashtable(8); - } - - FontTableHashEntry *entry = mFontTableCache->PutEntry(aTag); - if (MOZ_UNLIKELY(!entry)) { // OOM - return nullptr; - } - - if (!aBuffer) { - // ensure the entry is null - entry->Clear(); - return nullptr; - } - - return entry->ShareTableAndGetBlob(*aBuffer, mFontTableCache); -} - -static int -DirEntryCmp(const void* aKey, const void* aItem) -{ - int32_t tag = *static_cast(aKey); - const TableDirEntry* entry = static_cast(aItem); - return tag - int32_t(entry->tag); -} - -hb_blob_t* -gfxFontEntry::GetTableFromFontData(const void* aFontData, uint32_t aTableTag) -{ - const SFNTHeader* header = - reinterpret_cast(aFontData); - const TableDirEntry* dir = - reinterpret_cast(header + 1); - dir = static_cast - (bsearch(&aTableTag, dir, uint16_t(header->numTables), - sizeof(TableDirEntry), DirEntryCmp)); - if (dir) { - return hb_blob_create(reinterpret_cast(aFontData) + - dir->offset, dir->length, - HB_MEMORY_MODE_READONLY, nullptr, nullptr); - - } - return nullptr; -} - -already_AddRefed -gfxFontEntry::GetCMAPFromFontInfo(FontInfoData *aFontInfoData, - uint32_t& aUVSOffset, - bool& aSymbolFont) -{ - if (!aFontInfoData || !aFontInfoData->mLoadCmaps) { - return nullptr; - } - - return aFontInfoData->GetCMAP(mName, aUVSOffset, aSymbolFont); -} - -hb_blob_t * -gfxFontEntry::GetFontTable(uint32_t aTag) -{ - hb_blob_t *blob; - if (GetExistingFontTable(aTag, &blob)) { - return blob; - } - - FallibleTArray buffer; - bool haveTable = NS_SUCCEEDED(CopyFontTable(aTag, buffer)); - - return ShareFontTableAndGetBlob(aTag, haveTable ? &buffer : nullptr); -} - -// callback for HarfBuzz to get a font table (in hb_blob_t form) -// from the font entry (passed as aUserData) -/*static*/ hb_blob_t * -gfxFontEntry::HBGetTable(hb_face_t *face, uint32_t aTag, void *aUserData) -{ - gfxFontEntry *fontEntry = static_cast(aUserData); - - // bug 589682 - ignore the GDEF table in buggy fonts (applies to - // Italic and BoldItalic faces of Times New Roman) - if (aTag == TRUETYPE_TAG('G','D','E','F') && - fontEntry->IgnoreGDEF()) { - return nullptr; - } - - // bug 721719 - ignore the GSUB table in buggy fonts (applies to Roboto, - // at least on some Android ICS devices; set in gfxFT2FontList.cpp) - if (aTag == TRUETYPE_TAG('G','S','U','B') && - fontEntry->IgnoreGSUB()) { - return nullptr; - } - - return fontEntry->GetFontTable(aTag); -} - -/*static*/ void -gfxFontEntry::HBFaceDeletedCallback(void *aUserData) -{ - gfxFontEntry *fe = static_cast(aUserData); - fe->ForgetHBFace(); -} - -void -gfxFontEntry::ForgetHBFace() -{ - mHBFace = nullptr; -} - -hb_face_t* -gfxFontEntry::GetHBFace() -{ - if (!mHBFace) { - mHBFace = hb_face_create_for_tables(HBGetTable, this, - HBFaceDeletedCallback); - return mHBFace; - } - return hb_face_reference(mHBFace); -} - -/*static*/ const void* -gfxFontEntry::GrGetTable(const void *aAppFaceHandle, unsigned int aName, - size_t *aLen) -{ - gfxFontEntry *fontEntry = - static_cast(const_cast(aAppFaceHandle)); - hb_blob_t *blob = fontEntry->GetFontTable(aName); - if (blob) { - unsigned int blobLength; - const void *tableData = hb_blob_get_data(blob, &blobLength); - fontEntry->mGrTableMap->Put(tableData, blob); - *aLen = blobLength; - return tableData; - } - *aLen = 0; - return nullptr; -} - -/*static*/ void -gfxFontEntry::GrReleaseTable(const void *aAppFaceHandle, - const void *aTableBuffer) -{ - gfxFontEntry *fontEntry = - static_cast(const_cast(aAppFaceHandle)); - void *data; - if (fontEntry->mGrTableMap->Get(aTableBuffer, &data)) { - fontEntry->mGrTableMap->Remove(aTableBuffer); - hb_blob_destroy(static_cast(data)); - } -} - -gr_face* -gfxFontEntry::GetGrFace() -{ - if (!mGrFaceInitialized) { - gr_face_ops faceOps = { - sizeof(gr_face_ops), - GrGetTable, - GrReleaseTable - }; - mGrTableMap = new nsDataHashtable,void*>; - mGrFace = gr_make_face_with_ops(this, &faceOps, gr_face_default); - mGrFaceInitialized = true; - } - ++mGrFaceRefCnt; - return mGrFace; -} - -void -gfxFontEntry::ReleaseGrFace(gr_face *aFace) -{ - MOZ_ASSERT(aFace == mGrFace); // sanity-check - MOZ_ASSERT(mGrFaceRefCnt > 0); - if (--mGrFaceRefCnt == 0) { - gr_face_destroy(mGrFace); - mGrFace = nullptr; - mGrFaceInitialized = false; - delete mGrTableMap; - mGrTableMap = nullptr; - } -} - -void -gfxFontEntry::DisconnectSVG() -{ - if (mSVGInitialized && mSVGGlyphs) { - mSVGGlyphs = nullptr; - mSVGInitialized = false; - } -} - -bool -gfxFontEntry::HasFontTable(uint32_t aTableTag) -{ - AutoTable table(this, aTableTag); - return table && hb_blob_get_length(table) > 0; -} - -void -gfxFontEntry::CheckForGraphiteTables() -{ - mHasGraphiteTables = HasFontTable(TRUETYPE_TAG('S','i','l','f')); -} - - -#define FEATURE_SCRIPT_MASK 0x000000ff // script index replaces low byte of tag - -// check for too many script codes -PR_STATIC_ASSERT(MOZ_NUM_SCRIPT_CODES <= FEATURE_SCRIPT_MASK); - -// high-order three bytes of tag with script in low-order byte -#define SCRIPT_FEATURE(s,tag) (((~FEATURE_SCRIPT_MASK) & (tag)) | \ - ((FEATURE_SCRIPT_MASK) & (s))) - -bool -gfxFontEntry::SupportsOpenTypeFeature(int32_t aScript, uint32_t aFeatureTag) -{ - if (!mSupportedFeatures) { - mSupportedFeatures = new nsDataHashtable(); - } - - // note: high-order three bytes *must* be unique for each feature - // listed below (see SCRIPT_FEATURE macro def'n) - NS_ASSERTION(aFeatureTag == HB_TAG('s','m','c','p') || - aFeatureTag == HB_TAG('c','2','s','c') || - aFeatureTag == HB_TAG('p','c','a','p') || - aFeatureTag == HB_TAG('c','2','p','c') || - aFeatureTag == HB_TAG('s','u','p','s') || - aFeatureTag == HB_TAG('s','u','b','s'), - "use of unknown feature tag"); - - // note: graphite feature support uses the last script index - NS_ASSERTION(aScript < FEATURE_SCRIPT_MASK - 1, - "need to bump the size of the feature shift"); - - uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag); - bool result; - if (mSupportedFeatures->Get(scriptFeature, &result)) { - return result; - } - - result = false; - - hb_face_t *face = GetHBFace(); - - if (hb_ot_layout_has_substitution(face)) { - hb_script_t hbScript = - gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript); - - // Get the OpenType tag(s) that match this script code - hb_tag_t scriptTags[4] = { - HB_TAG_NONE, - HB_TAG_NONE, - HB_TAG_NONE, - HB_TAG_NONE - }; - hb_ot_tags_from_script(hbScript, &scriptTags[0], &scriptTags[1]); - - // Replace the first remaining NONE with DEFAULT - hb_tag_t* scriptTag = &scriptTags[0]; - while (*scriptTag != HB_TAG_NONE) { - ++scriptTag; - } - *scriptTag = HB_OT_TAG_DEFAULT_SCRIPT; - - // Now check for 'smcp' under the first of those scripts that is present - const hb_tag_t kGSUB = HB_TAG('G','S','U','B'); - scriptTag = &scriptTags[0]; - while (*scriptTag != HB_TAG_NONE) { - unsigned int scriptIndex; - if (hb_ot_layout_table_find_script(face, kGSUB, *scriptTag, - &scriptIndex)) { - if (hb_ot_layout_language_find_feature(face, kGSUB, - scriptIndex, - HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX, - aFeatureTag, nullptr)) { - result = true; - } - break; - } - ++scriptTag; - } - } - - hb_face_destroy(face); - - mSupportedFeatures->Put(scriptFeature, result); - - return result; -} - -const hb_set_t* -gfxFontEntry::InputsForOpenTypeFeature(int32_t aScript, uint32_t aFeatureTag) -{ - if (!mFeatureInputs) { - mFeatureInputs = new nsDataHashtable(); - } - - NS_ASSERTION(aFeatureTag == HB_TAG('s','u','p','s') || - aFeatureTag == HB_TAG('s','u','b','s'), - "use of unknown feature tag"); - - uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag); - hb_set_t *inputGlyphs; - if (mFeatureInputs->Get(scriptFeature, &inputGlyphs)) { - return inputGlyphs; - } - - inputGlyphs = hb_set_create(); - - hb_face_t *face = GetHBFace(); - - if (hb_ot_layout_has_substitution(face)) { - hb_script_t hbScript = - gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript); - - // Get the OpenType tag(s) that match this script code - hb_tag_t scriptTags[4] = { - HB_TAG_NONE, - HB_TAG_NONE, - HB_TAG_NONE, - HB_TAG_NONE - }; - hb_ot_tags_from_script(hbScript, &scriptTags[0], &scriptTags[1]); - - // Replace the first remaining NONE with DEFAULT - hb_tag_t* scriptTag = &scriptTags[0]; - while (*scriptTag != HB_TAG_NONE) { - ++scriptTag; - } - *scriptTag = HB_OT_TAG_DEFAULT_SCRIPT; - - const hb_tag_t kGSUB = HB_TAG('G','S','U','B'); - hb_tag_t features[2] = { aFeatureTag, HB_TAG_NONE }; - hb_set_t *featurelookups = hb_set_create(); - hb_ot_layout_collect_lookups(face, kGSUB, scriptTags, nullptr, - features, featurelookups); - hb_codepoint_t index = -1; - while (hb_set_next(featurelookups, &index)) { - hb_ot_layout_lookup_collect_glyphs(face, kGSUB, index, - nullptr, inputGlyphs, - nullptr, nullptr); - } - } - - hb_face_destroy(face); - - mFeatureInputs->Put(scriptFeature, inputGlyphs); - return inputGlyphs; -} - -bool -gfxFontEntry::SupportsGraphiteFeature(uint32_t aFeatureTag) -{ - if (!mSupportedFeatures) { - mSupportedFeatures = new nsDataHashtable(); - } - - // note: high-order three bytes *must* be unique for each feature - // listed below (see SCRIPT_FEATURE macro def'n) - NS_ASSERTION(aFeatureTag == HB_TAG('s','m','c','p') || - aFeatureTag == HB_TAG('c','2','s','c') || - aFeatureTag == HB_TAG('p','c','a','p') || - aFeatureTag == HB_TAG('c','2','p','c') || - aFeatureTag == HB_TAG('s','u','p','s') || - aFeatureTag == HB_TAG('s','u','b','s'), - "use of unknown feature tag"); - - // graphite feature check uses the last script slot - uint32_t scriptFeature = SCRIPT_FEATURE(FEATURE_SCRIPT_MASK, aFeatureTag); - bool result; - if (mSupportedFeatures->Get(scriptFeature, &result)) { - return result; - } - - gr_face* face = GetGrFace(); - result = gr_face_find_fref(face, aFeatureTag) != nullptr; - ReleaseGrFace(face); - - mSupportedFeatures->Put(scriptFeature, result); - - return result; -} - -bool -gfxFontEntry::GetColorLayersInfo(uint32_t aGlyphId, - nsTArray& aLayerGlyphs, - nsTArray& aLayerColors) -{ - return gfxFontUtils::GetColorGlyphLayers(mCOLR, - mCPAL, - aGlyphId, - aLayerGlyphs, - aLayerColors); -} - -size_t -gfxFontEntry::FontTableHashEntry::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const -{ - size_t n = 0; - if (mBlob) { - n += aMallocSizeOf(mBlob); - } - if (mSharedBlobData) { - n += mSharedBlobData->SizeOfIncludingThis(aMallocSizeOf); - } - return n; -} - -void -gfxFontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); - - // cmaps are shared so only non-shared cmaps are included here - if (mCharacterMap && mCharacterMap->mBuildOnTheFly) { - aSizes->mCharMapsSize += - mCharacterMap->SizeOfIncludingThis(aMallocSizeOf); - } - if (mFontTableCache) { - aSizes->mFontTableCacheSize += - mFontTableCache->SizeOfIncludingThis(aMallocSizeOf); - } -} - -void -gfxFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += aMallocSizeOf(this); - AddSizeOfExcludingThis(aMallocSizeOf, aSizes); -} - -////////////////////////////////////////////////////////////////////////////// -// -// class gfxFontFamily -// -////////////////////////////////////////////////////////////////////////////// - -// we consider faces with mStandardFace == true to be "greater than" those with false, -// because during style matching, later entries will replace earlier ones -class FontEntryStandardFaceComparator { - public: - bool Equals(const nsRefPtr& a, const nsRefPtr& b) const { - return a->mStandardFace == b->mStandardFace; - } - bool LessThan(const nsRefPtr& a, const nsRefPtr& b) const { - return (a->mStandardFace == false && b->mStandardFace == true); - } -}; - -void -gfxFontFamily::SortAvailableFonts() -{ - mAvailableFonts.Sort(FontEntryStandardFaceComparator()); -} - -bool -gfxFontFamily::HasOtherFamilyNames() -{ - // need to read in other family names to determine this - if (!mOtherFamilyNamesInitialized) { - ReadOtherFamilyNames(gfxPlatformFontList::PlatformFontList()); // sets mHasOtherFamilyNames - } - return mHasOtherFamilyNames; -} - -gfxFontEntry* -gfxFontFamily::FindFontForStyle(const gfxFontStyle& aFontStyle, - bool& aNeedsSyntheticBold) -{ - if (!mHasStyles) - FindStyleVariations(); // collect faces for the family, if not already done - - NS_ASSERTION(mAvailableFonts.Length() > 0, "font family with no faces!"); - - aNeedsSyntheticBold = false; - - int8_t baseWeight = aFontStyle.ComputeWeight(); - bool wantBold = baseWeight >= 6; - - // If the family has only one face, we simply return it; no further checking needed - if (mAvailableFonts.Length() == 1) { - gfxFontEntry *fe = mAvailableFonts[0]; - aNeedsSyntheticBold = - wantBold && !fe->IsBold() && aFontStyle.allowSyntheticWeight; - return fe; - } - - bool wantItalic = (aFontStyle.style & - (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0; - - // Most families are "simple", having just Regular/Bold/Italic/BoldItalic, - // or some subset of these. In this case, we have exactly 4 entries in mAvailableFonts, - // stored in the above order; note that some of the entries may be nullptr. - // We can then pick the required entry based on whether the request is for - // bold or non-bold, italic or non-italic, without running the more complex - // matching algorithm used for larger families with many weights and/or widths. - - if (mIsSimpleFamily) { - // Family has no more than the "standard" 4 faces, at fixed indexes; - // calculate which one we want. - // Note that we cannot simply return it as not all 4 faces are necessarily present. - uint8_t faceIndex = (wantItalic ? kItalicMask : 0) | - (wantBold ? kBoldMask : 0); - - // if the desired style is available, return it directly - gfxFontEntry *fe = mAvailableFonts[faceIndex]; - if (fe) { - // no need to set aNeedsSyntheticBold here as we matched the boldness request - return fe; - } - - // order to check fallback faces in a simple family, depending on requested style - static const uint8_t simpleFallbacks[4][3] = { - { kBoldFaceIndex, kItalicFaceIndex, kBoldItalicFaceIndex }, // fallbacks for Regular - { kRegularFaceIndex, kBoldItalicFaceIndex, kItalicFaceIndex },// Bold - { kBoldItalicFaceIndex, kRegularFaceIndex, kBoldFaceIndex }, // Italic - { kItalicFaceIndex, kBoldFaceIndex, kRegularFaceIndex } // BoldItalic - }; - const uint8_t *order = simpleFallbacks[faceIndex]; - - for (uint8_t trial = 0; trial < 3; ++trial) { - // check remaining faces in order of preference to find the first that actually exists - fe = mAvailableFonts[order[trial]]; - if (fe) { - aNeedsSyntheticBold = - wantBold && !fe->IsBold() && - aFontStyle.allowSyntheticWeight; - return fe; - } - } - - // this can't happen unless we have totally broken the font-list manager! - NS_NOTREACHED("no face found in simple font family!"); - return nullptr; - } - - // This is a large/rich font family, so we do full style- and weight-matching: - // first collect a list of weights that are the best match for the requested - // font-stretch and font-style, then pick the best weight match among those - // available. - - gfxFontEntry *weightList[10] = { 0 }; - bool foundWeights = FindWeightsForStyle(weightList, wantItalic, aFontStyle.stretch); - if (!foundWeights) { - return nullptr; - } - - // First find a match for the best weight - int8_t matchBaseWeight = 0; - int8_t i = baseWeight; - - // Need to special case when normal face doesn't exist but medium does. - // In that case, use medium otherwise weights < 400 - if (baseWeight == 4 && !weightList[4]) { - i = 5; // medium - } - - // Loop through weights, since one exists loop will terminate - int8_t direction = (baseWeight > 5) ? 1 : -1; - for (; ; i += direction) { - if (weightList[i]) { - matchBaseWeight = i; - break; - } - - // If we've reached one side without finding a font, - // start over and go the other direction until we find a match - if (i == 1 || i == 9) { - i = baseWeight; - direction = -direction; - } - } - - NS_ASSERTION(matchBaseWeight != 0, - "weight mapping should always find at least one font in a family"); - - gfxFontEntry *matchFE = weightList[matchBaseWeight]; - - NS_ASSERTION(matchFE, - "weight mapping should always find at least one font in a family"); - - if (!matchFE->IsBold() && baseWeight >= 6 && - aFontStyle.allowSyntheticWeight) - { - aNeedsSyntheticBold = true; - } - - return matchFE; -} - -void -gfxFontFamily::CheckForSimpleFamily() -{ - // already checked this family - if (mIsSimpleFamily) { - return; - }; - - uint32_t count = mAvailableFonts.Length(); - if (count > 4 || count == 0) { - return; // can't be "simple" if there are >4 faces; - // if none then the family is unusable anyway - } - - if (count == 1) { - mIsSimpleFamily = true; - return; - } - - int16_t firstStretch = mAvailableFonts[0]->Stretch(); - - gfxFontEntry *faces[4] = { 0 }; - for (uint8_t i = 0; i < count; ++i) { - gfxFontEntry *fe = mAvailableFonts[i]; - if (fe->Stretch() != firstStretch) { - return; // font-stretch doesn't match, don't treat as simple family - } - uint8_t faceIndex = (fe->IsItalic() ? kItalicMask : 0) | - (fe->Weight() >= 600 ? kBoldMask : 0); - if (faces[faceIndex]) { - return; // two faces resolve to the same slot; family isn't "simple" - } - faces[faceIndex] = fe; - } - - // we have successfully slotted the available faces into the standard - // 4-face framework - mAvailableFonts.SetLength(4); - for (uint8_t i = 0; i < 4; ++i) { - if (mAvailableFonts[i].get() != faces[i]) { - mAvailableFonts[i].swap(faces[i]); - } - } - - mIsSimpleFamily = true; -} - -#ifdef DEBUG -bool -gfxFontFamily::ContainsFace(gfxFontEntry* aFontEntry) { - uint32_t i, numFonts = mAvailableFonts.Length(); - for (i = 0; i < numFonts; i++) { - if (mAvailableFonts[i] == aFontEntry) { - return true; - } - // userfonts contain the actual real font entry - if (mAvailableFonts[i]->mIsUserFontContainer) { - gfxUserFontEntry* ufe = - static_cast(mAvailableFonts[i].get()); - if (ufe->GetPlatformFontEntry() == aFontEntry) { - return true; - } - } - } - return false; -} -#endif - -static inline uint32_t -StyleDistance(gfxFontEntry *aFontEntry, - bool anItalic, int16_t aStretch) -{ - // Compute a measure of the "distance" between the requested style - // and the given fontEntry, - // considering italicness and font-stretch but not weight. - - int32_t distance = 0; - if (aStretch != aFontEntry->mStretch) { - // stretch values are in the range -4 .. +4 - // if aStretch is positive, we prefer more-positive values; - // if zero or negative, prefer more-negative - if (aStretch > 0) { - distance = (aFontEntry->mStretch - aStretch) * 2; - } else { - distance = (aStretch - aFontEntry->mStretch) * 2; - } - // if the computed "distance" here is negative, it means that - // aFontEntry lies in the "non-preferred" direction from aStretch, - // so we treat that as larger than any preferred-direction distance - // (max possible is 8) by adding an extra 10 to the absolute value - if (distance < 0) { - distance = -distance + 10; - } - } - if (aFontEntry->IsItalic() != anItalic) { - distance += 1; - } - return uint32_t(distance); -} - -bool -gfxFontFamily::FindWeightsForStyle(gfxFontEntry* aFontsForWeights[], - bool anItalic, int16_t aStretch) -{ - uint32_t foundWeights = 0; - uint32_t bestMatchDistance = 0xffffffff; - - uint32_t count = mAvailableFonts.Length(); - for (uint32_t i = 0; i < count; i++) { - // this is not called for "simple" families, and therefore it does not - // need to check the mAvailableFonts entries for nullptr. - gfxFontEntry *fe = mAvailableFonts[i]; - uint32_t distance = StyleDistance(fe, anItalic, aStretch); - if (distance <= bestMatchDistance) { - int8_t wt = fe->mWeight / 100; - NS_ASSERTION(wt >= 1 && wt < 10, "invalid weight in fontEntry"); - if (!aFontsForWeights[wt]) { - // record this as a possible candidate for weight matching - aFontsForWeights[wt] = fe; - ++foundWeights; - } else { - uint32_t prevDistance = - StyleDistance(aFontsForWeights[wt], anItalic, aStretch); - if (prevDistance >= distance) { - // replacing a weight we already found, - // so don't increment foundWeights - aFontsForWeights[wt] = fe; - } - } - bestMatchDistance = distance; - } - } - - NS_ASSERTION(foundWeights > 0, "Font family containing no faces?"); - - if (foundWeights == 1) { - // no need to cull entries if we only found one weight - return true; - } - - // we might have recorded some faces that were a partial style match, but later found - // others that were closer; in this case, we need to cull the poorer matches from the - // weight list we'll return - for (uint32_t i = 0; i < 10; ++i) { - if (aFontsForWeights[i] && - StyleDistance(aFontsForWeights[i], anItalic, aStretch) > bestMatchDistance) - { - aFontsForWeights[i] = 0; - } - } - - return (foundWeights > 0); -} - - -void gfxFontFamily::LocalizedName(nsAString& aLocalizedName) -{ - // just return the primary name; subclasses should override - aLocalizedName = mName; -} - -// metric for how close a given font matches a style -static int32_t -CalcStyleMatch(gfxFontEntry *aFontEntry, const gfxFontStyle *aStyle) -{ - int32_t rank = 0; - if (aStyle) { - // italics - bool wantItalic = - (aStyle->style & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0; - if (aFontEntry->IsItalic() == wantItalic) { - rank += 10; - } - - // measure of closeness of weight to the desired value - rank += 9 - DeprecatedAbs(aFontEntry->Weight() / 100 - aStyle->ComputeWeight()); - } else { - // if no font to match, prefer non-bold, non-italic fonts - if (!aFontEntry->IsItalic()) { - rank += 3; - } - if (!aFontEntry->IsBold()) { - rank += 2; - } - } - - return rank; -} - -#define RANK_MATCHED_CMAP 20 - -void -gfxFontFamily::FindFontForChar(GlobalFontMatch *aMatchData) -{ - if (mFamilyCharacterMapInitialized && !TestCharacterMap(aMatchData->mCh)) { - // none of the faces in the family support the required char, - // so bail out immediately - return; - } - - bool needsBold; - gfxFontStyle normal; - gfxFontEntry *fe = FindFontForStyle( - (aMatchData->mStyle == nullptr) ? *aMatchData->mStyle : normal, - needsBold); - - if (fe && !fe->SkipDuringSystemFallback()) { - int32_t rank = 0; - - if (fe->TestCharacterMap(aMatchData->mCh)) { - rank += RANK_MATCHED_CMAP; - aMatchData->mCount++; -#ifdef PR_LOGGING - PRLogModuleInfo *log = gfxPlatform::GetLog(eGfxLog_textrun); - - if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_DEBUG))) { - uint32_t unicodeRange = FindCharUnicodeRange(aMatchData->mCh); - uint32_t script = GetScriptCode(aMatchData->mCh); - PR_LOG(log, PR_LOG_DEBUG,\ - ("(textrun-systemfallback-fonts) char: u+%6.6x " - "unicode-range: %d script: %d match: [%s]\n", - aMatchData->mCh, - unicodeRange, script, - NS_ConvertUTF16toUTF8(fe->Name()).get())); - } -#endif - } - - aMatchData->mCmapsTested++; - if (rank == 0) { - return; - } - - // omitting from original windows code -- family name, lang group, pitch - // not available in current FontEntry implementation - rank += CalcStyleMatch(fe, aMatchData->mStyle); - - // xxx - add whether AAT font with morphing info for specific lang groups - - if (rank > aMatchData->mMatchRank - || (rank == aMatchData->mMatchRank && - Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) - { - aMatchData->mBestMatch = fe; - aMatchData->mMatchedFamily = this; - aMatchData->mMatchRank = rank; - } - } -} - -void -gfxFontFamily::SearchAllFontsForChar(GlobalFontMatch *aMatchData) -{ - uint32_t i, numFonts = mAvailableFonts.Length(); - for (i = 0; i < numFonts; i++) { - gfxFontEntry *fe = mAvailableFonts[i]; - if (fe && fe->TestCharacterMap(aMatchData->mCh)) { - int32_t rank = RANK_MATCHED_CMAP; - rank += CalcStyleMatch(fe, aMatchData->mStyle); - if (rank > aMatchData->mMatchRank - || (rank == aMatchData->mMatchRank && - Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) - { - aMatchData->mBestMatch = fe; - aMatchData->mMatchedFamily = this; - aMatchData->mMatchRank = rank; - } - } - } -} - -/*static*/ void -gfxFontFamily::ReadOtherFamilyNamesForFace(const nsAString& aFamilyName, - const char *aNameData, - uint32_t aDataLength, - nsTArray& aOtherFamilyNames, - bool useFullName) -{ - const gfxFontUtils::NameHeader *nameHeader = - reinterpret_cast(aNameData); - - uint32_t nameCount = nameHeader->count; - if (nameCount * sizeof(gfxFontUtils::NameRecord) > aDataLength) { - NS_WARNING("invalid font (name records)"); - return; - } - - const gfxFontUtils::NameRecord *nameRecord = - reinterpret_cast(aNameData + sizeof(gfxFontUtils::NameHeader)); - uint32_t stringsBase = uint32_t(nameHeader->stringOffset); - - for (uint32_t i = 0; i < nameCount; i++, nameRecord++) { - uint32_t nameLen = nameRecord->length; - uint32_t nameOff = nameRecord->offset; // offset from base of string storage - - if (stringsBase + nameOff + nameLen > aDataLength) { - NS_WARNING("invalid font (name table strings)"); - return; - } - - uint16_t nameID = nameRecord->nameID; - if ((useFullName && nameID == gfxFontUtils::NAME_ID_FULL) || - (!useFullName && (nameID == gfxFontUtils::NAME_ID_FAMILY || - nameID == gfxFontUtils::NAME_ID_PREFERRED_FAMILY))) { - nsAutoString otherFamilyName; - bool ok = gfxFontUtils::DecodeFontName(aNameData + stringsBase + nameOff, - nameLen, - uint32_t(nameRecord->platformID), - uint32_t(nameRecord->encodingID), - uint32_t(nameRecord->languageID), - otherFamilyName); - // add if not same as canonical family name - if (ok && otherFamilyName != aFamilyName) { - aOtherFamilyNames.AppendElement(otherFamilyName); - } - } - } -} - -// returns true if other names were found, false otherwise -bool -gfxFontFamily::ReadOtherFamilyNamesForFace(gfxPlatformFontList *aPlatformFontList, - hb_blob_t *aNameTable, - bool useFullName) -{ - uint32_t dataLength; - const char *nameData = hb_blob_get_data(aNameTable, &dataLength); - nsAutoTArray otherFamilyNames; - - ReadOtherFamilyNamesForFace(mName, nameData, dataLength, - otherFamilyNames, useFullName); - - uint32_t n = otherFamilyNames.Length(); - for (uint32_t i = 0; i < n; i++) { - aPlatformFontList->AddOtherFamilyName(this, otherFamilyNames[i]); - } - - return n != 0; -} - -void -gfxFontFamily::ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList) -{ - if (mOtherFamilyNamesInitialized) - return; - mOtherFamilyNamesInitialized = true; - - FindStyleVariations(); - - // read in other family names for the first face in the list - uint32_t i, numFonts = mAvailableFonts.Length(); - const uint32_t kNAME = TRUETYPE_TAG('n','a','m','e'); - - for (i = 0; i < numFonts; ++i) { - gfxFontEntry *fe = mAvailableFonts[i]; - if (!fe) { - continue; - } - gfxFontEntry::AutoTable nameTable(fe, kNAME); - if (!nameTable) { - continue; - } - mHasOtherFamilyNames = ReadOtherFamilyNamesForFace(aPlatformFontList, - nameTable); - break; - } - - // read in other names for the first face in the list with the assumption - // that if extra names don't exist in that face then they don't exist in - // other faces for the same font - if (!mHasOtherFamilyNames) - return; - - // read in names for all faces, needed to catch cases where fonts have - // family names for individual weights (e.g. Hiragino Kaku Gothic Pro W6) - for ( ; i < numFonts; i++) { - gfxFontEntry *fe = mAvailableFonts[i]; - if (!fe) { - continue; - } - gfxFontEntry::AutoTable nameTable(fe, kNAME); - if (!nameTable) { - continue; - } - ReadOtherFamilyNamesForFace(aPlatformFontList, nameTable); - } -} - -void -gfxFontFamily::ReadFaceNames(gfxPlatformFontList *aPlatformFontList, - bool aNeedFullnamePostscriptNames, - FontInfoData *aFontInfoData) -{ - // if all needed names have already been read, skip - if (mOtherFamilyNamesInitialized && - (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) - return; - - bool asyncFontLoaderDisabled = false; - -#if defined(XP_MACOSX) - // bug 975460 - async font loader crashes sometimes under 10.6, disable - if (!nsCocoaFeatures::OnLionOrLater()) { - asyncFontLoaderDisabled = true; - } -#endif - - if (!mOtherFamilyNamesInitialized && - aFontInfoData && - aFontInfoData->mLoadOtherNames && - !asyncFontLoaderDisabled) - { - nsAutoTArray otherFamilyNames; - bool foundOtherNames = - aFontInfoData->GetOtherFamilyNames(mName, otherFamilyNames); - if (foundOtherNames) { - uint32_t i, n = otherFamilyNames.Length(); - for (i = 0; i < n; i++) { - aPlatformFontList->AddOtherFamilyName(this, otherFamilyNames[i]); - } - } - mOtherFamilyNamesInitialized = true; - } - - // if all needed data has been initialized, return - if (mOtherFamilyNamesInitialized && - (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) { - return; - } - - FindStyleVariations(aFontInfoData); - - // check again, as style enumeration code may have loaded names - if (mOtherFamilyNamesInitialized && - (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) { - return; - } - - uint32_t i, numFonts = mAvailableFonts.Length(); - const uint32_t kNAME = TRUETYPE_TAG('n','a','m','e'); - - bool firstTime = true, readAllFaces = false; - for (i = 0; i < numFonts; ++i) { - gfxFontEntry *fe = mAvailableFonts[i]; - if (!fe) { - continue; - } - - nsAutoString fullname, psname; - bool foundFaceNames = false; - if (!mFaceNamesInitialized && - aNeedFullnamePostscriptNames && - aFontInfoData && - aFontInfoData->mLoadFaceNames) { - aFontInfoData->GetFaceNames(fe->Name(), fullname, psname); - if (!fullname.IsEmpty()) { - aPlatformFontList->AddFullname(fe, fullname); - } - if (!psname.IsEmpty()) { - aPlatformFontList->AddPostscriptName(fe, psname); - } - foundFaceNames = true; - - // found everything needed? skip to next font - if (mOtherFamilyNamesInitialized) { - continue; - } - } - - // load directly from the name table - gfxFontEntry::AutoTable nameTable(fe, kNAME); - if (!nameTable) { - continue; - } - - if (aNeedFullnamePostscriptNames && !foundFaceNames) { - if (gfxFontUtils::ReadCanonicalName( - nameTable, gfxFontUtils::NAME_ID_FULL, fullname) == NS_OK) - { - aPlatformFontList->AddFullname(fe, fullname); - } - - if (gfxFontUtils::ReadCanonicalName( - nameTable, gfxFontUtils::NAME_ID_POSTSCRIPT, psname) == NS_OK) - { - aPlatformFontList->AddPostscriptName(fe, psname); - } - } - - if (!mOtherFamilyNamesInitialized && (firstTime || readAllFaces)) { - bool foundOtherName = ReadOtherFamilyNamesForFace(aPlatformFontList, - nameTable); - - // if the first face has a different name, scan all faces, otherwise - // assume the family doesn't have other names - if (firstTime && foundOtherName) { - mHasOtherFamilyNames = true; - readAllFaces = true; - } - firstTime = false; - } - - // if not reading in any more names, skip other faces - if (!readAllFaces && !aNeedFullnamePostscriptNames) { - break; - } - } - - mFaceNamesInitialized = true; - mOtherFamilyNamesInitialized = true; -} - - -gfxFontEntry* -gfxFontFamily::FindFont(const nsAString& aPostscriptName) -{ - // find the font using a simple linear search - uint32_t numFonts = mAvailableFonts.Length(); - for (uint32_t i = 0; i < numFonts; i++) { - gfxFontEntry *fe = mAvailableFonts[i].get(); - if (fe && fe->Name() == aPostscriptName) - return fe; - } - return nullptr; -} - -void -gfxFontFamily::ReadAllCMAPs(FontInfoData *aFontInfoData) -{ - FindStyleVariations(aFontInfoData); - - uint32_t i, numFonts = mAvailableFonts.Length(); - for (i = 0; i < numFonts; i++) { - gfxFontEntry *fe = mAvailableFonts[i]; - // don't try to load cmaps for downloadable fonts not yet loaded - if (!fe || fe->mIsUserFontContainer) { - continue; - } - fe->ReadCMAP(aFontInfoData); - mFamilyCharacterMap.Union(*(fe->mCharacterMap)); - } - mFamilyCharacterMap.Compact(); - mFamilyCharacterMapInitialized = true; -} - -void -gfxFontFamily::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += - mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); - aSizes->mCharMapsSize += - mFamilyCharacterMap.SizeOfExcludingThis(aMallocSizeOf); - - aSizes->mFontListSize += - mAvailableFonts.SizeOfExcludingThis(aMallocSizeOf); - for (uint32_t i = 0; i < mAvailableFonts.Length(); ++i) { - gfxFontEntry *fe = mAvailableFonts[i]; - if (fe) { - fe->AddSizeOfIncludingThis(aMallocSizeOf, aSizes); - } - } -} - -void -gfxFontFamily::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += aMallocSizeOf(this); - AddSizeOfExcludingThis(aMallocSizeOf, aSizes); -} /* * gfxFontCache - global cache of gfxFont instances. @@ -2320,6 +543,184 @@ gfxFontShaper::MergeFontFeatures( return aMergedFeatures.Count() != 0; } +void +gfxShapedText::SetupClusterBoundaries(uint32_t aOffset, + const char16_t *aString, + uint32_t aLength) +{ + CompressedGlyph *glyphs = GetCharacterGlyphs() + aOffset; + + gfxTextRun::CompressedGlyph extendCluster; + extendCluster.SetComplex(false, true, 0); + + ClusterIterator iter(aString, aLength); + + // the ClusterIterator won't be able to tell us if the string + // _begins_ with a cluster-extender, so we handle that here + if (aLength && IsClusterExtender(*aString)) { + *glyphs = extendCluster; + } + + while (!iter.AtEnd()) { + if (*iter == char16_t(' ')) { + glyphs->SetIsSpace(); + } + // advance iter to the next cluster-start (or end of text) + iter.Next(); + // step past the first char of the cluster + aString++; + glyphs++; + // mark all the rest as cluster-continuations + while (aString < iter) { + *glyphs = extendCluster; + if (NS_IS_LOW_SURROGATE(*aString)) { + glyphs->SetIsLowSurrogate(); + } + glyphs++; + aString++; + } + } +} + +void +gfxShapedText::SetupClusterBoundaries(uint32_t aOffset, + const uint8_t *aString, + uint32_t aLength) +{ + CompressedGlyph *glyphs = GetCharacterGlyphs() + aOffset; + const uint8_t *limit = aString + aLength; + + while (aString < limit) { + if (*aString == uint8_t(' ')) { + glyphs->SetIsSpace(); + } + aString++; + glyphs++; + } +} + +gfxShapedText::DetailedGlyph * +gfxShapedText::AllocateDetailedGlyphs(uint32_t aIndex, uint32_t aCount) +{ + NS_ASSERTION(aIndex < GetLength(), "Index out of range"); + + if (!mDetailedGlyphs) { + mDetailedGlyphs = new DetailedGlyphStore(); + } + + return mDetailedGlyphs->Allocate(aIndex, aCount); +} + +void +gfxShapedText::SetGlyphs(uint32_t aIndex, CompressedGlyph aGlyph, + const DetailedGlyph *aGlyphs) +{ + NS_ASSERTION(!aGlyph.IsSimpleGlyph(), "Simple glyphs not handled here"); + NS_ASSERTION(aIndex > 0 || aGlyph.IsLigatureGroupStart(), + "First character can't be a ligature continuation!"); + + uint32_t glyphCount = aGlyph.GetGlyphCount(); + if (glyphCount > 0) { + DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, glyphCount); + memcpy(details, aGlyphs, sizeof(DetailedGlyph)*glyphCount); + } + GetCharacterGlyphs()[aIndex] = aGlyph; +} + +#define ZWNJ 0x200C +#define ZWJ 0x200D +// U+061C ARABIC LETTER MARK is expected to be added to XIDMOD_DEFAULT_IGNORABLE +// in a future Unicode update. Add it manually for now +#define ALM 0x061C +static inline bool +IsDefaultIgnorable(uint32_t aChar) +{ + return GetIdentifierModification(aChar) == XIDMOD_DEFAULT_IGNORABLE || + aChar == ZWNJ || aChar == ZWJ || aChar == ALM; +} + +void +gfxShapedText::SetMissingGlyph(uint32_t aIndex, uint32_t aChar, gfxFont *aFont) +{ + uint8_t category = GetGeneralCategory(aChar); + if (category >= HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK && + category <= HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) + { + GetCharacterGlyphs()[aIndex].SetComplex(false, true, 0); + } + + DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); + + details->mGlyphID = aChar; + if (IsDefaultIgnorable(aChar)) { + // Setting advance width to zero will prevent drawing the hexbox + details->mAdvance = 0; + } else { + gfxFloat width = + std::max(aFont->GetMetrics().aveCharWidth, + gfxFontMissingGlyphs::GetDesiredMinWidth(aChar, + mAppUnitsPerDevUnit)); + details->mAdvance = uint32_t(width * mAppUnitsPerDevUnit); + } + details->mXOffset = 0; + details->mYOffset = 0; + GetCharacterGlyphs()[aIndex].SetMissing(1); +} + +bool +gfxShapedText::FilterIfIgnorable(uint32_t aIndex, uint32_t aCh) +{ + if (IsDefaultIgnorable(aCh)) { + DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); + details->mGlyphID = aCh; + details->mAdvance = 0; + details->mXOffset = 0; + details->mYOffset = 0; + GetCharacterGlyphs()[aIndex].SetMissing(1); + return true; + } + return false; +} + +void +gfxShapedText::AdjustAdvancesForSyntheticBold(float aSynBoldOffset, + uint32_t aOffset, + uint32_t aLength) +{ + uint32_t synAppUnitOffset = aSynBoldOffset * mAppUnitsPerDevUnit; + CompressedGlyph *charGlyphs = GetCharacterGlyphs(); + for (uint32_t i = aOffset; i < aOffset + aLength; ++i) { + CompressedGlyph *glyphData = charGlyphs + i; + if (glyphData->IsSimpleGlyph()) { + // simple glyphs ==> just add the advance + int32_t advance = glyphData->GetSimpleAdvance() + synAppUnitOffset; + if (CompressedGlyph::IsSimpleAdvance(advance)) { + glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph()); + } else { + // rare case, tested by making this the default + uint32_t glyphIndex = glyphData->GetSimpleGlyph(); + glyphData->SetComplex(true, true, 1); + DetailedGlyph detail = {glyphIndex, advance, 0, 0}; + SetGlyphs(i, *glyphData, &detail); + } + } else { + // complex glyphs ==> add offset at cluster/ligature boundaries + uint32_t detailedLength = glyphData->GetGlyphCount(); + if (detailedLength) { + DetailedGlyph *details = GetDetailedGlyphs(i); + if (!details) { + continue; + } + if (IsRightToLeft()) { + details[0].mAdvance += synAppUnitOffset; + } else { + details[detailedLength - 1].mAdvance += synAppUnitOffset; + } + } + } + } +} + void gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft) { @@ -3076,36 +1477,6 @@ static AntialiasMode Get2DAAMode(gfxFont::AntialiasOption aAAOption) { } } -// Parameters passed to gfxFont methods for drawing glyphs from a textrun. -// The TextRunDrawParams are set up once per textrun; the FontDrawParams -// are dependent on the specific font, so they are set per GlyphRun. - -struct TextRunDrawParams { - RefPtr dt; - gfxContext *context; - gfxFont::Spacing *spacing; - gfxTextRunDrawCallbacks *callbacks; - gfxTextContextPaint *runContextPaint; - gfxFloat direction; - double devPerApp; - DrawMode drawMode; - bool isRTL; - bool paintSVGGlyphs; -}; - -struct FontDrawParams { - RefPtr scaledFont; - RefPtr renderingOptions; - gfxTextContextPaint *contextPaint; - Matrix *passedInvMatrix; - Matrix matInv; - double synBoldOnePixelOffset; - int32_t extraStrikes; - DrawOptions drawOptions; - bool haveSVGGlyphs; - bool haveColorGlyphs; -}; - class GlyphBufferAzure { public: @@ -3658,20 +2029,6 @@ NeedsGlyphExtents(gfxFont *aFont, gfxTextRun *aTextRun) aFont->GetFontEntry()->IsUserFont(); } -static bool -NeedsGlyphExtents(gfxTextRun *aTextRun) -{ - if (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX) - return true; - uint32_t numRuns; - const gfxTextRun::GlyphRun *glyphRuns = aTextRun->GetGlyphRuns(&numRuns); - for (uint32_t i = 0; i < numRuns; ++i) { - if (glyphRuns[i].mFont->GetFontEntry()->IsUserFont()) - return true; - } - return false; -} - gfxFont::RunMetrics gfxFont::Measure(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd, @@ -3837,12 +2194,6 @@ IsBoundarySpace(char16_t aChar, char16_t aNextChar) return (aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar); } -static inline uint32_t -HashMix(uint32_t aHash, char16_t aCh) -{ - return (aHash >> 28) ^ (aHash << 4) ^ aCh; -} - #ifdef __GNUC__ #define GFX_MAYBE_UNUSED __attribute__((unused)) #else @@ -3930,7 +2281,7 @@ gfxFont::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const return false; } if (sw->GetLength() != aKey->mLength || - sw->Flags() != aKey->mFlags || + sw->GetFlags() != aKey->mFlags || sw->GetAppUnitsPerDevUnit() != aKey->mAppUnitsPerDevUnit || sw->Script() != aKey->mScript) { return false; @@ -4248,7 +2599,7 @@ gfxFont::SplitAndInitTextRun(gfxContext *aContext, wordIs8Bit = false; } // include this character in the hash, and move on to next - hash = HashMix(hash, ch); + hash = gfxShapedWord::HashMix(hash, ch); continue; } @@ -4298,7 +2649,7 @@ gfxFont::SplitAndInitTextRun(gfxContext *aContext, gfxShapedWord *sw = GetShapedWord(aContext, &space, 1, - HashMix(0, ' '), aRunScript, + gfxShapedWord::HashMix(0, ' '), aRunScript, appUnitsPerDevUnit, flags | gfxTextRunFactory::TEXT_IS_8BIT, tp); if (sw) { @@ -4340,6 +2691,220 @@ gfxFont::SplitAndInitTextRun(gfxContext *aContext, return true; } +// Explicit instantiations of SplitAndInitTextRun, to avoid libxul link failure +template bool +gfxFont::SplitAndInitTextRun(gfxContext *aContext, + gfxTextRun *aTextRun, + const uint8_t *aString, + uint32_t aRunStart, + uint32_t aRunLength, + int32_t aRunScript); +template bool +gfxFont::SplitAndInitTextRun(gfxContext *aContext, + gfxTextRun *aTextRun, + const char16_t *aString, + uint32_t aRunStart, + uint32_t aRunLength, + int32_t aRunScript); + +bool +gfxFont::InitFakeSmallCapsRun(gfxContext *aContext, + gfxTextRun *aTextRun, + const uint8_t *aText, + uint32_t aOffset, + uint32_t aLength, + uint8_t aMatchType, + int32_t aScript, + bool aSyntheticLower, + bool aSyntheticUpper) +{ + NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast(aText), + aLength); + return InitFakeSmallCapsRun(aContext, aTextRun, unicodeString.get(), + aOffset, aLength, aMatchType, aScript, + aSyntheticLower, aSyntheticUpper); +} + +bool +gfxFont::InitFakeSmallCapsRun(gfxContext *aContext, + gfxTextRun *aTextRun, + const char16_t *aText, + uint32_t aOffset, + uint32_t aLength, + uint8_t aMatchType, + int32_t aScript, + bool aSyntheticLower, + bool aSyntheticUpper) +{ + bool ok = true; + + nsRefPtr smallCapsFont = GetSmallCapsFont(); + + enum RunCaseAction { + kNoChange, + kUppercaseReduce, + kUppercase + }; + + RunCaseAction runAction = kNoChange; + uint32_t runStart = 0; + + for (uint32_t i = 0; i <= aLength; ++i) { + uint32_t extraCodeUnits = 0; // Will be set to 1 if we need to consume + // a trailing surrogate as well as the + // current code unit. + RunCaseAction chAction = kNoChange; + // Unless we're at the end, figure out what treatment the current + // character will need. + if (i < aLength) { + uint32_t ch = aText[i]; + if (NS_IS_HIGH_SURROGATE(ch) && i < aLength - 1 && + NS_IS_LOW_SURROGATE(aText[i + 1])) { + ch = SURROGATE_TO_UCS4(ch, aText[i + 1]); + extraCodeUnits = 1; + } + // Characters that aren't the start of a cluster are ignored here. + // They get added to whatever lowercase/non-lowercase run we're in. + if (IsClusterExtender(ch)) { + chAction = runAction; + } else { + if (ch != ToUpperCase(ch) || mozilla::unicode::SpecialUpper(ch)) { + // ch is lower case + chAction = (aSyntheticLower ? kUppercaseReduce : kNoChange); + } else if (ch != ToLowerCase(ch)) { + // ch is upper case + chAction = (aSyntheticUpper ? kUppercaseReduce : kNoChange); + if (mStyle.language == nsGkAtoms::el) { + // In Greek, check for characters that will be modified by + // the GreekUpperCase mapping - this catches accented + // capitals where the accent is to be removed (bug 307039). + // These are handled by using the full-size font with the + // uppercasing transform. + mozilla::GreekCasing::State state; + uint32_t ch2 = mozilla::GreekCasing::UpperCase(ch, state); + if (ch != ch2 && !aSyntheticUpper) { + chAction = kUppercase; + } + } + } + } + } + + // At the end of the text or when the current character needs different + // casing treatment from the current run, finish the run-in-progress + // and prepare to accumulate a new run. + // Note that we do not look at any source data for offset [i] here, + // as that would be invalid in the case where i==length. + if ((i == aLength || runAction != chAction) && runStart < i) { + uint32_t runLength = i - runStart; + gfxFont* f = this; + switch (runAction) { + case kNoChange: + // just use the current font and the existing string + aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true); + if (!f->SplitAndInitTextRun(aContext, aTextRun, + aText + runStart, + aOffset + runStart, runLength, + aScript)) { + ok = false; + } + break; + + case kUppercaseReduce: + // use reduced-size font, then fall through to uppercase the text + f = smallCapsFont; + + case kUppercase: + // apply uppercase transform to the string + nsDependentSubstring origString(aText + runStart, runLength); + nsAutoString convertedString; + nsAutoTArray charsToMergeArray; + nsAutoTArray deletedCharsArray; + + bool mergeNeeded = nsCaseTransformTextRunFactory:: + TransformString(origString, + convertedString, + true, + mStyle.language, + charsToMergeArray, + deletedCharsArray); + + if (mergeNeeded) { + // This is the hard case: the transformation caused chars + // to be inserted or deleted, so we can't shape directly + // into the destination textrun but have to handle the + // mismatch of character positions. + gfxTextRunFactory::Parameters params = { + aContext, nullptr, nullptr, nullptr, 0, + aTextRun->GetAppUnitsPerDevUnit() + }; + nsAutoPtr tempRun; + tempRun = + gfxTextRun::Create(¶ms, convertedString.Length(), + aTextRun->GetFontGroup(), 0); + tempRun->AddGlyphRun(f, aMatchType, 0, true); + if (!f->SplitAndInitTextRun(aContext, tempRun, + convertedString.BeginReading(), + 0, convertedString.Length(), + aScript)) { + ok = false; + } else { + nsAutoPtr mergedRun; + mergedRun = + gfxTextRun::Create(¶ms, runLength, + aTextRun->GetFontGroup(), 0); + MergeCharactersInTextRun(mergedRun, tempRun, + charsToMergeArray.Elements(), + deletedCharsArray.Elements()); + aTextRun->CopyGlyphDataFrom(mergedRun, 0, runLength, + aOffset + runStart); + } + } else { + aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, + true); + if (!f->SplitAndInitTextRun(aContext, aTextRun, + convertedString.BeginReading(), + aOffset + runStart, runLength, + aScript)) { + ok = false; + } + } + break; + } + + runStart = i; + } + + i += extraCodeUnits; + if (i < aLength) { + runAction = chAction; + } + } + + return ok; +} + +already_AddRefed +gfxFont::GetSmallCapsFont() +{ + gfxFontStyle style(*GetStyle()); + style.size *= SMALL_CAPS_SCALE_FACTOR; + style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL; + gfxFontEntry* fe = GetFontEntry(); + bool needsBold = style.weight >= 600 && !fe->IsBold(); + return fe->FindOrMakeFont(&style, needsBold); +} + +already_AddRefed +gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel) +{ + gfxFontStyle style(*GetStyle()); + style.AdjustForSubSuperscript(aAppUnitsPerDevPixel); + gfxFontEntry* fe = GetFontEntry(); + bool needsBold = style.weight >= 600 && !fe->IsBold(); + return fe->FindOrMakeFont(&style, needsBold); +} + gfxGlyphExtents * gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit) { uint32_t i, count = mGlyphExtentsArray.Length(); @@ -4687,1613 +3252,6 @@ gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver *aObserver) mGlyphChangeObservers->RemoveEntry(aObserver); } -gfxGlyphExtents::~gfxGlyphExtents() -{ -#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS - gGlyphExtentsWidthsTotalSize += - mContainedGlyphWidths.SizeOfExcludingThis(&FontCacheMallocSizeOf); - gGlyphExtentsCount++; -#endif - MOZ_COUNT_DTOR(gfxGlyphExtents); -} - -bool -gfxGlyphExtents::GetTightGlyphExtentsAppUnits(gfxFont *aFont, - gfxContext *aContext, uint32_t aGlyphID, gfxRect *aExtents) -{ - HashEntry *entry = mTightGlyphExtents.GetEntry(aGlyphID); - if (!entry) { - if (!aContext) { - NS_WARNING("Could not get glyph extents (no aContext)"); - return false; - } - - if (aFont->SetupCairoFont(aContext)) { -#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS - ++gGlyphExtentsSetupLazyTight; -#endif - aFont->SetupGlyphExtents(aContext, aGlyphID, true, this); - entry = mTightGlyphExtents.GetEntry(aGlyphID); - } - if (!entry) { - NS_WARNING("Could not get glyph extents"); - return false; - } - } - - *aExtents = gfxRect(entry->x, entry->y, entry->width, entry->height); - return true; -} - -gfxGlyphExtents::GlyphWidths::~GlyphWidths() -{ - uint32_t i, count = mBlocks.Length(); - for (i = 0; i < count; ++i) { - uintptr_t bits = mBlocks[i]; - if (bits && !(bits & 0x1)) { - delete[] reinterpret_cast(bits); - } - } -} - -uint32_t -gfxGlyphExtents::GlyphWidths::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const -{ - uint32_t i; - uint32_t size = mBlocks.SizeOfExcludingThis(aMallocSizeOf); - for (i = 0; i < mBlocks.Length(); ++i) { - uintptr_t bits = mBlocks[i]; - if (bits && !(bits & 0x1)) { - size += aMallocSizeOf(reinterpret_cast(bits)); - } - } - return size; -} - -void -gfxGlyphExtents::GlyphWidths::Set(uint32_t aGlyphID, uint16_t aWidth) -{ - uint32_t block = aGlyphID >> BLOCK_SIZE_BITS; - uint32_t len = mBlocks.Length(); - if (block >= len) { - uintptr_t *elems = mBlocks.AppendElements(block + 1 - len); - if (!elems) - return; - memset(elems, 0, sizeof(uintptr_t)*(block + 1 - len)); - } - - uintptr_t bits = mBlocks[block]; - uint32_t glyphOffset = aGlyphID & (BLOCK_SIZE - 1); - if (!bits) { - mBlocks[block] = MakeSingle(glyphOffset, aWidth); - return; - } - - uint16_t *newBlock; - if (bits & 0x1) { - // Expand the block to a real block. We could avoid this by checking - // glyphOffset == GetGlyphOffset(bits), but that never happens so don't bother - newBlock = new uint16_t[BLOCK_SIZE]; - if (!newBlock) - return; - uint32_t i; - for (i = 0; i < BLOCK_SIZE; ++i) { - newBlock[i] = INVALID_WIDTH; - } - newBlock[GetGlyphOffset(bits)] = GetWidth(bits); - mBlocks[block] = reinterpret_cast(newBlock); - } else { - newBlock = reinterpret_cast(bits); - } - newBlock[glyphOffset] = aWidth; -} - -void -gfxGlyphExtents::SetTightGlyphExtents(uint32_t aGlyphID, const gfxRect& aExtentsAppUnits) -{ - HashEntry *entry = mTightGlyphExtents.PutEntry(aGlyphID); - if (!entry) - return; - entry->x = aExtentsAppUnits.X(); - entry->y = aExtentsAppUnits.Y(); - entry->width = aExtentsAppUnits.Width(); - entry->height = aExtentsAppUnits.Height(); -} - -size_t -gfxGlyphExtents::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const -{ - return mContainedGlyphWidths.SizeOfExcludingThis(aMallocSizeOf) + - mTightGlyphExtents.SizeOfExcludingThis(nullptr, aMallocSizeOf); -} - -size_t -gfxGlyphExtents::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const -{ - return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); -} - -gfxFontGroup::gfxFontGroup(const FontFamilyList& aFontFamilyList, - const gfxFontStyle *aStyle, - gfxUserFontSet *aUserFontSet) - : mFamilyList(aFontFamilyList) - , mStyle(*aStyle) - , mUnderlineOffset(UNDERLINE_OFFSET_NOT_SET) - , mHyphenWidth(-1) - , mUserFontSet(aUserFontSet) - , mTextPerf(nullptr) - , mPageLang(gfxPlatform::GetFontPrefLangFor(aStyle->language)) - , mSkipDrawing(false) -{ - // We don't use SetUserFontSet() here, as we want to unconditionally call - // BuildFontList() rather than only do UpdateFontList() if it changed. - mCurrGeneration = GetGeneration(); - BuildFontList(); -} - -void -gfxFontGroup::FindGenericFonts(FontFamilyType aGenericType, - nsIAtom *aLanguage, - void *aClosure) -{ - nsAutoTArray resolvedGenerics; - ResolveGenericFontNames(aGenericType, aLanguage, resolvedGenerics); - uint32_t g = 0, numGenerics = resolvedGenerics.Length(); - for (g = 0; g < numGenerics; g++) { - FindPlatformFont(resolvedGenerics[g], false, aClosure); - } -} - -/* static */ void -gfxFontGroup::ResolveGenericFontNames(FontFamilyType aGenericType, - nsIAtom *aLanguage, - nsTArray& aGenericFamilies) -{ - static const char kGeneric_serif[] = "serif"; - static const char kGeneric_sans_serif[] = "sans-serif"; - static const char kGeneric_monospace[] = "monospace"; - static const char kGeneric_cursive[] = "cursive"; - static const char kGeneric_fantasy[] = "fantasy"; - - // treat -moz-fixed as monospace - if (aGenericType == eFamily_moz_fixed) { - aGenericType = eFamily_monospace; - } - - // type should be standard generic type at this point - NS_ASSERTION(aGenericType >= eFamily_serif && - aGenericType <= eFamily_fantasy, - "standard generic font family type required"); - - // create the lang string - nsIAtom *langGroupAtom = nullptr; - nsAutoCString langGroupString; - if (aLanguage) { - if (!gLangService) { - CallGetService(NS_LANGUAGEATOMSERVICE_CONTRACTID, &gLangService); - } - if (gLangService) { - nsresult rv; - langGroupAtom = gLangService->GetLanguageGroup(aLanguage, &rv); - } - } - if (!langGroupAtom) { - langGroupAtom = nsGkAtoms::Unicode; - } - langGroupAtom->ToUTF8String(langGroupString); - - // map generic type to string - const char *generic = nullptr; - switch (aGenericType) { - case eFamily_serif: - generic = kGeneric_serif; - break; - case eFamily_sans_serif: - generic = kGeneric_sans_serif; - break; - case eFamily_monospace: - generic = kGeneric_monospace; - break; - case eFamily_cursive: - generic = kGeneric_cursive; - break; - case eFamily_fantasy: - generic = kGeneric_fantasy; - break; - default: - break; - } - - if (!generic) { - return; - } - - aGenericFamilies.Clear(); - - // load family for "font.name.generic.lang" - nsAutoCString prefFontName("font.name."); - prefFontName.Append(generic); - prefFontName.Append('.'); - prefFontName.Append(langGroupString); - gfxFontUtils::AppendPrefsFontList(prefFontName.get(), - aGenericFamilies); - - // if lang has pref fonts, also load fonts for "font.name-list.generic.lang" - if (!aGenericFamilies.IsEmpty()) { - nsAutoCString prefFontListName("font.name-list."); - prefFontListName.Append(generic); - prefFontListName.Append('.'); - prefFontListName.Append(langGroupString); - gfxFontUtils::AppendPrefsFontList(prefFontListName.get(), - aGenericFamilies); - } - -#if 0 // dump out generic mappings - printf("%s ===> ", prefFontName.get()); - for (uint32_t k = 0; k < aGenericFamilies.Length(); k++) { - if (k > 0) printf(", "); - printf("%s", NS_ConvertUTF16toUTF8(aGenericFamilies[k]).get()); - } - printf("\n"); -#endif -} - -void gfxFontGroup::EnumerateFontList(nsIAtom *aLanguage, void *aClosure) -{ - // initialize fonts in the font family list - const nsTArray& fontlist = mFamilyList.GetFontlist(); - - // lookup fonts in the fontlist - uint32_t i, numFonts = fontlist.Length(); - for (i = 0; i < numFonts; i++) { - const FontFamilyName& name = fontlist[i]; - if (name.IsNamed()) { - FindPlatformFont(name.mName, true, aClosure); - } else { - FindGenericFonts(name.mType, aLanguage, aClosure); - } - } - - // if necessary, append default generic onto the end - if (mFamilyList.GetDefaultFontType() != eFamily_none && - !mFamilyList.HasDefaultGeneric()) { - FindGenericFonts(mFamilyList.GetDefaultFontType(), - aLanguage, - aClosure); - } -} - -void -gfxFontGroup::BuildFontList() -{ -// gfxPangoFontGroup behaves differently, so this method is a no-op on that platform -#if defined(XP_MACOSX) || defined(XP_WIN) || defined(ANDROID) - - EnumerateFontList(mStyle.language); - - // at this point, fontlist should have been filled in - // get a default font if none exists - if (mFonts.Length() == 0) { - bool needsBold; - gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList(); - gfxFontFamily *defaultFamily = pfl->GetDefaultFont(&mStyle); - NS_ASSERTION(defaultFamily, - "invalid default font returned by GetDefaultFont"); - - if (defaultFamily) { - gfxFontEntry *fe = defaultFamily->FindFontForStyle(mStyle, - needsBold); - if (fe) { - nsRefPtr font = fe->FindOrMakeFont(&mStyle, - needsBold); - if (font) { - mFonts.AppendElement(FamilyFace(defaultFamily, font)); - } - } - } - - if (mFonts.Length() == 0) { - // 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) - nsAutoTArray,200> families; - pfl->GetFontFamilyList(families); - uint32_t count = families.Length(); - for (uint32_t i = 0; i < count; ++i) { - gfxFontEntry *fe = families[i]->FindFontForStyle(mStyle, - needsBold); - if (fe) { - nsRefPtr font = fe->FindOrMakeFont(&mStyle, - needsBold); - if (font) { - mFonts.AppendElement(FamilyFace(families[i], font)); - break; - } - } - } - } - - if (mFonts.Length() == 0) { - // an empty font list at this point is fatal; we're not going to - // be able to do even the most basic layout operations - char msg[256]; // CHECK buffer length if revising message below - nsAutoString families; - mFamilyList.ToString(families); - sprintf(msg, "unable to find a usable font (%.220s)", - NS_ConvertUTF16toUTF8(families).get()); - NS_RUNTIMEABORT(msg); - } - } - - if (!mStyle.systemFont) { - uint32_t count = mFonts.Length(); - for (uint32_t i = 0; i < count; ++i) { - gfxFont* font = mFonts[i].Font(); - if (font->GetFontEntry()->mIsBadUnderlineFont) { - gfxFloat first = mFonts[0].Font()->GetMetrics().underlineOffset; - gfxFloat bad = font->GetMetrics().underlineOffset; - mUnderlineOffset = std::min(first, bad); - break; - } - } - } -#endif -} - -void -gfxFontGroup::FindPlatformFont(const nsAString& aName, - bool aUseFontSet, - void *aClosure) -{ - bool needsBold; - gfxFontFamily *family = nullptr; - gfxFontEntry *fe = nullptr; - - if (aUseFontSet) { - // 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) { - // If the fontSet matches the family, but the font has not yet finished - // loading (nor has its load timeout fired), the fontGroup should wait - // for the download, and not actually draw its text yet. - family = mUserFontSet->LookupFamily(aName); - if (family) { - bool waitForUserFont = false; - gfxUserFontEntry* userFontEntry = nullptr; - userFontEntry = mUserFontSet->FindUserFontEntry(family, mStyle, - needsBold, - waitForUserFont); - if (userFontEntry) { - fe = userFontEntry->GetPlatformFontEntry(); - } - if (!fe && waitForUserFont) { - mSkipDrawing = true; - } - } - } - } - - // Not known in the user font set ==> check system fonts - if (!family) { - gfxPlatformFontList* fontList = gfxPlatformFontList::PlatformFontList(); - family = fontList->FindFamily(aName, mStyle.systemFont); - if (family) { - fe = family->FindFontForStyle(mStyle, needsBold); - } - } - - // add to the font group, unless it's already there - if (fe && !HasFont(fe)) { - nsRefPtr font = fe->FindOrMakeFont(&mStyle, needsBold); - if (font) { - mFonts.AppendElement(FamilyFace(family, font)); - } - } -} - -bool -gfxFontGroup::HasFont(const gfxFontEntry *aFontEntry) -{ - uint32_t count = mFonts.Length(); - for (uint32_t i = 0; i < count; ++i) { - if (mFonts[i].Font()->GetFontEntry() == aFontEntry) - return true; - } - return false; -} - -gfxFontGroup::~gfxFontGroup() -{ - mFonts.Clear(); -} - -gfxFont * -gfxFontGroup::GetFirstMathFont() -{ - uint32_t count = mFonts.Length(); - for (uint32_t i = 0; i < count; ++i) { - gfxFont* font = GetFontAt(i); - if (font->GetFontEntry()->TryGetMathTable()) { - return font; - } - } - return nullptr; -} - -gfxFontGroup * -gfxFontGroup::Copy(const gfxFontStyle *aStyle) -{ - gfxFontGroup *fg = new gfxFontGroup(mFamilyList, aStyle, mUserFontSet); - fg->SetTextPerfMetrics(mTextPerf); - return fg; -} - -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; - } - return (((ch & 0xFF00) == 0x2000 /* Unicode control character */ && - (ch == 0x200B/*ZWSP*/ || ch == 0x2028/*LSEP*/ || ch == 0x2029/*PSEP*/)) || - IsBidiControl(ch)); -} - -gfxTextRun * -gfxFontGroup::MakeEmptyTextRun(const Parameters *aParams, uint32_t aFlags) -{ - aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT; - return gfxTextRun::Create(aParams, 0, this, aFlags); -} - -gfxTextRun * -gfxFontGroup::MakeSpaceTextRun(const Parameters *aParams, uint32_t aFlags) -{ - aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT; - - gfxTextRun *textRun = gfxTextRun::Create(aParams, 1, this, aFlags); - if (!textRun) { - return nullptr; - } - - gfxFont *font = GetFontAt(0); - if (MOZ_UNLIKELY(GetStyle()->size == 0)) { - // 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, gfxTextRange::kFontGroup, 0, 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->mContext, 0); - } else { - // In case the primary font doesn't have (bug 970891), - // find one that does. - uint8_t matchType; - nsRefPtr spaceFont = - FindFontForChar(' ', 0, MOZ_SCRIPT_LATIN, nullptr, &matchType); - if (spaceFont) { - textRun->SetSpaceGlyph(spaceFont, aParams->mContext, 0); - } - } - } - - // 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; -} - -gfxTextRun * -gfxFontGroup::MakeBlankTextRun(uint32_t aLength, - const Parameters *aParams, uint32_t aFlags) -{ - gfxTextRun *textRun = - gfxTextRun::Create(aParams, aLength, this, aFlags); - if (!textRun) { - return nullptr; - } - - textRun->AddGlyphRun(GetFontAt(0), gfxTextRange::kFontGroup, 0, false); - return textRun; -} - -gfxTextRun * -gfxFontGroup::MakeHyphenTextRun(gfxContext *aCtx, 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; - gfxFont *font = GetFontAt(0); - if (font && font->HasCharacter(hyphen)) { - return MakeTextRun(&hyphen, 1, aCtx, aAppUnitsPerDevUnit, - gfxFontGroup::TEXT_IS_PERSISTENT); - } - - static const uint8_t dash = '-'; - return MakeTextRun(&dash, 1, aCtx, aAppUnitsPerDevUnit, - gfxFontGroup::TEXT_IS_PERSISTENT); -} - -gfxFloat -gfxFontGroup::GetHyphenWidth(gfxTextRun::PropertyProvider *aProvider) -{ - if (mHyphenWidth < 0) { - nsRefPtr ctx(aProvider->GetContext()); - if (ctx) { - nsAutoPtr - hyphRun(MakeHyphenTextRun(ctx, - aProvider->GetAppUnitsPerDevUnit())); - mHyphenWidth = hyphRun.get() ? - hyphRun->GetAdvanceWidth(0, hyphRun->GetLength(), nullptr) : 0; - } - } - return mHyphenWidth; -} - -gfxTextRun * -gfxFontGroup::MakeTextRun(const uint8_t *aString, uint32_t aLength, - const Parameters *aParams, uint32_t aFlags) -{ - if (aLength == 0) { - return MakeEmptyTextRun(aParams, aFlags); - } - if (aLength == 1 && aString[0] == ' ') { - return MakeSpaceTextRun(aParams, aFlags); - } - - aFlags |= TEXT_IS_8BIT; - - if (GetStyle()->size == 0) { - // 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(aLength, aParams, aFlags); - } - - gfxTextRun *textRun = gfxTextRun::Create(aParams, aLength, - this, aFlags); - if (!textRun) { - return nullptr; - } - - InitTextRun(aParams->mContext, textRun, aString, aLength); - - textRun->FetchGlyphExtents(aParams->mContext); - - return textRun; -} - -gfxTextRun * -gfxFontGroup::MakeTextRun(const char16_t *aString, uint32_t aLength, - const Parameters *aParams, uint32_t aFlags) -{ - if (aLength == 0) { - return MakeEmptyTextRun(aParams, aFlags); - } - if (aLength == 1 && aString[0] == ' ') { - return MakeSpaceTextRun(aParams, aFlags); - } - if (GetStyle()->size == 0) { - return MakeBlankTextRun(aLength, aParams, aFlags); - } - - gfxTextRun *textRun = gfxTextRun::Create(aParams, aLength, - this, aFlags); - if (!textRun) { - return nullptr; - } - - InitTextRun(aParams->mContext, textRun, aString, aLength); - - textRun->FetchGlyphExtents(aParams->mContext); - - return textRun; -} - -template -void -gfxFontGroup::InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const T *aString, - uint32_t aLength) -{ - 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 - int32_t numOption = gfxPlatform::GetPlatform()->GetBidiNumeralOption(); - nsAutoArrayPtr 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() & gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR) != 0; - 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 = new char16_t[aLength]; - if (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); - } - } - -#ifdef PR_LOGGING - PRLogModuleInfo *log = (mStyle.systemFont ? - gfxPlatform::GetLog(eGfxLog_textrunui) : - gfxPlatform::GetLog(eGfxLog_textrun)); -#endif - - // variant fallback handling may end up passing through this twice - bool redo; - do { - redo = false; - - if (sizeof(T) == sizeof(uint8_t) && !transformedString) { - -#ifdef PR_LOGGING - if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) { - nsAutoCString lang; - mStyle.language->ToUTF8String(lang); - nsAutoString families; - mFamilyList.ToString(families); - nsAutoCString str((const char*)aString, aLength); - PR_LOG(log, PR_LOG_WARNING,\ - ("(%s) fontgroup: [%s] default: %s lang: %s script: %d " - "len %d weight: %d width: %d style: %s size: %6.2f %d-byte " - "TEXTRUN [%s] ENDTEXTRUN\n", - (mStyle.systemFont ? "textrunui" : "textrun"), - NS_ConvertUTF16toUTF8(families).get(), - (mFamilyList.GetDefaultFontType() == eFamily_serif ? - "serif" : - (mFamilyList.GetDefaultFontType() == eFamily_sans_serif ? - "sans-serif" : "none")), - lang.get(), MOZ_SCRIPT_LATIN, aLength, - uint32_t(mStyle.weight), uint32_t(mStyle.stretch), - (mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" : - (mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" : - "normal")), - mStyle.size, - sizeof(T), - str.get())); - } -#endif - - // the text is still purely 8-bit; bypass the script-run itemizer - // and treat it as a single Latin run - InitScriptRun(aContext, aTextRun, aString, - 0, aLength, MOZ_SCRIPT_LATIN); - } 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(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; - int32_t runScript = MOZ_SCRIPT_LATIN; - while (scriptRuns.Next(runStart, runLimit, runScript)) { - - #ifdef PR_LOGGING - if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) { - nsAutoCString lang; - mStyle.language->ToUTF8String(lang); - nsAutoString families; - mFamilyList.ToString(families); - uint32_t runLen = runLimit - runStart; - PR_LOG(log, PR_LOG_WARNING,\ - ("(%s) fontgroup: [%s] default: %s lang: %s script: %d " - "len %d weight: %d width: %d style: %s size: %6.2f " - "%d-byte TEXTRUN [%s] ENDTEXTRUN\n", - (mStyle.systemFont ? "textrunui" : "textrun"), - NS_ConvertUTF16toUTF8(families).get(), - (mFamilyList.GetDefaultFontType() == eFamily_serif ? - "serif" : - (mFamilyList.GetDefaultFontType() == eFamily_sans_serif ? - "sans-serif" : "none")), - lang.get(), runScript, runLen, - uint32_t(mStyle.weight), uint32_t(mStyle.stretch), - (mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" : - (mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" : - "normal")), - mStyle.size, - sizeof(T), - NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get())); - } - #endif - - InitScriptRun(aContext, aTextRun, textPtr + runStart, - runStart, runLimit - runStart, runScript); - } - } - - // 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(); - - aTextRun->SortGlyphRuns(); -} - -template -void -gfxFontGroup::InitScriptRun(gfxContext *aContext, - 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 - int32_t aRunScript) -{ - 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"); - - gfxFont *mainFont = GetFontAt(0); - - uint32_t runStart = 0; - nsAutoTArray fontRanges; - ComputeRanges(fontRanges, aString, aLength, aRunScript); - uint32_t numRanges = fontRanges.Length(); - - for (uint32_t r = 0; r < numRanges; r++) { - const gfxTextRange& range = fontRanges[r]; - uint32_t matchedLength = range.Length(); - 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)); - if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun, - aString + runStart, - aOffset + runStart, - matchedLength, - aRunScript)) { - // 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; - } - - nsRefPtr subSuperFont = - matchedFont->GetSubSuperscriptFont(aTextRun->GetAppUnitsPerDevUnit()); - aTextRun->AddGlyphRun(subSuperFont, range.matchType, - aOffset + runStart, (matchedLength > 0)); - if (!subSuperFont->SplitAndInitTextRun(aContext, aTextRun, - aString + runStart, - aOffset + runStart, - matchedLength, - aRunScript)) { - // glyph layout failed! treat as missing glyphs - matchedFont = nullptr; - } - } else if (mStyle.variantCaps != NS_FONT_VARIANT_CAPS_NORMAL && - !matchedFont->SupportsVariantCaps(aRunScript, - mStyle.variantCaps, - petiteToSmallCaps, - syntheticLower, - syntheticUpper)) - { - // fallback for small-caps variant glyphs - if (!matchedFont->InitFakeSmallCapsRun(aContext, aTextRun, - aString + runStart, - aOffset + runStart, - matchedLength, - range.matchType, - aRunScript, - 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)); - if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun, - aString + runStart, - aOffset + runStart, - matchedLength, - aRunScript)) { - // glyph layout failed! treat as missing glyphs - matchedFont = nullptr; - } - } - } else { - aTextRun->AddGlyphRun(mainFont, gfxTextRange::kFontGroup, - aOffset + runStart, (matchedLength > 0)); - } - - 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 (sizeof(T) == sizeof(char16_t)) { - if (NS_IS_HIGH_SURROGATE(ch) && - index + 1 < aLength && - NS_IS_LOW_SURROGATE(aString[index + 1])) - { - aTextRun->SetMissingGlyph(aOffset + index, - SURROGATE_TO_UCS4(ch, - aString[index + 1]), - mainFont); - index++; - 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; - detailedGlyph.mXOffset = detailedGlyph.mYOffset = 0; - gfxShapedText::CompressedGlyph g; - g.SetComplex(true, true, 1); - aTextRun->SetGlyphs(aOffset + index, - g, &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); - } - } - - runStart += matchedLength; - } -} - -bool -gfxFont::InitFakeSmallCapsRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const uint8_t *aText, - uint32_t aOffset, - uint32_t aLength, - uint8_t aMatchType, - int32_t aScript, - bool aSyntheticLower, - bool aSyntheticUpper) -{ - NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast(aText), - aLength); - return InitFakeSmallCapsRun(aContext, aTextRun, unicodeString.get(), - aOffset, aLength, aMatchType, aScript, - aSyntheticLower, aSyntheticUpper); -} - -bool -gfxFont::InitFakeSmallCapsRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const char16_t *aText, - uint32_t aOffset, - uint32_t aLength, - uint8_t aMatchType, - int32_t aScript, - bool aSyntheticLower, - bool aSyntheticUpper) -{ - bool ok = true; - - nsRefPtr smallCapsFont = GetSmallCapsFont(); - - enum RunCaseAction { - kNoChange, - kUppercaseReduce, - kUppercase - }; - - RunCaseAction runAction = kNoChange; - uint32_t runStart = 0; - - for (uint32_t i = 0; i <= aLength; ++i) { - uint32_t extraCodeUnits = 0; // Will be set to 1 if we need to consume - // a trailing surrogate as well as the - // current code unit. - RunCaseAction chAction = kNoChange; - // Unless we're at the end, figure out what treatment the current - // character will need. - if (i < aLength) { - uint32_t ch = aText[i]; - if (NS_IS_HIGH_SURROGATE(ch) && i < aLength - 1 && - NS_IS_LOW_SURROGATE(aText[i + 1])) { - ch = SURROGATE_TO_UCS4(ch, aText[i + 1]); - extraCodeUnits = 1; - } - // Characters that aren't the start of a cluster are ignored here. - // They get added to whatever lowercase/non-lowercase run we're in. - if (IsClusterExtender(ch)) { - chAction = runAction; - } else { - if (ch != ToUpperCase(ch) || mozilla::unicode::SpecialUpper(ch)) { - // ch is lower case - chAction = (aSyntheticLower ? kUppercaseReduce : kNoChange); - } else if (ch != ToLowerCase(ch)) { - // ch is upper case - chAction = (aSyntheticUpper ? kUppercaseReduce : kNoChange); - if (mStyle.language == nsGkAtoms::el) { - // In Greek, check for characters that will be modified by - // the GreekUpperCase mapping - this catches accented - // capitals where the accent is to be removed (bug 307039). - // These are handled by using the full-size font with the - // uppercasing transform. - mozilla::GreekCasing::State state; - uint32_t ch2 = mozilla::GreekCasing::UpperCase(ch, state); - if (ch != ch2 && !aSyntheticUpper) { - chAction = kUppercase; - } - } - } - } - } - - // At the end of the text or when the current character needs different - // casing treatment from the current run, finish the run-in-progress - // and prepare to accumulate a new run. - // Note that we do not look at any source data for offset [i] here, - // as that would be invalid in the case where i==length. - if ((i == aLength || runAction != chAction) && runStart < i) { - uint32_t runLength = i - runStart; - gfxFont* f = this; - switch (runAction) { - case kNoChange: - // just use the current font and the existing string - aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true); - if (!f->SplitAndInitTextRun(aContext, aTextRun, - aText + runStart, - aOffset + runStart, runLength, - aScript)) { - ok = false; - } - break; - - case kUppercaseReduce: - // use reduced-size font, then fall through to uppercase the text - f = smallCapsFont; - - case kUppercase: - // apply uppercase transform to the string - nsDependentSubstring origString(aText + runStart, runLength); - nsAutoString convertedString; - nsAutoTArray charsToMergeArray; - nsAutoTArray deletedCharsArray; - - bool mergeNeeded = nsCaseTransformTextRunFactory:: - TransformString(origString, - convertedString, - true, - mStyle.language, - charsToMergeArray, - deletedCharsArray); - - if (mergeNeeded) { - // This is the hard case: the transformation caused chars - // to be inserted or deleted, so we can't shape directly - // into the destination textrun but have to handle the - // mismatch of character positions. - gfxTextRunFactory::Parameters params = { - aContext, nullptr, nullptr, nullptr, 0, - aTextRun->GetAppUnitsPerDevUnit() - }; - nsAutoPtr tempRun; - tempRun = - gfxTextRun::Create(¶ms, convertedString.Length(), - aTextRun->GetFontGroup(), 0); - tempRun->AddGlyphRun(f, aMatchType, 0, true); - if (!f->SplitAndInitTextRun(aContext, tempRun, - convertedString.BeginReading(), - 0, convertedString.Length(), - aScript)) { - ok = false; - } else { - nsAutoPtr mergedRun; - mergedRun = - gfxTextRun::Create(¶ms, runLength, - aTextRun->GetFontGroup(), 0); - MergeCharactersInTextRun(mergedRun, tempRun, - charsToMergeArray.Elements(), - deletedCharsArray.Elements()); - aTextRun->CopyGlyphDataFrom(mergedRun, 0, runLength, - aOffset + runStart); - } - } else { - aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, - true); - if (!f->SplitAndInitTextRun(aContext, aTextRun, - convertedString.BeginReading(), - aOffset + runStart, runLength, - aScript)) { - ok = false; - } - } - break; - } - - runStart = i; - } - - i += extraCodeUnits; - if (i < aLength) { - runAction = chAction; - } - } - - return ok; -} - -already_AddRefed -gfxFont::GetSmallCapsFont() -{ - gfxFontStyle style(*GetStyle()); - style.size *= SMALL_CAPS_SCALE_FACTOR; - style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL; - gfxFontEntry* fe = GetFontEntry(); - bool needsBold = style.weight >= 600 && !fe->IsBold(); - return fe->FindOrMakeFont(&style, needsBold); -} - -already_AddRefed -gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel) -{ - gfxFontStyle style(*GetStyle()); - style.AdjustForSubSuperscript(aAppUnitsPerDevPixel); - gfxFontEntry* fe = GetFontEntry(); - bool needsBold = style.weight >= 600 && !fe->IsBold(); - return fe->FindOrMakeFont(&style, needsBold); -} - -gfxTextRun * -gfxFontGroup::GetEllipsisTextRun(int32_t aAppUnitsPerDevPixel, - LazyReferenceContextGetter& aRefContextGetter) -{ - if (mCachedEllipsisTextRun && - mCachedEllipsisTextRun->GetAppUnitsPerDevUnit() == aAppUnitsPerDevPixel) { - return mCachedEllipsisTextRun; - } - - // Use a Unicode ellipsis if the font supports it, - // otherwise use three ASCII periods as fallback. - gfxFont* firstFont = GetFontAt(0); - nsString ellipsis = firstFont->HasCharacter(kEllipsisChar[0]) - ? nsDependentString(kEllipsisChar, - ArrayLength(kEllipsisChar) - 1) - : nsDependentString(kASCIIPeriodsChar, - ArrayLength(kASCIIPeriodsChar) - 1); - - nsRefPtr refCtx = aRefContextGetter.GetRefContext(); - Parameters params = { - refCtx, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevPixel - }; - gfxTextRun* textRun = - MakeTextRun(ellipsis.get(), ellipsis.Length(), ¶ms, TEXT_IS_PERSISTENT); - if (!textRun) { - return nullptr; - } - mCachedEllipsisTextRun = textRun; - textRun->ReleaseFontGroup(); // don't let the presence of a cached ellipsis - // textrun prolong the fontgroup's life - return textRun; -} - -already_AddRefed -gfxFontGroup::TryAllFamilyMembers(gfxFontFamily* aFamily, uint32_t aCh) -{ - if (!aFamily->TestCharacterMap(aCh)) { - return nullptr; - } - - // Note that we don't need the actual runScript in matchData for - // gfxFontFamily::SearchAllFontsForChar, it's only used for the - // system-fallback case. So we can just set it to 0 here. - GlobalFontMatch matchData(aCh, 0, &mStyle); - aFamily->SearchAllFontsForChar(&matchData); - gfxFontEntry *fe = matchData.mBestMatch; - if (!fe) { - return nullptr; - } - - bool needsBold = mStyle.weight >= 600 && !fe->IsBold(); - nsRefPtr font = fe->FindOrMakeFont(&mStyle, needsBold); - return font.forget(); -} - -already_AddRefed -gfxFontGroup::FindFontForChar(uint32_t aCh, uint32_t aPrevCh, - int32_t aRunScript, gfxFont *aPrevMatchedFont, - uint8_t *aMatchType) -{ - // To optimize common cases, try the first font in the font-group - // before going into the more detailed checks below - uint32_t nextIndex = 0; - bool isJoinControl = gfxFontUtils::IsJoinControl(aCh); - bool wasJoinCauser = gfxFontUtils::IsJoinCauser(aPrevCh); - bool isVarSelector = gfxFontUtils::IsVarSelector(aCh); - - if (!isJoinControl && !wasJoinCauser && !isVarSelector) { - nsRefPtr firstFont = mFonts[0].Font(); - if (firstFont->HasCharacter(aCh)) { - *aMatchType = gfxTextRange::kFontGroup; - return firstFont.forget(); - } - // It's possible that another font in the family (e.g. regular face, - // where the requested style was italic) will support the character - nsRefPtr font = TryAllFamilyMembers(mFonts[0].Family(), aCh); - if (font) { - *aMatchType = gfxTextRange::kFontGroup; - return font.forget(); - } - // 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) { - nsRefPtr ret = aPrevMatchedFont; - return ret.forget(); - } - - // 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)) { - nsRefPtr ret = aPrevMatchedFont; - return ret.forget(); - } - } - } - - // if this character is a variation selector, - // use the previous font regardless of whether it supports VS or not. - // otherwise the text run will be divided. - if (isVarSelector) { - if (aPrevMatchedFont) { - nsRefPtr ret = aPrevMatchedFont; - return ret.forget(); - } - // VS alone. it's meaningless to search different fonts - return nullptr; - } - - // 1. check remaining fonts in the font group - uint32_t fontListLength = FontListLength(); - for (uint32_t i = nextIndex; i < fontListLength; i++) { - nsRefPtr font = mFonts[i].Font(); - if (font->HasCharacter(aCh)) { - *aMatchType = gfxTextRange::kFontGroup; - return font.forget(); - } - - font = TryAllFamilyMembers(mFonts[i].Family(), aCh); - if (font) { - *aMatchType = gfxTextRange::kFontGroup; - return font.forget(); - } - } - - // if character is in Private Use Area, don't do matching against pref or system fonts - if ((aCh >= 0xE000 && aCh <= 0xF8FF) || (aCh >= 0xF0000 && aCh <= 0x10FFFD)) - return nullptr; - - // 2. search pref fonts - nsRefPtr font = WhichPrefFontSupportsChar(aCh); - if (font) { - *aMatchType = gfxTextRange::kPrefsFallback; - return font.forget(); - } - - // 3. use fallback fonts - // -- before searching for something else check the font used for the previous character - if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) { - *aMatchType = gfxTextRange::kSystemFallback; - nsRefPtr ret = aPrevMatchedFont; - return ret.forget(); - } - - // never fall back for characters from unknown scripts - if (aRunScript == HB_SCRIPT_UNKNOWN) { - return nullptr; - } - - // for known "space" characters, don't do a full system-fallback search; - // we'll synthesize appropriate-width spaces instead of missing-glyph boxes - if (GetGeneralCategory(aCh) == - HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR && - GetFontAt(0)->SynthesizeSpaceWidth(aCh) >= 0.0) - { - return nullptr; - } - - // -- otherwise look for other stuff - *aMatchType = gfxTextRange::kSystemFallback; - font = WhichSystemFontSupportsChar(aCh, aRunScript); - return font.forget(); -} - -template -void gfxFontGroup::ComputeRanges(nsTArray& aRanges, - const T *aString, uint32_t aLength, - int32_t aRunScript) -{ - 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; - 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) - gfxFont *prevFont = GetFontAt(0); - - // if we use the initial value of prevFont, we treat this as a match from - // the font group; fixes bug 978313 - uint8_t matchType = gfxTextRange::kFontGroup; - - 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 = aString[i]; - - // in 16-bit case only, check for surrogate pair - if (sizeof(T) == sizeof(char16_t)) { - if ((i + 1 < aLength) && NS_IS_HIGH_SURROGATE(ch) && - NS_IS_LOW_SURROGATE(aString[i + 1])) { - i++; - ch = SURROGATE_TO_UCS4(ch, aString[i]); - } - } - - if (ch == 0xa0) { - ch = ' '; - } - - // find the font for this char - nsRefPtr font = - FindFontForChar(ch, prevCh, aRunScript, prevFont, &matchType); - -#ifndef RELEASE_BUILD - if (MOZ_UNLIKELY(mTextPerf)) { - if (matchType == gfxTextRange::kPrefsFallback) { - mTextPerf->current.fallbackPrefs++; - } else if (matchType == gfxTextRange::kSystemFallback) { - mTextPerf->current.fallbackSystem++; - } - } -#endif - - prevCh = ch; - - if (lastRangeIndex == -1) { - // first char ==> make a new range - aRanges.AppendElement(gfxTextRange(0, 1, font, matchType)); - lastRangeIndex++; - prevFont = font; - } else { - // if font has changed, make a new range - gfxTextRange& prevRange = aRanges[lastRangeIndex]; - if (prevRange.font != font || prevRange.matchType != matchType) { - // close out the previous range - prevRange.end = origI; - aRanges.AppendElement(gfxTextRange(origI, i + 1, - font, matchType)); - 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 = font; - } - } - } - } - - aRanges[lastRangeIndex].end = aLength; -} - -gfxUserFontSet* -gfxFontGroup::GetUserFontSet() -{ - return mUserFontSet; -} - -void -gfxFontGroup::SetUserFontSet(gfxUserFontSet *aUserFontSet) -{ - if (aUserFontSet == mUserFontSet) { - return; - } - mUserFontSet = aUserFontSet; - mCurrGeneration = GetGeneration() - 1; - UpdateFontList(); -} - -uint64_t -gfxFontGroup::GetGeneration() -{ - if (!mUserFontSet) - return 0; - return mUserFontSet->GetGeneration(); -} - -// note: gfxPangoFontGroup overrides UpdateFontList, such that -// BuildFontList is never used -void -gfxFontGroup::UpdateFontList() -{ - if (mCurrGeneration != GetGeneration()) { - // xxx - can probably improve this to detect when all fonts were found, so no need to update list - mFonts.Clear(); - mUnderlineOffset = UNDERLINE_OFFSET_NOT_SET; - mSkipDrawing = false; - BuildFontList(); - mCurrGeneration = GetGeneration(); - mCachedEllipsisTextRun = nullptr; - } -} - -struct PrefFontCallbackData { - explicit PrefFontCallbackData(nsTArray >& aFamiliesArray) - : mPrefFamilies(aFamiliesArray) - {} - - nsTArray >& mPrefFamilies; - - static bool AddFontFamilyEntry(eFontPrefLang aLang, const nsAString& aName, void *aClosure) - { - PrefFontCallbackData *prefFontData = static_cast(aClosure); - - gfxFontFamily *family = gfxPlatformFontList::PlatformFontList()->FindFamily(aName); - if (family) { - prefFontData->mPrefFamilies.AppendElement(family); - } - return true; - } -}; - -already_AddRefed -gfxFontGroup::WhichPrefFontSupportsChar(uint32_t aCh) -{ - nsRefPtr font; - - // get the pref font list if it hasn't been set up already - uint32_t unicodeRange = FindCharUnicodeRange(aCh); - eFontPrefLang charLang = gfxPlatform::GetPlatform()->GetFontPrefLangFor(unicodeRange); - - // 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)) { - font = mLastPrefFont; - return font.forget(); - } - - // based on char lang and page lang, set up list of pref lang fonts to check - eFontPrefLang prefLangs[kMaxLenPrefLangList]; - uint32_t i, numLangs = 0; - - gfxPlatform::GetPlatform()->GetLangPrefs(prefLangs, numLangs, charLang, mPageLang); - - for (i = 0; i < numLangs; i++) { - nsAutoTArray, 5> families; - eFontPrefLang currentLang = prefLangs[i]; - - gfxPlatformFontList *fontList = gfxPlatformFontList::PlatformFontList(); - - // get the pref families for a single pref lang - if (!fontList->GetPrefFontFamilyEntries(currentLang, &families)) { - eFontPrefLang prefLangsToSearch[1] = { currentLang }; - PrefFontCallbackData prefFontData(families); - gfxPlatform::ForEachPrefFont(prefLangsToSearch, 1, PrefFontCallbackData::AddFontFamilyEntry, - &prefFontData); - fontList->SetPrefFontFamilyEntries(currentLang, families); - } - - // 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 - gfxFontFamily *family = families[j]; - if (!family) 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)) { - font = mLastPrefFont; - return font.forget(); - } - - bool needsBold; - gfxFontEntry *fe = family->FindFontForStyle(mStyle, needsBold); - // if ch in cmap, create and return a gfxFont - if (fe && fe->TestCharacterMap(aCh)) { - nsRefPtr prefFont = fe->FindOrMakeFont(&mStyle, needsBold); - if (!prefFont) continue; - mLastPrefFamily = family; - mLastPrefFont = prefFont; - mLastPrefLang = charLang; - mLastPrefFirstFont = (i == 0 && j == 0); - return prefFont.forget(); - } - - } - } - - return nullptr; -} - -already_AddRefed -gfxFontGroup::WhichSystemFontSupportsChar(uint32_t aCh, int32_t aRunScript) -{ - gfxFontEntry *fe = - gfxPlatformFontList::PlatformFontList()-> - SystemFindFontForChar(aCh, aRunScript, &mStyle); - if (fe) { - bool wantBold = mStyle.ComputeWeight() >= 6; - nsRefPtr font = - fe->FindOrMakeFont(&mStyle, wantBold && !fe->IsBold()); - return font.forget(); - } - - return nullptr; -} - -/*static*/ void -gfxFontGroup::Shutdown() -{ - NS_IF_RELEASE(gLangService); -} - -nsILanguageAtomService* gfxFontGroup::gLangService = nullptr; - #define DEFAULT_PIXEL_FONT_SIZE 16.0f @@ -6437,1563 +3395,3 @@ gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel) // clear the variant field variantSubSuper = NS_FONT_VARIANT_POSITION_NORMAL; } - -void -gfxShapedText::SetupClusterBoundaries(uint32_t aOffset, - const char16_t *aString, - uint32_t aLength) -{ - CompressedGlyph *glyphs = GetCharacterGlyphs() + aOffset; - - gfxTextRun::CompressedGlyph extendCluster; - extendCluster.SetComplex(false, true, 0); - - ClusterIterator iter(aString, aLength); - - // the ClusterIterator won't be able to tell us if the string - // _begins_ with a cluster-extender, so we handle that here - if (aLength && IsClusterExtender(*aString)) { - *glyphs = extendCluster; - } - - while (!iter.AtEnd()) { - if (*iter == char16_t(' ')) { - glyphs->SetIsSpace(); - } - // advance iter to the next cluster-start (or end of text) - iter.Next(); - // step past the first char of the cluster - aString++; - glyphs++; - // mark all the rest as cluster-continuations - while (aString < iter) { - *glyphs = extendCluster; - if (NS_IS_LOW_SURROGATE(*aString)) { - glyphs->SetIsLowSurrogate(); - } - glyphs++; - aString++; - } - } -} - -void -gfxShapedText::SetupClusterBoundaries(uint32_t aOffset, - const uint8_t *aString, - uint32_t aLength) -{ - CompressedGlyph *glyphs = GetCharacterGlyphs() + aOffset; - const uint8_t *limit = aString + aLength; - - while (aString < limit) { - if (*aString == uint8_t(' ')) { - glyphs->SetIsSpace(); - } - aString++; - glyphs++; - } -} - -gfxShapedText::DetailedGlyph * -gfxShapedText::AllocateDetailedGlyphs(uint32_t aIndex, uint32_t aCount) -{ - NS_ASSERTION(aIndex < GetLength(), "Index out of range"); - - if (!mDetailedGlyphs) { - mDetailedGlyphs = new DetailedGlyphStore(); - } - - return mDetailedGlyphs->Allocate(aIndex, aCount); -} - -void -gfxShapedText::SetGlyphs(uint32_t aIndex, CompressedGlyph aGlyph, - const DetailedGlyph *aGlyphs) -{ - NS_ASSERTION(!aGlyph.IsSimpleGlyph(), "Simple glyphs not handled here"); - NS_ASSERTION(aIndex > 0 || aGlyph.IsLigatureGroupStart(), - "First character can't be a ligature continuation!"); - - uint32_t glyphCount = aGlyph.GetGlyphCount(); - if (glyphCount > 0) { - DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, glyphCount); - memcpy(details, aGlyphs, sizeof(DetailedGlyph)*glyphCount); - } - GetCharacterGlyphs()[aIndex] = aGlyph; -} - -#define ZWNJ 0x200C -#define ZWJ 0x200D -// U+061C ARABIC LETTER MARK is expected to be added to XIDMOD_DEFAULT_IGNORABLE -// in a future Unicode update. Add it manually for now -#define ALM 0x061C -static inline bool -IsDefaultIgnorable(uint32_t aChar) -{ - return GetIdentifierModification(aChar) == XIDMOD_DEFAULT_IGNORABLE || - aChar == ZWNJ || aChar == ZWJ || aChar == ALM; -} - -void -gfxShapedText::SetMissingGlyph(uint32_t aIndex, uint32_t aChar, gfxFont *aFont) -{ - uint8_t category = GetGeneralCategory(aChar); - if (category >= HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK && - category <= HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) - { - GetCharacterGlyphs()[aIndex].SetComplex(false, true, 0); - } - - DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); - - details->mGlyphID = aChar; - if (IsDefaultIgnorable(aChar)) { - // Setting advance width to zero will prevent drawing the hexbox - details->mAdvance = 0; - } else { - gfxFloat width = - std::max(aFont->GetMetrics().aveCharWidth, - gfxFontMissingGlyphs::GetDesiredMinWidth(aChar, - mAppUnitsPerDevUnit)); - details->mAdvance = uint32_t(width * mAppUnitsPerDevUnit); - } - details->mXOffset = 0; - details->mYOffset = 0; - GetCharacterGlyphs()[aIndex].SetMissing(1); -} - -bool -gfxShapedText::FilterIfIgnorable(uint32_t aIndex, uint32_t aCh) -{ - if (IsDefaultIgnorable(aCh)) { - DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); - details->mGlyphID = aCh; - details->mAdvance = 0; - details->mXOffset = 0; - details->mYOffset = 0; - GetCharacterGlyphs()[aIndex].SetMissing(1); - return true; - } - return false; -} - -void -gfxShapedText::AdjustAdvancesForSyntheticBold(float aSynBoldOffset, - uint32_t aOffset, - uint32_t aLength) -{ - uint32_t synAppUnitOffset = aSynBoldOffset * mAppUnitsPerDevUnit; - CompressedGlyph *charGlyphs = GetCharacterGlyphs(); - for (uint32_t i = aOffset; i < aOffset + aLength; ++i) { - CompressedGlyph *glyphData = charGlyphs + i; - if (glyphData->IsSimpleGlyph()) { - // simple glyphs ==> just add the advance - int32_t advance = glyphData->GetSimpleAdvance() + synAppUnitOffset; - if (CompressedGlyph::IsSimpleAdvance(advance)) { - glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph()); - } else { - // rare case, tested by making this the default - uint32_t glyphIndex = glyphData->GetSimpleGlyph(); - glyphData->SetComplex(true, true, 1); - DetailedGlyph detail = {glyphIndex, advance, 0, 0}; - SetGlyphs(i, *glyphData, &detail); - } - } else { - // complex glyphs ==> add offset at cluster/ligature boundaries - uint32_t detailedLength = glyphData->GetGlyphCount(); - if (detailedLength) { - DetailedGlyph *details = GetDetailedGlyphs(i); - if (!details) { - continue; - } - if (IsRightToLeft()) { - details[0].mAdvance += synAppUnitOffset; - } else { - details[detailedLength - 1].mAdvance += synAppUnitOffset; - } - } - } - } -} - -bool -gfxTextRun::GlyphRunIterator::NextRun() { - if (mNextIndex >= mTextRun->mGlyphRuns.Length()) - return false; - mGlyphRun = &mTextRun->mGlyphRuns[mNextIndex]; - if (mGlyphRun->mCharacterOffset >= mEndOffset) - return false; - - mStringStart = std::max(mStartOffset, mGlyphRun->mCharacterOffset); - uint32_t last = mNextIndex + 1 < mTextRun->mGlyphRuns.Length() - ? mTextRun->mGlyphRuns[mNextIndex + 1].mCharacterOffset : mTextRun->GetLength(); - mStringEnd = std::min(mEndOffset, last); - - ++mNextIndex; - return true; -} - -#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 - -// 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 = moz_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(storage) + aSize, 0, - aLength * sizeof(CompressedGlyph)); - - return storage; -} - -gfxTextRun * -gfxTextRun::Create(const gfxTextRunFactory::Parameters *aParams, - uint32_t aLength, gfxFontGroup *aFontGroup, uint32_t aFlags) -{ - void *storage = AllocateStorageForTextRun(sizeof(gfxTextRun), aLength); - if (!storage) { - return nullptr; - } - - return new (storage) gfxTextRun(aParams, aLength, aFontGroup, aFlags); -} - -gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters *aParams, - uint32_t aLength, gfxFontGroup *aFontGroup, uint32_t aFlags) - : gfxShapedText(aLength, aFlags, aParams->mAppUnitsPerDevUnit) - , mUserData(aParams->mUserData) - , mFontGroup(aFontGroup) - , mReleasedFontGroup(false) - , mShapingState(eShapingState_Normal) -{ - NS_ASSERTION(mAppUnitsPerDevUnit > 0, "Invalid app unit scale"); - MOZ_COUNT_CTOR(gfxTextRun); - NS_ADDREF(mFontGroup); - -#ifndef RELEASE_BUILD - gfxTextPerfMetrics *tp = aFontGroup->GetTextPerfMetrics(); - if (tp) { - tp->current.textrunConst++; - } -#endif - - mCharacterGlyphs = reinterpret_cast(this + 1); - - if (aParams->mSkipChars) { - mSkipChars.TakeFrom(aParams->mSkipChars); - } - -#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS - AccountStorageForTextRun(this, 1); -#endif - - mSkipDrawing = mFontGroup->ShouldSkipDrawing(); -} - -gfxTextRun::~gfxTextRun() -{ -#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS - AccountStorageForTextRun(this, -1); -#endif -#ifdef DEBUG - // Make it easy to detect a dead text run - mFlags = 0xFFFFFFFF; -#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_BUILD - gfxTextPerfMetrics *tp = mFontGroup->GetTextPerfMetrics(); - if (tp) { - tp->current.textrunDestr++; - } -#endif - NS_RELEASE(mFontGroup); - } - - MOZ_COUNT_DTOR(gfxTextRun); -} - -void -gfxTextRun::ReleaseFontGroup() -{ - NS_ASSERTION(!mReleasedFontGroup, "doubly released!"); - NS_RELEASE(mFontGroup); - mReleasedFontGroup = true; -} - -bool -gfxTextRun::SetPotentialLineBreaks(uint32_t aStart, uint32_t aLength, - uint8_t *aBreakBefore, - gfxContext *aRefContext) -{ - NS_ASSERTION(aStart + aLength <= GetLength(), "Overflow"); - - uint32_t changed = 0; - uint32_t i; - CompressedGlyph *charGlyphs = mCharacterGlyphs + aStart; - for (i = 0; i < aLength; ++i) { - uint8_t canBreak = aBreakBefore[i]; - if (canBreak && !charGlyphs[i].IsClusterStart()) { - // This can happen ... there is no guarantee that our linebreaking rules - // align with the platform's idea of what constitutes a cluster. - NS_WARNING("Break suggested inside cluster!"); - canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE; - } - changed |= charGlyphs[i].SetCanBreakBefore(canBreak); - } - return changed != 0; -} - -gfxTextRun::LigatureData -gfxTextRun::ComputeLigatureData(uint32_t aPartStart, uint32_t aPartEnd, - PropertyProvider *aProvider) -{ - NS_ASSERTION(aPartStart < aPartEnd, "Computing ligature data for empty range"); - NS_ASSERTION(aPartEnd <= GetLength(), "Character length overflow"); - - LigatureData result; - CompressedGlyph *charGlyphs = mCharacterGlyphs; - - uint32_t i; - for (i = aPartStart; !charGlyphs[i].IsLigatureGroupStart(); --i) { - NS_ASSERTION(i > 0, "Ligature at the start of the run??"); - } - result.mLigatureStart = i; - for (i = aPartStart + 1; i < GetLength() && !charGlyphs[i].IsLigatureGroupStart(); ++i) { - } - result.mLigatureEnd = i; - - int32_t ligatureWidth = - GetAdvanceForGlyphs(result.mLigatureStart, result.mLigatureEnd); - // Count the number of started clusters we have seen - uint32_t totalClusterCount = 0; - uint32_t partClusterIndex = 0; - uint32_t partClusterCount = 0; - for (i = result.mLigatureStart; i < result.mLigatureEnd; ++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.mLigatureStart || charGlyphs[i].IsClusterStart()) { - ++totalClusterCount; - if (i < aPartStart) { - ++partClusterIndex; - } else if (i < aPartEnd) { - ++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 (aPartEnd == result.mLigatureEnd) { - 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 & gfxTextRunFactory::TEXT_ENABLE_SPACING)) { - gfxFont::Spacing spacing; - if (aPartStart == result.mLigatureStart) { - aProvider->GetSpacing(aPartStart, 1, &spacing); - result.mPartWidth += spacing.mBefore; - } - if (aPartEnd == result.mLigatureEnd) { - aProvider->GetSpacing(aPartEnd - 1, 1, &spacing); - result.mPartWidth += spacing.mAfter; - } - } - - return result; -} - -gfxFloat -gfxTextRun::ComputePartialLigatureWidth(uint32_t aPartStart, uint32_t aPartEnd, - PropertyProvider *aProvider) -{ - if (aPartStart >= aPartEnd) - return 0; - LigatureData data = ComputeLigatureData(aPartStart, aPartEnd, aProvider); - return data.mPartWidth; -} - -int32_t -gfxTextRun::GetAdvanceForGlyphs(uint32_t aStart, uint32_t aEnd) -{ - const CompressedGlyph *glyphData = mCharacterGlyphs + aStart; - int32_t advance = 0; - uint32_t i; - for (i = aStart; i < aEnd; ++i, ++glyphData) { - if (glyphData->IsSimpleGlyph()) { - advance += glyphData->GetSimpleAdvance(); - } else { - uint32_t glyphCount = glyphData->GetGlyphCount(); - if (glyphCount == 0) { - continue; - } - const DetailedGlyph *details = GetDetailedGlyphs(i); - if (details) { - uint32_t j; - for (j = 0; j < glyphCount; ++j, ++details) { - advance += details->mAdvance; - } - } - } - } - return advance; -} - -static void -GetAdjustedSpacing(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd, - gfxTextRun::PropertyProvider *aProvider, - gfxTextRun::PropertyProvider::Spacing *aSpacing) -{ - if (aStart >= aEnd) - return; - - aProvider->GetSpacing(aStart, aEnd - aStart, aSpacing); - -#ifdef DEBUG - // Check to see if we have spacing inside ligatures - - const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs(); - uint32_t i; - - for (i = aStart; i < aEnd; ++i) { - if (!charGlyphs[i].IsLigatureGroupStart()) { - NS_ASSERTION(i == aStart || aSpacing[i - aStart].mBefore == 0, - "Before-spacing inside a ligature!"); - NS_ASSERTION(i - 1 <= aStart || aSpacing[i - 1 - aStart].mAfter == 0, - "After-spacing inside a ligature!"); - } - } -#endif -} - -bool -gfxTextRun::GetAdjustedSpacingArray(uint32_t aStart, uint32_t aEnd, - PropertyProvider *aProvider, - uint32_t aSpacingStart, uint32_t aSpacingEnd, - nsTArray *aSpacing) -{ - if (!aProvider || !(mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) - return false; - if (!aSpacing->AppendElements(aEnd - aStart)) - return false; - memset(aSpacing->Elements(), 0, sizeof(gfxFont::Spacing)*(aSpacingStart - aStart)); - GetAdjustedSpacing(this, aSpacingStart, aSpacingEnd, aProvider, - aSpacing->Elements() + aSpacingStart - aStart); - memset(aSpacing->Elements() + aSpacingEnd - aStart, 0, sizeof(gfxFont::Spacing)*(aEnd - aSpacingEnd)); - return true; -} - -void -gfxTextRun::ShrinkToLigatureBoundaries(uint32_t *aStart, uint32_t *aEnd) -{ - if (*aStart >= *aEnd) - return; - - CompressedGlyph *charGlyphs = mCharacterGlyphs; - - while (*aStart < *aEnd && !charGlyphs[*aStart].IsLigatureGroupStart()) { - ++(*aStart); - } - if (*aEnd < GetLength()) { - while (*aEnd > *aStart && !charGlyphs[*aEnd].IsLigatureGroupStart()) { - --(*aEnd); - } - } -} - -void -gfxTextRun::DrawGlyphs(gfxFont *aFont, uint32_t aStart, uint32_t aEnd, - gfxPoint *aPt, PropertyProvider *aProvider, - uint32_t aSpacingStart, uint32_t aSpacingEnd, - TextRunDrawParams& aParams) -{ - nsAutoTArray spacingBuffer; - bool haveSpacing = GetAdjustedSpacingArray(aStart, aEnd, aProvider, - aSpacingStart, aSpacingEnd, &spacingBuffer); - aParams.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr; - aFont->Draw(this, aStart, aEnd, aPt, aParams); -} - -static void -ClipPartialLigature(gfxTextRun *aTextRun, gfxFloat *aLeft, gfxFloat *aRight, - gfxFloat aXOrigin, gfxTextRun::LigatureData *aLigature) -{ - if (aLigature->mClipBeforePart) { - if (aTextRun->IsRightToLeft()) { - *aRight = std::min(*aRight, aXOrigin); - } else { - *aLeft = std::max(*aLeft, aXOrigin); - } - } - if (aLigature->mClipAfterPart) { - gfxFloat endEdge = aXOrigin + aTextRun->GetDirection()*aLigature->mPartWidth; - if (aTextRun->IsRightToLeft()) { - *aLeft = std::max(*aLeft, endEdge); - } else { - *aRight = std::min(*aRight, endEdge); - } - } -} - -void -gfxTextRun::DrawPartialLigature(gfxFont *aFont, uint32_t aStart, uint32_t aEnd, - gfxPoint *aPt, PropertyProvider *aProvider, - TextRunDrawParams& aParams) -{ - if (aStart >= aEnd) - return; - - // Draw partial ligature. We hack this by clipping the ligature. - LigatureData data = ComputeLigatureData(aStart, aEnd, aProvider); - gfxRect clipExtents = aParams.context->GetClipExtents(); - gfxFloat left = clipExtents.X() * mAppUnitsPerDevUnit; - gfxFloat right = clipExtents.XMost() * mAppUnitsPerDevUnit; - ClipPartialLigature(this, &left, &right, aPt->x, &data); - - { - // Need to preserve the path, otherwise this can break canvas text-on-path; - // in general it seems like a good thing, as naive callers probably won't - // expect gfxTextRun::Draw to implicitly destroy the current path. - gfxContextPathAutoSaveRestore savePath(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. - aParams.context->Save(); - aParams.context->NewPath(); - aParams.context->Rectangle(gfxRect(left / mAppUnitsPerDevUnit, - clipExtents.Y(), - (right - left) / mAppUnitsPerDevUnit, - clipExtents.Height()), true); - aParams.context->Clip(); - } - - gfxPoint pt(aPt->x - aParams.direction * data.mPartAdvance, aPt->y); - DrawGlyphs(aFont, data.mLigatureStart, data.mLigatureEnd, &pt, - aProvider, aStart, aEnd, aParams); - aParams.context->Restore(); - - aPt->x += aParams.direction * data.mPartWidth; -} - -// returns true if a glyph run is using a font with synthetic bolding enabled, false otherwise -static bool -HasSyntheticBold(gfxTextRun *aRun, uint32_t aStart, uint32_t aLength) -{ - gfxTextRun::GlyphRunIterator iter(aRun, aStart, aLength); - while (iter.NextRun()) { - gfxFont *font = iter.GetGlyphRun()->mFont; - if (font && font->IsSyntheticBold()) { - return true; - } - } - - return false; -} - -// returns true if color is non-opaque (i.e. alpha != 1.0) or completely transparent, false otherwise -// if true, color is set on output -static bool -HasNonOpaqueColor(gfxContext *aContext, gfxRGBA& aCurrentColor) -{ - if (aContext->GetDeviceColor(aCurrentColor)) { - if (aCurrentColor.a < 1.0 && aCurrentColor.a > 0.0) { - return true; - } - } - - return false; -} - -// helper class for double-buffering drawing with non-opaque color -struct BufferAlphaColor { - explicit BufferAlphaColor(gfxContext *aContext) - : mContext(aContext) - { - - } - - ~BufferAlphaColor() {} - - void PushSolidColor(const gfxRect& aBounds, const gfxRGBA& aAlphaColor, uint32_t appsPerDevUnit) - { - mContext->Save(); - mContext->NewPath(); - mContext->Rectangle(gfxRect(aBounds.X() / appsPerDevUnit, - aBounds.Y() / appsPerDevUnit, - aBounds.Width() / appsPerDevUnit, - aBounds.Height() / appsPerDevUnit), true); - mContext->Clip(); - mContext->SetColor(gfxRGBA(aAlphaColor.r, aAlphaColor.g, aAlphaColor.b)); - mContext->PushGroup(gfxContentType::COLOR_ALPHA); - mAlpha = aAlphaColor.a; - } - - void PopAlpha() - { - // pop the text, using the color alpha as the opacity - mContext->PopGroupToSource(); - mContext->SetOperator(gfxContext::OPERATOR_OVER); - mContext->Paint(mAlpha); - mContext->Restore(); - } - - gfxContext *mContext; - gfxFloat mAlpha; -}; - -void -gfxTextRun::Draw(gfxContext *aContext, gfxPoint aPt, DrawMode aDrawMode, - uint32_t aStart, uint32_t aLength, - PropertyProvider *aProvider, gfxFloat *aAdvanceWidth, - gfxTextContextPaint *aContextPaint, - gfxTextRunDrawCallbacks *aCallbacks) -{ - NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range"); - NS_ASSERTION(aDrawMode == DrawMode::GLYPH_PATH || - !(int(aDrawMode) & int(DrawMode::GLYPH_PATH)), - "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH"); - NS_ASSERTION(aDrawMode == DrawMode::GLYPH_PATH || !aCallbacks, - "callback must not be specified unless using GLYPH_PATH"); - - bool skipDrawing = mSkipDrawing; - if (aDrawMode == DrawMode::GLYPH_FILL) { - gfxRGBA currentColor; - if (aContext->GetDeviceColor(currentColor) && currentColor.a == 0) { - 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 (aAdvanceWidth) { - gfxTextRun::Metrics metrics = MeasureText(aStart, aLength, - gfxFont::LOOSE_INK_EXTENTS, - aContext, aProvider); - *aAdvanceWidth = metrics.mAdvanceWidth * direction; - } - - // return without drawing - return; - } - - // Set up parameters that will be constant across all glyph runs we need - // to draw, regardless of the font used. - TextRunDrawParams params; - params.context = aContext; - params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit()); - params.isRTL = IsRightToLeft(); - params.direction = direction; - params.drawMode = aDrawMode; - params.callbacks = aCallbacks; - params.runContextPaint = aContextPaint; - params.paintSVGGlyphs = !aCallbacks || aCallbacks->mShouldPaintSVGGlyphs; - params.dt = aContext->GetDrawTarget(); - - gfxPoint pt = aPt; - - // synthetic bolding draws glyphs twice ==> colors with opacity won't draw - // correctly unless first drawn without alpha - BufferAlphaColor syntheticBoldBuffer(aContext); - gfxRGBA currentColor; - bool needToRestore = false; - - if (aDrawMode == DrawMode::GLYPH_FILL && - HasNonOpaqueColor(aContext, currentColor) && - HasSyntheticBold(this, aStart, aLength)) { - needToRestore = true; - // measure text, use the bounding box - gfxTextRun::Metrics metrics = MeasureText(aStart, aLength, - gfxFont::LOOSE_INK_EXTENTS, - aContext, aProvider); - metrics.mBoundingBox.MoveBy(aPt); - syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor, - GetAppUnitsPerDevUnit()); - } - - GlyphRunIterator iter(this, aStart, aLength); - while (iter.NextRun()) { - gfxFont *font = iter.GetGlyphRun()->mFont; - uint32_t start = iter.GetStringStart(); - uint32_t end = iter.GetStringEnd(); - uint32_t ligatureRunStart = start; - uint32_t ligatureRunEnd = end; - ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); - - bool drawPartial = aDrawMode == DrawMode::GLYPH_FILL || - (aDrawMode == DrawMode::GLYPH_PATH && aCallbacks); - - if (drawPartial) { - DrawPartialLigature(font, start, ligatureRunStart, &pt, - aProvider, params); - } - - DrawGlyphs(font, ligatureRunStart, ligatureRunEnd, &pt, - aProvider, ligatureRunStart, ligatureRunEnd, params); - - if (drawPartial) { - DrawPartialLigature(font, ligatureRunEnd, end, &pt, - aProvider, params); - } - } - - // composite result when synthetic bolding used - if (needToRestore) { - syntheticBoldBuffer.PopAlpha(); - } - - if (aAdvanceWidth) { - *aAdvanceWidth = (pt.x - aPt.x)*direction; - } -} - -void -gfxTextRun::AccumulateMetricsForRun(gfxFont *aFont, - uint32_t aStart, uint32_t aEnd, - gfxFont::BoundingBoxType aBoundingBoxType, - gfxContext *aRefContext, - PropertyProvider *aProvider, - uint32_t aSpacingStart, uint32_t aSpacingEnd, - Metrics *aMetrics) -{ - nsAutoTArray spacingBuffer; - bool haveSpacing = GetAdjustedSpacingArray(aStart, aEnd, aProvider, - aSpacingStart, aSpacingEnd, &spacingBuffer); - Metrics metrics = aFont->Measure(this, aStart, aEnd, aBoundingBoxType, aRefContext, - haveSpacing ? spacingBuffer.Elements() : nullptr); - aMetrics->CombineWith(metrics, IsRightToLeft()); -} - -void -gfxTextRun::AccumulatePartialLigatureMetrics(gfxFont *aFont, - uint32_t aStart, uint32_t aEnd, - gfxFont::BoundingBoxType aBoundingBoxType, gfxContext *aRefContext, - PropertyProvider *aProvider, Metrics *aMetrics) -{ - if (aStart >= aEnd) - return; - - // Measure partial ligature. We hack this by clipping the metrics in the - // same way we clip the drawing. - LigatureData data = ComputeLigatureData(aStart, aEnd, aProvider); - - // First measure the complete ligature - Metrics metrics; - AccumulateMetricsForRun(aFont, data.mLigatureStart, data.mLigatureEnd, - aBoundingBoxType, aRefContext, - aProvider, aStart, aEnd, &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.x = bboxLeft; - metrics.mBoundingBox.width = bboxRight - bboxLeft; - - // mBoundingBox is now relative to the left baseline origin for the entire - // ligature. Shift it left. - metrics.mBoundingBox.x -= - IsRightToLeft() ? metrics.mAdvanceWidth - (data.mPartAdvance + data.mPartWidth) - : data.mPartAdvance; - metrics.mAdvanceWidth = data.mPartWidth; - - aMetrics->CombineWith(metrics, IsRightToLeft()); -} - -gfxTextRun::Metrics -gfxTextRun::MeasureText(uint32_t aStart, uint32_t aLength, - gfxFont::BoundingBoxType aBoundingBoxType, - gfxContext *aRefContext, - PropertyProvider *aProvider) -{ - NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range"); - - Metrics accumulatedMetrics; - GlyphRunIterator iter(this, aStart, aLength); - while (iter.NextRun()) { - gfxFont *font = iter.GetGlyphRun()->mFont; - uint32_t start = iter.GetStringStart(); - uint32_t end = iter.GetStringEnd(); - uint32_t ligatureRunStart = start; - uint32_t ligatureRunEnd = end; - ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); - - AccumulatePartialLigatureMetrics(font, start, ligatureRunStart, - aBoundingBoxType, aRefContext, aProvider, &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, - ligatureRunStart, ligatureRunEnd, aBoundingBoxType, - aRefContext, aProvider, ligatureRunStart, ligatureRunEnd, - &accumulatedMetrics); - - AccumulatePartialLigatureMetrics(font, ligatureRunEnd, end, - aBoundingBoxType, aRefContext, aProvider, &accumulatedMetrics); - } - - return accumulatedMetrics; -} - -#define MEASUREMENT_BUFFER_SIZE 100 - -uint32_t -gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength, - bool aLineBreakBefore, gfxFloat aWidth, - PropertyProvider *aProvider, - bool aSuppressInitialBreak, - gfxFloat *aTrimWhitespace, - Metrics *aMetrics, - gfxFont::BoundingBoxType aBoundingBoxType, - gfxContext *aRefContext, - bool *aUsedHyphenation, - uint32_t *aLastBreak, - bool aCanWordWrap, - gfxBreakPriority *aBreakPriority) -{ - aMaxLength = std::min(aMaxLength, GetLength() - aStart); - - NS_ASSERTION(aStart + aMaxLength <= GetLength(), "Substring out of range"); - - uint32_t bufferStart = aStart; - uint32_t bufferLength = std::min(aMaxLength, MEASUREMENT_BUFFER_SIZE); - PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE]; - bool haveSpacing = aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING) != 0; - if (haveSpacing) { - GetAdjustedSpacing(this, bufferStart, bufferStart + bufferLength, aProvider, - spacingBuffer); - } - bool hyphenBuffer[MEASUREMENT_BUFFER_SIZE]; - bool haveHyphenation = aProvider && - (aProvider->GetHyphensOption() == NS_STYLE_HYPHENS_AUTO || - (aProvider->GetHyphensOption() == NS_STYLE_HYPHENS_MANUAL && - (mFlags & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0)); - if (haveHyphenation) { - aProvider->GetHyphenationBreaks(bufferStart, bufferLength, - hyphenBuffer); - } - - gfxFloat width = 0; - gfxFloat advance = 0; - // The number of space characters that can be trimmed - 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; - bool aborted = false; - uint32_t end = aStart + aMaxLength; - bool lastBreakUsedHyphenation = false; - - uint32_t ligatureRunStart = aStart; - uint32_t ligatureRunEnd = end; - ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); - - uint32_t i; - for (i = aStart; i < end; ++i) { - if (i >= bufferStart + bufferLength) { - // Fetch more spacing and hyphenation data - bufferStart = i; - bufferLength = std::min(aStart + aMaxLength, i + MEASUREMENT_BUFFER_SIZE) - i; - if (haveSpacing) { - GetAdjustedSpacing(this, bufferStart, bufferStart + bufferLength, aProvider, - spacingBuffer); - } - if (haveHyphenation) { - aProvider->GetHyphenationBreaks(bufferStart, bufferLength, - hyphenBuffer); - } - } - - // 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 (!aSuppressInitialBreak || i > aStart) { - bool atNaturalBreak = mCharacterGlyphs[i].CanBreakBefore() == 1; - bool atHyphenationBreak = - !atNaturalBreak && haveHyphenation && hyphenBuffer[i - bufferStart]; - bool atBreak = atNaturalBreak || atHyphenationBreak; - bool wordWrapping = - aCanWordWrap && mCharacterGlyphs[i].IsClusterStart() && - *aBreakPriority <= gfxBreakPriority::eWordWrapBreak; - - if (atBreak || wordWrapping) { - 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 ? gfxBreakPriority::eNormalBreak - : gfxBreakPriority::eWordWrapBreak; - } - - width += advance; - advance = 0; - if (width - trimmableAdvance > aWidth) { - // No more text fits. Abort - aborted = true; - break; - } - } - } - - gfxFloat charAdvance; - if (i >= ligatureRunStart && i < ligatureRunEnd) { - charAdvance = GetAdvanceForGlyphs(i, i + 1); - if (haveSpacing) { - PropertyProvider::Spacing *space = &spacingBuffer[i - bufferStart]; - charAdvance += space->mBefore + space->mAfter; - } - } else { - charAdvance = ComputePartialLigatureWidth(i, i + 1, aProvider); - } - - advance += charAdvance; - if (aTrimWhitespace) { - 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; - bool usedHyphenation = false; - if (width - trimmableAdvance <= aWidth) { - charsFit = aMaxLength; - } else if (lastBreak >= 0) { - charsFit = lastBreak - aStart; - trimmableChars = lastBreakTrimmableChars; - trimmableAdvance = lastBreakTrimmableAdvance; - usedHyphenation = lastBreakUsedHyphenation; - } else { - charsFit = aMaxLength; - } - - if (aMetrics) { - *aMetrics = MeasureText(aStart, charsFit - trimmableChars, - aBoundingBoxType, aRefContext, aProvider); - } - if (aTrimWhitespace) { - *aTrimWhitespace = trimmableAdvance; - } - if (aUsedHyphenation) { - *aUsedHyphenation = usedHyphenation; - } - if (aLastBreak && charsFit == aMaxLength) { - if (lastBreak < 0) { - *aLastBreak = UINT32_MAX; - } else { - *aLastBreak = lastBreak - aStart; - } - } - - return charsFit; -} - -gfxFloat -gfxTextRun::GetAdvanceWidth(uint32_t aStart, uint32_t aLength, - PropertyProvider *aProvider) -{ - NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range"); - - uint32_t ligatureRunStart = aStart; - uint32_t ligatureRunEnd = aStart + aLength; - ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); - - gfxFloat result = ComputePartialLigatureWidth(aStart, ligatureRunStart, aProvider) + - ComputePartialLigatureWidth(ligatureRunEnd, aStart + aLength, aProvider); - - // Account for all remaining spacing here. This is more efficient than - // processing it along with the glyphs. - if (aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) { - uint32_t i; - nsAutoTArray spacingBuffer; - if (spacingBuffer.AppendElements(aLength)) { - GetAdjustedSpacing(this, ligatureRunStart, ligatureRunEnd, aProvider, - spacingBuffer.Elements()); - for (i = 0; i < ligatureRunEnd - ligatureRunStart; ++i) { - PropertyProvider::Spacing *space = &spacingBuffer[i]; - result += space->mBefore + space->mAfter; - } - } - } - - return result + GetAdvanceForGlyphs(ligatureRunStart, ligatureRunEnd); -} - -bool -gfxTextRun::SetLineBreaks(uint32_t aStart, uint32_t aLength, - bool aLineBreakBefore, bool aLineBreakAfter, - gfxFloat *aAdvanceWidthDelta, - gfxContext *aRefContext) -{ - // 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; -} - -uint32_t -gfxTextRun::FindFirstGlyphRunContaining(uint32_t aOffset) -{ - NS_ASSERTION(aOffset <= GetLength(), "Bad offset looking for glyphrun"); - NS_ASSERTION(GetLength() == 0 || mGlyphRuns.Length() > 0, - "non-empty text but no glyph runs present!"); - if (aOffset == GetLength()) - return mGlyphRuns.Length(); - uint32_t start = 0; - uint32_t end = mGlyphRuns.Length(); - while (end - start > 1) { - uint32_t mid = (start + end)/2; - if (mGlyphRuns[mid].mCharacterOffset <= aOffset) { - start = mid; - } else { - end = mid; - } - } - NS_ASSERTION(mGlyphRuns[start].mCharacterOffset <= aOffset, - "Hmm, something went wrong, aOffset should have been found"); - return start; -} - -nsresult -gfxTextRun::AddGlyphRun(gfxFont *aFont, uint8_t aMatchType, - uint32_t aUTF16Offset, bool aForceNewRun) -{ - NS_ASSERTION(aFont, "adding glyph run for null font!"); - if (!aFont) { - return NS_OK; - } - uint32_t numGlyphRuns = mGlyphRuns.Length(); - if (!aForceNewRun && numGlyphRuns > 0) { - GlyphRun *lastGlyphRun = &mGlyphRuns[numGlyphRuns - 1]; - - NS_ASSERTION(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->mFont == aFont && - lastGlyphRun->mMatchType == aMatchType) - { - return NS_OK; - } - - // 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].mFont == aFont && - mGlyphRuns[numGlyphRuns - 2].mMatchType == aMatchType) - { - mGlyphRuns.TruncateLength(numGlyphRuns - 1); - return NS_OK; - } - - lastGlyphRun->mFont = aFont; - lastGlyphRun->mMatchType = aMatchType; - return NS_OK; - } - } - - NS_ASSERTION(aForceNewRun || numGlyphRuns > 0 || aUTF16Offset == 0, - "First run doesn't cover the first character (and run not forced)?"); - - GlyphRun *glyphRun = mGlyphRuns.AppendElement(); - if (!glyphRun) - return NS_ERROR_OUT_OF_MEMORY; - glyphRun->mFont = aFont; - glyphRun->mCharacterOffset = aUTF16Offset; - glyphRun->mMatchType = aMatchType; - return NS_OK; -} - -void -gfxTextRun::SortGlyphRuns() -{ - if (mGlyphRuns.Length() <= 1) - return; - - nsTArray runs(mGlyphRuns); - GlyphRunOffsetComparator comp; - runs.Sort(comp); - - // Now copy back, coalescing adjacent glyph runs that have the same font - mGlyphRuns.Clear(); - uint32_t i, count = runs.Length(); - for (i = 0; i < count; ++i) { - // a GlyphRun with the same font as the previous GlyphRun can just - // be skipped; the last GlyphRun will cover its character range. - if (i == 0 || runs[i].mFont != runs[i - 1].mFont) { - mGlyphRuns.AppendElement(runs[i]); - // If two fonts have the same character offset, Sort() will have - // randomized the order. - NS_ASSERTION(i == 0 || - runs[i].mCharacterOffset != - runs[i - 1].mCharacterOffset, - "Two fonts for the same run, glyph indices may not match the font"); - } - } -} - -// Note that SanitizeGlyphRuns scans all glyph runs in the textrun; -// therefore we only call it once, at the end of textrun construction, -// NOT incrementally as each glyph run is added (bug 680402). -void -gfxTextRun::SanitizeGlyphRuns() -{ - if (mGlyphRuns.Length() <= 1) - return; - - // 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) - int32_t i, lastRunIndex = mGlyphRuns.Length() - 1; - const CompressedGlyph *charGlyphs = mCharacterGlyphs; - for (i = lastRunIndex; i >= 0; --i) { - GlyphRun& run = mGlyphRuns[i]; - while (charGlyphs[run.mCharacterOffset].IsLigatureContinuation() && - run.mCharacterOffset < GetLength()) { - run.mCharacterOffset++; - } - // if the run has become empty, eliminate it - if ((i < lastRunIndex && - run.mCharacterOffset >= mGlyphRuns[i+1].mCharacterOffset) || - (i == lastRunIndex && run.mCharacterOffset == GetLength())) { - mGlyphRuns.RemoveElementAt(i); - --lastRunIndex; - } - } -} - -uint32_t -gfxTextRun::CountMissingGlyphs() -{ - uint32_t i; - uint32_t count = 0; - for (i = 0; i < GetLength(); ++i) { - if (mCharacterGlyphs[i].IsMissing()) { - ++count; - } - } - return count; -} - -void -gfxTextRun::CopyGlyphDataFrom(gfxShapedWord *aShapedWord, uint32_t aOffset) -{ - uint32_t wordLen = aShapedWord->GetLength(); - NS_ASSERTION(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()) { - charGlyphs[aOffset] = g; - } else { - const DetailedGlyph *details = - g.GetGlyphCount() > 0 ? - aShapedWord->GetDetailedGlyphs(i) : nullptr; - SetGlyphs(aOffset, g, details); - } - } - } else { - memcpy(charGlyphs + aOffset, wordGlyphs, - wordLen * sizeof(CompressedGlyph)); - } -} - -void -gfxTextRun::CopyGlyphDataFrom(gfxTextRun *aSource, uint32_t aStart, - uint32_t aLength, uint32_t aDest) -{ - NS_ASSERTION(aStart + aLength <= aSource->GetLength(), - "Source substring out of range"); - NS_ASSERTION(aDest + aLength <= GetLength(), - "Destination substring out of range"); - - if (aSource->mSkipDrawing) { - mSkipDrawing = true; - } - - // Copy base glyph data, and DetailedGlyph data where present - const CompressedGlyph *srcGlyphs = aSource->mCharacterGlyphs + aStart; - CompressedGlyph *dstGlyphs = mCharacterGlyphs + aDest; - for (uint32_t i = 0; i < aLength; ++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) { - DetailedGlyph *dst = AllocateDetailedGlyphs(i + aDest, count); - if (dst) { - DetailedGlyph *src = aSource->GetDetailedGlyphs(i + aStart); - if (src) { - ::memcpy(dst, src, count * sizeof(DetailedGlyph)); - } else { - g.SetMissing(0); - } - } else { - g.SetMissing(0); - } - } - } - dstGlyphs[i] = g; - } - - // Copy glyph runs - GlyphRunIterator iter(aSource, aStart, aLength); -#ifdef DEBUG - gfxFont *lastFont = nullptr; -#endif - while (iter.NextRun()) { - gfxFont *font = iter.GetGlyphRun()->mFont; - NS_ASSERTION(font != lastFont, "Glyphruns not coalesced?"); -#ifdef DEBUG - lastFont = font; - uint32_t end = iter.GetStringEnd(); -#endif - uint32_t start = iter.GetStringStart(); - - // 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,

- // &%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_WARN_IF_FALSE(aSource->IsClusterStart(start), - "Started font run in the middle of a cluster"); - NS_WARN_IF_FALSE(end == aSource->GetLength() || aSource->IsClusterStart(end), - "Ended font run in the middle of a cluster"); - - nsresult rv = AddGlyphRun(font, iter.GetGlyphRun()->mMatchType, - start - aStart + aDest, false); - if (NS_FAILED(rv)) - return; - } -} - -void -gfxTextRun::ClearGlyphsAndCharacters() -{ - ResetGlyphRuns(); - memset(reinterpret_cast(mCharacterGlyphs), 0, - mLength * sizeof(CompressedGlyph)); - mDetailedGlyphs = nullptr; -} - -void -gfxTextRun::SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, - uint32_t aCharIndex) -{ - if (SetSpaceGlyphIfSimple(aFont, aContext, aCharIndex, ' ')) { - return; - } - - aFont->InitWordCache(); - static const uint8_t space = ' '; - gfxShapedWord *sw = aFont->GetShapedWord(aContext, - &space, 1, - HashMix(0, ' '), - MOZ_SCRIPT_LATIN, - mAppUnitsPerDevUnit, - gfxTextRunFactory::TEXT_IS_8BIT | - gfxTextRunFactory::TEXT_IS_ASCII | - gfxTextRunFactory::TEXT_IS_PERSISTENT, - nullptr); - if (sw) { - AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false); - CopyGlyphDataFrom(sw, aCharIndex); - } -} - -bool -gfxTextRun::SetSpaceGlyphIfSimple(gfxFont *aFont, gfxContext *aContext, - uint32_t aCharIndex, char16_t aSpaceChar) -{ - uint32_t spaceGlyph = aFont->GetSpaceGlyph(); - if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) { - return false; - } - - uint32_t spaceWidthAppUnits = - NS_lroundf(aFont->GetMetrics().spaceWidth * mAppUnitsPerDevUnit); - if (!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) { - return false; - } - - AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false); - CompressedGlyph g; - g.SetSimpleGlyph(spaceWidthAppUnits, spaceGlyph); - if (aSpaceChar == ' ') { - g.SetIsSpace(); - } - GetCharacterGlyphs()[aCharIndex] = g; - return true; -} - -void -gfxTextRun::FetchGlyphExtents(gfxContext *aRefContext) -{ - bool needsGlyphExtents = NeedsGlyphExtents(this); - if (!needsGlyphExtents && !mDetailedGlyphs) - return; - - uint32_t i, runCount = mGlyphRuns.Length(); - CompressedGlyph *charGlyphs = mCharacterGlyphs; - for (i = 0; i < runCount; ++i) { - const GlyphRun& run = mGlyphRuns[i]; - gfxFont *font = run.mFont; - if (MOZ_UNLIKELY(font->GetStyle()->size == 0)) { - continue; - } - - uint32_t start = run.mCharacterOffset; - uint32_t end = i + 1 < runCount ? - mGlyphRuns[i + 1].mCharacterOffset : GetLength(); - bool fontIsSetup = false; - uint32_t j; - gfxGlyphExtents *extents = font->GetOrCreateGlyphExtents(mAppUnitsPerDevUnit); - - for (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->IsGlyphKnown(glyphIndex)) { - if (!fontIsSetup) { - if (!font->SetupCairoFont(aRefContext)) { - NS_WARNING("failed to set up font for glyph extents"); - break; - } - fontIsSetup = true; - } -#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS - ++gGlyphExtentsSetupEagerSimple; -#endif - font->SetupGlyphExtents(aRefContext, glyphIndex, false, extents); - } - } - } 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->IsGlyphKnownWithTightExtents(glyphIndex)) { - if (!fontIsSetup) { - if (!font->SetupCairoFont(aRefContext)) { - NS_WARNING("failed to set up font for glyph extents"); - break; - } - fontIsSetup = true; - } -#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS - ++gGlyphExtentsSetupEagerTight; -#endif - font->SetupGlyphExtents(aRefContext, glyphIndex, true, extents); - } - } - } - } - } -} - - -gfxTextRun::ClusterIterator::ClusterIterator(gfxTextRun *aTextRun) - : mTextRun(aTextRun), mCurrentChar(uint32_t(-1)) -{ -} - -void -gfxTextRun::ClusterIterator::Reset() -{ - mCurrentChar = uint32_t(-1); -} - -bool -gfxTextRun::ClusterIterator::NextCluster() -{ - uint32_t len = mTextRun->GetLength(); - while (++mCurrentChar < len) { - if (mTextRun->IsClusterStart(mCurrentChar)) { - return true; - } - } - - mCurrentChar = uint32_t(-1); - return false; -} - -uint32_t -gfxTextRun::ClusterIterator::ClusterLength() const -{ - if (mCurrentChar == uint32_t(-1)) { - return 0; - } - - uint32_t i = mCurrentChar, - len = mTextRun->GetLength(); - while (++i < len) { - if (mTextRun->IsClusterStart(i)) { - break; - } - } - - return i - mCurrentChar; -} - -gfxFloat -gfxTextRun::ClusterIterator::ClusterAdvance(PropertyProvider *aProvider) const -{ - if (mCurrentChar == uint32_t(-1)) { - return 0; - } - - return mTextRun->GetAdvanceWidth(mCurrentChar, ClusterLength(), aProvider); -} - -size_t -gfxTextRun::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) -{ - // The second arg is how much gfxTextRun::AllocateStorage would have - // allocated. - size_t total = mGlyphRuns.SizeOfExcludingThis(aMallocSizeOf); - - if (mDetailedGlyphs) { - total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf); - } - - return total; -} - -size_t -gfxTextRun::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) -{ - return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); -} - - -#ifdef DEBUG -void -gfxTextRun::Dump(FILE* aOutput) { - if (!aOutput) { - aOutput = stdout; - } - - uint32_t i; - fputc('[', aOutput); - for (i = 0; i < mGlyphRuns.Length(); ++i) { - if (i > 0) { - fputc(',', aOutput); - } - gfxFont* font = mGlyphRuns[i].mFont; - const gfxFontStyle* style = font->GetStyle(); - NS_ConvertUTF16toUTF8 fontName(font->GetName()); - nsAutoCString lang; - style->language->ToUTF8String(lang); - fprintf(aOutput, "%d: %s %f/%d/%d/%s", mGlyphRuns[i].mCharacterOffset, - fontName.get(), style->size, - style->weight, style->style, lang.get()); - } - fputc(']', aOutput); -} -#endif diff --git a/gfx/thebes/gfxFont.h b/gfx/thebes/gfxFont.h index 11cb7e46254c..921b5e1f7c33 100644 --- a/gfx/thebes/gfxFont.h +++ b/gfx/thebes/gfxFont.h @@ -7,14 +7,12 @@ #define GFX_FONT_H #include "gfxTypes.h" +#include "gfxFontEntry.h" #include "nsString.h" #include "gfxPoint.h" -#include "gfxFontFamilyList.h" -#include "gfxFontUtils.h" #include "nsTArray.h" #include "nsTHashtable.h" #include "nsHashKeys.h" -#include "gfxSkipChars.h" #include "gfxRect.h" #include "nsExpirationTracker.h" #include "gfxPlatform.h" @@ -22,18 +20,16 @@ #include "mozilla/HashFunctions.h" #include "nsIMemoryReporter.h" #include "nsIObserver.h" -#include "gfxFontFeatures.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Attributes.h" #include #include "DrawMode.h" -#include "nsUnicodeScriptCodes.h" #include "nsDataHashtable.h" #include "harfbuzz/hb.h" #include "mozilla/gfx/2D.h" typedef struct _cairo_scaled_font cairo_scaled_font_t; -typedef struct gr_face gr_face; +//typedef struct gr_face gr_face; #ifdef DEBUG #include @@ -42,20 +38,11 @@ typedef struct gr_face gr_face; class gfxContext; class gfxTextRun; class gfxFont; -class gfxFontFamily; -class gfxFontGroup; -class gfxGraphiteShaper; -class gfxHarfBuzzShaper; -class gfxUserFontSet; -class gfxUserFontData; +class gfxGlyphExtents; class gfxShapedText; class gfxShapedWord; -class gfxSVGGlyphs; -class gfxMathTable; +class gfxSkipChars; class gfxTextContextPaint; -class FontInfoData; - -class nsILanguageAtomService; #define FONT_MAX_SIZE 2000.0 @@ -63,7 +50,6 @@ class nsILanguageAtomService; #define SMALL_CAPS_SCALE_FACTOR 0.8 -struct FontListSizes; struct gfxTextRunDrawCallbacks; namespace mozilla { @@ -213,784 +199,6 @@ struct gfxFontStyle { static uint32_t ParseFontLanguageOverride(const nsString& aLangTag); }; -class gfxCharacterMap : public gfxSparseBitSet { -public: - nsrefcnt AddRef() { - NS_PRECONDITION(int32_t(mRefCnt) >= 0, "illegal refcnt"); - ++mRefCnt; - NS_LOG_ADDREF(this, mRefCnt, "gfxCharacterMap", sizeof(*this)); - return mRefCnt; - } - - nsrefcnt Release() { - NS_PRECONDITION(0 != mRefCnt, "dup release"); - --mRefCnt; - NS_LOG_RELEASE(this, mRefCnt, "gfxCharacterMap"); - if (mRefCnt == 0) { - NotifyReleased(); - // |this| has been deleted. - return 0; - } - return mRefCnt; - } - - gfxCharacterMap() : - mHash(0), mBuildOnTheFly(false), mShared(false) - { } - - void CalcHash() { mHash = GetChecksum(); } - - size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { - return gfxSparseBitSet::SizeOfExcludingThis(aMallocSizeOf); - } - - // hash of the cmap bitvector - uint32_t mHash; - - // if cmap is built on the fly it's never shared - bool mBuildOnTheFly; - - // cmap is shared globally - bool mShared; - -protected: - void NotifyReleased(); - - nsAutoRefCnt mRefCnt; - -private: - gfxCharacterMap(const gfxCharacterMap&); - gfxCharacterMap& operator=(const gfxCharacterMap&); -}; - -class gfxFontEntry { -public: - NS_INLINE_DECL_REFCOUNTING(gfxFontEntry) - - explicit gfxFontEntry(const nsAString& aName, bool aIsStandardFace = false); - - // unique name for the face, *not* the family; not necessarily the - // "real" or user-friendly name, may be an internal identifier - const nsString& Name() const { return mName; } - - // family name - const nsString& FamilyName() const { return mFamilyName; } - - // The following two methods may be relatively expensive, as they - // will (usually, except on Linux) load and parse the 'name' table; - // they are intended only for the font-inspection API, not for - // perf-critical layout/drawing work. - - // The "real" name of the face, if available from the font resource; - // returns Name() if nothing better is available. - virtual nsString RealFaceName(); - - uint16_t Weight() const { return mWeight; } - int16_t Stretch() const { return mStretch; } - - bool IsUserFont() const { return mIsDataUserFont || mIsLocalUserFont; } - bool IsLocalUserFont() const { return mIsLocalUserFont; } - bool IsFixedPitch() const { return mFixedPitch; } - bool IsItalic() const { return mItalic; } - bool IsBold() const { return mWeight >= 600; } // bold == weights 600 and above - bool IgnoreGDEF() const { return mIgnoreGDEF; } - bool IgnoreGSUB() const { return mIgnoreGSUB; } - - // whether a feature is supported by the font (limited to a small set - // of features for which some form of fallback needs to be implemented) - bool SupportsOpenTypeFeature(int32_t aScript, uint32_t aFeatureTag); - bool SupportsGraphiteFeature(uint32_t aFeatureTag); - - // returns a set containing all input glyph ids for a given feature - const hb_set_t* - InputsForOpenTypeFeature(int32_t aScript, uint32_t aFeatureTag); - - virtual bool IsSymbolFont(); - - virtual bool HasFontTable(uint32_t aTableTag); - - inline bool HasGraphiteTables() { - if (!mCheckedForGraphiteTables) { - CheckForGraphiteTables(); - mCheckedForGraphiteTables = true; - } - return mHasGraphiteTables; - } - - inline bool HasCmapTable() { - if (!mCharacterMap) { - ReadCMAP(); - NS_ASSERTION(mCharacterMap, "failed to initialize character map"); - } - return mHasCmapTable; - } - - inline bool HasCharacter(uint32_t ch) { - if (mCharacterMap && mCharacterMap->test(ch)) { - return true; - } - return TestCharacterMap(ch); - } - - virtual bool SkipDuringSystemFallback() { return false; } - virtual bool TestCharacterMap(uint32_t aCh); - nsresult InitializeUVSMap(); - uint16_t GetUVSGlyph(uint32_t aCh, uint32_t aVS); - - // All concrete gfxFontEntry subclasses (except gfxUserFontEntry) need - // to override this, otherwise the font will never be used as it will - // be considered to support no characters. - // ReadCMAP() must *always* set the mCharacterMap pointer to a valid - // gfxCharacterMap, even if empty, as other code assumes this pointer - // can be safely dereferenced. - virtual nsresult ReadCMAP(FontInfoData *aFontInfoData = nullptr); - - bool TryGetSVGData(gfxFont* aFont); - bool HasSVGGlyph(uint32_t aGlyphId); - bool GetSVGGlyphExtents(gfxContext *aContext, uint32_t aGlyphId, - gfxRect *aResult); - bool RenderSVGGlyph(gfxContext *aContext, uint32_t aGlyphId, int aDrawMode, - gfxTextContextPaint *aContextPaint); - // Call this when glyph geometry or rendering has changed - // (e.g. animated SVG glyphs) - void NotifyGlyphsChanged(); - - enum MathConstant { - // The order of the constants must match the order of the fields - // defined in the MATH table. - ScriptPercentScaleDown, - ScriptScriptPercentScaleDown, - DelimitedSubFormulaMinHeight, - DisplayOperatorMinHeight, - MathLeading, - AxisHeight, - AccentBaseHeight, - FlattenedAccentBaseHeight, - SubscriptShiftDown, - SubscriptTopMax, - SubscriptBaselineDropMin, - SuperscriptShiftUp, - SuperscriptShiftUpCramped, - SuperscriptBottomMin, - SuperscriptBaselineDropMax, - SubSuperscriptGapMin, - SuperscriptBottomMaxWithSubscript, - SpaceAfterScript, - UpperLimitGapMin, - UpperLimitBaselineRiseMin, - LowerLimitGapMin, - LowerLimitBaselineDropMin, - StackTopShiftUp, - StackTopDisplayStyleShiftUp, - StackBottomShiftDown, - StackBottomDisplayStyleShiftDown, - StackGapMin, - StackDisplayStyleGapMin, - StretchStackTopShiftUp, - StretchStackBottomShiftDown, - StretchStackGapAboveMin, - StretchStackGapBelowMin, - FractionNumeratorShiftUp, - FractionNumeratorDisplayStyleShiftUp, - FractionDenominatorShiftDown, - FractionDenominatorDisplayStyleShiftDown, - FractionNumeratorGapMin, - FractionNumDisplayStyleGapMin, - FractionRuleThickness, - FractionDenominatorGapMin, - FractionDenomDisplayStyleGapMin, - SkewedFractionHorizontalGap, - SkewedFractionVerticalGap, - OverbarVerticalGap, - OverbarRuleThickness, - OverbarExtraAscender, - UnderbarVerticalGap, - UnderbarRuleThickness, - UnderbarExtraDescender, - RadicalVerticalGap, - RadicalDisplayStyleVerticalGap, - RadicalRuleThickness, - RadicalExtraAscender, - RadicalKernBeforeDegree, - RadicalKernAfterDegree, - RadicalDegreeBottomRaisePercent - }; - - // Call TryGetMathTable to try to load the Open Type MATH table. The other - // functions forward the call to the gfxMathTable class. The GetMath...() - // functions MUST NOT be called unless TryGetMathTable() has returned true. - bool TryGetMathTable(); - gfxFloat GetMathConstant(MathConstant aConstant); - bool GetMathItalicsCorrection(uint32_t aGlyphID, - gfxFloat* aItalicCorrection); - uint32_t GetMathVariantsSize(uint32_t aGlyphID, bool aVertical, - uint16_t aSize); - bool GetMathVariantsParts(uint32_t aGlyphID, bool aVertical, - uint32_t aGlyphs[4]); - - bool TryGetColorGlyphs(); - bool GetColorLayersInfo(uint32_t aGlyphId, - nsTArray& layerGlyphs, - nsTArray& layerColors); - - virtual bool MatchesGenericFamily(const nsACString& aGeneric) const { - return true; - } - virtual bool SupportsLangGroup(nsIAtom *aLangGroup) const { - return true; - } - - // Access to raw font table data (needed for Harfbuzz): - // returns a pointer to data owned by the fontEntry or the OS, - // which will remain valid until the blob is destroyed. - // The data MUST be treated as read-only; we may be getting a - // reference to a shared system font cache. - // - // The default implementation uses CopyFontTable to get the data - // into a byte array, and maintains a cache of loaded tables. - // - // Subclasses should override this if they can provide more efficient - // access than copying table data into our own buffers. - // - // Get blob that encapsulates a specific font table, or nullptr if - // the table doesn't exist in the font. - // - // Caller is responsible to call hb_blob_destroy() on the returned blob - // (if non-nullptr) when no longer required. For transient access to a - // table, use of AutoTable (below) is generally preferred. - virtual hb_blob_t *GetFontTable(uint32_t aTag); - - // Stack-based utility to return a specified table, automatically releasing - // the blob when the AutoTable goes out of scope. - class AutoTable { - public: - AutoTable(gfxFontEntry* aFontEntry, uint32_t aTag) - { - mBlob = aFontEntry->GetFontTable(aTag); - } - ~AutoTable() { - if (mBlob) { - hb_blob_destroy(mBlob); - } - } - operator hb_blob_t*() const { return mBlob; } - private: - hb_blob_t* mBlob; - // not implemented: - AutoTable(const AutoTable&) MOZ_DELETE; - AutoTable& operator=(const AutoTable&) MOZ_DELETE; - }; - - already_AddRefed FindOrMakeFont(const gfxFontStyle *aStyle, - bool aNeedsBold); - - // Get an existing font table cache entry in aBlob if it has been - // registered, or return false if not. Callers must call - // hb_blob_destroy on aBlob if true is returned. - // - // Note that some gfxFont implementations may not call this at all, - // if it is more efficient to get the table from the OS at that level. - bool GetExistingFontTable(uint32_t aTag, hb_blob_t** aBlob); - - // Elements of aTable are transferred (not copied) to and returned in a - // new hb_blob_t which is registered on the gfxFontEntry, but the initial - // reference is owned by the caller. Removing the last reference - // unregisters the table from the font entry. - // - // Pass nullptr for aBuffer to indicate that the table is not present and - // nullptr will be returned. Also returns nullptr on OOM. - hb_blob_t *ShareFontTableAndGetBlob(uint32_t aTag, - FallibleTArray* aTable); - - // Get the font's unitsPerEm from the 'head' table, in the case of an - // sfnt resource. Will return kInvalidUPEM for non-sfnt fonts, - // if present on the platform. - uint16_t UnitsPerEm(); - enum { - kMinUPEM = 16, // Limits on valid unitsPerEm range, from the - kMaxUPEM = 16384, // OpenType spec - kInvalidUPEM = uint16_t(-1) - }; - - // Shaper face accessors: - // NOTE that harfbuzz and graphite handle ownership/lifetime of the face - // object in completely different ways. - - // Get HarfBuzz face corresponding to this font file. - // Caller must release with hb_face_destroy() when finished with it, - // and the font entry will be notified via ForgetHBFace. - hb_face_t* GetHBFace(); - virtual void ForgetHBFace(); - - // Get Graphite face corresponding to this font file. - // Caller must call gfxFontEntry::ReleaseGrFace when finished with it. - gr_face* GetGrFace(); - virtual void ReleaseGrFace(gr_face* aFace); - - // Release any SVG-glyphs document this font may have loaded. - void DisconnectSVG(); - - // Called to notify that aFont is being destroyed. Needed when we're tracking - // the fonts belonging to this font entry. - void NotifyFontDestroyed(gfxFont* aFont); - - // For memory reporting - virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, - FontListSizes* aSizes) const; - virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, - FontListSizes* aSizes) const; - - // Used when checking for complex script support, to mask off cmap ranges - struct ScriptRange { - uint32_t rangeStart; - uint32_t rangeEnd; - hb_tag_t tags[3]; // one or two OpenType script tags to check, - // plus a NULL terminator - }; - - bool SupportsScriptInGSUB(const hb_tag_t* aScriptTags); - - nsString mName; - nsString mFamilyName; - - bool mItalic : 1; - bool mFixedPitch : 1; - bool mIsValid : 1; - bool mIsBadUnderlineFont : 1; - bool mIsUserFontContainer : 1; // userfont entry - bool mIsDataUserFont : 1; // platform font entry (data) - bool mIsLocalUserFont : 1; // platform font entry (local) - bool mStandardFace : 1; - bool mSymbolFont : 1; - bool mIgnoreGDEF : 1; - bool mIgnoreGSUB : 1; - bool mSVGInitialized : 1; - bool mMathInitialized : 1; - bool mHasSpaceFeaturesInitialized : 1; - bool mHasSpaceFeatures : 1; - bool mHasSpaceFeaturesKerning : 1; - bool mHasSpaceFeaturesNonKerning : 1; - bool mSkipDefaultFeatureSpaceCheck : 1; - bool mHasGraphiteTables : 1; - bool mCheckedForGraphiteTables : 1; - bool mHasCmapTable : 1; - bool mGrFaceInitialized : 1; - bool mCheckedForColorGlyph : 1; - - // bitvector of substitution space features per script, one each - // for default and non-default features - uint32_t mDefaultSubSpaceFeatures[(MOZ_NUM_SCRIPT_CODES + 31) / 32]; - uint32_t mNonDefaultSubSpaceFeatures[(MOZ_NUM_SCRIPT_CODES + 31) / 32]; - - uint16_t mWeight; - int16_t mStretch; - - nsRefPtr mCharacterMap; - uint32_t mUVSOffset; - nsAutoArrayPtr mUVSData; - nsAutoPtr mUserFontData; - nsAutoPtr mSVGGlyphs; - // list of gfxFonts that are using SVG glyphs - nsTArray mFontsUsingSVGGlyphs; - nsAutoPtr mMathTable; - nsTArray mFeatureSettings; - nsAutoPtr> mSupportedFeatures; - nsAutoPtr> mFeatureInputs; - uint32_t mLanguageOverride; - - // Color Layer font support - hb_blob_t* mCOLR; - hb_blob_t* mCPAL; - -protected: - friend class gfxPlatformFontList; - friend class gfxMacPlatformFontList; - friend class gfxUserFcFontEntry; - friend class gfxFontFamily; - friend class gfxSingleFaceMacFontFamily; - - gfxFontEntry(); - - // Protected destructor, to discourage deletion outside of Release(): - virtual ~gfxFontEntry(); - - virtual gfxFont *CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold) { - NS_NOTREACHED("oops, somebody didn't override CreateFontInstance"); - return nullptr; - } - - virtual void CheckForGraphiteTables(); - - // Copy a font table into aBuffer. - // The caller will be responsible for ownership of the data. - virtual nsresult CopyFontTable(uint32_t aTableTag, - FallibleTArray& aBuffer) { - NS_NOTREACHED("forgot to override either GetFontTable or CopyFontTable?"); - return NS_ERROR_FAILURE; - } - - // Return a blob that wraps a table found within a buffer of font data. - // The blob does NOT own its data; caller guarantees that the buffer - // will remain valid at least as long as the blob. - // Returns null if the specified table is not found. - // This method assumes aFontData is valid 'sfnt' data; before using this, - // caller is responsible to do any sanitization/validation necessary. - hb_blob_t* GetTableFromFontData(const void* aFontData, uint32_t aTableTag); - - // lookup the cmap in cached font data - virtual already_AddRefed - GetCMAPFromFontInfo(FontInfoData *aFontInfoData, - uint32_t& aUVSOffset, - bool& aSymbolFont); - - // Font's unitsPerEm from the 'head' table, if available (will be set to - // kInvalidUPEM for non-sfnt font formats) - uint16_t mUnitsPerEm; - - // Shaper-specific face objects, shared by all instantiations of the same - // physical font, regardless of size. - // Usually, only one of these will actually be created for any given font - // entry, depending on the font tables that are present. - - // hb_face_t is refcounted internally, so each shaper that's using it will - // bump the ref count when it acquires the face, and "destroy" (release) it - // in its destructor. The font entry has only this non-owning reference to - // the face; when the face is deleted, it will tell the font entry to forget - // it, so that a new face will be created next time it is needed. - hb_face_t* mHBFace; - - static hb_blob_t* HBGetTable(hb_face_t *face, uint32_t aTag, void *aUserData); - - // Callback that the hb_face will use to tell us when it is being deleted. - static void HBFaceDeletedCallback(void *aUserData); - - // gr_face is -not- refcounted, so it will be owned directly by the font - // entry, and we'll keep a count of how many references we've handed out; - // each shaper is responsible to call ReleaseGrFace on its entry when - // finished with it, so that we know when it can be deleted. - gr_face* mGrFace; - - // hashtable to map raw table data ptr back to its owning blob, for use by - // graphite table-release callback - nsDataHashtable,void*>* mGrTableMap; - - // number of current users of this entry's mGrFace - nsrefcnt mGrFaceRefCnt; - - static const void* GrGetTable(const void *aAppFaceHandle, - unsigned int aName, - size_t *aLen); - static void GrReleaseTable(const void *aAppFaceHandle, - const void *aTableBuffer); - -private: - /** - * Font table hashtable, to support GetFontTable for harfbuzz. - * - * The harfbuzz shaper (and potentially other clients) needs access to raw - * font table data. This needs to be cached so that it can be used - * repeatedly (each time we construct a text run; in some cases, for - * each character/glyph within the run) without re-fetching large tables - * every time. - * - * Because we may instantiate many gfxFonts for the same physical font - * file (at different sizes), we should ensure that they can share a - * single cached copy of the font tables. To do this, we implement table - * access and sharing on the fontEntry rather than the font itself. - * - * The default implementation uses GetFontTable() to read font table - * data into byte arrays, and wraps them in blobs which are registered in - * a hashtable. The hashtable can then return pre-existing blobs to - * harfbuzz. - * - * Harfbuzz will "destroy" the blobs when it is finished with them. When - * the last blob reference is removed, the FontTableBlobData user data - * will remove the blob from the hashtable if still registered. - */ - - class FontTableBlobData; - - /** - * FontTableHashEntry manages the entries of hb_blob_t's containing font - * table data. - * - * This is used to share font tables across fonts with the same - * font entry (but different sizes) for use by HarfBuzz. The hashtable - * does not own a strong reference to the blob, but keeps a weak pointer, - * managed by FontTableBlobData. Similarly FontTableBlobData keeps only a - * weak pointer to the hashtable, managed by FontTableHashEntry. - */ - - class FontTableHashEntry : public nsUint32HashKey - { - public: - // Declarations for nsTHashtable - - typedef nsUint32HashKey KeyClass; - typedef KeyClass::KeyType KeyType; - typedef KeyClass::KeyTypePointer KeyTypePointer; - - explicit FontTableHashEntry(KeyTypePointer aTag) - : KeyClass(aTag) - , mSharedBlobData(nullptr) - , mBlob(nullptr) - { } - - // NOTE: This assumes the new entry belongs to the same hashtable as - // the old, because the mHashtable pointer in mSharedBlobData (if - // present) will not be updated. - FontTableHashEntry(FontTableHashEntry&& toMove) - : KeyClass(mozilla::Move(toMove)) - , mSharedBlobData(mozilla::Move(toMove.mSharedBlobData)) - , mBlob(mozilla::Move(toMove.mBlob)) - { - toMove.mSharedBlobData = nullptr; - toMove.mBlob = nullptr; - } - - ~FontTableHashEntry() { Clear(); } - - // FontTable/Blob API - - // Transfer (not copy) elements of aTable to a new hb_blob_t and - // return ownership to the caller. A weak reference to the blob is - // recorded in the hashtable entry so that others may use the same - // table. - hb_blob_t * - ShareTableAndGetBlob(FallibleTArray& aTable, - nsTHashtable *aHashtable); - - // Return a strong reference to the blob. - // Callers must hb_blob_destroy the returned blob. - hb_blob_t *GetBlob() const; - - void Clear(); - - size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; - - private: - static void DeleteFontTableBlobData(void *aBlobData); - // not implemented - FontTableHashEntry& operator=(FontTableHashEntry& toCopy); - - FontTableBlobData *mSharedBlobData; - hb_blob_t *mBlob; - }; - - nsAutoPtr > mFontTableCache; - - gfxFontEntry(const gfxFontEntry&); - gfxFontEntry& operator=(const gfxFontEntry&); -}; - - -// used when iterating over all fonts looking for a match for a given character -struct GlobalFontMatch { - GlobalFontMatch(const uint32_t aCharacter, - int32_t aRunScript, - const gfxFontStyle *aStyle) : - mCh(aCharacter), mRunScript(aRunScript), mStyle(aStyle), - mMatchRank(0), mCount(0), mCmapsTested(0) - { - - } - - const uint32_t mCh; // codepoint to be matched - int32_t mRunScript; // Unicode script for the codepoint - const gfxFontStyle* mStyle; // style to match - int32_t mMatchRank; // metric indicating closest match - nsRefPtr mBestMatch; // current best match - nsRefPtr mMatchedFamily; // the family it belongs to - uint32_t mCount; // number of fonts matched - uint32_t mCmapsTested; // number of cmaps tested -}; - -class gfxFontFamily { -public: - NS_INLINE_DECL_REFCOUNTING(gfxFontFamily) - - explicit gfxFontFamily(const nsAString& aName) : - mName(aName), - mOtherFamilyNamesInitialized(false), - mHasOtherFamilyNames(false), - mFaceNamesInitialized(false), - mHasStyles(false), - mIsSimpleFamily(false), - mIsBadUnderlineFamily(false), - mFamilyCharacterMapInitialized(false), - mSkipDefaultFeatureSpaceCheck(false) - { } - - const nsString& Name() { return mName; } - - virtual void LocalizedName(nsAString& aLocalizedName); - virtual bool HasOtherFamilyNames(); - - nsTArray >& GetFontList() { return mAvailableFonts; } - - void AddFontEntry(nsRefPtr aFontEntry) { - // bug 589682 - set the IgnoreGDEF flag on entries for Italic faces - // of Times New Roman, because of buggy table in those fonts - if (aFontEntry->IsItalic() && !aFontEntry->IsUserFont() && - Name().EqualsLiteral("Times New Roman")) - { - aFontEntry->mIgnoreGDEF = true; - } - if (aFontEntry->mFamilyName.IsEmpty()) { - aFontEntry->mFamilyName = Name(); - } else { - MOZ_ASSERT(aFontEntry->mFamilyName.Equals(Name())); - } - aFontEntry->mSkipDefaultFeatureSpaceCheck = mSkipDefaultFeatureSpaceCheck; - mAvailableFonts.AppendElement(aFontEntry); - } - - // note that the styles for this family have been added - bool HasStyles() { return mHasStyles; } - void SetHasStyles(bool aHasStyles) { mHasStyles = aHasStyles; } - - // choose a specific face to match a style using CSS font matching - // rules (weight matching occurs here). may return a face that doesn't - // precisely match (e.g. normal face when no italic face exists). - // aNeedsSyntheticBold is set to true when synthetic bolding is - // needed, false otherwise - gfxFontEntry *FindFontForStyle(const gfxFontStyle& aFontStyle, - bool& aNeedsSyntheticBold); - - // checks for a matching font within the family - // used as part of the font fallback process - void FindFontForChar(GlobalFontMatch *aMatchData); - - // checks all fonts for a matching font within the family - void SearchAllFontsForChar(GlobalFontMatch *aMatchData); - - // read in other family names, if any, and use functor to add each into cache - virtual void ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList); - - // helper method for reading localized family names from the name table - // of a single face - static void ReadOtherFamilyNamesForFace(const nsAString& aFamilyName, - const char *aNameData, - uint32_t aDataLength, - nsTArray& aOtherFamilyNames, - bool useFullName); - - // set when other family names have been read in - void SetOtherFamilyNamesInitialized() { - mOtherFamilyNamesInitialized = true; - } - - // read in other localized family names, fullnames and Postscript names - // for all faces and append to lookup tables - virtual void ReadFaceNames(gfxPlatformFontList *aPlatformFontList, - bool aNeedFullnamePostscriptNames, - FontInfoData *aFontInfoData = nullptr); - - // find faces belonging to this family (platform implementations override this; - // should be made pure virtual once all subclasses have been updated) - virtual void FindStyleVariations(FontInfoData *aFontInfoData = nullptr) { } - - // search for a specific face using the Postscript name - gfxFontEntry* FindFont(const nsAString& aPostscriptName); - - // read in cmaps for all the faces - void ReadAllCMAPs(FontInfoData *aFontInfoData = nullptr); - - bool TestCharacterMap(uint32_t aCh) { - if (!mFamilyCharacterMapInitialized) { - ReadAllCMAPs(); - } - return mFamilyCharacterMap.test(aCh); - } - - void ResetCharacterMap() { - mFamilyCharacterMap.reset(); - mFamilyCharacterMapInitialized = false; - } - - // mark this family as being in the "bad" underline offset blacklist - void SetBadUnderlineFamily() { - mIsBadUnderlineFamily = true; - if (mHasStyles) { - SetBadUnderlineFonts(); - } - } - - bool IsBadUnderlineFamily() const { return mIsBadUnderlineFamily; } - - // sort available fonts to put preferred (standard) faces towards the end - void SortAvailableFonts(); - - // check whether the family fits into the simple 4-face model, - // so we can use simplified style-matching; - // if so set the mIsSimpleFamily flag (defaults to False before we've checked) - void CheckForSimpleFamily(); - - // For memory reporter - virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, - FontListSizes* aSizes) const; - virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, - FontListSizes* aSizes) const; - -#ifdef DEBUG - // Only used for debugging checks - does a linear search - bool ContainsFace(gfxFontEntry* aFontEntry); -#endif - - void SetSkipSpaceFeatureCheck(bool aSkipCheck) { - mSkipDefaultFeatureSpaceCheck = aSkipCheck; - } - -protected: - // Protected destructor, to discourage deletion outside of Release(): - virtual ~gfxFontFamily() - { - } - - // fills in an array with weights of faces that match style, - // returns whether any matching entries found - virtual bool FindWeightsForStyle(gfxFontEntry* aFontsForWeights[], - bool anItalic, int16_t aStretch); - - bool ReadOtherFamilyNamesForFace(gfxPlatformFontList *aPlatformFontList, - hb_blob_t *aNameTable, - bool useFullName = false); - - // set whether this font family is in "bad" underline offset blacklist. - void SetBadUnderlineFonts() { - uint32_t i, numFonts = mAvailableFonts.Length(); - for (i = 0; i < numFonts; i++) { - if (mAvailableFonts[i]) { - mAvailableFonts[i]->mIsBadUnderlineFont = true; - } - } - } - - nsString mName; - nsTArray > mAvailableFonts; - gfxSparseBitSet mFamilyCharacterMap; - bool mOtherFamilyNamesInitialized : 1; - bool mHasOtherFamilyNames : 1; - bool mFaceNamesInitialized : 1; - bool mHasStyles : 1; - bool mIsSimpleFamily : 1; - bool mIsBadUnderlineFamily : 1; - bool mFamilyCharacterMapInitialized : 1; - bool mSkipDefaultFeatureSpaceCheck : 1; - - enum { - // for "simple" families, the faces are stored in mAvailableFonts - // with fixed positions: - kRegularFaceIndex = 0, - kBoldFaceIndex = 1, - kItalicFaceIndex = 2, - kBoldItalicFaceIndex = 3, - // mask values for selecting face with bold and/or italic attributes - kBoldMask = 0x01, - kItalicMask = 0x02 - }; -}; - struct gfxTextRange { enum { // flags for recording the kind of font-matching that was used @@ -1339,131 +547,6 @@ protected: virtual ~gfxTextRunFactory() {} }; -/** - * This stores glyph bounds information for a particular gfxFont, at - * a particular appunits-per-dev-pixel ratio (because the compressed glyph - * width array is stored in appunits). - * - * We store a hashtable from glyph IDs to float bounding rects. For the - * common case where the glyph has no horizontal left bearing, and no - * y overflow above the font ascent or below the font descent, and tight - * bounding boxes are not required, we avoid storing the glyph ID in the hashtable - * and instead consult an array of 16-bit glyph XMost values (in appunits). - * This array always has an entry for the font's space glyph --- the width is - * assumed to be zero. - */ -class gfxGlyphExtents { -public: - explicit gfxGlyphExtents(int32_t aAppUnitsPerDevUnit) : - mAppUnitsPerDevUnit(aAppUnitsPerDevUnit) { - MOZ_COUNT_CTOR(gfxGlyphExtents); - } - ~gfxGlyphExtents(); - - enum { INVALID_WIDTH = 0xFFFF }; - - void NotifyGlyphsChanged() { - mTightGlyphExtents.Clear(); - } - - // returns INVALID_WIDTH => not a contained glyph - // Otherwise the glyph has no before-bearing or vertical bearings, - // and the result is its width measured from the baseline origin, in - // appunits. - uint16_t GetContainedGlyphWidthAppUnits(uint32_t aGlyphID) const { - return mContainedGlyphWidths.Get(aGlyphID); - } - - bool IsGlyphKnown(uint32_t aGlyphID) const { - return mContainedGlyphWidths.Get(aGlyphID) != INVALID_WIDTH || - mTightGlyphExtents.GetEntry(aGlyphID) != nullptr; - } - - bool IsGlyphKnownWithTightExtents(uint32_t aGlyphID) const { - return mTightGlyphExtents.GetEntry(aGlyphID) != nullptr; - } - - // Get glyph extents; a rectangle relative to the left baseline origin - // Returns true on success. Can fail on OOM or when aContext is null - // and extents were not (successfully) prefetched. - bool GetTightGlyphExtentsAppUnits(gfxFont *aFont, gfxContext *aContext, - uint32_t aGlyphID, gfxRect *aExtents); - - void SetContainedGlyphWidthAppUnits(uint32_t aGlyphID, uint16_t aWidth) { - mContainedGlyphWidths.Set(aGlyphID, aWidth); - } - void SetTightGlyphExtents(uint32_t aGlyphID, const gfxRect& aExtentsAppUnits); - - int32_t GetAppUnitsPerDevUnit() { return mAppUnitsPerDevUnit; } - - size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; - size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; - -private: - class HashEntry : public nsUint32HashKey { - public: - // When constructing a new entry in the hashtable, we'll leave this - // blank. The caller of Put() will fill this in. - explicit HashEntry(KeyTypePointer aPtr) : nsUint32HashKey(aPtr) {} - HashEntry(const HashEntry& toCopy) : nsUint32HashKey(toCopy) { - x = toCopy.x; y = toCopy.y; width = toCopy.width; height = toCopy.height; - } - - float x, y, width, height; - }; - - enum { BLOCK_SIZE_BITS = 7, BLOCK_SIZE = 1 << BLOCK_SIZE_BITS }; // 128-glyph blocks - - class GlyphWidths { - public: - void Set(uint32_t aIndex, uint16_t aValue); - uint16_t Get(uint32_t aIndex) const { - uint32_t block = aIndex >> BLOCK_SIZE_BITS; - if (block >= mBlocks.Length()) - return INVALID_WIDTH; - uintptr_t bits = mBlocks[block]; - if (!bits) - return INVALID_WIDTH; - uint32_t indexInBlock = aIndex & (BLOCK_SIZE - 1); - if (bits & 0x1) { - if (GetGlyphOffset(bits) != indexInBlock) - return INVALID_WIDTH; - return GetWidth(bits); - } - uint16_t *widths = reinterpret_cast(bits); - return widths[indexInBlock]; - } - - uint32_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; - - ~GlyphWidths(); - - private: - static uint32_t GetGlyphOffset(uintptr_t aBits) { - NS_ASSERTION(aBits & 0x1, "This is really a pointer..."); - return (aBits >> 1) & ((1 << BLOCK_SIZE_BITS) - 1); - } - static uint32_t GetWidth(uintptr_t aBits) { - NS_ASSERTION(aBits & 0x1, "This is really a pointer..."); - return aBits >> (1 + BLOCK_SIZE_BITS); - } - static uintptr_t MakeSingle(uint32_t aGlyphOffset, uint16_t aWidth) { - return (aWidth << (1 + BLOCK_SIZE_BITS)) + (aGlyphOffset << 1) + 1; - } - - nsTArray mBlocks; - }; - - GlyphWidths mContainedGlyphWidths; - nsTHashtable mTightGlyphExtents; - int32_t mAppUnitsPerDevUnit; - -private: - // not implemented: - gfxGlyphExtents(const gfxGlyphExtents& aOther) MOZ_DELETE; - gfxGlyphExtents& operator=(const gfxGlyphExtents& aOther) MOZ_DELETE; -}; - /** * gfxFontShaper * @@ -1519,6 +602,621 @@ protected: }; +/* + * gfxShapedText is an abstract superclass for gfxShapedWord and gfxTextRun. + * These are objects that store a list of zero or more glyphs for each character. + * For each glyph we store the glyph ID, the advance, and possibly x/y-offsets. + * The idea is that a string is rendered by a loop that draws each glyph + * at its designated offset from the current point, then advances the current + * point by the glyph's advance in the direction of the textrun (LTR or RTL). + * Each glyph advance is always rounded to the nearest appunit; this ensures + * consistent results when dividing the text in a textrun into multiple text + * frames (frame boundaries are always aligned to appunits). We optimize + * for the case where a character has a single glyph and zero xoffset and yoffset, + * and the glyph ID and advance are in a reasonable range so we can pack all + * necessary data into 32 bits. + * + * gfxFontShaper can shape text into either a gfxShapedWord (cached by a gfxFont) + * or directly into a gfxTextRun (for cases where we want to shape textruns in + * their entirety rather than using cached words, because there may be layout + * features that depend on the inter-word spaces). + */ +class gfxShapedText +{ +public: + gfxShapedText(uint32_t aLength, uint32_t aFlags, + int32_t aAppUnitsPerDevUnit) + : mLength(aLength) + , mFlags(aFlags) + , mAppUnitsPerDevUnit(aAppUnitsPerDevUnit) + { } + + virtual ~gfxShapedText() { } + + /** + * This class records the information associated with a character in the + * input string. It's optimized for the case where there is one glyph + * representing that character alone. + * + * A character can have zero or more associated glyphs. Each glyph + * has an advance width and an x and y offset. + * A character may be the start of a cluster. + * A character may be the start of a ligature group. + * A character can be "missing", indicating that the system is unable + * to render the character. + * + * All characters in a ligature group conceptually share all the glyphs + * associated with the characters in a group. + */ + class CompressedGlyph { + public: + CompressedGlyph() { mValue = 0; } + + enum { + // Indicates that a cluster and ligature group starts at this + // character; this character has a single glyph with a reasonable + // advance and zero offsets. A "reasonable" advance + // is one that fits in the available bits (currently 12) (specified + // in appunits). + FLAG_IS_SIMPLE_GLYPH = 0x80000000U, + + // Indicates whether a linebreak is allowed before this character; + // this is a two-bit field that holds a FLAG_BREAK_TYPE_xxx value + // indicating the kind of linebreak (if any) allowed here. + FLAGS_CAN_BREAK_BEFORE = 0x60000000U, + + FLAGS_CAN_BREAK_SHIFT = 29, + FLAG_BREAK_TYPE_NONE = 0, + FLAG_BREAK_TYPE_NORMAL = 1, + FLAG_BREAK_TYPE_HYPHEN = 2, + + FLAG_CHAR_IS_SPACE = 0x10000000U, + + // The advance is stored in appunits + ADVANCE_MASK = 0x0FFF0000U, + ADVANCE_SHIFT = 16, + + GLYPH_MASK = 0x0000FFFFU, + + // Non-simple glyphs may or may not have glyph data in the + // corresponding mDetailedGlyphs entry. They have the following + // flag bits: + + // When NOT set, indicates that this character corresponds to a + // missing glyph and should be skipped (or possibly, render the character + // Unicode value in some special way). If there are glyphs, + // the mGlyphID is actually the UTF16 character code. The bit is + // inverted so we can memset the array to zero to indicate all missing. + FLAG_NOT_MISSING = 0x01, + FLAG_NOT_CLUSTER_START = 0x02, + FLAG_NOT_LIGATURE_GROUP_START = 0x04, + + FLAG_CHAR_IS_TAB = 0x08, + FLAG_CHAR_IS_NEWLINE = 0x10, + FLAG_CHAR_IS_LOW_SURROGATE = 0x20, + CHAR_IDENTITY_FLAGS_MASK = 0x38, + + GLYPH_COUNT_MASK = 0x00FFFF00U, + GLYPH_COUNT_SHIFT = 8 + }; + + // "Simple glyphs" have a simple glyph ID, simple advance and their + // x and y offsets are zero. Also the glyph extents do not overflow + // the font-box defined by the font ascent, descent and glyph advance width. + // These case is optimized to avoid storing DetailedGlyphs. + + // Returns true if the glyph ID aGlyph fits into the compressed representation + static bool IsSimpleGlyphID(uint32_t aGlyph) { + return (aGlyph & GLYPH_MASK) == aGlyph; + } + // Returns true if the advance aAdvance fits into the compressed representation. + // aAdvance is in appunits. + static bool IsSimpleAdvance(uint32_t aAdvance) { + return (aAdvance & (ADVANCE_MASK >> ADVANCE_SHIFT)) == aAdvance; + } + + bool IsSimpleGlyph() const { return (mValue & FLAG_IS_SIMPLE_GLYPH) != 0; } + uint32_t GetSimpleAdvance() const { return (mValue & ADVANCE_MASK) >> ADVANCE_SHIFT; } + uint32_t GetSimpleGlyph() const { return mValue & GLYPH_MASK; } + + bool IsMissing() const { return (mValue & (FLAG_NOT_MISSING|FLAG_IS_SIMPLE_GLYPH)) == 0; } + bool IsClusterStart() const { + return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_CLUSTER_START); + } + bool IsLigatureGroupStart() const { + return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_LIGATURE_GROUP_START); + } + bool IsLigatureContinuation() const { + return (mValue & FLAG_IS_SIMPLE_GLYPH) == 0 && + (mValue & (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING)) == + (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING); + } + + // Return true if the original character was a normal (breakable, + // trimmable) space (U+0020). Not true for other characters that + // may happen to map to the space glyph (U+00A0). + bool CharIsSpace() const { + return (mValue & FLAG_CHAR_IS_SPACE) != 0; + } + + bool CharIsTab() const { + return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_TAB) != 0; + } + bool CharIsNewline() const { + return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_NEWLINE) != 0; + } + bool CharIsLowSurrogate() const { + return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_LOW_SURROGATE) != 0; + } + + uint32_t CharIdentityFlags() const { + return IsSimpleGlyph() ? 0 : (mValue & CHAR_IDENTITY_FLAGS_MASK); + } + + void SetClusterStart(bool aIsClusterStart) { + NS_ASSERTION(!IsSimpleGlyph(), + "can't call SetClusterStart on simple glyphs"); + if (aIsClusterStart) { + mValue &= ~FLAG_NOT_CLUSTER_START; + } else { + mValue |= FLAG_NOT_CLUSTER_START; + } + } + + uint8_t CanBreakBefore() const { + return (mValue & FLAGS_CAN_BREAK_BEFORE) >> FLAGS_CAN_BREAK_SHIFT; + } + // Returns FLAGS_CAN_BREAK_BEFORE if the setting changed, 0 otherwise + uint32_t SetCanBreakBefore(uint8_t aCanBreakBefore) { + NS_ASSERTION(aCanBreakBefore <= 2, + "Bogus break-before value!"); + uint32_t breakMask = (uint32_t(aCanBreakBefore) << FLAGS_CAN_BREAK_SHIFT); + uint32_t toggle = breakMask ^ (mValue & FLAGS_CAN_BREAK_BEFORE); + mValue ^= toggle; + return toggle; + } + + CompressedGlyph& SetSimpleGlyph(uint32_t aAdvanceAppUnits, uint32_t aGlyph) { + NS_ASSERTION(IsSimpleAdvance(aAdvanceAppUnits), "Advance overflow"); + NS_ASSERTION(IsSimpleGlyphID(aGlyph), "Glyph overflow"); + NS_ASSERTION(!CharIdentityFlags(), "Char identity flags lost"); + mValue = (mValue & (FLAGS_CAN_BREAK_BEFORE | FLAG_CHAR_IS_SPACE)) | + FLAG_IS_SIMPLE_GLYPH | + (aAdvanceAppUnits << ADVANCE_SHIFT) | aGlyph; + return *this; + } + CompressedGlyph& SetComplex(bool aClusterStart, bool aLigatureStart, + uint32_t aGlyphCount) { + mValue = (mValue & (FLAGS_CAN_BREAK_BEFORE | FLAG_CHAR_IS_SPACE)) | + FLAG_NOT_MISSING | + CharIdentityFlags() | + (aClusterStart ? 0 : FLAG_NOT_CLUSTER_START) | + (aLigatureStart ? 0 : FLAG_NOT_LIGATURE_GROUP_START) | + (aGlyphCount << GLYPH_COUNT_SHIFT); + return *this; + } + /** + * Missing glyphs are treated as ligature group starts; don't mess with + * the cluster-start flag (see bugs 618870 and 619286). + */ + CompressedGlyph& SetMissing(uint32_t aGlyphCount) { + mValue = (mValue & (FLAGS_CAN_BREAK_BEFORE | FLAG_NOT_CLUSTER_START | + FLAG_CHAR_IS_SPACE)) | + CharIdentityFlags() | + (aGlyphCount << GLYPH_COUNT_SHIFT); + return *this; + } + uint32_t GetGlyphCount() const { + NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph"); + return (mValue & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT; + } + + void SetIsSpace() { + mValue |= FLAG_CHAR_IS_SPACE; + } + void SetIsTab() { + NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph"); + mValue |= FLAG_CHAR_IS_TAB; + } + void SetIsNewline() { + NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph"); + mValue |= FLAG_CHAR_IS_NEWLINE; + } + void SetIsLowSurrogate() { + NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph"); + mValue |= FLAG_CHAR_IS_LOW_SURROGATE; + } + + private: + uint32_t mValue; + }; + + // Accessor for the array of CompressedGlyph records, which will be in + // a different place in gfxShapedWord vs gfxTextRun + virtual CompressedGlyph *GetCharacterGlyphs() = 0; + + /** + * When the glyphs for a character don't fit into a CompressedGlyph record + * in SimpleGlyph format, we use an array of DetailedGlyphs instead. + */ + struct DetailedGlyph { + /** The glyphID, or the Unicode character + * if this is a missing glyph */ + uint32_t mGlyphID; + /** The advance, x-offset and y-offset of the glyph, in appunits + * mAdvance is in the text direction (RTL or LTR) + * mXOffset is always from left to right + * mYOffset is always from top to bottom */ + int32_t mAdvance; + float mXOffset, mYOffset; + }; + + void SetGlyphs(uint32_t aCharIndex, CompressedGlyph aGlyph, + const DetailedGlyph *aGlyphs); + + void SetMissingGlyph(uint32_t aIndex, uint32_t aChar, gfxFont *aFont); + + void SetIsSpace(uint32_t aIndex) { + GetCharacterGlyphs()[aIndex].SetIsSpace(); + } + + void SetIsLowSurrogate(uint32_t aIndex) { + SetGlyphs(aIndex, CompressedGlyph().SetComplex(false, false, 0), nullptr); + GetCharacterGlyphs()[aIndex].SetIsLowSurrogate(); + } + + bool HasDetailedGlyphs() const { + return mDetailedGlyphs != nullptr; + } + + bool IsLigatureGroupStart(uint32_t aPos) { + NS_ASSERTION(aPos < GetLength(), "aPos out of range"); + return GetCharacterGlyphs()[aPos].IsLigatureGroupStart(); + } + + // NOTE that this must not be called for a character offset that does + // not have any DetailedGlyph records; callers must have verified that + // GetCharacterGlyphs()[aCharIndex].GetGlyphCount() is greater than zero. + DetailedGlyph *GetDetailedGlyphs(uint32_t aCharIndex) { + NS_ASSERTION(GetCharacterGlyphs() && HasDetailedGlyphs() && + !GetCharacterGlyphs()[aCharIndex].IsSimpleGlyph() && + GetCharacterGlyphs()[aCharIndex].GetGlyphCount() > 0, + "invalid use of GetDetailedGlyphs; check the caller!"); + return mDetailedGlyphs->Get(aCharIndex); + } + + void AdjustAdvancesForSyntheticBold(float aSynBoldOffset, + uint32_t aOffset, uint32_t aLength); + + // Mark clusters in the CompressedGlyph records, starting at aOffset, + // based on the Unicode properties of the text in aString. + // This is also responsible to set the IsSpace flag for space characters. + void SetupClusterBoundaries(uint32_t aOffset, + const char16_t *aString, + uint32_t aLength); + // In 8-bit text, there won't actually be any clusters, but we still need + // the space-marking functionality. + void SetupClusterBoundaries(uint32_t aOffset, + const uint8_t *aString, + uint32_t aLength); + + uint32_t GetFlags() const { + return mFlags; + } + + bool IsRightToLeft() const { + return (GetFlags() & gfxTextRunFactory::TEXT_IS_RTL) != 0; + } + + gfxFloat GetDirection() const { + return IsRightToLeft() ? -1.0f : 1.0f; + } + + bool DisableLigatures() const { + return (GetFlags() & + gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) != 0; + } + + bool TextIs8Bit() const { + return (GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) != 0; + } + + int32_t GetAppUnitsPerDevUnit() const { + return mAppUnitsPerDevUnit; + } + + uint32_t GetLength() const { + return mLength; + } + + bool FilterIfIgnorable(uint32_t aIndex, uint32_t aCh); + +protected: + // Allocate aCount DetailedGlyphs for the given index + DetailedGlyph *AllocateDetailedGlyphs(uint32_t aCharIndex, + uint32_t aCount); + + // For characters whose glyph data does not fit the "simple" glyph criteria + // in CompressedGlyph, we use a sorted array to store the association + // between the source character offset and an index into an array + // DetailedGlyphs. The CompressedGlyph record includes a count of + // the number of DetailedGlyph records that belong to the character, + // starting at the given index. + class DetailedGlyphStore { + public: + DetailedGlyphStore() + : mLastUsed(0) + { } + + // This is optimized for the most common calling patterns: + // we rarely need random access to the records, access is most commonly + // sequential through the textRun, so we record the last-used index + // and check whether the caller wants the same record again, or the + // next; if not, it's most likely we're starting over from the start + // of the run, so we check the first entry before resorting to binary + // search as a last resort. + // NOTE that this must not be called for a character offset that does + // not have any DetailedGlyph records; callers must have verified that + // mCharacterGlyphs[aOffset].GetGlyphCount() is greater than zero + // before calling this, otherwise the assertions here will fire (in a + // debug build), and we'll probably crash. + DetailedGlyph* Get(uint32_t aOffset) { + NS_ASSERTION(mOffsetToIndex.Length() > 0, + "no detailed glyph records!"); + DetailedGlyph* details = mDetails.Elements(); + // check common cases (fwd iteration, initial entry, etc) first + if (mLastUsed < mOffsetToIndex.Length() - 1 && + aOffset == mOffsetToIndex[mLastUsed + 1].mOffset) { + ++mLastUsed; + } else if (aOffset == mOffsetToIndex[0].mOffset) { + mLastUsed = 0; + } else if (aOffset == mOffsetToIndex[mLastUsed].mOffset) { + // do nothing + } else if (mLastUsed > 0 && + aOffset == mOffsetToIndex[mLastUsed - 1].mOffset) { + --mLastUsed; + } else { + mLastUsed = + mOffsetToIndex.BinaryIndexOf(aOffset, CompareToOffset()); + } + NS_ASSERTION(mLastUsed != nsTArray::NoIndex, + "detailed glyph record missing!"); + return details + mOffsetToIndex[mLastUsed].mIndex; + } + + DetailedGlyph* Allocate(uint32_t aOffset, uint32_t aCount) { + uint32_t detailIndex = mDetails.Length(); + DetailedGlyph *details = mDetails.AppendElements(aCount); + // We normally set up glyph records sequentially, so the common case + // here is to append new records to the mOffsetToIndex array; + // test for that before falling back to the InsertElementSorted + // method. + if (mOffsetToIndex.Length() == 0 || + aOffset > mOffsetToIndex[mOffsetToIndex.Length() - 1].mOffset) { + mOffsetToIndex.AppendElement(DGRec(aOffset, detailIndex)); + } else { + mOffsetToIndex.InsertElementSorted(DGRec(aOffset, detailIndex), + CompareRecordOffsets()); + } + return details; + } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { + return aMallocSizeOf(this) + + mDetails.SizeOfExcludingThis(aMallocSizeOf) + + mOffsetToIndex.SizeOfExcludingThis(aMallocSizeOf); + } + + private: + struct DGRec { + DGRec(const uint32_t& aOffset, const uint32_t& aIndex) + : mOffset(aOffset), mIndex(aIndex) { } + uint32_t mOffset; // source character offset in the textrun + uint32_t mIndex; // index where this char's DetailedGlyphs begin + }; + + struct CompareToOffset { + bool Equals(const DGRec& a, const uint32_t& b) const { + return a.mOffset == b; + } + bool LessThan(const DGRec& a, const uint32_t& b) const { + return a.mOffset < b; + } + }; + + struct CompareRecordOffsets { + bool Equals(const DGRec& a, const DGRec& b) const { + return a.mOffset == b.mOffset; + } + bool LessThan(const DGRec& a, const DGRec& b) const { + return a.mOffset < b.mOffset; + } + }; + + // Concatenated array of all the DetailedGlyph records needed for the + // textRun; individual character offsets are associated with indexes + // into this array via the mOffsetToIndex table. + nsTArray mDetails; + + // For each character offset that needs DetailedGlyphs, we record the + // index in mDetails where the list of glyphs begins. This array is + // sorted by mOffset. + nsTArray mOffsetToIndex; + + // Records the most recently used index into mOffsetToIndex, so that + // we can support sequential access more quickly than just doing + // a binary search each time. + nsTArray::index_type mLastUsed; + }; + + nsAutoPtr mDetailedGlyphs; + + // Number of char16_t characters and CompressedGlyph glyph records + uint32_t mLength; + + // Shaping flags (direction, ligature-suppression) + uint32_t mFlags; + + int32_t mAppUnitsPerDevUnit; +}; + +/* + * gfxShapedWord: an individual (space-delimited) run of text shaped with a + * particular font, without regard to external context. + * + * The glyph data is copied into gfxTextRuns as needed from the cache of + * ShapedWords associated with each gfxFont instance. + */ +class gfxShapedWord : public gfxShapedText +{ +public: + // Create a ShapedWord that can hold glyphs for aLength characters, + // with mCharacterGlyphs sized appropriately. + // + // Returns null on allocation failure (does NOT use infallible alloc) + // so caller must check for success. + // + // This does NOT perform shaping, so the returned word contains no + // glyph data; the caller must call gfxFont::ShapeText() with appropriate + // parameters to set up the glyphs. + static gfxShapedWord* Create(const uint8_t *aText, uint32_t aLength, + int32_t aRunScript, + int32_t aAppUnitsPerDevUnit, + uint32_t aFlags) { + NS_ASSERTION(aLength <= gfxPlatform::GetPlatform()->WordCacheCharLimit(), + "excessive length for gfxShapedWord!"); + + // Compute size needed including the mCharacterGlyphs array + // and a copy of the original text + uint32_t size = + offsetof(gfxShapedWord, mCharGlyphsStorage) + + aLength * (sizeof(CompressedGlyph) + sizeof(uint8_t)); + void *storage = moz_malloc(size); + if (!storage) { + return nullptr; + } + + // Construct in the pre-allocated storage, using placement new + return new (storage) gfxShapedWord(aText, aLength, aRunScript, + aAppUnitsPerDevUnit, aFlags); + } + + static gfxShapedWord* Create(const char16_t *aText, uint32_t aLength, + int32_t aRunScript, + int32_t aAppUnitsPerDevUnit, + uint32_t aFlags) { + NS_ASSERTION(aLength <= gfxPlatform::GetPlatform()->WordCacheCharLimit(), + "excessive length for gfxShapedWord!"); + + // In the 16-bit version of Create, if the TEXT_IS_8BIT flag is set, + // then we convert the text to an 8-bit version and call the 8-bit + // Create function instead. + if (aFlags & gfxTextRunFactory::TEXT_IS_8BIT) { + nsAutoCString narrowText; + LossyAppendUTF16toASCII(nsDependentSubstring(aText, aLength), + narrowText); + return Create((const uint8_t*)(narrowText.BeginReading()), + aLength, aRunScript, aAppUnitsPerDevUnit, aFlags); + } + + uint32_t size = + offsetof(gfxShapedWord, mCharGlyphsStorage) + + aLength * (sizeof(CompressedGlyph) + sizeof(char16_t)); + void *storage = moz_malloc(size); + if (!storage) { + return nullptr; + } + + return new (storage) gfxShapedWord(aText, aLength, aRunScript, + aAppUnitsPerDevUnit, aFlags); + } + + // Override operator delete to properly free the object that was + // allocated via moz_malloc. + void operator delete(void* p) { + moz_free(p); + } + + CompressedGlyph *GetCharacterGlyphs() { + return &mCharGlyphsStorage[0]; + } + + const uint8_t* Text8Bit() const { + NS_ASSERTION(TextIs8Bit(), "invalid use of Text8Bit()"); + return reinterpret_cast(mCharGlyphsStorage + GetLength()); + } + + const char16_t* TextUnicode() const { + NS_ASSERTION(!TextIs8Bit(), "invalid use of TextUnicode()"); + return reinterpret_cast(mCharGlyphsStorage + GetLength()); + } + + char16_t GetCharAt(uint32_t aOffset) const { + NS_ASSERTION(aOffset < GetLength(), "aOffset out of range"); + return TextIs8Bit() ? + char16_t(Text8Bit()[aOffset]) : TextUnicode()[aOffset]; + } + + int32_t Script() const { + return mScript; + } + + void ResetAge() { + mAgeCounter = 0; + } + uint32_t IncrementAge() { + return ++mAgeCounter; + } + + // Helper used when hashing a word for the shaped-word caches + static uint32_t HashMix(uint32_t aHash, char16_t aCh) + { + return (aHash >> 28) ^ (aHash << 4) ^ aCh; + } + +private: + // so that gfxTextRun can share our DetailedGlyphStore class + friend class gfxTextRun; + + // Construct storage for a ShapedWord, ready to receive glyph data + gfxShapedWord(const uint8_t *aText, uint32_t aLength, + int32_t aRunScript, int32_t aAppUnitsPerDevUnit, + uint32_t aFlags) + : gfxShapedText(aLength, aFlags | gfxTextRunFactory::TEXT_IS_8BIT, + aAppUnitsPerDevUnit) + , mScript(aRunScript) + , mAgeCounter(0) + { + memset(mCharGlyphsStorage, 0, aLength * sizeof(CompressedGlyph)); + uint8_t *text = reinterpret_cast(&mCharGlyphsStorage[aLength]); + memcpy(text, aText, aLength * sizeof(uint8_t)); + } + + gfxShapedWord(const char16_t *aText, uint32_t aLength, + int32_t aRunScript, int32_t aAppUnitsPerDevUnit, + uint32_t aFlags) + : gfxShapedText(aLength, aFlags, aAppUnitsPerDevUnit) + , mScript(aRunScript) + , mAgeCounter(0) + { + memset(mCharGlyphsStorage, 0, aLength * sizeof(CompressedGlyph)); + char16_t *text = reinterpret_cast(&mCharGlyphsStorage[aLength]); + memcpy(text, aText, aLength * sizeof(char16_t)); + SetupClusterBoundaries(0, aText, aLength); + } + + int32_t mScript; + + uint32_t mAgeCounter; + + // The mCharGlyphsStorage array is actually a variable-size member; + // when the ShapedWord is created, its size will be increased as necessary + // to allow the proper number of glyphs to be stored. + // The original text, in either 8-bit or 16-bit form, will be stored + // immediately following the CompressedGlyphs. + CompressedGlyph mCharGlyphsStorage[1]; +}; + class GlyphBufferAzure; struct TextRunDrawParams; struct FontDrawParams; @@ -2295,1585 +1993,34 @@ protected: // proportion of ascent used for x-height, if unable to read value from font #define DEFAULT_XHEIGHT_FACTOR 0.56f -/* - * gfxShapedText is an abstract superclass for gfxShapedWord and gfxTextRun. - * These are objects that store a list of zero or more glyphs for each character. - * For each glyph we store the glyph ID, the advance, and possibly x/y-offsets. - * The idea is that a string is rendered by a loop that draws each glyph - * at its designated offset from the current point, then advances the current - * point by the glyph's advance in the direction of the textrun (LTR or RTL). - * Each glyph advance is always rounded to the nearest appunit; this ensures - * consistent results when dividing the text in a textrun into multiple text - * frames (frame boundaries are always aligned to appunits). We optimize - * for the case where a character has a single glyph and zero xoffset and yoffset, - * and the glyph ID and advance are in a reasonable range so we can pack all - * necessary data into 32 bits. - * - * gfxFontShaper can shape text into either a gfxShapedWord (cached by a gfxFont) - * or directly into a gfxTextRun (for cases where we want to shape textruns in - * their entirety rather than using cached words, because there may be layout - * features that depend on the inter-word spaces). - */ -class gfxShapedText -{ -public: - gfxShapedText(uint32_t aLength, uint32_t aFlags, - int32_t aAppUnitsPerDevUnit) - : mLength(aLength) - , mFlags(aFlags) - , mAppUnitsPerDevUnit(aAppUnitsPerDevUnit) - { } +// Parameters passed to gfxFont methods for drawing glyphs from a textrun. +// The TextRunDrawParams are set up once per textrun; the FontDrawParams +// are dependent on the specific font, so they are set per GlyphRun. - virtual ~gfxShapedText() { } - - /** - * This class records the information associated with a character in the - * input string. It's optimized for the case where there is one glyph - * representing that character alone. - * - * A character can have zero or more associated glyphs. Each glyph - * has an advance width and an x and y offset. - * A character may be the start of a cluster. - * A character may be the start of a ligature group. - * A character can be "missing", indicating that the system is unable - * to render the character. - * - * All characters in a ligature group conceptually share all the glyphs - * associated with the characters in a group. - */ - class CompressedGlyph { - public: - CompressedGlyph() { mValue = 0; } - - enum { - // Indicates that a cluster and ligature group starts at this - // character; this character has a single glyph with a reasonable - // advance and zero offsets. A "reasonable" advance - // is one that fits in the available bits (currently 12) (specified - // in appunits). - FLAG_IS_SIMPLE_GLYPH = 0x80000000U, - - // Indicates whether a linebreak is allowed before this character; - // this is a two-bit field that holds a FLAG_BREAK_TYPE_xxx value - // indicating the kind of linebreak (if any) allowed here. - FLAGS_CAN_BREAK_BEFORE = 0x60000000U, - - FLAGS_CAN_BREAK_SHIFT = 29, - FLAG_BREAK_TYPE_NONE = 0, - FLAG_BREAK_TYPE_NORMAL = 1, - FLAG_BREAK_TYPE_HYPHEN = 2, - - FLAG_CHAR_IS_SPACE = 0x10000000U, - - // The advance is stored in appunits - ADVANCE_MASK = 0x0FFF0000U, - ADVANCE_SHIFT = 16, - - GLYPH_MASK = 0x0000FFFFU, - - // Non-simple glyphs may or may not have glyph data in the - // corresponding mDetailedGlyphs entry. They have the following - // flag bits: - - // When NOT set, indicates that this character corresponds to a - // missing glyph and should be skipped (or possibly, render the character - // Unicode value in some special way). If there are glyphs, - // the mGlyphID is actually the UTF16 character code. The bit is - // inverted so we can memset the array to zero to indicate all missing. - FLAG_NOT_MISSING = 0x01, - FLAG_NOT_CLUSTER_START = 0x02, - FLAG_NOT_LIGATURE_GROUP_START = 0x04, - - FLAG_CHAR_IS_TAB = 0x08, - FLAG_CHAR_IS_NEWLINE = 0x10, - FLAG_CHAR_IS_LOW_SURROGATE = 0x20, - CHAR_IDENTITY_FLAGS_MASK = 0x38, - - GLYPH_COUNT_MASK = 0x00FFFF00U, - GLYPH_COUNT_SHIFT = 8 - }; - - // "Simple glyphs" have a simple glyph ID, simple advance and their - // x and y offsets are zero. Also the glyph extents do not overflow - // the font-box defined by the font ascent, descent and glyph advance width. - // These case is optimized to avoid storing DetailedGlyphs. - - // Returns true if the glyph ID aGlyph fits into the compressed representation - static bool IsSimpleGlyphID(uint32_t aGlyph) { - return (aGlyph & GLYPH_MASK) == aGlyph; - } - // Returns true if the advance aAdvance fits into the compressed representation. - // aAdvance is in appunits. - static bool IsSimpleAdvance(uint32_t aAdvance) { - return (aAdvance & (ADVANCE_MASK >> ADVANCE_SHIFT)) == aAdvance; - } - - bool IsSimpleGlyph() const { return (mValue & FLAG_IS_SIMPLE_GLYPH) != 0; } - uint32_t GetSimpleAdvance() const { return (mValue & ADVANCE_MASK) >> ADVANCE_SHIFT; } - uint32_t GetSimpleGlyph() const { return mValue & GLYPH_MASK; } - - bool IsMissing() const { return (mValue & (FLAG_NOT_MISSING|FLAG_IS_SIMPLE_GLYPH)) == 0; } - bool IsClusterStart() const { - return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_CLUSTER_START); - } - bool IsLigatureGroupStart() const { - return (mValue & FLAG_IS_SIMPLE_GLYPH) || !(mValue & FLAG_NOT_LIGATURE_GROUP_START); - } - bool IsLigatureContinuation() const { - return (mValue & FLAG_IS_SIMPLE_GLYPH) == 0 && - (mValue & (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING)) == - (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING); - } - - // Return true if the original character was a normal (breakable, - // trimmable) space (U+0020). Not true for other characters that - // may happen to map to the space glyph (U+00A0). - bool CharIsSpace() const { - return (mValue & FLAG_CHAR_IS_SPACE) != 0; - } - - bool CharIsTab() const { - return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_TAB) != 0; - } - bool CharIsNewline() const { - return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_NEWLINE) != 0; - } - bool CharIsLowSurrogate() const { - return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_LOW_SURROGATE) != 0; - } - - uint32_t CharIdentityFlags() const { - return IsSimpleGlyph() ? 0 : (mValue & CHAR_IDENTITY_FLAGS_MASK); - } - - void SetClusterStart(bool aIsClusterStart) { - NS_ASSERTION(!IsSimpleGlyph(), - "can't call SetClusterStart on simple glyphs"); - if (aIsClusterStart) { - mValue &= ~FLAG_NOT_CLUSTER_START; - } else { - mValue |= FLAG_NOT_CLUSTER_START; - } - } - - uint8_t CanBreakBefore() const { - return (mValue & FLAGS_CAN_BREAK_BEFORE) >> FLAGS_CAN_BREAK_SHIFT; - } - // Returns FLAGS_CAN_BREAK_BEFORE if the setting changed, 0 otherwise - uint32_t SetCanBreakBefore(uint8_t aCanBreakBefore) { - NS_ASSERTION(aCanBreakBefore <= 2, - "Bogus break-before value!"); - uint32_t breakMask = (uint32_t(aCanBreakBefore) << FLAGS_CAN_BREAK_SHIFT); - uint32_t toggle = breakMask ^ (mValue & FLAGS_CAN_BREAK_BEFORE); - mValue ^= toggle; - return toggle; - } - - CompressedGlyph& SetSimpleGlyph(uint32_t aAdvanceAppUnits, uint32_t aGlyph) { - NS_ASSERTION(IsSimpleAdvance(aAdvanceAppUnits), "Advance overflow"); - NS_ASSERTION(IsSimpleGlyphID(aGlyph), "Glyph overflow"); - NS_ASSERTION(!CharIdentityFlags(), "Char identity flags lost"); - mValue = (mValue & (FLAGS_CAN_BREAK_BEFORE | FLAG_CHAR_IS_SPACE)) | - FLAG_IS_SIMPLE_GLYPH | - (aAdvanceAppUnits << ADVANCE_SHIFT) | aGlyph; - return *this; - } - CompressedGlyph& SetComplex(bool aClusterStart, bool aLigatureStart, - uint32_t aGlyphCount) { - mValue = (mValue & (FLAGS_CAN_BREAK_BEFORE | FLAG_CHAR_IS_SPACE)) | - FLAG_NOT_MISSING | - CharIdentityFlags() | - (aClusterStart ? 0 : FLAG_NOT_CLUSTER_START) | - (aLigatureStart ? 0 : FLAG_NOT_LIGATURE_GROUP_START) | - (aGlyphCount << GLYPH_COUNT_SHIFT); - return *this; - } - /** - * Missing glyphs are treated as ligature group starts; don't mess with - * the cluster-start flag (see bugs 618870 and 619286). - */ - CompressedGlyph& SetMissing(uint32_t aGlyphCount) { - mValue = (mValue & (FLAGS_CAN_BREAK_BEFORE | FLAG_NOT_CLUSTER_START | - FLAG_CHAR_IS_SPACE)) | - CharIdentityFlags() | - (aGlyphCount << GLYPH_COUNT_SHIFT); - return *this; - } - uint32_t GetGlyphCount() const { - NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph"); - return (mValue & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT; - } - - void SetIsSpace() { - mValue |= FLAG_CHAR_IS_SPACE; - } - void SetIsTab() { - NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph"); - mValue |= FLAG_CHAR_IS_TAB; - } - void SetIsNewline() { - NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph"); - mValue |= FLAG_CHAR_IS_NEWLINE; - } - void SetIsLowSurrogate() { - NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph"); - mValue |= FLAG_CHAR_IS_LOW_SURROGATE; - } - - private: - uint32_t mValue; - }; - - // Accessor for the array of CompressedGlyph records, which will be in - // a different place in gfxShapedWord vs gfxTextRun - virtual CompressedGlyph *GetCharacterGlyphs() = 0; - - /** - * When the glyphs for a character don't fit into a CompressedGlyph record - * in SimpleGlyph format, we use an array of DetailedGlyphs instead. - */ - struct DetailedGlyph { - /** The glyphID, or the Unicode character - * if this is a missing glyph */ - uint32_t mGlyphID; - /** The advance, x-offset and y-offset of the glyph, in appunits - * mAdvance is in the text direction (RTL or LTR) - * mXOffset is always from left to right - * mYOffset is always from top to bottom */ - int32_t mAdvance; - float mXOffset, mYOffset; - }; - - void SetGlyphs(uint32_t aCharIndex, CompressedGlyph aGlyph, - const DetailedGlyph *aGlyphs); - - void SetMissingGlyph(uint32_t aIndex, uint32_t aChar, gfxFont *aFont); - - void SetIsSpace(uint32_t aIndex) { - GetCharacterGlyphs()[aIndex].SetIsSpace(); - } - - void SetIsLowSurrogate(uint32_t aIndex) { - SetGlyphs(aIndex, CompressedGlyph().SetComplex(false, false, 0), nullptr); - GetCharacterGlyphs()[aIndex].SetIsLowSurrogate(); - } - - bool HasDetailedGlyphs() const { - return mDetailedGlyphs != nullptr; - } - - bool IsClusterStart(uint32_t aPos) { - NS_ASSERTION(aPos < GetLength(), "aPos out of range"); - return GetCharacterGlyphs()[aPos].IsClusterStart(); - } - - bool IsLigatureGroupStart(uint32_t aPos) { - NS_ASSERTION(aPos < GetLength(), "aPos out of range"); - return GetCharacterGlyphs()[aPos].IsLigatureGroupStart(); - } - - // NOTE that this must not be called for a character offset that does - // not have any DetailedGlyph records; callers must have verified that - // GetCharacterGlyphs()[aCharIndex].GetGlyphCount() is greater than zero. - DetailedGlyph *GetDetailedGlyphs(uint32_t aCharIndex) { - NS_ASSERTION(GetCharacterGlyphs() && HasDetailedGlyphs() && - !GetCharacterGlyphs()[aCharIndex].IsSimpleGlyph() && - GetCharacterGlyphs()[aCharIndex].GetGlyphCount() > 0, - "invalid use of GetDetailedGlyphs; check the caller!"); - return mDetailedGlyphs->Get(aCharIndex); - } - - void AdjustAdvancesForSyntheticBold(float aSynBoldOffset, - uint32_t aOffset, uint32_t aLength); - - // Mark clusters in the CompressedGlyph records, starting at aOffset, - // based on the Unicode properties of the text in aString. - // This is also responsible to set the IsSpace flag for space characters. - void SetupClusterBoundaries(uint32_t aOffset, - const char16_t *aString, - uint32_t aLength); - // In 8-bit text, there won't actually be any clusters, but we still need - // the space-marking functionality. - void SetupClusterBoundaries(uint32_t aOffset, - const uint8_t *aString, - uint32_t aLength); - - uint32_t Flags() const { - return mFlags; - } - - bool IsRightToLeft() const { - return (Flags() & gfxTextRunFactory::TEXT_IS_RTL) != 0; - } - - float GetDirection() const { - return IsRightToLeft() ? -1.0f : 1.0f; - } - - bool DisableLigatures() const { - return (Flags() & gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES) != 0; - } - - bool TextIs8Bit() const { - return (Flags() & gfxTextRunFactory::TEXT_IS_8BIT) != 0; - } - - int32_t GetAppUnitsPerDevUnit() const { - return mAppUnitsPerDevUnit; - } - - uint32_t GetLength() const { - return mLength; - } - - bool FilterIfIgnorable(uint32_t aIndex, uint32_t aCh); - -protected: - // Allocate aCount DetailedGlyphs for the given index - DetailedGlyph *AllocateDetailedGlyphs(uint32_t aCharIndex, - uint32_t aCount); - - // For characters whose glyph data does not fit the "simple" glyph criteria - // in CompressedGlyph, we use a sorted array to store the association - // between the source character offset and an index into an array - // DetailedGlyphs. The CompressedGlyph record includes a count of - // the number of DetailedGlyph records that belong to the character, - // starting at the given index. - class DetailedGlyphStore { - public: - DetailedGlyphStore() - : mLastUsed(0) - { } - - // This is optimized for the most common calling patterns: - // we rarely need random access to the records, access is most commonly - // sequential through the textRun, so we record the last-used index - // and check whether the caller wants the same record again, or the - // next; if not, it's most likely we're starting over from the start - // of the run, so we check the first entry before resorting to binary - // search as a last resort. - // NOTE that this must not be called for a character offset that does - // not have any DetailedGlyph records; callers must have verified that - // mCharacterGlyphs[aOffset].GetGlyphCount() is greater than zero - // before calling this, otherwise the assertions here will fire (in a - // debug build), and we'll probably crash. - DetailedGlyph* Get(uint32_t aOffset) { - NS_ASSERTION(mOffsetToIndex.Length() > 0, - "no detailed glyph records!"); - DetailedGlyph* details = mDetails.Elements(); - // check common cases (fwd iteration, initial entry, etc) first - if (mLastUsed < mOffsetToIndex.Length() - 1 && - aOffset == mOffsetToIndex[mLastUsed + 1].mOffset) { - ++mLastUsed; - } else if (aOffset == mOffsetToIndex[0].mOffset) { - mLastUsed = 0; - } else if (aOffset == mOffsetToIndex[mLastUsed].mOffset) { - // do nothing - } else if (mLastUsed > 0 && - aOffset == mOffsetToIndex[mLastUsed - 1].mOffset) { - --mLastUsed; - } else { - mLastUsed = - mOffsetToIndex.BinaryIndexOf(aOffset, CompareToOffset()); - } - NS_ASSERTION(mLastUsed != nsTArray::NoIndex, - "detailed glyph record missing!"); - return details + mOffsetToIndex[mLastUsed].mIndex; - } - - DetailedGlyph* Allocate(uint32_t aOffset, uint32_t aCount) { - uint32_t detailIndex = mDetails.Length(); - DetailedGlyph *details = mDetails.AppendElements(aCount); - // We normally set up glyph records sequentially, so the common case - // here is to append new records to the mOffsetToIndex array; - // test for that before falling back to the InsertElementSorted - // method. - if (mOffsetToIndex.Length() == 0 || - aOffset > mOffsetToIndex[mOffsetToIndex.Length() - 1].mOffset) { - mOffsetToIndex.AppendElement(DGRec(aOffset, detailIndex)); - } else { - mOffsetToIndex.InsertElementSorted(DGRec(aOffset, detailIndex), - CompareRecordOffsets()); - } - return details; - } - - size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { - return aMallocSizeOf(this) + - mDetails.SizeOfExcludingThis(aMallocSizeOf) + - mOffsetToIndex.SizeOfExcludingThis(aMallocSizeOf); - } - - private: - struct DGRec { - DGRec(const uint32_t& aOffset, const uint32_t& aIndex) - : mOffset(aOffset), mIndex(aIndex) { } - uint32_t mOffset; // source character offset in the textrun - uint32_t mIndex; // index where this char's DetailedGlyphs begin - }; - - struct CompareToOffset { - bool Equals(const DGRec& a, const uint32_t& b) const { - return a.mOffset == b; - } - bool LessThan(const DGRec& a, const uint32_t& b) const { - return a.mOffset < b; - } - }; - - struct CompareRecordOffsets { - bool Equals(const DGRec& a, const DGRec& b) const { - return a.mOffset == b.mOffset; - } - bool LessThan(const DGRec& a, const DGRec& b) const { - return a.mOffset < b.mOffset; - } - }; - - // Concatenated array of all the DetailedGlyph records needed for the - // textRun; individual character offsets are associated with indexes - // into this array via the mOffsetToIndex table. - nsTArray mDetails; - - // For each character offset that needs DetailedGlyphs, we record the - // index in mDetails where the list of glyphs begins. This array is - // sorted by mOffset. - nsTArray mOffsetToIndex; - - // Records the most recently used index into mOffsetToIndex, so that - // we can support sequential access more quickly than just doing - // a binary search each time. - nsTArray::index_type mLastUsed; - }; - - nsAutoPtr mDetailedGlyphs; - - // Number of char16_t characters and CompressedGlyph glyph records - uint32_t mLength; - - // Shaping flags (direction, ligature-suppression) - uint32_t mFlags; - - int32_t mAppUnitsPerDevUnit; +struct TextRunDrawParams { + mozilla::RefPtr dt; + gfxContext *context; + gfxFont::Spacing *spacing; + gfxTextRunDrawCallbacks *callbacks; + gfxTextContextPaint *runContextPaint; + gfxFloat direction; + double devPerApp; + DrawMode drawMode; + bool isRTL; + bool paintSVGGlyphs; }; -/* - * gfxShapedWord: an individual (space-delimited) run of text shaped with a - * particular font, without regard to external context. - * - * The glyph data is copied into gfxTextRuns as needed from the cache of - * ShapedWords associated with each gfxFont instance. - */ -class gfxShapedWord : public gfxShapedText -{ -public: - // Create a ShapedWord that can hold glyphs for aLength characters, - // with mCharacterGlyphs sized appropriately. - // - // Returns null on allocation failure (does NOT use infallible alloc) - // so caller must check for success. - // - // This does NOT perform shaping, so the returned word contains no - // glyph data; the caller must call gfxFont::ShapeText() with appropriate - // parameters to set up the glyphs. - static gfxShapedWord* Create(const uint8_t *aText, uint32_t aLength, - int32_t aRunScript, - int32_t aAppUnitsPerDevUnit, - uint32_t aFlags) { - NS_ASSERTION(aLength <= gfxPlatform::GetPlatform()->WordCacheCharLimit(), - "excessive length for gfxShapedWord!"); - - // Compute size needed including the mCharacterGlyphs array - // and a copy of the original text - uint32_t size = - offsetof(gfxShapedWord, mCharGlyphsStorage) + - aLength * (sizeof(CompressedGlyph) + sizeof(uint8_t)); - void *storage = moz_malloc(size); - if (!storage) { - return nullptr; - } - - // Construct in the pre-allocated storage, using placement new - return new (storage) gfxShapedWord(aText, aLength, aRunScript, - aAppUnitsPerDevUnit, aFlags); - } - - static gfxShapedWord* Create(const char16_t *aText, uint32_t aLength, - int32_t aRunScript, - int32_t aAppUnitsPerDevUnit, - uint32_t aFlags) { - NS_ASSERTION(aLength <= gfxPlatform::GetPlatform()->WordCacheCharLimit(), - "excessive length for gfxShapedWord!"); - - // In the 16-bit version of Create, if the TEXT_IS_8BIT flag is set, - // then we convert the text to an 8-bit version and call the 8-bit - // Create function instead. - if (aFlags & gfxTextRunFactory::TEXT_IS_8BIT) { - nsAutoCString narrowText; - LossyAppendUTF16toASCII(nsDependentSubstring(aText, aLength), - narrowText); - return Create((const uint8_t*)(narrowText.BeginReading()), - aLength, aRunScript, aAppUnitsPerDevUnit, aFlags); - } - - uint32_t size = - offsetof(gfxShapedWord, mCharGlyphsStorage) + - aLength * (sizeof(CompressedGlyph) + sizeof(char16_t)); - void *storage = moz_malloc(size); - if (!storage) { - return nullptr; - } - - return new (storage) gfxShapedWord(aText, aLength, aRunScript, - aAppUnitsPerDevUnit, aFlags); - } - - // Override operator delete to properly free the object that was - // allocated via moz_malloc. - void operator delete(void* p) { - moz_free(p); - } - - CompressedGlyph *GetCharacterGlyphs() { - return &mCharGlyphsStorage[0]; - } - - const uint8_t* Text8Bit() const { - NS_ASSERTION(TextIs8Bit(), "invalid use of Text8Bit()"); - return reinterpret_cast(mCharGlyphsStorage + GetLength()); - } - - const char16_t* TextUnicode() const { - NS_ASSERTION(!TextIs8Bit(), "invalid use of TextUnicode()"); - return reinterpret_cast(mCharGlyphsStorage + GetLength()); - } - - char16_t GetCharAt(uint32_t aOffset) const { - NS_ASSERTION(aOffset < GetLength(), "aOffset out of range"); - return TextIs8Bit() ? - char16_t(Text8Bit()[aOffset]) : TextUnicode()[aOffset]; - } - - int32_t Script() const { - return mScript; - } - - void ResetAge() { - mAgeCounter = 0; - } - uint32_t IncrementAge() { - return ++mAgeCounter; - } - -private: - // so that gfxTextRun can share our DetailedGlyphStore class - friend class gfxTextRun; - - // Construct storage for a ShapedWord, ready to receive glyph data - gfxShapedWord(const uint8_t *aText, uint32_t aLength, - int32_t aRunScript, int32_t aAppUnitsPerDevUnit, - uint32_t aFlags) - : gfxShapedText(aLength, aFlags | gfxTextRunFactory::TEXT_IS_8BIT, - aAppUnitsPerDevUnit) - , mScript(aRunScript) - , mAgeCounter(0) - { - memset(mCharGlyphsStorage, 0, aLength * sizeof(CompressedGlyph)); - uint8_t *text = reinterpret_cast(&mCharGlyphsStorage[aLength]); - memcpy(text, aText, aLength * sizeof(uint8_t)); - } - - gfxShapedWord(const char16_t *aText, uint32_t aLength, - int32_t aRunScript, int32_t aAppUnitsPerDevUnit, - uint32_t aFlags) - : gfxShapedText(aLength, aFlags, aAppUnitsPerDevUnit) - , mScript(aRunScript) - , mAgeCounter(0) - { - memset(mCharGlyphsStorage, 0, aLength * sizeof(CompressedGlyph)); - char16_t *text = reinterpret_cast(&mCharGlyphsStorage[aLength]); - memcpy(text, aText, aLength * sizeof(char16_t)); - SetupClusterBoundaries(0, aText, aLength); - } - - int32_t mScript; - - uint32_t mAgeCounter; - - // The mCharGlyphsStorage array is actually a variable-size member; - // when the ShapedWord is created, its size will be increased as necessary - // to allow the proper number of glyphs to be stored. - // The original text, in either 8-bit or 16-bit form, will be stored - // immediately following the CompressedGlyphs. - CompressedGlyph mCharGlyphsStorage[1]; +struct FontDrawParams { + mozilla::RefPtr scaledFont; + mozilla::RefPtr renderingOptions; + gfxTextContextPaint *contextPaint; + mozilla::gfx::Matrix *passedInvMatrix; + mozilla::gfx::Matrix matInv; + double synBoldOnePixelOffset; + int32_t extraStrikes; + mozilla::gfx::DrawOptions drawOptions; + bool haveSVGGlyphs; + bool haveColorGlyphs; }; -/** - * Callback for Draw() to use when drawing text with mode - * DrawMode::GLYPH_PATH. - */ -struct gfxTextRunDrawCallbacks { - - /** - * Constructs a new DrawCallbacks object. - * - * @param aShouldPaintSVGGlyphs If true, SVG glyphs will be - * painted and the NotifyBeforeSVGGlyphPainted/NotifyAfterSVGGlyphPainted - * callbacks will be invoked for each SVG glyph. If false, SVG glyphs - * will not be painted; fallback plain glyphs are not emitted either. - */ - explicit gfxTextRunDrawCallbacks(bool aShouldPaintSVGGlyphs = false) - : mShouldPaintSVGGlyphs(aShouldPaintSVGGlyphs) - { - } - - /** - * Called when a path has been emitted to the gfxContext when - * painting a text run. This can be called any number of times, - * due to partial ligatures and intervening SVG glyphs. - */ - virtual void NotifyGlyphPathEmitted() = 0; - - /** - * Called just before an SVG glyph has been painted to the gfxContext. - */ - virtual void NotifyBeforeSVGGlyphPainted() { } - - /** - * Called just after an SVG glyph has been painted to the gfxContext. - */ - virtual void NotifyAfterSVGGlyphPainted() { } - - bool mShouldPaintSVGGlyphs; -}; - -/** - * gfxTextRun is an abstraction for drawing and measuring substrings of a run - * of text. It stores runs of positioned glyph data, each run having a single - * gfxFont. The glyphs are associated with a string of source text, and the - * gfxTextRun APIs take parameters that are offsets into that source text. - * - * gfxTextRuns are not refcounted. They should be deleted when no longer required. - * - * gfxTextRuns are mostly immutable. The only things that can change are - * inter-cluster spacing and line break placement. Spacing is always obtained - * lazily by methods that need it, it is not cached. Line breaks are stored - * persistently (insofar as they affect the shaping of glyphs; gfxTextRun does - * not actually do anything to explicitly account for line breaks). Initially - * there are no line breaks. The textrun can record line breaks before or after - * any given cluster. (Line breaks specified inside clusters are ignored.) - * - * It is important that zero-length substrings are handled correctly. This will - * be on the test! - */ -class gfxTextRun : public gfxShapedText { -public: - - // Override operator delete to properly free the object that was - // allocated via moz_malloc. - void operator delete(void* p) { - moz_free(p); - } - - virtual ~gfxTextRun(); - - typedef gfxFont::RunMetrics Metrics; - - // Public textrun API for general use - - bool IsClusterStart(uint32_t aPos) { - NS_ASSERTION(aPos < GetLength(), "aPos out of range"); - return mCharacterGlyphs[aPos].IsClusterStart(); - } - bool IsLigatureGroupStart(uint32_t aPos) { - NS_ASSERTION(aPos < GetLength(), "aPos out of range"); - return mCharacterGlyphs[aPos].IsLigatureGroupStart(); - } - bool CanBreakLineBefore(uint32_t aPos) { - NS_ASSERTION(aPos < GetLength(), "aPos out of range"); - return mCharacterGlyphs[aPos].CanBreakBefore() == - CompressedGlyph::FLAG_BREAK_TYPE_NORMAL; - } - bool CanHyphenateBefore(uint32_t aPos) { - NS_ASSERTION(aPos < GetLength(), "aPos out of range"); - return mCharacterGlyphs[aPos].CanBreakBefore() == - CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN; - } - - bool CharIsSpace(uint32_t aPos) { - NS_ASSERTION(aPos < GetLength(), "aPos out of range"); - return mCharacterGlyphs[aPos].CharIsSpace(); - } - bool CharIsTab(uint32_t aPos) { - NS_ASSERTION(aPos < GetLength(), "aPos out of range"); - return mCharacterGlyphs[aPos].CharIsTab(); - } - bool CharIsNewline(uint32_t aPos) { - NS_ASSERTION(aPos < GetLength(), "aPos out of range"); - return mCharacterGlyphs[aPos].CharIsNewline(); - } - bool CharIsLowSurrogate(uint32_t aPos) { - NS_ASSERTION(aPos < GetLength(), "aPos out of range"); - return mCharacterGlyphs[aPos].CharIsLowSurrogate(); - } - - uint32_t GetLength() { return mLength; } - - // All uint32_t aStart, uint32_t aLength ranges below are restricted to - // grapheme cluster boundaries! All offsets are in terms of the string - // passed into MakeTextRun. - - // All coordinates are in layout/app units - - /** - * Set the potential linebreaks for a substring of the textrun. These are - * the "allow break before" points. Initially, there are no potential - * linebreaks. - * - * This can change glyphs and/or geometry! Some textruns' shapes - * depend on potential line breaks (e.g., title-case-converting textruns). - * This function is virtual so that those textruns can reshape themselves. - * - * @return true if this changed the linebreaks, false if the new line - * breaks are the same as the old - */ - virtual bool SetPotentialLineBreaks(uint32_t aStart, uint32_t aLength, - uint8_t *aBreakBefore, - gfxContext *aRefContext); - - /** - * Layout provides PropertyProvider objects. These allow detection of - * potential line break points and computation of spacing. We pass the data - * this way to allow lazy data acquisition; for example BreakAndMeasureText - * will want to only ask for properties of text it's actually looking at. - * - * NOTE that requested spacing may not actually be applied, if the textrun - * is unable to apply it in some context. Exception: spacing around a - * whitespace character MUST always be applied. - */ - class PropertyProvider { - public: - // Detect hyphenation break opportunities in the given range; breaks - // not at cluster boundaries will be ignored. - virtual void GetHyphenationBreaks(uint32_t aStart, uint32_t aLength, - bool *aBreakBefore) = 0; - - // Returns the provider's hyphenation setting, so callers can decide - // whether it is necessary to call GetHyphenationBreaks. - // Result is an NS_STYLE_HYPHENS_* value. - virtual int8_t GetHyphensOption() = 0; - - // Returns the extra width that will be consumed by a hyphen. This should - // be constant for a given textrun. - virtual gfxFloat GetHyphenWidth() = 0; - - typedef gfxFont::Spacing Spacing; - - /** - * Get the spacing around the indicated characters. Spacing must be zero - * inside clusters. In other words, if character i is not - * CLUSTER_START, then character i-1 must have zero after-spacing and - * character i must have zero before-spacing. - */ - virtual void GetSpacing(uint32_t aStart, uint32_t aLength, - Spacing *aSpacing) = 0; - - // Returns a gfxContext that can be used to measure the hyphen glyph. - // Only called if the hyphen width is requested. - virtual already_AddRefed GetContext() = 0; - - // Return the appUnitsPerDevUnit value to be used when measuring. - // Only called if the hyphen width is requested. - virtual uint32_t GetAppUnitsPerDevUnit() = 0; - }; - - class ClusterIterator { - public: - explicit ClusterIterator(gfxTextRun *aTextRun); - - void Reset(); - - bool NextCluster(); - - uint32_t Position() const { - return mCurrentChar; - } - - uint32_t ClusterLength() const; - - gfxFloat ClusterAdvance(PropertyProvider *aProvider) const; - - private: - gfxTextRun *mTextRun; - uint32_t mCurrentChar; - }; - - /** - * Draws a substring. Uses only GetSpacing from aBreakProvider. - * The provided point is the baseline origin on the left of the string - * for LTR, on the right of the string for RTL. - * @param aAdvanceWidth if non-null, the advance width of the substring - * is returned here. - * - * Drawing should respect advance widths in the sense that for LTR runs, - * Draw(ctx, pt, offset1, length1, dirty, &provider, &advance) followed by - * Draw(ctx, gfxPoint(pt.x + advance, pt.y), offset1 + length1, length2, - * dirty, &provider, nullptr) should have the same effect as - * Draw(ctx, pt, offset1, length1+length2, dirty, &provider, nullptr). - * For RTL runs the rule is: - * Draw(ctx, pt, offset1 + length1, length2, dirty, &provider, &advance) followed by - * Draw(ctx, gfxPoint(pt.x + advance, pt.y), offset1, length1, - * dirty, &provider, nullptr) should have the same effect as - * Draw(ctx, pt, offset1, length1+length2, dirty, &provider, nullptr). - * - * Glyphs should be drawn in logical content order, which can be significant - * if they overlap (perhaps due to negative spacing). - */ - void Draw(gfxContext *aContext, gfxPoint aPt, - DrawMode aDrawMode, - uint32_t aStart, uint32_t aLength, - PropertyProvider *aProvider, - gfxFloat *aAdvanceWidth, gfxTextContextPaint *aContextPaint, - gfxTextRunDrawCallbacks *aCallbacks = nullptr); - - /** - * Computes the ReflowMetrics for a substring. - * Uses GetSpacing from aBreakProvider. - * @param aBoundingBoxType which kind of bounding box (loose/tight) - */ - Metrics MeasureText(uint32_t aStart, uint32_t aLength, - gfxFont::BoundingBoxType aBoundingBoxType, - gfxContext *aRefContextForTightBoundingBox, - PropertyProvider *aProvider); - - /** - * Computes just the advance width for a substring. - * Uses GetSpacing from aBreakProvider. - */ - gfxFloat GetAdvanceWidth(uint32_t aStart, uint32_t aLength, - PropertyProvider *aProvider); - - /** - * Clear all stored line breaks for the given range (both before and after), - * and then set the line-break state before aStart to aBreakBefore and - * after the last cluster to aBreakAfter. - * - * We require that before and after line breaks be consistent. For clusters - * i and i+1, we require that if there is a break after cluster i, a break - * will be specified before cluster i+1. This may be temporarily violated - * (e.g. after reflowing line L and before reflowing line L+1); to handle - * these temporary violations, we say that there is a break betwen i and i+1 - * if a break is specified after i OR a break is specified before i+1. - * - * This can change textrun geometry! The existence of a linebreak can affect - * the advance width of the cluster before the break (when kerning) or the - * geometry of one cluster before the break or any number of clusters - * after the break. (The one-cluster-before-the-break limit is somewhat - * arbitrary; if some scripts require breaking it, then we need to - * alter nsTextFrame::TrimTrailingWhitespace, perhaps drastically becase - * it could affect the layout of frames before it...) - * - * We return true if glyphs or geometry changed, false otherwise. This - * function is virtual so that gfxTextRun subclasses can reshape - * properly. - * - * @param aAdvanceWidthDelta if non-null, returns the change in advance - * width of the given range. - */ - virtual bool SetLineBreaks(uint32_t aStart, uint32_t aLength, - bool aLineBreakBefore, bool aLineBreakAfter, - gfxFloat *aAdvanceWidthDelta, - gfxContext *aRefContext); - - /** - * Finds the longest substring that will fit into the given width. - * Uses GetHyphenationBreaks and GetSpacing from aBreakProvider. - * Guarantees the following: - * -- 0 <= result <= aMaxLength - * -- result is the maximal value of N such that either - * N < aMaxLength && line break at N && GetAdvanceWidth(aStart, N) <= aWidth - * OR N < aMaxLength && hyphen break at N && GetAdvanceWidth(aStart, N) + GetHyphenWidth() <= aWidth - * OR N == aMaxLength && GetAdvanceWidth(aStart, N) <= aWidth - * where GetAdvanceWidth assumes the effect of - * SetLineBreaks(aStart, N, aLineBreakBefore, N < aMaxLength, aProvider) - * -- if no such N exists, then result is the smallest N such that - * N < aMaxLength && line break at N - * OR N < aMaxLength && hyphen break at N - * OR N == aMaxLength - * - * The call has the effect of - * SetLineBreaks(aStart, result, aLineBreakBefore, result < aMaxLength, aProvider) - * and the returned metrics and the invariants above reflect this. - * - * @param aMaxLength this can be UINT32_MAX, in which case the length used - * is up to the end of the string - * @param aLineBreakBefore set to true if and only if there is an actual - * line break at the start of this string. - * @param aSuppressInitialBreak if true, then we assume there is no possible - * linebreak before aStart. If false, then we will check the internal - * line break opportunity state before deciding whether to return 0 as the - * character to break before. - * @param aTrimWhitespace if non-null, then we allow a trailing run of - * spaces to be trimmed; the width of the space(s) will not be included in - * the measured string width for comparison with the limit aWidth, and - * trimmed spaces will not be included in returned metrics. The width - * of the trimmed spaces will be returned in aTrimWhitespace. - * Trimmed spaces are still counted in the "characters fit" result. - * @param aMetrics if non-null, we fill this in for the returned substring. - * If a hyphenation break was used, the hyphen is NOT included in the returned metrics. - * @param aBoundingBoxType whether to make the bounding box in aMetrics tight - * @param aRefContextForTightBoundingBox a reference context to get the - * tight bounding box, if requested - * @param aUsedHyphenation if non-null, records if we selected a hyphenation break - * @param aLastBreak if non-null and result is aMaxLength, we set this to - * the maximal N such that - * N < aMaxLength && line break at N && GetAdvanceWidth(aStart, N) <= aWidth - * OR N < aMaxLength && hyphen break at N && GetAdvanceWidth(aStart, N) + GetHyphenWidth() <= aWidth - * or UINT32_MAX if no such N exists, where GetAdvanceWidth assumes - * the effect of - * SetLineBreaks(aStart, N, aLineBreakBefore, N < aMaxLength, aProvider) - * - * @param aCanWordWrap true if we can break between any two grapheme - * clusters. This is set by word-wrap: break-word - * - * @param aBreakPriority in/out the priority of the break opportunity - * saved in the line. If we are prioritizing break opportunities, we will - * not set a break with a lower priority. @see gfxBreakPriority. - * - * Note that negative advance widths are possible especially if negative - * spacing is provided. - */ - uint32_t BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength, - bool aLineBreakBefore, gfxFloat aWidth, - PropertyProvider *aProvider, - bool aSuppressInitialBreak, - gfxFloat *aTrimWhitespace, - Metrics *aMetrics, - gfxFont::BoundingBoxType aBoundingBoxType, - gfxContext *aRefContextForTightBoundingBox, - bool *aUsedHyphenation, - uint32_t *aLastBreak, - bool aCanWordWrap, - gfxBreakPriority *aBreakPriority); - - /** - * Update the reference context. - * XXX this is a hack. New text frame does not call this. Use only - * temporarily for old text frame. - */ - void SetContext(gfxContext *aContext) {} - - // Utility getters - - gfxFloat GetDirection() const { return (mFlags & gfxTextRunFactory::TEXT_IS_RTL) ? -1.0 : 1.0; } - void *GetUserData() const { return mUserData; } - void SetUserData(void *aUserData) { mUserData = aUserData; } - uint32_t GetFlags() const { return mFlags; } - void SetFlagBits(uint32_t aFlags) { - NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::SETTABLE_FLAGS), - "Only user flags should be mutable"); - mFlags |= aFlags; - } - void ClearFlagBits(uint32_t aFlags) { - NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::SETTABLE_FLAGS), - "Only user flags should be mutable"); - mFlags &= ~aFlags; - } - const gfxSkipChars& GetSkipChars() const { return mSkipChars; } - gfxFontGroup *GetFontGroup() const { return mFontGroup; } - - - // Call this, don't call "new gfxTextRun" directly. This does custom - // allocation and initialization - static gfxTextRun *Create(const gfxTextRunFactory::Parameters *aParams, - uint32_t aLength, gfxFontGroup *aFontGroup, - uint32_t aFlags); - - // The text is divided into GlyphRuns as necessary - struct GlyphRun { - nsRefPtr mFont; // never null - uint32_t mCharacterOffset; // into original UTF16 string - uint8_t mMatchType; - }; - - class GlyphRunIterator { - public: - GlyphRunIterator(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aLength) - : mTextRun(aTextRun), mStartOffset(aStart), mEndOffset(aStart + aLength) { - mNextIndex = mTextRun->FindFirstGlyphRunContaining(aStart); - } - bool NextRun(); - GlyphRun *GetGlyphRun() { return mGlyphRun; } - uint32_t GetStringStart() { return mStringStart; } - uint32_t GetStringEnd() { return mStringEnd; } - private: - gfxTextRun *mTextRun; - GlyphRun *mGlyphRun; - uint32_t mStringStart; - uint32_t mStringEnd; - uint32_t mNextIndex; - uint32_t mStartOffset; - uint32_t mEndOffset; - }; - - class GlyphRunOffsetComparator { - public: - bool Equals(const GlyphRun& a, - const GlyphRun& b) const - { - return a.mCharacterOffset == b.mCharacterOffset; - } - - bool LessThan(const GlyphRun& a, - const GlyphRun& b) const - { - return a.mCharacterOffset < b.mCharacterOffset; - } - }; - - friend class GlyphRunIterator; - friend class FontSelector; - - // API for setting up the textrun glyphs. Should only be called by - // things that construct textruns. - /** - * We've found a run of text that should use a particular font. Call this - * only during initialization when font substitution has been computed. - * Call it before setting up the glyphs for the characters in this run; - * SetMissingGlyph requires that the correct glyphrun be installed. - * - * If aForceNewRun, a new glyph run will be added, even if the - * previously added run uses the same font. If glyph runs are - * added out of strictly increasing aStartCharIndex order (via - * force), then SortGlyphRuns must be called after all glyph runs - * are added before any further operations are performed with this - * TextRun. - */ - nsresult AddGlyphRun(gfxFont *aFont, uint8_t aMatchType, - uint32_t aStartCharIndex, bool aForceNewRun); - void ResetGlyphRuns() { mGlyphRuns.Clear(); } - void SortGlyphRuns(); - void SanitizeGlyphRuns(); - - CompressedGlyph* GetCharacterGlyphs() { - NS_ASSERTION(mCharacterGlyphs, "failed to initialize mCharacterGlyphs"); - return mCharacterGlyphs; - } - - // clean out results from shaping in progress, used for fallback scenarios - void ClearGlyphsAndCharacters(); - - void SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, uint32_t aCharIndex); - - // Set the glyph data for the given character index to the font's - // space glyph, IF this can be done as a "simple" glyph record - // (not requiring a DetailedGlyph entry). This avoids the need to call - // the font shaper and go through the shaped-word cache for most spaces. - // - // The parameter aSpaceChar is the original character code for which - // this space glyph is being used; if this is U+0020, we need to record - // that it could be trimmed at a run edge, whereas other kinds of space - // (currently just U+00A0) would not be trimmable/breakable. - // - // Returns true if it was able to set simple glyph data for the space; - // if it returns false, the caller needs to fall back to some other - // means to create the necessary (detailed) glyph data. - bool SetSpaceGlyphIfSimple(gfxFont *aFont, gfxContext *aContext, - uint32_t aCharIndex, char16_t aSpaceChar); - - // Record the positions of specific characters that layout may need to - // detect in the textrun, even though it doesn't have an explicit copy - // of the original text. These are recorded using flag bits in the - // CompressedGlyph record; if necessary, we convert "simple" glyph records - // to "complex" ones as the Tab and Newline flags are not present in - // simple CompressedGlyph records. - void SetIsTab(uint32_t aIndex) { - CompressedGlyph *g = &mCharacterGlyphs[aIndex]; - if (g->IsSimpleGlyph()) { - DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); - details->mGlyphID = g->GetSimpleGlyph(); - details->mAdvance = g->GetSimpleAdvance(); - details->mXOffset = details->mYOffset = 0; - SetGlyphs(aIndex, CompressedGlyph().SetComplex(true, true, 1), details); - } - g->SetIsTab(); - } - void SetIsNewline(uint32_t aIndex) { - CompressedGlyph *g = &mCharacterGlyphs[aIndex]; - if (g->IsSimpleGlyph()) { - DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); - details->mGlyphID = g->GetSimpleGlyph(); - details->mAdvance = g->GetSimpleAdvance(); - details->mXOffset = details->mYOffset = 0; - SetGlyphs(aIndex, CompressedGlyph().SetComplex(true, true, 1), details); - } - g->SetIsNewline(); - } - void SetIsLowSurrogate(uint32_t aIndex) { - SetGlyphs(aIndex, CompressedGlyph().SetComplex(false, false, 0), nullptr); - mCharacterGlyphs[aIndex].SetIsLowSurrogate(); - } - - /** - * Prefetch all the glyph extents needed to ensure that Measure calls - * on this textrun not requesting tight boundingBoxes will succeed. Note - * that some glyph extents might not be fetched due to OOM or other - * errors. - */ - void FetchGlyphExtents(gfxContext *aRefContext); - - uint32_t CountMissingGlyphs(); - const GlyphRun *GetGlyphRuns(uint32_t *aNumGlyphRuns) { - *aNumGlyphRuns = mGlyphRuns.Length(); - return mGlyphRuns.Elements(); - } - // Returns the index of the GlyphRun containing the given offset. - // Returns mGlyphRuns.Length() when aOffset is mCharacterCount. - uint32_t FindFirstGlyphRunContaining(uint32_t aOffset); - - // Copy glyph data from a ShapedWord into this textrun. - void CopyGlyphDataFrom(gfxShapedWord *aSource, uint32_t aStart); - - // Copy glyph data for a range of characters from aSource to this - // textrun. - void CopyGlyphDataFrom(gfxTextRun *aSource, uint32_t aStart, - uint32_t aLength, uint32_t aDest); - - nsExpirationState *GetExpirationState() { return &mExpirationState; } - - // Tell the textrun to release its reference to its creating gfxFontGroup - // immediately, rather than on destruction. This is used for textruns - // that are actually owned by a gfxFontGroup, so that they don't keep it - // permanently alive due to a circular reference. (The caller of this is - // taking responsibility for ensuring the textrun will not outlive its - // mFontGroup.) - void ReleaseFontGroup(); - - struct LigatureData { - // textrun offsets of the start and end of the containing ligature - uint32_t mLigatureStart; - uint32_t mLigatureEnd; - // appunits advance to the start of the ligature part within the ligature; - // never includes any spacing - gfxFloat mPartAdvance; - // appunits width of the ligature part; includes before-spacing - // when the part is at the start of the ligature, and after-spacing - // when the part is as the end of the ligature - gfxFloat mPartWidth; - - bool mClipBeforePart; - bool mClipAfterPart; - }; - - // return storage used by this run, for memory reporter; - // nsTransformedTextRun needs to override this as it holds additional data - virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) - MOZ_MUST_OVERRIDE; - virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) - MOZ_MUST_OVERRIDE; - - // Get the size, if it hasn't already been gotten, marking as it goes. - size_t MaybeSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { - if (mFlags & gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED) { - return 0; - } - mFlags |= gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED; - return SizeOfIncludingThis(aMallocSizeOf); - } - void ResetSizeOfAccountingFlags() { - mFlags &= ~gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED; - } - - // shaping state - for some font features, fallback is required that - // affects the entire run. for example, fallback for one script/font - // portion of a textrun requires fallback to be applied to the entire run - - enum ShapingState { - eShapingState_Normal, // default state - eShapingState_ShapingWithFeature, // have shaped with feature - eShapingState_ShapingWithFallback, // have shaped with fallback - eShapingState_Aborted, // abort initial iteration - eShapingState_ForceFallbackFeature // redo with fallback forced on - }; - - ShapingState GetShapingState() const { return mShapingState; } - void SetShapingState(ShapingState aShapingState) { - mShapingState = aShapingState; - } - -#ifdef DEBUG - void Dump(FILE* aOutput); -#endif - -protected: - /** - * Create a textrun, and set its mCharacterGlyphs to point immediately - * after the base object; this is ONLY used in conjunction with placement - * new, after allocating a block large enough for the glyph records to - * follow the base textrun object. - */ - gfxTextRun(const gfxTextRunFactory::Parameters *aParams, - uint32_t aLength, gfxFontGroup *aFontGroup, uint32_t aFlags); - - /** - * Helper for the Create() factory method to allocate the required - * glyph storage for a textrun object with the basic size aSize, - * plus room for aLength glyph records. - */ - static void* AllocateStorageForTextRun(size_t aSize, uint32_t aLength); - - // Pointer to the array of CompressedGlyph records; must be initialized - // when the object is constructed. - CompressedGlyph *mCharacterGlyphs; - -private: - // **** general helpers **** - - // Get the total advance for a range of glyphs. - int32_t GetAdvanceForGlyphs(uint32_t aStart, uint32_t aEnd); - - // Spacing for characters outside the range aSpacingStart/aSpacingEnd - // is assumed to be zero; such characters are not passed to aProvider. - // This is useful to protect aProvider from being passed character indices - // it is not currently able to handle. - bool GetAdjustedSpacingArray(uint32_t aStart, uint32_t aEnd, - PropertyProvider *aProvider, - uint32_t aSpacingStart, uint32_t aSpacingEnd, - nsTArray *aSpacing); - - // **** ligature helpers **** - // (Platforms do the actual ligaturization, but we need to do a bunch of stuff - // to handle requests that begin or end inside a ligature) - - // if aProvider is null then mBeforeSpacing and mAfterSpacing are set to zero - LigatureData ComputeLigatureData(uint32_t aPartStart, uint32_t aPartEnd, - PropertyProvider *aProvider); - gfxFloat ComputePartialLigatureWidth(uint32_t aPartStart, uint32_t aPartEnd, - PropertyProvider *aProvider); - void DrawPartialLigature(gfxFont *aFont, uint32_t aStart, uint32_t aEnd, - gfxPoint *aPt, PropertyProvider *aProvider, - TextRunDrawParams& aParams); - // Advance aStart to the start of the nearest ligature; back up aEnd - // to the nearest ligature end; may result in *aStart == *aEnd - void ShrinkToLigatureBoundaries(uint32_t *aStart, uint32_t *aEnd); - // result in appunits - gfxFloat GetPartialLigatureWidth(uint32_t aStart, uint32_t aEnd, PropertyProvider *aProvider); - void AccumulatePartialLigatureMetrics(gfxFont *aFont, - uint32_t aStart, uint32_t aEnd, - gfxFont::BoundingBoxType aBoundingBoxType, - gfxContext *aRefContext, - PropertyProvider *aProvider, - Metrics *aMetrics); - - // **** measurement helper **** - void AccumulateMetricsForRun(gfxFont *aFont, uint32_t aStart, uint32_t aEnd, - gfxFont::BoundingBoxType aBoundingBoxType, - gfxContext *aRefContext, - PropertyProvider *aProvider, - uint32_t aSpacingStart, uint32_t aSpacingEnd, - Metrics *aMetrics); - - // **** drawing helper **** - void DrawGlyphs(gfxFont *aFont, uint32_t aStart, uint32_t aEnd, - gfxPoint *aPt, PropertyProvider *aProvider, - uint32_t aSpacingStart, uint32_t aSpacingEnd, - TextRunDrawParams& aParams); - - // XXX this should be changed to a GlyphRun plus a maybe-null GlyphRun*, - // for smaller size especially in the super-common one-glyphrun case - nsAutoTArray mGlyphRuns; - - void *mUserData; - gfxFontGroup *mFontGroup; // addrefed on creation, but our reference - // may be released by ReleaseFontGroup() - gfxSkipChars mSkipChars; - nsExpirationState mExpirationState; - - bool mSkipDrawing; // true if the font group we used had a user font - // download that's in progress, so we should hide text - // until the download completes (or timeout fires) - bool mReleasedFontGroup; // we already called NS_RELEASE on - // mFontGroup, so don't do it again - - // shaping state for handling variant fallback features - // such as subscript/superscript variant glyphs - ShapingState mShapingState; -}; - -class gfxFontGroup : public gfxTextRunFactory { -public: - class FamilyFace { - public: - FamilyFace() { } - - FamilyFace(gfxFontFamily* aFamily, gfxFont* aFont) - : mFamily(aFamily), mFont(aFont) - { - NS_ASSERTION(aFont, "font pointer must not be null"); - NS_ASSERTION(!aFamily || - aFamily->ContainsFace(aFont->GetFontEntry()), - "font is not a member of the given family"); - } - - gfxFontFamily* Family() const { return mFamily.get(); } - gfxFont* Font() const { return mFont.get(); } - - private: - nsRefPtr mFamily; - nsRefPtr mFont; - }; - - static void Shutdown(); // platform must call this to release the languageAtomService - - gfxFontGroup(const mozilla::FontFamilyList& aFontFamilyList, - const gfxFontStyle *aStyle, - gfxUserFontSet *aUserFontSet = nullptr); - - virtual ~gfxFontGroup(); - - virtual gfxFont *GetFontAt(int32_t i) { - // If it turns out to be hard for all clients that cache font - // groups to call UpdateFontList at appropriate times, we could - // instead consider just calling UpdateFontList from someplace - // more central (such as here). - NS_ASSERTION(!mUserFontSet || mCurrGeneration == GetGeneration(), - "Whoever was caching this font group should have " - "called UpdateFontList on it"); - NS_ASSERTION(mFonts.Length() > uint32_t(i) && mFonts[i].Font(), - "Requesting a font index that doesn't exist"); - - return mFonts[i].Font(); - } - - // Returns the first font in the font-group that has an OpenType MATH table, - // or null if no such font is available. The GetMathConstant methods may be - // called on the returned font. - gfxFont *GetFirstMathFont(); - - uint32_t FontListLength() const { - return mFonts.Length(); - } - - const gfxFontStyle *GetStyle() const { return &mStyle; } - - virtual gfxFontGroup *Copy(const gfxFontStyle *aStyle); - - /** - * The listed characters should be treated as invisible and zero-width - * when creating textruns. - */ - static bool IsInvalidChar(uint8_t ch); - static bool IsInvalidChar(char16_t ch); - - /** - * Make a textrun for a given string. - * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the - * textrun will copy it. - * This calls FetchGlyphExtents on the textrun. - */ - virtual gfxTextRun *MakeTextRun(const char16_t *aString, uint32_t aLength, - const Parameters *aParams, uint32_t aFlags); - /** - * Make a textrun for a given string. - * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the - * textrun will copy it. - * This calls FetchGlyphExtents on the textrun. - */ - virtual gfxTextRun *MakeTextRun(const uint8_t *aString, uint32_t aLength, - const Parameters *aParams, uint32_t aFlags); - - /** - * Textrun creation helper for clients that don't want to pass - * a full Parameters record. - */ - template - gfxTextRun *MakeTextRun(const T *aString, uint32_t aLength, - gfxContext *aRefContext, - int32_t aAppUnitsPerDevUnit, - uint32_t aFlags) - { - gfxTextRunFactory::Parameters params = { - aRefContext, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevUnit - }; - return MakeTextRun(aString, aLength, ¶ms, aFlags); - } - - /** - * Get the (possibly-cached) width of the hyphen character. - * The aCtx and aAppUnitsPerDevUnit parameters will be used only if - * needed to initialize the cached hyphen width; otherwise they are - * ignored. - */ - gfxFloat GetHyphenWidth(gfxTextRun::PropertyProvider* aProvider); - - /** - * Make a text run representing a single hyphen character. - * This will use U+2010 HYPHEN if available in the first font, - * otherwise fall back to U+002D HYPHEN-MINUS. - * The caller is responsible for deleting the returned text run - * when no longer required. - */ - gfxTextRun *MakeHyphenTextRun(gfxContext *aCtx, - uint32_t aAppUnitsPerDevUnit); - - /** - * Check whether a given font (specified by its gfxFontEntry) - * is already in the fontgroup's list of actual fonts - */ - bool HasFont(const gfxFontEntry *aFontEntry); - - // This returns the preferred underline for this font group. - // Some CJK fonts have wrong underline offset in its metrics. - // If this group has such "bad" font, each platform's gfxFontGroup initialized mUnderlineOffset. - // The value should be lower value of first font's metrics and the bad font's metrics. - // Otherwise, this returns from first font's metrics. - enum { UNDERLINE_OFFSET_NOT_SET = INT16_MAX }; - virtual gfxFloat GetUnderlineOffset() { - if (mUnderlineOffset == UNDERLINE_OFFSET_NOT_SET) - mUnderlineOffset = GetFontAt(0)->GetMetrics().underlineOffset; - return mUnderlineOffset; - } - - virtual already_AddRefed - FindFontForChar(uint32_t ch, uint32_t prevCh, int32_t aRunScript, - gfxFont *aPrevMatchedFont, - uint8_t *aMatchType); - - // search through pref fonts for a character, return nullptr if no matching pref font - virtual already_AddRefed WhichPrefFontSupportsChar(uint32_t aCh); - - virtual already_AddRefed - WhichSystemFontSupportsChar(uint32_t aCh, int32_t aRunScript); - - template - void ComputeRanges(nsTArray& mRanges, - const T *aString, uint32_t aLength, - int32_t aRunScript); - - gfxUserFontSet* GetUserFontSet(); - - // With downloadable fonts, the composition of the font group can change as fonts are downloaded - // for each change in state of the user font set, the generation value is bumped to avoid picking up - // previously created text runs in the text run word cache. For font groups based on stylesheets - // with no @font-face rule, this always returns 0. - uint64_t GetGeneration(); - - // used when logging text performance - gfxTextPerfMetrics *GetTextPerfMetrics() { return mTextPerf; } - void SetTextPerfMetrics(gfxTextPerfMetrics *aTextPerf) { mTextPerf = aTextPerf; } - - // This will call UpdateFontList() if the user font set is changed. - void SetUserFontSet(gfxUserFontSet *aUserFontSet); - - // If there is a user font set, check to see whether the font list or any - // caches need updating. - virtual void UpdateFontList(); - - bool ShouldSkipDrawing() const { - return mSkipDrawing; - } - - class LazyReferenceContextGetter { - public: - virtual already_AddRefed GetRefContext() = 0; - }; - // The gfxFontGroup keeps ownership of this textrun. - // It is only guaranteed to exist until the next call to GetEllipsisTextRun - // (which might use a different appUnitsPerDev value) for the font group, - // or until UpdateFontList is called, or the fontgroup is destroyed. - // Get it/use it/forget it :) - don't keep a reference that might go stale. - gfxTextRun* GetEllipsisTextRun(int32_t aAppUnitsPerDevPixel, - LazyReferenceContextGetter& aRefContextGetter); - - // helper method for resolving generic font families - static void - ResolveGenericFontNames(mozilla::FontFamilyType aGenericType, - nsIAtom *aLanguage, - nsTArray& aGenericFamilies); - -protected: - mozilla::FontFamilyList mFamilyList; - gfxFontStyle mStyle; - nsTArray mFonts; - gfxFloat mUnderlineOffset; - gfxFloat mHyphenWidth; - - nsRefPtr mUserFontSet; - uint64_t mCurrGeneration; // track the current user font set generation, rebuild font list if needed - - gfxTextPerfMetrics *mTextPerf; - - // Cache a textrun representing an ellipsis (useful for CSS text-overflow) - // at a specific appUnitsPerDevPixel size - nsAutoPtr mCachedEllipsisTextRun; - - // cache the most recent pref font to avoid general pref font lookup - nsRefPtr mLastPrefFamily; - nsRefPtr mLastPrefFont; - eFontPrefLang mLastPrefLang; // lang group for last pref font - eFontPrefLang mPageLang; - bool mLastPrefFirstFont; // is this the first font in the list of pref fonts for this lang group? - - bool mSkipDrawing; // hide text while waiting for a font - // download to complete (or fallback - // timer to fire) - - /** - * Textrun creation short-cuts for special cases where we don't need to - * call a font shaper to generate glyphs. - */ - gfxTextRun *MakeEmptyTextRun(const Parameters *aParams, uint32_t aFlags); - gfxTextRun *MakeSpaceTextRun(const Parameters *aParams, uint32_t aFlags); - gfxTextRun *MakeBlankTextRun(uint32_t aLength, - const Parameters *aParams, uint32_t aFlags); - - // Initialize the list of fonts - void BuildFontList(); - - // Init this font group's font metrics. If there no bad fonts, you don't need to call this. - // But if there are one or more bad fonts which have bad underline offset, - // you should call this with the *first* bad font. - void InitMetricsForBadFont(gfxFont* aBadFont); - - // Set up the textrun glyphs for an entire text run: - // find script runs, and then call InitScriptRun for each - template - void InitTextRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const T *aString, - uint32_t aLength); - - // InitTextRun helper to handle a single script run, by finding font ranges - // and calling each font's InitTextRun() as appropriate - template - void InitScriptRun(gfxContext *aContext, - gfxTextRun *aTextRun, - const T *aString, - uint32_t aScriptRunStart, - uint32_t aScriptRunEnd, - int32_t aRunScript); - - // Helper for font-matching: - // see if aCh is supported in any of the faces from aFamily; - // if so return the best style match, else return null. - already_AddRefed TryAllFamilyMembers(gfxFontFamily* aFamily, - uint32_t aCh); - - // helper methods for looking up fonts - - // iterate over the fontlist, lookup names and expand generics - void EnumerateFontList(nsIAtom *aLanguage, void *aClosure = nullptr); - - // expand a generic to a list of specific names based on prefs - void FindGenericFonts(mozilla::FontFamilyType aGenericType, - nsIAtom *aLanguage, - void *aClosure); - - // lookup and add a font with a given name (i.e. *not* a generic!) - virtual void FindPlatformFont(const nsAString& aName, - bool aUseFontSet, - void *aClosure); - - static nsILanguageAtomService* gLangService; -}; #endif diff --git a/gfx/thebes/gfxFontEntry.cpp b/gfx/thebes/gfxFontEntry.cpp new file mode 100644 index 000000000000..6d239464b2cb --- /dev/null +++ b/gfx/thebes/gfxFontEntry.cpp @@ -0,0 +1,1820 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "mozilla/DebugOnly.h" +#include "mozilla/MathAlgorithms.h" + +#ifdef MOZ_LOGGING +#define FORCE_PR_LOG /* Allow logging in the release build */ +#endif +#include "prlog.h" + +#include "nsServiceManagerUtils.h" +#include "nsExpirationTracker.h" +#include "nsILanguageAtomService.h" +#include "nsITimer.h" + +#include "gfxFontEntry.h" +#include "gfxTextRun.h" +#include "gfxPlatform.h" +#include "nsGkAtoms.h" + +#include "gfxTypes.h" +#include "gfxContext.h" +#include "gfxFontConstants.h" +#include "gfxHarfBuzzShaper.h" +#include "gfxUserFontSet.h" +#include "gfxPlatformFontList.h" +#include "nsUnicodeProperties.h" +#include "nsMathUtils.h" +#include "nsBidiUtils.h" +#include "nsUnicodeRange.h" +#include "nsStyleConsts.h" +#include "mozilla/AppUnits.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/Telemetry.h" +#include "gfxSVGGlyphs.h" +#include "gfxMathTable.h" +#include "gfx2DGlue.h" + +#if defined(XP_MACOSX) +#include "nsCocoaFeatures.h" +#endif + +#include "cairo.h" + +#include "harfbuzz/hb.h" +#include "harfbuzz/hb-ot.h" +#include "graphite2/Font.h" + +#include + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::unicode; +using mozilla::services::GetObserverService; + +void +gfxCharacterMap::NotifyReleased() +{ + gfxPlatformFontList *fontlist = gfxPlatformFontList::PlatformFontList(); + if (mShared) { + fontlist->RemoveCmap(this); + } + delete this; +} + +gfxFontEntry::gfxFontEntry() : + mItalic(false), mFixedPitch(false), + mIsValid(true), + mIsBadUnderlineFont(false), + mIsUserFontContainer(false), + mIsDataUserFont(false), + mIsLocalUserFont(false), + mStandardFace(false), + mSymbolFont(false), + mIgnoreGDEF(false), + mIgnoreGSUB(false), + mSVGInitialized(false), + mMathInitialized(false), + mHasSpaceFeaturesInitialized(false), + mHasSpaceFeatures(false), + mHasSpaceFeaturesKerning(false), + mHasSpaceFeaturesNonKerning(false), + mSkipDefaultFeatureSpaceCheck(false), + mCheckedForGraphiteTables(false), + mHasCmapTable(false), + mGrFaceInitialized(false), + mCheckedForColorGlyph(false), + mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL), + mUVSOffset(0), mUVSData(nullptr), + mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE), + mCOLR(nullptr), + mCPAL(nullptr), + mUnitsPerEm(0), + mHBFace(nullptr), + mGrFace(nullptr), + mGrFaceRefCnt(0) +{ + memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures)); + memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures)); +} + +gfxFontEntry::gfxFontEntry(const nsAString& aName, bool aIsStandardFace) : + mName(aName), mItalic(false), mFixedPitch(false), + mIsValid(true), + mIsBadUnderlineFont(false), + mIsUserFontContainer(false), + mIsDataUserFont(false), + mIsLocalUserFont(false), mStandardFace(aIsStandardFace), + mSymbolFont(false), + mIgnoreGDEF(false), + mIgnoreGSUB(false), + mSVGInitialized(false), + mMathInitialized(false), + mHasSpaceFeaturesInitialized(false), + mHasSpaceFeatures(false), + mHasSpaceFeaturesKerning(false), + mHasSpaceFeaturesNonKerning(false), + mSkipDefaultFeatureSpaceCheck(false), + mCheckedForGraphiteTables(false), + mHasCmapTable(false), + mGrFaceInitialized(false), + mCheckedForColorGlyph(false), + mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL), + mUVSOffset(0), mUVSData(nullptr), + mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE), + mCOLR(nullptr), + mCPAL(nullptr), + mUnitsPerEm(0), + mHBFace(nullptr), + mGrFace(nullptr), + mGrFaceRefCnt(0) +{ + memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures)); + memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures)); +} + +static PLDHashOperator +DestroyHBSet(const uint32_t& aTag, hb_set_t*& aSet, void *aUserArg) +{ + hb_set_destroy(aSet); + return PL_DHASH_NEXT; +} + +gfxFontEntry::~gfxFontEntry() +{ + if (mCOLR) { + hb_blob_destroy(mCOLR); + } + + if (mCPAL) { + hb_blob_destroy(mCPAL); + } + + // For downloaded fonts, we need to tell the user font cache that this + // entry is being deleted. + if (mIsDataUserFont) { + gfxUserFontSet::UserFontCache::ForgetFont(this); + } + + if (mFeatureInputs) { + mFeatureInputs->Enumerate(DestroyHBSet, nullptr); + } + + // By the time the entry is destroyed, all font instances that were + // using it should already have been deleted, and so the HB and/or Gr + // face objects should have been released. + MOZ_ASSERT(!mHBFace); + MOZ_ASSERT(!mGrFaceInitialized); +} + +bool gfxFontEntry::IsSymbolFont() +{ + return mSymbolFont; +} + +bool gfxFontEntry::TestCharacterMap(uint32_t aCh) +{ + if (!mCharacterMap) { + ReadCMAP(); + NS_ASSERTION(mCharacterMap, "failed to initialize character map"); + } + return mCharacterMap->test(aCh); +} + +nsresult gfxFontEntry::InitializeUVSMap() +{ + // mUVSOffset will not be initialized + // until cmap is initialized. + if (!mCharacterMap) { + ReadCMAP(); + NS_ASSERTION(mCharacterMap, "failed to initialize character map"); + } + + if (!mUVSOffset) { + return NS_ERROR_FAILURE; + } + + if (!mUVSData) { + const uint32_t kCmapTag = TRUETYPE_TAG('c','m','a','p'); + AutoTable cmapTable(this, kCmapTag); + if (!cmapTable) { + mUVSOffset = 0; // don't bother to read the table again + return NS_ERROR_FAILURE; + } + + uint8_t* uvsData; + unsigned int cmapLen; + const char* cmapData = hb_blob_get_data(cmapTable, &cmapLen); + nsresult rv = gfxFontUtils::ReadCMAPTableFormat14( + (const uint8_t*)cmapData + mUVSOffset, + cmapLen - mUVSOffset, uvsData); + + if (NS_FAILED(rv)) { + mUVSOffset = 0; // don't bother to read the table again + return rv; + } + + mUVSData = uvsData; + } + + return NS_OK; +} + +uint16_t gfxFontEntry::GetUVSGlyph(uint32_t aCh, uint32_t aVS) +{ + InitializeUVSMap(); + + if (mUVSData) { + return gfxFontUtils::MapUVSToGlyphFormat14(mUVSData, aCh, aVS); + } + + return 0; +} + +bool gfxFontEntry::SupportsScriptInGSUB(const hb_tag_t* aScriptTags) +{ + hb_face_t *face = GetHBFace(); + if (!face) { + return false; + } + + unsigned int index; + hb_tag_t chosenScript; + bool found = + hb_ot_layout_table_choose_script(face, TRUETYPE_TAG('G','S','U','B'), + aScriptTags, &index, &chosenScript); + hb_face_destroy(face); + + return found && chosenScript != TRUETYPE_TAG('D','F','L','T'); +} + +nsresult gfxFontEntry::ReadCMAP(FontInfoData *aFontInfoData) +{ + NS_ASSERTION(false, "using default no-op implementation of ReadCMAP"); + mCharacterMap = new gfxCharacterMap(); + return NS_OK; +} + +nsString +gfxFontEntry::RealFaceName() +{ + AutoTable nameTable(this, TRUETYPE_TAG('n','a','m','e')); + if (nameTable) { + nsAutoString name; + nsresult rv = gfxFontUtils::GetFullNameFromTable(nameTable, name); + if (NS_SUCCEEDED(rv)) { + return name; + } + } + return Name(); +} + +already_AddRefed +gfxFontEntry::FindOrMakeFont(const gfxFontStyle *aStyle, bool aNeedsBold) +{ + // the font entry name is the psname, not the family name + nsRefPtr font = gfxFontCache::GetCache()->Lookup(this, aStyle); + + if (!font) { + gfxFont *newFont = CreateFontInstance(aStyle, aNeedsBold); + if (!newFont) + return nullptr; + if (!newFont->Valid()) { + delete newFont; + return nullptr; + } + font = newFont; + gfxFontCache::GetCache()->AddNew(font); + } + return font.forget(); +} + +uint16_t +gfxFontEntry::UnitsPerEm() +{ + if (!mUnitsPerEm) { + AutoTable headTable(this, TRUETYPE_TAG('h','e','a','d')); + if (headTable) { + uint32_t len; + const HeadTable* head = + reinterpret_cast(hb_blob_get_data(headTable, + &len)); + if (len >= sizeof(HeadTable)) { + mUnitsPerEm = head->unitsPerEm; + } + } + + // if we didn't find a usable 'head' table, or if the value was + // outside the valid range, record it as invalid + if (mUnitsPerEm < kMinUPEM || mUnitsPerEm > kMaxUPEM) { + mUnitsPerEm = kInvalidUPEM; + } + } + return mUnitsPerEm; +} + +bool +gfxFontEntry::HasSVGGlyph(uint32_t aGlyphId) +{ + NS_ASSERTION(mSVGInitialized, "SVG data has not yet been loaded. TryGetSVGData() first."); + return mSVGGlyphs->HasSVGGlyph(aGlyphId); +} + +bool +gfxFontEntry::GetSVGGlyphExtents(gfxContext *aContext, uint32_t aGlyphId, + gfxRect *aResult) +{ + NS_ABORT_IF_FALSE(mSVGInitialized, + "SVG data has not yet been loaded. TryGetSVGData() first."); + NS_ABORT_IF_FALSE(mUnitsPerEm >= kMinUPEM && mUnitsPerEm <= kMaxUPEM, + "font has invalid unitsPerEm"); + + gfxContextAutoSaveRestore matrixRestore(aContext); + cairo_matrix_t fontMatrix; + cairo_get_font_matrix(aContext->GetCairo(), &fontMatrix); + + gfxMatrix svgToAppSpace = *reinterpret_cast(&fontMatrix); + svgToAppSpace.Scale(1.0f / mUnitsPerEm, 1.0f / mUnitsPerEm); + + return mSVGGlyphs->GetGlyphExtents(aGlyphId, svgToAppSpace, aResult); +} + +bool +gfxFontEntry::RenderSVGGlyph(gfxContext *aContext, uint32_t aGlyphId, + int aDrawMode, gfxTextContextPaint *aContextPaint) +{ + NS_ASSERTION(mSVGInitialized, "SVG data has not yet been loaded. TryGetSVGData() first."); + return mSVGGlyphs->RenderGlyph(aContext, aGlyphId, DrawMode(aDrawMode), + aContextPaint); +} + +bool +gfxFontEntry::TryGetSVGData(gfxFont* aFont) +{ + if (!gfxPlatform::GetPlatform()->OpenTypeSVGEnabled()) { + return false; + } + + if (!mSVGInitialized) { + mSVGInitialized = true; + + // If UnitsPerEm is not known/valid, we can't use SVG glyphs + if (UnitsPerEm() == kInvalidUPEM) { + return false; + } + + // We don't use AutoTable here because we'll pass ownership of this + // blob to the gfxSVGGlyphs, once we've confirmed the table exists + hb_blob_t *svgTable = GetFontTable(TRUETYPE_TAG('S','V','G',' ')); + if (!svgTable) { + return false; + } + + // gfxSVGGlyphs will hb_blob_destroy() the table when it is finished + // with it. + mSVGGlyphs = new gfxSVGGlyphs(svgTable, this); + } + + if (!mFontsUsingSVGGlyphs.Contains(aFont)) { + mFontsUsingSVGGlyphs.AppendElement(aFont); + } + + return !!mSVGGlyphs; +} + +void +gfxFontEntry::NotifyFontDestroyed(gfxFont* aFont) +{ + mFontsUsingSVGGlyphs.RemoveElement(aFont); +} + +void +gfxFontEntry::NotifyGlyphsChanged() +{ + for (uint32_t i = 0, count = mFontsUsingSVGGlyphs.Length(); i < count; ++i) { + gfxFont* font = mFontsUsingSVGGlyphs[i]; + font->NotifyGlyphsChanged(); + } +} + +bool +gfxFontEntry::TryGetMathTable() +{ + if (!mMathInitialized) { + mMathInitialized = true; + + // If UnitsPerEm is not known/valid, we can't use MATH table + if (UnitsPerEm() == kInvalidUPEM) { + return false; + } + + // We don't use AutoTable here because we'll pass ownership of this + // blob to the gfxMathTable, once we've confirmed the table exists + hb_blob_t *mathTable = GetFontTable(TRUETYPE_TAG('M','A','T','H')); + if (!mathTable) { + return false; + } + + // gfxMathTable will hb_blob_destroy() the table when it is finished + // with it. + mMathTable = new gfxMathTable(mathTable); + if (!mMathTable->HasValidHeaders()) { + mMathTable = nullptr; + return false; + } + } + + return !!mMathTable; +} + +gfxFloat +gfxFontEntry::GetMathConstant(gfxFontEntry::MathConstant aConstant) +{ + NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first."); + gfxFloat value = mMathTable->GetMathConstant(aConstant); + if (aConstant == gfxFontEntry::ScriptPercentScaleDown || + aConstant == gfxFontEntry::ScriptScriptPercentScaleDown || + aConstant == gfxFontEntry::RadicalDegreeBottomRaisePercent) { + return value / 100.0; + } + return value / mUnitsPerEm; +} + +bool +gfxFontEntry::GetMathItalicsCorrection(uint32_t aGlyphID, + gfxFloat* aItalicCorrection) +{ + NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first."); + int16_t italicCorrection; + if (!mMathTable->GetMathItalicsCorrection(aGlyphID, &italicCorrection)) { + return false; + } + *aItalicCorrection = gfxFloat(italicCorrection) / mUnitsPerEm; + return true; +} + +uint32_t +gfxFontEntry::GetMathVariantsSize(uint32_t aGlyphID, bool aVertical, + uint16_t aSize) +{ + NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first."); + return mMathTable->GetMathVariantsSize(aGlyphID, aVertical, aSize); +} + +bool +gfxFontEntry::GetMathVariantsParts(uint32_t aGlyphID, bool aVertical, + uint32_t aGlyphs[4]) +{ + NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first."); + return mMathTable->GetMathVariantsParts(aGlyphID, aVertical, aGlyphs); +} + +bool +gfxFontEntry::TryGetColorGlyphs() +{ + if (mCheckedForColorGlyph) { + return (mCOLR && mCPAL); + } + + mCheckedForColorGlyph = true; + + mCOLR = GetFontTable(TRUETYPE_TAG('C', 'O', 'L', 'R')); + if (!mCOLR) { + return false; + } + + mCPAL = GetFontTable(TRUETYPE_TAG('C', 'P', 'A', 'L')); + if (!mCPAL) { + hb_blob_destroy(mCOLR); + mCOLR = nullptr; + return false; + } + + // validation COLR and CPAL table + if (gfxFontUtils::ValidateColorGlyphs(mCOLR, mCPAL)) { + return true; + } + + hb_blob_destroy(mCOLR); + hb_blob_destroy(mCPAL); + mCOLR = nullptr; + mCPAL = nullptr; + return false; +} + +/** + * FontTableBlobData + * + * See FontTableHashEntry for the general strategy. + */ + +class gfxFontEntry::FontTableBlobData { +public: + // Adopts the content of aBuffer. + explicit FontTableBlobData(FallibleTArray& aBuffer) + : mHashtable(nullptr), mHashKey(0) + { + MOZ_COUNT_CTOR(FontTableBlobData); + mTableData.SwapElements(aBuffer); + } + + ~FontTableBlobData() { + MOZ_COUNT_DTOR(FontTableBlobData); + if (mHashtable && mHashKey) { + mHashtable->RemoveEntry(mHashKey); + } + } + + // Useful for creating blobs + const char *GetTable() const + { + return reinterpret_cast(mTableData.Elements()); + } + uint32_t GetTableLength() const { return mTableData.Length(); } + + // Tell this FontTableBlobData to remove the HashEntry when this is + // destroyed. + void ManageHashEntry(nsTHashtable *aHashtable, + uint32_t aHashKey) + { + mHashtable = aHashtable; + mHashKey = aHashKey; + } + + // Disconnect from the HashEntry (because the blob has already been + // removed from the hashtable). + void ForgetHashEntry() + { + mHashtable = nullptr; + mHashKey = 0; + } + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return mTableData.SizeOfExcludingThis(aMallocSizeOf); + } + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + +private: + // The font table data block, owned (via adoption) + FallibleTArray mTableData; + + // The blob destroy function needs to know the owning hashtable + // and the hashtable key, so that it can remove the entry. + nsTHashtable *mHashtable; + uint32_t mHashKey; + + // not implemented + FontTableBlobData(const FontTableBlobData&); +}; + +hb_blob_t * +gfxFontEntry::FontTableHashEntry:: +ShareTableAndGetBlob(FallibleTArray& aTable, + nsTHashtable *aHashtable) +{ + Clear(); + // adopts elements of aTable + mSharedBlobData = new FontTableBlobData(aTable); + mBlob = hb_blob_create(mSharedBlobData->GetTable(), + mSharedBlobData->GetTableLength(), + HB_MEMORY_MODE_READONLY, + mSharedBlobData, DeleteFontTableBlobData); + if (!mSharedBlobData) { + // The FontTableBlobData was destroyed during hb_blob_create(). + // The (empty) blob is still be held in the hashtable with a strong + // reference. + return hb_blob_reference(mBlob); + } + + // Tell the FontTableBlobData to remove this hash entry when destroyed. + // The hashtable does not keep a strong reference. + mSharedBlobData->ManageHashEntry(aHashtable, GetKey()); + return mBlob; +} + +void +gfxFontEntry::FontTableHashEntry::Clear() +{ + // If the FontTableBlobData is managing the hash entry, then the blob is + // not owned by this HashEntry; otherwise there is strong reference to the + // blob that must be removed. + if (mSharedBlobData) { + mSharedBlobData->ForgetHashEntry(); + mSharedBlobData = nullptr; + } else if (mBlob) { + hb_blob_destroy(mBlob); + } + mBlob = nullptr; +} + +// a hb_destroy_func for hb_blob_create + +/* static */ void +gfxFontEntry::FontTableHashEntry::DeleteFontTableBlobData(void *aBlobData) +{ + delete static_cast(aBlobData); +} + +hb_blob_t * +gfxFontEntry::FontTableHashEntry::GetBlob() const +{ + return hb_blob_reference(mBlob); +} + +bool +gfxFontEntry::GetExistingFontTable(uint32_t aTag, hb_blob_t **aBlob) +{ + if (!mFontTableCache) { + // we do this here rather than on fontEntry construction + // because not all shapers will access the table cache at all + mFontTableCache = new nsTHashtable(8); + } + + FontTableHashEntry *entry = mFontTableCache->GetEntry(aTag); + if (!entry) { + return false; + } + + *aBlob = entry->GetBlob(); + return true; +} + +hb_blob_t * +gfxFontEntry::ShareFontTableAndGetBlob(uint32_t aTag, + FallibleTArray* aBuffer) +{ + if (MOZ_UNLIKELY(!mFontTableCache)) { + // we do this here rather than on fontEntry construction + // because not all shapers will access the table cache at all + mFontTableCache = new nsTHashtable(8); + } + + FontTableHashEntry *entry = mFontTableCache->PutEntry(aTag); + if (MOZ_UNLIKELY(!entry)) { // OOM + return nullptr; + } + + if (!aBuffer) { + // ensure the entry is null + entry->Clear(); + return nullptr; + } + + return entry->ShareTableAndGetBlob(*aBuffer, mFontTableCache); +} + +static int +DirEntryCmp(const void* aKey, const void* aItem) +{ + int32_t tag = *static_cast(aKey); + const TableDirEntry* entry = static_cast(aItem); + return tag - int32_t(entry->tag); +} + +hb_blob_t* +gfxFontEntry::GetTableFromFontData(const void* aFontData, uint32_t aTableTag) +{ + const SFNTHeader* header = + reinterpret_cast(aFontData); + const TableDirEntry* dir = + reinterpret_cast(header + 1); + dir = static_cast + (bsearch(&aTableTag, dir, uint16_t(header->numTables), + sizeof(TableDirEntry), DirEntryCmp)); + if (dir) { + return hb_blob_create(reinterpret_cast(aFontData) + + dir->offset, dir->length, + HB_MEMORY_MODE_READONLY, nullptr, nullptr); + + } + return nullptr; +} + +already_AddRefed +gfxFontEntry::GetCMAPFromFontInfo(FontInfoData *aFontInfoData, + uint32_t& aUVSOffset, + bool& aSymbolFont) +{ + if (!aFontInfoData || !aFontInfoData->mLoadCmaps) { + return nullptr; + } + + return aFontInfoData->GetCMAP(mName, aUVSOffset, aSymbolFont); +} + +hb_blob_t * +gfxFontEntry::GetFontTable(uint32_t aTag) +{ + hb_blob_t *blob; + if (GetExistingFontTable(aTag, &blob)) { + return blob; + } + + FallibleTArray buffer; + bool haveTable = NS_SUCCEEDED(CopyFontTable(aTag, buffer)); + + return ShareFontTableAndGetBlob(aTag, haveTable ? &buffer : nullptr); +} + +// callback for HarfBuzz to get a font table (in hb_blob_t form) +// from the font entry (passed as aUserData) +/*static*/ hb_blob_t * +gfxFontEntry::HBGetTable(hb_face_t *face, uint32_t aTag, void *aUserData) +{ + gfxFontEntry *fontEntry = static_cast(aUserData); + + // bug 589682 - ignore the GDEF table in buggy fonts (applies to + // Italic and BoldItalic faces of Times New Roman) + if (aTag == TRUETYPE_TAG('G','D','E','F') && + fontEntry->IgnoreGDEF()) { + return nullptr; + } + + // bug 721719 - ignore the GSUB table in buggy fonts (applies to Roboto, + // at least on some Android ICS devices; set in gfxFT2FontList.cpp) + if (aTag == TRUETYPE_TAG('G','S','U','B') && + fontEntry->IgnoreGSUB()) { + return nullptr; + } + + return fontEntry->GetFontTable(aTag); +} + +/*static*/ void +gfxFontEntry::HBFaceDeletedCallback(void *aUserData) +{ + gfxFontEntry *fe = static_cast(aUserData); + fe->ForgetHBFace(); +} + +void +gfxFontEntry::ForgetHBFace() +{ + mHBFace = nullptr; +} + +hb_face_t* +gfxFontEntry::GetHBFace() +{ + if (!mHBFace) { + mHBFace = hb_face_create_for_tables(HBGetTable, this, + HBFaceDeletedCallback); + return mHBFace; + } + return hb_face_reference(mHBFace); +} + +/*static*/ const void* +gfxFontEntry::GrGetTable(const void *aAppFaceHandle, unsigned int aName, + size_t *aLen) +{ + gfxFontEntry *fontEntry = + static_cast(const_cast(aAppFaceHandle)); + hb_blob_t *blob = fontEntry->GetFontTable(aName); + if (blob) { + unsigned int blobLength; + const void *tableData = hb_blob_get_data(blob, &blobLength); + fontEntry->mGrTableMap->Put(tableData, blob); + *aLen = blobLength; + return tableData; + } + *aLen = 0; + return nullptr; +} + +/*static*/ void +gfxFontEntry::GrReleaseTable(const void *aAppFaceHandle, + const void *aTableBuffer) +{ + gfxFontEntry *fontEntry = + static_cast(const_cast(aAppFaceHandle)); + void *data; + if (fontEntry->mGrTableMap->Get(aTableBuffer, &data)) { + fontEntry->mGrTableMap->Remove(aTableBuffer); + hb_blob_destroy(static_cast(data)); + } +} + +gr_face* +gfxFontEntry::GetGrFace() +{ + if (!mGrFaceInitialized) { + gr_face_ops faceOps = { + sizeof(gr_face_ops), + GrGetTable, + GrReleaseTable + }; + mGrTableMap = new nsDataHashtable,void*>; + mGrFace = gr_make_face_with_ops(this, &faceOps, gr_face_default); + mGrFaceInitialized = true; + } + ++mGrFaceRefCnt; + return mGrFace; +} + +void +gfxFontEntry::ReleaseGrFace(gr_face *aFace) +{ + MOZ_ASSERT(aFace == mGrFace); // sanity-check + MOZ_ASSERT(mGrFaceRefCnt > 0); + if (--mGrFaceRefCnt == 0) { + gr_face_destroy(mGrFace); + mGrFace = nullptr; + mGrFaceInitialized = false; + delete mGrTableMap; + mGrTableMap = nullptr; + } +} + +void +gfxFontEntry::DisconnectSVG() +{ + if (mSVGInitialized && mSVGGlyphs) { + mSVGGlyphs = nullptr; + mSVGInitialized = false; + } +} + +bool +gfxFontEntry::HasFontTable(uint32_t aTableTag) +{ + AutoTable table(this, aTableTag); + return table && hb_blob_get_length(table) > 0; +} + +void +gfxFontEntry::CheckForGraphiteTables() +{ + mHasGraphiteTables = HasFontTable(TRUETYPE_TAG('S','i','l','f')); +} + + +#define FEATURE_SCRIPT_MASK 0x000000ff // script index replaces low byte of tag + +// check for too many script codes +PR_STATIC_ASSERT(MOZ_NUM_SCRIPT_CODES <= FEATURE_SCRIPT_MASK); + +// high-order three bytes of tag with script in low-order byte +#define SCRIPT_FEATURE(s,tag) (((~FEATURE_SCRIPT_MASK) & (tag)) | \ + ((FEATURE_SCRIPT_MASK) & (s))) + +bool +gfxFontEntry::SupportsOpenTypeFeature(int32_t aScript, uint32_t aFeatureTag) +{ + if (!mSupportedFeatures) { + mSupportedFeatures = new nsDataHashtable(); + } + + // note: high-order three bytes *must* be unique for each feature + // listed below (see SCRIPT_FEATURE macro def'n) + NS_ASSERTION(aFeatureTag == HB_TAG('s','m','c','p') || + aFeatureTag == HB_TAG('c','2','s','c') || + aFeatureTag == HB_TAG('p','c','a','p') || + aFeatureTag == HB_TAG('c','2','p','c') || + aFeatureTag == HB_TAG('s','u','p','s') || + aFeatureTag == HB_TAG('s','u','b','s'), + "use of unknown feature tag"); + + // note: graphite feature support uses the last script index + NS_ASSERTION(aScript < FEATURE_SCRIPT_MASK - 1, + "need to bump the size of the feature shift"); + + uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag); + bool result; + if (mSupportedFeatures->Get(scriptFeature, &result)) { + return result; + } + + result = false; + + hb_face_t *face = GetHBFace(); + + if (hb_ot_layout_has_substitution(face)) { + hb_script_t hbScript = + gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript); + + // Get the OpenType tag(s) that match this script code + hb_tag_t scriptTags[4] = { + HB_TAG_NONE, + HB_TAG_NONE, + HB_TAG_NONE, + HB_TAG_NONE + }; + hb_ot_tags_from_script(hbScript, &scriptTags[0], &scriptTags[1]); + + // Replace the first remaining NONE with DEFAULT + hb_tag_t* scriptTag = &scriptTags[0]; + while (*scriptTag != HB_TAG_NONE) { + ++scriptTag; + } + *scriptTag = HB_OT_TAG_DEFAULT_SCRIPT; + + // Now check for 'smcp' under the first of those scripts that is present + const hb_tag_t kGSUB = HB_TAG('G','S','U','B'); + scriptTag = &scriptTags[0]; + while (*scriptTag != HB_TAG_NONE) { + unsigned int scriptIndex; + if (hb_ot_layout_table_find_script(face, kGSUB, *scriptTag, + &scriptIndex)) { + if (hb_ot_layout_language_find_feature(face, kGSUB, + scriptIndex, + HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX, + aFeatureTag, nullptr)) { + result = true; + } + break; + } + ++scriptTag; + } + } + + hb_face_destroy(face); + + mSupportedFeatures->Put(scriptFeature, result); + + return result; +} + +const hb_set_t* +gfxFontEntry::InputsForOpenTypeFeature(int32_t aScript, uint32_t aFeatureTag) +{ + if (!mFeatureInputs) { + mFeatureInputs = new nsDataHashtable(); + } + + NS_ASSERTION(aFeatureTag == HB_TAG('s','u','p','s') || + aFeatureTag == HB_TAG('s','u','b','s'), + "use of unknown feature tag"); + + uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag); + hb_set_t *inputGlyphs; + if (mFeatureInputs->Get(scriptFeature, &inputGlyphs)) { + return inputGlyphs; + } + + inputGlyphs = hb_set_create(); + + hb_face_t *face = GetHBFace(); + + if (hb_ot_layout_has_substitution(face)) { + hb_script_t hbScript = + gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript); + + // Get the OpenType tag(s) that match this script code + hb_tag_t scriptTags[4] = { + HB_TAG_NONE, + HB_TAG_NONE, + HB_TAG_NONE, + HB_TAG_NONE + }; + hb_ot_tags_from_script(hbScript, &scriptTags[0], &scriptTags[1]); + + // Replace the first remaining NONE with DEFAULT + hb_tag_t* scriptTag = &scriptTags[0]; + while (*scriptTag != HB_TAG_NONE) { + ++scriptTag; + } + *scriptTag = HB_OT_TAG_DEFAULT_SCRIPT; + + const hb_tag_t kGSUB = HB_TAG('G','S','U','B'); + hb_tag_t features[2] = { aFeatureTag, HB_TAG_NONE }; + hb_set_t *featurelookups = hb_set_create(); + hb_ot_layout_collect_lookups(face, kGSUB, scriptTags, nullptr, + features, featurelookups); + hb_codepoint_t index = -1; + while (hb_set_next(featurelookups, &index)) { + hb_ot_layout_lookup_collect_glyphs(face, kGSUB, index, + nullptr, inputGlyphs, + nullptr, nullptr); + } + } + + hb_face_destroy(face); + + mFeatureInputs->Put(scriptFeature, inputGlyphs); + return inputGlyphs; +} + +bool +gfxFontEntry::SupportsGraphiteFeature(uint32_t aFeatureTag) +{ + if (!mSupportedFeatures) { + mSupportedFeatures = new nsDataHashtable(); + } + + // note: high-order three bytes *must* be unique for each feature + // listed below (see SCRIPT_FEATURE macro def'n) + NS_ASSERTION(aFeatureTag == HB_TAG('s','m','c','p') || + aFeatureTag == HB_TAG('c','2','s','c') || + aFeatureTag == HB_TAG('p','c','a','p') || + aFeatureTag == HB_TAG('c','2','p','c') || + aFeatureTag == HB_TAG('s','u','p','s') || + aFeatureTag == HB_TAG('s','u','b','s'), + "use of unknown feature tag"); + + // graphite feature check uses the last script slot + uint32_t scriptFeature = SCRIPT_FEATURE(FEATURE_SCRIPT_MASK, aFeatureTag); + bool result; + if (mSupportedFeatures->Get(scriptFeature, &result)) { + return result; + } + + gr_face* face = GetGrFace(); + result = gr_face_find_fref(face, aFeatureTag) != nullptr; + ReleaseGrFace(face); + + mSupportedFeatures->Put(scriptFeature, result); + + return result; +} + +bool +gfxFontEntry::GetColorLayersInfo(uint32_t aGlyphId, + nsTArray& aLayerGlyphs, + nsTArray& aLayerColors) +{ + return gfxFontUtils::GetColorGlyphLayers(mCOLR, + mCPAL, + aGlyphId, + aLayerGlyphs, + aLayerColors); +} + +size_t +gfxFontEntry::FontTableHashEntry::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + size_t n = 0; + if (mBlob) { + n += aMallocSizeOf(mBlob); + } + if (mSharedBlobData) { + n += mSharedBlobData->SizeOfIncludingThis(aMallocSizeOf); + } + return n; +} + +void +gfxFontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + + // cmaps are shared so only non-shared cmaps are included here + if (mCharacterMap && mCharacterMap->mBuildOnTheFly) { + aSizes->mCharMapsSize += + mCharacterMap->SizeOfIncludingThis(aMallocSizeOf); + } + if (mFontTableCache) { + aSizes->mFontTableCacheSize += + mFontTableCache->SizeOfIncludingThis(aMallocSizeOf); + } +} + +void +gfxFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +////////////////////////////////////////////////////////////////////////////// +// +// class gfxFontFamily +// +////////////////////////////////////////////////////////////////////////////// + +// we consider faces with mStandardFace == true to be "greater than" those with false, +// because during style matching, later entries will replace earlier ones +class FontEntryStandardFaceComparator { + public: + bool Equals(const nsRefPtr& a, const nsRefPtr& b) const { + return a->mStandardFace == b->mStandardFace; + } + bool LessThan(const nsRefPtr& a, const nsRefPtr& b) const { + return (a->mStandardFace == false && b->mStandardFace == true); + } +}; + +void +gfxFontFamily::SortAvailableFonts() +{ + mAvailableFonts.Sort(FontEntryStandardFaceComparator()); +} + +bool +gfxFontFamily::HasOtherFamilyNames() +{ + // need to read in other family names to determine this + if (!mOtherFamilyNamesInitialized) { + ReadOtherFamilyNames(gfxPlatformFontList::PlatformFontList()); // sets mHasOtherFamilyNames + } + return mHasOtherFamilyNames; +} + +gfxFontEntry* +gfxFontFamily::FindFontForStyle(const gfxFontStyle& aFontStyle, + bool& aNeedsSyntheticBold) +{ + if (!mHasStyles) + FindStyleVariations(); // collect faces for the family, if not already done + + NS_ASSERTION(mAvailableFonts.Length() > 0, "font family with no faces!"); + + aNeedsSyntheticBold = false; + + int8_t baseWeight = aFontStyle.ComputeWeight(); + bool wantBold = baseWeight >= 6; + + // If the family has only one face, we simply return it; no further checking needed + if (mAvailableFonts.Length() == 1) { + gfxFontEntry *fe = mAvailableFonts[0]; + aNeedsSyntheticBold = + wantBold && !fe->IsBold() && aFontStyle.allowSyntheticWeight; + return fe; + } + + bool wantItalic = (aFontStyle.style & + (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0; + + // Most families are "simple", having just Regular/Bold/Italic/BoldItalic, + // or some subset of these. In this case, we have exactly 4 entries in mAvailableFonts, + // stored in the above order; note that some of the entries may be nullptr. + // We can then pick the required entry based on whether the request is for + // bold or non-bold, italic or non-italic, without running the more complex + // matching algorithm used for larger families with many weights and/or widths. + + if (mIsSimpleFamily) { + // Family has no more than the "standard" 4 faces, at fixed indexes; + // calculate which one we want. + // Note that we cannot simply return it as not all 4 faces are necessarily present. + uint8_t faceIndex = (wantItalic ? kItalicMask : 0) | + (wantBold ? kBoldMask : 0); + + // if the desired style is available, return it directly + gfxFontEntry *fe = mAvailableFonts[faceIndex]; + if (fe) { + // no need to set aNeedsSyntheticBold here as we matched the boldness request + return fe; + } + + // order to check fallback faces in a simple family, depending on requested style + static const uint8_t simpleFallbacks[4][3] = { + { kBoldFaceIndex, kItalicFaceIndex, kBoldItalicFaceIndex }, // fallbacks for Regular + { kRegularFaceIndex, kBoldItalicFaceIndex, kItalicFaceIndex },// Bold + { kBoldItalicFaceIndex, kRegularFaceIndex, kBoldFaceIndex }, // Italic + { kItalicFaceIndex, kBoldFaceIndex, kRegularFaceIndex } // BoldItalic + }; + const uint8_t *order = simpleFallbacks[faceIndex]; + + for (uint8_t trial = 0; trial < 3; ++trial) { + // check remaining faces in order of preference to find the first that actually exists + fe = mAvailableFonts[order[trial]]; + if (fe) { + aNeedsSyntheticBold = + wantBold && !fe->IsBold() && + aFontStyle.allowSyntheticWeight; + return fe; + } + } + + // this can't happen unless we have totally broken the font-list manager! + NS_NOTREACHED("no face found in simple font family!"); + return nullptr; + } + + // This is a large/rich font family, so we do full style- and weight-matching: + // first collect a list of weights that are the best match for the requested + // font-stretch and font-style, then pick the best weight match among those + // available. + + gfxFontEntry *weightList[10] = { 0 }; + bool foundWeights = FindWeightsForStyle(weightList, wantItalic, aFontStyle.stretch); + if (!foundWeights) { + return nullptr; + } + + // First find a match for the best weight + int8_t matchBaseWeight = 0; + int8_t i = baseWeight; + + // Need to special case when normal face doesn't exist but medium does. + // In that case, use medium otherwise weights < 400 + if (baseWeight == 4 && !weightList[4]) { + i = 5; // medium + } + + // Loop through weights, since one exists loop will terminate + int8_t direction = (baseWeight > 5) ? 1 : -1; + for (; ; i += direction) { + if (weightList[i]) { + matchBaseWeight = i; + break; + } + + // If we've reached one side without finding a font, + // start over and go the other direction until we find a match + if (i == 1 || i == 9) { + i = baseWeight; + direction = -direction; + } + } + + NS_ASSERTION(matchBaseWeight != 0, + "weight mapping should always find at least one font in a family"); + + gfxFontEntry *matchFE = weightList[matchBaseWeight]; + + NS_ASSERTION(matchFE, + "weight mapping should always find at least one font in a family"); + + if (!matchFE->IsBold() && baseWeight >= 6 && + aFontStyle.allowSyntheticWeight) + { + aNeedsSyntheticBold = true; + } + + return matchFE; +} + +void +gfxFontFamily::CheckForSimpleFamily() +{ + // already checked this family + if (mIsSimpleFamily) { + return; + }; + + uint32_t count = mAvailableFonts.Length(); + if (count > 4 || count == 0) { + return; // can't be "simple" if there are >4 faces; + // if none then the family is unusable anyway + } + + if (count == 1) { + mIsSimpleFamily = true; + return; + } + + int16_t firstStretch = mAvailableFonts[0]->Stretch(); + + gfxFontEntry *faces[4] = { 0 }; + for (uint8_t i = 0; i < count; ++i) { + gfxFontEntry *fe = mAvailableFonts[i]; + if (fe->Stretch() != firstStretch) { + return; // font-stretch doesn't match, don't treat as simple family + } + uint8_t faceIndex = (fe->IsItalic() ? kItalicMask : 0) | + (fe->Weight() >= 600 ? kBoldMask : 0); + if (faces[faceIndex]) { + return; // two faces resolve to the same slot; family isn't "simple" + } + faces[faceIndex] = fe; + } + + // we have successfully slotted the available faces into the standard + // 4-face framework + mAvailableFonts.SetLength(4); + for (uint8_t i = 0; i < 4; ++i) { + if (mAvailableFonts[i].get() != faces[i]) { + mAvailableFonts[i].swap(faces[i]); + } + } + + mIsSimpleFamily = true; +} + +#ifdef DEBUG +bool +gfxFontFamily::ContainsFace(gfxFontEntry* aFontEntry) { + uint32_t i, numFonts = mAvailableFonts.Length(); + for (i = 0; i < numFonts; i++) { + if (mAvailableFonts[i] == aFontEntry) { + return true; + } + // userfonts contain the actual real font entry + if (mAvailableFonts[i]->mIsUserFontContainer) { + gfxUserFontEntry* ufe = + static_cast(mAvailableFonts[i].get()); + if (ufe->GetPlatformFontEntry() == aFontEntry) { + return true; + } + } + } + return false; +} +#endif + +static inline uint32_t +StyleDistance(gfxFontEntry *aFontEntry, + bool anItalic, int16_t aStretch) +{ + // Compute a measure of the "distance" between the requested style + // and the given fontEntry, + // considering italicness and font-stretch but not weight. + + int32_t distance = 0; + if (aStretch != aFontEntry->mStretch) { + // stretch values are in the range -4 .. +4 + // if aStretch is positive, we prefer more-positive values; + // if zero or negative, prefer more-negative + if (aStretch > 0) { + distance = (aFontEntry->mStretch - aStretch) * 2; + } else { + distance = (aStretch - aFontEntry->mStretch) * 2; + } + // if the computed "distance" here is negative, it means that + // aFontEntry lies in the "non-preferred" direction from aStretch, + // so we treat that as larger than any preferred-direction distance + // (max possible is 8) by adding an extra 10 to the absolute value + if (distance < 0) { + distance = -distance + 10; + } + } + if (aFontEntry->IsItalic() != anItalic) { + distance += 1; + } + return uint32_t(distance); +} + +bool +gfxFontFamily::FindWeightsForStyle(gfxFontEntry* aFontsForWeights[], + bool anItalic, int16_t aStretch) +{ + uint32_t foundWeights = 0; + uint32_t bestMatchDistance = 0xffffffff; + + uint32_t count = mAvailableFonts.Length(); + for (uint32_t i = 0; i < count; i++) { + // this is not called for "simple" families, and therefore it does not + // need to check the mAvailableFonts entries for nullptr. + gfxFontEntry *fe = mAvailableFonts[i]; + uint32_t distance = StyleDistance(fe, anItalic, aStretch); + if (distance <= bestMatchDistance) { + int8_t wt = fe->mWeight / 100; + NS_ASSERTION(wt >= 1 && wt < 10, "invalid weight in fontEntry"); + if (!aFontsForWeights[wt]) { + // record this as a possible candidate for weight matching + aFontsForWeights[wt] = fe; + ++foundWeights; + } else { + uint32_t prevDistance = + StyleDistance(aFontsForWeights[wt], anItalic, aStretch); + if (prevDistance >= distance) { + // replacing a weight we already found, + // so don't increment foundWeights + aFontsForWeights[wt] = fe; + } + } + bestMatchDistance = distance; + } + } + + NS_ASSERTION(foundWeights > 0, "Font family containing no faces?"); + + if (foundWeights == 1) { + // no need to cull entries if we only found one weight + return true; + } + + // we might have recorded some faces that were a partial style match, but later found + // others that were closer; in this case, we need to cull the poorer matches from the + // weight list we'll return + for (uint32_t i = 0; i < 10; ++i) { + if (aFontsForWeights[i] && + StyleDistance(aFontsForWeights[i], anItalic, aStretch) > bestMatchDistance) + { + aFontsForWeights[i] = 0; + } + } + + return (foundWeights > 0); +} + + +void gfxFontFamily::LocalizedName(nsAString& aLocalizedName) +{ + // just return the primary name; subclasses should override + aLocalizedName = mName; +} + +// metric for how close a given font matches a style +static int32_t +CalcStyleMatch(gfxFontEntry *aFontEntry, const gfxFontStyle *aStyle) +{ + int32_t rank = 0; + if (aStyle) { + // italics + bool wantItalic = + (aStyle->style & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0; + if (aFontEntry->IsItalic() == wantItalic) { + rank += 10; + } + + // measure of closeness of weight to the desired value + rank += 9 - DeprecatedAbs(aFontEntry->Weight() / 100 - aStyle->ComputeWeight()); + } else { + // if no font to match, prefer non-bold, non-italic fonts + if (!aFontEntry->IsItalic()) { + rank += 3; + } + if (!aFontEntry->IsBold()) { + rank += 2; + } + } + + return rank; +} + +#define RANK_MATCHED_CMAP 20 + +void +gfxFontFamily::FindFontForChar(GlobalFontMatch *aMatchData) +{ + if (mFamilyCharacterMapInitialized && !TestCharacterMap(aMatchData->mCh)) { + // none of the faces in the family support the required char, + // so bail out immediately + return; + } + + bool needsBold; + gfxFontStyle normal; + gfxFontEntry *fe = FindFontForStyle( + (aMatchData->mStyle == nullptr) ? *aMatchData->mStyle : normal, + needsBold); + + if (fe && !fe->SkipDuringSystemFallback()) { + int32_t rank = 0; + + if (fe->TestCharacterMap(aMatchData->mCh)) { + rank += RANK_MATCHED_CMAP; + aMatchData->mCount++; +#ifdef PR_LOGGING + PRLogModuleInfo *log = gfxPlatform::GetLog(eGfxLog_textrun); + + if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_DEBUG))) { + uint32_t unicodeRange = FindCharUnicodeRange(aMatchData->mCh); + uint32_t script = GetScriptCode(aMatchData->mCh); + PR_LOG(log, PR_LOG_DEBUG,\ + ("(textrun-systemfallback-fonts) char: u+%6.6x " + "unicode-range: %d script: %d match: [%s]\n", + aMatchData->mCh, + unicodeRange, script, + NS_ConvertUTF16toUTF8(fe->Name()).get())); + } +#endif + } + + aMatchData->mCmapsTested++; + if (rank == 0) { + return; + } + + // omitting from original windows code -- family name, lang group, pitch + // not available in current FontEntry implementation + rank += CalcStyleMatch(fe, aMatchData->mStyle); + + // xxx - add whether AAT font with morphing info for specific lang groups + + if (rank > aMatchData->mMatchRank + || (rank == aMatchData->mMatchRank && + Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) + { + aMatchData->mBestMatch = fe; + aMatchData->mMatchedFamily = this; + aMatchData->mMatchRank = rank; + } + } +} + +void +gfxFontFamily::SearchAllFontsForChar(GlobalFontMatch *aMatchData) +{ + uint32_t i, numFonts = mAvailableFonts.Length(); + for (i = 0; i < numFonts; i++) { + gfxFontEntry *fe = mAvailableFonts[i]; + if (fe && fe->TestCharacterMap(aMatchData->mCh)) { + int32_t rank = RANK_MATCHED_CMAP; + rank += CalcStyleMatch(fe, aMatchData->mStyle); + if (rank > aMatchData->mMatchRank + || (rank == aMatchData->mMatchRank && + Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) + { + aMatchData->mBestMatch = fe; + aMatchData->mMatchedFamily = this; + aMatchData->mMatchRank = rank; + } + } + } +} + +/*static*/ void +gfxFontFamily::ReadOtherFamilyNamesForFace(const nsAString& aFamilyName, + const char *aNameData, + uint32_t aDataLength, + nsTArray& aOtherFamilyNames, + bool useFullName) +{ + const gfxFontUtils::NameHeader *nameHeader = + reinterpret_cast(aNameData); + + uint32_t nameCount = nameHeader->count; + if (nameCount * sizeof(gfxFontUtils::NameRecord) > aDataLength) { + NS_WARNING("invalid font (name records)"); + return; + } + + const gfxFontUtils::NameRecord *nameRecord = + reinterpret_cast(aNameData + sizeof(gfxFontUtils::NameHeader)); + uint32_t stringsBase = uint32_t(nameHeader->stringOffset); + + for (uint32_t i = 0; i < nameCount; i++, nameRecord++) { + uint32_t nameLen = nameRecord->length; + uint32_t nameOff = nameRecord->offset; // offset from base of string storage + + if (stringsBase + nameOff + nameLen > aDataLength) { + NS_WARNING("invalid font (name table strings)"); + return; + } + + uint16_t nameID = nameRecord->nameID; + if ((useFullName && nameID == gfxFontUtils::NAME_ID_FULL) || + (!useFullName && (nameID == gfxFontUtils::NAME_ID_FAMILY || + nameID == gfxFontUtils::NAME_ID_PREFERRED_FAMILY))) { + nsAutoString otherFamilyName; + bool ok = gfxFontUtils::DecodeFontName(aNameData + stringsBase + nameOff, + nameLen, + uint32_t(nameRecord->platformID), + uint32_t(nameRecord->encodingID), + uint32_t(nameRecord->languageID), + otherFamilyName); + // add if not same as canonical family name + if (ok && otherFamilyName != aFamilyName) { + aOtherFamilyNames.AppendElement(otherFamilyName); + } + } + } +} + +// returns true if other names were found, false otherwise +bool +gfxFontFamily::ReadOtherFamilyNamesForFace(gfxPlatformFontList *aPlatformFontList, + hb_blob_t *aNameTable, + bool useFullName) +{ + uint32_t dataLength; + const char *nameData = hb_blob_get_data(aNameTable, &dataLength); + nsAutoTArray otherFamilyNames; + + ReadOtherFamilyNamesForFace(mName, nameData, dataLength, + otherFamilyNames, useFullName); + + uint32_t n = otherFamilyNames.Length(); + for (uint32_t i = 0; i < n; i++) { + aPlatformFontList->AddOtherFamilyName(this, otherFamilyNames[i]); + } + + return n != 0; +} + +void +gfxFontFamily::ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList) +{ + if (mOtherFamilyNamesInitialized) + return; + mOtherFamilyNamesInitialized = true; + + FindStyleVariations(); + + // read in other family names for the first face in the list + uint32_t i, numFonts = mAvailableFonts.Length(); + const uint32_t kNAME = TRUETYPE_TAG('n','a','m','e'); + + for (i = 0; i < numFonts; ++i) { + gfxFontEntry *fe = mAvailableFonts[i]; + if (!fe) { + continue; + } + gfxFontEntry::AutoTable nameTable(fe, kNAME); + if (!nameTable) { + continue; + } + mHasOtherFamilyNames = ReadOtherFamilyNamesForFace(aPlatformFontList, + nameTable); + break; + } + + // read in other names for the first face in the list with the assumption + // that if extra names don't exist in that face then they don't exist in + // other faces for the same font + if (!mHasOtherFamilyNames) + return; + + // read in names for all faces, needed to catch cases where fonts have + // family names for individual weights (e.g. Hiragino Kaku Gothic Pro W6) + for ( ; i < numFonts; i++) { + gfxFontEntry *fe = mAvailableFonts[i]; + if (!fe) { + continue; + } + gfxFontEntry::AutoTable nameTable(fe, kNAME); + if (!nameTable) { + continue; + } + ReadOtherFamilyNamesForFace(aPlatformFontList, nameTable); + } +} + +void +gfxFontFamily::ReadFaceNames(gfxPlatformFontList *aPlatformFontList, + bool aNeedFullnamePostscriptNames, + FontInfoData *aFontInfoData) +{ + // if all needed names have already been read, skip + if (mOtherFamilyNamesInitialized && + (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) + return; + + bool asyncFontLoaderDisabled = false; + +#if defined(XP_MACOSX) + // bug 975460 - async font loader crashes sometimes under 10.6, disable + if (!nsCocoaFeatures::OnLionOrLater()) { + asyncFontLoaderDisabled = true; + } +#endif + + if (!mOtherFamilyNamesInitialized && + aFontInfoData && + aFontInfoData->mLoadOtherNames && + !asyncFontLoaderDisabled) + { + nsAutoTArray otherFamilyNames; + bool foundOtherNames = + aFontInfoData->GetOtherFamilyNames(mName, otherFamilyNames); + if (foundOtherNames) { + uint32_t i, n = otherFamilyNames.Length(); + for (i = 0; i < n; i++) { + aPlatformFontList->AddOtherFamilyName(this, otherFamilyNames[i]); + } + } + mOtherFamilyNamesInitialized = true; + } + + // if all needed data has been initialized, return + if (mOtherFamilyNamesInitialized && + (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) { + return; + } + + FindStyleVariations(aFontInfoData); + + // check again, as style enumeration code may have loaded names + if (mOtherFamilyNamesInitialized && + (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) { + return; + } + + uint32_t i, numFonts = mAvailableFonts.Length(); + const uint32_t kNAME = TRUETYPE_TAG('n','a','m','e'); + + bool firstTime = true, readAllFaces = false; + for (i = 0; i < numFonts; ++i) { + gfxFontEntry *fe = mAvailableFonts[i]; + if (!fe) { + continue; + } + + nsAutoString fullname, psname; + bool foundFaceNames = false; + if (!mFaceNamesInitialized && + aNeedFullnamePostscriptNames && + aFontInfoData && + aFontInfoData->mLoadFaceNames) { + aFontInfoData->GetFaceNames(fe->Name(), fullname, psname); + if (!fullname.IsEmpty()) { + aPlatformFontList->AddFullname(fe, fullname); + } + if (!psname.IsEmpty()) { + aPlatformFontList->AddPostscriptName(fe, psname); + } + foundFaceNames = true; + + // found everything needed? skip to next font + if (mOtherFamilyNamesInitialized) { + continue; + } + } + + // load directly from the name table + gfxFontEntry::AutoTable nameTable(fe, kNAME); + if (!nameTable) { + continue; + } + + if (aNeedFullnamePostscriptNames && !foundFaceNames) { + if (gfxFontUtils::ReadCanonicalName( + nameTable, gfxFontUtils::NAME_ID_FULL, fullname) == NS_OK) + { + aPlatformFontList->AddFullname(fe, fullname); + } + + if (gfxFontUtils::ReadCanonicalName( + nameTable, gfxFontUtils::NAME_ID_POSTSCRIPT, psname) == NS_OK) + { + aPlatformFontList->AddPostscriptName(fe, psname); + } + } + + if (!mOtherFamilyNamesInitialized && (firstTime || readAllFaces)) { + bool foundOtherName = ReadOtherFamilyNamesForFace(aPlatformFontList, + nameTable); + + // if the first face has a different name, scan all faces, otherwise + // assume the family doesn't have other names + if (firstTime && foundOtherName) { + mHasOtherFamilyNames = true; + readAllFaces = true; + } + firstTime = false; + } + + // if not reading in any more names, skip other faces + if (!readAllFaces && !aNeedFullnamePostscriptNames) { + break; + } + } + + mFaceNamesInitialized = true; + mOtherFamilyNamesInitialized = true; +} + + +gfxFontEntry* +gfxFontFamily::FindFont(const nsAString& aPostscriptName) +{ + // find the font using a simple linear search + uint32_t numFonts = mAvailableFonts.Length(); + for (uint32_t i = 0; i < numFonts; i++) { + gfxFontEntry *fe = mAvailableFonts[i].get(); + if (fe && fe->Name() == aPostscriptName) + return fe; + } + return nullptr; +} + +void +gfxFontFamily::ReadAllCMAPs(FontInfoData *aFontInfoData) +{ + FindStyleVariations(aFontInfoData); + + uint32_t i, numFonts = mAvailableFonts.Length(); + for (i = 0; i < numFonts; i++) { + gfxFontEntry *fe = mAvailableFonts[i]; + // don't try to load cmaps for downloadable fonts not yet loaded + if (!fe || fe->mIsUserFontContainer) { + continue; + } + fe->ReadCMAP(aFontInfoData); + mFamilyCharacterMap.Union(*(fe->mCharacterMap)); + } + mFamilyCharacterMap.Compact(); + mFamilyCharacterMapInitialized = true; +} + +void +gfxFontFamily::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += + mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + aSizes->mCharMapsSize += + mFamilyCharacterMap.SizeOfExcludingThis(aMallocSizeOf); + + aSizes->mFontListSize += + mAvailableFonts.SizeOfExcludingThis(aMallocSizeOf); + for (uint32_t i = 0; i < mAvailableFonts.Length(); ++i) { + gfxFontEntry *fe = mAvailableFonts[i]; + if (fe) { + fe->AddSizeOfIncludingThis(aMallocSizeOf, aSizes); + } + } +} + +void +gfxFontFamily::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} diff --git a/gfx/thebes/gfxFontEntry.h b/gfx/thebes/gfxFontEntry.h new file mode 100644 index 000000000000..56d408970eda --- /dev/null +++ b/gfx/thebes/gfxFontEntry.h @@ -0,0 +1,819 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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/. */ + +#ifndef GFX_FONTENTRY_H +#define GFX_FONTENTRY_H + +#include "gfxTypes.h" +#include "nsString.h" +#include "gfxFontFeatures.h" +#include "gfxFontUtils.h" +#include "nsTArray.h" +#include "nsTHashtable.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/MemoryReporting.h" +#include "DrawMode.h" +#include "nsUnicodeScriptCodes.h" +#include "nsDataHashtable.h" +#include "harfbuzz/hb.h" +#include "mozilla/gfx/2D.h" + +typedef struct gr_face gr_face; + +#ifdef DEBUG +#include +#endif + +struct gfxFontStyle; +class gfxContext; +class gfxFont; +class gfxFontFamily; +class gfxUserFontData; +class gfxSVGGlyphs; +class gfxMathTable; +class gfxTextContextPaint; +class FontInfoData; +struct FontListSizes; +class nsIAtom; + +class gfxCharacterMap : public gfxSparseBitSet { +public: + nsrefcnt AddRef() { + NS_PRECONDITION(int32_t(mRefCnt) >= 0, "illegal refcnt"); + ++mRefCnt; + NS_LOG_ADDREF(this, mRefCnt, "gfxCharacterMap", sizeof(*this)); + return mRefCnt; + } + + nsrefcnt Release() { + NS_PRECONDITION(0 != mRefCnt, "dup release"); + --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "gfxCharacterMap"); + if (mRefCnt == 0) { + NotifyReleased(); + // |this| has been deleted. + return 0; + } + return mRefCnt; + } + + gfxCharacterMap() : + mHash(0), mBuildOnTheFly(false), mShared(false) + { } + + void CalcHash() { mHash = GetChecksum(); } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return gfxSparseBitSet::SizeOfExcludingThis(aMallocSizeOf); + } + + // hash of the cmap bitvector + uint32_t mHash; + + // if cmap is built on the fly it's never shared + bool mBuildOnTheFly; + + // cmap is shared globally + bool mShared; + +protected: + void NotifyReleased(); + + nsAutoRefCnt mRefCnt; + +private: + gfxCharacterMap(const gfxCharacterMap&); + gfxCharacterMap& operator=(const gfxCharacterMap&); +}; + +class gfxFontEntry { +public: + NS_INLINE_DECL_REFCOUNTING(gfxFontEntry) + + explicit gfxFontEntry(const nsAString& aName, bool aIsStandardFace = false); + + // unique name for the face, *not* the family; not necessarily the + // "real" or user-friendly name, may be an internal identifier + const nsString& Name() const { return mName; } + + // family name + const nsString& FamilyName() const { return mFamilyName; } + + // The following two methods may be relatively expensive, as they + // will (usually, except on Linux) load and parse the 'name' table; + // they are intended only for the font-inspection API, not for + // perf-critical layout/drawing work. + + // The "real" name of the face, if available from the font resource; + // returns Name() if nothing better is available. + virtual nsString RealFaceName(); + + uint16_t Weight() const { return mWeight; } + int16_t Stretch() const { return mStretch; } + + bool IsUserFont() const { return mIsDataUserFont || mIsLocalUserFont; } + bool IsLocalUserFont() const { return mIsLocalUserFont; } + bool IsFixedPitch() const { return mFixedPitch; } + bool IsItalic() const { return mItalic; } + bool IsBold() const { return mWeight >= 600; } // bold == weights 600 and above + bool IgnoreGDEF() const { return mIgnoreGDEF; } + bool IgnoreGSUB() const { return mIgnoreGSUB; } + + // whether a feature is supported by the font (limited to a small set + // of features for which some form of fallback needs to be implemented) + bool SupportsOpenTypeFeature(int32_t aScript, uint32_t aFeatureTag); + bool SupportsGraphiteFeature(uint32_t aFeatureTag); + + // returns a set containing all input glyph ids for a given feature + const hb_set_t* + InputsForOpenTypeFeature(int32_t aScript, uint32_t aFeatureTag); + + virtual bool IsSymbolFont(); + + virtual bool HasFontTable(uint32_t aTableTag); + + inline bool HasGraphiteTables() { + if (!mCheckedForGraphiteTables) { + CheckForGraphiteTables(); + mCheckedForGraphiteTables = true; + } + return mHasGraphiteTables; + } + + inline bool HasCmapTable() { + if (!mCharacterMap) { + ReadCMAP(); + NS_ASSERTION(mCharacterMap, "failed to initialize character map"); + } + return mHasCmapTable; + } + + inline bool HasCharacter(uint32_t ch) { + if (mCharacterMap && mCharacterMap->test(ch)) { + return true; + } + return TestCharacterMap(ch); + } + + virtual bool SkipDuringSystemFallback() { return false; } + virtual bool TestCharacterMap(uint32_t aCh); + nsresult InitializeUVSMap(); + uint16_t GetUVSGlyph(uint32_t aCh, uint32_t aVS); + + // All concrete gfxFontEntry subclasses (except gfxUserFontEntry) need + // to override this, otherwise the font will never be used as it will + // be considered to support no characters. + // ReadCMAP() must *always* set the mCharacterMap pointer to a valid + // gfxCharacterMap, even if empty, as other code assumes this pointer + // can be safely dereferenced. + virtual nsresult ReadCMAP(FontInfoData *aFontInfoData = nullptr); + + bool TryGetSVGData(gfxFont* aFont); + bool HasSVGGlyph(uint32_t aGlyphId); + bool GetSVGGlyphExtents(gfxContext *aContext, uint32_t aGlyphId, + gfxRect *aResult); + bool RenderSVGGlyph(gfxContext *aContext, uint32_t aGlyphId, int aDrawMode, + gfxTextContextPaint *aContextPaint); + // Call this when glyph geometry or rendering has changed + // (e.g. animated SVG glyphs) + void NotifyGlyphsChanged(); + + enum MathConstant { + // The order of the constants must match the order of the fields + // defined in the MATH table. + ScriptPercentScaleDown, + ScriptScriptPercentScaleDown, + DelimitedSubFormulaMinHeight, + DisplayOperatorMinHeight, + MathLeading, + AxisHeight, + AccentBaseHeight, + FlattenedAccentBaseHeight, + SubscriptShiftDown, + SubscriptTopMax, + SubscriptBaselineDropMin, + SuperscriptShiftUp, + SuperscriptShiftUpCramped, + SuperscriptBottomMin, + SuperscriptBaselineDropMax, + SubSuperscriptGapMin, + SuperscriptBottomMaxWithSubscript, + SpaceAfterScript, + UpperLimitGapMin, + UpperLimitBaselineRiseMin, + LowerLimitGapMin, + LowerLimitBaselineDropMin, + StackTopShiftUp, + StackTopDisplayStyleShiftUp, + StackBottomShiftDown, + StackBottomDisplayStyleShiftDown, + StackGapMin, + StackDisplayStyleGapMin, + StretchStackTopShiftUp, + StretchStackBottomShiftDown, + StretchStackGapAboveMin, + StretchStackGapBelowMin, + FractionNumeratorShiftUp, + FractionNumeratorDisplayStyleShiftUp, + FractionDenominatorShiftDown, + FractionDenominatorDisplayStyleShiftDown, + FractionNumeratorGapMin, + FractionNumDisplayStyleGapMin, + FractionRuleThickness, + FractionDenominatorGapMin, + FractionDenomDisplayStyleGapMin, + SkewedFractionHorizontalGap, + SkewedFractionVerticalGap, + OverbarVerticalGap, + OverbarRuleThickness, + OverbarExtraAscender, + UnderbarVerticalGap, + UnderbarRuleThickness, + UnderbarExtraDescender, + RadicalVerticalGap, + RadicalDisplayStyleVerticalGap, + RadicalRuleThickness, + RadicalExtraAscender, + RadicalKernBeforeDegree, + RadicalKernAfterDegree, + RadicalDegreeBottomRaisePercent + }; + + // Call TryGetMathTable to try to load the Open Type MATH table. The other + // functions forward the call to the gfxMathTable class. The GetMath...() + // functions MUST NOT be called unless TryGetMathTable() has returned true. + bool TryGetMathTable(); + gfxFloat GetMathConstant(MathConstant aConstant); + bool GetMathItalicsCorrection(uint32_t aGlyphID, + gfxFloat* aItalicCorrection); + uint32_t GetMathVariantsSize(uint32_t aGlyphID, bool aVertical, + uint16_t aSize); + bool GetMathVariantsParts(uint32_t aGlyphID, bool aVertical, + uint32_t aGlyphs[4]); + + bool TryGetColorGlyphs(); + bool GetColorLayersInfo(uint32_t aGlyphId, + nsTArray& layerGlyphs, + nsTArray& layerColors); + + virtual bool MatchesGenericFamily(const nsACString& aGeneric) const { + return true; + } + virtual bool SupportsLangGroup(nsIAtom *aLangGroup) const { + return true; + } + + // Access to raw font table data (needed for Harfbuzz): + // returns a pointer to data owned by the fontEntry or the OS, + // which will remain valid until the blob is destroyed. + // The data MUST be treated as read-only; we may be getting a + // reference to a shared system font cache. + // + // The default implementation uses CopyFontTable to get the data + // into a byte array, and maintains a cache of loaded tables. + // + // Subclasses should override this if they can provide more efficient + // access than copying table data into our own buffers. + // + // Get blob that encapsulates a specific font table, or nullptr if + // the table doesn't exist in the font. + // + // Caller is responsible to call hb_blob_destroy() on the returned blob + // (if non-nullptr) when no longer required. For transient access to a + // table, use of AutoTable (below) is generally preferred. + virtual hb_blob_t *GetFontTable(uint32_t aTag); + + // Stack-based utility to return a specified table, automatically releasing + // the blob when the AutoTable goes out of scope. + class AutoTable { + public: + AutoTable(gfxFontEntry* aFontEntry, uint32_t aTag) + { + mBlob = aFontEntry->GetFontTable(aTag); + } + ~AutoTable() { + if (mBlob) { + hb_blob_destroy(mBlob); + } + } + operator hb_blob_t*() const { return mBlob; } + private: + hb_blob_t* mBlob; + // not implemented: + AutoTable(const AutoTable&) MOZ_DELETE; + AutoTable& operator=(const AutoTable&) MOZ_DELETE; + }; + + already_AddRefed FindOrMakeFont(const gfxFontStyle *aStyle, + bool aNeedsBold); + + // Get an existing font table cache entry in aBlob if it has been + // registered, or return false if not. Callers must call + // hb_blob_destroy on aBlob if true is returned. + // + // Note that some gfxFont implementations may not call this at all, + // if it is more efficient to get the table from the OS at that level. + bool GetExistingFontTable(uint32_t aTag, hb_blob_t** aBlob); + + // Elements of aTable are transferred (not copied) to and returned in a + // new hb_blob_t which is registered on the gfxFontEntry, but the initial + // reference is owned by the caller. Removing the last reference + // unregisters the table from the font entry. + // + // Pass nullptr for aBuffer to indicate that the table is not present and + // nullptr will be returned. Also returns nullptr on OOM. + hb_blob_t *ShareFontTableAndGetBlob(uint32_t aTag, + FallibleTArray* aTable); + + // Get the font's unitsPerEm from the 'head' table, in the case of an + // sfnt resource. Will return kInvalidUPEM for non-sfnt fonts, + // if present on the platform. + uint16_t UnitsPerEm(); + enum { + kMinUPEM = 16, // Limits on valid unitsPerEm range, from the + kMaxUPEM = 16384, // OpenType spec + kInvalidUPEM = uint16_t(-1) + }; + + // Shaper face accessors: + // NOTE that harfbuzz and graphite handle ownership/lifetime of the face + // object in completely different ways. + + // Get HarfBuzz face corresponding to this font file. + // Caller must release with hb_face_destroy() when finished with it, + // and the font entry will be notified via ForgetHBFace. + hb_face_t* GetHBFace(); + virtual void ForgetHBFace(); + + // Get Graphite face corresponding to this font file. + // Caller must call gfxFontEntry::ReleaseGrFace when finished with it. + gr_face* GetGrFace(); + virtual void ReleaseGrFace(gr_face* aFace); + + // Release any SVG-glyphs document this font may have loaded. + void DisconnectSVG(); + + // Called to notify that aFont is being destroyed. Needed when we're tracking + // the fonts belonging to this font entry. + void NotifyFontDestroyed(gfxFont* aFont); + + // For memory reporting + virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const; + virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const; + + // Used when checking for complex script support, to mask off cmap ranges + struct ScriptRange { + uint32_t rangeStart; + uint32_t rangeEnd; + hb_tag_t tags[3]; // one or two OpenType script tags to check, + // plus a NULL terminator + }; + + bool SupportsScriptInGSUB(const hb_tag_t* aScriptTags); + + nsString mName; + nsString mFamilyName; + + bool mItalic : 1; + bool mFixedPitch : 1; + bool mIsValid : 1; + bool mIsBadUnderlineFont : 1; + bool mIsUserFontContainer : 1; // userfont entry + bool mIsDataUserFont : 1; // platform font entry (data) + bool mIsLocalUserFont : 1; // platform font entry (local) + bool mStandardFace : 1; + bool mSymbolFont : 1; + bool mIgnoreGDEF : 1; + bool mIgnoreGSUB : 1; + bool mSVGInitialized : 1; + bool mMathInitialized : 1; + bool mHasSpaceFeaturesInitialized : 1; + bool mHasSpaceFeatures : 1; + bool mHasSpaceFeaturesKerning : 1; + bool mHasSpaceFeaturesNonKerning : 1; + bool mSkipDefaultFeatureSpaceCheck : 1; + bool mHasGraphiteTables : 1; + bool mCheckedForGraphiteTables : 1; + bool mHasCmapTable : 1; + bool mGrFaceInitialized : 1; + bool mCheckedForColorGlyph : 1; + + // bitvector of substitution space features per script, one each + // for default and non-default features + uint32_t mDefaultSubSpaceFeatures[(MOZ_NUM_SCRIPT_CODES + 31) / 32]; + uint32_t mNonDefaultSubSpaceFeatures[(MOZ_NUM_SCRIPT_CODES + 31) / 32]; + + uint16_t mWeight; + int16_t mStretch; + + nsRefPtr mCharacterMap; + uint32_t mUVSOffset; + nsAutoArrayPtr mUVSData; + nsAutoPtr mUserFontData; + nsAutoPtr mSVGGlyphs; + // list of gfxFonts that are using SVG glyphs + nsTArray mFontsUsingSVGGlyphs; + nsAutoPtr mMathTable; + nsTArray mFeatureSettings; + nsAutoPtr> mSupportedFeatures; + nsAutoPtr> mFeatureInputs; + uint32_t mLanguageOverride; + + // Color Layer font support + hb_blob_t* mCOLR; + hb_blob_t* mCPAL; + +protected: + friend class gfxPlatformFontList; + friend class gfxMacPlatformFontList; + friend class gfxUserFcFontEntry; + friend class gfxFontFamily; + friend class gfxSingleFaceMacFontFamily; + + gfxFontEntry(); + + // Protected destructor, to discourage deletion outside of Release(): + virtual ~gfxFontEntry(); + + virtual gfxFont *CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold) { + NS_NOTREACHED("oops, somebody didn't override CreateFontInstance"); + return nullptr; + } + + virtual void CheckForGraphiteTables(); + + // Copy a font table into aBuffer. + // The caller will be responsible for ownership of the data. + virtual nsresult CopyFontTable(uint32_t aTableTag, + FallibleTArray& aBuffer) { + NS_NOTREACHED("forgot to override either GetFontTable or CopyFontTable?"); + return NS_ERROR_FAILURE; + } + + // Return a blob that wraps a table found within a buffer of font data. + // The blob does NOT own its data; caller guarantees that the buffer + // will remain valid at least as long as the blob. + // Returns null if the specified table is not found. + // This method assumes aFontData is valid 'sfnt' data; before using this, + // caller is responsible to do any sanitization/validation necessary. + hb_blob_t* GetTableFromFontData(const void* aFontData, uint32_t aTableTag); + + // lookup the cmap in cached font data + virtual already_AddRefed + GetCMAPFromFontInfo(FontInfoData *aFontInfoData, + uint32_t& aUVSOffset, + bool& aSymbolFont); + + // Font's unitsPerEm from the 'head' table, if available (will be set to + // kInvalidUPEM for non-sfnt font formats) + uint16_t mUnitsPerEm; + + // Shaper-specific face objects, shared by all instantiations of the same + // physical font, regardless of size. + // Usually, only one of these will actually be created for any given font + // entry, depending on the font tables that are present. + + // hb_face_t is refcounted internally, so each shaper that's using it will + // bump the ref count when it acquires the face, and "destroy" (release) it + // in its destructor. The font entry has only this non-owning reference to + // the face; when the face is deleted, it will tell the font entry to forget + // it, so that a new face will be created next time it is needed. + hb_face_t* mHBFace; + + static hb_blob_t* HBGetTable(hb_face_t *face, uint32_t aTag, void *aUserData); + + // Callback that the hb_face will use to tell us when it is being deleted. + static void HBFaceDeletedCallback(void *aUserData); + + // gr_face is -not- refcounted, so it will be owned directly by the font + // entry, and we'll keep a count of how many references we've handed out; + // each shaper is responsible to call ReleaseGrFace on its entry when + // finished with it, so that we know when it can be deleted. + gr_face* mGrFace; + + // hashtable to map raw table data ptr back to its owning blob, for use by + // graphite table-release callback + nsDataHashtable,void*>* mGrTableMap; + + // number of current users of this entry's mGrFace + nsrefcnt mGrFaceRefCnt; + + static const void* GrGetTable(const void *aAppFaceHandle, + unsigned int aName, + size_t *aLen); + static void GrReleaseTable(const void *aAppFaceHandle, + const void *aTableBuffer); + +private: + /** + * Font table hashtable, to support GetFontTable for harfbuzz. + * + * The harfbuzz shaper (and potentially other clients) needs access to raw + * font table data. This needs to be cached so that it can be used + * repeatedly (each time we construct a text run; in some cases, for + * each character/glyph within the run) without re-fetching large tables + * every time. + * + * Because we may instantiate many gfxFonts for the same physical font + * file (at different sizes), we should ensure that they can share a + * single cached copy of the font tables. To do this, we implement table + * access and sharing on the fontEntry rather than the font itself. + * + * The default implementation uses GetFontTable() to read font table + * data into byte arrays, and wraps them in blobs which are registered in + * a hashtable. The hashtable can then return pre-existing blobs to + * harfbuzz. + * + * Harfbuzz will "destroy" the blobs when it is finished with them. When + * the last blob reference is removed, the FontTableBlobData user data + * will remove the blob from the hashtable if still registered. + */ + + class FontTableBlobData; + + /** + * FontTableHashEntry manages the entries of hb_blob_t's containing font + * table data. + * + * This is used to share font tables across fonts with the same + * font entry (but different sizes) for use by HarfBuzz. The hashtable + * does not own a strong reference to the blob, but keeps a weak pointer, + * managed by FontTableBlobData. Similarly FontTableBlobData keeps only a + * weak pointer to the hashtable, managed by FontTableHashEntry. + */ + + class FontTableHashEntry : public nsUint32HashKey + { + public: + // Declarations for nsTHashtable + + typedef nsUint32HashKey KeyClass; + typedef KeyClass::KeyType KeyType; + typedef KeyClass::KeyTypePointer KeyTypePointer; + + explicit FontTableHashEntry(KeyTypePointer aTag) + : KeyClass(aTag) + , mSharedBlobData(nullptr) + , mBlob(nullptr) + { } + + // NOTE: This assumes the new entry belongs to the same hashtable as + // the old, because the mHashtable pointer in mSharedBlobData (if + // present) will not be updated. + FontTableHashEntry(FontTableHashEntry&& toMove) + : KeyClass(mozilla::Move(toMove)) + , mSharedBlobData(mozilla::Move(toMove.mSharedBlobData)) + , mBlob(mozilla::Move(toMove.mBlob)) + { + toMove.mSharedBlobData = nullptr; + toMove.mBlob = nullptr; + } + + ~FontTableHashEntry() { Clear(); } + + // FontTable/Blob API + + // Transfer (not copy) elements of aTable to a new hb_blob_t and + // return ownership to the caller. A weak reference to the blob is + // recorded in the hashtable entry so that others may use the same + // table. + hb_blob_t * + ShareTableAndGetBlob(FallibleTArray& aTable, + nsTHashtable *aHashtable); + + // Return a strong reference to the blob. + // Callers must hb_blob_destroy the returned blob. + hb_blob_t *GetBlob() const; + + void Clear(); + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + private: + static void DeleteFontTableBlobData(void *aBlobData); + // not implemented + FontTableHashEntry& operator=(FontTableHashEntry& toCopy); + + FontTableBlobData *mSharedBlobData; + hb_blob_t *mBlob; + }; + + nsAutoPtr > mFontTableCache; + + gfxFontEntry(const gfxFontEntry&); + gfxFontEntry& operator=(const gfxFontEntry&); +}; + + +// used when iterating over all fonts looking for a match for a given character +struct GlobalFontMatch { + GlobalFontMatch(const uint32_t aCharacter, + int32_t aRunScript, + const gfxFontStyle *aStyle) : + mCh(aCharacter), mRunScript(aRunScript), mStyle(aStyle), + mMatchRank(0), mCount(0), mCmapsTested(0) + { + + } + + const uint32_t mCh; // codepoint to be matched + int32_t mRunScript; // Unicode script for the codepoint + const gfxFontStyle* mStyle; // style to match + int32_t mMatchRank; // metric indicating closest match + nsRefPtr mBestMatch; // current best match + nsRefPtr mMatchedFamily; // the family it belongs to + uint32_t mCount; // number of fonts matched + uint32_t mCmapsTested; // number of cmaps tested +}; + +class gfxFontFamily { +public: + NS_INLINE_DECL_REFCOUNTING(gfxFontFamily) + + explicit gfxFontFamily(const nsAString& aName) : + mName(aName), + mOtherFamilyNamesInitialized(false), + mHasOtherFamilyNames(false), + mFaceNamesInitialized(false), + mHasStyles(false), + mIsSimpleFamily(false), + mIsBadUnderlineFamily(false), + mFamilyCharacterMapInitialized(false), + mSkipDefaultFeatureSpaceCheck(false) + { } + + const nsString& Name() { return mName; } + + virtual void LocalizedName(nsAString& aLocalizedName); + virtual bool HasOtherFamilyNames(); + + nsTArray >& GetFontList() { return mAvailableFonts; } + + void AddFontEntry(nsRefPtr aFontEntry) { + // bug 589682 - set the IgnoreGDEF flag on entries for Italic faces + // of Times New Roman, because of buggy table in those fonts + if (aFontEntry->IsItalic() && !aFontEntry->IsUserFont() && + Name().EqualsLiteral("Times New Roman")) + { + aFontEntry->mIgnoreGDEF = true; + } + if (aFontEntry->mFamilyName.IsEmpty()) { + aFontEntry->mFamilyName = Name(); + } else { + MOZ_ASSERT(aFontEntry->mFamilyName.Equals(Name())); + } + aFontEntry->mSkipDefaultFeatureSpaceCheck = mSkipDefaultFeatureSpaceCheck; + mAvailableFonts.AppendElement(aFontEntry); + } + + // note that the styles for this family have been added + bool HasStyles() { return mHasStyles; } + void SetHasStyles(bool aHasStyles) { mHasStyles = aHasStyles; } + + // choose a specific face to match a style using CSS font matching + // rules (weight matching occurs here). may return a face that doesn't + // precisely match (e.g. normal face when no italic face exists). + // aNeedsSyntheticBold is set to true when synthetic bolding is + // needed, false otherwise + gfxFontEntry *FindFontForStyle(const gfxFontStyle& aFontStyle, + bool& aNeedsSyntheticBold); + + // checks for a matching font within the family + // used as part of the font fallback process + void FindFontForChar(GlobalFontMatch *aMatchData); + + // checks all fonts for a matching font within the family + void SearchAllFontsForChar(GlobalFontMatch *aMatchData); + + // read in other family names, if any, and use functor to add each into cache + virtual void ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList); + + // helper method for reading localized family names from the name table + // of a single face + static void ReadOtherFamilyNamesForFace(const nsAString& aFamilyName, + const char *aNameData, + uint32_t aDataLength, + nsTArray& aOtherFamilyNames, + bool useFullName); + + // set when other family names have been read in + void SetOtherFamilyNamesInitialized() { + mOtherFamilyNamesInitialized = true; + } + + // read in other localized family names, fullnames and Postscript names + // for all faces and append to lookup tables + virtual void ReadFaceNames(gfxPlatformFontList *aPlatformFontList, + bool aNeedFullnamePostscriptNames, + FontInfoData *aFontInfoData = nullptr); + + // find faces belonging to this family (platform implementations override this; + // should be made pure virtual once all subclasses have been updated) + virtual void FindStyleVariations(FontInfoData *aFontInfoData = nullptr) { } + + // search for a specific face using the Postscript name + gfxFontEntry* FindFont(const nsAString& aPostscriptName); + + // read in cmaps for all the faces + void ReadAllCMAPs(FontInfoData *aFontInfoData = nullptr); + + bool TestCharacterMap(uint32_t aCh) { + if (!mFamilyCharacterMapInitialized) { + ReadAllCMAPs(); + } + return mFamilyCharacterMap.test(aCh); + } + + void ResetCharacterMap() { + mFamilyCharacterMap.reset(); + mFamilyCharacterMapInitialized = false; + } + + // mark this family as being in the "bad" underline offset blacklist + void SetBadUnderlineFamily() { + mIsBadUnderlineFamily = true; + if (mHasStyles) { + SetBadUnderlineFonts(); + } + } + + bool IsBadUnderlineFamily() const { return mIsBadUnderlineFamily; } + + // sort available fonts to put preferred (standard) faces towards the end + void SortAvailableFonts(); + + // check whether the family fits into the simple 4-face model, + // so we can use simplified style-matching; + // if so set the mIsSimpleFamily flag (defaults to False before we've checked) + void CheckForSimpleFamily(); + + // For memory reporter + virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const; + virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const; + +#ifdef DEBUG + // Only used for debugging checks - does a linear search + bool ContainsFace(gfxFontEntry* aFontEntry); +#endif + + void SetSkipSpaceFeatureCheck(bool aSkipCheck) { + mSkipDefaultFeatureSpaceCheck = aSkipCheck; + } + +protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~gfxFontFamily() + { + } + + // fills in an array with weights of faces that match style, + // returns whether any matching entries found + virtual bool FindWeightsForStyle(gfxFontEntry* aFontsForWeights[], + bool anItalic, int16_t aStretch); + + bool ReadOtherFamilyNamesForFace(gfxPlatformFontList *aPlatformFontList, + hb_blob_t *aNameTable, + bool useFullName = false); + + // set whether this font family is in "bad" underline offset blacklist. + void SetBadUnderlineFonts() { + uint32_t i, numFonts = mAvailableFonts.Length(); + for (i = 0; i < numFonts; i++) { + if (mAvailableFonts[i]) { + mAvailableFonts[i]->mIsBadUnderlineFont = true; + } + } + } + + nsString mName; + nsTArray > mAvailableFonts; + gfxSparseBitSet mFamilyCharacterMap; + bool mOtherFamilyNamesInitialized : 1; + bool mHasOtherFamilyNames : 1; + bool mFaceNamesInitialized : 1; + bool mHasStyles : 1; + bool mIsSimpleFamily : 1; + bool mIsBadUnderlineFamily : 1; + bool mFamilyCharacterMapInitialized : 1; + bool mSkipDefaultFeatureSpaceCheck : 1; + + enum { + // for "simple" families, the faces are stored in mAvailableFonts + // with fixed positions: + kRegularFaceIndex = 0, + kBoldFaceIndex = 1, + kItalicFaceIndex = 2, + kBoldItalicFaceIndex = 3, + // mask values for selecting face with bold and/or italic attributes + kBoldMask = 0x01, + kItalicMask = 0x02 + }; +}; + +#endif diff --git a/gfx/thebes/gfxGDIFont.cpp b/gfx/thebes/gfxGDIFont.cpp index 0dfad23f5f7b..3472d70c000a 100644 --- a/gfx/thebes/gfxGDIFont.cpp +++ b/gfx/thebes/gfxGDIFont.cpp @@ -14,6 +14,7 @@ #include "mozilla/Preferences.h" #include "nsUnicodeProperties.h" #include "gfxFontConstants.h" +#include "gfxTextRun.h" #include "cairo-win32.h" diff --git a/gfx/thebes/gfxGlyphExtents.cpp b/gfx/thebes/gfxGlyphExtents.cpp new file mode 100644 index 000000000000..0f5bd16e7d0d --- /dev/null +++ b/gfx/thebes/gfxGlyphExtents.cpp @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "gfxGlyphExtents.h" +#include "gfxTextRun.h" + +using namespace mozilla; + +#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 + +gfxGlyphExtents::~gfxGlyphExtents() +{ +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + gGlyphExtentsWidthsTotalSize += + mContainedGlyphWidths.SizeOfExcludingThis(&FontCacheMallocSizeOf); + gGlyphExtentsCount++; +#endif + MOZ_COUNT_DTOR(gfxGlyphExtents); +} + +bool +gfxGlyphExtents::GetTightGlyphExtentsAppUnits(gfxFont *aFont, + gfxContext *aContext, uint32_t aGlyphID, gfxRect *aExtents) +{ + HashEntry *entry = mTightGlyphExtents.GetEntry(aGlyphID); + if (!entry) { + if (!aContext) { + NS_WARNING("Could not get glyph extents (no aContext)"); + return false; + } + + if (aFont->SetupCairoFont(aContext)) { +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + ++gGlyphExtentsSetupLazyTight; +#endif + aFont->SetupGlyphExtents(aContext, aGlyphID, true, this); + entry = mTightGlyphExtents.GetEntry(aGlyphID); + } + if (!entry) { + NS_WARNING("Could not get glyph extents"); + return false; + } + } + + *aExtents = gfxRect(entry->x, entry->y, entry->width, entry->height); + return true; +} + +gfxGlyphExtents::GlyphWidths::~GlyphWidths() +{ + uint32_t i, count = mBlocks.Length(); + for (i = 0; i < count; ++i) { + uintptr_t bits = mBlocks[i]; + if (bits && !(bits & 0x1)) { + delete[] reinterpret_cast(bits); + } + } +} + +uint32_t +gfxGlyphExtents::GlyphWidths::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const +{ + uint32_t i; + uint32_t size = mBlocks.SizeOfExcludingThis(aMallocSizeOf); + for (i = 0; i < mBlocks.Length(); ++i) { + uintptr_t bits = mBlocks[i]; + if (bits && !(bits & 0x1)) { + size += aMallocSizeOf(reinterpret_cast(bits)); + } + } + return size; +} + +void +gfxGlyphExtents::GlyphWidths::Set(uint32_t aGlyphID, uint16_t aWidth) +{ + uint32_t block = aGlyphID >> BLOCK_SIZE_BITS; + uint32_t len = mBlocks.Length(); + if (block >= len) { + uintptr_t *elems = mBlocks.AppendElements(block + 1 - len); + if (!elems) + return; + memset(elems, 0, sizeof(uintptr_t)*(block + 1 - len)); + } + + uintptr_t bits = mBlocks[block]; + uint32_t glyphOffset = aGlyphID & (BLOCK_SIZE - 1); + if (!bits) { + mBlocks[block] = MakeSingle(glyphOffset, aWidth); + return; + } + + uint16_t *newBlock; + if (bits & 0x1) { + // Expand the block to a real block. We could avoid this by checking + // glyphOffset == GetGlyphOffset(bits), but that never happens so don't bother + newBlock = new uint16_t[BLOCK_SIZE]; + if (!newBlock) + return; + uint32_t i; + for (i = 0; i < BLOCK_SIZE; ++i) { + newBlock[i] = INVALID_WIDTH; + } + newBlock[GetGlyphOffset(bits)] = GetWidth(bits); + mBlocks[block] = reinterpret_cast(newBlock); + } else { + newBlock = reinterpret_cast(bits); + } + newBlock[glyphOffset] = aWidth; +} + +void +gfxGlyphExtents::SetTightGlyphExtents(uint32_t aGlyphID, const gfxRect& aExtentsAppUnits) +{ + HashEntry *entry = mTightGlyphExtents.PutEntry(aGlyphID); + if (!entry) + return; + entry->x = aExtentsAppUnits.X(); + entry->y = aExtentsAppUnits.Y(); + entry->width = aExtentsAppUnits.Width(); + entry->height = aExtentsAppUnits.Height(); +} + +size_t +gfxGlyphExtents::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const +{ + return mContainedGlyphWidths.SizeOfExcludingThis(aMallocSizeOf) + + mTightGlyphExtents.SizeOfExcludingThis(nullptr, aMallocSizeOf); +} + +size_t +gfxGlyphExtents::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} diff --git a/gfx/thebes/gfxGlyphExtents.h b/gfx/thebes/gfxGlyphExtents.h new file mode 100644 index 000000000000..59ec4225d55f --- /dev/null +++ b/gfx/thebes/gfxGlyphExtents.h @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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/. */ + +#ifndef GFX_GLYPHEXTENTS_H +#define GFX_GLYPHEXTENTS_H + +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "nsTArray.h" +#include "mozilla/MemoryReporting.h" + +class gfxContext; +class gfxFont; +struct gfxRect; + +/** + * This stores glyph bounds information for a particular gfxFont, at + * a particular appunits-per-dev-pixel ratio (because the compressed glyph + * width array is stored in appunits). + * + * We store a hashtable from glyph IDs to float bounding rects. For the + * common case where the glyph has no horizontal left bearing, and no + * y overflow above the font ascent or below the font descent, and tight + * bounding boxes are not required, we avoid storing the glyph ID in the hashtable + * and instead consult an array of 16-bit glyph XMost values (in appunits). + * This array always has an entry for the font's space glyph --- the width is + * assumed to be zero. + */ +class gfxGlyphExtents { +public: + explicit gfxGlyphExtents(int32_t aAppUnitsPerDevUnit) : + mAppUnitsPerDevUnit(aAppUnitsPerDevUnit) { + MOZ_COUNT_CTOR(gfxGlyphExtents); + } + ~gfxGlyphExtents(); + + enum { INVALID_WIDTH = 0xFFFF }; + + void NotifyGlyphsChanged() { + mTightGlyphExtents.Clear(); + } + + // returns INVALID_WIDTH => not a contained glyph + // Otherwise the glyph has no before-bearing or vertical bearings, + // and the result is its width measured from the baseline origin, in + // appunits. + uint16_t GetContainedGlyphWidthAppUnits(uint32_t aGlyphID) const { + return mContainedGlyphWidths.Get(aGlyphID); + } + + bool IsGlyphKnown(uint32_t aGlyphID) const { + return mContainedGlyphWidths.Get(aGlyphID) != INVALID_WIDTH || + mTightGlyphExtents.GetEntry(aGlyphID) != nullptr; + } + + bool IsGlyphKnownWithTightExtents(uint32_t aGlyphID) const { + return mTightGlyphExtents.GetEntry(aGlyphID) != nullptr; + } + + // Get glyph extents; a rectangle relative to the left baseline origin + // Returns true on success. Can fail on OOM or when aContext is null + // and extents were not (successfully) prefetched. + bool GetTightGlyphExtentsAppUnits(gfxFont *aFont, gfxContext *aContext, + uint32_t aGlyphID, gfxRect *aExtents); + + void SetContainedGlyphWidthAppUnits(uint32_t aGlyphID, uint16_t aWidth) { + mContainedGlyphWidths.Set(aGlyphID, aWidth); + } + void SetTightGlyphExtents(uint32_t aGlyphID, const gfxRect& aExtentsAppUnits); + + int32_t GetAppUnitsPerDevUnit() { return mAppUnitsPerDevUnit; } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + +private: + class HashEntry : public nsUint32HashKey { + public: + // When constructing a new entry in the hashtable, we'll leave this + // blank. The caller of Put() will fill this in. + explicit HashEntry(KeyTypePointer aPtr) : nsUint32HashKey(aPtr) {} + HashEntry(const HashEntry& toCopy) : nsUint32HashKey(toCopy) { + x = toCopy.x; y = toCopy.y; width = toCopy.width; height = toCopy.height; + } + + float x, y, width, height; + }; + + enum { BLOCK_SIZE_BITS = 7, BLOCK_SIZE = 1 << BLOCK_SIZE_BITS }; // 128-glyph blocks + + class GlyphWidths { + public: + void Set(uint32_t aIndex, uint16_t aValue); + uint16_t Get(uint32_t aIndex) const { + uint32_t block = aIndex >> BLOCK_SIZE_BITS; + if (block >= mBlocks.Length()) + return INVALID_WIDTH; + uintptr_t bits = mBlocks[block]; + if (!bits) + return INVALID_WIDTH; + uint32_t indexInBlock = aIndex & (BLOCK_SIZE - 1); + if (bits & 0x1) { + if (GetGlyphOffset(bits) != indexInBlock) + return INVALID_WIDTH; + return GetWidth(bits); + } + uint16_t *widths = reinterpret_cast(bits); + return widths[indexInBlock]; + } + + uint32_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + ~GlyphWidths(); + + private: + static uint32_t GetGlyphOffset(uintptr_t aBits) { + NS_ASSERTION(aBits & 0x1, "This is really a pointer..."); + return (aBits >> 1) & ((1 << BLOCK_SIZE_BITS) - 1); + } + static uint32_t GetWidth(uintptr_t aBits) { + NS_ASSERTION(aBits & 0x1, "This is really a pointer..."); + return aBits >> (1 + BLOCK_SIZE_BITS); + } + static uintptr_t MakeSingle(uint32_t aGlyphOffset, uint16_t aWidth) { + return (aWidth << (1 + BLOCK_SIZE_BITS)) + (aGlyphOffset << 1) + 1; + } + + nsTArray mBlocks; + }; + + GlyphWidths mContainedGlyphWidths; + nsTHashtable mTightGlyphExtents; + int32_t mAppUnitsPerDevUnit; + +private: + // not implemented: + gfxGlyphExtents(const gfxGlyphExtents& aOther) MOZ_DELETE; + gfxGlyphExtents& operator=(const gfxGlyphExtents& aOther) MOZ_DELETE; +}; + +#endif diff --git a/gfx/thebes/gfxGraphiteShaper.cpp b/gfx/thebes/gfxGraphiteShaper.cpp index 6c932ba52067..076b3df92552 100644 --- a/gfx/thebes/gfxGraphiteShaper.cpp +++ b/gfx/thebes/gfxGraphiteShaper.cpp @@ -7,6 +7,7 @@ #include "nsString.h" #include "gfxContext.h" #include "gfxFontConstants.h" +#include "gfxTextRun.h" #include "graphite2/Font.h" #include "graphite2/Segment.h" diff --git a/gfx/thebes/gfxHarfBuzzShaper.cpp b/gfx/thebes/gfxHarfBuzzShaper.cpp index 2e5f17a678bc..bbf4b8a2ead9 100644 --- a/gfx/thebes/gfxHarfBuzzShaper.cpp +++ b/gfx/thebes/gfxHarfBuzzShaper.cpp @@ -8,6 +8,7 @@ #include "gfxFontConstants.h" #include "gfxHarfBuzzShaper.h" #include "gfxFontUtils.h" +#include "gfxTextRun.h" #include "nsUnicodeProperties.h" #include "nsUnicodeScriptCodes.h" #include "nsUnicodeNormalizer.h" @@ -1009,7 +1010,7 @@ gfxHarfBuzzShaper::ShapeText(gfxContext *aContext, hb_buffer_set_direction(buffer, isRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR); hb_script_t scriptTag; - if (aShapedText->Flags() & gfxTextRunFactory::TEXT_USE_MATH_SCRIPT) { + if (aShapedText->GetFlags() & gfxTextRunFactory::TEXT_USE_MATH_SCRIPT) { scriptTag = sMathScript; } else { scriptTag = GetHBScriptUsedForShaping(aScript); diff --git a/gfx/thebes/gfxMacFont.cpp b/gfx/thebes/gfxMacFont.cpp index 8dd18d993e8e..0af44bcef4a8 100644 --- a/gfx/thebes/gfxMacFont.cpp +++ b/gfx/thebes/gfxMacFont.cpp @@ -14,6 +14,7 @@ #include "gfxFontUtils.h" #include "gfxMacPlatformFontList.h" #include "gfxFontConstants.h" +#include "gfxTextRun.h" #include "cairo-quartz.h" diff --git a/gfx/thebes/gfxPangoFonts.h b/gfx/thebes/gfxPangoFonts.h index ace7da75b84c..04175a083b72 100644 --- a/gfx/thebes/gfxPangoFonts.h +++ b/gfx/thebes/gfxPangoFonts.h @@ -8,7 +8,7 @@ #include "cairo.h" #include "gfxTypes.h" -#include "gfxFont.h" +#include "gfxTextRun.h" #include "nsAutoRef.h" #include "nsTArray.h" diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp index 3eaed23eff5b..78adb0b7d494 100644 --- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -18,6 +18,7 @@ #include "gfxPlatform.h" #include "gfxPrefs.h" +#include "gfxTextRun.h" #ifdef XP_WIN #include diff --git a/gfx/thebes/gfxPlatformFontList.cpp b/gfx/thebes/gfxPlatformFontList.cpp index b771f7098bec..f5c38fed0ec0 100644 --- a/gfx/thebes/gfxPlatformFontList.cpp +++ b/gfx/thebes/gfxPlatformFontList.cpp @@ -9,6 +9,7 @@ #include "prlog.h" #include "gfxPlatformFontList.h" +#include "gfxTextRun.h" #include "gfxUserFontSet.h" #include "nsUnicharUtils.h" diff --git a/gfx/thebes/gfxPlatformMac.cpp b/gfx/thebes/gfxPlatformMac.cpp index 6c09958454df..b1211adc5df8 100644 --- a/gfx/thebes/gfxPlatformMac.cpp +++ b/gfx/thebes/gfxPlatformMac.cpp @@ -12,6 +12,7 @@ #include "gfxMacPlatformFontList.h" #include "gfxMacFont.h" #include "gfxCoreTextShaper.h" +#include "gfxTextRun.h" #include "gfxUserFontSet.h" #include "nsTArray.h" diff --git a/gfx/thebes/gfxTextRun.cpp b/gfx/thebes/gfxTextRun.cpp new file mode 100644 index 000000000000..0deb4736a21c --- /dev/null +++ b/gfx/thebes/gfxTextRun.cpp @@ -0,0 +1,2724 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "gfxPlatformFontList.h" +#include "gfxUserFontSet.h" +#include "nsGkAtoms.h" +#include "nsILanguageAtomService.h" +#include "nsServiceManagerUtils.h" + +#include "gfxContext.h" +#include "gfxFontConstants.h" +#include "gfxFontMissingGlyphs.h" +#include "gfxScriptItemizer.h" +#include "nsUnicodeProperties.h" +#include "nsUnicodeRange.h" +#include "nsStyleConsts.h" +#include "mozilla/Likely.h" +#include "gfx2DGlue.h" + +#include "cairo.h" + +using namespace mozilla; +using namespace mozilla::unicode; + +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 + +bool +gfxTextRun::GlyphRunIterator::NextRun() { + if (mNextIndex >= mTextRun->mGlyphRuns.Length()) + return false; + mGlyphRun = &mTextRun->mGlyphRuns[mNextIndex]; + if (mGlyphRun->mCharacterOffset >= mEndOffset) + return false; + + mStringStart = std::max(mStartOffset, mGlyphRun->mCharacterOffset); + uint32_t last = mNextIndex + 1 < mTextRun->mGlyphRuns.Length() + ? mTextRun->mGlyphRuns[mNextIndex + 1].mCharacterOffset : mTextRun->GetLength(); + mStringEnd = std::min(mEndOffset, last); + + ++mNextIndex; + return true; +} + +#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 + +static bool +NeedsGlyphExtents(gfxTextRun *aTextRun) +{ + if (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX) + return true; + uint32_t numRuns; + const gfxTextRun::GlyphRun *glyphRuns = aTextRun->GetGlyphRuns(&numRuns); + for (uint32_t i = 0; i < numRuns; ++i) { + if (glyphRuns[i].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 = moz_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(storage) + aSize, 0, + aLength * sizeof(CompressedGlyph)); + + return storage; +} + +gfxTextRun * +gfxTextRun::Create(const gfxTextRunFactory::Parameters *aParams, + uint32_t aLength, gfxFontGroup *aFontGroup, uint32_t aFlags) +{ + void *storage = AllocateStorageForTextRun(sizeof(gfxTextRun), aLength); + if (!storage) { + return nullptr; + } + + return new (storage) gfxTextRun(aParams, aLength, aFontGroup, aFlags); +} + +gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters *aParams, + uint32_t aLength, gfxFontGroup *aFontGroup, uint32_t aFlags) + : gfxShapedText(aLength, aFlags, aParams->mAppUnitsPerDevUnit) + , mUserData(aParams->mUserData) + , mFontGroup(aFontGroup) + , mReleasedFontGroup(false) + , mShapingState(eShapingState_Normal) +{ + NS_ASSERTION(mAppUnitsPerDevUnit > 0, "Invalid app unit scale"); + MOZ_COUNT_CTOR(gfxTextRun); + NS_ADDREF(mFontGroup); + +#ifndef RELEASE_BUILD + gfxTextPerfMetrics *tp = aFontGroup->GetTextPerfMetrics(); + if (tp) { + tp->current.textrunConst++; + } +#endif + + mCharacterGlyphs = reinterpret_cast(this + 1); + + if (aParams->mSkipChars) { + mSkipChars.TakeFrom(aParams->mSkipChars); + } + +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + AccountStorageForTextRun(this, 1); +#endif + + mSkipDrawing = mFontGroup->ShouldSkipDrawing(); +} + +gfxTextRun::~gfxTextRun() +{ +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + AccountStorageForTextRun(this, -1); +#endif +#ifdef DEBUG + // Make it easy to detect a dead text run + mFlags = 0xFFFFFFFF; +#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_BUILD + gfxTextPerfMetrics *tp = mFontGroup->GetTextPerfMetrics(); + if (tp) { + tp->current.textrunDestr++; + } +#endif + NS_RELEASE(mFontGroup); + } + + MOZ_COUNT_DTOR(gfxTextRun); +} + +void +gfxTextRun::ReleaseFontGroup() +{ + NS_ASSERTION(!mReleasedFontGroup, "doubly released!"); + NS_RELEASE(mFontGroup); + mReleasedFontGroup = true; +} + +bool +gfxTextRun::SetPotentialLineBreaks(uint32_t aStart, uint32_t aLength, + uint8_t *aBreakBefore, + gfxContext *aRefContext) +{ + NS_ASSERTION(aStart + aLength <= GetLength(), "Overflow"); + + uint32_t changed = 0; + uint32_t i; + CompressedGlyph *charGlyphs = mCharacterGlyphs + aStart; + for (i = 0; i < aLength; ++i) { + uint8_t canBreak = aBreakBefore[i]; + if (canBreak && !charGlyphs[i].IsClusterStart()) { + // This can happen ... there is no guarantee that our linebreaking rules + // align with the platform's idea of what constitutes a cluster. + NS_WARNING("Break suggested inside cluster!"); + canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE; + } + changed |= charGlyphs[i].SetCanBreakBefore(canBreak); + } + return changed != 0; +} + +gfxTextRun::LigatureData +gfxTextRun::ComputeLigatureData(uint32_t aPartStart, uint32_t aPartEnd, + PropertyProvider *aProvider) +{ + NS_ASSERTION(aPartStart < aPartEnd, "Computing ligature data for empty range"); + NS_ASSERTION(aPartEnd <= GetLength(), "Character length overflow"); + + LigatureData result; + CompressedGlyph *charGlyphs = mCharacterGlyphs; + + uint32_t i; + for (i = aPartStart; !charGlyphs[i].IsLigatureGroupStart(); --i) { + NS_ASSERTION(i > 0, "Ligature at the start of the run??"); + } + result.mLigatureStart = i; + for (i = aPartStart + 1; i < GetLength() && !charGlyphs[i].IsLigatureGroupStart(); ++i) { + } + result.mLigatureEnd = i; + + int32_t ligatureWidth = + GetAdvanceForGlyphs(result.mLigatureStart, result.mLigatureEnd); + // Count the number of started clusters we have seen + uint32_t totalClusterCount = 0; + uint32_t partClusterIndex = 0; + uint32_t partClusterCount = 0; + for (i = result.mLigatureStart; i < result.mLigatureEnd; ++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.mLigatureStart || charGlyphs[i].IsClusterStart()) { + ++totalClusterCount; + if (i < aPartStart) { + ++partClusterIndex; + } else if (i < aPartEnd) { + ++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 (aPartEnd == result.mLigatureEnd) { + 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 & gfxTextRunFactory::TEXT_ENABLE_SPACING)) { + gfxFont::Spacing spacing; + if (aPartStart == result.mLigatureStart) { + aProvider->GetSpacing(aPartStart, 1, &spacing); + result.mPartWidth += spacing.mBefore; + } + if (aPartEnd == result.mLigatureEnd) { + aProvider->GetSpacing(aPartEnd - 1, 1, &spacing); + result.mPartWidth += spacing.mAfter; + } + } + + return result; +} + +gfxFloat +gfxTextRun::ComputePartialLigatureWidth(uint32_t aPartStart, uint32_t aPartEnd, + PropertyProvider *aProvider) +{ + if (aPartStart >= aPartEnd) + return 0; + LigatureData data = ComputeLigatureData(aPartStart, aPartEnd, aProvider); + return data.mPartWidth; +} + +int32_t +gfxTextRun::GetAdvanceForGlyphs(uint32_t aStart, uint32_t aEnd) +{ + const CompressedGlyph *glyphData = mCharacterGlyphs + aStart; + int32_t advance = 0; + uint32_t i; + for (i = aStart; i < aEnd; ++i, ++glyphData) { + if (glyphData->IsSimpleGlyph()) { + advance += glyphData->GetSimpleAdvance(); + } else { + uint32_t glyphCount = glyphData->GetGlyphCount(); + if (glyphCount == 0) { + continue; + } + const DetailedGlyph *details = GetDetailedGlyphs(i); + if (details) { + uint32_t j; + for (j = 0; j < glyphCount; ++j, ++details) { + advance += details->mAdvance; + } + } + } + } + return advance; +} + +static void +GetAdjustedSpacing(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd, + gfxTextRun::PropertyProvider *aProvider, + gfxTextRun::PropertyProvider::Spacing *aSpacing) +{ + if (aStart >= aEnd) + return; + + aProvider->GetSpacing(aStart, aEnd - aStart, aSpacing); + +#ifdef DEBUG + // Check to see if we have spacing inside ligatures + + const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs(); + uint32_t i; + + for (i = aStart; i < aEnd; ++i) { + if (!charGlyphs[i].IsLigatureGroupStart()) { + NS_ASSERTION(i == aStart || aSpacing[i - aStart].mBefore == 0, + "Before-spacing inside a ligature!"); + NS_ASSERTION(i - 1 <= aStart || aSpacing[i - 1 - aStart].mAfter == 0, + "After-spacing inside a ligature!"); + } + } +#endif +} + +bool +gfxTextRun::GetAdjustedSpacingArray(uint32_t aStart, uint32_t aEnd, + PropertyProvider *aProvider, + uint32_t aSpacingStart, uint32_t aSpacingEnd, + nsTArray *aSpacing) +{ + if (!aProvider || !(mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) + return false; + if (!aSpacing->AppendElements(aEnd - aStart)) + return false; + memset(aSpacing->Elements(), 0, sizeof(gfxFont::Spacing)*(aSpacingStart - aStart)); + GetAdjustedSpacing(this, aSpacingStart, aSpacingEnd, aProvider, + aSpacing->Elements() + aSpacingStart - aStart); + memset(aSpacing->Elements() + aSpacingEnd - aStart, 0, sizeof(gfxFont::Spacing)*(aEnd - aSpacingEnd)); + return true; +} + +void +gfxTextRun::ShrinkToLigatureBoundaries(uint32_t *aStart, uint32_t *aEnd) +{ + if (*aStart >= *aEnd) + return; + + CompressedGlyph *charGlyphs = mCharacterGlyphs; + + while (*aStart < *aEnd && !charGlyphs[*aStart].IsLigatureGroupStart()) { + ++(*aStart); + } + if (*aEnd < GetLength()) { + while (*aEnd > *aStart && !charGlyphs[*aEnd].IsLigatureGroupStart()) { + --(*aEnd); + } + } +} + +void +gfxTextRun::DrawGlyphs(gfxFont *aFont, uint32_t aStart, uint32_t aEnd, + gfxPoint *aPt, PropertyProvider *aProvider, + uint32_t aSpacingStart, uint32_t aSpacingEnd, + TextRunDrawParams& aParams) +{ + nsAutoTArray spacingBuffer; + bool haveSpacing = GetAdjustedSpacingArray(aStart, aEnd, aProvider, + aSpacingStart, aSpacingEnd, &spacingBuffer); + aParams.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr; + aFont->Draw(this, aStart, aEnd, aPt, aParams); +} + +static void +ClipPartialLigature(gfxTextRun *aTextRun, gfxFloat *aLeft, gfxFloat *aRight, + gfxFloat aXOrigin, gfxTextRun::LigatureData *aLigature) +{ + if (aLigature->mClipBeforePart) { + if (aTextRun->IsRightToLeft()) { + *aRight = std::min(*aRight, aXOrigin); + } else { + *aLeft = std::max(*aLeft, aXOrigin); + } + } + if (aLigature->mClipAfterPart) { + gfxFloat endEdge = aXOrigin + aTextRun->GetDirection()*aLigature->mPartWidth; + if (aTextRun->IsRightToLeft()) { + *aLeft = std::max(*aLeft, endEdge); + } else { + *aRight = std::min(*aRight, endEdge); + } + } +} + +void +gfxTextRun::DrawPartialLigature(gfxFont *aFont, uint32_t aStart, uint32_t aEnd, + gfxPoint *aPt, PropertyProvider *aProvider, + TextRunDrawParams& aParams) +{ + if (aStart >= aEnd) + return; + + // Draw partial ligature. We hack this by clipping the ligature. + LigatureData data = ComputeLigatureData(aStart, aEnd, aProvider); + gfxRect clipExtents = aParams.context->GetClipExtents(); + gfxFloat left = clipExtents.X() * mAppUnitsPerDevUnit; + gfxFloat right = clipExtents.XMost() * mAppUnitsPerDevUnit; + ClipPartialLigature(this, &left, &right, aPt->x, &data); + + { + // Need to preserve the path, otherwise this can break canvas text-on-path; + // in general it seems like a good thing, as naive callers probably won't + // expect gfxTextRun::Draw to implicitly destroy the current path. + gfxContextPathAutoSaveRestore savePath(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. + aParams.context->Save(); + aParams.context->NewPath(); + aParams.context->Rectangle(gfxRect(left / mAppUnitsPerDevUnit, + clipExtents.Y(), + (right - left) / mAppUnitsPerDevUnit, + clipExtents.Height()), true); + aParams.context->Clip(); + } + + gfxPoint pt(aPt->x - aParams.direction * data.mPartAdvance, aPt->y); + DrawGlyphs(aFont, data.mLigatureStart, data.mLigatureEnd, &pt, + aProvider, aStart, aEnd, aParams); + aParams.context->Restore(); + + aPt->x += aParams.direction * data.mPartWidth; +} + +// returns true if a glyph run is using a font with synthetic bolding enabled, false otherwise +static bool +HasSyntheticBold(gfxTextRun *aRun, uint32_t aStart, uint32_t aLength) +{ + gfxTextRun::GlyphRunIterator iter(aRun, aStart, aLength); + while (iter.NextRun()) { + gfxFont *font = iter.GetGlyphRun()->mFont; + if (font && font->IsSyntheticBold()) { + return true; + } + } + + return false; +} + +// returns true if color is non-opaque (i.e. alpha != 1.0) or completely transparent, false otherwise +// if true, color is set on output +static bool +HasNonOpaqueColor(gfxContext *aContext, gfxRGBA& aCurrentColor) +{ + if (aContext->GetDeviceColor(aCurrentColor)) { + if (aCurrentColor.a < 1.0 && aCurrentColor.a > 0.0) { + return true; + } + } + + return false; +} + +// helper class for double-buffering drawing with non-opaque color +struct BufferAlphaColor { + explicit BufferAlphaColor(gfxContext *aContext) + : mContext(aContext) + { + + } + + ~BufferAlphaColor() {} + + void PushSolidColor(const gfxRect& aBounds, const gfxRGBA& aAlphaColor, uint32_t appsPerDevUnit) + { + mContext->Save(); + mContext->NewPath(); + mContext->Rectangle(gfxRect(aBounds.X() / appsPerDevUnit, + aBounds.Y() / appsPerDevUnit, + aBounds.Width() / appsPerDevUnit, + aBounds.Height() / appsPerDevUnit), true); + mContext->Clip(); + mContext->SetColor(gfxRGBA(aAlphaColor.r, aAlphaColor.g, aAlphaColor.b)); + mContext->PushGroup(gfxContentType::COLOR_ALPHA); + mAlpha = aAlphaColor.a; + } + + void PopAlpha() + { + // pop the text, using the color alpha as the opacity + mContext->PopGroupToSource(); + mContext->SetOperator(gfxContext::OPERATOR_OVER); + mContext->Paint(mAlpha); + mContext->Restore(); + } + + gfxContext *mContext; + gfxFloat mAlpha; +}; + +void +gfxTextRun::Draw(gfxContext *aContext, gfxPoint aPt, DrawMode aDrawMode, + uint32_t aStart, uint32_t aLength, + PropertyProvider *aProvider, gfxFloat *aAdvanceWidth, + gfxTextContextPaint *aContextPaint, + gfxTextRunDrawCallbacks *aCallbacks) +{ + NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range"); + NS_ASSERTION(aDrawMode == DrawMode::GLYPH_PATH || + !(int(aDrawMode) & int(DrawMode::GLYPH_PATH)), + "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH"); + NS_ASSERTION(aDrawMode == DrawMode::GLYPH_PATH || !aCallbacks, + "callback must not be specified unless using GLYPH_PATH"); + + bool skipDrawing = mSkipDrawing; + if (aDrawMode == DrawMode::GLYPH_FILL) { + gfxRGBA currentColor; + if (aContext->GetDeviceColor(currentColor) && currentColor.a == 0) { + 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 (aAdvanceWidth) { + gfxTextRun::Metrics metrics = MeasureText(aStart, aLength, + gfxFont::LOOSE_INK_EXTENTS, + aContext, aProvider); + *aAdvanceWidth = metrics.mAdvanceWidth * direction; + } + + // return without drawing + return; + } + + // Set up parameters that will be constant across all glyph runs we need + // to draw, regardless of the font used. + TextRunDrawParams params; + params.context = aContext; + params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit()); + params.isRTL = IsRightToLeft(); + params.direction = direction; + params.drawMode = aDrawMode; + params.callbacks = aCallbacks; + params.runContextPaint = aContextPaint; + params.paintSVGGlyphs = !aCallbacks || aCallbacks->mShouldPaintSVGGlyphs; + params.dt = aContext->GetDrawTarget(); + + gfxPoint pt = aPt; + + // synthetic bolding draws glyphs twice ==> colors with opacity won't draw + // correctly unless first drawn without alpha + BufferAlphaColor syntheticBoldBuffer(aContext); + gfxRGBA currentColor; + bool needToRestore = false; + + if (aDrawMode == DrawMode::GLYPH_FILL && + HasNonOpaqueColor(aContext, currentColor) && + HasSyntheticBold(this, aStart, aLength)) { + needToRestore = true; + // measure text, use the bounding box + gfxTextRun::Metrics metrics = MeasureText(aStart, aLength, + gfxFont::LOOSE_INK_EXTENTS, + aContext, aProvider); + metrics.mBoundingBox.MoveBy(aPt); + syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor, + GetAppUnitsPerDevUnit()); + } + + GlyphRunIterator iter(this, aStart, aLength); + while (iter.NextRun()) { + gfxFont *font = iter.GetGlyphRun()->mFont; + uint32_t start = iter.GetStringStart(); + uint32_t end = iter.GetStringEnd(); + uint32_t ligatureRunStart = start; + uint32_t ligatureRunEnd = end; + ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); + + bool drawPartial = aDrawMode == DrawMode::GLYPH_FILL || + (aDrawMode == DrawMode::GLYPH_PATH && aCallbacks); + + if (drawPartial) { + DrawPartialLigature(font, start, ligatureRunStart, &pt, + aProvider, params); + } + + DrawGlyphs(font, ligatureRunStart, ligatureRunEnd, &pt, + aProvider, ligatureRunStart, ligatureRunEnd, params); + + if (drawPartial) { + DrawPartialLigature(font, ligatureRunEnd, end, &pt, + aProvider, params); + } + } + + // composite result when synthetic bolding used + if (needToRestore) { + syntheticBoldBuffer.PopAlpha(); + } + + if (aAdvanceWidth) { + *aAdvanceWidth = (pt.x - aPt.x)*direction; + } +} + +void +gfxTextRun::AccumulateMetricsForRun(gfxFont *aFont, + uint32_t aStart, uint32_t aEnd, + gfxFont::BoundingBoxType aBoundingBoxType, + gfxContext *aRefContext, + PropertyProvider *aProvider, + uint32_t aSpacingStart, uint32_t aSpacingEnd, + Metrics *aMetrics) +{ + nsAutoTArray spacingBuffer; + bool haveSpacing = GetAdjustedSpacingArray(aStart, aEnd, aProvider, + aSpacingStart, aSpacingEnd, &spacingBuffer); + Metrics metrics = aFont->Measure(this, aStart, aEnd, aBoundingBoxType, aRefContext, + haveSpacing ? spacingBuffer.Elements() : nullptr); + aMetrics->CombineWith(metrics, IsRightToLeft()); +} + +void +gfxTextRun::AccumulatePartialLigatureMetrics(gfxFont *aFont, + uint32_t aStart, uint32_t aEnd, + gfxFont::BoundingBoxType aBoundingBoxType, gfxContext *aRefContext, + PropertyProvider *aProvider, Metrics *aMetrics) +{ + if (aStart >= aEnd) + return; + + // Measure partial ligature. We hack this by clipping the metrics in the + // same way we clip the drawing. + LigatureData data = ComputeLigatureData(aStart, aEnd, aProvider); + + // First measure the complete ligature + Metrics metrics; + AccumulateMetricsForRun(aFont, data.mLigatureStart, data.mLigatureEnd, + aBoundingBoxType, aRefContext, + aProvider, aStart, aEnd, &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.x = bboxLeft; + metrics.mBoundingBox.width = bboxRight - bboxLeft; + + // mBoundingBox is now relative to the left baseline origin for the entire + // ligature. Shift it left. + metrics.mBoundingBox.x -= + IsRightToLeft() ? metrics.mAdvanceWidth - (data.mPartAdvance + data.mPartWidth) + : data.mPartAdvance; + metrics.mAdvanceWidth = data.mPartWidth; + + aMetrics->CombineWith(metrics, IsRightToLeft()); +} + +gfxTextRun::Metrics +gfxTextRun::MeasureText(uint32_t aStart, uint32_t aLength, + gfxFont::BoundingBoxType aBoundingBoxType, + gfxContext *aRefContext, + PropertyProvider *aProvider) +{ + NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range"); + + Metrics accumulatedMetrics; + GlyphRunIterator iter(this, aStart, aLength); + while (iter.NextRun()) { + gfxFont *font = iter.GetGlyphRun()->mFont; + uint32_t start = iter.GetStringStart(); + uint32_t end = iter.GetStringEnd(); + uint32_t ligatureRunStart = start; + uint32_t ligatureRunEnd = end; + ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); + + AccumulatePartialLigatureMetrics(font, start, ligatureRunStart, + aBoundingBoxType, aRefContext, aProvider, &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, + ligatureRunStart, ligatureRunEnd, aBoundingBoxType, + aRefContext, aProvider, ligatureRunStart, ligatureRunEnd, + &accumulatedMetrics); + + AccumulatePartialLigatureMetrics(font, ligatureRunEnd, end, + aBoundingBoxType, aRefContext, aProvider, &accumulatedMetrics); + } + + return accumulatedMetrics; +} + +#define MEASUREMENT_BUFFER_SIZE 100 + +uint32_t +gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength, + bool aLineBreakBefore, gfxFloat aWidth, + PropertyProvider *aProvider, + bool aSuppressInitialBreak, + gfxFloat *aTrimWhitespace, + Metrics *aMetrics, + gfxFont::BoundingBoxType aBoundingBoxType, + gfxContext *aRefContext, + bool *aUsedHyphenation, + uint32_t *aLastBreak, + bool aCanWordWrap, + gfxBreakPriority *aBreakPriority) +{ + aMaxLength = std::min(aMaxLength, GetLength() - aStart); + + NS_ASSERTION(aStart + aMaxLength <= GetLength(), "Substring out of range"); + + uint32_t bufferStart = aStart; + uint32_t bufferLength = std::min(aMaxLength, MEASUREMENT_BUFFER_SIZE); + PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE]; + bool haveSpacing = aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING) != 0; + if (haveSpacing) { + GetAdjustedSpacing(this, bufferStart, bufferStart + bufferLength, aProvider, + spacingBuffer); + } + bool hyphenBuffer[MEASUREMENT_BUFFER_SIZE]; + bool haveHyphenation = aProvider && + (aProvider->GetHyphensOption() == NS_STYLE_HYPHENS_AUTO || + (aProvider->GetHyphensOption() == NS_STYLE_HYPHENS_MANUAL && + (mFlags & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0)); + if (haveHyphenation) { + aProvider->GetHyphenationBreaks(bufferStart, bufferLength, + hyphenBuffer); + } + + gfxFloat width = 0; + gfxFloat advance = 0; + // The number of space characters that can be trimmed + 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; + bool aborted = false; + uint32_t end = aStart + aMaxLength; + bool lastBreakUsedHyphenation = false; + + uint32_t ligatureRunStart = aStart; + uint32_t ligatureRunEnd = end; + ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); + + uint32_t i; + for (i = aStart; i < end; ++i) { + if (i >= bufferStart + bufferLength) { + // Fetch more spacing and hyphenation data + bufferStart = i; + bufferLength = std::min(aStart + aMaxLength, i + MEASUREMENT_BUFFER_SIZE) - i; + if (haveSpacing) { + GetAdjustedSpacing(this, bufferStart, bufferStart + bufferLength, aProvider, + spacingBuffer); + } + if (haveHyphenation) { + aProvider->GetHyphenationBreaks(bufferStart, bufferLength, + hyphenBuffer); + } + } + + // 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 (!aSuppressInitialBreak || i > aStart) { + bool atNaturalBreak = mCharacterGlyphs[i].CanBreakBefore() == 1; + bool atHyphenationBreak = + !atNaturalBreak && haveHyphenation && hyphenBuffer[i - bufferStart]; + bool atBreak = atNaturalBreak || atHyphenationBreak; + bool wordWrapping = + aCanWordWrap && mCharacterGlyphs[i].IsClusterStart() && + *aBreakPriority <= gfxBreakPriority::eWordWrapBreak; + + if (atBreak || wordWrapping) { + 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 ? gfxBreakPriority::eNormalBreak + : gfxBreakPriority::eWordWrapBreak; + } + + width += advance; + advance = 0; + if (width - trimmableAdvance > aWidth) { + // No more text fits. Abort + aborted = true; + break; + } + } + } + + gfxFloat charAdvance; + if (i >= ligatureRunStart && i < ligatureRunEnd) { + charAdvance = GetAdvanceForGlyphs(i, i + 1); + if (haveSpacing) { + PropertyProvider::Spacing *space = &spacingBuffer[i - bufferStart]; + charAdvance += space->mBefore + space->mAfter; + } + } else { + charAdvance = ComputePartialLigatureWidth(i, i + 1, aProvider); + } + + advance += charAdvance; + if (aTrimWhitespace) { + 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; + bool usedHyphenation = false; + if (width - trimmableAdvance <= aWidth) { + charsFit = aMaxLength; + } else if (lastBreak >= 0) { + charsFit = lastBreak - aStart; + trimmableChars = lastBreakTrimmableChars; + trimmableAdvance = lastBreakTrimmableAdvance; + usedHyphenation = lastBreakUsedHyphenation; + } else { + charsFit = aMaxLength; + } + + if (aMetrics) { + *aMetrics = MeasureText(aStart, charsFit - trimmableChars, + aBoundingBoxType, aRefContext, aProvider); + } + if (aTrimWhitespace) { + *aTrimWhitespace = trimmableAdvance; + } + if (aUsedHyphenation) { + *aUsedHyphenation = usedHyphenation; + } + if (aLastBreak && charsFit == aMaxLength) { + if (lastBreak < 0) { + *aLastBreak = UINT32_MAX; + } else { + *aLastBreak = lastBreak - aStart; + } + } + + return charsFit; +} + +gfxFloat +gfxTextRun::GetAdvanceWidth(uint32_t aStart, uint32_t aLength, + PropertyProvider *aProvider) +{ + NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range"); + + uint32_t ligatureRunStart = aStart; + uint32_t ligatureRunEnd = aStart + aLength; + ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); + + gfxFloat result = ComputePartialLigatureWidth(aStart, ligatureRunStart, aProvider) + + ComputePartialLigatureWidth(ligatureRunEnd, aStart + aLength, aProvider); + + // Account for all remaining spacing here. This is more efficient than + // processing it along with the glyphs. + if (aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) { + uint32_t i; + nsAutoTArray spacingBuffer; + if (spacingBuffer.AppendElements(aLength)) { + GetAdjustedSpacing(this, ligatureRunStart, ligatureRunEnd, aProvider, + spacingBuffer.Elements()); + for (i = 0; i < ligatureRunEnd - ligatureRunStart; ++i) { + PropertyProvider::Spacing *space = &spacingBuffer[i]; + result += space->mBefore + space->mAfter; + } + } + } + + return result + GetAdvanceForGlyphs(ligatureRunStart, ligatureRunEnd); +} + +bool +gfxTextRun::SetLineBreaks(uint32_t aStart, uint32_t aLength, + bool aLineBreakBefore, bool aLineBreakAfter, + gfxFloat *aAdvanceWidthDelta, + gfxContext *aRefContext) +{ + // 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; +} + +uint32_t +gfxTextRun::FindFirstGlyphRunContaining(uint32_t aOffset) +{ + NS_ASSERTION(aOffset <= GetLength(), "Bad offset looking for glyphrun"); + NS_ASSERTION(GetLength() == 0 || mGlyphRuns.Length() > 0, + "non-empty text but no glyph runs present!"); + if (aOffset == GetLength()) + return mGlyphRuns.Length(); + uint32_t start = 0; + uint32_t end = mGlyphRuns.Length(); + while (end - start > 1) { + uint32_t mid = (start + end)/2; + if (mGlyphRuns[mid].mCharacterOffset <= aOffset) { + start = mid; + } else { + end = mid; + } + } + NS_ASSERTION(mGlyphRuns[start].mCharacterOffset <= aOffset, + "Hmm, something went wrong, aOffset should have been found"); + return start; +} + +nsresult +gfxTextRun::AddGlyphRun(gfxFont *aFont, uint8_t aMatchType, + uint32_t aUTF16Offset, bool aForceNewRun) +{ + NS_ASSERTION(aFont, "adding glyph run for null font!"); + if (!aFont) { + return NS_OK; + } + uint32_t numGlyphRuns = mGlyphRuns.Length(); + if (!aForceNewRun && numGlyphRuns > 0) { + GlyphRun *lastGlyphRun = &mGlyphRuns[numGlyphRuns - 1]; + + NS_ASSERTION(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->mFont == aFont && + lastGlyphRun->mMatchType == aMatchType) + { + return NS_OK; + } + + // 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].mFont == aFont && + mGlyphRuns[numGlyphRuns - 2].mMatchType == aMatchType) + { + mGlyphRuns.TruncateLength(numGlyphRuns - 1); + return NS_OK; + } + + lastGlyphRun->mFont = aFont; + lastGlyphRun->mMatchType = aMatchType; + return NS_OK; + } + } + + NS_ASSERTION(aForceNewRun || numGlyphRuns > 0 || aUTF16Offset == 0, + "First run doesn't cover the first character (and run not forced)?"); + + GlyphRun *glyphRun = mGlyphRuns.AppendElement(); + if (!glyphRun) + return NS_ERROR_OUT_OF_MEMORY; + glyphRun->mFont = aFont; + glyphRun->mCharacterOffset = aUTF16Offset; + glyphRun->mMatchType = aMatchType; + return NS_OK; +} + +void +gfxTextRun::SortGlyphRuns() +{ + if (mGlyphRuns.Length() <= 1) + return; + + nsTArray runs(mGlyphRuns); + GlyphRunOffsetComparator comp; + runs.Sort(comp); + + // Now copy back, coalescing adjacent glyph runs that have the same font + mGlyphRuns.Clear(); + uint32_t i, count = runs.Length(); + for (i = 0; i < count; ++i) { + // a GlyphRun with the same font as the previous GlyphRun can just + // be skipped; the last GlyphRun will cover its character range. + if (i == 0 || runs[i].mFont != runs[i - 1].mFont) { + mGlyphRuns.AppendElement(runs[i]); + // If two fonts have the same character offset, Sort() will have + // randomized the order. + NS_ASSERTION(i == 0 || + runs[i].mCharacterOffset != + runs[i - 1].mCharacterOffset, + "Two fonts for the same run, glyph indices may not match the font"); + } + } +} + +// Note that SanitizeGlyphRuns scans all glyph runs in the textrun; +// therefore we only call it once, at the end of textrun construction, +// NOT incrementally as each glyph run is added (bug 680402). +void +gfxTextRun::SanitizeGlyphRuns() +{ + if (mGlyphRuns.Length() <= 1) + return; + + // 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) + int32_t i, lastRunIndex = mGlyphRuns.Length() - 1; + const CompressedGlyph *charGlyphs = mCharacterGlyphs; + for (i = lastRunIndex; i >= 0; --i) { + GlyphRun& run = mGlyphRuns[i]; + while (charGlyphs[run.mCharacterOffset].IsLigatureContinuation() && + run.mCharacterOffset < GetLength()) { + run.mCharacterOffset++; + } + // if the run has become empty, eliminate it + if ((i < lastRunIndex && + run.mCharacterOffset >= mGlyphRuns[i+1].mCharacterOffset) || + (i == lastRunIndex && run.mCharacterOffset == GetLength())) { + mGlyphRuns.RemoveElementAt(i); + --lastRunIndex; + } + } +} + +uint32_t +gfxTextRun::CountMissingGlyphs() +{ + uint32_t i; + uint32_t count = 0; + for (i = 0; i < GetLength(); ++i) { + if (mCharacterGlyphs[i].IsMissing()) { + ++count; + } + } + return count; +} + +void +gfxTextRun::CopyGlyphDataFrom(gfxShapedWord *aShapedWord, uint32_t aOffset) +{ + uint32_t wordLen = aShapedWord->GetLength(); + NS_ASSERTION(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()) { + charGlyphs[aOffset] = g; + } else { + const DetailedGlyph *details = + g.GetGlyphCount() > 0 ? + aShapedWord->GetDetailedGlyphs(i) : nullptr; + SetGlyphs(aOffset, g, details); + } + } + } else { + memcpy(charGlyphs + aOffset, wordGlyphs, + wordLen * sizeof(CompressedGlyph)); + } +} + +void +gfxTextRun::CopyGlyphDataFrom(gfxTextRun *aSource, uint32_t aStart, + uint32_t aLength, uint32_t aDest) +{ + NS_ASSERTION(aStart + aLength <= aSource->GetLength(), + "Source substring out of range"); + NS_ASSERTION(aDest + aLength <= GetLength(), + "Destination substring out of range"); + + if (aSource->mSkipDrawing) { + mSkipDrawing = true; + } + + // Copy base glyph data, and DetailedGlyph data where present + const CompressedGlyph *srcGlyphs = aSource->mCharacterGlyphs + aStart; + CompressedGlyph *dstGlyphs = mCharacterGlyphs + aDest; + for (uint32_t i = 0; i < aLength; ++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) { + DetailedGlyph *dst = AllocateDetailedGlyphs(i + aDest, count); + if (dst) { + DetailedGlyph *src = aSource->GetDetailedGlyphs(i + aStart); + if (src) { + ::memcpy(dst, src, count * sizeof(DetailedGlyph)); + } else { + g.SetMissing(0); + } + } else { + g.SetMissing(0); + } + } + } + dstGlyphs[i] = g; + } + + // Copy glyph runs + GlyphRunIterator iter(aSource, aStart, aLength); +#ifdef DEBUG + gfxFont *lastFont = nullptr; +#endif + while (iter.NextRun()) { + gfxFont *font = iter.GetGlyphRun()->mFont; + NS_ASSERTION(font != lastFont, "Glyphruns not coalesced?"); +#ifdef DEBUG + lastFont = font; + uint32_t end = iter.GetStringEnd(); +#endif + uint32_t start = iter.GetStringStart(); + + // 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,

+ // &%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_WARN_IF_FALSE(aSource->IsClusterStart(start), + "Started font run in the middle of a cluster"); + NS_WARN_IF_FALSE(end == aSource->GetLength() || aSource->IsClusterStart(end), + "Ended font run in the middle of a cluster"); + + nsresult rv = AddGlyphRun(font, iter.GetGlyphRun()->mMatchType, + start - aStart + aDest, false); + if (NS_FAILED(rv)) + return; + } +} + +void +gfxTextRun::ClearGlyphsAndCharacters() +{ + ResetGlyphRuns(); + memset(reinterpret_cast(mCharacterGlyphs), 0, + mLength * sizeof(CompressedGlyph)); + mDetailedGlyphs = nullptr; +} + +void +gfxTextRun::SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, + uint32_t aCharIndex) +{ + if (SetSpaceGlyphIfSimple(aFont, aContext, aCharIndex, ' ')) { + return; + } + + aFont->InitWordCache(); + static const uint8_t space = ' '; + gfxShapedWord *sw = aFont->GetShapedWord(aContext, + &space, 1, + gfxShapedWord::HashMix(0, ' '), + MOZ_SCRIPT_LATIN, + mAppUnitsPerDevUnit, + gfxTextRunFactory::TEXT_IS_8BIT | + gfxTextRunFactory::TEXT_IS_ASCII | + gfxTextRunFactory::TEXT_IS_PERSISTENT, + nullptr); + if (sw) { + AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false); + CopyGlyphDataFrom(sw, aCharIndex); + } +} + +bool +gfxTextRun::SetSpaceGlyphIfSimple(gfxFont *aFont, gfxContext *aContext, + uint32_t aCharIndex, char16_t aSpaceChar) +{ + uint32_t spaceGlyph = aFont->GetSpaceGlyph(); + if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) { + return false; + } + + uint32_t spaceWidthAppUnits = + NS_lroundf(aFont->GetMetrics().spaceWidth * mAppUnitsPerDevUnit); + if (!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) { + return false; + } + + AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false); + CompressedGlyph g; + g.SetSimpleGlyph(spaceWidthAppUnits, spaceGlyph); + if (aSpaceChar == ' ') { + g.SetIsSpace(); + } + GetCharacterGlyphs()[aCharIndex] = g; + return true; +} + +void +gfxTextRun::FetchGlyphExtents(gfxContext *aRefContext) +{ + bool needsGlyphExtents = NeedsGlyphExtents(this); + if (!needsGlyphExtents && !mDetailedGlyphs) + return; + + uint32_t i, runCount = mGlyphRuns.Length(); + CompressedGlyph *charGlyphs = mCharacterGlyphs; + for (i = 0; i < runCount; ++i) { + const GlyphRun& run = mGlyphRuns[i]; + gfxFont *font = run.mFont; + if (MOZ_UNLIKELY(font->GetStyle()->size == 0)) { + continue; + } + + uint32_t start = run.mCharacterOffset; + uint32_t end = i + 1 < runCount ? + mGlyphRuns[i + 1].mCharacterOffset : GetLength(); + bool fontIsSetup = false; + uint32_t j; + gfxGlyphExtents *extents = font->GetOrCreateGlyphExtents(mAppUnitsPerDevUnit); + + for (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->IsGlyphKnown(glyphIndex)) { + if (!fontIsSetup) { + if (!font->SetupCairoFont(aRefContext)) { + NS_WARNING("failed to set up font for glyph extents"); + break; + } + fontIsSetup = true; + } +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + ++gGlyphExtentsSetupEagerSimple; +#endif + font->SetupGlyphExtents(aRefContext, glyphIndex, false, extents); + } + } + } 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->IsGlyphKnownWithTightExtents(glyphIndex)) { + if (!fontIsSetup) { + if (!font->SetupCairoFont(aRefContext)) { + NS_WARNING("failed to set up font for glyph extents"); + break; + } + fontIsSetup = true; + } +#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS + ++gGlyphExtentsSetupEagerTight; +#endif + font->SetupGlyphExtents(aRefContext, glyphIndex, true, extents); + } + } + } + } + } +} + + +gfxTextRun::ClusterIterator::ClusterIterator(gfxTextRun *aTextRun) + : mTextRun(aTextRun), mCurrentChar(uint32_t(-1)) +{ +} + +void +gfxTextRun::ClusterIterator::Reset() +{ + mCurrentChar = uint32_t(-1); +} + +bool +gfxTextRun::ClusterIterator::NextCluster() +{ + uint32_t len = mTextRun->GetLength(); + while (++mCurrentChar < len) { + if (mTextRun->IsClusterStart(mCurrentChar)) { + return true; + } + } + + mCurrentChar = uint32_t(-1); + return false; +} + +uint32_t +gfxTextRun::ClusterIterator::ClusterLength() const +{ + if (mCurrentChar == uint32_t(-1)) { + return 0; + } + + uint32_t i = mCurrentChar, + len = mTextRun->GetLength(); + while (++i < len) { + if (mTextRun->IsClusterStart(i)) { + break; + } + } + + return i - mCurrentChar; +} + +gfxFloat +gfxTextRun::ClusterIterator::ClusterAdvance(PropertyProvider *aProvider) const +{ + if (mCurrentChar == uint32_t(-1)) { + return 0; + } + + return mTextRun->GetAdvanceWidth(mCurrentChar, ClusterLength(), aProvider); +} + +size_t +gfxTextRun::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) +{ + // The second arg is how much gfxTextRun::AllocateStorage would have + // allocated. + size_t total = mGlyphRuns.SizeOfExcludingThis(aMallocSizeOf); + + if (mDetailedGlyphs) { + total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf); + } + + return total; +} + +size_t +gfxTextRun::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) +{ + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + + +#ifdef DEBUG +void +gfxTextRun::Dump(FILE* aOutput) { + if (!aOutput) { + aOutput = stdout; + } + + uint32_t i; + fputc('[', aOutput); + for (i = 0; i < mGlyphRuns.Length(); ++i) { + if (i > 0) { + fputc(',', aOutput); + } + gfxFont* font = mGlyphRuns[i].mFont; + const gfxFontStyle* style = font->GetStyle(); + NS_ConvertUTF16toUTF8 fontName(font->GetName()); + nsAutoCString lang; + style->language->ToUTF8String(lang); + fprintf(aOutput, "%d: %s %f/%d/%d/%s", mGlyphRuns[i].mCharacterOffset, + fontName.get(), style->size, + style->weight, style->style, lang.get()); + } + fputc(']', aOutput); +} +#endif + +gfxFontGroup::gfxFontGroup(const FontFamilyList& aFontFamilyList, + const gfxFontStyle *aStyle, + gfxUserFontSet *aUserFontSet) + : mFamilyList(aFontFamilyList) + , mStyle(*aStyle) + , mUnderlineOffset(UNDERLINE_OFFSET_NOT_SET) + , mHyphenWidth(-1) + , mUserFontSet(aUserFontSet) + , mTextPerf(nullptr) + , mPageLang(gfxPlatform::GetFontPrefLangFor(aStyle->language)) + , mSkipDrawing(false) +{ + // We don't use SetUserFontSet() here, as we want to unconditionally call + // BuildFontList() rather than only do UpdateFontList() if it changed. + mCurrGeneration = GetGeneration(); + BuildFontList(); +} + +void +gfxFontGroup::FindGenericFonts(FontFamilyType aGenericType, + nsIAtom *aLanguage, + void *aClosure) +{ + nsAutoTArray resolvedGenerics; + ResolveGenericFontNames(aGenericType, aLanguage, resolvedGenerics); + uint32_t g = 0, numGenerics = resolvedGenerics.Length(); + for (g = 0; g < numGenerics; g++) { + FindPlatformFont(resolvedGenerics[g], false, aClosure); + } +} + +/* static */ void +gfxFontGroup::ResolveGenericFontNames(FontFamilyType aGenericType, + nsIAtom *aLanguage, + nsTArray& aGenericFamilies) +{ + static const char kGeneric_serif[] = "serif"; + static const char kGeneric_sans_serif[] = "sans-serif"; + static const char kGeneric_monospace[] = "monospace"; + static const char kGeneric_cursive[] = "cursive"; + static const char kGeneric_fantasy[] = "fantasy"; + + // treat -moz-fixed as monospace + if (aGenericType == eFamily_moz_fixed) { + aGenericType = eFamily_monospace; + } + + // type should be standard generic type at this point + NS_ASSERTION(aGenericType >= eFamily_serif && + aGenericType <= eFamily_fantasy, + "standard generic font family type required"); + + // create the lang string + nsIAtom *langGroupAtom = nullptr; + nsAutoCString langGroupString; + if (aLanguage) { + if (!gLangService) { + CallGetService(NS_LANGUAGEATOMSERVICE_CONTRACTID, &gLangService); + } + if (gLangService) { + nsresult rv; + langGroupAtom = gLangService->GetLanguageGroup(aLanguage, &rv); + } + } + if (!langGroupAtom) { + langGroupAtom = nsGkAtoms::Unicode; + } + langGroupAtom->ToUTF8String(langGroupString); + + // map generic type to string + const char *generic = nullptr; + switch (aGenericType) { + case eFamily_serif: + generic = kGeneric_serif; + break; + case eFamily_sans_serif: + generic = kGeneric_sans_serif; + break; + case eFamily_monospace: + generic = kGeneric_monospace; + break; + case eFamily_cursive: + generic = kGeneric_cursive; + break; + case eFamily_fantasy: + generic = kGeneric_fantasy; + break; + default: + break; + } + + if (!generic) { + return; + } + + aGenericFamilies.Clear(); + + // load family for "font.name.generic.lang" + nsAutoCString prefFontName("font.name."); + prefFontName.Append(generic); + prefFontName.Append('.'); + prefFontName.Append(langGroupString); + gfxFontUtils::AppendPrefsFontList(prefFontName.get(), + aGenericFamilies); + + // if lang has pref fonts, also load fonts for "font.name-list.generic.lang" + if (!aGenericFamilies.IsEmpty()) { + nsAutoCString prefFontListName("font.name-list."); + prefFontListName.Append(generic); + prefFontListName.Append('.'); + prefFontListName.Append(langGroupString); + gfxFontUtils::AppendPrefsFontList(prefFontListName.get(), + aGenericFamilies); + } + +#if 0 // dump out generic mappings + printf("%s ===> ", prefFontName.get()); + for (uint32_t k = 0; k < aGenericFamilies.Length(); k++) { + if (k > 0) printf(", "); + printf("%s", NS_ConvertUTF16toUTF8(aGenericFamilies[k]).get()); + } + printf("\n"); +#endif +} + +void gfxFontGroup::EnumerateFontList(nsIAtom *aLanguage, void *aClosure) +{ + // initialize fonts in the font family list + const nsTArray& fontlist = mFamilyList.GetFontlist(); + + // lookup fonts in the fontlist + uint32_t i, numFonts = fontlist.Length(); + for (i = 0; i < numFonts; i++) { + const FontFamilyName& name = fontlist[i]; + if (name.IsNamed()) { + FindPlatformFont(name.mName, true, aClosure); + } else { + FindGenericFonts(name.mType, aLanguage, aClosure); + } + } + + // if necessary, append default generic onto the end + if (mFamilyList.GetDefaultFontType() != eFamily_none && + !mFamilyList.HasDefaultGeneric()) { + FindGenericFonts(mFamilyList.GetDefaultFontType(), + aLanguage, + aClosure); + } +} + +void +gfxFontGroup::BuildFontList() +{ +// gfxPangoFontGroup behaves differently, so this method is a no-op on that platform +#if defined(XP_MACOSX) || defined(XP_WIN) || defined(ANDROID) + + EnumerateFontList(mStyle.language); + + // at this point, fontlist should have been filled in + // get a default font if none exists + if (mFonts.Length() == 0) { + bool needsBold; + gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList(); + gfxFontFamily *defaultFamily = pfl->GetDefaultFont(&mStyle); + NS_ASSERTION(defaultFamily, + "invalid default font returned by GetDefaultFont"); + + if (defaultFamily) { + gfxFontEntry *fe = defaultFamily->FindFontForStyle(mStyle, + needsBold); + if (fe) { + nsRefPtr font = fe->FindOrMakeFont(&mStyle, + needsBold); + if (font) { + mFonts.AppendElement(FamilyFace(defaultFamily, font)); + } + } + } + + if (mFonts.Length() == 0) { + // 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) + nsAutoTArray,200> families; + pfl->GetFontFamilyList(families); + uint32_t count = families.Length(); + for (uint32_t i = 0; i < count; ++i) { + gfxFontEntry *fe = families[i]->FindFontForStyle(mStyle, + needsBold); + if (fe) { + nsRefPtr font = fe->FindOrMakeFont(&mStyle, + needsBold); + if (font) { + mFonts.AppendElement(FamilyFace(families[i], font)); + break; + } + } + } + } + + if (mFonts.Length() == 0) { + // an empty font list at this point is fatal; we're not going to + // be able to do even the most basic layout operations + char msg[256]; // CHECK buffer length if revising message below + nsAutoString families; + mFamilyList.ToString(families); + sprintf(msg, "unable to find a usable font (%.220s)", + NS_ConvertUTF16toUTF8(families).get()); + NS_RUNTIMEABORT(msg); + } + } + + if (!mStyle.systemFont) { + uint32_t count = mFonts.Length(); + for (uint32_t i = 0; i < count; ++i) { + gfxFont* font = mFonts[i].Font(); + if (font->GetFontEntry()->mIsBadUnderlineFont) { + gfxFloat first = mFonts[0].Font()->GetMetrics().underlineOffset; + gfxFloat bad = font->GetMetrics().underlineOffset; + mUnderlineOffset = std::min(first, bad); + break; + } + } + } +#endif +} + +void +gfxFontGroup::FindPlatformFont(const nsAString& aName, + bool aUseFontSet, + void *aClosure) +{ + bool needsBold; + gfxFontFamily *family = nullptr; + gfxFontEntry *fe = nullptr; + + if (aUseFontSet) { + // 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) { + // If the fontSet matches the family, but the font has not yet finished + // loading (nor has its load timeout fired), the fontGroup should wait + // for the download, and not actually draw its text yet. + family = mUserFontSet->LookupFamily(aName); + if (family) { + bool waitForUserFont = false; + gfxUserFontEntry* userFontEntry = + mUserFontSet->FindUserFontEntry(family, mStyle, needsBold, + waitForUserFont); + if (userFontEntry) { + fe = userFontEntry->GetPlatformFontEntry(); + } + if (!fe && waitForUserFont) { + mSkipDrawing = true; + } + } + } + } + + // Not known in the user font set ==> check system fonts + if (!family) { + gfxPlatformFontList *fontList = gfxPlatformFontList::PlatformFontList(); + family = fontList->FindFamily(aName, mStyle.systemFont); + if (family) { + fe = family->FindFontForStyle(mStyle, needsBold); + } + } + + // add to the font group, unless it's already there + if (fe && !HasFont(fe)) { + nsRefPtr font = fe->FindOrMakeFont(&mStyle, needsBold); + if (font) { + mFonts.AppendElement(FamilyFace(family, font)); + } + } +} + +bool +gfxFontGroup::HasFont(const gfxFontEntry *aFontEntry) +{ + uint32_t count = mFonts.Length(); + for (uint32_t i = 0; i < count; ++i) { + if (mFonts[i].Font()->GetFontEntry() == aFontEntry) + return true; + } + return false; +} + +gfxFontGroup::~gfxFontGroup() +{ + mFonts.Clear(); +} + +gfxFont * +gfxFontGroup::GetFirstMathFont() +{ + uint32_t count = mFonts.Length(); + for (uint32_t i = 0; i < count; ++i) { + gfxFont* font = GetFontAt(i); + if (font->GetFontEntry()->TryGetMathTable()) { + return font; + } + } + return nullptr; +} + +gfxFontGroup * +gfxFontGroup::Copy(const gfxFontStyle *aStyle) +{ + gfxFontGroup *fg = new gfxFontGroup(mFamilyList, aStyle, mUserFontSet); + fg->SetTextPerfMetrics(mTextPerf); + return fg; +} + +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; + } + return (((ch & 0xFF00) == 0x2000 /* Unicode control character */ && + (ch == 0x200B/*ZWSP*/ || ch == 0x2028/*LSEP*/ || ch == 0x2029/*PSEP*/)) || + IsBidiControl(ch)); +} + +gfxTextRun * +gfxFontGroup::MakeEmptyTextRun(const Parameters *aParams, uint32_t aFlags) +{ + aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT; + return gfxTextRun::Create(aParams, 0, this, aFlags); +} + +gfxTextRun * +gfxFontGroup::MakeSpaceTextRun(const Parameters *aParams, uint32_t aFlags) +{ + aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT; + + gfxTextRun *textRun = gfxTextRun::Create(aParams, 1, this, aFlags); + if (!textRun) { + return nullptr; + } + + gfxFont *font = GetFontAt(0); + if (MOZ_UNLIKELY(GetStyle()->size == 0)) { + // 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, gfxTextRange::kFontGroup, 0, 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->mContext, 0); + } else { + // In case the primary font doesn't have (bug 970891), + // find one that does. + uint8_t matchType; + nsRefPtr spaceFont = + FindFontForChar(' ', 0, MOZ_SCRIPT_LATIN, nullptr, &matchType); + if (spaceFont) { + textRun->SetSpaceGlyph(spaceFont, aParams->mContext, 0); + } + } + } + + // 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; +} + +gfxTextRun * +gfxFontGroup::MakeBlankTextRun(uint32_t aLength, + const Parameters *aParams, uint32_t aFlags) +{ + gfxTextRun *textRun = + gfxTextRun::Create(aParams, aLength, this, aFlags); + if (!textRun) { + return nullptr; + } + + textRun->AddGlyphRun(GetFontAt(0), gfxTextRange::kFontGroup, 0, false); + return textRun; +} + +gfxTextRun * +gfxFontGroup::MakeHyphenTextRun(gfxContext *aCtx, 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; + gfxFont *font = GetFontAt(0); + if (font && font->HasCharacter(hyphen)) { + return MakeTextRun(&hyphen, 1, aCtx, aAppUnitsPerDevUnit, + gfxFontGroup::TEXT_IS_PERSISTENT); + } + + static const uint8_t dash = '-'; + return MakeTextRun(&dash, 1, aCtx, aAppUnitsPerDevUnit, + gfxFontGroup::TEXT_IS_PERSISTENT); +} + +gfxFloat +gfxFontGroup::GetHyphenWidth(gfxTextRun::PropertyProvider *aProvider) +{ + if (mHyphenWidth < 0) { + nsRefPtr ctx(aProvider->GetContext()); + if (ctx) { + nsAutoPtr + hyphRun(MakeHyphenTextRun(ctx, + aProvider->GetAppUnitsPerDevUnit())); + mHyphenWidth = hyphRun.get() ? + hyphRun->GetAdvanceWidth(0, hyphRun->GetLength(), nullptr) : 0; + } + } + return mHyphenWidth; +} + +gfxTextRun * +gfxFontGroup::MakeTextRun(const uint8_t *aString, uint32_t aLength, + const Parameters *aParams, uint32_t aFlags) +{ + if (aLength == 0) { + return MakeEmptyTextRun(aParams, aFlags); + } + if (aLength == 1 && aString[0] == ' ') { + return MakeSpaceTextRun(aParams, aFlags); + } + + aFlags |= TEXT_IS_8BIT; + + if (GetStyle()->size == 0) { + // 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(aLength, aParams, aFlags); + } + + gfxTextRun *textRun = gfxTextRun::Create(aParams, aLength, + this, aFlags); + if (!textRun) { + return nullptr; + } + + InitTextRun(aParams->mContext, textRun, aString, aLength); + + textRun->FetchGlyphExtents(aParams->mContext); + + return textRun; +} + +gfxTextRun * +gfxFontGroup::MakeTextRun(const char16_t *aString, uint32_t aLength, + const Parameters *aParams, uint32_t aFlags) +{ + if (aLength == 0) { + return MakeEmptyTextRun(aParams, aFlags); + } + if (aLength == 1 && aString[0] == ' ') { + return MakeSpaceTextRun(aParams, aFlags); + } + if (GetStyle()->size == 0) { + return MakeBlankTextRun(aLength, aParams, aFlags); + } + + gfxTextRun *textRun = gfxTextRun::Create(aParams, aLength, + this, aFlags); + if (!textRun) { + return nullptr; + } + + InitTextRun(aParams->mContext, textRun, aString, aLength); + + textRun->FetchGlyphExtents(aParams->mContext); + + return textRun; +} + +template +void +gfxFontGroup::InitTextRun(gfxContext *aContext, + gfxTextRun *aTextRun, + const T *aString, + uint32_t aLength) +{ + 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 + int32_t numOption = gfxPlatform::GetPlatform()->GetBidiNumeralOption(); + nsAutoArrayPtr 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() & gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR) != 0; + 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 = new char16_t[aLength]; + if (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); + } + } + +#ifdef PR_LOGGING + PRLogModuleInfo *log = (mStyle.systemFont ? + gfxPlatform::GetLog(eGfxLog_textrunui) : + gfxPlatform::GetLog(eGfxLog_textrun)); +#endif + + // variant fallback handling may end up passing through this twice + bool redo; + do { + redo = false; + + if (sizeof(T) == sizeof(uint8_t) && !transformedString) { + +#ifdef PR_LOGGING + if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) { + nsAutoCString lang; + mStyle.language->ToUTF8String(lang); + nsAutoString families; + mFamilyList.ToString(families); + nsAutoCString str((const char*)aString, aLength); + PR_LOG(log, PR_LOG_WARNING,\ + ("(%s) fontgroup: [%s] default: %s lang: %s script: %d " + "len %d weight: %d width: %d style: %s size: %6.2f %d-byte " + "TEXTRUN [%s] ENDTEXTRUN\n", + (mStyle.systemFont ? "textrunui" : "textrun"), + NS_ConvertUTF16toUTF8(families).get(), + (mFamilyList.GetDefaultFontType() == eFamily_serif ? + "serif" : + (mFamilyList.GetDefaultFontType() == eFamily_sans_serif ? + "sans-serif" : "none")), + lang.get(), MOZ_SCRIPT_LATIN, aLength, + uint32_t(mStyle.weight), uint32_t(mStyle.stretch), + (mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" : + (mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" : + "normal")), + mStyle.size, + sizeof(T), + str.get())); + } +#endif + + // the text is still purely 8-bit; bypass the script-run itemizer + // and treat it as a single Latin run + InitScriptRun(aContext, aTextRun, aString, + 0, aLength, MOZ_SCRIPT_LATIN); + } 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(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; + int32_t runScript = MOZ_SCRIPT_LATIN; + while (scriptRuns.Next(runStart, runLimit, runScript)) { + + #ifdef PR_LOGGING + if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) { + nsAutoCString lang; + mStyle.language->ToUTF8String(lang); + nsAutoString families; + mFamilyList.ToString(families); + uint32_t runLen = runLimit - runStart; + PR_LOG(log, PR_LOG_WARNING,\ + ("(%s) fontgroup: [%s] default: %s lang: %s script: %d " + "len %d weight: %d width: %d style: %s size: %6.2f " + "%d-byte TEXTRUN [%s] ENDTEXTRUN\n", + (mStyle.systemFont ? "textrunui" : "textrun"), + NS_ConvertUTF16toUTF8(families).get(), + (mFamilyList.GetDefaultFontType() == eFamily_serif ? + "serif" : + (mFamilyList.GetDefaultFontType() == eFamily_sans_serif ? + "sans-serif" : "none")), + lang.get(), runScript, runLen, + uint32_t(mStyle.weight), uint32_t(mStyle.stretch), + (mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" : + (mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" : + "normal")), + mStyle.size, + sizeof(T), + NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get())); + } + #endif + + InitScriptRun(aContext, aTextRun, textPtr + runStart, + runStart, runLimit - runStart, runScript); + } + } + + // 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(); + + aTextRun->SortGlyphRuns(); +} + +template +void +gfxFontGroup::InitScriptRun(gfxContext *aContext, + 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 + int32_t aRunScript) +{ + 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"); + + gfxFont *mainFont = GetFontAt(0); + + uint32_t runStart = 0; + nsAutoTArray fontRanges; + ComputeRanges(fontRanges, aString, aLength, aRunScript); + uint32_t numRanges = fontRanges.Length(); + + for (uint32_t r = 0; r < numRanges; r++) { + const gfxTextRange& range = fontRanges[r]; + uint32_t matchedLength = range.Length(); + 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)); + if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun, + aString + runStart, + aOffset + runStart, + matchedLength, + aRunScript)) { + // 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; + } + + nsRefPtr subSuperFont = + matchedFont->GetSubSuperscriptFont(aTextRun->GetAppUnitsPerDevUnit()); + aTextRun->AddGlyphRun(subSuperFont, range.matchType, + aOffset + runStart, (matchedLength > 0)); + if (!subSuperFont->SplitAndInitTextRun(aContext, aTextRun, + aString + runStart, + aOffset + runStart, + matchedLength, + aRunScript)) { + // glyph layout failed! treat as missing glyphs + matchedFont = nullptr; + } + } else if (mStyle.variantCaps != NS_FONT_VARIANT_CAPS_NORMAL && + !matchedFont->SupportsVariantCaps(aRunScript, + mStyle.variantCaps, + petiteToSmallCaps, + syntheticLower, + syntheticUpper)) + { + // fallback for small-caps variant glyphs + if (!matchedFont->InitFakeSmallCapsRun(aContext, aTextRun, + aString + runStart, + aOffset + runStart, + matchedLength, + range.matchType, + aRunScript, + 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)); + if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun, + aString + runStart, + aOffset + runStart, + matchedLength, + aRunScript)) { + // glyph layout failed! treat as missing glyphs + matchedFont = nullptr; + } + } + } else { + aTextRun->AddGlyphRun(mainFont, gfxTextRange::kFontGroup, + aOffset + runStart, (matchedLength > 0)); + } + + 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 (sizeof(T) == sizeof(char16_t)) { + if (NS_IS_HIGH_SURROGATE(ch) && + index + 1 < aLength && + NS_IS_LOW_SURROGATE(aString[index + 1])) + { + aTextRun->SetMissingGlyph(aOffset + index, + SURROGATE_TO_UCS4(ch, + aString[index + 1]), + mainFont); + index++; + 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; + detailedGlyph.mXOffset = detailedGlyph.mYOffset = 0; + gfxShapedText::CompressedGlyph g; + g.SetComplex(true, true, 1); + aTextRun->SetGlyphs(aOffset + index, + g, &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); + } + } + + runStart += matchedLength; + } +} + +gfxTextRun * +gfxFontGroup::GetEllipsisTextRun(int32_t aAppUnitsPerDevPixel, + LazyReferenceContextGetter& aRefContextGetter) +{ + if (mCachedEllipsisTextRun && + mCachedEllipsisTextRun->GetAppUnitsPerDevUnit() == aAppUnitsPerDevPixel) { + return mCachedEllipsisTextRun; + } + + // Use a Unicode ellipsis if the font supports it, + // otherwise use three ASCII periods as fallback. + gfxFont* firstFont = GetFontAt(0); + nsString ellipsis = firstFont->HasCharacter(kEllipsisChar[0]) + ? nsDependentString(kEllipsisChar, + ArrayLength(kEllipsisChar) - 1) + : nsDependentString(kASCIIPeriodsChar, + ArrayLength(kASCIIPeriodsChar) - 1); + + nsRefPtr refCtx = aRefContextGetter.GetRefContext(); + Parameters params = { + refCtx, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevPixel + }; + gfxTextRun* textRun = + MakeTextRun(ellipsis.get(), ellipsis.Length(), ¶ms, TEXT_IS_PERSISTENT); + if (!textRun) { + return nullptr; + } + mCachedEllipsisTextRun = textRun; + textRun->ReleaseFontGroup(); // don't let the presence of a cached ellipsis + // textrun prolong the fontgroup's life + return textRun; +} + +already_AddRefed +gfxFontGroup::TryAllFamilyMembers(gfxFontFamily* aFamily, uint32_t aCh) +{ + if (!aFamily->TestCharacterMap(aCh)) { + return nullptr; + } + + // Note that we don't need the actual runScript in matchData for + // gfxFontFamily::SearchAllFontsForChar, it's only used for the + // system-fallback case. So we can just set it to 0 here. + GlobalFontMatch matchData(aCh, 0, &mStyle); + aFamily->SearchAllFontsForChar(&matchData); + gfxFontEntry *fe = matchData.mBestMatch; + if (!fe) { + return nullptr; + } + + bool needsBold = mStyle.weight >= 600 && !fe->IsBold(); + nsRefPtr font = fe->FindOrMakeFont(&mStyle, needsBold); + return font.forget(); +} + +already_AddRefed +gfxFontGroup::FindFontForChar(uint32_t aCh, uint32_t aPrevCh, + int32_t aRunScript, gfxFont *aPrevMatchedFont, + uint8_t *aMatchType) +{ + // To optimize common cases, try the first font in the font-group + // before going into the more detailed checks below + uint32_t nextIndex = 0; + bool isJoinControl = gfxFontUtils::IsJoinControl(aCh); + bool wasJoinCauser = gfxFontUtils::IsJoinCauser(aPrevCh); + bool isVarSelector = gfxFontUtils::IsVarSelector(aCh); + + if (!isJoinControl && !wasJoinCauser && !isVarSelector) { + nsRefPtr firstFont = mFonts[0].Font(); + if (firstFont->HasCharacter(aCh)) { + *aMatchType = gfxTextRange::kFontGroup; + return firstFont.forget(); + } + // It's possible that another font in the family (e.g. regular face, + // where the requested style was italic) will support the character + nsRefPtr font = TryAllFamilyMembers(mFonts[0].Family(), aCh); + if (font) { + *aMatchType = gfxTextRange::kFontGroup; + return font.forget(); + } + // 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) { + nsRefPtr ret = aPrevMatchedFont; + return ret.forget(); + } + + // 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)) { + nsRefPtr ret = aPrevMatchedFont; + return ret.forget(); + } + } + } + + // if this character is a variation selector, + // use the previous font regardless of whether it supports VS or not. + // otherwise the text run will be divided. + if (isVarSelector) { + if (aPrevMatchedFont) { + nsRefPtr ret = aPrevMatchedFont; + return ret.forget(); + } + // VS alone. it's meaningless to search different fonts + return nullptr; + } + + // 1. check remaining fonts in the font group + uint32_t fontListLength = FontListLength(); + for (uint32_t i = nextIndex; i < fontListLength; i++) { + nsRefPtr font = mFonts[i].Font(); + if (font->HasCharacter(aCh)) { + *aMatchType = gfxTextRange::kFontGroup; + return font.forget(); + } + + font = TryAllFamilyMembers(mFonts[i].Family(), aCh); + if (font) { + *aMatchType = gfxTextRange::kFontGroup; + return font.forget(); + } + } + + // if character is in Private Use Area, don't do matching against pref or system fonts + if ((aCh >= 0xE000 && aCh <= 0xF8FF) || (aCh >= 0xF0000 && aCh <= 0x10FFFD)) + return nullptr; + + // 2. search pref fonts + nsRefPtr font = WhichPrefFontSupportsChar(aCh); + if (font) { + *aMatchType = gfxTextRange::kPrefsFallback; + return font.forget(); + } + + // 3. use fallback fonts + // -- before searching for something else check the font used for the previous character + if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) { + *aMatchType = gfxTextRange::kSystemFallback; + nsRefPtr ret = aPrevMatchedFont; + return ret.forget(); + } + + // never fall back for characters from unknown scripts + if (aRunScript == HB_SCRIPT_UNKNOWN) { + return nullptr; + } + + // for known "space" characters, don't do a full system-fallback search; + // we'll synthesize appropriate-width spaces instead of missing-glyph boxes + if (GetGeneralCategory(aCh) == + HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR && + GetFontAt(0)->SynthesizeSpaceWidth(aCh) >= 0.0) + { + return nullptr; + } + + // -- otherwise look for other stuff + *aMatchType = gfxTextRange::kSystemFallback; + font = WhichSystemFontSupportsChar(aCh, aRunScript); + return font.forget(); +} + +template +void gfxFontGroup::ComputeRanges(nsTArray& aRanges, + const T *aString, uint32_t aLength, + int32_t aRunScript) +{ + 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; + 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) + gfxFont *prevFont = GetFontAt(0); + + // if we use the initial value of prevFont, we treat this as a match from + // the font group; fixes bug 978313 + uint8_t matchType = gfxTextRange::kFontGroup; + + 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 = aString[i]; + + // in 16-bit case only, check for surrogate pair + if (sizeof(T) == sizeof(char16_t)) { + if ((i + 1 < aLength) && NS_IS_HIGH_SURROGATE(ch) && + NS_IS_LOW_SURROGATE(aString[i + 1])) { + i++; + ch = SURROGATE_TO_UCS4(ch, aString[i]); + } + } + + if (ch == 0xa0) { + ch = ' '; + } + + // find the font for this char + nsRefPtr font = + FindFontForChar(ch, prevCh, aRunScript, prevFont, &matchType); + +#ifndef RELEASE_BUILD + if (MOZ_UNLIKELY(mTextPerf)) { + if (matchType == gfxTextRange::kPrefsFallback) { + mTextPerf->current.fallbackPrefs++; + } else if (matchType == gfxTextRange::kSystemFallback) { + mTextPerf->current.fallbackSystem++; + } + } +#endif + + prevCh = ch; + + if (lastRangeIndex == -1) { + // first char ==> make a new range + aRanges.AppendElement(gfxTextRange(0, 1, font, matchType)); + lastRangeIndex++; + prevFont = font; + } else { + // if font has changed, make a new range + gfxTextRange& prevRange = aRanges[lastRangeIndex]; + if (prevRange.font != font || prevRange.matchType != matchType) { + // close out the previous range + prevRange.end = origI; + aRanges.AppendElement(gfxTextRange(origI, i + 1, + font, matchType)); + 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 = font; + } + } + } + } + + aRanges[lastRangeIndex].end = aLength; +} + +gfxUserFontSet* +gfxFontGroup::GetUserFontSet() +{ + return mUserFontSet; +} + +void +gfxFontGroup::SetUserFontSet(gfxUserFontSet *aUserFontSet) +{ + if (aUserFontSet == mUserFontSet) { + return; + } + mUserFontSet = aUserFontSet; + mCurrGeneration = GetGeneration() - 1; + UpdateFontList(); +} + +uint64_t +gfxFontGroup::GetGeneration() +{ + if (!mUserFontSet) + return 0; + return mUserFontSet->GetGeneration(); +} + +// note: gfxPangoFontGroup overrides UpdateFontList, such that +// BuildFontList is never used +void +gfxFontGroup::UpdateFontList() +{ + if (mCurrGeneration != GetGeneration()) { + // xxx - can probably improve this to detect when all fonts were found, so no need to update list + mFonts.Clear(); + mUnderlineOffset = UNDERLINE_OFFSET_NOT_SET; + mSkipDrawing = false; + BuildFontList(); + mCurrGeneration = GetGeneration(); + mCachedEllipsisTextRun = nullptr; + } +} + +struct PrefFontCallbackData { + explicit PrefFontCallbackData(nsTArray >& aFamiliesArray) + : mPrefFamilies(aFamiliesArray) + {} + + nsTArray >& mPrefFamilies; + + static bool AddFontFamilyEntry(eFontPrefLang aLang, const nsAString& aName, void *aClosure) + { + PrefFontCallbackData *prefFontData = static_cast(aClosure); + + gfxFontFamily *family = gfxPlatformFontList::PlatformFontList()->FindFamily(aName); + if (family) { + prefFontData->mPrefFamilies.AppendElement(family); + } + return true; + } +}; + +already_AddRefed +gfxFontGroup::WhichPrefFontSupportsChar(uint32_t aCh) +{ + nsRefPtr font; + + // get the pref font list if it hasn't been set up already + uint32_t unicodeRange = FindCharUnicodeRange(aCh); + eFontPrefLang charLang = gfxPlatform::GetPlatform()->GetFontPrefLangFor(unicodeRange); + + // 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)) { + font = mLastPrefFont; + return font.forget(); + } + + // based on char lang and page lang, set up list of pref lang fonts to check + eFontPrefLang prefLangs[kMaxLenPrefLangList]; + uint32_t i, numLangs = 0; + + gfxPlatform::GetPlatform()->GetLangPrefs(prefLangs, numLangs, charLang, mPageLang); + + for (i = 0; i < numLangs; i++) { + nsAutoTArray, 5> families; + eFontPrefLang currentLang = prefLangs[i]; + + gfxPlatformFontList *fontList = gfxPlatformFontList::PlatformFontList(); + + // get the pref families for a single pref lang + if (!fontList->GetPrefFontFamilyEntries(currentLang, &families)) { + eFontPrefLang prefLangsToSearch[1] = { currentLang }; + PrefFontCallbackData prefFontData(families); + gfxPlatform::ForEachPrefFont(prefLangsToSearch, 1, PrefFontCallbackData::AddFontFamilyEntry, + &prefFontData); + fontList->SetPrefFontFamilyEntries(currentLang, families); + } + + // 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 + gfxFontFamily *family = families[j]; + if (!family) 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)) { + font = mLastPrefFont; + return font.forget(); + } + + bool needsBold; + gfxFontEntry *fe = family->FindFontForStyle(mStyle, needsBold); + // if ch in cmap, create and return a gfxFont + if (fe && fe->TestCharacterMap(aCh)) { + nsRefPtr prefFont = fe->FindOrMakeFont(&mStyle, needsBold); + if (!prefFont) continue; + mLastPrefFamily = family; + mLastPrefFont = prefFont; + mLastPrefLang = charLang; + mLastPrefFirstFont = (i == 0 && j == 0); + return prefFont.forget(); + } + + } + } + + return nullptr; +} + +already_AddRefed +gfxFontGroup::WhichSystemFontSupportsChar(uint32_t aCh, int32_t aRunScript) +{ + gfxFontEntry *fe = + gfxPlatformFontList::PlatformFontList()-> + SystemFindFontForChar(aCh, aRunScript, &mStyle); + if (fe) { + bool wantBold = mStyle.ComputeWeight() >= 6; + nsRefPtr font = + fe->FindOrMakeFont(&mStyle, wantBold && !fe->IsBold()); + return font.forget(); + } + + return nullptr; +} + +/*static*/ void +gfxFontGroup::Shutdown() +{ + NS_IF_RELEASE(gLangService); +} + +nsILanguageAtomService* gfxFontGroup::gLangService = nullptr; diff --git a/gfx/thebes/gfxTextRun.h b/gfx/thebes/gfxTextRun.h new file mode 100644 index 000000000000..cccf2fcc9c13 --- /dev/null +++ b/gfx/thebes/gfxTextRun.h @@ -0,0 +1,996 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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/. */ + +#ifndef GFX_TEXTRUN_H +#define GFX_TEXTRUN_H + +#include "gfxTypes.h" +#include "nsString.h" +#include "gfxPoint.h" +#include "gfxFont.h" +#include "nsTArray.h" +#include "gfxSkipChars.h" +#include "gfxPlatform.h" +#include "mozilla/MemoryReporting.h" +#include "DrawMode.h" +#include "harfbuzz/hb.h" + +#ifdef DEBUG +#include +#endif + +class gfxContext; +class gfxFontGroup; +class gfxUserFontSet; +class gfxTextContextPaint; +class nsIAtom; +class nsILanguageAtomService; + +/** + * Callback for Draw() to use when drawing text with mode + * DrawMode::GLYPH_PATH. + */ +struct gfxTextRunDrawCallbacks { + + /** + * Constructs a new DrawCallbacks object. + * + * @param aShouldPaintSVGGlyphs If true, SVG glyphs will be + * painted and the NotifyBeforeSVGGlyphPainted/NotifyAfterSVGGlyphPainted + * callbacks will be invoked for each SVG glyph. If false, SVG glyphs + * will not be painted; fallback plain glyphs are not emitted either. + */ + explicit gfxTextRunDrawCallbacks(bool aShouldPaintSVGGlyphs = false) + : mShouldPaintSVGGlyphs(aShouldPaintSVGGlyphs) + { + } + + /** + * Called when a path has been emitted to the gfxContext when + * painting a text run. This can be called any number of times, + * due to partial ligatures and intervening SVG glyphs. + */ + virtual void NotifyGlyphPathEmitted() = 0; + + /** + * Called just before an SVG glyph has been painted to the gfxContext. + */ + virtual void NotifyBeforeSVGGlyphPainted() { } + + /** + * Called just after an SVG glyph has been painted to the gfxContext. + */ + virtual void NotifyAfterSVGGlyphPainted() { } + + bool mShouldPaintSVGGlyphs; +}; + +/** + * gfxTextRun is an abstraction for drawing and measuring substrings of a run + * of text. It stores runs of positioned glyph data, each run having a single + * gfxFont. The glyphs are associated with a string of source text, and the + * gfxTextRun APIs take parameters that are offsets into that source text. + * + * gfxTextRuns are not refcounted. They should be deleted when no longer required. + * + * gfxTextRuns are mostly immutable. The only things that can change are + * inter-cluster spacing and line break placement. Spacing is always obtained + * lazily by methods that need it, it is not cached. Line breaks are stored + * persistently (insofar as they affect the shaping of glyphs; gfxTextRun does + * not actually do anything to explicitly account for line breaks). Initially + * there are no line breaks. The textrun can record line breaks before or after + * any given cluster. (Line breaks specified inside clusters are ignored.) + * + * It is important that zero-length substrings are handled correctly. This will + * be on the test! + */ +class gfxTextRun : public gfxShapedText { +public: + + // Override operator delete to properly free the object that was + // allocated via moz_malloc. + void operator delete(void* p) { + moz_free(p); + } + + virtual ~gfxTextRun(); + + typedef gfxFont::RunMetrics Metrics; + + // Public textrun API for general use + + bool IsClusterStart(uint32_t aPos) { + NS_ASSERTION(aPos < GetLength(), "aPos out of range"); + return mCharacterGlyphs[aPos].IsClusterStart(); + } + bool IsLigatureGroupStart(uint32_t aPos) { + NS_ASSERTION(aPos < GetLength(), "aPos out of range"); + return mCharacterGlyphs[aPos].IsLigatureGroupStart(); + } + bool CanBreakLineBefore(uint32_t aPos) { + NS_ASSERTION(aPos < GetLength(), "aPos out of range"); + return mCharacterGlyphs[aPos].CanBreakBefore() == + CompressedGlyph::FLAG_BREAK_TYPE_NORMAL; + } + bool CanHyphenateBefore(uint32_t aPos) { + NS_ASSERTION(aPos < GetLength(), "aPos out of range"); + return mCharacterGlyphs[aPos].CanBreakBefore() == + CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN; + } + + bool CharIsSpace(uint32_t aPos) { + NS_ASSERTION(aPos < GetLength(), "aPos out of range"); + return mCharacterGlyphs[aPos].CharIsSpace(); + } + bool CharIsTab(uint32_t aPos) { + NS_ASSERTION(aPos < GetLength(), "aPos out of range"); + return mCharacterGlyphs[aPos].CharIsTab(); + } + bool CharIsNewline(uint32_t aPos) { + NS_ASSERTION(aPos < GetLength(), "aPos out of range"); + return mCharacterGlyphs[aPos].CharIsNewline(); + } + bool CharIsLowSurrogate(uint32_t aPos) { + NS_ASSERTION(aPos < GetLength(), "aPos out of range"); + return mCharacterGlyphs[aPos].CharIsLowSurrogate(); + } + + // All uint32_t aStart, uint32_t aLength ranges below are restricted to + // grapheme cluster boundaries! All offsets are in terms of the string + // passed into MakeTextRun. + + // All coordinates are in layout/app units + + /** + * Set the potential linebreaks for a substring of the textrun. These are + * the "allow break before" points. Initially, there are no potential + * linebreaks. + * + * This can change glyphs and/or geometry! Some textruns' shapes + * depend on potential line breaks (e.g., title-case-converting textruns). + * This function is virtual so that those textruns can reshape themselves. + * + * @return true if this changed the linebreaks, false if the new line + * breaks are the same as the old + */ + virtual bool SetPotentialLineBreaks(uint32_t aStart, uint32_t aLength, + uint8_t *aBreakBefore, + gfxContext *aRefContext); + + /** + * Layout provides PropertyProvider objects. These allow detection of + * potential line break points and computation of spacing. We pass the data + * this way to allow lazy data acquisition; for example BreakAndMeasureText + * will want to only ask for properties of text it's actually looking at. + * + * NOTE that requested spacing may not actually be applied, if the textrun + * is unable to apply it in some context. Exception: spacing around a + * whitespace character MUST always be applied. + */ + class PropertyProvider { + public: + // Detect hyphenation break opportunities in the given range; breaks + // not at cluster boundaries will be ignored. + virtual void GetHyphenationBreaks(uint32_t aStart, uint32_t aLength, + bool *aBreakBefore) = 0; + + // Returns the provider's hyphenation setting, so callers can decide + // whether it is necessary to call GetHyphenationBreaks. + // Result is an NS_STYLE_HYPHENS_* value. + virtual int8_t GetHyphensOption() = 0; + + // Returns the extra width that will be consumed by a hyphen. This should + // be constant for a given textrun. + virtual gfxFloat GetHyphenWidth() = 0; + + typedef gfxFont::Spacing Spacing; + + /** + * Get the spacing around the indicated characters. Spacing must be zero + * inside clusters. In other words, if character i is not + * CLUSTER_START, then character i-1 must have zero after-spacing and + * character i must have zero before-spacing. + */ + virtual void GetSpacing(uint32_t aStart, uint32_t aLength, + Spacing *aSpacing) = 0; + + // Returns a gfxContext that can be used to measure the hyphen glyph. + // Only called if the hyphen width is requested. + virtual already_AddRefed GetContext() = 0; + + // Return the appUnitsPerDevUnit value to be used when measuring. + // Only called if the hyphen width is requested. + virtual uint32_t GetAppUnitsPerDevUnit() = 0; + }; + + class ClusterIterator { + public: + explicit ClusterIterator(gfxTextRun *aTextRun); + + void Reset(); + + bool NextCluster(); + + uint32_t Position() const { + return mCurrentChar; + } + + uint32_t ClusterLength() const; + + gfxFloat ClusterAdvance(PropertyProvider *aProvider) const; + + private: + gfxTextRun *mTextRun; + uint32_t mCurrentChar; + }; + + /** + * Draws a substring. Uses only GetSpacing from aBreakProvider. + * The provided point is the baseline origin on the left of the string + * for LTR, on the right of the string for RTL. + * @param aAdvanceWidth if non-null, the advance width of the substring + * is returned here. + * + * Drawing should respect advance widths in the sense that for LTR runs, + * Draw(ctx, pt, offset1, length1, dirty, &provider, &advance) followed by + * Draw(ctx, gfxPoint(pt.x + advance, pt.y), offset1 + length1, length2, + * dirty, &provider, nullptr) should have the same effect as + * Draw(ctx, pt, offset1, length1+length2, dirty, &provider, nullptr). + * For RTL runs the rule is: + * Draw(ctx, pt, offset1 + length1, length2, dirty, &provider, &advance) followed by + * Draw(ctx, gfxPoint(pt.x + advance, pt.y), offset1, length1, + * dirty, &provider, nullptr) should have the same effect as + * Draw(ctx, pt, offset1, length1+length2, dirty, &provider, nullptr). + * + * Glyphs should be drawn in logical content order, which can be significant + * if they overlap (perhaps due to negative spacing). + */ + void Draw(gfxContext *aContext, gfxPoint aPt, + DrawMode aDrawMode, + uint32_t aStart, uint32_t aLength, + PropertyProvider *aProvider, + gfxFloat *aAdvanceWidth, gfxTextContextPaint *aContextPaint, + gfxTextRunDrawCallbacks *aCallbacks = nullptr); + + /** + * Computes the ReflowMetrics for a substring. + * Uses GetSpacing from aBreakProvider. + * @param aBoundingBoxType which kind of bounding box (loose/tight) + */ + Metrics MeasureText(uint32_t aStart, uint32_t aLength, + gfxFont::BoundingBoxType aBoundingBoxType, + gfxContext *aRefContextForTightBoundingBox, + PropertyProvider *aProvider); + + /** + * Computes just the advance width for a substring. + * Uses GetSpacing from aBreakProvider. + */ + gfxFloat GetAdvanceWidth(uint32_t aStart, uint32_t aLength, + PropertyProvider *aProvider); + + /** + * Clear all stored line breaks for the given range (both before and after), + * and then set the line-break state before aStart to aBreakBefore and + * after the last cluster to aBreakAfter. + * + * We require that before and after line breaks be consistent. For clusters + * i and i+1, we require that if there is a break after cluster i, a break + * will be specified before cluster i+1. This may be temporarily violated + * (e.g. after reflowing line L and before reflowing line L+1); to handle + * these temporary violations, we say that there is a break betwen i and i+1 + * if a break is specified after i OR a break is specified before i+1. + * + * This can change textrun geometry! The existence of a linebreak can affect + * the advance width of the cluster before the break (when kerning) or the + * geometry of one cluster before the break or any number of clusters + * after the break. (The one-cluster-before-the-break limit is somewhat + * arbitrary; if some scripts require breaking it, then we need to + * alter nsTextFrame::TrimTrailingWhitespace, perhaps drastically becase + * it could affect the layout of frames before it...) + * + * We return true if glyphs or geometry changed, false otherwise. This + * function is virtual so that gfxTextRun subclasses can reshape + * properly. + * + * @param aAdvanceWidthDelta if non-null, returns the change in advance + * width of the given range. + */ + virtual bool SetLineBreaks(uint32_t aStart, uint32_t aLength, + bool aLineBreakBefore, bool aLineBreakAfter, + gfxFloat *aAdvanceWidthDelta, + gfxContext *aRefContext); + + /** + * Finds the longest substring that will fit into the given width. + * Uses GetHyphenationBreaks and GetSpacing from aBreakProvider. + * Guarantees the following: + * -- 0 <= result <= aMaxLength + * -- result is the maximal value of N such that either + * N < aMaxLength && line break at N && GetAdvanceWidth(aStart, N) <= aWidth + * OR N < aMaxLength && hyphen break at N && GetAdvanceWidth(aStart, N) + GetHyphenWidth() <= aWidth + * OR N == aMaxLength && GetAdvanceWidth(aStart, N) <= aWidth + * where GetAdvanceWidth assumes the effect of + * SetLineBreaks(aStart, N, aLineBreakBefore, N < aMaxLength, aProvider) + * -- if no such N exists, then result is the smallest N such that + * N < aMaxLength && line break at N + * OR N < aMaxLength && hyphen break at N + * OR N == aMaxLength + * + * The call has the effect of + * SetLineBreaks(aStart, result, aLineBreakBefore, result < aMaxLength, aProvider) + * and the returned metrics and the invariants above reflect this. + * + * @param aMaxLength this can be UINT32_MAX, in which case the length used + * is up to the end of the string + * @param aLineBreakBefore set to true if and only if there is an actual + * line break at the start of this string. + * @param aSuppressInitialBreak if true, then we assume there is no possible + * linebreak before aStart. If false, then we will check the internal + * line break opportunity state before deciding whether to return 0 as the + * character to break before. + * @param aTrimWhitespace if non-null, then we allow a trailing run of + * spaces to be trimmed; the width of the space(s) will not be included in + * the measured string width for comparison with the limit aWidth, and + * trimmed spaces will not be included in returned metrics. The width + * of the trimmed spaces will be returned in aTrimWhitespace. + * Trimmed spaces are still counted in the "characters fit" result. + * @param aMetrics if non-null, we fill this in for the returned substring. + * If a hyphenation break was used, the hyphen is NOT included in the returned metrics. + * @param aBoundingBoxType whether to make the bounding box in aMetrics tight + * @param aRefContextForTightBoundingBox a reference context to get the + * tight bounding box, if requested + * @param aUsedHyphenation if non-null, records if we selected a hyphenation break + * @param aLastBreak if non-null and result is aMaxLength, we set this to + * the maximal N such that + * N < aMaxLength && line break at N && GetAdvanceWidth(aStart, N) <= aWidth + * OR N < aMaxLength && hyphen break at N && GetAdvanceWidth(aStart, N) + GetHyphenWidth() <= aWidth + * or UINT32_MAX if no such N exists, where GetAdvanceWidth assumes + * the effect of + * SetLineBreaks(aStart, N, aLineBreakBefore, N < aMaxLength, aProvider) + * + * @param aCanWordWrap true if we can break between any two grapheme + * clusters. This is set by word-wrap: break-word + * + * @param aBreakPriority in/out the priority of the break opportunity + * saved in the line. If we are prioritizing break opportunities, we will + * not set a break with a lower priority. @see gfxBreakPriority. + * + * Note that negative advance widths are possible especially if negative + * spacing is provided. + */ + uint32_t BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength, + bool aLineBreakBefore, gfxFloat aWidth, + PropertyProvider *aProvider, + bool aSuppressInitialBreak, + gfxFloat *aTrimWhitespace, + Metrics *aMetrics, + gfxFont::BoundingBoxType aBoundingBoxType, + gfxContext *aRefContextForTightBoundingBox, + bool *aUsedHyphenation, + uint32_t *aLastBreak, + bool aCanWordWrap, + gfxBreakPriority *aBreakPriority); + + /** + * Update the reference context. + * XXX this is a hack. New text frame does not call this. Use only + * temporarily for old text frame. + */ + void SetContext(gfxContext *aContext) {} + + // Utility getters + + void *GetUserData() const { return mUserData; } + void SetUserData(void *aUserData) { mUserData = aUserData; } + + void SetFlagBits(uint32_t aFlags) { + NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::SETTABLE_FLAGS), + "Only user flags should be mutable"); + mFlags |= aFlags; + } + void ClearFlagBits(uint32_t aFlags) { + NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::SETTABLE_FLAGS), + "Only user flags should be mutable"); + mFlags &= ~aFlags; + } + const gfxSkipChars& GetSkipChars() const { return mSkipChars; } + gfxFontGroup *GetFontGroup() const { return mFontGroup; } + + + // Call this, don't call "new gfxTextRun" directly. This does custom + // allocation and initialization + static gfxTextRun *Create(const gfxTextRunFactory::Parameters *aParams, + uint32_t aLength, gfxFontGroup *aFontGroup, + uint32_t aFlags); + + // The text is divided into GlyphRuns as necessary + struct GlyphRun { + nsRefPtr mFont; // never null + uint32_t mCharacterOffset; // into original UTF16 string + uint8_t mMatchType; + }; + + class GlyphRunIterator { + public: + GlyphRunIterator(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aLength) + : mTextRun(aTextRun), mStartOffset(aStart), mEndOffset(aStart + aLength) { + mNextIndex = mTextRun->FindFirstGlyphRunContaining(aStart); + } + bool NextRun(); + GlyphRun *GetGlyphRun() { return mGlyphRun; } + uint32_t GetStringStart() { return mStringStart; } + uint32_t GetStringEnd() { return mStringEnd; } + private: + gfxTextRun *mTextRun; + GlyphRun *mGlyphRun; + uint32_t mStringStart; + uint32_t mStringEnd; + uint32_t mNextIndex; + uint32_t mStartOffset; + uint32_t mEndOffset; + }; + + class GlyphRunOffsetComparator { + public: + bool Equals(const GlyphRun& a, + const GlyphRun& b) const + { + return a.mCharacterOffset == b.mCharacterOffset; + } + + bool LessThan(const GlyphRun& a, + const GlyphRun& b) const + { + return a.mCharacterOffset < b.mCharacterOffset; + } + }; + + friend class GlyphRunIterator; + friend class FontSelector; + + // API for setting up the textrun glyphs. Should only be called by + // things that construct textruns. + /** + * We've found a run of text that should use a particular font. Call this + * only during initialization when font substitution has been computed. + * Call it before setting up the glyphs for the characters in this run; + * SetMissingGlyph requires that the correct glyphrun be installed. + * + * If aForceNewRun, a new glyph run will be added, even if the + * previously added run uses the same font. If glyph runs are + * added out of strictly increasing aStartCharIndex order (via + * force), then SortGlyphRuns must be called after all glyph runs + * are added before any further operations are performed with this + * TextRun. + */ + nsresult AddGlyphRun(gfxFont *aFont, uint8_t aMatchType, + uint32_t aStartCharIndex, bool aForceNewRun); + void ResetGlyphRuns() { mGlyphRuns.Clear(); } + void SortGlyphRuns(); + void SanitizeGlyphRuns(); + + CompressedGlyph* GetCharacterGlyphs() { + NS_ASSERTION(mCharacterGlyphs, "failed to initialize mCharacterGlyphs"); + return mCharacterGlyphs; + } + + // clean out results from shaping in progress, used for fallback scenarios + void ClearGlyphsAndCharacters(); + + void SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, uint32_t aCharIndex); + + // Set the glyph data for the given character index to the font's + // space glyph, IF this can be done as a "simple" glyph record + // (not requiring a DetailedGlyph entry). This avoids the need to call + // the font shaper and go through the shaped-word cache for most spaces. + // + // The parameter aSpaceChar is the original character code for which + // this space glyph is being used; if this is U+0020, we need to record + // that it could be trimmed at a run edge, whereas other kinds of space + // (currently just U+00A0) would not be trimmable/breakable. + // + // Returns true if it was able to set simple glyph data for the space; + // if it returns false, the caller needs to fall back to some other + // means to create the necessary (detailed) glyph data. + bool SetSpaceGlyphIfSimple(gfxFont *aFont, gfxContext *aContext, + uint32_t aCharIndex, char16_t aSpaceChar); + + // Record the positions of specific characters that layout may need to + // detect in the textrun, even though it doesn't have an explicit copy + // of the original text. These are recorded using flag bits in the + // CompressedGlyph record; if necessary, we convert "simple" glyph records + // to "complex" ones as the Tab and Newline flags are not present in + // simple CompressedGlyph records. + void SetIsTab(uint32_t aIndex) { + CompressedGlyph *g = &mCharacterGlyphs[aIndex]; + if (g->IsSimpleGlyph()) { + DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); + details->mGlyphID = g->GetSimpleGlyph(); + details->mAdvance = g->GetSimpleAdvance(); + details->mXOffset = details->mYOffset = 0; + SetGlyphs(aIndex, CompressedGlyph().SetComplex(true, true, 1), details); + } + g->SetIsTab(); + } + void SetIsNewline(uint32_t aIndex) { + CompressedGlyph *g = &mCharacterGlyphs[aIndex]; + if (g->IsSimpleGlyph()) { + DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); + details->mGlyphID = g->GetSimpleGlyph(); + details->mAdvance = g->GetSimpleAdvance(); + details->mXOffset = details->mYOffset = 0; + SetGlyphs(aIndex, CompressedGlyph().SetComplex(true, true, 1), details); + } + g->SetIsNewline(); + } + void SetIsLowSurrogate(uint32_t aIndex) { + SetGlyphs(aIndex, CompressedGlyph().SetComplex(false, false, 0), nullptr); + mCharacterGlyphs[aIndex].SetIsLowSurrogate(); + } + + /** + * Prefetch all the glyph extents needed to ensure that Measure calls + * on this textrun not requesting tight boundingBoxes will succeed. Note + * that some glyph extents might not be fetched due to OOM or other + * errors. + */ + void FetchGlyphExtents(gfxContext *aRefContext); + + uint32_t CountMissingGlyphs(); + const GlyphRun *GetGlyphRuns(uint32_t *aNumGlyphRuns) { + *aNumGlyphRuns = mGlyphRuns.Length(); + return mGlyphRuns.Elements(); + } + // Returns the index of the GlyphRun containing the given offset. + // Returns mGlyphRuns.Length() when aOffset is mCharacterCount. + uint32_t FindFirstGlyphRunContaining(uint32_t aOffset); + + // Copy glyph data from a ShapedWord into this textrun. + void CopyGlyphDataFrom(gfxShapedWord *aSource, uint32_t aStart); + + // Copy glyph data for a range of characters from aSource to this + // textrun. + void CopyGlyphDataFrom(gfxTextRun *aSource, uint32_t aStart, + uint32_t aLength, uint32_t aDest); + + nsExpirationState *GetExpirationState() { return &mExpirationState; } + + // Tell the textrun to release its reference to its creating gfxFontGroup + // immediately, rather than on destruction. This is used for textruns + // that are actually owned by a gfxFontGroup, so that they don't keep it + // permanently alive due to a circular reference. (The caller of this is + // taking responsibility for ensuring the textrun will not outlive its + // mFontGroup.) + void ReleaseFontGroup(); + + struct LigatureData { + // textrun offsets of the start and end of the containing ligature + uint32_t mLigatureStart; + uint32_t mLigatureEnd; + // appunits advance to the start of the ligature part within the ligature; + // never includes any spacing + gfxFloat mPartAdvance; + // appunits width of the ligature part; includes before-spacing + // when the part is at the start of the ligature, and after-spacing + // when the part is as the end of the ligature + gfxFloat mPartWidth; + + bool mClipBeforePart; + bool mClipAfterPart; + }; + + // return storage used by this run, for memory reporter; + // nsTransformedTextRun needs to override this as it holds additional data + virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) + MOZ_MUST_OVERRIDE; + virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) + MOZ_MUST_OVERRIDE; + + // Get the size, if it hasn't already been gotten, marking as it goes. + size_t MaybeSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { + if (mFlags & gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED) { + return 0; + } + mFlags |= gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED; + return SizeOfIncludingThis(aMallocSizeOf); + } + void ResetSizeOfAccountingFlags() { + mFlags &= ~gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED; + } + + // shaping state - for some font features, fallback is required that + // affects the entire run. for example, fallback for one script/font + // portion of a textrun requires fallback to be applied to the entire run + + enum ShapingState { + eShapingState_Normal, // default state + eShapingState_ShapingWithFeature, // have shaped with feature + eShapingState_ShapingWithFallback, // have shaped with fallback + eShapingState_Aborted, // abort initial iteration + eShapingState_ForceFallbackFeature // redo with fallback forced on + }; + + ShapingState GetShapingState() const { return mShapingState; } + void SetShapingState(ShapingState aShapingState) { + mShapingState = aShapingState; + } + +#ifdef DEBUG + void Dump(FILE* aOutput); +#endif + +protected: + /** + * Create a textrun, and set its mCharacterGlyphs to point immediately + * after the base object; this is ONLY used in conjunction with placement + * new, after allocating a block large enough for the glyph records to + * follow the base textrun object. + */ + gfxTextRun(const gfxTextRunFactory::Parameters *aParams, + uint32_t aLength, gfxFontGroup *aFontGroup, uint32_t aFlags); + + /** + * Helper for the Create() factory method to allocate the required + * glyph storage for a textrun object with the basic size aSize, + * plus room for aLength glyph records. + */ + static void* AllocateStorageForTextRun(size_t aSize, uint32_t aLength); + + // Pointer to the array of CompressedGlyph records; must be initialized + // when the object is constructed. + CompressedGlyph *mCharacterGlyphs; + +private: + // **** general helpers **** + + // Get the total advance for a range of glyphs. + int32_t GetAdvanceForGlyphs(uint32_t aStart, uint32_t aEnd); + + // Spacing for characters outside the range aSpacingStart/aSpacingEnd + // is assumed to be zero; such characters are not passed to aProvider. + // This is useful to protect aProvider from being passed character indices + // it is not currently able to handle. + bool GetAdjustedSpacingArray(uint32_t aStart, uint32_t aEnd, + PropertyProvider *aProvider, + uint32_t aSpacingStart, uint32_t aSpacingEnd, + nsTArray *aSpacing); + + // **** ligature helpers **** + // (Platforms do the actual ligaturization, but we need to do a bunch of stuff + // to handle requests that begin or end inside a ligature) + + // if aProvider is null then mBeforeSpacing and mAfterSpacing are set to zero + LigatureData ComputeLigatureData(uint32_t aPartStart, uint32_t aPartEnd, + PropertyProvider *aProvider); + gfxFloat ComputePartialLigatureWidth(uint32_t aPartStart, uint32_t aPartEnd, + PropertyProvider *aProvider); + void DrawPartialLigature(gfxFont *aFont, uint32_t aStart, uint32_t aEnd, + gfxPoint *aPt, PropertyProvider *aProvider, + TextRunDrawParams& aParams); + // Advance aStart to the start of the nearest ligature; back up aEnd + // to the nearest ligature end; may result in *aStart == *aEnd + void ShrinkToLigatureBoundaries(uint32_t *aStart, uint32_t *aEnd); + // result in appunits + gfxFloat GetPartialLigatureWidth(uint32_t aStart, uint32_t aEnd, PropertyProvider *aProvider); + void AccumulatePartialLigatureMetrics(gfxFont *aFont, + uint32_t aStart, uint32_t aEnd, + gfxFont::BoundingBoxType aBoundingBoxType, + gfxContext *aRefContext, + PropertyProvider *aProvider, + Metrics *aMetrics); + + // **** measurement helper **** + void AccumulateMetricsForRun(gfxFont *aFont, uint32_t aStart, uint32_t aEnd, + gfxFont::BoundingBoxType aBoundingBoxType, + gfxContext *aRefContext, + PropertyProvider *aProvider, + uint32_t aSpacingStart, uint32_t aSpacingEnd, + Metrics *aMetrics); + + // **** drawing helper **** + void DrawGlyphs(gfxFont *aFont, uint32_t aStart, uint32_t aEnd, + gfxPoint *aPt, PropertyProvider *aProvider, + uint32_t aSpacingStart, uint32_t aSpacingEnd, + TextRunDrawParams& aParams); + + // XXX this should be changed to a GlyphRun plus a maybe-null GlyphRun*, + // for smaller size especially in the super-common one-glyphrun case + nsAutoTArray mGlyphRuns; + + void *mUserData; + gfxFontGroup *mFontGroup; // addrefed on creation, but our reference + // may be released by ReleaseFontGroup() + gfxSkipChars mSkipChars; + nsExpirationState mExpirationState; + + bool mSkipDrawing; // true if the font group we used had a user font + // download that's in progress, so we should hide text + // until the download completes (or timeout fires) + bool mReleasedFontGroup; // we already called NS_RELEASE on + // mFontGroup, so don't do it again + + // shaping state for handling variant fallback features + // such as subscript/superscript variant glyphs + ShapingState mShapingState; +}; + +class gfxFontGroup : public gfxTextRunFactory { +public: + class FamilyFace { + public: + FamilyFace() { } + + FamilyFace(gfxFontFamily* aFamily, gfxFont* aFont) + : mFamily(aFamily), mFont(aFont) + { + NS_ASSERTION(aFont, "font pointer must not be null"); + NS_ASSERTION(!aFamily || + aFamily->ContainsFace(aFont->GetFontEntry()), + "font is not a member of the given family"); + } + + gfxFontFamily* Family() const { return mFamily.get(); } + gfxFont* Font() const { return mFont.get(); } + + private: + nsRefPtr mFamily; + nsRefPtr mFont; + }; + + static void Shutdown(); // platform must call this to release the languageAtomService + + gfxFontGroup(const mozilla::FontFamilyList& aFontFamilyList, + const gfxFontStyle *aStyle, + gfxUserFontSet *aUserFontSet = nullptr); + + virtual ~gfxFontGroup(); + + virtual gfxFont *GetFontAt(int32_t i) { + // If it turns out to be hard for all clients that cache font + // groups to call UpdateFontList at appropriate times, we could + // instead consider just calling UpdateFontList from someplace + // more central (such as here). + NS_ASSERTION(!mUserFontSet || mCurrGeneration == GetGeneration(), + "Whoever was caching this font group should have " + "called UpdateFontList on it"); + NS_ASSERTION(mFonts.Length() > uint32_t(i) && mFonts[i].Font(), + "Requesting a font index that doesn't exist"); + + return mFonts[i].Font(); + } + + // Returns the first font in the font-group that has an OpenType MATH table, + // or null if no such font is available. The GetMathConstant methods may be + // called on the returned font. + gfxFont *GetFirstMathFont(); + + uint32_t FontListLength() const { + return mFonts.Length(); + } + + const gfxFontStyle *GetStyle() const { return &mStyle; } + + virtual gfxFontGroup *Copy(const gfxFontStyle *aStyle); + + /** + * The listed characters should be treated as invisible and zero-width + * when creating textruns. + */ + static bool IsInvalidChar(uint8_t ch); + static bool IsInvalidChar(char16_t ch); + + /** + * Make a textrun for a given string. + * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the + * textrun will copy it. + * This calls FetchGlyphExtents on the textrun. + */ + virtual gfxTextRun *MakeTextRun(const char16_t *aString, uint32_t aLength, + const Parameters *aParams, uint32_t aFlags); + /** + * Make a textrun for a given string. + * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the + * textrun will copy it. + * This calls FetchGlyphExtents on the textrun. + */ + virtual gfxTextRun *MakeTextRun(const uint8_t *aString, uint32_t aLength, + const Parameters *aParams, uint32_t aFlags); + + /** + * Textrun creation helper for clients that don't want to pass + * a full Parameters record. + */ + template + gfxTextRun *MakeTextRun(const T *aString, uint32_t aLength, + gfxContext *aRefContext, + int32_t aAppUnitsPerDevUnit, + uint32_t aFlags) + { + gfxTextRunFactory::Parameters params = { + aRefContext, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevUnit + }; + return MakeTextRun(aString, aLength, ¶ms, aFlags); + } + + /** + * Get the (possibly-cached) width of the hyphen character. + * The aCtx and aAppUnitsPerDevUnit parameters will be used only if + * needed to initialize the cached hyphen width; otherwise they are + * ignored. + */ + gfxFloat GetHyphenWidth(gfxTextRun::PropertyProvider* aProvider); + + /** + * Make a text run representing a single hyphen character. + * This will use U+2010 HYPHEN if available in the first font, + * otherwise fall back to U+002D HYPHEN-MINUS. + * The caller is responsible for deleting the returned text run + * when no longer required. + */ + gfxTextRun *MakeHyphenTextRun(gfxContext *aCtx, + uint32_t aAppUnitsPerDevUnit); + + /** + * Check whether a given font (specified by its gfxFontEntry) + * is already in the fontgroup's list of actual fonts + */ + bool HasFont(const gfxFontEntry *aFontEntry); + + // This returns the preferred underline for this font group. + // Some CJK fonts have wrong underline offset in its metrics. + // If this group has such "bad" font, each platform's gfxFontGroup initialized mUnderlineOffset. + // The value should be lower value of first font's metrics and the bad font's metrics. + // Otherwise, this returns from first font's metrics. + enum { UNDERLINE_OFFSET_NOT_SET = INT16_MAX }; + virtual gfxFloat GetUnderlineOffset() { + if (mUnderlineOffset == UNDERLINE_OFFSET_NOT_SET) + mUnderlineOffset = GetFontAt(0)->GetMetrics().underlineOffset; + return mUnderlineOffset; + } + + virtual already_AddRefed + FindFontForChar(uint32_t ch, uint32_t prevCh, int32_t aRunScript, + gfxFont *aPrevMatchedFont, + uint8_t *aMatchType); + + // search through pref fonts for a character, return nullptr if no matching pref font + virtual already_AddRefed WhichPrefFontSupportsChar(uint32_t aCh); + + virtual already_AddRefed + WhichSystemFontSupportsChar(uint32_t aCh, int32_t aRunScript); + + template + void ComputeRanges(nsTArray& mRanges, + const T *aString, uint32_t aLength, + int32_t aRunScript); + + gfxUserFontSet* GetUserFontSet(); + + // With downloadable fonts, the composition of the font group can change as fonts are downloaded + // for each change in state of the user font set, the generation value is bumped to avoid picking up + // previously created text runs in the text run word cache. For font groups based on stylesheets + // with no @font-face rule, this always returns 0. + uint64_t GetGeneration(); + + // used when logging text performance + gfxTextPerfMetrics *GetTextPerfMetrics() { return mTextPerf; } + void SetTextPerfMetrics(gfxTextPerfMetrics *aTextPerf) { mTextPerf = aTextPerf; } + + // This will call UpdateFontList() if the user font set is changed. + void SetUserFontSet(gfxUserFontSet *aUserFontSet); + + // If there is a user font set, check to see whether the font list or any + // caches need updating. + virtual void UpdateFontList(); + + bool ShouldSkipDrawing() const { + return mSkipDrawing; + } + + class LazyReferenceContextGetter { + public: + virtual already_AddRefed GetRefContext() = 0; + }; + // The gfxFontGroup keeps ownership of this textrun. + // It is only guaranteed to exist until the next call to GetEllipsisTextRun + // (which might use a different appUnitsPerDev value) for the font group, + // or until UpdateFontList is called, or the fontgroup is destroyed. + // Get it/use it/forget it :) - don't keep a reference that might go stale. + gfxTextRun* GetEllipsisTextRun(int32_t aAppUnitsPerDevPixel, + LazyReferenceContextGetter& aRefContextGetter); + + // helper method for resolving generic font families + static void + ResolveGenericFontNames(mozilla::FontFamilyType aGenericType, + nsIAtom *aLanguage, + nsTArray& aGenericFamilies); + +protected: + mozilla::FontFamilyList mFamilyList; + gfxFontStyle mStyle; + nsTArray mFonts; + gfxFloat mUnderlineOffset; + gfxFloat mHyphenWidth; + + nsRefPtr mUserFontSet; + uint64_t mCurrGeneration; // track the current user font set generation, rebuild font list if needed + + gfxTextPerfMetrics *mTextPerf; + + // Cache a textrun representing an ellipsis (useful for CSS text-overflow) + // at a specific appUnitsPerDevPixel size + nsAutoPtr mCachedEllipsisTextRun; + + // cache the most recent pref font to avoid general pref font lookup + nsRefPtr mLastPrefFamily; + nsRefPtr mLastPrefFont; + eFontPrefLang mLastPrefLang; // lang group for last pref font + eFontPrefLang mPageLang; + bool mLastPrefFirstFont; // is this the first font in the list of pref fonts for this lang group? + + bool mSkipDrawing; // hide text while waiting for a font + // download to complete (or fallback + // timer to fire) + + /** + * Textrun creation short-cuts for special cases where we don't need to + * call a font shaper to generate glyphs. + */ + gfxTextRun *MakeEmptyTextRun(const Parameters *aParams, uint32_t aFlags); + gfxTextRun *MakeSpaceTextRun(const Parameters *aParams, uint32_t aFlags); + gfxTextRun *MakeBlankTextRun(uint32_t aLength, + const Parameters *aParams, uint32_t aFlags); + + // Initialize the list of fonts + void BuildFontList(); + + // Init this font group's font metrics. If there no bad fonts, you don't need to call this. + // But if there are one or more bad fonts which have bad underline offset, + // you should call this with the *first* bad font. + void InitMetricsForBadFont(gfxFont* aBadFont); + + // Set up the textrun glyphs for an entire text run: + // find script runs, and then call InitScriptRun for each + template + void InitTextRun(gfxContext *aContext, + gfxTextRun *aTextRun, + const T *aString, + uint32_t aLength); + + // InitTextRun helper to handle a single script run, by finding font ranges + // and calling each font's InitTextRun() as appropriate + template + void InitScriptRun(gfxContext *aContext, + gfxTextRun *aTextRun, + const T *aString, + uint32_t aScriptRunStart, + uint32_t aScriptRunEnd, + int32_t aRunScript); + + // Helper for font-matching: + // see if aCh is supported in any of the faces from aFamily; + // if so return the best style match, else return null. + already_AddRefed TryAllFamilyMembers(gfxFontFamily* aFamily, + uint32_t aCh); + + // helper methods for looking up fonts + + // iterate over the fontlist, lookup names and expand generics + void EnumerateFontList(nsIAtom *aLanguage, void *aClosure = nullptr); + + // expand a generic to a list of specific names based on prefs + void FindGenericFonts(mozilla::FontFamilyType aGenericType, + nsIAtom *aLanguage, + void *aClosure); + + // lookup and add a font with a given name (i.e. *not* a generic!) + virtual void FindPlatformFont(const nsAString& aName, + bool aUseFontSet, + void *aClosure); + + static nsILanguageAtomService* gLangService; +}; +#endif diff --git a/gfx/thebes/gfxWindowsPlatform.cpp b/gfx/thebes/gfxWindowsPlatform.cpp index 5c99e5d6d7bc..52d49f0a8cff 100644 --- a/gfx/thebes/gfxWindowsPlatform.cpp +++ b/gfx/thebes/gfxWindowsPlatform.cpp @@ -43,6 +43,7 @@ #include #endif +#include "gfxTextRun.h" #include "gfxUserFontSet.h" #include "nsWindowsHelpers.h" #include "gfx2DGlue.h" diff --git a/gfx/thebes/moz.build b/gfx/thebes/moz.build index d50d25a31868..a113e69d23f4 100644 --- a/gfx/thebes/moz.build +++ b/gfx/thebes/moz.build @@ -18,6 +18,7 @@ EXPORTS += [ 'gfxFailure.h', 'gfxFont.h', 'gfxFontConstants.h', + 'gfxFontEntry.h', 'gfxFontFamilyList.h', 'gfxFontFeatures.h', 'gfxFontInfoLoader.h', @@ -43,6 +44,7 @@ EXPORTS += [ 'gfxSkipChars.h', 'gfxSVGGlyphs.h', 'gfxTeeSurface.h', + 'gfxTextRun.h', 'gfxTypes.h', 'gfxUserFontSet.h', 'gfxUtils.h', @@ -205,6 +207,8 @@ SOURCES += [ # Uses FORCE_PR_LOG 'gfxFont.cpp', # Uses FORCE_PR_LOG + 'gfxFontEntry.cpp', + # Uses FORCE_PR_LOG 'gfxFontUtils.cpp', # Includes mac system header conflicting with point/size, and also uses FORCE_PR_LOG 'gfxPlatform.cpp', @@ -226,6 +230,7 @@ UNIFIED_SOURCES += [ 'gfxFontInfoLoader.cpp', 'gfxFontMissingGlyphs.cpp', 'gfxFontTest.cpp', + 'gfxGlyphExtents.cpp', 'gfxGradientCache.cpp', 'gfxGraphiteShaper.cpp', 'gfxHarfBuzzShaper.cpp', @@ -240,6 +245,7 @@ UNIFIED_SOURCES += [ 'gfxSkipChars.cpp', 'gfxSVGGlyphs.cpp', 'gfxTeeSurface.cpp', + 'gfxTextRun.cpp', 'gfxUtils.cpp', 'nsSurfaceTexture.cpp', 'nsUnicodeRange.cpp', diff --git a/gfx/thebes/nsUnicodeRange.h b/gfx/thebes/nsUnicodeRange.h index 6390d68a6268..96d1e90f79ed 100644 --- a/gfx/thebes/nsUnicodeRange.h +++ b/gfx/thebes/nsUnicodeRange.h @@ -3,6 +3,9 @@ * 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/. */ +#ifndef NS_UNICODERANGE_H +#define NS_UNICODERANGE_H + #include class nsIAtom; @@ -87,3 +90,5 @@ const uint8_t kRangeTertiaryTable = 145; // leave room for 16 subtable uint32_t FindCharUnicodeRange(uint32_t ch); nsIAtom* LangGroupFromUnicodeRange(uint8_t unicodeRange); + +#endif diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index 8577c5b4fee9..0da2dccb8f58 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -65,7 +65,6 @@ #include "nsPrintfCString.h" -#include "gfxFont.h" #include "gfxContext.h" #include "mozilla/dom/Element.h" diff --git a/layout/generic/nsTextFrame.h b/layout/generic/nsTextFrame.h index a745a5c4bbd0..486e26f92608 100644 --- a/layout/generic/nsTextFrame.h +++ b/layout/generic/nsTextFrame.h @@ -10,8 +10,8 @@ #include "nsFrame.h" #include "nsSplittableFrame.h" #include "nsLineBox.h" -#include "gfxFont.h" #include "gfxSkipChars.h" +#include "gfxTextRun.h" #include "nsDisplayList.h" class nsTextPaintStyle; diff --git a/layout/generic/nsTextRunTransformations.h b/layout/generic/nsTextRunTransformations.h index 2e7f987413eb..3a434238210a 100644 --- a/layout/generic/nsTextRunTransformations.h +++ b/layout/generic/nsTextRunTransformations.h @@ -8,7 +8,7 @@ #include "mozilla/Attributes.h" #include "mozilla/MemoryReporting.h" -#include "gfxFont.h" +#include "gfxTextRun.h" #include "nsStyleContext.h" class nsTransformedTextRun; diff --git a/layout/inspector/nsFontFace.cpp b/layout/inspector/nsFontFace.cpp index eb788d95b723..81336decf709 100644 --- a/layout/inspector/nsFontFace.cpp +++ b/layout/inspector/nsFontFace.cpp @@ -5,7 +5,7 @@ #include "nsFontFace.h" #include "nsIDOMCSSFontFaceRule.h" #include "nsCSSRules.h" -#include "gfxFont.h" +#include "gfxTextRun.h" #include "gfxUserFontSet.h" #include "nsFontFaceLoader.h" #include "mozilla/gfx/2D.h" diff --git a/layout/inspector/nsFontFaceList.cpp b/layout/inspector/nsFontFaceList.cpp index b0be644fad62..f5b45d638a26 100644 --- a/layout/inspector/nsFontFaceList.cpp +++ b/layout/inspector/nsFontFaceList.cpp @@ -6,7 +6,7 @@ #include "nsFontFace.h" #include "nsFontFaceLoader.h" #include "nsIFrame.h" -#include "gfxFont.h" +#include "gfxTextRun.h" #include "mozilla/gfx/2D.h" nsFontFaceList::nsFontFaceList()