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 42#define SYNTHESIZED_FONT_TRAITS (NSBoldFontMask | NSItalicFontMask) 43 44#define IMPORTANT_FONT_TRAITS (0 \ 45 | NSCompressedFontMask \ 46 | NSCondensedFontMask \ 47 | NSExpandedFontMask \ 48 | NSItalicFontMask \ 49 | NSNarrowFontMask \ 50 | NSPosterFontMask \ 51 | NSSmallCapsFontMask \ 52) 53 54static BOOL acceptableChoice(NSFontTraitMask desiredTraits, NSFontTraitMask candidateTraits) 55{ 56 desiredTraits &= ~SYNTHESIZED_FONT_TRAITS; 57 return (candidateTraits & desiredTraits) == desiredTraits; 58} 59 60static BOOL betterChoice(NSFontTraitMask desiredTraits, int desiredWeight, 61 NSFontTraitMask chosenTraits, int chosenWeight, 62 NSFontTraitMask candidateTraits, int candidateWeight) 63{ 64 if (!acceptableChoice(desiredTraits, candidateTraits)) 65 return NO; 66 67 // A list of the traits we care about. 68 // The top item in the list is the worst trait to mismatch; if a font has this 69 // and we didn't ask for it, we'd prefer any other font in the family. 70 const NSFontTraitMask masks[] = { 71 NSPosterFontMask, 72 NSSmallCapsFontMask, 73 NSItalicFontMask, 74 NSCompressedFontMask, 75 NSCondensedFontMask, 76 NSExpandedFontMask, 77 NSNarrowFontMask, 78 0 79 }; 80 81 int i = 0; 82 NSFontTraitMask mask; 83 while ((mask = masks[i++])) { 84 BOOL desired = (desiredTraits & mask) != 0; 85 BOOL chosenHasUnwantedTrait = desired != ((chosenTraits & mask) != 0); 86 BOOL candidateHasUnwantedTrait = desired != ((candidateTraits & mask) != 0); 87 if (!candidateHasUnwantedTrait && chosenHasUnwantedTrait) 88 return YES; 89 if (!chosenHasUnwantedTrait && candidateHasUnwantedTrait) 90 return NO; 91 } 92 93 int chosenWeightDeltaMagnitude = abs(chosenWeight - desiredWeight); 94 int candidateWeightDeltaMagnitude = abs(candidateWeight - desiredWeight); 95 96 // If both are the same distance from the desired weight, prefer the candidate if it is further from medium. 97 if (chosenWeightDeltaMagnitude == candidateWeightDeltaMagnitude) 98 return abs(candidateWeight - 6) > abs(chosenWeight - 6); 99 100 // Otherwise, prefer the one closer to the desired weight. 101 return candidateWeightDeltaMagnitude < chosenWeightDeltaMagnitude; 102} 103 104// Workaround for <rdar://problem/5781372>. 105static inline void fixUpWeight(NSInteger& weight, NSString *fontName) 106{ 107#ifndef BUILDING_ON_LEOPARD 108 UNUSED_PARAM(weight); 109 UNUSED_PARAM(fontName); 110#else 111 if (weight == 3 && [fontName rangeOfString:@"ultralight" options:NSCaseInsensitiveSearch | NSBackwardsSearch | NSLiteralSearch].location != NSNotFound) 112 weight = 2; 113#endif 114} 115 116static inline FontTraitsMask toTraitsMask(NSFontTraitMask appKitTraits, NSInteger appKitWeight) 117{ 118 return static_cast<FontTraitsMask>(((appKitTraits & NSFontItalicTrait) ? FontStyleItalicMask : FontStyleNormalMask) 119 | FontVariantNormalMask 120 | (appKitWeight == 1 ? FontWeight100Mask : 121 appKitWeight == 2 ? FontWeight200Mask : 122 appKitWeight <= 4 ? FontWeight300Mask : 123 appKitWeight == 5 ? FontWeight400Mask : 124 appKitWeight == 6 ? FontWeight500Mask : 125 appKitWeight <= 8 ? FontWeight600Mask : 126 appKitWeight == 9 ? FontWeight700Mask : 127 appKitWeight <= 11 ? FontWeight800Mask : 128 FontWeight900Mask)); 129} 130 131@implementation WebFontCache 132 133+ (void)getTraits:(Vector<unsigned>&)traitsMasks inFamily:(NSString *)desiredFamily 134{ 135 NSFontManager *fontManager = [NSFontManager sharedFontManager]; 136 137 NSEnumerator *e = [[fontManager availableFontFamilies] objectEnumerator]; 138 NSString *availableFamily; 139 while ((availableFamily = [e nextObject])) { 140 if ([desiredFamily caseInsensitiveCompare:availableFamily] == NSOrderedSame) 141 break; 142 } 143 144 if (!availableFamily) { 145 // Match by PostScript name. 146 NSEnumerator *availableFonts = [[fontManager availableFonts] objectEnumerator]; 147 NSString *availableFont; 148 while ((availableFont = [availableFonts nextObject])) { 149 if ([desiredFamily caseInsensitiveCompare:availableFont] == NSOrderedSame) { 150 NSFont *font = [NSFont fontWithName:availableFont size:10]; 151 NSInteger weight = [fontManager weightOfFont:font]; 152 fixUpWeight(weight, desiredFamily); 153 traitsMasks.append(toTraitsMask([fontManager traitsOfFont:font], weight)); 154 break; 155 } 156 } 157 return; 158 } 159 160 NSArray *fonts = [fontManager availableMembersOfFontFamily:availableFamily]; 161 unsigned n = [fonts count]; 162 unsigned i; 163 for (i = 0; i < n; i++) { 164 NSArray *fontInfo = [fonts objectAtIndex:i]; 165 // Array indices must be hard coded because of lame AppKit API. 166 NSString *fontFullName = [fontInfo objectAtIndex:0]; 167 NSInteger fontWeight = [[fontInfo objectAtIndex:2] intValue]; 168 fixUpWeight(fontWeight, fontFullName); 169 170 NSFontTraitMask fontTraits = [[fontInfo objectAtIndex:3] unsignedIntValue]; 171 traitsMasks.append(toTraitsMask(fontTraits, fontWeight)); 172 } 173} 174 175// Family name is somewhat of a misnomer here. We first attempt to find an exact match 176// comparing the desiredFamily to the PostScript name of the installed fonts. If that fails 177// we then do a search based on the family names of the installed fonts. 178+ (NSFont *)internalFontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits weight:(int)desiredWeight size:(float)size 179{ 180 NSFontManager *fontManager = [NSFontManager sharedFontManager]; 181 182 // Do a simple case insensitive search for a matching font family. 183 // NSFontManager requires exact name matches. 184 // This addresses the problem of matching arial to Arial, etc., but perhaps not all the issues. 185 NSEnumerator *e = [[fontManager availableFontFamilies] objectEnumerator]; 186 NSString *availableFamily; 187 while ((availableFamily = [e nextObject])) { 188 if ([desiredFamily caseInsensitiveCompare:availableFamily] == NSOrderedSame) 189 break; 190 } 191 192 if (!availableFamily) { 193 // Match by PostScript name. 194 NSEnumerator *availableFonts = [[fontManager availableFonts] objectEnumerator]; 195 NSString *availableFont; 196 NSFont *nameMatchedFont = nil; 197 NSFontTraitMask desiredTraitsForNameMatch = desiredTraits | (desiredWeight >= 7 ? NSBoldFontMask : 0); 198 while ((availableFont = [availableFonts nextObject])) { 199 if ([desiredFamily caseInsensitiveCompare:availableFont] == NSOrderedSame) { 200 nameMatchedFont = [NSFont fontWithName:availableFont size:size]; 201 202 // Special case Osaka-Mono. According to <rdar://problem/3999467>, we need to 203 // treat Osaka-Mono as fixed pitch. 204 if ([desiredFamily caseInsensitiveCompare:@"Osaka-Mono"] == NSOrderedSame && desiredTraitsForNameMatch == 0) 205 return nameMatchedFont; 206 207 NSFontTraitMask traits = [fontManager traitsOfFont:nameMatchedFont]; 208 if ((traits & desiredTraitsForNameMatch) == desiredTraitsForNameMatch) 209 return [fontManager convertFont:nameMatchedFont toHaveTrait:desiredTraitsForNameMatch]; 210 211 availableFamily = [nameMatchedFont familyName]; 212 break; 213 } 214 } 215 } 216 217 // Found a family, now figure out what weight and traits to use. 218 BOOL choseFont = false; 219 int chosenWeight = 0; 220 NSFontTraitMask chosenTraits = 0; 221 NSString *chosenFullName = 0; 222 223 NSArray *fonts = [fontManager availableMembersOfFontFamily:availableFamily]; 224 unsigned n = [fonts count]; 225 unsigned i; 226 for (i = 0; i < n; i++) { 227 NSArray *fontInfo = [fonts objectAtIndex:i]; 228 229 // Array indices must be hard coded because of lame AppKit API. 230 NSString *fontFullName = [fontInfo objectAtIndex:0]; 231 NSInteger fontWeight = [[fontInfo objectAtIndex:2] intValue]; 232 fixUpWeight(fontWeight, fontFullName); 233 234 NSFontTraitMask fontTraits = [[fontInfo objectAtIndex:3] unsignedIntValue]; 235 236 BOOL newWinner; 237 if (!choseFont) 238 newWinner = acceptableChoice(desiredTraits, fontTraits); 239 else 240 newWinner = betterChoice(desiredTraits, desiredWeight, chosenTraits, chosenWeight, fontTraits, fontWeight); 241 242 if (newWinner) { 243 choseFont = YES; 244 chosenWeight = fontWeight; 245 chosenTraits = fontTraits; 246 chosenFullName = fontFullName; 247 248 if (chosenWeight == desiredWeight && (chosenTraits & IMPORTANT_FONT_TRAITS) == (desiredTraits & IMPORTANT_FONT_TRAITS)) 249 break; 250 } 251 } 252 253 if (!choseFont) 254 return nil; 255 256 NSFont *font = [NSFont fontWithName:chosenFullName size:size]; 257 258 if (!font) 259 return nil; 260 261 NSFontTraitMask actualTraits = 0; 262 if (desiredTraits & NSFontItalicTrait) 263 actualTraits = [fontManager traitsOfFont:font]; 264 int actualWeight = [fontManager weightOfFont:font]; 265 266 bool syntheticBold = desiredWeight >= 7 && actualWeight < 7; 267 bool syntheticOblique = (desiredTraits & NSFontItalicTrait) && !(actualTraits & NSFontItalicTrait); 268 269 // There are some malformed fonts that will be correctly returned by -fontWithFamily:traits:weight:size: as a match for a particular trait, 270 // though -[NSFontManager traitsOfFont:] incorrectly claims the font does not have the specified trait. This could result in applying 271 // 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 272 // 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 273 // the same family without those traits (to apply the synthetic traits to later). 274 NSFontTraitMask nonSyntheticTraits = desiredTraits; 275 276 if (syntheticBold) 277 nonSyntheticTraits &= ~NSBoldFontMask; 278 279 if (syntheticOblique) 280 nonSyntheticTraits &= ~NSItalicFontMask; 281 282 if (nonSyntheticTraits != desiredTraits) { 283 NSFont *fontWithoutSyntheticTraits = [fontManager fontWithFamily:availableFamily traits:nonSyntheticTraits weight:chosenWeight size:size]; 284 if (fontWithoutSyntheticTraits) 285 font = fontWithoutSyntheticTraits; 286 } 287 288 return font; 289} 290 291+ (NSFont *)fontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits weight:(int)desiredWeight size:(float)size 292{ 293 NSFont *font = [self internalFontWithFamily:desiredFamily traits:desiredTraits weight:desiredWeight size:size]; 294 if (font) 295 return font; 296 297 // Auto activate the font before looking for it a second time. 298 // Ignore the result because we want to use our own algorithm to actually find the font. 299 [NSFont fontWithName:desiredFamily size:size]; 300 301 return [self internalFontWithFamily:desiredFamily traits:desiredTraits weight:desiredWeight size:size]; 302} 303 304+ (NSFont *)fontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits size:(float)size 305{ 306 int desiredWeight = (desiredTraits & NSBoldFontMask) ? 9 : 5; 307 return [self fontWithFamily:desiredFamily traits:desiredTraits weight:desiredWeight size:size]; 308} 309 310@end 311