summaryrefslogtreecommitdiffstats
path: root/gfx/thebes/gfxFontEntry.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /gfx/thebes/gfxFontEntry.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/thebes/gfxFontEntry.cpp')
-rw-r--r--gfx/thebes/gfxFontEntry.cpp2201
1 files changed, 2201 insertions, 0 deletions
diff --git a/gfx/thebes/gfxFontEntry.cpp b/gfx/thebes/gfxFontEntry.cpp
new file mode 100644
index 0000000000..a9fe04125c
--- /dev/null
+++ b/gfx/thebes/gfxFontEntry.cpp
@@ -0,0 +1,2201 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "gfxFontEntry.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/MathAlgorithms.h"
+
+#include "mozilla/Logging.h"
+
+#include "gfxTextRun.h"
+#include "gfxPlatform.h"
+#include "nsGkAtoms.h"
+
+#include "gfxTypes.h"
+#include "gfxContext.h"
+#include "gfxFontConstants.h"
+#include "gfxGraphiteShaper.h"
+#include "gfxHarfBuzzShaper.h"
+#include "gfxUserFontSet.h"
+#include "gfxPlatformFontList.h"
+#include "nsUnicodeProperties.h"
+#include "nsMathUtils.h"
+#include "nsBidiUtils.h"
+#include "nsStyleConsts.h"
+#include "mozilla/AppUnits.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/Telemetry.h"
+#include "gfxSVGGlyphs.h"
+#include "gfx2DGlue.h"
+
+#include "harfbuzz/hb.h"
+#include "harfbuzz/hb-ot.h"
+#include "graphite2/Font.h"
+
+#include "ThebesRLBox.h"
+
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::unicode;
+
+void gfxCharacterMap::NotifyMaybeReleased(gfxCharacterMap* aCmap) {
+ // Tell gfxPlatformFontList that a charmap's refcount was decremented,
+ // so it should check whether the object is to be deleted.
+ gfxPlatformFontList::PlatformFontList()->MaybeRemoveCmap(aCmap);
+}
+
+gfxFontEntry::gfxFontEntry(const nsACString& aName, bool aIsStandardFace)
+ : mName(aName),
+ mLock("gfxFontEntry lock"),
+ mFeatureInfoLock("gfxFontEntry featureInfo mutex"),
+ mFixedPitch(false),
+ mIsBadUnderlineFont(false),
+ mIsUserFontContainer(false),
+ mIsDataUserFont(false),
+ mIsLocalUserFont(false),
+ mStandardFace(aIsStandardFace),
+ mIgnoreGDEF(false),
+ mIgnoreGSUB(false),
+ mSkipDefaultFeatureSpaceCheck(false),
+ mSVGInitialized(false),
+ mHasCmapTable(false),
+ mGrFaceInitialized(false),
+ mCheckedForColorGlyph(false),
+ mCheckedForVariationAxes(false),
+ mSpaceGlyphIsInvisible(LazyFlag::Uninitialized),
+ mHasGraphiteTables(LazyFlag::Uninitialized),
+ mHasGraphiteSpaceContextuals(LazyFlag::Uninitialized),
+ mHasColorBitmapTable(LazyFlag::Uninitialized),
+ mHasSpaceFeatures(SpaceFeatures::Uninitialized) {
+ mTrakTable.exchange(kTrakTableUninitialized);
+ memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures));
+ memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures));
+}
+
+gfxFontEntry::~gfxFontEntry() {
+ // Should not be dropped by stylo
+ MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal());
+
+ hb_blob_destroy(mCOLR.exchange(nullptr));
+ hb_blob_destroy(mCPAL.exchange(nullptr));
+
+ if (TrakTableInitialized()) {
+ // Only if it was initialized, so that we don't try to call hb_blob_destroy
+ // on the kTrakTableUninitialized flag value!
+ hb_blob_destroy(mTrakTable.exchange(nullptr));
+ }
+
+ // For downloaded fonts, we need to tell the user font cache that this
+ // entry is being deleted.
+ if (mIsDataUserFont) {
+ gfxUserFontSet::UserFontCache::ForgetFont(this);
+ }
+
+ if (mFeatureInputs) {
+ for (auto iter = mFeatureInputs->Iter(); !iter.Done(); iter.Next()) {
+ hb_set_t*& set = iter.Data();
+ hb_set_destroy(set);
+ }
+ }
+
+ delete mFontTableCache.exchange(nullptr);
+ delete mSVGGlyphs.exchange(nullptr);
+ delete[] mUVSData.exchange(nullptr);
+
+ gfxCharacterMap* cmap = mCharacterMap.exchange(nullptr);
+ NS_IF_RELEASE(cmap);
+
+ // 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);
+}
+
+// Only used during initialization, before any other thread has a chance to see
+// the entry, so locking not required.
+void gfxFontEntry::InitializeFrom(fontlist::Face* aFace,
+ const fontlist::Family* aFamily) {
+ mShmemFace = aFace;
+ mShmemFamily = aFamily;
+ mStyleRange = aFace->mStyle;
+ mWeightRange = aFace->mWeight;
+ mStretchRange = aFace->mStretch;
+ mFixedPitch = aFace->mFixedPitch;
+ mIsBadUnderlineFont = aFamily->IsBadUnderlineFamily();
+ auto* list = gfxPlatformFontList::PlatformFontList()->SharedFontList();
+ mFamilyName = aFamily->DisplayName().AsString(list);
+ mHasCmapTable = TrySetShmemCharacterMap();
+}
+
+bool gfxFontEntry::TrySetShmemCharacterMap() {
+ MOZ_ASSERT(mShmemFace);
+ auto list = gfxPlatformFontList::PlatformFontList()->SharedFontList();
+ auto* shmemCmap = mShmemFace->mCharacterMap.ToPtr<const SharedBitSet>(list);
+ mShmemCharacterMap.exchange(shmemCmap);
+ return shmemCmap != nullptr;
+}
+
+bool gfxFontEntry::TestCharacterMap(uint32_t aCh) {
+ if (!mCharacterMap && !mShmemCharacterMap) {
+ ReadCMAP();
+ MOZ_ASSERT(mCharacterMap || mShmemCharacterMap,
+ "failed to initialize character map");
+ }
+ return mShmemCharacterMap ? GetShmemCharacterMap()->test(aCh)
+ : GetCharacterMap()->test(aCh);
+}
+
+void gfxFontEntry::EnsureUVSMapInitialized() {
+ // mUVSOffset will not be initialized
+ // until cmap is initialized.
+ if (!mCharacterMap && !mShmemCharacterMap) {
+ ReadCMAP();
+ NS_ASSERTION(mCharacterMap || mShmemCharacterMap,
+ "failed to initialize character map");
+ }
+
+ if (!mUVSOffset) {
+ return;
+ }
+
+ if (!mUVSData) {
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ const uint32_t kCmapTag = TRUETYPE_TAG('c', 'm', 'a', 'p');
+ AutoTable cmapTable(this, kCmapTag);
+ if (cmapTable) {
+ const uint8_t* uvsData = nullptr;
+ unsigned int cmapLen;
+ const char* cmapData = hb_blob_get_data(cmapTable, &cmapLen);
+ rv = gfxFontUtils::ReadCMAPTableFormat14(
+ (const uint8_t*)cmapData + mUVSOffset, cmapLen - mUVSOffset, uvsData);
+ if (NS_SUCCEEDED(rv)) {
+ if (!mUVSData.compareExchange(nullptr, uvsData)) {
+ delete uvsData;
+ }
+ }
+ }
+ if (NS_FAILED(rv)) {
+ mUVSOffset = 0; // don't try to read the table again
+ }
+ }
+}
+
+uint16_t gfxFontEntry::GetUVSGlyph(uint32_t aCh, uint32_t aVS) {
+ EnsureUVSMapInitialized();
+
+ if (const auto* uvsData = GetUVSData()) {
+ return gfxFontUtils::MapUVSToGlyphFormat14(uvsData, aCh, aVS);
+ }
+
+ return 0;
+}
+
+bool gfxFontEntry::SupportsScriptInGSUB(const hb_tag_t* aScriptTags,
+ uint32_t aNumTags) {
+ auto face(GetHBFace());
+
+ unsigned int index;
+ hb_tag_t chosenScript;
+ bool found = hb_ot_layout_table_select_script(
+ face, TRUETYPE_TAG('G', 'S', 'U', 'B'), aNumTags, aScriptTags, &index,
+ &chosenScript);
+
+ return found && chosenScript != TRUETYPE_TAG('D', 'F', 'L', 'T');
+}
+
+nsresult gfxFontEntry::ReadCMAP(FontInfoData* aFontInfoData) {
+ MOZ_ASSERT(false, "using default no-op implementation of ReadCMAP");
+ RefPtr<gfxCharacterMap> cmap = new gfxCharacterMap();
+ if (mCharacterMap.compareExchange(nullptr, cmap.get())) {
+ Unused << cmap.forget(); // mCharacterMap now owns the reference
+ }
+ return NS_OK;
+}
+
+nsCString gfxFontEntry::RealFaceName() {
+ AutoTable nameTable(this, TRUETYPE_TAG('n', 'a', 'm', 'e'));
+ if (nameTable) {
+ nsAutoCString name;
+ nsresult rv = gfxFontUtils::GetFullNameFromTable(nameTable, name);
+ if (NS_SUCCEEDED(rv)) {
+ return std::move(name);
+ }
+ }
+ return Name();
+}
+
+already_AddRefed<gfxFont> gfxFontEntry::FindOrMakeFont(
+ const gfxFontStyle* aStyle, gfxCharacterMap* aUnicodeRangeMap) {
+ RefPtr<gfxFont> font =
+ gfxFontCache::GetCache()->Lookup(this, aStyle, aUnicodeRangeMap);
+ if (font) {
+ return font.forget();
+ }
+
+ gfxFont* newFont = CreateFontInstance(aStyle);
+ if (!newFont) {
+ return nullptr;
+ }
+ if (!newFont->Valid()) {
+ newFont->Destroy();
+ return nullptr;
+ }
+ newFont->SetUnicodeRangeMap(aUnicodeRangeMap);
+ return gfxFontCache::GetCache()->MaybeInsert(newFont);
+}
+
+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<const HeadTable*>(hb_blob_get_data(headTable, &len));
+ if (len >= sizeof(HeadTable)) {
+ mUnitsPerEm = head->unitsPerEm;
+ if (int16_t(head->xMax) > int16_t(head->xMin) &&
+ int16_t(head->yMax) > int16_t(head->yMin)) {
+ mXMin = head->xMin;
+ mYMin = head->yMin;
+ mXMax = head->xMax;
+ mYMax = head->yMax;
+ }
+ }
+ }
+
+ // 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 GetSVGGlyphs()->HasSVGGlyph(aGlyphId);
+}
+
+bool gfxFontEntry::GetSVGGlyphExtents(DrawTarget* aDrawTarget,
+ uint32_t aGlyphId, gfxFloat aSize,
+ gfxRect* aResult) {
+ MOZ_ASSERT(mSVGInitialized,
+ "SVG data has not yet been loaded. TryGetSVGData() first.");
+ MOZ_ASSERT(mUnitsPerEm >= kMinUPEM && mUnitsPerEm <= kMaxUPEM,
+ "font has invalid unitsPerEm");
+
+ gfxMatrix svgToApp(aSize / mUnitsPerEm, 0, 0, aSize / mUnitsPerEm, 0, 0);
+ return GetSVGGlyphs()->GetGlyphExtents(aGlyphId, svgToApp, aResult);
+}
+
+void gfxFontEntry::RenderSVGGlyph(gfxContext* aContext, uint32_t aGlyphId,
+ SVGContextPaint* aContextPaint) {
+ NS_ASSERTION(mSVGInitialized,
+ "SVG data has not yet been loaded. TryGetSVGData() first.");
+ GetSVGGlyphs()->RenderGlyph(aContext, aGlyphId, aContextPaint);
+}
+
+bool gfxFontEntry::TryGetSVGData(const gfxFont* aFont) {
+ if (!gfxPlatform::GetPlatform()->OpenTypeSVGEnabled()) {
+ return false;
+ }
+
+ // We don't support SVG-in-OT glyphs in offscreen-canvas worker threads.
+ if (!NS_IsMainThread()) {
+ return false;
+ }
+
+ if (!mSVGInitialized) {
+ // If UnitsPerEm is not known/valid, we can't use SVG glyphs
+ if (UnitsPerEm() == kInvalidUPEM) {
+ mSVGInitialized = true;
+ 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) {
+ mSVGInitialized = true;
+ return false;
+ }
+
+ // gfxSVGGlyphs will hb_blob_destroy() the table when it is finished
+ // with it.
+ auto* svgGlyphs = new gfxSVGGlyphs(svgTable, this);
+ if (!mSVGGlyphs.compareExchange(nullptr, svgGlyphs)) {
+ delete svgGlyphs;
+ }
+ mSVGInitialized = true;
+ }
+
+ if (GetSVGGlyphs()) {
+ AutoWriteLock lock(mLock);
+ if (!mFontsUsingSVGGlyphs.Contains(aFont)) {
+ mFontsUsingSVGGlyphs.AppendElement(aFont);
+ }
+ }
+
+ return !!GetSVGGlyphs();
+}
+
+void gfxFontEntry::NotifyFontDestroyed(gfxFont* aFont) {
+ AutoWriteLock lock(mLock);
+ mFontsUsingSVGGlyphs.RemoveElement(aFont);
+}
+
+void gfxFontEntry::NotifyGlyphsChanged() {
+ AutoReadLock lock(mLock);
+ for (uint32_t i = 0, count = mFontsUsingSVGGlyphs.Length(); i < count; ++i) {
+ const gfxFont* font = mFontsUsingSVGGlyphs[i];
+ font->NotifyGlyphsChanged();
+ }
+}
+
+bool gfxFontEntry::TryGetColorGlyphs() {
+ if (mCheckedForColorGlyph) {
+ return mCOLR && mCPAL;
+ }
+
+ auto* colr = GetFontTable(TRUETYPE_TAG('C', 'O', 'L', 'R'));
+ auto* cpal = colr ? GetFontTable(TRUETYPE_TAG('C', 'P', 'A', 'L')) : nullptr;
+
+ if (colr && cpal && gfx::COLRFonts::ValidateColorGlyphs(colr, cpal)) {
+ if (!mCOLR.compareExchange(nullptr, colr)) {
+ hb_blob_destroy(colr);
+ }
+ if (!mCPAL.compareExchange(nullptr, cpal)) {
+ hb_blob_destroy(cpal);
+ }
+ } else {
+ hb_blob_destroy(colr);
+ hb_blob_destroy(cpal);
+ }
+
+ mCheckedForColorGlyph = true;
+ return mCOLR && mCPAL;
+}
+
+/**
+ * FontTableBlobData
+ *
+ * See FontTableHashEntry for the general strategy.
+ */
+
+class gfxFontEntry::FontTableBlobData {
+ public:
+ explicit FontTableBlobData(nsTArray<uint8_t>&& aBuffer)
+ : mTableData(std::move(aBuffer)), mHashtable(nullptr), mHashKey(0) {
+ MOZ_COUNT_CTOR(FontTableBlobData);
+ }
+
+ ~FontTableBlobData() {
+ MOZ_COUNT_DTOR(FontTableBlobData);
+ if (mHashtable && mHashKey) {
+ mHashtable->RemoveEntry(mHashKey);
+ }
+ }
+
+ // Useful for creating blobs
+ const char* GetTable() const {
+ return reinterpret_cast<const char*>(mTableData.Elements());
+ }
+ uint32_t GetTableLength() const { return mTableData.Length(); }
+
+ // Tell this FontTableBlobData to remove the HashEntry when this is
+ // destroyed.
+ void ManageHashEntry(nsTHashtable<FontTableHashEntry>* 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.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ // The font table data block
+ nsTArray<uint8_t> mTableData;
+
+ // The blob destroy function needs to know the owning hashtable
+ // and the hashtable key, so that it can remove the entry.
+ nsTHashtable<FontTableHashEntry>* mHashtable;
+ uint32_t mHashKey;
+
+ // not implemented
+ FontTableBlobData(const FontTableBlobData&);
+};
+
+hb_blob_t* gfxFontEntry::FontTableHashEntry::ShareTableAndGetBlob(
+ nsTArray<uint8_t>&& aTable, nsTHashtable<FontTableHashEntry>* aHashtable) {
+ Clear();
+ // adopts elements of aTable
+ mSharedBlobData = new FontTableBlobData(std::move(aTable));
+
+ mBlob = hb_blob_create(
+ mSharedBlobData->GetTable(), mSharedBlobData->GetTableLength(),
+ HB_MEMORY_MODE_READONLY, mSharedBlobData, DeleteFontTableBlobData);
+ if (mBlob == hb_blob_get_empty()) {
+ // 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 {
+ hb_blob_destroy(mBlob);
+ }
+ mBlob = nullptr;
+}
+
+// a hb_destroy_func for hb_blob_create
+
+/* static */
+void gfxFontEntry::FontTableHashEntry::DeleteFontTableBlobData(
+ void* aBlobData) {
+ delete static_cast<FontTableBlobData*>(aBlobData);
+}
+
+hb_blob_t* gfxFontEntry::FontTableHashEntry::GetBlob() const {
+ return hb_blob_reference(mBlob);
+}
+
+bool gfxFontEntry::GetExistingFontTable(uint32_t aTag, hb_blob_t** aBlob) {
+ // Accessing the mFontTableCache pointer is atomic, so we don't need to take
+ // a write lock even if we're initializing it here...
+ MOZ_PUSH_IGNORE_THREAD_SAFETY
+ if (MOZ_UNLIKELY(!mFontTableCache)) {
+ // We do this here rather than on fontEntry construction
+ // because not all shapers will access the table cache at all.
+ //
+ // We're not holding a write lock, so make sure to atomically update
+ // the cache pointer.
+ auto* newCache = new FontTableCache(8);
+ if (MOZ_UNLIKELY(!mFontTableCache.compareExchange(nullptr, newCache))) {
+ delete newCache;
+ }
+ }
+ FontTableCache* cache = GetFontTableCache();
+ MOZ_POP_THREAD_SAFETY
+
+ // ...but we do need a lock to read the actual hashtable contents.
+ AutoReadLock lock(mLock);
+ FontTableHashEntry* entry = cache->GetEntry(aTag);
+ if (!entry) {
+ return false;
+ }
+
+ *aBlob = entry->GetBlob();
+ return true;
+}
+
+hb_blob_t* gfxFontEntry::ShareFontTableAndGetBlob(uint32_t aTag,
+ nsTArray<uint8_t>* aBuffer) {
+ MOZ_PUSH_IGNORE_THREAD_SAFETY
+ if (MOZ_UNLIKELY(!mFontTableCache)) {
+ auto* newCache = new FontTableCache(8);
+ if (MOZ_UNLIKELY(!mFontTableCache.compareExchange(nullptr, newCache))) {
+ delete newCache;
+ }
+ }
+ FontTableCache* cache = GetFontTableCache();
+ MOZ_POP_THREAD_SAFETY
+
+ AutoWriteLock lock(mLock);
+ FontTableHashEntry* entry = cache->PutEntry(aTag);
+ if (MOZ_UNLIKELY(!entry)) { // OOM
+ return nullptr;
+ }
+
+ if (!aBuffer) {
+ // ensure the entry is null
+ entry->Clear();
+ return nullptr;
+ }
+
+ return entry->ShareTableAndGetBlob(std::move(*aBuffer), cache);
+}
+
+already_AddRefed<gfxCharacterMap> gfxFontEntry::GetCMAPFromFontInfo(
+ FontInfoData* aFontInfoData, uint32_t& aUVSOffset) {
+ if (!aFontInfoData || !aFontInfoData->mLoadCmaps) {
+ return nullptr;
+ }
+
+ return aFontInfoData->GetCMAP(mName, aUVSOffset);
+}
+
+hb_blob_t* gfxFontEntry::GetFontTable(uint32_t aTag) {
+ hb_blob_t* blob;
+ if (GetExistingFontTable(aTag, &blob)) {
+ return blob;
+ }
+
+ nsTArray<uint8_t> 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<gfxFontEntry*>(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 thread_local gfxFontEntry* tl_grGetFontTableCallbackData = nullptr;
+
+class gfxFontEntryCallbacks {
+ public:
+ static tainted_gr<const void*> GrGetTable(
+ rlbox_sandbox_gr& sandbox, tainted_gr<const void*> /* aAppFaceHandle */,
+ tainted_gr<unsigned int> aName, tainted_gr<unsigned int*> aLen) {
+ gfxFontEntry* fontEntry = tl_grGetFontTableCallbackData;
+ *aLen = 0;
+ tainted_gr<const void*> ret = nullptr;
+
+ if (fontEntry) {
+ unsigned int fontTableKey = aName.unverified_safe_because(
+ "This is only being used to index into a hashmap, which is robust "
+ "for any value. No checks needed.");
+ gfxFontUtils::AutoHBBlob blob(fontEntry->GetFontTable(fontTableKey));
+
+ if (blob) {
+ unsigned int blobLength;
+ const void* tableData = hb_blob_get_data(blob, &blobLength);
+ // tableData is read-only data shared with the sandbox.
+ // Making a copy in sandbox memory
+ tainted_gr<void*> t_tableData = rlbox::sandbox_reinterpret_cast<void*>(
+ sandbox.malloc_in_sandbox<char>(blobLength));
+ if (t_tableData) {
+ rlbox::memcpy(sandbox, t_tableData, tableData, blobLength);
+ *aLen = blobLength;
+ ret = rlbox::sandbox_const_cast<const void*>(t_tableData);
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ static void GrReleaseTable(rlbox_sandbox_gr& sandbox,
+ tainted_gr<const void*> /* aAppFaceHandle */,
+ tainted_gr<const void*> aTableBuffer) {
+ sandbox.free_in_sandbox(aTableBuffer);
+ }
+
+ static tainted_gr<float> GrGetAdvance(rlbox_sandbox_gr& sandbox,
+ tainted_gr<const void*> appFontHandle,
+ tainted_gr<uint16_t> glyphid) {
+ tainted_opaque_gr<float> ret = gfxGraphiteShaper::GrGetAdvance(
+ sandbox, appFontHandle.to_opaque(), glyphid.to_opaque());
+ return rlbox::from_opaque(ret);
+ }
+};
+
+struct gfxFontEntry::GrSandboxData {
+ rlbox_sandbox_gr sandbox;
+ sandbox_callback_gr<const void* (*)(const void*, unsigned int, unsigned int*)>
+ grGetTableCallback;
+ sandbox_callback_gr<void (*)(const void*, const void*)>
+ grReleaseTableCallback;
+ // Text Shapers register a callback to get glyph advances
+ sandbox_callback_gr<float (*)(const void*, uint16_t)>
+ grGetGlyphAdvanceCallback;
+
+ GrSandboxData() {
+ sandbox.create_sandbox();
+ grGetTableCallback =
+ sandbox.register_callback(gfxFontEntryCallbacks::GrGetTable);
+ grReleaseTableCallback =
+ sandbox.register_callback(gfxFontEntryCallbacks::GrReleaseTable);
+ grGetGlyphAdvanceCallback =
+ sandbox.register_callback(gfxFontEntryCallbacks::GrGetAdvance);
+ }
+
+ ~GrSandboxData() {
+ grGetTableCallback.unregister();
+ grReleaseTableCallback.unregister();
+ grGetGlyphAdvanceCallback.unregister();
+ sandbox.destroy_sandbox();
+ }
+};
+
+rlbox_sandbox_gr* gfxFontEntry::GetGrSandbox() {
+ AutoReadLock lock(mLock);
+ MOZ_ASSERT(mSandboxData != nullptr);
+ return &mSandboxData->sandbox;
+}
+
+sandbox_callback_gr<float (*)(const void*, uint16_t)>*
+gfxFontEntry::GetGrSandboxAdvanceCallbackHandle() {
+ AutoReadLock lock(mLock);
+ MOZ_ASSERT(mSandboxData != nullptr);
+ return &mSandboxData->grGetGlyphAdvanceCallback;
+}
+
+tainted_opaque_gr<gr_face*> gfxFontEntry::GetGrFace() {
+ if (!mGrFaceInitialized) {
+ // When possible, the below code will use WASM as a sandboxing mechanism.
+ // At this time the wasm sandbox does not support threads.
+ // If Thebes is updated to make callst to the sandbox on multiple threaads,
+ // we need to make sure the underlying sandbox supports threading.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mSandboxData = new GrSandboxData();
+
+ auto p_faceOps = mSandboxData->sandbox.malloc_in_sandbox<gr_face_ops>();
+ if (!p_faceOps) {
+ MOZ_CRASH("Graphite sandbox memory allocation failed");
+ }
+ p_faceOps->size = sizeof(*p_faceOps);
+ p_faceOps->get_table = mSandboxData->grGetTableCallback;
+ p_faceOps->release_table = mSandboxData->grReleaseTableCallback;
+
+ tl_grGetFontTableCallbackData = this;
+ auto face = sandbox_invoke(
+ mSandboxData->sandbox, gr_make_face_with_ops,
+ // For security, we do not pass the callback data to this arg, and use
+ // a TLS var instead. However, gr_make_face_with_ops expects this to
+ // be a non null ptr. Therefore, we should pass some dummy non null
+ // pointer which will be passed to callbacks, but never used. Let's just
+ // pass p_faceOps again, as this is a non-null tainted pointer.
+ p_faceOps /* appFaceHandle */, p_faceOps, gr_face_default);
+ tl_grGetFontTableCallbackData = nullptr;
+ mGrFace = face.to_opaque();
+ mGrFaceInitialized = true;
+ mSandboxData->sandbox.free_in_sandbox(p_faceOps);
+ }
+ ++mGrFaceRefCnt;
+ return mGrFace;
+}
+
+void gfxFontEntry::ReleaseGrFace(tainted_opaque_gr<gr_face*> aFace) {
+ MOZ_ASSERT(
+ (rlbox::from_opaque(aFace) == rlbox::from_opaque(mGrFace))
+ .unverified_safe_because(
+ "This is safe as the only thing we are doing is comparing "
+ "addresses of two tainted pointers. Furthermore this is used "
+ "merely as a debugging aid in the debug builds. This function is "
+ "called only from the trusted Firefox code rather than the "
+ "untrusted libGraphite.")); // sanity-check
+ MOZ_ASSERT(mGrFaceRefCnt > 0);
+ if (--mGrFaceRefCnt == 0) {
+ auto t_mGrFace = rlbox::from_opaque(mGrFace);
+
+ tl_grGetFontTableCallbackData = this;
+ sandbox_invoke(mSandboxData->sandbox, gr_face_destroy, t_mGrFace);
+ tl_grGetFontTableCallbackData = nullptr;
+
+ t_mGrFace = nullptr;
+ mGrFace = t_mGrFace.to_opaque();
+
+ delete mSandboxData;
+ mSandboxData = nullptr;
+
+ mGrFaceInitialized = false;
+ }
+}
+
+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;
+}
+
+tainted_boolean_hint gfxFontEntry::HasGraphiteSpaceContextuals() {
+ LazyFlag flag = mHasGraphiteSpaceContextuals;
+ if (flag == LazyFlag::Uninitialized) {
+ auto face = GetGrFace();
+ auto t_face = rlbox::from_opaque(face);
+ if (t_face) {
+ tainted_gr<const gr_faceinfo*> faceInfo =
+ sandbox_invoke(mSandboxData->sandbox, gr_face_info, t_face, 0);
+ // Comparison with a value in sandboxed memory returns a
+ // tainted_boolean_hint, i.e. a "hint", since the value could be changed
+ // maliciously at any moment.
+ tainted_boolean_hint is_not_none =
+ faceInfo->space_contextuals != gr_faceinfo::gr_space_none;
+ flag = is_not_none.unverified_safe_because(
+ "Note ideally mHasGraphiteSpaceContextuals would be "
+ "tainted_boolean_hint, but RLBox does not yet support "
+ "bitfields, so it is not wrapped. However, its value is only "
+ "ever accessed through this function which returns a "
+ "tainted_boolean_hint, so unwrapping temporarily is safe. "
+ "We remove the wrapper now and re-add it below.")
+ ? LazyFlag::Yes
+ : LazyFlag::No;
+ }
+ ReleaseGrFace(face); // always balance GetGrFace, even if face is null
+ mHasGraphiteSpaceContextuals = flag;
+ }
+
+ return tainted_boolean_hint(flag == LazyFlag::Yes);
+}
+
+#define FEATURE_SCRIPT_MASK 0x000000ff // script index replaces low byte of tag
+
+static_assert(int(intl::Script::NUM_SCRIPT_CODES) <= FEATURE_SCRIPT_MASK,
+ "Too many script codes");
+
+// high-order three bytes of tag with script in low-order byte
+#define SCRIPT_FEATURE(s, tag) \
+ (((~FEATURE_SCRIPT_MASK) & (tag)) | \
+ ((FEATURE_SCRIPT_MASK) & static_cast<uint32_t>(s)))
+
+bool gfxFontEntry::SupportsOpenTypeFeature(Script aScript,
+ uint32_t aFeatureTag) {
+ MutexAutoLock lock(mFeatureInfoLock);
+ if (!mSupportedFeatures) {
+ mSupportedFeatures = MakeUnique<nsTHashMap<nsUint32HashKey, bool>>();
+ }
+
+ // note: high-order three bytes *must* be unique for each feature
+ // listed below (see SCRIPT_FEATURE macro def'n)
+ NS_ASSERTION(aFeatureTag == HB_TAG('s', 'm', 'c', 'p') ||
+ aFeatureTag == HB_TAG('c', '2', 's', 'c') ||
+ aFeatureTag == HB_TAG('p', 'c', 'a', 'p') ||
+ aFeatureTag == HB_TAG('c', '2', 'p', 'c') ||
+ aFeatureTag == HB_TAG('s', 'u', 'p', 's') ||
+ aFeatureTag == HB_TAG('s', 'u', 'b', 's') ||
+ aFeatureTag == HB_TAG('v', 'e', 'r', 't'),
+ "use of unknown feature tag");
+
+ // note: graphite feature support uses the last script index
+ NS_ASSERTION(int(aScript) < FEATURE_SCRIPT_MASK - 1,
+ "need to bump the size of the feature shift");
+
+ uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag);
+ return mSupportedFeatures->LookupOrInsertWith(scriptFeature, [&] {
+ bool result = false;
+ auto face(GetHBFace());
+
+ if (hb_ot_layout_has_substitution(face)) {
+ hb_script_t hbScript =
+ gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript);
+
+ // Get the OpenType tag(s) that match this script code
+ unsigned int scriptCount = 4;
+ hb_tag_t scriptTags[4];
+ hb_ot_tags_from_script_and_language(hbScript, HB_LANGUAGE_INVALID,
+ &scriptCount, scriptTags, nullptr,
+ nullptr);
+
+ // Append DEFAULT to the returned tags, if room
+ if (scriptCount < 4) {
+ scriptTags[scriptCount++] = HB_OT_TAG_DEFAULT_SCRIPT;
+ }
+
+ // Now check for 'smcp' under the first of those scripts that is present
+ const hb_tag_t kGSUB = HB_TAG('G', 'S', 'U', 'B');
+ result = std::any_of(scriptTags, scriptTags + scriptCount,
+ [&](const hb_tag_t& scriptTag) {
+ unsigned int scriptIndex;
+ return hb_ot_layout_table_find_script(
+ face, kGSUB, scriptTag, &scriptIndex) &&
+ hb_ot_layout_language_find_feature(
+ face, kGSUB, scriptIndex,
+ HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX,
+ aFeatureTag, nullptr);
+ });
+ }
+
+ return result;
+ });
+}
+
+const hb_set_t* gfxFontEntry::InputsForOpenTypeFeature(Script aScript,
+ uint32_t aFeatureTag) {
+ MutexAutoLock lock(mFeatureInfoLock);
+ if (!mFeatureInputs) {
+ mFeatureInputs = MakeUnique<nsTHashMap<nsUint32HashKey, hb_set_t*>>();
+ }
+
+ NS_ASSERTION(aFeatureTag == HB_TAG('s', 'u', 'p', 's') ||
+ aFeatureTag == HB_TAG('s', 'u', 'b', 's') ||
+ aFeatureTag == HB_TAG('v', 'e', 'r', 't'),
+ "use of unknown feature tag");
+
+ uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag);
+ hb_set_t* inputGlyphs;
+ if (mFeatureInputs->Get(scriptFeature, &inputGlyphs)) {
+ return inputGlyphs;
+ }
+
+ inputGlyphs = hb_set_create();
+
+ auto face(GetHBFace());
+
+ if (hb_ot_layout_has_substitution(face)) {
+ hb_script_t hbScript =
+ gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript);
+
+ // Get the OpenType tag(s) that match this script code
+ unsigned int scriptCount = 4;
+ hb_tag_t scriptTags[5]; // space for null terminator
+ hb_ot_tags_from_script_and_language(hbScript, HB_LANGUAGE_INVALID,
+ &scriptCount, scriptTags, nullptr,
+ nullptr);
+
+ // Append DEFAULT to the returned tags, if room
+ if (scriptCount < 4) {
+ scriptTags[scriptCount++] = HB_OT_TAG_DEFAULT_SCRIPT;
+ }
+ scriptTags[scriptCount++] = 0;
+
+ const hb_tag_t kGSUB = HB_TAG('G', 'S', 'U', 'B');
+ hb_tag_t features[2] = {aFeatureTag, HB_TAG_NONE};
+ hb_set_t* featurelookups = hb_set_create();
+ hb_ot_layout_collect_lookups(face, kGSUB, scriptTags, nullptr, features,
+ featurelookups);
+ hb_codepoint_t index = -1;
+ while (hb_set_next(featurelookups, &index)) {
+ hb_ot_layout_lookup_collect_glyphs(face, kGSUB, index, nullptr,
+ inputGlyphs, nullptr, nullptr);
+ }
+ hb_set_destroy(featurelookups);
+ }
+
+ mFeatureInputs->InsertOrUpdate(scriptFeature, inputGlyphs);
+ return inputGlyphs;
+}
+
+bool gfxFontEntry::SupportsGraphiteFeature(uint32_t aFeatureTag) {
+ MutexAutoLock lock(mFeatureInfoLock);
+
+ if (!mSupportedFeatures) {
+ mSupportedFeatures = MakeUnique<nsTHashMap<nsUint32HashKey, bool>>();
+ }
+
+ // note: high-order three bytes *must* be unique for each feature
+ // listed below (see SCRIPT_FEATURE macro def'n)
+ NS_ASSERTION(aFeatureTag == HB_TAG('s', 'm', 'c', 'p') ||
+ aFeatureTag == HB_TAG('c', '2', 's', 'c') ||
+ aFeatureTag == HB_TAG('p', 'c', 'a', 'p') ||
+ aFeatureTag == HB_TAG('c', '2', 'p', 'c') ||
+ aFeatureTag == HB_TAG('s', 'u', 'p', 's') ||
+ aFeatureTag == HB_TAG('s', 'u', 'b', 's'),
+ "use of unknown feature tag");
+
+ // graphite feature check uses the last script slot
+ uint32_t scriptFeature = SCRIPT_FEATURE(FEATURE_SCRIPT_MASK, aFeatureTag);
+ bool result;
+ if (mSupportedFeatures->Get(scriptFeature, &result)) {
+ return result;
+ }
+
+ auto face = GetGrFace();
+ auto t_face = rlbox::from_opaque(face);
+ result = t_face ? sandbox_invoke(mSandboxData->sandbox, gr_face_find_fref,
+ t_face, aFeatureTag) != nullptr
+ : false;
+ ReleaseGrFace(face);
+
+ mSupportedFeatures->InsertOrUpdate(scriptFeature, result);
+
+ return result;
+}
+
+void gfxFontEntry::GetFeatureInfo(nsTArray<gfxFontFeatureInfo>& aFeatureInfo) {
+ // TODO: implement alternative code path for graphite fonts
+
+ auto autoFace(GetHBFace());
+ // Expose the raw hb_face_t to be captured by the lambdas (not the
+ // AutoHBFace wrapper).
+ hb_face_t* face = autoFace;
+
+ // Get the list of features for a specific <script,langSys> pair and
+ // append them to aFeatureInfo.
+ auto collectForLang = [=, &aFeatureInfo](
+ hb_tag_t aTableTag, unsigned int aScript,
+ hb_tag_t aScriptTag, unsigned int aLang,
+ hb_tag_t aLangTag) {
+ unsigned int featCount = hb_ot_layout_language_get_feature_tags(
+ face, aTableTag, aScript, aLang, 0, nullptr, nullptr);
+ AutoTArray<hb_tag_t, 32> featTags;
+ featTags.SetLength(featCount);
+ hb_ot_layout_language_get_feature_tags(face, aTableTag, aScript, aLang, 0,
+ &featCount, featTags.Elements());
+ MOZ_ASSERT(featCount <= featTags.Length());
+ // Just in case HB didn't fill featTags (i.e. in case it returned fewer
+ // tags than it promised), we truncate at the length it says it filled:
+ featTags.SetLength(featCount);
+ for (hb_tag_t t : featTags) {
+ aFeatureInfo.AppendElement(gfxFontFeatureInfo{t, aScriptTag, aLangTag});
+ }
+ };
+
+ // Iterate over the language systems supported by a given script,
+ // and call collectForLang for each of them.
+ auto collectForScript = [=](hb_tag_t aTableTag, unsigned int aScript,
+ hb_tag_t aScriptTag) {
+ collectForLang(aTableTag, aScript, aScriptTag,
+ HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX,
+ HB_TAG('d', 'f', 'l', 't'));
+ unsigned int langCount = hb_ot_layout_script_get_language_tags(
+ face, aTableTag, aScript, 0, nullptr, nullptr);
+ AutoTArray<hb_tag_t, 32> langTags;
+ langTags.SetLength(langCount);
+ hb_ot_layout_script_get_language_tags(face, aTableTag, aScript, 0,
+ &langCount, langTags.Elements());
+ MOZ_ASSERT(langCount <= langTags.Length());
+ langTags.SetLength(langCount);
+ for (unsigned int lang = 0; lang < langCount; ++lang) {
+ collectForLang(aTableTag, aScript, aScriptTag, lang, langTags[lang]);
+ }
+ };
+
+ // Iterate over the scripts supported by a table (GSUB or GPOS), and call
+ // collectForScript for each of them.
+ auto collectForTable = [=](hb_tag_t aTableTag) {
+ unsigned int scriptCount = hb_ot_layout_table_get_script_tags(
+ face, aTableTag, 0, nullptr, nullptr);
+ AutoTArray<hb_tag_t, 32> scriptTags;
+ scriptTags.SetLength(scriptCount);
+ hb_ot_layout_table_get_script_tags(face, aTableTag, 0, &scriptCount,
+ scriptTags.Elements());
+ MOZ_ASSERT(scriptCount <= scriptTags.Length());
+ scriptTags.SetLength(scriptCount);
+ for (unsigned int script = 0; script < scriptCount; ++script) {
+ collectForScript(aTableTag, script, scriptTags[script]);
+ }
+ };
+
+ // Collect all OpenType Layout features, both substitution and positioning,
+ // supported by the font resource.
+ collectForTable(HB_TAG('G', 'S', 'U', 'B'));
+ collectForTable(HB_TAG('G', 'P', 'O', 'S'));
+}
+
+typedef struct {
+ AutoSwap_PRUint32 version;
+ AutoSwap_PRUint16 format;
+ AutoSwap_PRUint16 horizOffset;
+ AutoSwap_PRUint16 vertOffset;
+ AutoSwap_PRUint16 reserved;
+ // TrackData horizData;
+ // TrackData vertData;
+} TrakHeader;
+
+typedef struct {
+ AutoSwap_PRUint16 nTracks;
+ AutoSwap_PRUint16 nSizes;
+ AutoSwap_PRUint32 sizeTableOffset;
+ // trackTableEntry trackTable[];
+ // fixed32 sizeTable[];
+} TrackData;
+
+typedef struct {
+ AutoSwap_PRUint32 track;
+ AutoSwap_PRUint16 nameIndex;
+ AutoSwap_PRUint16 offset;
+} TrackTableEntry;
+
+bool gfxFontEntry::HasTrackingTable() {
+ if (!TrakTableInitialized()) {
+ hb_blob_t* trak = GetFontTable(TRUETYPE_TAG('t', 'r', 'a', 'k'));
+ if (trak) {
+ // mTrakTable itself is atomic, but we also want to set the auxiliary
+ // pointers mTrakValues and mTrakSizeTable, so we take a lock here to
+ // avoid racing with another thread also initializing the same values.
+ AutoWriteLock lock(mLock);
+ if (!mTrakTable.compareExchange(kTrakTableUninitialized, trak)) {
+ hb_blob_destroy(trak);
+ } else if (!ParseTrakTable()) {
+ hb_blob_destroy(mTrakTable.exchange(nullptr));
+ }
+ } else {
+ mTrakTable.exchange(nullptr);
+ }
+ }
+ return GetTrakTable() != nullptr;
+}
+
+bool gfxFontEntry::ParseTrakTable() {
+ // Check table validity and set up the subtable pointers we need;
+ // if 'trak' table is invalid, or doesn't contain a 'normal' track,
+ // return false to tell the caller not to try using it.
+ unsigned int len;
+ const char* data = hb_blob_get_data(GetTrakTable(), &len);
+ if (len < sizeof(TrakHeader)) {
+ return false;
+ }
+ auto trak = reinterpret_cast<const TrakHeader*>(data);
+ uint16_t horizOffset = trak->horizOffset;
+ if (trak->version != 0x00010000 || uint16_t(trak->format) != 0 ||
+ horizOffset == 0 || uint16_t(trak->reserved) != 0) {
+ return false;
+ }
+ // Find the horizontal trackData, and check it doesn't overrun the buffer.
+ if (horizOffset > len - sizeof(TrackData)) {
+ return false;
+ }
+ auto trackData = reinterpret_cast<const TrackData*>(data + horizOffset);
+ uint16_t nTracks = trackData->nTracks;
+ mNumTrakSizes = trackData->nSizes;
+ if (nTracks == 0 || mNumTrakSizes < 2) {
+ return false;
+ }
+ uint32_t sizeTableOffset = trackData->sizeTableOffset;
+ // Find the trackTable, and check it doesn't overrun the buffer.
+ if (horizOffset >
+ len - (sizeof(TrackData) + nTracks * sizeof(TrackTableEntry))) {
+ return false;
+ }
+ auto trackTable = reinterpret_cast<const TrackTableEntry*>(
+ data + horizOffset + sizeof(TrackData));
+ // Look for 'normal' tracking, bail out if no such track is present.
+ unsigned trackIndex;
+ for (trackIndex = 0; trackIndex < nTracks; ++trackIndex) {
+ if (trackTable[trackIndex].track == 0x00000000) {
+ break;
+ }
+ }
+ if (trackIndex == nTracks) {
+ return false;
+ }
+ // Find list of tracking values, and check they won't overrun.
+ uint16_t offset = trackTable[trackIndex].offset;
+ if (offset > len - mNumTrakSizes * sizeof(uint16_t)) {
+ return false;
+ }
+ mTrakValues = reinterpret_cast<const AutoSwap_PRInt16*>(data + offset);
+ // Find the size subtable, and check it doesn't overrun the buffer.
+ mTrakSizeTable =
+ reinterpret_cast<const AutoSwap_PRInt32*>(data + sizeTableOffset);
+ if (mTrakSizeTable + mNumTrakSizes >
+ reinterpret_cast<const AutoSwap_PRInt32*>(data + len)) {
+ return false;
+ }
+ return true;
+}
+
+gfxFloat gfxFontEntry::TrackingForCSSPx(gfxFloat aSize) const {
+ // No locking because this does read-only access of fields that are inert
+ // once initialized.
+ MOZ_ASSERT(TrakTableInitialized() && mTrakTable && mTrakValues &&
+ mTrakSizeTable);
+
+ // Find index of first sizeTable entry that is >= the requested size.
+ int32_t fixedSize = int32_t(aSize * 65536.0); // float -> 16.16 fixed-point
+ unsigned sizeIndex;
+ for (sizeIndex = 0; sizeIndex < mNumTrakSizes; ++sizeIndex) {
+ if (mTrakSizeTable[sizeIndex] >= fixedSize) {
+ break;
+ }
+ }
+ // Return the tracking value for the requested size, or an interpolated
+ // value if the exact size isn't found.
+ if (sizeIndex == mNumTrakSizes) {
+ // Request is larger than last entry in the table, so just use that.
+ // (We don't attempt to extrapolate more extreme tracking values than
+ // the largest or smallest present in the table.)
+ return int16_t(mTrakValues[mNumTrakSizes - 1]);
+ }
+ if (sizeIndex == 0 || mTrakSizeTable[sizeIndex] == fixedSize) {
+ // Found an exact match, or size was smaller than the first entry.
+ return int16_t(mTrakValues[sizeIndex]);
+ }
+ // Requested size falls between two entries: interpolate value.
+ double s0 = mTrakSizeTable[sizeIndex - 1] / 65536.0; // 16.16 -> float
+ double s1 = mTrakSizeTable[sizeIndex] / 65536.0;
+ double t = (aSize - s0) / (s1 - s0);
+ return (1.0 - t) * int16_t(mTrakValues[sizeIndex - 1]) +
+ t * int16_t(mTrakValues[sizeIndex]);
+}
+
+void gfxFontEntry::SetupVariationRanges() {
+ // No locking because this is done during initialization before any other
+ // thread has access to the entry.
+ if (!gfxPlatform::HasVariationFontSupport() ||
+ !StaticPrefs::layout_css_font_variations_enabled() || !HasVariations() ||
+ IsUserFont()) {
+ return;
+ }
+ AutoTArray<gfxFontVariationAxis, 4> axes;
+ GetVariationAxes(axes);
+ for (const auto& axis : axes) {
+ switch (axis.mTag) {
+ case HB_TAG('w', 'g', 'h', 't'):
+ // If the axis range looks like it doesn't fit the CSS font-weight
+ // scale, we don't hook up the high-level property, and we mark
+ // the face (in mRangeFlags) as having non-standard weight. This
+ // means we won't map CSS font-weight to the axis. Setting 'wght'
+ // with font-variation-settings will still work.
+ // Strictly speaking, the min value should be checked against 1.0,
+ // not 0.0, but we'll allow font makers that amount of leeway, as
+ // in practice a number of fonts seem to use 0..1000.
+ if (axis.mMinValue >= 0.0f && axis.mMaxValue <= 1000.0f &&
+ // If axis.mMaxValue is less than the default weight we already
+ // set up, assume the axis has a non-standard range (like Skia)
+ // and don't try to map it.
+ Weight().Min() <= FontWeight::FromFloat(axis.mMaxValue)) {
+ if (FontWeight::FromFloat(axis.mDefaultValue) != Weight().Min()) {
+ mStandardFace = false;
+ }
+ mWeightRange =
+ WeightRange(FontWeight::FromFloat(std::max(1.0f, axis.mMinValue)),
+ FontWeight::FromFloat(axis.mMaxValue));
+ } else {
+ mRangeFlags |= RangeFlags::eNonCSSWeight;
+ }
+ break;
+
+ case HB_TAG('w', 'd', 't', 'h'):
+ if (axis.mMinValue >= 0.0f && axis.mMaxValue <= 1000.0f &&
+ Stretch().Min() <= FontStretch::FromFloat(axis.mMaxValue)) {
+ if (FontStretch::FromFloat(axis.mDefaultValue) != Stretch().Min()) {
+ mStandardFace = false;
+ }
+ mStretchRange = StretchRange(FontStretch::FromFloat(axis.mMinValue),
+ FontStretch::FromFloat(axis.mMaxValue));
+ } else {
+ mRangeFlags |= RangeFlags::eNonCSSStretch;
+ }
+ break;
+
+ case HB_TAG('s', 'l', 'n', 't'):
+ if (axis.mMinValue >= -90.0f && axis.mMaxValue <= 90.0f) {
+ if (FontSlantStyle::FromFloat(axis.mDefaultValue) !=
+ SlantStyle().Min()) {
+ mStandardFace = false;
+ }
+ // OpenType and CSS measure angles in opposite directions, so we
+ // have to flip signs and swap min/max when setting up the CSS
+ // font-style range here.
+ mStyleRange =
+ SlantStyleRange(FontSlantStyle::FromFloat(-axis.mMaxValue),
+ FontSlantStyle::FromFloat(-axis.mMinValue));
+ }
+ break;
+
+ case HB_TAG('i', 't', 'a', 'l'):
+ if (axis.mMinValue <= 0.0f && axis.mMaxValue >= 1.0f) {
+ if (axis.mDefaultValue != 0.0f) {
+ mStandardFace = false;
+ }
+ mStyleRange =
+ SlantStyleRange(FontSlantStyle::NORMAL, FontSlantStyle::ITALIC);
+ }
+ break;
+
+ default:
+ continue;
+ }
+ }
+}
+
+void gfxFontEntry::CheckForVariationAxes() {
+ if (mCheckedForVariationAxes) {
+ return;
+ }
+ mCheckedForVariationAxes = true;
+ if (HasVariations()) {
+ AutoTArray<gfxFontVariationAxis, 4> axes;
+ GetVariationAxes(axes);
+ for (const auto& axis : axes) {
+ if (axis.mTag == HB_TAG('w', 'g', 'h', 't') && axis.mMaxValue >= 600.0f) {
+ mRangeFlags |= RangeFlags::eBoldVariableWeight;
+ } else if (axis.mTag == HB_TAG('i', 't', 'a', 'l') &&
+ axis.mMaxValue >= 1.0f) {
+ mRangeFlags |= RangeFlags::eItalicVariation;
+ } else if (axis.mTag == HB_TAG('s', 'l', 'n', 't')) {
+ mRangeFlags |= RangeFlags::eSlantVariation;
+ } else if (axis.mTag == HB_TAG('o', 'p', 's', 'z')) {
+ mRangeFlags |= RangeFlags::eOpticalSize;
+ }
+ }
+ }
+}
+
+bool gfxFontEntry::HasBoldVariableWeight() {
+ MOZ_ASSERT(!mIsUserFontContainer,
+ "should not be called for user-font containers!");
+ CheckForVariationAxes();
+ return bool(mRangeFlags & RangeFlags::eBoldVariableWeight);
+}
+
+bool gfxFontEntry::HasItalicVariation() {
+ MOZ_ASSERT(!mIsUserFontContainer,
+ "should not be called for user-font containers!");
+ CheckForVariationAxes();
+ return bool(mRangeFlags & RangeFlags::eItalicVariation);
+}
+
+bool gfxFontEntry::HasSlantVariation() {
+ MOZ_ASSERT(!mIsUserFontContainer,
+ "should not be called for user-font containers!");
+ CheckForVariationAxes();
+ return bool(mRangeFlags & RangeFlags::eSlantVariation);
+}
+
+bool gfxFontEntry::HasOpticalSize() {
+ MOZ_ASSERT(!mIsUserFontContainer,
+ "should not be called for user-font containers!");
+ CheckForVariationAxes();
+ return bool(mRangeFlags & RangeFlags::eOpticalSize);
+}
+
+void gfxFontEntry::GetVariationsForStyle(nsTArray<gfxFontVariation>& aResult,
+ const gfxFontStyle& aStyle) {
+ if (!gfxPlatform::HasVariationFontSupport() ||
+ !StaticPrefs::layout_css_font_variations_enabled()) {
+ return;
+ }
+
+ if (!HasVariations()) {
+ return;
+ }
+
+ // Resolve high-level CSS properties from the requested style
+ // (font-{style,weight,stretch}) to the appropriate variations.
+ // The value used is clamped to the range available in the font face,
+ // unless the face is a user font where no explicit descriptor was
+ // given, indicated by the corresponding 'auto' range-flag.
+
+ // We don't do these mappings if the font entry has weight and/or stretch
+ // ranges that do not appear to use the CSS property scale. Some older
+ // fonts created for QuickDrawGX/AAT may use "normalized" values where the
+ // standard variation is 1.0 rather than 400.0 (weight) or 100.0 (stretch).
+
+ if (!(mRangeFlags & RangeFlags::eNonCSSWeight)) {
+ float weight = (IsUserFont() && (mRangeFlags & RangeFlags::eAutoWeight))
+ ? aStyle.weight.ToFloat()
+ : Weight().Clamp(aStyle.weight).ToFloat();
+ aResult.AppendElement(gfxFontVariation{HB_TAG('w', 'g', 'h', 't'), weight});
+ }
+
+ if (!(mRangeFlags & RangeFlags::eNonCSSStretch)) {
+ float stretch = (IsUserFont() && (mRangeFlags & RangeFlags::eAutoStretch))
+ ? aStyle.stretch.ToFloat()
+ : Stretch().Clamp(aStyle.stretch).ToFloat();
+ aResult.AppendElement(
+ gfxFontVariation{HB_TAG('w', 'd', 't', 'h'), stretch});
+ }
+
+ if (aStyle.style.IsItalic() && SupportsItalic()) {
+ // The 'ital' axis is normally a binary toggle; intermediate values
+ // can only be set using font-variation-settings.
+ aResult.AppendElement(gfxFontVariation{HB_TAG('i', 't', 'a', 'l'), 1.0f});
+ } else if (aStyle.style != StyleFontStyle::NORMAL && HasSlantVariation()) {
+ // Figure out what slant angle we should try to match from the
+ // requested style.
+ float angle = aStyle.style.SlantAngle();
+ // Clamp to the available range, unless the face is a user font
+ // with no explicit descriptor.
+ if (!(IsUserFont() && (mRangeFlags & RangeFlags::eAutoSlantStyle))) {
+ angle = SlantStyle().Clamp(FontSlantStyle::FromFloat(angle)).SlantAngle();
+ }
+ // OpenType and CSS measure angles in opposite directions, so we have to
+ // invert the sign of the CSS oblique value when setting OpenType 'slnt'.
+ aResult.AppendElement(gfxFontVariation{HB_TAG('s', 'l', 'n', 't'), -angle});
+ }
+
+ struct TagEquals {
+ bool Equals(const gfxFontVariation& aIter, uint32_t aTag) const {
+ return aIter.mTag == aTag;
+ }
+ };
+
+ auto replaceOrAppend = [&aResult](const gfxFontVariation& aSetting) {
+ auto index = aResult.IndexOf(aSetting.mTag, 0, TagEquals());
+ if (index == aResult.NoIndex) {
+ aResult.AppendElement(aSetting);
+ } else {
+ aResult[index].mValue = aSetting.mValue;
+ }
+ };
+
+ // The low-level font-variation-settings descriptor from @font-face,
+ // if present, takes precedence over automatic variation settings
+ // from high-level properties.
+ for (const auto& v : mVariationSettings) {
+ replaceOrAppend(v);
+ }
+
+ // And the low-level font-variation-settings property takes precedence
+ // over the descriptor.
+ for (const auto& v : aStyle.variationSettings) {
+ replaceOrAppend(v);
+ }
+
+ // If there's no explicit opsz in the settings, apply 'auto' value.
+ if (HasOpticalSize() && aStyle.autoOpticalSize >= 0.0f) {
+ const uint32_t kOpszTag = HB_TAG('o', 'p', 's', 'z');
+ auto index = aResult.IndexOf(kOpszTag, 0, TagEquals());
+ if (index == aResult.NoIndex) {
+ float value = aStyle.autoOpticalSize * mSizeAdjust;
+ aResult.AppendElement(gfxFontVariation{kOpszTag, value});
+ }
+ }
+}
+
+size_t gfxFontEntry::FontTableHashEntry::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ if (mBlob) {
+ n += aMallocSizeOf(mBlob);
+ }
+ if (mSharedBlobData) {
+ n += mSharedBlobData->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+void gfxFontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ aSizes->mFontListSize += mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+
+ // cmaps are shared so only non-shared cmaps are included here
+ if (mCharacterMap && GetCharacterMap()->mBuildOnTheFly) {
+ aSizes->mCharMapsSize +=
+ GetCharacterMap()->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ {
+ AutoReadLock lock(mLock);
+ if (mFontTableCache) {
+ aSizes->mFontTableCacheSize +=
+ GetFontTableCache()->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+
+ // If the font has UVS data, we count that as part of the character map.
+ if (mUVSData) {
+ aSizes->mCharMapsSize += aMallocSizeOf(GetUVSData());
+ }
+
+ // The following, if present, are essentially cached forms of font table
+ // data, so we'll accumulate them together with the basic table cache.
+ if (mUserFontData) {
+ aSizes->mFontTableCacheSize +=
+ mUserFontData->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (mSVGGlyphs) {
+ aSizes->mFontTableCacheSize +=
+ GetSVGGlyphs()->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ {
+ MutexAutoLock lock(mFeatureInfoLock);
+ if (mSupportedFeatures) {
+ aSizes->mFontTableCacheSize +=
+ mSupportedFeatures->ShallowSizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (mFeatureInputs) {
+ aSizes->mFontTableCacheSize +=
+ mFeatureInputs->ShallowSizeOfIncludingThis(aMallocSizeOf);
+ // XXX Can't this simply be
+ // aSizes->mFontTableCacheSize += 8192 * mFeatureInputs->Count();
+ for (auto iter = mFeatureInputs->ConstIter(); !iter.Done(); iter.Next()) {
+ // There's no API to get the real size of an hb_set, so we'll use
+ // an approximation based on knowledge of the implementation.
+ aSizes->mFontTableCacheSize += 8192; // vector of 64K bits
+ }
+ }
+ }
+ // We don't include the size of mCOLR/mCPAL here, because (depending on the
+ // font backend implementation) they will either wrap blocks of data owned
+ // by the system (and potentially shared), or tables that are in our font
+ // table cache and therefore already counted.
+}
+
+void gfxFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ aSizes->mFontListSize += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+// This is used to report the size of an individual downloaded font in the
+// user font cache. (Fonts that are part of the platform font list accumulate
+// their sizes to the font list's reporter using the AddSizeOf... methods
+// above.)
+size_t gfxFontEntry::ComputedSizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ FontListSizes s = {0};
+ AddSizeOfExcludingThis(aMallocSizeOf, &s);
+
+ // When reporting memory used for the main platform font list,
+ // where we're typically summing the totals for a few hundred font faces,
+ // we report the fields of FontListSizes separately.
+ // But for downloaded user fonts, the actual resource data (added below)
+ // will dominate, and the minor overhead of these pieces isn't worth
+ // splitting out for an individual font.
+ size_t result = s.mFontListSize + s.mFontTableCacheSize + s.mCharMapsSize;
+
+ if (mIsDataUserFont) {
+ MOZ_ASSERT(mComputedSizeOfUserFont > 0, "user font with no data?");
+ result += mComputedSizeOfUserFont;
+ }
+
+ return result;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// class gfxFontFamily
+//
+//////////////////////////////////////////////////////////////////////////////
+
+// we consider faces with mStandardFace == true to be "less than" those with
+// false, because during style matching, earlier entries are tried first
+class FontEntryStandardFaceComparator {
+ public:
+ bool Equals(const RefPtr<gfxFontEntry>& a,
+ const RefPtr<gfxFontEntry>& b) const {
+ return a->mStandardFace == b->mStandardFace;
+ }
+ bool LessThan(const RefPtr<gfxFontEntry>& a,
+ const RefPtr<gfxFontEntry>& b) const {
+ return (a->mStandardFace == true && b->mStandardFace == false);
+ }
+};
+
+void gfxFontFamily::SortAvailableFonts() {
+ MOZ_ASSERT(mLock.LockedForWritingByCurrentThread());
+ 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 aIgnoreSizeTolerance) {
+ AutoTArray<gfxFontEntry*, 4> matched;
+ FindAllFontsForStyle(aFontStyle, matched, aIgnoreSizeTolerance);
+ if (!matched.IsEmpty()) {
+ return matched[0];
+ }
+ return nullptr;
+}
+
+static inline double WeightStyleStretchDistance(
+ gfxFontEntry* aFontEntry, const gfxFontStyle& aTargetStyle) {
+ double stretchDist =
+ StretchDistance(aFontEntry->Stretch(), aTargetStyle.stretch);
+ double styleDist =
+ StyleDistance(aFontEntry->SlantStyle(), aTargetStyle.style);
+ double weightDist = WeightDistance(aFontEntry->Weight(), aTargetStyle.weight);
+
+ // Sanity-check that the distances are within the expected range
+ // (update if implementation of the distance functions is changed).
+ MOZ_ASSERT(stretchDist >= 0.0 && stretchDist <= 2000.0);
+ MOZ_ASSERT(styleDist >= 0.0 && styleDist <= 500.0);
+ MOZ_ASSERT(weightDist >= 0.0 && weightDist <= 1600.0);
+
+ // weight/style/stretch priority: stretch >> style >> weight
+ // so we multiply the stretch and style values to make them dominate
+ // the result
+ return stretchDist * kStretchFactor + styleDist * kStyleFactor +
+ weightDist * kWeightFactor;
+}
+
+void gfxFontFamily::FindAllFontsForStyle(
+ const gfxFontStyle& aFontStyle, nsTArray<gfxFontEntry*>& aFontEntryList,
+ bool aIgnoreSizeTolerance) {
+ if (!mHasStyles) {
+ FindStyleVariations(); // collect faces for the family, if not already
+ // done
+ }
+
+ AutoReadLock lock(mLock);
+
+ NS_ASSERTION(mAvailableFonts.Length() > 0, "font family with no faces!");
+ NS_ASSERTION(aFontEntryList.IsEmpty(), "non-empty fontlist passed in");
+
+ gfxFontEntry* fe = nullptr;
+
+ // If the family has only one face, we simply return it; no further
+ // checking needed
+ uint32_t count = mAvailableFonts.Length();
+ if (count == 1) {
+ fe = mAvailableFonts[0];
+ aFontEntryList.AppendElement(fe);
+ return;
+ }
+
+ // 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.
+ bool wantBold = aFontStyle.weight >= FontWeight::FromInt(600);
+ bool wantItalic = !aFontStyle.style.IsNormal();
+ uint8_t faceIndex =
+ (wantItalic ? kItalicMask : 0) | (wantBold ? kBoldMask : 0);
+
+ // if the desired style is available, return it directly
+ fe = mAvailableFonts[faceIndex];
+ if (fe) {
+ aFontEntryList.AppendElement(fe);
+ return;
+ }
+
+ // 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) {
+ aFontEntryList.AppendElement(fe);
+ return;
+ }
+ }
+
+ // this can't happen unless we have totally broken the font-list manager!
+ MOZ_ASSERT_UNREACHABLE("no face found in simple font family!");
+ }
+
+ // Pick the font(s) that are closest to the desired weight, style, and
+ // stretch. Iterate over all fonts, measuring the weight/style distance.
+ // Because of unicode-range values, there may be more than one font for a
+ // given but the 99% use case is only a single font entry per
+ // weight/style/stretch distance value. To optimize this, only add entries
+ // to the matched font array when another entry already has the same
+ // weight/style/stretch distance and add the last matched font entry. For
+ // normal platform fonts with a single font entry for each
+ // weight/style/stretch combination, only the last matched font entry will
+ // be added.
+
+ double minDistance = INFINITY;
+ gfxFontEntry* matched = nullptr;
+ // iterate in forward order so that faces like 'Bold' are matched before
+ // matching style distance faces such as 'Bold Outline' (see bug 1185812)
+ for (uint32_t i = 0; i < count; i++) {
+ fe = mAvailableFonts[i];
+ // weight/style/stretch priority: stretch >> style >> weight
+ double distance = WeightStyleStretchDistance(fe, aFontStyle);
+ if (distance < minDistance) {
+ matched = fe;
+ if (!aFontEntryList.IsEmpty()) {
+ aFontEntryList.Clear();
+ }
+ minDistance = distance;
+ } else if (distance == minDistance) {
+ if (matched) {
+ aFontEntryList.AppendElement(matched);
+ }
+ matched = fe;
+ }
+ }
+
+ NS_ASSERTION(matched, "didn't match a font within a family");
+
+ if (matched) {
+ aFontEntryList.AppendElement(matched);
+ }
+}
+
+void gfxFontFamily::CheckForSimpleFamily() {
+ MOZ_ASSERT(mLock.LockedForWritingByCurrentThread());
+ // 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;
+ }
+
+ StretchRange firstStretch = mAvailableFonts[0]->Stretch();
+ if (!firstStretch.IsSingle()) {
+ return; // family with variation fonts is not considered "simple"
+ }
+
+ gfxFontEntry* faces[4] = {0};
+ for (uint8_t i = 0; i < count; ++i) {
+ gfxFontEntry* fe = mAvailableFonts[i];
+ if (fe->Stretch() != firstStretch || fe->IsOblique()) {
+ // simple families don't have varying font-stretch or oblique
+ return;
+ }
+ if (!fe->Weight().IsSingle() || !fe->SlantStyle().IsSingle()) {
+ return; // family with variation fonts is not considered "simple"
+ }
+ uint8_t faceIndex = (fe->IsItalic() ? kItalicMask : 0) |
+ (fe->SupportsBold() ? kBoldMask : 0);
+ if (faces[faceIndex]) {
+ return; // two faces resolve to the same slot; family isn't "simple"
+ }
+ faces[faceIndex] = fe;
+ }
+
+ // we have successfully slotted the available faces into the standard
+ // 4-face framework
+ mAvailableFonts.SetLength(4);
+ for (uint8_t i = 0; i < 4; ++i) {
+ if (mAvailableFonts[i].get() != faces[i]) {
+ mAvailableFonts[i].swap(faces[i]);
+ }
+ }
+
+ mIsSimpleFamily = true;
+}
+
+#ifdef DEBUG
+bool gfxFontFamily::ContainsFace(gfxFontEntry* aFontEntry) {
+ AutoReadLock lock(mLock);
+
+ uint32_t i, numFonts = mAvailableFonts.Length();
+ for (i = 0; i < numFonts; i++) {
+ if (mAvailableFonts[i] == aFontEntry) {
+ return true;
+ }
+ // userfonts contain the actual real font entry
+ if (mAvailableFonts[i] && mAvailableFonts[i]->mIsUserFontContainer) {
+ gfxUserFontEntry* ufe =
+ static_cast<gfxUserFontEntry*>(mAvailableFonts[i].get());
+ if (ufe->GetPlatformFontEntry() == aFontEntry) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+#endif
+
+void gfxFontFamily::LocalizedName(nsACString& aLocalizedName) {
+ // just return the primary name; subclasses should override
+ aLocalizedName = mName;
+}
+
+void gfxFontFamily::FindFontForChar(GlobalFontMatch* aMatchData) {
+ gfxPlatformFontList::PlatformFontList()->mLock.AssertCurrentThreadIn();
+
+ {
+ AutoReadLock lock(mLock);
+ if (mFamilyCharacterMapInitialized && !TestCharacterMap(aMatchData->mCh)) {
+ // none of the faces in the family support the required char,
+ // so bail out immediately
+ return;
+ }
+ }
+
+ nsCString charAndName;
+ if (profiler_thread_is_being_profiled(
+ Combine(ThreadProfilingFeatures::Sampling,
+ ThreadProfilingFeatures::Markers))) {
+ charAndName = nsPrintfCString("\\u%x %s", aMatchData->mCh, mName.get());
+ }
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("gfxFontFamily::FindFontForChar",
+ LAYOUT, charAndName);
+
+ AutoTArray<gfxFontEntry*, 4> entries;
+ FindAllFontsForStyle(aMatchData->mStyle, entries,
+ /*aIgnoreSizeTolerance*/ true);
+ if (entries.IsEmpty()) {
+ return;
+ }
+
+ gfxFontEntry* fe = nullptr;
+ float distance = INFINITY;
+
+ for (auto e : entries) {
+ if (e->SkipDuringSystemFallback()) {
+ continue;
+ }
+
+ aMatchData->mCmapsTested++;
+ if (e->HasCharacter(aMatchData->mCh)) {
+ aMatchData->mCount++;
+
+ LogModule* log = gfxPlatform::GetLog(eGfxLog_textrun);
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Debug))) {
+ intl::Script script =
+ intl::UnicodeProperties::GetScriptCode(aMatchData->mCh);
+ MOZ_LOG(log, LogLevel::Debug,
+ ("(textrun-systemfallback-fonts) char: u+%6.6x "
+ "script: %d match: [%s]\n",
+ aMatchData->mCh, int(script), e->Name().get()));
+ }
+
+ fe = e;
+ distance = WeightStyleStretchDistance(fe, aMatchData->mStyle);
+ if (aMatchData->mPresentation != eFontPresentation::Any) {
+ RefPtr<gfxFont> font = fe->FindOrMakeFont(&aMatchData->mStyle);
+ if (!font) {
+ continue;
+ }
+ bool hasColorGlyph =
+ font->HasColorGlyphFor(aMatchData->mCh, aMatchData->mNextCh);
+ if (hasColorGlyph != PrefersColor(aMatchData->mPresentation)) {
+ distance += kPresentationMismatch;
+ }
+ }
+ break;
+ }
+ }
+
+ if (!fe && !aMatchData->mStyle.IsNormalStyle()) {
+ // If style/weight/stretch was not Normal, see if we can
+ // fall back to a next-best face (e.g. Arial Black -> Bold,
+ // or Arial Narrow -> Regular).
+ GlobalFontMatch data(aMatchData->mCh, aMatchData->mNextCh,
+ aMatchData->mStyle, aMatchData->mPresentation);
+ SearchAllFontsForChar(&data);
+ if (!data.mBestMatch) {
+ return;
+ }
+ fe = data.mBestMatch;
+ distance = data.mMatchDistance;
+ }
+
+ if (!fe) {
+ return;
+ }
+
+ if (distance < aMatchData->mMatchDistance ||
+ (distance == aMatchData->mMatchDistance &&
+ Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) {
+ aMatchData->mBestMatch = fe;
+ aMatchData->mMatchedFamily = this;
+ aMatchData->mMatchDistance = distance;
+ }
+}
+
+void gfxFontFamily::SearchAllFontsForChar(GlobalFontMatch* aMatchData) {
+ if (!mFamilyCharacterMapInitialized) {
+ ReadAllCMAPs();
+ }
+ AutoReadLock lock(mLock);
+ if (!mFamilyCharacterMap.test(aMatchData->mCh)) {
+ return;
+ }
+ uint32_t i, numFonts = mAvailableFonts.Length();
+ for (i = 0; i < numFonts; i++) {
+ gfxFontEntry* fe = mAvailableFonts[i];
+ if (fe && fe->HasCharacter(aMatchData->mCh)) {
+ float distance = WeightStyleStretchDistance(fe, aMatchData->mStyle);
+ if (aMatchData->mPresentation != eFontPresentation::Any) {
+ RefPtr<gfxFont> font = fe->FindOrMakeFont(&aMatchData->mStyle);
+ if (!font) {
+ continue;
+ }
+ bool hasColorGlyph =
+ font->HasColorGlyphFor(aMatchData->mCh, aMatchData->mNextCh);
+ if (hasColorGlyph != PrefersColor(aMatchData->mPresentation)) {
+ distance += kPresentationMismatch;
+ }
+ }
+ if (distance < aMatchData->mMatchDistance ||
+ (distance == aMatchData->mMatchDistance &&
+ Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) {
+ aMatchData->mBestMatch = fe;
+ aMatchData->mMatchedFamily = this;
+ aMatchData->mMatchDistance = distance;
+ }
+ }
+ }
+}
+
+/*virtual*/
+gfxFontFamily::~gfxFontFamily() {
+ // Should not be dropped by stylo, but the InitFontList thread might use
+ // a transient gfxFontFamily and that's OK.
+ MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal());
+}
+
+// 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);
+ AutoTArray<nsCString, 4> otherFamilyNames;
+
+ gfxFontUtils::ReadOtherFamilyNamesForFace(mName, nameData, dataLength,
+ otherFamilyNames, useFullName);
+
+ if (!otherFamilyNames.IsEmpty()) {
+ aPlatformFontList->AddOtherFamilyNames(this, otherFamilyNames);
+ }
+
+ return !otherFamilyNames.IsEmpty();
+}
+
+void gfxFontFamily::ReadOtherFamilyNames(
+ gfxPlatformFontList* aPlatformFontList) {
+ AutoWriteLock lock(mLock);
+ if (mOtherFamilyNamesInitialized) {
+ return;
+ }
+
+ mOtherFamilyNamesInitialized = true;
+
+ FindStyleVariationsLocked();
+
+ // 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);
+ }
+}
+
+static bool LookForLegacyFamilyName(const nsACString& aCanonicalName,
+ const char* aNameData, uint32_t aDataLength,
+ nsACString& aLegacyName /* outparam */) {
+ const gfxFontUtils::NameHeader* nameHeader =
+ reinterpret_cast<const gfxFontUtils::NameHeader*>(aNameData);
+
+ uint32_t nameCount = nameHeader->count;
+ if (nameCount * sizeof(gfxFontUtils::NameRecord) > aDataLength) {
+ NS_WARNING("invalid font (name records)");
+ return false;
+ }
+
+ const gfxFontUtils::NameRecord* nameRecord =
+ reinterpret_cast<const gfxFontUtils::NameRecord*>(
+ 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;
+
+ if (stringsBase + nameOff + nameLen > aDataLength) {
+ NS_WARNING("invalid font (name table strings)");
+ return false;
+ }
+
+ if (uint16_t(nameRecord->nameID) == gfxFontUtils::NAME_ID_FAMILY) {
+ bool ok = gfxFontUtils::DecodeFontName(
+ aNameData + stringsBase + nameOff, nameLen,
+ uint32_t(nameRecord->platformID), uint32_t(nameRecord->encodingID),
+ uint32_t(nameRecord->languageID), aLegacyName);
+ // It's only a legacy name if it case-insensitively differs from the
+ // canonical name (otherwise it would map to the same key).
+ if (ok && !aLegacyName.Equals(aCanonicalName,
+ nsCaseInsensitiveCStringComparator)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool gfxFontFamily::CheckForLegacyFamilyNames(gfxPlatformFontList* aFontList) {
+ aFontList->mLock.AssertCurrentThreadIn();
+ if (mCheckedForLegacyFamilyNames) {
+ // we already did this, so there's nothing more to add
+ return false;
+ }
+ mCheckedForLegacyFamilyNames = true;
+ bool added = false;
+ const uint32_t kNAME = TRUETYPE_TAG('n', 'a', 'm', 'e');
+ AutoTArray<RefPtr<gfxFontEntry>, 16> faces;
+ {
+ // Take a local copy of the array of font entries, because it's possible
+ // AddWithLegacyFamilyName will mutate it (and it needs to be able to take
+ // an exclusive lock on the family to do so, so we release the read lock
+ // here).
+ AutoReadLock lock(mLock);
+ faces.AppendElements(mAvailableFonts);
+ }
+ for (const auto& fe : faces) {
+ if (!fe) {
+ continue;
+ }
+ gfxFontEntry::AutoTable nameTable(fe, kNAME);
+ if (!nameTable) {
+ continue;
+ }
+ nsAutoCString legacyName;
+ uint32_t dataLength;
+ const char* nameData = hb_blob_get_data(nameTable, &dataLength);
+ if (LookForLegacyFamilyName(Name(), nameData, dataLength, legacyName)) {
+ if (aFontList->AddWithLegacyFamilyName(legacyName, fe, mVisibility)) {
+ added = true;
+ }
+ }
+ }
+ return added;
+}
+
+void gfxFontFamily::ReadFaceNames(gfxPlatformFontList* aPlatformFontList,
+ bool aNeedFullnamePostscriptNames,
+ FontInfoData* aFontInfoData) {
+ aPlatformFontList->mLock.AssertCurrentThreadIn();
+
+ // if all needed names have already been read, skip
+ if (mOtherFamilyNamesInitialized &&
+ (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) {
+ return;
+ }
+
+ AutoWriteLock lock(mLock);
+
+ bool asyncFontLoaderDisabled = false;
+
+ if (!mOtherFamilyNamesInitialized && aFontInfoData &&
+ aFontInfoData->mLoadOtherNames && !asyncFontLoaderDisabled) {
+ const auto* otherFamilyNames = aFontInfoData->GetOtherFamilyNames(mName);
+ if (otherFamilyNames && otherFamilyNames->Length()) {
+ aPlatformFontList->AddOtherFamilyNames(this, *otherFamilyNames);
+ }
+ mOtherFamilyNamesInitialized = true;
+ }
+
+ // if all needed data has been initialized, return
+ if (mOtherFamilyNamesInitialized &&
+ (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) {
+ return;
+ }
+
+ FindStyleVariationsLocked(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;
+ }
+
+ nsAutoCString fullname, psname;
+ bool foundFaceNames = false;
+ if (!mFaceNamesInitialized && aNeedFullnamePostscriptNames &&
+ aFontInfoData && aFontInfoData->mLoadFaceNames) {
+ aFontInfoData->GetFaceNames(fe->Name(), fullname, psname);
+ if (!fullname.IsEmpty()) {
+ aPlatformFontList->AddFullnameLocked(fe, fullname);
+ }
+ if (!psname.IsEmpty()) {
+ aPlatformFontList->AddPostscriptNameLocked(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->AddFullnameLocked(fe, fullname);
+ }
+
+ if (gfxFontUtils::ReadCanonicalName(
+ nameTable, gfxFontUtils::NAME_ID_POSTSCRIPT, psname) == NS_OK) {
+ aPlatformFontList->AddPostscriptNameLocked(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 nsACString& aFontName,
+ const nsCStringComparator& aCmp) const {
+ // find the font using a simple linear search
+ AutoReadLock lock(mLock);
+ uint32_t numFonts = mAvailableFonts.Length();
+ for (uint32_t i = 0; i < numFonts; i++) {
+ gfxFontEntry* fe = mAvailableFonts[i].get();
+ if (fe && fe->Name().Equals(aFontName, aCmp)) {
+ return fe;
+ }
+ }
+ return nullptr;
+}
+
+void gfxFontFamily::ReadAllCMAPs(FontInfoData* aFontInfoData) {
+ AutoWriteLock lock(mLock);
+ FindStyleVariationsLocked(aFontInfoData);
+
+ uint32_t i, numFonts = mAvailableFonts.Length();
+ for (i = 0; i < numFonts; i++) {
+ gfxFontEntry* fe = mAvailableFonts[i];
+ // don't try to load cmaps for downloadable fonts not yet loaded
+ if (!fe || fe->mIsUserFontContainer) {
+ continue;
+ }
+ fe->ReadCMAP(aFontInfoData);
+ mFamilyCharacterMap.Union(*(fe->GetCharacterMap()));
+ }
+ mFamilyCharacterMap.Compact();
+ mFamilyCharacterMapInitialized = true;
+}
+
+void gfxFontFamily::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const {
+ AutoReadLock lock(mLock);
+ aSizes->mFontListSize += mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ aSizes->mCharMapsSize +=
+ mFamilyCharacterMap.SizeOfExcludingThis(aMallocSizeOf);
+
+ aSizes->mFontListSize +=
+ mAvailableFonts.ShallowSizeOfExcludingThis(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);
+}