1 module dlangui.platforms.windows.win32fonts;
2 
3 version (Windows) {
4 
5 import win32.windows;
6 import dlangui.graphics.fonts;
7 import dlangui.platforms.windows.win32drawbuf;
8 import std.string;
9 import std.utf;
10 
11 //auto toUTF16z(S)(S s)
12 //{
13     //return toUTFz!(const(wchar)*)(s);
14 //}
15 
16 private struct FontDef {
17     immutable FontFamily _family;
18     immutable string _face;
19 	immutable ubyte _pitchAndFamily;
20 	@property FontFamily family() { return _family; }
21 	@property string face() { return _face; }
22 	@property ubyte pitchAndFamily() { return _pitchAndFamily; }
23 	this(FontFamily family, string face, ubyte putchAndFamily) {
24 		_family = family;
25 		_face = face;
26 		_pitchAndFamily = pitchAndFamily;
27 	}
28 }
29 
30 
31 /**
32 * Font implementation based on Win32 API system fonts.
33 */
34 class Win32Font : Font {
35     HFONT _hfont;
36     int _size;
37     int _height;
38     int _weight;
39     int _baseline;
40     bool _italic;
41     string _face;
42     FontFamily _family;
43     LOGFONTA _logfont;
44     Win32ColorDrawBuf _drawbuf;
45 	GlyphCache _glyphCache;
46 
47 	/// need to call create() after construction to initialize font
48     this() {
49     }
50 
51 	/// do cleanup
52 	~this() {
53 		clear();
54 	}
55 
56 	/// cleanup resources
57     override void clear() {
58         if (_hfont !is null)
59         {
60             DeleteObject(_hfont);
61             _hfont = NULL;
62             _height = 0;
63             _baseline = 0;
64             _size = 0;
65         }
66 		if (_drawbuf !is null) {
67 			destroy(_drawbuf);
68 			_drawbuf = null;
69 		}
70     }
71 
72 	uint getGlyphIndex(dchar code)
73 	{
74 		if (_drawbuf is null)
75 			return 0;
76 		wchar[2] s;
77 		wchar[2] g;
78 		s[0] = cast(wchar)code;
79 		s[1] = 0;
80 		g[0] = 0;
81 		GCP_RESULTSW gcp;
82 		gcp.lStructSize = GCP_RESULTSW.sizeof;
83 		gcp.lpOutString = null;
84 		gcp.lpOrder = null;
85 		gcp.lpDx = null;
86 		gcp.lpCaretPos = null;
87 		gcp.lpClass = null;
88 		gcp.lpGlyphs = g.ptr;
89 		gcp.nGlyphs = 2;
90 		gcp.nMaxFit = 2;
91 
92 		DWORD res = GetCharacterPlacementW(
93                                            _drawbuf.dc, s.ptr, 1,
94                                            1000,
95                                            &gcp,
96                                            0
97                                            );
98 		if (!res)
99 			return 0;
100 		return g[0];
101 	}
102 
103 	override Glyph * getCharGlyph(dchar ch, bool withImage = true) {
104 		uint glyphIndex = getGlyphIndex(ch);
105 		if (!glyphIndex)
106 			return null;
107 		if (glyphIndex >= 0xFFFF)
108 			return null;
109 		Glyph * found = _glyphCache.find(cast(ushort)glyphIndex);
110 		if (found !is null)
111 			return found;
112 		GLYPHMETRICS metrics;
113 
114 		MAT2 identity = { {0,1}, {0,0}, {0,0}, {0,1} };
115 		uint res;
116 		res = GetGlyphOutlineW( _drawbuf.dc, cast(wchar)ch,
117                                 GGO_METRICS,
118                                &metrics,
119                                0,
120                                null,
121                                &identity );
122 		if (res==GDI_ERROR)
123 			return null;
124 		int gs = GetGlyphOutlineW( _drawbuf.dc, cast(wchar)ch,
125                                    GGO_GRAY8_BITMAP, //GGO_METRICS
126 								  &metrics,
127 								  0,
128 								  NULL,
129 								  &identity );
130 		if (gs >= 0x10000 || gs < 0)
131 			return null;
132 
133 		Glyph g;
134         version (USE_OPENGL) {
135             g.id = nextGlyphId();
136         }
137 		g.blackBoxX = cast(ubyte)metrics.gmBlackBoxX;
138 		g.blackBoxY = cast(ubyte)metrics.gmBlackBoxY;
139 		g.originX = cast(byte)metrics.gmptGlyphOrigin.x;
140 		g.originY = cast(byte)metrics.gmptGlyphOrigin.y;
141 		g.width = cast(ubyte)metrics.gmCellIncX;
142 		g.glyphIndex = cast(ushort)glyphIndex;
143 
144 		if (g.blackBoxX>0 && g.blackBoxY>0)
145 		{
146 			g.glyph = new ubyte[g.blackBoxX * g.blackBoxY];
147 			if (gs>0)
148 			{
149 				ubyte glyph[] = new ubyte[gs];
150 				res = GetGlyphOutlineW( _drawbuf.dc, cast(wchar)ch,
151                                         GGO_GRAY8_BITMAP, //GGO_METRICS
152 									   &metrics,
153 									   gs,
154 									   glyph.ptr,
155 									   &identity );
156 				if (res==GDI_ERROR)
157 				{
158 					return null;
159 				}
160 				int glyph_row_size = (g.blackBoxX + 3) / 4 * 4;
161 				ubyte * src = glyph.ptr;
162 				ubyte * dst = g.glyph.ptr;
163 				for (int y = 0; y < g.blackBoxY; y++)
164 				{
165 					for (int x = 0; x < g.blackBoxX; x++)
166 					{
167 						ubyte b = src[x];
168 						if (b>=64)
169 							b = 63;
170 						b = (b<<2) & 0xFC;
171 						dst[x] = b;
172 					}
173 					src += glyph_row_size;
174 					dst += g.blackBoxX;
175 				}
176 			}
177 			else
178 			{
179 				// empty glyph
180 				for (int i = g.blackBoxX * g.blackBoxY - 1; i >= 0; i--)
181 					g.glyph[i] = 0;
182 			}
183 		}
184 		// found!
185 		return _glyphCache.put(cast(ushort)glyphIndex, &g);
186 	}
187 
188 	// draw text string to buffer
189 	override void drawText(DrawBuf buf, int x, int y, const dchar[] text, uint color) {
190 		int[] widths;
191 		int charsMeasured = measureText(text, widths, 3000);
192 		Rect clip = buf.clipOrFullRect;
193 		if (y + height < clip.top || y >= clip.bottom)
194 			return;
195 		for (int i = 0; i < charsMeasured; i++) {
196 			int xx = (i > 0) ? widths[i - 1] : 0;
197 			if (x + xx > clip.right)
198 				break;
199 			Glyph * glyph = getCharGlyph(text[i]);
200 			if (glyph is null)
201 				continue;
202 			if ( glyph.blackBoxX && glyph.blackBoxY ) {
203 				int gx = x + xx + glyph.originX;
204 				if (gx + glyph.blackBoxX < clip.left)
205 					continue;
206 				buf.drawGlyph( gx,
207                                y + _baseline - glyph.originY,
208                               glyph,
209                               color);
210 			}
211 		}
212 	}
213 
214     static if (true) {
215 	    override int measureText(const dchar[] text, ref int[] widths, int maxWidth) {
216 		    if (text.length == 0)
217 			    return 0;
218 		    const dchar * pstr = text.ptr;
219 		    uint len = cast(uint)text.length;
220             if (widths.length < len)
221                 widths.length = len;
222             int x = 0;
223             int charsMeasured = 0;
224 		    for (int i = 0; i < len; i++) {
225 			    Glyph * glyph = getCharGlyph(text[i], true); // TODO: what is better
226 			    if (glyph is null) {
227                     // if no glyph, use previous width - treat as zero width
228                     widths[i] = i > 0 ? widths[i-1] : 0;
229 				    continue;
230                 }
231                 int w = x + glyph.width; // using advance
232                 int w2 = x + glyph.originX + glyph.blackBoxX; // using black box
233                 if (w < w2) // choose bigger value
234                     w = w2;
235                 widths[i] = w;
236                 x += glyph.width;
237                 charsMeasured = i + 1;
238                 if (x > maxWidth)
239                     break;
240             }
241 		    return charsMeasured;
242 	    }
243     } else {
244 
245 	    override int measureText(const dchar[] text, ref int[] widths, int maxWidth) {
246 		    if (_hfont is null || _drawbuf is null || text.length == 0)
247 			    return 0;
248 		    wstring utf16text = toUTF16(text);
249 		    const wchar * pstr = utf16text.ptr;
250 		    uint len = cast(uint)utf16text.length;
251 		    GCP_RESULTSW gcpres;
252 		    gcpres.lStructSize = gcpres.sizeof;
253 		    if (widths.length < len + 1)
254 			    widths.length = len + 1;
255 		    gcpres.lpDx = widths.ptr;
256 		    gcpres.nMaxFit = len;
257 		    gcpres.nGlyphs = len;
258 		    uint res = GetCharacterPlacementW( 
259                                               _drawbuf.dc,
260                                               pstr,
261                                               len,
262                                               maxWidth,
263                                               &gcpres,
264                                               GCP_MAXEXTENT); //|GCP_USEKERNING
265 		    if (!res) {
266 			    widths[0] = 0;
267 			    return 0;
268 		    }
269 		    uint measured = gcpres.nMaxFit;
270 		    int total = 0;
271 		    for (int i = 0; i < measured; i++) {
272 			    int w = widths[i];
273 			    total += w;
274 			    widths[i] = total;
275 		    }
276 		    return measured;
277 	    }
278     }
279 
280 	bool create(FontDef * def, int size, int weight, bool italic) {
281         if (!isNull())
282             clear();
283 		LOGFONTA lf;
284         lf.lfCharSet = ANSI_CHARSET; //DEFAULT_CHARSET;
285 		lf.lfFaceName[0..def.face.length] = def.face;
286 		lf.lfFaceName[def.face.length] = 0;
287 		lf.lfHeight = -size;
288 		lf.lfItalic = italic;
289 		lf.lfOutPrecision = OUT_OUTLINE_PRECIS; //OUT_TT_ONLY_PRECIS;
290 		lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
291 		//lf.lfQuality = NONANTIALIASED_QUALITY; //ANTIALIASED_QUALITY;
292 		//lf.lfQuality = PROOF_QUALITY; //ANTIALIASED_QUALITY;
293 		lf.lfQuality = size < 18 ? NONANTIALIASED_QUALITY : PROOF_QUALITY; //ANTIALIASED_QUALITY;
294 		lf.lfPitchAndFamily = def.pitchAndFamily;
295         _hfont = CreateFontIndirectA(&lf);
296         _drawbuf = new Win32ColorDrawBuf(1, 1);
297         SelectObject(_drawbuf.dc, _hfont);
298 
299         TEXTMETRICW tm;
300         GetTextMetricsW(_drawbuf.dc, &tm);
301 
302 		_size = size;
303         _height = tm.tmHeight;
304         _baseline = _height - tm.tmDescent;
305         _weight = weight;
306         _italic = italic;
307         _face = def.face;
308         _family = def.family;
309 		Log.d("Created font ", _face, " ", _size);
310 		return true;
311 	}
312 
313 	// clear usage flags for all entries
314 	override void checkpoint() {
315 		_glyphCache.checkpoint();
316 	}
317 
318 	// removes entries not used after last call of checkpoint() or cleanup()
319 	override void cleanup() {
320 		_glyphCache.cleanup();
321 	}
322 
323     @property override int size() { return _size; }
324     @property override int height() { return _height; }
325     @property override int weight() { return _weight; }
326     @property override int baseline() { return _baseline; }
327     @property override bool italic() { return _italic; }
328     @property override string face() { return _face; }
329     @property override FontFamily family() { return _family; }
330     @property override bool isNull() { return _hfont is null; }
331 }
332 
333 
334 /**
335 * Font manager implementation based on Win32 API system fonts.
336 */
337 class Win32FontManager : FontManager {
338 	private FontList _activeFonts;
339 	private FontDef[] _fontFaces;
340 	private FontDef*[string] _faceByName;
341 
342 	/// initialize in constructor
343     this() {
344         Log.i("Creating Win32FontManager");
345         //instance = this;
346         init();
347     }
348     ~this() {
349         Log.i("Destroying Win32FontManager");
350     }
351 
352 	/// initialize font manager by enumerating of system fonts
353     bool init() {
354 		Log.i("Win32FontManager.init()");
355         Win32ColorDrawBuf drawbuf = new Win32ColorDrawBuf(1,1);
356         LOGFONTA lf;
357         lf.lfCharSet = ANSI_CHARSET; //DEFAULT_CHARSET;
358 		lf.lfFaceName[0] = 0;
359 		HDC dc = drawbuf.dc;
360         int res = 
361             EnumFontFamiliesExA(
362                                 dc,                  // handle to DC
363                                 &lf,                              // font information
364                                 &LVWin32FontEnumFontFamExProc, // callback function (FONTENUMPROC)
365                                 cast(LPARAM)(cast(void*)this),                    // additional data
366                                 0U                     // not used; must be 0
367                                     );
368 		destroy(drawbuf);
369 		Log.i("Found ", _fontFaces.length, " font faces");
370         return res!=0;
371     }
372 
373 	/// for returning of not found font
374 	FontRef _emptyFontRef;
375 
376 	/// get font by properties
377     override ref FontRef getFont(int size, int weight, bool italic, FontFamily family, string face) {
378 		//Log.i("getFont()");
379 		FontDef * def = findFace(family, face);
380 		if (def !is null) {
381 			int index = _activeFonts.find(size, weight, italic, def.family, def.face);
382 			if (index >= 0)
383 				return _activeFonts.get(index);
384 			Log.d("Creating new font");
385 			Win32Font item = new Win32Font();
386 			if (!item.create(def, size, weight, italic))
387 				return _emptyFontRef;
388 			Log.d("Adding to list of active fonts");
389 			return _activeFonts.add(item);
390 		} else {
391 			return _emptyFontRef;
392 		}
393     }
394 
395 	/// find font face definition by family only (try to get one of defaults for family if possible)
396 	FontDef * findFace(FontFamily family) {
397 		FontDef * res = null;
398 		switch(family) {
399             case FontFamily.SansSerif:
400                 res = findFace("Arial"); if (res !is null) return res;
401                 res = findFace("Tahoma"); if (res !is null) return res;
402                 res = findFace("Calibri"); if (res !is null) return res;
403                 res = findFace("Verdana"); if (res !is null) return res;
404                 res = findFace("Lucida Sans"); if (res !is null) return res;
405                 break;
406             case FontFamily.Serif:
407                 res = findFace("Times New Roman"); if (res !is null) return res;
408                 res = findFace("Georgia"); if (res !is null) return res;
409                 res = findFace("Century Schoolbook"); if (res !is null) return res;
410                 res = findFace("Bookman Old Style"); if (res !is null) return res;
411                 break;
412             case FontFamily.MonoSpace:
413                 res = findFace("Courier New"); if (res !is null) return res;
414                 res = findFace("Lucida Console"); if (res !is null) return res;
415                 res = findFace("Century Schoolbook"); if (res !is null) return res;
416                 res = findFace("Bookman Old Style"); if (res !is null) return res;
417                 break;
418             case FontFamily.Cursive:
419                 res = findFace("Comic Sans MS"); if (res !is null) return res;
420                 res = findFace("Lucida Handwriting"); if (res !is null) return res;
421                 res = findFace("Monotype Corsiva"); if (res !is null) return res;
422                 break;
423             default:
424                 break;
425 		}
426 		return null;
427 	}
428 
429 	/// find font face definition by face only
430 	FontDef * findFace(string face) {
431 		if (face.length == 0)
432 			return null;
433 		if (face in _faceByName)
434 			return _faceByName[face];
435 		return null;
436 	}
437 
438 	/// find font face definition by family and face
439 	FontDef * findFace(FontFamily family, string face) {
440 		// by face only
441 		FontDef * res = findFace(face);
442 		if (res !is null)
443 			return res;
444 		// best for family
445 		res = findFace(family);
446 		if (res !is null)
447 			return res;
448 		for (int i = 0; i < _fontFaces.length; i++) {
449 			res = &_fontFaces[i];
450 			if (res.family == family)
451 				return res;
452 		}
453 		res = findFace(FontFamily.SansSerif);
454 		if (res !is null)
455 			return res;
456 		return &_fontFaces[0];
457 	}
458 
459 	/// register enumerated font
460     bool registerFont(FontFamily family, string fontFace, ubyte pitchAndFamily) {
461 		Log.d("registerFont(", family, ",", fontFace, ")");
462 		_fontFaces ~= FontDef(family, fontFace, pitchAndFamily);
463 		_faceByName[fontFace] = &_fontFaces[$ - 1];
464         return true;
465     }
466 
467 	/// clear usage flags for all entries
468 	override void checkpoint() {
469 		_activeFonts.checkpoint();
470 	}
471 
472 	/// removes entries not used after last call of checkpoint() or cleanup()
473 	override void cleanup() {
474 		_activeFonts.cleanup();
475 		//_list.cleanup();
476 	}
477 }
478 
479 FontFamily pitchAndFamilyToFontFamily(ubyte flags) {
480 	if ((flags & FF_DECORATIVE) == FF_DECORATIVE)
481 		return FontFamily.Fantasy;
482 	else if ((flags & (FIXED_PITCH)) != 0) // | | MONO_FONT
483 		return FontFamily.MonoSpace;
484 	else if ((flags & (FF_ROMAN)) != 0)
485 		return FontFamily.Serif;
486 	else if ((flags & (FF_SCRIPT)) != 0)
487 		return FontFamily.Cursive;
488 	return FontFamily.SansSerif;
489 }
490 
491 // definition
492 extern(Windows) {
493     int LVWin32FontEnumFontFamExProc(
494                                      const (LOGFONTA) *lf,    // logical-font data
495                                      const (TEXTMETRICA) *lpntme,  // physical-font data
496                                      //ENUMLOGFONTEX *lpelfe,    // logical-font data
497                                      //NEWTEXTMETRICEX *lpntme,  // physical-font data
498                                      DWORD fontType,           // type of font
499                                      LPARAM lParam             // application-defined data
500                                          )
501     {
502         //
503 		//Log.d("LVWin32FontEnumFontFamExProc fontType=", fontType);
504         if (fontType == TRUETYPE_FONTTYPE)
505         {
506             void * p = cast(void*)lParam;
507             Win32FontManager fontman = cast(Win32FontManager)p;
508 			string face = fromStringz(lf.lfFaceName.ptr);
509 			FontFamily family = pitchAndFamilyToFontFamily(lf.lfPitchAndFamily);
510 			if (face.length < 2 || face[0] == '@')
511 				return 1;
512 			//Log.d("face:", face);
513 			fontman.registerFont(family, face, lf.lfPitchAndFamily);
514         }
515         return 1;
516     }
517 }
518 
519 }