/* -*- 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