xref: /haiku/src/servers/app/font/GlyphLayoutEngine.h (revision 6aff37d1c79e20748c683ae224bd629f88a5b0be)
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 <Debug.h>
20 
21 #include <ctype.h>
22 
23 class FontCacheReference {
24 public:
25 	FontCacheReference()
26 		:
27 		fCacheEntry(NULL),
28 		fWriteLocked(false)
29 	{
30 	}
31 
32 	~FontCacheReference()
33 	{
34 		Unset();
35 	}
36 
37 	void SetTo(FontCacheEntry* entry, bool writeLocked)
38 	{
39 		// NOTE: If the semantics are changed such
40 		// that the reference to a previous entry
41 		// is properly released, then don't forget
42 		// to adapt existing which transfers
43 		// responsibility of entries between
44 		// references!
45 		fCacheEntry = entry;
46 		fWriteLocked = writeLocked;
47 	}
48 
49 	void Unset()
50 	{
51 		if (fCacheEntry == NULL)
52 			return;
53 
54 		if (fWriteLocked)
55 			fCacheEntry->WriteUnlock();
56 		else
57 			fCacheEntry->ReadUnlock();
58 
59 		FontCache::Default()->Recycle(fCacheEntry);
60 	}
61 
62 	inline FontCacheEntry* Entry() const
63 	{
64 		return fCacheEntry;
65 	}
66 
67 	inline bool WriteLocked() const
68 	{
69 		return fWriteLocked;
70 	}
71 
72 private:
73 			FontCacheEntry*		fCacheEntry;
74 			bool				fWriteLocked;
75 };
76 
77 
78 class GlyphLayoutEngine {
79 public:
80 	static	bool				IsWhiteSpace(uint32 glyphCode);
81 
82 	static	FontCacheEntry*		FontCacheEntryFor(const ServerFont& font,
83 									bool forceVector,
84 									const FontCacheEntry* disallowedEntry,
85 									const char* utf8String, int32 length,
86 									FontCacheReference& cacheReference,
87 									bool needsWriteLock);
88 
89 			template<class GlyphConsumer>
90 	static	bool				LayoutGlyphs(GlyphConsumer& consumer,
91 									const ServerFont& font,
92 									const char* utf8String,
93 									int32 length,
94 									const escapement_delta* delta = NULL,
95 									uint8 spacing = B_BITMAP_SPACING,
96 									const BPoint* offsets = NULL,
97 									FontCacheReference* cacheReference = NULL);
98 
99 private:
100 	static	bool				_WriteLockAndAcquireFallbackEntry(
101 									FontCacheReference& cacheReference,
102 									FontCacheEntry* entry,
103 									const ServerFont& font, bool needsVector,
104 									const char* utf8String, int32 length,
105 									FontCacheReference& fallbackCacheReference,
106 									FontCacheEntry*& fallbackEntry);
107 
108 								GlyphLayoutEngine();
109 	virtual						~GlyphLayoutEngine();
110 };
111 
112 
113 inline bool
114 GlyphLayoutEngine::IsWhiteSpace(uint32 charCode)
115 {
116 	switch (charCode) {
117 		case 0x0009:	/* tab */
118 		case 0x000b:	/* vertical tab */
119 		case 0x000c:	/* form feed */
120 		case 0x0020:	/* space */
121 		case 0x00a0:	/* non breaking space */
122 		case 0x000a:	/* line feed */
123 		case 0x000d:	/* carriage return */
124 		case 0x2028:	/* line separator */
125 		case 0x2029:	/* paragraph separator */
126 			return true;
127 	}
128 
129 	return false;
130 }
131 
132 
133 inline FontCacheEntry*
134 GlyphLayoutEngine::FontCacheEntryFor(const ServerFont& font, bool forceVector,
135 	const FontCacheEntry* disallowedEntry, const char* utf8String, int32 length,
136 	FontCacheReference& cacheReference, bool needsWriteLock)
137 {
138 	ASSERT(cacheReference.Entry() == NULL);
139 
140 	FontCache* cache = FontCache::Default();
141 	FontCacheEntry* entry = cache->FontCacheEntryFor(font, forceVector);
142 	if (entry == NULL)
143 		return NULL;
144 
145 	if (entry == disallowedEntry) {
146 		cache->Recycle(entry);
147 		return NULL;
148 	}
149 
150 	if (needsWriteLock) {
151 		if (!entry->WriteLock()) {
152 			cache->Recycle(entry);
153 			return NULL;
154 		}
155 	} else {
156 		if (!entry->ReadLock()) {
157 			cache->Recycle(entry);
158 			return NULL;
159 		}
160 	}
161 
162 	// At this point, we have a valid FontCacheEntry and it is locked in the
163 	// proper mode. We can setup the FontCacheReference so it takes care of
164 	// the locking and recycling from now and return the entry.
165 	cacheReference.SetTo(entry, needsWriteLock);
166 	return entry;
167 }
168 
169 
170 template<class GlyphConsumer>
171 inline bool
172 GlyphLayoutEngine::LayoutGlyphs(GlyphConsumer& consumer,
173 	const ServerFont& font,
174 	const char* utf8String, int32 length,
175 	const escapement_delta* delta, uint8 spacing,
176 	const BPoint* offsets, FontCacheReference* _cacheReference)
177 {
178 	// TODO: implement spacing modes
179 	FontCacheEntry* entry = NULL;
180 	FontCacheReference cacheReference;
181 	FontCacheEntry* fallbackEntry = NULL;
182 	FontCacheReference fallbackCacheReference;
183 	if (_cacheReference != NULL) {
184 		entry = _cacheReference->Entry();
185 		// When there is already a cacheReference, it means there was already
186 		// an iteration over the glyphs. The use-case is for example to do
187 		// a layout pass to get the string width for the bounding box, then a
188 		// second layout pass to actually render the glyphs to the screen.
189 		// This means that the fallback entry mechanism will not do any good
190 		// for the second pass, since the fallback glyphs have been stored in
191 		// the original entry.
192 	}
193 
194 	if (entry == NULL) {
195 		entry = FontCacheEntryFor(font, consumer.NeedsVector(), NULL,
196 			utf8String, length, cacheReference, false);
197 
198 		if (entry == NULL)
199 			return false;
200 	} // else the entry was already used and is still locked
201 
202 	consumer.Start();
203 
204 	double x = 0.0;
205 	double y = 0.0;
206 	if (offsets) {
207 		x = offsets[0].x;
208 		y = offsets[0].y;
209 	}
210 
211 	double advanceX = 0.0;
212 	double advanceY = 0.0;
213 	double size = font.Size();
214 
215 	uint32 lastCharCode = 0; // Needed for kerning in B_STRING_SPACING mode
216 	uint32 charCode;
217 	int32 index = 0;
218 	bool writeLocked = false;
219 	const char* start = utf8String;
220 	while ((charCode = UTF8ToCharCode(&utf8String))) {
221 
222 		if (offsets != NULL) {
223 			// Use direct glyph locations instead of calculating them
224 			// from the advance values
225 			x = offsets[index].x;
226 			y = offsets[index].y;
227 		} else {
228 			if (spacing == B_STRING_SPACING)
229 				entry->GetKerning(lastCharCode, charCode, &advanceX, &advanceY);
230 
231 			x += advanceX;
232 			y += advanceY;
233 		}
234 
235 		const GlyphCache* glyph = entry->CachedGlyph(charCode);
236 		if (glyph == NULL) {
237 			// The glyph has not been cached yet, switch to a write lock,
238 			// acquire the fallback entry and create the glyph. Note that
239 			// the write lock will persist (in the cacheReference) so that
240 			// we only have to do this switch once for the whole string.
241 			if (!writeLocked) {
242 				writeLocked = _WriteLockAndAcquireFallbackEntry(cacheReference,
243 					entry, font, consumer.NeedsVector(), utf8String, length,
244 					fallbackCacheReference, fallbackEntry);
245 			}
246 
247 			if (writeLocked)
248 				glyph = entry->CreateGlyph(charCode, fallbackEntry);
249 		}
250 
251 		if (glyph == NULL) {
252 			consumer.ConsumeEmptyGlyph(index++, charCode, x, y);
253 			advanceX = 0;
254 			advanceY = 0;
255 		} else {
256 			// get next increment for pen position
257 			if (spacing == B_CHAR_SPACING) {
258 				advanceX = glyph->precise_advance_x * size;
259 				advanceY = glyph->precise_advance_y * size;
260 			} else {
261 				advanceX = glyph->advance_x;
262 				advanceY = glyph->advance_y;
263 			}
264 
265 			// adjust for custom spacing
266 			if (delta != NULL) {
267 				advanceX += IsWhiteSpace(charCode)
268 					? delta->space : delta->nonspace;
269 			}
270 
271 			if (!consumer.ConsumeGlyph(index++, charCode, glyph, entry, x, y,
272 					advanceX, advanceY)) {
273 				advanceX = 0.0;
274 				advanceY = 0.0;
275 				break;
276 			}
277 		}
278 
279 		lastCharCode = charCode;
280 		if (utf8String - start + 1 > length)
281 			break;
282 	}
283 
284 	x += advanceX;
285 	y += advanceY;
286 	consumer.Finish(x, y);
287 
288 	if (_cacheReference != NULL && _cacheReference->Entry() == NULL) {
289 		// The caller passed a FontCacheReference, but this is the first
290 		// iteration -> switch the ownership from the stack allocated
291 		// FontCacheReference to the one passed by the caller. The fallback
292 		// FontCacheReference is not affected by this, since it is never used
293 		// during a second iteration.
294 		_cacheReference->SetTo(entry, cacheReference.WriteLocked());
295 		cacheReference.SetTo(NULL, false);
296 	}
297 	return true;
298 }
299 
300 
301 inline bool
302 GlyphLayoutEngine::_WriteLockAndAcquireFallbackEntry(
303 	FontCacheReference& cacheReference, FontCacheEntry* entry,
304 	const ServerFont& font, bool forceVector, const char* utf8String,
305 	int32 length, FontCacheReference& fallbackCacheReference,
306 	FontCacheEntry*& fallbackEntry)
307 {
308 	// We need a fallback font, since potentially, we have to obtain missing
309 	// glyphs from it. We need to obtain the fallback font while we have not
310 	// locked anything, since locking the FontManager with the write-lock held
311 	// can obvisouly lead to a deadlock.
312 
313 	bool writeLocked = entry->IsWriteLocked();
314 
315 	if (writeLocked) {
316 		entry->WriteUnlock();
317 	} else {
318 		cacheReference.SetTo(NULL, false);
319 		entry->ReadUnlock();
320 	}
321 
322 	// TODO: We always get the fallback glyphs from the Noto family, but of
323 	// course the fallback font should a) contain the missing glyphs at all
324 	// and b) be similar to the original font. So there should be a mapping
325 	// of some kind to know the most suitable fallback font.
326 	static const char* fallbacks[] = {
327 		"Noto Sans",
328 		"Noto Sans CJK JP",
329 		"Noto Sans Symbols",
330 		NULL
331 	};
332 
333 	int i = 0;
334 
335 	// Try to get the glyph from the fallback fonts
336 	while(fallbacks[i] != NULL)
337 	{
338 		if (gFontManager->Lock()) {
339 			FontStyle* fallbackStyle = gFontManager->GetStyleByIndex(
340 				fallbacks[i], 0);
341 			if (fallbackStyle != NULL) {
342 				ServerFont fallbackFont(*fallbackStyle, font.Size());
343 				gFontManager->Unlock();
344 
345 				// Force the write-lock on the fallback entry, since we
346 				// don't transfer or copy GlyphCache objects from one cache
347 				// to the other, but create new glyphs which are stored in
348 				// "entry" in any case, which requires the write cache for
349 				// sure (used FontEngine of fallbackEntry).
350 				fallbackEntry = FontCacheEntryFor(fallbackFont, forceVector,
351 					entry, utf8String, length, fallbackCacheReference, true);
352 
353 				if (fallbackEntry != NULL)
354 					break;
355 			} else
356 				gFontManager->Unlock();
357 		}
358 
359 		i++;
360 	}
361 	// NOTE: We don't care if fallbackEntry is still NULL, fetching
362 	// alternate glyphs will simply not work.
363 
364 	if (!entry->WriteLock()) {
365 		FontCache::Default()->Recycle(entry);
366 		return false;
367 	}
368 
369 	if (!writeLocked) {
370 		// Update the FontCacheReference, since the locking kind changed.
371 		cacheReference.SetTo(entry, true);
372 	}
373 	return true;
374 }
375 
376 
377 #endif // GLYPH_LAYOUT_ENGINE_H
378