2007-02-06 Mark Rowe <mrowe@apple.com>
[WebKit-https.git] / WebCore / platform / mac / WebFontCache.mm
1 /*
2  * Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
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. 
16  *
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.
27  */
28
29 #import "config.h"
30 #import "WebFontCache.h"
31
32 #import <math.h>
33
34 #define SYNTHESIZED_FONT_TRAITS (NSBoldFontMask | NSItalicFontMask)
35
36 #define IMPORTANT_FONT_TRAITS (0 \
37     | NSBoldFontMask \
38     | NSCompressedFontMask \
39     | NSCondensedFontMask \
40     | NSExpandedFontMask \
41     | NSItalicFontMask \
42     | NSNarrowFontMask \
43     | NSPosterFontMask \
44     | NSSmallCapsFontMask \
45 )
46
47 #define DESIRED_WEIGHT 5
48
49 static BOOL acceptableChoice(NSFontTraitMask desiredTraits, int desiredWeight,
50     NSFontTraitMask candidateTraits, int candidateWeight)
51 {
52     desiredTraits &= ~SYNTHESIZED_FONT_TRAITS;
53     return (candidateTraits & desiredTraits) == desiredTraits;
54 }
55
56 static BOOL betterChoice(NSFontTraitMask desiredTraits, int desiredWeight,
57     NSFontTraitMask chosenTraits, int chosenWeight,
58     NSFontTraitMask candidateTraits, int candidateWeight)
59 {
60     if (!acceptableChoice(desiredTraits, desiredWeight, candidateTraits, candidateWeight)) {
61         return NO;
62     }
63     
64     // A list of the traits we care about.
65     // The top item in the list is the worst trait to mismatch; if a font has this
66     // and we didn't ask for it, we'd prefer any other font in the family.
67     const NSFontTraitMask masks[] = {
68         NSPosterFontMask,
69         NSSmallCapsFontMask,
70         NSItalicFontMask,
71         NSCompressedFontMask,
72         NSCondensedFontMask,
73         NSExpandedFontMask,
74         NSNarrowFontMask,
75         NSBoldFontMask,
76         0 };
77     int i = 0;
78     NSFontTraitMask mask;
79     while ((mask = masks[i++])) {
80         BOOL desired = (desiredTraits & mask) != 0;
81         BOOL chosenHasUnwantedTrait = desired != ((chosenTraits & mask) != 0);
82         BOOL candidateHasUnwantedTrait = desired != ((candidateTraits & mask) != 0);
83         if (!candidateHasUnwantedTrait && chosenHasUnwantedTrait)
84             return YES;
85         if (!chosenHasUnwantedTrait && candidateHasUnwantedTrait)
86             return NO;
87     }
88     
89     int chosenWeightDelta = chosenWeight - desiredWeight;
90     int candidateWeightDelta = candidateWeight - desiredWeight;
91     
92     int chosenWeightDeltaMagnitude = abs(chosenWeightDelta);
93     int candidateWeightDeltaMagnitude = abs(candidateWeightDelta);
94     
95     // Smaller magnitude wins.
96     // If both have same magnitude, tie breaker is that the smaller weight wins.
97     // Otherwise, first font in the array wins (should almost never happen).
98     if (candidateWeightDeltaMagnitude < chosenWeightDeltaMagnitude) {
99         return YES;
100     }
101     if (candidateWeightDeltaMagnitude == chosenWeightDeltaMagnitude && candidateWeight < chosenWeight) {
102         return YES;
103     }
104     
105     return NO;
106 }
107
108 @implementation WebFontCache
109
110 // Family name is somewhat of a misnomer here.  We first attempt to find an exact match
111 // comparing the desiredFamily to the PostScript name of the installed fonts.  If that fails
112 // we then do a search based on the family names of the installed fonts.
113 + (NSFont *)fontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits size:(float)size
114 {
115     NSFontManager *fontManager = [NSFontManager sharedFontManager];
116
117     // Look for an exact match first.
118     NSEnumerator *availableFonts = [[fontManager availableFonts] objectEnumerator];
119     NSString *availableFont;
120     while ((availableFont = [availableFonts nextObject])) {
121         if ([desiredFamily caseInsensitiveCompare:availableFont] == NSOrderedSame) {
122             NSFont *nameMatchedFont = [NSFont fontWithName:availableFont size:size];
123
124             // Special case Osaka-Mono.  According to <rdar://problem/3999467>, we need to 
125             // treat Osaka-Mono as fixed pitch.
126             if ([desiredFamily caseInsensitiveCompare:@"Osaka-Mono"] == NSOrderedSame && desiredTraits == 0)
127                 return nameMatchedFont;
128
129             NSFontTraitMask traits = [fontManager traitsOfFont:nameMatchedFont];
130             if ((traits & desiredTraits) == desiredTraits)
131                 return [fontManager convertFont:nameMatchedFont toHaveTrait:desiredTraits];
132             break;
133         }
134     }
135
136     NSFont *font = nil;
137 #ifndef BUILDING_ON_TIGER
138     // font was not immediately available, try auto activated fonts <rdar://problem/4564955>
139     font = [NSFont fontWithName:desiredFamily size:size];
140     if (font) {
141         NSFontTraitMask traits = [fontManager traitsOfFont:font];
142         if ((traits & desiredTraits) == desiredTraits)
143             return [fontManager convertFont:font toHaveTrait:desiredTraits];
144     }
145 #endif
146
147     // Do a simple case insensitive search for a matching font family.
148     // NSFontManager requires exact name matches.
149     // This addresses the problem of matching arial to Arial, etc., but perhaps not all the issues.
150     NSEnumerator *e = [[fontManager availableFontFamilies] objectEnumerator];
151     NSString *availableFamily;
152     while ((availableFamily = [e nextObject])) {
153         if ([desiredFamily caseInsensitiveCompare:availableFamily] == NSOrderedSame)
154             break;
155     }
156
157     // Found a family, now figure out what weight and traits to use.
158     BOOL choseFont = false;
159     int chosenWeight = 0;
160     NSFontTraitMask chosenTraits = 0;
161
162     NSArray *fonts = [fontManager availableMembersOfFontFamily:availableFamily];    
163     unsigned n = [fonts count];
164     unsigned i;
165     for (i = 0; i < n; i++) {
166         NSArray *fontInfo = [fonts objectAtIndex:i];
167
168         // Array indices must be hard coded because of lame AppKit API.
169         int fontWeight = [[fontInfo objectAtIndex:2] intValue];
170         NSFontTraitMask fontTraits = [[fontInfo objectAtIndex:3] unsignedIntValue];
171
172         BOOL newWinner;
173         if (!choseFont)
174             newWinner = acceptableChoice(desiredTraits, DESIRED_WEIGHT, fontTraits, fontWeight);
175         else
176             newWinner = betterChoice(desiredTraits, DESIRED_WEIGHT, chosenTraits, chosenWeight, fontTraits, fontWeight);
177
178         if (newWinner) {
179             choseFont = YES;
180             chosenWeight = fontWeight;
181             chosenTraits = fontTraits;
182
183             if (chosenWeight == DESIRED_WEIGHT && (chosenTraits & IMPORTANT_FONT_TRAITS) == (desiredTraits & IMPORTANT_FONT_TRAITS))
184                 break;
185         }
186     }
187
188     if (!choseFont)
189         return nil;
190
191     font = [fontManager fontWithFamily:availableFamily traits:chosenTraits weight:chosenWeight size:size];
192
193     if (!font)
194         return nil;
195
196     NSFontTraitMask actualTraits = 0;
197     if (desiredTraits & (NSItalicFontMask | NSBoldFontMask))
198         actualTraits = [[NSFontManager sharedFontManager] traitsOfFont:font];
199
200     bool syntheticBold = (desiredTraits & NSBoldFontMask) && !(actualTraits & NSBoldFontMask);
201     bool syntheticOblique = (desiredTraits & NSItalicFontMask) && !(actualTraits & NSItalicFontMask);
202
203     // There are some malformed fonts that will be correctly returned by -fontWithFamily:traits:weight:size: as a match for a particular trait,
204     // though -[NSFontManager traitsOfFont:] incorrectly claims the font does not have the specified trait. This could result in applying 
205     // synthetic bold on top of an already-bold font, as reported in <http://bugs.webkit.org/show_bug.cgi?id=6146>. To work around this
206     // problem, if we got an apparent exact match, but the requested traits aren't present in the matched font, we'll try to get a font from 
207     // the same family without those traits (to apply the synthetic traits to later).
208     NSFontTraitMask nonSyntheticTraits = desiredTraits;
209
210     if (syntheticBold)
211         nonSyntheticTraits &= ~NSBoldFontMask;
212
213     if (syntheticOblique)
214         nonSyntheticTraits &= ~NSItalicFontMask;
215
216     if (nonSyntheticTraits != desiredTraits) {
217         NSFont *fontWithoutSyntheticTraits = [fontManager fontWithFamily:availableFamily traits:nonSyntheticTraits weight:chosenWeight size:size];
218         if (fontWithoutSyntheticTraits)
219             font = fontWithoutSyntheticTraits;
220     }
221
222     return font;
223 }
224
225 @end