From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- gfx/thebes/CoreTextFontList.cpp | 1823 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1823 insertions(+) create mode 100644 gfx/thebes/CoreTextFontList.cpp (limited to 'gfx/thebes/CoreTextFontList.cpp') diff --git a/gfx/thebes/CoreTextFontList.cpp b/gfx/thebes/CoreTextFontList.cpp new file mode 100644 index 0000000000..0c8c179e8e --- /dev/null +++ b/gfx/thebes/CoreTextFontList.cpp @@ -0,0 +1,1823 @@ +/* -*- 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 "AppleUtils.h" +#include "CoreTextFontList.h" +#include "gfxFontConstants.h" +#include "gfxMacFont.h" +#include "gfxUserFontSet.h" + +#include "harfbuzz/hb.h" + +#include "MainThreadUtils.h" + +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/Telemetry.h" + +#include "nsAppDirectoryServiceDefs.h" +#include "nsCharTraits.h" +#include "nsComponentManagerUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsIDirectoryEnumerator.h" +#include "nsServiceManagerUtils.h" +#include "SharedFontList-impl.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +// List generated by diffing the arrays returned by +// CTFontManagerCopyAvailableFontFamilyNames() when built with +// MACOSX_DEPLOYMENT_TARGET=10.12 vs 11.0, to identify the font family names +// that Core Text is treating as "deprecated" and hiding from the app on newer +// systems. +constexpr nsLiteralCString kDeprecatedFontFamilies[] = { + // Dot-prefixed font families are supposed to be hidden from the + // user-visible + // font list anyhow, so we don't need to add them here. + // ".Al Bayan PUA"_ns, + // ".Al Nile PUA"_ns, + // ".Al Tarikh PUA"_ns, + // ".Apple Color Emoji UI"_ns, + // ".Apple SD Gothic NeoI"_ns, + // ".Aqua Kana"_ns, + // ".Arial Hebrew Desk Interface"_ns, + // ".Baghdad PUA"_ns, + // ".Beirut PUA"_ns, + // ".Damascus PUA"_ns, + // ".DecoType Naskh PUA"_ns, + // ".Diwan Kufi PUA"_ns, + // ".Farah PUA"_ns, + // ".Geeza Pro Interface"_ns, + // ".Geeza Pro PUA"_ns, + // ".Helvetica LT MM"_ns, + // ".Hiragino Kaku Gothic Interface"_ns, + // ".Hiragino Sans GB Interface"_ns, + // ".Keyboard"_ns, + // ".KufiStandardGK PUA"_ns, + // ".LastResort"_ns, + // ".Lucida Grande UI"_ns, + // ".Muna PUA"_ns, + // ".Nadeem PUA"_ns, + // ".New York"_ns, + // ".Noto Nastaliq Urdu UI"_ns, + // ".PingFang HK"_ns, + // ".PingFang SC"_ns, + // ".PingFang TC"_ns, + // ".Sana PUA"_ns, + // ".Savoye LET CC."_ns, + // ".SF Arabic"_ns, + // ".SF Compact Rounded"_ns, + // ".SF Compact"_ns, + // ".SF NS Mono"_ns, + // ".SF NS Rounded"_ns, + // ".SF NS"_ns, + // ".Times LT MM"_ns, + "Hiragino Kaku Gothic Pro"_ns, + "Hiragino Kaku Gothic ProN"_ns, + "Hiragino Kaku Gothic Std"_ns, + "Hiragino Kaku Gothic StdN"_ns, + "Hiragino Maru Gothic Pro"_ns, + "Hiragino Mincho Pro"_ns, + "Iowan Old Style"_ns, + "Noto Sans Adlam"_ns, + "Noto Sans Armenian"_ns, + "Noto Sans Avestan"_ns, + "Noto Sans Bamum"_ns, + "Noto Sans Bassa Vah"_ns, + "Noto Sans Batak"_ns, + "Noto Sans Bhaiksuki"_ns, + "Noto Sans Brahmi"_ns, + "Noto Sans Buginese"_ns, + "Noto Sans Buhid"_ns, + "Noto Sans Carian"_ns, + "Noto Sans Caucasian Albanian"_ns, + "Noto Sans Chakma"_ns, + "Noto Sans Cham"_ns, + "Noto Sans Coptic"_ns, + "Noto Sans Cuneiform"_ns, + "Noto Sans Cypriot"_ns, + "Noto Sans Duployan"_ns, + "Noto Sans Egyptian Hieroglyphs"_ns, + "Noto Sans Elbasan"_ns, + "Noto Sans Glagolitic"_ns, + "Noto Sans Gothic"_ns, + "Noto Sans Gunjala Gondi"_ns, + "Noto Sans Hanifi Rohingya"_ns, + "Noto Sans Hanunoo"_ns, + "Noto Sans Hatran"_ns, + "Noto Sans Imperial Aramaic"_ns, + "Noto Sans Inscriptional Pahlavi"_ns, + "Noto Sans Inscriptional Parthian"_ns, + "Noto Sans Javanese"_ns, + "Noto Sans Kaithi"_ns, + "Noto Sans Kayah Li"_ns, + "Noto Sans Kharoshthi"_ns, + "Noto Sans Khojki"_ns, + "Noto Sans Khudawadi"_ns, + "Noto Sans Lepcha"_ns, + "Noto Sans Limbu"_ns, + "Noto Sans Linear A"_ns, + "Noto Sans Linear B"_ns, + "Noto Sans Lisu"_ns, + "Noto Sans Lycian"_ns, + "Noto Sans Lydian"_ns, + "Noto Sans Mahajani"_ns, + "Noto Sans Mandaic"_ns, + "Noto Sans Manichaean"_ns, + "Noto Sans Marchen"_ns, + "Noto Sans Masaram Gondi"_ns, + "Noto Sans Meetei Mayek"_ns, + "Noto Sans Mende Kikakui"_ns, + "Noto Sans Meroitic"_ns, + "Noto Sans Miao"_ns, + "Noto Sans Modi"_ns, + "Noto Sans Mongolian"_ns, + "Noto Sans Mro"_ns, + "Noto Sans Multani"_ns, + "Noto Sans Nabataean"_ns, + "Noto Sans New Tai Lue"_ns, + "Noto Sans Newa"_ns, + "Noto Sans NKo"_ns, + "Noto Sans Ol Chiki"_ns, + "Noto Sans Old Hungarian"_ns, + "Noto Sans Old Italic"_ns, + "Noto Sans Old North Arabian"_ns, + "Noto Sans Old Permic"_ns, + "Noto Sans Old Persian"_ns, + "Noto Sans Old South Arabian"_ns, + "Noto Sans Old Turkic"_ns, + "Noto Sans Osage"_ns, + "Noto Sans Osmanya"_ns, + "Noto Sans Pahawh Hmong"_ns, + "Noto Sans Palmyrene"_ns, + "Noto Sans Pau Cin Hau"_ns, + "Noto Sans PhagsPa"_ns, + "Noto Sans Phoenician"_ns, + "Noto Sans Psalter Pahlavi"_ns, + "Noto Sans Rejang"_ns, + "Noto Sans Samaritan"_ns, + "Noto Sans Saurashtra"_ns, + "Noto Sans Sharada"_ns, + "Noto Sans Siddham"_ns, + "Noto Sans Sora Sompeng"_ns, + "Noto Sans Sundanese"_ns, + "Noto Sans Syloti Nagri"_ns, + "Noto Sans Syriac"_ns, + "Noto Sans Tagalog"_ns, + "Noto Sans Tagbanwa"_ns, + "Noto Sans Tai Le"_ns, + "Noto Sans Tai Tham"_ns, + "Noto Sans Tai Viet"_ns, + "Noto Sans Takri"_ns, + "Noto Sans Thaana"_ns, + "Noto Sans Tifinagh"_ns, + "Noto Sans Tirhuta"_ns, + "Noto Sans Ugaritic"_ns, + "Noto Sans Vai"_ns, + "Noto Sans Wancho"_ns, + "Noto Sans Warang Citi"_ns, + "Noto Sans Yi"_ns, + "Noto Sans Zawgyi"_ns, + "Noto Serif Ahom"_ns, + "Noto Serif Balinese"_ns, + "Noto Serif Yezidi"_ns, + "Athelas"_ns, + "Courier"_ns, + "Marion"_ns, + "Seravek"_ns, + "Superclarendon"_ns, + "Times"_ns, +}; + +static void GetStringForCFString(CFStringRef aSrc, nsAString& aDest) { + auto len = CFStringGetLength(aSrc); + aDest.SetLength(len); + CFStringGetCharacters(aSrc, CFRangeMake(0, len), + (UniChar*)aDest.BeginWriting()); +} + +static CFStringRef CreateCFStringForString(const nsACString& aSrc) { + return CFStringCreateWithBytes(kCFAllocatorDefault, + (const UInt8*)aSrc.BeginReading(), + aSrc.Length(), kCFStringEncodingUTF8, false); +} + +#define LOG_FONTLIST(args) \ + MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), mozilla::LogLevel::Debug, args) +#define LOG_FONTLIST_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontlist), mozilla::LogLevel::Debug) +#define LOG_CMAPDATA_ENABLED() \ + MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_cmapdata), mozilla::LogLevel::Debug) + +#pragma mark - + +// Complex scripts will not render correctly unless appropriate AAT or OT +// layout tables are present. +// For OpenType, we also check that the GSUB table supports the relevant +// script tag, to avoid using things like Arial Unicode MS for Lao (it has +// the characters, but lacks OpenType support). + +// TODO: consider whether we should move this to gfxFontEntry and do similar +// cmap-masking on other platforms to avoid using fonts that won't shape +// properly. + +nsresult CTFontEntry::ReadCMAP(FontInfoData* aFontInfoData) { + // attempt this once, if errors occur leave a blank cmap + if (mCharacterMap || mShmemCharacterMap) { + return NS_OK; + } + + RefPtr charmap; + nsresult rv; + + uint32_t uvsOffset = 0; + if (aFontInfoData && + (charmap = GetCMAPFromFontInfo(aFontInfoData, uvsOffset))) { + rv = NS_OK; + } else { + uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p'); + charmap = new gfxCharacterMap(); + AutoTable cmapTable(this, kCMAP); + + if (cmapTable) { + uint32_t cmapLen; + const uint8_t* cmapData = reinterpret_cast( + hb_blob_get_data(cmapTable, &cmapLen)); + rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, uvsOffset); + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + } + mUVSOffset.exchange(uvsOffset); + + if (NS_SUCCEEDED(rv) && !mIsDataUserFont && !HasGraphiteTables()) { + // For downloadable fonts, trust the author and don't + // try to munge the cmap based on script shaping support. + + // We also assume a Graphite font knows what it's doing, + // and provides whatever shaping is needed for the + // characters it supports, so only check/clear the + // complex-script ranges for non-Graphite fonts + + // for layout support, check for the presence of mort/morx/kerx and/or + // opentype layout tables + bool hasAATLayout = HasFontTable(TRUETYPE_TAG('m', 'o', 'r', 'x')) || + HasFontTable(TRUETYPE_TAG('m', 'o', 'r', 't')); + bool hasAppleKerning = HasFontTable(TRUETYPE_TAG('k', 'e', 'r', 'x')); + bool hasGSUB = HasFontTable(TRUETYPE_TAG('G', 'S', 'U', 'B')); + bool hasGPOS = HasFontTable(TRUETYPE_TAG('G', 'P', 'O', 'S')); + if ((hasAATLayout && !(hasGSUB || hasGPOS)) || hasAppleKerning) { + mRequiresAAT = true; // prefer CoreText if font has no OTL tables, + // or if it uses the Apple-specific 'kerx' + // variant of kerning table + } + + for (const ScriptRange* sr = gfxPlatformFontList::sComplexScriptRanges; + sr->rangeStart; sr++) { + // check to see if the cmap includes complex script codepoints + if (charmap->TestRange(sr->rangeStart, sr->rangeEnd)) { + if (hasAATLayout) { + // prefer CoreText for Apple's complex-script fonts, + // even if they also have some OpenType tables + // (e.g. Geeza Pro Bold on 10.6; see bug 614903) + mRequiresAAT = true; + // and don't mask off complex-script ranges, we assume + // the AAT tables will provide the necessary shaping + continue; + } + + // We check for GSUB here, as GPOS alone would not be ok. + if (hasGSUB && SupportsScriptInGSUB(sr->tags, sr->numTags)) { + continue; + } + + charmap->ClearRange(sr->rangeStart, sr->rangeEnd); + } + } + + // Bug 1360309, 1393624: several of Apple's Chinese fonts have spurious + // blank glyphs for obscure Tibetan and Arabic-script codepoints. + // Blocklist these so that font fallback will not use them. + if (mRequiresAAT && + (FamilyName().EqualsLiteral("Songti SC") || + FamilyName().EqualsLiteral("Songti TC") || + FamilyName().EqualsLiteral("STSong") || + // Bug 1390980: on 10.11, the Kaiti fonts are also affected. + FamilyName().EqualsLiteral("Kaiti SC") || + FamilyName().EqualsLiteral("Kaiti TC") || + FamilyName().EqualsLiteral("STKaiti"))) { + charmap->ClearRange(0x0f6b, 0x0f70); + charmap->ClearRange(0x0f8c, 0x0f8f); + charmap->clear(0x0f98); + charmap->clear(0x0fbd); + charmap->ClearRange(0x0fcd, 0x0fff); + charmap->clear(0x0620); + charmap->clear(0x065f); + charmap->ClearRange(0x06ee, 0x06ef); + charmap->clear(0x06ff); + } + } + + bool setCharMap = true; + if (NS_SUCCEEDED(rv)) { + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + fontlist::FontList* sharedFontList = pfl->SharedFontList(); + if (!IsUserFont() && mShmemFace && mShmemFamily) { + mShmemFace->SetCharacterMap(sharedFontList, charmap, mShmemFamily); + if (TrySetShmemCharacterMap()) { + setCharMap = false; + } + } else { + charmap = pfl->FindCharMap(charmap); + } + mHasCmapTable = true; + } else { + // if error occurred, initialize to null cmap + charmap = new gfxCharacterMap(); + mHasCmapTable = false; + } + if (setCharMap) { + // Temporarily retain charmap, until the shared version is + // ready for use. + if (mCharacterMap.compareExchange(nullptr, charmap.get())) { + charmap.get()->AddRef(); + } + } + + LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %zu hash: %8.8x%s\n", + mName.get(), charmap->SizeOfIncludingThis(moz_malloc_size_of), + charmap->mHash, mCharacterMap == charmap ? " new" : "")); + if (LOG_CMAPDATA_ENABLED()) { + char prefix[256]; + SprintfLiteral(prefix, "(cmapdata) name: %.220s", mName.get()); + charmap->Dump(prefix, eGfxLog_cmapdata); + } + + return rv; +} + +gfxFont* CTFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle) { + RefPtr unscaledFont(mUnscaledFont); + if (!unscaledFont) { + CGFontRef baseFont = GetFontRef(); + if (!baseFont) { + return nullptr; + } + unscaledFont = new UnscaledFontMac(baseFont, mIsDataUserFont); + mUnscaledFont = unscaledFont; + } + + return new gfxMacFont(unscaledFont, this, aFontStyle); +} + +bool CTFontEntry::HasVariations() { + if (!mHasVariationsInitialized) { + mHasVariationsInitialized = true; + mHasVariations = gfxPlatform::HasVariationFontSupport() && + HasFontTable(TRUETYPE_TAG('f', 'v', 'a', 'r')); + } + + return mHasVariations; +} + +void CTFontEntry::GetVariationAxes( + nsTArray& aVariationAxes) { + // We could do this by creating a CTFont and calling CTFontCopyVariationAxes, + // but it is expensive to instantiate a CTFont for every face just to set up + // the axis information. + // Instead we use gfxFontUtils to read the font tables directly. + gfxFontUtils::GetVariationData(this, &aVariationAxes, nullptr); +} + +void CTFontEntry::GetVariationInstances( + nsTArray& aInstances) { + // Core Text doesn't offer API for this, so we use gfxFontUtils to read the + // font tables directly. + gfxFontUtils::GetVariationData(this, nullptr, &aInstances); +} + +bool CTFontEntry::IsCFF() { + if (!mIsCFFInitialized) { + mIsCFFInitialized = true; + mIsCFF = HasFontTable(TRUETYPE_TAG('C', 'F', 'F', ' ')); + } + + return mIsCFF; +} + +CTFontEntry::CTFontEntry(const nsACString& aPostscriptName, WeightRange aWeight, + bool aIsStandardFace, double aSizeHint) + : gfxFontEntry(aPostscriptName, aIsStandardFace), + mFontRef(NULL), + mSizeHint(aSizeHint), + mFontRefInitialized(false), + mRequiresAAT(false), + mIsCFF(false), + mIsCFFInitialized(false), + mHasVariations(false), + mHasVariationsInitialized(false), + mHasAATSmallCaps(false), + mHasAATSmallCapsInitialized(false) { + mWeightRange = aWeight; + mOpszAxis.mTag = 0; +} + +CTFontEntry::CTFontEntry(const nsACString& aPostscriptName, CGFontRef aFontRef, + WeightRange aWeight, StretchRange aStretch, + SlantStyleRange aStyle, bool aIsDataUserFont, + bool aIsLocalUserFont) + : gfxFontEntry(aPostscriptName, false), + mFontRef(NULL), + mSizeHint(0.0), + mFontRefInitialized(false), + mRequiresAAT(false), + mIsCFF(false), + mIsCFFInitialized(false), + mHasVariations(false), + mHasVariationsInitialized(false), + mHasAATSmallCaps(false), + mHasAATSmallCapsInitialized(false) { + mFontRef = aFontRef; + mFontRefInitialized = true; + CFRetain(mFontRef); + + mWeightRange = aWeight; + mStretchRange = aStretch; + mFixedPitch = false; // xxx - do we need this for downloaded fonts? + mStyleRange = aStyle; + mOpszAxis.mTag = 0; + + NS_ASSERTION(!(aIsDataUserFont && aIsLocalUserFont), + "userfont is either a data font or a local font"); + mIsDataUserFont = aIsDataUserFont; + mIsLocalUserFont = aIsLocalUserFont; +} + +gfxFontEntry* CTFontEntry::Clone() const { + MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!"); + CTFontEntry* fe = new CTFontEntry(Name(), Weight(), mStandardFace, mSizeHint); + fe->mStyleRange = mStyleRange; + fe->mStretchRange = mStretchRange; + fe->mFixedPitch = mFixedPitch; + return fe; +} + +CGFontRef CTFontEntry::GetFontRef() { + { + AutoReadLock lock(mLock); + if (mFontRefInitialized) { + return mFontRef; + } + } + AutoWriteLock lock(mLock); + if (!mFontRefInitialized) { + // Cache the CGFontRef, to be released by our destructor. + mFontRef = CreateOrCopyFontRef(); + mFontRefInitialized = true; + } + // Return a non-retained reference; caller does not need to release. + return mFontRef; +} + +CGFontRef CTFontEntry::CreateOrCopyFontRef() { + if (mFontRef) { + // We have a cached CGFont, just add a reference. Caller must + // release, but we'll still own our reference. + ::CGFontRetain(mFontRef); + return mFontRef; + } + + CrashReporter::AutoAnnotateCrashReport autoFontName( + CrashReporter::Annotation::FontName, mName); + + // Create a new CGFont; caller will own the only reference to it. + AutoCFRelease psname = CreateCFStringForString(mName); + if (!psname) { + return nullptr; + } + + CGFontRef ref = CGFontCreateWithFontName(psname); + return ref; // Not saved in mFontRef; caller will own the reference +} + +// For a logging build, we wrap the CFDataRef in a FontTableRec so that we can +// use the MOZ_COUNT_[CD]TOR macros in it. A release build without logging +// does not get this overhead. +class FontTableRec { + public: + explicit FontTableRec(CFDataRef aDataRef) : mDataRef(aDataRef) { + MOZ_COUNT_CTOR(FontTableRec); + } + + ~FontTableRec() { + MOZ_COUNT_DTOR(FontTableRec); + CFRelease(mDataRef); + } + + private: + CFDataRef mDataRef; +}; + +/*static*/ void CTFontEntry::DestroyBlobFunc(void* aUserData) { +#ifdef NS_BUILD_REFCNT_LOGGING + FontTableRec* ftr = static_cast(aUserData); + delete ftr; +#else + CFRelease((CFDataRef)aUserData); +#endif +} + +hb_blob_t* CTFontEntry::GetFontTable(uint32_t aTag) { + mLock.ReadLock(); + AutoCFRelease fontRef = CreateOrCopyFontRef(); + mLock.ReadUnlock(); + if (!fontRef) { + return nullptr; + } + + CFDataRef dataRef = ::CGFontCopyTableForTag(fontRef, aTag); + if (dataRef) { + return hb_blob_create((const char*)CFDataGetBytePtr(dataRef), + CFDataGetLength(dataRef), HB_MEMORY_MODE_READONLY, +#ifdef NS_BUILD_REFCNT_LOGGING + new FontTableRec(dataRef), +#else + (void*)dataRef, +#endif + DestroyBlobFunc); + } + + return nullptr; +} + +bool CTFontEntry::HasFontTable(uint32_t aTableTag) { + { + // If we've already initialized mAvailableTables, we can return without + // needing to take an exclusive lock. + AutoReadLock lock(mLock); + if (mAvailableTables.Count()) { + return mAvailableTables.GetEntry(aTableTag); + } + } + + AutoWriteLock lock(mLock); + if (mAvailableTables.Count() == 0) { + AutoCFRelease fontRef = CreateOrCopyFontRef(); + if (!fontRef) { + return false; + } + AutoCFRelease tags = ::CGFontCopyTableTags(fontRef); + if (!tags) { + return false; + } + int numTags = (int)CFArrayGetCount(tags); + for (int t = 0; t < numTags; t++) { + uint32_t tag = (uint32_t)(uintptr_t)CFArrayGetValueAtIndex(tags, t); + mAvailableTables.PutEntry(tag); + } + } + + return mAvailableTables.GetEntry(aTableTag); +} + +static bool CheckForAATSmallCaps(CFArrayRef aFeatures) { + // Walk the array of feature descriptors from the font, and see whether + // a small-caps feature setting is available. + // Just bail out (returning false) if at any point we fail to find the + // expected dictionary keys, etc; if the font has bad data, we don't even + // try to search the rest of it. + auto numFeatures = CFArrayGetCount(aFeatures); + for (auto f = 0; f < numFeatures; ++f) { + auto featureDict = (CFDictionaryRef)CFArrayGetValueAtIndex(aFeatures, f); + if (!featureDict) { + return false; + } + auto featureNum = (CFNumberRef)CFDictionaryGetValue( + featureDict, CFSTR("CTFeatureTypeIdentifier")); + if (!featureNum) { + return false; + } + int16_t featureType; + if (!CFNumberGetValue(featureNum, kCFNumberSInt16Type, &featureType)) { + return false; + } + if (featureType == kLetterCaseType || featureType == kLowerCaseType) { + // Which selector to look for, depending whether we've found the + // legacy LetterCase feature or the new LowerCase one. + const uint16_t smallCaps = (featureType == kLetterCaseType) + ? kSmallCapsSelector + : kLowerCaseSmallCapsSelector; + auto selectors = (CFArrayRef)CFDictionaryGetValue( + featureDict, CFSTR("CTFeatureTypeSelectors")); + if (!selectors) { + return false; + } + auto numSelectors = CFArrayGetCount(selectors); + for (auto s = 0; s < numSelectors; s++) { + auto selectorDict = + (CFDictionaryRef)CFArrayGetValueAtIndex(selectors, s); + if (!selectorDict) { + return false; + } + auto selectorNum = (CFNumberRef)CFDictionaryGetValue( + selectorDict, CFSTR("CTFeatureSelectorIdentifier")); + if (!selectorNum) { + return false; + } + int16_t selectorValue; + if (!CFNumberGetValue(selectorNum, kCFNumberSInt16Type, + &selectorValue)) { + return false; + } + if (selectorValue == smallCaps) { + return true; + } + } + } + } + return false; +} + +bool CTFontEntry::SupportsOpenTypeFeature(Script aScript, + uint32_t aFeatureTag) { + // If we're going to shape with Core Text, we don't support added + // OpenType features (aside from any CT applies by default), except + // for 'smcp' which we map to an AAT feature selector. + if (RequiresAATLayout()) { + if (aFeatureTag != HB_TAG('s', 'm', 'c', 'p')) { + return false; + } + if (mHasAATSmallCapsInitialized) { + return mHasAATSmallCaps; + } + mHasAATSmallCapsInitialized = true; + CGFontRef cgFont = GetFontRef(); + if (!cgFont) { + return mHasAATSmallCaps; + } + + CrashReporter::AutoAnnotateCrashReport autoFontName( + CrashReporter::Annotation::FontName, FamilyName()); + + AutoCFRelease ctFont = + CTFontCreateWithGraphicsFont(cgFont, 0.0, nullptr, nullptr); + if (ctFont) { + AutoCFRelease features = CTFontCopyFeatures(ctFont); + if (features) { + mHasAATSmallCaps = CheckForAATSmallCaps(features); + } + } + return mHasAATSmallCaps; + } + return gfxFontEntry::SupportsOpenTypeFeature(aScript, aFeatureTag); +} + +void CTFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const { + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +static CTFontDescriptorRef CreateDescriptorForFamily( + const nsACString& aFamilyName, bool aNormalized) { + AutoCFRelease family = CreateCFStringForString(aFamilyName); + const void* values[] = {family}; + const void* keys[] = {kCTFontFamilyNameAttribute}; + AutoCFRelease attributes = CFDictionaryCreate( + kCFAllocatorDefault, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + // Not AutoCFRelease, because we might return it. + CTFontDescriptorRef descriptor = + CTFontDescriptorCreateWithAttributes(attributes); + + if (aNormalized) { + CTFontDescriptorRef normalized = + CTFontDescriptorCreateMatchingFontDescriptor(descriptor, nullptr); + if (normalized) { + CFRelease(descriptor); + return normalized; + } + } + + return descriptor; +} + +void CTFontFamily::LocalizedName(nsACString& aLocalizedName) { + AutoCFRelease descriptor = + CreateDescriptorForFamily(mName, true); + if (descriptor) { + AutoCFRelease name = + static_cast(CTFontDescriptorCopyLocalizedAttribute( + descriptor, kCTFontFamilyNameAttribute, nullptr)); + if (name) { + nsAutoString localized; + GetStringForCFString(name, localized); + if (!localized.IsEmpty()) { + CopyUTF16toUTF8(localized, aLocalizedName); + return; + } + } + } + + // failed to get localized name, just use the canonical one + aLocalizedName = mName; +} + +// Return the CSS weight value to use for the given face, overriding what +// AppKit gives us (used to adjust families with bad weight values, see +// bug 931426). +// A return value of 0 indicates no override - use the existing weight. +static inline int GetWeightOverride(const nsAString& aPSName) { + nsAutoCString prefName("font.weight-override."); + // The PostScript name is required to be ASCII; if it's not, the font is + // broken anyway, so we really don't care that this is lossy. + LossyAppendUTF16toASCII(aPSName, prefName); + return Preferences::GetInt(prefName.get(), 0); +} + +// The Core Text weight trait is documented as +// +// ...a float value between -1.0 and 1.0 for normalized weight. +// The value of 0.0 corresponds to the regular or medium font weight. +// +// (https://developer.apple.com/documentation/coretext/kctfontweighttrait) +// +// CSS 'normal' font-weight is defined as 400, so we map 0.0 to this. +// The exact mapping to use for other values is not well defined; the table +// here is empirically determined by looking at what Core Text returns for +// the various system fonts that have a range of weights. +static inline int32_t CoreTextWeightToCSSWeight(CGFloat aCTWeight) { + using Mapping = std::pair; + constexpr Mapping kCoreTextToCSSWeights[] = { + // clang-format off + {-1.0, 1}, + {-0.8, 100}, + {-0.6, 200}, + {-0.4, 300}, + {0.0, 400}, // standard 'regular' weight + {0.23, 500}, + {0.3, 600}, + {0.4, 700}, // standard 'bold' weight + {0.56, 800}, + {0.62, 900}, // Core Text seems to return 0.62 for faces with both + // usWeightClass=800 and 900 in their OS/2 tables! + // We use 900 as there are also fonts that return 0.56, + // so we want an intermediate value for that. + {1.0, 1000}, + // clang-format on + }; + const auto* begin = &kCoreTextToCSSWeights[0]; + const auto* end = begin + ArrayLength(kCoreTextToCSSWeights); + auto m = std::upper_bound(begin, end, aCTWeight, + [](CGFloat aValue, const Mapping& aMapping) { + return aValue <= aMapping.first; + }); + if (m == end) { + NS_WARNING("Core Text weight out of range"); + return 1000; + } + if (m->first == aCTWeight || m == begin) { + return m->second; + } + // Interpolate between the preceding and found entries: + const auto* prev = m - 1; + const auto t = (aCTWeight - prev->first) / (m->first - prev->first); + return NS_round(prev->second * (1.0 - t) + m->second * t); +} + +// The Core Text width trait is documented as +// +// ...a float between -1.0 and 1.0. The value of 0.0 corresponds to regular +// glyph spacing, and negative values represent condensed glyph spacing +// +// (https://developer.apple.com/documentation/coretext/kctfontweighttrait) +// +// CSS 'normal' font-stretch is 100%; 'ultra-expanded' is 200%, and 'ultra- +// condensed' is 50%. We map the extremes of the Core Text trait to these +// values, and interpolate in between these and normal. +static inline FontStretch CoreTextWidthToCSSStretch(CGFloat aCTWidth) { + if (aCTWidth >= 0.0) { + return FontStretch::FromFloat(100.0 + aCTWidth * 100.0); + } + return FontStretch::FromFloat(100.0 + aCTWidth * 50.0); +} + +void CTFontFamily::AddFace(CTFontDescriptorRef aFace) { + AutoCFRelease psname = + (CFStringRef)CTFontDescriptorCopyAttribute(aFace, kCTFontNameAttribute); + AutoCFRelease facename = + (CFStringRef)CTFontDescriptorCopyAttribute(aFace, + kCTFontStyleNameAttribute); + + AutoCFRelease traitsDict = + (CFDictionaryRef)CTFontDescriptorCopyAttribute(aFace, + kCTFontTraitsAttribute); + CFNumberRef weight = + (CFNumberRef)CFDictionaryGetValue(traitsDict, kCTFontWeightTrait); + CFNumberRef width = + (CFNumberRef)CFDictionaryGetValue(traitsDict, kCTFontWidthTrait); + CFNumberRef symbolicTraits = + (CFNumberRef)CFDictionaryGetValue(traitsDict, kCTFontSymbolicTrait); + + bool isStandardFace = false; + + // make a nsString + nsAutoString postscriptFontName; + GetStringForCFString(psname, postscriptFontName); + + int32_t cssWeight = GetWeightOverride(postscriptFontName); + if (cssWeight) { + // scale down and clamp, to get a value from 1..9 + cssWeight = ((cssWeight + 50) / 100); + cssWeight = std::max(1, std::min(cssWeight, 9)); + cssWeight *= 100; // scale up to CSS values + } else { + CGFloat weightValue; + CFNumberGetValue(weight, kCFNumberCGFloatType, &weightValue); + cssWeight = CoreTextWeightToCSSWeight(weightValue); + } + + if (kCFCompareEqualTo == CFStringCompare(facename, CFSTR("Regular"), 0) || + kCFCompareEqualTo == CFStringCompare(facename, CFSTR("Bold"), 0) || + kCFCompareEqualTo == CFStringCompare(facename, CFSTR("Italic"), 0) || + kCFCompareEqualTo == CFStringCompare(facename, CFSTR("Oblique"), 0) || + kCFCompareEqualTo == CFStringCompare(facename, CFSTR("Bold Italic"), 0) || + kCFCompareEqualTo == + CFStringCompare(facename, CFSTR("Bold Oblique"), 0)) { + isStandardFace = true; + } + + // create a font entry + CTFontEntry* fontEntry = new CTFontEntry( + NS_ConvertUTF16toUTF8(postscriptFontName), + WeightRange(FontWeight::FromInt(cssWeight)), isStandardFace); + + CGFloat widthValue; + CFNumberGetValue(width, kCFNumberCGFloatType, &widthValue); + fontEntry->mStretchRange = + StretchRange(CoreTextWidthToCSSStretch(widthValue)); + + SInt32 traitsValue; + CFNumberGetValue(symbolicTraits, kCFNumberSInt32Type, &traitsValue); + if (traitsValue & kCTFontItalicTrait) { + fontEntry->mStyleRange = SlantStyleRange(FontSlantStyle::ITALIC); + } + + if (traitsValue & kCTFontMonoSpaceTrait) { + fontEntry->mFixedPitch = true; + } + + if (gfxPlatform::HasVariationFontSupport()) { + fontEntry->SetupVariationRanges(); + } + + if (LOG_FONTLIST_ENABLED()) { + nsAutoCString weightString; + fontEntry->Weight().ToString(weightString); + nsAutoCString stretchString; + fontEntry->Stretch().ToString(stretchString); + LOG_FONTLIST( + ("(fontlist) added (%s) to family (%s)" + " with style: %s weight: %s stretch: %s", + fontEntry->Name().get(), Name().get(), + fontEntry->IsItalic() ? "italic" : "normal", weightString.get(), + stretchString.get())); + } + + // insert into font entry array of family + AddFontEntryLocked(fontEntry); +} + +void CTFontFamily::FindStyleVariationsLocked(FontInfoData* aFontInfoData) { + if (mHasStyles) { + return; + } + + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("CTFontFamily::FindStyleVariations", + LAYOUT, mName); + + struct Context { + CTFontFamily* family; + const void* prevValue = nullptr; + }; + + auto addFaceFunc = [](const void* aValue, void* aContext) -> void { + Context* context = (Context*)aContext; + if (aValue == context->prevValue) { + return; + } + context->prevValue = aValue; + CTFontFamily* family = context->family; + // Calling family->AddFace requires that family->mLock is held. We know + // this will be true because FindStyleVariationsLocked already requires it, + // but the thread-safety analysis can't track that through into the lambda + // here, so we disable the check to avoid a spurious warning. + MOZ_PUSH_IGNORE_THREAD_SAFETY; + family->AddFace((CTFontDescriptorRef)aValue); + MOZ_POP_THREAD_SAFETY; + }; + + AutoCFRelease descriptor = + CreateDescriptorForFamily(mName, false); + AutoCFRelease faces = + CTFontDescriptorCreateMatchingFontDescriptors(descriptor, nullptr); + + if (faces) { + Context context{this}; + CFArrayApplyFunction(faces, CFRangeMake(0, CFArrayGetCount(faces)), + addFaceFunc, &context); + } + + SortAvailableFonts(); + SetHasStyles(true); + + if (mIsBadUnderlineFamily) { + SetBadUnderlineFonts(); + } + + CheckForSimpleFamily(); +} + +/* CoreTextFontList */ +#pragma mark - + +CoreTextFontList::CoreTextFontList() + : gfxPlatformFontList(false), mDefaultFont(nullptr) { +#ifdef MOZ_BUNDLED_FONTS + // We activate bundled fonts if the pref is > 0 (on) or < 0 (auto), only an + // explicit value of 0 (off) will disable them. + if (StaticPrefs::gfx_bundled_fonts_activate_AtStartup() != 0) { + TimeStamp start = TimeStamp::Now(); + ActivateBundledFonts(); + TimeStamp end = TimeStamp::Now(); + Telemetry::Accumulate(Telemetry::FONTLIST_BUNDLEDFONTS_ACTIVATE, + (end - start).ToMilliseconds()); + } +#endif + + // Load the font-list preferences now, so that we don't have to do it from + // Init[Shared]FontListForPlatform, which may be called off-main-thread. + gfxFontUtils::GetPrefsFontList("font.preload-names-list", mPreloadFonts); +} + +CoreTextFontList::~CoreTextFontList() { + AutoLock lock(mLock); + + if (XRE_IsParentProcess()) { + CFNotificationCenterRemoveObserver( + CFNotificationCenterGetLocalCenter(), this, + kCTFontManagerRegisteredFontsChangedNotification, 0); + } + + if (mDefaultFont) { + CFRelease(mDefaultFont); + } +} + +void CoreTextFontList::AddFamily(const nsACString& aFamilyName, + FontVisibility aVisibility) { + nsAutoCString key; + ToLowerCase(aFamilyName, key); + + RefPtr familyEntry = + new CTFontFamily(aFamilyName, aVisibility); + mFontFamilies.InsertOrUpdate(key, RefPtr{familyEntry}); + + // check the bad underline blocklist + if (mBadUnderlineFamilyNames.ContainsSorted(key)) { + familyEntry->SetBadUnderlineFamily(); + } +} + +void CoreTextFontList::AddFamily(CFStringRef aFamily) { + // CTFontManager includes internal family names and LastResort; skip those. + if (!aFamily || + CFStringCompare(aFamily, CFSTR("LastResort"), + kCFCompareCaseInsensitive) == kCFCompareEqualTo || + CFStringCompare(aFamily, CFSTR(".LastResort"), + kCFCompareCaseInsensitive) == kCFCompareEqualTo) { + return; + } + + nsAutoString familyName; + GetStringForCFString(aFamily, familyName); + + NS_ConvertUTF16toUTF8 nameUtf8(familyName); + AddFamily(nameUtf8, GetVisibilityForFamily(nameUtf8)); +} + +/* static */ +void CoreTextFontList::ActivateFontsFromDir( + const nsACString& aDir, nsTHashSet* aLoadedFamilies) { + AutoCFRelease directory = CFURLCreateFromFileSystemRepresentation( + kCFAllocatorDefault, (const UInt8*)nsPromiseFlatCString(aDir).get(), + aDir.Length(), true); + if (!directory) { + return; + } + AutoCFRelease enumerator = + CFURLEnumeratorCreateForDirectoryURL(kCFAllocatorDefault, directory, + kCFURLEnumeratorDefaultBehavior, + nullptr); + if (!enumerator) { + return; + } + AutoCFRelease urls = + CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + if (!urls) { + return; + } + + CFURLRef url; + CFURLEnumeratorResult result; + do { + result = CFURLEnumeratorGetNextURL(enumerator, &url, nullptr); + if (result != kCFURLEnumeratorSuccess) { + continue; + } + CFArrayAppendValue(urls, url); + + if (!aLoadedFamilies) { + continue; + } + AutoCFRelease descriptors = + CTFontManagerCreateFontDescriptorsFromURL(url); + if (!descriptors || !CFArrayGetCount(descriptors)) { + continue; + } + CTFontDescriptorRef desc = + (CTFontDescriptorRef)CFArrayGetValueAtIndex(descriptors, 0); + AutoCFRelease name = + (CFStringRef)CTFontDescriptorCopyAttribute(desc, + kCTFontFamilyNameAttribute); + nsAutoCString key; + key.SetLength((CFStringGetLength(name) + 1) * 3); + if (CFStringGetCString(name, key.BeginWriting(), key.Length(), + kCFStringEncodingUTF8)) { + key.SetLength(strlen(key.get())); + aLoadedFamilies->Insert(key); + } + } while (result != kCFURLEnumeratorEnd); + + CTFontManagerRegisterFontURLs(urls, kCTFontManagerScopeProcess, false, + nullptr); +} + +void CoreTextFontList::ReadSystemFontList(dom::SystemFontList* aList) + MOZ_NO_THREAD_SAFETY_ANALYSIS { + // Note: We rely on the records for mSystemFontFamilyName (if present) being + // *before* the main font list, so that name is known in the content process + // by the time we add the actual family records to the font list. + aList->entries().AppendElement(FontFamilyListEntry( + mSystemFontFamilyName, FontVisibility::Unknown, kSystemFontFamily)); + + // Now collect the list of available families, with visibility attributes. + for (auto f = mFontFamilies.Iter(); !f.Done(); f.Next()) { + auto macFamily = f.Data().get(); + aList->entries().AppendElement(FontFamilyListEntry( + macFamily->Name(), macFamily->Visibility(), kStandardFontFamily)); + } +} + +void CoreTextFontList::PreloadNamesList() { + uint32_t numFonts = mPreloadFonts.Length(); + for (uint32_t i = 0; i < numFonts; i++) { + nsAutoCString key; + GenerateFontListKey(mPreloadFonts[i], key); + + // only search canonical names! + gfxFontFamily* familyEntry = mFontFamilies.GetWeak(key); + if (familyEntry) { + familyEntry->ReadOtherFamilyNames(this); + } + } +} + +nsresult CoreTextFontList::InitFontListForPlatform() { + // The font registration thread was created early in startup, to give the + // system a head start on activating all the supplemental-language fonts. + // Here, we need to wait until it has finished its work. + gfxPlatformMac::WaitForFontRegistration(); + + Telemetry::AutoTimer timer; + + InitSystemFontNames(); + + if (XRE_IsParentProcess()) { + static bool firstTime = true; + if (firstTime) { + CFNotificationCenterAddObserver( + CFNotificationCenterGetLocalCenter(), this, + RegisteredFontsChangedNotificationCallback, + kCTFontManagerRegisteredFontsChangedNotification, 0, + CFNotificationSuspensionBehaviorDeliverImmediately); + firstTime = false; + } + + // We're not a content process, so get the available fonts directly + // from Core Text. + AutoCFRelease familyNames = + CTFontManagerCopyAvailableFontFamilyNames(); + for (CFIndex i = 0; i < CFArrayGetCount(familyNames); i++) { + CFStringRef familyName = + (CFStringRef)CFArrayGetValueAtIndex(familyNames, i); + AddFamily(familyName); + } + for (const auto& name : kDeprecatedFontFamilies) { + if (DeprecatedFamilyIsAvailable(name)) { + AddFamily(name, GetVisibilityForFamily(name)); + } + } + } else { + // Content process: use font list passed from the chrome process via + // the GetXPCOMProcessAttributes message, because it's much faster than + // querying Core Text again in the child. + auto& fontList = dom::ContentChild::GetSingleton()->SystemFontList(); + for (FontFamilyListEntry& ffe : fontList.entries()) { + switch (ffe.entryType()) { + case kStandardFontFamily: + if (ffe.familyName() == mSystemFontFamilyName) { + continue; + } + AddFamily(ffe.familyName(), ffe.visibility()); + break; + case kSystemFontFamily: + mSystemFontFamilyName = ffe.familyName(); + break; + } + } + fontList.entries().Clear(); + } + + InitSingleFaceList(); + + // to avoid full search of font name tables, seed the other names table with + // localized names from some of the prefs fonts which are accessed via their + // localized names. changes in the pref fonts will only cause a font lookup + // miss earlier. this is a simple optimization, it's not required for + // correctness + PreloadNamesList(); + + // start the delayed cmap loader + GetPrefsAndStartLoader(); + + return NS_OK; +} + +void CoreTextFontList::InitSharedFontListForPlatform() { + gfxPlatformMac::WaitForFontRegistration(); + + InitSystemFontNames(); + + if (XRE_IsParentProcess()) { + // Only the parent process listens for OS font-changed notifications; + // after rebuilding its list, it will update the content processes. + static bool firstTime = true; + if (firstTime) { + CFNotificationCenterAddObserver( + CFNotificationCenterGetLocalCenter(), this, + RegisteredFontsChangedNotificationCallback, + kCTFontManagerRegisteredFontsChangedNotification, 0, + CFNotificationSuspensionBehaviorDeliverImmediately); + firstTime = false; + } + + AutoCFRelease familyNames = + CTFontManagerCopyAvailableFontFamilyNames(); + nsTArray families; + families.SetCapacity(CFArrayGetCount(familyNames) + + ArrayLength(kDeprecatedFontFamilies)); + for (CFIndex i = 0; i < CFArrayGetCount(familyNames); ++i) { + nsAutoString name16; + CFStringRef familyName = + (CFStringRef)CFArrayGetValueAtIndex(familyNames, i); + GetStringForCFString(familyName, name16); + NS_ConvertUTF16toUTF8 name(name16); + nsAutoCString key; + GenerateFontListKey(name, key); + families.AppendElement(fontlist::Family::InitData( + key, name, fontlist::Family::kNoIndex, GetVisibilityForFamily(name))); + } + for (const nsACString& name : kDeprecatedFontFamilies) { + if (DeprecatedFamilyIsAvailable(name)) { + nsAutoCString key; + GenerateFontListKey(name, key); + families.AppendElement( + fontlist::Family::InitData(key, name, fontlist::Family::kNoIndex, + GetVisibilityForFamily(name))); + } + } + SharedFontList()->SetFamilyNames(families); + InitAliasesForSingleFaceList(); + GetPrefsAndStartLoader(); + } +} + +gfxFontFamily* CoreTextFontList::FindSystemFontFamily( + const nsACString& aFamily) { + nsAutoCString key; + GenerateFontListKey(aFamily, key); + + gfxFontFamily* familyEntry; + if ((familyEntry = mFontFamilies.GetWeak(key))) { + return CheckFamily(familyEntry); + } + + return nullptr; +} + +void CoreTextFontList::RegisteredFontsChangedNotificationCallback( + CFNotificationCenterRef center, void* observer, CFStringRef name, + const void* object, CFDictionaryRef userInfo) { + if (!CFEqual(name, kCTFontManagerRegisteredFontsChangedNotification)) { + return; + } + + CoreTextFontList* fl = static_cast(observer); + if (!fl->IsInitialized()) { + return; + } + + // xxx - should be carefully pruning the list of fonts, not rebuilding it from + // scratch + fl->UpdateFontList(); + + gfxPlatform::ForceGlobalReflow(gfxPlatform::NeedsReframe::Yes); + dom::ContentParent::NotifyUpdatedFonts(true); +} + +gfxFontEntry* CoreTextFontList::PlatformGlobalFontFallback( + nsPresContext* aPresContext, const uint32_t aCh, Script aRunScript, + const gfxFontStyle* aMatchStyle, FontFamily& aMatchedFamily) { + CFStringRef str; + UniChar ch[2]; + CFIndex length = 1; + + if (IS_IN_BMP(aCh)) { + ch[0] = aCh; + str = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, ch, 1, + kCFAllocatorNull); + } else { + ch[0] = H_SURROGATE(aCh); + ch[1] = L_SURROGATE(aCh); + str = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, ch, 2, + kCFAllocatorNull); + length = 2; + } + if (!str) { + return nullptr; + } + + // use CoreText to find the fallback family + + gfxFontEntry* fontEntry = nullptr; + bool cantUseFallbackFont = false; + + if (!mDefaultFont) { + mDefaultFont = CTFontCreateWithName(CFSTR("LucidaGrande"), 12.f, NULL); + } + + AutoCFRelease fallback = + CTFontCreateForString(mDefaultFont, str, CFRangeMake(0, length)); + + if (fallback) { + AutoCFRelease familyNameRef = CTFontCopyFamilyName(fallback); + + if (familyNameRef && + CFStringCompare(familyNameRef, CFSTR("LastResort"), + kCFCompareCaseInsensitive) != kCFCompareEqualTo && + CFStringCompare(familyNameRef, CFSTR(".LastResort"), + kCFCompareCaseInsensitive) != kCFCompareEqualTo) { + AutoTArray buffer; + CFIndex familyNameLen = CFStringGetLength(familyNameRef); + buffer.SetLength(familyNameLen + 1); + CFStringGetCharacters(familyNameRef, CFRangeMake(0, familyNameLen), + buffer.Elements()); + buffer[familyNameLen] = 0; + NS_ConvertUTF16toUTF8 familyNameString( + reinterpret_cast(buffer.Elements()), familyNameLen); + + if (SharedFontList()) { + fontlist::Family* family = + FindSharedFamily(aPresContext, familyNameString); + if (family) { + fontlist::Face* face = + family->FindFaceForStyle(SharedFontList(), *aMatchStyle); + if (face) { + fontEntry = GetOrCreateFontEntryLocked(face, family); + } + if (fontEntry) { + if (fontEntry->HasCharacter(aCh)) { + aMatchedFamily = FontFamily(family); + } else { + fontEntry = nullptr; + cantUseFallbackFont = true; + } + } + } + } + + // The macOS system font does not appear in the shared font list, so if + // we didn't find the fallback font above, we should also check for an + // unshared fontFamily in the system list. + if (!fontEntry) { + gfxFontFamily* family = FindSystemFontFamily(familyNameString); + if (family) { + fontEntry = family->FindFontForStyle(*aMatchStyle); + if (fontEntry) { + if (fontEntry->HasCharacter(aCh)) { + aMatchedFamily = FontFamily(family); + } else { + fontEntry = nullptr; + cantUseFallbackFont = true; + } + } + } + } + } + } + + if (cantUseFallbackFont) { + Telemetry::Accumulate(Telemetry::BAD_FALLBACK_FONT, cantUseFallbackFont); + } + + CFRelease(str); + + return fontEntry; +} + +gfxFontEntry* CoreTextFontList::LookupLocalFont( + nsPresContext* aPresContext, const nsACString& aFontName, + WeightRange aWeightForEntry, StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry) { + if (aFontName.IsEmpty() || aFontName[0] == '.') { + return nullptr; + } + + AutoLock lock(mLock); + + CrashReporter::AutoAnnotateCrashReport autoFontName( + CrashReporter::Annotation::FontName, aFontName); + + AutoCFRelease faceName = CreateCFStringForString(aFontName); + if (!faceName) { + return nullptr; + } + + // lookup face based on postscript or full name + AutoCFRelease fontRef = CGFontCreateWithFontName(faceName); + if (!fontRef) { + return nullptr; + } + + // It's possible for CGFontCreateWithFontName to return a font that has been + // deactivated/uninstalled, or a font that is excluded from the font list due + // to CSS font-visibility restriction. So we need to check whether this font + // is allowed to be used. + + // CGFontRef doesn't offer a family-name API, so we go via a CTFontRef. + AutoCFRelease ctFont = + CTFontCreateWithGraphicsFont(fontRef, 0.0, nullptr, nullptr); + if (!ctFont) { + return nullptr; + } + AutoCFRelease name = CTFontCopyFamilyName(ctFont); + + // Convert the family name to a key suitable for font-list lookup (8-bit, + // lowercased). + nsAutoCString key; + // CFStringGetLength is in UTF-16 code units. The maximum this count can + // expand when converted to UTF-8 is 3x. We add 1 to ensure there will also be + // space for null-termination of the resulting C string. + key.SetLength((CFStringGetLength(name) + 1) * 3); + if (!CFStringGetCString(name, key.BeginWriting(), key.Length(), + kCFStringEncodingUTF8)) { + // This shouldn't ever happen, but if it does we just bail. + NS_WARNING("Failed to get family name?"); + key.Truncate(0); + } + if (key.IsEmpty()) { + return nullptr; + } + // Reset our string length to match the actual C string we got, which will + // usually be much shorter than the maximal buffer we allocated. + key.Truncate(strlen(key.get())); + ToLowerCase(key); + // If the family can't be looked up, this font is not available for use. + FontFamily family = FindFamily(aPresContext, key); + if (family.IsNull()) { + return nullptr; + } + + return new CTFontEntry(aFontName, fontRef, aWeightForEntry, aStretchForEntry, + aStyleForEntry, false, true); +} + +static void ReleaseData(void* info, const void* data, size_t size) { + free((void*)data); +} + +gfxFontEntry* CoreTextFontList::MakePlatformFont(const nsACString& aFontName, + WeightRange aWeightForEntry, + StretchRange aStretchForEntry, + SlantStyleRange aStyleForEntry, + const uint8_t* aFontData, + uint32_t aLength) { + NS_ASSERTION(aFontData, "MakePlatformFont called with null data"); + + // create the font entry + nsAutoString uniqueName; + + nsresult rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName); + if (NS_FAILED(rv)) { + return nullptr; + } + + CrashReporter::AutoAnnotateCrashReport autoFontName( + CrashReporter::Annotation::FontName, aFontName); + + AutoCFRelease provider = + ::CGDataProviderCreateWithData(nullptr, aFontData, aLength, &ReleaseData); + AutoCFRelease fontRef = ::CGFontCreateWithDataProvider(provider); + if (!fontRef) { + return nullptr; + } + + auto newFontEntry = MakeUnique( + NS_ConvertUTF16toUTF8(uniqueName), fontRef, aWeightForEntry, + aStretchForEntry, aStyleForEntry, true, false); + return newFontEntry.release(); +} + +// Webkit code uses a system font meta name, so mimic that here +// WebCore/platform/graphics/mac/FontCacheMac.mm +static const char kSystemFont_system[] = "-apple-system"; + +bool CoreTextFontList::FindAndAddFamiliesLocked( + nsPresContext* aPresContext, StyleGenericFontFamily aGeneric, + const nsACString& aFamily, nsTArray* aOutput, + FindFamiliesFlags aFlags, gfxFontStyle* aStyle, nsAtom* aLanguage, + gfxFloat aDevToCssSize) { + if (aFamily.EqualsLiteral(kSystemFont_system)) { + // Search for special system font name, -apple-system. This is not done via + // the shared fontlist because the hidden system font may not be included + // there; we create a separate gfxFontFamily to manage this family. + if (auto* fam = FindSystemFontFamily(mSystemFontFamilyName)) { + aOutput->AppendElement(fam); + return true; + } + return false; + } + + return gfxPlatformFontList::FindAndAddFamiliesLocked( + aPresContext, aGeneric, aFamily, aOutput, aFlags, aStyle, aLanguage, + aDevToCssSize); +} + +// used to load system-wide font info on off-main thread +class CTFontInfo final : public FontInfoData { + public: + CTFontInfo(bool aLoadOtherNames, bool aLoadFaceNames, bool aLoadCmaps, + RecursiveMutex& aLock) + : FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps), + mLock(aLock) {} + + virtual ~CTFontInfo() = default; + + virtual void Load() { FontInfoData::Load(); } + + // loads font data for all members of a given family + virtual void LoadFontFamilyData(const nsACString& aFamilyName); + + RecursiveMutex& mLock; +}; + +void CTFontInfo::LoadFontFamilyData(const nsACString& aFamilyName) { + CrashReporter::AutoAnnotateCrashReport autoFontName( + CrashReporter::Annotation::FontName, aFamilyName); + // Prevent this from running concurrently with CGFont operations on the main + // thread, because the macOS font cache is fragile with concurrent access. + // This appears to be a vulnerability within CoreText in versions of macOS + // before macOS 13. In time, we can remove this lock. + RecursiveMutexAutoLock lock(mLock); + + // family name ==> CTFontDescriptor + AutoCFRelease family = CreateCFStringForString(aFamilyName); + + AutoCFRelease attr = + CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, family); + AutoCFRelease fd = + CTFontDescriptorCreateWithAttributes(attr); + AutoCFRelease matchingFonts = + CTFontDescriptorCreateMatchingFontDescriptors(fd, NULL); + if (!matchingFonts) { + return; + } + + nsTArray otherFamilyNames; + bool hasOtherFamilyNames = true; + + // iterate over faces in the family + int f, numFaces = (int)CFArrayGetCount(matchingFonts); + CTFontDescriptorRef prevFace = nullptr; + for (f = 0; f < numFaces; f++) { + mLoadStats.fonts++; + + CTFontDescriptorRef faceDesc = + (CTFontDescriptorRef)CFArrayGetValueAtIndex(matchingFonts, f); + if (!faceDesc) { + continue; + } + + if (faceDesc == prevFace) { + continue; + } + prevFace = faceDesc; + + AutoCFRelease fontRef = + CTFontCreateWithFontDescriptor(faceDesc, 0.0, nullptr); + if (!fontRef) { + NS_WARNING("failed to create a CTFontRef"); + continue; + } + + if (mLoadCmaps) { + // face name + AutoCFRelease faceName = + (CFStringRef)CTFontDescriptorCopyAttribute(faceDesc, + kCTFontNameAttribute); + + AutoTArray buffer; + CFIndex len = CFStringGetLength(faceName); + buffer.SetLength(len + 1); + CFStringGetCharacters(faceName, CFRangeMake(0, len), buffer.Elements()); + buffer[len] = 0; + NS_ConvertUTF16toUTF8 fontName( + reinterpret_cast(buffer.Elements()), len); + + // load the cmap data + FontFaceData fontData; + AutoCFRelease cmapTable = CTFontCopyTable( + fontRef, kCTFontTableCmap, kCTFontTableOptionNoOptions); + + if (cmapTable) { + const uint8_t* cmapData = (const uint8_t*)CFDataGetBytePtr(cmapTable); + uint32_t cmapLen = CFDataGetLength(cmapTable); + RefPtr charmap = new gfxCharacterMap(); + uint32_t offset; + nsresult rv; + + rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, offset); + if (NS_SUCCEEDED(rv)) { + fontData.mCharacterMap = charmap; + fontData.mUVSOffset = offset; + mLoadStats.cmaps++; + } + } + + mFontFaceData.InsertOrUpdate(fontName, fontData); + } + + if (mLoadOtherNames && hasOtherFamilyNames) { + AutoCFRelease nameTable = CTFontCopyTable( + fontRef, kCTFontTableName, kCTFontTableOptionNoOptions); + + if (nameTable) { + const char* nameData = (const char*)CFDataGetBytePtr(nameTable); + uint32_t nameLen = CFDataGetLength(nameTable); + gfxFontUtils::ReadOtherFamilyNamesForFace( + aFamilyName, nameData, nameLen, otherFamilyNames, false); + hasOtherFamilyNames = otherFamilyNames.Length() != 0; + } + } + } + + // if found other names, insert them in the hash table + if (otherFamilyNames.Length() != 0) { + mOtherFamilyNames.InsertOrUpdate(aFamilyName, otherFamilyNames); + mLoadStats.othernames += otherFamilyNames.Length(); + } +} + +already_AddRefed CoreTextFontList::CreateFontInfoData() { + bool loadCmaps = !UsesSystemFallback() || + gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback(); + + mLock.AssertCurrentThreadIn(); + RefPtr fi = + new CTFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps, mLock); + return fi.forget(); +} + +gfxFontFamily* CoreTextFontList::CreateFontFamily( + const nsACString& aName, FontVisibility aVisibility) const { + return new CTFontFamily(aName, aVisibility); +} + +gfxFontEntry* CoreTextFontList::CreateFontEntry( + fontlist::Face* aFace, const fontlist::Family* aFamily) { + CTFontEntry* fe = new CTFontEntry( + aFace->mDescriptor.AsString(SharedFontList()), aFace->mWeight, false, + 0.0); // XXX standardFace, sizeHint + fe->InitializeFrom(aFace, aFamily); + return fe; +} + +void CoreTextFontList::AddFaceInitData( + CTFontDescriptorRef aFontDesc, nsTArray& aFaces, + bool aLoadCmaps) { + AutoCFRelease psname = + (CFStringRef)CTFontDescriptorCopyAttribute(aFontDesc, + kCTFontNameAttribute); + AutoCFRelease facename = + (CFStringRef)CTFontDescriptorCopyAttribute(aFontDesc, + kCTFontStyleNameAttribute); + AutoCFRelease traitsDict = + (CFDictionaryRef)CTFontDescriptorCopyAttribute(aFontDesc, + kCTFontTraitsAttribute); + + CFNumberRef weight = + (CFNumberRef)CFDictionaryGetValue(traitsDict, kCTFontWeightTrait); + CFNumberRef width = + (CFNumberRef)CFDictionaryGetValue(traitsDict, kCTFontWidthTrait); + CFNumberRef symbolicTraits = + (CFNumberRef)CFDictionaryGetValue(traitsDict, kCTFontSymbolicTrait); + + // make a nsString + nsAutoString postscriptFontName; + GetStringForCFString(psname, postscriptFontName); + + int32_t cssWeight = PR_GetCurrentThread() == sInitFontListThread + ? 0 + : GetWeightOverride(postscriptFontName); + if (cssWeight) { + // scale down and clamp, to get a value from 1..9 + cssWeight = ((cssWeight + 50) / 100); + cssWeight = std::max(1, std::min(cssWeight, 9)); + cssWeight *= 100; // scale up to CSS values + } else { + CGFloat weightValue; + CFNumberGetValue(weight, kCFNumberCGFloatType, &weightValue); + cssWeight = CoreTextWeightToCSSWeight(weightValue); + } + + CGFloat widthValue; + CFNumberGetValue(width, kCFNumberCGFloatType, &widthValue); + StretchRange stretch(CoreTextWidthToCSSStretch(widthValue)); + + SlantStyleRange slantStyle(FontSlantStyle::NORMAL); + SInt32 traitsValue; + CFNumberGetValue(symbolicTraits, kCFNumberSInt32Type, &traitsValue); + if (traitsValue & kCTFontItalicTrait) { + slantStyle = SlantStyleRange(FontSlantStyle::ITALIC); + } + + bool fixedPitch = traitsValue & kCTFontMonoSpaceTrait; + + RefPtr charmap; + if (aLoadCmaps) { + AutoCFRelease font = + CGFontCreateWithFontName(CFStringRef(psname)); + if (font) { + uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p'); + AutoCFRelease data = CGFontCopyTableForTag(font, kCMAP); + if (data) { + uint32_t offset; + charmap = new gfxCharacterMap(); + gfxFontUtils::ReadCMAP(CFDataGetBytePtr(data), CFDataGetLength(data), + *charmap, offset); + } + } + } + + // Ensure that a face named "Regular" goes to the front of the list, so it + // will take precedence over other faces with the same style attributes but + // a different name (such as "Outline"). + auto data = fontlist::Face::InitData{ + NS_ConvertUTF16toUTF8(postscriptFontName), + 0, + fixedPitch, + WeightRange(FontWeight::FromInt(cssWeight)), + stretch, + slantStyle, + charmap, + }; + if (kCFCompareEqualTo == CFStringCompare(facename, CFSTR("Regular"), 0)) { + aFaces.InsertElementAt(0, std::move(data)); + } else { + aFaces.AppendElement(std::move(data)); + } +} + +void CoreTextFontList::GetFacesInitDataForFamily( + const fontlist::Family* aFamily, nsTArray& aFaces, + bool aLoadCmaps) const { + auto name = aFamily->Key().AsString(SharedFontList()); + CrashReporter::AutoAnnotateCrashReport autoFontName( + CrashReporter::Annotation::FontName, name); + + struct Context { + nsTArray& mFaces; + bool mLoadCmaps; + const void* prevValue = nullptr; + }; + auto addFaceFunc = [](const void* aValue, void* aContext) -> void { + Context* context = (Context*)aContext; + if (aValue == context->prevValue) { + return; + } + context->prevValue = aValue; + CTFontDescriptorRef fontDesc = (CTFontDescriptorRef)aValue; + CoreTextFontList::AddFaceInitData(fontDesc, context->mFaces, + context->mLoadCmaps); + }; + + AutoCFRelease descriptor = + CreateDescriptorForFamily(name, false); + AutoCFRelease faces = + CTFontDescriptorCreateMatchingFontDescriptors(descriptor, nullptr); + + if (faces) { + Context context{aFaces, aLoadCmaps}; + CFArrayApplyFunction(faces, CFRangeMake(0, CFArrayGetCount(faces)), + addFaceFunc, &context); + } +} + +void CoreTextFontList::ReadFaceNamesForFamily( + fontlist::Family* aFamily, bool aNeedFullnamePostscriptNames) { + if (!aFamily->IsInitialized()) { + if (!InitializeFamily(aFamily)) { + return; + } + } + const uint32_t kNAME = TRUETYPE_TAG('n', 'a', 'm', 'e'); + fontlist::FontList* list = SharedFontList(); + nsAutoCString canonicalName(aFamily->DisplayName().AsString(list)); + const auto* facePtrs = aFamily->Faces(list); + for (uint32_t i = 0, n = aFamily->NumFaces(); i < n; i++) { + auto* face = facePtrs[i].ToPtr(list); + if (!face) { + continue; + } + nsAutoCString name(face->mDescriptor.AsString(list)); + // We create a temporary CTFontEntry just to read family names from the + // 'name' table in the font resource. The style attributes here are ignored + // as this entry is not used for font style matching. + // The size hint might be used to select which face is accessed in the case + // of the macOS UI font; see CTFontEntry::GetFontRef(). We pass 16.0 in + // order to get a standard text-size face in this case, although it's + // unlikely to matter for the purpose of just reading family names. + auto fe = MakeUnique(name, WeightRange(FontWeight::NORMAL), + false, 16.0); + if (!fe) { + continue; + } + gfxFontEntry::AutoTable nameTable(fe.get(), kNAME); + if (!nameTable) { + continue; + } + uint32_t dataLength; + const char* nameData = hb_blob_get_data(nameTable, &dataLength); + AutoTArray otherFamilyNames; + gfxFontUtils::ReadOtherFamilyNamesForFace( + canonicalName, nameData, dataLength, otherFamilyNames, false); + for (const auto& alias : otherFamilyNames) { + nsAutoCString key; + GenerateFontListKey(alias, key); + auto aliasData = mAliasTable.GetOrInsertNew(key); + aliasData->InitFromFamily(aFamily, canonicalName); + aliasData->mFaces.AppendElement(facePtrs[i]); + } + } +} + +#ifdef MOZ_BUNDLED_FONTS +void CoreTextFontList::ActivateBundledFonts() { + nsCOMPtr localDir; + if (NS_FAILED(NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir)))) { + return; + } + if (NS_FAILED(localDir->Append(u"fonts"_ns))) { + return; + } + nsAutoCString path; + if (NS_FAILED(localDir->GetNativePath(path))) { + return; + } + ActivateFontsFromDir(path, &mBundledFamilies); +} +#endif -- cgit v1.2.3