source: webkit/trunk/WebKit/WebCoreSupport.subproj/WebTextRendererFactory.m @ 8626

Last change on this file since 8626 was 8626, checked in by rjw, 19 years ago

WebCore:

Fixed <rdar://problem/3999467> when Osaka-Mono is specified as fixed width font, Osaka used instead

Fixed w/o introducing a performance regression. Add early
check for Osaka-Mono to avoid expensive call into WebKit.

Reviewed by Vicki.

  • kwq/KWQFont.mm: (QFont::isFixedPitch):

WebKit:

Fixed <rdar://problem/3999467> when Osaka-Mono is specified as fixed width font, Osaka used instead

Fixed w/o introducing a performance regression.

Reviewed by Vicki (and earlier by Dave Harrison).

  • WebCoreSupport.subproj/WebTextRenderer.m: (-[WebTextRenderer _computeWidthForSpace]): (widthForNextCharacter):
  • WebCoreSupport.subproj/WebTextRendererFactory.h:
  • WebCoreSupport.subproj/WebTextRendererFactory.m: (-[NSFont _web_isFakeFixedPitch]): (-[WebTextRendererFactory isFontFixedPitch:]): (-[WebTextRendererFactory fontWithFamily:traits:size:]):
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.1 KB
Line 
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
115static bool
116getAppDefaultValue(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
146static bool
147getUserDefaultValue(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
172static 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
194static 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
211static void
212fontsChanged( 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    return [font isFixedPitch] || [font _isFakeFixedPitch] || [font _web_isFakeFixedPitch];
265}
266
267- init
268{
269    [super init];
270   
271    cacheForScreen = [[NSMutableDictionary alloc] init];
272    cacheForPrinter = [[NSMutableDictionary alloc] init];
273   
274    return self;
275}
276
277- (void)dealloc
278{
279    [cacheForScreen release];
280    [cacheForPrinter release];
281    [viewBuffers release];
282    [viewStack release];
283   
284    [super dealloc];
285}
286
287- (WebTextRenderer *)rendererWithFont:(NSFont *)font usingPrinterFont:(BOOL)usingPrinterFont
288{
289    NSMutableDictionary *cache = usingPrinterFont ? cacheForPrinter : cacheForScreen;
290    WebTextRenderer *renderer = [cache objectForKey:font];
291    if (renderer == nil) {
292        renderer = [[WebTextRenderer alloc] initWithFont:font usingPrinterFont:usingPrinterFont];
293        [cache setObject:renderer forKey:font];
294        [renderer release];
295    }
296    return renderer;
297}
298
299- (NSFont *)fallbackFontWithTraits:(NSFontTraitMask)traits size:(float)size
300{
301    NSFont *font = [self cachedFontFromFamily:@"Helvetica" traits:traits size:size];
302    if (font == nil) {
303        // The Helvetica fallback will almost always work, since that's a basic
304        // font that we ship with all systems. But in the highly unusual case where
305        // the user removed Helvetica, we fall back on Lucida Grande because that's
306        // guaranteed to be there, according to Nathan Taylor. This is good enough
307        // to avoid a crash, at least. To reduce the possibility of failure even further,
308        // we don't even bother with traits.
309        font = [self cachedFontFromFamily:@"Lucida Grande" traits:0 size:size];
310    }
311    return font;
312}
313
314       
315
316- (NSFont *)fontWithFamilies:(NSString **)families traits:(NSFontTraitMask)traits size:(float)size
317{
318    NSFont *font = nil;
319    NSString *family;
320    int i = 0;
321   
322    while (families && families[i] != 0 && font == nil){
323        family = families[i++];
324        if ([family length] != 0)
325            font = [self cachedFontFromFamily: family traits:traits size:size];
326    }
327    if (font == nil) {
328        // We didn't find a font.  Use a fallback font.
329        static int matchCount = 3;
330        static NSString *matchWords[] = { @"Arabic", @"Pashto", @"Urdu" };
331        static NSString *matchFamilies[] = { @"Geeza Pro", @"Geeza Pro", @"Geeza Pro" };
332       
333        // First we'll attempt to find an appropriate font using a match based on
334        // the presence of keywords in the the requested names.  For example, we'll
335        // match any name that contains "Arabic" to Geeza Pro.
336        int j;
337        i = 0;
338        while (families && families[i] != 0 && font == nil) {
339            family = families[i++];
340            if ([family length] != 0) {
341                j = 0;
342                while (j < matchCount && font == nil) {
343                    if ([family rangeOfString:matchWords[j] options:NSCaseInsensitiveSearch].location != NSNotFound) {
344                        font = [self cachedFontFromFamily:matchFamilies[j] traits:traits size:size];
345                    }
346                    j++;
347                }
348            }
349        }
350       
351        // Still nothing found, use the final fallback.
352        if (font == nil) {
353            font = [self fallbackFontWithTraits:traits size:size];
354        }
355    }
356
357    return font;
358}
359
360static BOOL acceptableChoice(NSFontTraitMask desiredTraits, int desiredWeight,
361    NSFontTraitMask candidateTraits, int candidateWeight)
362{
363    return (candidateTraits & desiredTraits) == desiredTraits;
364}
365
366static BOOL betterChoice(NSFontTraitMask desiredTraits, int desiredWeight,
367    NSFontTraitMask chosenTraits, int chosenWeight,
368    NSFontTraitMask candidateTraits, int candidateWeight)
369{
370    if (!acceptableChoice(desiredTraits, desiredWeight, candidateTraits, candidateWeight)) {
371        return NO;
372    }
373   
374    // A list of the traits we care about.
375    // The top item in the list is the worst trait to mismatch; if a font has this
376    // and we didn't ask for it, we'd prefer any other font in the family.
377    const NSFontTraitMask masks[] = {
378        NSPosterFontMask,
379        NSSmallCapsFontMask,
380        NSItalicFontMask,
381        NSCompressedFontMask,
382        NSCondensedFontMask,
383        NSExpandedFontMask,
384        NSNarrowFontMask,
385        NSBoldFontMask,
386        0 };
387    int i = 0;
388    NSFontTraitMask mask;
389    while ((mask = masks[i++])) {
390        if ((desiredTraits & mask) != 0) {
391            ASSERT((chosenTraits & mask) != 0);
392            ASSERT((candidateTraits & mask) != 0);
393            continue;
394        }
395        BOOL chosenHasUnwantedTrait = (chosenTraits & mask) != 0;
396        BOOL candidateHasUnwantedTrait = (candidateTraits & mask) != 0;
397        if (!candidateHasUnwantedTrait && chosenHasUnwantedTrait) {
398            return YES;
399        }
400        if (!chosenHasUnwantedTrait && candidateHasUnwantedTrait) {
401            return NO;
402        }
403    }
404   
405    int chosenWeightDelta = chosenWeight - desiredWeight;
406    int candidateWeightDelta = candidateWeight - desiredWeight;
407   
408    int chosenWeightDeltaMagnitude = ABS(chosenWeightDelta);
409    int candidateWeightDeltaMagnitude = ABS(candidateWeightDelta);
410   
411    // Smaller magnitude wins.
412    // If both have same magnitude, tie breaker is that the smaller weight wins.
413    // Otherwise, first font in the array wins (should almost never happen).
414    if (candidateWeightDeltaMagnitude < chosenWeightDeltaMagnitude) {
415        return YES;
416    }
417    if (candidateWeightDeltaMagnitude == chosenWeightDeltaMagnitude && candidateWeight < chosenWeight) {
418        return YES;
419    }
420   
421    return NO;
422}
423
424// Family name is somewhat of a misnomer here.  We first attempt to find an exact match
425// comparing the desiredFamily to the PostScript name of the installed fonts.  If that fails
426// we then do a search based on the family names of the installed fonts.
427- (NSFont *)fontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits size:(float)size
428{
429    NSFontManager *fontManager = [NSFontManager sharedFontManager];
430    NSFont *font= nil;
431   
432    LOG (FontSelection, "looking for %@ with traits %x\n", desiredFamily, desiredTraits);
433   
434    // Look for an exact match first.
435    NSEnumerator *availableFonts = [[fontManager availableFonts] objectEnumerator];
436    NSString *availableFont;
437    while ((availableFont = [availableFonts nextObject])) {
438        if ([desiredFamily caseInsensitiveCompare:availableFont] == NSOrderedSame) {
439            NSFont *nameMatchedFont = [NSFont fontWithName:availableFont size:size];
440           
441            // Special case Osaka-Mono.  According to <rdar://problem/3999467>, we need to
442            // treat Osaka-Mono as fixed pitch.
443            if ([desiredFamily caseInsensitiveCompare:@"Osaka-Mono"] == NSOrderedSame && desiredTraits == 0) {
444                LOG (FontSelection, "found exact match for Osaka-Mono\n");
445                return nameMatchedFont;
446            }
447           
448            NSFontTraitMask traits = [fontManager traitsOfFont:nameMatchedFont];
449           
450            if ((traits & desiredTraits) == desiredTraits){
451                font = [fontManager convertFont:nameMatchedFont toHaveTrait:desiredTraits];
452                LOG (FontSelection, "returning exact match (%@)\n\n", [[[font fontDescriptor] fontAttributes] objectForKey: NSFontNameAttribute]);
453                return font;
454            }
455            LOG (FontSelection, "found exact match, but not desired traits, available traits %x\n", traits);
456            break;
457        }
458    }
459   
460    // Do a simple case insensitive search for a matching font family.
461    // NSFontManager requires exact name matches.
462    // This addresses the problem of matching arial to Arial, etc., but perhaps not all the issues.
463    NSEnumerator *e = [[fontManager availableFontFamilies] objectEnumerator];
464    NSString *availableFamily;
465    while ((availableFamily = [e nextObject])) {
466        if ([desiredFamily caseInsensitiveCompare:availableFamily] == NSOrderedSame) {
467            break;
468        }
469    }
470   
471    // Found a family, now figure out what weight and traits to use.
472    BOOL choseFont = false;
473    int chosenWeight = 0;
474    NSFontTraitMask chosenTraits = 0;
475
476    NSArray *fonts = [fontManager availableMembersOfFontFamily:availableFamily];   
477    unsigned n = [fonts count];
478    unsigned i;
479    for (i = 0; i < n; i++) {
480        NSArray *fontInfo = [fonts objectAtIndex:i];
481       
482        // Array indices must be hard coded because of lame AppKit API.
483        int fontWeight = [[fontInfo objectAtIndex:2] intValue];
484        NSFontTraitMask fontTraits = [[fontInfo objectAtIndex:3] unsignedIntValue];
485       
486        BOOL newWinner;
487       
488        if (!choseFont) {
489            newWinner = acceptableChoice(desiredTraits, DESIRED_WEIGHT, fontTraits, fontWeight);
490        } else {
491            newWinner = betterChoice(desiredTraits, DESIRED_WEIGHT,
492                chosenTraits, chosenWeight, fontTraits, fontWeight);
493        }
494
495        if (newWinner) {
496            choseFont = YES;
497            chosenWeight = fontWeight;
498            chosenTraits = fontTraits;
499           
500            if (chosenWeight == DESIRED_WEIGHT
501                    && (chosenTraits & IMPORTANT_FONT_TRAITS) == (desiredTraits & IMPORTANT_FONT_TRAITS)) {
502                break;
503            }
504        }
505    }
506   
507    if (!choseFont) {
508        LOG (FontSelection, "nothing appropriate to return\n\n");
509        return nil;
510    }
511
512    font = [fontManager fontWithFamily:availableFamily traits:chosenTraits weight:chosenWeight size:size];
513    LOG (FontSelection, "returning font family %@ (%@) traits %x, fontID = %x\n\n",
514            availableFamily, [[[font fontDescriptor] fontAttributes] objectForKey: NSFontNameAttribute], chosenTraits, (unsigned int)[font _atsFontID]);
515   
516    return font;
517}
518
519typedef struct {
520    NSString *family;
521    NSFontTraitMask traits;
522    float size;
523} FontCacheKey;
524
525static const void *FontCacheKeyCopy(CFAllocatorRef allocator, const void *value)
526{
527    const FontCacheKey *key = (const FontCacheKey *)value;
528    FontCacheKey *result = malloc(sizeof(FontCacheKey));
529    result->family = [key->family copy];
530    result->traits = key->traits;
531    result->size = key->size;
532    return result;
533}
534
535static void FontCacheKeyFree(CFAllocatorRef allocator, const void *value)
536{
537    const FontCacheKey *key = (const FontCacheKey *)value;
538    [key->family release];
539    free((void *)key);
540}
541
542static Boolean FontCacheKeyEqual(const void *value1, const void *value2)
543{
544    const FontCacheKey *key1 = (const FontCacheKey *)value1;
545    const FontCacheKey *key2 = (const FontCacheKey *)value2;
546    return key1->size == key2->size && key1->traits == key2->traits && [key1->family isEqualToString:key2->family];
547}
548
549static CFHashCode FontCacheKeyHash(const void *value)
550{
551    const FontCacheKey *key = (const FontCacheKey *)value;
552    return [key->family hash] ^ key->traits ^ (int)key->size;
553}
554
555static const void *FontCacheValueRetain(CFAllocatorRef allocator, const void *value)
556{
557    if (value != NULL) {
558        CFRetain(value);
559    }
560    return value;
561}
562
563static void FontCacheValueRelease(CFAllocatorRef allocator, const void *value)
564{
565    if (value != NULL) {
566        CFRelease(value);
567    }
568}
569
570- (NSFont *)cachedFontFromFamily:(NSString *)family traits:(NSFontTraitMask)traits size:(float)size
571{
572    ASSERT(family);
573   
574    if (!fontCache) {
575        static const CFDictionaryKeyCallBacks fontCacheKeyCallBacks = { 0, FontCacheKeyCopy, FontCacheKeyFree, NULL, FontCacheKeyEqual, FontCacheKeyHash };
576        static const CFDictionaryValueCallBacks fontCacheValueCallBacks = { 0, FontCacheValueRetain, FontCacheValueRelease, NULL, NULL };
577        fontCache = CFDictionaryCreateMutable(NULL, 0, &fontCacheKeyCallBacks, &fontCacheValueCallBacks);
578    }
579
580    const FontCacheKey fontKey = { family, traits, size };
581    const void *value;
582    NSFont *font;
583    if (CFDictionaryGetValueIfPresent(fontCache, &fontKey, &value)) {
584        font = (NSFont *)value;
585    } else {
586        if ([family length] == 0) {
587            return nil;
588        }
589        font = [self fontWithFamily:family traits:traits size:size];
590        CFDictionaryAddValue(fontCache, &fontKey, font);
591    }
592   
593#ifdef DEBUG_MISSING_FONT
594    static int unableToFindFontCount = 0;
595    if (font == nil) {
596        unableToFindFontCount++;
597        NSLog(@"unableToFindFontCount %@, traits 0x%08x, size %f, %d\n", family, traits, size, unableToFindFontCount);
598    }
599#endif
600   
601    return font;
602}
603
604@end
Note: See TracBrowser for help on using the repository browser.