0013507b662dfbb144c08149af39a63c3295ff7d
[WebKit-https.git] / WebKit / WebCoreSupport.subproj / WebTextRendererFactory.m
1 /*
2  * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #import <WebKit/WebAssertions.h>
30 #import <WebKit/WebBridge.h>
31 #import <WebKit/WebKitLogging.h>
32 #import <WebKit/WebKitSystemBits.h>
33 #import <WebKit/WebPreferences.h>
34 #import <WebKit/WebTextRendererFactory.h>
35 #import <WebKit/WebTextRenderer.h>
36
37 #import <CoreGraphics/CoreGraphicsPrivate.h>
38 #import <CoreGraphics/CGFontLCDSupport.h>
39 #import <CoreGraphics/CGFontCache.h>
40
41 #import <mach-o/dyld.h>
42
43 #define IMPORTANT_FONT_TRAITS (0 \
44     | NSBoldFontMask \
45     | NSCompressedFontMask \
46     | NSCondensedFontMask \
47     | NSExpandedFontMask \
48     | NSItalicFontMask \
49     | NSNarrowFontMask \
50     | NSPosterFontMask \
51     | NSSmallCapsFontMask \
52 )
53
54 #define DESIRED_WEIGHT 5
55
56 @interface NSFont (WebPrivate)
57 - (ATSUFontID)_atsFontID;
58 @end
59
60 @interface NSFont (WebAppKitSecretAPI)
61 - (BOOL)_isFakeFixedPitch;
62 @end
63
64 @implementation WebTextRendererFactory
65
66 - (BOOL)coalesceTextDrawing
67 {
68     return [viewStack objectAtIndex: [viewStack count]-1] == [NSView focusView] ? YES : NO;
69 }
70
71 - (void)startCoalesceTextDrawing
72 {
73     if (!viewStack)
74         viewStack = [[NSMutableArray alloc] init];
75     if (!viewBuffers)
76         viewBuffers = [[NSMutableDictionary alloc] init];
77     [viewStack addObject: [NSView focusView]];
78 }
79
80 - (void)endCoalesceTextDrawing
81 {
82     ASSERT([self coalesceTextDrawing]);
83     
84     NSView *targetView = [viewStack objectAtIndex: [viewStack count]-1];
85     [viewStack removeLastObject];
86     NSValue *viewKey = [NSValue valueWithNonretainedObject: targetView];
87     NSMutableSet *glyphBuffers = [viewBuffers objectForKey:viewKey];
88
89     [glyphBuffers makeObjectsPerformSelector: @selector(drawInView:) withObject: targetView];
90     [glyphBuffers makeObjectsPerformSelector: @selector(reset)];
91     [viewBuffers removeObjectForKey: viewKey];
92 }
93
94 - (WebGlyphBuffer *)glyphBufferForFont: (NSFont *)font andColor: (NSColor *)color
95 {
96     ASSERT([self coalesceTextDrawing]);
97
98     NSMutableSet *glyphBuffers;
99     WebGlyphBuffer *glyphBuffer = nil;
100     NSValue *viewKey = [NSValue valueWithNonretainedObject: [NSView focusView]];
101     
102     glyphBuffers = [viewBuffers objectForKey:viewKey];
103     if (glyphBuffers == nil){
104         glyphBuffers = [[NSMutableSet alloc] init];
105         [viewBuffers setObject: glyphBuffers forKey: viewKey];
106         [glyphBuffers release];
107     }
108     
109     NSEnumerator *enumerator = [glyphBuffers objectEnumerator];
110     id value;
111     
112     // Could use a dictionary w/ font/color key for faster lookup.
113     while ((value = [enumerator nextObject])) {
114         if ([value font] == font && [[value color] isEqual: color])
115             glyphBuffer = value;
116     }
117     if (glyphBuffer == nil){
118         glyphBuffer = [[WebGlyphBuffer alloc] initWithFont: font color: color];
119         [glyphBuffers addObject: glyphBuffer];
120         [glyphBuffer release];
121     }
122         
123     return glyphBuffer;
124 }
125
126 static bool
127 getAppDefaultValue(CFStringRef key, int *v)
128 {
129     CFPropertyListRef value;
130
131     value = CFPreferencesCopyValue(key, kCFPreferencesCurrentApplication,
132                                    kCFPreferencesAnyUser,
133                                    kCFPreferencesAnyHost);
134     if (value == NULL) {
135         value = CFPreferencesCopyValue(key, kCFPreferencesCurrentApplication,
136                                        kCFPreferencesCurrentUser,
137                                        kCFPreferencesAnyHost);
138         if (value == NULL)
139             return false;
140     }
141
142     if (CFGetTypeID(value) == CFNumberGetTypeID()) {
143         if (v != NULL)
144             CFNumberGetValue(value, kCFNumberIntType, v);
145     } else if (CFGetTypeID(value) == CFStringGetTypeID()) {
146         if (v != NULL)
147             *v = CFStringGetIntValue(value);
148     } else {
149         CFRelease(value);
150         return false;
151     }
152
153     CFRelease(value);
154     return true;
155 }
156
157 static bool
158 getUserDefaultValue(CFStringRef key, int *v)
159 {
160     CFPropertyListRef value;
161
162     value = CFPreferencesCopyValue(key, kCFPreferencesAnyApplication,
163                                    kCFPreferencesCurrentUser,
164                                    kCFPreferencesCurrentHost);
165     if (value == NULL)
166         return false;
167
168     if (CFGetTypeID(value) == CFNumberGetTypeID()) {
169         if (v != NULL)
170             CFNumberGetValue(value, kCFNumberIntType, v);
171     } else if (CFGetTypeID(value) == CFStringGetTypeID()) {
172         if (v != NULL)
173             *v = CFStringGetIntValue(value);
174     } else {
175         CFRelease(value);
176         return false;
177     }
178
179     CFRelease(value);
180     return true;
181 }
182
183 static int getLCDScaleParameters(void)
184 {
185     int mode;
186     CFStringRef key;
187
188     key = CFSTR("AppleFontSmoothing");
189     if (!getAppDefaultValue(key, &mode)) {
190         if (!getUserDefaultValue(key, &mode))
191             return 1;
192     }
193
194     switch (mode) {
195         case kCGFontSmoothingLCDLight:
196         case kCGFontSmoothingLCDMedium:
197         case kCGFontSmoothingLCDStrong:
198             return 4;
199         default:
200             return 1;
201     }
202
203 }
204
205 static CFMutableDictionaryRef fontCache;
206 static CFMutableDictionaryRef fixedPitchFonts;
207
208 - (void)clearCaches
209 {
210     [cacheForScreen release];
211     [cacheForPrinter release];
212     
213     cacheForScreen = [[NSMutableDictionary alloc] init];
214     cacheForPrinter = [[NSMutableDictionary alloc] init];
215
216     if (fontCache)
217         CFRelease(fontCache);
218     fontCache = NULL;
219     
220     if (fixedPitchFonts) {
221         CFRelease (fixedPitchFonts);
222         fixedPitchFonts = 0;
223     }
224         
225     [super clearCaches];
226 }
227
228 static void 
229 fontsChanged( ATSFontNotificationInfoRef info, void *_factory)
230 {
231     WebTextRendererFactory *factory = (WebTextRendererFactory *)_factory;
232     
233     LOG (FontCache, "clearing font caches");
234
235     ASSERT (factory);
236
237     [factory clearCaches];
238 }
239
240 #define MINIMUM_GLYPH_CACHE_SIZE 1536 * 1024
241
242 + (void)createSharedFactory
243 {
244     if (![self sharedFactory]) {
245         [[[self alloc] init] release];
246
247 #if !defined(BUILDING_ON_PANTHER)
248         // Turn on local font cache, in addition to the system cache.
249         // See 3835148
250         CGFontSetShouldUseMulticache(true);
251 #endif
252         
253         CGFontCache *fontCache;
254         fontCache = CGFontCacheGetLocalCache();
255         CGFontCacheSetShouldAutoExpire (fontCache, false);
256
257         size_t s;
258         if (WebSystemMainMemory() > 128 * 1024 * 1024)
259             s = MINIMUM_GLYPH_CACHE_SIZE*getLCDScaleParameters();
260         else
261             s = MINIMUM_GLYPH_CACHE_SIZE;
262 #ifndef NDEBUG
263         LOG (CacheSizes, "Glyph cache size set to %d bytes.", s);
264 #endif
265         CGFontCacheSetMaxSize (fontCache, s);
266
267         // Ignore errors returned from ATSFontNotificationSubscribe.  If we can't subscribe then we
268         // won't be told about changes to fonts.
269         ATSFontNotificationSubscribe( fontsChanged, kATSFontNotifyOptionDefault, (void *)[super sharedFactory], nil );
270     }
271     ASSERT([[self sharedFactory] isKindOfClass:self]);
272 }
273
274 + (WebTextRendererFactory *)sharedFactory;
275 {
276     return (WebTextRendererFactory *)[super sharedFactory];
277 }
278
279 - (BOOL)isFontFixedPitch: (NSFont *)font
280 {
281     BOOL ret = NO;
282     
283     if (!fixedPitchFonts) {
284         fixedPitchFonts = CFDictionaryCreateMutable (0, 0, &kCFTypeDictionaryKeyCallBacks, 0);
285     }
286     
287     CFBooleanRef val = CFDictionaryGetValue (fixedPitchFonts, font);
288     if (val) {
289         ret = (val == kCFBooleanTrue);
290     }
291     else {
292         // Special case Osaka-Mono.  According to <rdar://problem/3999467>, we should treat Osaka-Mono
293         // as fixed pitch. Note that the AppKit does not report MS-PGothic as fixed pitch.
294
295         // Special case MS PGothic.  According to <rdar://problem/4032938, we should not treat MS-PGothic
296         // as fixed pitch.  Note that AppKit does report MS-PGothic as fixed pitch.
297         if (([font isFixedPitch] || [font _isFakeFixedPitch] || [[font fontName] caseInsensitiveCompare:@"Osaka-Mono"] == NSOrderedSame) && 
298                 ![[font fontName] caseInsensitiveCompare:@"MS-PGothic"] == NSOrderedSame) {
299             CFDictionarySetValue (fixedPitchFonts, font, kCFBooleanTrue);
300             ret = YES;
301         }
302         else {
303             CFDictionarySetValue (fixedPitchFonts, font, kCFBooleanFalse);
304             ret = NO;
305         }
306     }
307     return ret;    
308 }
309
310 - init
311 {
312     [super init];
313     
314     cacheForScreen = [[NSMutableDictionary alloc] init];
315     cacheForPrinter = [[NSMutableDictionary alloc] init];
316     
317     return self;
318 }
319
320 - (void)dealloc
321 {
322     [cacheForScreen release];
323     [cacheForPrinter release];
324     [viewBuffers release];
325     [viewStack release];
326     
327     [super dealloc];
328 }
329
330 - (WebTextRenderer *)rendererWithFont:(NSFont *)font usingPrinterFont:(BOOL)usingPrinterFont
331 {
332     NSMutableDictionary *cache = usingPrinterFont ? cacheForPrinter : cacheForScreen;
333     WebTextRenderer *renderer = [cache objectForKey:font];
334     if (renderer == nil) {
335         renderer = [[WebTextRenderer alloc] initWithFont:font usingPrinterFont:usingPrinterFont];
336         [cache setObject:renderer forKey:font];
337         [renderer release];
338     }
339     return renderer;
340 }
341
342 - (NSFont *)fallbackFontWithTraits:(NSFontTraitMask)traits size:(float)size 
343 {
344     NSFont *font = [self cachedFontFromFamily:@"Helvetica" traits:traits size:size];
345     if (font == nil) {
346         // The Helvetica fallback will almost always work, since that's a basic
347         // font that we ship with all systems. But in the highly unusual case where
348         // the user removed Helvetica, we fall back on Lucida Grande because that's
349         // guaranteed to be there, according to Nathan Taylor. This is good enough
350         // to avoid a crash, at least. To reduce the possibility of failure even further,
351         // we don't even bother with traits.
352         font = [self cachedFontFromFamily:@"Lucida Grande" traits:0 size:size];
353     }
354     return font;
355 }
356
357         
358
359 - (NSFont *)fontWithFamilies:(NSString **)families traits:(NSFontTraitMask)traits size:(float)size
360 {
361     NSFont *font = nil;
362     NSString *family;
363     int i = 0;
364     
365     while (families && families[i] != 0 && font == nil){
366         family = families[i++];
367         if ([family length] != 0)
368             font = [self cachedFontFromFamily: family traits:traits size:size];
369     }
370     if (font == nil) {
371         // We didn't find a font.  Use a fallback font.
372         static int matchCount = 3;
373         static NSString *matchWords[] = { @"Arabic", @"Pashto", @"Urdu" };
374         static NSString *matchFamilies[] = { @"Geeza Pro", @"Geeza Pro", @"Geeza Pro" };
375         
376         // First we'll attempt to find an appropriate font using a match based on 
377         // the presence of keywords in the the requested names.  For example, we'll
378         // match any name that contains "Arabic" to Geeza Pro.
379         int j;
380         i = 0;
381         while (families && families[i] != 0 && font == nil) {
382             family = families[i++];
383             if ([family length] != 0) {
384                 j = 0;
385                 while (j < matchCount && font == nil) {
386                     if ([family rangeOfString:matchWords[j] options:NSCaseInsensitiveSearch].location != NSNotFound) {
387                         font = [self cachedFontFromFamily:matchFamilies[j] traits:traits size:size];
388                     }
389                     j++;
390                 }
391             }
392         }
393         
394         // Still nothing found, use the final fallback.
395         if (font == nil) {
396             font = [self fallbackFontWithTraits:traits size:size];
397         }
398     }
399
400     return font;
401 }
402
403 static BOOL acceptableChoice(NSFontTraitMask desiredTraits, int desiredWeight,
404     NSFontTraitMask candidateTraits, int candidateWeight)
405 {
406     return (candidateTraits & desiredTraits) == desiredTraits;
407 }
408
409 static BOOL betterChoice(NSFontTraitMask desiredTraits, int desiredWeight,
410     NSFontTraitMask chosenTraits, int chosenWeight,
411     NSFontTraitMask candidateTraits, int candidateWeight)
412 {
413     if (!acceptableChoice(desiredTraits, desiredWeight, candidateTraits, candidateWeight)) {
414         return NO;
415     }
416     
417     // A list of the traits we care about.
418     // The top item in the list is the worst trait to mismatch; if a font has this
419     // and we didn't ask for it, we'd prefer any other font in the family.
420     const NSFontTraitMask masks[] = {
421         NSPosterFontMask,
422         NSSmallCapsFontMask,
423         NSItalicFontMask,
424         NSCompressedFontMask,
425         NSCondensedFontMask,
426         NSExpandedFontMask,
427         NSNarrowFontMask,
428         NSBoldFontMask,
429         0 };
430     int i = 0;
431     NSFontTraitMask mask;
432     while ((mask = masks[i++])) {
433         if ((desiredTraits & mask) != 0) {
434             ASSERT((chosenTraits & mask) != 0);
435             ASSERT((candidateTraits & mask) != 0);
436             continue;
437         }
438         BOOL chosenHasUnwantedTrait = (chosenTraits & mask) != 0;
439         BOOL candidateHasUnwantedTrait = (candidateTraits & mask) != 0;
440         if (!candidateHasUnwantedTrait && chosenHasUnwantedTrait) {
441             return YES;
442         }
443         if (!chosenHasUnwantedTrait && candidateHasUnwantedTrait) {
444             return NO;
445         }
446     }
447     
448     int chosenWeightDelta = chosenWeight - desiredWeight;
449     int candidateWeightDelta = candidateWeight - desiredWeight;
450     
451     int chosenWeightDeltaMagnitude = ABS(chosenWeightDelta);
452     int candidateWeightDeltaMagnitude = ABS(candidateWeightDelta);
453     
454     // Smaller magnitude wins.
455     // If both have same magnitude, tie breaker is that the smaller weight wins.
456     // Otherwise, first font in the array wins (should almost never happen).
457     if (candidateWeightDeltaMagnitude < chosenWeightDeltaMagnitude) {
458         return YES;
459     }
460     if (candidateWeightDeltaMagnitude == chosenWeightDeltaMagnitude && candidateWeight < chosenWeight) {
461         return YES;
462     }
463     
464     return NO;
465 }
466
467 // Family name is somewhat of a misnomer here.  We first attempt to find an exact match
468 // comparing the desiredFamily to the PostScript name of the installed fonts.  If that fails
469 // we then do a search based on the family names of the installed fonts.
470 - (NSFont *)fontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits size:(float)size
471 {
472     NSFontManager *fontManager = [NSFontManager sharedFontManager];
473     NSFont *font= nil;
474     
475     LOG (FontSelection, "looking for %@ with traits %x\n", desiredFamily, desiredTraits);
476     
477     // Look for an exact match first.
478     NSEnumerator *availableFonts = [[fontManager availableFonts] objectEnumerator];
479     NSString *availableFont;
480     while ((availableFont = [availableFonts nextObject])) {
481         if ([desiredFamily caseInsensitiveCompare:availableFont] == NSOrderedSame) {
482             NSFont *nameMatchedFont = [NSFont fontWithName:availableFont size:size];
483             
484             // Special case Osaka-Mono.  According to <rdar://problem/3999467>, we need to 
485             // treat Osaka-Mono as fixed pitch.
486             if ([desiredFamily caseInsensitiveCompare:@"Osaka-Mono"] == NSOrderedSame && desiredTraits == 0) {
487                 LOG (FontSelection, "found exact match for Osaka-Mono\n");
488                 return nameMatchedFont;
489             }
490             
491             NSFontTraitMask traits = [fontManager traitsOfFont:nameMatchedFont];
492             
493             if ((traits & desiredTraits) == desiredTraits){
494                 font = [fontManager convertFont:nameMatchedFont toHaveTrait:desiredTraits];
495                 LOG (FontSelection, "returning exact match (%@)\n\n", [[[font fontDescriptor] fontAttributes] objectForKey: NSFontNameAttribute]);
496                 return font;
497             }
498             LOG (FontSelection, "found exact match, but not desired traits, available traits %x\n", traits);
499             break;
500         }
501     }
502     
503     // Do a simple case insensitive search for a matching font family.
504     // NSFontManager requires exact name matches.
505     // This addresses the problem of matching arial to Arial, etc., but perhaps not all the issues.
506     NSEnumerator *e = [[fontManager availableFontFamilies] objectEnumerator];
507     NSString *availableFamily;
508     while ((availableFamily = [e nextObject])) {
509         if ([desiredFamily caseInsensitiveCompare:availableFamily] == NSOrderedSame) {
510             break;
511         }
512     }
513     
514     // Found a family, now figure out what weight and traits to use.
515     BOOL choseFont = false;
516     int chosenWeight = 0;
517     NSFontTraitMask chosenTraits = 0;
518
519     NSArray *fonts = [fontManager availableMembersOfFontFamily:availableFamily];    
520     unsigned n = [fonts count];
521     unsigned i;
522     for (i = 0; i < n; i++) {
523         NSArray *fontInfo = [fonts objectAtIndex:i];
524         
525         // Array indices must be hard coded because of lame AppKit API.
526         int fontWeight = [[fontInfo objectAtIndex:2] intValue];
527         NSFontTraitMask fontTraits = [[fontInfo objectAtIndex:3] unsignedIntValue];
528         
529         BOOL newWinner;
530         
531         if (!choseFont) {
532             newWinner = acceptableChoice(desiredTraits, DESIRED_WEIGHT, fontTraits, fontWeight);
533         } else {
534             newWinner = betterChoice(desiredTraits, DESIRED_WEIGHT,
535                 chosenTraits, chosenWeight, fontTraits, fontWeight);
536         }
537
538         if (newWinner) {
539             choseFont = YES;
540             chosenWeight = fontWeight;
541             chosenTraits = fontTraits;
542             
543             if (chosenWeight == DESIRED_WEIGHT
544                     && (chosenTraits & IMPORTANT_FONT_TRAITS) == (desiredTraits & IMPORTANT_FONT_TRAITS)) {
545                 break;
546             }
547         }
548     }
549     
550     if (!choseFont) {
551         LOG (FontSelection, "nothing appropriate to return\n\n");
552         return nil;
553     }
554
555     font = [fontManager fontWithFamily:availableFamily traits:chosenTraits weight:chosenWeight size:size];
556     LOG (FontSelection, "returning font family %@ (%@) traits %x, fontID = %x\n\n", 
557             availableFamily, [[[font fontDescriptor] fontAttributes] objectForKey: NSFontNameAttribute], chosenTraits, (unsigned int)[font _atsFontID]);
558     
559     return font;
560 }
561
562 typedef struct {
563     NSString *family;
564     NSFontTraitMask traits;
565     float size;
566 } FontCacheKey;
567
568 static const void *FontCacheKeyCopy(CFAllocatorRef allocator, const void *value)
569 {
570     const FontCacheKey *key = (const FontCacheKey *)value;
571     FontCacheKey *result = malloc(sizeof(FontCacheKey));
572     result->family = [key->family copy];
573     result->traits = key->traits;
574     result->size = key->size;
575     return result;
576 }
577
578 static void FontCacheKeyFree(CFAllocatorRef allocator, const void *value)
579 {
580     const FontCacheKey *key = (const FontCacheKey *)value;
581     [key->family release];
582     free((void *)key);
583 }
584
585 static Boolean FontCacheKeyEqual(const void *value1, const void *value2)
586 {
587     const FontCacheKey *key1 = (const FontCacheKey *)value1;
588     const FontCacheKey *key2 = (const FontCacheKey *)value2;
589     return key1->size == key2->size && key1->traits == key2->traits && [key1->family isEqualToString:key2->family];
590 }
591
592 static CFHashCode FontCacheKeyHash(const void *value)
593 {
594     const FontCacheKey *key = (const FontCacheKey *)value;
595     return [key->family hash] ^ key->traits ^ (int)key->size;
596 }
597
598 static const void *FontCacheValueRetain(CFAllocatorRef allocator, const void *value)
599 {
600     if (value != NULL) {
601         CFRetain(value);
602     }
603     return value;
604 }
605
606 static void FontCacheValueRelease(CFAllocatorRef allocator, const void *value)
607 {
608     if (value != NULL) {
609         CFRelease(value);
610     }
611 }
612
613 - (NSFont *)cachedFontFromFamily:(NSString *)family traits:(NSFontTraitMask)traits size:(float)size
614 {
615     ASSERT(family);
616     
617     if (!fontCache) {
618         static const CFDictionaryKeyCallBacks fontCacheKeyCallBacks = { 0, FontCacheKeyCopy, FontCacheKeyFree, NULL, FontCacheKeyEqual, FontCacheKeyHash };
619         static const CFDictionaryValueCallBacks fontCacheValueCallBacks = { 0, FontCacheValueRetain, FontCacheValueRelease, NULL, NULL };
620         fontCache = CFDictionaryCreateMutable(NULL, 0, &fontCacheKeyCallBacks, &fontCacheValueCallBacks);
621     }
622
623     const FontCacheKey fontKey = { family, traits, size };
624     const void *value;
625     NSFont *font;
626     if (CFDictionaryGetValueIfPresent(fontCache, &fontKey, &value)) {
627         font = (NSFont *)value;
628     } else {
629         if ([family length] == 0) {
630             return nil;
631         }
632         font = [self fontWithFamily:family traits:traits size:size];
633         CFDictionaryAddValue(fontCache, &fontKey, font);
634     }
635     
636 #ifdef DEBUG_MISSING_FONT
637     static int unableToFindFontCount = 0;
638     if (font == nil) {
639         unableToFindFontCount++;
640         NSLog(@"unableToFindFontCount %@, traits 0x%08x, size %f, %d\n", family, traits, size, unableToFindFontCount);
641     }
642 #endif
643     
644     return font;
645 }
646
647 @end