1 module des.fonts.ftglyphrender;
2 
3 import des.math.linear;
4 import des.il;
5 import des.util.arch.emm;
6 import des.util.stdext;
7 
8 import derelict.freetype.ft;
9 import derelict.freetype.types;
10 
11 import std.traits;
12 
13 alias CrdVector!2 imsize_t;
14 
15 struct BitmapFont
16 {
17     ivec2[wchar] offset;
18     ivec2[wchar] size;
19     ivec2[wchar] bearing;
20 
21     Image!2 texture;
22 }
23 
24 class FTGlyphRenderException: Exception
25 {
26     @safe pure nothrow this( string msg, string file=__FILE__, size_t line=__LINE__ )
27     { super( msg, file, line ); }
28 }
29 
30 struct GlyphInfo
31 {
32     ivec2 pos, next;
33     @property ivec2 size() const { return ivec2( img.size ); }
34     Image!2 img;
35 }
36 
37 struct GlyphParam
38 {
39     enum Flag
40     {
41         NONE        = cast(ubyte)0,
42         BOLD        = 0b0001,
43         ITALIC      = 0b0010,
44         UNDERLINE   = 0b0100,
45         STRIKED     = 0b1000
46     }
47 
48     ubyte flag = Flag.NONE;
49     uint height=12;
50 }
51 
52 interface GlyphRender
53 {
54     void setParams( in GlyphParam p );
55     @property ElemInfo imtype() const;
56     GlyphInfo render( wchar ch );
57     BitmapFont generateBitmapFont( wstring );
58 }
59 
60 class FTGlyphRender : GlyphRender, ExternalMemoryManager
61 {
62     mixin EMM;
63 protected:
64     void selfDestroy()
65     { 
66         if( FT_Done_Face !is null )
67             FT_Done_Face( face ); 
68     }
69 private:
70     static lib_inited = false;
71     static FT_Library ft;
72 
73     static FTGlyphRender[string] openFTGR;
74 
75     FT_Face face;
76 
77     static this()
78     {
79         if( !lib_inited )
80         {
81             DerelictFT.load();
82             if( FT_Init_FreeType( &ft ) )
83                 throw new FTGlyphRenderException( "Couldn't init freetype library" );
84 
85             lib_inited = true;
86         }
87     }
88 
89     this( string fontname )
90     {
91         import std.file;
92         if( !fontname.exists )
93             throw new FTGlyphRenderException( "Couldn't open font '" ~ fontname ~ "': file not exist" );
94 
95         bool loaderror = false;
96         foreach( i; 0 .. 100 )
97         {
98             if( FT_New_Face( ft, fontname.dup.ptr, 0, &face ) ) loaderror = true;
99             else { loaderror = false; break; }
100         }
101 
102         if( loaderror )
103             throw new FTGlyphRenderException( "Couldn't open font '" ~ fontname ~ "': loading error" );
104 
105         if( FT_Select_Charmap( face, FT_ENCODING_UNICODE ) )
106             throw new FTGlyphRenderException( "Couldn't select unicode encoding" );
107     }
108 
109 public:
110 
111     static GlyphRender get( string fontname )
112     {
113         if( fontname !in openFTGR )
114             openFTGR[fontname] = new FTGlyphRender( fontname );
115         return openFTGR[fontname];
116     }
117 
118     void setParams( in GlyphParam p )
119     {
120         FT_Set_Pixel_Sizes( face, 0, p.height );
121     }
122 
123     @property ElemInfo imtype() const { return ElemInfo( DataType.FLOAT, 1 ); }
124 
125     GlyphInfo render( wchar ch )
126     {
127         if( FT_Load_Char( face, cast(size_t)ch, FT_LOAD_RENDER ) )
128             throw new FTGlyphRenderException( "Couldn't load char" );
129 
130         auto g = face.glyph;
131 
132         ivec2 sz;
133         float[] img_data;
134 
135         if( ch == ' ' )
136         {
137             auto width = g.metrics.horiAdvance / 128.0;//TODO not proper way I think
138             sz = ivec2( width, g.bitmap.rows );
139             img_data.length = sz.x * sz.y;
140             img_data[] = 0;
141         }
142         else
143         {
144             sz = ivec2( g.bitmap.width, g.bitmap.rows );
145             img_data = amap!(a => a / 255.0f)(g.bitmap.buffer[0 .. sz.x * sz.y]);
146         }
147 
148         return GlyphInfo( ivec2( g.bitmap_left, -g.bitmap_top ), 
149                                 ivec2( cast(int)( g.advance.x >> 6 ), 
150                                        cast(int)( g.advance.y >> 6 ) ),
151                                 Image!2( imsize_t(sz), imtype, img_data ) );
152     }
153 
154     BitmapFont generateBitmapFont( wstring chars )
155     {
156         BitmapFont res;
157         GlyphInfo[wchar] glyphs;
158         foreach( c; chars )
159             glyphs[c] = render( c );
160         uint maxh = 0;
161         uint width = 0;
162         foreach( ref g; glyphs )
163         {
164             if( g.img.size.h > maxh )
165                 maxh = cast( uint )g.img.size.h;
166             width += g.img.size.w;
167         }
168         res.texture = Image!2( imsize_t( width, maxh ), imtype );
169 
170         uint offset = 0;
171 
172         foreach( key, ref g; glyphs )
173         {
174             res.offset[ key ] = ivec2( offset, 0 ); 
175             res.size[ key ] = ivec2( g.img.size );
176             res.bearing[ key ] = ivec2( g.pos );
177             imPaste( res.texture, ivec2( offset, 0 ), g.img );
178             offset += g.img.size.w;
179         }
180         return res;
181     }
182 
183     static ~this() 
184     { 
185         if( lib_inited && FT_Done_FreeType !is null ) 
186             FT_Done_FreeType( ft ); 
187     }
188 }