cosmetix
[amper.git] / egfx / text.d
blobeee12114071dbc4285197bf1a6e695822906b96d
1 /* E-Mail Client
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module egfx.text;
19 private:
21 import arsd.simpledisplay;
23 import iv.alice;
24 import iv.bclamp;
25 import iv.cmdcon;
26 import iv.fontconfig;
27 import iv.freetype;
28 import iv.utfutil;
29 import iv.vfs;
31 import egfx.base;
34 // ////////////////////////////////////////////////////////////////////////// //
35 //__gshared string chiFontName = "Arial:pixelsize=16";
36 //__gshared string chiFontName = "Verdana:pixelsize=16";
37 __gshared string chiFontName = "Verdana:pixelsize=12";
38 __gshared string chiFontFile;
39 __gshared int fontSize;
40 __gshared int fontHeight;
41 __gshared int fontBaselineOfs;
44 // ////////////////////////////////////////////////////////////////////////// //
45 __gshared ubyte* ttfontdata;
46 __gshared uint ttfontdatasize;
48 __gshared FT_Library ttflibrary;
49 __gshared FTC_Manager ttfcache;
50 __gshared FTC_CMapCache ttfcachecmap;
51 __gshared FTC_ImageCache ttfcacheimage;
54 shared static ~this () {
55 if (ttflibrary) {
56 if (ttfcache) {
57 FTC_Manager_Done(ttfcache);
58 ttfcache = null;
60 FT_Done_FreeType(ttflibrary);
61 ttflibrary = null;
66 enum FontID = cast(FTC_FaceID)1;
69 extern(C) nothrow {
70 void ttfFontFinalizer (void* obj) {
71 import core.stdc.stdlib : free;
72 if (obj is null) return;
73 auto tf = cast(FT_Face)obj;
74 if (tf.generic.data !is ttfontdata) return;
75 if (ttfontdata !is null) {
76 //conwriteln("TTF CACHE: freeing loaded font...");
77 free(ttfontdata);
78 ttfontdata = null;
79 ttfontdatasize = 0;
83 FT_Error ttfFontLoader (FTC_FaceID face_id, FT_Library library, FT_Pointer request_data, FT_Face* aface) {
84 if (face_id == FontID) {
85 try {
86 if (ttfontdata is null) {
87 //conwriteln("TTF CACHE: loading '", chiFontFile, "'...");
88 import core.stdc.stdlib : malloc;
89 auto fl = VFile(chiFontFile);
90 auto fsz = fl.size;
91 if (fsz < 16 || fsz > int.max/8) throw new Exception("invalid ttf size");
92 ttfontdatasize = cast(uint)fsz;
93 ttfontdata = cast(ubyte*)malloc(ttfontdatasize);
94 if (ttfontdata is null) assert(0, "out of memory");
95 fl.rawReadExact(ttfontdata[0..ttfontdatasize]);
97 auto res = FT_New_Memory_Face(library, cast(const(FT_Byte)*)ttfontdata, ttfontdatasize, 0, aface);
98 if (res != 0) throw new Exception("error loading ttf: '"~chiFontFile~"'");
99 (*aface).generic.data = ttfontdata;
100 (*aface).generic.finalizer = &ttfFontFinalizer;
101 } catch (Exception e) {
102 if (ttfontdata !is null) {
103 import core.stdc.stdlib : free;
104 free(ttfontdata);
105 ttfontdata = null;
106 ttfontdatasize = 0;
108 conwriteln("ERROR loading font: ", e.msg);
109 return FT_Err_Cannot_Open_Resource;
111 return FT_Err_Ok;
112 } else {
113 conwriteln("TTF CACHE: invalid font id");
115 return FT_Err_Cannot_Open_Resource;
120 void ttfLoad () nothrow {
121 if (FT_Init_FreeType(&ttflibrary)) assert(0, "can't initialize FreeType");
122 if (FTC_Manager_New(ttflibrary, 0, 0, 0, &ttfFontLoader, null, &ttfcache)) assert(0, "can't initialize FreeType cache manager");
123 if (FTC_CMapCache_New(ttfcache, &ttfcachecmap)) assert(0, "can't initialize FreeType cache manager");
124 if (FTC_ImageCache_New(ttfcache, &ttfcacheimage)) assert(0, "can't initialize FreeType cache manager");
126 FTC_ScalerRec fsc;
127 fsc.face_id = FontID;
128 fsc.width = 0;
129 fsc.height = fontSize;
130 fsc.pixel = 1; // size in pixels
132 FT_Size ttfontsz;
133 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) assert(0, "cannot find FreeType font");
134 fontHeight = ttfontsz.metrics.height>>6; // 26.6
135 fontBaselineOfs = cast(int)((ttfontsz.metrics.height+ttfontsz.metrics.descender)>>6);
136 if (fontHeight < 2 || fontHeight > 128) assert(0, "invalid FreeType font metrics");
138 //conwriteln("TTF CACHE initialized.");
142 void initFontEngine () nothrow {
143 if (ttflibrary is null) {
144 import std.string : fromStringz, toStringz;
145 if (!FcInit()) assert(0, "cannot init fontconfig");
146 FcPattern* pat = FcNameParse(chiFontName.toStringz);
147 if (pat is null) assert(0, "cannot parse font name");
148 if (!FcConfigSubstitute(null, pat, FcMatchPattern)) assert(0, "cannot find fontconfig substitute");
149 FcDefaultSubstitute(pat);
150 // find the font
151 FcResult result;
152 FcPattern* font = FcFontMatch(null, pat, &result);
153 if (font !is null) {
154 char* file = null;
155 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
156 //conwriteln("font file: [", file, "]");
157 chiFontFile = file.fromStringz.idup;
159 double pixelsize;
160 if (FcPatternGetDouble(font, FC_PIXEL_SIZE, 0, &pixelsize) == FcResultMatch) {
161 //conwriteln("pixel size: ", pixelsize);
162 fontSize = cast(int)pixelsize;
165 FcPatternDestroy(pat);
166 // arbitrary limits
167 if (fontSize < 6) fontSize = 6;
168 if (fontSize > 42) fontSize = 42;
169 ttfLoad();
174 // ////////////////////////////////////////////////////////////////////////// //
175 public void utfByDChar (const(char)[] s, scope void delegate (dchar ch) nothrow @trusted dg) nothrow @trusted {
176 if (dg is null) return;
177 Utf8DecoderFast dc;
178 foreach (char ch; s) {
179 if (dc.decode(cast(ubyte)ch)) dg(dc.complete ? dc.codepoint : dc.replacement);
184 public void utfByDCharSPos (const(char)[] s, scope void delegate (dchar ch, usize stpos) nothrow @trusted dg) nothrow @trusted {
185 if (dg is null) return;
186 Utf8DecoderFast dc;
187 usize stpos = 0;
188 foreach (immutable idx, char ch; s) {
189 if (dc.decode(cast(ubyte)ch)) {
190 dg(dc.complete ? dc.codepoint : dc.replacement, stpos);
191 stpos = idx+1;
197 // ////////////////////////////////////////////////////////////////////////// //
198 void drawFTBitmap (int x, int y, in ref FT_Bitmap bitmap, uint clr)nothrow @trusted @nogc {
199 if (bitmap.pixel_mode != FT_PIXEL_MODE_MONO) return; // alas
200 if (bitmap.rows < 1 || bitmap.width < 1) return; // nothing to do
201 if (gxIsTransparent(clr)) return; // just in case
202 // prepare
203 bool topdown = true;
204 const(ubyte)* src = bitmap.buffer;
205 int spt = bitmap.pitch;
206 if (spt < 0) {
207 topdown = false;
208 spt = -spt;
209 src += spt*(bitmap.rows-1);
211 if (!gxIsSolid(clr)) {
212 // let this be slow
213 foreach (immutable int dy; 0..bitmap.rows) {
214 ubyte count = 0, b = 0;
215 auto ss = src;
216 foreach (immutable int dx; 0..bitmap.width) {
217 if (count-- == 0) { count = 7; b = *ss++; } else b <<= 1;
218 if (b&0x80) gxPutPixel(x+dx, y, clr);
220 ++y;
221 if (topdown) src += spt; else src -= spt;
223 return;
225 // check if we can use fastest path
226 auto brc = GxRect(x, y, bitmap.width, bitmap.rows);
227 if (gxClipRect.contains(brc) && GxRect(0, 0, VBufWidth, VBufHeight).contains(brc)) {
228 // yay, the fastest one!
229 uint* dptr = vglTexBuf+y*VBufWidth+x;
230 foreach (immutable int dy; 0..bitmap.rows) {
231 ubyte count = 0, b = 0;
232 auto ss = src;
233 auto curptr = dptr;
234 foreach (immutable int dx; 0..bitmap.width) {
235 if (count-- == 0) { count = 7; b = *ss++; } else b <<= 1;
236 if (b&0x80) *curptr = clr;
237 ++curptr;
239 if (topdown) src += spt; else src -= spt;
240 dptr += VBufWidth;
242 } else {
243 // do it slow
244 foreach (immutable int dy; 0..bitmap.rows) {
245 ubyte count = 0, b = 0;
246 auto ss = src;
247 foreach (immutable int dx; 0..bitmap.width) {
248 if (count-- == 0) { count = 7; b = *ss++; } else b <<= 1;
249 if (b&0x80) gxSetPixel(x+dx, y, clr);
251 ++y;
252 if (topdown) src += spt; else src -= spt;
258 // y is baseline; returns advance
259 int ttfDrawGlyph (bool firstchar, int scale, int x, int y, int glyphidx, uint clr) nothrow @trusted {
260 enum mono = true;
261 if (glyphidx == 0) return 0;
263 FTC_ImageTypeRec fimg;
264 fimg.face_id = FontID;
265 fimg.width = 0;
266 fimg.height = fontSize*scale;
267 static if (mono) {
268 fimg.flags = FT_LOAD_TARGET_MONO|(gxIsTransparent(clr) ? 0 : FT_LOAD_MONOCHROME|FT_LOAD_RENDER);
269 } else {
270 fimg.flags = (gxIsTransparent(clr) ? 0 : FT_LOAD_RENDER);
273 FT_Glyph fg;
274 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyphidx, &fg, null)) return 0;
276 int advdec = 0;
277 if (!gxIsTransparent(clr)) {
278 if (fg.format != FT_GLYPH_FORMAT_BITMAP) return 0;
279 FT_BitmapGlyph fgi = cast(FT_BitmapGlyph)fg;
280 int x0 = x+fgi.left;
281 if (firstchar && fgi.bitmap.width > 0) { x0 -= fgi.left; advdec = fgi.left; }
282 drawFTBitmap(x0, y-fgi.top, fgi.bitmap, clr);
284 return cast(int)(fg.advance.x>>16)-advdec;
288 int ttfGetKerning (int scale, int gl0idx, int gl1idx) nothrow @trusted {
289 if (gl0idx == 0 || gl1idx == 0) return 0;
291 FTC_ScalerRec fsc;
292 fsc.face_id = FontID;
293 fsc.width = 0;
294 fsc.height = fontSize*scale;
295 fsc.pixel = 1; // size in pixels
297 FT_Size ttfontsz;
298 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) return 0;
299 if (!FT_HAS_KERNING(ttfontsz.face)) return 0;
301 FT_Vector kk;
302 if (FT_Get_Kerning(ttfontsz.face, gl0idx, gl1idx, FT_KERNING_UNSCALED, &kk)) return 0;
303 if (!kk.x) return 0;
304 auto kadvfrac = FT_MulFix(kk.x, ttfontsz.metrics.x_scale); // 1/64 of pixel
305 return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
309 // ////////////////////////////////////////////////////////////////////////// //
310 public int gxCharWidthScaled (int scale, dchar ch) nothrow @trusted {
311 if (scale < 1) return 0;
313 initFontEngine();
315 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
316 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, '\u25A1');
317 if (glyph == 0) return 0;
319 FTC_ImageTypeRec fimg;
320 fimg.face_id = FontID;
321 fimg.width = 0;
322 fimg.height = fontSize*scale;
323 fimg.flags = FT_LOAD_TARGET_MONO;
325 FT_Glyph fg;
326 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null)) return -666;
328 int res = fg.advance.x>>16;
329 return (res > 0 ? res : 0);
332 public int gxCharWidth (dchar ch) nothrow @trusted { return gxCharWidthScaled(1, ch); }
334 // return char width
335 public int gxDrawChar (int x, int y, dchar ch, uint fg, int prevcp=-1) nothrow @trusted { return gxDrawCharScaled(1, x, y, ch, fg, prevcp); }
336 public int gxDrawChar() (in auto ref GxPoint p, char ch, uint fg, int prevcp=-1) nothrow @trusted { return gxDrawCharScaled(1, p.x, p.y, ch, fg, prevcp); }
339 // ////////////////////////////////////////////////////////////////////////// //
340 // return char width
341 public int gxDrawCharScaled (int scale, int x, int y, dchar ch, uint clr, int prevcp=-1) nothrow @trusted {
342 if (scale < 1) return 0;
344 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
345 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, '\u25A1');
346 if (glyph == 0) return 0;
348 int kadv = ttfGetKerning(scale, (prevcp >= 0 ? FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, prevcp) : 0), glyph);
349 return ttfDrawGlyph(false, scale, x+kadv, y+fontBaselineOfs*scale, glyph, clr);
352 public int gxDrawCharScaled() (int scale, in auto ref GxPoint p, char ch, uint fg, int prevcp=-1) nothrow @trusted { return gxDrawCharScaled(scale, p.x, p.y, ch, fg, prevcp); }
355 // ////////////////////////////////////////////////////////////////////////// //
356 public struct GxKerning {
357 int prevgidx = 0;
358 int wdt = 0;
359 int lastadv = 0;
360 int lastcw = 0;
361 int tabsize = 0;
362 int scale = 1;
363 bool firstchar = true;
365 nothrow @trusted:
366 this (int atabsize, int ascale=1, bool firstCharIsFull=false) {
367 initFontEngine();
368 firstchar = !firstCharIsFull;
369 scale = ascale;
370 if ((tabsize = (atabsize > 0 ? atabsize : 0)) != 0) tabsize = tabsize*gxCharWidthScaled(ascale, ' ');
373 void reset (int atabsize) {
374 initFontEngine();
375 prevgidx = 0;
376 wdt = 0;
377 lastadv = 0;
378 lastcw = 0;
379 if ((tabsize = (atabsize > 0 ? atabsize : 0)) != 0) tabsize = tabsize*gxCharWidthScaled(scale, ' ');
380 firstchar = true;
383 // tab length for current position
384 int tablength () { pragma(inline, true); return (tabsize > 0 ? (wdt/tabsize+1)*tabsize-wdt : 0); }
386 int fixWidthPre (dchar ch) {
387 immutable int prevgl = prevgidx;
388 wdt += lastadv;
389 lastadv = 0;
390 lastcw = 0;
391 prevgidx = 0;
392 if (ch == '\t' && tabsize > 0) {
393 // tab
394 lastadv = lastcw = tablength;
395 firstchar = false;
396 } else {
397 initFontEngine();
398 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
399 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, '\u25A1');
400 if (glyph != 0) {
401 wdt += ttfGetKerning(scale, prevgl, glyph);
403 FTC_ImageTypeRec fimg;
404 fimg.face_id = FontID;
405 fimg.width = 0;
406 fimg.height = fontSize*scale;
407 version(none) {
408 fimg.flags = FT_LOAD_TARGET_MONO;
409 } else {
410 fimg.flags = FT_LOAD_TARGET_MONO|FT_LOAD_MONOCHROME|FT_LOAD_RENDER;
413 FT_Glyph fg;
414 version(none) {
415 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null) == 0) {
416 prevgidx = glyph;
417 lastadv = fg.advance.x>>16;
419 } else {
420 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null) == 0) {
421 int advdec = 0;
422 if (fg.format == FT_GLYPH_FORMAT_BITMAP) {
423 FT_BitmapGlyph fgi = cast(FT_BitmapGlyph)fg;
424 if (firstchar && fgi.bitmap.width > 0) {
425 lastcw = fgi.bitmap.width;
426 advdec = fgi.left;
427 if (lastcw < 1) { advdec = 0; lastcw = fg.advance.x>>16; }
428 } else {
429 lastcw = fgi.left+fgi.bitmap.width;
430 if (lastcw < 1) lastcw = fg.advance.x>>16;
433 prevgidx = glyph;
434 lastadv = (fg.advance.x>>16)-advdec;
435 firstchar = false;
440 return wdt;
443 @property int finalWidth () const { pragma(inline, true); return wdt+/*lastadv*/lastcw; }
445 // BUGGY!
446 @property int nextCharOfs () const { pragma(inline, true); return wdt+lastadv; }
448 @property int currOfs () const { pragma(inline, true); return wdt; }
450 @property int nextOfsNoSpacing () const { pragma(inline, true); return wdt+lastcw; }
454 // ////////////////////////////////////////////////////////////////////////// //
455 public struct GxDrawTextOptions {
456 int tabsize = 0;
457 uint clr = gxTransparent;
458 int scale = 1;
459 bool firstCharIsFull = false;
461 static pure nothrow @safe @nogc:
462 auto Color (uint aclr) { pragma(inline, true); return GxDrawTextOptions(0, aclr, 1, false); }
463 auto Tab (int atabsize) { pragma(inline, true); return GxDrawTextOptions(atabsize, gxTransparent, 1, false); }
464 auto TabColor (int atabsize, uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, 1, false); }
465 auto TabColorFirstFull (int atabsize, uint aclr, bool fcf) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, 1, fcf); }
466 auto ScaleTabColor (int ascale, int atabsize, uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, ascale, false); }
467 auto ColorNFC (uint aclr) { pragma(inline, true); return GxDrawTextOptions(0, aclr, 1, true); }
468 auto TabColorNFC (int atabsize, uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, 1, true); }
469 auto ScaleTabColorNFC (int ascale, int atabsize, uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, ascale, true); }
470 // more ctors?
473 public struct GxDrawTextState {
474 usize spos; // current codepoint starting position
475 usize epos; // current codepoint ending position (exclusive; i.e. *after* codepoint)
476 int curx; // current x (before drawing the glyph)
480 // delegate should return color
481 public int gxDrawTextUtf(R) (in auto ref GxDrawTextOptions opt, int x, int y, auto ref R srng, uint delegate (in ref GxDrawTextState state) nothrow @trusted clrdg=null) nothrow @trusted
482 if (Imp!"std.range.primitives".isInputRange!R && is(Imp!"std.range.primitives".ElementEncodingType!R == char))
484 // rely on the assumption that font face won't be unloaded while we are in this function
485 if (opt.scale < 1) return 0;
487 initFontEngine();
489 GxDrawTextState state;
491 y += fontBaselineOfs*opt.scale;
493 immutable int tabpix = (opt.tabsize > 0 ? opt.tabsize*gxCharWidthScaled(opt.scale, ' ') : 0);
495 FT_Size ttfontsz;
497 int prevglyph = 0;
498 immutable int stx = x;
500 bool dokern = true;
501 bool firstchar = !opt.firstCharIsFull;
502 Utf8DecoderFast dc;
504 while (!srng.empty) {
505 immutable ubyte srbyte = cast(ubyte)srng.front;
506 srng.popFront();
507 ++state.epos;
509 if (dc.decode(srbyte)) {
510 int ch = (dc.complete ? dc.codepoint : dc.replacement);
511 int pgl = prevglyph;
512 prevglyph = 0;
513 state.curx = x;
515 if (opt.tabsize > 0) {
516 if (ch == '\t') {
517 firstchar = false;
518 int wdt = x-stx;
519 state.curx = x;
520 x += (wdt/tabpix+1)*tabpix-wdt;
521 if (clrdg !is null) clrdg(state);
522 state.spos = state.epos;
523 continue;
527 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
528 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, '\u25A1');
529 if (glyph != 0) {
530 // kerning
531 int kadv = 0;
532 if (pgl != 0 && dokern) {
533 if (ttfontsz is null) {
534 FTC_ScalerRec fsc;
535 fsc.face_id = FontID;
536 fsc.width = 0;
537 fsc.height = fontSize*opt.scale;
538 fsc.pixel = 1; // size in pixels
539 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) {
540 dokern = false;
541 ttfontsz = null;
542 } else {
543 dokern = (FT_HAS_KERNING(ttfontsz.face) != 0);
546 if (dokern) {
547 FT_Vector kk;
548 if (FT_Get_Kerning(ttfontsz.face, pgl, glyph, FT_KERNING_UNSCALED, &kk) == 0) {
549 if (kk.x) {
550 auto kadvfrac = FT_MulFix(kk.x, ttfontsz.metrics.x_scale); // 1/64 of pixel
551 kadv = cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
556 x += kadv;
557 uint clr = opt.clr;
558 if (clrdg !is null) { state.curx = x; clr = clrdg(state); }
559 x += ttfDrawGlyph(firstchar, opt.scale, x, y, glyph, clr);
560 firstchar = false;
562 state.spos = state.epos;
563 prevglyph = glyph;
567 return x-stx;
571 public int gxDrawTextUtf() (in auto ref GxDrawTextOptions opt, int x, int y, const(char)[] s, uint delegate (in ref GxDrawTextState state) nothrow @trusted clrdg=null) nothrow @trusted {
572 static struct StrIterator {
573 private:
574 const(char)[] str;
575 public pure nothrow @trusted @nogc:
576 this (const(char)[] s) { pragma(inline, true); str = s; }
577 @property bool empty () const { pragma(inline, true); return (str.length == 0); }
578 @property char front () const { pragma(inline, true); return (str.length ? str.ptr[0] : 0); }
579 void popFront () { if (str.length) str = str[1..$]; }
582 return gxDrawTextUtf(opt, x, y, StrIterator(s), clrdg);
586 public int gxTextWidthScaledUtf (int scale, const(char)[] s, int tabsize=0, bool firstCharIsFull=false) nothrow @trusted {
587 if (scale < 1) return 0;
588 auto kern = GxKerning(tabsize, scale, firstCharIsFull);
589 s.utfByDChar(delegate (dchar ch) @trusted { kern.fixWidthPre(ch); });
590 return kern.finalWidth;
595 // ////////////////////////////////////////////////////////////////////////// //
596 public int gxTextHeightUtf () nothrow @trusted { initFontEngine(); return fontHeight; }
597 public int gxTextHeightScaledUtf (int scale) nothrow @trusted { initFontEngine(); return (scale < 1 ? 0 : fontHeight*scale); }
599 public int gxTextWidthUtf (const(char)[] s, int tabsize=0, bool firstCharIsFull=false) nothrow @trusted { return gxTextWidthScaledUtf(1, s, tabsize, firstCharIsFull); }
601 public int gxDrawTextUtf() (int x, int y, const(char)[] s, uint clr) nothrow @trusted { return gxDrawTextUtf(GxDrawTextOptions.Color(clr), x, y, s); }
602 public int gxDrawTextUtf() (in auto ref GxPoint p, const(char)[] s, uint clr) nothrow @trusted { return gxDrawTextUtf(p.x, p.y, s, clr); }
605 public int gxDrawTextOutScaledUtf (int scale, int x, int y, const(char)[] s, uint clr, uint clrout) nothrow @trusted {
606 if (scale < 1) return 0;
607 auto opt = GxDrawTextOptions.ScaleTabColor(scale, 0, clrout);
608 foreach (immutable dy; -1*scale..2*scale) {
609 foreach (immutable dx; -1*scale..2*scale) {
610 if (dx || dy) gxDrawTextUtf(opt, x+dx, y+dy, s);
613 opt.clr = clr;
614 return gxDrawTextUtf(opt, x, y, s);