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
26class FontCacheReference {
27public:
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
166private:
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
181private:
182			FontCacheEntry*		fCacheEntry;
183			FontCacheReference*	fFallbackReference;
184			bool				fLocked;
185			bool				fWriteLocked;
186};
187
188
189class GlyphLayoutEngine {
190public:
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
214private:
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
226inline bool
227GlyphLayoutEngine::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
246inline FontCacheEntry*
247GlyphLayoutEngine::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
255template<class GlyphConsumer>
256inline bool
257GlyphLayoutEngine::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
375inline const GlyphCache*
376GlyphLayoutEngine::_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
407inline void
408GlyphLayoutEngine::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
433	for (int c = 0; c < 3; c++) {
434		const char* fontStyle;
435		if (c == 0)
436			fontStyle = font.Style();
437		else if (c == 1)
438			fontStyle = "Regular";
439		else
440			fontStyle = NULL;
441
442		for (int i = 0; i < nFallbacks; i++) {
443
444			FontStyle* fallbackStyle = gFontManager->GetStyle(fallbacks[i],
445				fontStyle, 0xffff, 0);
446			if (fallbackStyle == NULL)
447				continue;
448
449			ServerFont fallbackFont(*fallbackStyle, font.Size());
450
451			FontCacheEntry* entry = FontCacheEntryFor(fallbackFont, forceVector);
452			if (entry == NULL)
453				continue;
454
455			FontCacheReference* cacheReference = new(std::nothrow) FontCacheReference();
456			if (cacheReference != NULL) {
457				cacheReference->SetTo(entry);
458				fallbacksList.AddItem(cacheReference);
459			} else
460				FontCache::Default()->Recycle(entry);
461
462		}
463
464	}
465
466	gFontManager->Unlock();
467}
468
469
470inline FontCacheReference*
471GlyphLayoutEngine::GetFallbackReference(
472	BObjectList<FontCacheReference>& fallbacks, uint32 charCode)
473{
474	int32 count = fallbacks.CountItems();
475	for (int32 index = 0; index < count; index++) {
476		FontCacheReference* fallbackReference = fallbacks.ItemAt(index);
477		FontCacheEntry* fallbackEntry = fallbackReference->Entry();
478		if (fallbackEntry != NULL && fallbackEntry->CanCreateGlyph(charCode))
479			return fallbackReference;
480	}
481	return NULL;
482}
483
484
485#endif // GLYPH_LAYOUT_ENGINE_H
486