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

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