2 WebTextRendererFactory.m
3 Copyright 2002, Apple, Inc. All rights reserved.
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>
14 #import <CoreGraphics/CoreGraphicsPrivate.h>
15 #import <CoreGraphics/CGFontLCDSupport.h>
16 #import <CoreGraphics/CGFontCache.h>
18 #import <mach-o/dyld.h>
20 #define IMPORTANT_FONT_TRAITS (0 \
22 | NSCompressedFontMask \
23 | NSCondensedFontMask \
24 | NSExpandedFontMask \
28 | NSSmallCapsFontMask \
31 #define DESIRED_WEIGHT 5
33 @interface NSFont (WebPrivate)
34 - (ATSUFontID)_atsFontID;
37 @interface NSFont (WebAppKitSecretAPI)
38 - (BOOL)_isFakeFixedPitch;
41 @implementation NSFont (WebPrivateExtensions)
42 - (BOOL)_web_isFakeFixedPitch
44 // Special case Osaka-Mono. According to <rdar://problem/3999467>, we should treat Osaka-Mono
46 if ([[self fontName] caseInsensitiveCompare:@"Osaka-Mono"] == NSOrderedSame)
53 @implementation WebTextRendererFactory
55 - (BOOL)coalesceTextDrawing
57 return [viewStack objectAtIndex: [viewStack count]-1] == [NSView focusView] ? YES : NO;
60 - (void)startCoalesceTextDrawing
63 viewStack = [[NSMutableArray alloc] init];
65 viewBuffers = [[NSMutableDictionary alloc] init];
66 [viewStack addObject: [NSView focusView]];
69 - (void)endCoalesceTextDrawing
71 ASSERT([self coalesceTextDrawing]);
73 NSView *targetView = [viewStack objectAtIndex: [viewStack count]-1];
74 [viewStack removeLastObject];
75 NSValue *viewKey = [NSValue valueWithNonretainedObject: targetView];
76 NSMutableSet *glyphBuffers = [viewBuffers objectForKey:viewKey];
78 [glyphBuffers makeObjectsPerformSelector: @selector(drawInView:) withObject: targetView];
79 [glyphBuffers makeObjectsPerformSelector: @selector(reset)];
80 [viewBuffers removeObjectForKey: viewKey];
83 - (WebGlyphBuffer *)glyphBufferForFont: (NSFont *)font andColor: (NSColor *)color
85 ASSERT([self coalesceTextDrawing]);
87 NSMutableSet *glyphBuffers;
88 WebGlyphBuffer *glyphBuffer = nil;
89 NSValue *viewKey = [NSValue valueWithNonretainedObject: [NSView focusView]];
91 glyphBuffers = [viewBuffers objectForKey:viewKey];
92 if (glyphBuffers == nil){
93 glyphBuffers = [[NSMutableSet alloc] init];
94 [viewBuffers setObject: glyphBuffers forKey: viewKey];
95 [glyphBuffers release];
98 NSEnumerator *enumerator = [glyphBuffers objectEnumerator];
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])
106 if (glyphBuffer == nil){
107 glyphBuffer = [[WebGlyphBuffer alloc] initWithFont: font color: color];
108 [glyphBuffers addObject: glyphBuffer];
109 [glyphBuffer release];
116 getAppDefaultValue(CFStringRef key, int *v)
118 CFPropertyListRef value;
120 value = CFPreferencesCopyValue(key, kCFPreferencesCurrentApplication,
121 kCFPreferencesAnyUser,
122 kCFPreferencesAnyHost);
124 value = CFPreferencesCopyValue(key, kCFPreferencesCurrentApplication,
125 kCFPreferencesCurrentUser,
126 kCFPreferencesAnyHost);
131 if (CFGetTypeID(value) == CFNumberGetTypeID()) {
133 CFNumberGetValue(value, kCFNumberIntType, v);
134 } else if (CFGetTypeID(value) == CFStringGetTypeID()) {
136 *v = CFStringGetIntValue(value);
147 getUserDefaultValue(CFStringRef key, int *v)
149 CFPropertyListRef value;
151 value = CFPreferencesCopyValue(key, kCFPreferencesAnyApplication,
152 kCFPreferencesCurrentUser,
153 kCFPreferencesCurrentHost);
157 if (CFGetTypeID(value) == CFNumberGetTypeID()) {
159 CFNumberGetValue(value, kCFNumberIntType, v);
160 } else if (CFGetTypeID(value) == CFStringGetTypeID()) {
162 *v = CFStringGetIntValue(value);
172 static int getLCDScaleParameters(void)
177 key = CFSTR("AppleFontSmoothing");
178 if (!getAppDefaultValue(key, &mode)) {
179 if (!getUserDefaultValue(key, &mode))
184 case kCGFontSmoothingLCDLight:
185 case kCGFontSmoothingLCDMedium:
186 case kCGFontSmoothingLCDStrong:
194 static CFMutableDictionaryRef fontCache = NULL;
198 [cacheForScreen release];
199 [cacheForPrinter release];
201 cacheForScreen = [[NSMutableDictionary alloc] init];
202 cacheForPrinter = [[NSMutableDictionary alloc] init];
205 CFRelease(fontCache);
212 fontsChanged( ATSFontNotificationInfoRef info, void *_factory)
214 WebTextRendererFactory *factory = (WebTextRendererFactory *)_factory;
216 LOG (FontCache, "clearing font caches");
220 [factory clearCaches];
223 #define MINIMUM_GLYPH_CACHE_SIZE 1536 * 1024
225 + (void)createSharedFactory
227 if (![self sharedFactory]) {
228 [[[self alloc] init] release];
230 #if !defined(BUILDING_ON_PANTHER)
231 // Turn on local font cache, in addition to the system cache.
233 CGFontSetShouldUseMulticache(true);
236 CGFontCache *fontCache;
237 fontCache = CGFontCacheGetLocalCache();
238 CGFontCacheSetShouldAutoExpire (fontCache, false);
241 if (WebSystemMainMemory() > 128 * 1024 * 1024)
242 s = MINIMUM_GLYPH_CACHE_SIZE*getLCDScaleParameters();
244 s = MINIMUM_GLYPH_CACHE_SIZE;
246 LOG (CacheSizes, "Glyph cache size set to %d bytes.", s);
248 CGFontCacheSetMaxSize (fontCache, s);
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 );
254 ASSERT([[self sharedFactory] isKindOfClass:self]);
257 + (WebTextRendererFactory *)sharedFactory;
259 return (WebTextRendererFactory *)[super sharedFactory];
262 - (BOOL)isFontFixedPitch: (NSFont *)font
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];
274 cacheForScreen = [[NSMutableDictionary alloc] init];
275 cacheForPrinter = [[NSMutableDictionary alloc] init];
282 [cacheForScreen release];
283 [cacheForPrinter release];
284 [viewBuffers release];
290 - (WebTextRenderer *)rendererWithFont:(NSFont *)font usingPrinterFont:(BOOL)usingPrinterFont
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];
302 - (NSFont *)fallbackFontWithTraits:(NSFontTraitMask)traits size:(float)size
304 NSFont *font = [self cachedFontFromFamily:@"Helvetica" traits:traits size:size];
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];
319 - (NSFont *)fontWithFamilies:(NSString **)families traits:(NSFontTraitMask)traits size:(float)size
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];
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" };
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.
341 while (families && families[i] != 0 && font == nil) {
342 family = families[i++];
343 if ([family length] != 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];
354 // Still nothing found, use the final fallback.
356 font = [self fallbackFontWithTraits:traits size:size];
363 static BOOL acceptableChoice(NSFontTraitMask desiredTraits, int desiredWeight,
364 NSFontTraitMask candidateTraits, int candidateWeight)
366 return (candidateTraits & desiredTraits) == desiredTraits;
369 static BOOL betterChoice(NSFontTraitMask desiredTraits, int desiredWeight,
370 NSFontTraitMask chosenTraits, int chosenWeight,
371 NSFontTraitMask candidateTraits, int candidateWeight)
373 if (!acceptableChoice(desiredTraits, desiredWeight, candidateTraits, candidateWeight)) {
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[] = {
384 NSCompressedFontMask,
391 NSFontTraitMask mask;
392 while ((mask = masks[i++])) {
393 if ((desiredTraits & mask) != 0) {
394 ASSERT((chosenTraits & mask) != 0);
395 ASSERT((candidateTraits & mask) != 0);
398 BOOL chosenHasUnwantedTrait = (chosenTraits & mask) != 0;
399 BOOL candidateHasUnwantedTrait = (candidateTraits & mask) != 0;
400 if (!candidateHasUnwantedTrait && chosenHasUnwantedTrait) {
403 if (!chosenHasUnwantedTrait && candidateHasUnwantedTrait) {
408 int chosenWeightDelta = chosenWeight - desiredWeight;
409 int candidateWeightDelta = candidateWeight - desiredWeight;
411 int chosenWeightDeltaMagnitude = ABS(chosenWeightDelta);
412 int candidateWeightDeltaMagnitude = ABS(candidateWeightDelta);
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) {
420 if (candidateWeightDeltaMagnitude == chosenWeightDeltaMagnitude && candidateWeight < chosenWeight) {
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
432 NSFontManager *fontManager = [NSFontManager sharedFontManager];
435 LOG (FontSelection, "looking for %@ with traits %x\n", desiredFamily, desiredTraits);
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];
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;
451 NSFontTraitMask traits = [fontManager traitsOfFont:nameMatchedFont];
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]);
458 LOG (FontSelection, "found exact match, but not desired traits, available traits %x\n", traits);
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) {
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;
479 NSArray *fonts = [fontManager availableMembersOfFontFamily:availableFamily];
480 unsigned n = [fonts count];
482 for (i = 0; i < n; i++) {
483 NSArray *fontInfo = [fonts objectAtIndex:i];
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];
492 newWinner = acceptableChoice(desiredTraits, DESIRED_WEIGHT, fontTraits, fontWeight);
494 newWinner = betterChoice(desiredTraits, DESIRED_WEIGHT,
495 chosenTraits, chosenWeight, fontTraits, fontWeight);
500 chosenWeight = fontWeight;
501 chosenTraits = fontTraits;
503 if (chosenWeight == DESIRED_WEIGHT
504 && (chosenTraits & IMPORTANT_FONT_TRAITS) == (desiredTraits & IMPORTANT_FONT_TRAITS)) {
511 LOG (FontSelection, "nothing appropriate to return\n\n");
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]);
524 NSFontTraitMask traits;
528 static const void *FontCacheKeyCopy(CFAllocatorRef allocator, const void *value)
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;
538 static void FontCacheKeyFree(CFAllocatorRef allocator, const void *value)
540 const FontCacheKey *key = (const FontCacheKey *)value;
541 [key->family release];
545 static Boolean FontCacheKeyEqual(const void *value1, const void *value2)
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];
552 static CFHashCode FontCacheKeyHash(const void *value)
554 const FontCacheKey *key = (const FontCacheKey *)value;
555 return [key->family hash] ^ key->traits ^ (int)key->size;
558 static const void *FontCacheValueRetain(CFAllocatorRef allocator, const void *value)
566 static void FontCacheValueRelease(CFAllocatorRef allocator, const void *value)
573 - (NSFont *)cachedFontFromFamily:(NSString *)family traits:(NSFontTraitMask)traits size:(float)size
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);
583 const FontCacheKey fontKey = { family, traits, size };
586 if (CFDictionaryGetValueIfPresent(fontCache, &fontKey, &value)) {
587 font = (NSFont *)value;
589 if ([family length] == 0) {
592 font = [self fontWithFamily:family traits:traits size:size];
593 CFDictionaryAddValue(fontCache, &fontKey, font);
596 #ifdef DEBUG_MISSING_FONT
597 static int unableToFindFontCount = 0;
599 unableToFindFontCount++;
600 NSLog(@"unableToFindFontCount %@, traits 0x%08x, size %f, %d\n", family, traits, size, unableToFindFontCount);