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 "platform/mac/WebFontCache.h" 32 33#import <AppKit/AppKit.h> 34#import <Foundation/Foundation.h> 35#import <math.h> 36 37#define SYNTHESIZED_FONT_TRAITS (NSBoldFontMask | NSItalicFontMask) 38 39#define IMPORTANT_FONT_TRAITS (0 \ 40 | NSCompressedFontMask \ 41 | NSCondensedFontMask \ 42 | NSExpandedFontMask \ 43 | NSItalicFontMask \ 44 | NSNarrowFontMask \ 45 | NSPosterFontMask \ 46 | NSSmallCapsFontMask \ 47) 48 49static BOOL acceptableChoice(NSFontTraitMask desiredTraits, NSFontTraitMask candidateTraits) 50{ 51 desiredTraits &= ~SYNTHESIZED_FONT_TRAITS; 52 return (candidateTraits & desiredTraits) == desiredTraits; 53} 54 55static BOOL betterChoice(NSFontTraitMask desiredTraits, int desiredWeight, 56 NSFontTraitMask chosenTraits, int chosenWeight, 57 NSFontTraitMask candidateTraits, int candidateWeight) 58{ 59 if (!acceptableChoice(desiredTraits, candidateTraits)) 60 return NO; 61 62 // A list of the traits we care about. 63 // The top item in the list is the worst trait to mismatch; if a font has this 64 // and we didn't ask for it, we'd prefer any other font in the family. 65 const NSFontTraitMask masks[] = { 66 NSPosterFontMask, 67 NSSmallCapsFontMask, 68 NSItalicFontMask, 69 NSCompressedFontMask, 70 NSCondensedFontMask, 71 NSExpandedFontMask, 72 NSNarrowFontMask, 73 0 74 }; 75 76 int i = 0; 77 NSFontTraitMask mask; 78 while ((mask = masks[i++])) { 79 BOOL desired = (desiredTraits & mask) != 0; 80 BOOL chosenHasUnwantedTrait = desired != ((chosenTraits & mask) != 0); 81 BOOL candidateHasUnwantedTrait = desired != ((candidateTraits & mask) != 0); 82 if (!candidateHasUnwantedTrait && chosenHasUnwantedTrait) 83 return YES; 84 if (!chosenHasUnwantedTrait && candidateHasUnwantedTrait) 85 return NO; 86 } 87 88 int chosenWeightDeltaMagnitude = abs(chosenWeight - desiredWeight); 89 int candidateWeightDeltaMagnitude = abs(candidateWeight - desiredWeight); 90 91 // If both are the same distance from the desired weight, prefer the candidate if it is further from medium. 92 if (chosenWeightDeltaMagnitude == candidateWeightDeltaMagnitude) 93 return abs(candidateWeight - 6) > abs(chosenWeight - 6); 94 95 // Otherwise, prefer the one closer to the desired weight. 96 return candidateWeightDeltaMagnitude < chosenWeightDeltaMagnitude; 97} 98 99@implementation WebFontCache 100 101// Family name is somewhat of a misnomer here. We first attempt to find an exact match 102// comparing the desiredFamily to the PostScript name of the installed fonts. If that fails 103// we then do a search based on the family names of the installed fonts. 104+ (NSFont *)internalFontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits weight:(int)desiredWeight size:(float)size 105{ 106 NSFontManager *fontManager = [NSFontManager sharedFontManager]; 107 108 // Do a simple case insensitive search for a matching font family. 109 // NSFontManager requires exact name matches. 110 // This addresses the problem of matching arial to Arial, etc., but perhaps not all the issues. 111 NSEnumerator *e = [[fontManager availableFontFamilies] objectEnumerator]; 112 NSString *availableFamily; 113 while ((availableFamily = [e nextObject])) { 114 if ([desiredFamily caseInsensitiveCompare:availableFamily] == NSOrderedSame) 115 break; 116 } 117 118 if (!availableFamily) { 119 // Match by PostScript name. 120 NSEnumerator *availableFonts = [[fontManager availableFonts] objectEnumerator]; 121 NSString *availableFont; 122 NSFont *nameMatchedFont = nil; 123 NSFontTraitMask desiredTraitsForNameMatch = desiredTraits | (desiredWeight >= 7 ? NSBoldFontMask : 0); 124 while ((availableFont = [availableFonts nextObject])) { 125 if ([desiredFamily caseInsensitiveCompare:availableFont] == NSOrderedSame) { 126 nameMatchedFont = [NSFont fontWithName:availableFont size:size]; 127 128 // Special case Osaka-Mono. According to <rdar://problem/3999467>, we need to 129 // treat Osaka-Mono as fixed pitch. 130 if ([desiredFamily caseInsensitiveCompare:@"Osaka-Mono"] == NSOrderedSame && desiredTraitsForNameMatch == 0) 131 return nameMatchedFont; 132 133 NSFontTraitMask traits = [fontManager traitsOfFont:nameMatchedFont]; 134 if ((traits & desiredTraitsForNameMatch) == desiredTraitsForNameMatch) 135 return [fontManager convertFont:nameMatchedFont toHaveTrait:desiredTraitsForNameMatch]; 136 137 availableFamily = [nameMatchedFont familyName]; 138 break; 139 } 140 } 141 } 142 143 // Found a family, now figure out what weight and traits to use. 144 BOOL choseFont = false; 145 int chosenWeight = 0; 146 NSFontTraitMask chosenTraits = 0; 147 NSString *chosenFullName = 0; 148 149 NSArray *fonts = [fontManager availableMembersOfFontFamily:availableFamily]; 150 unsigned n = [fonts count]; 151 unsigned i; 152 for (i = 0; i < n; i++) { 153 NSArray *fontInfo = [fonts objectAtIndex:i]; 154 155 // Array indices must be hard coded because of lame AppKit API. 156 NSString *fontFullName = [fontInfo objectAtIndex:0]; 157 NSInteger fontWeight = [[fontInfo objectAtIndex:2] intValue]; 158 159 NSFontTraitMask fontTraits = [[fontInfo objectAtIndex:3] unsignedIntValue]; 160 161 BOOL newWinner; 162 if (!choseFont) 163 newWinner = acceptableChoice(desiredTraits, fontTraits); 164 else 165 newWinner = betterChoice(desiredTraits, desiredWeight, chosenTraits, chosenWeight, fontTraits, fontWeight); 166 167 if (newWinner) { 168 choseFont = YES; 169 chosenWeight = fontWeight; 170 chosenTraits = fontTraits; 171 chosenFullName = fontFullName; 172 173 if (chosenWeight == desiredWeight && (chosenTraits & IMPORTANT_FONT_TRAITS) == (desiredTraits & IMPORTANT_FONT_TRAITS)) 174 break; 175 } 176 } 177 178 if (!choseFont) 179 return nil; 180 181 NSFont *font = [NSFont fontWithName:chosenFullName size:size]; 182 183 if (!font) 184 return nil; 185 186 NSFontTraitMask actualTraits = 0; 187 if (desiredTraits & NSFontItalicTrait) 188 actualTraits = [fontManager traitsOfFont:font]; 189 int actualWeight = [fontManager weightOfFont:font]; 190 191 bool syntheticBold = desiredWeight >= 7 && actualWeight < 7; 192 bool syntheticItalic = (desiredTraits & NSFontItalicTrait) && !(actualTraits & NSFontItalicTrait); 193 194 // There are some malformed fonts that will be correctly returned by -fontWithFamily:traits:weight:size: as a match for a particular trait, 195 // though -[NSFontManager traitsOfFont:] incorrectly claims the font does not have the specified trait. This could result in applying 196 // 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 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 198 // the same family without those traits (to apply the synthetic traits to later). 199 NSFontTraitMask nonSyntheticTraits = desiredTraits; 200 201 if (syntheticBold) 202 nonSyntheticTraits &= ~NSBoldFontMask; 203 204 if (syntheticItalic) 205 nonSyntheticTraits &= ~NSItalicFontMask; 206 207 if (nonSyntheticTraits != desiredTraits) { 208 NSFont *fontWithoutSyntheticTraits = [fontManager fontWithFamily:availableFamily traits:nonSyntheticTraits weight:chosenWeight size:size]; 209 if (fontWithoutSyntheticTraits) 210 font = fontWithoutSyntheticTraits; 211 } 212 213 return font; 214} 215 216+ (NSFont *)fontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits weight:(int)desiredWeight size:(float)size 217{ 218 NSFont *font = [self internalFontWithFamily:desiredFamily traits:desiredTraits weight:desiredWeight size:size]; 219 if (font) 220 return font; 221 222 // Auto activate the font before looking for it a second time. 223 // Ignore the result because we want to use our own algorithm to actually find the font. 224 [NSFont fontWithName:desiredFamily size:size]; 225 226 return [self internalFontWithFamily:desiredFamily traits:desiredTraits weight:desiredWeight size:size]; 227} 228 229+ (NSFont *)fontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits size:(float)size 230{ 231 int desiredWeight = (desiredTraits & NSBoldFontMask) ? 9 : 5; 232 return [self fontWithFamily:desiredFamily traits:desiredTraits weight:desiredWeight size:size]; 233} 234 235@end 236