michael@0: /* -*- Mode: ObjC; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * ***** BEGIN LICENSE BLOCK ***** michael@0: * Version: BSD michael@0: * michael@0: * Copyright (C) 2006-2009 Mozilla Corporation. All rights reserved. michael@0: * michael@0: * Contributor(s): michael@0: * Vladimir Vukicevic michael@0: * Masayuki Nakano michael@0: * John Daggett michael@0: * Jonathan Kew michael@0: * michael@0: * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. michael@0: * michael@0: * Redistribution and use in source and binary forms, with or without michael@0: * modification, are permitted provided that the following conditions michael@0: * are met: michael@0: * michael@0: * 1. Redistributions of source code must retain the above copyright michael@0: * notice, this list of conditions and the following disclaimer. michael@0: * 2. Redistributions in binary form must reproduce the above copyright michael@0: * notice, this list of conditions and the following disclaimer in the michael@0: * documentation and/or other materials provided with the distribution. michael@0: * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of michael@0: * its contributors may be used to endorse or promote products derived michael@0: * from this software without specific prior written permission. michael@0: * michael@0: * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY michael@0: * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED michael@0: * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE michael@0: * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY michael@0: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES michael@0: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; michael@0: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND michael@0: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF michael@0: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: * michael@0: * ***** END LICENSE BLOCK ***** */ michael@0: michael@0: #ifdef MOZ_LOGGING michael@0: #define FORCE_PR_LOG /* Allow logging in the release build */ michael@0: #endif michael@0: #include "prlog.h" michael@0: michael@0: #include michael@0: michael@0: #import michael@0: michael@0: #include "gfxPlatformMac.h" michael@0: #include "gfxMacPlatformFontList.h" michael@0: #include "gfxMacFont.h" michael@0: #include "gfxUserFontSet.h" michael@0: #include "harfbuzz/hb.h" michael@0: michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsTArray.h" michael@0: michael@0: #include "nsDirectoryServiceUtils.h" michael@0: #include "nsDirectoryServiceDefs.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: #include "nsCharTraits.h" michael@0: #include "nsCocoaFeatures.h" michael@0: #include "gfxFontConstants.h" michael@0: michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: michael@0: #include michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: michael@0: class nsAutoreleasePool { michael@0: public: michael@0: nsAutoreleasePool() michael@0: { michael@0: mLocalPool = [[NSAutoreleasePool alloc] init]; michael@0: } michael@0: ~nsAutoreleasePool() michael@0: { michael@0: [mLocalPool release]; michael@0: } michael@0: private: michael@0: NSAutoreleasePool *mLocalPool; michael@0: }; michael@0: michael@0: // indexes into the NSArray objects that the Cocoa font manager returns michael@0: // as the available members of a family michael@0: #define INDEX_FONT_POSTSCRIPT_NAME 0 michael@0: #define INDEX_FONT_FACE_NAME 1 michael@0: #define INDEX_FONT_WEIGHT 2 michael@0: #define INDEX_FONT_TRAITS 3 michael@0: michael@0: static const int kAppleMaxWeight = 14; michael@0: static const int kAppleExtraLightWeight = 3; michael@0: static const int kAppleUltraLightWeight = 2; michael@0: michael@0: static const int gAppleWeightToCSSWeight[] = { michael@0: 0, michael@0: 1, // 1. michael@0: 1, // 2. W1, ultralight michael@0: 2, // 3. W2, extralight michael@0: 3, // 4. W3, light michael@0: 4, // 5. W4, semilight michael@0: 5, // 6. W5, medium michael@0: 6, // 7. michael@0: 6, // 8. W6, semibold michael@0: 7, // 9. W7, bold michael@0: 8, // 10. W8, extrabold michael@0: 8, // 11. michael@0: 9, // 12. W9, ultrabold michael@0: 9, // 13 michael@0: 9 // 14 michael@0: }; michael@0: michael@0: // cache Cocoa's "shared font manager" for performance michael@0: static NSFontManager *sFontManager; michael@0: michael@0: static void GetStringForNSString(const NSString *aSrc, nsAString& aDist) michael@0: { michael@0: aDist.SetLength([aSrc length]); michael@0: [aSrc getCharacters:reinterpret_cast(aDist.BeginWriting())]; michael@0: } michael@0: michael@0: static NSString* GetNSStringForString(const nsAString& aSrc) michael@0: { michael@0: return [NSString stringWithCharacters:reinterpret_cast(aSrc.BeginReading()) michael@0: length:aSrc.Length()]; michael@0: } michael@0: michael@0: #ifdef PR_LOGGING michael@0: michael@0: #define LOG_FONTLIST(args) PR_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), \ michael@0: PR_LOG_DEBUG, args) michael@0: #define LOG_FONTLIST_ENABLED() PR_LOG_TEST( \ michael@0: gfxPlatform::GetLog(eGfxLog_fontlist), \ michael@0: PR_LOG_DEBUG) michael@0: #define LOG_CMAPDATA_ENABLED() PR_LOG_TEST( \ michael@0: gfxPlatform::GetLog(eGfxLog_cmapdata), \ michael@0: PR_LOG_DEBUG) michael@0: michael@0: #endif // PR_LOGGING michael@0: michael@0: #pragma mark- michael@0: michael@0: // Complex scripts will not render correctly unless appropriate AAT or OT michael@0: // layout tables are present. michael@0: // For OpenType, we also check that the GSUB table supports the relevant michael@0: // script tag, to avoid using things like Arial Unicode MS for Lao (it has michael@0: // the characters, but lacks OpenType support). michael@0: michael@0: // TODO: consider whether we should move this to gfxFontEntry and do similar michael@0: // cmap-masking on other platforms to avoid using fonts that won't shape michael@0: // properly. michael@0: michael@0: nsresult michael@0: MacOSFontEntry::ReadCMAP(FontInfoData *aFontInfoData) michael@0: { michael@0: // attempt this once, if errors occur leave a blank cmap michael@0: if (mCharacterMap) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr charmap; michael@0: nsresult rv; michael@0: bool symbolFont; michael@0: michael@0: if (aFontInfoData && (charmap = GetCMAPFromFontInfo(aFontInfoData, michael@0: mUVSOffset, michael@0: symbolFont))) { michael@0: rv = NS_OK; michael@0: } else { michael@0: uint32_t kCMAP = TRUETYPE_TAG('c','m','a','p'); michael@0: charmap = new gfxCharacterMap(); michael@0: AutoTable cmapTable(this, kCMAP); michael@0: michael@0: if (cmapTable) { michael@0: bool unicodeFont = false, symbolFont = false; // currently ignored michael@0: uint32_t cmapLen; michael@0: const uint8_t* cmapData = michael@0: reinterpret_cast(hb_blob_get_data(cmapTable, michael@0: &cmapLen)); michael@0: rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, michael@0: *charmap, mUVSOffset, michael@0: unicodeFont, symbolFont); michael@0: } else { michael@0: rv = NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv) && !HasGraphiteTables()) { michael@0: // We assume a Graphite font knows what it's doing, michael@0: // and provides whatever shaping is needed for the michael@0: // characters it supports, so only check/clear the michael@0: // complex-script ranges for non-Graphite fonts michael@0: michael@0: // for layout support, check for the presence of mort/morx and/or michael@0: // opentype layout tables michael@0: bool hasAATLayout = HasFontTable(TRUETYPE_TAG('m','o','r','x')) || michael@0: HasFontTable(TRUETYPE_TAG('m','o','r','t')); michael@0: bool hasGSUB = HasFontTable(TRUETYPE_TAG('G','S','U','B')); michael@0: bool hasGPOS = HasFontTable(TRUETYPE_TAG('G','P','O','S')); michael@0: if (hasAATLayout && !(hasGSUB || hasGPOS)) { michael@0: mRequiresAAT = true; // prefer CoreText if font has no OTL tables michael@0: } michael@0: michael@0: for (const ScriptRange* sr = gfxPlatformFontList::sComplexScriptRanges; michael@0: sr->rangeStart; sr++) { michael@0: // check to see if the cmap includes complex script codepoints michael@0: if (charmap->TestRange(sr->rangeStart, sr->rangeEnd)) { michael@0: if (hasAATLayout) { michael@0: // prefer CoreText for Apple's complex-script fonts, michael@0: // even if they also have some OpenType tables michael@0: // (e.g. Geeza Pro Bold on 10.6; see bug 614903) michael@0: mRequiresAAT = true; michael@0: // and don't mask off complex-script ranges, we assume michael@0: // the AAT tables will provide the necessary shaping michael@0: continue; michael@0: } michael@0: michael@0: // We check for GSUB here, as GPOS alone would not be ok. michael@0: if (hasGSUB && SupportsScriptInGSUB(sr->tags)) { michael@0: continue; michael@0: } michael@0: michael@0: charmap->ClearRange(sr->rangeStart, sr->rangeEnd); michael@0: } michael@0: } michael@0: } michael@0: michael@0: mHasCmapTable = NS_SUCCEEDED(rv); michael@0: if (mHasCmapTable) { michael@0: gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList(); michael@0: mCharacterMap = pfl->FindCharMap(charmap); michael@0: } else { michael@0: // if error occurred, initialize to null cmap michael@0: mCharacterMap = new gfxCharacterMap(); michael@0: } michael@0: michael@0: #ifdef PR_LOGGING michael@0: LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d hash: %8.8x%s\n", michael@0: NS_ConvertUTF16toUTF8(mName).get(), michael@0: charmap->SizeOfIncludingThis(moz_malloc_size_of), michael@0: charmap->mHash, mCharacterMap == charmap ? " new" : "")); michael@0: if (LOG_CMAPDATA_ENABLED()) { michael@0: char prefix[256]; michael@0: sprintf(prefix, "(cmapdata) name: %.220s", michael@0: NS_ConvertUTF16toUTF8(mName).get()); michael@0: charmap->Dump(prefix, eGfxLog_cmapdata); michael@0: } michael@0: #endif michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: gfxFont* michael@0: MacOSFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold) michael@0: { michael@0: return new gfxMacFont(this, aFontStyle, aNeedsBold); michael@0: } michael@0: michael@0: bool michael@0: MacOSFontEntry::IsCFF() michael@0: { michael@0: if (!mIsCFFInitialized) { michael@0: mIsCFFInitialized = true; michael@0: mIsCFF = HasFontTable(TRUETYPE_TAG('C','F','F',' ')); michael@0: } michael@0: michael@0: return mIsCFF; michael@0: } michael@0: michael@0: MacOSFontEntry::MacOSFontEntry(const nsAString& aPostscriptName, michael@0: int32_t aWeight, michael@0: bool aIsStandardFace) michael@0: : gfxFontEntry(aPostscriptName, aIsStandardFace), michael@0: mFontRef(NULL), michael@0: mFontRefInitialized(false), michael@0: mRequiresAAT(false), michael@0: mIsCFF(false), michael@0: mIsCFFInitialized(false) michael@0: { michael@0: mWeight = aWeight; michael@0: } michael@0: michael@0: MacOSFontEntry::MacOSFontEntry(const nsAString& aPostscriptName, michael@0: CGFontRef aFontRef, michael@0: uint16_t aWeight, uint16_t aStretch, michael@0: uint32_t aItalicStyle, michael@0: bool aIsUserFont, bool aIsLocal) michael@0: : gfxFontEntry(aPostscriptName, false), michael@0: mFontRef(NULL), michael@0: mFontRefInitialized(false), michael@0: mRequiresAAT(false), michael@0: mIsCFF(false), michael@0: mIsCFFInitialized(false) michael@0: { michael@0: mFontRef = aFontRef; michael@0: mFontRefInitialized = true; michael@0: ::CFRetain(mFontRef); michael@0: michael@0: mWeight = aWeight; michael@0: mStretch = aStretch; michael@0: mFixedPitch = false; // xxx - do we need this for downloaded fonts? michael@0: mItalic = (aItalicStyle & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0; michael@0: mIsUserFont = aIsUserFont; michael@0: mIsLocalUserFont = aIsLocal; michael@0: } michael@0: michael@0: CGFontRef michael@0: MacOSFontEntry::GetFontRef() michael@0: { michael@0: if (!mFontRefInitialized) { michael@0: mFontRefInitialized = true; michael@0: NSString *psname = GetNSStringForString(mName); michael@0: mFontRef = ::CGFontCreateWithFontName(CFStringRef(psname)); michael@0: } michael@0: return mFontRef; michael@0: } michael@0: michael@0: /*static*/ void michael@0: MacOSFontEntry::DestroyBlobFunc(void* aUserData) michael@0: { michael@0: ::CFRelease((CFDataRef)aUserData); michael@0: } michael@0: michael@0: hb_blob_t * michael@0: MacOSFontEntry::GetFontTable(uint32_t aTag) michael@0: { michael@0: CGFontRef fontRef = GetFontRef(); michael@0: if (!fontRef) { michael@0: return nullptr; michael@0: } michael@0: michael@0: CFDataRef dataRef = ::CGFontCopyTableForTag(fontRef, aTag); michael@0: if (dataRef) { michael@0: return hb_blob_create((const char*)::CFDataGetBytePtr(dataRef), michael@0: ::CFDataGetLength(dataRef), michael@0: HB_MEMORY_MODE_READONLY, michael@0: (void*)dataRef, DestroyBlobFunc); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: MacOSFontEntry::HasFontTable(uint32_t aTableTag) michael@0: { michael@0: nsAutoreleasePool localPool; michael@0: michael@0: CGFontRef fontRef = GetFontRef(); michael@0: if (!fontRef) { michael@0: return false; michael@0: } michael@0: michael@0: CFDataRef tableData = ::CGFontCopyTableForTag(fontRef, aTableTag); michael@0: if (!tableData) { michael@0: return false; michael@0: } michael@0: michael@0: ::CFRelease(tableData); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: MacOSFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, michael@0: FontListSizes* aSizes) const michael@0: { michael@0: aSizes->mFontListSize += aMallocSizeOf(this); michael@0: AddSizeOfExcludingThis(aMallocSizeOf, aSizes); michael@0: } michael@0: michael@0: /* gfxMacFontFamily */ michael@0: #pragma mark- michael@0: michael@0: class gfxMacFontFamily : public gfxFontFamily michael@0: { michael@0: public: michael@0: gfxMacFontFamily(nsAString& aName) : michael@0: gfxFontFamily(aName) michael@0: {} michael@0: michael@0: virtual ~gfxMacFontFamily() {} michael@0: michael@0: virtual void LocalizedName(nsAString& aLocalizedName); michael@0: michael@0: virtual void FindStyleVariations(FontInfoData *aFontInfoData = nullptr); michael@0: }; michael@0: michael@0: void michael@0: gfxMacFontFamily::LocalizedName(nsAString& aLocalizedName) michael@0: { michael@0: nsAutoreleasePool localPool; michael@0: michael@0: if (!HasOtherFamilyNames()) { michael@0: aLocalizedName = mName; michael@0: return; michael@0: } michael@0: michael@0: NSString *family = GetNSStringForString(mName); michael@0: NSString *localized = [sFontManager michael@0: localizedNameForFamily:family michael@0: face:nil]; michael@0: michael@0: if (localized) { michael@0: GetStringForNSString(localized, aLocalizedName); michael@0: return; michael@0: } michael@0: michael@0: // failed to get localized name, just use the canonical one michael@0: aLocalizedName = mName; michael@0: } michael@0: michael@0: // Return the CSS weight value to use for the given face, overriding what michael@0: // AppKit gives us (used to adjust families with bad weight values, see michael@0: // bug 931426). michael@0: // A return value of 0 indicates no override - use the existing weight. michael@0: static inline int michael@0: GetWeightOverride(const nsAString& aPSName) michael@0: { michael@0: nsAutoCString prefName("font.weight-override."); michael@0: // The PostScript name is required to be ASCII; if it's not, the font is michael@0: // broken anyway, so we really don't care that this is lossy. michael@0: LossyAppendUTF16toASCII(aPSName, prefName); michael@0: return Preferences::GetInt(prefName.get(), 0); michael@0: } michael@0: michael@0: void michael@0: gfxMacFontFamily::FindStyleVariations(FontInfoData *aFontInfoData) michael@0: { michael@0: if (mHasStyles) michael@0: return; michael@0: michael@0: nsAutoreleasePool localPool; michael@0: michael@0: NSString *family = GetNSStringForString(mName); michael@0: michael@0: // create a font entry for each face michael@0: NSArray *fontfaces = [sFontManager michael@0: availableMembersOfFontFamily:family]; // returns an array of [psname, style name, weight, traits] elements, goofy api michael@0: int faceCount = [fontfaces count]; michael@0: int faceIndex; michael@0: michael@0: for (faceIndex = 0; faceIndex < faceCount; faceIndex++) { michael@0: NSArray *face = [fontfaces objectAtIndex:faceIndex]; michael@0: NSString *psname = [face objectAtIndex:INDEX_FONT_POSTSCRIPT_NAME]; michael@0: int32_t appKitWeight = [[face objectAtIndex:INDEX_FONT_WEIGHT] unsignedIntValue]; michael@0: uint32_t macTraits = [[face objectAtIndex:INDEX_FONT_TRAITS] unsignedIntValue]; michael@0: NSString *facename = [face objectAtIndex:INDEX_FONT_FACE_NAME]; michael@0: bool isStandardFace = false; michael@0: michael@0: if (appKitWeight == kAppleExtraLightWeight) { michael@0: // if the facename contains UltraLight, set the weight to the ultralight weight value michael@0: NSRange range = [facename rangeOfString:@"ultralight" options:NSCaseInsensitiveSearch]; michael@0: if (range.location != NSNotFound) { michael@0: appKitWeight = kAppleUltraLightWeight; michael@0: } michael@0: } michael@0: michael@0: // make a nsString michael@0: nsAutoString postscriptFontName; michael@0: GetStringForNSString(psname, postscriptFontName); michael@0: michael@0: int32_t cssWeight = GetWeightOverride(postscriptFontName); michael@0: if (cssWeight) { michael@0: // scale down and clamp, to get a value from 1..9 michael@0: cssWeight = ((cssWeight + 50) / 100); michael@0: cssWeight = std::max(1, std::min(cssWeight, 9)); michael@0: } else { michael@0: cssWeight = michael@0: gfxMacPlatformFontList::AppleWeightToCSSWeight(appKitWeight); michael@0: } michael@0: cssWeight *= 100; // scale up to CSS values michael@0: michael@0: if ([facename isEqualToString:@"Regular"] || michael@0: [facename isEqualToString:@"Bold"] || michael@0: [facename isEqualToString:@"Italic"] || michael@0: [facename isEqualToString:@"Oblique"] || michael@0: [facename isEqualToString:@"Bold Italic"] || michael@0: [facename isEqualToString:@"Bold Oblique"]) michael@0: { michael@0: isStandardFace = true; michael@0: } michael@0: michael@0: // create a font entry michael@0: MacOSFontEntry *fontEntry = michael@0: new MacOSFontEntry(postscriptFontName, cssWeight, isStandardFace); michael@0: if (!fontEntry) { michael@0: break; michael@0: } michael@0: michael@0: // set additional properties based on the traits reported by Cocoa michael@0: if (macTraits & (NSCondensedFontMask | NSNarrowFontMask | NSCompressedFontMask)) { michael@0: fontEntry->mStretch = NS_FONT_STRETCH_CONDENSED; michael@0: } else if (macTraits & NSExpandedFontMask) { michael@0: fontEntry->mStretch = NS_FONT_STRETCH_EXPANDED; michael@0: } michael@0: // Cocoa fails to set the Italic traits bit for HelveticaLightItalic, michael@0: // at least (see bug 611855), so check for style name endings as well michael@0: if ((macTraits & NSItalicFontMask) || michael@0: [facename hasSuffix:@"Italic"] || michael@0: [facename hasSuffix:@"Oblique"]) michael@0: { michael@0: fontEntry->mItalic = true; michael@0: } michael@0: if (macTraits & NSFixedPitchFontMask) { michael@0: fontEntry->mFixedPitch = true; michael@0: } michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (LOG_FONTLIST_ENABLED()) { michael@0: LOG_FONTLIST(("(fontlist) added (%s) to family (%s)" michael@0: " with style: %s weight: %d stretch: %d" michael@0: " (apple-weight: %d macTraits: %8.8x)", michael@0: NS_ConvertUTF16toUTF8(fontEntry->Name()).get(), michael@0: NS_ConvertUTF16toUTF8(Name()).get(), michael@0: fontEntry->IsItalic() ? "italic" : "normal", michael@0: cssWeight, fontEntry->Stretch(), michael@0: appKitWeight, macTraits)); michael@0: } michael@0: #endif michael@0: michael@0: // insert into font entry array of family michael@0: AddFontEntry(fontEntry); michael@0: } michael@0: michael@0: SortAvailableFonts(); michael@0: SetHasStyles(true); michael@0: michael@0: if (mIsBadUnderlineFamily) { michael@0: SetBadUnderlineFonts(); michael@0: } michael@0: } michael@0: michael@0: michael@0: /* gfxSingleFaceMacFontFamily */ michael@0: #pragma mark- michael@0: michael@0: class gfxSingleFaceMacFontFamily : public gfxFontFamily michael@0: { michael@0: public: michael@0: gfxSingleFaceMacFontFamily(nsAString& aName) : michael@0: gfxFontFamily(aName) michael@0: { michael@0: mFaceNamesInitialized = true; // omit from face name lists michael@0: } michael@0: michael@0: virtual ~gfxSingleFaceMacFontFamily() {} michael@0: michael@0: virtual void LocalizedName(nsAString& aLocalizedName); michael@0: michael@0: virtual void ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList); michael@0: }; michael@0: michael@0: void michael@0: gfxSingleFaceMacFontFamily::LocalizedName(nsAString& aLocalizedName) michael@0: { michael@0: nsAutoreleasePool localPool; michael@0: michael@0: if (!HasOtherFamilyNames()) { michael@0: aLocalizedName = mName; michael@0: return; michael@0: } michael@0: michael@0: gfxFontEntry *fe = mAvailableFonts[0]; michael@0: NSFont *font = [NSFont fontWithName:GetNSStringForString(fe->Name()) michael@0: size:0.0]; michael@0: if (font) { michael@0: NSString *localized = [font displayName]; michael@0: if (localized) { michael@0: GetStringForNSString(localized, aLocalizedName); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // failed to get localized name, just use the canonical one michael@0: aLocalizedName = mName; michael@0: } michael@0: michael@0: void michael@0: gfxSingleFaceMacFontFamily::ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList) michael@0: { michael@0: if (mOtherFamilyNamesInitialized) { michael@0: return; michael@0: } michael@0: michael@0: gfxFontEntry *fe = mAvailableFonts[0]; michael@0: if (!fe) { michael@0: return; michael@0: } michael@0: michael@0: const uint32_t kNAME = TRUETYPE_TAG('n','a','m','e'); michael@0: michael@0: gfxFontEntry::AutoTable nameTable(fe, kNAME); michael@0: if (!nameTable) { michael@0: return; michael@0: } michael@0: michael@0: mHasOtherFamilyNames = ReadOtherFamilyNamesForFace(aPlatformFontList, michael@0: nameTable, michael@0: true); michael@0: michael@0: mOtherFamilyNamesInitialized = true; michael@0: } michael@0: michael@0: michael@0: /* gfxMacPlatformFontList */ michael@0: #pragma mark- michael@0: michael@0: gfxMacPlatformFontList::gfxMacPlatformFontList() : michael@0: gfxPlatformFontList(false), michael@0: mDefaultFont(nullptr) michael@0: { michael@0: ::CFNotificationCenterAddObserver(::CFNotificationCenterGetLocalCenter(), michael@0: this, michael@0: RegisteredFontsChangedNotificationCallback, michael@0: kCTFontManagerRegisteredFontsChangedNotification, michael@0: 0, michael@0: CFNotificationSuspensionBehaviorDeliverImmediately); michael@0: michael@0: // cache this in a static variable so that MacOSFontFamily objects michael@0: // don't have to repeatedly look it up michael@0: sFontManager = [NSFontManager sharedFontManager]; michael@0: } michael@0: michael@0: gfxMacPlatformFontList::~gfxMacPlatformFontList() michael@0: { michael@0: if (mDefaultFont) { michael@0: ::CFRelease(mDefaultFont); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: gfxMacPlatformFontList::InitFontList() michael@0: { michael@0: nsAutoreleasePool localPool; michael@0: michael@0: Telemetry::AutoTimer timer; michael@0: michael@0: // reset font lists michael@0: gfxPlatformFontList::InitFontList(); michael@0: michael@0: // iterate over available families michael@0: michael@0: CFArrayRef familyNames = CTFontManagerCopyAvailableFontFamilyNames(); michael@0: michael@0: // iterate over families michael@0: uint32_t i, numFamilies; michael@0: michael@0: numFamilies = CFArrayGetCount(familyNames); michael@0: for (i = 0; i < numFamilies; i++) { michael@0: CFStringRef family = (CFStringRef)CFArrayGetValueAtIndex(familyNames, i); michael@0: michael@0: // CTFontManager includes weird internal family names and michael@0: // LastResort, skip over those michael@0: if (!family || michael@0: ::CFStringHasPrefix(family, CFSTR(".")) || michael@0: CFStringCompare(family, CFSTR("LastResort"), michael@0: kCFCompareCaseInsensitive) == kCFCompareEqualTo) { michael@0: continue; michael@0: } michael@0: michael@0: nsAutoTArray buffer; michael@0: CFIndex len = ::CFStringGetLength(family); michael@0: buffer.SetLength(len+1); michael@0: ::CFStringGetCharacters(family, ::CFRangeMake(0, len), michael@0: buffer.Elements()); michael@0: buffer[len] = 0; michael@0: nsAutoString familyName(reinterpret_cast(buffer.Elements()), len); michael@0: michael@0: // create a family entry michael@0: gfxFontFamily *familyEntry = new gfxMacFontFamily(familyName); michael@0: if (!familyEntry) break; michael@0: michael@0: // add the family entry to the hash table michael@0: ToLowerCase(familyName); michael@0: mFontFamilies.Put(familyName, familyEntry); michael@0: michael@0: // check the bad underline blacklist michael@0: if (mBadUnderlineFamilyNames.Contains(familyName)) michael@0: familyEntry->SetBadUnderlineFamily(); michael@0: } michael@0: michael@0: CFRelease(familyNames); michael@0: michael@0: InitSingleFaceList(); michael@0: michael@0: // to avoid full search of font name tables, seed the other names table with localized names from michael@0: // some of the prefs fonts which are accessed via their localized names. changes in the pref fonts will only cause michael@0: // a font lookup miss earlier. this is a simple optimization, it's not required for correctness michael@0: PreloadNamesList(); michael@0: michael@0: // start the delayed cmap loader michael@0: GetPrefsAndStartLoader(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: gfxMacPlatformFontList::InitSingleFaceList() michael@0: { michael@0: nsAutoTArray singleFaceFonts; michael@0: gfxFontUtils::GetPrefsFontList("font.single-face-list", singleFaceFonts); michael@0: michael@0: uint32_t numFonts = singleFaceFonts.Length(); michael@0: for (uint32_t i = 0; i < numFonts; i++) { michael@0: #ifdef PR_LOGGING michael@0: LOG_FONTLIST(("(fontlist-singleface) face name: %s\n", michael@0: NS_ConvertUTF16toUTF8(singleFaceFonts[i]).get())); michael@0: #endif michael@0: gfxFontEntry *fontEntry = LookupLocalFont(nullptr, singleFaceFonts[i]); michael@0: if (fontEntry) { michael@0: nsAutoString familyName, key; michael@0: familyName = singleFaceFonts[i]; michael@0: GenerateFontListKey(familyName, key); michael@0: #ifdef PR_LOGGING michael@0: LOG_FONTLIST(("(fontlist-singleface) family name: %s, key: %s\n", michael@0: NS_ConvertUTF16toUTF8(familyName).get(), michael@0: NS_ConvertUTF16toUTF8(key).get())); michael@0: #endif michael@0: michael@0: // add only if doesn't exist already michael@0: if (!mFontFamilies.GetWeak(key)) { michael@0: gfxFontFamily *familyEntry = michael@0: new gfxSingleFaceMacFontFamily(familyName); michael@0: familyEntry->AddFontEntry(fontEntry); michael@0: familyEntry->SetHasStyles(true); michael@0: mFontFamilies.Put(key, familyEntry); michael@0: #ifdef PR_LOGGING michael@0: LOG_FONTLIST(("(fontlist-singleface) added new family\n", michael@0: NS_ConvertUTF16toUTF8(familyName).get(), michael@0: NS_ConvertUTF16toUTF8(key).get())); michael@0: #endif michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: gfxMacPlatformFontList::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName) michael@0: { michael@0: gfxFontFamily *family = FindFamily(aFontName); michael@0: if (family) { michael@0: family->LocalizedName(aFamilyName); michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: gfxMacPlatformFontList::RegisteredFontsChangedNotificationCallback(CFNotificationCenterRef center, michael@0: void *observer, michael@0: CFStringRef name, michael@0: const void *object, michael@0: CFDictionaryRef userInfo) michael@0: { michael@0: if (!::CFEqual(name, kCTFontManagerRegisteredFontsChangedNotification)) { michael@0: return; michael@0: } michael@0: michael@0: gfxMacPlatformFontList* fl = static_cast(observer); michael@0: michael@0: // xxx - should be carefully pruning the list of fonts, not rebuilding it from scratch michael@0: fl->UpdateFontList(); michael@0: michael@0: // modify a preference that will trigger reflow everywhere michael@0: fl->ForceGlobalReflow(); michael@0: } michael@0: michael@0: gfxFontEntry* michael@0: gfxMacPlatformFontList::GlobalFontFallback(const uint32_t aCh, michael@0: int32_t aRunScript, michael@0: const gfxFontStyle* aMatchStyle, michael@0: uint32_t& aCmapCount, michael@0: gfxFontFamily** aMatchedFamily) michael@0: { michael@0: bool useCmaps = gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback(); michael@0: michael@0: if (useCmaps) { michael@0: return gfxPlatformFontList::GlobalFontFallback(aCh, michael@0: aRunScript, michael@0: aMatchStyle, michael@0: aCmapCount, michael@0: aMatchedFamily); michael@0: } michael@0: michael@0: CFStringRef str; michael@0: UniChar ch[2]; michael@0: CFIndex len = 1; michael@0: michael@0: if (IS_IN_BMP(aCh)) { michael@0: ch[0] = aCh; michael@0: str = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, ch, 1, michael@0: kCFAllocatorNull); michael@0: } else { michael@0: ch[0] = H_SURROGATE(aCh); michael@0: ch[1] = L_SURROGATE(aCh); michael@0: str = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, ch, 2, michael@0: kCFAllocatorNull); michael@0: if (!str) { michael@0: return nullptr; michael@0: } michael@0: len = 2; michael@0: } michael@0: michael@0: // use CoreText to find the fallback family michael@0: michael@0: gfxFontEntry *fontEntry = nullptr; michael@0: CTFontRef fallback; michael@0: bool cantUseFallbackFont = false; michael@0: michael@0: if (!mDefaultFont) { michael@0: mDefaultFont = ::CTFontCreateWithName(CFSTR("LucidaGrande"), 12.f, michael@0: NULL); michael@0: } michael@0: michael@0: fallback = ::CTFontCreateForString(mDefaultFont, str, michael@0: ::CFRangeMake(0, len)); michael@0: michael@0: if (fallback) { michael@0: CFStringRef familyName = ::CTFontCopyFamilyName(fallback); michael@0: ::CFRelease(fallback); michael@0: michael@0: if (familyName && michael@0: ::CFStringCompare(familyName, CFSTR("LastResort"), michael@0: kCFCompareCaseInsensitive) != kCFCompareEqualTo) michael@0: { michael@0: nsAutoTArray buffer; michael@0: CFIndex len = ::CFStringGetLength(familyName); michael@0: buffer.SetLength(len+1); michael@0: ::CFStringGetCharacters(familyName, ::CFRangeMake(0, len), michael@0: buffer.Elements()); michael@0: buffer[len] = 0; michael@0: nsDependentString familyName(reinterpret_cast(buffer.Elements()), len); michael@0: michael@0: bool needsBold; // ignored in the system fallback case michael@0: michael@0: gfxFontFamily *family = FindFamily(familyName); michael@0: if (family) { michael@0: fontEntry = family->FindFontForStyle(*aMatchStyle, needsBold); michael@0: if (fontEntry) { michael@0: if (fontEntry->TestCharacterMap(aCh)) { michael@0: *aMatchedFamily = family; michael@0: } else { michael@0: fontEntry = nullptr; michael@0: cantUseFallbackFont = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (familyName) { michael@0: ::CFRelease(familyName); michael@0: } michael@0: } michael@0: michael@0: if (cantUseFallbackFont) { michael@0: Telemetry::Accumulate(Telemetry::BAD_FALLBACK_FONT, cantUseFallbackFont); michael@0: } michael@0: michael@0: ::CFRelease(str); michael@0: michael@0: return fontEntry; michael@0: } michael@0: michael@0: gfxFontFamily* michael@0: gfxMacPlatformFontList::GetDefaultFont(const gfxFontStyle* aStyle) michael@0: { michael@0: nsAutoreleasePool localPool; michael@0: michael@0: NSString *defaultFamily = [[NSFont userFontOfSize:aStyle->size] familyName]; michael@0: nsAutoString familyName; michael@0: michael@0: GetStringForNSString(defaultFamily, familyName); michael@0: return FindFamily(familyName); michael@0: } michael@0: michael@0: int32_t michael@0: gfxMacPlatformFontList::AppleWeightToCSSWeight(int32_t aAppleWeight) michael@0: { michael@0: if (aAppleWeight < 1) michael@0: aAppleWeight = 1; michael@0: else if (aAppleWeight > kAppleMaxWeight) michael@0: aAppleWeight = kAppleMaxWeight; michael@0: return gAppleWeightToCSSWeight[aAppleWeight]; michael@0: } michael@0: michael@0: gfxFontEntry* michael@0: gfxMacPlatformFontList::LookupLocalFont(const gfxProxyFontEntry *aProxyEntry, michael@0: const nsAString& aFontName) michael@0: { michael@0: nsAutoreleasePool localPool; michael@0: michael@0: NSString *faceName = GetNSStringForString(aFontName); michael@0: MacOSFontEntry *newFontEntry; michael@0: michael@0: // lookup face based on postscript or full name michael@0: CGFontRef fontRef = ::CGFontCreateWithFontName(CFStringRef(faceName)); michael@0: if (!fontRef) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (aProxyEntry) { michael@0: uint16_t w = aProxyEntry->mWeight; michael@0: NS_ASSERTION(w >= 100 && w <= 900, "bogus font weight value!"); michael@0: michael@0: newFontEntry = michael@0: new MacOSFontEntry(aFontName, fontRef, michael@0: w, aProxyEntry->mStretch, michael@0: aProxyEntry->mItalic ? michael@0: NS_FONT_STYLE_ITALIC : NS_FONT_STYLE_NORMAL, michael@0: true, true); michael@0: } else { michael@0: newFontEntry = michael@0: new MacOSFontEntry(aFontName, fontRef, michael@0: 400, 0, NS_FONT_STYLE_NORMAL, michael@0: false, false); michael@0: } michael@0: ::CFRelease(fontRef); michael@0: michael@0: return newFontEntry; michael@0: } michael@0: michael@0: static void ReleaseData(void *info, const void *data, size_t size) michael@0: { michael@0: NS_Free((void*)data); michael@0: } michael@0: michael@0: gfxFontEntry* michael@0: gfxMacPlatformFontList::MakePlatformFont(const gfxProxyFontEntry *aProxyEntry, michael@0: const uint8_t *aFontData, michael@0: uint32_t aLength) michael@0: { michael@0: NS_ASSERTION(aFontData, "MakePlatformFont called with null data"); michael@0: michael@0: uint16_t w = aProxyEntry->mWeight; michael@0: NS_ASSERTION(w >= 100 && w <= 900, "bogus font weight value!"); michael@0: michael@0: // create the font entry michael@0: nsAutoString uniqueName; michael@0: michael@0: nsresult rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName); michael@0: if (NS_FAILED(rv)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: CGDataProviderRef provider = michael@0: ::CGDataProviderCreateWithData(nullptr, aFontData, aLength, michael@0: &ReleaseData); michael@0: CGFontRef fontRef = ::CGFontCreateWithDataProvider(provider); michael@0: ::CGDataProviderRelease(provider); michael@0: michael@0: if (!fontRef) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsAutoPtr michael@0: newFontEntry(new MacOSFontEntry(uniqueName, fontRef, w, michael@0: aProxyEntry->mStretch, michael@0: aProxyEntry->mItalic ? michael@0: NS_FONT_STYLE_ITALIC : michael@0: NS_FONT_STYLE_NORMAL, michael@0: true, false)); michael@0: ::CFRelease(fontRef); michael@0: michael@0: // if succeeded and font cmap is good, return the new font michael@0: if (newFontEntry->mIsValid && NS_SUCCEEDED(newFontEntry->ReadCMAP())) { michael@0: return newFontEntry.forget(); michael@0: } michael@0: michael@0: // if something is funky about this font, delete immediately michael@0: michael@0: #if DEBUG michael@0: NS_WARNING("downloaded font not loaded properly"); michael@0: #endif michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: // used to load system-wide font info on off-main thread michael@0: class MacFontInfo : public FontInfoData { michael@0: public: michael@0: MacFontInfo(bool aLoadOtherNames, michael@0: bool aLoadFaceNames, michael@0: bool aLoadCmaps) : michael@0: FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps) michael@0: {} michael@0: michael@0: virtual ~MacFontInfo() {} michael@0: michael@0: virtual void Load() { michael@0: nsAutoreleasePool localPool; michael@0: // bug 975460 - async font loader crashes sometimes under 10.6, disable michael@0: if (nsCocoaFeatures::OnLionOrLater()) { michael@0: FontInfoData::Load(); michael@0: } michael@0: } michael@0: michael@0: // loads font data for all members of a given family michael@0: virtual void LoadFontFamilyData(const nsAString& aFamilyName); michael@0: }; michael@0: michael@0: void michael@0: MacFontInfo::LoadFontFamilyData(const nsAString& aFamilyName) michael@0: { michael@0: // family name ==> CTFontDescriptor michael@0: NSString *famName = GetNSStringForString(aFamilyName); michael@0: CFStringRef family = CFStringRef(famName); michael@0: michael@0: CFMutableDictionaryRef attr = michael@0: CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, michael@0: &kCFTypeDictionaryValueCallBacks); michael@0: CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, family); michael@0: CTFontDescriptorRef fd = CTFontDescriptorCreateWithAttributes(attr); michael@0: CFRelease(attr); michael@0: CFArrayRef matchingFonts = michael@0: CTFontDescriptorCreateMatchingFontDescriptors(fd, NULL); michael@0: CFRelease(fd); michael@0: if (!matchingFonts) { michael@0: return; michael@0: } michael@0: michael@0: nsTArray otherFamilyNames; michael@0: bool hasOtherFamilyNames = true; michael@0: michael@0: // iterate over faces in the family michael@0: int f, numFaces = (int) CFArrayGetCount(matchingFonts); michael@0: for (f = 0; f < numFaces; f++) { michael@0: mLoadStats.fonts++; michael@0: michael@0: CTFontDescriptorRef faceDesc = michael@0: (CTFontDescriptorRef)CFArrayGetValueAtIndex(matchingFonts, f); michael@0: if (!faceDesc) { michael@0: continue; michael@0: } michael@0: CTFontRef fontRef = CTFontCreateWithFontDescriptor(faceDesc, michael@0: 0.0, nullptr); michael@0: if (!fontRef) { michael@0: NS_WARNING("failed to create a CTFontRef"); michael@0: continue; michael@0: } michael@0: michael@0: if (mLoadCmaps) { michael@0: // face name michael@0: CFStringRef faceName = (CFStringRef) michael@0: CTFontDescriptorCopyAttribute(faceDesc, kCTFontNameAttribute); michael@0: michael@0: nsAutoTArray buffer; michael@0: CFIndex len = CFStringGetLength(faceName); michael@0: buffer.SetLength(len+1); michael@0: CFStringGetCharacters(faceName, ::CFRangeMake(0, len), michael@0: buffer.Elements()); michael@0: buffer[len] = 0; michael@0: nsAutoString fontName(reinterpret_cast(buffer.Elements()), michael@0: len); michael@0: michael@0: // load the cmap data michael@0: FontFaceData fontData; michael@0: CFDataRef cmapTable = CTFontCopyTable(fontRef, kCTFontTableCmap, michael@0: kCTFontTableOptionNoOptions); michael@0: michael@0: if (cmapTable) { michael@0: bool unicodeFont = false, symbolFont = false; // ignored michael@0: const uint8_t *cmapData = michael@0: (const uint8_t*)CFDataGetBytePtr(cmapTable); michael@0: uint32_t cmapLen = CFDataGetLength(cmapTable); michael@0: nsRefPtr charmap = new gfxCharacterMap(); michael@0: uint32_t offset; michael@0: nsresult rv; michael@0: michael@0: rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, offset, michael@0: unicodeFont, symbolFont); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: fontData.mCharacterMap = charmap; michael@0: fontData.mUVSOffset = offset; michael@0: fontData.mSymbolFont = symbolFont; michael@0: mLoadStats.cmaps++; michael@0: } michael@0: CFRelease(cmapTable); michael@0: } michael@0: michael@0: mFontFaceData.Put(fontName, fontData); michael@0: CFRelease(faceName); michael@0: } michael@0: michael@0: if (mLoadOtherNames && hasOtherFamilyNames) { michael@0: CFDataRef nameTable = CTFontCopyTable(fontRef, kCTFontTableName, michael@0: kCTFontTableOptionNoOptions); michael@0: michael@0: if (nameTable) { michael@0: const char *nameData = (const char*)CFDataGetBytePtr(nameTable); michael@0: uint32_t nameLen = CFDataGetLength(nameTable); michael@0: gfxFontFamily::ReadOtherFamilyNamesForFace(aFamilyName, michael@0: nameData, nameLen, michael@0: otherFamilyNames, michael@0: false); michael@0: hasOtherFamilyNames = otherFamilyNames.Length() != 0; michael@0: CFRelease(nameTable); michael@0: } michael@0: } michael@0: michael@0: CFRelease(fontRef); michael@0: } michael@0: CFRelease(matchingFonts); michael@0: michael@0: // if found other names, insert them in the hash table michael@0: if (otherFamilyNames.Length() != 0) { michael@0: mOtherFamilyNames.Put(aFamilyName, otherFamilyNames); michael@0: mLoadStats.othernames += otherFamilyNames.Length(); michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: gfxMacPlatformFontList::CreateFontInfoData() michael@0: { michael@0: bool loadCmaps = !UsesSystemFallback() || michael@0: gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback(); michael@0: michael@0: nsRefPtr fi = michael@0: new MacFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps); michael@0: return fi.forget(); michael@0: } michael@0: