1 /* 2 * Copyright 2007-2022, Haiku. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 */ 8 9 #ifndef GLYPH_LAYOUT_ENGINE_H 10 #define GLYPH_LAYOUT_ENGINE_H 11 12 #include "utf8_functions.h" 13 14 #include "FontCache.h" 15 #include "FontCacheEntry.h" 16 #include "GlobalFontManager.h" 17 #include "ServerFont.h" 18 19 #include <Autolock.h> 20 #include <Debug.h> 21 #include <ObjectList.h> 22 #include <SupportDefs.h> 23 24 #include <ctype.h> 25 26 class FontCacheReference { 27 public: 28 FontCacheReference() 29 : 30 fCacheEntry(NULL), 31 fFallbackReference(NULL), 32 fLocked(false), 33 fWriteLocked(false) 34 { 35 } 36 37 ~FontCacheReference() 38 { 39 if (fCacheEntry == NULL) 40 return; 41 42 fFallbackReference = NULL; 43 Unlock(); 44 if (fCacheEntry != NULL) 45 FontCache::Default()->Recycle(fCacheEntry); 46 } 47 48 void SetTo(FontCacheEntry* entry) 49 { 50 ASSERT(entry != NULL); 51 ASSERT(fCacheEntry == NULL); 52 53 fCacheEntry = entry; 54 } 55 56 bool ReadLock() 57 { 58 ASSERT(fCacheEntry != NULL); 59 ASSERT(fWriteLocked == false); 60 61 if (fLocked) 62 return true; 63 64 if (!fCacheEntry->ReadLock()) { 65 _Cleanup(); 66 return false; 67 } 68 69 fLocked = true; 70 return true; 71 } 72 73 bool WriteLock() 74 { 75 ASSERT(fCacheEntry != NULL); 76 77 if (fWriteLocked) 78 return true; 79 80 if (fLocked) { 81 if (!fCacheEntry->ReadUnlock()) { 82 _Cleanup(); 83 return false; 84 } 85 } 86 if (!fCacheEntry->WriteLock()) { 87 _Cleanup(); 88 return false; 89 } 90 91 fLocked = true; 92 fWriteLocked = true; 93 return true; 94 } 95 96 bool Unlock() 97 { 98 ASSERT(fCacheEntry != NULL); 99 100 if (!fLocked) 101 return true; 102 103 if (fWriteLocked) { 104 if (!fCacheEntry->WriteUnlock()) { 105 _Cleanup(); 106 return false; 107 } 108 } else { 109 if (!fCacheEntry->ReadUnlock()) { 110 _Cleanup(); 111 return false; 112 } 113 } 114 115 fLocked = false; 116 fWriteLocked = false; 117 return true; 118 } 119 120 bool SetFallback(FontCacheReference* fallback) 121 { 122 ASSERT(fCacheEntry != NULL); 123 ASSERT(fallback != NULL); 124 ASSERT(fallback->Entry() != NULL); 125 ASSERT(fallback->Entry() != fCacheEntry); 126 127 if (fFallbackReference == fallback) 128 return true; 129 130 if (fFallbackReference != NULL) { 131 fFallbackReference->Unlock(); 132 fFallbackReference = NULL; 133 } 134 135 // We need to create new glyphs with the engine of the fallback font 136 // and store them in the main font cache (not just transfer them from 137 // one cache to the other). So we need both to be write-locked. 138 if (fallback->Entry() < fCacheEntry) { 139 if (fLocked && !Unlock()) 140 return false; 141 if (!fallback->WriteLock()) { 142 WriteLock(); 143 return false; 144 } 145 fFallbackReference = fallback; 146 return WriteLock(); 147 } 148 if (fLocked && !fWriteLocked && !Unlock()) 149 return false; 150 if (!WriteLock() || !fallback->WriteLock()) 151 return false; 152 fFallbackReference = fallback; 153 return true; 154 } 155 156 inline FontCacheEntry* Entry() const 157 { 158 return fCacheEntry; 159 } 160 161 inline bool WriteLocked() const 162 { 163 return fWriteLocked; 164 } 165 166 private: 167 168 void _Cleanup() 169 { 170 if (fFallbackReference != NULL) { 171 fFallbackReference->Unlock(); 172 fFallbackReference = NULL; 173 } 174 if (fCacheEntry != NULL) 175 FontCache::Default()->Recycle(fCacheEntry); 176 fCacheEntry = NULL; 177 fLocked = false; 178 fWriteLocked = false; 179 } 180 181 private: 182 FontCacheEntry* fCacheEntry; 183 FontCacheReference* fFallbackReference; 184 bool fLocked; 185 bool fWriteLocked; 186 }; 187 188 189 class GlyphLayoutEngine { 190 public: 191 static bool IsWhiteSpace(uint32 glyphCode); 192 193 static FontCacheEntry* FontCacheEntryFor(const ServerFont& font, 194 bool forceVector); 195 196 template<class GlyphConsumer> 197 static bool LayoutGlyphs(GlyphConsumer& consumer, 198 const ServerFont& font, 199 const char* utf8String, 200 int32 length, int32 maxChars, 201 const escapement_delta* delta = NULL, 202 uint8 spacing = B_BITMAP_SPACING, 203 const BPoint* offsets = NULL, 204 FontCacheReference* cacheReference = NULL); 205 206 static void PopulateFallbacks( 207 BObjectList<FontCacheReference>& fallbacks, 208 const ServerFont& font, bool forceVector); 209 210 static FontCacheReference* GetFallbackReference( 211 BObjectList<FontCacheReference>& fallbacks, 212 uint32 charCode); 213 214 private: 215 static const GlyphCache* _CreateGlyph( 216 FontCacheReference& cacheReference, 217 BObjectList<FontCacheReference>& fallbacks, 218 const ServerFont& font, bool needsVector, 219 uint32 glyphCode); 220 221 GlyphLayoutEngine(); 222 virtual ~GlyphLayoutEngine(); 223 }; 224 225 226 inline bool 227 GlyphLayoutEngine::IsWhiteSpace(uint32 charCode) 228 { 229 switch (charCode) { 230 case 0x0009: /* tab */ 231 case 0x000b: /* vertical tab */ 232 case 0x000c: /* form feed */ 233 case 0x0020: /* space */ 234 case 0x00a0: /* non breaking space */ 235 case 0x000a: /* line feed */ 236 case 0x000d: /* carriage return */ 237 case 0x2028: /* line separator */ 238 case 0x2029: /* paragraph separator */ 239 return true; 240 } 241 242 return false; 243 } 244 245 246 inline FontCacheEntry* 247 GlyphLayoutEngine::FontCacheEntryFor(const ServerFont& font, bool forceVector) 248 { 249 FontCache* cache = FontCache::Default(); 250 FontCacheEntry* entry = cache->FontCacheEntryFor(font, forceVector); 251 return entry; 252 } 253 254 255 template<class GlyphConsumer> 256 inline bool 257 GlyphLayoutEngine::LayoutGlyphs(GlyphConsumer& consumer, 258 const ServerFont& font, 259 const char* utf8String, int32 length, int32 maxChars, 260 const escapement_delta* delta, uint8 spacing, 261 const BPoint* offsets, FontCacheReference* _cacheReference) 262 { 263 // TODO: implement spacing modes 264 FontCacheEntry* entry = NULL; 265 FontCacheReference* pCacheReference; 266 FontCacheReference cacheReference; 267 BObjectList<FontCacheReference> fallbacksList(21, true); 268 269 if (_cacheReference != NULL) { 270 pCacheReference = _cacheReference; 271 entry = _cacheReference->Entry(); 272 // When there is already a cacheReference, it means there was already 273 // an iteration over the glyphs. The use-case is for example to do 274 // a layout pass to get the string width for the bounding box, then a 275 // second layout pass to actually render the glyphs to the screen. 276 // This means that the fallback entry mechanism will not do any good 277 // for the second pass, since the fallback glyphs have been stored in 278 // the original entry. 279 } else 280 pCacheReference = &cacheReference; 281 282 if (entry == NULL) { 283 entry = FontCacheEntryFor(font, consumer.NeedsVector()); 284 285 if (entry == NULL) 286 return false; 287 pCacheReference->SetTo(entry); 288 if (!pCacheReference->ReadLock()) 289 return false; 290 } // else the entry was already used and is still locked 291 292 consumer.Start(); 293 294 double x = 0.0; 295 double y = 0.0; 296 if (offsets) { 297 x = offsets[0].x; 298 y = offsets[0].y; 299 } 300 301 double advanceX = 0.0; 302 double advanceY = 0.0; 303 double size = font.Size(); 304 305 uint32 lastCharCode = 0; // Needed for kerning in B_STRING_SPACING mode 306 uint32 charCode; 307 int32 index = 0; 308 const char* start = utf8String; 309 while (maxChars-- > 0 && (charCode = UTF8ToCharCode(&utf8String)) != 0) { 310 311 if (offsets != NULL) { 312 // Use direct glyph locations instead of calculating them 313 // from the advance values 314 x = offsets[index].x; 315 y = offsets[index].y; 316 } else { 317 if (spacing == B_STRING_SPACING) 318 entry->GetKerning(lastCharCode, charCode, &advanceX, &advanceY); 319 320 x += advanceX; 321 y += advanceY; 322 } 323 324 const GlyphCache* glyph = entry->CachedGlyph(charCode); 325 if (glyph == NULL) { 326 glyph = _CreateGlyph(*pCacheReference, fallbacksList, font, 327 consumer.NeedsVector(), charCode); 328 329 // Something may have gone wrong while reacquiring the entry lock 330 if (pCacheReference->Entry() == NULL) 331 return false; 332 } 333 334 if (glyph == NULL) { 335 consumer.ConsumeEmptyGlyph(index++, charCode, x, y); 336 advanceX = 0; 337 advanceY = 0; 338 } else { 339 // get next increment for pen position 340 if (spacing == B_CHAR_SPACING) { 341 advanceX = glyph->precise_advance_x * size; 342 advanceY = glyph->precise_advance_y * size; 343 } else { 344 advanceX = glyph->advance_x; 345 advanceY = glyph->advance_y; 346 } 347 348 // adjust for custom spacing 349 if (delta != NULL) { 350 advanceX += IsWhiteSpace(charCode) 351 ? delta->space : delta->nonspace; 352 } 353 354 if (!consumer.ConsumeGlyph(index++, charCode, glyph, entry, x, y, 355 advanceX, advanceY)) { 356 advanceX = 0.0; 357 advanceY = 0.0; 358 break; 359 } 360 } 361 362 lastCharCode = charCode; 363 if (utf8String - start + 1 > length) 364 break; 365 } 366 367 x += advanceX; 368 y += advanceY; 369 consumer.Finish(x, y); 370 371 return true; 372 } 373 374 375 inline const GlyphCache* 376 GlyphLayoutEngine::_CreateGlyph(FontCacheReference& cacheReference, 377 BObjectList<FontCacheReference>& fallbacks, 378 const ServerFont& font, bool forceVector, uint32 charCode) 379 { 380 FontCacheEntry* entry = cacheReference.Entry(); 381 382 // Avoid loading the fallbacks if our font can create the glyph. 383 if (entry->CanCreateGlyph(charCode)) { 384 if (cacheReference.WriteLock()) 385 return entry->CreateGlyph(charCode); 386 return NULL; 387 } 388 389 if (fallbacks.IsEmpty()) 390 PopulateFallbacks(fallbacks, font, forceVector); 391 392 FontCacheReference* fallbackReference = GetFallbackReference(fallbacks, charCode); 393 if (fallbackReference != NULL) { 394 if (cacheReference.SetFallback(fallbackReference)) 395 return entry->CreateGlyph(charCode, fallbackReference->Entry()); 396 if (cacheReference.Entry() == NULL) 397 return NULL; 398 } 399 400 // No one knows how to draw this, so use the missing glyph symbol. 401 if (cacheReference.WriteLock()) 402 return entry->CreateGlyph(charCode); 403 return NULL; 404 } 405 406 407 inline void 408 GlyphLayoutEngine::PopulateFallbacks( 409 BObjectList<FontCacheReference>& fallbacksList, 410 const ServerFont& font, bool forceVector) 411 { 412 ASSERT(fallbacksList.IsEmpty()); 413 414 // TODO: We always get the fallback glyphs from the Noto family, but of 415 // course the fallback font should a) contain the missing glyphs at all 416 // and b) be similar to the original font. So there should be a mapping 417 // of some kind to know the most suitable fallback font. 418 static const char* fallbacks[] = { 419 "Noto Sans", 420 "Noto Sans Thai", 421 "Noto Sans CJK JP", 422 "Noto Sans Cherokee", 423 "Noto Sans Symbols", 424 "Noto Sans Symbols 2", 425 "Noto Emoji", 426 }; 427 428 if (!gFontManager->Lock()) 429 return; 430 431 static const int nFallbacks = B_COUNT_OF(fallbacks); 432 static const int acceptAnyStyle = 2; 433 434 for (int degradeLevel = 0; degradeLevel <= acceptAnyStyle; degradeLevel++) { 435 const char* fontStyle; 436 if (degradeLevel == 0) 437 fontStyle = font.Style(); 438 else if (degradeLevel == 1) 439 fontStyle = "Regular"; 440 else 441 fontStyle = NULL; 442 443 for (int i = 0; i < nFallbacks; i++) { 444 445 FontStyle* fallbackStyle; 446 if (degradeLevel != acceptAnyStyle) { 447 fallbackStyle = gFontManager->GetStyle(fallbacks[i], fontStyle); 448 } else { 449 // At this point we'll just take whatever we are given 450 fallbackStyle = gFontManager->GetStyleByIndex(fallbacks[i], 0); 451 } 452 453 if (fallbackStyle == NULL) 454 continue; 455 456 ServerFont fallbackFont(*fallbackStyle, font.Size()); 457 458 FontCacheEntry* entry = FontCacheEntryFor(fallbackFont, forceVector); 459 if (entry == NULL) 460 continue; 461 462 FontCacheReference* cacheReference = new(std::nothrow) FontCacheReference(); 463 if (cacheReference != NULL) { 464 cacheReference->SetTo(entry); 465 fallbacksList.AddItem(cacheReference); 466 } else 467 FontCache::Default()->Recycle(entry); 468 } 469 } 470 471 gFontManager->Unlock(); 472 } 473 474 475 inline FontCacheReference* 476 GlyphLayoutEngine::GetFallbackReference( 477 BObjectList<FontCacheReference>& fallbacks, uint32 charCode) 478 { 479 int32 count = fallbacks.CountItems(); 480 for (int32 index = 0; index < count; index++) { 481 FontCacheReference* fallbackReference = fallbacks.ItemAt(index); 482 FontCacheEntry* fallbackEntry = fallbackReference->Entry(); 483 if (fallbackEntry != NULL && fallbackEntry->CanCreateGlyph(charCode)) 484 return fallbackReference; 485 } 486 return NULL; 487 } 488 489 490 #endif // GLYPH_LAYOUT_ENGINE_H 491