• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3 * Copyright (C) 2007 Nicholas Shanks <webkit@nickshanks.com>
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1.  Redistributions of source code must retain the above copyright
10 *     notice, this list of conditions and the following disclaimer.
11 * 2.  Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 *     its contributors may be used to endorse or promote products derived
16 *     from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#import "config.h"
31#import "WebFontCache.h"
32
33#import "FontTraitsMask.h"
34#import <AppKit/AppKit.h>
35#import <Foundation/Foundation.h>
36#import <math.h>
37#import <wtf/UnusedParam.h>
38
39using namespace WebCore;
40
41#ifdef BUILDING_ON_TIGER
42typedef int NSInteger;
43#endif
44
45#define SYNTHESIZED_FONT_TRAITS (NSBoldFontMask | NSItalicFontMask)
46
47#define IMPORTANT_FONT_TRAITS (0 \
48    | NSCompressedFontMask \
49    | NSCondensedFontMask \
50    | NSExpandedFontMask \
51    | NSItalicFontMask \
52    | NSNarrowFontMask \
53    | NSPosterFontMask \
54    | NSSmallCapsFontMask \
55)
56
57static BOOL acceptableChoice(NSFontTraitMask desiredTraits, NSFontTraitMask candidateTraits)
58{
59    desiredTraits &= ~SYNTHESIZED_FONT_TRAITS;
60    return (candidateTraits & desiredTraits) == desiredTraits;
61}
62
63static BOOL betterChoice(NSFontTraitMask desiredTraits, int desiredWeight,
64    NSFontTraitMask chosenTraits, int chosenWeight,
65    NSFontTraitMask candidateTraits, int candidateWeight)
66{
67    if (!acceptableChoice(desiredTraits, candidateTraits))
68        return NO;
69
70    // A list of the traits we care about.
71    // The top item in the list is the worst trait to mismatch; if a font has this
72    // and we didn't ask for it, we'd prefer any other font in the family.
73    const NSFontTraitMask masks[] = {
74        NSPosterFontMask,
75        NSSmallCapsFontMask,
76        NSItalicFontMask,
77        NSCompressedFontMask,
78        NSCondensedFontMask,
79        NSExpandedFontMask,
80        NSNarrowFontMask,
81        0
82    };
83
84    int i = 0;
85    NSFontTraitMask mask;
86    while ((mask = masks[i++])) {
87        BOOL desired = (desiredTraits & mask) != 0;
88        BOOL chosenHasUnwantedTrait = desired != ((chosenTraits & mask) != 0);
89        BOOL candidateHasUnwantedTrait = desired != ((candidateTraits & mask) != 0);
90        if (!candidateHasUnwantedTrait && chosenHasUnwantedTrait)
91            return YES;
92        if (!chosenHasUnwantedTrait && candidateHasUnwantedTrait)
93            return NO;
94    }
95
96    int chosenWeightDeltaMagnitude = abs(chosenWeight - desiredWeight);
97    int candidateWeightDeltaMagnitude = abs(candidateWeight - desiredWeight);
98
99    // If both are the same distance from the desired weight, prefer the candidate if it is further from medium.
100    if (chosenWeightDeltaMagnitude == candidateWeightDeltaMagnitude)
101        return abs(candidateWeight - 6) > abs(chosenWeight - 6);
102
103    // Otherwise, prefer the one closer to the desired weight.
104    return candidateWeightDeltaMagnitude < chosenWeightDeltaMagnitude;
105}
106
107// Workaround for <rdar://problem/5781372>.
108static inline void fixUpWeight(NSInteger& weight, NSString *fontName)
109{
110#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
111    UNUSED_PARAM(weight);
112    UNUSED_PARAM(fontName);
113#else
114    if (weight == 3 && [fontName rangeOfString:@"ultralight" options:NSCaseInsensitiveSearch | NSBackwardsSearch | NSLiteralSearch].location != NSNotFound)
115        weight = 2;
116#endif
117}
118
119static inline FontTraitsMask toTraitsMask(NSFontTraitMask appKitTraits, NSInteger appKitWeight)
120{
121    return static_cast<FontTraitsMask>(((appKitTraits & NSFontItalicTrait) ? FontStyleItalicMask : FontStyleNormalMask)
122        | FontVariantNormalMask
123        | (appKitWeight == 1 ? FontWeight100Mask :
124              appKitWeight == 2 ? FontWeight200Mask :
125              appKitWeight <= 4 ? FontWeight300Mask :
126              appKitWeight == 5 ? FontWeight400Mask :
127              appKitWeight == 6 ? FontWeight500Mask :
128              appKitWeight <= 8 ? FontWeight600Mask :
129              appKitWeight == 9 ? FontWeight700Mask :
130              appKitWeight <= 11 ? FontWeight800Mask :
131                                   FontWeight900Mask));
132}
133
134@implementation WebFontCache
135
136+ (void)getTraits:(Vector<unsigned>&)traitsMasks inFamily:(NSString *)desiredFamily
137{
138    NSFontManager *fontManager = [NSFontManager sharedFontManager];
139
140    NSEnumerator *e = [[fontManager availableFontFamilies] objectEnumerator];
141    NSString *availableFamily;
142    while ((availableFamily = [e nextObject])) {
143        if ([desiredFamily caseInsensitiveCompare:availableFamily] == NSOrderedSame)
144            break;
145    }
146
147    if (!availableFamily) {
148        // Match by PostScript name.
149        NSEnumerator *availableFonts = [[fontManager availableFonts] objectEnumerator];
150        NSString *availableFont;
151        while ((availableFont = [availableFonts nextObject])) {
152            if ([desiredFamily caseInsensitiveCompare:availableFont] == NSOrderedSame) {
153                NSFont *font = [NSFont fontWithName:availableFont size:10];
154                NSInteger weight = [fontManager weightOfFont:font];
155                fixUpWeight(weight, desiredFamily);
156                traitsMasks.append(toTraitsMask([fontManager traitsOfFont:font], weight));
157                break;
158            }
159        }
160        return;
161    }
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        // Array indices must be hard coded because of lame AppKit API.
169        NSString *fontFullName = [fontInfo objectAtIndex:0];
170        NSInteger fontWeight = [[fontInfo objectAtIndex:2] intValue];
171        fixUpWeight(fontWeight, fontFullName);
172
173        NSFontTraitMask fontTraits = [[fontInfo objectAtIndex:3] unsignedIntValue];
174        traitsMasks.append(toTraitsMask(fontTraits, fontWeight));
175    }
176}
177
178// Family name is somewhat of a misnomer here.  We first attempt to find an exact match
179// comparing the desiredFamily to the PostScript name of the installed fonts.  If that fails
180// we then do a search based on the family names of the installed fonts.
181+ (NSFont *)internalFontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits weight:(int)desiredWeight size:(float)size
182{
183    NSFontManager *fontManager = [NSFontManager sharedFontManager];
184
185    // Do a simple case insensitive search for a matching font family.
186    // NSFontManager requires exact name matches.
187    // This addresses the problem of matching arial to Arial, etc., but perhaps not all the issues.
188    NSEnumerator *e = [[fontManager availableFontFamilies] objectEnumerator];
189    NSString *availableFamily;
190    while ((availableFamily = [e nextObject])) {
191        if ([desiredFamily caseInsensitiveCompare:availableFamily] == NSOrderedSame)
192            break;
193    }
194
195    if (!availableFamily) {
196        // Match by PostScript name.
197        NSEnumerator *availableFonts = [[fontManager availableFonts] objectEnumerator];
198        NSString *availableFont;
199        NSFont *nameMatchedFont = nil;
200        NSFontTraitMask desiredTraitsForNameMatch = desiredTraits | (desiredWeight >= 7 ? NSBoldFontMask : 0);
201        while ((availableFont = [availableFonts nextObject])) {
202            if ([desiredFamily caseInsensitiveCompare:availableFont] == NSOrderedSame) {
203                nameMatchedFont = [NSFont fontWithName:availableFont size:size];
204
205                // Special case Osaka-Mono.  According to <rdar://problem/3999467>, we need to
206                // treat Osaka-Mono as fixed pitch.
207                if ([desiredFamily caseInsensitiveCompare:@"Osaka-Mono"] == NSOrderedSame && desiredTraitsForNameMatch == 0)
208                    return nameMatchedFont;
209
210                NSFontTraitMask traits = [fontManager traitsOfFont:nameMatchedFont];
211                if ((traits & desiredTraitsForNameMatch) == desiredTraitsForNameMatch)
212                    return [fontManager convertFont:nameMatchedFont toHaveTrait:desiredTraitsForNameMatch];
213
214                availableFamily = [nameMatchedFont familyName];
215                break;
216            }
217        }
218    }
219
220    // Found a family, now figure out what weight and traits to use.
221    BOOL choseFont = false;
222    int chosenWeight = 0;
223    NSFontTraitMask chosenTraits = 0;
224    NSString *chosenFullName = 0;
225
226    NSArray *fonts = [fontManager availableMembersOfFontFamily:availableFamily];
227    unsigned n = [fonts count];
228    unsigned i;
229    for (i = 0; i < n; i++) {
230        NSArray *fontInfo = [fonts objectAtIndex:i];
231
232        // Array indices must be hard coded because of lame AppKit API.
233        NSString *fontFullName = [fontInfo objectAtIndex:0];
234        NSInteger fontWeight = [[fontInfo objectAtIndex:2] intValue];
235        fixUpWeight(fontWeight, fontFullName);
236
237        NSFontTraitMask fontTraits = [[fontInfo objectAtIndex:3] unsignedIntValue];
238
239        BOOL newWinner;
240        if (!choseFont)
241            newWinner = acceptableChoice(desiredTraits, fontTraits);
242        else
243            newWinner = betterChoice(desiredTraits, desiredWeight, chosenTraits, chosenWeight, fontTraits, fontWeight);
244
245        if (newWinner) {
246            choseFont = YES;
247            chosenWeight = fontWeight;
248            chosenTraits = fontTraits;
249            chosenFullName = fontFullName;
250
251            if (chosenWeight == desiredWeight && (chosenTraits & IMPORTANT_FONT_TRAITS) == (desiredTraits & IMPORTANT_FONT_TRAITS))
252                break;
253        }
254    }
255
256    if (!choseFont)
257        return nil;
258
259    NSFont *font = [NSFont fontWithName:chosenFullName size:size];
260
261    if (!font)
262        return nil;
263
264    NSFontTraitMask actualTraits = 0;
265    if (desiredTraits & NSFontItalicTrait)
266        actualTraits = [fontManager traitsOfFont:font];
267    int actualWeight = [fontManager weightOfFont:font];
268
269    bool syntheticBold = desiredWeight >= 7 && actualWeight < 7;
270    bool syntheticOblique = (desiredTraits & NSFontItalicTrait) && !(actualTraits & NSFontItalicTrait);
271
272    // There are some malformed fonts that will be correctly returned by -fontWithFamily:traits:weight:size: as a match for a particular trait,
273    // though -[NSFontManager traitsOfFont:] incorrectly claims the font does not have the specified trait. This could result in applying
274    // 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
275    // 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
276    // the same family without those traits (to apply the synthetic traits to later).
277    NSFontTraitMask nonSyntheticTraits = desiredTraits;
278
279    if (syntheticBold)
280        nonSyntheticTraits &= ~NSBoldFontMask;
281
282    if (syntheticOblique)
283        nonSyntheticTraits &= ~NSItalicFontMask;
284
285    if (nonSyntheticTraits != desiredTraits) {
286        NSFont *fontWithoutSyntheticTraits = [fontManager fontWithFamily:availableFamily traits:nonSyntheticTraits weight:chosenWeight size:size];
287        if (fontWithoutSyntheticTraits)
288            font = fontWithoutSyntheticTraits;
289    }
290
291    return font;
292}
293
294+ (NSFont *)fontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits weight:(int)desiredWeight size:(float)size
295{
296#ifndef BUILDING_ON_TIGER
297    NSFont *font = [self internalFontWithFamily:desiredFamily traits:desiredTraits weight:desiredWeight size:size];
298    if (font)
299        return font;
300
301    // Auto activate the font before looking for it a second time.
302    // Ignore the result because we want to use our own algorithm to actually find the font.
303    [NSFont fontWithName:desiredFamily size:size];
304#endif
305
306    return [self internalFontWithFamily:desiredFamily traits:desiredTraits weight:desiredWeight size:size];
307}
308
309+ (NSFont *)fontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits size:(float)size
310{
311    int desiredWeight = (desiredTraits & NSBoldFontMask) ? 9 : 5;
312    return [self fontWithFamily:desiredFamily traits:desiredTraits weight:desiredWeight size:size];
313}
314
315@end
316