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