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 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 "FontCache.h"
32
33#if !PLATFORM(IOS)
34
35#import "Font.h"
36#import "SimpleFontData.h"
37#import "FontPlatformData.h"
38#import "WebCoreSystemInterface.h"
39#import "WebFontCache.h"
40#import <AppKit/AppKit.h>
41#import <wtf/MainThread.h>
42#import <wtf/StdLibExtras.h>
43
44
45namespace WebCore {
46
47// The "void*" parameter makes the function match the prototype for callbacks from callOnMainThread.
48static void invalidateFontCache(void*)
49{
50    if (!isMainThread()) {
51        callOnMainThread(&invalidateFontCache, 0);
52        return;
53    }
54    fontCache().invalidate();
55}
56
57static void fontCacheRegisteredFontsChangedNotificationCallback(CFNotificationCenterRef, void* observer, CFStringRef name, const void *, CFDictionaryRef)
58{
59    ASSERT_UNUSED(observer, observer == &fontCache());
60    ASSERT_UNUSED(name, CFEqual(name, kCTFontManagerRegisteredFontsChangedNotification));
61    invalidateFontCache(0);
62}
63
64void FontCache::platformInit()
65{
66    wkSetUpFontCache();
67    CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), this, fontCacheRegisteredFontsChangedNotificationCallback, kCTFontManagerRegisteredFontsChangedNotification, 0, CFNotificationSuspensionBehaviorDeliverImmediately);
68}
69
70static int toAppKitFontWeight(FontWeight fontWeight)
71{
72    static int appKitFontWeights[] = {
73        2,  // FontWeight100
74        3,  // FontWeight200
75        4,  // FontWeight300
76        5,  // FontWeight400
77        6,  // FontWeight500
78        8,  // FontWeight600
79        9,  // FontWeight700
80        10, // FontWeight800
81        12, // FontWeight900
82    };
83    return appKitFontWeights[fontWeight];
84}
85
86static inline bool isAppKitFontWeightBold(NSInteger appKitFontWeight)
87{
88    return appKitFontWeight >= 7;
89}
90
91PassRefPtr<SimpleFontData> FontCache::systemFallbackForCharacters(const FontDescription& description, const SimpleFontData* originalFontData, bool isPlatformFont, const UChar* characters, int length)
92{
93    UChar32 character;
94    U16_GET(characters, 0, 0, length, character);
95    const FontPlatformData& platformData = originalFontData->platformData();
96    NSFont *nsFont = platformData.font();
97
98    NSString *string = [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(characters) length:length freeWhenDone:NO];
99    NSFont *substituteFont = wkGetFontInLanguageForRange(nsFont, string, NSMakeRange(0, length));
100    [string release];
101
102    if (!substituteFont && length == 1)
103        substituteFont = wkGetFontInLanguageForCharacter(nsFont, characters[0]);
104    if (!substituteFont)
105        return 0;
106
107    // Use the family name from the AppKit-supplied substitute font, requesting the
108    // traits, weight, and size we want. One way this does better than the original
109    // AppKit request is that it takes synthetic bold and oblique into account.
110    // But it does create the possibility that we could end up with a font that
111    // doesn't actually cover the characters we need.
112
113    NSFontManager *fontManager = [NSFontManager sharedFontManager];
114
115    NSFontTraitMask traits;
116    NSInteger weight;
117    CGFloat size;
118
119    if (nsFont) {
120        traits = [fontManager traitsOfFont:nsFont];
121        if (platformData.m_syntheticBold)
122            traits |= NSBoldFontMask;
123        if (platformData.m_syntheticOblique)
124            traits |= NSFontItalicTrait;
125        weight = [fontManager weightOfFont:nsFont];
126        size = [nsFont pointSize];
127    } else {
128        // For custom fonts nsFont is nil.
129        traits = description.italic() ? NSFontItalicTrait : 0;
130        weight = toAppKitFontWeight(description.weight());
131        size = description.computedPixelSize();
132    }
133
134    NSFontTraitMask substituteFontTraits = [fontManager traitsOfFont:substituteFont];
135    NSInteger substituteFontWeight = [fontManager weightOfFont:substituteFont];
136
137    if (traits != substituteFontTraits || weight != substituteFontWeight || !nsFont) {
138        if (NSFont *bestVariation = [fontManager fontWithFamily:[substituteFont familyName] traits:traits weight:weight size:size]) {
139            if (!nsFont || (([fontManager traitsOfFont:bestVariation] != substituteFontTraits || [fontManager weightOfFont:bestVariation] != substituteFontWeight)
140                && [[bestVariation coveredCharacterSet] longCharacterIsMember:character]))
141                substituteFont = bestVariation;
142        }
143    }
144
145    substituteFont = description.usePrinterFont() ? [substituteFont printerFont] : [substituteFont screenFont];
146
147    substituteFontTraits = [fontManager traitsOfFont:substituteFont];
148    substituteFontWeight = [fontManager weightOfFont:substituteFont];
149
150    FontPlatformData alternateFont(substituteFont, platformData.size(), platformData.isPrinterFont(),
151        !isPlatformFont && isAppKitFontWeightBold(weight) && !isAppKitFontWeightBold(substituteFontWeight),
152        !isPlatformFont && (traits & NSFontItalicTrait) && !(substituteFontTraits & NSFontItalicTrait),
153        platformData.m_orientation);
154
155    return getCachedFontData(&alternateFont, DoNotRetain);
156}
157
158PassRefPtr<SimpleFontData> FontCache::similarFontPlatformData(const FontDescription& description)
159{
160    // Attempt to find an appropriate font using a match based on
161    // the presence of keywords in the the requested names.  For example, we'll
162    // match any name that contains "Arabic" to Geeza Pro.
163    RefPtr<SimpleFontData> simpleFontData;
164    for (unsigned i = 0; i < description.familyCount(); ++i) {
165        const AtomicString& family = description.familyAt(i);
166        if (family.isEmpty())
167            continue;
168        static String* matchWords[3] = { new String("Arabic"), new String("Pashto"), new String("Urdu") };
169        DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, geezaStr, ("Geeza Pro", AtomicString::ConstructFromLiteral));
170        for (int j = 0; j < 3 && !simpleFontData; ++j)
171            if (family.contains(*matchWords[j], false))
172                simpleFontData = getCachedFontData(description, geezaStr);
173    }
174    return simpleFontData.release();
175}
176
177PassRefPtr<SimpleFontData> FontCache::getLastResortFallbackFont(const FontDescription& fontDescription, ShouldRetain shouldRetain)
178{
179    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, timesStr, ("Times", AtomicString::ConstructFromLiteral));
180
181    // FIXME: Would be even better to somehow get the user's default font here.  For now we'll pick
182    // the default that the user would get without changing any prefs.
183    RefPtr<SimpleFontData> simpleFontData = getCachedFontData(fontDescription, timesStr, false, shouldRetain);
184    if (simpleFontData)
185        return simpleFontData.release();
186
187    // The Times fallback will almost always work, but in the highly unusual case where
188    // the user doesn't have it, we fall back on Lucida Grande because that's
189    // guaranteed to be there, according to Nathan Taylor. This is good enough
190    // to avoid a crash at least.
191    DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, lucidaGrandeStr, ("Lucida Grande", AtomicString::ConstructFromLiteral));
192    return getCachedFontData(fontDescription, lucidaGrandeStr, false, shouldRetain);
193}
194
195void FontCache::getTraitsInFamily(const AtomicString& familyName, Vector<unsigned>& traitsMasks)
196{
197    [WebFontCache getTraits:traitsMasks inFamily:familyName];
198}
199
200PassOwnPtr<FontPlatformData> FontCache::createFontPlatformData(const FontDescription& fontDescription, const AtomicString& family)
201{
202    NSFontTraitMask traits = fontDescription.italic() ? NSFontItalicTrait : 0;
203    NSInteger weight = toAppKitFontWeight(fontDescription.weight());
204    float size = fontDescription.computedPixelSize();
205
206    NSFont *nsFont = [WebFontCache fontWithFamily:family traits:traits weight:weight size:size];
207    if (!nsFont)
208        return nullptr;
209
210    NSFontManager *fontManager = [NSFontManager sharedFontManager];
211    NSFontTraitMask actualTraits = 0;
212    if (fontDescription.italic())
213        actualTraits = [fontManager traitsOfFont:nsFont];
214    NSInteger actualWeight = [fontManager weightOfFont:nsFont];
215
216    NSFont *platformFont = fontDescription.usePrinterFont() ? [nsFont printerFont] : [nsFont screenFont];
217    bool syntheticBold = isAppKitFontWeightBold(weight) && !isAppKitFontWeightBold(actualWeight);
218    bool syntheticOblique = (traits & NSFontItalicTrait) && !(actualTraits & NSFontItalicTrait);
219
220    OwnPtr<FontPlatformData> platformData = adoptPtr(new FontPlatformData(platformFont, size, fontDescription.usePrinterFont(), syntheticBold, syntheticOblique, fontDescription.orientation(), fontDescription.widthVariant()));
221    return platformData.release();
222}
223
224} // namespace WebCore
225
226#endif // !PLATFORM(IOS)
227