Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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;
#ifdef MOZ_WIDGET_COCOA
// Building with newer macOS SDKs can cause a bunch of font-family names to be
// hidden from the Core Text API we use to enumerate available fonts. Because
// some content still benefits from having these names recognized, we forcibly
// include them in the list. Some day we might want to drop support for these.
# define USE_DEPRECATED_FONT_FAMILY_NAMES 1
#endif
#if USE_DEPRECATED_FONT_FAMILY_NAMES
// 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,
};
#endif // USE_DEPRECATED_FONT_FAMILY_NAMES
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<gfxCharacterMap> 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<const uint8_t*>(
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<UnscaledFontMac> 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<gfxFontVariationAxis>& 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<gfxFontVariationInstance>& 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::AutoRecordAnnotation autoFontName(
CrashReporter::Annotation::FontName, mName);
// Create a new CGFont; caller will own the only reference to it.
AutoCFRelease<CFStringRef> 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<FontTableRec*>(aUserData);
delete ftr;
#else
CFRelease((CFDataRef)aUserData);
#endif
}
hb_blob_t* CTFontEntry::GetFontTable(uint32_t aTag) {
mLock.ReadLock();
AutoCFRelease<CGFontRef> 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<CGFontRef> fontRef = CreateOrCopyFontRef();
if (!fontRef) {
return false;
}
AutoCFRelease<CFArrayRef> 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::AutoRecordAnnotation autoFontName(
CrashReporter::Annotation::FontName, FamilyName());
AutoCFRelease<CTFontRef> ctFont =
CTFontCreateWithGraphicsFont(cgFont, 0.0, nullptr, nullptr);
if (ctFont) {
AutoCFRelease<CFArrayRef> 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<CFStringRef> family = CreateCFStringForString(aFamilyName);
const void* values[] = {family};
const void* keys[] = {kCTFontFamilyNameAttribute};
AutoCFRelease<CFDictionaryRef> 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<CTFontDescriptorRef> descriptor =
CreateDescriptorForFamily(mName, true);
if (descriptor) {
AutoCFRelease<CFStringRef> name =
static_cast<CFStringRef>(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.
//
//
// 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<CGFloat, int32_t>;
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
//
//
// 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<CFStringRef> psname =
(CFStringRef)CTFontDescriptorCopyAttribute(aFace, kCTFontNameAttribute);
AutoCFRelease<CFStringRef> facename =
(CFStringRef)CTFontDescriptorCopyAttribute(aFace,
kCTFontStyleNameAttribute);
AutoCFRelease<CFDictionaryRef> 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);
if (mForSystemFont) {
MOZ_ASSERT(gfxPlatform::HasVariationFontSupport());
auto addToFamily = [&](CTFontRef aFont) MOZ_REQUIRES(mLock) {
AutoCFRelease<CFStringRef> psName = CTFontCopyPostScriptName(aFont);
nsAutoString nameUTF16;
nsAutoCString nameUTF8;
GetStringForCFString(psName, nameUTF16);
CopyUTF16toUTF8(nameUTF16, nameUTF8);
auto* fe =
new CTFontEntry(nameUTF8, WeightRange(FontWeight::NORMAL), true, 0.0);
// Set the appropriate style, assuming it may not have a variation range.
CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(aFont);
fe->mStyleRange = SlantStyleRange((traits & kCTFontTraitItalic)
? FontSlantStyle::ITALIC
: FontSlantStyle::NORMAL);
// Set up weight (and width, if present) ranges.
fe->SetupVariationRanges();
AddFontEntryLocked(fe);
};
addToFamily(mForSystemFont);
// See if there is a corresponding italic face, and add it to the family.
AutoCFRelease<CTFontRef> italicFont = CTFontCreateCopyWithSymbolicTraits(
mForSystemFont, 0.0, nullptr, kCTFontTraitItalic, kCTFontTraitItalic);
if (italicFont != mForSystemFont) {
addToFamily(italicFont);
}
CFRelease(mForSystemFont);
mForSystemFont = nullptr;
SetHasStyles(true);
return;
}
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<CTFontDescriptorRef> descriptor =
CreateDescriptorForFamily(mName, false);
AutoCFRelease<CFArrayRef> 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<gfxFontFamily> 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<nsCStringHashKey>* aLoadedFamilies) {
AutoCFRelease<CFURLRef> directory = CFURLCreateFromFileSystemRepresentation(
kCFAllocatorDefault, (const UInt8*)nsPromiseFlatCString(aDir).get(),
aDir.Length(), true);
if (!directory) {
return;
}
AutoCFRelease<CFURLEnumeratorRef> enumerator =
CFURLEnumeratorCreateForDirectoryURL(kCFAllocatorDefault, directory,
kCFURLEnumeratorDefaultBehavior,
nullptr);
if (!enumerator) {
return;
}
AutoCFRelease<CFMutableArrayRef> 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<CFArrayRef> descriptors =
CTFontManagerCreateFontDescriptorsFromURL(url);
if (!descriptors || !CFArrayGetCount(descriptors)) {
continue;
}
CTFontDescriptorRef desc =
(CTFontDescriptorRef)CFArrayGetValueAtIndex(descriptors, 0);
AutoCFRelease<CFStringRef> 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<Telemetry::MAC_INITFONTLIST_TOTAL> 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<CFArrayRef> familyNames =
CTFontManagerCopyAvailableFontFamilyNames();
for (CFIndex i = 0; i < CFArrayGetCount(familyNames); i++) {
CFStringRef familyName =
(CFStringRef)CFArrayGetValueAtIndex(familyNames, i);
AddFamily(familyName);
}
#if USE_DEPRECATED_FONT_FAMILY_NAMES
for (const auto& name : kDeprecatedFontFamilies) {
if (DeprecatedFamilyIsAvailable(name)) {
AddFamily(name, GetVisibilityForFamily(name));
}
}
#endif
} 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<CFArrayRef> familyNames =
CTFontManagerCopyAvailableFontFamilyNames();
nsTArray<fontlist::Family::InitData> families;
families.SetCapacity(CFArrayGetCount(familyNames)
#if USE_DEPRECATED_FONT_FAMILY_NAMES
+ ArrayLength(kDeprecatedFontFamilies)
#endif
);
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)));
}
#if USE_DEPRECATED_FONT_FAMILY_NAMES
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)));
}
}
#endif
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<CoreTextFontList*>(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<CTFontRef> fallback =
CTFontCreateForString(mDefaultFont, str, CFRangeMake(0, length));
if (fallback) {
AutoCFRelease<CFStringRef> familyNameRef = CTFontCopyFamilyName(fallback);
if (familyNameRef &&
CFStringCompare(familyNameRef, CFSTR("LastResort"),
kCFCompareCaseInsensitive) != kCFCompareEqualTo &&
CFStringCompare(familyNameRef, CFSTR(".LastResort"),
kCFCompareCaseInsensitive) != kCFCompareEqualTo) {
AutoTArray<UniChar, 1024> buffer;
CFIndex familyNameLen = CFStringGetLength(familyNameRef);
buffer.SetLength(familyNameLen + 1);
CFStringGetCharacters(familyNameRef, CFRangeMake(0, familyNameLen),
buffer.Elements());
buffer[familyNameLen] = 0;
NS_ConvertUTF16toUTF8 familyNameString(
reinterpret_cast<char16_t*>(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::AutoRecordAnnotation autoFontName(
CrashReporter::Annotation::FontName, aFontName);
AutoCFRelease<CFStringRef> faceName = CreateCFStringForString(aFontName);
if (!faceName) {
return nullptr;
}
// lookup face based on postscript or full name
AutoCFRelease<CGFontRef> 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<CTFontRef> ctFont =
CTFontCreateWithGraphicsFont(fontRef, 0.0, nullptr, nullptr);
if (!ctFont) {
return nullptr;
}
AutoCFRelease<CFStringRef> 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::AutoRecordAnnotation autoFontName(
CrashReporter::Annotation::FontName, aFontName);
AutoCFRelease<CGDataProviderRef> provider =
::CGDataProviderCreateWithData(nullptr, aFontData, aLength, &ReleaseData);
AutoCFRelease<CGFontRef> fontRef = ::CGFontCreateWithDataProvider(provider);
if (!fontRef) {
return nullptr;
}
auto newFontEntry = MakeUnique<CTFontEntry>(
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<FamilyAndGeneric>* 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::AutoRecordAnnotation 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<CFStringRef> family = CreateCFStringForString(aFamilyName);
AutoCFRelease<CFMutableDictionaryRef> attr =
CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, family);
AutoCFRelease<CTFontDescriptorRef> fd =
CTFontDescriptorCreateWithAttributes(attr);
AutoCFRelease<CFArrayRef> matchingFonts =
CTFontDescriptorCreateMatchingFontDescriptors(fd, NULL);
if (!matchingFonts) {
return;
}
nsTArray<nsCString> 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<CTFontRef> fontRef =
CTFontCreateWithFontDescriptor(faceDesc, 0.0, nullptr);
if (!fontRef) {
NS_WARNING("failed to create a CTFontRef");
continue;
}
if (mLoadCmaps) {
// face name
AutoCFRelease<CFStringRef> faceName =
(CFStringRef)CTFontDescriptorCopyAttribute(faceDesc,
kCTFontNameAttribute);
AutoTArray<UniChar, 1024> buffer;
CFIndex len = CFStringGetLength(faceName);
buffer.SetLength(len + 1);
CFStringGetCharacters(faceName, CFRangeMake(0, len), buffer.Elements());
buffer[len] = 0;
NS_ConvertUTF16toUTF8 fontName(
reinterpret_cast<char16_t*>(buffer.Elements()), len);
// load the cmap data
FontFaceData fontData;
AutoCFRelease<CFDataRef> cmapTable = CTFontCopyTable(
fontRef, kCTFontTableCmap, kCTFontTableOptionNoOptions);
if (cmapTable) {
const uint8_t* cmapData = (const uint8_t*)CFDataGetBytePtr(cmapTable);
uint32_t cmapLen = CFDataGetLength(cmapTable);
RefPtr<gfxCharacterMap> 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<CFDataRef> 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<FontInfoData> CoreTextFontList::CreateFontInfoData() {
bool loadCmaps = !UsesSystemFallback() ||
gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
mLock.AssertCurrentThreadIn();
RefPtr<CTFontInfo> 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<fontlist::Face::InitData>& aFaces,
bool aLoadCmaps) {
AutoCFRelease<CFStringRef> psname =
(CFStringRef)CTFontDescriptorCopyAttribute(aFontDesc,
kCTFontNameAttribute);
AutoCFRelease<CFStringRef> facename =
(CFStringRef)CTFontDescriptorCopyAttribute(aFontDesc,
kCTFontStyleNameAttribute);
AutoCFRelease<CFDictionaryRef> 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<gfxCharacterMap> charmap;
if (aLoadCmaps) {
AutoCFRelease<CGFontRef> font =
CGFontCreateWithFontName(CFStringRef(psname));
if (font) {
uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p');
AutoCFRelease<CFDataRef> 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<fontlist::Face::InitData>& aFaces,
bool aLoadCmaps) const {
auto name = aFamily->Key().AsString(SharedFontList());
CrashReporter::AutoRecordAnnotation autoFontName(
CrashReporter::Annotation::FontName, name);
struct Context {
nsTArray<fontlist::Face::InitData>& 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<CTFontDescriptorRef> descriptor =
CreateDescriptorForFamily(name, false);
AutoCFRelease<CFArrayRef> 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<const fontlist::Face>(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<CTFontEntry>(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<nsCString, 4> 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]);
}
}
}
static CFStringRef CopyRealFamilyName(CTFontRef aFont) {
AutoCFRelease<CFStringRef> psName = CTFontCopyPostScriptName(aFont);
AutoCFRelease<CGFontRef> cgFont =
CGFontCreateWithFontName(CFStringRef(psName));
if (!cgFont) {
return CTFontCopyFamilyName(aFont);
}
AutoCFRelease<CTFontRef> ctFont =
CTFontCreateWithGraphicsFont(cgFont, 0.0, nullptr, nullptr);
if (!ctFont) {
return CTFontCopyFamilyName(aFont);
}
return CTFontCopyFamilyName(ctFont);
}
void CoreTextFontList::InitSystemFontNames() {
// text font family
AutoCFRelease<CTFontRef> font = CTFontCreateUIFontForLanguage(
kCTFontUIFontSystem, 0.0, nullptr); // TODO: language
AutoCFRelease<CFStringRef> name = CopyRealFamilyName(font);
nsAutoString familyName;
GetStringForCFString(name, familyName);
CopyUTF16toUTF8(familyName, mSystemFontFamilyName);
// We store an in-process gfxFontFamily for the system font even if using the
// shared fontlist to manage "normal" fonts, because the hidden system fonts
// may be excluded from the font list altogether. This family will be
// populated based on the given NSFont.
RefPtr<gfxFontFamily> fam = new CTFontFamily(mSystemFontFamilyName, font);
if (fam) {
nsAutoCString key;
GenerateFontListKey(mSystemFontFamilyName, key);
mFontFamilies.InsertOrUpdate(key, std::move(fam));
}
}
FontFamily CoreTextFontList::GetDefaultFontForPlatform(
nsPresContext* aPresContext, const gfxFontStyle* aStyle,
nsAtom* aLanguage) {
AutoCFRelease<CTFontRef> font = CTFontCreateUIFontForLanguage(
kCTFontUIFontUser, 0.0, nullptr); // TODO: language
AutoCFRelease<CFStringRef> name = CTFontCopyFamilyName(font);
nsAutoString familyName;
GetStringForCFString(name, familyName);
return FindFamily(aPresContext, NS_ConvertUTF16toUTF8(familyName));
}
#ifdef MOZ_BUNDLED_FONTS
void CoreTextFontList::ActivateBundledFonts() {
nsCOMPtr<nsIFile> 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