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