blob: e6bb46ffa37e4dbcef80fc36ee2b25d6218da244 [file] [log] [blame]
/*
* Copyright (C) 2006-2021 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 "FontCascade.h"
#include "FontCreationContext.h"
#include "FontPlatformData.h"
#include "FontSelector.h"
#include "Logging.h"
#include "ThreadGlobalData.h"
#include "WebKitFontFamilyNames.h"
#include "WorkerOrWorkletThread.h"
#include <wtf/HashMap.h>
#include <wtf/MemoryPressureHandler.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/StdLibExtras.h>
#include <wtf/text/AtomStringHash.h>
#include <wtf/text/StringHash.h>
#if ENABLE(OPENTYPE_VERTICAL)
#include "OpenTypeVerticalData.h"
#endif
namespace WebCore {
FontFamilyName::FontFamilyName() = default;
inline FontFamilyName::FontFamilyName(const AtomString& name)
: m_name { name }
{
}
inline const AtomString& FontFamilyName::string() const
{
return m_name;
}
inline void add(Hasher& hasher, const FontFamilyName& name)
{
// FIXME: Would be better to hash the characters in the name instead of hashing a hash.
if (!name.string().isNull())
add(hasher, FontCascadeDescription::familyNameHash(name.string()));
}
inline bool operator==(const FontFamilyName& a, const FontFamilyName& b)
{
return (a.string().isNull() || b.string().isNull()) ? a.string() == b.string() : FontCascadeDescription::familyNamesAreEqual(a.string(), b.string());
}
inline bool operator!=(const FontFamilyName& a, const FontFamilyName& b)
{
return !(a == b);
}
struct FontPlatformDataCacheKey {
FontDescriptionKey descriptionKey;
FontFamilyName family;
FontCreationContext fontCreationContext;
};
inline void add(Hasher& hasher, const FontPlatformDataCacheKey& key)
{
add(hasher, key.descriptionKey, key.family, key.fontCreationContext);
}
static bool operator==(const FontPlatformDataCacheKey& a, const FontPlatformDataCacheKey& b)
{
return a.descriptionKey == b.descriptionKey
&& a.family == b.family
&& a.fontCreationContext == b.fontCreationContext;
}
struct FontPlatformDataCacheKeyHash {
static unsigned hash(const FontPlatformDataCacheKey& key) { return computeHash(key); }
static bool equal(const FontPlatformDataCacheKey& a, const FontPlatformDataCacheKey& b) { return a == b; }
static constexpr bool safeToCompareToEmptyOrDeleted = true;
};
struct FontPlatformDataCacheKeyHashTraits : public SimpleClassHashTraits<FontPlatformDataCacheKey> {
static constexpr bool emptyValueIsZero = false;
static void constructDeletedValue(FontPlatformDataCacheKey& slot)
{
new (NotNull, &slot.descriptionKey) FontDescriptionKey(WTF::HashTableDeletedValue);
}
static bool isDeletedValue(const FontPlatformDataCacheKey& key)
{
return key.descriptionKey.isHashTableDeletedValue();
}
};
struct FontDataCacheKeyHash {
static unsigned hash(const FontPlatformData& data) { return data.hash(); }
static bool equal(const FontPlatformData& a, const FontPlatformData& b) { return a == b; }
static constexpr bool safeToCompareToEmptyOrDeleted = true;
};
struct FontDataCacheKeyTraits : WTF::GenericHashTraits<FontPlatformData> {
static constexpr bool emptyValueIsZero = true;
static const FontPlatformData& emptyValue()
{
static NeverDestroyed<FontPlatformData> key(0.f, false, false);
return key;
}
static void constructDeletedValue(FontPlatformData& slot)
{
new (NotNull, &slot) FontPlatformData(WTF::HashTableDeletedValue);
}
static bool isDeletedValue(const FontPlatformData& value)
{
return value.isHashTableDeletedValue();
}
};
using FontPlatformDataCache = HashMap<FontPlatformDataCacheKey, std::unique_ptr<FontPlatformData>, FontPlatformDataCacheKeyHash, FontPlatformDataCacheKeyHashTraits>;
using FontDataCache = HashMap<FontPlatformData, Ref<Font>, FontDataCacheKeyHash, FontDataCacheKeyTraits>;
#if ENABLE(OPENTYPE_VERTICAL)
using FontVerticalDataCache = HashMap<FontPlatformData, RefPtr<OpenTypeVerticalData>, FontDataCacheKeyHash, FontDataCacheKeyTraits>;
#endif
struct FontCache::FontDataCaches {
WTF_MAKE_STRUCT_FAST_ALLOCATED;
FontDataCache data;
FontPlatformDataCache platformData;
#if ENABLE(OPENTYPE_VERTICAL)
FontVerticalDataCache verticalData;
#endif
};
FontCache& FontCache::forCurrentThread()
{
return threadGlobalData().fontCache();
}
FontCache* FontCache::forCurrentThreadIfNotDestroyed()
{
return threadGlobalData().fontCacheIfNotDestroyed();
}
FontCache::FontCache()
: m_purgeTimer(*this, &FontCache::purgeInactiveFontDataIfNeeded)
, m_fontDataCaches(makeUniqueRef<FontDataCaches>())
{
}
FontCache::~FontCache() = default;
std::optional<ASCIILiteral> FontCache::alternateFamilyName(const String& familyName)
{
if (auto platformSpecificAlternate = platformAlternateFamilyName(familyName))
return platformSpecificAlternate;
switch (familyName.length()) {
case 5:
if (equalLettersIgnoringASCIICase(familyName, "arial"_s))
return "Helvetica"_s;
if (equalLettersIgnoringASCIICase(familyName, "times"_s))
return "Times New Roman"_s;
break;
case 7:
if (equalLettersIgnoringASCIICase(familyName, "courier"_s))
return "Courier New"_s;
break;
case 9:
if (equalLettersIgnoringASCIICase(familyName, "helvetica"_s))
return "Arial"_s;
break;
#if !OS(WINDOWS)
// On Windows, Courier New is a TrueType font that is always present and
// Courier is a bitmap font that we do not support. So, we don't want to map
// Courier New to Courier.
// FIXME: Not sure why this is harmful on Windows, since the alternative will
// only be tried if Courier New is not found.
case 11:
if (equalLettersIgnoringASCIICase(familyName, "courier new"_s))
return "Courier"_s;
break;
#endif
case 15:
if (equalLettersIgnoringASCIICase(familyName, "times new roman"_s))
return "Times"_s;
break;
}
return std::nullopt;
}
FontPlatformData* FontCache::cachedFontPlatformData(const FontDescription& fontDescription, const String& passedFamilyName, const FontCreationContext& fontCreationContext, bool checkingAlternateName)
{
#if PLATFORM(IOS_FAMILY)
Locker locker { m_fontLock };
#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 orientation, so we follow the behavior.
auto familyName = StringView(passedFamilyName).substring(passedFamilyName[0] == '@' ? 1 : 0).toAtomString();
#else
AtomString familyName { passedFamilyName };
#endif
static std::once_flag onceFlag;
std::call_once(onceFlag, [&]() {
platformInit();
});
FontPlatformDataCacheKey key { fontDescription, { familyName }, fontCreationContext };
auto addResult = m_fontDataCaches->platformData.add(key, nullptr);
FontPlatformDataCache::iterator it = addResult.iterator;
if (addResult.isNewEntry) {
it->value = createFontPlatformData(fontDescription, familyName, fontCreationContext);
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.
if (auto alternateName = alternateFamilyName(familyName)) {
auto* alternateData = cachedFontPlatformData(fontDescription, *alternateName, fontCreationContext, true);
// Look up the key in the hash table again as the previous iterator may have
// been invalidated by the recursive call to cachedFontPlatformData().
it = m_fontDataCaches->platformData.find(key);
ASSERT(it != m_fontDataCaches->platformData.end());
if (alternateData)
it->value = makeUnique<FontPlatformData>(*alternateData);
}
}
}
return it->value.get();
}
#if PLATFORM(IOS_FAMILY)
const unsigned cMaxInactiveFontData = 120;
const unsigned cTargetInactiveFontData = 100;
#else
const unsigned cMaxInactiveFontData = 225;
const unsigned cTargetInactiveFontData = 200;
#endif
const unsigned cMaxUnderMemoryPressureInactiveFontData = 50;
const unsigned cTargetUnderMemoryPressureInactiveFontData = 30;
RefPtr<Font> FontCache::fontForFamily(const FontDescription& fontDescription, const String& family, const FontCreationContext& fontCreationContext, bool checkingAlternateName)
{
if (!m_purgeTimer.isActive())
m_purgeTimer.startOneShot(0_s);
if (auto* platformData = cachedFontPlatformData(fontDescription, family, fontCreationContext, checkingAlternateName))
return fontForPlatformData(*platformData);
return nullptr;
}
Ref<Font> FontCache::fontForPlatformData(const FontPlatformData& platformData)
{
#if PLATFORM(IOS_FAMILY)
Locker locker { m_fontLock };
#endif
auto addResult = m_fontDataCaches->data.ensure(platformData, [&] {
return Font::create(platformData, Font::Origin::Local);
});
ASSERT(addResult.iterator->value->platformData() == platformData);
return addResult.iterator->value.copyRef();
}
void FontCache::purgeInactiveFontDataIfNeeded()
{
bool underMemoryPressure = MemoryPressureHandler::singleton().isUnderMemoryPressure();
unsigned inactiveFontDataLimit = underMemoryPressure ? cMaxUnderMemoryPressureInactiveFontData : cMaxInactiveFontData;
LOG(Fonts, "FontCache::purgeInactiveFontDataIfNeeded() - underMemoryPressure %d, inactiveFontDataLimit %u", underMemoryPressure, inactiveFontDataLimit);
if (m_fontDataCaches->data.size() < inactiveFontDataLimit)
return;
unsigned inactiveCount = inactiveFontCount();
if (inactiveCount <= inactiveFontDataLimit)
return;
unsigned targetFontDataLimit = underMemoryPressure ? cTargetUnderMemoryPressureInactiveFontData : cTargetInactiveFontData;
purgeInactiveFontData(inactiveCount - targetFontDataLimit);
}
void FontCache::purgeInactiveFontData(unsigned purgeCount)
{
LOG(Fonts, "FontCache::purgeInactiveFontData(%u)", purgeCount);
pruneUnreferencedEntriesFromFontCascadeCache();
pruneSystemFallbackFonts();
#if PLATFORM(IOS_FAMILY)
Locker locker { m_fontLock };
#endif
while (purgeCount) {
Vector<Ref<Font>, 20> fontsToDelete;
for (auto& font : m_fontDataCaches->data.values()) {
LOG(Fonts, " trying to purge font %s (has one ref %d)", font->platformData().description().utf8().data(), font->hasOneRef());
if (!font->hasOneRef())
continue;
fontsToDelete.append(font.copyRef());
if (!--purgeCount)
break;
}
// Fonts may ref other fonts so we loop until there are no changes.
if (fontsToDelete.isEmpty())
break;
for (auto& font : fontsToDelete) {
bool success = m_fontDataCaches->data.remove(font->platformData());
ASSERT_UNUSED(success, success);
#if ENABLE(OPENTYPE_VERTICAL)
m_fontDataCaches->verticalData.remove(font->platformData());
#endif
}
};
Vector<FontPlatformDataCacheKey> keysToRemove;
keysToRemove.reserveInitialCapacity(m_fontDataCaches->platformData.size());
for (auto& entry : m_fontDataCaches->platformData) {
if (entry.value && !m_fontDataCaches->data.contains(*entry.value))
keysToRemove.uncheckedAppend(entry.key);
}
LOG(Fonts, " removing %lu keys", keysToRemove.size());
for (auto& key : keysToRemove)
m_fontDataCaches->platformData.remove(key);
platformPurgeInactiveFontData();
}
bool operator==(const FontCascadeCacheKey& a, const FontCascadeCacheKey& b)
{
return a.fontDescriptionKey == b.fontDescriptionKey
&& a.fontSelectorId == b.fontSelectorId
&& a.fontSelectorVersion == b.fontSelectorVersion
&& a.families == b.families;
}
void FontCache::invalidateFontCascadeCache()
{
m_fontCascadeCache.clear();
}
void FontCache::clearWidthCaches()
{
for (auto& value : m_fontCascadeCache.values())
value->fonts.get().widthCache().clear();
}
#if ENABLE(OPENTYPE_VERTICAL)
RefPtr<OpenTypeVerticalData> FontCache::verticalData(const FontPlatformData& platformData)
{
auto addResult = m_fontDataCaches->verticalData.ensure(platformData, [&platformData] {
return OpenTypeVerticalData::create(platformData);
});
return addResult.iterator->value;
}
#endif
static FontCascadeCacheKey makeFontCascadeCacheKey(const FontCascadeDescription& description, FontSelector* fontSelector)
{
FontCascadeCacheKey key;
key.fontDescriptionKey = FontDescriptionKey(description);
unsigned familyCount = description.familyCount();
key.families.reserveInitialCapacity(familyCount);
for (unsigned i = 0; i < familyCount; ++i)
key.families.uncheckedAppend(description.familyAt(i));
key.fontSelectorId = fontSelector ? fontSelector->uniqueId() : 0;
key.fontSelectorVersion = fontSelector ? fontSelector->version() : 0;
return key;
}
void FontCache::pruneUnreferencedEntriesFromFontCascadeCache()
{
m_fontCascadeCache.removeIf([](auto& entry) {
return entry.value->fonts.get().hasOneRef();
});
}
void FontCache::pruneSystemFallbackFonts()
{
for (auto& entry : m_fontCascadeCache.values())
entry->fonts->pruneSystemFallbacks();
}
Ref<FontCascadeFonts> FontCache::retrieveOrAddCachedFonts(const FontCascadeDescription& fontDescription, RefPtr<FontSelector>&& fontSelector)
{
auto key = makeFontCascadeCacheKey(fontDescription, fontSelector.get());
auto addResult = m_fontCascadeCache.add(key, nullptr);
if (!addResult.isNewEntry)
return addResult.iterator->value->fonts.get();
auto& newEntry = addResult.iterator->value;
newEntry = makeUnique<FontCascadeCacheEntry>(FontCascadeCacheEntry { WTFMove(key), FontCascadeFonts::create(WTFMove(fontSelector)) });
Ref<FontCascadeFonts> glyphs = newEntry->fonts.get();
static constexpr unsigned unreferencedPruneInterval = 50;
static constexpr int maximumEntries = 400;
static unsigned pruneCounter;
// Referenced FontCascadeFonts would exist anyway so pruning them saves little memory.
if (!(++pruneCounter % unreferencedPruneInterval))
pruneUnreferencedEntriesFromFontCascadeCache();
// Prevent pathological growth.
if (m_fontCascadeCache.size() > maximumEntries)
m_fontCascadeCache.remove(m_fontCascadeCache.random());
return glyphs;
}
void FontCache::updateFontCascade(const FontCascade& fontCascade, RefPtr<FontSelector>&& fontSelector)
{
fontCascade.updateFonts(retrieveOrAddCachedFonts(fontCascade.fontDescription(), WTFMove(fontSelector)));
}
size_t FontCache::fontCount()
{
return m_fontDataCaches->data.size();
}
size_t FontCache::inactiveFontCount()
{
#if PLATFORM(IOS_FAMILY)
Locker locker { m_fontLock };
#endif
unsigned count = 0;
for (auto& font : m_fontDataCaches->data.values()) {
if (font->hasOneRef())
++count;
}
return count;
}
void FontCache::addClient(FontSelector& client)
{
ASSERT(!m_clients.contains(&client));
m_clients.add(&client);
}
void FontCache::removeClient(FontSelector& client)
{
ASSERT(m_clients.contains(&client));
m_clients.remove(&client);
}
void FontCache::invalidate()
{
m_fontDataCaches->platformData.clear();
#if ENABLE(OPENTYPE_VERTICAL)
m_fontDataCaches->verticalData.clear();
#endif
invalidateFontCascadeCache();
++m_generation;
for (auto& client : copyToVectorOf<RefPtr<FontSelector>>(m_clients))
client->fontCacheInvalidated();
purgeInactiveFontData();
}
static Function<void()>& fontCacheInvalidationCallback()
{
static NeverDestroyed<Function<void()>> callback;
return callback.get();
}
void FontCache::registerFontCacheInvalidationCallback(Function<void()>&& callback)
{
fontCacheInvalidationCallback() = WTFMove(callback);
}
void FontCache::invalidateAllFontCaches()
{
ASSERT(isMainThread());
// FIXME: Invalidate FontCaches in workers too.
FontCache::forCurrentThread().invalidate();
if (fontCacheInvalidationCallback())
fontCacheInvalidationCallback()();
}
#if !PLATFORM(COCOA)
FontCache::PrewarmInformation FontCache::collectPrewarmInformation() const
{
return { };
}
void FontCache::prewarmGlobally()
{
}
void FontCache::prewarm(PrewarmInformation&&)
{
}
RefPtr<Font> FontCache::similarFont(const FontDescription&, const String&)
{
return nullptr;
}
#endif
} // namespace WebCore