michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/MathAlgorithms.h" michael@0: michael@0: #ifdef MOZ_LOGGING michael@0: #define FORCE_PR_LOG /* Allow logging in the release build */ michael@0: #endif michael@0: #include "prlog.h" michael@0: michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsExpirationTracker.h" michael@0: #include "nsILanguageAtomService.h" michael@0: #include "nsITimer.h" michael@0: michael@0: #include "gfxFont.h" michael@0: #include "gfxPlatform.h" michael@0: #include "nsGkAtoms.h" michael@0: michael@0: #include "gfxTypes.h" michael@0: #include "gfxContext.h" michael@0: #include "gfxFontMissingGlyphs.h" michael@0: #include "gfxHarfBuzzShaper.h" michael@0: #include "gfxUserFontSet.h" michael@0: #include "gfxPlatformFontList.h" michael@0: #include "gfxScriptItemizer.h" michael@0: #include "nsUnicodeProperties.h" michael@0: #include "nsMathUtils.h" michael@0: #include "nsBidiUtils.h" michael@0: #include "nsUnicodeRange.h" michael@0: #include "nsStyleConsts.h" michael@0: #include "mozilla/FloatingPoint.h" michael@0: #include "mozilla/Likely.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "gfxSVGGlyphs.h" michael@0: #include "gfxMathTable.h" michael@0: #include "gfx2DGlue.h" michael@0: michael@0: #if defined(XP_MACOSX) michael@0: #include "nsCocoaFeatures.h" michael@0: #endif michael@0: michael@0: #include "cairo.h" michael@0: #include "gfxFontTest.h" michael@0: michael@0: #include "harfbuzz/hb.h" michael@0: #include "harfbuzz/hb-ot.h" michael@0: #include "graphite2/Font.h" michael@0: michael@0: #include "nsCRT.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "gfxFontConstants.h" michael@0: michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::gfx; michael@0: using namespace mozilla::unicode; michael@0: using mozilla::services::GetObserverService; michael@0: michael@0: gfxFontCache *gfxFontCache::gGlobalCache = nullptr; michael@0: michael@0: static const char16_t kEllipsisChar[] = { 0x2026, 0x0 }; michael@0: static const char16_t kASCIIPeriodsChar[] = { '.', '.', '.', 0x0 }; michael@0: michael@0: #ifdef DEBUG_roc michael@0: #define DEBUG_TEXT_RUN_STORAGE_METRICS michael@0: #endif michael@0: michael@0: #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS michael@0: static uint32_t gTextRunStorageHighWaterMark = 0; michael@0: static uint32_t gTextRunStorage = 0; michael@0: static uint32_t gFontCount = 0; michael@0: static uint32_t gGlyphExtentsCount = 0; michael@0: static uint32_t gGlyphExtentsWidthsTotalSize = 0; michael@0: static uint32_t gGlyphExtentsSetupEagerSimple = 0; michael@0: static uint32_t gGlyphExtentsSetupEagerTight = 0; michael@0: static uint32_t gGlyphExtentsSetupLazyTight = 0; michael@0: static uint32_t gGlyphExtentsSetupFallBackToTight = 0; michael@0: #endif michael@0: michael@0: #ifdef PR_LOGGING michael@0: #define LOG_FONTINIT(args) PR_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), \ michael@0: PR_LOG_DEBUG, args) michael@0: #define LOG_FONTINIT_ENABLED() PR_LOG_TEST( \ michael@0: gfxPlatform::GetLog(eGfxLog_fontinit), \ michael@0: PR_LOG_DEBUG) michael@0: #endif // PR_LOGGING michael@0: michael@0: void michael@0: gfxCharacterMap::NotifyReleased() michael@0: { michael@0: gfxPlatformFontList *fontlist = gfxPlatformFontList::PlatformFontList(); michael@0: if (mShared) { michael@0: fontlist->RemoveCmap(this); michael@0: } michael@0: delete this; michael@0: } michael@0: michael@0: gfxFontEntry::gfxFontEntry() : michael@0: mItalic(false), mFixedPitch(false), michael@0: mIsProxy(false), mIsValid(true), michael@0: mIsBadUnderlineFont(false), michael@0: mIsUserFont(false), michael@0: mIsLocalUserFont(false), michael@0: mStandardFace(false), michael@0: mSymbolFont(false), michael@0: mIgnoreGDEF(false), michael@0: mIgnoreGSUB(false), michael@0: mSVGInitialized(false), michael@0: mMathInitialized(false), michael@0: mHasSpaceFeaturesInitialized(false), michael@0: mHasSpaceFeatures(false), michael@0: mHasSpaceFeaturesKerning(false), michael@0: mHasSpaceFeaturesNonKerning(false), michael@0: mSkipDefaultFeatureSpaceCheck(false), michael@0: mCheckedForGraphiteTables(false), michael@0: mHasCmapTable(false), michael@0: mGrFaceInitialized(false), michael@0: mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL), michael@0: mUVSOffset(0), mUVSData(nullptr), michael@0: mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE), michael@0: mUnitsPerEm(0), michael@0: mHBFace(nullptr), michael@0: mGrFace(nullptr), michael@0: mGrFaceRefCnt(0) michael@0: { michael@0: memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures)); michael@0: memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures)); michael@0: } michael@0: michael@0: gfxFontEntry::gfxFontEntry(const nsAString& aName, bool aIsStandardFace) : michael@0: mName(aName), mItalic(false), mFixedPitch(false), michael@0: mIsProxy(false), mIsValid(true), michael@0: mIsBadUnderlineFont(false), mIsUserFont(false), michael@0: mIsLocalUserFont(false), mStandardFace(aIsStandardFace), michael@0: mSymbolFont(false), michael@0: mIgnoreGDEF(false), michael@0: mIgnoreGSUB(false), michael@0: mSVGInitialized(false), michael@0: mMathInitialized(false), michael@0: mHasSpaceFeaturesInitialized(false), michael@0: mHasSpaceFeatures(false), michael@0: mHasSpaceFeaturesKerning(false), michael@0: mHasSpaceFeaturesNonKerning(false), michael@0: mSkipDefaultFeatureSpaceCheck(false), michael@0: mCheckedForGraphiteTables(false), michael@0: mHasCmapTable(false), michael@0: mGrFaceInitialized(false), michael@0: mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL), michael@0: mUVSOffset(0), mUVSData(nullptr), michael@0: mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE), michael@0: mUnitsPerEm(0), michael@0: mHBFace(nullptr), michael@0: mGrFace(nullptr), michael@0: mGrFaceRefCnt(0) michael@0: { michael@0: memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures)); michael@0: memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures)); michael@0: } michael@0: michael@0: gfxFontEntry::~gfxFontEntry() michael@0: { michael@0: // For downloaded fonts, we need to tell the user font cache that this michael@0: // entry is being deleted. michael@0: if (!mIsProxy && IsUserFont() && !IsLocalUserFont()) { michael@0: gfxUserFontSet::UserFontCache::ForgetFont(this); michael@0: } michael@0: michael@0: // By the time the entry is destroyed, all font instances that were michael@0: // using it should already have been deleted, and so the HB and/or Gr michael@0: // face objects should have been released. michael@0: MOZ_ASSERT(!mHBFace); michael@0: MOZ_ASSERT(!mGrFaceInitialized); michael@0: } michael@0: michael@0: bool gfxFontEntry::IsSymbolFont() michael@0: { michael@0: return mSymbolFont; michael@0: } michael@0: michael@0: bool gfxFontEntry::TestCharacterMap(uint32_t aCh) michael@0: { michael@0: if (!mCharacterMap) { michael@0: ReadCMAP(); michael@0: NS_ASSERTION(mCharacterMap, "failed to initialize character map"); michael@0: } michael@0: return mCharacterMap->test(aCh); michael@0: } michael@0: michael@0: nsresult gfxFontEntry::InitializeUVSMap() michael@0: { michael@0: // mUVSOffset will not be initialized michael@0: // until cmap is initialized. michael@0: if (!mCharacterMap) { michael@0: ReadCMAP(); michael@0: NS_ASSERTION(mCharacterMap, "failed to initialize character map"); michael@0: } michael@0: michael@0: if (!mUVSOffset) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!mUVSData) { michael@0: const uint32_t kCmapTag = TRUETYPE_TAG('c','m','a','p'); michael@0: AutoTable cmapTable(this, kCmapTag); michael@0: if (!cmapTable) { michael@0: mUVSOffset = 0; // don't bother to read the table again michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: uint8_t* uvsData; michael@0: unsigned int cmapLen; michael@0: const char* cmapData = hb_blob_get_data(cmapTable, &cmapLen); michael@0: nsresult rv = gfxFontUtils::ReadCMAPTableFormat14( michael@0: (const uint8_t*)cmapData + mUVSOffset, michael@0: cmapLen - mUVSOffset, uvsData); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: mUVSOffset = 0; // don't bother to read the table again michael@0: return rv; michael@0: } michael@0: michael@0: mUVSData = uvsData; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint16_t gfxFontEntry::GetUVSGlyph(uint32_t aCh, uint32_t aVS) michael@0: { michael@0: InitializeUVSMap(); michael@0: michael@0: if (mUVSData) { michael@0: return gfxFontUtils::MapUVSToGlyphFormat14(mUVSData, aCh, aVS); michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: bool gfxFontEntry::SupportsScriptInGSUB(const hb_tag_t* aScriptTags) michael@0: { michael@0: hb_face_t *face = GetHBFace(); michael@0: if (!face) { michael@0: return false; michael@0: } michael@0: michael@0: unsigned int index; michael@0: hb_tag_t chosenScript; michael@0: bool found = michael@0: hb_ot_layout_table_choose_script(face, TRUETYPE_TAG('G','S','U','B'), michael@0: aScriptTags, &index, &chosenScript); michael@0: hb_face_destroy(face); michael@0: michael@0: return found && chosenScript != TRUETYPE_TAG('D','F','L','T'); michael@0: } michael@0: michael@0: nsresult gfxFontEntry::ReadCMAP(FontInfoData *aFontInfoData) michael@0: { michael@0: NS_ASSERTION(false, "using default no-op implementation of ReadCMAP"); michael@0: mCharacterMap = new gfxCharacterMap(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsString michael@0: gfxFontEntry::RealFaceName() michael@0: { michael@0: AutoTable nameTable(this, TRUETYPE_TAG('n','a','m','e')); michael@0: if (nameTable) { michael@0: nsAutoString name; michael@0: nsresult rv = gfxFontUtils::GetFullNameFromTable(nameTable, name); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: return name; michael@0: } michael@0: } michael@0: return Name(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: gfxFontEntry::FindOrMakeFont(const gfxFontStyle *aStyle, bool aNeedsBold) michael@0: { michael@0: // the font entry name is the psname, not the family name michael@0: nsRefPtr font = gfxFontCache::GetCache()->Lookup(this, aStyle); michael@0: michael@0: if (!font) { michael@0: gfxFont *newFont = CreateFontInstance(aStyle, aNeedsBold); michael@0: if (!newFont) michael@0: return nullptr; michael@0: if (!newFont->Valid()) { michael@0: delete newFont; michael@0: return nullptr; michael@0: } michael@0: font = newFont; michael@0: gfxFontCache::GetCache()->AddNew(font); michael@0: } michael@0: return font.forget(); michael@0: } michael@0: michael@0: uint16_t michael@0: gfxFontEntry::UnitsPerEm() michael@0: { michael@0: if (!mUnitsPerEm) { michael@0: AutoTable headTable(this, TRUETYPE_TAG('h','e','a','d')); michael@0: if (headTable) { michael@0: uint32_t len; michael@0: const HeadTable* head = michael@0: reinterpret_cast(hb_blob_get_data(headTable, michael@0: &len)); michael@0: if (len >= sizeof(HeadTable)) { michael@0: mUnitsPerEm = head->unitsPerEm; michael@0: } michael@0: } michael@0: michael@0: // if we didn't find a usable 'head' table, or if the value was michael@0: // outside the valid range, record it as invalid michael@0: if (mUnitsPerEm < kMinUPEM || mUnitsPerEm > kMaxUPEM) { michael@0: mUnitsPerEm = kInvalidUPEM; michael@0: } michael@0: } michael@0: return mUnitsPerEm; michael@0: } michael@0: michael@0: bool michael@0: gfxFontEntry::HasSVGGlyph(uint32_t aGlyphId) michael@0: { michael@0: NS_ASSERTION(mSVGInitialized, "SVG data has not yet been loaded. TryGetSVGData() first."); michael@0: return mSVGGlyphs->HasSVGGlyph(aGlyphId); michael@0: } michael@0: michael@0: bool michael@0: gfxFontEntry::GetSVGGlyphExtents(gfxContext *aContext, uint32_t aGlyphId, michael@0: gfxRect *aResult) michael@0: { michael@0: NS_ABORT_IF_FALSE(mSVGInitialized, michael@0: "SVG data has not yet been loaded. TryGetSVGData() first."); michael@0: NS_ABORT_IF_FALSE(mUnitsPerEm >= kMinUPEM && mUnitsPerEm <= kMaxUPEM, michael@0: "font has invalid unitsPerEm"); michael@0: michael@0: gfxContextAutoSaveRestore matrixRestore(aContext); michael@0: cairo_matrix_t fontMatrix; michael@0: cairo_get_font_matrix(aContext->GetCairo(), &fontMatrix); michael@0: michael@0: gfxMatrix svgToAppSpace = *reinterpret_cast(&fontMatrix); michael@0: svgToAppSpace.Scale(1.0f / mUnitsPerEm, 1.0f / mUnitsPerEm); michael@0: michael@0: return mSVGGlyphs->GetGlyphExtents(aGlyphId, svgToAppSpace, aResult); michael@0: } michael@0: michael@0: bool michael@0: gfxFontEntry::RenderSVGGlyph(gfxContext *aContext, uint32_t aGlyphId, michael@0: int aDrawMode, gfxTextContextPaint *aContextPaint) michael@0: { michael@0: NS_ASSERTION(mSVGInitialized, "SVG data has not yet been loaded. TryGetSVGData() first."); michael@0: return mSVGGlyphs->RenderGlyph(aContext, aGlyphId, DrawMode(aDrawMode), michael@0: aContextPaint); michael@0: } michael@0: michael@0: bool michael@0: gfxFontEntry::TryGetSVGData(gfxFont* aFont) michael@0: { michael@0: if (!gfxPlatform::GetPlatform()->OpenTypeSVGEnabled()) { michael@0: return false; michael@0: } michael@0: michael@0: if (!mSVGInitialized) { michael@0: mSVGInitialized = true; michael@0: michael@0: // If UnitsPerEm is not known/valid, we can't use SVG glyphs michael@0: if (UnitsPerEm() == kInvalidUPEM) { michael@0: return false; michael@0: } michael@0: michael@0: // We don't use AutoTable here because we'll pass ownership of this michael@0: // blob to the gfxSVGGlyphs, once we've confirmed the table exists michael@0: hb_blob_t *svgTable = GetFontTable(TRUETYPE_TAG('S','V','G',' ')); michael@0: if (!svgTable) { michael@0: return false; michael@0: } michael@0: michael@0: // gfxSVGGlyphs will hb_blob_destroy() the table when it is finished michael@0: // with it. michael@0: mSVGGlyphs = new gfxSVGGlyphs(svgTable, this); michael@0: } michael@0: michael@0: if (!mFontsUsingSVGGlyphs.Contains(aFont)) { michael@0: mFontsUsingSVGGlyphs.AppendElement(aFont); michael@0: } michael@0: michael@0: return !!mSVGGlyphs; michael@0: } michael@0: michael@0: void michael@0: gfxFontEntry::NotifyFontDestroyed(gfxFont* aFont) michael@0: { michael@0: mFontsUsingSVGGlyphs.RemoveElement(aFont); michael@0: } michael@0: michael@0: void michael@0: gfxFontEntry::NotifyGlyphsChanged() michael@0: { michael@0: for (uint32_t i = 0, count = mFontsUsingSVGGlyphs.Length(); i < count; ++i) { michael@0: gfxFont* font = mFontsUsingSVGGlyphs[i]; michael@0: font->NotifyGlyphsChanged(); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: gfxFontEntry::TryGetMathTable(gfxFont* aFont) michael@0: { michael@0: if (!mMathInitialized) { michael@0: mMathInitialized = true; michael@0: michael@0: // If UnitsPerEm is not known/valid, we can't use MATH table michael@0: if (UnitsPerEm() == kInvalidUPEM) { michael@0: return false; michael@0: } michael@0: michael@0: // We don't use AutoTable here because we'll pass ownership of this michael@0: // blob to the gfxMathTable, once we've confirmed the table exists michael@0: hb_blob_t *mathTable = GetFontTable(TRUETYPE_TAG('M','A','T','H')); michael@0: if (!mathTable) { michael@0: return false; michael@0: } michael@0: michael@0: // gfxMathTable will hb_blob_destroy() the table when it is finished michael@0: // with it. michael@0: mMathTable = new gfxMathTable(mathTable); michael@0: if (!mMathTable->HasValidHeaders()) { michael@0: mMathTable = nullptr; michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return !!mMathTable; michael@0: } michael@0: michael@0: gfxFloat michael@0: gfxFontEntry::GetMathConstant(gfxFontEntry::MathConstant aConstant) michael@0: { michael@0: NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first."); michael@0: gfxFloat value = mMathTable->GetMathConstant(aConstant); michael@0: if (aConstant == gfxFontEntry::ScriptPercentScaleDown || michael@0: aConstant == gfxFontEntry::ScriptScriptPercentScaleDown || michael@0: aConstant == gfxFontEntry::RadicalDegreeBottomRaisePercent) { michael@0: return value / 100.0; michael@0: } michael@0: return value / mUnitsPerEm; michael@0: } michael@0: michael@0: bool michael@0: gfxFontEntry::GetMathItalicsCorrection(uint32_t aGlyphID, michael@0: gfxFloat* aItalicCorrection) michael@0: { michael@0: NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first."); michael@0: int16_t italicCorrection; michael@0: if (!mMathTable->GetMathItalicsCorrection(aGlyphID, &italicCorrection)) { michael@0: return false; michael@0: } michael@0: *aItalicCorrection = gfxFloat(italicCorrection) / mUnitsPerEm; michael@0: return true; michael@0: } michael@0: michael@0: uint32_t michael@0: gfxFontEntry::GetMathVariantsSize(uint32_t aGlyphID, bool aVertical, michael@0: uint16_t aSize) michael@0: { michael@0: NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first."); michael@0: return mMathTable->GetMathVariantsSize(aGlyphID, aVertical, aSize); michael@0: } michael@0: michael@0: bool michael@0: gfxFontEntry::GetMathVariantsParts(uint32_t aGlyphID, bool aVertical, michael@0: uint32_t aGlyphs[4]) michael@0: { michael@0: NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first."); michael@0: return mMathTable->GetMathVariantsParts(aGlyphID, aVertical, aGlyphs); michael@0: } michael@0: michael@0: /** michael@0: * FontTableBlobData michael@0: * michael@0: * See FontTableHashEntry for the general strategy. michael@0: */ michael@0: michael@0: class gfxFontEntry::FontTableBlobData { michael@0: public: michael@0: // Adopts the content of aBuffer. michael@0: FontTableBlobData(FallibleTArray& aBuffer) michael@0: : mHashtable(nullptr), mHashKey(0) michael@0: { michael@0: MOZ_COUNT_CTOR(FontTableBlobData); michael@0: mTableData.SwapElements(aBuffer); michael@0: } michael@0: michael@0: ~FontTableBlobData() { michael@0: MOZ_COUNT_DTOR(FontTableBlobData); michael@0: if (mHashtable && mHashKey) { michael@0: mHashtable->RemoveEntry(mHashKey); michael@0: } michael@0: } michael@0: michael@0: // Useful for creating blobs michael@0: const char *GetTable() const michael@0: { michael@0: return reinterpret_cast(mTableData.Elements()); michael@0: } michael@0: uint32_t GetTableLength() const { return mTableData.Length(); } michael@0: michael@0: // Tell this FontTableBlobData to remove the HashEntry when this is michael@0: // destroyed. michael@0: void ManageHashEntry(nsTHashtable *aHashtable, michael@0: uint32_t aHashKey) michael@0: { michael@0: mHashtable = aHashtable; michael@0: mHashKey = aHashKey; michael@0: } michael@0: michael@0: // Disconnect from the HashEntry (because the blob has already been michael@0: // removed from the hashtable). michael@0: void ForgetHashEntry() michael@0: { michael@0: mHashtable = nullptr; michael@0: mHashKey = 0; michael@0: } michael@0: michael@0: size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { michael@0: return mTableData.SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: private: michael@0: // The font table data block, owned (via adoption) michael@0: FallibleTArray mTableData; michael@0: michael@0: // The blob destroy function needs to know the owning hashtable michael@0: // and the hashtable key, so that it can remove the entry. michael@0: nsTHashtable *mHashtable; michael@0: uint32_t mHashKey; michael@0: michael@0: // not implemented michael@0: FontTableBlobData(const FontTableBlobData&); michael@0: }; michael@0: michael@0: hb_blob_t * michael@0: gfxFontEntry::FontTableHashEntry:: michael@0: ShareTableAndGetBlob(FallibleTArray& aTable, michael@0: nsTHashtable *aHashtable) michael@0: { michael@0: Clear(); michael@0: // adopts elements of aTable michael@0: mSharedBlobData = new FontTableBlobData(aTable); michael@0: mBlob = hb_blob_create(mSharedBlobData->GetTable(), michael@0: mSharedBlobData->GetTableLength(), michael@0: HB_MEMORY_MODE_READONLY, michael@0: mSharedBlobData, DeleteFontTableBlobData); michael@0: if (!mSharedBlobData) { michael@0: // The FontTableBlobData was destroyed during hb_blob_create(). michael@0: // The (empty) blob is still be held in the hashtable with a strong michael@0: // reference. michael@0: return hb_blob_reference(mBlob); michael@0: } michael@0: michael@0: // Tell the FontTableBlobData to remove this hash entry when destroyed. michael@0: // The hashtable does not keep a strong reference. michael@0: mSharedBlobData->ManageHashEntry(aHashtable, GetKey()); michael@0: return mBlob; michael@0: } michael@0: michael@0: void michael@0: gfxFontEntry::FontTableHashEntry::Clear() michael@0: { michael@0: // If the FontTableBlobData is managing the hash entry, then the blob is michael@0: // not owned by this HashEntry; otherwise there is strong reference to the michael@0: // blob that must be removed. michael@0: if (mSharedBlobData) { michael@0: mSharedBlobData->ForgetHashEntry(); michael@0: mSharedBlobData = nullptr; michael@0: } else if (mBlob) { michael@0: hb_blob_destroy(mBlob); michael@0: } michael@0: mBlob = nullptr; michael@0: } michael@0: michael@0: // a hb_destroy_func for hb_blob_create michael@0: michael@0: /* static */ void michael@0: gfxFontEntry::FontTableHashEntry::DeleteFontTableBlobData(void *aBlobData) michael@0: { michael@0: delete static_cast(aBlobData); michael@0: } michael@0: michael@0: hb_blob_t * michael@0: gfxFontEntry::FontTableHashEntry::GetBlob() const michael@0: { michael@0: return hb_blob_reference(mBlob); michael@0: } michael@0: michael@0: bool michael@0: gfxFontEntry::GetExistingFontTable(uint32_t aTag, hb_blob_t **aBlob) michael@0: { michael@0: if (!mFontTableCache) { michael@0: // we do this here rather than on fontEntry construction michael@0: // because not all shapers will access the table cache at all michael@0: mFontTableCache = new nsTHashtable(10); michael@0: } michael@0: michael@0: FontTableHashEntry *entry = mFontTableCache->GetEntry(aTag); michael@0: if (!entry) { michael@0: return false; michael@0: } michael@0: michael@0: *aBlob = entry->GetBlob(); michael@0: return true; michael@0: } michael@0: michael@0: hb_blob_t * michael@0: gfxFontEntry::ShareFontTableAndGetBlob(uint32_t aTag, michael@0: FallibleTArray* aBuffer) michael@0: { michael@0: if (MOZ_UNLIKELY(!mFontTableCache)) { michael@0: // we do this here rather than on fontEntry construction michael@0: // because not all shapers will access the table cache at all michael@0: mFontTableCache = new nsTHashtable(10); michael@0: } michael@0: michael@0: FontTableHashEntry *entry = mFontTableCache->PutEntry(aTag); michael@0: if (MOZ_UNLIKELY(!entry)) { // OOM michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!aBuffer) { michael@0: // ensure the entry is null michael@0: entry->Clear(); michael@0: return nullptr; michael@0: } michael@0: michael@0: return entry->ShareTableAndGetBlob(*aBuffer, mFontTableCache); michael@0: } michael@0: michael@0: static int michael@0: DirEntryCmp(const void* aKey, const void* aItem) michael@0: { michael@0: int32_t tag = *static_cast(aKey); michael@0: const TableDirEntry* entry = static_cast(aItem); michael@0: return tag - int32_t(entry->tag); michael@0: } michael@0: michael@0: hb_blob_t* michael@0: gfxFontEntry::GetTableFromFontData(const void* aFontData, uint32_t aTableTag) michael@0: { michael@0: const SFNTHeader* header = michael@0: reinterpret_cast(aFontData); michael@0: const TableDirEntry* dir = michael@0: reinterpret_cast(header + 1); michael@0: dir = static_cast michael@0: (bsearch(&aTableTag, dir, uint16_t(header->numTables), michael@0: sizeof(TableDirEntry), DirEntryCmp)); michael@0: if (dir) { michael@0: return hb_blob_create(reinterpret_cast(aFontData) + michael@0: dir->offset, dir->length, michael@0: HB_MEMORY_MODE_READONLY, nullptr, nullptr); michael@0: michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: already_AddRefed michael@0: gfxFontEntry::GetCMAPFromFontInfo(FontInfoData *aFontInfoData, michael@0: uint32_t& aUVSOffset, michael@0: bool& aSymbolFont) michael@0: { michael@0: if (!aFontInfoData || !aFontInfoData->mLoadCmaps) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return aFontInfoData->GetCMAP(mName, aUVSOffset, aSymbolFont); michael@0: } michael@0: michael@0: hb_blob_t * michael@0: gfxFontEntry::GetFontTable(uint32_t aTag) michael@0: { michael@0: hb_blob_t *blob; michael@0: if (GetExistingFontTable(aTag, &blob)) { michael@0: return blob; michael@0: } michael@0: michael@0: FallibleTArray buffer; michael@0: bool haveTable = NS_SUCCEEDED(CopyFontTable(aTag, buffer)); michael@0: michael@0: return ShareFontTableAndGetBlob(aTag, haveTable ? &buffer : nullptr); michael@0: } michael@0: michael@0: // callback for HarfBuzz to get a font table (in hb_blob_t form) michael@0: // from the font entry (passed as aUserData) michael@0: /*static*/ hb_blob_t * michael@0: gfxFontEntry::HBGetTable(hb_face_t *face, uint32_t aTag, void *aUserData) michael@0: { michael@0: gfxFontEntry *fontEntry = static_cast(aUserData); michael@0: michael@0: // bug 589682 - ignore the GDEF table in buggy fonts (applies to michael@0: // Italic and BoldItalic faces of Times New Roman) michael@0: if (aTag == TRUETYPE_TAG('G','D','E','F') && michael@0: fontEntry->IgnoreGDEF()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // bug 721719 - ignore the GSUB table in buggy fonts (applies to Roboto, michael@0: // at least on some Android ICS devices; set in gfxFT2FontList.cpp) michael@0: if (aTag == TRUETYPE_TAG('G','S','U','B') && michael@0: fontEntry->IgnoreGSUB()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return fontEntry->GetFontTable(aTag); michael@0: } michael@0: michael@0: /*static*/ void michael@0: gfxFontEntry::HBFaceDeletedCallback(void *aUserData) michael@0: { michael@0: gfxFontEntry *fe = static_cast(aUserData); michael@0: fe->ForgetHBFace(); michael@0: } michael@0: michael@0: void michael@0: gfxFontEntry::ForgetHBFace() michael@0: { michael@0: mHBFace = nullptr; michael@0: } michael@0: michael@0: hb_face_t* michael@0: gfxFontEntry::GetHBFace() michael@0: { michael@0: if (!mHBFace) { michael@0: mHBFace = hb_face_create_for_tables(HBGetTable, this, michael@0: HBFaceDeletedCallback); michael@0: return mHBFace; michael@0: } michael@0: return hb_face_reference(mHBFace); michael@0: } michael@0: michael@0: /*static*/ const void* michael@0: gfxFontEntry::GrGetTable(const void *aAppFaceHandle, unsigned int aName, michael@0: size_t *aLen) michael@0: { michael@0: gfxFontEntry *fontEntry = michael@0: static_cast(const_cast(aAppFaceHandle)); michael@0: hb_blob_t *blob = fontEntry->GetFontTable(aName); michael@0: if (blob) { michael@0: unsigned int blobLength; michael@0: const void *tableData = hb_blob_get_data(blob, &blobLength); michael@0: fontEntry->mGrTableMap->Put(tableData, blob); michael@0: *aLen = blobLength; michael@0: return tableData; michael@0: } michael@0: *aLen = 0; michael@0: return nullptr; michael@0: } michael@0: michael@0: /*static*/ void michael@0: gfxFontEntry::GrReleaseTable(const void *aAppFaceHandle, michael@0: const void *aTableBuffer) michael@0: { michael@0: gfxFontEntry *fontEntry = michael@0: static_cast(const_cast(aAppFaceHandle)); michael@0: void *data; michael@0: if (fontEntry->mGrTableMap->Get(aTableBuffer, &data)) { michael@0: fontEntry->mGrTableMap->Remove(aTableBuffer); michael@0: hb_blob_destroy(static_cast(data)); michael@0: } michael@0: } michael@0: michael@0: gr_face* michael@0: gfxFontEntry::GetGrFace() michael@0: { michael@0: if (!mGrFaceInitialized) { michael@0: gr_face_ops faceOps = { michael@0: sizeof(gr_face_ops), michael@0: GrGetTable, michael@0: GrReleaseTable michael@0: }; michael@0: mGrTableMap = new nsDataHashtable,void*>; michael@0: mGrFace = gr_make_face_with_ops(this, &faceOps, gr_face_default); michael@0: mGrFaceInitialized = true; michael@0: } michael@0: ++mGrFaceRefCnt; michael@0: return mGrFace; michael@0: } michael@0: michael@0: void michael@0: gfxFontEntry::ReleaseGrFace(gr_face *aFace) michael@0: { michael@0: MOZ_ASSERT(aFace == mGrFace); // sanity-check michael@0: MOZ_ASSERT(mGrFaceRefCnt > 0); michael@0: if (--mGrFaceRefCnt == 0) { michael@0: gr_face_destroy(mGrFace); michael@0: mGrFace = nullptr; michael@0: mGrFaceInitialized = false; michael@0: delete mGrTableMap; michael@0: mGrTableMap = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxFontEntry::DisconnectSVG() michael@0: { michael@0: if (mSVGInitialized && mSVGGlyphs) { michael@0: mSVGGlyphs = nullptr; michael@0: mSVGInitialized = false; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: gfxFontEntry::HasFontTable(uint32_t aTableTag) michael@0: { michael@0: AutoTable table(this, aTableTag); michael@0: return table && hb_blob_get_length(table) > 0; michael@0: } michael@0: michael@0: void michael@0: gfxFontEntry::CheckForGraphiteTables() michael@0: { michael@0: mHasGraphiteTables = HasFontTable(TRUETYPE_TAG('S','i','l','f')); michael@0: } michael@0: michael@0: /* static */ size_t michael@0: gfxFontEntry::FontTableHashEntry::SizeOfEntryExcludingThis michael@0: (FontTableHashEntry *aEntry, michael@0: MallocSizeOf aMallocSizeOf, michael@0: void* aUserArg) michael@0: { michael@0: size_t n = 0; michael@0: if (aEntry->mBlob) { michael@0: n += aMallocSizeOf(aEntry->mBlob); michael@0: } michael@0: if (aEntry->mSharedBlobData) { michael@0: n += aEntry->mSharedBlobData->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: return n; michael@0: } michael@0: michael@0: void michael@0: gfxFontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, michael@0: FontListSizes* aSizes) const michael@0: { michael@0: aSizes->mFontListSize += mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); michael@0: michael@0: // cmaps are shared so only non-shared cmaps are included here michael@0: if (mCharacterMap && mCharacterMap->mBuildOnTheFly) { michael@0: aSizes->mCharMapsSize += michael@0: mCharacterMap->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: if (mFontTableCache) { michael@0: aSizes->mFontTableCacheSize += michael@0: mFontTableCache->SizeOfExcludingThis( michael@0: FontTableHashEntry::SizeOfEntryExcludingThis, michael@0: aMallocSizeOf); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, michael@0: FontListSizes* aSizes) const michael@0: { michael@0: aSizes->mFontListSize += aMallocSizeOf(this); michael@0: AddSizeOfExcludingThis(aMallocSizeOf, aSizes); michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: // michael@0: // class gfxFontFamily michael@0: // michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: // we consider faces with mStandardFace == true to be "greater than" those with false, michael@0: // because during style matching, later entries will replace earlier ones michael@0: class FontEntryStandardFaceComparator { michael@0: public: michael@0: bool Equals(const nsRefPtr& a, const nsRefPtr& b) const { michael@0: return a->mStandardFace == b->mStandardFace; michael@0: } michael@0: bool LessThan(const nsRefPtr& a, const nsRefPtr& b) const { michael@0: return (a->mStandardFace == false && b->mStandardFace == true); michael@0: } michael@0: }; michael@0: michael@0: void michael@0: gfxFontFamily::SortAvailableFonts() michael@0: { michael@0: mAvailableFonts.Sort(FontEntryStandardFaceComparator()); michael@0: } michael@0: michael@0: bool michael@0: gfxFontFamily::HasOtherFamilyNames() michael@0: { michael@0: // need to read in other family names to determine this michael@0: if (!mOtherFamilyNamesInitialized) { michael@0: ReadOtherFamilyNames(gfxPlatformFontList::PlatformFontList()); // sets mHasOtherFamilyNames michael@0: } michael@0: return mHasOtherFamilyNames; michael@0: } michael@0: michael@0: gfxFontEntry* michael@0: gfxFontFamily::FindFontForStyle(const gfxFontStyle& aFontStyle, michael@0: bool& aNeedsSyntheticBold) michael@0: { michael@0: if (!mHasStyles) michael@0: FindStyleVariations(); // collect faces for the family, if not already done michael@0: michael@0: NS_ASSERTION(mAvailableFonts.Length() > 0, "font family with no faces!"); michael@0: michael@0: aNeedsSyntheticBold = false; michael@0: michael@0: int8_t baseWeight = aFontStyle.ComputeWeight(); michael@0: bool wantBold = baseWeight >= 6; michael@0: michael@0: // If the family has only one face, we simply return it; no further checking needed michael@0: if (mAvailableFonts.Length() == 1) { michael@0: gfxFontEntry *fe = mAvailableFonts[0]; michael@0: aNeedsSyntheticBold = wantBold && !fe->IsBold(); michael@0: return fe; michael@0: } michael@0: michael@0: bool wantItalic = (aFontStyle.style & michael@0: (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0; michael@0: michael@0: // Most families are "simple", having just Regular/Bold/Italic/BoldItalic, michael@0: // or some subset of these. In this case, we have exactly 4 entries in mAvailableFonts, michael@0: // stored in the above order; note that some of the entries may be nullptr. michael@0: // We can then pick the required entry based on whether the request is for michael@0: // bold or non-bold, italic or non-italic, without running the more complex michael@0: // matching algorithm used for larger families with many weights and/or widths. michael@0: michael@0: if (mIsSimpleFamily) { michael@0: // Family has no more than the "standard" 4 faces, at fixed indexes; michael@0: // calculate which one we want. michael@0: // Note that we cannot simply return it as not all 4 faces are necessarily present. michael@0: uint8_t faceIndex = (wantItalic ? kItalicMask : 0) | michael@0: (wantBold ? kBoldMask : 0); michael@0: michael@0: // if the desired style is available, return it directly michael@0: gfxFontEntry *fe = mAvailableFonts[faceIndex]; michael@0: if (fe) { michael@0: // no need to set aNeedsSyntheticBold here as we matched the boldness request michael@0: return fe; michael@0: } michael@0: michael@0: // order to check fallback faces in a simple family, depending on requested style michael@0: static const uint8_t simpleFallbacks[4][3] = { michael@0: { kBoldFaceIndex, kItalicFaceIndex, kBoldItalicFaceIndex }, // fallbacks for Regular michael@0: { kRegularFaceIndex, kBoldItalicFaceIndex, kItalicFaceIndex },// Bold michael@0: { kBoldItalicFaceIndex, kRegularFaceIndex, kBoldFaceIndex }, // Italic michael@0: { kItalicFaceIndex, kBoldFaceIndex, kRegularFaceIndex } // BoldItalic michael@0: }; michael@0: const uint8_t *order = simpleFallbacks[faceIndex]; michael@0: michael@0: for (uint8_t trial = 0; trial < 3; ++trial) { michael@0: // check remaining faces in order of preference to find the first that actually exists michael@0: fe = mAvailableFonts[order[trial]]; michael@0: if (fe) { michael@0: aNeedsSyntheticBold = wantBold && !fe->IsBold(); michael@0: return fe; michael@0: } michael@0: } michael@0: michael@0: // this can't happen unless we have totally broken the font-list manager! michael@0: NS_NOTREACHED("no face found in simple font family!"); michael@0: return nullptr; michael@0: } michael@0: michael@0: // This is a large/rich font family, so we do full style- and weight-matching: michael@0: // first collect a list of weights that are the best match for the requested michael@0: // font-stretch and font-style, then pick the best weight match among those michael@0: // available. michael@0: michael@0: gfxFontEntry *weightList[10] = { 0 }; michael@0: bool foundWeights = FindWeightsForStyle(weightList, wantItalic, aFontStyle.stretch); michael@0: if (!foundWeights) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // First find a match for the best weight michael@0: int8_t matchBaseWeight = 0; michael@0: int8_t i = baseWeight; michael@0: michael@0: // Need to special case when normal face doesn't exist but medium does. michael@0: // In that case, use medium otherwise weights < 400 michael@0: if (baseWeight == 4 && !weightList[4]) { michael@0: i = 5; // medium michael@0: } michael@0: michael@0: // Loop through weights, since one exists loop will terminate michael@0: int8_t direction = (baseWeight > 5) ? 1 : -1; michael@0: for (; ; i += direction) { michael@0: if (weightList[i]) { michael@0: matchBaseWeight = i; michael@0: break; michael@0: } michael@0: michael@0: // If we've reached one side without finding a font, michael@0: // start over and go the other direction until we find a match michael@0: if (i == 1 || i == 9) { michael@0: i = baseWeight; michael@0: direction = -direction; michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(matchBaseWeight != 0, michael@0: "weight mapping should always find at least one font in a family"); michael@0: michael@0: gfxFontEntry *matchFE = weightList[matchBaseWeight]; michael@0: michael@0: NS_ASSERTION(matchFE, michael@0: "weight mapping should always find at least one font in a family"); michael@0: michael@0: if (!matchFE->IsBold() && baseWeight >= 6) michael@0: { michael@0: aNeedsSyntheticBold = true; michael@0: } michael@0: michael@0: return matchFE; michael@0: } michael@0: michael@0: void michael@0: gfxFontFamily::CheckForSimpleFamily() michael@0: { michael@0: // already checked this family michael@0: if (mIsSimpleFamily) { michael@0: return; michael@0: }; michael@0: michael@0: uint32_t count = mAvailableFonts.Length(); michael@0: if (count > 4 || count == 0) { michael@0: return; // can't be "simple" if there are >4 faces; michael@0: // if none then the family is unusable anyway michael@0: } michael@0: michael@0: if (count == 1) { michael@0: mIsSimpleFamily = true; michael@0: return; michael@0: } michael@0: michael@0: int16_t firstStretch = mAvailableFonts[0]->Stretch(); michael@0: michael@0: gfxFontEntry *faces[4] = { 0 }; michael@0: for (uint8_t i = 0; i < count; ++i) { michael@0: gfxFontEntry *fe = mAvailableFonts[i]; michael@0: if (fe->Stretch() != firstStretch) { michael@0: return; // font-stretch doesn't match, don't treat as simple family michael@0: } michael@0: uint8_t faceIndex = (fe->IsItalic() ? kItalicMask : 0) | michael@0: (fe->Weight() >= 600 ? kBoldMask : 0); michael@0: if (faces[faceIndex]) { michael@0: return; // two faces resolve to the same slot; family isn't "simple" michael@0: } michael@0: faces[faceIndex] = fe; michael@0: } michael@0: michael@0: // we have successfully slotted the available faces into the standard michael@0: // 4-face framework michael@0: mAvailableFonts.SetLength(4); michael@0: for (uint8_t i = 0; i < 4; ++i) { michael@0: if (mAvailableFonts[i].get() != faces[i]) { michael@0: mAvailableFonts[i].swap(faces[i]); michael@0: } michael@0: } michael@0: michael@0: mIsSimpleFamily = true; michael@0: } michael@0: michael@0: static inline uint32_t michael@0: StyleDistance(gfxFontEntry *aFontEntry, michael@0: bool anItalic, int16_t aStretch) michael@0: { michael@0: // Compute a measure of the "distance" between the requested style michael@0: // and the given fontEntry, michael@0: // considering italicness and font-stretch but not weight. michael@0: michael@0: int32_t distance = 0; michael@0: if (aStretch != aFontEntry->mStretch) { michael@0: // stretch values are in the range -4 .. +4 michael@0: // if aStretch is positive, we prefer more-positive values; michael@0: // if zero or negative, prefer more-negative michael@0: if (aStretch > 0) { michael@0: distance = (aFontEntry->mStretch - aStretch) * 2; michael@0: } else { michael@0: distance = (aStretch - aFontEntry->mStretch) * 2; michael@0: } michael@0: // if the computed "distance" here is negative, it means that michael@0: // aFontEntry lies in the "non-preferred" direction from aStretch, michael@0: // so we treat that as larger than any preferred-direction distance michael@0: // (max possible is 8) by adding an extra 10 to the absolute value michael@0: if (distance < 0) { michael@0: distance = -distance + 10; michael@0: } michael@0: } michael@0: if (aFontEntry->IsItalic() != anItalic) { michael@0: distance += 1; michael@0: } michael@0: return uint32_t(distance); michael@0: } michael@0: michael@0: bool michael@0: gfxFontFamily::FindWeightsForStyle(gfxFontEntry* aFontsForWeights[], michael@0: bool anItalic, int16_t aStretch) michael@0: { michael@0: uint32_t foundWeights = 0; michael@0: uint32_t bestMatchDistance = 0xffffffff; michael@0: michael@0: uint32_t count = mAvailableFonts.Length(); michael@0: for (uint32_t i = 0; i < count; i++) { michael@0: // this is not called for "simple" families, and therefore it does not michael@0: // need to check the mAvailableFonts entries for nullptr. michael@0: gfxFontEntry *fe = mAvailableFonts[i]; michael@0: uint32_t distance = StyleDistance(fe, anItalic, aStretch); michael@0: if (distance <= bestMatchDistance) { michael@0: int8_t wt = fe->mWeight / 100; michael@0: NS_ASSERTION(wt >= 1 && wt < 10, "invalid weight in fontEntry"); michael@0: if (!aFontsForWeights[wt]) { michael@0: // record this as a possible candidate for weight matching michael@0: aFontsForWeights[wt] = fe; michael@0: ++foundWeights; michael@0: } else { michael@0: uint32_t prevDistance = michael@0: StyleDistance(aFontsForWeights[wt], anItalic, aStretch); michael@0: if (prevDistance >= distance) { michael@0: // replacing a weight we already found, michael@0: // so don't increment foundWeights michael@0: aFontsForWeights[wt] = fe; michael@0: } michael@0: } michael@0: bestMatchDistance = distance; michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(foundWeights > 0, "Font family containing no faces?"); michael@0: michael@0: if (foundWeights == 1) { michael@0: // no need to cull entries if we only found one weight michael@0: return true; michael@0: } michael@0: michael@0: // we might have recorded some faces that were a partial style match, but later found michael@0: // others that were closer; in this case, we need to cull the poorer matches from the michael@0: // weight list we'll return michael@0: for (uint32_t i = 0; i < 10; ++i) { michael@0: if (aFontsForWeights[i] && michael@0: StyleDistance(aFontsForWeights[i], anItalic, aStretch) > bestMatchDistance) michael@0: { michael@0: aFontsForWeights[i] = 0; michael@0: } michael@0: } michael@0: michael@0: return (foundWeights > 0); michael@0: } michael@0: michael@0: michael@0: void gfxFontFamily::LocalizedName(nsAString& aLocalizedName) michael@0: { michael@0: // just return the primary name; subclasses should override michael@0: aLocalizedName = mName; michael@0: } michael@0: michael@0: // metric for how close a given font matches a style michael@0: static int32_t michael@0: CalcStyleMatch(gfxFontEntry *aFontEntry, const gfxFontStyle *aStyle) michael@0: { michael@0: int32_t rank = 0; michael@0: if (aStyle) { michael@0: // italics michael@0: bool wantItalic = michael@0: (aStyle->style & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0; michael@0: if (aFontEntry->IsItalic() == wantItalic) { michael@0: rank += 10; michael@0: } michael@0: michael@0: // measure of closeness of weight to the desired value michael@0: rank += 9 - DeprecatedAbs(aFontEntry->Weight() / 100 - aStyle->ComputeWeight()); michael@0: } else { michael@0: // if no font to match, prefer non-bold, non-italic fonts michael@0: if (!aFontEntry->IsItalic()) { michael@0: rank += 3; michael@0: } michael@0: if (!aFontEntry->IsBold()) { michael@0: rank += 2; michael@0: } michael@0: } michael@0: michael@0: return rank; michael@0: } michael@0: michael@0: #define RANK_MATCHED_CMAP 20 michael@0: michael@0: void michael@0: gfxFontFamily::FindFontForChar(GlobalFontMatch *aMatchData) michael@0: { michael@0: if (mFamilyCharacterMapInitialized && !TestCharacterMap(aMatchData->mCh)) { michael@0: // none of the faces in the family support the required char, michael@0: // so bail out immediately michael@0: return; michael@0: } michael@0: michael@0: bool needsBold; michael@0: gfxFontStyle normal; michael@0: gfxFontEntry *fe = FindFontForStyle( michael@0: (aMatchData->mStyle == nullptr) ? *aMatchData->mStyle : normal, michael@0: needsBold); michael@0: michael@0: if (fe && !fe->SkipDuringSystemFallback()) { michael@0: int32_t rank = 0; michael@0: michael@0: if (fe->TestCharacterMap(aMatchData->mCh)) { michael@0: rank += RANK_MATCHED_CMAP; michael@0: aMatchData->mCount++; michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo *log = gfxPlatform::GetLog(eGfxLog_textrun); michael@0: michael@0: if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_DEBUG))) { michael@0: uint32_t unicodeRange = FindCharUnicodeRange(aMatchData->mCh); michael@0: uint32_t script = GetScriptCode(aMatchData->mCh); michael@0: PR_LOG(log, PR_LOG_DEBUG,\ michael@0: ("(textrun-systemfallback-fonts) char: u+%6.6x " michael@0: "unicode-range: %d script: %d match: [%s]\n", michael@0: aMatchData->mCh, michael@0: unicodeRange, script, michael@0: NS_ConvertUTF16toUTF8(fe->Name()).get())); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: aMatchData->mCmapsTested++; michael@0: if (rank == 0) { michael@0: return; michael@0: } michael@0: michael@0: // omitting from original windows code -- family name, lang group, pitch michael@0: // not available in current FontEntry implementation michael@0: rank += CalcStyleMatch(fe, aMatchData->mStyle); michael@0: michael@0: // xxx - add whether AAT font with morphing info for specific lang groups michael@0: michael@0: if (rank > aMatchData->mMatchRank michael@0: || (rank == aMatchData->mMatchRank && michael@0: Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) michael@0: { michael@0: aMatchData->mBestMatch = fe; michael@0: aMatchData->mMatchedFamily = this; michael@0: aMatchData->mMatchRank = rank; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxFontFamily::SearchAllFontsForChar(GlobalFontMatch *aMatchData) michael@0: { michael@0: uint32_t i, numFonts = mAvailableFonts.Length(); michael@0: for (i = 0; i < numFonts; i++) { michael@0: gfxFontEntry *fe = mAvailableFonts[i]; michael@0: if (fe && fe->TestCharacterMap(aMatchData->mCh)) { michael@0: int32_t rank = RANK_MATCHED_CMAP; michael@0: rank += CalcStyleMatch(fe, aMatchData->mStyle); michael@0: if (rank > aMatchData->mMatchRank michael@0: || (rank == aMatchData->mMatchRank && michael@0: Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) michael@0: { michael@0: aMatchData->mBestMatch = fe; michael@0: aMatchData->mMatchedFamily = this; michael@0: aMatchData->mMatchRank = rank; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: /*static*/ void michael@0: gfxFontFamily::ReadOtherFamilyNamesForFace(const nsAString& aFamilyName, michael@0: const char *aNameData, michael@0: uint32_t aDataLength, michael@0: nsTArray& aOtherFamilyNames, michael@0: bool useFullName) michael@0: { michael@0: const gfxFontUtils::NameHeader *nameHeader = michael@0: reinterpret_cast(aNameData); michael@0: michael@0: uint32_t nameCount = nameHeader->count; michael@0: if (nameCount * sizeof(gfxFontUtils::NameRecord) > aDataLength) { michael@0: NS_WARNING("invalid font (name records)"); michael@0: return; michael@0: } michael@0: michael@0: const gfxFontUtils::NameRecord *nameRecord = michael@0: reinterpret_cast(aNameData + sizeof(gfxFontUtils::NameHeader)); michael@0: uint32_t stringsBase = uint32_t(nameHeader->stringOffset); michael@0: michael@0: for (uint32_t i = 0; i < nameCount; i++, nameRecord++) { michael@0: uint32_t nameLen = nameRecord->length; michael@0: uint32_t nameOff = nameRecord->offset; // offset from base of string storage michael@0: michael@0: if (stringsBase + nameOff + nameLen > aDataLength) { michael@0: NS_WARNING("invalid font (name table strings)"); michael@0: return; michael@0: } michael@0: michael@0: uint16_t nameID = nameRecord->nameID; michael@0: if ((useFullName && nameID == gfxFontUtils::NAME_ID_FULL) || michael@0: (!useFullName && (nameID == gfxFontUtils::NAME_ID_FAMILY || michael@0: nameID == gfxFontUtils::NAME_ID_PREFERRED_FAMILY))) { michael@0: nsAutoString otherFamilyName; michael@0: bool ok = gfxFontUtils::DecodeFontName(aNameData + stringsBase + nameOff, michael@0: nameLen, michael@0: uint32_t(nameRecord->platformID), michael@0: uint32_t(nameRecord->encodingID), michael@0: uint32_t(nameRecord->languageID), michael@0: otherFamilyName); michael@0: // add if not same as canonical family name michael@0: if (ok && otherFamilyName != aFamilyName) { michael@0: aOtherFamilyNames.AppendElement(otherFamilyName); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // returns true if other names were found, false otherwise michael@0: bool michael@0: gfxFontFamily::ReadOtherFamilyNamesForFace(gfxPlatformFontList *aPlatformFontList, michael@0: hb_blob_t *aNameTable, michael@0: bool useFullName) michael@0: { michael@0: uint32_t dataLength; michael@0: const char *nameData = hb_blob_get_data(aNameTable, &dataLength); michael@0: nsAutoTArray otherFamilyNames; michael@0: michael@0: ReadOtherFamilyNamesForFace(mName, nameData, dataLength, michael@0: otherFamilyNames, useFullName); michael@0: michael@0: uint32_t n = otherFamilyNames.Length(); michael@0: for (uint32_t i = 0; i < n; i++) { michael@0: aPlatformFontList->AddOtherFamilyName(this, otherFamilyNames[i]); michael@0: } michael@0: michael@0: return n != 0; michael@0: } michael@0: michael@0: void michael@0: gfxFontFamily::ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList) michael@0: { michael@0: if (mOtherFamilyNamesInitialized) michael@0: return; michael@0: mOtherFamilyNamesInitialized = true; michael@0: michael@0: FindStyleVariations(); michael@0: michael@0: // read in other family names for the first face in the list michael@0: uint32_t i, numFonts = mAvailableFonts.Length(); michael@0: const uint32_t kNAME = TRUETYPE_TAG('n','a','m','e'); michael@0: michael@0: for (i = 0; i < numFonts; ++i) { michael@0: gfxFontEntry *fe = mAvailableFonts[i]; michael@0: if (!fe) { michael@0: continue; michael@0: } michael@0: gfxFontEntry::AutoTable nameTable(fe, kNAME); michael@0: if (!nameTable) { michael@0: continue; michael@0: } michael@0: mHasOtherFamilyNames = ReadOtherFamilyNamesForFace(aPlatformFontList, michael@0: nameTable); michael@0: break; michael@0: } michael@0: michael@0: // read in other names for the first face in the list with the assumption michael@0: // that if extra names don't exist in that face then they don't exist in michael@0: // other faces for the same font michael@0: if (!mHasOtherFamilyNames) michael@0: return; michael@0: michael@0: // read in names for all faces, needed to catch cases where fonts have michael@0: // family names for individual weights (e.g. Hiragino Kaku Gothic Pro W6) michael@0: for ( ; i < numFonts; i++) { michael@0: gfxFontEntry *fe = mAvailableFonts[i]; michael@0: if (!fe) { michael@0: continue; michael@0: } michael@0: gfxFontEntry::AutoTable nameTable(fe, kNAME); michael@0: if (!nameTable) { michael@0: continue; michael@0: } michael@0: ReadOtherFamilyNamesForFace(aPlatformFontList, nameTable); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxFontFamily::ReadFaceNames(gfxPlatformFontList *aPlatformFontList, michael@0: bool aNeedFullnamePostscriptNames, michael@0: FontInfoData *aFontInfoData) michael@0: { michael@0: // if all needed names have already been read, skip michael@0: if (mOtherFamilyNamesInitialized && michael@0: (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) michael@0: return; michael@0: michael@0: bool asyncFontLoaderDisabled = false; michael@0: michael@0: #if defined(XP_MACOSX) michael@0: // bug 975460 - async font loader crashes sometimes under 10.6, disable michael@0: if (!nsCocoaFeatures::OnLionOrLater()) { michael@0: asyncFontLoaderDisabled = true; michael@0: } michael@0: #endif michael@0: michael@0: if (!mOtherFamilyNamesInitialized && michael@0: aFontInfoData && michael@0: aFontInfoData->mLoadOtherNames && michael@0: !asyncFontLoaderDisabled) michael@0: { michael@0: nsAutoTArray otherFamilyNames; michael@0: bool foundOtherNames = michael@0: aFontInfoData->GetOtherFamilyNames(mName, otherFamilyNames); michael@0: if (foundOtherNames) { michael@0: uint32_t i, n = otherFamilyNames.Length(); michael@0: for (i = 0; i < n; i++) { michael@0: aPlatformFontList->AddOtherFamilyName(this, otherFamilyNames[i]); michael@0: } michael@0: } michael@0: mOtherFamilyNamesInitialized = true; michael@0: } michael@0: michael@0: // if all needed data has been initialized, return michael@0: if (mOtherFamilyNamesInitialized && michael@0: (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) { michael@0: return; michael@0: } michael@0: michael@0: FindStyleVariations(aFontInfoData); michael@0: michael@0: // check again, as style enumeration code may have loaded names michael@0: if (mOtherFamilyNamesInitialized && michael@0: (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) { michael@0: return; michael@0: } michael@0: michael@0: uint32_t i, numFonts = mAvailableFonts.Length(); michael@0: const uint32_t kNAME = TRUETYPE_TAG('n','a','m','e'); michael@0: michael@0: bool firstTime = true, readAllFaces = false; michael@0: for (i = 0; i < numFonts; ++i) { michael@0: gfxFontEntry *fe = mAvailableFonts[i]; michael@0: if (!fe) { michael@0: continue; michael@0: } michael@0: michael@0: nsAutoString fullname, psname; michael@0: bool foundFaceNames = false; michael@0: if (!mFaceNamesInitialized && michael@0: aNeedFullnamePostscriptNames && michael@0: aFontInfoData && michael@0: aFontInfoData->mLoadFaceNames) { michael@0: aFontInfoData->GetFaceNames(fe->Name(), fullname, psname); michael@0: if (!fullname.IsEmpty()) { michael@0: aPlatformFontList->AddFullname(fe, fullname); michael@0: } michael@0: if (!psname.IsEmpty()) { michael@0: aPlatformFontList->AddPostscriptName(fe, psname); michael@0: } michael@0: foundFaceNames = true; michael@0: michael@0: // found everything needed? skip to next font michael@0: if (mOtherFamilyNamesInitialized) { michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: // load directly from the name table michael@0: gfxFontEntry::AutoTable nameTable(fe, kNAME); michael@0: if (!nameTable) { michael@0: continue; michael@0: } michael@0: michael@0: if (aNeedFullnamePostscriptNames && !foundFaceNames) { michael@0: if (gfxFontUtils::ReadCanonicalName( michael@0: nameTable, gfxFontUtils::NAME_ID_FULL, fullname) == NS_OK) michael@0: { michael@0: aPlatformFontList->AddFullname(fe, fullname); michael@0: } michael@0: michael@0: if (gfxFontUtils::ReadCanonicalName( michael@0: nameTable, gfxFontUtils::NAME_ID_POSTSCRIPT, psname) == NS_OK) michael@0: { michael@0: aPlatformFontList->AddPostscriptName(fe, psname); michael@0: } michael@0: } michael@0: michael@0: if (!mOtherFamilyNamesInitialized && (firstTime || readAllFaces)) { michael@0: bool foundOtherName = ReadOtherFamilyNamesForFace(aPlatformFontList, michael@0: nameTable); michael@0: michael@0: // if the first face has a different name, scan all faces, otherwise michael@0: // assume the family doesn't have other names michael@0: if (firstTime && foundOtherName) { michael@0: mHasOtherFamilyNames = true; michael@0: readAllFaces = true; michael@0: } michael@0: firstTime = false; michael@0: } michael@0: michael@0: // if not reading in any more names, skip other faces michael@0: if (!readAllFaces && !aNeedFullnamePostscriptNames) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: mFaceNamesInitialized = true; michael@0: mOtherFamilyNamesInitialized = true; michael@0: } michael@0: michael@0: michael@0: gfxFontEntry* michael@0: gfxFontFamily::FindFont(const nsAString& aPostscriptName) michael@0: { michael@0: // find the font using a simple linear search michael@0: uint32_t numFonts = mAvailableFonts.Length(); michael@0: for (uint32_t i = 0; i < numFonts; i++) { michael@0: gfxFontEntry *fe = mAvailableFonts[i].get(); michael@0: if (fe && fe->Name() == aPostscriptName) michael@0: return fe; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: gfxFontFamily::ReadAllCMAPs(FontInfoData *aFontInfoData) michael@0: { michael@0: FindStyleVariations(aFontInfoData); michael@0: michael@0: uint32_t i, numFonts = mAvailableFonts.Length(); michael@0: for (i = 0; i < numFonts; i++) { michael@0: gfxFontEntry *fe = mAvailableFonts[i]; michael@0: // don't try to load cmaps for downloadable fonts not yet loaded michael@0: if (!fe || fe->mIsProxy) { michael@0: continue; michael@0: } michael@0: fe->ReadCMAP(aFontInfoData); michael@0: mFamilyCharacterMap.Union(*(fe->mCharacterMap)); michael@0: } michael@0: mFamilyCharacterMap.Compact(); michael@0: mFamilyCharacterMapInitialized = true; michael@0: } michael@0: michael@0: void michael@0: gfxFontFamily::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, michael@0: FontListSizes* aSizes) const michael@0: { michael@0: aSizes->mFontListSize += michael@0: mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); michael@0: aSizes->mCharMapsSize += michael@0: mFamilyCharacterMap.SizeOfExcludingThis(aMallocSizeOf); michael@0: michael@0: aSizes->mFontListSize += michael@0: mAvailableFonts.SizeOfExcludingThis(aMallocSizeOf); michael@0: for (uint32_t i = 0; i < mAvailableFonts.Length(); ++i) { michael@0: gfxFontEntry *fe = mAvailableFonts[i]; michael@0: if (fe) { michael@0: fe->AddSizeOfIncludingThis(aMallocSizeOf, aSizes); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxFontFamily::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, michael@0: FontListSizes* aSizes) const michael@0: { michael@0: aSizes->mFontListSize += aMallocSizeOf(this); michael@0: AddSizeOfExcludingThis(aMallocSizeOf, aSizes); michael@0: } michael@0: michael@0: /* michael@0: * gfxFontCache - global cache of gfxFont instances. michael@0: * Expires unused fonts after a short interval; michael@0: * notifies fonts to age their cached shaped-word records; michael@0: * observes memory-pressure notification and tells fonts to clear their michael@0: * shaped-word caches to free up memory. michael@0: */ michael@0: michael@0: MOZ_DEFINE_MALLOC_SIZE_OF(FontCacheMallocSizeOf) michael@0: michael@0: NS_IMPL_ISUPPORTS(gfxFontCache::MemoryReporter, nsIMemoryReporter) michael@0: michael@0: NS_IMETHODIMP michael@0: gfxFontCache::MemoryReporter::CollectReports michael@0: (nsIMemoryReporterCallback* aCb, michael@0: nsISupports* aClosure) michael@0: { michael@0: FontCacheSizes sizes; michael@0: michael@0: gfxFontCache::GetCache()->AddSizeOfIncludingThis(&FontCacheMallocSizeOf, michael@0: &sizes); michael@0: michael@0: aCb->Callback(EmptyCString(), michael@0: NS_LITERAL_CSTRING("explicit/gfx/font-cache"), michael@0: KIND_HEAP, UNITS_BYTES, sizes.mFontInstances, michael@0: NS_LITERAL_CSTRING("Memory used for active font instances."), michael@0: aClosure); michael@0: michael@0: aCb->Callback(EmptyCString(), michael@0: NS_LITERAL_CSTRING("explicit/gfx/font-shaped-words"), michael@0: KIND_HEAP, UNITS_BYTES, sizes.mShapedWords, michael@0: NS_LITERAL_CSTRING("Memory used to cache shaped glyph data."), michael@0: aClosure); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(gfxFontCache::Observer, nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: gfxFontCache::Observer::Observe(nsISupports *aSubject, michael@0: const char *aTopic, michael@0: const char16_t *someData) michael@0: { michael@0: if (!nsCRT::strcmp(aTopic, "memory-pressure")) { michael@0: gfxFontCache *fontCache = gfxFontCache::GetCache(); michael@0: if (fontCache) { michael@0: fontCache->FlushShapedWordCaches(); michael@0: } michael@0: } else { michael@0: NS_NOTREACHED("unexpected notification topic"); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: gfxFontCache::Init() michael@0: { michael@0: NS_ASSERTION(!gGlobalCache, "Where did this come from?"); michael@0: gGlobalCache = new gfxFontCache(); michael@0: if (!gGlobalCache) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: RegisterStrongMemoryReporter(new MemoryReporter()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: gfxFontCache::Shutdown() michael@0: { michael@0: delete gGlobalCache; michael@0: gGlobalCache = nullptr; michael@0: michael@0: #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS michael@0: printf("Textrun storage high water mark=%d\n", gTextRunStorageHighWaterMark); michael@0: printf("Total number of fonts=%d\n", gFontCount); michael@0: printf("Total glyph extents allocated=%d (size %d)\n", gGlyphExtentsCount, michael@0: int(gGlyphExtentsCount*sizeof(gfxGlyphExtents))); michael@0: printf("Total glyph extents width-storage size allocated=%d\n", gGlyphExtentsWidthsTotalSize); michael@0: printf("Number of simple glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerSimple); michael@0: printf("Number of tight glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerTight); michael@0: printf("Number of tight glyph extents lazily requested=%d\n", gGlyphExtentsSetupLazyTight); michael@0: printf("Number of simple glyph extent setups that fell back to tight=%d\n", gGlyphExtentsSetupFallBackToTight); michael@0: #endif michael@0: } michael@0: michael@0: gfxFontCache::gfxFontCache() michael@0: : nsExpirationTracker(FONT_TIMEOUT_SECONDS * 1000) michael@0: { michael@0: nsCOMPtr obs = GetObserverService(); michael@0: if (obs) { michael@0: obs->AddObserver(new Observer, "memory-pressure", false); michael@0: } michael@0: michael@0: #ifndef RELEASE_BUILD michael@0: // Currently disabled for release builds, due to unexplained crashes michael@0: // during expiration; see bug 717175 & 894798. michael@0: mWordCacheExpirationTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: if (mWordCacheExpirationTimer) { michael@0: mWordCacheExpirationTimer-> michael@0: InitWithFuncCallback(WordCacheExpirationTimerCallback, this, michael@0: SHAPED_WORD_TIMEOUT_SECONDS * 1000, michael@0: nsITimer::TYPE_REPEATING_SLACK); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: gfxFontCache::~gfxFontCache() michael@0: { michael@0: // Ensure the user font cache releases its references to font entries, michael@0: // so they aren't kept alive after the font instances and font-list michael@0: // have been shut down. michael@0: gfxUserFontSet::UserFontCache::Shutdown(); michael@0: michael@0: if (mWordCacheExpirationTimer) { michael@0: mWordCacheExpirationTimer->Cancel(); michael@0: mWordCacheExpirationTimer = nullptr; michael@0: } michael@0: michael@0: // Expire everything that has a zero refcount, so we don't leak them. michael@0: AgeAllGenerations(); michael@0: // All fonts should be gone. michael@0: NS_WARN_IF_FALSE(mFonts.Count() == 0, michael@0: "Fonts still alive while shutting down gfxFontCache"); michael@0: // Note that we have to delete everything through the expiration michael@0: // tracker, since there might be fonts not in the hashtable but in michael@0: // the tracker. michael@0: } michael@0: michael@0: bool michael@0: gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey) const michael@0: { michael@0: return aKey->mFontEntry == mFont->GetFontEntry() && michael@0: aKey->mStyle->Equals(*mFont->GetStyle()); michael@0: } michael@0: michael@0: already_AddRefed michael@0: gfxFontCache::Lookup(const gfxFontEntry *aFontEntry, michael@0: const gfxFontStyle *aStyle) michael@0: { michael@0: Key key(aFontEntry, aStyle); michael@0: HashEntry *entry = mFonts.GetEntry(key); michael@0: michael@0: Telemetry::Accumulate(Telemetry::FONT_CACHE_HIT, entry != nullptr); michael@0: if (!entry) michael@0: return nullptr; michael@0: michael@0: nsRefPtr font = entry->mFont; michael@0: return font.forget(); michael@0: } michael@0: michael@0: void michael@0: gfxFontCache::AddNew(gfxFont *aFont) michael@0: { michael@0: Key key(aFont->GetFontEntry(), aFont->GetStyle()); michael@0: HashEntry *entry = mFonts.PutEntry(key); michael@0: if (!entry) michael@0: return; michael@0: gfxFont *oldFont = entry->mFont; michael@0: entry->mFont = aFont; michael@0: // Assert that we can find the entry we just put in (this fails if the key michael@0: // has a NaN float value in it, e.g. 'sizeAdjust'). michael@0: MOZ_ASSERT(entry == mFonts.GetEntry(key)); michael@0: // If someone's asked us to replace an existing font entry, then that's a michael@0: // bit weird, but let it happen, and expire the old font if it's not used. michael@0: if (oldFont && oldFont->GetExpirationState()->IsTracked()) { michael@0: // if oldFont == aFont, recount should be > 0, michael@0: // so we shouldn't be here. michael@0: NS_ASSERTION(aFont != oldFont, "new font is tracked for expiry!"); michael@0: NotifyExpired(oldFont); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxFontCache::NotifyReleased(gfxFont *aFont) michael@0: { michael@0: nsresult rv = AddObject(aFont); michael@0: if (NS_FAILED(rv)) { michael@0: // We couldn't track it for some reason. Kill it now. michael@0: DestroyFont(aFont); michael@0: } michael@0: // Note that we might have fonts that aren't in the hashtable, perhaps because michael@0: // of OOM adding to the hashtable or because someone did an AddNew where michael@0: // we already had a font. These fonts are added to the expiration tracker michael@0: // anyway, even though Lookup can't resurrect them. Eventually they will michael@0: // expire and be deleted. michael@0: } michael@0: michael@0: void michael@0: gfxFontCache::NotifyExpired(gfxFont *aFont) michael@0: { michael@0: aFont->ClearCachedWords(); michael@0: RemoveObject(aFont); michael@0: DestroyFont(aFont); michael@0: } michael@0: michael@0: void michael@0: gfxFontCache::DestroyFont(gfxFont *aFont) michael@0: { michael@0: Key key(aFont->GetFontEntry(), aFont->GetStyle()); michael@0: HashEntry *entry = mFonts.GetEntry(key); michael@0: if (entry && entry->mFont == aFont) { michael@0: mFonts.RemoveEntry(key); michael@0: } michael@0: NS_ASSERTION(aFont->GetRefCount() == 0, michael@0: "Destroying with non-zero ref count!"); michael@0: delete aFont; michael@0: } michael@0: michael@0: /*static*/ michael@0: PLDHashOperator michael@0: gfxFontCache::AgeCachedWordsForFont(HashEntry* aHashEntry, void* aUserData) michael@0: { michael@0: aHashEntry->mFont->AgeCachedWords(); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: /*static*/ michael@0: void michael@0: gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache) michael@0: { michael@0: gfxFontCache* cache = static_cast(aCache); michael@0: cache->mFonts.EnumerateEntries(AgeCachedWordsForFont, nullptr); michael@0: } michael@0: michael@0: /*static*/ michael@0: PLDHashOperator michael@0: gfxFontCache::ClearCachedWordsForFont(HashEntry* aHashEntry, void* aUserData) michael@0: { michael@0: aHashEntry->mFont->ClearCachedWords(); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: /*static*/ michael@0: size_t michael@0: gfxFontCache::AddSizeOfFontEntryExcludingThis(HashEntry* aHashEntry, michael@0: MallocSizeOf aMallocSizeOf, michael@0: void* aUserArg) michael@0: { michael@0: HashEntry *entry = static_cast(aHashEntry); michael@0: FontCacheSizes *sizes = static_cast(aUserArg); michael@0: entry->mFont->AddSizeOfExcludingThis(aMallocSizeOf, sizes); michael@0: michael@0: // The entry's size is recorded in the |sizes| parameter, so we return zero michael@0: // here to the hashtable enumerator. michael@0: return 0; michael@0: } michael@0: michael@0: void michael@0: gfxFontCache::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, michael@0: FontCacheSizes* aSizes) const michael@0: { michael@0: // TODO: add the overhead of the expiration tracker (generation arrays) michael@0: michael@0: aSizes->mFontInstances += michael@0: mFonts.SizeOfExcludingThis(AddSizeOfFontEntryExcludingThis, michael@0: aMallocSizeOf, aSizes); michael@0: } michael@0: michael@0: void michael@0: gfxFontCache::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, michael@0: FontCacheSizes* aSizes) const michael@0: { michael@0: aSizes->mFontInstances += aMallocSizeOf(this); michael@0: AddSizeOfExcludingThis(aMallocSizeOf, aSizes); michael@0: } michael@0: michael@0: #define MAX_SSXX_VALUE 99 michael@0: #define MAX_CVXX_VALUE 99 michael@0: michael@0: static void michael@0: LookupAlternateValues(gfxFontFeatureValueSet *featureLookup, michael@0: const nsAString& aFamily, michael@0: const nsTArray& altValue, michael@0: nsTArray& aFontFeatures) michael@0: { michael@0: uint32_t numAlternates = altValue.Length(); michael@0: for (uint32_t i = 0; i < numAlternates; i++) { michael@0: const gfxAlternateValue& av = altValue.ElementAt(i); michael@0: nsAutoTArray values; michael@0: michael@0: // map ==> michael@0: bool found = michael@0: featureLookup->GetFontFeatureValuesFor(aFamily, av.alternate, michael@0: av.value, values); michael@0: uint32_t numValues = values.Length(); michael@0: michael@0: // nothing defined, skip michael@0: if (!found || numValues == 0) { michael@0: continue; michael@0: } michael@0: michael@0: gfxFontFeature feature; michael@0: if (av.alternate == NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT) { michael@0: NS_ASSERTION(numValues <= 2, michael@0: "too many values allowed for character-variant"); michael@0: // character-variant(12 3) ==> 'cv12' = 3 michael@0: uint32_t nn = values.ElementAt(0); michael@0: // ignore values greater than 99 michael@0: if (nn == 0 || nn > MAX_CVXX_VALUE) { michael@0: continue; michael@0: } michael@0: feature.mValue = 1; michael@0: if (numValues > 1) { michael@0: feature.mValue = values.ElementAt(1); michael@0: } michael@0: feature.mTag = HB_TAG('c','v',('0' + nn / 10), ('0' + nn % 10)); michael@0: aFontFeatures.AppendElement(feature); michael@0: michael@0: } else if (av.alternate == NS_FONT_VARIANT_ALTERNATES_STYLESET) { michael@0: // styleset(1 2 7) ==> 'ss01' = 1, 'ss02' = 1, 'ss07' = 1 michael@0: feature.mValue = 1; michael@0: for (uint32_t v = 0; v < numValues; v++) { michael@0: uint32_t nn = values.ElementAt(v); michael@0: if (nn == 0 || nn > MAX_SSXX_VALUE) { michael@0: continue; michael@0: } michael@0: feature.mTag = HB_TAG('s','s',('0' + nn / 10), ('0' + nn % 10)); michael@0: aFontFeatures.AppendElement(feature); michael@0: } michael@0: michael@0: } else { michael@0: NS_ASSERTION(numValues == 1, michael@0: "too many values for font-specific font-variant-alternates"); michael@0: feature.mValue = values.ElementAt(0); michael@0: michael@0: switch (av.alternate) { michael@0: case NS_FONT_VARIANT_ALTERNATES_STYLISTIC: // salt michael@0: feature.mTag = HB_TAG('s','a','l','t'); michael@0: break; michael@0: case NS_FONT_VARIANT_ALTERNATES_SWASH: // swsh, cswh michael@0: feature.mTag = HB_TAG('s','w','s','h'); michael@0: aFontFeatures.AppendElement(feature); michael@0: feature.mTag = HB_TAG('c','s','w','h'); michael@0: break; michael@0: case NS_FONT_VARIANT_ALTERNATES_ORNAMENTS: // ornm michael@0: feature.mTag = HB_TAG('o','r','n','m'); michael@0: break; michael@0: case NS_FONT_VARIANT_ALTERNATES_ANNOTATION: // nalt michael@0: feature.mTag = HB_TAG('n','a','l','t'); michael@0: break; michael@0: default: michael@0: feature.mTag = 0; michael@0: break; michael@0: } michael@0: michael@0: NS_ASSERTION(feature.mTag, "unsupported alternate type"); michael@0: if (!feature.mTag) { michael@0: continue; michael@0: } michael@0: aFontFeatures.AppendElement(feature); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* static */ bool michael@0: gfxFontShaper::MergeFontFeatures( michael@0: const gfxFontStyle *aStyle, michael@0: const nsTArray& aFontFeatures, michael@0: bool aDisableLigatures, michael@0: const nsAString& aFamilyName, michael@0: nsDataHashtable& aMergedFeatures) michael@0: { michael@0: uint32_t numAlts = aStyle->alternateValues.Length(); michael@0: const nsTArray& styleRuleFeatures = michael@0: aStyle->featureSettings; michael@0: michael@0: // bail immediately if nothing to do michael@0: if (styleRuleFeatures.IsEmpty() && michael@0: aFontFeatures.IsEmpty() && michael@0: !aDisableLigatures && michael@0: numAlts == 0) { michael@0: return false; michael@0: } michael@0: michael@0: // Ligature features are enabled by default in the generic shaper, michael@0: // so we explicitly turn them off if necessary (for letter-spacing) michael@0: if (aDisableLigatures) { michael@0: aMergedFeatures.Put(HB_TAG('l','i','g','a'), 0); michael@0: aMergedFeatures.Put(HB_TAG('c','l','i','g'), 0); michael@0: } michael@0: michael@0: // add feature values from font michael@0: uint32_t i, count; michael@0: michael@0: count = aFontFeatures.Length(); michael@0: for (i = 0; i < count; i++) { michael@0: const gfxFontFeature& feature = aFontFeatures.ElementAt(i); michael@0: aMergedFeatures.Put(feature.mTag, feature.mValue); michael@0: } michael@0: michael@0: // add font-specific feature values from style rules michael@0: if (aStyle->featureValueLookup && numAlts > 0) { michael@0: nsAutoTArray featureList; michael@0: michael@0: // insert list of alternate feature settings michael@0: LookupAlternateValues(aStyle->featureValueLookup, aFamilyName, michael@0: aStyle->alternateValues, featureList); michael@0: michael@0: count = featureList.Length(); michael@0: for (i = 0; i < count; i++) { michael@0: const gfxFontFeature& feature = featureList.ElementAt(i); michael@0: aMergedFeatures.Put(feature.mTag, feature.mValue); michael@0: } michael@0: } michael@0: michael@0: // add feature values from style rules michael@0: count = styleRuleFeatures.Length(); michael@0: for (i = 0; i < count; i++) { michael@0: const gfxFontFeature& feature = styleRuleFeatures.ElementAt(i); michael@0: aMergedFeatures.Put(feature.mTag, feature.mValue); michael@0: } michael@0: michael@0: return aMergedFeatures.Count() != 0; michael@0: } michael@0: michael@0: void michael@0: gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft) michael@0: { michael@0: mAscent = std::max(mAscent, aOther.mAscent); michael@0: mDescent = std::max(mDescent, aOther.mDescent); michael@0: if (aOtherIsOnLeft) { michael@0: mBoundingBox = michael@0: (mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0)).Union(aOther.mBoundingBox); michael@0: } else { michael@0: mBoundingBox = michael@0: mBoundingBox.Union(aOther.mBoundingBox + gfxPoint(mAdvanceWidth, 0)); michael@0: } michael@0: mAdvanceWidth += aOther.mAdvanceWidth; michael@0: } michael@0: michael@0: gfxFont::gfxFont(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle, michael@0: AntialiasOption anAAOption, cairo_scaled_font_t *aScaledFont) : michael@0: mScaledFont(aScaledFont), michael@0: mFontEntry(aFontEntry), mIsValid(true), michael@0: mApplySyntheticBold(false), michael@0: mStyle(*aFontStyle), michael@0: mAdjustedSize(0.0), michael@0: mFUnitsConvFactor(0.0f), michael@0: mAntialiasOption(anAAOption) michael@0: { michael@0: #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS michael@0: ++gFontCount; michael@0: #endif michael@0: mKerningSet = HasFeatureSet(HB_TAG('k','e','r','n'), mKerningEnabled); michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: NotifyFontDestroyed(nsPtrHashKey* aKey, michael@0: void* aClosure) michael@0: { michael@0: aKey->GetKey()->ForgetFont(); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: gfxFont::~gfxFont() michael@0: { michael@0: uint32_t i, count = mGlyphExtentsArray.Length(); michael@0: // We destroy the contents of mGlyphExtentsArray explicitly instead of michael@0: // using nsAutoPtr because VC++ can't deal with nsTArrays of nsAutoPtrs michael@0: // of classes that lack a proper copy constructor michael@0: for (i = 0; i < count; ++i) { michael@0: delete mGlyphExtentsArray[i]; michael@0: } michael@0: michael@0: mFontEntry->NotifyFontDestroyed(this); michael@0: michael@0: if (mGlyphChangeObservers) { michael@0: mGlyphChangeObservers->EnumerateEntries(NotifyFontDestroyed, nullptr); michael@0: } michael@0: } michael@0: michael@0: gfxFloat michael@0: gfxFont::GetGlyphHAdvance(gfxContext *aCtx, uint16_t aGID) michael@0: { michael@0: if (!SetupCairoFont(aCtx)) { michael@0: return 0; michael@0: } michael@0: if (ProvidesGlyphWidths()) { michael@0: return GetGlyphWidth(aCtx, aGID) / 65536.0; michael@0: } michael@0: if (mFUnitsConvFactor == 0.0f) { michael@0: GetMetrics(); michael@0: } michael@0: NS_ASSERTION(mFUnitsConvFactor > 0.0f, michael@0: "missing font unit conversion factor"); michael@0: if (!mHarfBuzzShaper) { michael@0: mHarfBuzzShaper = new gfxHarfBuzzShaper(this); michael@0: } michael@0: gfxHarfBuzzShaper* shaper = michael@0: static_cast(mHarfBuzzShaper.get()); michael@0: if (!shaper->Initialize()) { michael@0: return 0; michael@0: } michael@0: return shaper->GetGlyphHAdvance(aCtx, aGID) / 65536.0; michael@0: } michael@0: michael@0: /*static*/ michael@0: PLDHashOperator michael@0: gfxFont::AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData) michael@0: { michael@0: if (!aEntry->mShapedWord) { michael@0: NS_ASSERTION(aEntry->mShapedWord, "cache entry has no gfxShapedWord!"); michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: if (aEntry->mShapedWord->IncrementAge() == kShapedWordCacheMaxAge) { michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: static void michael@0: CollectLookupsByFeature(hb_face_t *aFace, hb_tag_t aTableTag, michael@0: uint32_t aFeatureIndex, hb_set_t *aLookups) michael@0: { michael@0: uint32_t lookups[32]; michael@0: uint32_t i, len, offset; michael@0: michael@0: offset = 0; michael@0: do { michael@0: len = ArrayLength(lookups); michael@0: hb_ot_layout_feature_get_lookups(aFace, aTableTag, aFeatureIndex, michael@0: offset, &len, lookups); michael@0: for (i = 0; i < len; i++) { michael@0: hb_set_add(aLookups, lookups[i]); michael@0: } michael@0: offset += len; michael@0: } while (len == ArrayLength(lookups)); michael@0: } michael@0: michael@0: static void michael@0: CollectLookupsByLanguage(hb_face_t *aFace, hb_tag_t aTableTag, michael@0: const nsTHashtable& michael@0: aSpecificFeatures, michael@0: hb_set_t *aOtherLookups, michael@0: hb_set_t *aSpecificFeatureLookups, michael@0: uint32_t aScriptIndex, uint32_t aLangIndex) michael@0: { michael@0: uint32_t reqFeatureIndex; michael@0: if (hb_ot_layout_language_get_required_feature_index(aFace, aTableTag, michael@0: aScriptIndex, michael@0: aLangIndex, michael@0: &reqFeatureIndex)) { michael@0: CollectLookupsByFeature(aFace, aTableTag, reqFeatureIndex, michael@0: aOtherLookups); michael@0: } michael@0: michael@0: uint32_t featureIndexes[32]; michael@0: uint32_t i, len, offset; michael@0: michael@0: offset = 0; michael@0: do { michael@0: len = ArrayLength(featureIndexes); michael@0: hb_ot_layout_language_get_feature_indexes(aFace, aTableTag, michael@0: aScriptIndex, aLangIndex, michael@0: offset, &len, featureIndexes); michael@0: michael@0: for (i = 0; i < len; i++) { michael@0: uint32_t featureIndex = featureIndexes[i]; michael@0: michael@0: // get the feature tag michael@0: hb_tag_t featureTag; michael@0: uint32_t tagLen = 1; michael@0: hb_ot_layout_language_get_feature_tags(aFace, aTableTag, michael@0: aScriptIndex, aLangIndex, michael@0: offset + i, &tagLen, michael@0: &featureTag); michael@0: michael@0: // collect lookups michael@0: hb_set_t *lookups = aSpecificFeatures.GetEntry(featureTag) ? michael@0: aSpecificFeatureLookups : aOtherLookups; michael@0: CollectLookupsByFeature(aFace, aTableTag, featureIndex, lookups); michael@0: } michael@0: offset += len; michael@0: } while (len == ArrayLength(featureIndexes)); michael@0: } michael@0: michael@0: static bool michael@0: HasLookupRuleWithGlyphByScript(hb_face_t *aFace, hb_tag_t aTableTag, michael@0: hb_tag_t aScriptTag, uint32_t aScriptIndex, michael@0: uint16_t aGlyph, michael@0: const nsTHashtable& michael@0: aDefaultFeatures, michael@0: bool& aHasDefaultFeatureWithGlyph) michael@0: { michael@0: uint32_t numLangs, lang; michael@0: hb_set_t *defaultFeatureLookups = hb_set_create(); michael@0: hb_set_t *nonDefaultFeatureLookups = hb_set_create(); michael@0: michael@0: // default lang michael@0: CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures, michael@0: nonDefaultFeatureLookups, defaultFeatureLookups, michael@0: aScriptIndex, michael@0: HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX); michael@0: michael@0: // iterate over langs michael@0: numLangs = hb_ot_layout_script_get_language_tags(aFace, aTableTag, michael@0: aScriptIndex, 0, michael@0: nullptr, nullptr); michael@0: for (lang = 0; lang < numLangs; lang++) { michael@0: CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures, michael@0: nonDefaultFeatureLookups, michael@0: defaultFeatureLookups, michael@0: aScriptIndex, lang); michael@0: } michael@0: michael@0: // look for the glyph among default feature lookups michael@0: aHasDefaultFeatureWithGlyph = false; michael@0: hb_set_t *glyphs = hb_set_create(); michael@0: hb_codepoint_t index = -1; michael@0: while (hb_set_next(defaultFeatureLookups, &index)) { michael@0: hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, michael@0: glyphs, glyphs, glyphs, michael@0: glyphs); michael@0: if (hb_set_has(glyphs, aGlyph)) { michael@0: aHasDefaultFeatureWithGlyph = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // look for the glyph among non-default feature lookups michael@0: // if no default feature lookups contained spaces michael@0: bool hasNonDefaultFeatureWithGlyph = false; michael@0: if (!aHasDefaultFeatureWithGlyph) { michael@0: hb_set_clear(glyphs); michael@0: index = -1; michael@0: while (hb_set_next(nonDefaultFeatureLookups, &index)) { michael@0: hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, michael@0: glyphs, glyphs, glyphs, michael@0: glyphs); michael@0: if (hb_set_has(glyphs, aGlyph)) { michael@0: hasNonDefaultFeatureWithGlyph = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: hb_set_destroy(glyphs); michael@0: hb_set_destroy(defaultFeatureLookups); michael@0: hb_set_destroy(nonDefaultFeatureLookups); michael@0: michael@0: return aHasDefaultFeatureWithGlyph || hasNonDefaultFeatureWithGlyph; michael@0: } michael@0: michael@0: static void michael@0: HasLookupRuleWithGlyph(hb_face_t *aFace, hb_tag_t aTableTag, bool& aHasGlyph, michael@0: hb_tag_t aSpecificFeature, bool& aHasGlyphSpecific, michael@0: uint16_t aGlyph) michael@0: { michael@0: // iterate over the scripts in the font michael@0: uint32_t numScripts, numLangs, script, lang; michael@0: hb_set_t *otherLookups = hb_set_create(); michael@0: hb_set_t *specificFeatureLookups = hb_set_create(); michael@0: nsTHashtable specificFeature; michael@0: michael@0: specificFeature.PutEntry(aSpecificFeature); michael@0: michael@0: numScripts = hb_ot_layout_table_get_script_tags(aFace, aTableTag, 0, michael@0: nullptr, nullptr); michael@0: michael@0: for (script = 0; script < numScripts; script++) { michael@0: // default lang michael@0: CollectLookupsByLanguage(aFace, aTableTag, specificFeature, michael@0: otherLookups, specificFeatureLookups, michael@0: script, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX); michael@0: michael@0: // iterate over langs michael@0: numLangs = hb_ot_layout_script_get_language_tags(aFace, HB_OT_TAG_GPOS, michael@0: script, 0, michael@0: nullptr, nullptr); michael@0: for (lang = 0; lang < numLangs; lang++) { michael@0: CollectLookupsByLanguage(aFace, aTableTag, specificFeature, michael@0: otherLookups, specificFeatureLookups, michael@0: script, lang); michael@0: } michael@0: } michael@0: michael@0: // look for the glyph among non-specific feature lookups michael@0: hb_set_t *glyphs = hb_set_create(); michael@0: hb_codepoint_t index = -1; michael@0: while (hb_set_next(otherLookups, &index)) { michael@0: hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, michael@0: glyphs, glyphs, glyphs, michael@0: glyphs); michael@0: if (hb_set_has(glyphs, aGlyph)) { michael@0: aHasGlyph = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // look for the glyph among specific feature lookups michael@0: hb_set_clear(glyphs); michael@0: index = -1; michael@0: while (hb_set_next(specificFeatureLookups, &index)) { michael@0: hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, michael@0: glyphs, glyphs, glyphs, michael@0: glyphs); michael@0: if (hb_set_has(glyphs, aGlyph)) { michael@0: aHasGlyphSpecific = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: hb_set_destroy(glyphs); michael@0: hb_set_destroy(specificFeatureLookups); michael@0: hb_set_destroy(otherLookups); michael@0: } michael@0: michael@0: nsDataHashtable *gfxFont::sScriptTagToCode = nullptr; michael@0: nsTHashtable *gfxFont::sDefaultFeatures = nullptr; michael@0: michael@0: static inline bool michael@0: HasSubstitution(uint32_t *aBitVector, uint32_t aBit) { michael@0: return (aBitVector[aBit >> 5] & (1 << (aBit & 0x1f))) != 0; michael@0: } michael@0: michael@0: // union of all default substitution features across scripts michael@0: static const hb_tag_t defaultFeatures[] = { michael@0: HB_TAG('a','b','v','f'), michael@0: HB_TAG('a','b','v','s'), michael@0: HB_TAG('a','k','h','n'), michael@0: HB_TAG('b','l','w','f'), michael@0: HB_TAG('b','l','w','s'), michael@0: HB_TAG('c','a','l','t'), michael@0: HB_TAG('c','c','m','p'), michael@0: HB_TAG('c','f','a','r'), michael@0: HB_TAG('c','j','c','t'), michael@0: HB_TAG('c','l','i','g'), michael@0: HB_TAG('f','i','n','2'), michael@0: HB_TAG('f','i','n','3'), michael@0: HB_TAG('f','i','n','a'), michael@0: HB_TAG('h','a','l','f'), michael@0: HB_TAG('h','a','l','n'), michael@0: HB_TAG('i','n','i','t'), michael@0: HB_TAG('i','s','o','l'), michael@0: HB_TAG('l','i','g','a'), michael@0: HB_TAG('l','j','m','o'), michael@0: HB_TAG('l','o','c','l'), michael@0: HB_TAG('l','t','r','a'), michael@0: HB_TAG('l','t','r','m'), michael@0: HB_TAG('m','e','d','2'), michael@0: HB_TAG('m','e','d','i'), michael@0: HB_TAG('m','s','e','t'), michael@0: HB_TAG('n','u','k','t'), michael@0: HB_TAG('p','r','e','f'), michael@0: HB_TAG('p','r','e','s'), michael@0: HB_TAG('p','s','t','f'), michael@0: HB_TAG('p','s','t','s'), michael@0: HB_TAG('r','c','l','t'), michael@0: HB_TAG('r','l','i','g'), michael@0: HB_TAG('r','k','r','f'), michael@0: HB_TAG('r','p','h','f'), michael@0: HB_TAG('r','t','l','a'), michael@0: HB_TAG('r','t','l','m'), michael@0: HB_TAG('t','j','m','o'), michael@0: HB_TAG('v','a','t','u'), michael@0: HB_TAG('v','e','r','t'), michael@0: HB_TAG('v','j','m','o') michael@0: }; michael@0: michael@0: void michael@0: gfxFont::CheckForFeaturesInvolvingSpace() michael@0: { michael@0: mFontEntry->mHasSpaceFeaturesInitialized = true; michael@0: michael@0: #ifdef PR_LOGGING michael@0: bool log = LOG_FONTINIT_ENABLED(); michael@0: TimeStamp start; michael@0: if (MOZ_UNLIKELY(log)) { michael@0: start = TimeStamp::Now(); michael@0: } michael@0: #endif michael@0: michael@0: bool result = false; michael@0: michael@0: uint32_t spaceGlyph = GetSpaceGlyph(); michael@0: if (!spaceGlyph) { michael@0: return; michael@0: } michael@0: michael@0: hb_face_t *face = GetFontEntry()->GetHBFace(); michael@0: michael@0: // GSUB lookups - examine per script michael@0: if (hb_ot_layout_has_substitution(face)) { michael@0: michael@0: // set up the script ==> code hashtable if needed michael@0: if (!sScriptTagToCode) { michael@0: sScriptTagToCode = michael@0: new nsDataHashtable(MOZ_NUM_SCRIPT_CODES); michael@0: sScriptTagToCode->Put(HB_TAG('D','F','L','T'), MOZ_SCRIPT_COMMON); michael@0: for (int32_t s = MOZ_SCRIPT_ARABIC; s < MOZ_NUM_SCRIPT_CODES; s++) { michael@0: hb_script_t scriptTag = hb_script_t(GetScriptTagForCode(s)); michael@0: hb_tag_t s1, s2; michael@0: hb_ot_tags_from_script(scriptTag, &s1, &s2); michael@0: sScriptTagToCode->Put(s1, s); michael@0: if (s2 != HB_OT_TAG_DEFAULT_SCRIPT) { michael@0: sScriptTagToCode->Put(s2, s); michael@0: } michael@0: } michael@0: michael@0: uint32_t numDefaultFeatures = ArrayLength(defaultFeatures); michael@0: sDefaultFeatures = michael@0: new nsTHashtable(numDefaultFeatures); michael@0: for (uint32_t i = 0; i < numDefaultFeatures; i++) { michael@0: sDefaultFeatures->PutEntry(defaultFeatures[i]); michael@0: } michael@0: } michael@0: michael@0: // iterate over the scripts in the font michael@0: hb_tag_t scriptTags[8]; michael@0: michael@0: uint32_t len, offset = 0; michael@0: do { michael@0: len = ArrayLength(scriptTags); michael@0: hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, offset, michael@0: &len, scriptTags); michael@0: for (uint32_t i = 0; i < len; i++) { michael@0: bool isDefaultFeature = false; michael@0: int32_t s; michael@0: if (!HasLookupRuleWithGlyphByScript(face, HB_OT_TAG_GSUB, michael@0: scriptTags[i], offset + i, michael@0: spaceGlyph, michael@0: *sDefaultFeatures, michael@0: isDefaultFeature) || michael@0: !sScriptTagToCode->Get(scriptTags[i], &s)) michael@0: { michael@0: continue; michael@0: } michael@0: result = true; michael@0: uint32_t index = s >> 5; michael@0: uint32_t bit = s & 0x1f; michael@0: if (isDefaultFeature) { michael@0: mFontEntry->mDefaultSubSpaceFeatures[index] |= (1 << bit); michael@0: } else { michael@0: mFontEntry->mNonDefaultSubSpaceFeatures[index] |= (1 << bit); michael@0: } michael@0: } michael@0: offset += len; michael@0: } while (len == ArrayLength(scriptTags)); michael@0: } michael@0: michael@0: // spaces in default features of default script? michael@0: // ==> can't use word cache, skip GPOS analysis michael@0: bool canUseWordCache = true; michael@0: if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, michael@0: MOZ_SCRIPT_COMMON)) { michael@0: canUseWordCache = false; michael@0: } michael@0: michael@0: // GPOS lookups - distinguish kerning from non-kerning features michael@0: mFontEntry->mHasSpaceFeaturesKerning = false; michael@0: mFontEntry->mHasSpaceFeaturesNonKerning = false; michael@0: michael@0: if (canUseWordCache && hb_ot_layout_has_positioning(face)) { michael@0: bool hasKerning = false, hasNonKerning = false; michael@0: HasLookupRuleWithGlyph(face, HB_OT_TAG_GPOS, hasNonKerning, michael@0: HB_TAG('k','e','r','n'), hasKerning, spaceGlyph); michael@0: if (hasKerning || hasNonKerning) { michael@0: result = true; michael@0: } michael@0: mFontEntry->mHasSpaceFeaturesKerning = hasKerning; michael@0: mFontEntry->mHasSpaceFeaturesNonKerning = hasNonKerning; michael@0: } michael@0: michael@0: hb_face_destroy(face); michael@0: mFontEntry->mHasSpaceFeatures = result; michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (MOZ_UNLIKELY(log)) { michael@0: TimeDuration elapsed = TimeStamp::Now() - start; michael@0: LOG_FONTINIT(( michael@0: "(fontinit-spacelookups) font: %s - " michael@0: "subst default: %8.8x %8.8x %8.8x %8.8x " michael@0: "subst non-default: %8.8x %8.8x %8.8x %8.8x " michael@0: "kerning: %s non-kerning: %s time: %6.3f\n", michael@0: NS_ConvertUTF16toUTF8(mFontEntry->Name()).get(), michael@0: mFontEntry->mDefaultSubSpaceFeatures[3], michael@0: mFontEntry->mDefaultSubSpaceFeatures[2], michael@0: mFontEntry->mDefaultSubSpaceFeatures[1], michael@0: mFontEntry->mDefaultSubSpaceFeatures[0], michael@0: mFontEntry->mNonDefaultSubSpaceFeatures[3], michael@0: mFontEntry->mNonDefaultSubSpaceFeatures[2], michael@0: mFontEntry->mNonDefaultSubSpaceFeatures[1], michael@0: mFontEntry->mNonDefaultSubSpaceFeatures[0], michael@0: (mFontEntry->mHasSpaceFeaturesKerning ? "true" : "false"), michael@0: (mFontEntry->mHasSpaceFeaturesNonKerning ? "true" : "false"), michael@0: elapsed.ToMilliseconds() michael@0: )); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: bool michael@0: gfxFont::HasSubstitutionRulesWithSpaceLookups(int32_t aRunScript) michael@0: { michael@0: NS_ASSERTION(GetFontEntry()->mHasSpaceFeaturesInitialized, michael@0: "need to initialize space lookup flags"); michael@0: NS_ASSERTION(aRunScript < MOZ_NUM_SCRIPT_CODES, "weird script code"); michael@0: if (aRunScript == MOZ_SCRIPT_INVALID || michael@0: aRunScript >= MOZ_NUM_SCRIPT_CODES) { michael@0: return false; michael@0: } michael@0: michael@0: // default features have space lookups ==> true michael@0: if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, michael@0: MOZ_SCRIPT_COMMON) || michael@0: HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, michael@0: aRunScript)) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: // non-default features have space lookups and some type of michael@0: // font feature, in font or style is specified ==> true michael@0: if ((HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures, michael@0: MOZ_SCRIPT_COMMON) || michael@0: HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures, michael@0: aRunScript)) && michael@0: (!mStyle.featureSettings.IsEmpty() || michael@0: !mFontEntry->mFeatureSettings.IsEmpty())) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: gfxFont::SpaceMayParticipateInShaping(int32_t aRunScript) michael@0: { michael@0: // avoid checking fonts known not to include default space-dependent features michael@0: if (MOZ_UNLIKELY(mFontEntry->mSkipDefaultFeatureSpaceCheck)) { michael@0: if (!mKerningSet && mStyle.featureSettings.IsEmpty() && michael@0: mFontEntry->mFeatureSettings.IsEmpty()) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // We record the presence of space-dependent features in the font entry michael@0: // so that subsequent instantiations for the same font face won't michael@0: // require us to re-check the tables; however, the actual check is done michael@0: // by gfxFont because not all font entry subclasses know how to create michael@0: // a harfbuzz face for introspection. michael@0: if (!mFontEntry->mHasSpaceFeaturesInitialized) { michael@0: CheckForFeaturesInvolvingSpace(); michael@0: } michael@0: michael@0: if (!mFontEntry->mHasSpaceFeatures) { michael@0: return false; michael@0: } michael@0: michael@0: // if font has substitution rules or non-kerning positioning rules michael@0: // that involve spaces, bypass michael@0: if (HasSubstitutionRulesWithSpaceLookups(aRunScript) || michael@0: mFontEntry->mHasSpaceFeaturesNonKerning) { michael@0: return true; michael@0: } michael@0: michael@0: // if kerning explicitly enabled/disabled via font-feature-settings or michael@0: // font-kerning and kerning rules use spaces, only bypass when enabled michael@0: if (mKerningSet && mFontEntry->mHasSpaceFeaturesKerning) { michael@0: return mKerningEnabled; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn) michael@0: { michael@0: aFeatureOn = false; michael@0: michael@0: if (mStyle.featureSettings.IsEmpty() && michael@0: GetFontEntry()->mFeatureSettings.IsEmpty()) { michael@0: return false; michael@0: } michael@0: michael@0: // add feature values from font michael@0: bool featureSet = false; michael@0: uint32_t i, count; michael@0: michael@0: nsTArray& fontFeatures = GetFontEntry()->mFeatureSettings; michael@0: count = fontFeatures.Length(); michael@0: for (i = 0; i < count; i++) { michael@0: const gfxFontFeature& feature = fontFeatures.ElementAt(i); michael@0: if (feature.mTag == aFeature) { michael@0: featureSet = true; michael@0: aFeatureOn = (feature.mValue != 0); michael@0: } michael@0: } michael@0: michael@0: // add feature values from style rules michael@0: nsTArray& styleFeatures = mStyle.featureSettings; michael@0: count = styleFeatures.Length(); michael@0: for (i = 0; i < count; i++) { michael@0: const gfxFontFeature& feature = styleFeatures.ElementAt(i); michael@0: if (feature.mTag == aFeature) { michael@0: featureSet = true; michael@0: aFeatureOn = (feature.mValue != 0); michael@0: } michael@0: } michael@0: michael@0: return featureSet; michael@0: } michael@0: michael@0: /** michael@0: * A helper function in case we need to do any rounding or other michael@0: * processing here. michael@0: */ michael@0: #define ToDeviceUnits(aAppUnits, aDevUnitsPerAppUnit) \ michael@0: (double(aAppUnits)*double(aDevUnitsPerAppUnit)) michael@0: michael@0: struct GlyphBuffer { michael@0: #define GLYPH_BUFFER_SIZE (2048/sizeof(cairo_glyph_t)) michael@0: cairo_glyph_t mGlyphBuffer[GLYPH_BUFFER_SIZE]; michael@0: unsigned int mNumGlyphs; michael@0: michael@0: GlyphBuffer() michael@0: : mNumGlyphs(0) { } michael@0: michael@0: cairo_glyph_t *AppendGlyph() { michael@0: return &mGlyphBuffer[mNumGlyphs++]; michael@0: } michael@0: michael@0: void Flush(cairo_t *aCR, DrawMode aDrawMode, bool aReverse, michael@0: gfxTextContextPaint *aContextPaint, michael@0: const gfxMatrix& aGlobalMatrix, bool aFinish = false) { michael@0: // Ensure there's enough room for a glyph to be added to the buffer michael@0: // and we actually have glyphs to draw michael@0: if ((!aFinish && mNumGlyphs < GLYPH_BUFFER_SIZE) || !mNumGlyphs) { michael@0: return; michael@0: } michael@0: michael@0: if (aReverse) { michael@0: for (uint32_t i = 0; i < mNumGlyphs/2; ++i) { michael@0: cairo_glyph_t tmp = mGlyphBuffer[i]; michael@0: mGlyphBuffer[i] = mGlyphBuffer[mNumGlyphs - 1 - i]; michael@0: mGlyphBuffer[mNumGlyphs - 1 - i] = tmp; michael@0: } michael@0: } michael@0: michael@0: if (aDrawMode == DrawMode::GLYPH_PATH) { michael@0: cairo_glyph_path(aCR, mGlyphBuffer, mNumGlyphs); michael@0: } else { michael@0: if ((int(aDrawMode) & (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) == michael@0: (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) { michael@0: FlushStroke(aCR, aContextPaint, aGlobalMatrix); michael@0: } michael@0: if (int(aDrawMode) & int(DrawMode::GLYPH_FILL)) { michael@0: PROFILER_LABEL("GlyphBuffer", "cairo_show_glyphs"); michael@0: nsRefPtr pattern; michael@0: if (aContextPaint && michael@0: !!(pattern = aContextPaint->GetFillPattern(aGlobalMatrix))) { michael@0: cairo_save(aCR); michael@0: cairo_set_source(aCR, pattern->CairoPattern()); michael@0: } michael@0: michael@0: cairo_show_glyphs(aCR, mGlyphBuffer, mNumGlyphs); michael@0: michael@0: if (pattern) { michael@0: cairo_restore(aCR); michael@0: } michael@0: } michael@0: if ((int(aDrawMode) & (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) == michael@0: int(DrawMode::GLYPH_STROKE)) { michael@0: FlushStroke(aCR, aContextPaint, aGlobalMatrix); michael@0: } michael@0: } michael@0: michael@0: mNumGlyphs = 0; michael@0: } michael@0: michael@0: private: michael@0: void FlushStroke(cairo_t *aCR, gfxTextContextPaint *aContextPaint, michael@0: const gfxMatrix& aGlobalMatrix) { michael@0: nsRefPtr pattern; michael@0: if (aContextPaint && michael@0: !!(pattern = aContextPaint->GetStrokePattern(aGlobalMatrix))) { michael@0: cairo_save(aCR); michael@0: cairo_set_source(aCR, pattern->CairoPattern()); michael@0: } michael@0: michael@0: cairo_new_path(aCR); michael@0: cairo_glyph_path(aCR, mGlyphBuffer, mNumGlyphs); michael@0: cairo_stroke(aCR); michael@0: michael@0: if (pattern) { michael@0: cairo_restore(aCR); michael@0: } michael@0: } michael@0: michael@0: #undef GLYPH_BUFFER_SIZE michael@0: }; michael@0: michael@0: static AntialiasMode Get2DAAMode(gfxFont::AntialiasOption aAAOption) { michael@0: switch (aAAOption) { michael@0: case gfxFont::kAntialiasSubpixel: michael@0: return AntialiasMode::SUBPIXEL; michael@0: case gfxFont::kAntialiasGrayscale: michael@0: return AntialiasMode::GRAY; michael@0: case gfxFont::kAntialiasNone: michael@0: return AntialiasMode::NONE; michael@0: default: michael@0: return AntialiasMode::DEFAULT; michael@0: } michael@0: } michael@0: michael@0: struct GlyphBufferAzure { michael@0: #define GLYPH_BUFFER_SIZE (2048/sizeof(Glyph)) michael@0: Glyph mGlyphBuffer[GLYPH_BUFFER_SIZE]; michael@0: unsigned int mNumGlyphs; michael@0: michael@0: GlyphBufferAzure() michael@0: : mNumGlyphs(0) { } michael@0: michael@0: Glyph *AppendGlyph() { michael@0: return &mGlyphBuffer[mNumGlyphs++]; michael@0: } michael@0: michael@0: void Flush(DrawTarget *aDT, gfxTextContextPaint *aContextPaint, ScaledFont *aFont, michael@0: DrawMode aDrawMode, bool aReverse, const GlyphRenderingOptions *aOptions, michael@0: gfxContext *aThebesContext, const Matrix *aInvFontMatrix, const DrawOptions &aDrawOptions, michael@0: bool aFinish = false) michael@0: { michael@0: // Ensure there's enough room for a glyph to be added to the buffer michael@0: if ((!aFinish && mNumGlyphs < GLYPH_BUFFER_SIZE) || !mNumGlyphs) { michael@0: return; michael@0: } michael@0: michael@0: if (aReverse) { michael@0: Glyph *begin = &mGlyphBuffer[0]; michael@0: Glyph *end = &mGlyphBuffer[mNumGlyphs]; michael@0: std::reverse(begin, end); michael@0: } michael@0: michael@0: gfx::GlyphBuffer buf; michael@0: buf.mGlyphs = mGlyphBuffer; michael@0: buf.mNumGlyphs = mNumGlyphs; michael@0: michael@0: gfxContext::AzureState state = aThebesContext->CurrentState(); michael@0: if ((int(aDrawMode) & (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) == michael@0: (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) { michael@0: FlushStroke(aDT, aContextPaint, aFont, aThebesContext, buf, state); michael@0: } michael@0: if (int(aDrawMode) & int(DrawMode::GLYPH_FILL)) { michael@0: if (state.pattern || aContextPaint) { michael@0: Pattern *pat; michael@0: michael@0: nsRefPtr fillPattern; michael@0: if (!aContextPaint || michael@0: !(fillPattern = aContextPaint->GetFillPattern(aThebesContext->CurrentMatrix()))) { michael@0: if (state.pattern) { michael@0: pat = state.pattern->GetPattern(aDT, state.patternTransformChanged ? &state.patternTransform : nullptr); michael@0: } else { michael@0: pat = nullptr; michael@0: } michael@0: } else { michael@0: pat = fillPattern->GetPattern(aDT); michael@0: } michael@0: michael@0: if (pat) { michael@0: Matrix saved; michael@0: Matrix *mat = nullptr; michael@0: if (aInvFontMatrix) { michael@0: // The brush matrix needs to be multiplied with the inverted matrix michael@0: // as well, to move the brush into the space of the glyphs. Before michael@0: // the render target transformation michael@0: michael@0: // This relies on the returned Pattern not to be reused by michael@0: // others, but regenerated on GetPattern calls. This is true! michael@0: if (pat->GetType() == PatternType::LINEAR_GRADIENT) { michael@0: mat = &static_cast(pat)->mMatrix; michael@0: } else if (pat->GetType() == PatternType::RADIAL_GRADIENT) { michael@0: mat = &static_cast(pat)->mMatrix; michael@0: } else if (pat->GetType() == PatternType::SURFACE) { michael@0: mat = &static_cast(pat)->mMatrix; michael@0: } michael@0: michael@0: if (mat) { michael@0: saved = *mat; michael@0: *mat = (*mat) * (*aInvFontMatrix); michael@0: } michael@0: } michael@0: michael@0: aDT->FillGlyphs(aFont, buf, *pat, michael@0: aDrawOptions, aOptions); michael@0: michael@0: if (mat) { michael@0: *mat = saved; michael@0: } michael@0: } michael@0: } else if (state.sourceSurface) { michael@0: aDT->FillGlyphs(aFont, buf, SurfacePattern(state.sourceSurface, michael@0: ExtendMode::CLAMP, michael@0: state.surfTransform), michael@0: aDrawOptions, aOptions); michael@0: } else { michael@0: aDT->FillGlyphs(aFont, buf, ColorPattern(state.color), michael@0: aDrawOptions, aOptions); michael@0: } michael@0: } michael@0: if (int(aDrawMode) & int(DrawMode::GLYPH_PATH)) { michael@0: aThebesContext->EnsurePathBuilder(); michael@0: Matrix mat = aDT->GetTransform(); michael@0: aFont->CopyGlyphsToBuilder(buf, aThebesContext->mPathBuilder, aDT->GetType(), &mat); michael@0: } michael@0: if ((int(aDrawMode) & (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) == michael@0: int(DrawMode::GLYPH_STROKE)) { michael@0: FlushStroke(aDT, aContextPaint, aFont, aThebesContext, buf, state); michael@0: } michael@0: michael@0: mNumGlyphs = 0; michael@0: } michael@0: michael@0: private: michael@0: void FlushStroke(DrawTarget *aDT, gfxTextContextPaint *aContextPaint, michael@0: ScaledFont *aFont, gfxContext *aThebesContext, michael@0: gfx::GlyphBuffer& aBuf, gfxContext::AzureState& aState) michael@0: { michael@0: RefPtr path = aFont->GetPathForGlyphs(aBuf, aDT); michael@0: if (aContextPaint) { michael@0: nsRefPtr strokePattern = michael@0: aContextPaint->GetStrokePattern(aThebesContext->CurrentMatrix()); michael@0: if (strokePattern) { michael@0: aDT->Stroke(path, *strokePattern->GetPattern(aDT), aState.strokeOptions); michael@0: } michael@0: } michael@0: } michael@0: michael@0: #undef GLYPH_BUFFER_SIZE michael@0: }; michael@0: michael@0: // Bug 674909. When synthetic bolding text by drawing twice, need to michael@0: // render using a pixel offset in device pixels, otherwise text michael@0: // doesn't appear bolded, it appears as if a bad text shadow exists michael@0: // when a non-identity transform exists. Use an offset factor so that michael@0: // the second draw occurs at a constant offset in device pixels. michael@0: michael@0: double michael@0: gfxFont::CalcXScale(gfxContext *aContext) michael@0: { michael@0: // determine magnitude of a 1px x offset in device space michael@0: gfxSize t = aContext->UserToDevice(gfxSize(1.0, 0.0)); michael@0: if (t.width == 1.0 && t.height == 0.0) { michael@0: // short-circuit the most common case to avoid sqrt() and division michael@0: return 1.0; michael@0: } michael@0: michael@0: double m = sqrt(t.width * t.width + t.height * t.height); michael@0: michael@0: NS_ASSERTION(m != 0.0, "degenerate transform while synthetic bolding"); michael@0: if (m == 0.0) { michael@0: return 0.0; // effectively disables offset michael@0: } michael@0: michael@0: // scale factor so that offsets are 1px in device pixels michael@0: return 1.0 / m; michael@0: } michael@0: michael@0: static DrawMode michael@0: ForcePaintingDrawMode(DrawMode aDrawMode) michael@0: { michael@0: return aDrawMode == DrawMode::GLYPH_PATH ? michael@0: DrawMode(int(DrawMode::GLYPH_FILL) | int(DrawMode::GLYPH_STROKE)) : michael@0: aDrawMode; michael@0: } michael@0: michael@0: void michael@0: gfxFont::Draw(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd, michael@0: gfxContext *aContext, DrawMode aDrawMode, gfxPoint *aPt, michael@0: Spacing *aSpacing, gfxTextContextPaint *aContextPaint, michael@0: gfxTextRunDrawCallbacks *aCallbacks) michael@0: { michael@0: NS_ASSERTION(aDrawMode == DrawMode::GLYPH_PATH || !(int(aDrawMode) & int(DrawMode::GLYPH_PATH)), michael@0: "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH"); michael@0: michael@0: if (aStart >= aEnd) michael@0: return; michael@0: michael@0: const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs(); michael@0: const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); michael@0: const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit); michael@0: bool isRTL = aTextRun->IsRightToLeft(); michael@0: double direction = aTextRun->GetDirection(); michael@0: gfxMatrix globalMatrix = aContext->CurrentMatrix(); michael@0: michael@0: bool haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this); michael@0: nsAutoPtr contextPaint; michael@0: if (haveSVGGlyphs && !aContextPaint) { michael@0: // If no pattern is specified for fill, use the current pattern michael@0: NS_ASSERTION((int(aDrawMode) & int(DrawMode::GLYPH_STROKE)) == 0, "no pattern supplied for stroking text"); michael@0: nsRefPtr fillPattern = aContext->GetPattern(); michael@0: contextPaint = new SimpleTextContextPaint(fillPattern, nullptr, michael@0: aContext->CurrentMatrix()); michael@0: aContextPaint = contextPaint; michael@0: } michael@0: michael@0: // synthetic-bold strikes are each offset one device pixel in run direction michael@0: // (these values are only needed if IsSyntheticBold() is true) michael@0: double synBoldOnePixelOffset = 0; michael@0: int32_t strikes = 1; michael@0: if (IsSyntheticBold()) { michael@0: double xscale = CalcXScale(aContext); michael@0: synBoldOnePixelOffset = direction * xscale; michael@0: if (xscale != 0.0) { michael@0: // use as many strikes as needed for the the increased advance michael@0: strikes = NS_lroundf(GetSyntheticBoldOffset() / xscale); michael@0: } michael@0: } michael@0: michael@0: uint32_t i; michael@0: // Current position in appunits michael@0: double x = aPt->x; michael@0: double y = aPt->y; michael@0: michael@0: cairo_t *cr = aContext->GetCairo(); michael@0: RefPtr dt = aContext->GetDrawTarget(); michael@0: michael@0: bool paintSVGGlyphs = !aCallbacks || aCallbacks->mShouldPaintSVGGlyphs; michael@0: bool emittedGlyphs = false; michael@0: michael@0: if (aContext->IsCairo()) { michael@0: bool success = SetupCairoFont(aContext); michael@0: if (MOZ_UNLIKELY(!success)) michael@0: return; michael@0: michael@0: ::GlyphBuffer glyphs; michael@0: cairo_glyph_t *glyph; michael@0: michael@0: if (aSpacing) { michael@0: x += direction*aSpacing[0].mBefore; michael@0: } michael@0: for (i = aStart; i < aEnd; ++i) { michael@0: const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[i]; michael@0: if (glyphData->IsSimpleGlyph()) { michael@0: double advance = glyphData->GetSimpleAdvance(); michael@0: double glyphX; michael@0: if (isRTL) { michael@0: x -= advance; michael@0: glyphX = x; michael@0: } else { michael@0: glyphX = x; michael@0: x += advance; michael@0: } michael@0: michael@0: if (haveSVGGlyphs) { michael@0: if (!paintSVGGlyphs) { michael@0: continue; michael@0: } michael@0: gfxPoint point(ToDeviceUnits(glyphX, devUnitsPerAppUnit), michael@0: ToDeviceUnits(y, devUnitsPerAppUnit)); michael@0: DrawMode mode = ForcePaintingDrawMode(aDrawMode); michael@0: if (RenderSVGGlyph(aContext, point, mode, michael@0: glyphData->GetSimpleGlyph(), aContextPaint, michael@0: aCallbacks, emittedGlyphs)) { michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: // Perhaps we should put a scale in the cairo context instead of michael@0: // doing this scaling here... michael@0: // Multiplying by the reciprocal may introduce tiny error here, michael@0: // but we assume cairo is going to round coordinates at some stage michael@0: // and this is faster michael@0: glyph = glyphs.AppendGlyph(); michael@0: glyph->index = glyphData->GetSimpleGlyph(); michael@0: glyph->x = ToDeviceUnits(glyphX, devUnitsPerAppUnit); michael@0: glyph->y = ToDeviceUnits(y, devUnitsPerAppUnit); michael@0: glyphs.Flush(cr, aDrawMode, isRTL, aContextPaint, globalMatrix); michael@0: michael@0: // synthetic bolding by multi-striking with 1-pixel offsets michael@0: // at least once, more if there's room (large font sizes) michael@0: if (IsSyntheticBold()) { michael@0: double strikeOffset = synBoldOnePixelOffset; michael@0: int32_t strikeCount = strikes; michael@0: do { michael@0: cairo_glyph_t *doubleglyph; michael@0: doubleglyph = glyphs.AppendGlyph(); michael@0: doubleglyph->index = glyph->index; michael@0: doubleglyph->x = michael@0: ToDeviceUnits(glyphX + strikeOffset * appUnitsPerDevUnit, michael@0: devUnitsPerAppUnit); michael@0: doubleglyph->y = glyph->y; michael@0: strikeOffset += synBoldOnePixelOffset; michael@0: glyphs.Flush(cr, aDrawMode, isRTL, aContextPaint, globalMatrix); michael@0: } while (--strikeCount > 0); michael@0: } michael@0: emittedGlyphs = true; michael@0: } else { michael@0: uint32_t glyphCount = glyphData->GetGlyphCount(); michael@0: if (glyphCount > 0) { michael@0: const gfxTextRun::DetailedGlyph *details = michael@0: aTextRun->GetDetailedGlyphs(i); michael@0: NS_ASSERTION(details, "detailedGlyph should not be missing!"); michael@0: double advance; michael@0: for (uint32_t j = 0; j < glyphCount; ++j, ++details, x += direction * advance) { michael@0: advance = details->mAdvance; michael@0: if (glyphData->IsMissing()) { michael@0: // default ignorable characters will have zero advance width. michael@0: // we don't have to draw the hexbox for them michael@0: if (aDrawMode != DrawMode::GLYPH_PATH && advance > 0) { michael@0: double glyphX = x; michael@0: if (isRTL) { michael@0: glyphX -= advance; michael@0: } michael@0: gfxPoint pt(ToDeviceUnits(glyphX, devUnitsPerAppUnit), michael@0: ToDeviceUnits(y, devUnitsPerAppUnit)); michael@0: gfxFloat advanceDevUnits = ToDeviceUnits(advance, devUnitsPerAppUnit); michael@0: gfxFloat height = GetMetrics().maxAscent; michael@0: gfxRect glyphRect(pt.x, pt.y - height, advanceDevUnits, height); michael@0: gfxFontMissingGlyphs::DrawMissingGlyph(aContext, michael@0: glyphRect, michael@0: details->mGlyphID, michael@0: appUnitsPerDevUnit); michael@0: } michael@0: } else { michael@0: double glyphX = x + details->mXOffset; michael@0: if (isRTL) { michael@0: glyphX -= advance; michael@0: } michael@0: michael@0: gfxPoint point(ToDeviceUnits(glyphX, devUnitsPerAppUnit), michael@0: ToDeviceUnits(y, devUnitsPerAppUnit)); michael@0: michael@0: if (haveSVGGlyphs) { michael@0: if (!paintSVGGlyphs) { michael@0: continue; michael@0: } michael@0: DrawMode mode = ForcePaintingDrawMode(aDrawMode); michael@0: if (RenderSVGGlyph(aContext, point, mode, michael@0: details->mGlyphID, michael@0: aContextPaint, aCallbacks, michael@0: emittedGlyphs)) { michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: glyph = glyphs.AppendGlyph(); michael@0: glyph->index = details->mGlyphID; michael@0: glyph->x = ToDeviceUnits(glyphX, devUnitsPerAppUnit); michael@0: glyph->y = ToDeviceUnits(y + details->mYOffset, devUnitsPerAppUnit); michael@0: glyphs.Flush(cr, aDrawMode, isRTL, aContextPaint, globalMatrix); michael@0: michael@0: if (IsSyntheticBold()) { michael@0: double strikeOffset = synBoldOnePixelOffset; michael@0: int32_t strikeCount = strikes; michael@0: do { michael@0: cairo_glyph_t *doubleglyph; michael@0: doubleglyph = glyphs.AppendGlyph(); michael@0: doubleglyph->index = glyph->index; michael@0: doubleglyph->x = michael@0: ToDeviceUnits(glyphX + strikeOffset * michael@0: appUnitsPerDevUnit, michael@0: devUnitsPerAppUnit); michael@0: doubleglyph->y = glyph->y; michael@0: strikeOffset += synBoldOnePixelOffset; michael@0: glyphs.Flush(cr, aDrawMode, isRTL, aContextPaint, globalMatrix); michael@0: } while (--strikeCount > 0); michael@0: } michael@0: emittedGlyphs = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (aSpacing) { michael@0: double space = aSpacing[i - aStart].mAfter; michael@0: if (i + 1 < aEnd) { michael@0: space += aSpacing[i + 1 - aStart].mBefore; michael@0: } michael@0: x += direction*space; michael@0: } michael@0: } michael@0: michael@0: if (gfxFontTestStore::CurrentStore()) { michael@0: /* This assumes that the tests won't have anything that results michael@0: * in more than GLYPH_BUFFER_SIZE glyphs. Do this before we michael@0: * flush, since that'll blow away the num_glyphs. michael@0: */ michael@0: gfxFontTestStore::CurrentStore()->AddItem(GetName(), michael@0: glyphs.mGlyphBuffer, michael@0: glyphs.mNumGlyphs); michael@0: } michael@0: michael@0: // draw any remaining glyphs michael@0: glyphs.Flush(cr, aDrawMode, isRTL, aContextPaint, globalMatrix, true); michael@0: if (aCallbacks && emittedGlyphs) { michael@0: aCallbacks->NotifyGlyphPathEmitted(); michael@0: } michael@0: michael@0: } else { michael@0: RefPtr scaledFont = GetScaledFont(dt); michael@0: michael@0: if (!scaledFont) { michael@0: return; michael@0: } michael@0: michael@0: bool oldSubpixelAA = dt->GetPermitSubpixelAA(); michael@0: michael@0: if (!AllowSubpixelAA()) { michael@0: dt->SetPermitSubpixelAA(false); michael@0: } michael@0: michael@0: GlyphBufferAzure glyphs; michael@0: Glyph *glyph; michael@0: michael@0: Matrix mat, matInv; michael@0: Matrix oldMat = dt->GetTransform(); michael@0: michael@0: // This is nullptr when we have inverse-transformed glyphs and we need michael@0: // to transform the Brush inside flush. michael@0: Matrix *passedInvMatrix = nullptr; michael@0: michael@0: RefPtr renderingOptions = michael@0: GetGlyphRenderingOptions(); michael@0: michael@0: DrawOptions drawOptions; michael@0: drawOptions.mAntialiasMode = Get2DAAMode(mAntialiasOption); michael@0: michael@0: // The cairo DrawTarget backend uses the cairo_scaled_font directly michael@0: // and so has the font skew matrix applied already. michael@0: if (mScaledFont && michael@0: dt->GetType() != BackendType::CAIRO) { michael@0: cairo_matrix_t matrix; michael@0: cairo_scaled_font_get_font_matrix(mScaledFont, &matrix); michael@0: if (matrix.xy != 0) { michael@0: // If this matrix applies a skew, which can happen when drawing michael@0: // oblique fonts, we will set the DrawTarget matrix to apply the michael@0: // skew. We'll need to move the glyphs by the inverse of the skew to michael@0: // get the glyphs positioned correctly in the new device space michael@0: // though, since the font matrix should only be applied to drawing michael@0: // the glyphs, and not to their position. michael@0: mat = ToMatrix(*reinterpret_cast(&matrix)); michael@0: michael@0: mat._11 = mat._22 = 1.0; michael@0: float adjustedSize = mAdjustedSize > 0 ? mAdjustedSize : GetStyle()->size; michael@0: mat._21 /= adjustedSize; michael@0: michael@0: dt->SetTransform(mat * oldMat); michael@0: michael@0: matInv = mat; michael@0: matInv.Invert(); michael@0: michael@0: passedInvMatrix = &matInv; michael@0: } michael@0: } michael@0: michael@0: if (aSpacing) { michael@0: x += direction*aSpacing[0].mBefore; michael@0: } michael@0: for (i = aStart; i < aEnd; ++i) { michael@0: const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[i]; michael@0: if (glyphData->IsSimpleGlyph()) { michael@0: double advance = glyphData->GetSimpleAdvance(); michael@0: double glyphX; michael@0: if (isRTL) { michael@0: x -= advance; michael@0: glyphX = x; michael@0: } else { michael@0: glyphX = x; michael@0: x += advance; michael@0: } michael@0: michael@0: if (haveSVGGlyphs) { michael@0: if (!paintSVGGlyphs) { michael@0: continue; michael@0: } michael@0: gfxPoint point(ToDeviceUnits(glyphX, devUnitsPerAppUnit), michael@0: ToDeviceUnits(y, devUnitsPerAppUnit)); michael@0: DrawMode mode = ForcePaintingDrawMode(aDrawMode); michael@0: if (RenderSVGGlyph(aContext, point, mode, michael@0: glyphData->GetSimpleGlyph(), aContextPaint, michael@0: aCallbacks, emittedGlyphs)) { michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: // Perhaps we should put a scale in the cairo context instead of michael@0: // doing this scaling here... michael@0: // Multiplying by the reciprocal may introduce tiny error here, michael@0: // but we assume cairo is going to round coordinates at some stage michael@0: // and this is faster michael@0: glyph = glyphs.AppendGlyph(); michael@0: glyph->mIndex = glyphData->GetSimpleGlyph(); michael@0: glyph->mPosition.x = ToDeviceUnits(glyphX, devUnitsPerAppUnit); michael@0: glyph->mPosition.y = ToDeviceUnits(y, devUnitsPerAppUnit); michael@0: glyph->mPosition = matInv * glyph->mPosition; michael@0: glyphs.Flush(dt, aContextPaint, scaledFont, michael@0: aDrawMode, isRTL, renderingOptions, michael@0: aContext, passedInvMatrix, michael@0: drawOptions); michael@0: michael@0: // synthetic bolding by multi-striking with 1-pixel offsets michael@0: // at least once, more if there's room (large font sizes) michael@0: if (IsSyntheticBold()) { michael@0: double strikeOffset = synBoldOnePixelOffset; michael@0: int32_t strikeCount = strikes; michael@0: do { michael@0: Glyph *doubleglyph; michael@0: doubleglyph = glyphs.AppendGlyph(); michael@0: doubleglyph->mIndex = glyph->mIndex; michael@0: doubleglyph->mPosition.x = michael@0: ToDeviceUnits(glyphX + strikeOffset * appUnitsPerDevUnit, michael@0: devUnitsPerAppUnit); michael@0: doubleglyph->mPosition.y = glyph->mPosition.y; michael@0: doubleglyph->mPosition = matInv * doubleglyph->mPosition; michael@0: strikeOffset += synBoldOnePixelOffset; michael@0: glyphs.Flush(dt, aContextPaint, scaledFont, michael@0: aDrawMode, isRTL, renderingOptions, michael@0: aContext, passedInvMatrix, michael@0: drawOptions); michael@0: } while (--strikeCount > 0); michael@0: } michael@0: emittedGlyphs = true; michael@0: } else { michael@0: uint32_t glyphCount = glyphData->GetGlyphCount(); michael@0: if (glyphCount > 0) { michael@0: const gfxTextRun::DetailedGlyph *details = michael@0: aTextRun->GetDetailedGlyphs(i); michael@0: NS_ASSERTION(details, "detailedGlyph should not be missing!"); michael@0: double advance; michael@0: for (uint32_t j = 0; j < glyphCount; ++j, ++details, x += direction * advance) { michael@0: advance = details->mAdvance; michael@0: if (glyphData->IsMissing()) { michael@0: // default ignorable characters will have zero advance width. michael@0: // we don't have to draw the hexbox for them michael@0: if (aDrawMode != DrawMode::GLYPH_PATH && advance > 0) { michael@0: double glyphX = x; michael@0: if (isRTL) { michael@0: glyphX -= advance; michael@0: } michael@0: gfxPoint pt(ToDeviceUnits(glyphX, devUnitsPerAppUnit), michael@0: ToDeviceUnits(y, devUnitsPerAppUnit)); michael@0: gfxFloat advanceDevUnits = ToDeviceUnits(advance, devUnitsPerAppUnit); michael@0: gfxFloat height = GetMetrics().maxAscent; michael@0: gfxRect glyphRect(pt.x, pt.y - height, advanceDevUnits, height); michael@0: gfxFontMissingGlyphs::DrawMissingGlyph(aContext, michael@0: glyphRect, michael@0: details->mGlyphID, michael@0: appUnitsPerDevUnit); michael@0: } michael@0: } else { michael@0: double glyphX = x + details->mXOffset; michael@0: if (isRTL) { michael@0: glyphX -= advance; michael@0: } michael@0: michael@0: gfxPoint point(ToDeviceUnits(glyphX, devUnitsPerAppUnit), michael@0: ToDeviceUnits(y, devUnitsPerAppUnit)); michael@0: michael@0: if (haveSVGGlyphs) { michael@0: if (!paintSVGGlyphs) { michael@0: continue; michael@0: } michael@0: DrawMode mode = ForcePaintingDrawMode(aDrawMode); michael@0: if (RenderSVGGlyph(aContext, point, mode, michael@0: details->mGlyphID, michael@0: aContextPaint, aCallbacks, michael@0: emittedGlyphs)) { michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: glyph = glyphs.AppendGlyph(); michael@0: glyph->mIndex = details->mGlyphID; michael@0: glyph->mPosition.x = ToDeviceUnits(glyphX, devUnitsPerAppUnit); michael@0: glyph->mPosition.y = ToDeviceUnits(y + details->mYOffset, devUnitsPerAppUnit); michael@0: glyph->mPosition = matInv * glyph->mPosition; michael@0: glyphs.Flush(dt, aContextPaint, scaledFont, aDrawMode, michael@0: isRTL, renderingOptions, aContext, passedInvMatrix, michael@0: drawOptions); michael@0: michael@0: if (IsSyntheticBold()) { michael@0: double strikeOffset = synBoldOnePixelOffset; michael@0: int32_t strikeCount = strikes; michael@0: do { michael@0: Glyph *doubleglyph; michael@0: doubleglyph = glyphs.AppendGlyph(); michael@0: doubleglyph->mIndex = glyph->mIndex; michael@0: doubleglyph->mPosition.x = michael@0: ToDeviceUnits(glyphX + strikeOffset * michael@0: appUnitsPerDevUnit, michael@0: devUnitsPerAppUnit); michael@0: doubleglyph->mPosition.y = glyph->mPosition.y; michael@0: strikeOffset += synBoldOnePixelOffset; michael@0: doubleglyph->mPosition = matInv * doubleglyph->mPosition; michael@0: glyphs.Flush(dt, aContextPaint, scaledFont, michael@0: aDrawMode, isRTL, renderingOptions, michael@0: aContext, passedInvMatrix, drawOptions); michael@0: } while (--strikeCount > 0); michael@0: } michael@0: emittedGlyphs = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (aSpacing) { michael@0: double space = aSpacing[i - aStart].mAfter; michael@0: if (i + 1 < aEnd) { michael@0: space += aSpacing[i + 1 - aStart].mBefore; michael@0: } michael@0: x += direction*space; michael@0: } michael@0: } michael@0: michael@0: glyphs.Flush(dt, aContextPaint, scaledFont, aDrawMode, isRTL, michael@0: renderingOptions, aContext, passedInvMatrix, michael@0: drawOptions, true); michael@0: if (aCallbacks && emittedGlyphs) { michael@0: aCallbacks->NotifyGlyphPathEmitted(); michael@0: } michael@0: michael@0: dt->SetTransform(oldMat); michael@0: michael@0: dt->SetPermitSubpixelAA(oldSubpixelAA); michael@0: } michael@0: michael@0: *aPt = gfxPoint(x, y); michael@0: } michael@0: michael@0: bool michael@0: gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint, DrawMode aDrawMode, michael@0: uint32_t aGlyphId, gfxTextContextPaint *aContextPaint) michael@0: { michael@0: if (!GetFontEntry()->HasSVGGlyph(aGlyphId)) { michael@0: return false; michael@0: } michael@0: michael@0: const gfxFloat devUnitsPerSVGUnit = michael@0: GetAdjustedSize() / GetFontEntry()->UnitsPerEm(); michael@0: gfxContextMatrixAutoSaveRestore matrixRestore(aContext); michael@0: michael@0: aContext->Translate(gfxPoint(aPoint.x, aPoint.y)); michael@0: aContext->Scale(devUnitsPerSVGUnit, devUnitsPerSVGUnit); michael@0: michael@0: aContextPaint->InitStrokeGeometry(aContext, devUnitsPerSVGUnit); michael@0: michael@0: return GetFontEntry()->RenderSVGGlyph(aContext, aGlyphId, int(aDrawMode), michael@0: aContextPaint); michael@0: } michael@0: michael@0: bool michael@0: gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint, DrawMode aDrawMode, michael@0: uint32_t aGlyphId, gfxTextContextPaint *aContextPaint, michael@0: gfxTextRunDrawCallbacks *aCallbacks, michael@0: bool& aEmittedGlyphs) michael@0: { michael@0: if (aCallbacks) { michael@0: if (aEmittedGlyphs) { michael@0: aCallbacks->NotifyGlyphPathEmitted(); michael@0: aEmittedGlyphs = false; michael@0: } michael@0: aCallbacks->NotifyBeforeSVGGlyphPainted(); michael@0: } michael@0: bool rendered = RenderSVGGlyph(aContext, aPoint, aDrawMode, aGlyphId, michael@0: aContextPaint); michael@0: if (aCallbacks) { michael@0: aCallbacks->NotifyAfterSVGGlyphPainted(); michael@0: } michael@0: return rendered; michael@0: } michael@0: michael@0: static void michael@0: UnionRange(gfxFloat aX, gfxFloat* aDestMin, gfxFloat* aDestMax) michael@0: { michael@0: *aDestMin = std::min(*aDestMin, aX); michael@0: *aDestMax = std::max(*aDestMax, aX); michael@0: } michael@0: michael@0: // We get precise glyph extents if the textrun creator requested them, or michael@0: // if the font is a user font --- in which case the author may be relying michael@0: // on overflowing glyphs. michael@0: static bool michael@0: NeedsGlyphExtents(gfxFont *aFont, gfxTextRun *aTextRun) michael@0: { michael@0: return (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX) || michael@0: aFont->GetFontEntry()->IsUserFont(); michael@0: } michael@0: michael@0: static bool michael@0: NeedsGlyphExtents(gfxTextRun *aTextRun) michael@0: { michael@0: if (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX) michael@0: return true; michael@0: uint32_t numRuns; michael@0: const gfxTextRun::GlyphRun *glyphRuns = aTextRun->GetGlyphRuns(&numRuns); michael@0: for (uint32_t i = 0; i < numRuns; ++i) { michael@0: if (glyphRuns[i].mFont->GetFontEntry()->IsUserFont()) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: gfxFont::RunMetrics michael@0: gfxFont::Measure(gfxTextRun *aTextRun, michael@0: uint32_t aStart, uint32_t aEnd, michael@0: BoundingBoxType aBoundingBoxType, michael@0: gfxContext *aRefContext, michael@0: Spacing *aSpacing) michael@0: { michael@0: // If aBoundingBoxType is TIGHT_HINTED_OUTLINE_EXTENTS michael@0: // and the underlying cairo font may be antialiased, michael@0: // we need to create a copy in order to avoid getting cached extents. michael@0: // This is only used by MathML layout at present. michael@0: if (aBoundingBoxType == TIGHT_HINTED_OUTLINE_EXTENTS && michael@0: mAntialiasOption != kAntialiasNone) { michael@0: if (!mNonAAFont) { michael@0: mNonAAFont = CopyWithAntialiasOption(kAntialiasNone); michael@0: } michael@0: // if font subclass doesn't implement CopyWithAntialiasOption(), michael@0: // it will return null and we'll proceed to use the existing font michael@0: if (mNonAAFont) { michael@0: return mNonAAFont->Measure(aTextRun, aStart, aEnd, michael@0: TIGHT_HINTED_OUTLINE_EXTENTS, michael@0: aRefContext, aSpacing); michael@0: } michael@0: } michael@0: michael@0: const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); michael@0: // Current position in appunits michael@0: const gfxFont::Metrics& fontMetrics = GetMetrics(); michael@0: michael@0: RunMetrics metrics; michael@0: metrics.mAscent = fontMetrics.maxAscent*appUnitsPerDevUnit; michael@0: metrics.mDescent = fontMetrics.maxDescent*appUnitsPerDevUnit; michael@0: if (aStart == aEnd) { michael@0: // exit now before we look at aSpacing[0], which is undefined michael@0: metrics.mBoundingBox = gfxRect(0, -metrics.mAscent, 0, metrics.mAscent + metrics.mDescent); michael@0: return metrics; michael@0: } michael@0: michael@0: gfxFloat advanceMin = 0, advanceMax = 0; michael@0: const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs(); michael@0: bool isRTL = aTextRun->IsRightToLeft(); michael@0: double direction = aTextRun->GetDirection(); michael@0: bool needsGlyphExtents = NeedsGlyphExtents(this, aTextRun); michael@0: gfxGlyphExtents *extents = michael@0: (aBoundingBoxType == LOOSE_INK_EXTENTS && michael@0: !needsGlyphExtents && michael@0: !aTextRun->HasDetailedGlyphs()) ? nullptr michael@0: : GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit()); michael@0: double x = 0; michael@0: if (aSpacing) { michael@0: x += direction*aSpacing[0].mBefore; michael@0: } michael@0: uint32_t i; michael@0: for (i = aStart; i < aEnd; ++i) { michael@0: const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[i]; michael@0: if (glyphData->IsSimpleGlyph()) { michael@0: double advance = glyphData->GetSimpleAdvance(); michael@0: // Only get the real glyph horizontal extent if we were asked michael@0: // for the tight bounding box or we're in quality mode michael@0: if ((aBoundingBoxType != LOOSE_INK_EXTENTS || needsGlyphExtents) && michael@0: extents) { michael@0: uint32_t glyphIndex = glyphData->GetSimpleGlyph(); michael@0: uint16_t extentsWidth = extents->GetContainedGlyphWidthAppUnits(glyphIndex); michael@0: if (extentsWidth != gfxGlyphExtents::INVALID_WIDTH && michael@0: aBoundingBoxType == LOOSE_INK_EXTENTS) { michael@0: UnionRange(x, &advanceMin, &advanceMax); michael@0: UnionRange(x + direction*extentsWidth, &advanceMin, &advanceMax); michael@0: } else { michael@0: gfxRect glyphRect; michael@0: if (!extents->GetTightGlyphExtentsAppUnits(this, michael@0: aRefContext, glyphIndex, &glyphRect)) { michael@0: glyphRect = gfxRect(0, metrics.mBoundingBox.Y(), michael@0: advance, metrics.mBoundingBox.Height()); michael@0: } michael@0: if (isRTL) { michael@0: glyphRect -= gfxPoint(advance, 0); michael@0: } michael@0: glyphRect += gfxPoint(x, 0); michael@0: metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect); michael@0: } michael@0: } michael@0: x += direction*advance; michael@0: } else { michael@0: uint32_t glyphCount = glyphData->GetGlyphCount(); michael@0: if (glyphCount > 0) { michael@0: const gfxTextRun::DetailedGlyph *details = michael@0: aTextRun->GetDetailedGlyphs(i); michael@0: NS_ASSERTION(details != nullptr, michael@0: "detaiedGlyph record should not be missing!"); michael@0: uint32_t j; michael@0: for (j = 0; j < glyphCount; ++j, ++details) { michael@0: uint32_t glyphIndex = details->mGlyphID; michael@0: gfxPoint glyphPt(x + details->mXOffset, details->mYOffset); michael@0: double advance = details->mAdvance; michael@0: gfxRect glyphRect; michael@0: if (glyphData->IsMissing() || !extents || michael@0: !extents->GetTightGlyphExtentsAppUnits(this, michael@0: aRefContext, glyphIndex, &glyphRect)) { michael@0: // We might have failed to get glyph extents due to michael@0: // OOM or something michael@0: glyphRect = gfxRect(0, -metrics.mAscent, michael@0: advance, metrics.mAscent + metrics.mDescent); michael@0: } michael@0: if (isRTL) { michael@0: glyphRect -= gfxPoint(advance, 0); michael@0: } michael@0: glyphRect += gfxPoint(x, 0); michael@0: metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect); michael@0: x += direction*advance; michael@0: } michael@0: } michael@0: } michael@0: // Every other glyph type is ignored michael@0: if (aSpacing) { michael@0: double space = aSpacing[i - aStart].mAfter; michael@0: if (i + 1 < aEnd) { michael@0: space += aSpacing[i + 1 - aStart].mBefore; michael@0: } michael@0: x += direction*space; michael@0: } michael@0: } michael@0: michael@0: if (aBoundingBoxType == LOOSE_INK_EXTENTS) { michael@0: UnionRange(x, &advanceMin, &advanceMax); michael@0: gfxRect fontBox(advanceMin, -metrics.mAscent, michael@0: advanceMax - advanceMin, metrics.mAscent + metrics.mDescent); michael@0: metrics.mBoundingBox = metrics.mBoundingBox.Union(fontBox); michael@0: } michael@0: if (isRTL) { michael@0: metrics.mBoundingBox -= gfxPoint(x, 0); michael@0: } michael@0: michael@0: metrics.mAdvanceWidth = x*direction; michael@0: return metrics; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: NotifyGlyphChangeObservers(nsPtrHashKey* aKey, michael@0: void* aClosure) michael@0: { michael@0: aKey->GetKey()->NotifyGlyphsChanged(); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: gfxFont::NotifyGlyphsChanged() michael@0: { michael@0: uint32_t i, count = mGlyphExtentsArray.Length(); michael@0: for (i = 0; i < count; ++i) { michael@0: // Flush cached extents array michael@0: mGlyphExtentsArray[i]->NotifyGlyphsChanged(); michael@0: } michael@0: michael@0: if (mGlyphChangeObservers) { michael@0: mGlyphChangeObservers->EnumerateEntries(NotifyGlyphChangeObservers, nullptr); michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: IsBoundarySpace(char16_t aChar, char16_t aNextChar) michael@0: { michael@0: return (aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar); michael@0: } michael@0: michael@0: static inline uint32_t michael@0: HashMix(uint32_t aHash, char16_t aCh) michael@0: { michael@0: return (aHash >> 28) ^ (aHash << 4) ^ aCh; michael@0: } michael@0: michael@0: #ifdef __GNUC__ michael@0: #define GFX_MAYBE_UNUSED __attribute__((unused)) michael@0: #else michael@0: #define GFX_MAYBE_UNUSED michael@0: #endif michael@0: michael@0: template michael@0: gfxShapedWord* michael@0: gfxFont::GetShapedWord(gfxContext *aContext, michael@0: const T *aText, michael@0: uint32_t aLength, michael@0: uint32_t aHash, michael@0: int32_t aRunScript, michael@0: int32_t aAppUnitsPerDevUnit, michael@0: uint32_t aFlags, michael@0: gfxTextPerfMetrics *aTextPerf GFX_MAYBE_UNUSED) michael@0: { michael@0: // if the cache is getting too big, flush it and start over michael@0: uint32_t wordCacheMaxEntries = michael@0: gfxPlatform::GetPlatform()->WordCacheMaxEntries(); michael@0: if (mWordCache->Count() > wordCacheMaxEntries) { michael@0: NS_WARNING("flushing shaped-word cache"); michael@0: ClearCachedWords(); michael@0: } michael@0: michael@0: // if there's a cached entry for this word, just return it michael@0: CacheHashKey key(aText, aLength, aHash, michael@0: aRunScript, michael@0: aAppUnitsPerDevUnit, michael@0: aFlags); michael@0: michael@0: CacheHashEntry *entry = mWordCache->PutEntry(key); michael@0: if (!entry) { michael@0: NS_WARNING("failed to create word cache entry - expect missing text"); michael@0: return nullptr; michael@0: } michael@0: gfxShapedWord *sw = entry->mShapedWord; michael@0: michael@0: bool isContent = !mStyle.systemFont; michael@0: michael@0: if (sw) { michael@0: sw->ResetAge(); michael@0: Telemetry::Accumulate((isContent ? Telemetry::WORD_CACHE_HITS_CONTENT : michael@0: Telemetry::WORD_CACHE_HITS_CHROME), michael@0: aLength); michael@0: #ifndef RELEASE_BUILD michael@0: if (aTextPerf) { michael@0: aTextPerf->current.wordCacheHit++; michael@0: } michael@0: #endif michael@0: return sw; michael@0: } michael@0: michael@0: Telemetry::Accumulate((isContent ? Telemetry::WORD_CACHE_MISSES_CONTENT : michael@0: Telemetry::WORD_CACHE_MISSES_CHROME), michael@0: aLength); michael@0: #ifndef RELEASE_BUILD michael@0: if (aTextPerf) { michael@0: aTextPerf->current.wordCacheMiss++; michael@0: } michael@0: #endif michael@0: michael@0: sw = entry->mShapedWord = gfxShapedWord::Create(aText, aLength, michael@0: aRunScript, michael@0: aAppUnitsPerDevUnit, michael@0: aFlags); michael@0: if (!sw) { michael@0: NS_WARNING("failed to create gfxShapedWord - expect missing text"); michael@0: return nullptr; michael@0: } michael@0: michael@0: DebugOnly ok = michael@0: ShapeText(aContext, aText, 0, aLength, aRunScript, sw); michael@0: michael@0: NS_WARN_IF_FALSE(ok, "failed to shape word - expect garbled text"); michael@0: michael@0: return sw; michael@0: } michael@0: michael@0: bool michael@0: gfxFont::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const michael@0: { michael@0: const gfxShapedWord *sw = mShapedWord; michael@0: if (!sw) { michael@0: return false; michael@0: } michael@0: if (sw->GetLength() != aKey->mLength || michael@0: sw->Flags() != aKey->mFlags || michael@0: sw->GetAppUnitsPerDevUnit() != aKey->mAppUnitsPerDevUnit || michael@0: sw->Script() != aKey->mScript) { michael@0: return false; michael@0: } michael@0: if (sw->TextIs8Bit()) { michael@0: if (aKey->mTextIs8Bit) { michael@0: return (0 == memcmp(sw->Text8Bit(), aKey->mText.mSingle, michael@0: aKey->mLength * sizeof(uint8_t))); michael@0: } michael@0: // The key has 16-bit text, even though all the characters are < 256, michael@0: // so the TEXT_IS_8BIT flag was set and the cached ShapedWord we're michael@0: // comparing with will have 8-bit text. michael@0: const uint8_t *s1 = sw->Text8Bit(); michael@0: const char16_t *s2 = aKey->mText.mDouble; michael@0: const char16_t *s2end = s2 + aKey->mLength; michael@0: while (s2 < s2end) { michael@0: if (*s1++ != *s2++) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: NS_ASSERTION((aKey->mFlags & gfxTextRunFactory::TEXT_IS_8BIT) == 0 && michael@0: !aKey->mTextIs8Bit, "didn't expect 8-bit text here"); michael@0: return (0 == memcmp(sw->TextUnicode(), aKey->mText.mDouble, michael@0: aKey->mLength * sizeof(char16_t))); michael@0: } michael@0: michael@0: bool michael@0: gfxFont::ShapeText(gfxContext *aContext, michael@0: const uint8_t *aText, michael@0: uint32_t aOffset, michael@0: uint32_t aLength, michael@0: int32_t aScript, michael@0: gfxShapedText *aShapedText, michael@0: bool aPreferPlatformShaping) michael@0: { michael@0: nsDependentCSubstring ascii((const char*)aText, aLength); michael@0: nsAutoString utf16; michael@0: AppendASCIItoUTF16(ascii, utf16); michael@0: if (utf16.Length() != aLength) { michael@0: return false; michael@0: } michael@0: return ShapeText(aContext, utf16.BeginReading(), aOffset, aLength, michael@0: aScript, aShapedText, aPreferPlatformShaping); michael@0: } michael@0: michael@0: bool michael@0: gfxFont::ShapeText(gfxContext *aContext, michael@0: const char16_t *aText, michael@0: uint32_t aOffset, michael@0: uint32_t aLength, michael@0: int32_t aScript, michael@0: gfxShapedText *aShapedText, michael@0: bool aPreferPlatformShaping) michael@0: { michael@0: bool ok = false; michael@0: michael@0: if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { michael@0: ok = mGraphiteShaper->ShapeText(aContext, aText, aOffset, aLength, michael@0: aScript, aShapedText); michael@0: } michael@0: michael@0: if (!ok && mHarfBuzzShaper && !aPreferPlatformShaping) { michael@0: if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aScript)) { michael@0: ok = mHarfBuzzShaper->ShapeText(aContext, aText, aOffset, aLength, michael@0: aScript, aShapedText); michael@0: } michael@0: } michael@0: michael@0: if (!ok) { michael@0: if (!mPlatformShaper) { michael@0: CreatePlatformShaper(); michael@0: NS_ASSERTION(mPlatformShaper, "no platform shaper available!"); michael@0: } michael@0: if (mPlatformShaper) { michael@0: ok = mPlatformShaper->ShapeText(aContext, aText, aOffset, aLength, michael@0: aScript, aShapedText); michael@0: } michael@0: } michael@0: michael@0: PostShapingFixup(aContext, aText, aOffset, aLength, aShapedText); michael@0: michael@0: return ok; michael@0: } michael@0: michael@0: void michael@0: gfxFont::PostShapingFixup(gfxContext *aContext, michael@0: const char16_t *aText, michael@0: uint32_t aOffset, michael@0: uint32_t aLength, michael@0: gfxShapedText *aShapedText) michael@0: { michael@0: if (IsSyntheticBold()) { michael@0: float synBoldOffset = michael@0: GetSyntheticBoldOffset() * CalcXScale(aContext); michael@0: aShapedText->AdjustAdvancesForSyntheticBold(synBoldOffset, michael@0: aOffset, aLength); michael@0: } michael@0: } michael@0: michael@0: #define MAX_SHAPING_LENGTH 32760 // slightly less than 32K, trying to avoid michael@0: // over-stressing platform shapers michael@0: #define BACKTRACK_LIMIT 16 // backtrack this far looking for a good place michael@0: // to split into fragments for separate shaping michael@0: michael@0: template michael@0: bool michael@0: gfxFont::ShapeFragmentWithoutWordCache(gfxContext *aContext, michael@0: const T *aText, michael@0: uint32_t aOffset, michael@0: uint32_t aLength, michael@0: int32_t aScript, michael@0: gfxTextRun *aTextRun) michael@0: { michael@0: aTextRun->SetupClusterBoundaries(aOffset, aText, aLength); michael@0: michael@0: bool ok = true; michael@0: michael@0: while (ok && aLength > 0) { michael@0: uint32_t fragLen = aLength; michael@0: michael@0: // limit the length of text we pass to shapers in a single call michael@0: if (fragLen > MAX_SHAPING_LENGTH) { michael@0: fragLen = MAX_SHAPING_LENGTH; michael@0: michael@0: // in the 8-bit case, there are no multi-char clusters, michael@0: // so we don't need to do this check michael@0: if (sizeof(T) == sizeof(char16_t)) { michael@0: uint32_t i; michael@0: for (i = 0; i < BACKTRACK_LIMIT; ++i) { michael@0: if (aTextRun->IsClusterStart(aOffset + fragLen - i)) { michael@0: fragLen -= i; michael@0: break; michael@0: } michael@0: } michael@0: if (i == BACKTRACK_LIMIT) { michael@0: // if we didn't find any cluster start while backtracking, michael@0: // just check that we're not in the middle of a surrogate michael@0: // pair; back up by one code unit if we are. michael@0: if (NS_IS_LOW_SURROGATE(aText[fragLen]) && michael@0: NS_IS_HIGH_SURROGATE(aText[fragLen - 1])) { michael@0: --fragLen; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: ok = ShapeText(aContext, aText, aOffset, fragLen, aScript, aTextRun); michael@0: michael@0: aText += fragLen; michael@0: aOffset += fragLen; michael@0: aLength -= fragLen; michael@0: } michael@0: michael@0: return ok; michael@0: } michael@0: michael@0: // Check if aCh is an unhandled control character that should be displayed michael@0: // as a hexbox rather than rendered by some random font on the system. michael@0: // We exclude \r as stray s are rather common (bug 941940). michael@0: // Note that \n and \t don't come through here, as they have specific michael@0: // meanings that have already been handled. michael@0: static bool michael@0: IsInvalidControlChar(uint32_t aCh) michael@0: { michael@0: return aCh != '\r' && ((aCh & 0x7f) < 0x20 || aCh == 0x7f); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: gfxFont::ShapeTextWithoutWordCache(gfxContext *aContext, michael@0: const T *aText, michael@0: uint32_t aOffset, michael@0: uint32_t aLength, michael@0: int32_t aScript, michael@0: gfxTextRun *aTextRun) michael@0: { michael@0: uint32_t fragStart = 0; michael@0: bool ok = true; michael@0: michael@0: for (uint32_t i = 0; i <= aLength && ok; ++i) { michael@0: T ch = (i < aLength) ? aText[i] : '\n'; michael@0: bool invalid = gfxFontGroup::IsInvalidChar(ch); michael@0: uint32_t length = i - fragStart; michael@0: michael@0: // break into separate fragments when we hit an invalid char michael@0: if (!invalid) { michael@0: continue; michael@0: } michael@0: michael@0: if (length > 0) { michael@0: ok = ShapeFragmentWithoutWordCache(aContext, aText + fragStart, michael@0: aOffset + fragStart, length, michael@0: aScript, aTextRun); michael@0: } michael@0: michael@0: if (i == aLength) { michael@0: break; michael@0: } michael@0: michael@0: // fragment was terminated by an invalid char: skip it, michael@0: // unless it's a control char that we want to show as a hexbox, michael@0: // but record where TAB or NEWLINE occur michael@0: if (ch == '\t') { michael@0: aTextRun->SetIsTab(aOffset + i); michael@0: } else if (ch == '\n') { michael@0: aTextRun->SetIsNewline(aOffset + i); michael@0: } else if (IsInvalidControlChar(ch) && michael@0: !(aTextRun->GetFlags() & gfxTextRunFactory::TEXT_HIDE_CONTROL_CHARACTERS)) { michael@0: aTextRun->SetMissingGlyph(aOffset + i, ch, this); michael@0: } michael@0: fragStart = i + 1; michael@0: } michael@0: michael@0: NS_WARN_IF_FALSE(ok, "failed to shape text - expect garbled text"); michael@0: return ok; michael@0: } michael@0: michael@0: #ifndef RELEASE_BUILD michael@0: #define TEXT_PERF_INCR(tp, m) (tp ? (tp)->current.m++ : 0) michael@0: #else michael@0: #define TEXT_PERF_INCR(tp, m) michael@0: #endif michael@0: michael@0: inline static bool IsChar8Bit(uint8_t /*aCh*/) { return true; } michael@0: inline static bool IsChar8Bit(char16_t aCh) { return aCh < 0x100; } michael@0: michael@0: inline static bool HasSpaces(const uint8_t *aString, uint32_t aLen) michael@0: { michael@0: return memchr(aString, 0x20, aLen) != nullptr; michael@0: } michael@0: michael@0: inline static bool HasSpaces(const char16_t *aString, uint32_t aLen) michael@0: { michael@0: for (const char16_t *ch = aString; ch < aString + aLen; ch++) { michael@0: if (*ch == 0x20) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: gfxFont::SplitAndInitTextRun(gfxContext *aContext, michael@0: gfxTextRun *aTextRun, michael@0: const T *aString, michael@0: uint32_t aRunStart, michael@0: uint32_t aRunLength, michael@0: int32_t aRunScript) michael@0: { michael@0: if (aRunLength == 0) { michael@0: return true; michael@0: } michael@0: michael@0: gfxTextPerfMetrics *tp = nullptr; michael@0: michael@0: #ifndef RELEASE_BUILD michael@0: tp = aTextRun->GetFontGroup()->GetTextPerfMetrics(); michael@0: if (tp) { michael@0: if (mStyle.systemFont) { michael@0: tp->current.numChromeTextRuns++; michael@0: } else { michael@0: tp->current.numContentTextRuns++; michael@0: } michael@0: tp->current.numChars += aRunLength; michael@0: if (aRunLength > tp->current.maxTextRunLen) { michael@0: tp->current.maxTextRunLen = aRunLength; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: uint32_t wordCacheCharLimit = michael@0: gfxPlatform::GetPlatform()->WordCacheCharLimit(); michael@0: michael@0: // If spaces can participate in shaping (e.g. within lookups for automatic michael@0: // fractions), need to shape without using the word cache which segments michael@0: // textruns on space boundaries. Word cache can be used if the textrun michael@0: // is short enough to fit in the word cache and it lacks spaces. michael@0: if (SpaceMayParticipateInShaping(aRunScript)) { michael@0: if (aRunLength > wordCacheCharLimit || michael@0: HasSpaces(aString + aRunStart, aRunLength)) { michael@0: TEXT_PERF_INCR(tp, wordCacheSpaceRules); michael@0: return ShapeTextWithoutWordCache(aContext, aString + aRunStart, michael@0: aRunStart, aRunLength, aRunScript, michael@0: aTextRun); michael@0: } michael@0: } michael@0: michael@0: InitWordCache(); michael@0: michael@0: // the only flags we care about for ShapedWord construction/caching michael@0: uint32_t flags = aTextRun->GetFlags(); michael@0: flags &= (gfxTextRunFactory::TEXT_IS_RTL | michael@0: gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES | michael@0: gfxTextRunFactory::TEXT_USE_MATH_SCRIPT); michael@0: if (sizeof(T) == sizeof(uint8_t)) { michael@0: flags |= gfxTextRunFactory::TEXT_IS_8BIT; michael@0: } michael@0: michael@0: const T *text = aString + aRunStart; michael@0: uint32_t wordStart = 0; michael@0: uint32_t hash = 0; michael@0: bool wordIs8Bit = true; michael@0: int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); michael@0: michael@0: T nextCh = text[0]; michael@0: for (uint32_t i = 0; i <= aRunLength; ++i) { michael@0: T ch = nextCh; michael@0: nextCh = (i < aRunLength - 1) ? text[i + 1] : '\n'; michael@0: bool boundary = IsBoundarySpace(ch, nextCh); michael@0: bool invalid = !boundary && gfxFontGroup::IsInvalidChar(ch); michael@0: uint32_t length = i - wordStart; michael@0: michael@0: // break into separate ShapedWords when we hit an invalid char, michael@0: // or a boundary space (always handled individually), michael@0: // or the first non-space after a space michael@0: if (!boundary && !invalid) { michael@0: if (!IsChar8Bit(ch)) { michael@0: wordIs8Bit = false; michael@0: } michael@0: // include this character in the hash, and move on to next michael@0: hash = HashMix(hash, ch); michael@0: continue; michael@0: } michael@0: michael@0: // We've decided to break here (i.e. we're at the end of a "word"); michael@0: // shape the word and add it to the textrun. michael@0: // For words longer than the limit, we don't use the michael@0: // font's word cache but just shape directly into the textrun. michael@0: if (length > wordCacheCharLimit) { michael@0: TEXT_PERF_INCR(tp, wordCacheLong); michael@0: bool ok = ShapeFragmentWithoutWordCache(aContext, michael@0: text + wordStart, michael@0: aRunStart + wordStart, michael@0: length, michael@0: aRunScript, michael@0: aTextRun); michael@0: if (!ok) { michael@0: return false; michael@0: } michael@0: } else if (length > 0) { michael@0: uint32_t wordFlags = flags; michael@0: // in the 8-bit version of this method, TEXT_IS_8BIT was michael@0: // already set as part of |flags|, so no need for a per-word michael@0: // adjustment here michael@0: if (sizeof(T) == sizeof(char16_t)) { michael@0: if (wordIs8Bit) { michael@0: wordFlags |= gfxTextRunFactory::TEXT_IS_8BIT; michael@0: } michael@0: } michael@0: gfxShapedWord *sw = GetShapedWord(aContext, michael@0: text + wordStart, length, michael@0: hash, aRunScript, michael@0: appUnitsPerDevUnit, michael@0: wordFlags, tp); michael@0: if (sw) { michael@0: aTextRun->CopyGlyphDataFrom(sw, aRunStart + wordStart); michael@0: } else { michael@0: return false; // failed, presumably out of memory? michael@0: } michael@0: } michael@0: michael@0: if (boundary) { michael@0: // word was terminated by a space: add that to the textrun michael@0: if (!aTextRun->SetSpaceGlyphIfSimple(this, aContext, michael@0: aRunStart + i, ch)) michael@0: { michael@0: static const uint8_t space = ' '; michael@0: gfxShapedWord *sw = michael@0: GetShapedWord(aContext, michael@0: &space, 1, michael@0: HashMix(0, ' '), aRunScript, michael@0: appUnitsPerDevUnit, michael@0: flags | gfxTextRunFactory::TEXT_IS_8BIT, tp); michael@0: if (sw) { michael@0: aTextRun->CopyGlyphDataFrom(sw, aRunStart + i); michael@0: } else { michael@0: return false; michael@0: } michael@0: } michael@0: hash = 0; michael@0: wordStart = i + 1; michael@0: wordIs8Bit = true; michael@0: continue; michael@0: } michael@0: michael@0: if (i == aRunLength) { michael@0: break; michael@0: } michael@0: michael@0: NS_ASSERTION(invalid, michael@0: "how did we get here except via an invalid char?"); michael@0: michael@0: // word was terminated by an invalid char: skip it, michael@0: // unless it's a control char that we want to show as a hexbox, michael@0: // but record where TAB or NEWLINE occur michael@0: if (ch == '\t') { michael@0: aTextRun->SetIsTab(aRunStart + i); michael@0: } else if (ch == '\n') { michael@0: aTextRun->SetIsNewline(aRunStart + i); michael@0: } else if (IsInvalidControlChar(ch) && michael@0: !(aTextRun->GetFlags() & gfxTextRunFactory::TEXT_HIDE_CONTROL_CHARACTERS)) { michael@0: aTextRun->SetMissingGlyph(aRunStart + i, ch, this); michael@0: } michael@0: michael@0: hash = 0; michael@0: wordStart = i + 1; michael@0: wordIs8Bit = true; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: gfxGlyphExtents * michael@0: gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit) { michael@0: uint32_t i, count = mGlyphExtentsArray.Length(); michael@0: for (i = 0; i < count; ++i) { michael@0: if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit) michael@0: return mGlyphExtentsArray[i]; michael@0: } michael@0: gfxGlyphExtents *glyphExtents = new gfxGlyphExtents(aAppUnitsPerDevUnit); michael@0: if (glyphExtents) { michael@0: mGlyphExtentsArray.AppendElement(glyphExtents); michael@0: // Initialize the extents of a space glyph, assuming that spaces don't michael@0: // render anything! michael@0: glyphExtents->SetContainedGlyphWidthAppUnits(GetSpaceGlyph(), 0); michael@0: } michael@0: return glyphExtents; michael@0: } michael@0: michael@0: void michael@0: gfxFont::SetupGlyphExtents(gfxContext *aContext, uint32_t aGlyphID, bool aNeedTight, michael@0: gfxGlyphExtents *aExtents) michael@0: { michael@0: gfxContextMatrixAutoSaveRestore matrixRestore(aContext); michael@0: aContext->IdentityMatrix(); michael@0: michael@0: gfxRect svgBounds; michael@0: if (mFontEntry->TryGetSVGData(this) && mFontEntry->HasSVGGlyph(aGlyphID) && michael@0: mFontEntry->GetSVGGlyphExtents(aContext, aGlyphID, &svgBounds)) { michael@0: gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit(); michael@0: aExtents->SetTightGlyphExtents(aGlyphID, michael@0: gfxRect(svgBounds.x * d2a, michael@0: svgBounds.y * d2a, michael@0: svgBounds.width * d2a, michael@0: svgBounds.height * d2a)); michael@0: return; michael@0: } michael@0: michael@0: cairo_glyph_t glyph; michael@0: glyph.index = aGlyphID; michael@0: glyph.x = 0; michael@0: glyph.y = 0; michael@0: cairo_text_extents_t extents; michael@0: cairo_glyph_extents(aContext->GetCairo(), &glyph, 1, &extents); michael@0: michael@0: const Metrics& fontMetrics = GetMetrics(); michael@0: int32_t appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit(); michael@0: if (!aNeedTight && extents.x_bearing >= 0 && michael@0: extents.y_bearing >= -fontMetrics.maxAscent && michael@0: extents.height + extents.y_bearing <= fontMetrics.maxDescent) { michael@0: uint32_t appUnitsWidth = michael@0: uint32_t(ceil((extents.x_bearing + extents.width)*appUnitsPerDevUnit)); michael@0: if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) { michael@0: aExtents->SetContainedGlyphWidthAppUnits(aGlyphID, uint16_t(appUnitsWidth)); michael@0: return; michael@0: } michael@0: } michael@0: #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS michael@0: if (!aNeedTight) { michael@0: ++gGlyphExtentsSetupFallBackToTight; michael@0: } michael@0: #endif michael@0: michael@0: gfxFloat d2a = appUnitsPerDevUnit; michael@0: gfxRect bounds(extents.x_bearing*d2a, extents.y_bearing*d2a, michael@0: extents.width*d2a, extents.height*d2a); michael@0: aExtents->SetTightGlyphExtents(aGlyphID, bounds); michael@0: } michael@0: michael@0: // Try to initialize font metrics by reading sfnt tables directly; michael@0: // set mIsValid=TRUE and return TRUE on success. michael@0: // Return FALSE if the gfxFontEntry subclass does not michael@0: // implement GetFontTable(), or for non-sfnt fonts where tables are michael@0: // not available. michael@0: // If this returns TRUE without setting the mIsValid flag, then we -did- michael@0: // apparently find an sfnt, but it was too broken to be used. michael@0: bool michael@0: gfxFont::InitMetricsFromSfntTables(Metrics& aMetrics) michael@0: { michael@0: mIsValid = false; // font is NOT valid in case of early return michael@0: michael@0: const uint32_t kHheaTableTag = TRUETYPE_TAG('h','h','e','a'); michael@0: const uint32_t kPostTableTag = TRUETYPE_TAG('p','o','s','t'); michael@0: const uint32_t kOS_2TableTag = TRUETYPE_TAG('O','S','/','2'); michael@0: michael@0: uint32_t len; michael@0: michael@0: if (mFUnitsConvFactor == 0.0) { michael@0: // If the conversion factor from FUnits is not yet set, michael@0: // get the unitsPerEm from the 'head' table via the font entry michael@0: uint16_t unitsPerEm = GetFontEntry()->UnitsPerEm(); michael@0: if (unitsPerEm == gfxFontEntry::kInvalidUPEM) { michael@0: return false; michael@0: } michael@0: mFUnitsConvFactor = mAdjustedSize / unitsPerEm; michael@0: } michael@0: michael@0: // 'hhea' table is required to get vertical extents michael@0: gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag); michael@0: if (!hheaTable) { michael@0: return false; // no 'hhea' table -> not an sfnt michael@0: } michael@0: const HheaTable* hhea = michael@0: reinterpret_cast(hb_blob_get_data(hheaTable, &len)); michael@0: if (len < sizeof(HheaTable)) { michael@0: return false; michael@0: } michael@0: michael@0: #define SET_UNSIGNED(field,src) aMetrics.field = uint16_t(src) * mFUnitsConvFactor michael@0: #define SET_SIGNED(field,src) aMetrics.field = int16_t(src) * mFUnitsConvFactor michael@0: michael@0: SET_UNSIGNED(maxAdvance, hhea->advanceWidthMax); michael@0: SET_SIGNED(maxAscent, hhea->ascender); michael@0: SET_SIGNED(maxDescent, -int16_t(hhea->descender)); michael@0: SET_SIGNED(externalLeading, hhea->lineGap); michael@0: michael@0: // 'post' table is required for underline metrics michael@0: gfxFontEntry::AutoTable postTable(mFontEntry, kPostTableTag); michael@0: if (!postTable) { michael@0: return true; // no 'post' table -> sfnt is not valid michael@0: } michael@0: const PostTable *post = michael@0: reinterpret_cast(hb_blob_get_data(postTable, &len)); michael@0: if (len < offsetof(PostTable, underlineThickness) + sizeof(uint16_t)) { michael@0: return true; // bad post table -> sfnt is not valid michael@0: } michael@0: michael@0: SET_SIGNED(underlineOffset, post->underlinePosition); michael@0: SET_UNSIGNED(underlineSize, post->underlineThickness); michael@0: michael@0: // 'OS/2' table is optional, if not found we'll estimate xHeight michael@0: // and aveCharWidth by measuring glyphs michael@0: gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag); michael@0: if (os2Table) { michael@0: const OS2Table *os2 = michael@0: reinterpret_cast(hb_blob_get_data(os2Table, &len)); michael@0: if (len >= offsetof(OS2Table, sxHeight) + sizeof(int16_t) && michael@0: uint16_t(os2->version) >= 2) { michael@0: // version 2 and later includes the x-height field michael@0: SET_SIGNED(xHeight, os2->sxHeight); michael@0: // Abs because of negative xHeight seen in Kokonor (Tibetan) font michael@0: aMetrics.xHeight = Abs(aMetrics.xHeight); michael@0: } michael@0: // this should always be present in any valid OS/2 of any version michael@0: if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) { michael@0: SET_SIGNED(aveCharWidth, os2->xAvgCharWidth); michael@0: SET_SIGNED(subscriptOffset, os2->ySubscriptYOffset); michael@0: SET_SIGNED(superscriptOffset, os2->ySuperscriptYOffset); michael@0: SET_SIGNED(strikeoutSize, os2->yStrikeoutSize); michael@0: SET_SIGNED(strikeoutOffset, os2->yStrikeoutPosition); michael@0: michael@0: // for fonts with USE_TYPO_METRICS set in the fsSelection field, michael@0: // and for all OpenType math fonts (having a 'MATH' table), michael@0: // let the OS/2 sTypo* metrics override those from the hhea table michael@0: // (see http://www.microsoft.com/typography/otspec/os2.htm#fss) michael@0: const uint16_t kUseTypoMetricsMask = 1 << 7; michael@0: if ((uint16_t(os2->fsSelection) & kUseTypoMetricsMask) || michael@0: mFontEntry->HasFontTable(TRUETYPE_TAG('M','A','T','H'))) { michael@0: SET_SIGNED(maxAscent, os2->sTypoAscender); michael@0: SET_SIGNED(maxDescent, - int16_t(os2->sTypoDescender)); michael@0: SET_SIGNED(externalLeading, os2->sTypoLineGap); michael@0: } michael@0: } michael@0: } michael@0: michael@0: mIsValid = true; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static double michael@0: RoundToNearestMultiple(double aValue, double aFraction) michael@0: { michael@0: return floor(aValue/aFraction + 0.5) * aFraction; michael@0: } michael@0: michael@0: void gfxFont::CalculateDerivedMetrics(Metrics& aMetrics) michael@0: { michael@0: aMetrics.maxAscent = michael@0: ceil(RoundToNearestMultiple(aMetrics.maxAscent, 1/1024.0)); michael@0: aMetrics.maxDescent = michael@0: ceil(RoundToNearestMultiple(aMetrics.maxDescent, 1/1024.0)); michael@0: michael@0: if (aMetrics.xHeight <= 0) { michael@0: // only happens if we couldn't find either font metrics michael@0: // or a char to measure; michael@0: // pick an arbitrary value that's better than zero michael@0: aMetrics.xHeight = aMetrics.maxAscent * DEFAULT_XHEIGHT_FACTOR; michael@0: } michael@0: michael@0: aMetrics.maxHeight = aMetrics.maxAscent + aMetrics.maxDescent; michael@0: michael@0: if (aMetrics.maxHeight - aMetrics.emHeight > 0.0) { michael@0: aMetrics.internalLeading = aMetrics.maxHeight - aMetrics.emHeight; michael@0: } else { michael@0: aMetrics.internalLeading = 0.0; michael@0: } michael@0: michael@0: aMetrics.emAscent = aMetrics.maxAscent * aMetrics.emHeight michael@0: / aMetrics.maxHeight; michael@0: aMetrics.emDescent = aMetrics.emHeight - aMetrics.emAscent; michael@0: michael@0: if (GetFontEntry()->IsFixedPitch()) { michael@0: // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger michael@0: // advance than the average character width... this forces michael@0: // those fonts to be recognized like fixed pitch fonts by layout. michael@0: aMetrics.maxAdvance = aMetrics.aveCharWidth; michael@0: } michael@0: michael@0: if (!aMetrics.subscriptOffset) { michael@0: aMetrics.subscriptOffset = aMetrics.xHeight; michael@0: } michael@0: if (!aMetrics.superscriptOffset) { michael@0: aMetrics.superscriptOffset = aMetrics.xHeight; michael@0: } michael@0: michael@0: if (!aMetrics.strikeoutOffset) { michael@0: aMetrics.strikeoutOffset = aMetrics.xHeight * 0.5; michael@0: } michael@0: if (!aMetrics.strikeoutSize) { michael@0: aMetrics.strikeoutSize = aMetrics.underlineSize; michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxFont::SanitizeMetrics(gfxFont::Metrics *aMetrics, bool aIsBadUnderlineFont) michael@0: { michael@0: // Even if this font size is zero, this font is created with non-zero size. michael@0: // However, for layout and others, we should return the metrics of zero size font. michael@0: if (mStyle.size == 0.0) { michael@0: memset(aMetrics, 0, sizeof(gfxFont::Metrics)); michael@0: return; michael@0: } michael@0: michael@0: // MS (P)Gothic and MS (P)Mincho are not having suitable values in their super script offset. michael@0: // If the values are not suitable, we should use x-height instead of them. michael@0: // See https://bugzilla.mozilla.org/show_bug.cgi?id=353632 michael@0: if (aMetrics->superscriptOffset <= 0 || michael@0: aMetrics->superscriptOffset >= aMetrics->maxAscent) { michael@0: aMetrics->superscriptOffset = aMetrics->xHeight; michael@0: } michael@0: // And also checking the case of sub script offset. The old gfx for win has checked this too. michael@0: if (aMetrics->subscriptOffset <= 0 || michael@0: aMetrics->subscriptOffset >= aMetrics->maxAscent) { michael@0: aMetrics->subscriptOffset = aMetrics->xHeight; michael@0: } michael@0: michael@0: aMetrics->underlineSize = std::max(1.0, aMetrics->underlineSize); michael@0: aMetrics->strikeoutSize = std::max(1.0, aMetrics->strikeoutSize); michael@0: michael@0: aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -1.0); michael@0: michael@0: if (aMetrics->maxAscent < 1.0) { michael@0: // We cannot draw strikeout line and overline in the ascent... michael@0: aMetrics->underlineSize = 0; michael@0: aMetrics->underlineOffset = 0; michael@0: aMetrics->strikeoutSize = 0; michael@0: aMetrics->strikeoutOffset = 0; michael@0: return; michael@0: } michael@0: michael@0: /** michael@0: * Some CJK fonts have bad underline offset. Therefore, if this is such font, michael@0: * we need to lower the underline offset to bottom of *em* descent. michael@0: * However, if this is system font, we should not do this for the rendering compatibility with michael@0: * another application's UI on the platform. michael@0: * XXX Should not use this hack if the font size is too small? michael@0: * Such text cannot be read, this might be used for tight CSS rendering? (E.g., Acid2) michael@0: */ michael@0: if (!mStyle.systemFont && aIsBadUnderlineFont) { michael@0: // First, we need 2 pixels between baseline and underline at least. Because many CJK characters michael@0: // put their glyphs on the baseline, so, 1 pixel is too close for CJK characters. michael@0: aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -2.0); michael@0: michael@0: // Next, we put the underline to bottom of below of the descent space. michael@0: if (aMetrics->internalLeading + aMetrics->externalLeading > aMetrics->underlineSize) { michael@0: aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -aMetrics->emDescent); michael@0: } else { michael@0: aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, michael@0: aMetrics->underlineSize - aMetrics->emDescent); michael@0: } michael@0: } michael@0: // If underline positioned is too far from the text, descent position is preferred so that underline michael@0: // will stay within the boundary. michael@0: else if (aMetrics->underlineSize - aMetrics->underlineOffset > aMetrics->maxDescent) { michael@0: if (aMetrics->underlineSize > aMetrics->maxDescent) michael@0: aMetrics->underlineSize = std::max(aMetrics->maxDescent, 1.0); michael@0: // The max underlineOffset is 1px (the min underlineSize is 1px, and min maxDescent is 0px.) michael@0: aMetrics->underlineOffset = aMetrics->underlineSize - aMetrics->maxDescent; michael@0: } michael@0: michael@0: // If strikeout line is overflowed from the ascent, the line should be resized and moved for michael@0: // that being in the ascent space. michael@0: // Note that the strikeoutOffset is *middle* of the strikeout line position. michael@0: gfxFloat halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5); michael@0: if (halfOfStrikeoutSize + aMetrics->strikeoutOffset > aMetrics->maxAscent) { michael@0: if (aMetrics->strikeoutSize > aMetrics->maxAscent) { michael@0: aMetrics->strikeoutSize = std::max(aMetrics->maxAscent, 1.0); michael@0: halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5); michael@0: } michael@0: gfxFloat ascent = floor(aMetrics->maxAscent + 0.5); michael@0: aMetrics->strikeoutOffset = std::max(halfOfStrikeoutSize, ascent / 2.0); michael@0: } michael@0: michael@0: // If overline is larger than the ascent, the line should be resized. michael@0: if (aMetrics->underlineSize > aMetrics->maxAscent) { michael@0: aMetrics->underlineSize = aMetrics->maxAscent; michael@0: } michael@0: } michael@0: michael@0: gfxFloat michael@0: gfxFont::SynthesizeSpaceWidth(uint32_t aCh) michael@0: { michael@0: // return an appropriate width for various Unicode space characters michael@0: // that we "fake" if they're not actually present in the font; michael@0: // returns negative value if the char is not a known space. michael@0: switch (aCh) { michael@0: case 0x2000: // en quad michael@0: case 0x2002: return GetAdjustedSize() / 2; // en space michael@0: case 0x2001: // em quad michael@0: case 0x2003: return GetAdjustedSize(); // em space michael@0: case 0x2004: return GetAdjustedSize() / 3; // three-per-em space michael@0: case 0x2005: return GetAdjustedSize() / 4; // four-per-em space michael@0: case 0x2006: return GetAdjustedSize() / 6; // six-per-em space michael@0: case 0x2007: return GetMetrics().zeroOrAveCharWidth; // figure space michael@0: case 0x2008: return GetMetrics().spaceWidth; // punctuation space michael@0: case 0x2009: return GetAdjustedSize() / 5; // thin space michael@0: case 0x200a: return GetAdjustedSize() / 10; // hair space michael@0: case 0x202f: return GetAdjustedSize() / 5; // narrow no-break space michael@0: default: return -1.0; michael@0: } michael@0: } michael@0: michael@0: /*static*/ size_t michael@0: gfxFont::WordCacheEntrySizeOfExcludingThis(CacheHashEntry* aHashEntry, michael@0: MallocSizeOf aMallocSizeOf, michael@0: void* aUserArg) michael@0: { michael@0: return aMallocSizeOf(aHashEntry->mShapedWord.get()); michael@0: } michael@0: michael@0: void michael@0: gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, michael@0: FontCacheSizes* aSizes) const michael@0: { michael@0: for (uint32_t i = 0; i < mGlyphExtentsArray.Length(); ++i) { michael@0: aSizes->mFontInstances += michael@0: mGlyphExtentsArray[i]->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: if (mWordCache) { michael@0: aSizes->mShapedWords += michael@0: mWordCache->SizeOfExcludingThis(WordCacheEntrySizeOfExcludingThis, michael@0: aMallocSizeOf); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, michael@0: FontCacheSizes* aSizes) const michael@0: { michael@0: aSizes->mFontInstances += aMallocSizeOf(this); michael@0: AddSizeOfExcludingThis(aMallocSizeOf, aSizes); michael@0: } michael@0: michael@0: void michael@0: gfxFont::AddGlyphChangeObserver(GlyphChangeObserver *aObserver) michael@0: { michael@0: if (!mGlyphChangeObservers) { michael@0: mGlyphChangeObservers = new nsTHashtable >; michael@0: } michael@0: mGlyphChangeObservers->PutEntry(aObserver); michael@0: } michael@0: michael@0: void michael@0: gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver *aObserver) michael@0: { michael@0: NS_ASSERTION(mGlyphChangeObservers, "No observers registered"); michael@0: NS_ASSERTION(mGlyphChangeObservers->Contains(aObserver), "Observer not registered"); michael@0: mGlyphChangeObservers->RemoveEntry(aObserver); michael@0: } michael@0: michael@0: gfxGlyphExtents::~gfxGlyphExtents() michael@0: { michael@0: #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS michael@0: gGlyphExtentsWidthsTotalSize += michael@0: mContainedGlyphWidths.SizeOfExcludingThis(&FontCacheMallocSizeOf); michael@0: gGlyphExtentsCount++; michael@0: #endif michael@0: MOZ_COUNT_DTOR(gfxGlyphExtents); michael@0: } michael@0: michael@0: bool michael@0: gfxGlyphExtents::GetTightGlyphExtentsAppUnits(gfxFont *aFont, michael@0: gfxContext *aContext, uint32_t aGlyphID, gfxRect *aExtents) michael@0: { michael@0: HashEntry *entry = mTightGlyphExtents.GetEntry(aGlyphID); michael@0: if (!entry) { michael@0: if (!aContext) { michael@0: NS_WARNING("Could not get glyph extents (no aContext)"); michael@0: return false; michael@0: } michael@0: michael@0: if (aFont->SetupCairoFont(aContext)) { michael@0: #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS michael@0: ++gGlyphExtentsSetupLazyTight; michael@0: #endif michael@0: aFont->SetupGlyphExtents(aContext, aGlyphID, true, this); michael@0: entry = mTightGlyphExtents.GetEntry(aGlyphID); michael@0: } michael@0: if (!entry) { michael@0: NS_WARNING("Could not get glyph extents"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: *aExtents = gfxRect(entry->x, entry->y, entry->width, entry->height); michael@0: return true; michael@0: } michael@0: michael@0: gfxGlyphExtents::GlyphWidths::~GlyphWidths() michael@0: { michael@0: uint32_t i, count = mBlocks.Length(); michael@0: for (i = 0; i < count; ++i) { michael@0: uintptr_t bits = mBlocks[i]; michael@0: if (bits && !(bits & 0x1)) { michael@0: delete[] reinterpret_cast(bits); michael@0: } michael@0: } michael@0: } michael@0: michael@0: uint32_t michael@0: gfxGlyphExtents::GlyphWidths::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: uint32_t i; michael@0: uint32_t size = mBlocks.SizeOfExcludingThis(aMallocSizeOf); michael@0: for (i = 0; i < mBlocks.Length(); ++i) { michael@0: uintptr_t bits = mBlocks[i]; michael@0: if (bits && !(bits & 0x1)) { michael@0: size += aMallocSizeOf(reinterpret_cast(bits)); michael@0: } michael@0: } michael@0: return size; michael@0: } michael@0: michael@0: void michael@0: gfxGlyphExtents::GlyphWidths::Set(uint32_t aGlyphID, uint16_t aWidth) michael@0: { michael@0: uint32_t block = aGlyphID >> BLOCK_SIZE_BITS; michael@0: uint32_t len = mBlocks.Length(); michael@0: if (block >= len) { michael@0: uintptr_t *elems = mBlocks.AppendElements(block + 1 - len); michael@0: if (!elems) michael@0: return; michael@0: memset(elems, 0, sizeof(uintptr_t)*(block + 1 - len)); michael@0: } michael@0: michael@0: uintptr_t bits = mBlocks[block]; michael@0: uint32_t glyphOffset = aGlyphID & (BLOCK_SIZE - 1); michael@0: if (!bits) { michael@0: mBlocks[block] = MakeSingle(glyphOffset, aWidth); michael@0: return; michael@0: } michael@0: michael@0: uint16_t *newBlock; michael@0: if (bits & 0x1) { michael@0: // Expand the block to a real block. We could avoid this by checking michael@0: // glyphOffset == GetGlyphOffset(bits), but that never happens so don't bother michael@0: newBlock = new uint16_t[BLOCK_SIZE]; michael@0: if (!newBlock) michael@0: return; michael@0: uint32_t i; michael@0: for (i = 0; i < BLOCK_SIZE; ++i) { michael@0: newBlock[i] = INVALID_WIDTH; michael@0: } michael@0: newBlock[GetGlyphOffset(bits)] = GetWidth(bits); michael@0: mBlocks[block] = reinterpret_cast(newBlock); michael@0: } else { michael@0: newBlock = reinterpret_cast(bits); michael@0: } michael@0: newBlock[glyphOffset] = aWidth; michael@0: } michael@0: michael@0: void michael@0: gfxGlyphExtents::SetTightGlyphExtents(uint32_t aGlyphID, const gfxRect& aExtentsAppUnits) michael@0: { michael@0: HashEntry *entry = mTightGlyphExtents.PutEntry(aGlyphID); michael@0: if (!entry) michael@0: return; michael@0: entry->x = aExtentsAppUnits.X(); michael@0: entry->y = aExtentsAppUnits.Y(); michael@0: entry->width = aExtentsAppUnits.Width(); michael@0: entry->height = aExtentsAppUnits.Height(); michael@0: } michael@0: michael@0: size_t michael@0: gfxGlyphExtents::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return mContainedGlyphWidths.SizeOfExcludingThis(aMallocSizeOf) + michael@0: mTightGlyphExtents.SizeOfExcludingThis(nullptr, aMallocSizeOf); michael@0: } michael@0: michael@0: size_t michael@0: gfxGlyphExtents::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: gfxFontGroup::gfxFontGroup(const nsAString& aFamilies, michael@0: const gfxFontStyle *aStyle, michael@0: gfxUserFontSet *aUserFontSet) michael@0: : mFamilies(aFamilies) michael@0: , mStyle(*aStyle) michael@0: , mUnderlineOffset(UNDERLINE_OFFSET_NOT_SET) michael@0: , mHyphenWidth(-1) michael@0: , mUserFontSet(aUserFontSet) michael@0: , mTextPerf(nullptr) michael@0: , mPageLang(gfxPlatform::GetFontPrefLangFor(aStyle->language)) michael@0: , mSkipDrawing(false) michael@0: { michael@0: // We don't use SetUserFontSet() here, as we want to unconditionally call michael@0: // BuildFontList() rather than only do UpdateFontList() if it changed. michael@0: mCurrGeneration = GetGeneration(); michael@0: BuildFontList(); michael@0: } michael@0: michael@0: void michael@0: gfxFontGroup::BuildFontList() michael@0: { michael@0: // "#if" to be removed once all platforms are moved to gfxPlatformFontList interface michael@0: // and subclasses of gfxFontGroup eliminated michael@0: #if defined(XP_MACOSX) || defined(XP_WIN) || defined(ANDROID) michael@0: ForEachFont(FindPlatformFont, this); michael@0: michael@0: if (mFonts.Length() == 0) { michael@0: bool needsBold; michael@0: gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList(); michael@0: gfxFontFamily *defaultFamily = pfl->GetDefaultFont(&mStyle); michael@0: NS_ASSERTION(defaultFamily, michael@0: "invalid default font returned by GetDefaultFont"); michael@0: michael@0: if (defaultFamily) { michael@0: gfxFontEntry *fe = defaultFamily->FindFontForStyle(mStyle, michael@0: needsBold); michael@0: if (fe) { michael@0: nsRefPtr font = fe->FindOrMakeFont(&mStyle, michael@0: needsBold); michael@0: if (font) { michael@0: mFonts.AppendElement(FamilyFace(defaultFamily, font)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (mFonts.Length() == 0) { michael@0: // Try for a "font of last resort...." michael@0: // Because an empty font list would be Really Bad for later code michael@0: // that assumes it will be able to get valid metrics for layout, michael@0: // just look for the first usable font and put in the list. michael@0: // (see bug 554544) michael@0: nsAutoTArray,200> families; michael@0: pfl->GetFontFamilyList(families); michael@0: uint32_t count = families.Length(); michael@0: for (uint32_t i = 0; i < count; ++i) { michael@0: gfxFontEntry *fe = families[i]->FindFontForStyle(mStyle, michael@0: needsBold); michael@0: if (fe) { michael@0: nsRefPtr font = fe->FindOrMakeFont(&mStyle, michael@0: needsBold); michael@0: if (font) { michael@0: mFonts.AppendElement(FamilyFace(families[i], font)); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (mFonts.Length() == 0) { michael@0: // an empty font list at this point is fatal; we're not going to michael@0: // be able to do even the most basic layout operations michael@0: char msg[256]; // CHECK buffer length if revising message below michael@0: sprintf(msg, "unable to find a usable font (%.220s)", michael@0: NS_ConvertUTF16toUTF8(mFamilies).get()); michael@0: NS_RUNTIMEABORT(msg); michael@0: } michael@0: } michael@0: michael@0: if (!mStyle.systemFont) { michael@0: uint32_t count = mFonts.Length(); michael@0: for (uint32_t i = 0; i < count; ++i) { michael@0: gfxFont* font = mFonts[i].Font(); michael@0: if (font->GetFontEntry()->mIsBadUnderlineFont) { michael@0: gfxFloat first = mFonts[0].Font()->GetMetrics().underlineOffset; michael@0: gfxFloat bad = font->GetMetrics().underlineOffset; michael@0: mUnderlineOffset = std::min(first, bad); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: bool michael@0: gfxFontGroup::FindPlatformFont(const nsAString& aName, michael@0: const nsACString& aGenericName, michael@0: bool aUseFontSet, michael@0: void *aClosure) michael@0: { michael@0: gfxFontGroup *fontGroup = static_cast(aClosure); michael@0: const gfxFontStyle *fontStyle = fontGroup->GetStyle(); michael@0: michael@0: bool needsBold; michael@0: gfxFontFamily *family = nullptr; michael@0: gfxFontEntry *fe = nullptr; michael@0: michael@0: if (aUseFontSet) { michael@0: // First, look up in the user font set... michael@0: // If the fontSet matches the family, we must not look for a platform michael@0: // font of the same name, even if we fail to actually get a fontEntry michael@0: // here; we'll fall back to the next name in the CSS font-family list. michael@0: gfxUserFontSet *fs = fontGroup->GetUserFontSet(); michael@0: if (fs) { michael@0: // If the fontSet matches the family, but the font has not yet finished michael@0: // loading (nor has its load timeout fired), the fontGroup should wait michael@0: // for the download, and not actually draw its text yet. michael@0: family = fs->GetFamily(aName); michael@0: if (family) { michael@0: bool waitForUserFont = false; michael@0: fe = fs->FindFontEntry(family, *fontStyle, michael@0: needsBold, waitForUserFont); michael@0: if (!fe && waitForUserFont) { michael@0: fontGroup->mSkipDrawing = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Not known in the user font set ==> check system fonts michael@0: // XXX: Fallback is bad.. michael@0: if (!family) { michael@0: gfxPlatformFontList *fontList = gfxPlatformFontList::PlatformFontList(); michael@0: family = fontList->FindFamily(aName); michael@0: if (family) { michael@0: fe = family->FindFontForStyle(*fontStyle, needsBold); michael@0: } michael@0: } michael@0: michael@0: // add to the font group, unless it's already there michael@0: if (fe && !fontGroup->HasFont(fe)) { michael@0: nsRefPtr font = fe->FindOrMakeFont(fontStyle, needsBold); michael@0: if (font) { michael@0: fontGroup->mFonts.AppendElement(FamilyFace(family, font)); michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: gfxFontGroup::HasFont(const gfxFontEntry *aFontEntry) michael@0: { michael@0: uint32_t count = mFonts.Length(); michael@0: for (uint32_t i = 0; i < count; ++i) { michael@0: if (mFonts[i].Font()->GetFontEntry() == aFontEntry) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: gfxFontGroup::~gfxFontGroup() michael@0: { michael@0: mFonts.Clear(); michael@0: } michael@0: michael@0: gfxFontGroup * michael@0: gfxFontGroup::Copy(const gfxFontStyle *aStyle) michael@0: { michael@0: gfxFontGroup *fg = new gfxFontGroup(mFamilies, aStyle, mUserFontSet); michael@0: fg->SetTextPerfMetrics(mTextPerf); michael@0: return fg; michael@0: } michael@0: michael@0: bool michael@0: gfxFontGroup::IsInvalidChar(uint8_t ch) michael@0: { michael@0: return ((ch & 0x7f) < 0x20 || ch == 0x7f); michael@0: } michael@0: michael@0: bool michael@0: gfxFontGroup::IsInvalidChar(char16_t ch) michael@0: { michael@0: // All printable 7-bit ASCII values are OK michael@0: if (ch >= ' ' && ch < 0x7f) { michael@0: return false; michael@0: } michael@0: // No point in sending non-printing control chars through font shaping michael@0: if (ch <= 0x9f) { michael@0: return true; michael@0: } michael@0: return (((ch & 0xFF00) == 0x2000 /* Unicode control character */ && michael@0: (ch == 0x200B/*ZWSP*/ || ch == 0x2028/*LSEP*/ || ch == 0x2029/*PSEP*/)) || michael@0: IsBidiControl(ch)); michael@0: } michael@0: michael@0: bool michael@0: gfxFontGroup::ForEachFont(FontCreationCallback fc, michael@0: void *closure) michael@0: { michael@0: return ForEachFontInternal(mFamilies, mStyle.language, michael@0: true, true, true, fc, closure); michael@0: } michael@0: michael@0: bool michael@0: gfxFontGroup::ForEachFont(const nsAString& aFamilies, michael@0: nsIAtom *aLanguage, michael@0: FontCreationCallback fc, michael@0: void *closure) michael@0: { michael@0: return ForEachFontInternal(aFamilies, aLanguage, michael@0: false, true, true, fc, closure); michael@0: } michael@0: michael@0: struct ResolveData { michael@0: ResolveData(gfxFontGroup::FontCreationCallback aCallback, michael@0: nsACString& aGenericFamily, michael@0: bool aUseFontSet, michael@0: void *aClosure) : michael@0: mCallback(aCallback), michael@0: mGenericFamily(aGenericFamily), michael@0: mUseFontSet(aUseFontSet), michael@0: mClosure(aClosure) { michael@0: } michael@0: gfxFontGroup::FontCreationCallback mCallback; michael@0: nsCString mGenericFamily; michael@0: bool mUseFontSet; michael@0: void *mClosure; michael@0: }; michael@0: michael@0: bool michael@0: gfxFontGroup::ForEachFontInternal(const nsAString& aFamilies, michael@0: nsIAtom *aLanguage, michael@0: bool aResolveGeneric, michael@0: bool aResolveFontName, michael@0: bool aUseFontSet, michael@0: FontCreationCallback fc, michael@0: void *closure) michael@0: { michael@0: const char16_t kSingleQuote = char16_t('\''); michael@0: const char16_t kDoubleQuote = char16_t('\"'); michael@0: const char16_t kComma = char16_t(','); michael@0: michael@0: nsIAtom *groupAtom = nullptr; michael@0: nsAutoCString groupString; michael@0: if (aLanguage) { michael@0: if (!gLangService) { michael@0: CallGetService(NS_LANGUAGEATOMSERVICE_CONTRACTID, &gLangService); michael@0: } michael@0: if (gLangService) { michael@0: nsresult rv; michael@0: groupAtom = gLangService->GetLanguageGroup(aLanguage, &rv); michael@0: } michael@0: } michael@0: if (!groupAtom) { michael@0: groupAtom = nsGkAtoms::Unicode; michael@0: } michael@0: groupAtom->ToUTF8String(groupString); michael@0: michael@0: nsPromiseFlatString families(aFamilies); michael@0: const char16_t *p, *p_end; michael@0: families.BeginReading(p); michael@0: families.EndReading(p_end); michael@0: nsAutoString family; michael@0: nsAutoCString lcFamily; michael@0: nsAutoString genericFamily; michael@0: michael@0: while (p < p_end) { michael@0: while (nsCRT::IsAsciiSpace(*p) || *p == kComma) michael@0: if (++p == p_end) michael@0: return true; michael@0: michael@0: bool generic; michael@0: if (*p == kSingleQuote || *p == kDoubleQuote) { michael@0: // quoted font family michael@0: char16_t quoteMark = *p; michael@0: if (++p == p_end) michael@0: return true; michael@0: const char16_t *nameStart = p; michael@0: michael@0: // XXX What about CSS character escapes? michael@0: while (*p != quoteMark) michael@0: if (++p == p_end) michael@0: return true; michael@0: michael@0: family = Substring(nameStart, p); michael@0: generic = false; michael@0: genericFamily.SetIsVoid(true); michael@0: michael@0: while (++p != p_end && *p != kComma) michael@0: /* nothing */ ; michael@0: michael@0: } else { michael@0: // unquoted font family michael@0: const char16_t *nameStart = p; michael@0: while (++p != p_end && *p != kComma) michael@0: /* nothing */ ; michael@0: michael@0: family = Substring(nameStart, p); michael@0: family.CompressWhitespace(false, true); michael@0: michael@0: if (aResolveGeneric && michael@0: (family.LowerCaseEqualsLiteral("serif") || michael@0: family.LowerCaseEqualsLiteral("sans-serif") || michael@0: family.LowerCaseEqualsLiteral("monospace") || michael@0: family.LowerCaseEqualsLiteral("cursive") || michael@0: family.LowerCaseEqualsLiteral("fantasy"))) michael@0: { michael@0: generic = true; michael@0: michael@0: ToLowerCase(NS_LossyConvertUTF16toASCII(family), lcFamily); michael@0: michael@0: nsAutoCString prefName("font.name."); michael@0: prefName.Append(lcFamily); michael@0: prefName.AppendLiteral("."); michael@0: prefName.Append(groupString); michael@0: michael@0: nsAdoptingString value = Preferences::GetString(prefName.get()); michael@0: if (value) { michael@0: CopyASCIItoUTF16(lcFamily, genericFamily); michael@0: family = value; michael@0: } michael@0: } else { michael@0: generic = false; michael@0: genericFamily.SetIsVoid(true); michael@0: } michael@0: } michael@0: michael@0: NS_LossyConvertUTF16toASCII gf(genericFamily); michael@0: if (generic) { michael@0: ForEachFontInternal(family, groupAtom, false, michael@0: aResolveFontName, false, michael@0: fc, closure); michael@0: } else if (!family.IsEmpty()) { michael@0: if (aResolveFontName) { michael@0: ResolveData data(fc, gf, aUseFontSet, closure); michael@0: bool aborted = false, needsBold; michael@0: nsresult rv = NS_OK; michael@0: bool foundFamily = false; michael@0: bool waitForUserFont = false; michael@0: gfxFontEntry *fe = nullptr; michael@0: if (aUseFontSet && mUserFontSet) { michael@0: gfxFontFamily *fam = mUserFontSet->GetFamily(family); michael@0: if (fam) { michael@0: fe = mUserFontSet->FindFontEntry(fam, mStyle, michael@0: needsBold, michael@0: waitForUserFont); michael@0: } michael@0: } michael@0: if (fe) { michael@0: gfxFontGroup::FontResolverProc(family, &data); michael@0: } else { michael@0: if (waitForUserFont) { michael@0: mSkipDrawing = true; michael@0: } michael@0: if (!foundFamily) { michael@0: gfxPlatform *pf = gfxPlatform::GetPlatform(); michael@0: // XXX: Fallback is bad michael@0: rv = pf->ResolveFontName(family, michael@0: gfxFontGroup::FontResolverProc, michael@0: &data, aborted); michael@0: } michael@0: } michael@0: if (NS_FAILED(rv) || aborted) michael@0: return false; michael@0: } michael@0: else { michael@0: if (!fc(family, gf, aUseFontSet, closure)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (generic && aResolveGeneric) { michael@0: nsAutoCString prefName("font.name-list."); michael@0: prefName.Append(lcFamily); michael@0: prefName.AppendLiteral("."); michael@0: prefName.Append(groupString); michael@0: nsAdoptingString value = Preferences::GetString(prefName.get()); michael@0: if (value) { michael@0: ForEachFontInternal(value, groupAtom, false, michael@0: aResolveFontName, false, michael@0: fc, closure); michael@0: } michael@0: } michael@0: michael@0: ++p; // may advance past p_end michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: gfxFontGroup::FontResolverProc(const nsAString& aName, void *aClosure) michael@0: { michael@0: ResolveData *data = reinterpret_cast(aClosure); michael@0: return (data->mCallback)(aName, data->mGenericFamily, data->mUseFontSet, michael@0: data->mClosure); michael@0: } michael@0: michael@0: gfxTextRun * michael@0: gfxFontGroup::MakeEmptyTextRun(const Parameters *aParams, uint32_t aFlags) michael@0: { michael@0: aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT; michael@0: return gfxTextRun::Create(aParams, 0, this, aFlags); michael@0: } michael@0: michael@0: gfxTextRun * michael@0: gfxFontGroup::MakeSpaceTextRun(const Parameters *aParams, uint32_t aFlags) michael@0: { michael@0: aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT; michael@0: michael@0: gfxTextRun *textRun = gfxTextRun::Create(aParams, 1, this, aFlags); michael@0: if (!textRun) { michael@0: return nullptr; michael@0: } michael@0: michael@0: gfxFont *font = GetFontAt(0); michael@0: if (MOZ_UNLIKELY(GetStyle()->size == 0)) { michael@0: // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle michael@0: // them, and always create at least size 1 fonts, i.e. they still michael@0: // render something for size 0 fonts. michael@0: textRun->AddGlyphRun(font, gfxTextRange::kFontGroup, 0, false); michael@0: } michael@0: else { michael@0: if (font->GetSpaceGlyph()) { michael@0: // Normally, the font has a cached space glyph, so we can avoid michael@0: // the cost of calling FindFontForChar. michael@0: textRun->SetSpaceGlyph(font, aParams->mContext, 0); michael@0: } else { michael@0: // In case the primary font doesn't have (bug 970891), michael@0: // find one that does. michael@0: uint8_t matchType; michael@0: nsRefPtr spaceFont = michael@0: FindFontForChar(' ', 0, MOZ_SCRIPT_LATIN, nullptr, &matchType); michael@0: if (spaceFont) { michael@0: textRun->SetSpaceGlyph(spaceFont, aParams->mContext, 0); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Note that the gfxGlyphExtents glyph bounds storage for the font will michael@0: // always contain an entry for the font's space glyph, so we don't have michael@0: // to call FetchGlyphExtents here. michael@0: return textRun; michael@0: } michael@0: michael@0: gfxTextRun * michael@0: gfxFontGroup::MakeBlankTextRun(uint32_t aLength, michael@0: const Parameters *aParams, uint32_t aFlags) michael@0: { michael@0: gfxTextRun *textRun = michael@0: gfxTextRun::Create(aParams, aLength, this, aFlags); michael@0: if (!textRun) { michael@0: return nullptr; michael@0: } michael@0: michael@0: textRun->AddGlyphRun(GetFontAt(0), gfxTextRange::kFontGroup, 0, false); michael@0: return textRun; michael@0: } michael@0: michael@0: gfxTextRun * michael@0: gfxFontGroup::MakeHyphenTextRun(gfxContext *aCtx, uint32_t aAppUnitsPerDevUnit) michael@0: { michael@0: // only use U+2010 if it is supported by the first font in the group; michael@0: // it's better to use ASCII '-' from the primary font than to fall back to michael@0: // U+2010 from some other, possibly poorly-matching face michael@0: static const char16_t hyphen = 0x2010; michael@0: gfxFont *font = GetFontAt(0); michael@0: if (font && font->HasCharacter(hyphen)) { michael@0: return MakeTextRun(&hyphen, 1, aCtx, aAppUnitsPerDevUnit, michael@0: gfxFontGroup::TEXT_IS_PERSISTENT); michael@0: } michael@0: michael@0: static const uint8_t dash = '-'; michael@0: return MakeTextRun(&dash, 1, aCtx, aAppUnitsPerDevUnit, michael@0: gfxFontGroup::TEXT_IS_PERSISTENT); michael@0: } michael@0: michael@0: gfxFloat michael@0: gfxFontGroup::GetHyphenWidth(gfxTextRun::PropertyProvider *aProvider) michael@0: { michael@0: if (mHyphenWidth < 0) { michael@0: nsRefPtr ctx(aProvider->GetContext()); michael@0: if (ctx) { michael@0: nsAutoPtr michael@0: hyphRun(MakeHyphenTextRun(ctx, michael@0: aProvider->GetAppUnitsPerDevUnit())); michael@0: mHyphenWidth = hyphRun.get() ? michael@0: hyphRun->GetAdvanceWidth(0, hyphRun->GetLength(), nullptr) : 0; michael@0: } michael@0: } michael@0: return mHyphenWidth; michael@0: } michael@0: michael@0: gfxTextRun * michael@0: gfxFontGroup::MakeTextRun(const uint8_t *aString, uint32_t aLength, michael@0: const Parameters *aParams, uint32_t aFlags) michael@0: { michael@0: if (aLength == 0) { michael@0: return MakeEmptyTextRun(aParams, aFlags); michael@0: } michael@0: if (aLength == 1 && aString[0] == ' ') { michael@0: return MakeSpaceTextRun(aParams, aFlags); michael@0: } michael@0: michael@0: aFlags |= TEXT_IS_8BIT; michael@0: michael@0: if (GetStyle()->size == 0) { michael@0: // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle michael@0: // them, and always create at least size 1 fonts, i.e. they still michael@0: // render something for size 0 fonts. michael@0: return MakeBlankTextRun(aLength, aParams, aFlags); michael@0: } michael@0: michael@0: gfxTextRun *textRun = gfxTextRun::Create(aParams, aLength, michael@0: this, aFlags); michael@0: if (!textRun) { michael@0: return nullptr; michael@0: } michael@0: michael@0: InitTextRun(aParams->mContext, textRun, aString, aLength); michael@0: michael@0: textRun->FetchGlyphExtents(aParams->mContext); michael@0: michael@0: return textRun; michael@0: } michael@0: michael@0: gfxTextRun * michael@0: gfxFontGroup::MakeTextRun(const char16_t *aString, uint32_t aLength, michael@0: const Parameters *aParams, uint32_t aFlags) michael@0: { michael@0: if (aLength == 0) { michael@0: return MakeEmptyTextRun(aParams, aFlags); michael@0: } michael@0: if (aLength == 1 && aString[0] == ' ') { michael@0: return MakeSpaceTextRun(aParams, aFlags); michael@0: } michael@0: if (GetStyle()->size == 0) { michael@0: return MakeBlankTextRun(aLength, aParams, aFlags); michael@0: } michael@0: michael@0: gfxTextRun *textRun = gfxTextRun::Create(aParams, aLength, michael@0: this, aFlags); michael@0: if (!textRun) { michael@0: return nullptr; michael@0: } michael@0: michael@0: InitTextRun(aParams->mContext, textRun, aString, aLength); michael@0: michael@0: textRun->FetchGlyphExtents(aParams->mContext); michael@0: michael@0: return textRun; michael@0: } michael@0: michael@0: template michael@0: void michael@0: gfxFontGroup::InitTextRun(gfxContext *aContext, michael@0: gfxTextRun *aTextRun, michael@0: const T *aString, michael@0: uint32_t aLength) michael@0: { michael@0: NS_ASSERTION(aLength > 0, "don't call InitTextRun for a zero-length run"); michael@0: michael@0: // we need to do numeral processing even on 8-bit text, michael@0: // in case we're converting Western to Hindi/Arabic digits michael@0: int32_t numOption = gfxPlatform::GetPlatform()->GetBidiNumeralOption(); michael@0: nsAutoArrayPtr transformedString; michael@0: if (numOption != IBMBIDI_NUMERAL_NOMINAL) { michael@0: // scan the string for numerals that may need to be transformed; michael@0: // if we find any, we'll make a local copy here and use that for michael@0: // font matching and glyph generation/shaping michael@0: bool prevIsArabic = michael@0: (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR) != 0; michael@0: for (uint32_t i = 0; i < aLength; ++i) { michael@0: char16_t origCh = aString[i]; michael@0: char16_t newCh = HandleNumberInChar(origCh, prevIsArabic, numOption); michael@0: if (newCh != origCh) { michael@0: if (!transformedString) { michael@0: transformedString = new char16_t[aLength]; michael@0: if (sizeof(T) == sizeof(char16_t)) { michael@0: memcpy(transformedString.get(), aString, i * sizeof(char16_t)); michael@0: } else { michael@0: for (uint32_t j = 0; j < i; ++j) { michael@0: transformedString[j] = aString[j]; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (transformedString) { michael@0: transformedString[i] = newCh; michael@0: } michael@0: prevIsArabic = IS_ARABIC_CHAR(newCh); michael@0: } michael@0: } michael@0: michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo *log = (mStyle.systemFont ? michael@0: gfxPlatform::GetLog(eGfxLog_textrunui) : michael@0: gfxPlatform::GetLog(eGfxLog_textrun)); michael@0: #endif michael@0: michael@0: if (sizeof(T) == sizeof(uint8_t) && !transformedString) { michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) { michael@0: nsAutoCString lang; michael@0: mStyle.language->ToUTF8String(lang); michael@0: nsAutoCString str((const char*)aString, aLength); michael@0: PR_LOG(log, PR_LOG_WARNING,\ michael@0: ("(%s) fontgroup: [%s] lang: %s script: %d len %d " michael@0: "weight: %d width: %d style: %s size: %6.2f %d-byte " michael@0: "TEXTRUN [%s] ENDTEXTRUN\n", michael@0: (mStyle.systemFont ? "textrunui" : "textrun"), michael@0: NS_ConvertUTF16toUTF8(mFamilies).get(), michael@0: lang.get(), MOZ_SCRIPT_LATIN, aLength, michael@0: uint32_t(mStyle.weight), uint32_t(mStyle.stretch), michael@0: (mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" : michael@0: (mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" : michael@0: "normal")), michael@0: mStyle.size, michael@0: sizeof(T), michael@0: str.get())); michael@0: } michael@0: #endif michael@0: michael@0: // the text is still purely 8-bit; bypass the script-run itemizer michael@0: // and treat it as a single Latin run michael@0: InitScriptRun(aContext, aTextRun, aString, michael@0: 0, aLength, MOZ_SCRIPT_LATIN); michael@0: } else { michael@0: const char16_t *textPtr; michael@0: if (transformedString) { michael@0: textPtr = transformedString.get(); michael@0: } else { michael@0: // typecast to avoid compilation error for the 8-bit version, michael@0: // even though this is dead code in that case michael@0: textPtr = reinterpret_cast(aString); michael@0: } michael@0: michael@0: // split into script runs so that script can potentially influence michael@0: // the font matching process below michael@0: gfxScriptItemizer scriptRuns(textPtr, aLength); michael@0: michael@0: uint32_t runStart = 0, runLimit = aLength; michael@0: int32_t runScript = MOZ_SCRIPT_LATIN; michael@0: while (scriptRuns.Next(runStart, runLimit, runScript)) { michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) { michael@0: nsAutoCString lang; michael@0: mStyle.language->ToUTF8String(lang); michael@0: uint32_t runLen = runLimit - runStart; michael@0: PR_LOG(log, PR_LOG_WARNING,\ michael@0: ("(%s) fontgroup: [%s] lang: %s script: %d len %d " michael@0: "weight: %d width: %d style: %s size: %6.2f %d-byte " michael@0: "TEXTRUN [%s] ENDTEXTRUN\n", michael@0: (mStyle.systemFont ? "textrunui" : "textrun"), michael@0: NS_ConvertUTF16toUTF8(mFamilies).get(), michael@0: lang.get(), runScript, runLen, michael@0: uint32_t(mStyle.weight), uint32_t(mStyle.stretch), michael@0: (mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" : michael@0: (mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" : michael@0: "normal")), michael@0: mStyle.size, michael@0: sizeof(T), michael@0: NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get())); michael@0: } michael@0: #endif michael@0: michael@0: InitScriptRun(aContext, aTextRun, textPtr, michael@0: runStart, runLimit, runScript); michael@0: } michael@0: } michael@0: michael@0: if (sizeof(T) == sizeof(char16_t) && aLength > 0) { michael@0: gfxTextRun::CompressedGlyph *glyph = aTextRun->GetCharacterGlyphs(); michael@0: if (!glyph->IsSimpleGlyph()) { michael@0: glyph->SetClusterStart(true); michael@0: } michael@0: } michael@0: michael@0: // It's possible for CoreText to omit glyph runs if it decides they contain michael@0: // only invisibles (e.g., U+FEFF, see reftest 474417-1). In this case, we michael@0: // need to eliminate them from the glyph run array to avoid drawing "partial michael@0: // ligatures" with the wrong font. michael@0: // We don't do this during InitScriptRun (or gfxFont::InitTextRun) because michael@0: // it will iterate back over all glyphruns in the textrun, which leads to michael@0: // pathologically-bad perf in the case where a textrun contains many script michael@0: // changes (see bug 680402) - we'd end up re-sanitizing all the earlier runs michael@0: // every time a new script subrun is processed. michael@0: aTextRun->SanitizeGlyphRuns(); michael@0: michael@0: aTextRun->SortGlyphRuns(); michael@0: } michael@0: michael@0: template michael@0: void michael@0: gfxFontGroup::InitScriptRun(gfxContext *aContext, michael@0: gfxTextRun *aTextRun, michael@0: const T *aString, michael@0: uint32_t aScriptRunStart, michael@0: uint32_t aScriptRunEnd, michael@0: int32_t aRunScript) michael@0: { michael@0: NS_ASSERTION(aScriptRunEnd > aScriptRunStart, michael@0: "don't call InitScriptRun for a zero-length run"); michael@0: michael@0: gfxFont *mainFont = GetFontAt(0); michael@0: michael@0: uint32_t runStart = aScriptRunStart; michael@0: nsAutoTArray fontRanges; michael@0: ComputeRanges(fontRanges, aString + aScriptRunStart, michael@0: aScriptRunEnd - aScriptRunStart, aRunScript); michael@0: uint32_t numRanges = fontRanges.Length(); michael@0: michael@0: for (uint32_t r = 0; r < numRanges; r++) { michael@0: const gfxTextRange& range = fontRanges[r]; michael@0: uint32_t matchedLength = range.Length(); michael@0: gfxFont *matchedFont = range.font; michael@0: michael@0: // create the glyph run for this range michael@0: if (matchedFont) { michael@0: aTextRun->AddGlyphRun(matchedFont, range.matchType, michael@0: runStart, (matchedLength > 0)); michael@0: // do glyph layout and record the resulting positioned glyphs michael@0: if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun, aString, michael@0: runStart, matchedLength, michael@0: aRunScript)) { michael@0: // glyph layout failed! treat as missing glyphs michael@0: matchedFont = nullptr; michael@0: } michael@0: } else { michael@0: aTextRun->AddGlyphRun(mainFont, gfxTextRange::kFontGroup, michael@0: runStart, (matchedLength > 0)); michael@0: } michael@0: michael@0: if (!matchedFont) { michael@0: // We need to set cluster boundaries (and mark spaces) so that michael@0: // surrogate pairs, combining characters, etc behave properly, michael@0: // even if we don't have glyphs for them michael@0: aTextRun->SetupClusterBoundaries(runStart, aString + runStart, michael@0: matchedLength); michael@0: michael@0: // various "missing" characters may need special handling, michael@0: // so we check for them here michael@0: uint32_t runLimit = runStart + matchedLength; michael@0: for (uint32_t index = runStart; index < runLimit; index++) { michael@0: T ch = aString[index]; michael@0: michael@0: // tab and newline are not to be displayed as hexboxes, michael@0: // but do need to be recorded in the textrun michael@0: if (ch == '\n') { michael@0: aTextRun->SetIsNewline(index); michael@0: continue; michael@0: } michael@0: if (ch == '\t') { michael@0: aTextRun->SetIsTab(index); michael@0: continue; michael@0: } michael@0: michael@0: // for 16-bit textruns only, check for surrogate pairs and michael@0: // special Unicode spaces; omit these checks in 8-bit runs michael@0: if (sizeof(T) == sizeof(char16_t)) { michael@0: if (NS_IS_HIGH_SURROGATE(ch) && michael@0: index + 1 < aScriptRunEnd && michael@0: NS_IS_LOW_SURROGATE(aString[index + 1])) michael@0: { michael@0: aTextRun->SetMissingGlyph(index, michael@0: SURROGATE_TO_UCS4(ch, michael@0: aString[index + 1]), michael@0: mainFont); michael@0: index++; michael@0: continue; michael@0: } michael@0: michael@0: // check if this is a known Unicode whitespace character that michael@0: // we can render using the space glyph with a custom width michael@0: gfxFloat wid = mainFont->SynthesizeSpaceWidth(ch); michael@0: if (wid >= 0.0) { michael@0: nscoord advance = michael@0: aTextRun->GetAppUnitsPerDevUnit() * floor(wid + 0.5); michael@0: if (gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance)) { michael@0: aTextRun->GetCharacterGlyphs()[index]. michael@0: SetSimpleGlyph(advance, michael@0: mainFont->GetSpaceGlyph()); michael@0: } else { michael@0: gfxTextRun::DetailedGlyph detailedGlyph; michael@0: detailedGlyph.mGlyphID = mainFont->GetSpaceGlyph(); michael@0: detailedGlyph.mAdvance = advance; michael@0: detailedGlyph.mXOffset = detailedGlyph.mYOffset = 0; michael@0: gfxShapedText::CompressedGlyph g; michael@0: g.SetComplex(true, true, 1); michael@0: aTextRun->SetGlyphs(index, michael@0: g, &detailedGlyph); michael@0: } michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: if (IsInvalidChar(ch)) { michael@0: // invalid chars are left as zero-width/invisible michael@0: continue; michael@0: } michael@0: michael@0: // record char code so we can draw a box with the Unicode value michael@0: aTextRun->SetMissingGlyph(index, ch, mainFont); michael@0: } michael@0: } michael@0: michael@0: runStart += matchedLength; michael@0: } michael@0: } michael@0: michael@0: gfxTextRun * michael@0: gfxFontGroup::GetEllipsisTextRun(int32_t aAppUnitsPerDevPixel, michael@0: LazyReferenceContextGetter& aRefContextGetter) michael@0: { michael@0: if (mCachedEllipsisTextRun && michael@0: mCachedEllipsisTextRun->GetAppUnitsPerDevUnit() == aAppUnitsPerDevPixel) { michael@0: return mCachedEllipsisTextRun; michael@0: } michael@0: michael@0: // Use a Unicode ellipsis if the font supports it, michael@0: // otherwise use three ASCII periods as fallback. michael@0: gfxFont* firstFont = GetFontAt(0); michael@0: nsString ellipsis = firstFont->HasCharacter(kEllipsisChar[0]) michael@0: ? nsDependentString(kEllipsisChar, michael@0: ArrayLength(kEllipsisChar) - 1) michael@0: : nsDependentString(kASCIIPeriodsChar, michael@0: ArrayLength(kASCIIPeriodsChar) - 1); michael@0: michael@0: nsRefPtr refCtx = aRefContextGetter.GetRefContext(); michael@0: Parameters params = { michael@0: refCtx, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevPixel michael@0: }; michael@0: gfxTextRun* textRun = michael@0: MakeTextRun(ellipsis.get(), ellipsis.Length(), ¶ms, TEXT_IS_PERSISTENT); michael@0: if (!textRun) { michael@0: return nullptr; michael@0: } michael@0: mCachedEllipsisTextRun = textRun; michael@0: textRun->ReleaseFontGroup(); // don't let the presence of a cached ellipsis michael@0: // textrun prolong the fontgroup's life michael@0: return textRun; michael@0: } michael@0: michael@0: already_AddRefed michael@0: gfxFontGroup::TryAllFamilyMembers(gfxFontFamily* aFamily, uint32_t aCh) michael@0: { michael@0: if (!aFamily->TestCharacterMap(aCh)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Note that we don't need the actual runScript in matchData for michael@0: // gfxFontFamily::SearchAllFontsForChar, it's only used for the michael@0: // system-fallback case. So we can just set it to 0 here. michael@0: GlobalFontMatch matchData(aCh, 0, &mStyle); michael@0: aFamily->SearchAllFontsForChar(&matchData); michael@0: gfxFontEntry *fe = matchData.mBestMatch; michael@0: if (!fe) { michael@0: return nullptr; michael@0: } michael@0: michael@0: bool needsBold = mStyle.weight >= 600 && !fe->IsBold(); michael@0: nsRefPtr font = fe->FindOrMakeFont(&mStyle, needsBold); michael@0: return font.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: gfxFontGroup::FindFontForChar(uint32_t aCh, uint32_t aPrevCh, michael@0: int32_t aRunScript, gfxFont *aPrevMatchedFont, michael@0: uint8_t *aMatchType) michael@0: { michael@0: // To optimize common cases, try the first font in the font-group michael@0: // before going into the more detailed checks below michael@0: uint32_t nextIndex = 0; michael@0: bool isJoinControl = gfxFontUtils::IsJoinControl(aCh); michael@0: bool wasJoinCauser = gfxFontUtils::IsJoinCauser(aPrevCh); michael@0: bool isVarSelector = gfxFontUtils::IsVarSelector(aCh); michael@0: michael@0: if (!isJoinControl && !wasJoinCauser && !isVarSelector) { michael@0: nsRefPtr firstFont = mFonts[0].Font(); michael@0: if (firstFont->HasCharacter(aCh)) { michael@0: *aMatchType = gfxTextRange::kFontGroup; michael@0: return firstFont.forget(); michael@0: } michael@0: // It's possible that another font in the family (e.g. regular face, michael@0: // where the requested style was italic) will support the character michael@0: nsRefPtr font = TryAllFamilyMembers(mFonts[0].Family(), aCh); michael@0: if (font) { michael@0: *aMatchType = gfxTextRange::kFontGroup; michael@0: return font.forget(); michael@0: } michael@0: // we don't need to check the first font again below michael@0: ++nextIndex; michael@0: } michael@0: michael@0: if (aPrevMatchedFont) { michael@0: // Don't switch fonts for control characters, regardless of michael@0: // whether they are present in the current font, as they won't michael@0: // actually be rendered (see bug 716229) michael@0: if (isJoinControl || michael@0: GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_CONTROL) { michael@0: nsRefPtr ret = aPrevMatchedFont; michael@0: return ret.forget(); michael@0: } michael@0: michael@0: // if previous character was a join-causer (ZWJ), michael@0: // use the same font as the previous range if we can michael@0: if (wasJoinCauser) { michael@0: if (aPrevMatchedFont->HasCharacter(aCh)) { michael@0: nsRefPtr ret = aPrevMatchedFont; michael@0: return ret.forget(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // if this character is a variation selector, michael@0: // use the previous font regardless of whether it supports VS or not. michael@0: // otherwise the text run will be divided. michael@0: if (isVarSelector) { michael@0: if (aPrevMatchedFont) { michael@0: nsRefPtr ret = aPrevMatchedFont; michael@0: return ret.forget(); michael@0: } michael@0: // VS alone. it's meaningless to search different fonts michael@0: return nullptr; michael@0: } michael@0: michael@0: // 1. check remaining fonts in the font group michael@0: uint32_t fontListLength = FontListLength(); michael@0: for (uint32_t i = nextIndex; i < fontListLength; i++) { michael@0: nsRefPtr font = mFonts[i].Font(); michael@0: if (font->HasCharacter(aCh)) { michael@0: *aMatchType = gfxTextRange::kFontGroup; michael@0: return font.forget(); michael@0: } michael@0: michael@0: font = TryAllFamilyMembers(mFonts[i].Family(), aCh); michael@0: if (font) { michael@0: *aMatchType = gfxTextRange::kFontGroup; michael@0: return font.forget(); michael@0: } michael@0: } michael@0: michael@0: // if character is in Private Use Area, don't do matching against pref or system fonts michael@0: if ((aCh >= 0xE000 && aCh <= 0xF8FF) || (aCh >= 0xF0000 && aCh <= 0x10FFFD)) michael@0: return nullptr; michael@0: michael@0: // 2. search pref fonts michael@0: nsRefPtr font = WhichPrefFontSupportsChar(aCh); michael@0: if (font) { michael@0: *aMatchType = gfxTextRange::kPrefsFallback; michael@0: return font.forget(); michael@0: } michael@0: michael@0: // 3. use fallback fonts michael@0: // -- before searching for something else check the font used for the previous character michael@0: if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) { michael@0: *aMatchType = gfxTextRange::kSystemFallback; michael@0: nsRefPtr ret = aPrevMatchedFont; michael@0: return ret.forget(); michael@0: } michael@0: michael@0: // never fall back for characters from unknown scripts michael@0: if (aRunScript == HB_SCRIPT_UNKNOWN) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // for known "space" characters, don't do a full system-fallback search; michael@0: // we'll synthesize appropriate-width spaces instead of missing-glyph boxes michael@0: if (GetGeneralCategory(aCh) == michael@0: HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR && michael@0: GetFontAt(0)->SynthesizeSpaceWidth(aCh) >= 0.0) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: // -- otherwise look for other stuff michael@0: *aMatchType = gfxTextRange::kSystemFallback; michael@0: font = WhichSystemFontSupportsChar(aCh, aRunScript); michael@0: return font.forget(); michael@0: } michael@0: michael@0: template michael@0: void gfxFontGroup::ComputeRanges(nsTArray& aRanges, michael@0: const T *aString, uint32_t aLength, michael@0: int32_t aRunScript) michael@0: { michael@0: NS_ASSERTION(aRanges.Length() == 0, "aRanges must be initially empty"); michael@0: NS_ASSERTION(aLength > 0, "don't call ComputeRanges for zero-length text"); michael@0: michael@0: uint32_t prevCh = 0; michael@0: int32_t lastRangeIndex = -1; michael@0: michael@0: // initialize prevFont to the group's primary font, so that this will be michael@0: // used for string-initial control chars, etc rather than risk hitting font michael@0: // fallback for these (bug 716229) michael@0: gfxFont *prevFont = GetFontAt(0); michael@0: michael@0: // if we use the initial value of prevFont, we treat this as a match from michael@0: // the font group; fixes bug 978313 michael@0: uint8_t matchType = gfxTextRange::kFontGroup; michael@0: michael@0: for (uint32_t i = 0; i < aLength; i++) { michael@0: michael@0: const uint32_t origI = i; // save off in case we increase for surrogate michael@0: michael@0: // set up current ch michael@0: uint32_t ch = aString[i]; michael@0: michael@0: // in 16-bit case only, check for surrogate pair michael@0: if (sizeof(T) == sizeof(char16_t)) { michael@0: if ((i + 1 < aLength) && NS_IS_HIGH_SURROGATE(ch) && michael@0: NS_IS_LOW_SURROGATE(aString[i + 1])) { michael@0: i++; michael@0: ch = SURROGATE_TO_UCS4(ch, aString[i]); michael@0: } michael@0: } michael@0: michael@0: if (ch == 0xa0) { michael@0: ch = ' '; michael@0: } michael@0: michael@0: // find the font for this char michael@0: nsRefPtr font = michael@0: FindFontForChar(ch, prevCh, aRunScript, prevFont, &matchType); michael@0: michael@0: #ifndef RELEASE_BUILD michael@0: if (MOZ_UNLIKELY(mTextPerf)) { michael@0: if (matchType == gfxTextRange::kPrefsFallback) { michael@0: mTextPerf->current.fallbackPrefs++; michael@0: } else if (matchType == gfxTextRange::kSystemFallback) { michael@0: mTextPerf->current.fallbackSystem++; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: prevCh = ch; michael@0: michael@0: if (lastRangeIndex == -1) { michael@0: // first char ==> make a new range michael@0: aRanges.AppendElement(gfxTextRange(0, 1, font, matchType)); michael@0: lastRangeIndex++; michael@0: prevFont = font; michael@0: } else { michael@0: // if font has changed, make a new range michael@0: gfxTextRange& prevRange = aRanges[lastRangeIndex]; michael@0: if (prevRange.font != font || prevRange.matchType != matchType) { michael@0: // close out the previous range michael@0: prevRange.end = origI; michael@0: aRanges.AppendElement(gfxTextRange(origI, i + 1, michael@0: font, matchType)); michael@0: lastRangeIndex++; michael@0: michael@0: // update prevFont for the next match, *unless* we switched michael@0: // fonts on a ZWJ, in which case propagating the changed font michael@0: // is probably not a good idea (see bug 619511) michael@0: if (sizeof(T) == sizeof(uint8_t) || michael@0: !gfxFontUtils::IsJoinCauser(ch)) michael@0: { michael@0: prevFont = font; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: aRanges[lastRangeIndex].end = aLength; michael@0: } michael@0: michael@0: gfxUserFontSet* michael@0: gfxFontGroup::GetUserFontSet() michael@0: { michael@0: return mUserFontSet; michael@0: } michael@0: michael@0: void michael@0: gfxFontGroup::SetUserFontSet(gfxUserFontSet *aUserFontSet) michael@0: { michael@0: if (aUserFontSet == mUserFontSet) { michael@0: return; michael@0: } michael@0: mUserFontSet = aUserFontSet; michael@0: mCurrGeneration = GetGeneration() - 1; michael@0: UpdateFontList(); michael@0: } michael@0: michael@0: uint64_t michael@0: gfxFontGroup::GetGeneration() michael@0: { michael@0: if (!mUserFontSet) michael@0: return 0; michael@0: return mUserFontSet->GetGeneration(); michael@0: } michael@0: michael@0: void michael@0: gfxFontGroup::UpdateFontList() michael@0: { michael@0: if (mCurrGeneration != GetGeneration()) { michael@0: // xxx - can probably improve this to detect when all fonts were found, so no need to update list michael@0: mFonts.Clear(); michael@0: mUnderlineOffset = UNDERLINE_OFFSET_NOT_SET; michael@0: mSkipDrawing = false; michael@0: michael@0: // bug 548184 - need to clean up FT2, OS/2 platform code to use BuildFontList michael@0: #if defined(XP_MACOSX) || defined(XP_WIN) || defined(ANDROID) michael@0: BuildFontList(); michael@0: #else michael@0: ForEachFont(FindPlatformFont, this); michael@0: #endif michael@0: mCurrGeneration = GetGeneration(); michael@0: mCachedEllipsisTextRun = nullptr; michael@0: } michael@0: } michael@0: michael@0: struct PrefFontCallbackData { michael@0: PrefFontCallbackData(nsTArray >& aFamiliesArray) michael@0: : mPrefFamilies(aFamiliesArray) michael@0: {} michael@0: michael@0: nsTArray >& mPrefFamilies; michael@0: michael@0: static bool AddFontFamilyEntry(eFontPrefLang aLang, const nsAString& aName, void *aClosure) michael@0: { michael@0: PrefFontCallbackData *prefFontData = static_cast(aClosure); michael@0: michael@0: gfxFontFamily *family = gfxPlatformFontList::PlatformFontList()->FindFamily(aName); michael@0: if (family) { michael@0: prefFontData->mPrefFamilies.AppendElement(family); michael@0: } michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: already_AddRefed michael@0: gfxFontGroup::WhichPrefFontSupportsChar(uint32_t aCh) michael@0: { michael@0: nsRefPtr font; michael@0: michael@0: // get the pref font list if it hasn't been set up already michael@0: uint32_t unicodeRange = FindCharUnicodeRange(aCh); michael@0: eFontPrefLang charLang = gfxPlatform::GetPlatform()->GetFontPrefLangFor(unicodeRange); michael@0: michael@0: // if the last pref font was the first family in the pref list, no need to recheck through a list of families michael@0: if (mLastPrefFont && charLang == mLastPrefLang && michael@0: mLastPrefFirstFont && mLastPrefFont->HasCharacter(aCh)) { michael@0: font = mLastPrefFont; michael@0: return font.forget(); michael@0: } michael@0: michael@0: // based on char lang and page lang, set up list of pref lang fonts to check michael@0: eFontPrefLang prefLangs[kMaxLenPrefLangList]; michael@0: uint32_t i, numLangs = 0; michael@0: michael@0: gfxPlatform::GetPlatform()->GetLangPrefs(prefLangs, numLangs, charLang, mPageLang); michael@0: michael@0: for (i = 0; i < numLangs; i++) { michael@0: nsAutoTArray, 5> families; michael@0: eFontPrefLang currentLang = prefLangs[i]; michael@0: michael@0: gfxPlatformFontList *fontList = gfxPlatformFontList::PlatformFontList(); michael@0: michael@0: // get the pref families for a single pref lang michael@0: if (!fontList->GetPrefFontFamilyEntries(currentLang, &families)) { michael@0: eFontPrefLang prefLangsToSearch[1] = { currentLang }; michael@0: PrefFontCallbackData prefFontData(families); michael@0: gfxPlatform::ForEachPrefFont(prefLangsToSearch, 1, PrefFontCallbackData::AddFontFamilyEntry, michael@0: &prefFontData); michael@0: fontList->SetPrefFontFamilyEntries(currentLang, families); michael@0: } michael@0: michael@0: // find the first pref font that includes the character michael@0: uint32_t j, numPrefs; michael@0: numPrefs = families.Length(); michael@0: for (j = 0; j < numPrefs; j++) { michael@0: // look up the appropriate face michael@0: gfxFontFamily *family = families[j]; michael@0: if (!family) continue; michael@0: michael@0: // if a pref font is used, it's likely to be used again in the same text run. michael@0: // the style doesn't change so the face lookup can be cached rather than calling michael@0: // FindOrMakeFont repeatedly. speeds up FindFontForChar lookup times for subsequent michael@0: // pref font lookups michael@0: if (family == mLastPrefFamily && mLastPrefFont->HasCharacter(aCh)) { michael@0: font = mLastPrefFont; michael@0: return font.forget(); michael@0: } michael@0: michael@0: bool needsBold; michael@0: gfxFontEntry *fe = family->FindFontForStyle(mStyle, needsBold); michael@0: // if ch in cmap, create and return a gfxFont michael@0: if (fe && fe->TestCharacterMap(aCh)) { michael@0: nsRefPtr prefFont = fe->FindOrMakeFont(&mStyle, needsBold); michael@0: if (!prefFont) continue; michael@0: mLastPrefFamily = family; michael@0: mLastPrefFont = prefFont; michael@0: mLastPrefLang = charLang; michael@0: mLastPrefFirstFont = (i == 0 && j == 0); michael@0: return prefFont.forget(); michael@0: } michael@0: michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: already_AddRefed michael@0: gfxFontGroup::WhichSystemFontSupportsChar(uint32_t aCh, int32_t aRunScript) michael@0: { michael@0: gfxFontEntry *fe = michael@0: gfxPlatformFontList::PlatformFontList()-> michael@0: SystemFindFontForChar(aCh, aRunScript, &mStyle); michael@0: if (fe) { michael@0: bool wantBold = mStyle.ComputeWeight() >= 6; michael@0: nsRefPtr font = michael@0: fe->FindOrMakeFont(&mStyle, wantBold && !fe->IsBold()); michael@0: return font.forget(); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: /*static*/ void michael@0: gfxFontGroup::Shutdown() michael@0: { michael@0: NS_IF_RELEASE(gLangService); michael@0: } michael@0: michael@0: nsILanguageAtomService* gfxFontGroup::gLangService = nullptr; michael@0: michael@0: michael@0: #define DEFAULT_PIXEL_FONT_SIZE 16.0f michael@0: michael@0: /*static*/ uint32_t michael@0: gfxFontStyle::ParseFontLanguageOverride(const nsString& aLangTag) michael@0: { michael@0: if (!aLangTag.Length() || aLangTag.Length() > 4) { michael@0: return NO_FONT_LANGUAGE_OVERRIDE; michael@0: } michael@0: uint32_t index, result = 0; michael@0: for (index = 0; index < aLangTag.Length(); ++index) { michael@0: char16_t ch = aLangTag[index]; michael@0: if (!nsCRT::IsAscii(ch)) { // valid tags are pure ASCII michael@0: return NO_FONT_LANGUAGE_OVERRIDE; michael@0: } michael@0: result = (result << 8) + ch; michael@0: } michael@0: while (index++ < 4) { michael@0: result = (result << 8) + 0x20; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: gfxFontStyle::gfxFontStyle() : michael@0: language(nsGkAtoms::x_western), michael@0: size(DEFAULT_PIXEL_FONT_SIZE), sizeAdjust(0.0f), michael@0: languageOverride(NO_FONT_LANGUAGE_OVERRIDE), michael@0: weight(NS_FONT_WEIGHT_NORMAL), stretch(NS_FONT_STRETCH_NORMAL), michael@0: systemFont(true), printerFont(false), useGrayscaleAntialiasing(false), michael@0: style(NS_FONT_STYLE_NORMAL) michael@0: { michael@0: } michael@0: michael@0: gfxFontStyle::gfxFontStyle(uint8_t aStyle, uint16_t aWeight, int16_t aStretch, michael@0: gfxFloat aSize, nsIAtom *aLanguage, michael@0: float aSizeAdjust, bool aSystemFont, michael@0: bool aPrinterFont, michael@0: const nsString& aLanguageOverride): michael@0: language(aLanguage), michael@0: size(aSize), sizeAdjust(aSizeAdjust), michael@0: languageOverride(ParseFontLanguageOverride(aLanguageOverride)), michael@0: weight(aWeight), stretch(aStretch), michael@0: systemFont(aSystemFont), printerFont(aPrinterFont), michael@0: useGrayscaleAntialiasing(false), style(aStyle) michael@0: { michael@0: MOZ_ASSERT(!mozilla::IsNaN(size)); michael@0: MOZ_ASSERT(!mozilla::IsNaN(sizeAdjust)); michael@0: michael@0: if (weight > 900) michael@0: weight = 900; michael@0: if (weight < 100) michael@0: weight = 100; michael@0: michael@0: if (size >= FONT_MAX_SIZE) { michael@0: size = FONT_MAX_SIZE; michael@0: sizeAdjust = 0.0; michael@0: } else if (size < 0.0) { michael@0: NS_WARNING("negative font size"); michael@0: size = 0.0; michael@0: } michael@0: michael@0: if (!language) { michael@0: NS_WARNING("null language"); michael@0: language = nsGkAtoms::x_western; michael@0: } michael@0: } michael@0: michael@0: gfxFontStyle::gfxFontStyle(const gfxFontStyle& aStyle) : michael@0: language(aStyle.language), michael@0: featureValueLookup(aStyle.featureValueLookup), michael@0: size(aStyle.size), sizeAdjust(aStyle.sizeAdjust), michael@0: languageOverride(aStyle.languageOverride), michael@0: weight(aStyle.weight), stretch(aStyle.stretch), michael@0: systemFont(aStyle.systemFont), printerFont(aStyle.printerFont), michael@0: useGrayscaleAntialiasing(aStyle.useGrayscaleAntialiasing), michael@0: style(aStyle.style) michael@0: { michael@0: featureSettings.AppendElements(aStyle.featureSettings); michael@0: alternateValues.AppendElements(aStyle.alternateValues); michael@0: } michael@0: michael@0: int8_t michael@0: gfxFontStyle::ComputeWeight() const michael@0: { michael@0: int8_t baseWeight = (weight + 50) / 100; michael@0: michael@0: if (baseWeight < 0) michael@0: baseWeight = 0; michael@0: if (baseWeight > 9) michael@0: baseWeight = 9; michael@0: michael@0: return baseWeight; michael@0: } michael@0: michael@0: void michael@0: gfxShapedText::SetupClusterBoundaries(uint32_t aOffset, michael@0: const char16_t *aString, michael@0: uint32_t aLength) michael@0: { michael@0: CompressedGlyph *glyphs = GetCharacterGlyphs() + aOffset; michael@0: michael@0: gfxTextRun::CompressedGlyph extendCluster; michael@0: extendCluster.SetComplex(false, true, 0); michael@0: michael@0: ClusterIterator iter(aString, aLength); michael@0: michael@0: // the ClusterIterator won't be able to tell us if the string michael@0: // _begins_ with a cluster-extender, so we handle that here michael@0: if (aLength && IsClusterExtender(*aString)) { michael@0: *glyphs = extendCluster; michael@0: } michael@0: michael@0: while (!iter.AtEnd()) { michael@0: if (*iter == char16_t(' ')) { michael@0: glyphs->SetIsSpace(); michael@0: } michael@0: // advance iter to the next cluster-start (or end of text) michael@0: iter.Next(); michael@0: // step past the first char of the cluster michael@0: aString++; michael@0: glyphs++; michael@0: // mark all the rest as cluster-continuations michael@0: while (aString < iter) { michael@0: *glyphs = extendCluster; michael@0: if (NS_IS_LOW_SURROGATE(*aString)) { michael@0: glyphs->SetIsLowSurrogate(); michael@0: } michael@0: glyphs++; michael@0: aString++; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxShapedText::SetupClusterBoundaries(uint32_t aOffset, michael@0: const uint8_t *aString, michael@0: uint32_t aLength) michael@0: { michael@0: CompressedGlyph *glyphs = GetCharacterGlyphs() + aOffset; michael@0: const uint8_t *limit = aString + aLength; michael@0: michael@0: while (aString < limit) { michael@0: if (*aString == uint8_t(' ')) { michael@0: glyphs->SetIsSpace(); michael@0: } michael@0: aString++; michael@0: glyphs++; michael@0: } michael@0: } michael@0: michael@0: gfxShapedText::DetailedGlyph * michael@0: gfxShapedText::AllocateDetailedGlyphs(uint32_t aIndex, uint32_t aCount) michael@0: { michael@0: NS_ASSERTION(aIndex < GetLength(), "Index out of range"); michael@0: michael@0: if (!mDetailedGlyphs) { michael@0: mDetailedGlyphs = new DetailedGlyphStore(); michael@0: } michael@0: michael@0: DetailedGlyph *details = mDetailedGlyphs->Allocate(aIndex, aCount); michael@0: if (!details) { michael@0: GetCharacterGlyphs()[aIndex].SetMissing(0); michael@0: return nullptr; michael@0: } michael@0: michael@0: return details; michael@0: } michael@0: michael@0: void michael@0: gfxShapedText::SetGlyphs(uint32_t aIndex, CompressedGlyph aGlyph, michael@0: const DetailedGlyph *aGlyphs) michael@0: { michael@0: NS_ASSERTION(!aGlyph.IsSimpleGlyph(), "Simple glyphs not handled here"); michael@0: NS_ASSERTION(aIndex > 0 || aGlyph.IsLigatureGroupStart(), michael@0: "First character can't be a ligature continuation!"); michael@0: michael@0: uint32_t glyphCount = aGlyph.GetGlyphCount(); michael@0: if (glyphCount > 0) { michael@0: DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, glyphCount); michael@0: if (!details) { michael@0: return; michael@0: } michael@0: memcpy(details, aGlyphs, sizeof(DetailedGlyph)*glyphCount); michael@0: } michael@0: GetCharacterGlyphs()[aIndex] = aGlyph; michael@0: } michael@0: michael@0: #define ZWNJ 0x200C michael@0: #define ZWJ 0x200D michael@0: // U+061C ARABIC LETTER MARK is expected to be added to XIDMOD_DEFAULT_IGNORABLE michael@0: // in a future Unicode update. Add it manually for now michael@0: #define ALM 0x061C michael@0: static inline bool michael@0: IsDefaultIgnorable(uint32_t aChar) michael@0: { michael@0: return GetIdentifierModification(aChar) == XIDMOD_DEFAULT_IGNORABLE || michael@0: aChar == ZWNJ || aChar == ZWJ || aChar == ALM; michael@0: } michael@0: michael@0: void michael@0: gfxShapedText::SetMissingGlyph(uint32_t aIndex, uint32_t aChar, gfxFont *aFont) michael@0: { michael@0: uint8_t category = GetGeneralCategory(aChar); michael@0: if (category >= HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK && michael@0: category <= HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) michael@0: { michael@0: GetCharacterGlyphs()[aIndex].SetComplex(false, true, 0); michael@0: } michael@0: michael@0: DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); michael@0: if (!details) { michael@0: return; michael@0: } michael@0: michael@0: details->mGlyphID = aChar; michael@0: if (IsDefaultIgnorable(aChar)) { michael@0: // Setting advance width to zero will prevent drawing the hexbox michael@0: details->mAdvance = 0; michael@0: } else { michael@0: gfxFloat width = michael@0: std::max(aFont->GetMetrics().aveCharWidth, michael@0: gfxFontMissingGlyphs::GetDesiredMinWidth(aChar, michael@0: mAppUnitsPerDevUnit)); michael@0: details->mAdvance = uint32_t(width * mAppUnitsPerDevUnit); michael@0: } michael@0: details->mXOffset = 0; michael@0: details->mYOffset = 0; michael@0: GetCharacterGlyphs()[aIndex].SetMissing(1); michael@0: } michael@0: michael@0: bool michael@0: gfxShapedText::FilterIfIgnorable(uint32_t aIndex, uint32_t aCh) michael@0: { michael@0: if (IsDefaultIgnorable(aCh)) { michael@0: DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); michael@0: if (details) { michael@0: details->mGlyphID = aCh; michael@0: details->mAdvance = 0; michael@0: details->mXOffset = 0; michael@0: details->mYOffset = 0; michael@0: GetCharacterGlyphs()[aIndex].SetMissing(1); michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: gfxShapedText::AdjustAdvancesForSyntheticBold(float aSynBoldOffset, michael@0: uint32_t aOffset, michael@0: uint32_t aLength) michael@0: { michael@0: uint32_t synAppUnitOffset = aSynBoldOffset * mAppUnitsPerDevUnit; michael@0: CompressedGlyph *charGlyphs = GetCharacterGlyphs(); michael@0: for (uint32_t i = aOffset; i < aOffset + aLength; ++i) { michael@0: CompressedGlyph *glyphData = charGlyphs + i; michael@0: if (glyphData->IsSimpleGlyph()) { michael@0: // simple glyphs ==> just add the advance michael@0: int32_t advance = glyphData->GetSimpleAdvance() + synAppUnitOffset; michael@0: if (CompressedGlyph::IsSimpleAdvance(advance)) { michael@0: glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph()); michael@0: } else { michael@0: // rare case, tested by making this the default michael@0: uint32_t glyphIndex = glyphData->GetSimpleGlyph(); michael@0: glyphData->SetComplex(true, true, 1); michael@0: DetailedGlyph detail = {glyphIndex, advance, 0, 0}; michael@0: SetGlyphs(i, *glyphData, &detail); michael@0: } michael@0: } else { michael@0: // complex glyphs ==> add offset at cluster/ligature boundaries michael@0: uint32_t detailedLength = glyphData->GetGlyphCount(); michael@0: if (detailedLength) { michael@0: DetailedGlyph *details = GetDetailedGlyphs(i); michael@0: if (!details) { michael@0: continue; michael@0: } michael@0: if (IsRightToLeft()) { michael@0: details[0].mAdvance += synAppUnitOffset; michael@0: } else { michael@0: details[detailedLength - 1].mAdvance += synAppUnitOffset; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: gfxTextRun::GlyphRunIterator::NextRun() { michael@0: if (mNextIndex >= mTextRun->mGlyphRuns.Length()) michael@0: return false; michael@0: mGlyphRun = &mTextRun->mGlyphRuns[mNextIndex]; michael@0: if (mGlyphRun->mCharacterOffset >= mEndOffset) michael@0: return false; michael@0: michael@0: mStringStart = std::max(mStartOffset, mGlyphRun->mCharacterOffset); michael@0: uint32_t last = mNextIndex + 1 < mTextRun->mGlyphRuns.Length() michael@0: ? mTextRun->mGlyphRuns[mNextIndex + 1].mCharacterOffset : mTextRun->GetLength(); michael@0: mStringEnd = std::min(mEndOffset, last); michael@0: michael@0: ++mNextIndex; michael@0: return true; michael@0: } michael@0: michael@0: #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS michael@0: static void michael@0: AccountStorageForTextRun(gfxTextRun *aTextRun, int32_t aSign) michael@0: { michael@0: // Ignores detailed glyphs... we don't know when those have been constructed michael@0: // Also ignores gfxSkipChars dynamic storage (which won't be anything michael@0: // for preformatted text) michael@0: // Also ignores GlyphRun array, again because it hasn't been constructed michael@0: // by the time this gets called. If there's only one glyphrun that's stored michael@0: // directly in the textrun anyway so no additional overhead. michael@0: uint32_t length = aTextRun->GetLength(); michael@0: int32_t bytes = length * sizeof(gfxTextRun::CompressedGlyph); michael@0: bytes += sizeof(gfxTextRun); michael@0: gTextRunStorage += bytes*aSign; michael@0: gTextRunStorageHighWaterMark = std::max(gTextRunStorageHighWaterMark, gTextRunStorage); michael@0: } michael@0: #endif michael@0: michael@0: // Helper for textRun creation to preallocate storage for glyph records; michael@0: // this function returns a pointer to the newly-allocated glyph storage. michael@0: // Returns nullptr if allocation fails. michael@0: void * michael@0: gfxTextRun::AllocateStorageForTextRun(size_t aSize, uint32_t aLength) michael@0: { michael@0: // Allocate the storage we need, returning nullptr on failure rather than michael@0: // throwing an exception (because web content can create huge runs). michael@0: void *storage = moz_malloc(aSize + aLength * sizeof(CompressedGlyph)); michael@0: if (!storage) { michael@0: NS_WARNING("failed to allocate storage for text run!"); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Initialize the glyph storage (beyond aSize) to zero michael@0: memset(reinterpret_cast(storage) + aSize, 0, michael@0: aLength * sizeof(CompressedGlyph)); michael@0: michael@0: return storage; michael@0: } michael@0: michael@0: gfxTextRun * michael@0: gfxTextRun::Create(const gfxTextRunFactory::Parameters *aParams, michael@0: uint32_t aLength, gfxFontGroup *aFontGroup, uint32_t aFlags) michael@0: { michael@0: void *storage = AllocateStorageForTextRun(sizeof(gfxTextRun), aLength); michael@0: if (!storage) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return new (storage) gfxTextRun(aParams, aLength, aFontGroup, aFlags); michael@0: } michael@0: michael@0: gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters *aParams, michael@0: uint32_t aLength, gfxFontGroup *aFontGroup, uint32_t aFlags) michael@0: : gfxShapedText(aLength, aFlags, aParams->mAppUnitsPerDevUnit) michael@0: , mUserData(aParams->mUserData) michael@0: , mFontGroup(aFontGroup) michael@0: , mReleasedFontGroup(false) michael@0: { michael@0: NS_ASSERTION(mAppUnitsPerDevUnit > 0, "Invalid app unit scale"); michael@0: MOZ_COUNT_CTOR(gfxTextRun); michael@0: NS_ADDREF(mFontGroup); michael@0: michael@0: #ifndef RELEASE_BUILD michael@0: gfxTextPerfMetrics *tp = aFontGroup->GetTextPerfMetrics(); michael@0: if (tp) { michael@0: tp->current.textrunConst++; michael@0: } michael@0: #endif michael@0: michael@0: mCharacterGlyphs = reinterpret_cast(this + 1); michael@0: michael@0: if (aParams->mSkipChars) { michael@0: mSkipChars.TakeFrom(aParams->mSkipChars); michael@0: } michael@0: michael@0: #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS michael@0: AccountStorageForTextRun(this, 1); michael@0: #endif michael@0: michael@0: mSkipDrawing = mFontGroup->ShouldSkipDrawing(); michael@0: } michael@0: michael@0: gfxTextRun::~gfxTextRun() michael@0: { michael@0: #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS michael@0: AccountStorageForTextRun(this, -1); michael@0: #endif michael@0: #ifdef DEBUG michael@0: // Make it easy to detect a dead text run michael@0: mFlags = 0xFFFFFFFF; michael@0: #endif michael@0: michael@0: // The cached ellipsis textrun (if any) in a fontgroup will have already michael@0: // been told to release its reference to the group, so we mustn't do that michael@0: // again here. michael@0: if (!mReleasedFontGroup) { michael@0: #ifndef RELEASE_BUILD michael@0: gfxTextPerfMetrics *tp = mFontGroup->GetTextPerfMetrics(); michael@0: if (tp) { michael@0: tp->current.textrunDestr++; michael@0: } michael@0: #endif michael@0: NS_RELEASE(mFontGroup); michael@0: } michael@0: michael@0: MOZ_COUNT_DTOR(gfxTextRun); michael@0: } michael@0: michael@0: void michael@0: gfxTextRun::ReleaseFontGroup() michael@0: { michael@0: NS_ASSERTION(!mReleasedFontGroup, "doubly released!"); michael@0: NS_RELEASE(mFontGroup); michael@0: mReleasedFontGroup = true; michael@0: } michael@0: michael@0: bool michael@0: gfxTextRun::SetPotentialLineBreaks(uint32_t aStart, uint32_t aLength, michael@0: uint8_t *aBreakBefore, michael@0: gfxContext *aRefContext) michael@0: { michael@0: NS_ASSERTION(aStart + aLength <= GetLength(), "Overflow"); michael@0: michael@0: uint32_t changed = 0; michael@0: uint32_t i; michael@0: CompressedGlyph *charGlyphs = mCharacterGlyphs + aStart; michael@0: for (i = 0; i < aLength; ++i) { michael@0: uint8_t canBreak = aBreakBefore[i]; michael@0: if (canBreak && !charGlyphs[i].IsClusterStart()) { michael@0: // This can happen ... there is no guarantee that our linebreaking rules michael@0: // align with the platform's idea of what constitutes a cluster. michael@0: NS_WARNING("Break suggested inside cluster!"); michael@0: canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE; michael@0: } michael@0: changed |= charGlyphs[i].SetCanBreakBefore(canBreak); michael@0: } michael@0: return changed != 0; michael@0: } michael@0: michael@0: gfxTextRun::LigatureData michael@0: gfxTextRun::ComputeLigatureData(uint32_t aPartStart, uint32_t aPartEnd, michael@0: PropertyProvider *aProvider) michael@0: { michael@0: NS_ASSERTION(aPartStart < aPartEnd, "Computing ligature data for empty range"); michael@0: NS_ASSERTION(aPartEnd <= GetLength(), "Character length overflow"); michael@0: michael@0: LigatureData result; michael@0: CompressedGlyph *charGlyphs = mCharacterGlyphs; michael@0: michael@0: uint32_t i; michael@0: for (i = aPartStart; !charGlyphs[i].IsLigatureGroupStart(); --i) { michael@0: NS_ASSERTION(i > 0, "Ligature at the start of the run??"); michael@0: } michael@0: result.mLigatureStart = i; michael@0: for (i = aPartStart + 1; i < GetLength() && !charGlyphs[i].IsLigatureGroupStart(); ++i) { michael@0: } michael@0: result.mLigatureEnd = i; michael@0: michael@0: int32_t ligatureWidth = michael@0: GetAdvanceForGlyphs(result.mLigatureStart, result.mLigatureEnd); michael@0: // Count the number of started clusters we have seen michael@0: uint32_t totalClusterCount = 0; michael@0: uint32_t partClusterIndex = 0; michael@0: uint32_t partClusterCount = 0; michael@0: for (i = result.mLigatureStart; i < result.mLigatureEnd; ++i) { michael@0: // Treat the first character of the ligature as the start of a michael@0: // cluster for our purposes of allocating ligature width to its michael@0: // characters. michael@0: if (i == result.mLigatureStart || charGlyphs[i].IsClusterStart()) { michael@0: ++totalClusterCount; michael@0: if (i < aPartStart) { michael@0: ++partClusterIndex; michael@0: } else if (i < aPartEnd) { michael@0: ++partClusterCount; michael@0: } michael@0: } michael@0: } michael@0: NS_ASSERTION(totalClusterCount > 0, "Ligature involving no clusters??"); michael@0: result.mPartAdvance = partClusterIndex * (ligatureWidth / totalClusterCount); michael@0: result.mPartWidth = partClusterCount * (ligatureWidth / totalClusterCount); michael@0: michael@0: // Any rounding errors are apportioned to the final part of the ligature, michael@0: // so that measuring all parts of a ligature and summing them is equal to michael@0: // the ligature width. michael@0: if (aPartEnd == result.mLigatureEnd) { michael@0: gfxFloat allParts = totalClusterCount * (ligatureWidth / totalClusterCount); michael@0: result.mPartWidth += ligatureWidth - allParts; michael@0: } michael@0: michael@0: if (partClusterCount == 0) { michael@0: // nothing to draw michael@0: result.mClipBeforePart = result.mClipAfterPart = true; michael@0: } else { michael@0: // Determine whether we should clip before or after this part when michael@0: // drawing its slice of the ligature. michael@0: // We need to clip before the part if any cluster is drawn before michael@0: // this part. michael@0: result.mClipBeforePart = partClusterIndex > 0; michael@0: // We need to clip after the part if any cluster is drawn after michael@0: // this part. michael@0: result.mClipAfterPart = partClusterIndex + partClusterCount < totalClusterCount; michael@0: } michael@0: michael@0: if (aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) { michael@0: gfxFont::Spacing spacing; michael@0: if (aPartStart == result.mLigatureStart) { michael@0: aProvider->GetSpacing(aPartStart, 1, &spacing); michael@0: result.mPartWidth += spacing.mBefore; michael@0: } michael@0: if (aPartEnd == result.mLigatureEnd) { michael@0: aProvider->GetSpacing(aPartEnd - 1, 1, &spacing); michael@0: result.mPartWidth += spacing.mAfter; michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: gfxFloat michael@0: gfxTextRun::ComputePartialLigatureWidth(uint32_t aPartStart, uint32_t aPartEnd, michael@0: PropertyProvider *aProvider) michael@0: { michael@0: if (aPartStart >= aPartEnd) michael@0: return 0; michael@0: LigatureData data = ComputeLigatureData(aPartStart, aPartEnd, aProvider); michael@0: return data.mPartWidth; michael@0: } michael@0: michael@0: int32_t michael@0: gfxTextRun::GetAdvanceForGlyphs(uint32_t aStart, uint32_t aEnd) michael@0: { michael@0: const CompressedGlyph *glyphData = mCharacterGlyphs + aStart; michael@0: int32_t advance = 0; michael@0: uint32_t i; michael@0: for (i = aStart; i < aEnd; ++i, ++glyphData) { michael@0: if (glyphData->IsSimpleGlyph()) { michael@0: advance += glyphData->GetSimpleAdvance(); michael@0: } else { michael@0: uint32_t glyphCount = glyphData->GetGlyphCount(); michael@0: if (glyphCount == 0) { michael@0: continue; michael@0: } michael@0: const DetailedGlyph *details = GetDetailedGlyphs(i); michael@0: if (details) { michael@0: uint32_t j; michael@0: for (j = 0; j < glyphCount; ++j, ++details) { michael@0: advance += details->mAdvance; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return advance; michael@0: } michael@0: michael@0: static void michael@0: GetAdjustedSpacing(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd, michael@0: gfxTextRun::PropertyProvider *aProvider, michael@0: gfxTextRun::PropertyProvider::Spacing *aSpacing) michael@0: { michael@0: if (aStart >= aEnd) michael@0: return; michael@0: michael@0: aProvider->GetSpacing(aStart, aEnd - aStart, aSpacing); michael@0: michael@0: #ifdef DEBUG michael@0: // Check to see if we have spacing inside ligatures michael@0: michael@0: const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs(); michael@0: uint32_t i; michael@0: michael@0: for (i = aStart; i < aEnd; ++i) { michael@0: if (!charGlyphs[i].IsLigatureGroupStart()) { michael@0: NS_ASSERTION(i == aStart || aSpacing[i - aStart].mBefore == 0, michael@0: "Before-spacing inside a ligature!"); michael@0: NS_ASSERTION(i - 1 <= aStart || aSpacing[i - 1 - aStart].mAfter == 0, michael@0: "After-spacing inside a ligature!"); michael@0: } michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: bool michael@0: gfxTextRun::GetAdjustedSpacingArray(uint32_t aStart, uint32_t aEnd, michael@0: PropertyProvider *aProvider, michael@0: uint32_t aSpacingStart, uint32_t aSpacingEnd, michael@0: nsTArray *aSpacing) michael@0: { michael@0: if (!aProvider || !(mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) michael@0: return false; michael@0: if (!aSpacing->AppendElements(aEnd - aStart)) michael@0: return false; michael@0: memset(aSpacing->Elements(), 0, sizeof(gfxFont::Spacing)*(aSpacingStart - aStart)); michael@0: GetAdjustedSpacing(this, aSpacingStart, aSpacingEnd, aProvider, michael@0: aSpacing->Elements() + aSpacingStart - aStart); michael@0: memset(aSpacing->Elements() + aSpacingEnd - aStart, 0, sizeof(gfxFont::Spacing)*(aEnd - aSpacingEnd)); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: gfxTextRun::ShrinkToLigatureBoundaries(uint32_t *aStart, uint32_t *aEnd) michael@0: { michael@0: if (*aStart >= *aEnd) michael@0: return; michael@0: michael@0: CompressedGlyph *charGlyphs = mCharacterGlyphs; michael@0: michael@0: while (*aStart < *aEnd && !charGlyphs[*aStart].IsLigatureGroupStart()) { michael@0: ++(*aStart); michael@0: } michael@0: if (*aEnd < GetLength()) { michael@0: while (*aEnd > *aStart && !charGlyphs[*aEnd].IsLigatureGroupStart()) { michael@0: --(*aEnd); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxTextRun::DrawGlyphs(gfxFont *aFont, gfxContext *aContext, michael@0: DrawMode aDrawMode, gfxPoint *aPt, michael@0: gfxTextContextPaint *aContextPaint, michael@0: uint32_t aStart, uint32_t aEnd, michael@0: PropertyProvider *aProvider, michael@0: uint32_t aSpacingStart, uint32_t aSpacingEnd, michael@0: gfxTextRunDrawCallbacks *aCallbacks) michael@0: { michael@0: nsAutoTArray spacingBuffer; michael@0: bool haveSpacing = GetAdjustedSpacingArray(aStart, aEnd, aProvider, michael@0: aSpacingStart, aSpacingEnd, &spacingBuffer); michael@0: aFont->Draw(this, aStart, aEnd, aContext, aDrawMode, aPt, michael@0: haveSpacing ? spacingBuffer.Elements() : nullptr, aContextPaint, michael@0: aCallbacks); michael@0: } michael@0: michael@0: static void michael@0: ClipPartialLigature(gfxTextRun *aTextRun, gfxFloat *aLeft, gfxFloat *aRight, michael@0: gfxFloat aXOrigin, gfxTextRun::LigatureData *aLigature) michael@0: { michael@0: if (aLigature->mClipBeforePart) { michael@0: if (aTextRun->IsRightToLeft()) { michael@0: *aRight = std::min(*aRight, aXOrigin); michael@0: } else { michael@0: *aLeft = std::max(*aLeft, aXOrigin); michael@0: } michael@0: } michael@0: if (aLigature->mClipAfterPart) { michael@0: gfxFloat endEdge = aXOrigin + aTextRun->GetDirection()*aLigature->mPartWidth; michael@0: if (aTextRun->IsRightToLeft()) { michael@0: *aLeft = std::max(*aLeft, endEdge); michael@0: } else { michael@0: *aRight = std::min(*aRight, endEdge); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxTextRun::DrawPartialLigature(gfxFont *aFont, gfxContext *aCtx, michael@0: uint32_t aStart, uint32_t aEnd, michael@0: gfxPoint *aPt, michael@0: PropertyProvider *aProvider, michael@0: gfxTextRunDrawCallbacks *aCallbacks) michael@0: { michael@0: if (aStart >= aEnd) michael@0: return; michael@0: michael@0: // Draw partial ligature. We hack this by clipping the ligature. michael@0: LigatureData data = ComputeLigatureData(aStart, aEnd, aProvider); michael@0: gfxRect clipExtents = aCtx->GetClipExtents(); michael@0: gfxFloat left = clipExtents.X()*mAppUnitsPerDevUnit; michael@0: gfxFloat right = clipExtents.XMost()*mAppUnitsPerDevUnit; michael@0: ClipPartialLigature(this, &left, &right, aPt->x, &data); michael@0: michael@0: { michael@0: // Need to preserve the path, otherwise this can break canvas text-on-path; michael@0: // in general it seems like a good thing, as naive callers probably won't michael@0: // expect gfxTextRun::Draw to implicitly destroy the current path. michael@0: gfxContextPathAutoSaveRestore savePath(aCtx); michael@0: michael@0: // use division here to ensure that when the rect is aligned on multiples michael@0: // of mAppUnitsPerDevUnit, we clip to true device unit boundaries. michael@0: // Also, make sure we snap the rectangle to device pixels. michael@0: aCtx->Save(); michael@0: aCtx->NewPath(); michael@0: aCtx->Rectangle(gfxRect(left / mAppUnitsPerDevUnit, michael@0: clipExtents.Y(), michael@0: (right - left) / mAppUnitsPerDevUnit, michael@0: clipExtents.Height()), true); michael@0: aCtx->Clip(); michael@0: } michael@0: michael@0: gfxFloat direction = GetDirection(); michael@0: gfxPoint pt(aPt->x - direction*data.mPartAdvance, aPt->y); michael@0: DrawGlyphs(aFont, aCtx, michael@0: aCallbacks ? DrawMode::GLYPH_PATH : DrawMode::GLYPH_FILL, &pt, michael@0: nullptr, data.mLigatureStart, data.mLigatureEnd, aProvider, michael@0: aStart, aEnd, aCallbacks); michael@0: aCtx->Restore(); michael@0: michael@0: aPt->x += direction*data.mPartWidth; michael@0: } michael@0: michael@0: // returns true if a glyph run is using a font with synthetic bolding enabled, false otherwise michael@0: static bool michael@0: HasSyntheticBold(gfxTextRun *aRun, uint32_t aStart, uint32_t aLength) michael@0: { michael@0: gfxTextRun::GlyphRunIterator iter(aRun, aStart, aLength); michael@0: while (iter.NextRun()) { michael@0: gfxFont *font = iter.GetGlyphRun()->mFont; michael@0: if (font && font->IsSyntheticBold()) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // returns true if color is non-opaque (i.e. alpha != 1.0) or completely transparent, false otherwise michael@0: // if true, color is set on output michael@0: static bool michael@0: HasNonOpaqueColor(gfxContext *aContext, gfxRGBA& aCurrentColor) michael@0: { michael@0: if (aContext->GetDeviceColor(aCurrentColor)) { michael@0: if (aCurrentColor.a < 1.0 && aCurrentColor.a > 0.0) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // helper class for double-buffering drawing with non-opaque color michael@0: struct BufferAlphaColor { michael@0: BufferAlphaColor(gfxContext *aContext) michael@0: : mContext(aContext) michael@0: { michael@0: michael@0: } michael@0: michael@0: ~BufferAlphaColor() {} michael@0: michael@0: void PushSolidColor(const gfxRect& aBounds, const gfxRGBA& aAlphaColor, uint32_t appsPerDevUnit) michael@0: { michael@0: mContext->Save(); michael@0: mContext->NewPath(); michael@0: mContext->Rectangle(gfxRect(aBounds.X() / appsPerDevUnit, michael@0: aBounds.Y() / appsPerDevUnit, michael@0: aBounds.Width() / appsPerDevUnit, michael@0: aBounds.Height() / appsPerDevUnit), true); michael@0: mContext->Clip(); michael@0: mContext->SetColor(gfxRGBA(aAlphaColor.r, aAlphaColor.g, aAlphaColor.b)); michael@0: mContext->PushGroup(gfxContentType::COLOR_ALPHA); michael@0: mAlpha = aAlphaColor.a; michael@0: } michael@0: michael@0: void PopAlpha() michael@0: { michael@0: // pop the text, using the color alpha as the opacity michael@0: mContext->PopGroupToSource(); michael@0: mContext->SetOperator(gfxContext::OPERATOR_OVER); michael@0: mContext->Paint(mAlpha); michael@0: mContext->Restore(); michael@0: } michael@0: michael@0: gfxContext *mContext; michael@0: gfxFloat mAlpha; michael@0: }; michael@0: michael@0: void michael@0: gfxTextRun::Draw(gfxContext *aContext, gfxPoint aPt, DrawMode aDrawMode, michael@0: uint32_t aStart, uint32_t aLength, michael@0: PropertyProvider *aProvider, gfxFloat *aAdvanceWidth, michael@0: gfxTextContextPaint *aContextPaint, michael@0: gfxTextRunDrawCallbacks *aCallbacks) michael@0: { michael@0: NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range"); michael@0: NS_ASSERTION(aDrawMode == DrawMode::GLYPH_PATH || !(int(aDrawMode) & int(DrawMode::GLYPH_PATH)), michael@0: "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH"); michael@0: NS_ASSERTION(aDrawMode == DrawMode::GLYPH_PATH || !aCallbacks, "callback must not be specified unless using GLYPH_PATH"); michael@0: michael@0: bool skipDrawing = mSkipDrawing; michael@0: if (aDrawMode == DrawMode::GLYPH_FILL) { michael@0: gfxRGBA currentColor; michael@0: if (aContext->GetDeviceColor(currentColor) && currentColor.a == 0) { michael@0: skipDrawing = true; michael@0: } michael@0: } michael@0: michael@0: gfxFloat direction = GetDirection(); michael@0: michael@0: if (skipDrawing) { michael@0: // We don't need to draw anything; michael@0: // but if the caller wants advance width, we need to compute it here michael@0: if (aAdvanceWidth) { michael@0: gfxTextRun::Metrics metrics = MeasureText(aStart, aLength, michael@0: gfxFont::LOOSE_INK_EXTENTS, michael@0: aContext, aProvider); michael@0: *aAdvanceWidth = metrics.mAdvanceWidth * direction; michael@0: } michael@0: michael@0: // return without drawing michael@0: return; michael@0: } michael@0: michael@0: gfxPoint pt = aPt; michael@0: michael@0: // synthetic bolding draws glyphs twice ==> colors with opacity won't draw correctly unless first drawn without alpha michael@0: BufferAlphaColor syntheticBoldBuffer(aContext); michael@0: gfxRGBA currentColor; michael@0: bool needToRestore = false; michael@0: michael@0: if (aDrawMode == DrawMode::GLYPH_FILL && HasNonOpaqueColor(aContext, currentColor) michael@0: && HasSyntheticBold(this, aStart, aLength)) { michael@0: needToRestore = true; michael@0: // measure text, use the bounding box michael@0: gfxTextRun::Metrics metrics = MeasureText(aStart, aLength, gfxFont::LOOSE_INK_EXTENTS, michael@0: aContext, aProvider); michael@0: metrics.mBoundingBox.MoveBy(aPt); michael@0: syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor, GetAppUnitsPerDevUnit()); michael@0: } michael@0: michael@0: GlyphRunIterator iter(this, aStart, aLength); michael@0: while (iter.NextRun()) { michael@0: gfxFont *font = iter.GetGlyphRun()->mFont; michael@0: uint32_t start = iter.GetStringStart(); michael@0: uint32_t end = iter.GetStringEnd(); michael@0: uint32_t ligatureRunStart = start; michael@0: uint32_t ligatureRunEnd = end; michael@0: ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); michael@0: michael@0: bool drawPartial = aDrawMode == DrawMode::GLYPH_FILL || michael@0: (aDrawMode == DrawMode::GLYPH_PATH && aCallbacks); michael@0: michael@0: if (drawPartial) { michael@0: DrawPartialLigature(font, aContext, start, ligatureRunStart, &pt, michael@0: aProvider, aCallbacks); michael@0: } michael@0: michael@0: DrawGlyphs(font, aContext, aDrawMode, &pt, aContextPaint, ligatureRunStart, michael@0: ligatureRunEnd, aProvider, ligatureRunStart, ligatureRunEnd, michael@0: aCallbacks); michael@0: michael@0: if (drawPartial) { michael@0: DrawPartialLigature(font, aContext, ligatureRunEnd, end, &pt, michael@0: aProvider, aCallbacks); michael@0: } michael@0: } michael@0: michael@0: // composite result when synthetic bolding used michael@0: if (needToRestore) { michael@0: syntheticBoldBuffer.PopAlpha(); michael@0: } michael@0: michael@0: if (aAdvanceWidth) { michael@0: *aAdvanceWidth = (pt.x - aPt.x)*direction; michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxTextRun::AccumulateMetricsForRun(gfxFont *aFont, michael@0: uint32_t aStart, uint32_t aEnd, michael@0: gfxFont::BoundingBoxType aBoundingBoxType, michael@0: gfxContext *aRefContext, michael@0: PropertyProvider *aProvider, michael@0: uint32_t aSpacingStart, uint32_t aSpacingEnd, michael@0: Metrics *aMetrics) michael@0: { michael@0: nsAutoTArray spacingBuffer; michael@0: bool haveSpacing = GetAdjustedSpacingArray(aStart, aEnd, aProvider, michael@0: aSpacingStart, aSpacingEnd, &spacingBuffer); michael@0: Metrics metrics = aFont->Measure(this, aStart, aEnd, aBoundingBoxType, aRefContext, michael@0: haveSpacing ? spacingBuffer.Elements() : nullptr); michael@0: aMetrics->CombineWith(metrics, IsRightToLeft()); michael@0: } michael@0: michael@0: void michael@0: gfxTextRun::AccumulatePartialLigatureMetrics(gfxFont *aFont, michael@0: uint32_t aStart, uint32_t aEnd, michael@0: gfxFont::BoundingBoxType aBoundingBoxType, gfxContext *aRefContext, michael@0: PropertyProvider *aProvider, Metrics *aMetrics) michael@0: { michael@0: if (aStart >= aEnd) michael@0: return; michael@0: michael@0: // Measure partial ligature. We hack this by clipping the metrics in the michael@0: // same way we clip the drawing. michael@0: LigatureData data = ComputeLigatureData(aStart, aEnd, aProvider); michael@0: michael@0: // First measure the complete ligature michael@0: Metrics metrics; michael@0: AccumulateMetricsForRun(aFont, data.mLigatureStart, data.mLigatureEnd, michael@0: aBoundingBoxType, aRefContext, michael@0: aProvider, aStart, aEnd, &metrics); michael@0: michael@0: // Clip the bounding box to the ligature part michael@0: gfxFloat bboxLeft = metrics.mBoundingBox.X(); michael@0: gfxFloat bboxRight = metrics.mBoundingBox.XMost(); michael@0: // Where we are going to start "drawing" relative to our left baseline origin michael@0: gfxFloat origin = IsRightToLeft() ? metrics.mAdvanceWidth - data.mPartAdvance : 0; michael@0: ClipPartialLigature(this, &bboxLeft, &bboxRight, origin, &data); michael@0: metrics.mBoundingBox.x = bboxLeft; michael@0: metrics.mBoundingBox.width = bboxRight - bboxLeft; michael@0: michael@0: // mBoundingBox is now relative to the left baseline origin for the entire michael@0: // ligature. Shift it left. michael@0: metrics.mBoundingBox.x -= michael@0: IsRightToLeft() ? metrics.mAdvanceWidth - (data.mPartAdvance + data.mPartWidth) michael@0: : data.mPartAdvance; michael@0: metrics.mAdvanceWidth = data.mPartWidth; michael@0: michael@0: aMetrics->CombineWith(metrics, IsRightToLeft()); michael@0: } michael@0: michael@0: gfxTextRun::Metrics michael@0: gfxTextRun::MeasureText(uint32_t aStart, uint32_t aLength, michael@0: gfxFont::BoundingBoxType aBoundingBoxType, michael@0: gfxContext *aRefContext, michael@0: PropertyProvider *aProvider) michael@0: { michael@0: NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range"); michael@0: michael@0: Metrics accumulatedMetrics; michael@0: GlyphRunIterator iter(this, aStart, aLength); michael@0: while (iter.NextRun()) { michael@0: gfxFont *font = iter.GetGlyphRun()->mFont; michael@0: uint32_t start = iter.GetStringStart(); michael@0: uint32_t end = iter.GetStringEnd(); michael@0: uint32_t ligatureRunStart = start; michael@0: uint32_t ligatureRunEnd = end; michael@0: ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); michael@0: michael@0: AccumulatePartialLigatureMetrics(font, start, ligatureRunStart, michael@0: aBoundingBoxType, aRefContext, aProvider, &accumulatedMetrics); michael@0: michael@0: // XXX This sucks. We have to get glyph extents just so we can detect michael@0: // glyphs outside the font box, even when aBoundingBoxType is LOOSE, michael@0: // even though in almost all cases we could get correct results just michael@0: // by getting some ascent/descent from the font and using our stored michael@0: // advance widths. michael@0: AccumulateMetricsForRun(font, michael@0: ligatureRunStart, ligatureRunEnd, aBoundingBoxType, michael@0: aRefContext, aProvider, ligatureRunStart, ligatureRunEnd, michael@0: &accumulatedMetrics); michael@0: michael@0: AccumulatePartialLigatureMetrics(font, ligatureRunEnd, end, michael@0: aBoundingBoxType, aRefContext, aProvider, &accumulatedMetrics); michael@0: } michael@0: michael@0: return accumulatedMetrics; michael@0: } michael@0: michael@0: #define MEASUREMENT_BUFFER_SIZE 100 michael@0: michael@0: uint32_t michael@0: gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength, michael@0: bool aLineBreakBefore, gfxFloat aWidth, michael@0: PropertyProvider *aProvider, michael@0: bool aSuppressInitialBreak, michael@0: gfxFloat *aTrimWhitespace, michael@0: Metrics *aMetrics, michael@0: gfxFont::BoundingBoxType aBoundingBoxType, michael@0: gfxContext *aRefContext, michael@0: bool *aUsedHyphenation, michael@0: uint32_t *aLastBreak, michael@0: bool aCanWordWrap, michael@0: gfxBreakPriority *aBreakPriority) michael@0: { michael@0: aMaxLength = std::min(aMaxLength, GetLength() - aStart); michael@0: michael@0: NS_ASSERTION(aStart + aMaxLength <= GetLength(), "Substring out of range"); michael@0: michael@0: uint32_t bufferStart = aStart; michael@0: uint32_t bufferLength = std::min(aMaxLength, MEASUREMENT_BUFFER_SIZE); michael@0: PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE]; michael@0: bool haveSpacing = aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING) != 0; michael@0: if (haveSpacing) { michael@0: GetAdjustedSpacing(this, bufferStart, bufferStart + bufferLength, aProvider, michael@0: spacingBuffer); michael@0: } michael@0: bool hyphenBuffer[MEASUREMENT_BUFFER_SIZE]; michael@0: bool haveHyphenation = aProvider && michael@0: (aProvider->GetHyphensOption() == NS_STYLE_HYPHENS_AUTO || michael@0: (aProvider->GetHyphensOption() == NS_STYLE_HYPHENS_MANUAL && michael@0: (mFlags & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0)); michael@0: if (haveHyphenation) { michael@0: aProvider->GetHyphenationBreaks(bufferStart, bufferLength, michael@0: hyphenBuffer); michael@0: } michael@0: michael@0: gfxFloat width = 0; michael@0: gfxFloat advance = 0; michael@0: // The number of space characters that can be trimmed michael@0: uint32_t trimmableChars = 0; michael@0: // The amount of space removed by ignoring trimmableChars michael@0: gfxFloat trimmableAdvance = 0; michael@0: int32_t lastBreak = -1; michael@0: int32_t lastBreakTrimmableChars = -1; michael@0: gfxFloat lastBreakTrimmableAdvance = -1; michael@0: bool aborted = false; michael@0: uint32_t end = aStart + aMaxLength; michael@0: bool lastBreakUsedHyphenation = false; michael@0: michael@0: uint32_t ligatureRunStart = aStart; michael@0: uint32_t ligatureRunEnd = end; michael@0: ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); michael@0: michael@0: uint32_t i; michael@0: for (i = aStart; i < end; ++i) { michael@0: if (i >= bufferStart + bufferLength) { michael@0: // Fetch more spacing and hyphenation data michael@0: bufferStart = i; michael@0: bufferLength = std::min(aStart + aMaxLength, i + MEASUREMENT_BUFFER_SIZE) - i; michael@0: if (haveSpacing) { michael@0: GetAdjustedSpacing(this, bufferStart, bufferStart + bufferLength, aProvider, michael@0: spacingBuffer); michael@0: } michael@0: if (haveHyphenation) { michael@0: aProvider->GetHyphenationBreaks(bufferStart, bufferLength, michael@0: hyphenBuffer); michael@0: } michael@0: } michael@0: michael@0: // There can't be a word-wrap break opportunity at the beginning of the michael@0: // line: if the width is too small for even one character to fit, it michael@0: // could be the first and last break opportunity on the line, and that michael@0: // would trigger an infinite loop. michael@0: if (!aSuppressInitialBreak || i > aStart) { michael@0: bool atNaturalBreak = mCharacterGlyphs[i].CanBreakBefore() == 1; michael@0: bool atHyphenationBreak = michael@0: !atNaturalBreak && haveHyphenation && hyphenBuffer[i - bufferStart]; michael@0: bool atBreak = atNaturalBreak || atHyphenationBreak; michael@0: bool wordWrapping = michael@0: aCanWordWrap && mCharacterGlyphs[i].IsClusterStart() && michael@0: *aBreakPriority <= gfxBreakPriority::eWordWrapBreak; michael@0: michael@0: if (atBreak || wordWrapping) { michael@0: gfxFloat hyphenatedAdvance = advance; michael@0: if (atHyphenationBreak) { michael@0: hyphenatedAdvance += aProvider->GetHyphenWidth(); michael@0: } michael@0: michael@0: if (lastBreak < 0 || width + hyphenatedAdvance - trimmableAdvance <= aWidth) { michael@0: // We can break here. michael@0: lastBreak = i; michael@0: lastBreakTrimmableChars = trimmableChars; michael@0: lastBreakTrimmableAdvance = trimmableAdvance; michael@0: lastBreakUsedHyphenation = atHyphenationBreak; michael@0: *aBreakPriority = atBreak ? gfxBreakPriority::eNormalBreak michael@0: : gfxBreakPriority::eWordWrapBreak; michael@0: } michael@0: michael@0: width += advance; michael@0: advance = 0; michael@0: if (width - trimmableAdvance > aWidth) { michael@0: // No more text fits. Abort michael@0: aborted = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: gfxFloat charAdvance; michael@0: if (i >= ligatureRunStart && i < ligatureRunEnd) { michael@0: charAdvance = GetAdvanceForGlyphs(i, i + 1); michael@0: if (haveSpacing) { michael@0: PropertyProvider::Spacing *space = &spacingBuffer[i - bufferStart]; michael@0: charAdvance += space->mBefore + space->mAfter; michael@0: } michael@0: } else { michael@0: charAdvance = ComputePartialLigatureWidth(i, i + 1, aProvider); michael@0: } michael@0: michael@0: advance += charAdvance; michael@0: if (aTrimWhitespace) { michael@0: if (mCharacterGlyphs[i].CharIsSpace()) { michael@0: ++trimmableChars; michael@0: trimmableAdvance += charAdvance; michael@0: } else { michael@0: trimmableAdvance = 0; michael@0: trimmableChars = 0; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!aborted) { michael@0: width += advance; michael@0: } michael@0: michael@0: // There are three possibilities: michael@0: // 1) all the text fit (width <= aWidth) michael@0: // 2) some of the text fit up to a break opportunity (width > aWidth && lastBreak >= 0) michael@0: // 3) none of the text fits before a break opportunity (width > aWidth && lastBreak < 0) michael@0: uint32_t charsFit; michael@0: bool usedHyphenation = false; michael@0: if (width - trimmableAdvance <= aWidth) { michael@0: charsFit = aMaxLength; michael@0: } else if (lastBreak >= 0) { michael@0: charsFit = lastBreak - aStart; michael@0: trimmableChars = lastBreakTrimmableChars; michael@0: trimmableAdvance = lastBreakTrimmableAdvance; michael@0: usedHyphenation = lastBreakUsedHyphenation; michael@0: } else { michael@0: charsFit = aMaxLength; michael@0: } michael@0: michael@0: if (aMetrics) { michael@0: *aMetrics = MeasureText(aStart, charsFit - trimmableChars, michael@0: aBoundingBoxType, aRefContext, aProvider); michael@0: } michael@0: if (aTrimWhitespace) { michael@0: *aTrimWhitespace = trimmableAdvance; michael@0: } michael@0: if (aUsedHyphenation) { michael@0: *aUsedHyphenation = usedHyphenation; michael@0: } michael@0: if (aLastBreak && charsFit == aMaxLength) { michael@0: if (lastBreak < 0) { michael@0: *aLastBreak = UINT32_MAX; michael@0: } else { michael@0: *aLastBreak = lastBreak - aStart; michael@0: } michael@0: } michael@0: michael@0: return charsFit; michael@0: } michael@0: michael@0: gfxFloat michael@0: gfxTextRun::GetAdvanceWidth(uint32_t aStart, uint32_t aLength, michael@0: PropertyProvider *aProvider) michael@0: { michael@0: NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range"); michael@0: michael@0: uint32_t ligatureRunStart = aStart; michael@0: uint32_t ligatureRunEnd = aStart + aLength; michael@0: ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); michael@0: michael@0: gfxFloat result = ComputePartialLigatureWidth(aStart, ligatureRunStart, aProvider) + michael@0: ComputePartialLigatureWidth(ligatureRunEnd, aStart + aLength, aProvider); michael@0: michael@0: // Account for all remaining spacing here. This is more efficient than michael@0: // processing it along with the glyphs. michael@0: if (aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) { michael@0: uint32_t i; michael@0: nsAutoTArray spacingBuffer; michael@0: if (spacingBuffer.AppendElements(aLength)) { michael@0: GetAdjustedSpacing(this, ligatureRunStart, ligatureRunEnd, aProvider, michael@0: spacingBuffer.Elements()); michael@0: for (i = 0; i < ligatureRunEnd - ligatureRunStart; ++i) { michael@0: PropertyProvider::Spacing *space = &spacingBuffer[i]; michael@0: result += space->mBefore + space->mAfter; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return result + GetAdvanceForGlyphs(ligatureRunStart, ligatureRunEnd); michael@0: } michael@0: michael@0: bool michael@0: gfxTextRun::SetLineBreaks(uint32_t aStart, uint32_t aLength, michael@0: bool aLineBreakBefore, bool aLineBreakAfter, michael@0: gfxFloat *aAdvanceWidthDelta, michael@0: gfxContext *aRefContext) michael@0: { michael@0: // Do nothing because our shaping does not currently take linebreaks into michael@0: // account. There is no change in advance width. michael@0: if (aAdvanceWidthDelta) { michael@0: *aAdvanceWidthDelta = 0; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: uint32_t michael@0: gfxTextRun::FindFirstGlyphRunContaining(uint32_t aOffset) michael@0: { michael@0: NS_ASSERTION(aOffset <= GetLength(), "Bad offset looking for glyphrun"); michael@0: NS_ASSERTION(GetLength() == 0 || mGlyphRuns.Length() > 0, michael@0: "non-empty text but no glyph runs present!"); michael@0: if (aOffset == GetLength()) michael@0: return mGlyphRuns.Length(); michael@0: uint32_t start = 0; michael@0: uint32_t end = mGlyphRuns.Length(); michael@0: while (end - start > 1) { michael@0: uint32_t mid = (start + end)/2; michael@0: if (mGlyphRuns[mid].mCharacterOffset <= aOffset) { michael@0: start = mid; michael@0: } else { michael@0: end = mid; michael@0: } michael@0: } michael@0: NS_ASSERTION(mGlyphRuns[start].mCharacterOffset <= aOffset, michael@0: "Hmm, something went wrong, aOffset should have been found"); michael@0: return start; michael@0: } michael@0: michael@0: nsresult michael@0: gfxTextRun::AddGlyphRun(gfxFont *aFont, uint8_t aMatchType, michael@0: uint32_t aUTF16Offset, bool aForceNewRun) michael@0: { michael@0: NS_ASSERTION(aFont, "adding glyph run for null font!"); michael@0: if (!aFont) { michael@0: return NS_OK; michael@0: } michael@0: uint32_t numGlyphRuns = mGlyphRuns.Length(); michael@0: if (!aForceNewRun && numGlyphRuns > 0) { michael@0: GlyphRun *lastGlyphRun = &mGlyphRuns[numGlyphRuns - 1]; michael@0: michael@0: NS_ASSERTION(lastGlyphRun->mCharacterOffset <= aUTF16Offset, michael@0: "Glyph runs out of order (and run not forced)"); michael@0: michael@0: // Don't append a run if the font is already the one we want michael@0: if (lastGlyphRun->mFont == aFont && michael@0: lastGlyphRun->mMatchType == aMatchType) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If the offset has not changed, avoid leaving a zero-length run michael@0: // by overwriting the last entry instead of appending... michael@0: if (lastGlyphRun->mCharacterOffset == aUTF16Offset) { michael@0: michael@0: // ...except that if the run before the last entry had the same michael@0: // font as the new one wants, merge with it instead of creating michael@0: // adjacent runs with the same font michael@0: if (numGlyphRuns > 1 && michael@0: mGlyphRuns[numGlyphRuns - 2].mFont == aFont && michael@0: mGlyphRuns[numGlyphRuns - 2].mMatchType == aMatchType) michael@0: { michael@0: mGlyphRuns.TruncateLength(numGlyphRuns - 1); michael@0: return NS_OK; michael@0: } michael@0: michael@0: lastGlyphRun->mFont = aFont; michael@0: lastGlyphRun->mMatchType = aMatchType; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(aForceNewRun || numGlyphRuns > 0 || aUTF16Offset == 0, michael@0: "First run doesn't cover the first character (and run not forced)?"); michael@0: michael@0: GlyphRun *glyphRun = mGlyphRuns.AppendElement(); michael@0: if (!glyphRun) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: glyphRun->mFont = aFont; michael@0: glyphRun->mCharacterOffset = aUTF16Offset; michael@0: glyphRun->mMatchType = aMatchType; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: gfxTextRun::SortGlyphRuns() michael@0: { michael@0: if (mGlyphRuns.Length() <= 1) michael@0: return; michael@0: michael@0: nsTArray runs(mGlyphRuns); michael@0: GlyphRunOffsetComparator comp; michael@0: runs.Sort(comp); michael@0: michael@0: // Now copy back, coalescing adjacent glyph runs that have the same font michael@0: mGlyphRuns.Clear(); michael@0: uint32_t i, count = runs.Length(); michael@0: for (i = 0; i < count; ++i) { michael@0: // a GlyphRun with the same font as the previous GlyphRun can just michael@0: // be skipped; the last GlyphRun will cover its character range. michael@0: if (i == 0 || runs[i].mFont != runs[i - 1].mFont) { michael@0: mGlyphRuns.AppendElement(runs[i]); michael@0: // If two fonts have the same character offset, Sort() will have michael@0: // randomized the order. michael@0: NS_ASSERTION(i == 0 || michael@0: runs[i].mCharacterOffset != michael@0: runs[i - 1].mCharacterOffset, michael@0: "Two fonts for the same run, glyph indices may not match the font"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Note that SanitizeGlyphRuns scans all glyph runs in the textrun; michael@0: // therefore we only call it once, at the end of textrun construction, michael@0: // NOT incrementally as each glyph run is added (bug 680402). michael@0: void michael@0: gfxTextRun::SanitizeGlyphRuns() michael@0: { michael@0: if (mGlyphRuns.Length() <= 1) michael@0: return; michael@0: michael@0: // If any glyph run starts with ligature-continuation characters, we need to advance it michael@0: // to the first "real" character to avoid drawing partial ligature glyphs from wrong font michael@0: // (seen with U+FEFF in reftest 474417-1, as Core Text eliminates the glyph, which makes michael@0: // it appear as if a ligature has been formed) michael@0: int32_t i, lastRunIndex = mGlyphRuns.Length() - 1; michael@0: const CompressedGlyph *charGlyphs = mCharacterGlyphs; michael@0: for (i = lastRunIndex; i >= 0; --i) { michael@0: GlyphRun& run = mGlyphRuns[i]; michael@0: while (charGlyphs[run.mCharacterOffset].IsLigatureContinuation() && michael@0: run.mCharacterOffset < GetLength()) { michael@0: run.mCharacterOffset++; michael@0: } michael@0: // if the run has become empty, eliminate it michael@0: if ((i < lastRunIndex && michael@0: run.mCharacterOffset >= mGlyphRuns[i+1].mCharacterOffset) || michael@0: (i == lastRunIndex && run.mCharacterOffset == GetLength())) { michael@0: mGlyphRuns.RemoveElementAt(i); michael@0: --lastRunIndex; michael@0: } michael@0: } michael@0: } michael@0: michael@0: uint32_t michael@0: gfxTextRun::CountMissingGlyphs() michael@0: { michael@0: uint32_t i; michael@0: uint32_t count = 0; michael@0: for (i = 0; i < GetLength(); ++i) { michael@0: if (mCharacterGlyphs[i].IsMissing()) { michael@0: ++count; michael@0: } michael@0: } michael@0: return count; michael@0: } michael@0: michael@0: gfxTextRun::DetailedGlyph * michael@0: gfxTextRun::AllocateDetailedGlyphs(uint32_t aIndex, uint32_t aCount) michael@0: { michael@0: NS_ASSERTION(aIndex < GetLength(), "Index out of range"); michael@0: michael@0: if (!mDetailedGlyphs) { michael@0: mDetailedGlyphs = new DetailedGlyphStore(); michael@0: } michael@0: michael@0: DetailedGlyph *details = mDetailedGlyphs->Allocate(aIndex, aCount); michael@0: if (!details) { michael@0: mCharacterGlyphs[aIndex].SetMissing(0); michael@0: return nullptr; michael@0: } michael@0: michael@0: return details; michael@0: } michael@0: michael@0: void michael@0: gfxTextRun::CopyGlyphDataFrom(gfxShapedWord *aShapedWord, uint32_t aOffset) michael@0: { michael@0: uint32_t wordLen = aShapedWord->GetLength(); michael@0: NS_ASSERTION(aOffset + wordLen <= GetLength(), michael@0: "word overruns end of textrun!"); michael@0: michael@0: CompressedGlyph *charGlyphs = GetCharacterGlyphs(); michael@0: const CompressedGlyph *wordGlyphs = aShapedWord->GetCharacterGlyphs(); michael@0: if (aShapedWord->HasDetailedGlyphs()) { michael@0: for (uint32_t i = 0; i < wordLen; ++i, ++aOffset) { michael@0: const CompressedGlyph& g = wordGlyphs[i]; michael@0: if (g.IsSimpleGlyph()) { michael@0: charGlyphs[aOffset] = g; michael@0: } else { michael@0: const DetailedGlyph *details = michael@0: g.GetGlyphCount() > 0 ? michael@0: aShapedWord->GetDetailedGlyphs(i) : nullptr; michael@0: SetGlyphs(aOffset, g, details); michael@0: } michael@0: } michael@0: } else { michael@0: memcpy(charGlyphs + aOffset, wordGlyphs, michael@0: wordLen * sizeof(CompressedGlyph)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxTextRun::CopyGlyphDataFrom(gfxTextRun *aSource, uint32_t aStart, michael@0: uint32_t aLength, uint32_t aDest) michael@0: { michael@0: NS_ASSERTION(aStart + aLength <= aSource->GetLength(), michael@0: "Source substring out of range"); michael@0: NS_ASSERTION(aDest + aLength <= GetLength(), michael@0: "Destination substring out of range"); michael@0: michael@0: if (aSource->mSkipDrawing) { michael@0: mSkipDrawing = true; michael@0: } michael@0: michael@0: // Copy base glyph data, and DetailedGlyph data where present michael@0: const CompressedGlyph *srcGlyphs = aSource->mCharacterGlyphs + aStart; michael@0: CompressedGlyph *dstGlyphs = mCharacterGlyphs + aDest; michael@0: for (uint32_t i = 0; i < aLength; ++i) { michael@0: CompressedGlyph g = srcGlyphs[i]; michael@0: g.SetCanBreakBefore(!g.IsClusterStart() ? michael@0: CompressedGlyph::FLAG_BREAK_TYPE_NONE : michael@0: dstGlyphs[i].CanBreakBefore()); michael@0: if (!g.IsSimpleGlyph()) { michael@0: uint32_t count = g.GetGlyphCount(); michael@0: if (count > 0) { michael@0: DetailedGlyph *dst = AllocateDetailedGlyphs(i + aDest, count); michael@0: if (dst) { michael@0: DetailedGlyph *src = aSource->GetDetailedGlyphs(i + aStart); michael@0: if (src) { michael@0: ::memcpy(dst, src, count * sizeof(DetailedGlyph)); michael@0: } else { michael@0: g.SetMissing(0); michael@0: } michael@0: } else { michael@0: g.SetMissing(0); michael@0: } michael@0: } michael@0: } michael@0: dstGlyphs[i] = g; michael@0: } michael@0: michael@0: // Copy glyph runs michael@0: GlyphRunIterator iter(aSource, aStart, aLength); michael@0: #ifdef DEBUG michael@0: gfxFont *lastFont = nullptr; michael@0: #endif michael@0: while (iter.NextRun()) { michael@0: gfxFont *font = iter.GetGlyphRun()->mFont; michael@0: NS_ASSERTION(font != lastFont, "Glyphruns not coalesced?"); michael@0: #ifdef DEBUG michael@0: lastFont = font; michael@0: uint32_t end = iter.GetStringEnd(); michael@0: #endif michael@0: uint32_t start = iter.GetStringStart(); michael@0: michael@0: // These used to be NS_ASSERTION()s, but WARNING is more appropriate. michael@0: // Although it's unusual (and not desirable), it's possible for us to assign michael@0: // different fonts to a base character and a following diacritic. michael@0: // Example on OSX 10.5/10.6 with default fonts installed: michael@0: // data:text/html,

michael@0: // &%23x043E;&%23x0486;&%23x20;&%23x043E;&%23x0486; michael@0: // This means the rendering of the cluster will probably not be very good, michael@0: // but it's the best we can do for now if the specified font only covered the michael@0: // initial base character and not its applied marks. michael@0: NS_WARN_IF_FALSE(aSource->IsClusterStart(start), michael@0: "Started font run in the middle of a cluster"); michael@0: NS_WARN_IF_FALSE(end == aSource->GetLength() || aSource->IsClusterStart(end), michael@0: "Ended font run in the middle of a cluster"); michael@0: michael@0: nsresult rv = AddGlyphRun(font, iter.GetGlyphRun()->mMatchType, michael@0: start - aStart + aDest, false); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxTextRun::SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, michael@0: uint32_t aCharIndex) michael@0: { michael@0: if (SetSpaceGlyphIfSimple(aFont, aContext, aCharIndex, ' ')) { michael@0: return; michael@0: } michael@0: michael@0: aFont->InitWordCache(); michael@0: static const uint8_t space = ' '; michael@0: gfxShapedWord *sw = aFont->GetShapedWord(aContext, michael@0: &space, 1, michael@0: HashMix(0, ' '), michael@0: MOZ_SCRIPT_LATIN, michael@0: mAppUnitsPerDevUnit, michael@0: gfxTextRunFactory::TEXT_IS_8BIT | michael@0: gfxTextRunFactory::TEXT_IS_ASCII | michael@0: gfxTextRunFactory::TEXT_IS_PERSISTENT, michael@0: nullptr); michael@0: if (sw) { michael@0: AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false); michael@0: CopyGlyphDataFrom(sw, aCharIndex); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: gfxTextRun::SetSpaceGlyphIfSimple(gfxFont *aFont, gfxContext *aContext, michael@0: uint32_t aCharIndex, char16_t aSpaceChar) michael@0: { michael@0: uint32_t spaceGlyph = aFont->GetSpaceGlyph(); michael@0: if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) { michael@0: return false; michael@0: } michael@0: michael@0: uint32_t spaceWidthAppUnits = michael@0: NS_lroundf(aFont->GetMetrics().spaceWidth * mAppUnitsPerDevUnit); michael@0: if (!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) { michael@0: return false; michael@0: } michael@0: michael@0: AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false); michael@0: CompressedGlyph g; michael@0: g.SetSimpleGlyph(spaceWidthAppUnits, spaceGlyph); michael@0: if (aSpaceChar == ' ') { michael@0: g.SetIsSpace(); michael@0: } michael@0: GetCharacterGlyphs()[aCharIndex] = g; michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: gfxTextRun::FetchGlyphExtents(gfxContext *aRefContext) michael@0: { michael@0: bool needsGlyphExtents = NeedsGlyphExtents(this); michael@0: if (!needsGlyphExtents && !mDetailedGlyphs) michael@0: return; michael@0: michael@0: uint32_t i, runCount = mGlyphRuns.Length(); michael@0: CompressedGlyph *charGlyphs = mCharacterGlyphs; michael@0: for (i = 0; i < runCount; ++i) { michael@0: const GlyphRun& run = mGlyphRuns[i]; michael@0: gfxFont *font = run.mFont; michael@0: uint32_t start = run.mCharacterOffset; michael@0: uint32_t end = i + 1 < runCount ? michael@0: mGlyphRuns[i + 1].mCharacterOffset : GetLength(); michael@0: bool fontIsSetup = false; michael@0: uint32_t j; michael@0: gfxGlyphExtents *extents = font->GetOrCreateGlyphExtents(mAppUnitsPerDevUnit); michael@0: michael@0: for (j = start; j < end; ++j) { michael@0: const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[j]; michael@0: if (glyphData->IsSimpleGlyph()) { michael@0: // If we're in speed mode, don't set up glyph extents here; we'll michael@0: // just return "optimistic" glyph bounds later michael@0: if (needsGlyphExtents) { michael@0: uint32_t glyphIndex = glyphData->GetSimpleGlyph(); michael@0: if (!extents->IsGlyphKnown(glyphIndex)) { michael@0: if (!fontIsSetup) { michael@0: if (!font->SetupCairoFont(aRefContext)) { michael@0: NS_WARNING("failed to set up font for glyph extents"); michael@0: break; michael@0: } michael@0: fontIsSetup = true; michael@0: } michael@0: #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS michael@0: ++gGlyphExtentsSetupEagerSimple; michael@0: #endif michael@0: font->SetupGlyphExtents(aRefContext, glyphIndex, false, extents); michael@0: } michael@0: } michael@0: } else if (!glyphData->IsMissing()) { michael@0: uint32_t glyphCount = glyphData->GetGlyphCount(); michael@0: if (glyphCount == 0) { michael@0: continue; michael@0: } michael@0: const gfxTextRun::DetailedGlyph *details = GetDetailedGlyphs(j); michael@0: if (!details) { michael@0: continue; michael@0: } michael@0: for (uint32_t k = 0; k < glyphCount; ++k, ++details) { michael@0: uint32_t glyphIndex = details->mGlyphID; michael@0: if (!extents->IsGlyphKnownWithTightExtents(glyphIndex)) { michael@0: if (!fontIsSetup) { michael@0: if (!font->SetupCairoFont(aRefContext)) { michael@0: NS_WARNING("failed to set up font for glyph extents"); michael@0: break; michael@0: } michael@0: fontIsSetup = true; michael@0: } michael@0: #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS michael@0: ++gGlyphExtentsSetupEagerTight; michael@0: #endif michael@0: font->SetupGlyphExtents(aRefContext, glyphIndex, true, extents); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: gfxTextRun::ClusterIterator::ClusterIterator(gfxTextRun *aTextRun) michael@0: : mTextRun(aTextRun), mCurrentChar(uint32_t(-1)) michael@0: { michael@0: } michael@0: michael@0: void michael@0: gfxTextRun::ClusterIterator::Reset() michael@0: { michael@0: mCurrentChar = uint32_t(-1); michael@0: } michael@0: michael@0: bool michael@0: gfxTextRun::ClusterIterator::NextCluster() michael@0: { michael@0: uint32_t len = mTextRun->GetLength(); michael@0: while (++mCurrentChar < len) { michael@0: if (mTextRun->IsClusterStart(mCurrentChar)) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: mCurrentChar = uint32_t(-1); michael@0: return false; michael@0: } michael@0: michael@0: uint32_t michael@0: gfxTextRun::ClusterIterator::ClusterLength() const michael@0: { michael@0: if (mCurrentChar == uint32_t(-1)) { michael@0: return 0; michael@0: } michael@0: michael@0: uint32_t i = mCurrentChar, michael@0: len = mTextRun->GetLength(); michael@0: while (++i < len) { michael@0: if (mTextRun->IsClusterStart(i)) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return i - mCurrentChar; michael@0: } michael@0: michael@0: gfxFloat michael@0: gfxTextRun::ClusterIterator::ClusterAdvance(PropertyProvider *aProvider) const michael@0: { michael@0: if (mCurrentChar == uint32_t(-1)) { michael@0: return 0; michael@0: } michael@0: michael@0: return mTextRun->GetAdvanceWidth(mCurrentChar, ClusterLength(), aProvider); michael@0: } michael@0: michael@0: size_t michael@0: gfxTextRun::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) michael@0: { michael@0: // The second arg is how much gfxTextRun::AllocateStorage would have michael@0: // allocated. michael@0: size_t total = mGlyphRuns.SizeOfExcludingThis(aMallocSizeOf); michael@0: michael@0: if (mDetailedGlyphs) { michael@0: total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: return total; michael@0: } michael@0: michael@0: size_t michael@0: gfxTextRun::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: michael@0: #ifdef DEBUG michael@0: void michael@0: gfxTextRun::Dump(FILE* aOutput) { michael@0: if (!aOutput) { michael@0: aOutput = stdout; michael@0: } michael@0: michael@0: uint32_t i; michael@0: fputc('[', aOutput); michael@0: for (i = 0; i < mGlyphRuns.Length(); ++i) { michael@0: if (i > 0) { michael@0: fputc(',', aOutput); michael@0: } michael@0: gfxFont* font = mGlyphRuns[i].mFont; michael@0: const gfxFontStyle* style = font->GetStyle(); michael@0: NS_ConvertUTF16toUTF8 fontName(font->GetName()); michael@0: nsAutoCString lang; michael@0: style->language->ToUTF8String(lang); michael@0: fprintf(aOutput, "%d: %s %f/%d/%d/%s", mGlyphRuns[i].mCharacterOffset, michael@0: fontName.get(), style->size, michael@0: style->weight, style->style, lang.get()); michael@0: } michael@0: fputc(']', aOutput); michael@0: } michael@0: #endif