xref: /haiku/src/servers/app/font/GlyphLayoutEngine.h (revision 13581b3d2a71545960b98fefebc5225b5bf29072)
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