egfx: ...and removed ;-)
[chiroptera.git] / egfx / text.d
blob9787b03b9de76faf0012ef7d6e26f900e12c795c
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, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module egfx.text /*is aliced*/;
18 private:
20 import arsd.simpledisplay;
22 import iv.alice;
23 import iv.bclamp;
24 import iv.cmdcon;
25 import iv.fontconfig;
26 import iv.freetype;
27 import iv.utfutil;
28 import iv.vfs;
30 import egfx.config;
31 import egfx.base;
34 // ////////////////////////////////////////////////////////////////////////// //
35 enum ReplacementChar = 0xFFFD;
36 //__gshared string chiFontName = "Arial:pixelsize=16";
37 //__gshared string chiFontName = "Verdana:pixelsize=16";
38 __gshared string chiFontName = "Verdana:weight=bold:pixelsize=16";
39 __gshared string chiFontFile;
40 __gshared int fontSize;
41 __gshared int fontHeight;
42 __gshared int fontBaselineOfs;
45 // ////////////////////////////////////////////////////////////////////////// //
46 __gshared ubyte* ttfontdata;
47 __gshared uint ttfontdatasize;
49 __gshared FT_Library ttflibrary;
50 __gshared FTC_Manager ttfcache;
51 __gshared FTC_CMapCache ttfcachecmap;
52 __gshared FTC_ImageCache ttfcacheimage;
55 shared static ~this () {
56 if (ttflibrary) {
57 if (ttfcache) {
58 FTC_Manager_Done(ttfcache);
59 ttfcache = null;
61 FT_Done_FreeType(ttflibrary);
62 ttflibrary = null;
67 enum FontID = cast(FTC_FaceID)1;
70 extern(C) nothrow {
71 void ttfFontFinalizer (void* obj) {
72 import core.stdc.stdlib : free;
73 if (obj is null) return;
74 auto tf = cast(iv.freetype.FT_Face)obj;
75 if (tf.generic.data !is ttfontdata) return;
76 if (ttfontdata !is null) {
77 version(aliced) conwriteln("TTF CACHE: freeing loaded font...");
78 free(ttfontdata);
79 ttfontdata = null;
80 ttfontdatasize = 0;
84 FT_Error ttfFontLoader (FTC_FaceID face_id, FT_Library library, FT_Pointer request_data, iv.freetype.FT_Face* aface) {
85 if (face_id == FontID) {
86 try {
87 if (ttfontdata is null) {
88 conwriteln("TTF CACHE: loading '", chiFontFile, "'...");
89 import core.stdc.stdlib : malloc;
90 auto fl = VFile(chiFontFile);
91 auto fsz = fl.size;
92 if (fsz < 16 || fsz > int.max/8) throw new Exception("invalid ttf size");
93 ttfontdatasize = cast(uint)fsz;
94 ttfontdata = cast(ubyte*)malloc(ttfontdatasize);
95 if (ttfontdata is null) { import core.exception : onOutOfMemoryErrorNoGC; onOutOfMemoryErrorNoGC(); }
96 fl.rawReadExact(ttfontdata[0..ttfontdatasize]);
98 auto res = FT_New_Memory_Face(library, cast(const(FT_Byte)*)ttfontdata, ttfontdatasize, 0, aface);
99 if (res != 0) throw new Exception("error loading ttf: '"~chiFontFile~"'");
100 (*aface).generic.data = ttfontdata;
101 (*aface).generic.finalizer = &ttfFontFinalizer;
102 } catch (Exception e) {
103 if (ttfontdata !is null) {
104 import core.stdc.stdlib : free;
105 free(ttfontdata);
106 ttfontdata = null;
107 ttfontdatasize = 0;
109 version(aliced) conwriteln("ERROR loading font: ", e.msg);
110 return FT_Err_Cannot_Open_Resource;
112 return FT_Err_Ok;
113 } else {
114 version(aliced) conwriteln("TTF CACHE: invalid font id");
116 return FT_Err_Cannot_Open_Resource;
121 void ttfLoad () nothrow {
122 if (FT_Init_FreeType(&ttflibrary)) assert(0, "can't initialize FreeType");
123 if (FTC_Manager_New(ttflibrary, 0, 0, 0, &ttfFontLoader, null, &ttfcache)) assert(0, "can't initialize FreeType cache manager");
124 if (FTC_CMapCache_New(ttfcache, &ttfcachecmap)) assert(0, "can't initialize FreeType cache manager");
125 if (FTC_ImageCache_New(ttfcache, &ttfcacheimage)) assert(0, "can't initialize FreeType cache manager");
127 FTC_ScalerRec fsc;
128 fsc.face_id = FontID;
129 fsc.width = 0;
130 fsc.height = fontSize;
131 fsc.pixel = 1; // size in pixels
133 FT_Size ttfontsz;
134 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) assert(0, "cannot find FreeType font");
135 fontHeight = cast(int)ttfontsz.metrics.height>>6; // 26.6
136 fontBaselineOfs = cast(int)((ttfontsz.metrics.height+ttfontsz.metrics.descender)>>6);
137 if (fontHeight < 2 || fontHeight > 128) assert(0, "invalid FreeType font metrics");
139 version(aliced) conwriteln("TTF CACHE initialized.");
143 void initFontEngine () nothrow {
144 if (ttflibrary is null) {
145 import std.string : fromStringz, toStringz;
146 if (!FcInit()) assert(0, "cannot init fontconfig");
147 iv.fontconfig.FcPattern* pat = FcNameParse(chiFontName.toStringz);
148 if (pat is null) assert(0, "cannot parse font name");
149 if (!FcConfigSubstitute(null, pat, FcMatchPattern)) assert(0, "cannot find fontconfig substitute");
150 FcDefaultSubstitute(pat);
151 // find the font
152 iv.fontconfig.FcResult result;
153 iv.fontconfig.FcPattern* font = FcFontMatch(null, pat, &result);
154 if (font !is null) {
155 char* file = null;
156 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
157 version(aliced) conwriteln("font file: [", file, "]");
158 chiFontFile = file.fromStringz.idup;
160 double pixelsize;
161 if (FcPatternGetDouble(font, FC_PIXEL_SIZE, 0, &pixelsize) == FcResultMatch) {
162 version(aliced) conwriteln("pixel size: ", pixelsize);
163 fontSize = cast(int)pixelsize;
166 FcPatternDestroy(pat);
167 // arbitrary limits
168 if (fontSize < 6) fontSize = 6;
169 if (fontSize > 42) fontSize = 42;
170 ttfLoad();
175 // ////////////////////////////////////////////////////////////////////////// //
176 public void utfByDChar (const(char)[] s, scope void delegate (dchar ch) nothrow @trusted dg) nothrow @trusted {
177 if (dg is null) return;
178 Utf8DecoderFast dc;
179 foreach (char ch; s) {
180 if (dc.decode(cast(ubyte)ch)) dg(dc.complete ? dc.codepoint : dc.replacement);
185 public void utfByDCharSPos (const(char)[] s, scope void delegate (dchar ch, usize stpos) nothrow @trusted dg) nothrow @trusted {
186 if (dg is null) return;
187 Utf8DecoderFast dc;
188 usize stpos = 0;
189 foreach (immutable idx, char ch; s) {
190 if (dc.decode(cast(ubyte)ch)) {
191 dg(dc.complete ? dc.codepoint : dc.replacement, stpos);
192 stpos = idx+1;
198 // ////////////////////////////////////////////////////////////////////////// //
199 void drawFTBitmap (int x, int y, in ref FT_Bitmap bitmap, uint clr) nothrow @trusted @nogc {
200 if (bitmap.pixel_mode != FT_PIXEL_MODE_MONO) return; // alas
201 if (bitmap.rows < 1 || bitmap.width < 1) return; // nothing to do
202 if (gxIsTransparent(clr)) return; // just in case
203 // prepare
204 bool topdown = true;
205 const(ubyte)* src = bitmap.buffer;
206 int spt = bitmap.pitch;
207 if (spt < 0) {
208 topdown = false;
209 spt = -spt;
210 src += spt*(bitmap.rows-1);
212 if (!gxIsSolid(clr)) {
213 // let this be slow
214 foreach (immutable int dy; 0..bitmap.rows) {
215 ubyte count = 0, b = 0;
216 auto ss = src;
217 foreach (immutable int dx; 0..bitmap.width) {
218 if (count-- == 0) { count = 7; b = *ss++; } else b <<= 1;
219 if (b&0x80) gxPutPixel(x+dx, y, clr);
221 ++y;
222 if (topdown) src += spt; else src -= spt;
224 return;
226 // check if we can use the fastest path
227 auto brc = GxRect(x, y, bitmap.width, bitmap.rows);
228 if (gxClipRect.contains(brc) && GxRect(0, 0, VBufWidth, VBufHeight).contains(brc)) {
229 // yay, the fastest one!
230 uint* dptr = vglTexBuf+y*VBufWidth+x;
231 foreach (immutable int dy; 0..bitmap.rows) {
232 ubyte count = 0, b = 0;
233 auto ss = src;
234 auto curptr = dptr;
235 foreach (immutable int dx; 0..bitmap.width) {
236 if (count-- == 0) { count = 7; b = *ss++; } else b <<= 1;
237 if (b&0x80) *curptr = clr;
238 ++curptr;
240 if (topdown) src += spt; else src -= spt;
241 dptr += VBufWidth;
243 } else {
244 // do it slow
245 foreach (immutable int dy; 0..bitmap.rows) {
246 ubyte count = 0, b = 0;
247 auto ss = src;
248 foreach (immutable int dx; 0..bitmap.width) {
249 if (count-- == 0) { count = 7; b = *ss++; } else b <<= 1;
250 if (b&0x80) gxSetPixel(x+dx, y, clr);
252 ++y;
253 if (topdown) src += spt; else src -= spt;
259 // y is baseline; returns advance
260 int ttfDrawGlyph (bool firstchar, int scale, int x, int y, int glyphidx, uint clr) nothrow @trusted {
261 enum mono = true;
262 if (glyphidx == 0) return 0;
264 FTC_ImageTypeRec fimg;
265 fimg.face_id = FontID;
266 fimg.width = 0;
267 fimg.height = fontSize*scale;
268 static if (mono) {
269 fimg.flags = FT_LOAD_TARGET_MONO|(gxIsTransparent(clr) ? 0 : FT_LOAD_MONOCHROME|FT_LOAD_RENDER);
270 } else {
271 fimg.flags = (gxIsTransparent(clr) ? 0 : FT_LOAD_RENDER);
274 FT_Glyph fg;
275 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyphidx, &fg, null)) return 0;
277 int advdec = 0;
278 if (!gxIsTransparent(clr)) {
279 if (fg.format != FT_GLYPH_FORMAT_BITMAP) return 0;
280 FT_BitmapGlyph fgi = cast(FT_BitmapGlyph)fg;
281 int x0 = x+fgi.left;
282 if (firstchar && fgi.bitmap.width > 0) { x0 -= fgi.left; advdec = fgi.left; }
283 drawFTBitmap(x0, y-fgi.top, fgi.bitmap, clr);
285 return cast(int)(fg.advance.x>>16)-advdec;
289 int ttfGetKerning (int scale, int gl0idx, int gl1idx) nothrow @trusted {
290 if (gl0idx == 0 || gl1idx == 0) return 0;
292 FTC_ScalerRec fsc;
293 fsc.face_id = FontID;
294 fsc.width = 0;
295 fsc.height = fontSize*scale;
296 fsc.pixel = 1; // size in pixels
298 FT_Size ttfontsz;
299 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) return 0;
300 if (!FT_HAS_KERNING(ttfontsz.face)) return 0;
302 FT_Vector kk;
303 if (FT_Get_Kerning(ttfontsz.face, gl0idx, gl1idx, FT_KERNING_UNSCALED, &kk)) return 0;
304 if (!kk.x) return 0;
305 auto kadvfrac = FT_MulFix(kk.x, ttfontsz.metrics.x_scale); // 1/64 of pixel
306 return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
310 // ////////////////////////////////////////////////////////////////////////// //
311 public int gxCharWidthScaled (int scale, dchar ch) nothrow @trusted {
312 if (scale < 1) return 0;
314 initFontEngine();
316 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
317 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ReplacementChar);
318 if (glyph == 0) return 0;
320 FTC_ImageTypeRec fimg;
321 fimg.face_id = FontID;
322 fimg.width = 0;
323 fimg.height = fontSize*scale;
324 fimg.flags = FT_LOAD_TARGET_MONO;
326 FT_Glyph fg;
327 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null)) return -666;
329 int res = cast(int)fg.advance.x>>16;
330 return (res > 0 ? res : 0);
333 public int gxCharWidth (dchar ch) nothrow @trusted { return gxCharWidthScaled(1, ch); }
335 // return char width
336 public int gxDrawChar (int x, int y, dchar ch, uint fg, int prevcp=-1) nothrow @trusted { return gxDrawCharScaled(1, x, y, ch, fg, prevcp); }
337 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); }
340 // ////////////////////////////////////////////////////////////////////////// //
341 // return char width
342 public int gxDrawCharScaled (int scale, int x, int y, dchar ch, uint clr, int prevcp=-1) nothrow @trusted {
343 if (scale < 1) return 0;
345 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
346 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ReplacementChar);
347 if (glyph == 0) return 0;
349 int kadv = ttfGetKerning(scale, (prevcp >= 0 ? FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, prevcp) : 0), glyph);
350 return ttfDrawGlyph(false, scale, x+kadv, y+fontBaselineOfs*scale, glyph, clr);
353 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); }
356 // ////////////////////////////////////////////////////////////////////////// //
357 public struct GxKerning {
358 int prevgidx = 0;
359 int wdt = 0;
360 int lastadv = 0;
361 int lastcw = 0;
362 int tabsize = 0;
363 int scale = 1;
364 bool firstchar = true;
366 nothrow @trusted:
367 this (int atabsize, int ascale=1, bool firstCharIsFull=false) {
368 initFontEngine();
369 firstchar = !firstCharIsFull;
370 scale = ascale;
371 if ((tabsize = (atabsize > 0 ? atabsize : 0)) != 0) tabsize = tabsize*gxCharWidthScaled(ascale, ' ');
374 void reset (int atabsize) {
375 initFontEngine();
376 prevgidx = 0;
377 wdt = 0;
378 lastadv = 0;
379 lastcw = 0;
380 if ((tabsize = (atabsize > 0 ? atabsize : 0)) != 0) tabsize = tabsize*gxCharWidthScaled(scale, ' ');
381 firstchar = true;
384 // tab length for current position
385 int tablength () { pragma(inline, true); return (tabsize > 0 ? (wdt/tabsize+1)*tabsize-wdt : 0); }
387 int fixWidthPre (dchar ch) {
388 immutable int prevgl = prevgidx;
389 wdt += lastadv;
390 lastadv = 0;
391 lastcw = 0;
392 prevgidx = 0;
393 if (ch == '\t' && tabsize > 0) {
394 // tab
395 lastadv = lastcw = tablength;
396 firstchar = false;
397 } else {
398 initFontEngine();
399 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
400 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ReplacementChar);
401 if (glyph != 0) {
402 wdt += ttfGetKerning(scale, prevgl, glyph);
404 FTC_ImageTypeRec fimg;
405 fimg.face_id = FontID;
406 fimg.width = 0;
407 fimg.height = fontSize*scale;
408 version(none) {
409 fimg.flags = FT_LOAD_TARGET_MONO;
410 } else {
411 fimg.flags = FT_LOAD_TARGET_MONO|FT_LOAD_MONOCHROME|FT_LOAD_RENDER;
414 FT_Glyph fg;
415 version(none) {
416 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null) == 0) {
417 prevgidx = glyph;
418 lastadv = fg.advance.x>>16;
420 } else {
421 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null) == 0) {
422 int advdec = 0;
423 if (fg.format == FT_GLYPH_FORMAT_BITMAP) {
424 FT_BitmapGlyph fgi = cast(FT_BitmapGlyph)fg;
425 if (firstchar && fgi.bitmap.width > 0) {
426 lastcw = fgi.bitmap.width;
427 advdec = fgi.left;
428 if (lastcw < 1) { advdec = 0; lastcw = cast(int)fg.advance.x>>16; }
429 } else {
430 lastcw = fgi.left+fgi.bitmap.width;
431 if (lastcw < 1) lastcw = cast(int)fg.advance.x>>16;
434 prevgidx = glyph;
435 lastadv = (cast(int)fg.advance.x>>16)-advdec;
436 firstchar = false;
441 return wdt;
444 @property int finalWidth () const { pragma(inline, true); return wdt+/*lastadv*/lastcw; }
446 // BUGGY!
447 @property int nextCharOfs () const { pragma(inline, true); return wdt+lastadv; }
449 @property int currOfs () const { pragma(inline, true); return wdt; }
451 @property int nextOfsNoSpacing () const { pragma(inline, true); return wdt+lastcw; }
455 // ////////////////////////////////////////////////////////////////////////// //
456 public struct GxDrawTextOptions {
457 int tabsize = 0;
458 uint clr = gxTransparent;
459 int scale = 1;
460 bool firstCharIsFull = false;
462 static pure nothrow @safe @nogc:
463 auto Color (uint aclr) { pragma(inline, true); return GxDrawTextOptions(0, aclr, 1, false); }
464 auto Tab (int atabsize) { pragma(inline, true); return GxDrawTextOptions(atabsize, gxTransparent, 1, false); }
465 auto TabColor (int atabsize, uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, 1, false); }
466 auto TabColorFirstFull (int atabsize, uint aclr, bool fcf) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, 1, fcf); }
467 auto ScaleTabColor (int ascale, int atabsize, uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, ascale, false); }
468 auto ColorNFC (uint aclr) { pragma(inline, true); return GxDrawTextOptions(0, aclr, 1, true); }
469 auto TabColorNFC (int atabsize, uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, 1, true); }
470 auto ScaleTabColorNFC (int ascale, int atabsize, uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, ascale, true); }
471 // more ctors?
474 public struct GxDrawTextState {
475 usize spos; // current codepoint starting position
476 usize epos; // current codepoint ending position (exclusive; i.e. *after* codepoint)
477 int curx; // current x (before drawing the glyph)
481 // delegate should return color
482 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
483 if (Imp!"std.range.primitives".isInputRange!R && is(Imp!"std.range.primitives".ElementEncodingType!R == char))
485 // rely on the assumption that font face won't be unloaded while we are in this function
486 if (opt.scale < 1) return 0;
488 initFontEngine();
490 GxDrawTextState state;
492 y += fontBaselineOfs*opt.scale;
494 immutable int tabpix = (opt.tabsize > 0 ? opt.tabsize*gxCharWidthScaled(opt.scale, ' ') : 0);
496 FT_Size ttfontsz;
498 int prevglyph = 0;
499 immutable int stx = x;
501 bool dokern = true;
502 bool firstchar = !opt.firstCharIsFull;
503 Utf8DecoderFast dc;
505 while (!srng.empty) {
506 immutable ubyte srbyte = cast(ubyte)srng.front;
507 srng.popFront();
508 ++state.epos;
510 if (dc.decode(srbyte)) {
511 int ch = (dc.complete ? dc.codepoint : dc.replacement);
512 int pgl = prevglyph;
513 prevglyph = 0;
514 state.curx = x;
516 if (opt.tabsize > 0) {
517 if (ch == '\t') {
518 firstchar = false;
519 int wdt = x-stx;
520 state.curx = x;
521 x += (wdt/tabpix+1)*tabpix-wdt;
522 if (clrdg !is null) clrdg(state);
523 state.spos = state.epos;
524 continue;
528 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
529 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ReplacementChar);
530 if (glyph != 0) {
531 // kerning
532 int kadv = 0;
533 if (pgl != 0 && dokern) {
534 if (ttfontsz is null) {
535 FTC_ScalerRec fsc;
536 fsc.face_id = FontID;
537 fsc.width = 0;
538 fsc.height = fontSize*opt.scale;
539 fsc.pixel = 1; // size in pixels
540 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) {
541 dokern = false;
542 ttfontsz = null;
543 } else {
544 dokern = (FT_HAS_KERNING(ttfontsz.face) != 0);
547 if (dokern) {
548 FT_Vector kk;
549 if (FT_Get_Kerning(ttfontsz.face, pgl, glyph, FT_KERNING_UNSCALED, &kk) == 0) {
550 if (kk.x) {
551 auto kadvfrac = FT_MulFix(kk.x, ttfontsz.metrics.x_scale); // 1/64 of pixel
552 kadv = cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
557 x += kadv;
558 uint clr = opt.clr;
559 if (clrdg !is null) { state.curx = x; clr = clrdg(state); }
560 x += ttfDrawGlyph(firstchar, opt.scale, x, y, glyph, clr);
561 firstchar = false;
563 state.spos = state.epos;
564 prevglyph = glyph;
568 return x-stx;
572 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 {
573 static struct StrIterator {
574 private:
575 const(char)[] str;
576 public pure nothrow @trusted @nogc:
577 this (const(char)[] s) { pragma(inline, true); str = s; }
578 @property bool empty () const { pragma(inline, true); return (str.length == 0); }
579 @property char front () const { pragma(inline, true); return (str.length ? str.ptr[0] : 0); }
580 void popFront () { if (str.length) str = str[1..$]; }
583 return gxDrawTextUtf(opt, x, y, StrIterator(s), clrdg);
587 public int gxTextWidthScaledUtf (int scale, const(char)[] s, int tabsize=0, bool firstCharIsFull=false) nothrow @trusted {
588 if (scale < 1) return 0;
589 auto kern = GxKerning(tabsize, scale, firstCharIsFull);
590 s.utfByDChar(delegate (dchar ch) @trusted { kern.fixWidthPre(ch); });
591 return kern.finalWidth;
596 // ////////////////////////////////////////////////////////////////////////// //
597 public int gxTextHeightUtf () nothrow @trusted { initFontEngine(); return fontHeight; }
598 public int gxTextHeightScaledUtf (int scale) nothrow @trusted { initFontEngine(); return (scale < 1 ? 0 : fontHeight*scale); }
600 public int gxTextWidthUtf (const(char)[] s, int tabsize=0, bool firstCharIsFull=false) nothrow @trusted { return gxTextWidthScaledUtf(1, s, tabsize, firstCharIsFull); }
602 public int gxDrawTextUtf() (int x, int y, const(char)[] s, uint clr) nothrow @trusted { return gxDrawTextUtf(GxDrawTextOptions.Color(clr), x, y, s); }
603 public int gxDrawTextUtf() (in auto ref GxPoint p, const(char)[] s, uint clr) nothrow @trusted { return gxDrawTextUtf(p.x, p.y, s, clr); }
606 public int gxDrawTextOutScaledUtf (int scale, int x, int y, const(char)[] s, uint clr, uint clrout) nothrow @trusted {
607 if (scale < 1) return 0;
608 auto opt = GxDrawTextOptions.ScaleTabColor(scale, 0, clrout);
609 foreach (immutable dy; -1*scale..2*scale) {
610 foreach (immutable dx; -1*scale..2*scale) {
611 if (dx || dy) gxDrawTextUtf(opt, x+dx, y+dy, s);
614 opt.clr = clr;
615 return gxDrawTextUtf(opt, x, y, s);