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