xref: /haiku/src/servers/app/font/GlyphLayoutEngine.h (revision cbe0a0c436162d78cc3f92a305b64918c839d079)
1 /*
2  * Copyright 2007-2014, 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 "FontManager.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 		fWriteLocked(false)
32 	{
33 	}
34 
35 	~FontCacheReference()
36 	{
37 		Unset();
38 	}
39 
40 	bool SetTo(FontCacheEntry* entry, bool writeLock)
41 	{
42 		ASSERT(entry != NULL);
43 
44 		if (entry == fCacheEntry) {
45 			if (writeLock == fWriteLocked)
46 				return true;
47 			UnlockAndDisown();
48 		} else if (fCacheEntry != NULL)
49 			Unset();
50 
51 		if (writeLock) {
52 			if (!entry->WriteLock()) {
53 				FontCache::Default()->Recycle(entry);
54 				return false;
55 			}
56 		} else if (!entry->ReadLock()) {
57 			FontCache::Default()->Recycle(entry);
58 			return false;
59 		}
60 
61 		fCacheEntry = entry;
62 		fWriteLocked = writeLock;
63 		return true;
64 	}
65 
66 	FontCacheEntry* UnlockAndDisown()
67 	{
68 		if (fCacheEntry == NULL)
69 			return NULL;
70 
71 		if (fWriteLocked)
72 			fCacheEntry->WriteUnlock();
73 		else
74 			fCacheEntry->ReadUnlock();
75 
76 		FontCacheEntry* entry = fCacheEntry;
77 		fCacheEntry = NULL;
78 		fWriteLocked = false;
79 		return entry;
80 	}
81 
82 	void Unset()
83 	{
84 		if (fCacheEntry == NULL)
85 			return;
86 
87 		FontCache::Default()->Recycle(UnlockAndDisown());
88 	}
89 
90 	inline FontCacheEntry* Entry() const
91 	{
92 		return fCacheEntry;
93 	}
94 
95 	inline bool WriteLocked() const
96 	{
97 		return fWriteLocked;
98 	}
99 
100 private:
101 			FontCacheEntry*		fCacheEntry;
102 			bool				fWriteLocked;
103 };
104 
105 
106 class GlyphLayoutEngine {
107 public:
108 	static	bool				IsWhiteSpace(uint32 glyphCode);
109 
110 	static	FontCacheEntry*		FontCacheEntryFor(const ServerFont& font,
111 									bool forceVector);
112 
113 			template<class GlyphConsumer>
114 	static	bool				LayoutGlyphs(GlyphConsumer& consumer,
115 									const ServerFont& font,
116 									const char* utf8String,
117 									int32 length, int32 maxChars,
118 									const escapement_delta* delta = NULL,
119 									uint8 spacing = B_BITMAP_SPACING,
120 									const BPoint* offsets = NULL,
121 									FontCacheReference* cacheReference = NULL);
122 
123 	static	void				PopulateAndLockFallbacks(
124 									BObjectList<FontCacheReference>& fallbacks,
125 									const ServerFont& font, bool forceVector,
126 									bool writeLock);
127 
128 private:
129 	static	const GlyphCache*	_CreateGlyph(
130 									FontCacheReference& cacheReference,
131 									BObjectList<FontCacheReference>& fallbacks,
132 									const ServerFont& font, bool needsVector,
133 									uint32 glyphCode);
134 
135 								GlyphLayoutEngine();
136 	virtual						~GlyphLayoutEngine();
137 };
138 
139 
140 inline bool
141 GlyphLayoutEngine::IsWhiteSpace(uint32 charCode)
142 {
143 	switch (charCode) {
144 		case 0x0009:	/* tab */
145 		case 0x000b:	/* vertical tab */
146 		case 0x000c:	/* form feed */
147 		case 0x0020:	/* space */
148 		case 0x00a0:	/* non breaking space */
149 		case 0x000a:	/* line feed */
150 		case 0x000d:	/* carriage return */
151 		case 0x2028:	/* line separator */
152 		case 0x2029:	/* paragraph separator */
153 			return true;
154 	}
155 
156 	return false;
157 }
158 
159 
160 inline FontCacheEntry*
161 GlyphLayoutEngine::FontCacheEntryFor(const ServerFont& font, bool forceVector)
162 {
163 	FontCache* cache = FontCache::Default();
164 	FontCacheEntry* entry = cache->FontCacheEntryFor(font, forceVector);
165 	return entry;
166 }
167 
168 
169 template<class GlyphConsumer>
170 inline bool
171 GlyphLayoutEngine::LayoutGlyphs(GlyphConsumer& consumer,
172 	const ServerFont& font,
173 	const char* utf8String, int32 length, int32 maxChars,
174 	const escapement_delta* delta, uint8 spacing,
175 	const BPoint* offsets, FontCacheReference* _cacheReference)
176 {
177 	// TODO: implement spacing modes
178 	FontCacheEntry* entry = NULL;
179 	FontCacheReference* pCacheReference;
180 	FontCacheReference cacheReference;
181 	BObjectList<FontCacheReference> fallbacksList(21, true);
182 
183 	if (_cacheReference != NULL) {
184 		pCacheReference = _cacheReference;
185 		entry = _cacheReference->Entry();
186 		// When there is already a cacheReference, it means there was already
187 		// an iteration over the glyphs. The use-case is for example to do
188 		// a layout pass to get the string width for the bounding box, then a
189 		// second layout pass to actually render the glyphs to the screen.
190 		// This means that the fallback entry mechanism will not do any good
191 		// for the second pass, since the fallback glyphs have been stored in
192 		// the original entry.
193 	} else
194 		pCacheReference = &cacheReference;
195 
196 	if (entry == NULL) {
197 		entry = FontCacheEntryFor(font, consumer.NeedsVector());
198 
199 		if (entry == NULL)
200 			return false;
201 		if (!pCacheReference->SetTo(entry, false))
202 			return false;
203 	} // else the entry was already used and is still locked
204 
205 	consumer.Start();
206 
207 	double x = 0.0;
208 	double y = 0.0;
209 	if (offsets) {
210 		x = offsets[0].x;
211 		y = offsets[0].y;
212 	}
213 
214 	double advanceX = 0.0;
215 	double advanceY = 0.0;
216 	double size = font.Size();
217 
218 	uint32 lastCharCode = 0; // Needed for kerning in B_STRING_SPACING mode
219 	uint32 charCode;
220 	int32 index = 0;
221 	const char* start = utf8String;
222 	while (maxChars-- > 0 && (charCode = UTF8ToCharCode(&utf8String)) != 0) {
223 
224 		if (offsets != NULL) {
225 			// Use direct glyph locations instead of calculating them
226 			// from the advance values
227 			x = offsets[index].x;
228 			y = offsets[index].y;
229 		} else {
230 			if (spacing == B_STRING_SPACING)
231 				entry->GetKerning(lastCharCode, charCode, &advanceX, &advanceY);
232 
233 			x += advanceX;
234 			y += advanceY;
235 		}
236 
237 		const GlyphCache* glyph = entry->CachedGlyph(charCode);
238 		if (glyph == NULL) {
239 			glyph = _CreateGlyph(*pCacheReference, fallbacksList, font,
240 				consumer.NeedsVector(), charCode);
241 
242 			// Something may have gone wrong while reacquiring the entry lock
243 			if (pCacheReference->Entry() == NULL)
244 				return false;
245 		}
246 
247 		if (glyph == NULL) {
248 			consumer.ConsumeEmptyGlyph(index++, charCode, x, y);
249 			advanceX = 0;
250 			advanceY = 0;
251 		} else {
252 			// get next increment for pen position
253 			if (spacing == B_CHAR_SPACING) {
254 				advanceX = glyph->precise_advance_x * size;
255 				advanceY = glyph->precise_advance_y * size;
256 			} else {
257 				advanceX = glyph->advance_x;
258 				advanceY = glyph->advance_y;
259 			}
260 
261 			// adjust for custom spacing
262 			if (delta != NULL) {
263 				advanceX += IsWhiteSpace(charCode)
264 					? delta->space : delta->nonspace;
265 			}
266 
267 			if (!consumer.ConsumeGlyph(index++, charCode, glyph, entry, x, y,
268 					advanceX, advanceY)) {
269 				advanceX = 0.0;
270 				advanceY = 0.0;
271 				break;
272 			}
273 		}
274 
275 		lastCharCode = charCode;
276 		if (utf8String - start + 1 > length)
277 			break;
278 	}
279 
280 	x += advanceX;
281 	y += advanceY;
282 	consumer.Finish(x, y);
283 
284 	return true;
285 }
286 
287 
288 inline const GlyphCache*
289 GlyphLayoutEngine::_CreateGlyph(FontCacheReference& cacheReference,
290 	BObjectList<FontCacheReference>& fallbacks,
291 	const ServerFont& font, bool forceVector, uint32 charCode)
292 {
293 	FontCacheEntry* entry = cacheReference.Entry();
294 
295 	// Avoid loading and locking the fallbacks if our font can create the glyph.
296 	if (entry->CanCreateGlyph(charCode)) {
297 		if (cacheReference.SetTo(entry, true))
298 			return entry->CreateGlyph(charCode);
299 		return NULL;
300 	}
301 
302 	if (fallbacks.IsEmpty()) {
303 		// We need to create new glyphs with the engine of the fallback font
304 		// and store them in the main font cache (not just transfer them from
305 		// one cache to the other). So we need both to be write-locked.
306 		// The main font is unlocked first, in case it is also in the fallback
307 		// list, so that we always keep the same order to avoid deadlocks.
308 		cacheReference.UnlockAndDisown();
309 		PopulateAndLockFallbacks(fallbacks, font, forceVector, true);
310 		if (!cacheReference.SetTo(entry, true)) {
311 			return NULL;
312 		}
313 	}
314 
315 	int32 count = fallbacks.CountItems();
316 	for (int32 index = 0; index < count; index++) {
317 		FontCacheEntry* fallbackEntry = fallbacks.ItemAt(index)->Entry();
318 		if (fallbackEntry->CanCreateGlyph(charCode))
319 			return entry->CreateGlyph(charCode, fallbackEntry);
320 	}
321 
322 	return entry->CreateGlyph(charCode);
323 		// No one knows how to draw this, so use the missing glyph symbol.
324 }
325 
326 
327 inline void
328 GlyphLayoutEngine::PopulateAndLockFallbacks(
329 	BObjectList<FontCacheReference>& fallbacksList,
330 	const ServerFont& font, bool forceVector, bool writeLock)
331 {
332 	ASSERT(fallbacksList.IsEmpty());
333 
334 	// TODO: We always get the fallback glyphs from the Noto family, but of
335 	// course the fallback font should a) contain the missing glyphs at all
336 	// and b) be similar to the original font. So there should be a mapping
337 	// of some kind to know the most suitable fallback font.
338 	static const char* fallbacks[] = {
339 		"Noto Sans Display",
340 		"Noto Sans Thai",
341 		"Noto Sans CJK JP",
342 		"Noto Sans Symbols",
343 		"Noto Sans Symbols2",
344 		"Noto Emoji",
345 	};
346 
347 	if (!gFontManager->Lock())
348 		return;
349 
350 	static const int nStyles = 3;
351 	static const int nFallbacks = B_COUNT_OF(fallbacks);
352 	FontCacheEntry* fallbackCacheEntries[nStyles * nFallbacks];
353 	int entries = 0;
354 
355 	for (int c = 0; c < nStyles; c++) {
356 		const char* fontStyle;
357 		if (c == 0)
358 			fontStyle = font.Style();
359 		else if (c == 1)
360 			fontStyle = "Regular";
361 		else
362 			fontStyle = NULL;
363 
364 		for (int i = 0; i < nFallbacks; i++) {
365 
366 			FontStyle* fallbackStyle = gFontManager->GetStyle(fallbacks[i],
367 				fontStyle, 0xffff, 0);
368 
369 			if (fallbackStyle == NULL)
370 				continue;
371 
372 			ServerFont fallbackFont(*fallbackStyle, font.Size());
373 
374 			FontCacheEntry* entry = FontCacheEntryFor(
375 				fallbackFont, forceVector);
376 
377 			if (entry == NULL)
378 				continue;
379 
380 			fallbackCacheEntries[entries++] = entry;
381 
382 		}
383 
384 	}
385 
386 	gFontManager->Unlock();
387 
388 	// Finally lock the entries and save their references
389 	for (int i = 0; i < entries; i++) {
390 		FontCacheReference* cacheReference =
391 			new(std::nothrow) FontCacheReference();
392 		if (cacheReference != NULL) {
393 			if (cacheReference->SetTo(fallbackCacheEntries[i], writeLock))
394 				fallbacksList.AddItem(cacheReference);
395 			else
396 				delete cacheReference;
397 		}
398 	}
399 
400 	return;
401 }
402 
403 
404 #endif // GLYPH_LAYOUT_ENGINE_H
405