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 alias SizeVector!2 imsize_t;
12 
13 struct BitmapFont
14 {
15     ivec2[wchar] offset;
16     ivec2[wchar] size;
17     ivec2[wchar] bearing;
18 
19     Image!2 texture;
20 }
21 
22 class FTGlyphRenderException: Exception
23 {
24     @safe pure nothrow this( string msg, string file=__FILE__, size_t line=__LINE__ )
25     { super( msg, file, line ); }
26 }
27 
28 struct GlyphInfo
29 {
30     ivec2 pos, next;
31     @property ivec2 size() const { return ivec2( img.size ); }
32     Image!2 img;
33 }
34 
35 struct GlyphParam
36 {
37     enum Flag
38     {
39         NONE        = cast(ubyte)0,
40         BOLD        = 0b0001,
41         ITALIC      = 0b0010,
42         UNDERLINE   = 0b0100,
43         STRIKED     = 0b1000
44     }
45 
46     ubyte flag = Flag.NONE;
47     uint height=12;
48 }
49 
50 interface GlyphRender
51 {
52     void setParams( in GlyphParam p );
53     @property ElemInfo imtype() const;
54     GlyphInfo render( wchar ch );
55     BitmapFont generateBitmapFont();
56 }
57 
58 class FTGlyphRender : GlyphRender, ExternalMemoryManager
59 {
60     mixin EMM;
61 protected:
62     void selfDestroy()
63     { 
64         if( FT_Done_Face !is null )
65             FT_Done_Face( face ); 
66     }
67 private:
68     static lib_inited = false;
69     static FT_Library ft;
70 
71     static FTGlyphRender[string] openFTGR;
72 
73     FT_Face face;
74 
75     static this()
76     {
77         if( !lib_inited )
78         {
79             DerelictFT.load();
80             if( FT_Init_FreeType( &ft ) )
81                 throw new FTGlyphRenderException( "Couldn't init freetype library" );
82 
83             lib_inited = true;
84         }
85     }
86 
87     this( string fontname )
88     {
89         import std.file;
90         if( !fontname.exists )
91             throw new FTGlyphRenderException( "Couldn't open font '" ~ fontname ~ "': file not exist" );
92 
93         bool loaderror = false;
94         foreach( i; 0 .. 100 )
95         {
96             if( FT_New_Face( ft, fontname.dup.ptr, 0, &face ) ) loaderror = true;
97             else { loaderror = false; break; }
98         }
99 
100         if( loaderror )
101             throw new FTGlyphRenderException( "Couldn't open font '" ~ fontname ~ "': loading error" );
102 
103         if( FT_Select_Charmap( face, FT_ENCODING_UNICODE ) )
104             throw new FTGlyphRenderException( "Couldn't select unicode encoding" );
105     }
106 
107 public:
108 
109     static GlyphRender get( string fontname )
110     {
111         if( fontname !in openFTGR )
112             openFTGR[fontname] = new FTGlyphRender( fontname );
113         return openFTGR[fontname];
114     }
115 
116     void setParams( in GlyphParam p )
117     {
118         FT_Set_Pixel_Sizes( face, 0, p.height );
119     }
120 
121     @property ElemInfo imtype() const { return ElemInfo( DataType.FLOAT, 1 ); }
122 
123     GlyphInfo render( wchar ch )
124     {
125         if( FT_Load_Char( face, cast(size_t)ch, FT_LOAD_RENDER ) )
126             throw new FTGlyphRenderException( "Couldn't load char" );
127 
128         auto g = face.glyph;
129 
130         ivec2 sz;
131         float[] img_data;
132 
133         if( ch == ' ' )
134         {
135             auto width = g.metrics.horiAdvance / 128.0;//TODO not proper way i think
136             sz = ivec2( width, g.bitmap.rows );
137             img_data.length = sz.x * sz.y;
138             img_data[] = 0;
139         }
140         else
141         {
142             sz = ivec2( g.bitmap.width, g.bitmap.rows );
143             img_data = amap!(a => a / 255.0f)(g.bitmap.buffer[0 .. sz.x * sz.y]);
144         }
145 
146         return GlyphInfo( ivec2( g.bitmap_left, -g.bitmap_top ), 
147                                 ivec2( cast(int)( g.advance.x >> 6 ), 
148                                        cast(int)( g.advance.y >> 6 ) ),
149                                 Image!2( imsize_t(sz), imtype, img_data ) );
150     }
151 
152     BitmapFont generateBitmapFont()//rendering only russian/english letters and basic symbols
153     {//TODO check if one line texture is proper
154         BitmapFont res;
155         GlyphInfo[wchar] glyphs;
156         foreach( wchar i; 32 .. 128 )// !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
157             glyphs[i] = render( i );
158         foreach( wchar i; 1040 .. 1104 )//АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя
159             glyphs[i] = render( i );
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 }