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