1 /* 2 * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. 3 * Copyright (C) 2007 Nicholas Shanks <webkit (at) 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 "core/platform/graphics/FontCache.h" 32 33 #import <AppKit/AppKit.h> 34 #import "core/platform/graphics/Font.h" 35 #import "core/platform/graphics/FontPlatformData.h" 36 #import "core/platform/graphics/SimpleFontData.h" 37 #import "core/platform/mac/WebFontCache.h" 38 #import <wtf/MainThread.h> 39 #import <wtf/StdLibExtras.h> 40 41 // Forward declare Mac SPIs. 42 // Request for public API: rdar://13803570 43 @interface NSFont (WebKitSPI) 44 + (NSFont*)findFontLike:(NSFont*)font forString:(NSString*)string withRange:(NSRange)range inLanguage:(id)useNil; 45 + (NSFont*)findFontLike:(NSFont*)font forCharacter:(UniChar)uc inLanguage:(id)useNil; 46 @end 47 48 namespace WebCore { 49 50 // The "void*" parameter makes the function match the prototype for callbacks from callOnMainThread. 51 static void invalidateFontCache(void*) 52 { 53 if (!isMainThread()) { 54 callOnMainThread(&invalidateFontCache, 0); 55 return; 56 } 57 fontCache()->invalidate(); 58 } 59 60 static void fontCacheRegisteredFontsChangedNotificationCallback(CFNotificationCenterRef, void* observer, CFStringRef name, const void *, CFDictionaryRef) 61 { 62 ASSERT_UNUSED(observer, observer == fontCache()); 63 ASSERT_UNUSED(name, CFEqual(name, kCTFontManagerRegisteredFontsChangedNotification)); 64 invalidateFontCache(0); 65 } 66 67 void FontCache::platformInit() 68 { 69 CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), this, fontCacheRegisteredFontsChangedNotificationCallback, kCTFontManagerRegisteredFontsChangedNotification, 0, CFNotificationSuspensionBehaviorDeliverImmediately); 70 } 71 72 static int toAppKitFontWeight(FontWeight fontWeight) 73 { 74 static int appKitFontWeights[] = { 75 2, // FontWeight100 76 3, // FontWeight200 77 4, // FontWeight300 78 5, // FontWeight400 79 6, // FontWeight500 80 8, // FontWeight600 81 9, // FontWeight700 82 10, // FontWeight800 83 12, // FontWeight900 84 }; 85 return appKitFontWeights[fontWeight]; 86 } 87 88 static inline bool isAppKitFontWeightBold(NSInteger appKitFontWeight) 89 { 90 return appKitFontWeight >= 7; 91 } 92 93 PassRefPtr<SimpleFontData> FontCache::getFontDataForCharacter(const Font& font, UChar32 character) 94 { 95 // FIXME: We should fix getFallbackFamily to take a UChar32 96 // and remove this split-to-UChar16 code. 97 UChar codeUnits[2]; 98 int codeUnitsLength; 99 if (character <= 0xFFFF) { 100 codeUnits[0] = character; 101 codeUnitsLength = 1; 102 } else { 103 codeUnits[0] = U16_LEAD(character); 104 codeUnits[1] = U16_TRAIL(character); 105 codeUnitsLength = 2; 106 } 107 108 const FontPlatformData& platformData = font.fontDataAt(0)->fontDataForCharacter(character)->platformData(); 109 NSFont *nsFont = platformData.font(); 110 111 NSString *string = [[NSString alloc] initWithCharactersNoCopy:codeUnits length:codeUnitsLength freeWhenDone:NO]; 112 NSFont *substituteFont = [NSFont findFontLike:nsFont forString:string withRange:NSMakeRange(0, codeUnitsLength) inLanguage:nil]; 113 [string release]; 114 115 // FIXME: Remove this SPI usage: http://crbug.com/255122 116 if (!substituteFont && codeUnitsLength == 1) 117 substituteFont = [NSFont findFontLike:nsFont forCharacter:codeUnits[0] inLanguage:nil]; 118 if (!substituteFont) 119 return 0; 120 121 // Chromium can't render AppleColorEmoji. 122 if ([[substituteFont familyName] isEqual:@"Apple Color Emoji"]) 123 return 0; 124 125 // Use the family name from the AppKit-supplied substitute font, requesting the 126 // traits, weight, and size we want. One way this does better than the original 127 // AppKit request is that it takes synthetic bold and oblique into account. 128 // But it does create the possibility that we could end up with a font that 129 // doesn't actually cover the characters we need. 130 131 NSFontManager *fontManager = [NSFontManager sharedFontManager]; 132 133 NSFontTraitMask traits; 134 NSInteger weight; 135 CGFloat size; 136 137 if (nsFont) { 138 traits = [fontManager traitsOfFont:nsFont]; 139 if (platformData.m_syntheticBold) 140 traits |= NSBoldFontMask; 141 if (platformData.m_syntheticOblique) 142 traits |= NSFontItalicTrait; 143 weight = [fontManager weightOfFont:nsFont]; 144 size = [nsFont pointSize]; 145 } else { 146 // For custom fonts nsFont is nil. 147 traits = font.italic() ? NSFontItalicTrait : 0; 148 weight = toAppKitFontWeight(font.weight()); 149 size = font.pixelSize(); 150 } 151 152 NSFontTraitMask substituteFontTraits = [fontManager traitsOfFont:substituteFont]; 153 NSInteger substituteFontWeight = [fontManager weightOfFont:substituteFont]; 154 155 if (traits != substituteFontTraits || weight != substituteFontWeight || !nsFont) { 156 if (NSFont *bestVariation = [fontManager fontWithFamily:[substituteFont familyName] traits:traits weight:weight size:size]) { 157 if (!nsFont || (([fontManager traitsOfFont:bestVariation] != substituteFontTraits || [fontManager weightOfFont:bestVariation] != substituteFontWeight) 158 && [[bestVariation coveredCharacterSet] longCharacterIsMember:character])) 159 substituteFont = bestVariation; 160 } 161 } 162 163 substituteFont = font.fontDescription().usePrinterFont() ? [substituteFont printerFont] : [substituteFont screenFont]; 164 165 substituteFontTraits = [fontManager traitsOfFont:substituteFont]; 166 substituteFontWeight = [fontManager weightOfFont:substituteFont]; 167 168 FontPlatformData alternateFont(substituteFont, platformData.size(), platformData.isPrinterFont(), 169 !font.isPlatformFont() && isAppKitFontWeightBold(weight) && !isAppKitFontWeightBold(substituteFontWeight), 170 !font.isPlatformFont() && (traits & NSFontItalicTrait) && !(substituteFontTraits & NSFontItalicTrait), 171 platformData.m_orientation); 172 173 return getFontResourceData(&alternateFont, DoNotRetain); 174 } 175 176 PassRefPtr<SimpleFontData> FontCache::getSimilarFontPlatformData(const Font& font) 177 { 178 // Attempt to find an appropriate font using a match based on 179 // the presence of keywords in the the requested names. For example, we'll 180 // match any name that contains "Arabic" to Geeza Pro. 181 RefPtr<SimpleFontData> simpleFontData; 182 const FontFamily* currFamily = &font.fontDescription().family(); 183 while (currFamily && !simpleFontData) { 184 if (currFamily->family().length()) { 185 static String* matchWords[3] = { new String("Arabic"), new String("Pashto"), new String("Urdu") }; 186 DEFINE_STATIC_LOCAL(AtomicString, geezaStr, ("Geeza Pro", AtomicString::ConstructFromLiteral)); 187 for (int j = 0; j < 3 && !simpleFontData; ++j) 188 if (currFamily->family().contains(*matchWords[j], false)) 189 simpleFontData = getFontResourceData(font.fontDescription(), geezaStr); 190 } 191 currFamily = currFamily->next(); 192 } 193 194 return simpleFontData.release(); 195 } 196 197 PassRefPtr<SimpleFontData> FontCache::getLastResortFallbackFont(const FontDescription& fontDescription, ShouldRetain shouldRetain) 198 { 199 DEFINE_STATIC_LOCAL(AtomicString, timesStr, ("Times", AtomicString::ConstructFromLiteral)); 200 201 // FIXME: Would be even better to somehow get the user's default font here. For now we'll pick 202 // the default that the user would get without changing any prefs. 203 RefPtr<SimpleFontData> simpleFontData = getFontResourceData(fontDescription, timesStr, false, shouldRetain); 204 if (simpleFontData) 205 return simpleFontData.release(); 206 207 // The Times fallback will almost always work, but in the highly unusual case where 208 // the user doesn't have it, we fall back on Lucida Grande because that's 209 // guaranteed to be there, according to Nathan Taylor. This is good enough 210 // to avoid a crash at least. 211 DEFINE_STATIC_LOCAL(AtomicString, lucidaGrandeStr, ("Lucida Grande", AtomicString::ConstructFromLiteral)); 212 return getFontResourceData(fontDescription, lucidaGrandeStr, false, shouldRetain); 213 } 214 215 void FontCache::getTraitsInFamily(const AtomicString& familyName, Vector<unsigned>& traitsMasks) 216 { 217 [WebFontCache getTraits:traitsMasks inFamily:familyName]; 218 } 219 220 FontPlatformData* FontCache::createFontPlatformData(const FontDescription& fontDescription, const AtomicString& family) 221 { 222 NSFontTraitMask traits = fontDescription.italic() ? NSFontItalicTrait : 0; 223 NSInteger weight = toAppKitFontWeight(fontDescription.weight()); 224 float size = fontDescription.computedPixelSize(); 225 226 NSFont *nsFont = [WebFontCache fontWithFamily:family traits:traits weight:weight size:size]; 227 if (!nsFont) 228 return 0; 229 230 NSFontManager *fontManager = [NSFontManager sharedFontManager]; 231 NSFontTraitMask actualTraits = 0; 232 if (fontDescription.italic()) 233 actualTraits = [fontManager traitsOfFont:nsFont]; 234 NSInteger actualWeight = [fontManager weightOfFont:nsFont]; 235 236 NSFont *platformFont = fontDescription.usePrinterFont() ? [nsFont printerFont] : [nsFont screenFont]; 237 bool syntheticBold = isAppKitFontWeightBold(weight) && !isAppKitFontWeightBold(actualWeight); 238 bool syntheticOblique = (traits & NSFontItalicTrait) && !(actualTraits & NSFontItalicTrait); 239 240 // FontPlatformData::font() can be null for the case of Chromium out-of-process font loading. 241 // In that case, we don't want to use the platformData. 242 OwnPtr<FontPlatformData> platformData = adoptPtr(new FontPlatformData(platformFont, size, fontDescription.usePrinterFont(), syntheticBold, syntheticOblique, fontDescription.orientation(), fontDescription.widthVariant())); 243 if (!platformData->font()) 244 return 0; 245 return platformData.leakPtr(); 246 } 247 248 } // namespace WebCore 249