WebFontCache.mm 9.42 KB
Newer Older
1
2
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
/*
 * Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
 *
 * 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
#import "WebFontCache.h"
31
#import "FontDescription.h"
32

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

35
#define SYNTHESIZED_FONT_TRAITS (NSBoldFontMask | NSItalicFontMask)
36
#define ACCEPTABLE_FONT_TRAITS (NSFontCondensedTrait | NSFontExpandedTrait)
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#define IMPORTANT_FONT_TRAITS (0 \
    | NSBoldFontMask \
    | NSCompressedFontMask \
    | NSCondensedFontMask \
    | NSExpandedFontMask \
    | NSItalicFontMask \
    | NSNarrowFontMask \
    | NSPosterFontMask \
    | NSSmallCapsFontMask \
)

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

static BOOL betterChoice(NSFontTraitMask desiredTraits, int desiredWeight,
    NSFontTraitMask chosenTraits, int chosenWeight,
    NSFontTraitMask candidateTraits, int candidateWeight)
{
60
61
62
63
64
65
66
67
68
69
    if (!acceptableChoice(desiredTraits, desiredWeight, candidateTraits, candidateWeight))
        return NO;
    
    unsigned chosenWeightDelta = abs(chosenWeight - desiredWeight);
    unsigned candidateWeightDelta = abs(candidateWeight - desiredWeight);
    
    // prefer a closer weight regardless of traits
    if (candidateWeightDelta < chosenWeightDelta)
        return YES;
    if (candidateWeightDelta > chosenWeightDelta)
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
        return NO;
    
    // 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;
    }
    
    // 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).
100
    if (candidateWeightDelta < chosenWeightDelta)
101
        return YES;
102
    if (candidateWeightDelta == chosenWeightDelta && candidateWeight < chosenWeight)
103
104
105
106
107
108
109
110
111
112
113
114
115
        return YES;
    
    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.
+ (NSFont *)fontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits size:(float)size
{
    NSFontManager *fontManager = [NSFontManager sharedFontManager];
116
    int desiredWeight = (desiredTraits & NSFontBoldTrait)? 9 : 5;
thatcher's avatar
thatcher committed
117

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

125
126
127
            // 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
128
                return nameMatchedFont;
129
130

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

thatcher's avatar
thatcher committed
137
138
    NSFont *font = nil;
#ifndef BUILDING_ON_TIGER
thatcher's avatar
thatcher committed
139
    // font was not immediately available, try auto activated fonts <rdar://problem/4564955>
thatcher's avatar
thatcher committed
140
    font = [NSFont fontWithName:desiredFamily size:size];
thatcher's avatar
thatcher committed
141
142
143
144
145
    if (font) {
        NSFontTraitMask traits = [fontManager traitsOfFont:font];
        if ((traits & desiredTraits) == desiredTraits)
            return [fontManager convertFont:font toHaveTrait:desiredTraits];
    }
thatcher's avatar
thatcher committed
146
#endif
thatcher's avatar
thatcher committed
147

148
149
150
151
152
153
    // 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
154
        if ([desiredFamily caseInsensitiveCompare:availableFamily] == NSOrderedSame)
155
156
            break;
    }
thatcher's avatar
thatcher committed
157

158
159
160
161
162
163
164
165
166
167
    // 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
168

169
170
171
        // 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
172

173
174
        BOOL newWinner;
        if (!choseFont)
175
            newWinner = acceptableChoice(desiredTraits, desiredWeight, fontTraits, fontWeight);
176
        else
177
            newWinner = betterChoice(desiredTraits, desiredWeight, chosenTraits, chosenWeight, fontTraits, fontWeight);
178
179
180
181
182

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

184
            if (chosenWeight == desiredWeight && (chosenTraits & IMPORTANT_FONT_TRAITS) == (desiredTraits & IMPORTANT_FONT_TRAITS))
185
186
187
                break;
        }
    }
thatcher's avatar
thatcher committed
188

189
190
191
192
    if (!choseFont)
        return nil;

    font = [fontManager fontWithFamily:availableFamily traits:chosenTraits weight:chosenWeight size:size];
thatcher's avatar
thatcher committed
193

194
195
196
197
198
199
    if (!font)
        return nil;

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

201
202
    bool syntheticBold = (desiredTraits & NSBoldFontMask) && !(actualTraits & NSBoldFontMask);
    bool syntheticOblique = (desiredTraits & NSItalicFontMask) && !(actualTraits & NSItalicFontMask);
thatcher's avatar
thatcher committed
203

204
205
    // 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
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
208
209
    // 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
210

211
212
    if (syntheticBold)
        nonSyntheticTraits &= ~NSBoldFontMask;
thatcher's avatar
thatcher committed
213

214
215
    if (syntheticOblique)
        nonSyntheticTraits &= ~NSItalicFontMask;
thatcher's avatar
thatcher committed
216

217
218
219
220
221
222
223
224
225
226
    if (nonSyntheticTraits != desiredTraits) {
        NSFont *fontWithoutSyntheticTraits = [fontManager fontWithFamily:availableFamily traits:nonSyntheticTraits weight:chosenWeight size:size];
        if (fontWithoutSyntheticTraits)
            font = fontWithoutSyntheticTraits;
    }

    return font;
}

@end