1 // Written in the D programming language.
2 
3 /**
4 DLANGUI library.
5 
6 This module contains base fonts access implementation.
7 
8 To enable OpenGL support, build with version(USE_OPENGL);
9 
10 Synopsis:
11 
12 ----
13 import dlangui.graphics.glsupport;
14 
15 ----
16 
17 Copyright: Vadim Lopatin, 2014
18 License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
19 Authors:   $(WEB coolreader.org, Vadim Lopatin)
20 */
21 module dlangui.graphics.fonts;
22 public import dlangui.graphics.drawbuf;
23 public import dlangui.core.types;
24 public import dlangui.core.logger;
25 import std.algorithm;
26 
27 /// font family
28 enum FontFamily : ubyte {
29     Unspecified,
30     SansSerif,
31     Serif,
32 	Fantasy,
33 	Cursive,
34     MonoSpace
35 }
36 
37 /// useful font weight constants
38 enum FontWeight : int {
39     Normal = 400,
40     Bold = 800
41 }
42 
43 const dchar UNICODE_SOFT_HYPHEN_CODE = 0x00ad;
44 const dchar UNICODE_ZERO_WIDTH_SPACE = 0x200b;
45 const dchar UNICODE_NO_BREAK_SPACE = 0x00a0;
46 const dchar UNICODE_HYPHEN = 0x2010;
47 const dchar UNICODE_NB_HYPHEN = 0x2011;
48 
49 version (USE_OPENGL) {
50     private __gshared void function(uint id) _glyphDestroyCallback;
51     /// get glyph destroy callback (to cleanup OpenGL caches)
52     @property void function(uint id) glyphDestroyCallback() { return _glyphDestroyCallback; }
53     /// set glyph destroy callback (to cleanup OpenGL caches)
54     @property void glyphDestroyCallback(void function(uint id) callback) { _glyphDestroyCallback = callback; }
55 
56     private __gshared uint _nextGlyphId;
57     uint nextGlyphId() { return _nextGlyphId++; }
58 }
59 
60 /// font glyph cache
61 struct GlyphCache
62 {
63 	//Glyph[ushort] _map;
64     Glyph[][256] _glyphs;
65 
66 	//Glyph[] _array;
67 
68 	// find glyph in cache
69 	Glyph * find(ushort glyphIndex) {
70         //if (_array is null)
71         //    _array = new Glyph[0x10000];
72         ushort p = glyphIndex >> 8;
73         if (_glyphs[p] is null)
74             return null;
75         ushort i = glyphIndex & 0xFF;
76         if (_glyphs[p][i].glyphIndex == 0)
77 			return null;
78         return &_glyphs[p][i];
79         //if (_array[glyphIndex].glyphIndex)
80         //    return &_array[glyphIndex];
81         //return null;
82 		
83         //Glyph * res = (glyphIndex in _map);
84         //if (res !is null)
85         //    res.lastUsage = 1;
86         //return res;
87 	}
88 
89 	/// put glyph to cache
90 	Glyph * put(ushort glyphIndex, Glyph * glyph) {
91         ushort p = glyphIndex >> 8;
92         ushort i = glyphIndex & 0xFF;
93         if (_glyphs[p] is null)
94             _glyphs[p] = new Glyph[256];
95         _glyphs[p][i] = *glyph;
96         return &_glyphs[p][i]; // = *glyph;
97 
98         //_array[glyphIndex] = *glyph;
99         //return &_array[glyphIndex];
100 
101         //_map[glyphIndex] = *glyph;
102         //Glyph * res = glyphIndex in _map;
103         //res.lastUsage = 1;
104         //return res;
105 	}
106 
107 	// clear usage flags for all entries
108 	void checkpoint() {
109         //foreach(ref Glyph item; _map) {
110         //    item.lastUsage = 0;
111         //}
112         foreach(ref Glyph[] part; _glyphs) {
113             if (part !is null)
114                 foreach(ref Glyph item; part) {
115                     item.lastUsage = 0;
116                 }
117 		}
118         //foreach(ref Glyph item; _array) {
119         //    item.lastUsage = 0;
120         //}
121 	}
122 
123 	/// removes entries not used after last call of checkpoint() or cleanup()
124 	void cleanup() {
125 		//uint dst = 0;
126         // notify about destroyed glyphs
127         version (USE_OPENGL) {
128             if (_glyphDestroyCallback !is null) {
129                 foreach(ref Glyph[] part; _glyphs) {
130                     if (part !is null)
131                         foreach(ref Glyph item; part) {
132                             if (item.lastUsage == 0 && item.glyphIndex)
133                                 _glyphDestroyCallback(item.id);
134                         }
135                 }
136                 //foreach(ref Glyph item; _map) {
137                 //    if (item.lastUsage == 0)
138                 //        _glyphDestroyCallback(item.id);
139                 //}
140             }
141         }
142         //ushort[] forDelete;
143         //foreach(ref Glyph item; _map)
144         //    if (item.lastUsage == 0)
145         //        forDelete ~= item.glyphIndex;
146         //foreach(ushort index; forDelete)
147         //    _map.remove(index);
148         foreach(ref Glyph[] part; _glyphs) {
149             if (part !is null)
150                 foreach(ref Glyph item; part) {
151                     if (item.lastUsage == 0 && item.glyphIndex) {
152                         item.glyphIndex = 0;
153                         item.glyph = null;
154                         version (USE_OPENGL) {
155                             item.id = 0;
156                         }
157                     }
158                 }
159         }
160         //foreach(ref Glyph item; _array) {
161         //    if (item.lastUsage == 0 && item.glyphIndex) {
162         //        item.glyphIndex = 0;
163         //        item.glyph = null;
164         //        item.id = 0;
165         //    }
166         //}
167 	}
168 
169 	/// removes all entries
170 	void clear() {
171         // notify about destroyed glyphs
172         version (USE_OPENGL) {
173             if (_glyphDestroyCallback !is null) {
174                 foreach(ref Glyph[] part; _glyphs) {
175                     if (part !is null)
176                         foreach(ref Glyph item; part) {
177                             if (item.glyphIndex)
178                                 _glyphDestroyCallback(item.id);
179                         }
180                 }
181             }
182         }
183         foreach(ref Glyph[] part; _glyphs) {
184             if (part !is null)
185                 foreach(ref Glyph item; part) {
186                     if (item.glyphIndex) {
187                         item.glyphIndex = 0;
188                         item.glyph = null;
189                         version (USE_OPENGL) {
190                             item.id = 0;
191                         }
192                     }
193                 }
194         }
195 
196         //version (USE_OPENGL) {
197         //    if (_glyphDestroyCallback !is null) {
198         //        foreach(ref Glyph item; _array) {
199         //            if (item.glyphIndex)
200         //                _glyphDestroyCallback(item.id);
201         //        }
202         //        //foreach(ref Glyph item; _map) {
203         //        //    if (item.lastUsage == 0)
204         //        //        _glyphDestroyCallback(item.id);
205         //        //}
206         //    }
207         //}
208         ////_map.clear();
209         //foreach(ref Glyph item; _array) {
210         //    if (item.glyphIndex) {
211         //        item.glyphIndex = 0;
212         //        item.glyph = null;
213         //        item.id = 0;
214         //    }
215         //}
216 	}
217 	~this() {
218 		clear();
219 	}
220 }
221 
222 /// Font object
223 class Font : RefCountedObject {
224     abstract @property int size();
225     abstract @property int height();
226     abstract @property int weight();
227     abstract @property int baseline();
228     abstract @property bool italic();
229     abstract @property string face();
230     abstract @property FontFamily family();
231     abstract @property bool isNull();
232 	// measure text string, return accumulated widths[] (distance to end of n-th character), returns number of measured chars.
233 	abstract int measureText(const dchar[] text, ref int[] widths, int maxWidth);
234     private int[] _textSizeBuffer; // to avoid GC
235 	// measure text string as single line, returns width and height
236 	Point textSize(const dchar[] text, int maxWidth = 3000) {
237         if (_textSizeBuffer.length < text.length + 1)
238             _textSizeBuffer.length = text.length + 1;
239         //int[] widths = new int[text.length + 1];
240         int charsMeasured = measureText(text, _textSizeBuffer, maxWidth);
241         if (charsMeasured < 1)
242             return Point(0,0);
243         return Point(_textSizeBuffer[charsMeasured - 1], height);
244     }
245 	/// draw text string to buffer
246 	abstract void drawText(DrawBuf buf, int x, int y, const dchar[] text, uint color);
247 	/// get character glyph information
248 	abstract Glyph * getCharGlyph(dchar ch, bool withImage = true);
249 
250 	/// clear usage flags for all entries
251 	abstract void checkpoint();
252 	/// removes entries not used after last call of checkpoint() or cleanup()
253 	abstract void cleanup();
254 
255     void clear() {}
256 
257     ~this() { clear(); }
258 }
259 alias FontRef = Ref!Font;
260 
261 /// font instance collection - utility class, for font manager implementations
262 struct FontList {
263 	FontRef[] _list;
264 	uint _len;
265 	~this() {
266 		clear();
267 	}
268 	
269 	@property uint length() {
270 		return _len;
271 	}
272 	
273 	void clear() {
274 		for (uint i = 0; i < _len; i++) {
275 			_list[i].clear();
276 			_list[i] = null;
277 		}
278 		_len = 0;
279 	}
280 	// returns item by index
281 	ref FontRef get(int index) {
282 		return _list[index];
283 	}
284 	// find by a set of parameters - returns index of found item, -1 if not found
285 	int find(int size, int weight, bool italic, FontFamily family, string face) {
286 		for (int i = 0; i < _len; i++) {
287 			Font item = _list[i].get;
288 			if (item.family != family)
289 				continue;
290 			if (item.size != size)
291 				continue;
292 			if (item.italic != italic || item.weight != weight)
293 				continue;
294 			if (!equal(item.face, face))
295 				continue;
296 			return i;
297 		}
298 		return -1;
299 	}
300 	// find by size only - returns index of found item, -1 if not found
301 	int find(int size) {
302 		for (int i = 0; i < _len; i++) {
303 			Font item = _list[i].get;
304 			if (item.size != size)
305 				continue;
306 			return i;
307 		}
308 		return -1;
309 	}
310 	ref FontRef add(Font item) {
311 		Log.d("FontList.add() enter");
312 		if (_len >= _list.length) {
313 			_list.length = _len < 16 ? 16 : _list.length * 2;
314 		}
315 		_list[_len++] = item;
316 		Log.d("FontList.add() exit");
317 		return _list[_len - 1];
318 	}
319 	// remove unused items - with reference == 1
320 	void cleanup() {
321 		for (int i = 0; i < _len; i++)
322 			if (_list[i].refCount <= 1)
323 				_list[i].clear();
324 		int dst = 0;
325 		for (int i = 0; i < _len; i++) {
326 			if (!_list[i].isNull)
327 				if (i != dst)
328 					_list[dst++] = _list[i];
329 		}
330 		_len = dst;
331 		for (int i = 0; i < _len; i++)
332 			_list[i].cleanup();
333 	}
334 	void checkpoint() {
335 		for (int i = 0; i < _len; i++)
336 			_list[i].checkpoint();
337 	}
338 }
339 
340 
341 /// Access points to fonts.
342 class FontManager {
343     protected static __gshared FontManager _instance;
344 
345     /// sets new font manager singleton instance
346     static @property void instance(FontManager manager) {
347 		if (_instance !is null) {
348 			destroy(_instance);
349             _instance = null;
350         }
351         _instance = manager;
352     }
353 
354     /// returns font manager singleton instance
355     static @property FontManager instance() {
356         return _instance;
357     }
358 
359     /// get font instance best matched specified parameters
360     abstract ref FontRef getFont(int size, int weight, bool italic, FontFamily family, string face);
361 
362 	/// clear usage flags for all entries -- for cleanup of unused fonts
363 	abstract void checkpoint();
364 
365 	/// removes entries not used after last call of checkpoint() or cleanup()
366 	abstract void cleanup();
367 
368 	~this() {
369 		Log.d("Destroying font manager");
370 	}
371 }