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