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