gecko-dev/gfx/thebes/gfxMacPlatformFontList.mm

1381 строка
46 KiB
Plaintext

/* -*- Mode: ObjC; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* ***** 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 "gfxPlatformMac.h"
#include "gfxMacPlatformFontList.h"
#include "gfxMacFont.h"
#include "gfxUserFontSet.h"
#include "harfbuzz/hb.h"
#include "nsServiceManagerUtils.h"
#include "nsTArray.h"
#include "nsDirectoryServiceUtils.h"
#include "nsDirectoryServiceDefs.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsISimpleEnumerator.h"
#include "nsCharTraits.h"
#include "nsCocoaFeatures.h"
#include "nsCocoaUtils.h"
#include "gfxFontConstants.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Preferences.h"
#include "mozilla/Telemetry.h"
#include "mozilla/gfx/2D.h"
#include <unistd.h>
#include <time.h>
using namespace mozilla;
// 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& aDist)
{
aDist.SetLength([aSrc length]);
[aSrc getCharacters:reinterpret_cast<unichar*>(aDist.BeginWriting())];
}
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) {
return NS_OK;
}
RefPtr<gfxCharacterMap> charmap;
nsresult rv;
bool symbolFont = false; // currently ignored
if (aFontInfoData && (charmap = GetCMAPFromFontInfo(aFontInfoData,
mUVSOffset,
symbolFont))) {
rv = NS_OK;
} else {
uint32_t kCMAP = TRUETYPE_TAG('c','m','a','p');
charmap = new gfxCharacterMap();
AutoTable cmapTable(this, kCMAP);
if (cmapTable) {
bool unicodeFont = false; // currently ignored
uint32_t cmapLen;
const uint8_t* cmapData =
reinterpret_cast<const uint8_t*>(hb_blob_get_data(cmapTable,
&cmapLen));
rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen,
*charmap, mUVSOffset,
unicodeFont, symbolFont);
} else {
rv = NS_ERROR_NOT_AVAILABLE;
}
}
if (NS_SUCCEEDED(rv) && !HasGraphiteTables()) {
// We 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 and/or
// opentype layout tables
bool hasAATLayout = HasFontTable(TRUETYPE_TAG('m','o','r','x')) ||
HasFontTable(TRUETYPE_TAG('m','o','r','t'));
bool hasGSUB = HasFontTable(TRUETYPE_TAG('G','S','U','B'));
bool hasGPOS = HasFontTable(TRUETYPE_TAG('G','P','O','S'));
if (hasAATLayout && !(hasGSUB || hasGPOS)) {
mRequiresAAT = true; // prefer CoreText if font has no OTL tables
}
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)) {
continue;
}
charmap->ClearRange(sr->rangeStart, sr->rangeEnd);
}
}
}
mHasCmapTable = NS_SUCCEEDED(rv);
if (mHasCmapTable) {
gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList();
mCharacterMap = pfl->FindCharMap(charmap);
} else {
// if error occurred, initialize to null cmap
mCharacterMap = new gfxCharacterMap();
}
LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d hash: %8.8x%s\n",
NS_ConvertUTF16toUTF8(mName).get(),
charmap->SizeOfIncludingThis(moz_malloc_size_of),
charmap->mHash, mCharacterMap == charmap ? " new" : ""));
if (LOG_CMAPDATA_ENABLED()) {
char prefix[256];
sprintf(prefix, "(cmapdata) name: %.220s",
NS_ConvertUTF16toUTF8(mName).get());
charmap->Dump(prefix, eGfxLog_cmapdata);
}
return rv;
}
gfxFont*
MacOSFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold)
{
return new gfxMacFont(this, aFontStyle, aNeedsBold);
}
bool
MacOSFontEntry::IsCFF()
{
if (!mIsCFFInitialized) {
mIsCFFInitialized = true;
mIsCFF = HasFontTable(TRUETYPE_TAG('C','F','F',' '));
}
return mIsCFF;
}
MacOSFontEntry::MacOSFontEntry(const nsAString& aPostscriptName,
int32_t aWeight,
bool aIsStandardFace)
: gfxFontEntry(aPostscriptName, aIsStandardFace),
mFontRef(NULL),
mFontRefInitialized(false),
mRequiresAAT(false),
mIsCFF(false),
mIsCFFInitialized(false)
{
mWeight = aWeight;
}
MacOSFontEntry::MacOSFontEntry(const nsAString& aPostscriptName,
CGFontRef aFontRef,
uint16_t aWeight, uint16_t aStretch,
uint8_t aStyle,
bool aIsDataUserFont,
bool aIsLocalUserFont)
: gfxFontEntry(aPostscriptName, false),
mFontRef(NULL),
mFontRefInitialized(false),
mRequiresAAT(false),
mIsCFF(false),
mIsCFFInitialized(false)
{
mFontRef = aFontRef;
mFontRefInitialized = true;
::CFRetain(mFontRef);
mWeight = aWeight;
mStretch = aStretch;
mFixedPitch = false; // xxx - do we need this for downloaded fonts?
mStyle = aStyle;
NS_ASSERTION(!(aIsDataUserFont && aIsLocalUserFont),
"userfont is either a data font or a local font");
mIsDataUserFont = aIsDataUserFont;
mIsLocalUserFont = aIsLocalUserFont;
}
CGFontRef
MacOSFontEntry::GetFontRef()
{
if (!mFontRefInitialized) {
mFontRefInitialized = true;
NSString *psname = GetNSStringForString(mName);
mFontRef = ::CGFontCreateWithFontName(CFStringRef(psname));
}
return mFontRef;
}
// 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)
{
CGFontRef fontRef = GetFontRef();
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;
CGFontRef fontRef = GetFontRef();
if (!fontRef) {
return false;
}
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);
}
::CFRelease(tags);
}
return mAvailableTables.GetEntry(aTableTag);
}
void
MacOSFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
FontListSizes* aSizes) const
{
aSizes->mFontListSize += aMallocSizeOf(this);
AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
}
/* gfxMacFontFamily */
#pragma mark-
class gfxMacFontFamily : public gfxFontFamily
{
public:
explicit gfxMacFontFamily(nsAString& aName) :
gfxFontFamily(aName)
{}
virtual ~gfxMacFontFamily() {}
virtual void LocalizedName(nsAString& aLocalizedName);
virtual void FindStyleVariations(FontInfoData *aFontInfoData = nullptr);
};
void
gfxMacFontFamily::LocalizedName(nsAString& aLocalizedName)
{
nsAutoreleasePool localPool;
if (!HasOtherFamilyNames()) {
aLocalizedName = mName;
return;
}
NSString *family = GetNSStringForString(mName);
NSString *localized = [sFontManager
localizedNameForFamily:family
face:nil];
if (localized) {
GetStringForNSString(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);
}
void
gfxMacFontFamily::FindStyleVariations(FontInfoData *aFontInfoData)
{
if (mHasStyles)
return;
nsAutoreleasePool localPool;
NSString *family = GetNSStringForString(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 = GetWeightOverride(postscriptFontName);
if (cssWeight) {
// scale down and clamp, to get a value from 1..9
cssWeight = ((cssWeight + 50) / 100);
cssWeight = std::max(1, std::min(cssWeight, 9));
} else {
cssWeight =
gfxMacPlatformFontList::AppleWeightToCSSWeight(appKitWeight);
}
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(postscriptFontName, cssWeight, isStandardFace);
if (!fontEntry) {
break;
}
// set additional properties based on the traits reported by Cocoa
if (macTraits & (NSCondensedFontMask | NSNarrowFontMask | NSCompressedFontMask)) {
fontEntry->mStretch = NS_FONT_STRETCH_CONDENSED;
} else if (macTraits & NSExpandedFontMask) {
fontEntry->mStretch = NS_FONT_STRETCH_EXPANDED;
}
// Cocoa fails to set the Italic traits bit for HelveticaLightItalic,
// at least (see bug 611855), so check for style name endings as well
if ((macTraits & NSItalicFontMask) ||
[facename hasSuffix:@"Italic"] ||
[facename hasSuffix:@"Oblique"])
{
fontEntry->mStyle = NS_FONT_STYLE_ITALIC;
}
if (macTraits & NSFixedPitchFontMask) {
fontEntry->mFixedPitch = true;
}
if (LOG_FONTLIST_ENABLED()) {
LOG_FONTLIST(("(fontlist) added (%s) to family (%s)"
" with style: %s weight: %d stretch: %d"
" (apple-weight: %d macTraits: %8.8x)",
NS_ConvertUTF16toUTF8(fontEntry->Name()).get(),
NS_ConvertUTF16toUTF8(Name()).get(),
fontEntry->IsItalic() ? "italic" : "normal",
cssWeight, fontEntry->Stretch(),
appKitWeight, macTraits));
}
// insert into font entry array of family
AddFontEntry(fontEntry);
}
SortAvailableFonts();
SetHasStyles(true);
if (mIsBadUnderlineFamily) {
SetBadUnderlineFonts();
}
}
/* gfxSingleFaceMacFontFamily */
#pragma mark-
class gfxSingleFaceMacFontFamily : public gfxFontFamily
{
public:
explicit gfxSingleFaceMacFontFamily(nsAString& aName) :
gfxFontFamily(aName)
{
mFaceNamesInitialized = true; // omit from face name lists
}
virtual ~gfxSingleFaceMacFontFamily() {}
virtual void LocalizedName(nsAString& aLocalizedName);
virtual void ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList);
};
void
gfxSingleFaceMacFontFamily::LocalizedName(nsAString& aLocalizedName)
{
nsAutoreleasePool localPool;
if (!HasOtherFamilyNames()) {
aLocalizedName = mName;
return;
}
gfxFontEntry *fe = mAvailableFonts[0];
NSFont *font = [NSFont fontWithName:GetNSStringForString(fe->Name())
size:0.0];
if (font) {
NSString *localized = [font displayName];
if (localized) {
GetStringForNSString(localized, aLocalizedName);
return;
}
}
// failed to get localized name, just use the canonical one
aLocalizedName = mName;
}
void
gfxSingleFaceMacFontFamily::ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList)
{
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)
{
#ifdef MOZ_BUNDLED_FONTS
ActivateBundledFonts();
#endif
::CFNotificationCenterAddObserver(::CFNotificationCenterGetLocalCenter(),
this,
RegisteredFontsChangedNotificationCallback,
kCTFontManagerRegisteredFontsChangedNotification,
0,
CFNotificationSuspensionBehaviorDeliverImmediately);
// cache this in a static variable so that MacOSFontFamily objects
// don't have to repeatedly look it up
sFontManager = [NSFontManager sharedFontManager];
}
gfxMacPlatformFontList::~gfxMacPlatformFontList()
{
if (mDefaultFont) {
::CFRelease(mDefaultFont);
}
}
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) {
return;
}
bool hiddenSystemFont = [family hasPrefix:@"."];
FontFamilyTable& table =
hiddenSystemFont ? mSystemFontFamilies : mFontFamilies;
nsAutoString familyName;
nsCocoaUtils::GetStringForNSString(family, familyName);
nsAutoString key;
ToLowerCase(familyName, key);
gfxFontFamily* familyEntry = new gfxMacFontFamily(familyName);
table.Put(key, familyEntry);
// check the bad underline blacklist
if (mBadUnderlineFamilyNames.Contains(key)) {
familyEntry->SetBadUnderlineFamily();
}
}
nsresult
gfxMacPlatformFontList::InitFontList()
{
nsAutoreleasePool localPool;
Telemetry::AutoTimer<Telemetry::MAC_INITFONTLIST_TOTAL> timer;
// reset font lists
gfxPlatformFontList::InitFontList();
mSystemFontFamilies.Clear();
// iterate over available families
CFArrayRef familyNames = CTFontManagerCopyAvailableFontFamilyNames();
for (NSString* familyName in (NSArray*)familyNames) {
AddFamily((CFStringRef)familyName);
}
CFRelease(familyNames);
InitSingleFaceList();
InitSystemFonts();
// 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::InitSingleFaceList()
{
nsAutoTArray<nsString, 10> singleFaceFonts;
gfxFontUtils::GetPrefsFontList("font.single-face-list", singleFaceFonts);
uint32_t numFonts = singleFaceFonts.Length();
for (uint32_t i = 0; i < numFonts; i++) {
LOG_FONTLIST(("(fontlist-singleface) face name: %s\n",
NS_ConvertUTF16toUTF8(singleFaceFonts[i]).get()));
gfxFontEntry *fontEntry = LookupLocalFont(singleFaceFonts[i],
400, 0,
NS_FONT_STYLE_NORMAL);
if (fontEntry) {
nsAutoString familyName, key;
familyName = singleFaceFonts[i];
GenerateFontListKey(familyName, key);
LOG_FONTLIST(("(fontlist-singleface) family name: %s, key: %s\n",
NS_ConvertUTF16toUTF8(familyName).get(),
NS_ConvertUTF16toUTF8(key).get()));
// add only if doesn't exist already
if (!mFontFamilies.GetWeak(key)) {
gfxFontFamily *familyEntry =
new gfxSingleFaceMacFontFamily(familyName);
// LookupLocalFont sets this, need to clear
fontEntry->mIsLocalUserFont = false;
familyEntry->AddFontEntry(fontEntry);
familyEntry->SetHasStyles(true);
mFontFamilies.Put(key, familyEntry);
LOG_FONTLIST(("(fontlist-singleface) added new family\n",
NS_ConvertUTF16toUTF8(familyName).get(),
NS_ConvertUTF16toUTF8(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)
{
NSFont* f = [NSFont fontWithName: [[aFont fontDescriptor] postscriptName]
size: 0.0];
return [f familyName];
}
// 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::InitSystemFonts()
{
// system font under 10.11 are two distinct families for text/display sizes
if (nsCocoaFeatures::OnElCapitanOrLater()) {
mUseSizeSensitiveSystemFont = true;
}
// text font family
NSFont* sys = [NSFont systemFontOfSize: 0.0];
NSString* textFamilyName = GetRealFamilyName(sys);
nsAutoString familyName;
nsCocoaUtils::GetStringForNSString(textFamilyName, familyName);
mSystemTextFontFamily = FindSystemFontFamily(familyName);
NS_ASSERTION(mSystemTextFontFamily, "null system display font family");
// display font family, if on OSX 10.11
if (mUseSizeSensitiveSystemFont) {
sys = [NSFont systemFontOfSize: 128.0];
NSString* displayFamilyName = GetRealFamilyName(sys);
nsCocoaUtils::GetStringForNSString(displayFamilyName, familyName);
mSystemDisplayFontFamily = FindSystemFontFamily(familyName);
NS_ASSERTION(mSystemDisplayFontFamily, "null system display font family");
#if DEBUG
// confirm that the optical size switch is at 20.0
NS_ASSERTION(mSystemTextFontFamily && mSystemDisplayFontFamily &&
[textFamilyName compare:displayFamilyName] != NSOrderedSame,
"system text/display fonts are the same!");
NSString* fam19 = GetRealFamilyName([NSFont systemFontOfSize:
(kTextDisplayCrossover - 1.0)]);
NSString* fam20 = GetRealFamilyName([NSFont systemFontOfSize:
kTextDisplayCrossover]);
NS_ASSERTION(fam19 && fam20 && [fam19 compare:fam20] != NSOrderedSame,
"system text/display font size switch point is not as expected!");
#endif
}
#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 nsAString& aFamily)
{
nsAutoString key;
GenerateFontListKey(aFamily, key);
gfxFontFamily* familyEntry;
// lookup in hidden system family name list
if ((familyEntry = mSystemFontFamilies.GetWeak(key))) {
return CheckFamily(familyEntry);
}
// lookup in user-exposed family name list
if ((familyEntry = mFontFamilies.GetWeak(key))) {
return CheckFamily(familyEntry);
}
return nullptr;
}
bool
gfxMacPlatformFontList::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName)
{
gfxFontFamily *family = FindFamily(aFontName);
if (family) {
family->LocalizedName(aFamilyName);
return true;
}
return false;
}
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);
// xxx - should be carefully pruning the list of fonts, not rebuilding it from scratch
fl->UpdateFontList();
// modify a preference that will trigger reflow everywhere
fl->ForceGlobalReflow();
}
gfxFontEntry*
gfxMacPlatformFontList::GlobalFontFallback(const uint32_t aCh,
int32_t aRunScript,
const gfxFontStyle* aMatchStyle,
uint32_t& aCmapCount,
gfxFontFamily** aMatchedFamily)
{
bool useCmaps = gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
if (useCmaps) {
return gfxPlatformFontList::GlobalFontFallback(aCh,
aRunScript,
aMatchStyle,
aCmapCount,
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;
CTFontRef fallback;
bool cantUseFallbackFont = false;
if (!mDefaultFont) {
mDefaultFont = ::CTFontCreateWithName(CFSTR("LucidaGrande"), 12.f,
NULL);
}
fallback = ::CTFontCreateForString(mDefaultFont, str,
::CFRangeMake(0, length));
if (fallback) {
CFStringRef familyNameRef = ::CTFontCopyFamilyName(fallback);
::CFRelease(fallback);
if (familyNameRef &&
::CFStringCompare(familyNameRef, CFSTR("LastResort"),
kCFCompareCaseInsensitive) != kCFCompareEqualTo)
{
nsAutoTArray<UniChar, 1024> buffer;
CFIndex familyNameLen = ::CFStringGetLength(familyNameRef);
buffer.SetLength(familyNameLen+1);
::CFStringGetCharacters(familyNameRef, ::CFRangeMake(0, familyNameLen),
buffer.Elements());
buffer[familyNameLen] = 0;
nsDependentString familyNameString(reinterpret_cast<char16_t*>(buffer.Elements()), familyNameLen);
bool needsBold; // ignored in the system fallback case
gfxFontFamily *family = FindFamily(familyNameString);
if (family) {
fontEntry = family->FindFontForStyle(*aMatchStyle, needsBold);
if (fontEntry) {
if (fontEntry->HasCharacter(aCh)) {
*aMatchedFamily = family;
} else {
fontEntry = nullptr;
cantUseFallbackFont = true;
}
}
}
}
if (familyNameRef) {
::CFRelease(familyNameRef);
}
}
if (cantUseFallbackFont) {
Telemetry::Accumulate(Telemetry::BAD_FALLBACK_FONT, cantUseFallbackFont);
}
::CFRelease(str);
return fontEntry;
}
gfxFontFamily*
gfxMacPlatformFontList::GetDefaultFont(const gfxFontStyle* aStyle)
{
nsAutoreleasePool localPool;
NSString *defaultFamily = [[NSFont userFontOfSize:aStyle->size] familyName];
nsAutoString familyName;
GetStringForNSString(defaultFamily, familyName);
return FindFamily(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(const nsAString& aFontName,
uint16_t aWeight,
int16_t aStretch,
uint8_t aStyle)
{
nsAutoreleasePool localPool;
NSString *faceName = GetNSStringForString(aFontName);
MacOSFontEntry *newFontEntry;
// lookup face based on postscript or full name
CGFontRef fontRef = ::CGFontCreateWithFontName(CFStringRef(faceName));
if (!fontRef) {
return nullptr;
}
NS_ASSERTION(aWeight >= 100 && aWeight <= 900,
"bogus font weight value!");
newFontEntry =
new MacOSFontEntry(aFontName, fontRef, aWeight, aStretch, aStyle,
false, true);
::CFRelease(fontRef);
return newFontEntry;
}
static void ReleaseData(void *info, const void *data, size_t size)
{
free((void*)data);
}
gfxFontEntry*
gfxMacPlatformFontList::MakePlatformFont(const nsAString& aFontName,
uint16_t aWeight,
int16_t aStretch,
uint8_t aStyle,
const uint8_t* aFontData,
uint32_t aLength)
{
NS_ASSERTION(aFontData, "MakePlatformFont called with null data");
NS_ASSERTION(aWeight >= 100 && aWeight <= 900, "bogus font weight value!");
// create the font entry
nsAutoString uniqueName;
nsresult rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName);
if (NS_FAILED(rv)) {
return nullptr;
}
CGDataProviderRef provider =
::CGDataProviderCreateWithData(nullptr, aFontData, aLength,
&ReleaseData);
CGFontRef fontRef = ::CGFontCreateWithDataProvider(provider);
::CGDataProviderRelease(provider);
if (!fontRef) {
return nullptr;
}
nsAutoPtr<MacOSFontEntry>
newFontEntry(new MacOSFontEntry(uniqueName, fontRef, aWeight,
aStretch, aStyle, true, false));
::CFRelease(fontRef);
// if succeeded and font cmap is good, return the new font
if (newFontEntry->mIsValid && NS_SUCCEEDED(newFontEntry->ReadCMAP())) {
return newFontEntry.forget();
}
// if something is funky about this font, delete immediately
#if DEBUG
NS_WARNING("downloaded font not loaded properly");
#endif
return nullptr;
}
// 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";
gfxFontFamily*
gfxMacPlatformFontList::FindFamily(const nsAString& aFamily, gfxFontStyle* aStyle,
gfxFloat aDevToCssSize)
{
// search for special system font name, -apple-system
if (aFamily.EqualsLiteral(kSystemFont_system)) {
if (mUseSizeSensitiveSystemFont &&
aStyle && (aStyle->size * aDevToCssSize) >= kTextDisplayCrossover) {
return mSystemDisplayFontFamily;
}
return mSystemTextFontFamily;
}
return gfxPlatformFontList::FindFamily(aFamily, aStyle, aDevToCssSize);
}
void
gfxMacPlatformFontList::LookupSystemFont(LookAndFeel::FontID aSystemFontID,
nsAString& aSystemFontName,
gfxFontStyle &aFontStyle,
float aDevPixPerCSSPixel)
{
// code moved here from widget/cocoa/nsLookAndFeel.mm
NSFont *font = nullptr;
char* systemFontName = nullptr;
switch (aSystemFontID) {
case LookAndFeel::eFont_MessageBox:
case LookAndFeel::eFont_StatusBar:
case LookAndFeel::eFont_List:
case LookAndFeel::eFont_Field:
case LookAndFeel::eFont_Button:
case LookAndFeel::eFont_Widget:
font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
systemFontName = (char*) kSystemFont_system;
break;
case LookAndFeel::eFont_SmallCaption:
font = [NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]];
systemFontName = (char*) kSystemFont_system;
break;
case LookAndFeel::eFont_Icon: // used in urlbar; tried labelFont, but too small
case LookAndFeel::eFont_Workspace:
case LookAndFeel::eFont_Desktop:
case LookAndFeel::eFont_Info:
font = [NSFont controlContentFontOfSize:0.0];
systemFontName = (char*) kSystemFont_system;
break;
case LookAndFeel::eFont_PullDownMenu:
font = [NSFont menuBarFontOfSize:0.0];
systemFontName = (char*) kSystemFont_system;
break;
case LookAndFeel::eFont_Tooltips:
font = [NSFont toolTipsFontOfSize:0.0];
systemFontName = (char*) kSystemFont_system;
break;
case LookAndFeel::eFont_Caption:
case LookAndFeel::eFont_Menu:
case LookAndFeel::eFont_Dialog:
default:
font = [NSFont systemFontOfSize:0.0];
systemFontName = (char*) kSystemFont_system;
break;
}
NS_ASSERTION(font, "system font not set");
NS_ASSERTION(systemFontName, "system font name not set");
if (systemFontName) {
aSystemFontName.AssignASCII(systemFontName);
}
NSFontSymbolicTraits traits = [[font fontDescriptor] symbolicTraits];
aFontStyle.style =
(traits & NSFontItalicTrait) ? NS_FONT_STYLE_ITALIC : NS_FONT_STYLE_NORMAL;
aFontStyle.weight =
(traits & NSFontBoldTrait) ? NS_FONT_WEIGHT_BOLD : NS_FONT_WEIGHT_NORMAL;
aFontStyle.stretch =
(traits & NSFontExpandedTrait) ?
NS_FONT_STRETCH_EXPANDED : (traits & NSFontCondensedTrait) ?
NS_FONT_STRETCH_CONDENSED : NS_FONT_STRETCH_NORMAL;
// convert size from css pixels to device pixels
aFontStyle.size = [font pointSize] * aDevPixPerCSSPixel;
aFontStyle.systemFont = true;
}
// used to load system-wide font info on off-main thread
class MacFontInfo : public FontInfoData {
public:
MacFontInfo(bool aLoadOtherNames,
bool aLoadFaceNames,
bool aLoadCmaps) :
FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps)
{}
virtual ~MacFontInfo() {}
virtual void Load() {
nsAutoreleasePool localPool;
// bug 975460 - async font loader crashes sometimes under 10.6, disable
if (nsCocoaFeatures::OnLionOrLater()) {
FontInfoData::Load();
}
}
// loads font data for all members of a given family
virtual void LoadFontFamilyData(const nsAString& aFamilyName);
};
void
MacFontInfo::LoadFontFamilyData(const nsAString& aFamilyName)
{
// family name ==> CTFontDescriptor
NSString *famName = GetNSStringForString(aFamilyName);
CFStringRef family = CFStringRef(famName);
CFMutableDictionaryRef attr =
CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, family);
CTFontDescriptorRef fd = CTFontDescriptorCreateWithAttributes(attr);
CFRelease(attr);
CFArrayRef matchingFonts =
CTFontDescriptorCreateMatchingFontDescriptors(fd, NULL);
CFRelease(fd);
if (!matchingFonts) {
return;
}
nsTArray<nsString> otherFamilyNames;
bool hasOtherFamilyNames = true;
// iterate over faces in the family
int f, numFaces = (int) CFArrayGetCount(matchingFonts);
for (f = 0; f < numFaces; f++) {
mLoadStats.fonts++;
CTFontDescriptorRef faceDesc =
(CTFontDescriptorRef)CFArrayGetValueAtIndex(matchingFonts, f);
if (!faceDesc) {
continue;
}
CTFontRef fontRef = CTFontCreateWithFontDescriptor(faceDesc,
0.0, nullptr);
if (!fontRef) {
NS_WARNING("failed to create a CTFontRef");
continue;
}
if (mLoadCmaps) {
// face name
CFStringRef faceName = (CFStringRef)
CTFontDescriptorCopyAttribute(faceDesc, kCTFontNameAttribute);
nsAutoTArray<UniChar, 1024> buffer;
CFIndex len = CFStringGetLength(faceName);
buffer.SetLength(len+1);
CFStringGetCharacters(faceName, ::CFRangeMake(0, len),
buffer.Elements());
buffer[len] = 0;
nsAutoString fontName(reinterpret_cast<char16_t*>(buffer.Elements()),
len);
// load the cmap data
FontFaceData fontData;
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;
bool unicodeFont = false; // ignored
bool symbolFont = false;
nsresult rv;
rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, offset,
unicodeFont, symbolFont);
if (NS_SUCCEEDED(rv)) {
fontData.mCharacterMap = charmap;
fontData.mUVSOffset = offset;
fontData.mSymbolFont = symbolFont;
mLoadStats.cmaps++;
}
CFRelease(cmapTable);
}
mFontFaceData.Put(fontName, fontData);
CFRelease(faceName);
}
if (mLoadOtherNames && hasOtherFamilyNames) {
CFDataRef nameTable = CTFontCopyTable(fontRef, kCTFontTableName,
kCTFontTableOptionNoOptions);
if (nameTable) {
const char *nameData = (const char*)CFDataGetBytePtr(nameTable);
uint32_t nameLen = CFDataGetLength(nameTable);
gfxFontFamily::ReadOtherFamilyNamesForFace(aFamilyName,
nameData, nameLen,
otherFamilyNames,
false);
hasOtherFamilyNames = otherFamilyNames.Length() != 0;
CFRelease(nameTable);
}
}
CFRelease(fontRef);
}
CFRelease(matchingFonts);
// if found other names, insert them in the hash table
if (otherFamilyNames.Length() != 0) {
mOtherFamilyNames.Put(aFamilyName, otherFamilyNames);
mLoadStats.othernames += otherFamilyNames.Length();
}
}
already_AddRefed<FontInfoData>
gfxMacPlatformFontList::CreateFontInfoData()
{
bool loadCmaps = !UsesSystemFallback() ||
gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
RefPtr<MacFontInfo> fi =
new MacFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps);
return fi.forget();
}
#ifdef MOZ_BUNDLED_FONTS
void
gfxMacPlatformFontList::ActivateBundledFonts()
{
nsCOMPtr<nsIFile> localDir;
nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir));
if (NS_FAILED(rv)) {
return;
}
if (NS_FAILED(localDir->Append(NS_LITERAL_STRING("fonts")))) {
return;
}
bool isDir;
if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) {
return;
}
nsCOMPtr<nsISimpleEnumerator> e;
rv = localDir->GetDirectoryEntries(getter_AddRefs(e));
if (NS_FAILED(rv)) {
return;
}
bool hasMore;
while (NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore) {
nsCOMPtr<nsISupports> entry;
if (NS_FAILED(e->GetNext(getter_AddRefs(entry)))) {
break;
}
nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
if (!file) {
continue;
}
nsCString path;
if (NS_FAILED(file->GetNativePath(path))) {
continue;
}
CFURLRef fontURL =
::CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
(uint8_t*)path.get(),
path.Length(),
false);
if (fontURL) {
CFErrorRef error = nullptr;
::CTFontManagerRegisterFontsForURL(fontURL,
kCTFontManagerScopeProcess,
&error);
::CFRelease(fontURL);
}
}
}
#endif