2 * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
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>
37 #import <CoreGraphics/CoreGraphicsPrivate.h>
38 #import <CoreGraphics/CGFontLCDSupport.h>
39 #import <CoreGraphics/CGFontCache.h>
41 #import <mach-o/dyld.h>
43 #define IMPORTANT_FONT_TRAITS (0 \
45 | NSCompressedFontMask \
46 | NSCondensedFontMask \
47 | NSExpandedFontMask \
51 | NSSmallCapsFontMask \
54 #define DESIRED_WEIGHT 5
56 @interface NSFont (WebPrivate)
57 - (ATSUFontID)_atsFontID;
60 @interface NSFont (WebAppKitSecretAPI)
61 - (BOOL)_isFakeFixedPitch;
64 @implementation WebTextRendererFactory
66 - (BOOL)coalesceTextDrawing
68 return [viewStack objectAtIndex: [viewStack count]-1] == [NSView focusView] ? YES : NO;
71 - (void)startCoalesceTextDrawing
74 viewStack = [[NSMutableArray alloc] init];
76 viewBuffers = [[NSMutableDictionary alloc] init];
77 [viewStack addObject: [NSView focusView]];
80 - (void)endCoalesceTextDrawing
82 ASSERT([self coalesceTextDrawing]);
84 NSView *targetView = [viewStack objectAtIndex: [viewStack count]-1];
85 [viewStack removeLastObject];
86 NSValue *viewKey = [NSValue valueWithNonretainedObject: targetView];
87 NSMutableSet *glyphBuffers = [viewBuffers objectForKey:viewKey];
89 [glyphBuffers makeObjectsPerformSelector: @selector(drawInView:) withObject: targetView];
90 [glyphBuffers makeObjectsPerformSelector: @selector(reset)];
91 [viewBuffers removeObjectForKey: viewKey];
94 - (WebGlyphBuffer *)glyphBufferForFont: (NSFont *)font andColor: (NSColor *)color
96 ASSERT([self coalesceTextDrawing]);
98 NSMutableSet *glyphBuffers;
99 WebGlyphBuffer *glyphBuffer = nil;
100 NSValue *viewKey = [NSValue valueWithNonretainedObject: [NSView focusView]];
102 glyphBuffers = [viewBuffers objectForKey:viewKey];
103 if (glyphBuffers == nil){
104 glyphBuffers = [[NSMutableSet alloc] init];
105 [viewBuffers setObject: glyphBuffers forKey: viewKey];
106 [glyphBuffers release];
109 NSEnumerator *enumerator = [glyphBuffers objectEnumerator];
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])
117 if (glyphBuffer == nil){
118 glyphBuffer = [[WebGlyphBuffer alloc] initWithFont: font color: color];
119 [glyphBuffers addObject: glyphBuffer];
120 [glyphBuffer release];
127 getAppDefaultValue(CFStringRef key, int *v)
129 CFPropertyListRef value;
131 value = CFPreferencesCopyValue(key, kCFPreferencesCurrentApplication,
132 kCFPreferencesAnyUser,
133 kCFPreferencesAnyHost);
135 value = CFPreferencesCopyValue(key, kCFPreferencesCurrentApplication,
136 kCFPreferencesCurrentUser,
137 kCFPreferencesAnyHost);
142 if (CFGetTypeID(value) == CFNumberGetTypeID()) {
144 CFNumberGetValue(value, kCFNumberIntType, v);
145 } else if (CFGetTypeID(value) == CFStringGetTypeID()) {
147 *v = CFStringGetIntValue(value);
158 getUserDefaultValue(CFStringRef key, int *v)
160 CFPropertyListRef value;
162 value = CFPreferencesCopyValue(key, kCFPreferencesAnyApplication,
163 kCFPreferencesCurrentUser,
164 kCFPreferencesCurrentHost);
168 if (CFGetTypeID(value) == CFNumberGetTypeID()) {
170 CFNumberGetValue(value, kCFNumberIntType, v);
171 } else if (CFGetTypeID(value) == CFStringGetTypeID()) {
173 *v = CFStringGetIntValue(value);
183 static int getLCDScaleParameters(void)
188 key = CFSTR("AppleFontSmoothing");
189 if (!getAppDefaultValue(key, &mode)) {
190 if (!getUserDefaultValue(key, &mode))
195 case kCGFontSmoothingLCDLight:
196 case kCGFontSmoothingLCDMedium:
197 case kCGFontSmoothingLCDStrong:
205 static CFMutableDictionaryRef fontCache;
206 static CFMutableDictionaryRef fixedPitchFonts;
210 [cacheForScreen release];
211 [cacheForPrinter release];
213 cacheForScreen = [[NSMutableDictionary alloc] init];
214 cacheForPrinter = [[NSMutableDictionary alloc] init];
217 CFRelease(fontCache);
220 if (fixedPitchFonts) {
221 CFRelease (fixedPitchFonts);
229 fontsChanged( ATSFontNotificationInfoRef info, void *_factory)
231 WebTextRendererFactory *factory = (WebTextRendererFactory *)_factory;
233 LOG (FontCache, "clearing font caches");
237 [factory clearCaches];
240 #define MINIMUM_GLYPH_CACHE_SIZE 1536 * 1024
242 + (void)createSharedFactory
244 if (![self sharedFactory]) {
245 [[[self alloc] init] release];
247 #if !defined(BUILDING_ON_PANTHER)
248 // Turn on local font cache, in addition to the system cache.
250 CGFontSetShouldUseMulticache(true);
253 CGFontCache *fontCache;
254 fontCache = CGFontCacheGetLocalCache();
255 CGFontCacheSetShouldAutoExpire (fontCache, false);
258 if (WebSystemMainMemory() > 128 * 1024 * 1024)
259 s = MINIMUM_GLYPH_CACHE_SIZE*getLCDScaleParameters();
261 s = MINIMUM_GLYPH_CACHE_SIZE;
263 LOG (CacheSizes, "Glyph cache size set to %d bytes.", s);
265 CGFontCacheSetMaxSize (fontCache, s);
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 );
271 ASSERT([[self sharedFactory] isKindOfClass:self]);
274 + (WebTextRendererFactory *)sharedFactory;
276 return (WebTextRendererFactory *)[super sharedFactory];
279 - (BOOL)isFontFixedPitch: (NSFont *)font
283 if (!fixedPitchFonts) {
284 fixedPitchFonts = CFDictionaryCreateMutable (0, 0, &kCFTypeDictionaryKeyCallBacks, 0);
287 CFBooleanRef val = CFDictionaryGetValue (fixedPitchFonts, font);
289 ret = (val == kCFBooleanTrue);
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.
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);
303 CFDictionarySetValue (fixedPitchFonts, font, kCFBooleanFalse);
314 cacheForScreen = [[NSMutableDictionary alloc] init];
315 cacheForPrinter = [[NSMutableDictionary alloc] init];
322 [cacheForScreen release];
323 [cacheForPrinter release];
324 [viewBuffers release];
330 - (WebTextRenderer *)rendererWithFont:(NSFont *)font usingPrinterFont:(BOOL)usingPrinterFont
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];
342 - (NSFont *)fallbackFontWithTraits:(NSFontTraitMask)traits size:(float)size
344 NSFont *font = [self cachedFontFromFamily:@"Helvetica" traits:traits size:size];
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];
359 - (NSFont *)fontWithFamilies:(NSString **)families traits:(NSFontTraitMask)traits size:(float)size
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];
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" };
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.
381 while (families && families[i] != 0 && font == nil) {
382 family = families[i++];
383 if ([family length] != 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];
394 // Still nothing found, use the final fallback.
396 font = [self fallbackFontWithTraits:traits size:size];
403 static BOOL acceptableChoice(NSFontTraitMask desiredTraits, int desiredWeight,
404 NSFontTraitMask candidateTraits, int candidateWeight)
406 return (candidateTraits & desiredTraits) == desiredTraits;
409 static BOOL betterChoice(NSFontTraitMask desiredTraits, int desiredWeight,
410 NSFontTraitMask chosenTraits, int chosenWeight,
411 NSFontTraitMask candidateTraits, int candidateWeight)
413 if (!acceptableChoice(desiredTraits, desiredWeight, candidateTraits, candidateWeight)) {
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[] = {
424 NSCompressedFontMask,
431 NSFontTraitMask mask;
432 while ((mask = masks[i++])) {
433 if ((desiredTraits & mask) != 0) {
434 ASSERT((chosenTraits & mask) != 0);
435 ASSERT((candidateTraits & mask) != 0);
438 BOOL chosenHasUnwantedTrait = (chosenTraits & mask) != 0;
439 BOOL candidateHasUnwantedTrait = (candidateTraits & mask) != 0;
440 if (!candidateHasUnwantedTrait && chosenHasUnwantedTrait) {
443 if (!chosenHasUnwantedTrait && candidateHasUnwantedTrait) {
448 int chosenWeightDelta = chosenWeight - desiredWeight;
449 int candidateWeightDelta = candidateWeight - desiredWeight;
451 int chosenWeightDeltaMagnitude = ABS(chosenWeightDelta);
452 int candidateWeightDeltaMagnitude = ABS(candidateWeightDelta);
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) {
460 if (candidateWeightDeltaMagnitude == chosenWeightDeltaMagnitude && candidateWeight < chosenWeight) {
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
472 NSFontManager *fontManager = [NSFontManager sharedFontManager];
475 LOG (FontSelection, "looking for %@ with traits %x\n", desiredFamily, desiredTraits);
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];
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;
491 NSFontTraitMask traits = [fontManager traitsOfFont:nameMatchedFont];
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]);
498 LOG (FontSelection, "found exact match, but not desired traits, available traits %x\n", traits);
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) {
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;
519 NSArray *fonts = [fontManager availableMembersOfFontFamily:availableFamily];
520 unsigned n = [fonts count];
522 for (i = 0; i < n; i++) {
523 NSArray *fontInfo = [fonts objectAtIndex:i];
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];
532 newWinner = acceptableChoice(desiredTraits, DESIRED_WEIGHT, fontTraits, fontWeight);
534 newWinner = betterChoice(desiredTraits, DESIRED_WEIGHT,
535 chosenTraits, chosenWeight, fontTraits, fontWeight);
540 chosenWeight = fontWeight;
541 chosenTraits = fontTraits;
543 if (chosenWeight == DESIRED_WEIGHT
544 && (chosenTraits & IMPORTANT_FONT_TRAITS) == (desiredTraits & IMPORTANT_FONT_TRAITS)) {
551 LOG (FontSelection, "nothing appropriate to return\n\n");
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]);
564 NSFontTraitMask traits;
568 static const void *FontCacheKeyCopy(CFAllocatorRef allocator, const void *value)
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;
578 static void FontCacheKeyFree(CFAllocatorRef allocator, const void *value)
580 const FontCacheKey *key = (const FontCacheKey *)value;
581 [key->family release];
585 static Boolean FontCacheKeyEqual(const void *value1, const void *value2)
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];
592 static CFHashCode FontCacheKeyHash(const void *value)
594 const FontCacheKey *key = (const FontCacheKey *)value;
595 return [key->family hash] ^ key->traits ^ (int)key->size;
598 static const void *FontCacheValueRetain(CFAllocatorRef allocator, const void *value)
606 static void FontCacheValueRelease(CFAllocatorRef allocator, const void *value)
613 - (NSFont *)cachedFontFromFamily:(NSString *)family traits:(NSFontTraitMask)traits size:(float)size
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);
623 const FontCacheKey fontKey = { family, traits, size };
626 if (CFDictionaryGetValueIfPresent(fontCache, &fontKey, &value)) {
627 font = (NSFont *)value;
629 if ([family length] == 0) {
632 font = [self fontWithFamily:family traits:traits size:size];
633 CFDictionaryAddValue(fontCache, &fontKey, font);
636 #ifdef DEBUG_MISSING_FONT
637 static int unableToFindFontCount = 0;
639 unableToFindFontCount++;
640 NSLog(@"unableToFindFontCount %@, traits 0x%08x, size %f, %d\n", family, traits, size, unableToFindFontCount);