Backed out changeset b88172246b66 due to Win32 debug failures.
[mozilla-central.git] / gfx / thebes / gfxMacPlatformFontList.mm
bloba3a4964957d5354be475ec0933927170ee14c94a
1 /* -*- Mode: ObjC; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * ***** BEGIN LICENSE BLOCK *****
3  * Version: BSD
4  *
5  * Copyright (C) 2006-2009 Mozilla Corporation.  All rights reserved.
6  *
7  * Contributor(s):
8  *   Vladimir Vukicevic <vladimir@pobox.com>
9  *   Masayuki Nakano <masayuki@d-toybox.com>
10  *   John Daggett <jdaggett@mozilla.com>
11  *   Jonathan Kew <jfkthame@gmail.com>
12  *
13  * Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
14  *
15  * Redistribution and use in source and binary forms, with or without
16  * modification, are permitted provided that the following conditions
17  * are met:
18  *
19  * 1.  Redistributions of source code must retain the above copyright
20  *     notice, this list of conditions and the following disclaimer.
21  * 2.  Redistributions in binary form must reproduce the above copyright
22  *     notice, this list of conditions and the following disclaimer in the
23  *     documentation and/or other materials provided with the distribution.
24  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
25  *     its contributors may be used to endorse or promote products derived
26  *     from this software without specific prior written permission.
27  *
28  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
29  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
30  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
31  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
32  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
33  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
34  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
35  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
37  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38  *
39  * ***** END LICENSE BLOCK ***** */
41 #ifdef MOZ_LOGGING
42 #define FORCE_PR_LOG /* Allow logging in the release build */
43 #endif
44 #include "prlog.h"
46 #include <Carbon/Carbon.h>
48 #import <AppKit/AppKit.h>
50 #include "gfxPlatformMac.h"
51 #include "gfxMacPlatformFontList.h"
52 #include "gfxMacFont.h"
53 #include "gfxUserFontSet.h"
55 #include "nsServiceManagerUtils.h"
56 #include "nsTArray.h"
58 #include "nsDirectoryServiceUtils.h"
59 #include "nsDirectoryServiceDefs.h"
60 #include "nsISimpleEnumerator.h"
62 #include <unistd.h>
63 #include <time.h>
65 class nsAutoreleasePool {
66 public:
67     nsAutoreleasePool()
68     {
69         mLocalPool = [[NSAutoreleasePool alloc] init];
70     }
71     ~nsAutoreleasePool()
72     {
73         [mLocalPool release];
74     }
75 private:
76     NSAutoreleasePool *mLocalPool;
79 // font info loader constants
80 static const PRUint32 kDelayBeforeLoadingCmaps = 8 * 1000; // 8secs
81 static const PRUint32 kIntervalBetweenLoadingCmaps = 150; // 150ms
82 static const PRUint32 kNumFontsPerSlice = 10; // read in info 10 fonts at a time
84 // indexes into the NSArray objects that the Cocoa font manager returns
85 // as the available members of a family
86 #define INDEX_FONT_POSTSCRIPT_NAME 0
87 #define INDEX_FONT_FACE_NAME 1
88 #define INDEX_FONT_WEIGHT 2
89 #define INDEX_FONT_TRAITS 3
91 static const int kAppleMaxWeight = 14;
92 static const int kAppleExtraLightWeight = 3;
93 static const int kAppleUltraLightWeight = 2;
95 static const int gAppleWeightToCSSWeight[] = {
96     0,
97     1, // 1.
98     1, // 2.  W1, ultralight
99     2, // 3.  W2, extralight
100     3, // 4.  W3, light
101     4, // 5.  W4, semilight
102     5, // 6.  W5, medium
103     6, // 7.
104     6, // 8.  W6, semibold
105     7, // 9.  W7, bold
106     8, // 10. W8, extrabold
107     8, // 11.
108     9, // 12. W9, ultrabold
109     9, // 13
110     9  // 14
113 // cache Cocoa's "shared font manager" for performance
114 static NSFontManager *sFontManager;
116 static void GetStringForNSString(const NSString *aSrc, nsAString& aDist)
118     aDist.SetLength([aSrc length]);
119     [aSrc getCharacters:aDist.BeginWriting()];
122 static NSString* GetNSStringForString(const nsAString& aSrc)
124     return [NSString stringWithCharacters:aSrc.BeginReading()
125                      length:aSrc.Length()];
128 #ifdef PR_LOGGING
130 #define LOG_FONTLIST(args) PR_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), \
131                                PR_LOG_DEBUG, args)
132 #define LOG_FONTLIST_ENABLED() PR_LOG_TEST( \
133                                    gfxPlatform::GetLog(eGfxLog_fontlist), \
134                                    PR_LOG_DEBUG)
136 #endif // PR_LOGGING
138 /* MacOSFontEntry */
139 #pragma mark-
141 MacOSFontEntry::MacOSFontEntry(const nsAString& aPostscriptName,
142                                PRInt32 aWeight,
143                                gfxFontFamily *aFamily,
144                                PRBool aIsStandardFace)
145     : gfxFontEntry(aPostscriptName, aFamily, aIsStandardFace),
146       mATSFontRef(0),
147       mATSFontRefInitialized(PR_FALSE),
148       mRequiresAAT(PR_FALSE),
149       mIsCFF(PR_FALSE),
150       mIsCFFInitialized(PR_FALSE)
152     mWeight = aWeight;
155 MacOSFontEntry::MacOSFontEntry(const nsAString& aPostscriptName, ATSFontRef aFontRef,
156                                PRUint16 aWeight, PRUint16 aStretch, PRUint32 aItalicStyle,
157                                gfxUserFontData *aUserFontData)
158     : gfxFontEntry(aPostscriptName),
159       mATSFontRef(aFontRef),
160       mATSFontRefInitialized(PR_TRUE),
161       mRequiresAAT(PR_FALSE),
162       mIsCFFInitialized(PR_FALSE)
164     // xxx - stretch is basically ignored for now
166     mUserFontData = aUserFontData;
167     mWeight = aWeight;
168     mStretch = aStretch;
169     mFixedPitch = PR_FALSE; // xxx - do we need this for downloaded fonts?
170     mItalic = (aItalicStyle & (FONT_STYLE_ITALIC | FONT_STYLE_OBLIQUE)) != 0;
171     mIsUserFont = aUserFontData != nsnull;
174 ATSFontRef
175 MacOSFontEntry::GetFontRef()
177     if (!mATSFontRefInitialized) {
178         mATSFontRefInitialized = PR_TRUE;
179         NSString *psname = GetNSStringForString(mName);
180         mATSFontRef = ::ATSFontFindFromPostScriptName(CFStringRef(psname),
181                                                       kATSOptionFlagsDefault);
182     }
183     return mATSFontRef;
186 // ATSUI requires AAT-enabled fonts to render complex scripts correctly.
187 // For now, simple clear out the cmap codepoints for fonts that have
188 // codepoints for complex scripts. (Bug 361986)
189 // Core Text is similar, but can render Arabic using OpenType fonts as well.
191 enum eComplexScript {
192     eComplexScriptArabic,
193     eComplexScriptIndic,
194     eComplexScriptTibetan
197 struct ScriptRange {
198     eComplexScript   script;
199     PRUint32         rangeStart;
200     PRUint32         rangeEnd;
203 const ScriptRange gScriptsThatRequireShaping[] = {
204     { eComplexScriptArabic, 0x0600, 0x077F },   // Basic Arabic, Syriac, Arabic Supplement
205     { eComplexScriptIndic, 0x0900, 0x0D7F },     // Indic scripts - Devanagari, Bengali, ..., Malayalam
206     { eComplexScriptTibetan, 0x0F00, 0x0FFF }     // Tibetan
207     // Thai seems to be "renderable" without AAT morphing tables
208     // xxx - Lao, Khmer?
211 nsresult
212 MacOSFontEntry::ReadCMAP()
214     ByteCount size;
216     // attempt this once, if errors occur leave a blank cmap
217     if (mCmapInitialized)
218         return NS_OK;
219     mCmapInitialized = PR_TRUE;
221     PRUint32 kCMAP = TRUETYPE_TAG('c','m','a','p');
223     AutoFallibleTArray<PRUint8,16384> cmap;
224     if (GetFontTable(kCMAP, cmap) != NS_OK)
225         return NS_ERROR_FAILURE;
227     PRPackedBool  unicodeFont, symbolFont; // currently ignored
228     nsresult rv = gfxFontUtils::ReadCMAP(cmap.Elements(), cmap.Length(),
229                                          mCharacterMap, mUVSOffset,
230                                          unicodeFont, symbolFont);
231     if (NS_FAILED(rv)) {
232         mCharacterMap.reset();
233         return rv;
234     }
235     mHasCmapTable = PR_TRUE;
237     ATSFontRef fontRef = GetFontRef();
239     // for layout support, check for the presence of mort/morx and/or
240     // opentype layout tables
241     PRBool hasAATLayout =
242         (::ATSFontGetTable(fontRef, TRUETYPE_TAG('m','o','r','x'),
243                            0, 0, 0, &size) == noErr) ||
244         (::ATSFontGetTable(fontRef, TRUETYPE_TAG('m','o','r','t'),
245                            0, 0, 0, &size) == noErr);
247     PRBool hasGSUB =
248         (::ATSFontGetTable(fontRef, TRUETYPE_TAG('G','S','U','B'),
249                            0, 0, 0, &size) == noErr);
250     PRBool hasGPOS =
251         (::ATSFontGetTable(fontRef, TRUETYPE_TAG('G','P','O','S'),
252                            0, 0, 0, &size) == noErr);
254     if (hasAATLayout && !(hasGSUB || hasGPOS)) {
255         mRequiresAAT = PR_TRUE; // prefer CoreText if font has no OTL tables
256     }
258     PRUint32 numScripts =
259         sizeof(gScriptsThatRequireShaping) / sizeof(ScriptRange);
261     for (PRUint32 s = 0; s < numScripts; s++) {
262         eComplexScript  whichScript = gScriptsThatRequireShaping[s].script;
264         // check to see if the cmap includes complex script codepoints
265         if (mCharacterMap.TestRange(gScriptsThatRequireShaping[s].rangeStart,
266                                     gScriptsThatRequireShaping[s].rangeEnd)) {
267             PRBool omitRange = PR_TRUE;
269             if (hasAATLayout) {
270                 omitRange = PR_FALSE;
271                 // prefer CoreText for Apple's complex-script fonts,
272                 // even if they also have some OpenType tables
273                 // (e.g. Geeza Pro Bold on 10.6; see bug 614903)
274                 mRequiresAAT = PR_TRUE;
275             } else if (whichScript == eComplexScriptArabic) {
276                 // special-case for Arabic:
277                 // even if there's no morph table, CoreText can shape Arabic
278                 // using OpenType layout; or if it's a downloaded font,
279                 // assume the site knows what it's doing (as harfbuzz will
280                 // be able to shape even though the font itself lacks tables
281                 // stripped during sanitization).
282                 // We check for GSUB here, as GPOS alone would not be ok
283                 // for Arabic shaping.
284                 if (hasGSUB || (mIsUserFont && !mIsLocalUserFont)) {
285                     // TODO: to be really thorough, we could check that the
286                     // GSUB table actually supports the 'arab' script tag.
287                     omitRange = PR_FALSE;
288                 }
289             }
291             if (omitRange) {
292                 mCharacterMap.ClearRange(gScriptsThatRequireShaping[s].rangeStart,
293                                          gScriptsThatRequireShaping[s].rangeEnd);
294             }
295         }
296     }
298 #ifdef PR_LOGGING
299     LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d\n",
300                   NS_ConvertUTF16toUTF8(mName).get(),
301                   mCharacterMap.GetSize()));
302 #endif
304     return rv;
307 nsresult
308 MacOSFontEntry::GetFontTable(PRUint32 aTableTag, FallibleTArray<PRUint8>& aBuffer)
310     nsAutoreleasePool localPool;
312     ATSFontRef fontRef = GetFontRef();
313     if (fontRef == (ATSFontRef)kATSUInvalidFontID) {
314         return NS_ERROR_FAILURE;
315     }
317     ByteCount dataLength;
318     OSStatus status = ::ATSFontGetTable(fontRef, aTableTag, 0, 0, 0, &dataLength);
319     if (status != noErr) {
320         return NS_ERROR_FAILURE;
321     }
323     if (!aBuffer.AppendElements(dataLength)) {
324         return NS_ERROR_OUT_OF_MEMORY;
325     }
326     PRUint8 *dataPtr = aBuffer.Elements();
328     status = ::ATSFontGetTable(fontRef, aTableTag, 0, dataLength, dataPtr, &dataLength);
329     NS_ENSURE_TRUE(status == noErr, NS_ERROR_FAILURE);
331     return NS_OK;
334 gfxFont*
335 MacOSFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, PRBool aNeedsBold)
337     return new gfxMacFont(this, aFontStyle, aNeedsBold);
340 PRBool
341 MacOSFontEntry::IsCFF()
343     if (!mIsCFFInitialized) {
344         mIsCFFInitialized = PR_TRUE;
345         ATSFontRef fontRef = GetFontRef();
346         if (fontRef != (ATSFontRef)kATSUInvalidFontID) {
347             ByteCount dataLength;
348             OSStatus status = ::ATSFontGetTable(fontRef,
349                                                 TRUETYPE_TAG('C','F','F',' '),
350                                                 0, 0, 0, &dataLength);
351             if (status == noErr && dataLength > 0) {
352                 mIsCFF = PR_TRUE;
353             }
354         }
355     }
357     return mIsCFF;
361 /* gfxMacFontFamily */
362 #pragma mark-
364 class gfxMacFontFamily : public gfxFontFamily
366 public:
367     gfxMacFontFamily(nsAString& aName) :
368         gfxFontFamily(aName)
369     {}
371     virtual ~gfxMacFontFamily() {}
373     virtual void LocalizedName(nsAString& aLocalizedName);
375     virtual void FindStyleVariations();
377     void EliminateDuplicateFaces();
380 void
381 gfxMacFontFamily::LocalizedName(nsAString& aLocalizedName)
383     nsAutoreleasePool localPool;
385     if (!HasOtherFamilyNames()) {
386         aLocalizedName = mName;
387         return;
388     }
390     NSString *family = GetNSStringForString(mName);
391     NSString *localized = [sFontManager
392                            localizedNameForFamily:family
393                                              face:nil];
395     if (localized) {
396         GetStringForNSString(localized, aLocalizedName);
397         return;
398     }
400     // failed to get localized name, just use the canonical one
401     aLocalizedName = mName;
404 void
405 gfxMacFontFamily::FindStyleVariations()
407     if (mHasStyles)
408         return;
410     nsAutoreleasePool localPool;
412     NSString *family = GetNSStringForString(mName);
414     // create a font entry for each face
415     NSArray *fontfaces = [sFontManager
416                           availableMembersOfFontFamily:family];  // returns an array of [psname, style name, weight, traits] elements, goofy api
417     int faceCount = [fontfaces count];
418     int faceIndex;
420     // Bug 420981 - under 10.5, UltraLight and Light have the same weight value
421     PRBool needToCheckLightFaces =
422         (gfxPlatformMac::GetPlatform()->OSXVersion() >= MAC_OS_X_VERSION_10_5_HEX);
424     for (faceIndex = 0; faceIndex < faceCount; faceIndex++) {
425         NSArray *face = [fontfaces objectAtIndex:faceIndex];
426         NSString *psname = [face objectAtIndex:INDEX_FONT_POSTSCRIPT_NAME];
427         PRInt32 appKitWeight = [[face objectAtIndex:INDEX_FONT_WEIGHT] unsignedIntValue];
428         PRUint32 macTraits = [[face objectAtIndex:INDEX_FONT_TRAITS] unsignedIntValue];
429         NSString *facename = [face objectAtIndex:INDEX_FONT_FACE_NAME];
430         PRBool isStandardFace = PR_FALSE;
432         if (needToCheckLightFaces && appKitWeight == kAppleExtraLightWeight) {
433             // if the facename contains UltraLight, set the weight to the ultralight weight value
434             NSRange range = [facename rangeOfString:@"ultralight" options:NSCaseInsensitiveSearch];
435             if (range.location != NSNotFound) {
436                 appKitWeight = kAppleUltraLightWeight;
437             }
438         }
440         PRInt32 cssWeight = gfxMacPlatformFontList::AppleWeightToCSSWeight(appKitWeight) * 100;
442         // make a nsString
443         nsAutoString postscriptFontName;
444         GetStringForNSString(psname, postscriptFontName);
446         if ([facename isEqualToString:@"Regular"] ||
447             [facename isEqualToString:@"Bold"] ||
448             [facename isEqualToString:@"Italic"] ||
449             [facename isEqualToString:@"Oblique"] ||
450             [facename isEqualToString:@"Bold Italic"] ||
451             [facename isEqualToString:@"Bold Oblique"])
452         {
453             isStandardFace = PR_TRUE;
454         }
456         // create a font entry
457         MacOSFontEntry *fontEntry = new MacOSFontEntry(postscriptFontName,
458                                                        cssWeight, this, isStandardFace);
459         if (!fontEntry) break;
461         // set additional properties based on the traits reported by Cocoa
462         if (macTraits & (NSCondensedFontMask | NSNarrowFontMask | NSCompressedFontMask)) {
463             fontEntry->mStretch = NS_FONT_STRETCH_CONDENSED;
464         } else if (macTraits & NSExpandedFontMask) {
465             fontEntry->mStretch = NS_FONT_STRETCH_EXPANDED;
466         }
467         // Cocoa fails to set the Italic traits bit for HelveticaLightItalic,
468         // at least (see bug 611855), so check for style name endings as well
469         if ((macTraits & NSItalicFontMask) ||
470             [facename hasSuffix:@"Italic"] ||
471             [facename hasSuffix:@"Oblique"])
472         {
473             fontEntry->mItalic = PR_TRUE;
474         }
475         if (macTraits & NSFixedPitchFontMask) {
476             fontEntry->mFixedPitch = PR_TRUE;
477         }
479 #ifdef PR_LOGGING
480         if (LOG_FONTLIST_ENABLED()) {
481             LOG_FONTLIST(("(fontlist) added (%s) to family (%s)"
482                  " with style: %s weight: %d stretch: %d"
483                  " (apple-weight: %d macTraits: %8.8x)",
484                  NS_ConvertUTF16toUTF8(fontEntry->Name()).get(), 
485                  NS_ConvertUTF16toUTF8(Name()).get(), 
486                  fontEntry->IsItalic() ? "italic" : "normal",
487                  cssWeight, fontEntry->Stretch(),
488                  appKitWeight, macTraits));
489         }
490 #endif
492         // insert into font entry array of family
493         AddFontEntry(fontEntry);
494     }
496     SortAvailableFonts();
497     SetHasStyles(PR_TRUE);
499     if (mIsBadUnderlineFamily) {
500         SetBadUnderlineFonts();
501     }
504 void
505 gfxMacFontFamily::EliminateDuplicateFaces()
507     PRUint32 i, bold, numFonts, italicIndex;
508     MacOSFontEntry *italic, *nonitalic;
510     FindStyleVariations();
512     // if normal and italic have the same ATS font ref, delete italic
513     // if bold and bold-italic have the same ATS font ref, delete bold-italic
515     // two iterations, one for normal, one for bold
516     for (bold = 0; bold < 2; bold++) {
517         numFonts = mAvailableFonts.Length();
519         // find the non-italic face
520         nonitalic = nsnull;
521         for (i = 0; i < numFonts; i++) {
522             if ((mAvailableFonts[i]->IsBold() == (bold == 1)) &&
523                 !mAvailableFonts[i]->IsItalic()) {
524                 nonitalic = static_cast<MacOSFontEntry*>(mAvailableFonts[i].get());
525                 break;
526             }
527         }
529         // find the italic face
530         if (nonitalic) {
531             italic = nsnull;
532             for (i = 0; i < numFonts; i++) {
533                 if ((mAvailableFonts[i]->IsBold() == (bold == 1)) &&
534                      mAvailableFonts[i]->IsItalic()) {
535                     italic = static_cast<MacOSFontEntry*>(mAvailableFonts[i].get());
536                     italicIndex = i;
537                     break;
538                 }
539             }
541             // if italic face and non-italic face have matching ATS refs,
542             // or if the italic returns 0 rather than an actual ATSFontRef,
543             // then the italic face is bogus so remove it
544             if (italic && (italic->GetFontRef() == 0 ||
545                            italic->GetFontRef() == nonitalic->GetFontRef())) {
546                 mAvailableFonts.RemoveElementAt(italicIndex);
547             }
548         }
549     }
553 /* gfxSingleFaceMacFontFamily */
554 #pragma mark-
556 class gfxSingleFaceMacFontFamily : public gfxFontFamily
558 public:
559     gfxSingleFaceMacFontFamily(nsAString& aName) :
560         gfxFontFamily(aName)
561     {}
563     virtual ~gfxSingleFaceMacFontFamily() {}
565     virtual void LocalizedName(nsAString& aLocalizedName);
567     virtual void ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList);
570 void
571 gfxSingleFaceMacFontFamily::LocalizedName(nsAString& aLocalizedName)
573     nsAutoreleasePool localPool;
575     if (!HasOtherFamilyNames()) {
576         aLocalizedName = mName;
577         return;
578     }
580     gfxFontEntry *fe = mAvailableFonts[0];
581     NSFont *font = [NSFont fontWithName:GetNSStringForString(fe->Name())
582                                    size:0.0];
583     if (font) {
584         NSString *localized = [font displayName];
585         if (localized) {
586             GetStringForNSString(localized, aLocalizedName);
587             return;
588         }
589     }
591     // failed to get localized name, just use the canonical one
592     aLocalizedName = mName;
595 void
596 gfxSingleFaceMacFontFamily::ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList)
598     if (mOtherFamilyNamesInitialized)
599         return;
601     gfxFontEntry *fe = mAvailableFonts[0];
602     if (!fe)
603         return;
605     const PRUint32 kNAME = TRUETYPE_TAG('n','a','m','e');
606     AutoFallibleTArray<PRUint8,8192> buffer;
608     if (fe->GetFontTable(kNAME, buffer) != NS_OK)
609         return;
611     mHasOtherFamilyNames = ReadOtherFamilyNamesForFace(aPlatformFontList,
612                                                        buffer,
613                                                        PR_TRUE);
614     mOtherFamilyNamesInitialized = PR_TRUE;
618 /* gfxMacPlatformFontList */
619 #pragma mark-
621 gfxMacPlatformFontList::gfxMacPlatformFontList() :
622     gfxPlatformFontList(PR_FALSE), mATSGeneration(PRUint32(kATSGenerationInitial))
624     ::ATSFontNotificationSubscribe(ATSNotification,
625                                    kATSFontNotifyOptionDefault,
626                                    (void*)this, nsnull);
628     // this should always be available (though we won't actually fail if it's missing,
629     // we'll just end up doing a search and then caching the new result instead)
630     mReplacementCharFallbackFamily = NS_LITERAL_STRING("Lucida Grande");
632     // cache this in a static variable so that MacOSFontFamily objects
633     // don't have to repeatedly look it up
634     sFontManager = [NSFontManager sharedFontManager];
637 nsresult
638 gfxMacPlatformFontList::InitFontList()
640     nsAutoreleasePool localPool;
642     ATSGeneration currentGeneration = ::ATSGetGeneration();
644     // need to ignore notifications after adding each font
645     if (mATSGeneration == currentGeneration)
646         return NS_OK;
648     mATSGeneration = currentGeneration;
649 #ifdef PR_LOGGING
650     LOG_FONTLIST(("(fontlist) updating to generation: %d", mATSGeneration));
651 #endif
653     // reset font lists
654     gfxPlatformFontList::InitFontList();
655     
656     // iterate over available families
657     NSEnumerator *families = [[sFontManager availableFontFamilies]
658                               objectEnumerator];  // returns "canonical", non-localized family name
660     nsAutoString availableFamilyName;
662     NSString *availableFamily = nil;
663     while ((availableFamily = [families nextObject])) {
665         // make a nsString
666         GetStringForNSString(availableFamily, availableFamilyName);
668         // create a family entry
669         gfxFontFamily *familyEntry = new gfxMacFontFamily(availableFamilyName);
670         if (!familyEntry) break;
672         // add the family entry to the hash table
673         ToLowerCase(availableFamilyName);
674         mFontFamilies.Put(availableFamilyName, familyEntry);
676         // check the bad underline blacklist
677         if (mBadUnderlineFamilyNames.Contains(availableFamilyName))
678             familyEntry->SetBadUnderlineFamily();
679     }
681     InitSingleFaceList();
683     // to avoid full search of font name tables, seed the other names table with localized names from
684     // some of the prefs fonts which are accessed via their localized names.  changes in the pref fonts will only cause
685     // a font lookup miss earlier. this is a simple optimization, it's not required for correctness
686     PreloadNamesList();
688     // clean up various minor 10.4 font problems for specific fonts
689     if (gfxPlatformMac::GetPlatform()->OSXVersion() < MAC_OS_X_VERSION_10_5_HEX) {
690         // Cocoa calls report that italic faces exist for Courier and Helvetica,
691         // even though only bold faces exist so test for this using ATS font refs (10.5 has proper faces)
692         EliminateDuplicateFaces(NS_LITERAL_STRING("Courier"));
693         EliminateDuplicateFaces(NS_LITERAL_STRING("Helvetica"));
695         // Cocoa reports that Courier and Monaco are not fixed-pitch fonts
696         // so explicitly tweak these settings
697         SetFixedPitch(NS_LITERAL_STRING("Courier"));
698         SetFixedPitch(NS_LITERAL_STRING("Monaco"));
699     }
701     // start the delayed cmap loader
702     StartLoader(kDelayBeforeLoadingCmaps, kIntervalBetweenLoadingCmaps);
704         return NS_OK;
707 void
708 gfxMacPlatformFontList::InitSingleFaceList()
710     nsAutoTArray<nsString, 10> singleFaceFonts;
711     gfxFontUtils::GetPrefsFontList("font.single-face-list", singleFaceFonts);
713     PRUint32 numFonts = singleFaceFonts.Length();
714     for (PRUint32 i = 0; i < numFonts; i++) {
715 #ifdef PR_LOGGING
716         LOG_FONTLIST(("(fontlist-singleface) face name: %s\n",
717                       NS_ConvertUTF16toUTF8(singleFaceFonts[i]).get()));
718 #endif
719         gfxFontEntry *fontEntry = LookupLocalFont(nsnull, singleFaceFonts[i]);
720         if (fontEntry) {
721             nsAutoString familyName, key;
722             familyName = singleFaceFonts[i];
723             GenerateFontListKey(familyName, key);
724 #ifdef PR_LOGGING
725             LOG_FONTLIST(("(fontlist-singleface) family name: %s, key: %s\n",
726                           NS_ConvertUTF16toUTF8(familyName).get(),
727                           NS_ConvertUTF16toUTF8(key).get()));
728 #endif
730             // add only if doesn't exist already
731             PRBool found;
732             gfxFontFamily *familyEntry;
733             if (!(familyEntry = mFontFamilies.GetWeak(key, &found))) {
734                 familyEntry = new gfxSingleFaceMacFontFamily(familyName);
735                 familyEntry->AddFontEntry(fontEntry);
736                 familyEntry->SetHasStyles(PR_TRUE);
737                 mFontFamilies.Put(key, familyEntry);
738                 fontEntry->mFamily = familyEntry;
739 #ifdef PR_LOGGING
740                 LOG_FONTLIST(("(fontlist-singleface) added new family\n",
741                               NS_ConvertUTF16toUTF8(familyName).get(),
742                               NS_ConvertUTF16toUTF8(key).get()));
743 #endif
744             }
745         }
746     }
749 void
750 gfxMacPlatformFontList::EliminateDuplicateFaces(const nsAString& aFamilyName)
752     gfxMacFontFamily *family =
753         static_cast<gfxMacFontFamily*>(FindFamily(aFamilyName));
755     if (family)
756         family->EliminateDuplicateFaces();
759 PRBool
760 gfxMacPlatformFontList::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName)
762     gfxFontFamily *family = FindFamily(aFontName);
763     if (family) {
764         family->LocalizedName(aFamilyName);
765         return PR_TRUE;
766     }
768     // Gecko 1.8 used Quickdraw font api's which produce a slightly different set of "family"
769     // names.  Try to resolve based on these names, in case this is stored in an old profile
770     // 1.8: "Futura", "Futura Condensed" ==> 1.9: "Futura"
772     // convert the name to a Pascal-style Str255 to try as Quickdraw name
773     Str255 qdname;
774     NS_ConvertUTF16toUTF8 utf8name(aFontName);
775     qdname[0] = PR_MAX(255, strlen(utf8name.get()));
776     memcpy(&qdname[1], utf8name.get(), qdname[0]);
778     // look up the Quickdraw name
779     ATSFontFamilyRef atsFamily = ::ATSFontFamilyFindFromQuickDrawName(qdname);
780     if (atsFamily == (ATSFontFamilyRef)kInvalidFontFamily) {
781         return PR_FALSE;
782     }
784     // if we found a family, get its ATS name
785     CFStringRef cfName;
786     OSStatus status = ::ATSFontFamilyGetName(atsFamily, kATSOptionFlagsDefault, &cfName);
787     if (status != noErr) {
788         return PR_FALSE;
789     }
791     // then use this to locate the family entry and retrieve its localized name
792     nsAutoString familyName;
793     GetStringForNSString((const NSString*)cfName, familyName);
794     ::CFRelease(cfName);
796     family = FindFamily(familyName);
797     if (family) {
798         family->LocalizedName(aFamilyName);
799         return PR_TRUE;
800     }
802     return PR_FALSE;
805 void
806 gfxMacPlatformFontList::ATSNotification(ATSFontNotificationInfoRef aInfo,
807                                     void* aUserArg)
809     // xxx - should be carefully pruning the list of fonts, not rebuilding it from scratch
810     gfxMacPlatformFontList *qfc = (gfxMacPlatformFontList*)aUserArg;
811     qfc->UpdateFontList();
814 gfxFontEntry*
815 gfxMacPlatformFontList::GetDefaultFont(const gfxFontStyle* aStyle, PRBool& aNeedsBold)
817     nsAutoreleasePool localPool;
819     NSString *defaultFamily = [[NSFont userFontOfSize:aStyle->size] familyName];
820     nsAutoString familyName;
822     GetStringForNSString(defaultFamily, familyName);
823     return FindFontForFamily(familyName, aStyle, aNeedsBold);
826 PRInt32
827 gfxMacPlatformFontList::AppleWeightToCSSWeight(PRInt32 aAppleWeight)
829     if (aAppleWeight < 1)
830         aAppleWeight = 1;
831     else if (aAppleWeight > kAppleMaxWeight)
832         aAppleWeight = kAppleMaxWeight;
833     return gAppleWeightToCSSWeight[aAppleWeight];
836 gfxFontEntry*
837 gfxMacPlatformFontList::LookupLocalFont(const gfxProxyFontEntry *aProxyEntry,
838                                         const nsAString& aFontName)
840     nsAutoreleasePool localPool;
842     NSString *faceName = GetNSStringForString(aFontName);
844     // first lookup a single face based on postscript name
845     ATSFontRef fontRef = ::ATSFontFindFromPostScriptName(CFStringRef(faceName),
846                                                          kATSOptionFlagsDefault);
848     // if not found, lookup using full font name
849     if (fontRef == kInvalidFont)
850         fontRef = ::ATSFontFindFromName(CFStringRef(faceName),
851                                         kATSOptionFlagsDefault);
853     // not found
854     if (fontRef == kInvalidFont)
855         return nsnull;
857     MacOSFontEntry *newFontEntry;
858     if (aProxyEntry) {
859         PRUint16 w = aProxyEntry->mWeight;
860         NS_ASSERTION(w >= 100 && w <= 900, "bogus font weight value!");
862         newFontEntry =
863             new MacOSFontEntry(aFontName, fontRef,
864                                w, aProxyEntry->mStretch,
865                                aProxyEntry->mItalic ?
866                                    FONT_STYLE_ITALIC : FONT_STYLE_NORMAL,
867                                nsnull);
868     } else {
869         newFontEntry =
870             new MacOSFontEntry(aFontName, fontRef,
871                                400, 0, FONT_STYLE_NORMAL, nsnull);
872     }
874     return newFontEntry;
877 // grumble, another non-publised Apple API dependency (found in Webkit code)
878 // activated with this value, font will not be found via system lookup routines
879 // it can only be used via the created ATSFontRef
880 // needed to prevent one doc from finding a font used in a separate doc
882 enum {
883     kPrivateATSFontContextPrivate = 3
886 class MacOSUserFontData : public gfxUserFontData {
887 public:
888     MacOSUserFontData(ATSFontContainerRef aContainerRef)
889         : mContainerRef(aContainerRef)
890     { }
892     virtual ~MacOSUserFontData()
893     {
894         // deactivate font
895         if (mContainerRef)
896             ::ATSFontDeactivate(mContainerRef, NULL, kATSOptionFlagsDefault);
897     }
899     ATSFontContainerRef     mContainerRef;
902 gfxFontEntry*
903 gfxMacPlatformFontList::MakePlatformFont(const gfxProxyFontEntry *aProxyEntry,
904                                          const PRUint8 *aFontData,
905                                          PRUint32 aLength)
907     OSStatus err;
909     NS_ASSERTION(aFontData, "MakePlatformFont called with null data");
911     // MakePlatformFont is responsible for deleting the font data with NS_Free
912     // so we set up a stack object to ensure it is freed even if we take an
913     // early exit
914     struct FontDataDeleter {
915         FontDataDeleter(const PRUint8 *aFontData)
916             : mFontData(aFontData) { }
917         ~FontDataDeleter() { NS_Free((void*)mFontData); }
918         const PRUint8 *mFontData;
919     };
920     FontDataDeleter autoDelete(aFontData);
922     ATSFontRef fontRef;
923     ATSFontContainerRef containerRef;
925     // we get occasional failures when multiple fonts are activated in quick succession
926     // if the ATS font cache is damaged; to work around this, we can retry the activation
927     const PRUint32 kMaxRetries = 3;
928     PRUint32 retryCount = 0;
929     while (retryCount++ < kMaxRetries) {
930         err = ::ATSFontActivateFromMemory(const_cast<PRUint8*>(aFontData), aLength,
931                                           kPrivateATSFontContextPrivate,
932                                           kATSFontFormatUnspecified,
933                                           NULL,
934                                           kATSOptionFlagsDoNotNotify,
935                                           &containerRef);
936         mATSGeneration = ::ATSGetGeneration();
938         if (err != noErr) {
939 #if DEBUG
940             char warnBuf[1024];
941             sprintf(warnBuf, "downloaded font error, ATSFontActivateFromMemory err: %d for (%s)",
942                     PRInt32(err),
943                     NS_ConvertUTF16toUTF8(aProxyEntry->mFamily->Name()).get());
944             NS_WARNING(warnBuf);
945 #endif
946             return nsnull;
947         }
949         // ignoring containers with multiple fonts, use the first face only for now
950         err = ::ATSFontFindFromContainer(containerRef, kATSOptionFlagsDefault, 1,
951                                          &fontRef, NULL);
952         if (err != noErr) {
953 #if DEBUG
954             char warnBuf[1024];
955             sprintf(warnBuf, "downloaded font error, ATSFontFindFromContainer err: %d for (%s)",
956                     PRInt32(err),
957                     NS_ConvertUTF16toUTF8(aProxyEntry->mFamily->Name()).get());
958             NS_WARNING(warnBuf);
959 #endif
960             ::ATSFontDeactivate(containerRef, NULL, kATSOptionFlagsDefault);
961             return nsnull;
962         }
964         // now lookup the Postscript name; this may fail if the font cache is bad
965         OSStatus err;
966         NSString *psname = NULL;
967         err = ::ATSFontGetPostScriptName(fontRef, kATSOptionFlagsDefault, (CFStringRef*) (&psname));
968         if (err == noErr) {
969             [psname release];
970         } else {
971 #ifdef DEBUG
972             char warnBuf[1024];
973             sprintf(warnBuf, "ATSFontGetPostScriptName err = %d for (%s), retries = %d", (PRInt32)err,
974                     NS_ConvertUTF16toUTF8(aProxyEntry->mFamily->Name()).get(), retryCount);
975             NS_WARNING(warnBuf);
976 #endif
977             ::ATSFontDeactivate(containerRef, NULL, kATSOptionFlagsDefault);
978             // retry the activation a couple of times if this fails
979             // (may be a transient failure due to ATS font cache issues)
980             continue;
981         }
983         // font entry will own this
984         MacOSUserFontData *userFontData = new MacOSUserFontData(containerRef);
986         if (!userFontData) {
987             ::ATSFontDeactivate(containerRef, NULL, kATSOptionFlagsDefault);
988             return nsnull;
989         }
991         PRUint16 w = aProxyEntry->mWeight;
992         NS_ASSERTION(w >= 100 && w <= 900, "bogus font weight value!");
994         // create the font entry
995         nsAutoString uniqueName;
997         nsresult rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName);
998         if (NS_FAILED(rv)) {
999             delete userFontData;
1000             return nsnull;
1001         }
1003         MacOSFontEntry *newFontEntry =
1004             new MacOSFontEntry(uniqueName,
1005                                fontRef,
1006                                w, aProxyEntry->mStretch,
1007                                aProxyEntry->mItalic ?
1008                                    FONT_STYLE_ITALIC : FONT_STYLE_NORMAL,
1009                                userFontData);
1011         if (!newFontEntry) {
1012             delete userFontData;
1013             return nsnull;
1014         }
1016         // if succeeded and font cmap is good, return the new font
1017         if (newFontEntry->mIsValid && NS_SUCCEEDED(newFontEntry->ReadCMAP()))
1018             return newFontEntry;
1020         // if something is funky about this font, delete immediately
1021 #if DEBUG
1022         char warnBuf[1024];
1023         sprintf(warnBuf, "downloaded font not loaded properly, removed face for (%s)",
1024                 NS_ConvertUTF16toUTF8(aProxyEntry->mFamily->Name()).get());
1025         NS_WARNING(warnBuf);
1026 #endif
1027         delete newFontEntry;
1029         // We don't retry from here; the ATS font cache issue would have caused failure earlier
1030         // so if we get here, there's something else bad going on within our font data structures.
1031         // Currently, there should be no way to reach here, as fontentry creation cannot fail
1032         // except by memory allocation failure.
1033         NS_WARNING("invalid font entry for a newly activated font");
1034         break;
1035     }
1037     // if we get here, the activation failed (even with possible retries); can't use this font
1038     return nsnull;