Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: ObjC; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: BSD
*
* Copyright (C) 2006-2009 Mozilla Corporation. All rights reserved.
*
* Contributor(s):
* Vladimir Vukicevic <vladimir@pobox.com>
* Masayuki Nakano <masayuki@d-toybox.com>
* John Daggett <jdaggett@mozilla.com>
* Jonathan Kew <jfkthame@gmail.com>
*
* Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ***** END LICENSE BLOCK ***** */
#include "mozilla/Logging.h"
#include <algorithm>
#import <AppKit/AppKit.h>
#include "gfxFontConstants.h"
#include "gfxPlatformMac.h"
#include "gfxMacPlatformFontList.h"
#include "gfxMacFont.h"
#include "gfxUserFontSet.h"
#include "SharedFontList-impl.h"
#include "harfbuzz/hb.h"
#include "AppleUtils.h"
#include "MainThreadUtils.h"
#include "nsDirectoryServiceUtils.h"
#include "nsDirectoryServiceDefs.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsIDirectoryEnumerator.h"
#include "nsCharTraits.h"
#include "nsCocoaFeatures.h"
#include "nsCocoaUtils.h"
#include "nsComponentManagerUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsTArray.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/FontPropertyTypes.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/Telemetry.h"
#include "mozilla/gfx/2D.h"
#include <unistd.h>
#include <time.h>
#include <dlfcn.h>
#include "StandardFonts-macos.inc"
using namespace mozilla;
using namespace mozilla::gfx;
// 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
#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
// indexes into the NSArray objects that the Cocoa font manager returns
// as the available members of a family
#define INDEX_FONT_POSTSCRIPT_NAME 0
#define INDEX_FONT_FACE_NAME 1
#define INDEX_FONT_WEIGHT 2
#define INDEX_FONT_TRAITS 3
static const int kAppleMaxWeight = 14;
static const int kAppleExtraLightWeight = 3;
static const int kAppleUltraLightWeight = 2;
static const int gAppleWeightToCSSWeight[] = {
0,
1, // 1.
1, // 2. W1, ultralight
2, // 3. W2, extralight
3, // 4. W3, light
4, // 5. W4, semilight
5, // 6. W5, medium
6, // 7.
6, // 8. W6, semibold
7, // 9. W7, bold
8, // 10. W8, extrabold
8, // 11.
9, // 12. W9, ultrabold
9, // 13
9 // 14
};
// cache Cocoa's "shared font manager" for performance
static NSFontManager* sFontManager;
static void GetStringForNSString(const NSString* aSrc, nsAString& aDest) {
aDest.SetLength([aSrc length]);
[aSrc getCharacters:reinterpret_cast<unichar*>(aDest.BeginWriting())
range:NSMakeRange(0, [aSrc length])];
}
static NSString* GetNSStringForString(const nsAString& aSrc) {
return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(aSrc.BeginReading())
length:aSrc.Length()];
}
#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 MacOSFontEntry::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
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);
}
}
// 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") ||
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) {
mShmemFace->SetCharacterMap(sharedFontList, charmap); // async
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* MacOSFontEntry::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 MacOSFontEntry::HasVariations() {
if (!mHasVariationsInitialized) {
mHasVariationsInitialized = true;
mHasVariations =
gfxPlatform::HasVariationFontSupport() && HasFontTable(TRUETYPE_TAG('f', 'v', 'a', 'r'));
}
return mHasVariations;
}
void MacOSFontEntry::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 MacOSFontEntry::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 MacOSFontEntry::IsCFF() {
if (!mIsCFFInitialized) {
mIsCFFInitialized = true;
mIsCFF = HasFontTable(TRUETYPE_TAG('C', 'F', 'F', ' '));
}
return mIsCFF;
}
MacOSFontEntry::MacOSFontEntry(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;
}
MacOSFontEntry::MacOSFontEntry(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* MacOSFontEntry::Clone() const {
MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!");
MacOSFontEntry* fe = new MacOSFontEntry(Name(), Weight(), mStandardFace, mSizeHint);
fe->mStyleRange = mStyleRange;
fe->mStretchRange = mStretchRange;
fe->mFixedPitch = mFixedPitch;
return fe;
}
CGFontRef MacOSFontEntry::GetFontRef() {
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 MacOSFontEntry::CreateOrCopyFontRef() {
if (mFontRef) {
// We have a cached CGFont, just add a reference. Caller must
// release, but we'll still own our reference.
::CGFontRetain(mFontRef);
return mFontRef;
}
CrashReporter::AutoAnnotateCrashReport autoFontName(CrashReporter::Annotation::FontName, mName);
// Create a new CGFont; caller will own the only reference to it.
NSString* psname = GetNSStringForString(NS_ConvertUTF8toUTF16(mName));
CGFontRef ref = CGFontCreateWithFontName(CFStringRef(psname));
if (!ref) {
// This happens on macOS 10.12 for font entry names that start with
// .AppleSystemUIFont. For those fonts, we need to go through NSFont
// to get the correct CGFontRef.
// Both the Text and the Display variant of the display font use
// .AppleSystemUIFontSomethingSomething as their member names.
// That's why we're carrying along mSizeHint to this place so that
// we get the variant that we want for this family.
NSFont* font = [NSFont fontWithName:psname size:mSizeHint];
if (font) {
ref = CTFontCopyGraphicsFont((CTFontRef)font, nullptr);
}
}
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 MacOSFontEntry::DestroyBlobFunc(void* aUserData) {
#ifdef NS_BUILD_REFCNT_LOGGING
FontTableRec* ftr = static_cast<FontTableRec*>(aUserData);
delete ftr;
#else
::CFRelease((CFDataRef)aUserData);
#endif
}
hb_blob_t* MacOSFontEntry::GetFontTable(uint32_t aTag) {
AutoCFRelease<CGFontRef> fontRef = CreateOrCopyFontRef();
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 MacOSFontEntry::HasFontTable(uint32_t aTableTag) {
if (mAvailableTables.Count() == 0) {
nsAutoreleasePool localPool;
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 MacOSFontEntry::SupportsOpenTypeFeature(Script aScript, uint32_t aFeatureTag) {
// If we're going to shape with Core Text, we don't support added
// OpenType features (aside from any CT applies by default), except
// for 'smcp' which we map to an AAT feature selector.
if (RequiresAATLayout()) {
if (aFeatureTag != HB_TAG('s', 'm', 'c', 'p')) {
return false;
}
if (mHasAATSmallCapsInitialized) {
return mHasAATSmallCaps;
}
mHasAATSmallCapsInitialized = true;
CGFontRef cgFont = GetFontRef();
if (!cgFont) {
return mHasAATSmallCaps;
}
CrashReporter::AutoAnnotateCrashReport autoFontName(CrashReporter::Annotation::FontName,
FamilyName());
AutoCFRelease<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 MacOSFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
FontListSizes* aSizes) const {
aSizes->mFontListSize += aMallocSizeOf(this);
AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
}
/* gfxMacFontFamily */
#pragma mark -
class gfxMacFontFamily final : public gfxFontFamily {
public:
gfxMacFontFamily(const nsACString& aName, FontVisibility aVisibility, double aSizeHint = 0.0)
: gfxFontFamily(aName, aVisibility), mSizeHint(aSizeHint) {}
gfxMacFontFamily(const nsACString& aName, NSFont* aSystemFont)
: gfxFontFamily(aName, FontVisibility::Unknown), mForSystemFont(aSystemFont) {
// I don't think the system font instance is at much risk of being deleted,
// but to be on the safe side let's retain a reference until we're finished
// using it for lazy initialization.
[mForSystemFont retain];
}
virtual ~gfxMacFontFamily() = default;
void LocalizedName(nsACString& aLocalizedName) override;
void FindStyleVariationsLocked(FontInfoData* aFontInfoData = nullptr)
MOZ_REQUIRES(mLock) override;
protected:
double mSizeHint = 0.0;
// If non-null, this is a family representing the system UI font, and should use
// the given NSFont as the basis for initialization as the normal font-manager APIs
// based on family name won't handle it.
NSFont* mForSystemFont = nullptr;
};
void gfxMacFontFamily::LocalizedName(nsACString& aLocalizedName) {
nsAutoreleasePool localPool;
// It's unsafe to call HasOtherFamilyNames off the main thread because
// it entrains FindStyleVariations, which calls GetWeightOverride, which
// retrieves prefs. And the pref names can change (via user overrides),
// so we can't use StaticPrefs to access them.
if (NS_IsMainThread() && !HasOtherFamilyNames()) {
aLocalizedName = mName;
return;
}
NSString* family = GetNSStringForString(NS_ConvertUTF8toUTF16(mName));
NSString* localized = [sFontManager localizedNameForFamily:family face:nil];
if (localized) {
nsAutoString locName;
GetStringForNSString(localized, locName);
CopyUTF16toUTF8(locName, 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
// 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);
}
void gfxMacFontFamily::FindStyleVariationsLocked(FontInfoData* aFontInfoData) {
if (mHasStyles) {
return;
}
AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("gfxMacFontFamily::FindStyleVariations", LAYOUT, mName);
nsAutoreleasePool localPool;
if (mForSystemFont) {
MOZ_ASSERT(gfxPlatform::HasVariationFontSupport());
auto addToFamily = [&](NSFont* aNSFont) MOZ_REQUIRES(mLock) {
NSString* psNameNS = [[aNSFont fontDescriptor] postscriptName];
nsAutoString nameUTF16;
nsAutoCString psName;
nsCocoaUtils::GetStringForNSString(psNameNS, nameUTF16);
CopyUTF16toUTF8(nameUTF16, psName);
auto* fe = new MacOSFontEntry(psName, WeightRange(FontWeight::NORMAL), true, 0.0);
// Set the appropriate style, assuming it may not have a variation range.
fe->mStyleRange = SlantStyleRange(
([[aNSFont fontDescriptor] symbolicTraits] & NSFontItalicTrait) ? 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.
NSFont* italicFont = [sFontManager convertFont:mForSystemFont toHaveTrait:NSItalicFontMask];
if (italicFont != mForSystemFont) {
addToFamily(italicFont);
}
[mForSystemFont release];
mForSystemFont = nullptr;
SetHasStyles(true);
return;
}
NSString* family = GetNSStringForString(NS_ConvertUTF8toUTF16(mName));
// create a font entry for each face
NSArray* fontfaces = [sFontManager
availableMembersOfFontFamily:family]; // returns an array of [psname, style name, weight,
// traits] elements, goofy api
int faceCount = [fontfaces count];
int faceIndex;
for (faceIndex = 0; faceIndex < faceCount; faceIndex++) {
NSArray* face = [fontfaces objectAtIndex:faceIndex];
NSString* psname = [face objectAtIndex:INDEX_FONT_POSTSCRIPT_NAME];
int32_t appKitWeight = [[face objectAtIndex:INDEX_FONT_WEIGHT] unsignedIntValue];
uint32_t macTraits = [[face objectAtIndex:INDEX_FONT_TRAITS] unsignedIntValue];
NSString* facename = [face objectAtIndex:INDEX_FONT_FACE_NAME];
bool isStandardFace = false;
if (appKitWeight == kAppleExtraLightWeight) {
// if the facename contains UltraLight, set the weight to the ultralight weight value
NSRange range = [facename rangeOfString:@"ultralight" options:NSCaseInsensitiveSearch];
if (range.location != NSNotFound) {
appKitWeight = kAppleUltraLightWeight;
}
}
// make a nsString
nsAutoString postscriptFontName;
GetStringForNSString(psname, postscriptFontName);
int32_t cssWeight = gfxMacPlatformFontList::AppleWeightToCSSWeight(appKitWeight);
// If we are on the startup-time InitFontList thread, we skip this as it
// wants to retrieve faces for the default font family, but cannot safely
// call the Preferences APIs. Fortunately, the default font doesn't actually
// depend on a weight override pref.
if (!gfxPlatformFontList::IsInitFontListThread()) {
int32_t weightOverride = GetWeightOverride(postscriptFontName);
if (weightOverride) {
// scale down and clamp, to get a value from 1..9
cssWeight = ((weightOverride + 50) / 100);
cssWeight = std::max(1, std::min(cssWeight, 9));
}
}
cssWeight *= 100; // scale up to CSS values
if ([facename isEqualToString:@"Regular"] || [facename isEqualToString:@"Bold"] ||
[facename isEqualToString:@"Italic"] || [facename isEqualToString:@"Oblique"] ||
[facename isEqualToString:@"Bold Italic"] || [facename isEqualToString:@"Bold Oblique"]) {
isStandardFace = true;
}
// create a font entry
MacOSFontEntry* fontEntry =
new MacOSFontEntry(NS_ConvertUTF16toUTF8(postscriptFontName),
WeightRange(FontWeight::FromInt(cssWeight)), isStandardFace, mSizeHint);
if (!fontEntry) {
break;
}
// set additional properties based on the traits reported by Cocoa
if (macTraits & (NSCondensedFontMask | NSNarrowFontMask | NSCompressedFontMask)) {
fontEntry->mStretchRange = StretchRange(FontStretch::CONDENSED);
} else if (macTraits & NSExpandedFontMask) {
fontEntry->mStretchRange = StretchRange(FontStretch::EXPANDED);
}
// Cocoa fails to set the Italic traits bit for HelveticaLightItalic,
if ((macTraits & NSItalicFontMask) || [facename hasSuffix:@"Italic"] ||
[facename hasSuffix:@"Oblique"]) {
fontEntry->mStyleRange = SlantStyleRange(FontSlantStyle::ITALIC);
}
if (macTraits & NSFixedPitchFontMask) {
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"
" (apple-weight: %d macTraits: %8.8x)",
fontEntry->Name().get(), Name().get(),
fontEntry->IsItalic() ? "italic" : "normal", weightString.get(),
stretchString.get(), appKitWeight, macTraits));
}
// insert into font entry array of family
AddFontEntryLocked(fontEntry);
}
SortAvailableFonts();
SetHasStyles(true);
if (mIsBadUnderlineFamily) {
SetBadUnderlineFonts();
}
CheckForSimpleFamily();
}
/* gfxSingleFaceMacFontFamily */
#pragma mark -
class gfxSingleFaceMacFontFamily final : public gfxFontFamily {
public:
gfxSingleFaceMacFontFamily(const nsACString& aName, FontVisibility aVisibility)
: gfxFontFamily(aName, aVisibility) {
mFaceNamesInitialized = true; // omit from face name lists
}
virtual ~gfxSingleFaceMacFontFamily() = default;
void FindStyleVariationsLocked(FontInfoData* aFontInfoData = nullptr)
MOZ_REQUIRES(mLock) override{};
void LocalizedName(nsACString& aLocalizedName) override;
void ReadOtherFamilyNames(gfxPlatformFontList* aPlatformFontList) override;
bool IsSingleFaceFamily() const override { return true; }
};
void gfxSingleFaceMacFontFamily::LocalizedName(nsACString& aLocalizedName) {
nsAutoreleasePool localPool;
AutoReadLock lock(mLock);
if (!HasOtherFamilyNames()) {
aLocalizedName = mName;
return;
}
gfxFontEntry* fe = mAvailableFonts[0];
NSFont* font = [NSFont fontWithName:GetNSStringForString(NS_ConvertUTF8toUTF16(fe->Name()))
size:0.0];
if (font) {
NSString* localized = [font displayName];
if (localized) {
nsAutoString locName;
GetStringForNSString(localized, locName);
CopyUTF16toUTF8(locName, aLocalizedName);
return;
}
}
// failed to get localized name, just use the canonical one
aLocalizedName = mName;
}
void gfxSingleFaceMacFontFamily::ReadOtherFamilyNames(gfxPlatformFontList* aPlatformFontList) {
AutoWriteLock lock(mLock);
if (mOtherFamilyNamesInitialized) {
return;
}
gfxFontEntry* fe = mAvailableFonts[0];
if (!fe) {
return;
}
const uint32_t kNAME = TRUETYPE_TAG('n', 'a', 'm', 'e');
gfxFontEntry::AutoTable nameTable(fe, kNAME);
if (!nameTable) {
return;
}
mHasOtherFamilyNames = ReadOtherFamilyNamesForFace(aPlatformFontList, nameTable, true);
mOtherFamilyNamesInitialized = true;
}
/* gfxMacPlatformFontList */
#pragma mark -
gfxMacPlatformFontList::gfxMacPlatformFontList()
: gfxPlatformFontList(false), mDefaultFont(nullptr), mUseSizeSensitiveSystemFont(false) {
CheckFamilyList(kBaseFonts);
#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
// cache this in a static variable so that MacOSFontFamily objects
// don't have to repeatedly look it up
sFontManager = [NSFontManager sharedFontManager];
// 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.single-face-list", mSingleFaceFonts);
gfxFontUtils::GetPrefsFontList("font.preload-names-list", mPreloadFonts);
}
gfxMacPlatformFontList::~gfxMacPlatformFontList() {
AutoLock lock(mLock);
if (XRE_IsParentProcess()) {
::CFNotificationCenterRemoveObserver(::CFNotificationCenterGetLocalCenter(), this,
kCTFontManagerRegisteredFontsChangedNotification, 0);
}
if (mDefaultFont) {
::CFRelease(mDefaultFont);
}
}
void gfxMacPlatformFontList::AddFamily(const nsACString& aFamilyName, FontVisibility aVisibility) {
double sizeHint = 0.0;
if (aVisibility == FontVisibility::Hidden && mUseSizeSensitiveSystemFont &&
mSystemDisplayFontFamilyName.Equals(aFamilyName)) {
sizeHint = 128.0;
}
nsAutoCString key;
ToLowerCase(aFamilyName, key);
RefPtr<gfxFontFamily> familyEntry = new gfxMacFontFamily(aFamilyName, aVisibility, sizeHint);
mFontFamilies.InsertOrUpdate(key, RefPtr{familyEntry});
// check the bad underline blocklist
if (mBadUnderlineFamilyNames.ContainsSorted(key)) {
familyEntry->SetBadUnderlineFamily();
}
}
FontVisibility gfxMacPlatformFontList::GetVisibilityForFamily(const nsACString& aName) const {
if (aName[0] == '.' || aName.LowerCaseEqualsLiteral("lastresort")) {
return FontVisibility::Hidden;
}
if (FamilyInList(aName, kBaseFonts)) {
return FontVisibility::Base;
}
#ifdef MOZ_BUNDLED_FONTS
if (mBundledFamilies.Contains(aName)) {
return FontVisibility::Base;
}
#endif
return FontVisibility::User;
}
void gfxMacPlatformFontList::AddFamily(CFStringRef aFamily) {
NSString* family = (NSString*)aFamily;
// CTFontManager includes weird internal family names and
// LastResort, skip over those
if (!family || [family caseInsensitiveCompare:@"LastResort"] == NSOrderedSame ||
[family caseInsensitiveCompare:@".LastResort"] == NSOrderedSame) {
return;
}
nsAutoString familyName;
nsCocoaUtils::GetStringForNSString(family, familyName);
NS_ConvertUTF16toUTF8 nameUtf8(familyName);
AddFamily(nameUtf8, GetVisibilityForFamily(nameUtf8));
}
/* static */
void gfxMacPlatformFontList::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);
CTFontManagerRegisterFontsForURLs(urls, kCTFontManagerScopeProcess, nullptr);
}
void gfxMacPlatformFontList::ReadSystemFontList(dom::SystemFontList* aList)
MOZ_NO_THREAD_SAFETY_ANALYSIS {
// Note: We rely on the records for mSystemTextFontFamilyName and
// mSystemDisplayFontFamilyName (if present) being *before* the main
// font list, so that those names are known in the content process
// by the time we add the actual family records to the font list.
aList->entries().AppendElement(FontFamilyListEntry(
mSystemTextFontFamilyName, FontVisibility::Unknown, kTextSizeSystemFontFamily));
if (mUseSizeSensitiveSystemFont) {
aList->entries().AppendElement(FontFamilyListEntry(
mSystemDisplayFontFamilyName, FontVisibility::Unknown, kDisplaySizeSystemFontFamily));
}
// Now collect the list of available families, with visibility attributes.
for (auto f = mFontFamilies.Iter(); !f.Done(); f.Next()) {
auto macFamily = f.Data().get();
if (macFamily->IsSingleFaceFamily()) {
continue; // skip, this will be recreated separately in the child
}
aList->entries().AppendElement(
FontFamilyListEntry(macFamily->Name(), macFamily->Visibility(), kStandardFontFamily));
}
}
void gfxMacPlatformFontList::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);
}
}
}
#if USE_DEPRECATED_FONT_FAMILY_NAMES
static bool DeprecatedFamilyIsAvailable(const nsACString& aName) {
NSString* family = GetNSStringForString(NS_ConvertUTF8toUTF16(aName));
return [[sFontManager availableMembersOfFontFamily:family] count] > 0;
}
#endif
nsresult gfxMacPlatformFontList::InitFontListForPlatform() {
nsAutoreleasePool localPool;
// 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 (NSString* familyName in (NSArray*)(CFArrayRef)familyNames) {
AddFamily((CFStringRef)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:
// On Catalina or later, we pre-initialize system font-family entries
// in InitSystemFontNames(), so we can just skip them here.
if (nsCocoaFeatures::OnCatalinaOrLater() &&
(ffe.familyName() == mSystemTextFontFamilyName ||
ffe.familyName() == mSystemDisplayFontFamilyName)) {
continue;
}
AddFamily(ffe.familyName(), ffe.visibility());
break;
case kTextSizeSystemFontFamily:
mSystemTextFontFamilyName = ffe.familyName();
break;
case kDisplaySizeSystemFontFamily:
mSystemDisplayFontFamilyName = ffe.familyName();
mUseSizeSensitiveSystemFont = true;
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 gfxMacPlatformFontList::InitSharedFontListForPlatform() {
nsAutoreleasePool localPool;
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 (NSString* familyName in (NSArray*)(CFArrayRef)familyNames) {
nsAutoString name16;
GetStringForNSString(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();
}
}
void gfxMacPlatformFontList::InitAliasesForSingleFaceList() {
for (const auto& familyName : mSingleFaceFonts) {
LOG_FONTLIST(("(fontlist-singleface) face name: %s\n", familyName.get()));
// Each entry in the "single face families" list is expected to be a
// colon-separated pair of FaceName:Family,
// where FaceName is the individual face name (psname) of a font
// that should be exposed as a separate family name,
// and Family is the standard family to which that face belongs.
// The only such face listed by default is
// Osaka-Mono:Osaka
auto colon = familyName.FindChar(':');
if (colon == kNotFound) {
continue;
}
// Look for the parent family in the main font family list,
// and ensure we have loaded its list of available faces.
nsAutoCString key;
GenerateFontListKey(Substring(familyName, colon + 1), key);
fontlist::Family* family = SharedFontList()->FindFamily(key);
if (!family) {
// The parent family is not present, so just ignore this entry.
continue;
}
if (!family->IsInitialized()) {
if (!gfxPlatformFontList::InitializeFamily(family)) {
// This shouldn't ever fail, but if it does, we can safely ignore it.
MOZ_ASSERT(false, "failed to initialize font family");
continue;
}
}
// Truncate the entry from prefs at the colon, so now it is just the
// desired single-face-family name.
nsAutoCString aliasName(Substring(familyName, 0, colon));
// Look through the family's faces to see if this one is present.
fontlist::FontList* list = SharedFontList();
const fontlist::Pointer* facePtrs = family->Faces(list);
for (size_t i = 0; i < family->NumFaces(); i++) {
if (facePtrs[i].IsNull()) {
continue;
}
auto face = static_cast<const fontlist::Face*>(facePtrs[i].ToPtr(list));
if (face->mDescriptor.AsString(list).Equals(aliasName)) {
// Found it! Create an entry in the Alias table.
GenerateFontListKey(aliasName, key);
if (SharedFontList()->FindFamily(key) || mAliasTable.Get(key)) {
// If the family name is already known, something's misconfigured;
// just ignore it.
MOZ_ASSERT(false, "single-face family already known");
break;
}
auto aliasData = mAliasTable.GetOrInsertNew(key);
// The "alias" here isn't based on an existing family, so we don't call
// aliasData->InitFromFamily(); the various flags are left as defaults.
aliasData->mFaces.AppendElement(facePtrs[i]);
aliasData->mBaseFamily = aliasName;
aliasData->mVisibility = family->Visibility();
break;
}
}
}
if (!mAliasTable.IsEmpty()) {
// This will be updated when the font loader completes, but we require
// at least the Osaka-Mono alias to be available immediately.
SharedFontList()->SetAliases(mAliasTable);
}
}
void gfxMacPlatformFontList::InitSingleFaceList() {
for (const auto& familyName : mSingleFaceFonts) {
LOG_FONTLIST(("(fontlist-singleface) face name: %s\n", familyName.get()));
// Each entry in the "single face families" list is expected to be a
// colon-separated pair of FaceName:Family,
// where FaceName is the individual face name (psname) of a font
// that should be exposed as a separate family name,
// and Family is the standard family to which that face belongs.
// The only such face listed by default is
// Osaka-Mono:Osaka
auto colon = familyName.FindChar(':');
if (colon == kNotFound) {
continue;
}
// Look for the parent family in the main font family list,
// and ensure we have loaded its list of available faces.
nsAutoCString key(Substring(familyName, colon + 1));
ToLowerCase(key);
gfxFontFamily* family = mFontFamilies.GetWeak(key);
if (!family || family->IsHidden()) {
continue;
}
family->FindStyleVariations();
// Truncate the entry from prefs at the colon, so now it is just the
// desired single-face-family name.
nsAutoCString aliasName(Substring(familyName, 0, colon));
// Look through the family's faces to see if this one is present.
const gfxFontEntry* fe = nullptr;
family->ReadLock();
for (const auto& face : family->GetFontList()) {
if (face->Name().Equals(aliasName)) {
fe = face;
break;
}
}
family->ReadUnlock();
if (!fe) {
continue;
}
// We found the correct face, so create the single-face family record.
GenerateFontListKey(aliasName, key);
LOG_FONTLIST(("(fontlist-singleface) family name: %s, key: %s\n", aliasName.get(), key.get()));
// add only if doesn't exist already
if (!mFontFamilies.GetWeak(key)) {
RefPtr<gfxFontFamily> familyEntry =
new gfxSingleFaceMacFontFamily(aliasName, family->Visibility());
// We need a separate font entry, because its family name will
// differ from the one we found in the main list.
MacOSFontEntry* fontEntry = new MacOSFontEntry(
fe->Name(), fe->Weight(), true, static_cast<const MacOSFontEntry*>(fe)->mSizeHint);
familyEntry->AddFontEntry(fontEntry);
familyEntry->SetHasStyles(true);
mFontFamilies.InsertOrUpdate(key, std::move(familyEntry));
LOG_FONTLIST(
("(fontlist-singleface) added new family: %s, key: %s\n", aliasName.get(), key.get()));
}
}
}
// System fonts under OSX may contain weird "meta" names but if we create
// a new font using just the Postscript name, the NSFont api returns an object
// with the actual real family name. For example, under OSX 10.11:
//
// [[NSFont menuFontOfSize:8.0] familyName] ==> .AppleSystemUIFont
// [[NSFont fontWithName:[[[NSFont menuFontOfSize:8.0] fontDescriptor] postscriptName]
// size:8.0] familyName] ==> .SF NS Text
static NSString* GetRealFamilyName(NSFont* aFont) {
NSString* psName = [[aFont fontDescriptor] postscriptName];
// With newer macOS versions and SDKs (e.g. when compiled against SDK 10.15),
// [NSFont fontWithName:] fails for hidden system fonts, because the underlying
// Core Text functions it uses reject such names and tell us to use the special
// CTFontCreateUIFontForLanguage API instead.
// To work around this, as we don't yet work directly with the CTFontUIFontType
// identifiers, we create a Core Graphics font (as it doesn't reject system font
// names), and use this to create a Core Text font that we can query for the
// family name.
// Eventually we should move to using CTFontUIFontType constants to identify
// system fonts, and eliminate the need to instantiate them (indirectly) from
// their postscript names.
AutoCFRelease<CGFontRef> cgFont = CGFontCreateWithFontName(CFStringRef(psName));
if (!cgFont) {
return [aFont familyName];
}
AutoCFRelease<CTFontRef> ctFont = CTFontCreateWithGraphicsFont(cgFont, 0.0, nullptr, nullptr);
if (!ctFont) {
return [aFont familyName];
}
NSString* familyName = (NSString*)CTFontCopyFamilyName(ctFont);
return [familyName autorelease];
}
// System fonts under OSX 10.11 use a combination of two families, one
// for text sizes and another for larger, display sizes. Each has a
// different number of weights. There aren't efficient API's for looking
// this information up, so hard code the logic here but confirm via
// debug assertions that the logic is correct.
const CGFloat kTextDisplayCrossover = 20.0; // use text family below this size
void gfxMacPlatformFontList::InitSystemFontNames() {
// On Catalina+, the system font uses optical sizing rather than individual
// faces, so we don't need to look for a separate display-sized face.
mUseSizeSensitiveSystemFont = !nsCocoaFeatures::OnCatalinaOrLater();
// text font family
NSFont* sys = [NSFont systemFontOfSize:0.0];
NSString* textFamilyName = GetRealFamilyName(sys);
nsAutoString familyName;
nsCocoaUtils::GetStringForNSString(textFamilyName, familyName);
CopyUTF16toUTF8(familyName, mSystemTextFontFamilyName);
// On Catalina or later, 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.
if (nsCocoaFeatures::OnCatalinaOrLater()) {
// This family will be populated based on the given NSFont.
RefPtr<gfxFontFamily> fam = new gfxMacFontFamily(mSystemTextFontFamilyName, sys);
if (fam) {
nsAutoCString key;
GenerateFontListKey(mSystemTextFontFamilyName, key);
mFontFamilies.InsertOrUpdate(key, std::move(fam));
}
}
// display font family, if on OSX 10.11 - 10.14
if (mUseSizeSensitiveSystemFont) {
NSFont* displaySys = [NSFont systemFontOfSize:128.0];
NSString* displayFamilyName = GetRealFamilyName(displaySys);
if ([displayFamilyName isEqualToString:textFamilyName]) {
mUseSizeSensitiveSystemFont = false;
} else {
nsCocoaUtils::GetStringForNSString(displayFamilyName, familyName);
CopyUTF16toUTF8(familyName, mSystemDisplayFontFamilyName);
}
}
#ifdef DEBUG
// different system font API's always map to the same family under OSX, so
// just assume that and emit a warning if that ever changes
NSString* sysFamily = GetRealFamilyName([NSFont systemFontOfSize:0.0]);
if ([sysFamily compare:GetRealFamilyName([NSFont boldSystemFontOfSize:0.0])] != NSOrderedSame ||
[sysFamily compare:GetRealFamilyName([NSFont controlContentFontOfSize:0.0])] !=
NSOrderedSame ||
[sysFamily compare:GetRealFamilyName([NSFont menuBarFontOfSize:0.0])] != NSOrderedSame ||
[sysFamily compare:GetRealFamilyName([NSFont toolTipsFontOfSize:0.0])] != NSOrderedSame) {
NS_WARNING("system font types map to different font families"
" -- please log a bug!!");
}
#endif
}
gfxFontFamily* gfxMacPlatformFontList::FindSystemFontFamily(const nsACString& aFamily) {
nsAutoCString key;
GenerateFontListKey(aFamily, key);
gfxFontFamily* familyEntry;
if ((familyEntry = mFontFamilies.GetWeak(key))) {
return CheckFamily(familyEntry);
}
return nullptr;
}
void gfxMacPlatformFontList::RegisteredFontsChangedNotificationCallback(
CFNotificationCenterRef center, void* observer, CFStringRef name, const void* object,
CFDictionaryRef userInfo) {
if (!::CFEqual(name, kCTFontManagerRegisteredFontsChangedNotification)) {
return;
}
gfxMacPlatformFontList* fl = static_cast<gfxMacPlatformFontList*>(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* gfxMacPlatformFontList::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);
if (!str) {
return nullptr;
}
length = 2;
}
// 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;
}
FontFamily gfxMacPlatformFontList::GetDefaultFontForPlatform(nsPresContext* aPresContext,
const gfxFontStyle* aStyle,
nsAtom* aLanguage) {
nsAutoreleasePool localPool;
NSString* defaultFamily = [[NSFont userFontOfSize:aStyle->size] familyName];
nsAutoString familyName;
GetStringForNSString(defaultFamily, familyName);
return FindFamily(aPresContext, NS_ConvertUTF16toUTF8(familyName));
}
int32_t gfxMacPlatformFontList::AppleWeightToCSSWeight(int32_t aAppleWeight) {
if (aAppleWeight < 1)
aAppleWeight = 1;
else if (aAppleWeight > kAppleMaxWeight)
aAppleWeight = kAppleMaxWeight;
return gAppleWeightToCSSWeight[aAppleWeight];
}
gfxFontEntry* gfxMacPlatformFontList::LookupLocalFont(nsPresContext* aPresContext,
const nsACString& aFontName,
WeightRange aWeightForEntry,
StretchRange aStretchForEntry,
SlantStyleRange aStyleForEntry) {
if (aFontName.IsEmpty() || aFontName[0] == '.') {
return nullptr;
}
AutoLock lock(mLock);
nsAutoreleasePool localPool;
// LookupLocalFont.
static bool firstTime = true;
if (firstTime) {
firstTime = false;
// get the main bundle identifier
CFBundleRef mainBundle = ::CFBundleGetMainBundle();
CFStringRef mainBundleID = nullptr;
if (mainBundle) {
mainBundleID = ::CFBundleGetIdentifier(mainBundle);
}
if (!mainBundleID) {
NS_WARNING("missing bundle ID, packaging set up incorrectly");
} else {
CTFontManagerSetAutoActivationSetting(mainBundleID, kCTFontManagerAutoActivationDisabled);
}
}
CrashReporter::AutoAnnotateCrashReport autoFontName(CrashReporter::Annotation::FontName,
aFontName);
NSString* faceName = GetNSStringForString(NS_ConvertUTF8toUTF16(aFontName));
// lookup face based on postscript or full name
AutoCFRelease<CGFontRef> fontRef = CGFontCreateWithFontName(CFStringRef(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