erga: use `widgetChanged()` instead of direct screen rebuild posting; fixes to editor...
[iv.d.git] / egra / gfx / text.d
blob855d81a593796148a9e28f3728a15056fceb4b32
1 /*
2 * Simple Framebuffer Gfx/GUI lib
4 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
5 * Understanding is not required. Only obedience.
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, version 3 of the License ONLY.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 module iv.egra.gfx.text /*is aliced*/;
20 private:
22 import iv.alice;
23 import iv.bclamp;
24 import iv.cmdcon;
25 import iv.dynstring;
26 import iv.fontconfig;
27 import iv.freetype;
28 import iv.utfutil;
29 import iv.vfs;
31 import iv.egra.gfx.config;
32 import iv.egra.gfx.base;
34 version (DigitalMars) {
35 version(X86) {
36 version = EGRA_GFX_TEXT_ASM_ALLOWED;
41 // ////////////////////////////////////////////////////////////////////////// //
42 enum ReplacementChar = 0xFFFD;
43 //__gshared string egraFontName = "Arial:pixelsize=16";
44 //__gshared string egraFontName = "Verdana:pixelsize=16";
45 public __gshared string egraFontName = "Verdana:weight=bold:pixelsize=16";
46 public __gshared int egraDefaultFontSize = 0;
47 __gshared string egraFontFile;
48 __gshared int fontSize;
49 __gshared int fontHeight;
50 __gshared int fontBaselineOfs;
53 // ////////////////////////////////////////////////////////////////////////// //
54 __gshared ubyte* ttfontdata;
55 __gshared uint ttfontdatasize;
57 __gshared FT_Library ttflibrary;
58 __gshared FTC_Manager ttfcache;
59 __gshared FTC_CMapCache ttfcachecmap;
60 __gshared FTC_ImageCache ttfcacheimage;
63 shared static ~this () {
64 if (ttflibrary) {
65 if (ttfcache) {
66 FTC_Manager_Done(ttfcache);
67 ttfcache = null;
69 FT_Done_FreeType(ttflibrary);
70 ttflibrary = null;
75 enum FontID = cast(FTC_FaceID)1;
78 extern(C) nothrow {
79 void ttfFontFinalizer (void* obj) {
80 import core.stdc.stdlib : free;
81 if (obj is null) return;
82 auto tf = cast(iv.freetype.FT_Face)obj;
83 if (tf.generic.data !is ttfontdata) return;
84 if (ttfontdata !is null) {
85 version(aliced) conwriteln("TTF CACHE: freeing loaded font...");
86 free(ttfontdata);
87 ttfontdata = null;
88 ttfontdatasize = 0;
92 FT_Error ttfFontLoader (FTC_FaceID face_id, FT_Library library, FT_Pointer request_data, iv.freetype.FT_Face* aface) {
93 if (face_id == FontID) {
94 try {
95 if (ttfontdata is null) {
96 conwriteln("TTF CACHE: loading '", egraFontFile, "'...");
97 import core.stdc.stdlib : malloc;
98 auto fl = VFile(egraFontFile);
99 auto fsz = fl.size;
100 if (fsz < 16 || fsz > int.max/8) throw new Exception("invalid ttf size");
101 ttfontdatasize = cast(uint)fsz;
102 ttfontdata = cast(ubyte*)malloc(ttfontdatasize);
103 if (ttfontdata is null) { import core.exception : onOutOfMemoryErrorNoGC; onOutOfMemoryErrorNoGC(); }
104 fl.rawReadExact(ttfontdata[0..ttfontdatasize]);
106 auto res = FT_New_Memory_Face(library, cast(const(FT_Byte)*)ttfontdata, ttfontdatasize, 0, aface);
107 if (res != 0) throw new Exception("error loading ttf: '"~egraFontFile~"'");
108 (*aface).generic.data = ttfontdata;
109 (*aface).generic.finalizer = &ttfFontFinalizer;
110 } catch (Exception e) {
111 if (ttfontdata !is null) {
112 import core.stdc.stdlib : free;
113 free(ttfontdata);
114 ttfontdata = null;
115 ttfontdatasize = 0;
117 version(aliced) conwriteln("ERROR loading font: ", e.msg);
118 return FT_Err_Cannot_Open_Resource;
120 return FT_Err_Ok;
121 } else {
122 version(aliced) conwriteln("TTF CACHE: invalid font id");
124 return FT_Err_Cannot_Open_Resource;
129 void ttfLoad () nothrow {
130 if (FT_Init_FreeType(&ttflibrary)) assert(0, "can't initialize FreeType");
131 if (FTC_Manager_New(ttflibrary, 0, 0, 0, &ttfFontLoader, null, &ttfcache)) assert(0, "can't initialize FreeType cache manager");
132 if (FTC_CMapCache_New(ttfcache, &ttfcachecmap)) assert(0, "can't initialize FreeType cache manager");
133 if (FTC_ImageCache_New(ttfcache, &ttfcacheimage)) assert(0, "can't initialize FreeType cache manager");
135 FTC_ScalerRec fsc;
136 fsc.face_id = FontID;
137 fsc.width = 0;
138 fsc.height = fontSize;
139 fsc.pixel = 1; // size in pixels
141 FT_Size ttfontsz;
142 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) assert(0, "cannot find FreeType font");
143 fontHeight = cast(int)ttfontsz.metrics.height>>6; // 26.6
144 fontBaselineOfs = cast(int)((ttfontsz.metrics.height+ttfontsz.metrics.descender)>>6);
145 if (fontHeight < 2 || fontHeight > 128) assert(0, "invalid FreeType font metrics");
147 version(aliced) conwriteln("TTF CACHE initialized.");
151 void initFontEngine () nothrow {
152 if (ttflibrary is null) {
153 import std.string : fromStringz/*, toStringz*/;
154 import std.internal.cstring : tempCString;
155 if (!FcInit()) assert(0, "cannot init fontconfig");
156 iv.fontconfig.FcPattern* pat = FcNameParse(egraFontName.tempCString);
157 if (pat is null) assert(0, "cannot parse font name");
158 if (!FcConfigSubstitute(null, pat, FcMatchPattern)) assert(0, "cannot find fontconfig substitute");
159 FcDefaultSubstitute(pat);
160 // find the font
161 iv.fontconfig.FcResult result;
162 iv.fontconfig.FcPattern* font = FcFontMatch(null, pat, &result);
163 if (font !is null) {
164 char* file = null;
165 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
166 version(aliced) conwriteln("font file: [", file, "]");
167 egraFontFile = file.fromStringz.idup;
169 double pixelsize;
170 if (FcPatternGetDouble(font, FC_PIXEL_SIZE, 0, &pixelsize) == FcResultMatch) {
171 version(aliced) conwriteln("pixel size: ", pixelsize);
172 fontSize = cast(int)pixelsize;
175 FcPatternDestroy(pat);
176 // arbitrary limits
177 if (fontSize < 6) fontSize = 6;
178 if (fontSize > 42) fontSize = 42;
179 if (egraDefaultFontSize > 0) fontSize = egraDefaultFontSize;
180 ttfLoad();
185 // ////////////////////////////////////////////////////////////////////////// //
186 public void utfByDChar (const(char)[] s, scope void delegate (dchar ch) nothrow @safe dg) nothrow @safe {
187 if (dg is null || s.length == 0) return;
188 Utf8DecoderFast dc;
189 foreach (char ch; s) {
190 if (dc.decode(cast(ubyte)ch)) dg(dc.complete ? dc.codepoint : dc.replacement);
195 public void utfByDCharSPos (const(char)[] s, scope void delegate (dchar ch, usize stpos) nothrow @safe dg) nothrow @safe {
196 if (dg is null || s.length == 0) return;
197 Utf8DecoderFast dc;
198 usize stpos = 0;
199 foreach (immutable idx, char ch; s) {
200 if (dc.decode(cast(ubyte)ch)) {
201 dg(dc.complete ? dc.codepoint : dc.replacement, stpos);
202 stpos = idx+1;
208 // ////////////////////////////////////////////////////////////////////////// //
209 private void drawMonoBMP (int x, int y, int wdt, int hgt, const(ubyte)* bmp, in usize bpitch, in uint clr) nothrow @trusted @nogc {
210 immutable int origWdt = wdt;
212 int leftSkip, topSkip;
213 if (!gxClipRect.clipHVStripes(ref x, ref y, ref wdt, ref hgt, &leftSkip, &topSkip)) return;
214 if (!GxRect(0, 0, VBufWidth, VBufHeight).clipHVStripes(ref x, ref y, ref wdt, ref hgt, &leftSkip, &topSkip)) return;
216 // skip unused top part
217 if (topSkip) bmp += bpitch*cast(uint)topSkip;
218 // skip unused bytes
219 bmp += cast(usize)(leftSkip>>3);
220 leftSkip &= 0x07;
222 if (gxIsSolid(clr) && leftSkip == 0) {
223 // yay, the fastest one!
224 enum RenderBitMixin = `if (b&0x80) *curptr = clr; ++curptr; b <<= 1;`;
225 uint* dptr = vglTexBuf+y*VBufWidth+x;
226 while (hgt--) {
227 const(ubyte)* ss = bmp;
228 int left = wdt;
229 uint* curptr = dptr;
230 while (left >= 8) {
231 ubyte b = *ss++;
232 if (b == 0xff) {
233 curptr[0..8] = clr;
234 curptr += 8;
235 } else if (b) {
236 mixin(RenderBitMixin);
237 mixin(RenderBitMixin);
238 mixin(RenderBitMixin);
239 mixin(RenderBitMixin);
240 mixin(RenderBitMixin);
241 mixin(RenderBitMixin);
242 mixin(RenderBitMixin);
243 mixin(RenderBitMixin);
244 } else {
245 curptr += 8;
247 left -= 8;
249 //assert(left >= 0 && left < 8);
250 if (left) {
251 ubyte b = *ss;
252 if (b) {
253 final switch (left) {
254 case 7: mixin(RenderBitMixin); goto case;
255 case 6: mixin(RenderBitMixin); goto case;
256 case 5: mixin(RenderBitMixin); goto case;
257 case 4: mixin(RenderBitMixin); goto case;
258 case 3: mixin(RenderBitMixin); goto case;
259 case 2: mixin(RenderBitMixin); goto case;
260 case 1: if (b&0x80) *curptr = clr; break;
261 //case 0: break;
264 //while (left--) { mixin(RenderBitMixin); }
266 bmp += bpitch;
267 dptr += cast(usize)VBufWidth;
269 return;
272 if (gxIsSolid(clr)) {
273 // yay, the fast one!
274 enum RenderBitMixin = `if (b&0x80) *curptr = clr; ++curptr; b <<= 1;`;
275 uint* dptr = vglTexBuf+y*VBufWidth+x;
276 int ldraw = 8-leftSkip;
277 if (ldraw > wdt) ldraw = wdt;
278 while (hgt--) {
279 const(ubyte)* ss = bmp;
280 uint* curptr = dptr;
282 ubyte b = cast(ubyte)((*ss++)<<cast(ubyte)(leftSkip&0x07u));
283 foreach (; 0..ldraw) { mixin(RenderBitMixin); }
285 int left = wdt-ldraw;
286 while (left >= 8) {
287 ubyte b = *ss++;
288 if (b == 0xff) {
289 curptr[0..8] = clr;
290 curptr += 8;
291 } else if (b) {
292 mixin(RenderBitMixin);
293 mixin(RenderBitMixin);
294 mixin(RenderBitMixin);
295 mixin(RenderBitMixin);
296 mixin(RenderBitMixin);
297 mixin(RenderBitMixin);
298 mixin(RenderBitMixin);
299 mixin(RenderBitMixin);
300 } else {
301 curptr += 8;
303 left -= 8;
305 //assert(left >= 0 && left < 8);
306 if (left) {
307 ubyte b = *ss;
308 if (b) {
309 final switch (left) {
310 case 7: mixin(RenderBitMixin); goto case;
311 case 6: mixin(RenderBitMixin); goto case;
312 case 5: mixin(RenderBitMixin); goto case;
313 case 4: mixin(RenderBitMixin); goto case;
314 case 3: mixin(RenderBitMixin); goto case;
315 case 2: mixin(RenderBitMixin); goto case;
316 case 1: if (b&0x80) *curptr = clr; break;
317 //case 0: break;
320 //while (left--) { mixin(RenderBitMixin); }
322 bmp += bpitch;
323 dptr += cast(usize)VBufWidth;
325 return;
328 // the slowest path
329 x -= leftSkip;
330 wdt += leftSkip;
331 while (hgt--) {
332 const(ubyte)* ss = bmp;
333 ubyte count = 1, b = void;
334 int cx = x;
335 int left = wdt;
336 while (left > 0) {
337 if (--count == 0) {
338 b = *ss++;
339 if (!b || b == 0xff) {
340 if (b) gxHLine(cx, y, (left >= 8 ? 8 : left), clr);
341 cx += 8;
342 left -= 8;
343 count = 1;
344 continue;
346 count = 8;
347 } else {
348 b <<= 1;
350 if (b&0x80) gxPutPixel(cx, y, clr);
351 ++cx;
352 --left;
354 ++y;
355 bmp += bpitch;
360 // ////////////////////////////////////////////////////////////////////////// //
361 void drawFTBitmap (int x, int y, in ref FT_Bitmap bitmap, in uint clr) nothrow @trusted @nogc {
362 if (bitmap.pixel_mode != FT_PIXEL_MODE_MONO) return; // alas
363 if (bitmap.rows < 1 || bitmap.width < 1) return; // nothing to do
364 if (gxIsTransparent(clr)) return; // just in case
365 // prepare
366 const(ubyte)* src = bitmap.buffer;
367 usize bpitch = void;
368 if (bitmap.pitch >= 0) {
369 bpitch = cast(usize)bitmap.pitch;
370 } else {
371 bpitch = cast(usize)0-cast(usize)(-bitmap.pitch);
372 src += bpitch*(cast(uint)bitmap.rows-1u);
374 drawMonoBMP(x, y, bitmap.width, bitmap.rows, src, bpitch, clr);
378 // y is baseline; returns advance
379 int ttfDrawGlyph (bool firstchar, int scale, int x, int y, int glyphidx, uint clr) nothrow @trusted @nogc {
380 enum mono = true;
381 if (glyphidx == 0) return 0;
383 FTC_ImageTypeRec fimg;
384 fimg.face_id = FontID;
385 fimg.width = 0;
386 fimg.height = fontSize*scale;
387 static if (mono) {
388 fimg.flags = FT_LOAD_TARGET_MONO|(gxIsTransparent(clr) ? 0 : FT_LOAD_MONOCHROME|FT_LOAD_RENDER);
389 } else {
390 fimg.flags = (gxIsTransparent(clr) ? 0 : FT_LOAD_RENDER);
393 FT_Glyph fg;
394 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyphidx, &fg, null)) return 0;
396 int advdec = 0;
397 if (!gxIsTransparent(clr)) {
398 if (fg.format != FT_GLYPH_FORMAT_BITMAP) return 0;
399 FT_BitmapGlyph fgi = cast(FT_BitmapGlyph)fg;
400 int x0 = x+fgi.left;
401 if (firstchar && fgi.bitmap.width > 0) { x0 -= fgi.left; advdec = fgi.left; }
402 drawFTBitmap(x0, y-fgi.top, fgi.bitmap, clr);
404 return cast(int)(fg.advance.x>>16)-advdec;
408 int ttfGetKerning (int scale, int gl0idx, int gl1idx) nothrow @trusted @nogc {
409 if (gl0idx == 0 || gl1idx == 0) return 0;
411 FTC_ScalerRec fsc;
412 fsc.face_id = FontID;
413 fsc.width = 0;
414 fsc.height = fontSize*scale;
415 fsc.pixel = 1; // size in pixels
417 FT_Size ttfontsz;
418 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) return 0;
419 if (!FT_HAS_KERNING(ttfontsz.face)) return 0;
421 FT_Vector kk;
422 if (FT_Get_Kerning(ttfontsz.face, gl0idx, gl1idx, FT_KERNING_UNSCALED, &kk)) return 0;
423 if (!kk.x) return 0;
424 auto kadvfrac = FT_MulFix(kk.x, ttfontsz.metrics.x_scale); // 1/64 of pixel
425 return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
429 // ////////////////////////////////////////////////////////////////////////// //
430 public int gxCharWidthScaled (int scale, in dchar ch) nothrow @trusted {
431 if (scale < 1) return 0;
433 if (ttflibrary is null) initFontEngine();
435 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
436 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ReplacementChar);
437 if (glyph == 0) return 0;
439 FTC_ImageTypeRec fimg;
440 fimg.face_id = FontID;
441 fimg.width = 0;
442 fimg.height = fontSize*scale;
443 fimg.flags = FT_LOAD_TARGET_MONO;
445 FT_Glyph fg;
446 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null)) return -666;
448 immutable int res = cast(int)fg.advance.x>>16;
449 return (res > 0 ? res : 0);
452 public int gxCharWidth (dchar ch) nothrow @trusted { pragma(inline, true); return gxCharWidthScaled(1, ch); }
454 // return char width
455 public int gxDrawChar (int x, int y, dchar ch, uint fg, int prevcp=-1) nothrow @trusted { pragma(inline, true); return gxDrawCharScaled(1, x, y, ch, fg, prevcp); }
456 public int gxDrawChar() (in auto ref GxPoint p, char ch, uint fg, int prevcp=-1) nothrow @trusted { pragma(inline, true); return gxDrawCharScaled(1, p.x, p.y, ch, fg, prevcp); }
459 // ////////////////////////////////////////////////////////////////////////// //
460 // return char width
461 public int gxDrawCharScaled (int scale, int x, int y, dchar ch, uint clr, int prevcp=-1) nothrow @trusted {
462 if (scale < 1) return 0;
464 if (ttflibrary is null) initFontEngine();
466 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
467 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ReplacementChar);
468 if (glyph == 0) return 0;
470 int kadv = ttfGetKerning(scale, (prevcp >= 0 ? FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, prevcp) : 0), glyph);
471 return ttfDrawGlyph(false, scale, x+kadv, y+fontBaselineOfs*scale, glyph, clr);
474 public int gxDrawCharScaled() (int scale, in auto ref GxPoint p, char ch, uint fg, int prevcp=-1) nothrow @trusted { pragma(inline, true); return gxDrawCharScaled(scale, p.x, p.y, ch, fg, prevcp); }
477 // ////////////////////////////////////////////////////////////////////////// //
478 public struct GxKerning {
479 int prevgidx = 0;
480 int wdt = 0;
481 int lastadv = 0;
482 int lastcw = 0;
483 int tabsize = 0;
484 int scale = 1;
485 bool firstchar = true;
487 nothrow @trusted:
488 this (int atabsize, int ascale=1, bool firstCharIsFull=false) {
489 if (ttflibrary is null) initFontEngine();
490 firstchar = !firstCharIsFull;
491 scale = ascale;
492 if ((tabsize = (atabsize > 0 ? atabsize : 0)) != 0) tabsize = tabsize*gxCharWidthScaled(ascale, ' ');
495 void reset (int atabsize) {
496 if (ttflibrary is null) initFontEngine();
497 prevgidx = 0;
498 wdt = 0;
499 lastadv = 0;
500 lastcw = 0;
501 if ((tabsize = (atabsize > 0 ? atabsize : 0)) != 0) tabsize = tabsize*gxCharWidthScaled(scale, ' ');
502 firstchar = true;
505 // tab length for current position
506 int tablength () pure const @nogc { pragma(inline, true); return (tabsize > 0 ? (wdt/tabsize+1)*tabsize-wdt : 0); }
508 int fixWidthPre (dchar ch) {
509 immutable int prevgl = prevgidx;
510 wdt += lastadv;
511 lastadv = 0;
512 lastcw = 0;
513 prevgidx = 0;
514 if (ch == '\t' && tabsize > 0) {
515 // tab
516 lastadv = lastcw = tablength;
517 firstchar = false;
518 } else {
519 if (ttflibrary is null) initFontEngine();
520 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
521 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ReplacementChar);
522 if (glyph != 0) {
523 wdt += ttfGetKerning(scale, prevgl, glyph);
525 FTC_ImageTypeRec fimg;
526 fimg.face_id = FontID;
527 fimg.width = 0;
528 fimg.height = fontSize*scale;
529 version(none) {
530 fimg.flags = FT_LOAD_TARGET_MONO;
531 } else {
532 fimg.flags = FT_LOAD_TARGET_MONO|FT_LOAD_MONOCHROME|FT_LOAD_RENDER;
535 FT_Glyph fg;
536 version(none) {
537 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null) == 0) {
538 prevgidx = glyph;
539 lastadv = fg.advance.x>>16;
541 } else {
542 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null) == 0) {
543 int advdec = 0;
544 if (fg.format == FT_GLYPH_FORMAT_BITMAP) {
545 FT_BitmapGlyph fgi = cast(FT_BitmapGlyph)fg;
546 if (firstchar && fgi.bitmap.width > 0) {
547 lastcw = fgi.bitmap.width;
548 advdec = fgi.left;
549 if (lastcw < 1) { advdec = 0; lastcw = cast(int)fg.advance.x>>16; }
550 } else {
551 lastcw = fgi.left+fgi.bitmap.width;
552 if (lastcw < 1) lastcw = cast(int)fg.advance.x>>16;
555 prevgidx = glyph;
556 lastadv = (cast(int)fg.advance.x>>16)-advdec;
557 firstchar = false;
562 return wdt;
565 @property int finalWidth () pure const @nogc { pragma(inline, true); return wdt+/*lastadv*/lastcw; }
567 // BUGGY!
568 @property int nextCharOfs () pure const @nogc { pragma(inline, true); return wdt+lastadv; }
570 @property int currOfs () pure const @nogc { pragma(inline, true); return wdt; }
572 @property int nextOfsNoSpacing () pure const @nogc { pragma(inline, true); return wdt+lastcw; }
576 // ////////////////////////////////////////////////////////////////////////// //
577 public struct GxDrawTextOptions {
578 int tabsize = 0;
579 uint clr = gxTransparent;
580 int scale = 1;
581 bool firstCharIsFull = false;
583 static nothrow @safe @nogc:
584 auto Color (in uint aclr) { pragma(inline, true); return GxDrawTextOptions(0, aclr, 1, false); }
585 auto Tab (in int atabsize) { pragma(inline, true); return GxDrawTextOptions(atabsize, gxTransparent, 1, false); }
586 auto TabColor (in int atabsize, in uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, 1, false); }
587 auto TabColorFirstFull (in int atabsize, in uint aclr, in bool fcf) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, 1, fcf); }
588 auto ScaleTabColor (in int ascale, in int atabsize, in uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, ascale, false); }
589 auto ColorNFC (in uint aclr) { pragma(inline, true); return GxDrawTextOptions(0, aclr, 1, true); }
590 auto TabColorNFC (in int atabsize, in uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, 1, true); }
591 auto ScaleTabColorNFC (in int ascale, in int atabsize, in uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, ascale, true); }
592 // more ctors?
595 public struct GxDrawTextState {
596 usize spos; // current codepoint starting position
597 usize epos; // current codepoint ending position (exclusive; i.e. *after* codepoint)
598 int curx; // current x (before drawing the glyph)
602 // delegate should return color
603 public int gxDrawTextUtf(R) (in auto ref GxDrawTextOptions opt, int x, int y, auto ref R srng, uint delegate (in ref GxDrawTextState state) nothrow @safe clrdg=null) nothrow @trusted
604 if (Imp!"std.range.primitives".isInputRange!R && is(Imp!"std.range.primitives".ElementEncodingType!R == char))
606 // rely on the assumption that font face won't be unloaded while we are in this function
607 if (opt.scale < 1) return 0;
609 if (ttflibrary is null) initFontEngine();
611 GxDrawTextState state;
613 y += fontBaselineOfs*opt.scale;
615 immutable int tabpix = (opt.tabsize > 0 ? opt.tabsize*gxCharWidthScaled(opt.scale, ' ') : 0);
617 FT_Size ttfontsz;
619 int prevglyph = 0;
620 immutable int stx = x;
622 bool dokern = true;
623 bool firstchar = !opt.firstCharIsFull;
624 Utf8DecoderFast dc;
626 while (!srng.empty) {
627 immutable ubyte srbyte = cast(ubyte)srng.front;
628 srng.popFront();
629 ++state.epos;
631 if (dc.decode(srbyte)) {
632 int ch = (dc.complete ? dc.codepoint : dc.replacement);
633 int pgl = prevglyph;
634 prevglyph = 0;
635 state.curx = x;
637 if (opt.tabsize > 0) {
638 if (ch == '\t') {
639 firstchar = false;
640 int wdt = x-stx;
641 state.curx = x;
642 x += (wdt/tabpix+1)*tabpix-wdt;
643 if (clrdg !is null) clrdg(state);
644 state.spos = state.epos;
645 continue;
649 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
650 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ReplacementChar);
651 if (glyph != 0) {
652 // kerning
653 int kadv = 0;
654 if (pgl != 0 && dokern) {
655 if (ttfontsz is null) {
656 FTC_ScalerRec fsc;
657 fsc.face_id = FontID;
658 fsc.width = 0;
659 fsc.height = fontSize*opt.scale;
660 fsc.pixel = 1; // size in pixels
661 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) {
662 dokern = false;
663 ttfontsz = null;
664 } else {
665 dokern = (FT_HAS_KERNING(ttfontsz.face) != 0);
668 if (dokern) {
669 FT_Vector kk;
670 if (FT_Get_Kerning(ttfontsz.face, pgl, glyph, FT_KERNING_UNSCALED, &kk) == 0) {
671 if (kk.x) {
672 auto kadvfrac = FT_MulFix(kk.x, ttfontsz.metrics.x_scale); // 1/64 of pixel
673 kadv = cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
678 x += kadv;
679 uint clr = opt.clr;
680 if (clrdg !is null) { state.curx = x; clr = clrdg(state); }
681 x += ttfDrawGlyph(firstchar, opt.scale, x, y, glyph, clr);
682 firstchar = false;
684 state.spos = state.epos;
685 prevglyph = glyph;
689 return x-stx;
693 public int gxDrawTextUtf() (in auto ref GxDrawTextOptions opt, int x, int y, const(char)[] s, uint delegate (in ref GxDrawTextState state) nothrow @safe clrdg=null) nothrow @trusted {
694 static struct StrIterator {
695 private:
696 const(char)[] str;
697 public nothrow @trusted @nogc:
698 this (const(char)[] s) { pragma(inline, true); str = s; }
699 @property bool empty () pure const { pragma(inline, true); return (str.length == 0); }
700 @property char front () pure const { pragma(inline, true); return (str.length ? str.ptr[0] : 0); }
701 void popFront () { if (str.length) str = str[1..$]; }
704 if (s.length == 0) return 0;
705 return gxDrawTextUtf(opt, x, y, StrIterator(s), clrdg);
709 public int gxTextWidthScaledUtf (int scale, const(char)[] s, int tabsize=0, bool firstCharIsFull=false) nothrow @trusted {
710 if (scale < 1 || s.length == 0) return 0;
711 auto kern = GxKerning(tabsize, scale, firstCharIsFull);
712 s.utfByDChar(delegate (dchar ch) @trusted { kern.fixWidthPre(ch); });
713 return kern.finalWidth;
718 // ////////////////////////////////////////////////////////////////////////// //
719 public int gxTextHeightUtf () nothrow @trusted { if (ttflibrary is null) initFontEngine(); return fontHeight; }
720 public int gxTextHeightScaledUtf (int scale) nothrow @trusted { if (ttflibrary is null) initFontEngine(); return (scale < 1 ? 0 : fontHeight*scale); }
722 public int gxTextBaseLineUtf () nothrow @trusted { if (ttflibrary is null) initFontEngine(); return fontBaselineOfs; }
723 public int gxTextUnderLineUtf () nothrow @trusted { if (ttflibrary is null) initFontEngine(); return fontBaselineOfs+2; }
725 public int gxTextWidthUtf (const(char)[] s, int tabsize=0, bool firstCharIsFull=false) nothrow @trusted { return gxTextWidthScaledUtf(1, s, tabsize, firstCharIsFull); }
727 public int gxDrawTextUtf() (int x, int y, const(char)[] s, uint clr) nothrow @safe { return gxDrawTextUtf(GxDrawTextOptions.Color(clr), x, y, s); }
728 public int gxDrawTextUtf() (in auto ref GxPoint p, const(char)[] s, uint clr) nothrow @safe { return gxDrawTextUtf(p.x, p.y, s, clr); }
731 public int gxDrawTextOutScaledUtf (int scale, int x, int y, const(char)[] s, uint clr, uint clrout) nothrow @trusted {
732 if (scale < 1 || s.length == 0) return 0;
734 if (scale == 1 && gxIsTransparent(clrout)) return gxDrawTextUtf(x, y, s, clr);
736 auto opt = GxDrawTextOptions.ScaleTabColor(scale, 0, clrout);
737 if (!gxIsTransparent(clrout)) {
738 version(all) {
739 foreach (immutable dy; -1*scale..2*scale) {
740 foreach (immutable dx; -1*scale..2*scale) {
741 if (dx || dy) gxDrawTextUtf(opt, x+dx, y+dy, s);
744 } else {
745 immutable int origWdt = gxTextWidthScaledUtf(scale, s);
746 immutable int origHgt = gxTextHeightScaledUtf(scale);
747 immutable int oldFontSize = fontSize;
748 scope(exit) fontSize = oldFontSize;
749 fontSize += scale*2;
750 immutable int outWdt = gxTextWidthScaledUtf(scale, s);
751 immutable int outHgt = gxTextHeightScaledUtf(scale);
752 gxDrawTextUtf(opt, x-(outWdt-origWdt)/2, y-(outHgt-origHgt)/2, s);
756 opt.clr = clr;
757 return gxDrawTextUtf(opt, x, y, s);