WebFontCache.mm 9.37 KB
Newer Older
1
/*
darin's avatar
darin committed
2
 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

weinig's avatar
weinig committed
29
#import "config.h"
30
31
#import "WebFontCache.h"

weinig's avatar
weinig committed
32
33
#import <math.h>

34
#define SYNTHESIZED_FONT_TRAITS (NSBoldFontMask | NSItalicFontMask)
bdash's avatar
bdash committed
35

36
37
38
39
40
41
42
43
44
45
46
#define IMPORTANT_FONT_TRAITS (0 \
    | NSBoldFontMask \
    | NSCompressedFontMask \
    | NSCondensedFontMask \
    | NSExpandedFontMask \
    | NSItalicFontMask \
    | NSNarrowFontMask \
    | NSPosterFontMask \
    | NSSmallCapsFontMask \
)

bdash's avatar
bdash committed
47
48
#define DESIRED_WEIGHT 5

49
50
51
52
53
54
55
56
57
58
59
static BOOL acceptableChoice(NSFontTraitMask desiredTraits, int desiredWeight,
    NSFontTraitMask candidateTraits, int candidateWeight)
{
    desiredTraits &= ~SYNTHESIZED_FONT_TRAITS;
    return (candidateTraits & desiredTraits) == desiredTraits;
}

static BOOL betterChoice(NSFontTraitMask desiredTraits, int desiredWeight,
    NSFontTraitMask chosenTraits, int chosenWeight,
    NSFontTraitMask candidateTraits, int candidateWeight)
{
bdash's avatar
bdash committed
60
    if (!acceptableChoice(desiredTraits, desiredWeight, candidateTraits, candidateWeight)) {
61
        return NO;
bdash's avatar
bdash committed
62
    }
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
    
    // A list of the traits we care about.
    // The top item in the list is the worst trait to mismatch; if a font has this
    // and we didn't ask for it, we'd prefer any other font in the family.
    const NSFontTraitMask masks[] = {
        NSPosterFontMask,
        NSSmallCapsFontMask,
        NSItalicFontMask,
        NSCompressedFontMask,
        NSCondensedFontMask,
        NSExpandedFontMask,
        NSNarrowFontMask,
        NSBoldFontMask,
        0 };
    int i = 0;
    NSFontTraitMask mask;
    while ((mask = masks[i++])) {
        BOOL desired = (desiredTraits & mask) != 0;
        BOOL chosenHasUnwantedTrait = desired != ((chosenTraits & mask) != 0);
        BOOL candidateHasUnwantedTrait = desired != ((candidateTraits & mask) != 0);
        if (!candidateHasUnwantedTrait && chosenHasUnwantedTrait)
            return YES;
        if (!chosenHasUnwantedTrait && candidateHasUnwantedTrait)
            return NO;
    }
    
bdash's avatar
bdash committed
89
90
91
92
93
94
    int chosenWeightDelta = chosenWeight - desiredWeight;
    int candidateWeightDelta = candidateWeight - desiredWeight;
    
    int chosenWeightDeltaMagnitude = abs(chosenWeightDelta);
    int candidateWeightDeltaMagnitude = abs(candidateWeightDelta);
    
95
96
97
    // Smaller magnitude wins.
    // If both have same magnitude, tie breaker is that the smaller weight wins.
    // Otherwise, first font in the array wins (should almost never happen).
bdash's avatar
bdash committed
98
    if (candidateWeightDeltaMagnitude < chosenWeightDeltaMagnitude) {
99
        return YES;
bdash's avatar
bdash committed
100
101
    }
    if (candidateWeightDeltaMagnitude == chosenWeightDeltaMagnitude && candidateWeight < chosenWeight) {
102
        return YES;
bdash's avatar
bdash committed
103
    }
104
105
106
107
108
109
110
111
112
    
    return NO;
}

@implementation WebFontCache

// Family name is somewhat of a misnomer here.  We first attempt to find an exact match
// comparing the desiredFamily to the PostScript name of the installed fonts.  If that fails
// we then do a search based on the family names of the installed fonts.
darin's avatar
darin committed
113
+ (NSFont *)internalFontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits size:(float)size
114
115
{
    NSFontManager *fontManager = [NSFontManager sharedFontManager];
thatcher's avatar
thatcher committed
116

117
    // Look for an exact match first.
118
119
120
121
    NSEnumerator *availableFonts = [[fontManager availableFonts] objectEnumerator];
    NSString *availableFont;
    while ((availableFont = [availableFonts nextObject])) {
        if ([desiredFamily caseInsensitiveCompare:availableFont] == NSOrderedSame) {
122
            NSFont *nameMatchedFont = [NSFont fontWithName:availableFont size:size];
thatcher's avatar
thatcher committed
123

124
125
126
            // Special case Osaka-Mono.  According to <rdar://problem/3999467>, we need to 
            // treat Osaka-Mono as fixed pitch.
            if ([desiredFamily caseInsensitiveCompare:@"Osaka-Mono"] == NSOrderedSame && desiredTraits == 0)
thatcher's avatar
thatcher committed
127
                return nameMatchedFont;
128
129

            NSFontTraitMask traits = [fontManager traitsOfFont:nameMatchedFont];
thatcher's avatar
thatcher committed
130
131
            if ((traits & desiredTraits) == desiredTraits)
                return [fontManager convertFont:nameMatchedFont toHaveTrait:desiredTraits];
132
133
134
            break;
        }
    }
thatcher's avatar
thatcher committed
135

136
137
138
139
140
141
    // Do a simple case insensitive search for a matching font family.
    // NSFontManager requires exact name matches.
    // This addresses the problem of matching arial to Arial, etc., but perhaps not all the issues.
    NSEnumerator *e = [[fontManager availableFontFamilies] objectEnumerator];
    NSString *availableFamily;
    while ((availableFamily = [e nextObject])) {
thatcher's avatar
thatcher committed
142
        if ([desiredFamily caseInsensitiveCompare:availableFamily] == NSOrderedSame)
143
144
            break;
    }
thatcher's avatar
thatcher committed
145

146
147
148
149
150
151
152
153
154
155
    // Found a family, now figure out what weight and traits to use.
    BOOL choseFont = false;
    int chosenWeight = 0;
    NSFontTraitMask chosenTraits = 0;

    NSArray *fonts = [fontManager availableMembersOfFontFamily:availableFamily];    
    unsigned n = [fonts count];
    unsigned i;
    for (i = 0; i < n; i++) {
        NSArray *fontInfo = [fonts objectAtIndex:i];
thatcher's avatar
thatcher committed
156

157
158
159
        // Array indices must be hard coded because of lame AppKit API.
        int fontWeight = [[fontInfo objectAtIndex:2] intValue];
        NSFontTraitMask fontTraits = [[fontInfo objectAtIndex:3] unsignedIntValue];
thatcher's avatar
thatcher committed
160

161
162
        BOOL newWinner;
        if (!choseFont)
bdash's avatar
bdash committed
163
            newWinner = acceptableChoice(desiredTraits, DESIRED_WEIGHT, fontTraits, fontWeight);
164
        else
bdash's avatar
bdash committed
165
            newWinner = betterChoice(desiredTraits, DESIRED_WEIGHT, chosenTraits, chosenWeight, fontTraits, fontWeight);
166
167
168
169
170

        if (newWinner) {
            choseFont = YES;
            chosenWeight = fontWeight;
            chosenTraits = fontTraits;
thatcher's avatar
thatcher committed
171

bdash's avatar
bdash committed
172
            if (chosenWeight == DESIRED_WEIGHT && (chosenTraits & IMPORTANT_FONT_TRAITS) == (desiredTraits & IMPORTANT_FONT_TRAITS))
173
174
175
                break;
        }
    }
thatcher's avatar
thatcher committed
176

177
178
179
    if (!choseFont)
        return nil;

darin's avatar
darin committed
180
    NSFont *font = [fontManager fontWithFamily:availableFamily traits:chosenTraits weight:chosenWeight size:size];
thatcher's avatar
thatcher committed
181

182
183
184
185
186
187
    if (!font)
        return nil;

    NSFontTraitMask actualTraits = 0;
    if (desiredTraits & (NSItalicFontMask | NSBoldFontMask))
        actualTraits = [[NSFontManager sharedFontManager] traitsOfFont:font];
thatcher's avatar
thatcher committed
188

189
190
    bool syntheticBold = (desiredTraits & NSBoldFontMask) && !(actualTraits & NSBoldFontMask);
    bool syntheticOblique = (desiredTraits & NSItalicFontMask) && !(actualTraits & NSItalicFontMask);
thatcher's avatar
thatcher committed
191

192
193
    // There are some malformed fonts that will be correctly returned by -fontWithFamily:traits:weight:size: as a match for a particular trait,
    // though -[NSFontManager traitsOfFont:] incorrectly claims the font does not have the specified trait. This could result in applying 
darin's avatar
darin committed
194
    // 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
195
196
197
    // 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 
    // the same family without those traits (to apply the synthetic traits to later).
    NSFontTraitMask nonSyntheticTraits = desiredTraits;
thatcher's avatar
thatcher committed
198

199
200
    if (syntheticBold)
        nonSyntheticTraits &= ~NSBoldFontMask;
thatcher's avatar
thatcher committed
201

202
203
    if (syntheticOblique)
        nonSyntheticTraits &= ~NSItalicFontMask;
thatcher's avatar
thatcher committed
204

205
206
207
208
209
210
211
212
213
    if (nonSyntheticTraits != desiredTraits) {
        NSFont *fontWithoutSyntheticTraits = [fontManager fontWithFamily:availableFamily traits:nonSyntheticTraits weight:chosenWeight size:size];
        if (fontWithoutSyntheticTraits)
            font = fontWithoutSyntheticTraits;
    }

    return font;
}

darin's avatar
darin committed
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
+ (NSFont *)fontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits size:(float)size
{
#ifndef BUILDING_ON_TIGER
    NSFont *font = [self internalFontWithFamily:desiredFamily traits:desiredTraits size:size];
    if (font)
        return font;

    // Auto activate the font before looking for it a second time.
    // Ignore the result because we want to use our own algorithm to actually find the font.
    [NSFont fontWithName:desiredFamily size:size];
#endif

    return [self internalFontWithFamily:desiredFamily traits:desiredTraits size:size];
}

229
@end