1 // Written in the D programming language.
2 
3 /**
4 
5 This module contains implementation of Win32 fonts support
6 
7 Part of Win32 platform support.
8 
9 Usually you don't need to use this module directly.
10 
11 
12 Synopsis:
13 
14 ----
15 import dlangui.platforms.windows.win32fonts;
16 ----
17 
18 Copyright: Vadim Lopatin, 2014
19 License:   Boost License 1.0
20 Authors:   Vadim Lopatin, coolreader.org@gmail.com
21 */
22 module dlangui.platforms.windows.win32fonts;
23 
24 version(Windows):
25 public import dlangui.core.config;
26 static if (BACKEND_GUI):
27 
28 import core.sys.windows.windows;
29 import dlangui.graphics.fonts;
30 import dlangui.platforms.windows.win32drawbuf;
31 import std.string;
32 import std.utf;
33 
34 /// define debug=FontResources for logging of font file resources creation/freeing
35 //debug = FontResources;
36 
37 //auto toUTF16z(S)(S s)
38 //{
39     //return toUTFz!(const(wchar)*)(s);
40 //}
41 
42 private struct FontDef {
43     immutable FontFamily _family;
44     immutable string _face;
45     immutable ubyte _pitchAndFamily;
46     @property FontFamily family() { return _family; }
47     @property string face() { return _face; }
48     @property ubyte pitchAndFamily() { return _pitchAndFamily; }
49     this(FontFamily family, string face, ubyte putchAndFamily) {
50         _family = family;
51         _face = face;
52         _pitchAndFamily = pitchAndFamily;
53     }
54 }
55 
56 // support of subpixel rendering
57 // from AntigrainGeometry https://rsdn.ru/forum/src/830679.1
58 import std.math;
59 // Sub-pixel energy distribution lookup table.
60 // See description by Steve Gibson: http://grc.com/cttech.htm
61 // The class automatically normalizes the coefficients
62 // in such a way that primary + 2*secondary + 3*tertiary = 1.0
63 // Also, the input values are in range of 0...NumLevels, output ones
64 // are 0...255
65 //---------------------------------
66 struct lcd_distribution_lut(int maxv = 65)
67 {
68     this(double prim, double second, double tert)
69     {
70         double norm = (255.0 / (maxv - 1)) / (prim + second*2 + tert*2);
71         prim   *= norm;
72         second *= norm;
73         tert   *= norm;
74         for(int i = 0; i < maxv; i++)
75         {
76             m_primary[i]   = cast(ubyte)floor(prim   * i);
77             m_secondary[i] = cast(ubyte)floor(second * i);
78             m_tertiary[i]  = cast(ubyte)floor(tert   * i);
79         }
80     }
81 
82     uint primary(int v)   const {
83         if (v >= maxv) {
84             Log.e("pixel value returned from font engine > 64: ", v);
85             v = maxv - 1;
86         }
87         return m_primary[v];
88     }
89     uint secondary(int v) const {
90         if (v >= maxv) {
91             Log.e("pixel value returned from font engine > 64: ", v);
92             v = maxv - 1;
93         }
94         return m_secondary[v];
95     }
96     uint tertiary(int v)  const {
97         if (v >= maxv) {
98             Log.e("pixel value returned from font engine > 64: ", v);
99             v = maxv - 1;
100         }
101         return m_tertiary[v];
102     }
103 
104 private:
105     ubyte[maxv] m_primary;
106     ubyte[maxv] m_secondary;
107     ubyte[maxv] m_tertiary;
108 };
109 
110 private __gshared lcd_distribution_lut!65 lut;
111 void initWin32FontsTables() {
112     lut = lcd_distribution_lut!65(0.5, 0.25, 0.125);
113 }
114 
115 
116 
117 private int myabs(int n) {
118     return n < 0 ? -n : n;
119 }
120 private int colorStat(ubyte * p) {
121     int avg = (cast(int)p[0] + cast(int)p[1] + cast(int)p[2]) / 3;
122     return myabs(avg - cast(int)p[0]) + myabs(avg - cast(int)p[1]) + myabs(avg - cast(int)p[2]);
123 }
124 
125 private int minIndex(int n0, int n1, int n2) {
126     if (n0 <= n1 && n0 <= n2)
127         return 0;
128     if (n1 <= n0 && n1 <= n2)
129         return 1;
130     return n2;
131 }
132 
133 // This function prepares the alpha-channel information
134 // for the glyph averaging the values in accordance with
135 // the method suggested by Steve Gibson. The function
136 // extends the width by 4 extra pixels, 2 at the beginning
137 // and 2 at the end. Also, it doesn't align the new width
138 // to 4 bytes, that is, the output gm.gmBlackBoxX is the
139 // actual width of the array.
140 // returns dst glyph width
141 //---------------------------------
142 ushort prepare_lcd_glyph(ubyte * gbuf1,
143                        ref GLYPHMETRICS gm,
144                        ref ubyte[] gbuf2,
145                        ref int shiftedBy)
146 {
147     shiftedBy = 0;
148     uint src_stride = (gm.gmBlackBoxX + 3) / 4 * 4;
149     uint dst_width  = src_stride + 4;
150     gbuf2 = new ubyte[dst_width * gm.gmBlackBoxY];
151 
152     for(uint y = 0; y < gm.gmBlackBoxY; ++y)
153     {
154         ubyte * src_ptr = gbuf1 + src_stride * y;
155         ubyte * dst_ptr = gbuf2.ptr + dst_width * y;
156         for(uint x = 0; x < gm.gmBlackBoxX; ++x)
157         {
158             uint v = *src_ptr++;
159             dst_ptr[0] += lut.tertiary(v);
160             dst_ptr[1] += lut.secondary(v);
161             dst_ptr[2] += lut.primary(v);
162             dst_ptr[3] += lut.secondary(v);
163             dst_ptr[4] += lut.tertiary(v);
164             ++dst_ptr;
165         }
166     }
167     /*
168     int dx = (dst_width - 2) / 3;
169     int stats[3] = [0, 0, 0];
170     for (uint y = 0; y < gm.gmBlackBoxY; y++) {
171         for(uint x = 0; x < dx; ++x)
172         {
173             for (uint x0 = 0; x0 < 3; x0++) {
174                 stats[x0] += colorStat(gbuf2.ptr + dst_width * y + x0);
175             }
176         }
177     }
178     shiftedBy = 0; //minIndex(stats[0], stats[1], stats[2]);
179     if (shiftedBy) {
180         for (uint y = 0; y < gm.gmBlackBoxY; y++) {
181             ubyte * dst_ptr = gbuf2.ptr + dst_width * y;
182             for(uint x = 0; x < gm.gmBlackBoxX; ++x)
183             {
184                 if (x + shiftedBy < gm.gmBlackBoxX)
185                     dst_ptr[x] = dst_ptr[x + shiftedBy];
186                 else
187                     dst_ptr[x] = 0;
188             }
189         }
190     }
191     */
192     return cast(ushort) dst_width;
193 }
194 
195 /**
196 * Font implementation based on Win32 API system fonts.
197 */
198 class Win32Font : Font {
199     protected HFONT _hfont;
200     protected int _dpi;
201     protected int _size;
202     protected int _height;
203     protected int _weight;
204     protected int _baseline;
205     protected bool _italic;
206     protected string _face;
207     protected FontFamily _family;
208     protected LOGFONTA _logfont;
209     protected Win32ColorDrawBuf _drawbuf;
210     protected GlyphCache _glyphCache;
211 
212     /// need to call create() after construction to initialize font
213     this() {
214     }
215 
216     /// do cleanup
217     ~this() {
218         clear();
219     }
220 
221     /// cleanup resources
222     override void clear() {
223         if (_hfont !is null)
224         {
225             DeleteObject(_hfont);
226             _hfont = NULL;
227             _height = 0;
228             _baseline = 0;
229             _size = 0;
230         }
231         if (_drawbuf !is null) {
232             destroy(_drawbuf);
233             _drawbuf = null;
234         }
235     }
236 
237     uint getGlyphIndex(dchar code)
238     {
239         if (_drawbuf is null)
240             return 0;
241         wchar[2] s;
242         wchar[2] g;
243         s[0] = cast(wchar)code;
244         s[1] = 0;
245         g[0] = 0;
246         GCP_RESULTSW gcp;
247         gcp.lStructSize = GCP_RESULTSW.sizeof;
248         gcp.lpOutString = null;
249         gcp.lpOrder = null;
250         gcp.lpDx = null;
251         gcp.lpCaretPos = null;
252         gcp.lpClass = null;
253         gcp.lpGlyphs = g.ptr;
254         gcp.nGlyphs = 2;
255         gcp.nMaxFit = 2;
256 
257         DWORD res = GetCharacterPlacementW(
258                                            _drawbuf.dc, s.ptr, 1,
259                                            1000,
260                                            &gcp,
261                                            0
262                                            );
263         if (!res)
264             return 0;
265         return g[0];
266     }
267 
268     override Glyph * getCharGlyph(dchar ch, bool withImage = true) {
269         Glyph * found = _glyphCache.find(ch);
270         if (found !is null)
271             return found;
272         uint glyphIndex = getGlyphIndex(ch);
273         if (!glyphIndex) {
274             ch = getReplacementChar(ch);
275             if (!ch)
276                 return null;
277             glyphIndex = getGlyphIndex(ch);
278             if (!glyphIndex) {
279                 ch = getReplacementChar(ch);
280                 if (!ch)
281                     return null;
282                 glyphIndex = getGlyphIndex(ch);
283             }
284         }
285         if (!glyphIndex)
286             return null;
287         if (glyphIndex >= 0xFFFF)
288             return null;
289         GLYPHMETRICS metrics;
290 
291         bool needSubpixelRendering = FontManager.subpixelRenderingMode && antialiased;
292         MAT2 scaleMatrix = { {0,1}, {0,0}, {0,0}, {0,1} };
293 
294         uint res;
295         res = GetGlyphOutlineW( _drawbuf.dc, cast(wchar)ch,
296                                 GGO_METRICS,
297                                &metrics,
298                                0,
299                                null,
300                                &scaleMatrix );
301         if (res == GDI_ERROR)
302             return null;
303 
304         Glyph * g = new Glyph;
305         static if (ENABLE_OPENGL) {
306             g.id = nextGlyphId();
307         }
308         //g.blackBoxX = cast(ushort)metrics.gmBlackBoxX;
309         //g.blackBoxY = cast(ubyte)metrics.gmBlackBoxY;
310         //g.originX = cast(byte)metrics.gmptGlyphOrigin.x;
311         //g.originY = cast(byte)metrics.gmptGlyphOrigin.y;
312         //g.width = cast(ubyte)metrics.gmCellIncX;
313         g.subpixelMode = SubpixelRenderingMode.None;
314 
315         if (needSubpixelRendering) {
316             scaleMatrix.eM11.value = 3; // request glyph 3 times wider for subpixel antialiasing
317         }
318 
319         int gs = 0;
320         // calculate bitmap size
321         if (antialiased) {
322             gs = GetGlyphOutlineW( _drawbuf.dc, cast(wchar)ch,
323                                        GGO_GRAY8_BITMAP,
324                                       &metrics,
325                                       0,
326                                       NULL,
327                                       &scaleMatrix );
328         } else {
329             gs = GetGlyphOutlineW( _drawbuf.dc, cast(wchar)ch,
330                                    GGO_BITMAP,
331                                   &metrics,
332                                   0,
333                                   NULL,
334                                   &scaleMatrix );
335         }
336 
337         if (gs >= 0x10000 || gs < 0)
338             return null;
339 
340         if (needSubpixelRendering) {
341             //Log.d("ch=", ch);
342             //Log.d("NORMAL:  blackBoxX=", g.blackBoxX, " \tblackBoxY=", g.blackBoxY, " \torigin.x=", g.originX, " \torigin.y=", g.originY, "\tgmCellIncX=", g.width);
343             g.blackBoxX = cast(ushort)metrics.gmBlackBoxX;
344             g.blackBoxY = cast(ubyte)metrics.gmBlackBoxY;
345             g.originX = cast(byte)((metrics.gmptGlyphOrigin.x + 0) / 3);
346             g.originY = cast(byte)metrics.gmptGlyphOrigin.y;
347             g.width = cast(ubyte)((metrics.gmCellIncX  + 2) / 3);
348             g.subpixelMode = FontManager.subpixelRenderingMode;
349             //Log.d(" *3   :  blackBoxX=", metrics.gmBlackBoxX, " \tblackBoxY=", metrics.gmBlackBoxY, " \torigin.x=", metrics.gmptGlyphOrigin.x, " \torigin.y=", metrics.gmptGlyphOrigin.y, " \tgmCellIncX=", metrics.gmCellIncX);
350             //Log.d("  /3  :  blackBoxX=", g.blackBoxX, " \tblackBoxY=", g.blackBoxY, " \torigin.x=", g.originX, " \torigin.y=", g.originY, "\tgmCellIncX=", g.width);
351         } else {
352             g.blackBoxX = cast(ushort)metrics.gmBlackBoxX;
353             g.blackBoxY = cast(ubyte)metrics.gmBlackBoxY;
354             g.originX = cast(byte)metrics.gmptGlyphOrigin.x;
355             g.originY = cast(byte)metrics.gmptGlyphOrigin.y;
356             g.width = cast(ubyte)metrics.gmCellIncX;
357         }
358 
359         if (g.blackBoxX > 0 && g.blackBoxY > 0)    {
360             g.glyph = new ubyte[g.blackBoxX * g.blackBoxY];
361             if (gs>0)
362             {
363                 if (antialiased) {
364                     // antialiased glyph
365                     ubyte[] glyph = new ubyte[gs];
366                     res = GetGlyphOutlineW( _drawbuf.dc, cast(wchar)ch,
367                                             GGO_GRAY8_BITMAP, //GGO_METRICS
368                                            &metrics,
369                                            gs,
370                                            glyph.ptr,
371                                            &scaleMatrix);
372                     if (res==GDI_ERROR)
373                     {
374                         return null;
375                     }
376                     if (needSubpixelRendering) {
377                         ubyte[] newglyph;
378                         int shiftedBy = 0;
379                         g.blackBoxX = prepare_lcd_glyph(glyph.ptr,
380                                                  metrics,
381                                                  newglyph,
382                                                  shiftedBy);
383                         g.glyph = newglyph;
384                         //g.originX = cast(ubyte)((metrics.gmptGlyphOrigin.x + 2 - shiftedBy) / 3);
385                         //g.width = cast(ubyte)((metrics.gmCellIncX  + 2 - shiftedBy) / 3);
386                     } else {
387                         int glyph_row_size = (g.blackBoxX + 3) / 4 * 4;
388                         ubyte * src = glyph.ptr;
389                         ubyte * dst = g.glyph.ptr;
390                         for (int y = 0; y < g.blackBoxY; y++)
391                         {
392                             for (int x = 0; x < g.blackBoxX; x++)
393                             {
394                                 dst[x] = _gamma65.correct(src[x]);
395                             }
396                             src += glyph_row_size;
397                             dst += g.blackBoxX;
398                         }
399                     }
400                 } else {
401                     // bitmap glyph
402                     ubyte[] glyph = new ubyte[gs];
403                     res = GetGlyphOutlineW( _drawbuf.dc, cast(wchar)ch,
404                                             GGO_BITMAP, //GGO_METRICS
405                                            &metrics,
406                                            gs,
407                                            glyph.ptr,
408                                            &scaleMatrix );
409                     if (res==GDI_ERROR)
410                     {
411                         return null;
412                     }
413                     int glyph_row_bytes = ((g.blackBoxX + 7) / 8);
414                     int glyph_row_size = (glyph_row_bytes + 3) / 4 * 4;
415                     ubyte * src = glyph.ptr;
416                     ubyte * dst = g.glyph.ptr;
417                     for (int y = 0; y < g.blackBoxY; y++)
418                     {
419                         for (int x = 0; x < g.blackBoxX; x++)
420                         {
421                             int offset = x >> 3;
422                             int shift = 7 - (x & 7);
423                             ubyte b = ((src[offset] >> shift) & 1) ? 255 : 0;
424                             dst[x] = b;
425                         }
426                         src += glyph_row_size;
427                         dst += g.blackBoxX;
428                     }
429                 }
430             }
431             else
432             {
433                 // empty glyph
434                 for (int i = g.blackBoxX * g.blackBoxY - 1; i >= 0; i--)
435                     g.glyph[i] = 0;
436             }
437         }
438         // found!
439         return _glyphCache.put(ch, g);
440     }
441 
442     /// init from font definition
443     bool create(FontDef * def, int size, int weight, bool italic) {
444         if (!isNull())
445             clear();
446         LOGFONTA lf;
447         lf.lfCharSet = ANSI_CHARSET; //DEFAULT_CHARSET;
448         lf.lfFaceName[0..def.face.length] = def.face;
449         lf.lfFaceName[def.face.length] = 0;
450         lf.lfHeight = -size; //pixelsToPoints(size);
451         lf.lfItalic = italic;
452         lf.lfWeight = weight;
453         lf.lfOutPrecision = OUT_TT_ONLY_PRECIS; //OUT_OUTLINE_PRECIS; //OUT_TT_ONLY_PRECIS;
454         lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
455         //lf.lfQuality = NONANTIALIASED_QUALITY; //ANTIALIASED_QUALITY;
456         //lf.lfQuality = PROOF_QUALITY; //ANTIALIASED_QUALITY;
457         lf.lfQuality = antialiased ? NONANTIALIASED_QUALITY : ANTIALIASED_QUALITY; //PROOF_QUALITY; //ANTIALIASED_QUALITY; //size < 18 ? NONANTIALIASED_QUALITY : PROOF_QUALITY; //ANTIALIASED_QUALITY;
458         lf.lfPitchAndFamily = def.pitchAndFamily;
459         _hfont = CreateFontIndirectA(&lf);
460         _drawbuf = new Win32ColorDrawBuf(1, 1);
461         SelectObject(_drawbuf.dc, _hfont);
462 
463         TEXTMETRICW tm;
464         GetTextMetricsW(_drawbuf.dc, &tm);
465 
466         _size = size;
467         _height = tm.tmHeight;
468         debug(FontResources) Log.d("Win32Font.create: height=", _height, " for size=", _size, "  points=", lf.lfHeight, " dpi=", _dpi);
469         _baseline = _height - tm.tmDescent;
470         _weight = weight;
471         _italic = italic;
472         _face = def.face;
473         _family = def.family;
474         debug(FontResources) Log.d("Created font ", _face, " ", _size);
475         return true;
476     }
477 
478     /// clear usage flags for all entries
479     override void checkpoint() {
480         _glyphCache.checkpoint();
481     }
482 
483     /// removes entries not used after last call of checkpoint() or cleanup()
484     override void cleanup() {
485         _glyphCache.cleanup();
486     }
487 
488     /// clears glyph cache
489     override void clearGlyphCache() {
490         _glyphCache.clear();
491     }
492 
493     @property override int size() { return _size; }
494     @property override int height() { return _height; }
495     @property override int weight() { return _weight; }
496     @property override int baseline() { return _baseline; }
497     @property override bool italic() { return _italic; }
498     @property override string face() { return _face; }
499     @property override FontFamily family() { return _family; }
500     @property override bool isNull() { return _hfont is null; }
501 }
502 
503 
504 /**
505 * Font manager implementation based on Win32 API system fonts.
506 */
507 class Win32FontManager : FontManager {
508     private FontList _activeFonts;
509     private FontDef[] _fontFaces;
510     private FontDef*[string] _faceByName;
511 
512     /// override to return list of font faces available
513     override FontFaceProps[] getFaces() {
514         FontFaceProps[] res;
515         for (int i = 0; i < _fontFaces.length; i++) {
516             FontFaceProps item = FontFaceProps(_fontFaces[i].face, _fontFaces[i].family);
517             bool found = false;
518             for (int j = 0; j < res.length; j++) {
519                 if (res[j].face == item.face) {
520                     found = true;
521                     break;
522                 }
523             }
524             if (!found)
525                 res ~= item;
526         }
527         return res;
528     }
529 
530 
531     /// initialize in constructor
532     this() {
533         debug Log.i("Creating Win32FontManager");
534         //instance = this;
535         initialize();
536     }
537     ~this() {
538         debug Log.i("Destroying Win32FontManager");
539     }
540 
541     /// initialize font manager by enumerating of system fonts
542     bool initialize() {
543         debug Log.i("Win32FontManager.initialize()");
544         Win32ColorDrawBuf drawbuf = new Win32ColorDrawBuf(1,1);
545         LOGFONTA lf;
546         lf.lfCharSet = ANSI_CHARSET; //DEFAULT_CHARSET;
547         lf.lfFaceName[0] = 0;
548         HDC dc = drawbuf.dc;
549         int res =
550             EnumFontFamiliesExA(
551                                 dc,                  // handle to DC
552                                 &lf,                              // font information
553                                 cast(FONTENUMPROCA)&LVWin32FontEnumFontFamExProc, // callback function (FONTENUMPROC)
554                                 cast(LPARAM)(cast(void*)this),                    // additional data
555                                 0U                     // not used; must be 0
556                                     );
557         destroy(drawbuf);
558         Log.i("Found ", _fontFaces.length, " font faces");
559         return res!=0;
560     }
561 
562     /// for returning of not found font
563     FontRef _emptyFontRef;
564 
565     /// get font by properties
566     override ref FontRef getFont(int size, int weight, bool italic, FontFamily family, string face) {
567         //Log.i("getFont()");
568         FontDef * def = findFace(family, face);
569         //Log.i("getFont() found face ", def.face, " by requested face ", face);
570         if (def !is null) {
571             int index = _activeFonts.find(size, weight, italic, def.family, def.face);
572             if (index >= 0)
573                 return _activeFonts.get(index);
574             debug(FontResources) Log.d("Creating new font");
575             Win32Font item = new Win32Font();
576             if (!item.create(def, size, weight, italic))
577                 return _emptyFontRef;
578             debug(FontResources) Log.d("Adding to list of active fonts");
579             return _activeFonts.add(item);
580         } else {
581             return _emptyFontRef;
582         }
583     }
584 
585     /// find font face definition by family only (try to get one of defaults for family if possible)
586     FontDef * findFace(FontFamily family) {
587         FontDef * res = null;
588         switch(family) {
589             case FontFamily.SansSerif:
590                 res = findFace("Arial"); if (res !is null) return res;
591                 res = findFace("Tahoma"); if (res !is null) return res;
592                 res = findFace("Calibri"); if (res !is null) return res;
593                 res = findFace("Verdana"); if (res !is null) return res;
594                 res = findFace("Lucida Sans"); if (res !is null) return res;
595                 break;
596             case FontFamily.Serif:
597                 res = findFace("Times New Roman"); if (res !is null) return res;
598                 res = findFace("Georgia"); if (res !is null) return res;
599                 res = findFace("Century Schoolbook"); if (res !is null) return res;
600                 res = findFace("Bookman Old Style"); if (res !is null) return res;
601                 break;
602             case FontFamily.MonoSpace:
603                 res = findFace("Courier New"); if (res !is null) return res;
604                 res = findFace("Lucida Console"); if (res !is null) return res;
605                 res = findFace("Century Schoolbook"); if (res !is null) return res;
606                 res = findFace("Bookman Old Style"); if (res !is null) return res;
607                 break;
608             case FontFamily.Cursive:
609                 res = findFace("Comic Sans MS"); if (res !is null) return res;
610                 res = findFace("Lucida Handwriting"); if (res !is null) return res;
611                 res = findFace("Monotype Corsiva"); if (res !is null) return res;
612                 break;
613             default:
614                 break;
615         }
616         return null;
617     }
618 
619     /// find font face definition by face only
620     FontDef * findFace(string face) {
621         if (face.length == 0)
622             return null;
623         string[] faces = split(face, ",");
624         foreach(f; faces) {
625             if (f in _faceByName)
626                 return _faceByName[f];
627         }
628         return null;
629     }
630 
631     /// find font face definition by family and face
632     FontDef * findFace(FontFamily family, string face) {
633         // by face only
634         FontDef * res = findFace(face);
635         if (res !is null)
636             return res;
637         // best for family
638         res = findFace(family);
639         if (res !is null)
640             return res;
641         for (int i = 0; i < _fontFaces.length; i++) {
642             res = &_fontFaces[i];
643             if (res.family == family)
644                 return res;
645         }
646         res = findFace(FontFamily.SansSerif);
647         if (res !is null)
648             return res;
649         return &_fontFaces[0];
650     }
651 
652     /// register enumerated font
653     bool registerFont(FontFamily family, string fontFace, ubyte pitchAndFamily) {
654         Log.d("registerFont(", family, ",", fontFace, ")");
655         _fontFaces ~= FontDef(family, fontFace, pitchAndFamily);
656         _faceByName[fontFace] = &_fontFaces[$ - 1];
657         return true;
658     }
659 
660     /// clear usage flags for all entries
661     override void checkpoint() {
662         _activeFonts.checkpoint();
663     }
664 
665     /// removes entries not used after last call of checkpoint() or cleanup()
666     override void cleanup() {
667         _activeFonts.cleanup();
668         //_list.cleanup();
669     }
670 
671     /// clears glyph cache
672     override void clearGlyphCaches() {
673         _activeFonts.clearGlyphCache();
674     }
675 }
676 
677 FontFamily pitchAndFamilyToFontFamily(ubyte flags) {
678     if ((flags & FF_DECORATIVE) == FF_DECORATIVE)
679         return FontFamily.Fantasy;
680     else if ((flags & (FIXED_PITCH)) != 0) // | | MONO_FONT
681         return FontFamily.MonoSpace;
682     else if ((flags & (FF_ROMAN)) != 0)
683         return FontFamily.Serif;
684     else if ((flags & (FF_SCRIPT)) != 0)
685         return FontFamily.Cursive;
686     return FontFamily.SansSerif;
687 }
688 
689 // definition
690 extern(Windows) {
691     int LVWin32FontEnumFontFamExProc(
692                                      const (LOGFONTA) *lf,    // logical-font data
693                                      const (TEXTMETRICA) *lpntme,  // physical-font data
694                                      //ENUMLOGFONTEX *lpelfe,    // logical-font data
695                                      //NEWTEXTMETRICEX *lpntme,  // physical-font data
696                                      DWORD fontType,           // type of font
697                                      LPARAM lParam             // application-defined data
698                                          )
699     {
700         //
701         //Log.d("LVWin32FontEnumFontFamExProc fontType=", fontType);
702         if (fontType == TRUETYPE_FONTTYPE)
703         {
704             void * p = cast(void*)lParam;
705             Win32FontManager fontman = cast(Win32FontManager)p;
706             string face = fromStringz(lf.lfFaceName.ptr).dup;
707             FontFamily family = pitchAndFamilyToFontFamily(lf.lfPitchAndFamily);
708             if (face.length < 2 || face[0] == '@')
709                 return 1;
710             //Log.d("face:", face);
711             fontman.registerFont(family, face, lf.lfPitchAndFamily);
712         }
713         return 1;
714     }
715 }