FontCache.cpp revision 81bc750723a18f21cd17d1b173cd2a4dda9cea6e
1/*
2 * Copyright (C) 2006, 2008 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#include "config.h"
31#include "FontCache.h"
32
33#include "Font.h"
34#include "FontFallbackList.h"
35#include "FontPlatformData.h"
36#include "FontSelector.h"
37#include <wtf/HashMap.h>
38#include <wtf/ListHashSet.h>
39#include <wtf/StdLibExtras.h>
40#include <wtf/text/StringHash.h>
41
42using namespace WTF;
43
44namespace WebCore {
45
46FontCache* fontCache()
47{
48    DEFINE_STATIC_LOCAL(FontCache, globalFontCache, ());
49    return &globalFontCache;
50}
51
52FontCache::FontCache()
53{
54}
55
56struct FontPlatformDataCacheKey {
57    WTF_MAKE_FAST_ALLOCATED;
58public:
59    FontPlatformDataCacheKey(const AtomicString& family = AtomicString(), unsigned size = 0, unsigned weight = 0, bool italic = false,
60                             bool isPrinterFont = false, FontRenderingMode renderingMode = NormalRenderingMode, FontOrientation orientation = Horizontal, FontWidthVariant widthVariant = RegularWidth)
61        : m_size(size)
62        , m_weight(weight)
63        , m_family(family)
64        , m_italic(italic)
65        , m_printerFont(isPrinterFont)
66        , m_renderingMode(renderingMode)
67        , m_orientation(orientation)
68        , m_widthVariant(widthVariant)
69    {
70    }
71
72    FontPlatformDataCacheKey(HashTableDeletedValueType) : m_size(hashTableDeletedSize()) { }
73    bool isHashTableDeletedValue() const { return m_size == hashTableDeletedSize(); }
74
75    bool operator==(const FontPlatformDataCacheKey& other) const
76    {
77        return equalIgnoringCase(m_family, other.m_family) && m_size == other.m_size &&
78               m_weight == other.m_weight && m_italic == other.m_italic && m_printerFont == other.m_printerFont &&
79               m_renderingMode == other.m_renderingMode && m_orientation == other.m_orientation && m_widthVariant == other.m_widthVariant;
80    }
81
82    unsigned m_size;
83    unsigned m_weight;
84    AtomicString m_family;
85    bool m_italic;
86    bool m_printerFont;
87    FontRenderingMode m_renderingMode;
88    FontOrientation m_orientation;
89    FontWidthVariant m_widthVariant;
90
91private:
92    static unsigned hashTableDeletedSize() { return 0xFFFFFFFFU; }
93};
94
95inline unsigned computeHash(const FontPlatformDataCacheKey& fontKey)
96{
97    unsigned hashCodes[5] = {
98        CaseFoldingHash::hash(fontKey.m_family),
99        fontKey.m_size,
100        fontKey.m_weight,
101        fontKey.m_widthVariant,
102        static_cast<unsigned>(fontKey.m_orientation) << 3 | static_cast<unsigned>(fontKey.m_italic) << 2 | static_cast<unsigned>(fontKey.m_printerFont) << 1 | static_cast<unsigned>(fontKey.m_renderingMode)
103    };
104    return WTF::StringHasher::createBlobHash<sizeof(hashCodes)>(hashCodes);
105}
106
107struct FontPlatformDataCacheKeyHash {
108    static unsigned hash(const FontPlatformDataCacheKey& font)
109    {
110        return computeHash(font);
111    }
112
113    static bool equal(const FontPlatformDataCacheKey& a, const FontPlatformDataCacheKey& b)
114    {
115        return a == b;
116    }
117
118    static const bool safeToCompareToEmptyOrDeleted = true;
119};
120
121struct FontPlatformDataCacheKeyTraits : WTF::GenericHashTraits<FontPlatformDataCacheKey> {
122    static const bool emptyValueIsZero = true;
123    static const FontPlatformDataCacheKey& emptyValue()
124    {
125        DEFINE_STATIC_LOCAL(FontPlatformDataCacheKey, key, (nullAtom));
126        return key;
127    }
128    static void constructDeletedValue(FontPlatformDataCacheKey& slot)
129    {
130        new (&slot) FontPlatformDataCacheKey(HashTableDeletedValue);
131    }
132    static bool isDeletedValue(const FontPlatformDataCacheKey& value)
133    {
134        return value.isHashTableDeletedValue();
135    }
136};
137
138typedef HashMap<FontPlatformDataCacheKey, FontPlatformData*, FontPlatformDataCacheKeyHash, FontPlatformDataCacheKeyTraits> FontPlatformDataCache;
139
140static FontPlatformDataCache* gFontPlatformDataCache = 0;
141
142static const AtomicString& alternateFamilyName(const AtomicString& familyName)
143{
144    // Alias Courier <-> Courier New
145    DEFINE_STATIC_LOCAL(AtomicString, courier, ("Courier"));
146    DEFINE_STATIC_LOCAL(AtomicString, courierNew, ("Courier New"));
147    if (equalIgnoringCase(familyName, courier))
148        return courierNew;
149#if !OS(WINDOWS)
150    // On Windows, Courier New (truetype font) is always present and
151    // Courier is a bitmap font. So, we don't want to map Courier New to
152    // Courier.
153    if (equalIgnoringCase(familyName, courierNew))
154        return courier;
155#endif
156
157    // Alias Times and Times New Roman.
158    DEFINE_STATIC_LOCAL(AtomicString, times, ("Times"));
159    DEFINE_STATIC_LOCAL(AtomicString, timesNewRoman, ("Times New Roman"));
160    if (equalIgnoringCase(familyName, times))
161        return timesNewRoman;
162    if (equalIgnoringCase(familyName, timesNewRoman))
163        return times;
164
165    // Alias Arial and Helvetica
166    DEFINE_STATIC_LOCAL(AtomicString, arial, ("Arial"));
167    DEFINE_STATIC_LOCAL(AtomicString, helvetica, ("Helvetica"));
168    if (equalIgnoringCase(familyName, arial))
169        return helvetica;
170    if (equalIgnoringCase(familyName, helvetica))
171        return arial;
172
173#if OS(WINDOWS)
174    // On Windows, bitmap fonts are blocked altogether so that we have to
175    // alias MS Sans Serif (bitmap font) -> Microsoft Sans Serif (truetype font)
176    DEFINE_STATIC_LOCAL(AtomicString, msSans, ("MS Sans Serif"));
177    DEFINE_STATIC_LOCAL(AtomicString, microsoftSans, ("Microsoft Sans Serif"));
178    if (equalIgnoringCase(familyName, msSans))
179        return microsoftSans;
180
181    // Alias MS Serif (bitmap) -> Times New Roman (truetype font). There's no
182    // 'Microsoft Sans Serif-equivalent' for Serif.
183    static AtomicString msSerif("MS Serif");
184    if (equalIgnoringCase(familyName, msSerif))
185        return timesNewRoman;
186#endif
187
188    return emptyAtom;
189}
190
191FontPlatformData* FontCache::getCachedFontPlatformData(const FontDescription& fontDescription,
192                                                       const AtomicString& familyName,
193                                                       bool checkingAlternateName)
194{
195    if (!gFontPlatformDataCache) {
196        gFontPlatformDataCache = new FontPlatformDataCache;
197        platformInit();
198    }
199
200    FontPlatformDataCacheKey key(familyName, fontDescription.computedPixelSize(), fontDescription.weight(), fontDescription.italic(),
201                                 fontDescription.usePrinterFont(), fontDescription.renderingMode(), fontDescription.orientation(), fontDescription.widthVariant());
202    FontPlatformData* result = 0;
203    bool foundResult;
204    FontPlatformDataCache::iterator it = gFontPlatformDataCache->find(key);
205    if (it == gFontPlatformDataCache->end()) {
206        result = createFontPlatformData(fontDescription, familyName);
207        gFontPlatformDataCache->set(key, result);
208        foundResult = result;
209    } else {
210        result = it->second;
211        foundResult = true;
212    }
213
214    if (!foundResult && !checkingAlternateName) {
215        // We were unable to find a font.  We have a small set of fonts that we alias to other names,
216        // e.g., Arial/Helvetica, Courier/Courier New, etc.  Try looking up the font under the aliased name.
217        const AtomicString& alternateName = alternateFamilyName(familyName);
218        if (!alternateName.isEmpty())
219            result = getCachedFontPlatformData(fontDescription, alternateName, true);
220        if (result)
221            gFontPlatformDataCache->set(key, new FontPlatformData(*result)); // Cache the result under the old name.
222    }
223
224    return result;
225}
226
227struct FontDataCacheKeyHash {
228    static unsigned hash(const FontPlatformData& platformData)
229    {
230        return platformData.hash();
231    }
232
233    static bool equal(const FontPlatformData& a, const FontPlatformData& b)
234    {
235        return a == b;
236    }
237
238    static const bool safeToCompareToEmptyOrDeleted = true;
239};
240
241struct FontDataCacheKeyTraits : WTF::GenericHashTraits<FontPlatformData> {
242    static const bool emptyValueIsZero = true;
243    static const bool needsDestruction = true;
244    static const FontPlatformData& emptyValue()
245    {
246        DEFINE_STATIC_LOCAL(FontPlatformData, key, (0.f, false, false));
247        return key;
248    }
249    static void constructDeletedValue(FontPlatformData& slot)
250    {
251        new (&slot) FontPlatformData(HashTableDeletedValue);
252    }
253    static bool isDeletedValue(const FontPlatformData& value)
254    {
255        return value.isHashTableDeletedValue();
256    }
257};
258
259typedef HashMap<FontPlatformData, pair<SimpleFontData*, unsigned>, FontDataCacheKeyHash, FontDataCacheKeyTraits> FontDataCache;
260
261static FontDataCache* gFontDataCache = 0;
262
263const int cMaxInactiveFontData = 120;  // Pretty Low Threshold
264const int cTargetInactiveFontData = 100;
265static ListHashSet<const SimpleFontData*>* gInactiveFontData = 0;
266
267SimpleFontData* FontCache::getCachedFontData(const FontDescription& fontDescription, const AtomicString& family, bool checkingAlternateName)
268{
269    FontPlatformData* platformData = getCachedFontPlatformData(fontDescription, family, checkingAlternateName);
270    if (!platformData)
271        return 0;
272
273    return getCachedFontData(platformData);
274}
275
276SimpleFontData* FontCache::getCachedFontData(const FontPlatformData* platformData)
277{
278    if (!platformData)
279        return 0;
280
281    if (!gFontDataCache) {
282        gFontDataCache = new FontDataCache;
283        gInactiveFontData = new ListHashSet<const SimpleFontData*>;
284    }
285
286    FontDataCache::iterator result = gFontDataCache->find(*platformData);
287    if (result == gFontDataCache->end()) {
288        pair<SimpleFontData*, unsigned> newValue(new SimpleFontData(*platformData), 1);
289        gFontDataCache->set(*platformData, newValue);
290        return newValue.first;
291    }
292    if (!result.get()->second.second++) {
293        ASSERT(gInactiveFontData->contains(result.get()->second.first));
294        gInactiveFontData->remove(result.get()->second.first);
295    }
296
297    return result.get()->second.first;
298}
299
300void FontCache::releaseFontData(const SimpleFontData* fontData)
301{
302    ASSERT(gFontDataCache);
303    ASSERT(!fontData->isCustomFont());
304
305    FontDataCache::iterator it = gFontDataCache->find(fontData->platformData());
306    ASSERT(it != gFontDataCache->end());
307
308    if (!--it->second.second) {
309        gInactiveFontData->add(fontData);
310        if (gInactiveFontData->size() > cMaxInactiveFontData)
311            purgeInactiveFontData(gInactiveFontData->size() - cTargetInactiveFontData);
312    }
313}
314
315void FontCache::purgeInactiveFontData(int count)
316{
317    if (!gInactiveFontData)
318        return;
319
320    static bool isPurging;  // Guard against reentry when e.g. a deleted FontData releases its small caps FontData.
321    if (isPurging)
322        return;
323
324    isPurging = true;
325
326    Vector<const SimpleFontData*, 20> fontDataToDelete;
327    ListHashSet<const SimpleFontData*>::iterator end = gInactiveFontData->end();
328    ListHashSet<const SimpleFontData*>::iterator it = gInactiveFontData->begin();
329    for (int i = 0; i < count && it != end; ++it, ++i) {
330        const SimpleFontData* fontData = *it.get();
331        gFontDataCache->remove(fontData->platformData());
332        fontDataToDelete.append(fontData);
333    }
334
335    if (it == end) {
336        // Removed everything
337        gInactiveFontData->clear();
338    } else {
339        for (int i = 0; i < count; ++i)
340            gInactiveFontData->remove(gInactiveFontData->begin());
341    }
342
343    size_t fontDataToDeleteCount = fontDataToDelete.size();
344    for (size_t i = 0; i < fontDataToDeleteCount; ++i)
345        delete fontDataToDelete[i];
346
347    if (gFontPlatformDataCache) {
348        Vector<FontPlatformDataCacheKey> keysToRemove;
349        keysToRemove.reserveInitialCapacity(gFontPlatformDataCache->size());
350        FontPlatformDataCache::iterator platformDataEnd = gFontPlatformDataCache->end();
351        for (FontPlatformDataCache::iterator platformData = gFontPlatformDataCache->begin(); platformData != platformDataEnd; ++platformData) {
352            if (platformData->second && !gFontDataCache->contains(*platformData->second))
353                keysToRemove.append(platformData->first);
354        }
355
356        size_t keysToRemoveCount = keysToRemove.size();
357        for (size_t i = 0; i < keysToRemoveCount; ++i)
358            delete gFontPlatformDataCache->take(keysToRemove[i]);
359    }
360
361    isPurging = false;
362}
363
364size_t FontCache::fontDataCount()
365{
366    if (gFontDataCache)
367        return gFontDataCache->size();
368    return 0;
369}
370
371size_t FontCache::inactiveFontDataCount()
372{
373    if (gInactiveFontData)
374        return gInactiveFontData->size();
375    return 0;
376}
377
378const FontData* FontCache::getFontData(const Font& font, int& familyIndex, FontSelector* fontSelector)
379{
380    SimpleFontData* result = 0;
381
382    int startIndex = familyIndex;
383    const FontFamily* startFamily = &font.fontDescription().family();
384    for (int i = 0; startFamily && i < startIndex; i++)
385        startFamily = startFamily->next();
386    const FontFamily* currFamily = startFamily;
387    while (currFamily && !result) {
388        familyIndex++;
389        if (currFamily->family().length()) {
390            if (fontSelector) {
391                FontData* data = fontSelector->getFontData(font.fontDescription(), currFamily->family());
392                if (data)
393                    return data;
394            }
395            result = getCachedFontData(font.fontDescription(), currFamily->family());
396        }
397        currFamily = currFamily->next();
398    }
399
400    if (!currFamily)
401        familyIndex = cAllFamiliesScanned;
402
403    if (!result)
404        // We didn't find a font. Try to find a similar font using our own specific knowledge about our platform.
405        // For example on OS X, we know to map any families containing the words Arabic, Pashto, or Urdu to the
406        // Geeza Pro font.
407        result = getSimilarFontPlatformData(font);
408
409    if (!result && startIndex == 0) {
410        // If it's the primary font that we couldn't find, we try the following. In all other cases, we will
411        // just use per-character system fallback.
412
413        if (fontSelector) {
414            // Try the user's preferred standard font.
415            if (FontData* data = fontSelector->getFontData(font.fontDescription(), "-webkit-standard"))
416                return data;
417        }
418
419        // Still no result.  Hand back our last resort fallback font.
420        result = getLastResortFallbackFont(font.fontDescription());
421    }
422    return result;
423}
424
425static HashSet<FontSelector*>* gClients;
426
427void FontCache::addClient(FontSelector* client)
428{
429    if (!gClients)
430        gClients = new HashSet<FontSelector*>;
431
432    ASSERT(!gClients->contains(client));
433    gClients->add(client);
434}
435
436void FontCache::removeClient(FontSelector* client)
437{
438    ASSERT(gClients);
439    ASSERT(gClients->contains(client));
440
441    gClients->remove(client);
442}
443
444static unsigned gGeneration = 0;
445
446unsigned FontCache::generation()
447{
448    return gGeneration;
449}
450
451void FontCache::invalidate()
452{
453    if (!gClients) {
454        ASSERT(!gFontPlatformDataCache);
455        return;
456    }
457
458    if (gFontPlatformDataCache) {
459        deleteAllValues(*gFontPlatformDataCache);
460        delete gFontPlatformDataCache;
461        gFontPlatformDataCache = new FontPlatformDataCache;
462    }
463
464    gGeneration++;
465
466    Vector<RefPtr<FontSelector> > clients;
467    size_t numClients = gClients->size();
468    clients.reserveInitialCapacity(numClients);
469    HashSet<FontSelector*>::iterator end = gClients->end();
470    for (HashSet<FontSelector*>::iterator it = gClients->begin(); it != end; ++it)
471        clients.append(*it);
472
473    ASSERT(numClients == clients.size());
474    for (size_t i = 0; i < numClients; ++i)
475        clients[i]->fontCacheInvalidated();
476
477    purgeInactiveFontData();
478}
479
480} // namespace WebCore
481