/* WebTextRendererFactory.m Copyright 2002, Apple, Inc. All rights reserved. */ #import #import #import #import #import #import #import #import #import #import #import #define IMPORTANT_FONT_TRAITS (0 \ | NSBoldFontMask \ | NSCompressedFontMask \ | NSCondensedFontMask \ | NSExpandedFontMask \ | NSItalicFontMask \ | NSNarrowFontMask \ | NSPosterFontMask \ | NSSmallCapsFontMask \ ) #define DESIRED_WEIGHT 5 @interface NSFont (WebPrivate) - (ATSUFontID)_atsFontID; @end @interface NSFont (WebAppKitSecretAPI) - (BOOL)_isFakeFixedPitch; @end @implementation WebTextRendererFactory - (BOOL)coalesceTextDrawing { return [viewStack objectAtIndex: [viewStack count]-1] == [NSView focusView] ? YES : NO; } - (void)startCoalesceTextDrawing { if (!viewStack) viewStack = [[NSMutableArray alloc] init]; if (!viewBuffers) viewBuffers = [[NSMutableDictionary alloc] init]; [viewStack addObject: [NSView focusView]]; } - (void)endCoalesceTextDrawing { ASSERT([self coalesceTextDrawing]); NSView *targetView = [viewStack objectAtIndex: [viewStack count]-1]; [viewStack removeLastObject]; NSValue *viewKey = [NSValue valueWithNonretainedObject: targetView]; NSMutableSet *glyphBuffers = [viewBuffers objectForKey:viewKey]; [glyphBuffers makeObjectsPerformSelector: @selector(drawInView:) withObject: targetView]; [glyphBuffers makeObjectsPerformSelector: @selector(reset)]; [viewBuffers removeObjectForKey: viewKey]; } - (WebGlyphBuffer *)glyphBufferForFont: (NSFont *)font andColor: (NSColor *)color { ASSERT([self coalesceTextDrawing]); NSMutableSet *glyphBuffers; WebGlyphBuffer *glyphBuffer = nil; NSValue *viewKey = [NSValue valueWithNonretainedObject: [NSView focusView]]; glyphBuffers = [viewBuffers objectForKey:viewKey]; if (glyphBuffers == nil){ glyphBuffers = [[NSMutableSet alloc] init]; [viewBuffers setObject: glyphBuffers forKey: viewKey]; [glyphBuffers release]; } NSEnumerator *enumerator = [glyphBuffers objectEnumerator]; id value; // Could use a dictionary w/ font/color key for faster lookup. while ((value = [enumerator nextObject])) { if ([value font] == font && [[value color] isEqual: color]) glyphBuffer = value; } if (glyphBuffer == nil){ glyphBuffer = [[WebGlyphBuffer alloc] initWithFont: font color: color]; [glyphBuffers addObject: glyphBuffer]; [glyphBuffer release]; } return glyphBuffer; } static bool getAppDefaultValue(CFStringRef key, int *v) { CFPropertyListRef value; value = CFPreferencesCopyValue(key, kCFPreferencesCurrentApplication, kCFPreferencesAnyUser, kCFPreferencesAnyHost); if (value == NULL) { value = CFPreferencesCopyValue(key, kCFPreferencesCurrentApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost); if (value == NULL) return false; } if (CFGetTypeID(value) == CFNumberGetTypeID()) { if (v != NULL) CFNumberGetValue(value, kCFNumberIntType, v); } else if (CFGetTypeID(value) == CFStringGetTypeID()) { if (v != NULL) *v = CFStringGetIntValue(value); } else { CFRelease(value); return false; } CFRelease(value); return true; } static bool getUserDefaultValue(CFStringRef key, int *v) { CFPropertyListRef value; value = CFPreferencesCopyValue(key, kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost); if (value == NULL) return false; if (CFGetTypeID(value) == CFNumberGetTypeID()) { if (v != NULL) CFNumberGetValue(value, kCFNumberIntType, v); } else if (CFGetTypeID(value) == CFStringGetTypeID()) { if (v != NULL) *v = CFStringGetIntValue(value); } else { CFRelease(value); return false; } CFRelease(value); return true; } static int getLCDScaleParameters(void) { int mode; CFStringRef key; key = CFSTR("AppleFontSmoothing"); if (!getAppDefaultValue(key, &mode)) { if (!getUserDefaultValue(key, &mode)) return 1; } switch (mode) { case kCGFontSmoothingLCDLight: case kCGFontSmoothingLCDMedium: case kCGFontSmoothingLCDStrong: return 4; default: return 1; } } static CFMutableDictionaryRef fontCache; static CFMutableDictionaryRef fixedPitchFonts; - (void)clearCaches { [cacheForScreen release]; [cacheForPrinter release]; cacheForScreen = [[NSMutableDictionary alloc] init]; cacheForPrinter = [[NSMutableDictionary alloc] init]; if (fontCache) CFRelease(fontCache); fontCache = NULL; if (fixedPitchFonts) { CFRelease (fixedPitchFonts); fixedPitchFonts = 0; } [super clearCaches]; } static void fontsChanged( ATSFontNotificationInfoRef info, void *_factory) { WebTextRendererFactory *factory = (WebTextRendererFactory *)_factory; LOG (FontCache, "clearing font caches"); ASSERT (factory); [factory clearCaches]; } #define MINIMUM_GLYPH_CACHE_SIZE 1536 * 1024 + (void)createSharedFactory { if (![self sharedFactory]) { [[[self alloc] init] release]; #if !defined(BUILDING_ON_PANTHER) // Turn on local font cache, in addition to the system cache. // See 3835148 CGFontSetShouldUseMulticache(true); #endif CGFontCache *fontCache; fontCache = CGFontCacheGetLocalCache(); CGFontCacheSetShouldAutoExpire (fontCache, false); size_t s; if (WebSystemMainMemory() > 128 * 1024 * 1024) s = MINIMUM_GLYPH_CACHE_SIZE*getLCDScaleParameters(); else s = MINIMUM_GLYPH_CACHE_SIZE; #ifndef NDEBUG LOG (CacheSizes, "Glyph cache size set to %d bytes.", s); #endif CGFontCacheSetMaxSize (fontCache, s); // Ignore errors returned from ATSFontNotificationSubscribe. If we can't subscribe then we // won't be told about changes to fonts. ATSFontNotificationSubscribe( fontsChanged, kATSFontNotifyOptionDefault, (void *)[super sharedFactory], nil ); } ASSERT([[self sharedFactory] isKindOfClass:self]); } + (WebTextRendererFactory *)sharedFactory; { return (WebTextRendererFactory *)[super sharedFactory]; } - (BOOL)isFontFixedPitch: (NSFont *)font { BOOL ret = NO; if (!fixedPitchFonts) { fixedPitchFonts = CFDictionaryCreateMutable (0, 0, &kCFTypeDictionaryKeyCallBacks, 0); } CFBooleanRef val = CFDictionaryGetValue (fixedPitchFonts, font); if (val) { ret = (val == kCFBooleanTrue); } else { // Special case Osaka-Mono. According to , we should treat Osaka-Mono // as fixed pitch. Note that the AppKit does not report MS-PGothic as fixed pitch. // Special case MS PGothic. According to , we need to // treat Osaka-Mono as fixed pitch. if ([desiredFamily caseInsensitiveCompare:@"Osaka-Mono"] == NSOrderedSame && desiredTraits == 0) { LOG (FontSelection, "found exact match for Osaka-Mono\n"); return nameMatchedFont; } NSFontTraitMask traits = [fontManager traitsOfFont:nameMatchedFont]; if ((traits & desiredTraits) == desiredTraits){ font = [fontManager convertFont:nameMatchedFont toHaveTrait:desiredTraits]; LOG (FontSelection, "returning exact match (%@)\n\n", [[[font fontDescriptor] fontAttributes] objectForKey: NSFontNameAttribute]); return font; } LOG (FontSelection, "found exact match, but not desired traits, available traits %x\n", traits); break; } } // Do a simple case insensitive search for a matching font family. // NSFontManager requires exact name matches. // This addresses the problem of matching arial to Arial, etc., but perhaps not all the issues. NSEnumerator *e = [[fontManager availableFontFamilies] objectEnumerator]; NSString *availableFamily; while ((availableFamily = [e nextObject])) { if ([desiredFamily caseInsensitiveCompare:availableFamily] == NSOrderedSame) { break; } } // Found a family, now figure out what weight and traits to use. BOOL choseFont = false; int chosenWeight = 0; NSFontTraitMask chosenTraits = 0; NSArray *fonts = [fontManager availableMembersOfFontFamily:availableFamily]; unsigned n = [fonts count]; unsigned i; for (i = 0; i < n; i++) { NSArray *fontInfo = [fonts objectAtIndex:i]; // Array indices must be hard coded because of lame AppKit API. int fontWeight = [[fontInfo objectAtIndex:2] intValue]; NSFontTraitMask fontTraits = [[fontInfo objectAtIndex:3] unsignedIntValue]; BOOL newWinner; if (!choseFont) { newWinner = acceptableChoice(desiredTraits, DESIRED_WEIGHT, fontTraits, fontWeight); } else { newWinner = betterChoice(desiredTraits, DESIRED_WEIGHT, chosenTraits, chosenWeight, fontTraits, fontWeight); } if (newWinner) { choseFont = YES; chosenWeight = fontWeight; chosenTraits = fontTraits; if (chosenWeight == DESIRED_WEIGHT && (chosenTraits & IMPORTANT_FONT_TRAITS) == (desiredTraits & IMPORTANT_FONT_TRAITS)) { break; } } } if (!choseFont) { LOG (FontSelection, "nothing appropriate to return\n\n"); return nil; } font = [fontManager fontWithFamily:availableFamily traits:chosenTraits weight:chosenWeight size:size]; LOG (FontSelection, "returning font family %@ (%@) traits %x, fontID = %x\n\n", availableFamily, [[[font fontDescriptor] fontAttributes] objectForKey: NSFontNameAttribute], chosenTraits, (unsigned int)[font _atsFontID]); return font; } typedef struct { NSString *family; NSFontTraitMask traits; float size; } FontCacheKey; static const void *FontCacheKeyCopy(CFAllocatorRef allocator, const void *value) { const FontCacheKey *key = (const FontCacheKey *)value; FontCacheKey *result = malloc(sizeof(FontCacheKey)); result->family = [key->family copy]; result->traits = key->traits; result->size = key->size; return result; } static void FontCacheKeyFree(CFAllocatorRef allocator, const void *value) { const FontCacheKey *key = (const FontCacheKey *)value; [key->family release]; free((void *)key); } static Boolean FontCacheKeyEqual(const void *value1, const void *value2) { const FontCacheKey *key1 = (const FontCacheKey *)value1; const FontCacheKey *key2 = (const FontCacheKey *)value2; return key1->size == key2->size && key1->traits == key2->traits && [key1->family isEqualToString:key2->family]; } static CFHashCode FontCacheKeyHash(const void *value) { const FontCacheKey *key = (const FontCacheKey *)value; return [key->family hash] ^ key->traits ^ (int)key->size; } static const void *FontCacheValueRetain(CFAllocatorRef allocator, const void *value) { if (value != NULL) { CFRetain(value); } return value; } static void FontCacheValueRelease(CFAllocatorRef allocator, const void *value) { if (value != NULL) { CFRelease(value); } } - (NSFont *)cachedFontFromFamily:(NSString *)family traits:(NSFontTraitMask)traits size:(float)size { ASSERT(family); if (!fontCache) { static const CFDictionaryKeyCallBacks fontCacheKeyCallBacks = { 0, FontCacheKeyCopy, FontCacheKeyFree, NULL, FontCacheKeyEqual, FontCacheKeyHash }; static const CFDictionaryValueCallBacks fontCacheValueCallBacks = { 0, FontCacheValueRetain, FontCacheValueRelease, NULL, NULL }; fontCache = CFDictionaryCreateMutable(NULL, 0, &fontCacheKeyCallBacks, &fontCacheValueCallBacks); } const FontCacheKey fontKey = { family, traits, size }; const void *value; NSFont *font; if (CFDictionaryGetValueIfPresent(fontCache, &fontKey, &value)) { font = (NSFont *)value; } else { if ([family length] == 0) { return nil; } font = [self fontWithFamily:family traits:traits size:size]; CFDictionaryAddValue(fontCache, &fontKey, font); } #ifdef DEBUG_MISSING_FONT static int unableToFindFontCount = 0; if (font == nil) { unableToFindFontCount++; NSLog(@"unableToFindFontCount %@, traits 0x%08x, size %f, %d\n", family, traits, size, unableToFindFontCount); } #endif return font; } @end