textlayouter: added simple and incomplete text selection tools
[iv.d.git] / egra / gfx / text.d
blobd065b7ae23629d9493448b0724b06abda548fb8e
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;
33 import iv.egra.gfx.aggmini;
35 version (DigitalMars) {
36 version(X86) {
37 version = EGRA_GFX_TEXT_ASM_ALLOWED;
42 // ////////////////////////////////////////////////////////////////////////// //
43 enum ReplacementChar = 0xFFFD;
44 //__gshared string egraFontName = "Arial:pixelsize=16";
45 //__gshared string egraFontName = "Verdana:pixelsize=16";
46 public __gshared string egraFontName = "Verdana:weight=bold:pixelsize=16";
47 public __gshared int egraFontSize = 100; // percents
48 __gshared string egraFontFile;
49 __gshared int fontSize;
50 __gshared int fontHeight;
51 __gshared int fontBaselineOfs;
54 // ////////////////////////////////////////////////////////////////////////// //
55 __gshared ubyte* ttfontdata;
56 __gshared uint ttfontdatasize;
58 __gshared FT_Library ttflibrary;
59 __gshared FTC_Manager ttfcache;
60 __gshared FTC_CMapCache ttfcachecmap;
61 __gshared FTC_ImageCache ttfcacheimage;
64 shared static ~this () {
65 if (ttflibrary) {
66 if (ttfcache) {
67 FTC_Manager_Done(ttfcache);
68 ttfcache = null;
70 FT_Done_FreeType(ttflibrary);
71 ttflibrary = null;
76 enum FontID = cast(FTC_FaceID)1;
79 extern(C) nothrow {
80 void ttfFontFinalizer (void* obj) {
81 import core.stdc.stdlib : free;
82 if (obj is null) return;
83 auto tf = cast(iv.freetype.FT_Face)obj;
84 if (tf.generic.data !is ttfontdata) return;
85 if (ttfontdata !is null) {
86 version(aliced) conwriteln("TTF CACHE: freeing loaded font...");
87 free(ttfontdata);
88 ttfontdata = null;
89 ttfontdatasize = 0;
93 FT_Error ttfFontLoader (FTC_FaceID face_id, FT_Library library, FT_Pointer request_data, iv.freetype.FT_Face* aface) {
94 if (face_id == FontID) {
95 try {
96 if (ttfontdata is null) {
97 conwriteln("TTF CACHE: loading '", egraFontFile, "'...");
98 import core.stdc.stdlib : malloc;
99 auto fl = VFile(egraFontFile);
100 auto fsz = fl.size;
101 if (fsz < 16 || fsz > int.max/8) throw new Exception("invalid ttf size");
102 ttfontdatasize = cast(uint)fsz;
103 ttfontdata = cast(ubyte*)malloc(ttfontdatasize);
104 if (ttfontdata is null) { import core.exception : onOutOfMemoryErrorNoGC; onOutOfMemoryErrorNoGC(); }
105 fl.rawReadExact(ttfontdata[0..ttfontdatasize]);
107 auto res = FT_New_Memory_Face(library, cast(const(FT_Byte)*)ttfontdata, ttfontdatasize, 0, aface);
108 if (res != 0) throw new Exception("error loading ttf: '"~egraFontFile~"'");
109 (*aface).generic.data = ttfontdata;
110 (*aface).generic.finalizer = &ttfFontFinalizer;
111 } catch (Exception e) {
112 if (ttfontdata !is null) {
113 import core.stdc.stdlib : free;
114 free(ttfontdata);
115 ttfontdata = null;
116 ttfontdatasize = 0;
118 version(aliced) conwriteln("ERROR loading font: ", e.msg);
119 return FT_Err_Cannot_Open_Resource;
121 return FT_Err_Ok;
122 } else {
123 version(aliced) conwriteln("TTF CACHE: invalid font id");
125 return FT_Err_Cannot_Open_Resource;
130 void ttfLoad () nothrow {
131 if (FT_Init_FreeType(&ttflibrary)) assert(0, "can't initialize FreeType");
132 if (FTC_Manager_New(ttflibrary, 1/*max faces*/, 0, 0, &ttfFontLoader, null, &ttfcache)) assert(0, "can't initialize FreeType cache manager");
133 if (FTC_CMapCache_New(ttfcache, &ttfcachecmap)) assert(0, "can't initialize FreeType cache manager");
134 if (FTC_ImageCache_New(ttfcache, &ttfcacheimage)) assert(0, "can't initialize FreeType cache manager");
136 FTC_ScalerRec fsc;
137 fsc.face_id = FontID;
138 fsc.width = 0;
139 fsc.height = fontSize;
140 fsc.pixel = 1; // size in pixels
142 FT_Size ttfontsz;
143 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) assert(0, "cannot find FreeType font");
144 fontHeight = cast(int)ttfontsz.metrics.height>>6; // 26.6
145 fontBaselineOfs = cast(int)((ttfontsz.metrics.height+ttfontsz.metrics.descender)>>6);
146 if (fontHeight < 2 || fontHeight > 128) assert(0, "invalid FreeType font metrics");
148 version(aliced) conwriteln("TTF CACHE initialized.");
152 void initFontEngine () nothrow {
153 import std.string : fromStringz/*, toStringz*/;
154 import std.internal.cstring : tempCString;
156 if (ttflibrary !is null) return;
157 if (!FcInit()) assert(0, "cannot init fontconfig");
159 iv.fontconfig.FcPattern* pat = FcNameParse(egraFontName.tempCString);
160 if (pat is null) assert(0, "cannot parse font name");
162 if (!FcConfigSubstitute(null, pat, FcMatchPattern)) assert(0, "cannot find fontconfig substitute");
163 FcDefaultSubstitute(pat);
165 // find the font
166 iv.fontconfig.FcResult result;
167 iv.fontconfig.FcPattern* font = FcFontMatch(null, pat, &result);
168 if (font !is null) {
169 char* file = null;
170 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
171 version(aliced) conwriteln("font file: [", file, "]");
172 egraFontFile = file.fromStringz.idup;
174 double pixelsize;
175 if (FcPatternGetDouble(font, FC_PIXEL_SIZE, 0, &pixelsize) == FcResultMatch) {
176 version(aliced) conwriteln("pixel size: ", pixelsize);
177 fontSize = cast(int)pixelsize;
180 FcPatternDestroy(pat);
182 // arbitrary limits
183 if (fontSize < 6) fontSize = 6;
184 if (fontSize > 42) fontSize = 42;
186 ttfLoad();
190 // ////////////////////////////////////////////////////////////////////////// //
191 public void utfByDChar (const(char)[] s, scope void delegate (dchar ch) nothrow @safe dg) nothrow @safe {
192 if (dg is null || s.length == 0) return;
193 Utf8DecoderFast dc;
194 foreach (char ch; s) {
195 if (dc.decode(cast(ubyte)ch)) dg(dc.complete ? dc.codepoint : dc.replacement);
200 public void utfByDCharSPos (const(char)[] s, scope void delegate (dchar ch, usize stpos) nothrow @safe dg) nothrow @safe {
201 if (dg is null || s.length == 0) return;
202 Utf8DecoderFast dc;
203 usize stpos = 0;
204 foreach (immutable idx, char ch; s) {
205 if (dc.decode(cast(ubyte)ch)) {
206 dg(dc.complete ? dc.codepoint : dc.replacement, stpos);
207 stpos = idx+1;
213 // ////////////////////////////////////////////////////////////////////////// //
214 private void drawMonoBMP (int x, int y, int wdt, int hgt, const(ubyte)* bmp, in usize bpitch, in uint clr) nothrow @trusted @nogc {
215 immutable int origWdt = wdt;
217 int leftSkip, topSkip;
218 if (!gxClipRect.clipHVStripes(ref x, ref y, ref wdt, ref hgt, &leftSkip, &topSkip)) return;
219 if (!GxRect(0, 0, VBufWidth, VBufHeight).clipHVStripes(ref x, ref y, ref wdt, ref hgt, &leftSkip, &topSkip)) return;
221 // skip unused top part
222 if (topSkip) bmp += bpitch*cast(uint)topSkip;
223 // skip unused bytes
224 bmp += cast(usize)(leftSkip>>3);
225 leftSkip &= 0x07;
227 if (gxIsSolid(clr) && leftSkip == 0) {
228 // yay, the fastest one!
229 enum RenderBitMixin = `if (b&0x80) *curptr = clr; ++curptr; b <<= 1;`;
230 uint* dptr = vglTexBuf+y*VBufWidth+x;
231 while (hgt--) {
232 const(ubyte)* ss = bmp;
233 int left = wdt;
234 uint* curptr = dptr;
235 while (left >= 8) {
236 ubyte b = *ss++;
237 if (b == 0xff) {
238 curptr[0..8] = clr;
239 curptr += 8;
240 } else if (b) {
241 mixin(RenderBitMixin);
242 mixin(RenderBitMixin);
243 mixin(RenderBitMixin);
244 mixin(RenderBitMixin);
245 mixin(RenderBitMixin);
246 mixin(RenderBitMixin);
247 mixin(RenderBitMixin);
248 mixin(RenderBitMixin);
249 } else {
250 curptr += 8;
252 left -= 8;
254 //assert(left >= 0 && left < 8);
255 if (left) {
256 ubyte b = *ss;
257 if (b) {
258 final switch (left) {
259 case 7: mixin(RenderBitMixin); goto case;
260 case 6: mixin(RenderBitMixin); goto case;
261 case 5: mixin(RenderBitMixin); goto case;
262 case 4: mixin(RenderBitMixin); goto case;
263 case 3: mixin(RenderBitMixin); goto case;
264 case 2: mixin(RenderBitMixin); goto case;
265 case 1: if (b&0x80) *curptr = clr; break;
266 //case 0: break;
269 //while (left--) { mixin(RenderBitMixin); }
271 bmp += bpitch;
272 dptr += cast(usize)VBufWidth;
274 return;
277 if (gxIsSolid(clr)) {
278 // yay, the fast one!
279 enum RenderBitMixin = `if (b&0x80) *curptr = clr; ++curptr; b <<= 1;`;
280 uint* dptr = vglTexBuf+y*VBufWidth+x;
281 int ldraw = 8-leftSkip;
282 if (ldraw > wdt) ldraw = wdt;
283 while (hgt--) {
284 const(ubyte)* ss = bmp;
285 uint* curptr = dptr;
287 ubyte b = cast(ubyte)((*ss++)<<cast(ubyte)(leftSkip&0x07u));
288 foreach (; 0..ldraw) { mixin(RenderBitMixin); }
290 int left = wdt-ldraw;
291 while (left >= 8) {
292 ubyte b = *ss++;
293 if (b == 0xff) {
294 curptr[0..8] = clr;
295 curptr += 8;
296 } else if (b) {
297 mixin(RenderBitMixin);
298 mixin(RenderBitMixin);
299 mixin(RenderBitMixin);
300 mixin(RenderBitMixin);
301 mixin(RenderBitMixin);
302 mixin(RenderBitMixin);
303 mixin(RenderBitMixin);
304 mixin(RenderBitMixin);
305 } else {
306 curptr += 8;
308 left -= 8;
310 //assert(left >= 0 && left < 8);
311 if (left) {
312 ubyte b = *ss;
313 if (b) {
314 final switch (left) {
315 case 7: mixin(RenderBitMixin); goto case;
316 case 6: mixin(RenderBitMixin); goto case;
317 case 5: mixin(RenderBitMixin); goto case;
318 case 4: mixin(RenderBitMixin); goto case;
319 case 3: mixin(RenderBitMixin); goto case;
320 case 2: mixin(RenderBitMixin); goto case;
321 case 1: if (b&0x80) *curptr = clr; break;
322 //case 0: break;
325 //while (left--) { mixin(RenderBitMixin); }
327 bmp += bpitch;
328 dptr += cast(usize)VBufWidth;
330 return;
333 // the slowest path
334 x -= leftSkip;
335 wdt += leftSkip;
336 while (hgt--) {
337 const(ubyte)* ss = bmp;
338 ubyte count = 1, b = void;
339 int cx = x;
340 int left = wdt;
341 while (left > 0) {
342 if (--count == 0) {
343 b = *ss++;
344 if (!b || b == 0xff) {
345 if (b) gxHLine(cx, y, (left >= 8 ? 8 : left), clr);
346 cx += 8;
347 left -= 8;
348 count = 1;
349 continue;
351 count = 8;
352 } else {
353 b <<= 1;
355 if (b&0x80) gxPutPixel(cx, y, clr);
356 ++cx;
357 --left;
359 ++y;
360 bmp += bpitch;
365 // ////////////////////////////////////////////////////////////////////////// //
366 void drawFTBitmap (int x, int y, in ref FT_Bitmap bitmap, in uint clr) nothrow @trusted @nogc {
367 if (bitmap.pixel_mode != FT_PIXEL_MODE_MONO) return; // alas
368 if (bitmap.rows < 1 || bitmap.width < 1) return; // nothing to do
369 if (gxIsTransparent(clr)) return; // just in case
370 // prepare
371 const(ubyte)* src = bitmap.buffer;
372 usize bpitch = void;
373 if (bitmap.pitch >= 0) {
374 bpitch = cast(usize)bitmap.pitch;
375 } else {
376 bpitch = cast(usize)0-cast(usize)(-bitmap.pitch);
377 src += bpitch*(cast(uint)bitmap.rows-1u);
379 drawMonoBMP(x, y, bitmap.width, bitmap.rows, src, bpitch, clr);
383 // return 0 if invalid
384 int calcScaledValue (in int v, int scalePRC) pure nothrow @trusted @nogc {
385 pragma(inline, true);
386 if (scalePRC == 100) return v;
387 if (scalePRC < 1) return v;
388 if (scalePRC > 10000) scalePRC = 10000;
389 return v*scalePRC/100;
392 // return 0 if invalid
393 int calcScaledFontSize (int scalePRC) nothrow @trusted @nogc {
394 pragma(inline, true);
395 if (scalePRC == 100) return fontSize;
396 if (scalePRC < 1) return 0;
397 if (scalePRC > 10000) scalePRC = 10000;
398 immutable int fsz = fontSize*scalePRC/100;
399 return (fsz > 0 ? fsz : 0);
403 // y is baseline; returns advance
404 int ttfDrawGlyph (bool firstchar, int scalePRC, int x, int y, int glyphidx, uint clr) nothrow @trusted @nogc {
405 enum mono = true;
406 if (glyphidx == 0) return 0;
407 immutable int fsz = calcScaledFontSize(scalePRC);
408 if (!fsz) return 0;
410 FTC_ImageTypeRec fimg;
411 fimg.face_id = FontID;
412 fimg.width = 0;
413 fimg.height = fsz;
414 static if (mono) {
415 fimg.flags = FT_LOAD_TARGET_MONO|(gxIsTransparent(clr) ? 0 : FT_LOAD_MONOCHROME|FT_LOAD_RENDER);
416 } else {
417 fimg.flags = (gxIsTransparent(clr) ? 0 : FT_LOAD_RENDER);
420 FT_Glyph fg;
421 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyphidx, &fg, null)) return 0;
423 int advdec = 0;
424 if (!gxIsTransparent(clr)) {
425 if (fg.format != FT_GLYPH_FORMAT_BITMAP) return 0;
426 FT_BitmapGlyph fgi = cast(FT_BitmapGlyph)fg;
427 int x0 = x+fgi.left;
428 if (firstchar && fgi.bitmap.width > 0) { x0 -= fgi.left; advdec = fgi.left; }
429 drawFTBitmap(x0, y-fgi.top, fgi.bitmap, clr);
431 return cast(int)(fg.advance.x>>16)-advdec;
435 int ttfGetKerning (int scalePRC, int gl0idx, int gl1idx) nothrow @trusted @nogc {
436 if (gl0idx == 0 || gl1idx == 0) return 0;
437 immutable int fsz = calcScaledFontSize(scalePRC);
438 if (!fsz) return 0;
440 FTC_ScalerRec fsc;
441 fsc.face_id = FontID;
442 fsc.width = 0;
443 fsc.height = fsz;
444 fsc.pixel = 1; // size in pixels
446 FT_Size ttfontsz;
447 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) return 0;
448 if (!FT_HAS_KERNING(ttfontsz.face)) return 0;
450 FT_Vector kk;
451 if (FT_Get_Kerning(ttfontsz.face, gl0idx, gl1idx, FT_KERNING_UNSCALED, &kk)) return 0;
452 if (!kk.x) return 0;
453 auto kadvfrac = FT_MulFix(kk.x, ttfontsz.metrics.x_scale); // 1/64 of pixel
454 return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
458 // ////////////////////////////////////////////////////////////////////////// //
459 public int gxCharWidth (in dchar ch) nothrow @trusted {
460 immutable int fsz = calcScaledFontSize(egraFontSize);
461 if (!fsz) return 0;
463 if (ttflibrary is null) initFontEngine();
465 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
466 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ReplacementChar);
467 if (glyph == 0) return 0;
469 FTC_ImageTypeRec fimg;
470 fimg.face_id = FontID;
471 fimg.width = 0;
472 fimg.height = fsz;
473 fimg.flags = FT_LOAD_TARGET_MONO;
475 FT_Glyph fg;
476 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null)) return -666;
478 immutable int res = cast(int)fg.advance.x>>16;
479 return (res > 0 ? res : 0);
483 // ////////////////////////////////////////////////////////////////////////// //
484 // returns char width
485 public int gxDrawChar (int x, int y, dchar ch, uint clr, int prevcp=-1) nothrow @trusted {
486 if (ttflibrary is null) initFontEngine();
488 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
489 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ReplacementChar);
490 if (glyph == 0) return 0;
492 int kadv = ttfGetKerning(egraFontSize, (prevcp >= 0 ? FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, prevcp) : 0), glyph);
493 return ttfDrawGlyph(false, egraFontSize, x+kadv, y+calcScaledValue(fontBaselineOfs, egraFontSize), glyph, clr);
496 // returns char width
497 public int gxDrawChar() (in auto ref GxPoint p, char ch, uint fg, int prevcp=-1) nothrow @trusted { pragma(inline, true); return gxDrawChar(p.x, p.y, ch, fg, prevcp); }
500 // ////////////////////////////////////////////////////////////////////////// //
501 public struct GxKerning {
502 int prevgidx = 0;
503 int wdt = 0;
504 int lastadv = 0;
505 int lastcw = 0;
506 int tabsize = 0;
507 int scale = int.min;
508 bool firstchar = true;
510 nothrow @trusted:
511 this (int atabsize, int ascale, bool firstCharIsFull=false) {
512 if (ttflibrary is null) initFontEngine();
513 firstchar = !firstCharIsFull;
514 if (ascale <= 0) ascale = egraFontSize;
515 scale = ascale;
516 if ((tabsize = (atabsize > 0 ? atabsize : 0)) != 0) {
517 immutable osz = egraFontSize;
518 egraFontSize = scale;
519 scope(exit) egraFontSize = osz;
520 tabsize = tabsize*gxCharWidth(' ');
524 void reset (int atabsize, int ascale) {
525 if (ttflibrary is null) initFontEngine();
526 if (ascale <= 0) ascale = egraFontSize;
527 scale = ascale;
528 prevgidx = 0;
529 wdt = 0;
530 lastadv = 0;
531 lastcw = 0;
532 if ((tabsize = (atabsize > 0 ? atabsize : 0)) != 0) {
533 immutable osz = egraFontSize;
534 egraFontSize = scale;
535 scope(exit) egraFontSize = osz;
536 tabsize = tabsize*gxCharWidth(' ');
538 firstchar = true;
541 // tab length for current position
542 int tablength () pure const @nogc { pragma(inline, true); return (tabsize > 0 ? (wdt/tabsize+1)*tabsize-wdt : 0); }
544 int fixWidthPre (dchar ch) {
545 if (scale == int.min) scale = egraFontSize;
546 immutable int prevgl = prevgidx;
547 wdt += lastadv;
548 lastadv = 0;
549 lastcw = 0;
550 prevgidx = 0;
551 if (ch == '\t' && tabsize > 0) {
552 // tab
553 lastadv = lastcw = tablength;
554 firstchar = false;
555 } else {
556 if (ttflibrary is null) initFontEngine();
557 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
558 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ReplacementChar);
559 immutable int fsz = calcScaledFontSize(scale);
560 if (fsz && glyph != 0) {
561 wdt += ttfGetKerning(scale, prevgl, glyph);
563 FTC_ImageTypeRec fimg;
564 fimg.face_id = FontID;
565 fimg.width = 0;
566 fimg.height = fsz;
567 version(none) {
568 fimg.flags = FT_LOAD_TARGET_MONO;
569 } else {
570 fimg.flags = FT_LOAD_TARGET_MONO|FT_LOAD_MONOCHROME|FT_LOAD_RENDER;
573 FT_Glyph fg;
574 version(none) {
575 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null) == 0) {
576 prevgidx = glyph;
577 lastadv = fg.advance.x>>16;
579 } else {
580 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null) == 0) {
581 int advdec = 0;
582 if (fg.format == FT_GLYPH_FORMAT_BITMAP) {
583 FT_BitmapGlyph fgi = cast(FT_BitmapGlyph)fg;
584 if (firstchar && fgi.bitmap.width > 0) {
585 lastcw = fgi.bitmap.width;
586 advdec = fgi.left;
587 if (lastcw < 1) { advdec = 0; lastcw = cast(int)fg.advance.x>>16; }
588 } else {
589 lastcw = fgi.left+fgi.bitmap.width;
590 if (lastcw < 1) lastcw = cast(int)fg.advance.x>>16;
593 prevgidx = glyph;
594 lastadv = (cast(int)fg.advance.x>>16)-advdec;
595 firstchar = false;
600 return wdt;
603 @property int finalWidth () pure const @nogc { pragma(inline, true); return wdt+/*lastadv*/lastcw; }
605 // BUGGY!
606 @property int nextCharOfs () pure const @nogc { pragma(inline, true); return wdt+lastadv; }
608 @property int currOfs () pure const @nogc { pragma(inline, true); return wdt; }
610 @property int nextOfsNoSpacing () pure const @nogc { pragma(inline, true); return wdt+lastcw; }
614 // ////////////////////////////////////////////////////////////////////////// //
615 public struct GxDrawTextOptions {
616 int tabsize = 0;
617 uint clr = gxTransparent;
618 int scale = 100;
619 bool firstCharIsFull = false;
621 static nothrow @trusted @nogc:
622 auto Color (in uint aclr) { pragma(inline, true); return GxDrawTextOptions(0, aclr, egraFontSize, false); }
623 auto Tab (in int atabsize) { pragma(inline, true); return GxDrawTextOptions(atabsize, gxTransparent, egraFontSize, false); }
624 auto TabColor (in int atabsize, in uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, egraFontSize, false); }
625 auto TabColorFirstFull (in int atabsize, in uint aclr, in bool fcf) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, egraFontSize, fcf); }
626 auto ScaleTabColor (in int ascale, in int atabsize, in uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, ascale, false); }
627 auto ColorNFC (in uint aclr) { pragma(inline, true); return GxDrawTextOptions(0, aclr, egraFontSize, true); }
628 auto TabColorNFC (in int atabsize, in uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, egraFontSize, true); }
629 auto ScaleTabColorNFC (in int ascale, in int atabsize, in uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, ascale, true); }
630 // more ctors?
633 public struct GxDrawTextState {
634 usize spos; // current codepoint starting position
635 usize epos; // current codepoint ending position (exclusive; i.e. *after* codepoint)
636 int curx; // current x (before drawing the glyph)
640 // delegate should return color
641 public int gxDrawTextUtfOpt(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
642 if (Imp!"std.range.primitives".isInputRange!R && is(Imp!"std.range.primitives".ElementEncodingType!R == char))
644 // rely on the assumption that font face won't be unloaded while we are in this function
645 if (opt.scale < 1) return 0;
647 if (ttflibrary is null) initFontEngine();
649 GxDrawTextState state;
651 immutable oldEScale = egraFontSize;
652 scope(exit) egraFontSize = oldEScale;
653 egraFontSize = opt.scale;
655 y += calcScaledValue(fontBaselineOfs, egraFontSize);
657 immutable int tabpix = (opt.tabsize > 0 ? opt.tabsize*gxCharWidth(' ') : 0);
659 FT_Size ttfontsz;
661 int prevglyph = 0;
662 immutable int stx = x;
664 bool dokern = true;
665 bool firstchar = !opt.firstCharIsFull;
666 Utf8DecoderFast dc;
668 while (!srng.empty) {
669 immutable ubyte srbyte = cast(ubyte)srng.front;
670 srng.popFront();
671 ++state.epos;
673 if (dc.decode(srbyte)) {
674 int ch = (dc.complete ? dc.codepoint : dc.replacement);
675 int pgl = prevglyph;
676 prevglyph = 0;
677 state.curx = x;
679 if (opt.tabsize > 0) {
680 if (ch == '\t') {
681 firstchar = false;
682 int wdt = x-stx;
683 state.curx = x;
684 x += (wdt/tabpix+1)*tabpix-wdt;
685 if (clrdg !is null) clrdg(state);
686 state.spos = state.epos;
687 continue;
691 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
692 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ReplacementChar);
693 immutable int fsz = calcScaledFontSize(egraFontSize);
694 if (fsz && glyph != 0) {
695 // kerning
696 int kadv = 0;
697 if (pgl != 0 && dokern) {
698 if (ttfontsz is null) {
699 FTC_ScalerRec fsc;
700 fsc.face_id = FontID;
701 fsc.width = 0;
702 fsc.height = fsz;
703 fsc.pixel = 1; // size in pixels
704 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) {
705 dokern = false;
706 ttfontsz = null;
707 } else {
708 dokern = (FT_HAS_KERNING(ttfontsz.face) != 0);
711 if (dokern) {
712 FT_Vector kk;
713 if (FT_Get_Kerning(ttfontsz.face, pgl, glyph, FT_KERNING_UNSCALED, &kk) == 0) {
714 if (kk.x) {
715 auto kadvfrac = FT_MulFix(kk.x, ttfontsz.metrics.x_scale); // 1/64 of pixel
716 kadv = cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
721 x += kadv;
722 uint clr = opt.clr;
723 if (clrdg !is null) { state.curx = x; clr = clrdg(state); }
724 x += ttfDrawGlyph(firstchar, egraFontSize, x, y, glyph, clr);
725 firstchar = false;
727 state.spos = state.epos;
728 prevglyph = glyph;
732 return x-stx;
736 public int gxDrawTextUtfOpt() (in auto ref GxDrawTextOptions opt, int x, int y, const(char)[] s, uint delegate (in ref GxDrawTextState state) nothrow @safe clrdg=null) nothrow @trusted {
737 static struct StrIterator {
738 private:
739 const(char)[] str;
740 public nothrow @trusted @nogc:
741 this (const(char)[] s) { pragma(inline, true); str = s; }
742 @property bool empty () pure const { pragma(inline, true); return (str.length == 0); }
743 @property char front () pure const { pragma(inline, true); return (str.length ? str.ptr[0] : 0); }
744 void popFront () { if (str.length) str = str[1..$]; }
747 if (s.length == 0) return 0;
748 return gxDrawTextUtfOpt(opt, x, y, StrIterator(s), clrdg);
752 public int gxTextWidthUtfOpt() (in auto ref GxDrawTextOptions opt, const(char)[] s) nothrow @trusted {
753 if (s.length == 0) return 0;
754 auto kern = GxKerning(opt.tabsize, opt.scale, opt.firstCharIsFull);
755 s.utfByDChar(delegate (dchar ch) @trusted { kern.fixWidthPre(ch); });
756 return kern.finalWidth;
759 public int gxTextWidthUtf (const(char)[] s, int atabsize=0) nothrow @trusted {
760 if (s.length == 0) return 0;
761 auto kern = GxKerning(atabsize, egraFontSize);
762 s.utfByDChar(delegate (dchar ch) @trusted { kern.fixWidthPre(ch); });
763 return kern.finalWidth;
767 // ////////////////////////////////////////////////////////////////////////// //
768 public int gxTextHeightUtf () nothrow @trusted { if (ttflibrary is null) initFontEngine(); return calcScaledValue(fontHeight, egraFontSize); }
770 public int gxTextBaseLineUtf () nothrow @trusted { if (ttflibrary is null) initFontEngine(); return calcScaledValue(fontBaselineOfs, egraFontSize); }
771 public int gxTextUnderLineUtf () nothrow @trusted { if (ttflibrary is null) initFontEngine(); return calcScaledValue(fontBaselineOfs, egraFontSize)+2; }
773 public int gxDrawTextUtf() (int x, int y, const(char)[] s, uint clr) nothrow @safe { pragma(inline, true); return gxDrawTextUtfOpt(GxDrawTextOptions.Color(clr), x, y, s); }
774 public int gxDrawTextUtf() (in auto ref GxPoint p, const(char)[] s, uint clr) nothrow @safe { pragma(inline, true); return gxDrawTextUtfOpt(GxDrawTextOptions.Color(clr), p.x, p.y, s); }
777 public int gxDrawTextOutUtf (int x, int y, const(char)[] s, uint clr, uint clrout) nothrow @trusted {
778 if (s.length == 0) return 0;
780 auto opt = GxDrawTextOptions.ScaleTabColor(egraFontSize, 0, clrout);
781 if (!gxIsTransparent(clrout)) {
782 if (ttflibrary is null) initFontEngine();
783 int lim = calcScaledValue(1, egraFontSize);
784 if (lim < 1) lim = 1;
785 foreach (immutable dy; -lim..lim+1) {
786 foreach (immutable dx; -lim..lim+1) {
787 if (dx || dy) gxDrawTextUtfOpt(opt, x+dx, y+dy, s);
792 opt.clr = clr;
793 return gxDrawTextUtfOpt(opt, x, y, s);
797 extern(C) nothrow @trusted @nogc {
798 int fons__nvg__moveto_cb (const(FT_Vector)* to, void* user) {
799 gxagg.moveTo(to.x, -to.y);
800 return 0;
803 int fons__nvg__lineto_cb (const(FT_Vector)* to, void* user) {
804 gxagg.lineTo(to.x, -to.y);
805 return 0;
808 int fons__nvg__quadto_cb (const(FT_Vector)* c1, const(FT_Vector)* to, void* user) {
809 gxagg.quadTo(c1.x, -c1.y, to.x, -to.y);
810 return 0;
813 int fons__nvg__cubicto_cb (const(FT_Vector)* c1, const(FT_Vector)* c2, const(FT_Vector)* to, void* user) {
814 gxagg.bezierTo(c1.x, -c1.y, c2.x, -c2.y, to.x, -to.y);
815 return 0;
820 public bool gxFontCharToPath (in dchar dch, float[] bounds=null) nothrow @trusted {
821 if (ttflibrary is null) initFontEngine();
823 if (bounds.length > 4) bounds = bounds.ptr[0..4];
825 int glyphidx = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, dch);
826 if (glyphidx == 0) glyphidx = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ReplacementChar);
827 if (glyphidx == 0) { bounds[] = 0.0f; return false; }
829 FT_Face font;
830 if (FTC_Manager_LookupFace(ttfcache, FontID, &font)) assert(0, "cannot find FreeType font");
832 auto err = FT_Load_Glyph(font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
833 if (err) { bounds[] = 0.0f; return false; }
834 if (font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0.0f; return false; }
836 FT_Outline outline = font.glyph.outline;
838 if (bounds.length) {
839 FT_BBox outlineBBox;
840 FT_Outline_Get_CBox(&outline, &outlineBBox);
841 if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin;
842 if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax;
843 if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax;
844 if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin;
847 FT_Outline_Funcs funcs;
848 funcs.move_to = &fons__nvg__moveto_cb;
849 funcs.line_to = &fons__nvg__lineto_cb;
850 funcs.conic_to = &fons__nvg__quadto_cb;
851 funcs.cubic_to = &fons__nvg__cubicto_cb;
852 funcs.shift = 0;
853 funcs.delta = 0;
856 A contour that contains a single point only is represented by a 'move to' operation
857 followed by 'line to' to the same point. In most cases, it is best to filter this
858 out before using the outline for stroking purposes (otherwise it would result in
859 a visible dot when round caps are used).
861 err = FT_Outline_Decompose(&outline, &funcs, null);
862 if (err) { bounds[] = 0.0f; return false; }
864 return true;
868 public bool gxFontCharToPathEmboldenXY (in dchar dch, in int xstrength, in int ystrength, float[] bounds=null) nothrow @trusted {
869 if (ttflibrary is null) initFontEngine();
871 if (bounds.length > 4) bounds = bounds.ptr[0..4];
873 int glyphidx = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, dch);
874 if (glyphidx == 0) glyphidx = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ReplacementChar);
875 if (glyphidx == 0) { bounds[] = 0.0f; return false; }
877 FT_Face font;
878 if (FTC_Manager_LookupFace(ttfcache, FontID, &font)) assert(0, "cannot find FreeType font");
880 auto err = FT_Load_Glyph(font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
881 if (err) { bounds[] = 0.0f; return false; }
882 if (font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0.0f; return false; }
884 FT_Outline outline = font.glyph.outline;
886 FT_Outline emo;
887 if (FT_Outline_New(ttflibrary, outline.n_points, outline.n_contours, &emo)) { bounds[] = 0.0f; return false; }
888 scope(exit) { FT_Outline_Done(ttflibrary, &emo); }
890 if (FT_Outline_Copy(&outline, &emo)) { bounds[] = 0.0f; return false; }
891 if (FT_Outline_EmboldenXY(&emo, xstrength, ystrength)) { bounds[] = 0.0f; return false; }
893 if (bounds.length) {
894 FT_BBox outlineBBox;
895 FT_Outline_Get_CBox(&emo, &outlineBBox);
896 if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin;
897 if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax;
898 if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax;
899 if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin;
902 FT_Outline_Funcs funcs;
903 funcs.move_to = &fons__nvg__moveto_cb;
904 funcs.line_to = &fons__nvg__lineto_cb;
905 funcs.conic_to = &fons__nvg__quadto_cb;
906 funcs.cubic_to = &fons__nvg__cubicto_cb;
907 funcs.shift = 0;
908 funcs.delta = 0;
911 A contour that contains a single point only is represented by a 'move to' operation
912 followed by 'line to' to the same point. In most cases, it is best to filter this
913 out before using the outline for stroking purposes (otherwise it would result in
914 a visible dot when round caps are used).
916 err = FT_Outline_Decompose(&emo, &funcs, null);
917 if (err) { bounds[] = 0.0f; return false; }
919 return true;
922 public bool gxFontCharToPathEmbolden (in dchar dch, in int strength, float[] bounds=null) nothrow @trusted {
923 pragma(inline, true);
924 return gxFontCharToPathEmboldenXY(dch, strength, strength, bounds);
928 public bool gxFontCharPathBounds (in dchar dch, float[] bounds=null) nothrow @trusted {
929 if (ttflibrary is null) initFontEngine();
931 if (bounds.length > 4) bounds = bounds.ptr[0..4];
933 int glyphidx = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, dch);
934 if (glyphidx == 0) glyphidx = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ReplacementChar);
935 if (glyphidx == 0) { bounds[] = 0.0f; return false; }
937 FT_Face font;
938 if (FTC_Manager_LookupFace(ttfcache, FontID, &font)) assert(0, "cannot find FreeType font");
940 auto err = FT_Load_Glyph(font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
941 if (err) { bounds[] = 0.0f; return false; }
942 if (font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0.0f; return false; }
944 if (bounds.length) {
945 FT_Outline outline = font.glyph.outline;
947 FT_BBox outlineBBox;
948 FT_Outline_Get_CBox(&outline, &outlineBBox);
950 if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin;
951 if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax;
952 if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax;
953 if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin;
956 return true;
960 public bool gxFontCharPathEmboldenBoundsXY (in dchar dch, in int xstrength, in int ystrength, float[] bounds=null) nothrow @trusted {
961 if (ttflibrary is null) initFontEngine();
963 if (bounds.length > 4) bounds = bounds.ptr[0..4];
965 int glyphidx = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, dch);
966 if (glyphidx == 0) glyphidx = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ReplacementChar);
967 if (glyphidx == 0) { bounds[] = 0.0f; return false; }
969 FT_Face font;
970 if (FTC_Manager_LookupFace(ttfcache, FontID, &font)) assert(0, "cannot find FreeType font");
972 auto err = FT_Load_Glyph(font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE);
973 if (err) { bounds[] = 0.0f; return false; }
974 if (font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0.0f; return false; }
976 FT_Outline outline = font.glyph.outline;
978 FT_Outline emo;
979 if (FT_Outline_New(ttflibrary, outline.n_points, outline.n_contours, &emo)) { bounds[] = 0.0f; return false; }
980 scope(exit) { FT_Outline_Done(ttflibrary, &emo); }
982 if (FT_Outline_Copy(&outline, &emo)) { bounds[] = 0.0f; return false; }
983 if (FT_Outline_EmboldenXY(&emo, xstrength, ystrength)) { bounds[] = 0.0f; return false; }
985 if (bounds.length) {
986 FT_BBox outlineBBox;
987 FT_Outline_Get_CBox(&emo, &outlineBBox);
988 if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin;
989 if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax;
990 if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax;
991 if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin;
994 return true;
997 public bool gxFontCharPathEmboldenBounds (in dchar dch, in int strength, float[] bounds=null) nothrow @trusted {
998 pragma(inline, true);
999 return gxFontCharPathEmboldenBoundsXY(dch, strength, strength, bounds);