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