| 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 |
} |
|---|