root/trunk/qd/SDL_ttf.d

Revision 778, 13.5 kB (checked in by FeepingCreature, 2 years ago)

Small fixes

Line 
1 module SDL_ttf;
2 import qd, tools.compat;
3
4 import tools.compat;
5
6 int deflt_size = 14;
7
8 struct fontsettings {
9   bool bold, italic, underline;
10   rgb color=Black; int size=-1;
11 }
12
13 import tools.base;
14
15 const SDL_Color SDL_White={255, 255, 255, 0};
16
17 TTF_FontClass[int] standard;
18 string lastname;
19 void initFont(int size = 14, string name="") {
20   if (name == lastname && size in standard) { deflt_size = size; return; }
21   lastname = name;
22   void[] data;
23   if (name.length) data = name.read();
24   else data = import("Vera.ttf");
25   if (!standard.length) deflt_size = size;
26   standard[size] = new TTF_FontClass(data, size);
27 }
28
29 int htoi(string s) {
30   int res;
31   foreach (ch; s) {
32     if (ch >= '0' && ch <= '9') { res = res * 16 + (ch - '0'); continue; }
33     if (ch >= 'a' && ch <= 'f') ch -= ('a' - 'A');
34     if (ch >= 'A' && ch <= 'F') { res = res * 16 + (ch - 'A') + 10; continue; }
35     throw new Exception("Not a hexadecimal string: "~s);
36   }
37   return res;
38 }
39
40 void prettyprint(T...)(T all) {
41   static if (is(T[$-1] == fontsettings) && is(T[$-2]: string)) {
42     auto param = all[0 .. $-2], text = all[$-2], deflt = all[$-1];
43   } else static if (is(T[$-1]: string)) {
44     auto param = all[0 .. $-1], text = all[$-1], deflt = Init!(fontsettings);
45   } else static assert(false, "Invalid parameters for prettyprint: "~T.stringof);
46   if (deflt.size == -1) deflt.size = deflt_size;
47   string[] chunks;
48   fontsettings[] sets;
49   Stuple!(string, fontsettings)[] stack;
50   fontsettings fs_top() {
51     if (stack.length) {
52       return stack[$-1]._1;
53     } else return deflt;
54   }
55   fontsettings fs_pop(string compare) {
56     if (stack[$-1]._0 != compare)
57       throw new Exception("Mismatched closing tag: expected "~stack[$-1]._0~", got "~compare);
58     return .pop(stack)._1;
59   }
60   void push(string text, string tag, void delegate(ref fontsettings fs) dg) {
61     chunks ~= text;
62     sets ~= fs_top();
63     stack ~= stuple(tag, fs_top()); dg(stack[$-1]._1);
64   }
65   void pop(string text, string tag) {
66     chunks ~= text;
67     sets ~= fs_pop(tag);
68   }
69   void delegate(string, ref string) close_tag(string tag) {
70     return stuple(&pop, tag) /apply/ (typeof(&pop) p, string tag, string pre, ref string post) { return p(pre, tag); };
71   }
72   void delegate(string, ref string) [string] tags = [
73     "[b]"[]: (string pre, ref string post) {
74       push(pre, "b", (ref fontsettings fs) { fs.bold = true; });
75     }, "[/b]": close_tag("b"),
76     "[i]": (string pre, ref string post) {
77       push(pre, "i", (ref fontsettings fs) { fs.italic = true; });
78     }, "[/i]": close_tag("i"),
79     "[u]": (string pre, ref string post) {
80       push(pre, "u", (ref fontsettings fs) { fs.underline = true; });
81     }, "[/u]": close_tag("u"),
82     "[color ": (string pre, ref string post) {
83       string color; ptuple(color, post) = post.splitAt("]");
84       //                            color.length != 3 || color.length != 6 -- see, it's shorter :)
85       color = color.startsWith("#");
86       if (!color || color.length != 3 /or/ 6)
87         throw new Exception("Colors except #xxx and #xxxxxx not supported: "~color~".");
88       if (color.length == 3) color = color[0]~"0"~color[1]~"0"~color[2]~"0";
89       auto res = rgb(color[0 .. 2].htoi(), color[2 .. 4].htoi(), color[4 .. 6].htoi());
90       push(pre, "color", (ref fontsettings fs) { fs.color = res; });
91     }, "[/color]": close_tag("color"),
92     "[size ": (string pre, ref string post) {
93       int dir;
94       if (post[0] == '+') dir = 1;
95       else if (post[0] == '-') dir = -1;
96       if (post[0] == '+' /or/ '-') post = post[1 .. $];
97       string s; ptuple(s, post) = post.splitAt("]");
98       auto n = s.atoi();
99       push(pre, "size", (ref fontsettings fs) {
100         if (!dir) fs.size = n;
101         else fs.size += n * dir;
102       });
103     }, "[/size]": close_tag("size")
104   ];
105   text.glomp_parse(tags, (string s) {
106     if (stack.length) throw new Exception("Unclosed tag: "~stack[$-1]._0);
107     if (s.length) { chunks ~= s; sets ~= deflt; }
108   });
109   print(param, sets, chunks);
110 }
111
112 void _print(Area delegate(int w, int h) dg, int maxwidth, int delegate() linebreak, fontsettings[] settings, string[] strings) in {
113     assert(settings.length == strings.length);
114   } body {
115   Area[] surfs;
116   scope(exit) foreach (surf; surfs) delete surf;
117   int width, height, fullwidth, fullheight;
118   int[] heights;
119   void newline(string str, bool lbcall = true) {
120     if (lbcall && linebreak) { auto value = linebreak(); if (value) height = value; } // override
121     if (width > fullwidth) fullwidth = width;
122     fullheight += height;
123     heights ~= height;
124     // logln("newline -> ", fullwidth, ", ", fullheight, " <- ", width, ", ", height);
125     // logln("Remain: ", str);
126     height = width = 0;
127     if (lbcall) surfs ~= null; // marker
128   }
129   foreach (i, str; strings) {
130     auto s = settings[i];
131     if (!(s.size in standard)) initFont(s.size);
132 redo:
133     surfs ~= standard[s.size].render_linebreak(str, maxwidth, s);
134     width += surfs[$-1].width;
135     if (surfs[$-1].height > height) height = surfs[$-1].height;
136     if (str.length) {
137       newline(str);
138       goto redo;
139     }
140   }
141   newline("", false);
142   //auto target = display.select(pt(x, y), pt(width, height), how);
143   auto target = dg(fullwidth, fullheight);
144   if (!target) return;
145   auto mysurf = target.select(0, 0);
146   foreach (surf; surfs) {
147     if (!surf) {
148       target = target.select(0, heights[0]); heights = heights[1 .. $];
149       mysurf = target.select(0, 0);
150     } else {
151       mysurf.blit(surf, Top|Left);
152       mysurf.width = surf.width - width; // yes this is correct.
153       mysurf.tl.x += surf.width;
154       width -= surf.width;
155     }
156   }
157 }
158
159 import tools.base;
160
161 template _SelectSet(TH /* TupleWrapper */, TYPES...) {
162   static if (!TYPES.length) {
163     alias Tuple!() values;
164   } else {
165     const value = Select!(TYPES[0], TH.Tuple);
166     static if (value == -1) alias TH TH2;
167     else alias TupleWrapper!(TH.Tuple[0..value], TH.Tuple[value+1 .. $]) TH2;
168     static assert(Select!(TYPES[0], TH2.Tuple) == -1, "Duplicate "~TYPES[0].stringof~"!");
169     alias Tuple!(value, _SelectSet!(TH, TYPES[1..$]).values) values;
170   }
171 }
172
173 template SelectSet(TH, TYPES...) { alias _SelectSet!(TH, TYPES).values SelectSet; }
174
175 typedef int _linebreak;
176 _linebreak Linebreak(int width) { return cast(_linebreak) width; }
177
178 void print(T...)(T params) {
179   static if (is(T[0]: int) && is(T[1]: int)) {
180     Area fn(int w, int h, Align how) { return display.select(pt(params[0], params[1]), pt(w, h), how); }
181     alias T[2 .. $] Rest; const offs = 2;
182   } else static if (is(T[0] == pt)) {
183     Area fn(int w, int h, Align how) { return display.select(params[0], pt(w, h), how); }
184     alias T[1 .. $] Rest; const offs = 1;
185   } else static if (is(T[0] == Area)) {
186     Area fn(int w, int h, Align how) { return params[0].select(pt(w, h), how); }
187     alias T[1 .. $] Rest; const offs = 1;
188   } else static if (is(T[0] == Area delegate(int w, int h))) {
189     Area fn(int w, int h, Align how) { return params[0](w, h); }
190     alias T[1 .. $] Rest; const offs = 1;
191   } else static assert(false, "print: "~T.stringof~": no position");
192  
193   alias SelectSet!(TupleWrapper!(TupleMap!(Unstatic, Rest)),
194     string, Align, rgb, string[], fontsettings[], back_rgb, _linebreak, int delegate()) POS;
195   fontsettings foo;
196   foo.size = deflt_size;
197   static assert(POS[0] != -1 ^ POS[3] != -1, "print needs string parameter xor string[] parameter");
198   static if (POS[0] != -1) auto strings = ([params[offs+POS[0]]])[];
199   else auto strings = params[offs+POS[3]];
200   static if (POS[1] != -1) auto how = params[offs+POS[1]];
201   else auto how = Center;
202   static if (POS[2] != -1) foo.color = params[offs+POS[2]];
203   else foo.color = White;
204   static if (POS[4] != -1) auto fs_array = params[offs+POS[4]];
205   else auto fs_array = ([foo])[];
206   bool fill; rgb fillcol;
207   static if (POS[5] != -1) { fill = true; fillcol = params[offs+POS[5]]; }
208   int width = -1; int delegate() dg;
209   static if (POS[6] != -1) {
210     width = params[offs+POS[6]];
211     static assert(POS[7] == POS[6] + 1, "Linebreak = num, { ... }?");
212     dg = params[offs+POS[7]];
213   }
214   _print((int w, int h) {
215     auto res = fn(w, h, how);
216     if (fill) line(res.tl, res.br, Fill=fillcol);
217     return res;
218   }, width, dg, fs_array, strings);
219 }
220
221 class TTF_FontClass {
222   static this() { if (!TTF_WasInit) TTF_Init; }
223   static ~this() { if (TTF_WasInit) TTF_Quit; }
224   private TTF_Font *font;
225   int height() { return TTF_FontHeight(font); }
226   int ascent() { return TTF_FontAscent(font); }
227   int descent() { return TTF_FontDescent(font); }
228   int lineskip() { return TTF_FontLineSkip(font); }
229   static fontsettings Default;
230   int getWidth(char[] text) {
231     int w, h; TTF_SizeUTF8(font, toStringz(text), &w, &h);
232     return w;
233   }
234   int curStyle;
235   private SDL_Surface *_render(char[] text, fontsettings s=Default, int rendermode=2, SDL_Color *bg=null) {
236     //logln("Rendering ", text);
237     /// Make sure no two routines change font settings at the same time
238     synchronized {
239       int style=void;
240       with (s) style=(bold?1:0) + (italic?2:0) + (underline?4:0);
241       if (curStyle!=style) TTF_SetFontStyle(font, style);
242       curStyle=style;
243       /// Text mode: 0=Latin1, 1=UTF8, 2=Unicode
244       switch(rendermode) {
245         case 0: // Solid
246           return TTF_RenderUTF8_Solid(font, toStringz(text), s.color.toSDL());
247         case 1: // Shaded
248           if (!bg) throw new Exception("Shaded selected but no background color given");
249           return TTF_RenderUTF8_Shaded(font, toStringz(text), s.color.toSDL(), *bg);
250         case 2: // Blended
251           return TTF_RenderUTF8_Blended(font, toStringz(text), s.color.toSDL());
252         default: throw new Exception("Invalid case");
253       }
254     }
255     assert(false);
256   }
257   Area render(string text, fontsettings s=Default) {
258     if (!text.length) return Area(SDL_CreateRGBSurface(0, 1, 1, 32));
259     auto res=_render(text, s);
260     if (!res) throw new Exception("Couldn't render "~'"'~text~'"'~": "~.toString(SDL_GetError));
261     return Area(res);
262   }
263   Area render_linebreak(ref string text, int width, fontsettings s=Default) {
264     if (width == -1) {
265       scope(exit) text = null;
266       return render(text, s);
267     }
268     auto start_text = text, len = text.length;
269     scope(exit) text = start_text[text.length .. $];
270    
271     if (!text.length) return Area(SDL_CreateRGBSurface(0, 1, 1, 32));
272     int change;
273     retry:
274     auto res=_render(text, s);
275     if (!res) throw new Exception("Couldn't render "~'"'~text~'"'~": "~.toString(SDL_GetError));
276     if (res.w > width) {
277       if (!change) change = start_text.length / 2 + 1;
278       len = len - change; change = change / 2 + 1;
279       text = start_text[0 .. len];
280       SDL_FreeSurface(res); goto retry;
281     }
282     if (change <= 2) return Area(res);
283     len = min(len + change, start_text.length); change = change / 2 + 1;
284     text = start_text[0 .. len];
285     SDL_FreeSurface(res); goto retry;
286   }
287   void[] file_buffer;
288   this(void[] file, int ptsize) {
289     file_buffer = file;
290     font=TTF_OpenFontRW(SDL_RWFromMem(file.ptr, file.length), 1, ptsize);
291     if (!font) throw new Exception("TTF_FontClass.this: Couldn't open font: "~.toString(SDL_GetError));
292   }
293   ~this() { TTF_CloseFont(font); }
294 }
295
296 extern(C) {
297   alias void TTF_Font; /// Opaque struct
298   // General
299     // Activation
300     int TTF_Init();
301     int TTF_WasInit();
302     void TTF_Quit();
303     // Errors
304     /// Just use SDL_GetError.
305   // Management
306     // Loading
307     TTF_Font *TTF_OpenFontRW(SDL_RWops *src, int freesrc, int ptsize);
308     TTF_Font *TTF_OpenFontIndex(char *file, int ptsize, long index);
309     // Freeing
310     void TTF_CloseFont(TTF_Font *font);
311   // Attributes
312     // Global Attributes
313     void TTF_ByteSwappedUNICODE(int swapped);
314     // Font Style
315     int TTF_GetFontStyle(TTF_Font *font);
316     void TTF_SetFontStyle(TTF_Font *font, int style);
317     // Font Metrics
318     int TTF_FontHeight(TTF_Font *font);
319     int TTF_FontAscent(TTF_Font *font);
320     int TTF_FontDescent(TTF_Font *font);
321     int TTF_FontLineSkip(TTF_Font *font); /// Recommended pixel height of a rendered line
322     // Face Attributes
323     long TTF_FontFaces(TTF_Font *font);
324     int TTF_FontFaceIsFixedWidth(TTF_Font *font);
325     char *TTF_FontFaceFamilyName(TTF_Font *font);
326     char *TTF_FontFaceStyleName(TTF_Font *font);
327     // Glyph Metrics
328     int TTF_GlyphMetrics(TTF_Font *font, ushort unichar,
329                          int *minx, int *maxx,
330                          int *miny, int *maxy,
331                          int *advance
332                         );
333     // Text Metrics
334     int TTF_SizeText(TTF_Font *font, char *text, int *w, int *h);
335     int TTF_SizeUTF8(TTF_Font *font, char *text, int *w, int *h);
336     int TTF_SizeUNICODE(TTF_Font *font, wchar *text, int *w, int *h);
337   // Render
338     // Solid
339     SDL_Surface *TTF_RenderText_Solid(TTF_Font *font, char *text, SDL_Color fg);
340     SDL_Surface *TTF_RenderUTF8_Solid(TTF_Font *font, char *text, SDL_Color fg);
341     SDL_Surface *TTF_RenderUNICODE_Solid(TTF_Font *font, wchar *text, SDL_Color fg);
342     SDL_Surface *TTF_RenderGlyph_Solid(TTF_Font *font, ushort unichar, SDL_Color fg);
343     // Shaded
344     SDL_Surface *TTF_RenderText_Shaded(TTF_Font *font, char *text, SDL_Color fg, SDL_Color bg);
345     SDL_Surface *TTF_RenderUTF8_Shaded(TTF_Font *font, char *text, SDL_Color fg, SDL_Color bg);
346     SDL_Surface *TTF_RenderUNICODE_Shaded(TTF_Font *font, wchar *text, SDL_Color fg, SDL_Color bg);
347     SDL_Surface *TTF_RenderGlyph_Shaded(TTF_Font *font, ushort unichar, SDL_Color fg, SDL_Color bg);
348     // Blended
349     SDL_Surface *TTF_RenderText_Blended(TTF_Font *font, char *text, SDL_Color fg);
350     SDL_Surface *TTF_RenderUTF8_Blended(TTF_Font *font, char *text, SDL_Color fg);
351     SDL_Surface *TTF_RenderUNICODE_Blended(TTF_Font *font, wchar *text, SDL_Color fg);
352     SDL_Surface *TTF_RenderGlyph_Blended(TTF_Font *font, ushort unichar, SDL_Color fg);
353 }
Note: See TracBrowser for help on using the browser.