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:
FontCacheReference()28 FontCacheReference()
29 :
30 fCacheEntry(NULL),
31 fFallbackReference(NULL),
32 fLocked(false),
33 fWriteLocked(false)
34 {
35 }
36
~FontCacheReference()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
SetTo(FontCacheEntry * entry)48 void SetTo(FontCacheEntry* entry)
49 {
50 ASSERT(entry != NULL);
51 ASSERT(fCacheEntry == NULL);
52
53 fCacheEntry = entry;
54 }
55
ReadLock()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
WriteLock()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
Unlock()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
SetFallback(FontCacheReference * fallback)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
Entry()156 inline FontCacheEntry* Entry() const
157 {
158 return fCacheEntry;
159 }
160
WriteLocked()161 inline bool WriteLocked() const
162 {
163 return fWriteLocked;
164 }
165
166 private:
167
_Cleanup()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
IsWhiteSpace(uint32 charCode)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*
FontCacheEntryFor(const ServerFont & font,bool forceVector)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
LayoutGlyphs(GlyphConsumer & consumer,const ServerFont & font,const char * utf8String,int32 length,int32 maxChars,const escapement_delta * delta,uint8 spacing,const BPoint * offsets,FontCacheReference * _cacheReference)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*
_CreateGlyph(FontCacheReference & cacheReference,BObjectList<FontCacheReference> & fallbacks,const ServerFont & font,bool forceVector,uint32 charCode)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
PopulateFallbacks(BObjectList<FontCacheReference> & fallbacksList,const ServerFont & font,bool forceVector)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*
GetFallbackReference(BObjectList<FontCacheReference> & fallbacks,uint32 charCode)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