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