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.widthPixels = cast(ubyte)((metrics.gmCellIncX  + 2) / 3);
348             g.widthScaled = g.widthPixels << 6;
349             g.subpixelMode = FontManager.subpixelRenderingMode;
350             //Log.d(" *3   :  blackBoxX=", metrics.gmBlackBoxX, " \tblackBoxY=", metrics.gmBlackBoxY, " \torigin.x=", metrics.gmptGlyphOrigin.x, " \torigin.y=", metrics.gmptGlyphOrigin.y, " \tgmCellIncX=", metrics.gmCellIncX);
351             //Log.d("  /3  :  blackBoxX=", g.blackBoxX, " \tblackBoxY=", g.blackBoxY, " \torigin.x=", g.originX, " \torigin.y=", g.originY, "\tgmCellIncX=", g.width);
352         } else {
353             g.blackBoxX = cast(ushort)metrics.gmBlackBoxX;
354             g.blackBoxY = cast(ubyte)metrics.gmBlackBoxY;
355             g.originX = cast(byte)metrics.gmptGlyphOrigin.x;
356             g.originY = cast(byte)metrics.gmptGlyphOrigin.y;
357             g.widthPixels = cast(ubyte)metrics.gmCellIncX;
358             g.widthScaled = g.widthPixels << 6;
359         }
360 
361         if (g.blackBoxX > 0 && g.blackBoxY > 0)    {
362             g.glyph = new ubyte[g.blackBoxX * g.blackBoxY];
363             if (gs>0)
364             {
365                 if (antialiased) {
366                     // antialiased glyph
367                     ubyte[] glyph = new ubyte[gs];
368                     res = GetGlyphOutlineW( _drawbuf.dc, cast(wchar)ch,
369                                             GGO_GRAY8_BITMAP, //GGO_METRICS
370                                            &metrics,
371                                            gs,
372                                            glyph.ptr,
373                                            &scaleMatrix);
374                     if (res==GDI_ERROR)
375                     {
376                         return null;
377                     }
378                     if (needSubpixelRendering) {
379                         ubyte[] newglyph;
380                         int shiftedBy = 0;
381                         g.blackBoxX = prepare_lcd_glyph(glyph.ptr,
382                                                  metrics,
383                                                  newglyph,
384                                                  shiftedBy);
385                         g.glyph = newglyph;
386                         //g.originX = cast(ubyte)((metrics.gmptGlyphOrigin.x + 2 - shiftedBy) / 3);
387                         //g.width = cast(ubyte)((metrics.gmCellIncX  + 2 - shiftedBy) / 3);
388                     } else {
389                         int glyph_row_size = (g.blackBoxX + 3) / 4 * 4;
390                         ubyte * src = glyph.ptr;
391                         ubyte * dst = g.glyph.ptr;
392                         for (int y = 0; y < g.blackBoxY; y++)
393                         {
394                             for (int x = 0; x < g.blackBoxX; x++)
395                             {
396                                 dst[x] = _gamma65.correct(src[x]);
397                             }
398                             src += glyph_row_size;
399                             dst += g.blackBoxX;
400                         }
401                     }
402                 } else {
403                     // bitmap glyph
404                     ubyte[] glyph = new ubyte[gs];
405                     res = GetGlyphOutlineW( _drawbuf.dc, cast(wchar)ch,
406                                             GGO_BITMAP, //GGO_METRICS
407                                            &metrics,
408                                            gs,
409                                            glyph.ptr,
410                                            &scaleMatrix );
411                     if (res==GDI_ERROR)
412                     {
413                         return null;
414                     }
415                     int glyph_row_bytes = ((g.blackBoxX + 7) / 8);
416                     int glyph_row_size = (glyph_row_bytes + 3) / 4 * 4;
417                     ubyte * src = glyph.ptr;
418                     ubyte * dst = g.glyph.ptr;
419                     for (int y = 0; y < g.blackBoxY; y++)
420                     {
421                         for (int x = 0; x < g.blackBoxX; x++)
422                         {
423                             int offset = x >> 3;
424                             int shift = 7 - (x & 7);
425                             ubyte b = ((src[offset] >> shift) & 1) ? 255 : 0;
426                             dst[x] = b;
427                         }
428                         src += glyph_row_size;
429                         dst += g.blackBoxX;
430                     }
431                 }
432             }
433             else
434             {
435                 // empty glyph
436                 for (int i = g.blackBoxX * g.blackBoxY - 1; i >= 0; i--)
437                     g.glyph[i] = 0;
438             }
439         }
440         // found!
441         return _glyphCache.put(ch, g);
442     }
443 
444     /// init from font definition
445     bool create(FontDef * def, int size, int weight, bool italic) {
446         if (!isNull())
447             clear();
448         LOGFONTA lf;
449         lf.lfCharSet = ANSI_CHARSET; //DEFAULT_CHARSET;
450         lf.lfFaceName[0..def.face.length] = def.face;
451         lf.lfFaceName[def.face.length] = 0;
452         lf.lfHeight = -size; //pixelsToPoints(size);
453         lf.lfItalic = italic;
454         lf.lfWeight = weight;
455         lf.lfOutPrecision = OUT_TT_ONLY_PRECIS; //OUT_OUTLINE_PRECIS; //OUT_TT_ONLY_PRECIS;
456         lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
457         //lf.lfQuality = NONANTIALIASED_QUALITY; //ANTIALIASED_QUALITY;
458         //lf.lfQuality = PROOF_QUALITY; //ANTIALIASED_QUALITY;
459         lf.lfQuality = antialiased ? NONANTIALIASED_QUALITY : ANTIALIASED_QUALITY; //PROOF_QUALITY; //ANTIALIASED_QUALITY; //size < 18 ? NONANTIALIASED_QUALITY : PROOF_QUALITY; //ANTIALIASED_QUALITY;
460         lf.lfPitchAndFamily = def.pitchAndFamily;
461         _hfont = CreateFontIndirectA(&lf);
462         _drawbuf = new Win32ColorDrawBuf(1, 1);
463         SelectObject(_drawbuf.dc, _hfont);
464 
465         TEXTMETRICW tm;
466         GetTextMetricsW(_drawbuf.dc, &tm);
467 
468         _size = size;
469         _height = tm.tmHeight;
470         debug(FontResources) Log.d("Win32Font.create: height=", _height, " for size=", _size, "  points=", lf.lfHeight, " dpi=", _dpi);
471         _baseline = _height - tm.tmDescent;
472         _weight = weight;
473         _italic = italic;
474         _face = def.face;
475         _family = def.family;
476         debug(FontResources) Log.d("Created font ", _face, " ", _size);
477         return true;
478     }
479 
480     /// clear usage flags for all entries
481     override void checkpoint() {
482         _glyphCache.checkpoint();
483     }
484 
485     /// removes entries not used after last call of checkpoint() or cleanup()
486     override void cleanup() {
487         _glyphCache.cleanup();
488     }
489 
490     /// clears glyph cache
491     override void clearGlyphCache() {
492         _glyphCache.clear();
493     }
494 
495     @property override int size() { return _size; }
496     @property override int height() { return _height; }
497     @property override int weight() { return _weight; }
498     @property override int baseline() { return _baseline; }
499     @property override bool italic() { return _italic; }
500     @property override string face() { return _face; }
501     @property override FontFamily family() { return _family; }
502     @property override bool isNull() { return _hfont is null; }
503 }
504 
505 
506 /**
507 * Font manager implementation based on Win32 API system fonts.
508 */
509 class Win32FontManager : FontManager {
510     private FontList _activeFonts;
511     private FontDef[] _fontFaces;
512     private FontDef*[string] _faceByName;
513 
514     /// override to return list of font faces available
515     override FontFaceProps[] getFaces() {
516         FontFaceProps[] res;
517         for (int i = 0; i < _fontFaces.length; i++) {
518             FontFaceProps item = FontFaceProps(_fontFaces[i].face, _fontFaces[i].family);
519             bool found = false;
520             for (int j = 0; j < res.length; j++) {
521                 if (res[j].face == item.face) {
522                     found = true;
523                     break;
524                 }
525             }
526             if (!found)
527                 res ~= item;
528         }
529         return res;
530     }
531 
532 
533     /// initialize in constructor
534     this() {
535         debug Log.i("Creating Win32FontManager");
536         //instance = this;
537         initialize();
538     }
539     ~this() {
540         debug Log.i("Destroying Win32FontManager");
541     }
542 
543     /// initialize font manager by enumerating of system fonts
544     bool initialize() {
545         debug Log.i("Win32FontManager.initialize()");
546         Win32ColorDrawBuf drawbuf = new Win32ColorDrawBuf(1,1);
547         LOGFONTA lf;
548         lf.lfCharSet = ANSI_CHARSET; //DEFAULT_CHARSET;
549         lf.lfFaceName[0] = 0;
550         HDC dc = drawbuf.dc;
551         int res =
552             EnumFontFamiliesExA(
553                                 dc,                  // handle to DC
554                                 &lf,                              // font information
555                                 cast(FONTENUMPROCA)&LVWin32FontEnumFontFamExProc, // callback function (FONTENUMPROC)
556                                 cast(LPARAM)(cast(void*)this),                    // additional data
557                                 0U                     // not used; must be 0
558                                     );
559         destroy(drawbuf);
560         Log.i("Found ", _fontFaces.length, " font faces");
561         return res!=0;
562     }
563 
564     /// for returning of not found font
565     FontRef _emptyFontRef;
566 
567     /// get font by properties
568     override ref FontRef getFont(int size, int weight, bool italic, FontFamily family, string face) {
569         //Log.i("getFont()");
570         FontDef * def = findFace(family, face);
571         //Log.i("getFont() found face ", def.face, " by requested face ", face);
572         if (def !is null) {
573             int index = _activeFonts.find(size, weight, italic, def.family, def.face);
574             if (index >= 0)
575                 return _activeFonts.get(index);
576             debug(FontResources) Log.d("Creating new font");
577             Win32Font item = new Win32Font();
578             if (!item.create(def, size, weight, italic))
579                 return _emptyFontRef;
580             debug(FontResources) Log.d("Adding to list of active fonts");
581             return _activeFonts.add(item);
582         } else {
583             return _emptyFontRef;
584         }
585     }
586 
587     /// find font face definition by family only (try to get one of defaults for family if possible)
588     FontDef * findFace(FontFamily family) {
589         FontDef * res = null;
590         switch(family) {
591             case FontFamily.SansSerif:
592                 res = findFace("Arial"); if (res !is null) return res;
593                 res = findFace("Tahoma"); if (res !is null) return res;
594                 res = findFace("Calibri"); if (res !is null) return res;
595                 res = findFace("Verdana"); if (res !is null) return res;
596                 res = findFace("Lucida Sans"); if (res !is null) return res;
597                 break;
598             case FontFamily.Serif:
599                 res = findFace("Times New Roman"); if (res !is null) return res;
600                 res = findFace("Georgia"); if (res !is null) return res;
601                 res = findFace("Century Schoolbook"); if (res !is null) return res;
602                 res = findFace("Bookman Old Style"); if (res !is null) return res;
603                 break;
604             case FontFamily.MonoSpace:
605                 res = findFace("Courier New"); if (res !is null) return res;
606                 res = findFace("Lucida Console"); if (res !is null) return res;
607                 res = findFace("Century Schoolbook"); if (res !is null) return res;
608                 res = findFace("Bookman Old Style"); if (res !is null) return res;
609                 break;
610             case FontFamily.Cursive:
611                 res = findFace("Comic Sans MS"); if (res !is null) return res;
612                 res = findFace("Lucida Handwriting"); if (res !is null) return res;
613                 res = findFace("Monotype Corsiva"); if (res !is null) return res;
614                 break;
615             default:
616                 break;
617         }
618         return null;
619     }
620 
621     /// find font face definition by face only
622     FontDef * findFace(string face) {
623         if (face.length == 0)
624             return null;
625         string[] faces = split(face, ",");
626         foreach(f; faces) {
627             if (f in _faceByName)
628                 return _faceByName[f];
629         }
630         return null;
631     }
632 
633     /// find font face definition by family and face
634     FontDef * findFace(FontFamily family, string face) {
635         // by face only
636         FontDef * res = findFace(face);
637         if (res !is null)
638             return res;
639         // best for family
640         res = findFace(family);
641         if (res !is null)
642             return res;
643         for (int i = 0; i < _fontFaces.length; i++) {
644             res = &_fontFaces[i];
645             if (res.family == family)
646                 return res;
647         }
648         res = findFace(FontFamily.SansSerif);
649         if (res !is null)
650             return res;
651         return &_fontFaces[0];
652     }
653 
654     /// register enumerated font
655     bool registerFont(FontFamily family, string fontFace, ubyte pitchAndFamily) {
656         Log.d("registerFont(", family, ",", fontFace, ")");
657         _fontFaces ~= FontDef(family, fontFace, pitchAndFamily);
658         _faceByName[fontFace] = &_fontFaces[$ - 1];
659         return true;
660     }
661 
662     /// clear usage flags for all entries
663     override void checkpoint() {
664         _activeFonts.checkpoint();
665     }
666 
667     /// removes entries not used after last call of checkpoint() or cleanup()
668     override void cleanup() {
669         _activeFonts.cleanup();
670         //_list.cleanup();
671     }
672 
673     /// clears glyph cache
674     override void clearGlyphCaches() {
675         _activeFonts.clearGlyphCache();
676     }
677 }
678 
679 FontFamily pitchAndFamilyToFontFamily(ubyte flags) {
680     if ((flags & FF_DECORATIVE) == FF_DECORATIVE)
681         return FontFamily.Fantasy;
682     else if ((flags & (FIXED_PITCH)) != 0) // | | MONO_FONT
683         return FontFamily.MonoSpace;
684     else if ((flags & (FF_ROMAN)) != 0)
685         return FontFamily.Serif;
686     else if ((flags & (FF_SCRIPT)) != 0)
687         return FontFamily.Cursive;
688     return FontFamily.SansSerif;
689 }
690 
691 // definition
692 extern(Windows) {
693     int LVWin32FontEnumFontFamExProc(
694                                      const (LOGFONTA) *lf,    // logical-font data
695                                      const (TEXTMETRICA) *lpntme,  // physical-font data
696                                      //ENUMLOGFONTEX *lpelfe,    // logical-font data
697                                      //NEWTEXTMETRICEX *lpntme,  // physical-font data
698                                      DWORD fontType,           // type of font
699                                      LPARAM lParam             // application-defined data
700                                          )
701     {
702         //
703         //Log.d("LVWin32FontEnumFontFamExProc fontType=", fontType);
704         if (fontType == TRUETYPE_FONTTYPE)
705         {
706             void * p = cast(void*)lParam;
707             Win32FontManager fontman = cast(Win32FontManager)p;
708             string face = fromStringz(lf.lfFaceName.ptr).dup;
709             FontFamily family = pitchAndFamilyToFontFamily(lf.lfPitchAndFamily);
710             if (face.length < 2 || face[0] == '@')
711                 return 1;
712             //Log.d("face:", face);
713             fontman.registerFont(family, face, lf.lfPitchAndFamily);
714         }
715         return 1;
716     }
717 }