blob: 8cdff12352285d7ca26f797e6637a878f2147309 [file] [log] [blame]
/*
* Copyright (C) 2006, 2008, 2013 Apple Inc. All rights reserved.
* Copyright (C) 2007 Nicholas Shanks <webkit@nickshanks.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "FontCache.h"
#include "Font.h"
#include "FontGlyphs.h"
#include "FontPlatformData.h"
#include "FontSelector.h"
#include "WebKitFontFamilyNames.h"
#include <wtf/HashMap.h>
#include <wtf/ListHashSet.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/StdLibExtras.h>
#include <wtf/text/AtomicStringHash.h>
#include <wtf/text/StringHash.h>
#if ENABLE(OPENTYPE_VERTICAL)
#include "OpenTypeVerticalData.h"
#endif
#if PLATFORM(IOS)
#include "MemoryPressureHandler.h"
#include <wtf/Noncopyable.h>
// FIXME: We may be able to simplify this code using C++11 threading primitives, including std::call_once().
static pthread_mutex_t fontDataLock;
static void initFontCacheLockOnce()
{
pthread_mutexattr_t mutexAttribute;
pthread_mutexattr_init(&mutexAttribute);
pthread_mutexattr_settype(&mutexAttribute, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&fontDataLock, &mutexAttribute);
pthread_mutexattr_destroy(&mutexAttribute);
}
static pthread_once_t initFontLockControl = PTHREAD_ONCE_INIT;
class FontLocker {
WTF_MAKE_NONCOPYABLE(FontLocker);
public:
FontLocker()
{
pthread_once(&initFontLockControl, initFontCacheLockOnce);
int lockcode = pthread_mutex_lock(&fontDataLock);
ASSERT_WITH_MESSAGE_UNUSED(lockcode, !lockcode, "fontDataLock lock failed with code:%d", lockcode);
}
~FontLocker()
{
int lockcode = pthread_mutex_unlock(&fontDataLock);
ASSERT_WITH_MESSAGE_UNUSED(lockcode, !lockcode, "fontDataLock unlock failed with code:%d", lockcode);
}
};
#endif // PLATFORM(IOS)
using namespace WTF;
namespace WebCore {
// FIXME: We should return a reference instead of a pointer since we never return a nullptr.
FontCache* fontCache()
{
static NeverDestroyed<FontCache> globalFontCache;
return &globalFontCache.get();
}
FontCache::FontCache()
: m_purgePreventCount(0)
{
}
struct FontPlatformDataCacheKey {
WTF_MAKE_FAST_ALLOCATED;
public:
FontPlatformDataCacheKey() { }
FontPlatformDataCacheKey(const AtomicString& family, const FontDescription& description)
: m_fontDescriptionKey(description)
, m_family(family)
{ }
FontPlatformDataCacheKey(HashTableDeletedValueType) : m_fontDescriptionKey(hashTableDeletedSize()) { }
bool isHashTableDeletedValue() const { return m_fontDescriptionKey.size == hashTableDeletedSize(); }
bool operator==(const FontPlatformDataCacheKey& other) const
{
return equalIgnoringCase(m_family, other.m_family) && m_fontDescriptionKey == other.m_fontDescriptionKey;
}
FontDescriptionFontDataCacheKey m_fontDescriptionKey;
AtomicString m_family;
private:
static unsigned hashTableDeletedSize() { return 0xFFFFFFFFU; }
};
inline unsigned computeHash(const FontPlatformDataCacheKey& fontKey)
{
return pairIntHash(CaseFoldingHash::hash(fontKey.m_family), fontKey.m_fontDescriptionKey.computeHash());
}
struct FontPlatformDataCacheKeyHash {
static unsigned hash(const FontPlatformDataCacheKey& font)
{
return computeHash(font);
}
static bool equal(const FontPlatformDataCacheKey& a, const FontPlatformDataCacheKey& b)
{
return a == b;
}
static const bool safeToCompareToEmptyOrDeleted = true;
};
struct FontPlatformDataCacheKeyTraits : WTF::SimpleClassHashTraits<FontPlatformDataCacheKey> { };
typedef HashMap<FontPlatformDataCacheKey, OwnPtr<FontPlatformData>, FontPlatformDataCacheKeyHash, FontPlatformDataCacheKeyTraits> FontPlatformDataCache;
static FontPlatformDataCache* gFontPlatformDataCache = 0;
static bool familyNameEqualIgnoringCase(const AtomicString& familyName, const char* reference, unsigned length)
{
ASSERT(length > 0);
ASSERT(familyName.length() == length);
ASSERT(strlen(reference) == length);
const AtomicStringImpl* familyNameImpl = familyName.impl();
if (familyNameImpl->is8Bit())
return equalIgnoringCase(familyNameImpl->characters8(), reinterpret_cast<const LChar*>(reference), length);
return equalIgnoringCase(familyNameImpl->characters16(), reinterpret_cast<const LChar*>(reference), length);
}
template<size_t length>
static inline bool familyNameEqualIgnoringCase(const AtomicString& familyName, const char (&reference)[length])
{
return familyNameEqualIgnoringCase(familyName, reference, length - 1);
}
static const AtomicString alternateFamilyName(const AtomicString& familyName)
{
// Alias Courier and Courier New.
// Alias Times and Times New Roman.
// Alias Arial and Helvetica.
switch (familyName.length()) {
case 5:
if (familyNameEqualIgnoringCase(familyName, "Arial"))
return AtomicString("Helvetica", AtomicString::ConstructFromLiteral);
if (familyNameEqualIgnoringCase(familyName, "Times"))
return AtomicString("Times New Roman", AtomicString::ConstructFromLiteral);
break;
case 7:
if (familyNameEqualIgnoringCase(familyName, "Courier"))
return AtomicString("Courier New", AtomicString::ConstructFromLiteral);
break;
case 9:
if (familyNameEqualIgnoringCase(familyName, "Helvetica"))
return AtomicString("Arial", AtomicString::ConstructFromLiteral);
break;
#if !OS(WINDOWS)
// On Windows, Courier New (truetype font) is always present and
// Courier is a bitmap font. So, we don't want to map Courier New to
// Courier.
case 11:
if (familyNameEqualIgnoringCase(familyName, "Courier New"))
return AtomicString("Courier", AtomicString::ConstructFromLiteral);
break;
#endif // !OS(WINDOWS)
case 15:
if (familyNameEqualIgnoringCase(familyName, "Times New Roman"))
return AtomicString("Times", AtomicString::ConstructFromLiteral);
break;
#if OS(WINDOWS)
// On Windows, bitmap fonts are blocked altogether so that we have to
// alias MS Sans Serif (bitmap font) -> Microsoft Sans Serif (truetype font)
case 13:
if (familyNameEqualIgnoringCase(familyName, "MS Sans Serif"))
return AtomicString("Microsoft Sans Serif", AtomicString::ConstructFromLiteral);
break;
// Alias MS Serif (bitmap) -> Times New Roman (truetype font). There's no
// 'Microsoft Sans Serif-equivalent' for Serif.
case 8:
if (familyNameEqualIgnoringCase(familyName, "MS Serif"))
return AtomicString("Times New Roman", AtomicString::ConstructFromLiteral);
break;
#endif // OS(WINDOWS)
}
return nullAtom;
}
FontPlatformData* FontCache::getCachedFontPlatformData(const FontDescription& fontDescription,
const AtomicString& passedFamilyName,
bool checkingAlternateName)
{
#if PLATFORM(IOS)
FontLocker fontLocker;
#endif
#if OS(WINDOWS) && ENABLE(OPENTYPE_VERTICAL)
// Leading "@" in the font name enables Windows vertical flow flag for the font.
// Because we do vertical flow by ourselves, we don't want to use the Windows feature.
// IE disregards "@" regardless of the orientatoin, so we follow the behavior.
const AtomicString& familyName = (passedFamilyName.isEmpty() || passedFamilyName[0] != '@') ?
passedFamilyName : AtomicString(passedFamilyName.impl()->substring(1));
#else
const AtomicString& familyName = passedFamilyName;
#endif
if (!gFontPlatformDataCache) {
gFontPlatformDataCache = new FontPlatformDataCache;
platformInit();
}
FontPlatformDataCacheKey key(familyName, fontDescription);
FontPlatformDataCache::AddResult result = gFontPlatformDataCache->add(key, nullptr);
FontPlatformDataCache::iterator it = result.iterator;
if (result.isNewEntry) {
it->value = createFontPlatformData(fontDescription, familyName);
if (!it->value && !checkingAlternateName) {
// We were unable to find a font. We have a small set of fonts that we alias to other names,
// e.g., Arial/Helvetica, Courier/Courier New, etc. Try looking up the font under the aliased name.
const AtomicString alternateName = alternateFamilyName(familyName);
if (!alternateName.isNull()) {
FontPlatformData* fontPlatformDataForAlternateName = getCachedFontPlatformData(fontDescription, alternateName, true);
// Lookup the key in the hash table again as the previous iterator may have
// been invalidated by the recursive call to getCachedFontPlatformData().
it = gFontPlatformDataCache->find(key);
ASSERT(it != gFontPlatformDataCache->end());
if (fontPlatformDataForAlternateName)
it->value = adoptPtr(new FontPlatformData(*fontPlatformDataForAlternateName));
}
}
}
return it->value.get();
}
#if ENABLE(OPENTYPE_VERTICAL)
struct FontVerticalDataCacheKeyHash {
static unsigned hash(const FontCache::FontFileKey& fontFileKey)
{
return PtrHash<const FontCache::FontFileKey*>::hash(&fontFileKey);
}
static bool equal(const FontCache::FontFileKey& a, const FontCache::FontFileKey& b)
{
return a == b;
}
static const bool safeToCompareToEmptyOrDeleted = true;
};
struct FontVerticalDataCacheKeyTraits : WTF::GenericHashTraits<FontCache::FontFileKey> {
static const bool emptyValueIsZero = true;
static const bool needsDestruction = true;
static const FontCache::FontFileKey& emptyValue()
{
static NeverDestroyed<FontCache::FontFileKey> key = nullAtom;
return key;
}
static void constructDeletedValue(FontCache::FontFileKey& slot)
{
new (NotNull, &slot) FontCache::FontFileKey(HashTableDeletedValue);
}
static bool isDeletedValue(const FontCache::FontFileKey& value)
{
return value.isHashTableDeletedValue();
}
};
typedef HashMap<FontCache::FontFileKey, RefPtr<OpenTypeVerticalData>, FontVerticalDataCacheKeyHash, FontVerticalDataCacheKeyTraits> FontVerticalDataCache;
FontVerticalDataCache& fontVerticalDataCacheInstance()
{
static NeverDestroyed<FontVerticalDataCache> fontVerticalDataCache;
return fontVerticalDataCache;
}
PassRefPtr<OpenTypeVerticalData> FontCache::getVerticalData(const FontFileKey& key, const FontPlatformData& platformData)
{
FontVerticalDataCache& fontVerticalDataCache = fontVerticalDataCacheInstance();
FontVerticalDataCache::iterator result = fontVerticalDataCache.find(key);
if (result != fontVerticalDataCache.end())
return result.get()->value;
RefPtr<OpenTypeVerticalData> verticalData = OpenTypeVerticalData::create(platformData);
if (!verticalData->isOpenType())
verticalData.clear();
fontVerticalDataCache.set(key, verticalData);
return verticalData;
}
#endif
struct FontDataCacheKeyHash {
static unsigned hash(const FontPlatformData& platformData)
{
return platformData.hash();
}
static bool equal(const FontPlatformData& a, const FontPlatformData& b)
{
return a == b;
}
static const bool safeToCompareToEmptyOrDeleted = true;
};
struct FontDataCacheKeyTraits : WTF::GenericHashTraits<FontPlatformData> {
static const bool emptyValueIsZero = true;
static const bool needsDestruction = true;
static const FontPlatformData& emptyValue()
{
static NeverDestroyed<FontPlatformData> key(0.f, false, false);
return key;
}
static void constructDeletedValue(FontPlatformData& slot)
{
new (NotNull, &slot) FontPlatformData(HashTableDeletedValue);
}
static bool isDeletedValue(const FontPlatformData& value)
{
return value.isHashTableDeletedValue();
}
};
typedef HashMap<FontPlatformData, std::pair<RefPtr<SimpleFontData>, unsigned>, FontDataCacheKeyHash, FontDataCacheKeyTraits> FontDataCache;
static FontDataCache* gFontDataCache = 0;
#if PLATFORM(IOS)
const int cMaxInactiveFontData = 120;
const int cTargetInactiveFontData = 100;
const int cMaxUnderMemoryPressureInactiveFontData = 50;
const int cTargetUnderMemoryPressureInactiveFontData = 30;
#else
const int cMaxInactiveFontData = 225;
const int cTargetInactiveFontData = 200;
#endif
static ListHashSet<RefPtr<SimpleFontData>>* gInactiveFontData = 0;
PassRefPtr<SimpleFontData> FontCache::getCachedFontData(const FontDescription& fontDescription, const AtomicString& family, bool checkingAlternateName, ShouldRetain shouldRetain)
{
FontPlatformData* platformData = getCachedFontPlatformData(fontDescription, family, checkingAlternateName);
if (!platformData)
return 0;
return getCachedFontData(platformData, shouldRetain);
}
PassRefPtr<SimpleFontData> FontCache::getCachedFontData(const FontPlatformData* platformData, ShouldRetain shouldRetain)
{
if (!platformData)
return 0;
#if !ASSERT_DISABLED
if (shouldRetain == DoNotRetain)
ASSERT(m_purgePreventCount);
#endif
#if PLATFORM(IOS)
FontLocker fontLocker;
#endif
if (!gFontDataCache) {
gFontDataCache = new FontDataCache;
gInactiveFontData = new ListHashSet<RefPtr<SimpleFontData>>;
}
FontDataCache::iterator result = gFontDataCache->find(*platformData);
if (result == gFontDataCache->end()) {
std::pair<RefPtr<SimpleFontData>, unsigned> newValue(SimpleFontData::create(*platformData), shouldRetain == Retain ? 1 : 0);
gFontDataCache->set(*platformData, newValue);
if (shouldRetain == DoNotRetain)
gInactiveFontData->add(newValue.first);
return newValue.first.release();
}
if (!result.get()->value.second) {
ASSERT(gInactiveFontData->contains(result.get()->value.first));
gInactiveFontData->remove(result.get()->value.first);
}
if (shouldRetain == Retain)
result.get()->value.second++;
else if (!result.get()->value.second) {
// If shouldRetain is DoNotRetain and count is 0, we want to remove the fontData from
// gInactiveFontData (above) and re-add here to update LRU position.
gInactiveFontData->add(result.get()->value.first);
}
return result.get()->value.first;
}
SimpleFontData* FontCache::getNonRetainedLastResortFallbackFont(const FontDescription& fontDescription)
{
return getLastResortFallbackFont(fontDescription, DoNotRetain).leakRef();
}
void FontCache::releaseFontData(const SimpleFontData* fontData)
{
ASSERT(gFontDataCache);
ASSERT(!fontData->isCustomFont());
#if PLATFORM(IOS)
FontLocker fontLocker;
#endif
FontDataCache::iterator it = gFontDataCache->find(fontData->platformData());
ASSERT(it != gFontDataCache->end());
if (it == gFontDataCache->end())
return;
ASSERT(it->value.second);
if (!--it->value.second)
gInactiveFontData->add(it->value.first);
}
void FontCache::purgeInactiveFontDataIfNeeded()
{
#if PLATFORM(IOS)
bool underMemoryPressure = memoryPressureHandler().hasReceivedMemoryPressure();
int inactiveFontDataLimit = underMemoryPressure ? cMaxUnderMemoryPressureInactiveFontData : cMaxInactiveFontData;
int targetFontDataLimit = underMemoryPressure ? cTargetUnderMemoryPressureInactiveFontData : cTargetInactiveFontData;
if (gInactiveFontData && !m_purgePreventCount && gInactiveFontData->size() > inactiveFontDataLimit)
purgeInactiveFontData(gInactiveFontData->size() - targetFontDataLimit);
#else
if (gInactiveFontData && !m_purgePreventCount && gInactiveFontData->size() > cMaxInactiveFontData)
purgeInactiveFontData(gInactiveFontData->size() - cTargetInactiveFontData);
#endif
}
void FontCache::purgeInactiveFontData(int count)
{
pruneUnreferencedEntriesFromFontGlyphsCache();
if (!gInactiveFontData || m_purgePreventCount)
return;
static bool isPurging; // Guard against reentry when e.g. a deleted FontData releases its small caps FontData.
if (isPurging)
return;
isPurging = true;
#if PLATFORM(IOS)
FontLocker fontLocker;
#endif
Vector<RefPtr<SimpleFontData>, 20> fontDataToDelete;
ListHashSet<RefPtr<SimpleFontData>>::iterator end = gInactiveFontData->end();
ListHashSet<RefPtr<SimpleFontData>>::iterator it = gInactiveFontData->begin();
for (int i = 0; i < count && it != end; ++it, ++i) {
RefPtr<SimpleFontData>& fontData = *it.get();
gFontDataCache->remove(fontData->platformData());
// We should not delete SimpleFontData here because deletion can modify gInactiveFontData. See http://trac.webkit.org/changeset/44011
fontDataToDelete.append(fontData);
}
if (it == end) {
// Removed everything
gInactiveFontData->clear();
} else {
for (int i = 0; i < count; ++i)
gInactiveFontData->remove(gInactiveFontData->begin());
}
fontDataToDelete.clear();
if (gFontPlatformDataCache) {
Vector<FontPlatformDataCacheKey> keysToRemove;
keysToRemove.reserveInitialCapacity(gFontPlatformDataCache->size());
FontPlatformDataCache::iterator platformDataEnd = gFontPlatformDataCache->end();
for (FontPlatformDataCache::iterator platformData = gFontPlatformDataCache->begin(); platformData != platformDataEnd; ++platformData) {
if (platformData->value && !gFontDataCache->contains(*platformData->value))
keysToRemove.append(platformData->key);
}
size_t keysToRemoveCount = keysToRemove.size();
for (size_t i = 0; i < keysToRemoveCount; ++i)
gFontPlatformDataCache->remove(keysToRemove[i]);
}
#if ENABLE(OPENTYPE_VERTICAL)
FontVerticalDataCache& fontVerticalDataCache = fontVerticalDataCacheInstance();
if (!fontVerticalDataCache.isEmpty()) {
// Mark & sweep unused verticalData
FontVerticalDataCache::iterator verticalDataEnd = fontVerticalDataCache.end();
for (FontVerticalDataCache::iterator verticalData = fontVerticalDataCache.begin(); verticalData != verticalDataEnd; ++verticalData) {
if (verticalData->value)
verticalData->value->m_inFontCache = false;
}
FontDataCache::iterator fontDataEnd = gFontDataCache->end();
for (FontDataCache::iterator fontData = gFontDataCache->begin(); fontData != fontDataEnd; ++fontData) {
OpenTypeVerticalData* verticalData = const_cast<OpenTypeVerticalData*>(fontData->value.first->verticalData());
if (verticalData)
verticalData->m_inFontCache = true;
}
Vector<FontFileKey> keysToRemove;
keysToRemove.reserveInitialCapacity(fontVerticalDataCache.size());
for (FontVerticalDataCache::iterator verticalData = fontVerticalDataCache.begin(); verticalData != verticalDataEnd; ++verticalData) {
if (!verticalData->value || !verticalData->value->m_inFontCache)
keysToRemove.append(verticalData->key);
}
for (size_t i = 0, count = keysToRemove.size(); i < count; ++i)
fontVerticalDataCache.take(keysToRemove[i]);
}
#endif
isPurging = false;
}
size_t FontCache::fontDataCount()
{
if (gFontDataCache)
return gFontDataCache->size();
return 0;
}
size_t FontCache::inactiveFontDataCount()
{
if (gInactiveFontData)
return gInactiveFontData->size();
return 0;
}
PassRefPtr<FontData> FontCache::getFontData(const FontDescription& description, int& familyIndex, FontSelector* fontSelector)
{
ASSERT(familyIndex != cAllFamiliesScanned);
RefPtr<FontData> result;
bool isFirst = !familyIndex;
int familyCount = description.familyCount();
for (;familyIndex < familyCount && !result; ++familyIndex) {
const AtomicString& family = description.familyAt(familyIndex);
if (family.isEmpty())
continue;
if (fontSelector)
result = fontSelector->getFontData(description, family);
if (!result)
result = getCachedFontData(description, family);
}
if (familyIndex == familyCount)
familyIndex = cAllFamiliesScanned;
#if PLATFORM(COCOA)
if (!result) {
// We didn't find a font. Try to find a similar font using our own specific knowledge about our platform.
// For example on OS X, we know to map any families containing the words Arabic, Pashto, or Urdu to the
// Geeza Pro font.
result = similarFontPlatformData(description);
}
#endif
if (!result && isFirst) {
// If it's the primary font that we couldn't find, we try the following. In all other cases, we will
// just use per-character system fallback.
if (fontSelector) {
// Try the user's preferred standard font.
if (RefPtr<FontData> data = fontSelector->getFontData(description, standardFamily))
return data.release();
}
// Still no result. Hand back our last resort fallback font.
result = getLastResortFallbackFont(description);
}
return result.release();
}
static HashSet<FontSelector*>* gClients;
void FontCache::addClient(FontSelector* client)
{
if (!gClients)
gClients = new HashSet<FontSelector*>;
ASSERT(!gClients->contains(client));
gClients->add(client);
}
void FontCache::removeClient(FontSelector* client)
{
ASSERT(gClients);
ASSERT(gClients->contains(client));
gClients->remove(client);
}
static unsigned short gGeneration = 0;
unsigned short FontCache::generation()
{
return gGeneration;
}
void FontCache::invalidate()
{
if (!gClients) {
ASSERT(!gFontPlatformDataCache);
return;
}
if (gFontPlatformDataCache)
gFontPlatformDataCache->clear();
invalidateFontGlyphsCache();
gGeneration++;
Vector<Ref<FontSelector>> clients;
clients.reserveInitialCapacity(gClients->size());
for (auto it = gClients->begin(), end = gClients->end(); it != end; ++it)
clients.uncheckedAppend(**it);
for (unsigned i = 0; i < clients.size(); ++i)
clients[i]->fontCacheInvalidated();
purgeInactiveFontData();
}
} // namespace WebCore