/* * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. * Copyright (C) 2007 Nicholas Shanks * * 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. */ #import "config.h" #import "WebFontCache.h" #import "FontTraitsMask.h" #import #import #import #import using namespace WebCore; #ifdef BUILDING_ON_TIGER typedef int NSInteger; #endif #define SYNTHESIZED_FONT_TRAITS (NSBoldFontMask | NSItalicFontMask) #define IMPORTANT_FONT_TRAITS (0 \ | NSCompressedFontMask \ | NSCondensedFontMask \ | NSExpandedFontMask \ | NSItalicFontMask \ | NSNarrowFontMask \ | NSPosterFontMask \ | NSSmallCapsFontMask \ ) static BOOL acceptableChoice(NSFontTraitMask desiredTraits, NSFontTraitMask candidateTraits) { desiredTraits &= ~SYNTHESIZED_FONT_TRAITS; return (candidateTraits & desiredTraits) == desiredTraits; } static BOOL betterChoice(NSFontTraitMask desiredTraits, int desiredWeight, NSFontTraitMask chosenTraits, int chosenWeight, NSFontTraitMask candidateTraits, int candidateWeight) { if (!acceptableChoice(desiredTraits, candidateTraits)) 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, 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; } int chosenWeightDeltaMagnitude = abs(chosenWeight - desiredWeight); int candidateWeightDeltaMagnitude = abs(candidateWeight - desiredWeight); // If both are the same distance from the desired weight, prefer the candidate if it is further from medium. if (chosenWeightDeltaMagnitude == candidateWeightDeltaMagnitude) return abs(candidateWeight - 6) > abs(chosenWeight - 6); // Otherwise, prefer the one closer to the desired weight. return candidateWeightDeltaMagnitude < chosenWeightDeltaMagnitude; } // Workaround for . static inline void fixUpWeight(NSInteger& weight, NSString *fontName) { #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) UNUSED_PARAM(weight); UNUSED_PARAM(fontName); #else if (weight == 3 && [fontName rangeOfString:@"ultralight" options:NSCaseInsensitiveSearch | NSBackwardsSearch | NSLiteralSearch].location != NSNotFound) weight = 2; #endif } static inline FontTraitsMask toTraitsMask(NSFontTraitMask appKitTraits, NSInteger appKitWeight) { return static_cast(((appKitTraits & NSFontItalicTrait) ? FontStyleItalicMask : FontStyleNormalMask) | FontVariantNormalMask | (appKitWeight == 1 ? FontWeight100Mask : appKitWeight == 2 ? FontWeight200Mask : appKitWeight <= 4 ? FontWeight300Mask : appKitWeight == 5 ? FontWeight400Mask : appKitWeight == 6 ? FontWeight500Mask : appKitWeight <= 8 ? FontWeight600Mask : appKitWeight == 9 ? FontWeight700Mask : appKitWeight <= 11 ? FontWeight800Mask : FontWeight900Mask)); } @implementation WebFontCache + (void)getTraits:(Vector&)traitsMasks inFamily:(NSString *)desiredFamily { NSFontManager *fontManager = [NSFontManager sharedFontManager]; NSEnumerator *e = [[fontManager availableFontFamilies] objectEnumerator]; NSString *availableFamily; while ((availableFamily = [e nextObject])) { if ([desiredFamily caseInsensitiveCompare:availableFamily] == NSOrderedSame) break; } if (!availableFamily) { // Match by PostScript name. NSEnumerator *availableFonts = [[fontManager availableFonts] objectEnumerator]; NSString *availableFont; while ((availableFont = [availableFonts nextObject])) { if ([desiredFamily caseInsensitiveCompare:availableFont] == NSOrderedSame) { NSFont *font = [NSFont fontWithName:availableFont size:10]; NSInteger weight = [fontManager weightOfFont:font]; fixUpWeight(weight, desiredFamily); traitsMasks.append(toTraitsMask([fontManager traitsOfFont:font], weight)); break; } } return; } NSArray *fonts = [fontManager availableMembersOfFontFamily:availableFamily]; unsigned n = [fonts count]; unsigned i; for (i = 0; i < n; i++) { NSArray *fontInfo = [fonts objectAtIndex:i]; // Array indices must be hard coded because of lame AppKit API. NSString *fontFullName = [fontInfo objectAtIndex:0]; NSInteger fontWeight = [[fontInfo objectAtIndex:2] intValue]; fixUpWeight(fontWeight, fontFullName); NSFontTraitMask fontTraits = [[fontInfo objectAtIndex:3] unsignedIntValue]; traitsMasks.append(toTraitsMask(fontTraits, fontWeight)); } } // 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 *)internalFontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits weight:(int)desiredWeight size:(float)size { NSFontManager *fontManager = [NSFontManager sharedFontManager]; // 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])) { if ([desiredFamily caseInsensitiveCompare:availableFamily] == NSOrderedSame) break; } if (!availableFamily) { // Match by PostScript name. NSEnumerator *availableFonts = [[fontManager availableFonts] objectEnumerator]; NSString *availableFont; NSFont *nameMatchedFont = nil; NSFontTraitMask desiredTraitsForNameMatch = desiredTraits | (desiredWeight >= 7 ? NSBoldFontMask : 0); while ((availableFont = [availableFonts nextObject])) { if ([desiredFamily caseInsensitiveCompare:availableFont] == NSOrderedSame) { nameMatchedFont = [NSFont fontWithName:availableFont size:size]; // Special case Osaka-Mono. According to , we need to // treat Osaka-Mono as fixed pitch. if ([desiredFamily caseInsensitiveCompare:@"Osaka-Mono"] == NSOrderedSame && desiredTraitsForNameMatch == 0) return nameMatchedFont; NSFontTraitMask traits = [fontManager traitsOfFont:nameMatchedFont]; if ((traits & desiredTraitsForNameMatch) == desiredTraitsForNameMatch) return [fontManager convertFont:nameMatchedFont toHaveTrait:desiredTraitsForNameMatch]; availableFamily = [nameMatchedFont familyName]; break; } } } // Found a family, now figure out what weight and traits to use. BOOL choseFont = false; int chosenWeight = 0; NSFontTraitMask chosenTraits = 0; NSString *chosenFullName = 0; NSArray *fonts = [fontManager availableMembersOfFontFamily:availableFamily]; unsigned n = [fonts count]; unsigned i; for (i = 0; i < n; i++) { NSArray *fontInfo = [fonts objectAtIndex:i]; // Array indices must be hard coded because of lame AppKit API. NSString *fontFullName = [fontInfo objectAtIndex:0]; NSInteger fontWeight = [[fontInfo objectAtIndex:2] intValue]; fixUpWeight(fontWeight, fontFullName); NSFontTraitMask fontTraits = [[fontInfo objectAtIndex:3] unsignedIntValue]; BOOL newWinner; if (!choseFont) newWinner = acceptableChoice(desiredTraits, fontTraits); else newWinner = betterChoice(desiredTraits, desiredWeight, chosenTraits, chosenWeight, fontTraits, fontWeight); if (newWinner) { choseFont = YES; chosenWeight = fontWeight; chosenTraits = fontTraits; chosenFullName = fontFullName; if (chosenWeight == desiredWeight && (chosenTraits & IMPORTANT_FONT_TRAITS) == (desiredTraits & IMPORTANT_FONT_TRAITS)) break; } } if (!choseFont) return nil; NSFont *font = [NSFont fontWithName:chosenFullName size:size]; if (!font) return nil; NSFontTraitMask actualTraits = 0; if (desiredTraits & NSFontItalicTrait) actualTraits = [fontManager traitsOfFont:font]; int actualWeight = [fontManager weightOfFont:font]; bool syntheticBold = desiredWeight >= 7 && actualWeight < 7; bool syntheticOblique = (desiredTraits & NSFontItalicTrait) && !(actualTraits & NSFontItalicTrait); // 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 // synthetic bold on top of an already-bold font, as reported in . To work around this // 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; if (syntheticBold) nonSyntheticTraits &= ~NSBoldFontMask; if (syntheticOblique) nonSyntheticTraits &= ~NSItalicFontMask; if (nonSyntheticTraits != desiredTraits) { NSFont *fontWithoutSyntheticTraits = [fontManager fontWithFamily:availableFamily traits:nonSyntheticTraits weight:chosenWeight size:size]; if (fontWithoutSyntheticTraits) font = fontWithoutSyntheticTraits; } return font; } + (NSFont *)fontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits weight:(int)desiredWeight size:(float)size { #ifndef BUILDING_ON_TIGER NSFont *font = [self internalFontWithFamily:desiredFamily traits:desiredTraits weight:desiredWeight 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 weight:desiredWeight size:size]; } + (NSFont *)fontWithFamily:(NSString *)desiredFamily traits:(NSFontTraitMask)desiredTraits size:(float)size { int desiredWeight = (desiredTraits & NSBoldFontMask) ? 9 : 5; return [self fontWithFamily:desiredFamily traits:desiredTraits weight:desiredWeight size:size]; } @end