1/*
2 * Copyright (C) 2006, 2008, 2013 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 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 "FontCascade.h"
34#include "FontPlatformData.h"
35#include "FontSelector.h"
36#include "Logging.h"
37#include "WebKitFontFamilyNames.h"
38#include <wtf/HashMap.h>
39#include <wtf/MemoryPressureHandler.h>
40#include <wtf/NeverDestroyed.h>
41#include <wtf/StdLibExtras.h>
42#include <wtf/text/AtomicStringHash.h>
43#include <wtf/text/StringHash.h>
44
45#if ENABLE(OPENTYPE_VERTICAL)
46#include "OpenTypeVerticalData.h"
47#endif
48
49#if USE(DIRECT2D)
50#include <dwrite.h>
51#endif
52
53#if PLATFORM(IOS_FAMILY)
54#include <wtf/Lock.h>
55#include <wtf/RecursiveLockAdapter.h>
56
57static RecursiveLock fontLock;
58
59#endif // PLATFORM(IOS_FAMILY)
60
61
62namespace WebCore {
63using namespace WTF;
64
65FontCache& FontCache::singleton()
66{
67 static NeverDestroyed<FontCache> globalFontCache;
68 return globalFontCache;
69}
70
71FontCache::FontCache()
72 : m_purgeTimer(*this, &FontCache::purgeInactiveFontDataIfNeeded)
73{
74}
75
76struct FontPlatformDataCacheKey {
77 WTF_MAKE_FAST_ALLOCATED;
78public:
79 FontPlatformDataCacheKey() { }
80 FontPlatformDataCacheKey(const AtomicString& family, const FontDescription& description, const FontFeatureSettings* fontFaceFeatures, const FontVariantSettings* fontFaceVariantSettings, FontSelectionSpecifiedCapabilities fontFaceCapabilities)
81 : m_fontDescriptionKey(description)
82 , m_family(family)
83 , m_fontFaceFeatures(fontFaceFeatures ? *fontFaceFeatures : FontFeatureSettings())
84 , m_fontFaceVariantSettings(fontFaceVariantSettings ? *fontFaceVariantSettings : FontVariantSettings())
85 , m_fontFaceCapabilities(fontFaceCapabilities)
86 { }
87
88 explicit FontPlatformDataCacheKey(HashTableDeletedValueType t)
89 : m_fontDescriptionKey(t)
90 { }
91
92 bool isHashTableDeletedValue() const { return m_fontDescriptionKey.isHashTableDeletedValue(); }
93
94 bool operator==(const FontPlatformDataCacheKey& other) const
95 {
96 if (m_fontDescriptionKey != other.m_fontDescriptionKey
97 || m_fontFaceFeatures != other.m_fontFaceFeatures
98 || m_fontFaceVariantSettings != other.m_fontFaceVariantSettings
99 || m_fontFaceCapabilities != other.m_fontFaceCapabilities)
100 return false;
101 if (m_family.impl() == other.m_family.impl())
102 return true;
103 if (m_family.isNull() || other.m_family.isNull())
104 return false;
105 return FontCascadeDescription::familyNamesAreEqual(m_family, other.m_family);
106 }
107
108 FontDescriptionKey m_fontDescriptionKey;
109 AtomicString m_family;
110 FontFeatureSettings m_fontFaceFeatures;
111 FontVariantSettings m_fontFaceVariantSettings;
112 FontSelectionSpecifiedCapabilities m_fontFaceCapabilities;
113};
114
115struct FontPlatformDataCacheKeyHash {
116 static unsigned hash(const FontPlatformDataCacheKey& fontKey)
117 {
118 IntegerHasher hasher;
119 hasher.add(FontCascadeDescription::familyNameHash(fontKey.m_family));
120 hasher.add(fontKey.m_fontDescriptionKey.computeHash());
121 hasher.add(fontKey.m_fontFaceFeatures.hash());
122 hasher.add(fontKey.m_fontFaceVariantSettings.uniqueValue());
123 if (auto weight = fontKey.m_fontFaceCapabilities.weight)
124 hasher.add(weight->uniqueValue());
125 else
126 hasher.add(std::numeric_limits<unsigned>::max());
127 if (auto width = fontKey.m_fontFaceCapabilities.weight)
128 hasher.add(width->uniqueValue());
129 else
130 hasher.add(std::numeric_limits<unsigned>::max());
131 if (auto slope = fontKey.m_fontFaceCapabilities.weight)
132 hasher.add(slope->uniqueValue());
133 else
134 hasher.add(std::numeric_limits<unsigned>::max());
135 return hasher.hash();
136 }
137
138 static bool equal(const FontPlatformDataCacheKey& a, const FontPlatformDataCacheKey& b)
139 {
140 return a == b;
141 }
142
143 static const bool safeToCompareToEmptyOrDeleted = true;
144};
145
146struct FontPlatformDataCacheKeyHashTraits : public SimpleClassHashTraits<FontPlatformDataCacheKey> {
147 static const bool emptyValueIsZero = false;
148};
149
150typedef HashMap<FontPlatformDataCacheKey, std::unique_ptr<FontPlatformData>, FontPlatformDataCacheKeyHash, FontPlatformDataCacheKeyHashTraits> FontPlatformDataCache;
151
152static FontPlatformDataCache& fontPlatformDataCache()
153{
154 static NeverDestroyed<FontPlatformDataCache> cache;
155 return cache;
156}
157
158const AtomicString& FontCache::alternateFamilyName(const AtomicString& familyName)
159{
160 static NeverDestroyed<AtomicString> arial("Arial", AtomicString::ConstructFromLiteral);
161 static NeverDestroyed<AtomicString> courier("Courier", AtomicString::ConstructFromLiteral);
162 static NeverDestroyed<AtomicString> courierNew("Courier New", AtomicString::ConstructFromLiteral);
163 static NeverDestroyed<AtomicString> helvetica("Helvetica", AtomicString::ConstructFromLiteral);
164 static NeverDestroyed<AtomicString> times("Times", AtomicString::ConstructFromLiteral);
165 static NeverDestroyed<AtomicString> timesNewRoman("Times New Roman", AtomicString::ConstructFromLiteral);
166
167 const AtomicString& platformSpecificAlternate = platformAlternateFamilyName(familyName);
168 if (!platformSpecificAlternate.isNull())
169 return platformSpecificAlternate;
170
171 switch (familyName.length()) {
172 case 5:
173 if (equalLettersIgnoringASCIICase(familyName, "arial"))
174 return helvetica;
175 if (equalLettersIgnoringASCIICase(familyName, "times"))
176 return timesNewRoman;
177 break;
178 case 7:
179 if (equalLettersIgnoringASCIICase(familyName, "courier"))
180 return courierNew;
181 break;
182 case 9:
183 if (equalLettersIgnoringASCIICase(familyName, "helvetica"))
184 return arial;
185 break;
186#if !OS(WINDOWS)
187 // On Windows, Courier New is a TrueType font that is always present and
188 // Courier is a bitmap font that we do not support. So, we don't want to map
189 // Courier New to Courier.
190 // FIXME: Not sure why this is harmful on Windows, since the alternative will
191 // only be tried if Courier New is not found.
192 case 11:
193 if (equalLettersIgnoringASCIICase(familyName, "courier new"))
194 return courier;
195 break;
196#endif
197 case 15:
198 if (equalLettersIgnoringASCIICase(familyName, "times new roman"))
199 return times;
200 break;
201 }
202
203 return nullAtom();
204}
205
206FontPlatformData* FontCache::getCachedFontPlatformData(const FontDescription& fontDescription, const AtomicString& passedFamilyName,
207 const FontFeatureSettings* fontFaceFeatures, const FontVariantSettings* fontFaceVariantSettings, FontSelectionSpecifiedCapabilities fontFaceCapabilities, bool checkingAlternateName)
208{
209#if PLATFORM(IOS_FAMILY)
210 auto locker = holdLock(fontLock);
211#endif
212
213#if OS(WINDOWS) && ENABLE(OPENTYPE_VERTICAL)
214 // Leading "@" in the font name enables Windows vertical flow flag for the font.
215 // Because we do vertical flow by ourselves, we don't want to use the Windows feature.
216 // IE disregards "@" regardless of the orientatoin, so we follow the behavior.
217 const AtomicString& familyName = (passedFamilyName.isEmpty() || passedFamilyName[0] != '@') ?
218 passedFamilyName : AtomicString(passedFamilyName.impl()->substring(1));
219#else
220 const AtomicString& familyName = passedFamilyName;
221#endif
222
223 static bool initialized;
224 if (!initialized) {
225 platformInit();
226 initialized = true;
227 }
228
229 FontPlatformDataCacheKey key(familyName, fontDescription, fontFaceFeatures, fontFaceVariantSettings, fontFaceCapabilities);
230
231 auto addResult = fontPlatformDataCache().add(key, nullptr);
232 FontPlatformDataCache::iterator it = addResult.iterator;
233 if (addResult.isNewEntry) {
234 it->value = createFontPlatformData(fontDescription, familyName, fontFaceFeatures, fontFaceVariantSettings, fontFaceCapabilities);
235
236 if (!it->value && !checkingAlternateName) {
237 // We were unable to find a font. We have a small set of fonts that we alias to other names,
238 // e.g., Arial/Helvetica, Courier/Courier New, etc. Try looking up the font under the aliased name.
239 const AtomicString& alternateName = alternateFamilyName(familyName);
240 if (!alternateName.isNull()) {
241 FontPlatformData* fontPlatformDataForAlternateName = getCachedFontPlatformData(fontDescription, alternateName, fontFaceFeatures, fontFaceVariantSettings, fontFaceCapabilities, true);
242 // Lookup the key in the hash table again as the previous iterator may have
243 // been invalidated by the recursive call to getCachedFontPlatformData().
244 it = fontPlatformDataCache().find(key);
245 ASSERT(it != fontPlatformDataCache().end());
246 if (fontPlatformDataForAlternateName)
247 it->value = std::make_unique<FontPlatformData>(*fontPlatformDataForAlternateName);
248 }
249 }
250 }
251
252 return it->value.get();
253}
254
255struct FontDataCacheKeyHash {
256 static unsigned hash(const FontPlatformData& platformData)
257 {
258 return platformData.hash();
259 }
260
261 static bool equal(const FontPlatformData& a, const FontPlatformData& b)
262 {
263 return a == b;
264 }
265
266 static const bool safeToCompareToEmptyOrDeleted = true;
267};
268
269struct FontDataCacheKeyTraits : WTF::GenericHashTraits<FontPlatformData> {
270 static const bool emptyValueIsZero = true;
271 static const FontPlatformData& emptyValue()
272 {
273 static NeverDestroyed<FontPlatformData> key(0.f, false, false);
274 return key;
275 }
276 static void constructDeletedValue(FontPlatformData& slot)
277 {
278 new (NotNull, &slot) FontPlatformData(HashTableDeletedValue);
279 }
280 static bool isDeletedValue(const FontPlatformData& value)
281 {
282 return value.isHashTableDeletedValue();
283 }
284};
285
286typedef HashMap<FontPlatformData, Ref<Font>, FontDataCacheKeyHash, FontDataCacheKeyTraits> FontDataCache;
287
288static FontDataCache& cachedFonts()
289{
290 static NeverDestroyed<FontDataCache> cache;
291 return cache;
292}
293
294#if ENABLE(OPENTYPE_VERTICAL)
295typedef HashMap<FontPlatformData, RefPtr<OpenTypeVerticalData>, FontDataCacheKeyHash, FontDataCacheKeyTraits> FontVerticalDataCache;
296
297FontVerticalDataCache& fontVerticalDataCache()
298{
299 static NeverDestroyed<FontVerticalDataCache> fontVerticalDataCache;
300 return fontVerticalDataCache;
301}
302
303RefPtr<OpenTypeVerticalData> FontCache::verticalData(const FontPlatformData& platformData)
304{
305 auto addResult = fontVerticalDataCache().ensure(platformData, [&platformData] {
306 return OpenTypeVerticalData::create(platformData);
307 });
308 return addResult.iterator->value;
309}
310#endif
311
312#if PLATFORM(IOS_FAMILY)
313const unsigned cMaxInactiveFontData = 120;
314const unsigned cTargetInactiveFontData = 100;
315#else
316const unsigned cMaxInactiveFontData = 225;
317const unsigned cTargetInactiveFontData = 200;
318#endif
319
320const unsigned cMaxUnderMemoryPressureInactiveFontData = 50;
321const unsigned cTargetUnderMemoryPressureInactiveFontData = 30;
322
323RefPtr<Font> FontCache::fontForFamily(const FontDescription& fontDescription, const AtomicString& family, const FontFeatureSettings* fontFaceFeatures, const FontVariantSettings* fontFaceVariantSettings, FontSelectionSpecifiedCapabilities fontFaceCapabilities, bool checkingAlternateName)
324{
325 if (!m_purgeTimer.isActive())
326 m_purgeTimer.startOneShot(0_s);
327
328 if (auto* platformData = getCachedFontPlatformData(fontDescription, family, fontFaceFeatures, fontFaceVariantSettings, fontFaceCapabilities, checkingAlternateName))
329 return fontForPlatformData(*platformData);
330
331 return nullptr;
332}
333
334Ref<Font> FontCache::fontForPlatformData(const FontPlatformData& platformData)
335{
336#if PLATFORM(IOS_FAMILY)
337 auto locker = holdLock(fontLock);
338#endif
339
340 auto addResult = cachedFonts().ensure(platformData, [&] {
341 return Font::create(platformData);
342 });
343
344 ASSERT(addResult.iterator->value->platformData() == platformData);
345
346 return addResult.iterator->value.copyRef();
347}
348
349void FontCache::purgeInactiveFontDataIfNeeded()
350{
351 bool underMemoryPressure = MemoryPressureHandler::singleton().isUnderMemoryPressure();
352 unsigned inactiveFontDataLimit = underMemoryPressure ? cMaxUnderMemoryPressureInactiveFontData : cMaxInactiveFontData;
353
354 LOG(Fonts, "FontCache::purgeInactiveFontDataIfNeeded() - underMemoryPressure %d, inactiveFontDataLimit %u", underMemoryPressure, inactiveFontDataLimit);
355
356 if (cachedFonts().size() < inactiveFontDataLimit)
357 return;
358 unsigned inactiveCount = inactiveFontCount();
359 if (inactiveCount <= inactiveFontDataLimit)
360 return;
361
362 unsigned targetFontDataLimit = underMemoryPressure ? cTargetUnderMemoryPressureInactiveFontData : cTargetInactiveFontData;
363 purgeInactiveFontData(inactiveCount - targetFontDataLimit);
364}
365
366void FontCache::purgeInactiveFontData(unsigned purgeCount)
367{
368 LOG(Fonts, "FontCache::purgeInactiveFontData(%u)", purgeCount);
369
370 pruneUnreferencedEntriesFromFontCascadeCache();
371 pruneSystemFallbackFonts();
372
373#if PLATFORM(IOS_FAMILY)
374 auto locker = holdLock(fontLock);
375#endif
376
377 while (purgeCount) {
378 Vector<Ref<Font>, 20> fontsToDelete;
379 for (auto& font : cachedFonts().values()) {
380 LOG(Fonts, " trying to purge font %s (has one ref %d)", font->platformData().description().utf8().data(), font->hasOneRef());
381 if (!font->hasOneRef())
382 continue;
383 fontsToDelete.append(font.copyRef());
384 if (!--purgeCount)
385 break;
386 }
387 // Fonts may ref other fonts so we loop until there are no changes.
388 if (fontsToDelete.isEmpty())
389 break;
390 for (auto& font : fontsToDelete) {
391 bool success = cachedFonts().remove(font->platformData());
392 ASSERT_UNUSED(success, success);
393#if ENABLE(OPENTYPE_VERTICAL)
394 fontVerticalDataCache().remove(font->platformData());
395#endif
396 }
397 };
398
399 Vector<FontPlatformDataCacheKey> keysToRemove;
400 keysToRemove.reserveInitialCapacity(fontPlatformDataCache().size());
401 for (auto& entry : fontPlatformDataCache()) {
402 if (entry.value && !cachedFonts().contains(*entry.value))
403 keysToRemove.uncheckedAppend(entry.key);
404 }
405
406 LOG(Fonts, " removing %lu keys", keysToRemove.size());
407
408 for (auto& key : keysToRemove)
409 fontPlatformDataCache().remove(key);
410
411 platformPurgeInactiveFontData();
412}
413
414size_t FontCache::fontCount()
415{
416 return cachedFonts().size();
417}
418
419size_t FontCache::inactiveFontCount()
420{
421#if PLATFORM(IOS_FAMILY)
422 auto locker = holdLock(fontLock);
423#endif
424 unsigned count = 0;
425 for (auto& font : cachedFonts().values()) {
426 if (font->hasOneRef())
427 ++count;
428 }
429 return count;
430}
431
432static HashSet<FontSelector*>* gClients;
433
434void FontCache::addClient(FontSelector& client)
435{
436 if (!gClients)
437 gClients = new HashSet<FontSelector*>;
438
439 ASSERT(!gClients->contains(&client));
440 gClients->add(&client);
441}
442
443void FontCache::removeClient(FontSelector& client)
444{
445 ASSERT(gClients);
446 ASSERT(gClients->contains(&client));
447
448 gClients->remove(&client);
449}
450
451static unsigned short gGeneration = 0;
452
453unsigned short FontCache::generation()
454{
455 return gGeneration;
456}
457
458void FontCache::invalidate()
459{
460 if (!gClients) {
461 ASSERT(fontPlatformDataCache().isEmpty());
462 return;
463 }
464
465 fontPlatformDataCache().clear();
466#if ENABLE(OPENTYPE_VERTICAL)
467 fontVerticalDataCache().clear();
468#endif
469 invalidateFontCascadeCache();
470
471 gGeneration++;
472
473 Vector<Ref<FontSelector>> clients;
474 clients.reserveInitialCapacity(gClients->size());
475 for (auto it = gClients->begin(), end = gClients->end(); it != end; ++it)
476 clients.uncheckedAppend(**it);
477
478 for (unsigned i = 0; i < clients.size(); ++i)
479 clients[i]->fontCacheInvalidated();
480
481 purgeInactiveFontData();
482}
483
484#if !PLATFORM(COCOA)
485
486FontCache::PrewarmInformation FontCache::collectPrewarmInformation() const
487{
488 return { };
489}
490
491void FontCache::prewarm(const PrewarmInformation&)
492{
493}
494
495RefPtr<Font> FontCache::similarFont(const FontDescription&, const AtomicString&)
496{
497 return nullptr;
498}
499#endif
500
501} // namespace WebCore
502