blob: cef19ba2439aa1551180f19baab6b23176ff4cfd [file] [log] [blame]
/*
* Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
* Copyright (C) 2007 Nicholas Shanks <webkit@nickshanks.com>
*
* 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 "platform/fonts/mac/FontFamilyMatcherMac.h"
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#import <math.h>
#include "platform/fonts/FontTraits.h"
#include "platform/LayoutTestSupport.h"
#include "platform/mac/VersionUtilMac.h"
#import "wtf/HashSet.h"
#import "wtf/text/AtomicStringHash.h"
@interface NSFont (YosemiteAdditions)
+ (NSFont*)systemFontOfSize:(CGFloat)size weight:(CGFloat)weight;
@end
namespace {
static CGFloat toYosemiteFontWeight(blink::FontWeight fontWeight) {
static uint64_t nsFontWeights[] = {
0xbfe99999a0000000, // NSFontWeightUltraLight
0xbfe3333340000000, // NSFontWeightThin
0xbfd99999a0000000, // NSFontWeightLight
0x0000000000000000, // NSFontWeightRegular
0x3fcd70a3e0000000, // NSFontWeightMedium
0x3fd3333340000000, // NSFontWeightSemibold
0x3fd99999a0000000, // NSFontWeightBold
0x3fe1eb8520000000, // NSFontWeightHeavy
0x3fe3d70a40000000, // NSFontWeightBlack
};
ASSERT(fontWeight >= 0 && fontWeight <= 8);
CGFloat* weight = reinterpret_cast<CGFloat*>(&nsFontWeights[fontWeight]);
return *weight;
}
}
namespace blink {
const NSFontTraitMask SYNTHESIZED_FONT_TRAITS =
(NSBoldFontMask | NSItalicFontMask);
const NSFontTraitMask IMPORTANT_FONT_TRAITS =
(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;
}
// 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* MatchNSFontFamily(NSString* desiredFamily,
NSFontTraitMask desiredTraits,
FontWeight desiredWeight,
float size) {
if ([desiredFamily isEqualToString:@"BlinkMacSystemFont"]) {
// On OSX 10.9, the default system font depends on the SDK version. When
// compiled against the OSX 10.10 SDK, the font is .LucidaGrandeUI. When
// compiled against the OSX 10.6 SDK, the font is Lucida Grande. Layout
// tests don't support different expectations based on the SDK version,
// so force layout tests to use "Lucida Grande". Once the 10.10 SDK
// switch is made, this should be changed to return .LucidaGrandeUI and
// the Layout Expectations should be updated. http://crbug.com/515836.
if (LayoutTestSupport::isRunningLayoutTest() && IsOS10_9()) {
if (desiredWeight >= blink::FontWeightBold)
return [NSFont fontWithName:@"Lucida Grande Bold" size:size];
else
return [NSFont fontWithName:@"Lucida Grande" size:size];
}
NSFont* font = nil;
if (IsOS10_9()) {
// On older OSX versions, only bold and regular are available.
if (desiredWeight >= blink::FontWeightBold)
font = [NSFont boldSystemFontOfSize:size];
else
font = [NSFont systemFontOfSize:size];
} else {
// On OSX 10.10+, the default system font has more weights.
font = [NSFont systemFontOfSize:size
weight:toYosemiteFontWeight(desiredWeight)];
}
if (desiredTraits & IMPORTANT_FONT_TRAITS)
font = [[NSFontManager sharedFontManager] convertFont:font
toHaveTrait:desiredTraits];
return font;
}
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;
}
int appKitFontWeight = toAppKitFontWeight(desiredWeight);
if (!availableFamily) {
// Match by PostScript name.
NSEnumerator* availableFonts =
[[fontManager availableFonts] objectEnumerator];
NSString* availableFont;
NSFont* nameMatchedFont = nil;
NSFontTraitMask desiredTraitsForNameMatch =
desiredTraits | (appKitFontWeight >= 7 ? NSBoldFontMask : 0);
while ((availableFont = [availableFonts nextObject])) {
if ([desiredFamily caseInsensitiveCompare:availableFont] ==
NSOrderedSame) {
nameMatchedFont = [NSFont fontWithName:availableFont size:size];
// Special case Osaka-Mono. According to <rdar://problem/3999467>, 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];
NSFontTraitMask fontTraits = [[fontInfo objectAtIndex:3] unsignedIntValue];
BOOL newWinner;
if (!choseFont)
newWinner = acceptableChoice(desiredTraits, fontTraits);
else
newWinner = betterChoice(desiredTraits, appKitFontWeight, chosenTraits,
chosenWeight, fontTraits, fontWeight);
if (newWinner) {
choseFont = YES;
chosenWeight = fontWeight;
chosenTraits = fontTraits;
chosenFullName = fontFullName;
if (chosenWeight == appKitFontWeight &&
(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 = appKitFontWeight >= 7 && actualWeight < 7;
bool syntheticItalic = (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
// <http://bugs.webkit.org/show_bug.cgi?id=6146>. 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 (syntheticItalic)
nonSyntheticTraits &= ~NSItalicFontMask;
if (nonSyntheticTraits != desiredTraits) {
NSFont* fontWithoutSyntheticTraits =
[fontManager fontWithFamily:availableFamily
traits:nonSyntheticTraits
weight:chosenWeight
size:size];
if (fontWithoutSyntheticTraits)
font = fontWithoutSyntheticTraits;
}
return font;
}
int toAppKitFontWeight(FontWeight fontWeight) {
static int appKitFontWeights[] = {
2, // FontWeight100
3, // FontWeight200
4, // FontWeight300
5, // FontWeight400
6, // FontWeight500
8, // FontWeight600
9, // FontWeight700
10, // FontWeight800
12, // FontWeight900
};
return appKitFontWeights[fontWeight];
}
} // namespace blink