1934 lines
66 KiB
C++
1934 lines
66 KiB
C++
/* -*- 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/glean/GfxMetrics.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.
|
|
AutoCFTypeRef<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();
|
|
AutoCFTypeRef<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) {
|
|
AutoCFTypeRef<CGFontRef> fontRef(CreateOrCopyFontRef());
|
|
if (!fontRef) {
|
|
return false;
|
|
}
|
|
AutoCFTypeRef<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());
|
|
|
|
AutoCFTypeRef<CTFontRef> ctFont(
|
|
CTFontCreateWithGraphicsFont(cgFont, 0.0, nullptr, nullptr));
|
|
if (ctFont) {
|
|
AutoCFTypeRef<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) {
|
|
AutoCFTypeRef<CFStringRef> family(CreateCFStringForString(aFamilyName));
|
|
const void* values[] = {family};
|
|
const void* keys[] = {kCTFontFamilyNameAttribute};
|
|
AutoCFTypeRef<CFDictionaryRef> attributes(CFDictionaryCreate(
|
|
kCFAllocatorDefault, keys, values, 1, &kCFTypeDictionaryKeyCallBacks,
|
|
&kCFTypeDictionaryValueCallBacks));
|
|
|
|
// Not AutoCFTypeRef, 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) {
|
|
AutoCFTypeRef<CTFontDescriptorRef> descriptor(
|
|
CreateDescriptorForFamily(mName, true));
|
|
if (descriptor) {
|
|
AutoCFTypeRef<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.
|
|
//
|
|
// (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<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 + std::size(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) {
|
|
AutoCFTypeRef<CFStringRef> psname(
|
|
(CFStringRef)CTFontDescriptorCopyAttribute(aFace, kCTFontNameAttribute));
|
|
AutoCFTypeRef<CFStringRef> facename(
|
|
(CFStringRef)CTFontDescriptorCopyAttribute(aFace,
|
|
kCTFontStyleNameAttribute));
|
|
|
|
AutoCFTypeRef<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::clamp(cssWeight, 1, 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) {
|
|
AutoCFTypeRef<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.
|
|
AutoCFTypeRef<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;
|
|
};
|
|
|
|
AutoCFTypeRef<CTFontDescriptorRef> descriptor(
|
|
CreateDescriptorForFamily(mName, false));
|
|
AutoCFTypeRef<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) {
|
|
auto timerId = glean::fontlist::bundledfonts_activate.Start();
|
|
ActivateBundledFonts();
|
|
glean::fontlist::bundledfonts_activate.StopAndAccumulate(
|
|
std::move(timerId));
|
|
}
|
|
#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) {
|
|
AutoCFTypeRef<CFURLRef> directory(CFURLCreateFromFileSystemRepresentation(
|
|
kCFAllocatorDefault, (const UInt8*)nsPromiseFlatCString(aDir).get(),
|
|
aDir.Length(), true));
|
|
if (!directory) {
|
|
return;
|
|
}
|
|
AutoCFTypeRef<CFURLEnumeratorRef> enumerator(
|
|
CFURLEnumeratorCreateForDirectoryURL(kCFAllocatorDefault, directory,
|
|
kCFURLEnumeratorDefaultBehavior,
|
|
nullptr));
|
|
if (!enumerator) {
|
|
return;
|
|
}
|
|
AutoCFTypeRef<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;
|
|
}
|
|
AutoCFTypeRef<CFArrayRef> descriptors(
|
|
CTFontManagerCreateFontDescriptorsFromURL(url));
|
|
if (!descriptors || !CFArrayGetCount(descriptors)) {
|
|
continue;
|
|
}
|
|
CTFontDescriptorRef desc =
|
|
(CTFontDescriptorRef)CFArrayGetValueAtIndex(descriptors, 0);
|
|
AutoCFTypeRef<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();
|
|
|
|
auto timer = glean::fontlist::mac_init_total.Measure();
|
|
|
|
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.
|
|
AutoCFTypeRef<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;
|
|
}
|
|
|
|
AutoCFTypeRef<CFArrayRef> familyNames(
|
|
CTFontManagerCopyAvailableFontFamilyNames());
|
|
nsTArray<fontlist::Family::InitData> families;
|
|
families.SetCapacity(CFArrayGetCount(familyNames)
|
|
#if USE_DEPRECATED_FONT_FAMILY_NAMES
|
|
+ std::size(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();
|
|
|
|
auto flags = gfxPlatform::GlobalReflowFlags::NeedsReframe |
|
|
gfxPlatform::GlobalReflowFlags::FontsChanged;
|
|
gfxPlatform::ForceGlobalReflow(flags);
|
|
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);
|
|
}
|
|
|
|
AutoCFTypeRef<CTFontRef> fallback(
|
|
CTFontCreateForString(mDefaultFont, str, CFRangeMake(0, length)));
|
|
|
|
if (fallback) {
|
|
AutoCFTypeRef<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) {
|
|
glean::fontlist::bad_fallback_font
|
|
.EnumGet(static_cast<glean::fontlist::BadFallbackFontLabel>(
|
|
cantUseFallbackFont))
|
|
.Add();
|
|
}
|
|
|
|
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);
|
|
|
|
AutoCFTypeRef<CFStringRef> faceName(CreateCFStringForString(aFontName));
|
|
if (!faceName) {
|
|
return nullptr;
|
|
}
|
|
|
|
// lookup face based on postscript or full name
|
|
AutoCFTypeRef<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.
|
|
AutoCFTypeRef<CTFontRef> ctFont(
|
|
CTFontCreateWithGraphicsFont(fontRef, 0.0, nullptr, nullptr));
|
|
if (!ctFont) {
|
|
return nullptr;
|
|
}
|
|
AutoCFTypeRef<CFStringRef> name(CTFontCopyFamilyName(ctFont));
|
|
// Convert the family name to a key suitable for font-list lookup (8-bit,
|
|
// lowercased).
|
|
nsAutoCString key;
|
|
if (name) {
|
|
// 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 (!name || !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);
|
|
|
|
AutoCFTypeRef<CGDataProviderRef> provider(::CGDataProviderCreateWithData(
|
|
nullptr, aFontData, aLength, &ReleaseData));
|
|
AutoCFTypeRef<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
|
|
AutoCFTypeRef<CFStringRef> family(CreateCFStringForString(aFamilyName));
|
|
|
|
AutoCFTypeRef<CFMutableDictionaryRef> attr(
|
|
CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
|
|
&kCFTypeDictionaryValueCallBacks));
|
|
CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, family);
|
|
AutoCFTypeRef<CTFontDescriptorRef> fd(
|
|
CTFontDescriptorCreateWithAttributes(attr));
|
|
AutoCFTypeRef<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;
|
|
|
|
AutoCFTypeRef<CTFontRef> fontRef(
|
|
CTFontCreateWithFontDescriptor(faceDesc, 0.0, nullptr));
|
|
if (!fontRef) {
|
|
NS_WARNING("failed to create a CTFontRef");
|
|
continue;
|
|
}
|
|
|
|
if (mLoadCmaps) {
|
|
// face name
|
|
AutoCFTypeRef<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;
|
|
AutoCFTypeRef<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) {
|
|
AutoCFTypeRef<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) {
|
|
AutoCFTypeRef<CFStringRef> psname((CFStringRef)CTFontDescriptorCopyAttribute(
|
|
aFontDesc, kCTFontNameAttribute));
|
|
AutoCFTypeRef<CFStringRef> facename(
|
|
(CFStringRef)CTFontDescriptorCopyAttribute(aFontDesc,
|
|
kCTFontStyleNameAttribute));
|
|
AutoCFTypeRef<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::clamp(cssWeight, 1, 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) {
|
|
AutoCFTypeRef<CGFontRef> font(
|
|
CGFontCreateWithFontName(CFStringRef(psname)));
|
|
if (font) {
|
|
uint32_t kCMAP = TRUETYPE_TAG('c', 'm', 'a', 'p');
|
|
AutoCFTypeRef<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);
|
|
};
|
|
|
|
AutoCFTypeRef<CTFontDescriptorRef> descriptor(
|
|
CreateDescriptorForFamily(name, false));
|
|
AutoCFTypeRef<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) {
|
|
AutoCFTypeRef<CFStringRef> psName(CTFontCopyPostScriptName(aFont));
|
|
AutoCFTypeRef<CGFontRef> cgFont(
|
|
CGFontCreateWithFontName(CFStringRef(psName)));
|
|
if (!cgFont) {
|
|
return CTFontCopyFamilyName(aFont);
|
|
}
|
|
AutoCFTypeRef<CTFontRef> ctFont(
|
|
CTFontCreateWithGraphicsFont(cgFont, 0.0, nullptr, nullptr));
|
|
if (!ctFont) {
|
|
return CTFontCopyFamilyName(aFont);
|
|
}
|
|
return CTFontCopyFamilyName(ctFont);
|
|
}
|
|
|
|
void CoreTextFontList::InitSystemFontNames() {
|
|
// text font family
|
|
AutoCFTypeRef<CTFontRef> font(CTFontCreateUIFontForLanguage(
|
|
kCTFontUIFontSystem, 0.0, nullptr)); // TODO: language
|
|
AutoCFTypeRef<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) {
|
|
AutoCFTypeRef<CTFontRef> font(CTFontCreateUIFontForLanguage(
|
|
kCTFontUIFontUser, 0.0, nullptr)); // TODO: language
|
|
AutoCFTypeRef<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
|