dd4a04a08d384a37bd6896f518143719b029a316
[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 #import "FontDescription.h"
32
33 #import <math.h>
34
35 #define SYNTHESIZED_FONT_TRAITS (NSBoldFontMask | NSItalicFontMask)
36 #define ACCEPTABLE_FONT_TRAITS (NSFontCondensedTrait | NSFontExpandedTrait)
37 #define IMPORTANT_FONT_TRAITS (0 \
38     | NSBoldFontMask \
39     | NSCompressedFontMask \
40     | NSCondensedFontMask \
41     | NSExpandedFontMask \
42     | NSItalicFontMask \
43     | NSNarrowFontMask \
44     | NSPosterFontMask \
45     | NSSmallCapsFontMask \
46 )
47
48 static BOOL acceptableChoice(NSFontTraitMask desiredTraits, int desiredWeight,
49     NSFontTraitMask candidateTraits, int candidateWeight)
50 {
51     desiredTraits &= ~SYNTHESIZED_FONT_TRAITS;
52     desiredTraits &= ~ACCEPTABLE_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     unsigned chosenWeightDelta = abs(chosenWeight - desiredWeight);
64     unsigned candidateWeightDelta = abs(candidateWeight - desiredWeight);
65     
66     // prefer a closer weight regardless of traits
67     if (candidateWeightDelta < chosenWeightDelta)
68         return YES;
69     if (candidateWeightDelta > chosenWeightDelta)
70         return NO;
71     
72     // A list of the traits we care about.
73     // The top item in the list is the worst trait to mismatch; if a font has this
74     // and we didn't ask for it, we'd prefer any other font in the family.
75     const NSFontTraitMask masks[] = {
76         NSPosterFontMask,
77         NSSmallCapsFontMask,
78         NSItalicFontMask,
79         NSCompressedFontMask,
80         NSCondensedFontMask,
81         NSExpandedFontMask,
82         NSNarrowFontMask,
83         NSBoldFontMask,
84         0 };
85     int i = 0;
86     NSFontTraitMask mask;
87     while ((mask = masks[i++])) {
88         BOOL desired = (desiredTraits & mask) != 0;
89         BOOL chosenHasUnwantedTrait = desired != ((chosenTraits & mask) != 0);
90         BOOL candidateHasUnwantedTrait = desired != ((candidateTraits & mask) != 0);
91         if (!candidateHasUnwantedTrait && chosenHasUnwantedTrait)
92             return YES;
93         if (!chosenHasUnwantedTrait && candidateHasUnwantedTrait)
94             return NO;
95     }
96     
97     // Smaller magnitude wins.
98     // If both have same magnitude, tie breaker is that the smaller weight wins.
99     // Otherwise, first font in the array wins (should almost never happen).
100     if (candidateWeightDelta < chosenWeightDelta)
101         return YES;
102     if (candidateWeightDelta == chosenWeightDelta && candidateWeight < chosenWeight)
103         return YES;
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     int desiredWeight = (desiredTraits & NSFontBoldTrait)? 9 : 5;
117
118     // Look for an exact match first.
119     NSEnumerator *availableFonts = [[fontManager availableFonts] objectEnumerator];
120     NSString *availableFont;
121     while ((availableFont = [availableFonts nextObject])) {
122         if ([desiredFamily caseInsensitiveCompare:availableFont] == NSOrderedSame) {
123             NSFont *nameMatchedFont = [NSFont fontWithName:availableFont size:size];
124
125             // Special case Osaka-Mono.  According to <rdar://problem/3999467>, we need to 
126             // treat Osaka-Mono as fixed pitch.
127             if ([desiredFamily caseInsensitiveCompare:@"Osaka-Mono"] == NSOrderedSame && desiredTraits == 0)
128                 return nameMatchedFont;
129
130             NSFontTraitMask traits = [fontManager traitsOfFont:nameMatchedFont];
131             if ((traits & desiredTraits) == desiredTraits)
132                 return [fontManager convertFont:nameMatchedFont toHaveTrait:desiredTraits];
133             break;
134         }
135     }
136
137     NSFont *font = nil;
138 #ifndef BUILDING_ON_TIGER
139     // font was not immediately available, try auto activated fonts <rdar://problem/4564955>
140     font = [NSFont fontWithName:desiredFamily size:size];
141     if (font) {
142         NSFontTraitMask traits = [fontManager traitsOfFont:font];
143         if ((traits & desiredTraits) == desiredTraits)
144             return [fontManager convertFont:font toHaveTrait:desiredTraits];
145     }
146 #endif
147
148     // Do a simple case insensitive search for a matching font family.
149     // NSFontManager requires exact name matches.
150     // This addresses the problem of matching arial to Arial, etc., but perhaps not all the issues.
151     NSEnumerator *e = [[fontManager availableFontFamilies] objectEnumerator];
152     NSString *availableFamily;
153     while ((availableFamily = [e nextObject])) {
154         if ([desiredFamily caseInsensitiveCompare:availableFamily] == NSOrderedSame)
155             break;
156     }
157
158     // Found a family, now figure out what weight and traits to use.
159     BOOL choseFont = false;
160     int chosenWeight = 0;
161     NSFontTraitMask chosenTraits = 0;
162
163     NSArray *fonts = [fontManager availableMembersOfFontFamily:availableFamily];    
164     unsigned n = [fonts count];
165     unsigned i;
166     for (i = 0; i < n; i++) {
167         NSArray *fontInfo = [fonts objectAtIndex:i];
168
169         // Array indices must be hard coded because of lame AppKit API.
170         int fontWeight = [[fontInfo objectAtIndex:2] intValue];
171         NSFontTraitMask fontTraits = [[fontInfo objectAtIndex:3] unsignedIntValue];
172
173         BOOL newWinner;
174         if (!choseFont)
175             newWinner = acceptableChoice(desiredTraits, desiredWeight, fontTraits, fontWeight);
176         else
177             newWinner = betterChoice(desiredTraits, desiredWeight, chosenTraits, chosenWeight, fontTraits, fontWeight);
178
179         if (newWinner) {
180             choseFont = YES;
181             chosenWeight = fontWeight;
182             chosenTraits = fontTraits;
183
184             if (chosenWeight == desiredWeight && (chosenTraits & IMPORTANT_FONT_TRAITS) == (desiredTraits & IMPORTANT_FONT_TRAITS))
185                 break;
186         }
187     }
188
189     if (!choseFont)
190         return nil;
191
192     font = [fontManager fontWithFamily:availableFamily traits:chosenTraits weight:chosenWeight size:size];
193
194     if (!font)
195         return nil;
196
197     NSFontTraitMask actualTraits = 0;
198     if (desiredTraits & (NSItalicFontMask | NSBoldFontMask))
199         actualTraits = [[NSFontManager sharedFontManager] traitsOfFont:font];
200
201     bool syntheticBold = (desiredTraits & NSBoldFontMask) && !(actualTraits & NSBoldFontMask);
202     bool syntheticOblique = (desiredTraits & NSItalicFontMask) && !(actualTraits & NSItalicFontMask);
203
204     // There are some malformed fonts that will be correctly returned by -fontWithFamily:traits:weight:size: as a match for a particular trait,
205     // though -[NSFontManager traitsOfFont:] incorrectly claims the font does not have the specified trait. This could result in applying 
206     // 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
207     // 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 
208     // the same family without those traits (to apply the synthetic traits to later).
209     NSFontTraitMask nonSyntheticTraits = desiredTraits;
210
211     if (syntheticBold)
212         nonSyntheticTraits &= ~NSBoldFontMask;
213
214     if (syntheticOblique)
215         nonSyntheticTraits &= ~NSItalicFontMask;
216
217     if (nonSyntheticTraits != desiredTraits) {
218         NSFont *fontWithoutSyntheticTraits = [fontManager fontWithFamily:availableFamily traits:nonSyntheticTraits weight:chosenWeight size:size];
219         if (fontWithoutSyntheticTraits)
220             font = fontWithoutSyntheticTraits;
221     }
222
223     return font;
224 }
225
226 @end