diff -r 000000000000 -r 6474c204b198 gfx/thebes/gfxMacPlatformFontList.mm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gfx/thebes/gfxMacPlatformFontList.mm Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1127 @@ +/* -*- 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 + * Masayuki Nakano + * John Daggett + * Jonathan Kew + * + * 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 ***** */ + +#ifdef MOZ_LOGGING +#define FORCE_PR_LOG /* Allow logging in the release build */ +#endif +#include "prlog.h" + +#include + +#import + +#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 "nsISimpleEnumerator.h" +#include "nsCharTraits.h" +#include "nsCocoaFeatures.h" +#include "gfxFontConstants.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" +#include "mozilla/Telemetry.h" +#include "mozilla/gfx/2D.h" + +#include +#include + +using namespace mozilla; + +class nsAutoreleasePool { +public: + nsAutoreleasePool() + { + mLocalPool = [[NSAutoreleasePool alloc] init]; + } + ~nsAutoreleasePool() + { + [mLocalPool release]; + } +private: + NSAutoreleasePool *mLocalPool; +}; + +// 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(aDist.BeginWriting())]; +} + +static NSString* GetNSStringForString(const nsAString& aSrc) +{ + return [NSString stringWithCharacters:reinterpret_cast(aSrc.BeginReading()) + length:aSrc.Length()]; +} + +#ifdef PR_LOGGING + +#define LOG_FONTLIST(args) PR_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), \ + PR_LOG_DEBUG, args) +#define LOG_FONTLIST_ENABLED() PR_LOG_TEST( \ + gfxPlatform::GetLog(eGfxLog_fontlist), \ + PR_LOG_DEBUG) +#define LOG_CMAPDATA_ENABLED() PR_LOG_TEST( \ + gfxPlatform::GetLog(eGfxLog_cmapdata), \ + PR_LOG_DEBUG) + +#endif // PR_LOGGING + +#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; + } + + nsRefPtr charmap; + nsresult rv; + bool symbolFont; + + 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, symbolFont = false; // currently ignored + uint32_t cmapLen; + const uint8_t* cmapData = + reinterpret_cast(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(); + } + +#ifdef PR_LOGGING + 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); + } +#endif + + 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, + uint32_t aItalicStyle, + bool aIsUserFont, bool aIsLocal) + : 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? + mItalic = (aItalicStyle & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0; + mIsUserFont = aIsUserFont; + mIsLocalUserFont = aIsLocal; +} + +CGFontRef +MacOSFontEntry::GetFontRef() +{ + if (!mFontRefInitialized) { + mFontRefInitialized = true; + NSString *psname = GetNSStringForString(mName); + mFontRef = ::CGFontCreateWithFontName(CFStringRef(psname)); + } + return mFontRef; +} + +/*static*/ void +MacOSFontEntry::DestroyBlobFunc(void* aUserData) +{ + ::CFRelease((CFDataRef)aUserData); +} + +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, + (void*)dataRef, DestroyBlobFunc); + } + + return nullptr; +} + +bool +MacOSFontEntry::HasFontTable(uint32_t aTableTag) +{ + nsAutoreleasePool localPool; + + CGFontRef fontRef = GetFontRef(); + if (!fontRef) { + return false; + } + + CFDataRef tableData = ::CGFontCopyTableForTag(fontRef, aTableTag); + if (!tableData) { + return false; + } + + ::CFRelease(tableData); + return true; +} + +void +MacOSFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +/* gfxMacFontFamily */ +#pragma mark- + +class gfxMacFontFamily : public gfxFontFamily +{ +public: + 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->mItalic = true; + } + if (macTraits & NSFixedPitchFontMask) { + fontEntry->mFixedPitch = true; + } + +#ifdef PR_LOGGING + 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)); + } +#endif + + // insert into font entry array of family + AddFontEntry(fontEntry); + } + + SortAvailableFonts(); + SetHasStyles(true); + + if (mIsBadUnderlineFamily) { + SetBadUnderlineFonts(); + } +} + + +/* gfxSingleFaceMacFontFamily */ +#pragma mark- + +class gfxSingleFaceMacFontFamily : public gfxFontFamily +{ +public: + 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) +{ + ::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); + } +} + +nsresult +gfxMacPlatformFontList::InitFontList() +{ + nsAutoreleasePool localPool; + + Telemetry::AutoTimer timer; + + // reset font lists + gfxPlatformFontList::InitFontList(); + + // iterate over available families + + CFArrayRef familyNames = CTFontManagerCopyAvailableFontFamilyNames(); + + // iterate over families + uint32_t i, numFamilies; + + numFamilies = CFArrayGetCount(familyNames); + for (i = 0; i < numFamilies; i++) { + CFStringRef family = (CFStringRef)CFArrayGetValueAtIndex(familyNames, i); + + // CTFontManager includes weird internal family names and + // LastResort, skip over those + if (!family || + ::CFStringHasPrefix(family, CFSTR(".")) || + CFStringCompare(family, CFSTR("LastResort"), + kCFCompareCaseInsensitive) == kCFCompareEqualTo) { + continue; + } + + nsAutoTArray buffer; + CFIndex len = ::CFStringGetLength(family); + buffer.SetLength(len+1); + ::CFStringGetCharacters(family, ::CFRangeMake(0, len), + buffer.Elements()); + buffer[len] = 0; + nsAutoString familyName(reinterpret_cast(buffer.Elements()), len); + + // create a family entry + gfxFontFamily *familyEntry = new gfxMacFontFamily(familyName); + if (!familyEntry) break; + + // add the family entry to the hash table + ToLowerCase(familyName); + mFontFamilies.Put(familyName, familyEntry); + + // check the bad underline blacklist + if (mBadUnderlineFamilyNames.Contains(familyName)) + familyEntry->SetBadUnderlineFamily(); + } + + CFRelease(familyNames); + + 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::InitSingleFaceList() +{ + nsAutoTArray singleFaceFonts; + gfxFontUtils::GetPrefsFontList("font.single-face-list", singleFaceFonts); + + uint32_t numFonts = singleFaceFonts.Length(); + for (uint32_t i = 0; i < numFonts; i++) { +#ifdef PR_LOGGING + LOG_FONTLIST(("(fontlist-singleface) face name: %s\n", + NS_ConvertUTF16toUTF8(singleFaceFonts[i]).get())); +#endif + gfxFontEntry *fontEntry = LookupLocalFont(nullptr, singleFaceFonts[i]); + if (fontEntry) { + nsAutoString familyName, key; + familyName = singleFaceFonts[i]; + GenerateFontListKey(familyName, key); +#ifdef PR_LOGGING + LOG_FONTLIST(("(fontlist-singleface) family name: %s, key: %s\n", + NS_ConvertUTF16toUTF8(familyName).get(), + NS_ConvertUTF16toUTF8(key).get())); +#endif + + // add only if doesn't exist already + if (!mFontFamilies.GetWeak(key)) { + gfxFontFamily *familyEntry = + new gfxSingleFaceMacFontFamily(familyName); + familyEntry->AddFontEntry(fontEntry); + familyEntry->SetHasStyles(true); + mFontFamilies.Put(key, familyEntry); +#ifdef PR_LOGGING + LOG_FONTLIST(("(fontlist-singleface) added new family\n", + NS_ConvertUTF16toUTF8(familyName).get(), + NS_ConvertUTF16toUTF8(key).get())); +#endif + } + } + } +} + +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(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 len = 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; + } + len = 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, len)); + + if (fallback) { + CFStringRef familyName = ::CTFontCopyFamilyName(fallback); + ::CFRelease(fallback); + + if (familyName && + ::CFStringCompare(familyName, CFSTR("LastResort"), + kCFCompareCaseInsensitive) != kCFCompareEqualTo) + { + nsAutoTArray buffer; + CFIndex len = ::CFStringGetLength(familyName); + buffer.SetLength(len+1); + ::CFStringGetCharacters(familyName, ::CFRangeMake(0, len), + buffer.Elements()); + buffer[len] = 0; + nsDependentString familyName(reinterpret_cast(buffer.Elements()), len); + + bool needsBold; // ignored in the system fallback case + + gfxFontFamily *family = FindFamily(familyName); + if (family) { + fontEntry = family->FindFontForStyle(*aMatchStyle, needsBold); + if (fontEntry) { + if (fontEntry->TestCharacterMap(aCh)) { + *aMatchedFamily = family; + } else { + fontEntry = nullptr; + cantUseFallbackFont = true; + } + } + } + } + + if (familyName) { + ::CFRelease(familyName); + } + } + + 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 gfxProxyFontEntry *aProxyEntry, + const nsAString& aFontName) +{ + 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; + } + + if (aProxyEntry) { + uint16_t w = aProxyEntry->mWeight; + NS_ASSERTION(w >= 100 && w <= 900, "bogus font weight value!"); + + newFontEntry = + new MacOSFontEntry(aFontName, fontRef, + w, aProxyEntry->mStretch, + aProxyEntry->mItalic ? + NS_FONT_STYLE_ITALIC : NS_FONT_STYLE_NORMAL, + true, true); + } else { + newFontEntry = + new MacOSFontEntry(aFontName, fontRef, + 400, 0, NS_FONT_STYLE_NORMAL, + false, false); + } + ::CFRelease(fontRef); + + return newFontEntry; +} + +static void ReleaseData(void *info, const void *data, size_t size) +{ + NS_Free((void*)data); +} + +gfxFontEntry* +gfxMacPlatformFontList::MakePlatformFont(const gfxProxyFontEntry *aProxyEntry, + const uint8_t *aFontData, + uint32_t aLength) +{ + NS_ASSERTION(aFontData, "MakePlatformFont called with null data"); + + uint16_t w = aProxyEntry->mWeight; + NS_ASSERTION(w >= 100 && w <= 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 + newFontEntry(new MacOSFontEntry(uniqueName, fontRef, w, + aProxyEntry->mStretch, + aProxyEntry->mItalic ? + NS_FONT_STYLE_ITALIC : + NS_FONT_STYLE_NORMAL, + 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; +} + +// 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 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 buffer; + CFIndex len = CFStringGetLength(faceName); + buffer.SetLength(len+1); + CFStringGetCharacters(faceName, ::CFRangeMake(0, len), + buffer.Elements()); + buffer[len] = 0; + nsAutoString fontName(reinterpret_cast(buffer.Elements()), + len); + + // load the cmap data + FontFaceData fontData; + CFDataRef cmapTable = CTFontCopyTable(fontRef, kCTFontTableCmap, + kCTFontTableOptionNoOptions); + + if (cmapTable) { + bool unicodeFont = false, symbolFont = false; // ignored + const uint8_t *cmapData = + (const uint8_t*)CFDataGetBytePtr(cmapTable); + uint32_t cmapLen = CFDataGetLength(cmapTable); + nsRefPtr charmap = new gfxCharacterMap(); + uint32_t offset; + 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 +gfxMacPlatformFontList::CreateFontInfoData() +{ + bool loadCmaps = !UsesSystemFallback() || + gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback(); + + nsRefPtr fi = + new MacFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps); + return fi.forget(); +} +