cosmetic fix
[amper.git] / egfx / text.d
blobac2f81792bed70dc2ae8f02bc494719f12592604
1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
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;
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.base;
33 // ////////////////////////////////////////////////////////////////////////// //
34 // x and y are always in range; !0 is set
35 public __gshared Pixmap delegate (int wdt, int hgt, scope uint delegate (int x, int y) getPixel) gxCreatePixmap1bpp;
36 public __gshared void delegate (Pixmap px) gxDeletePixmap;
39 // ////////////////////////////////////////////////////////////////////////// //
40 enum {
41 GXclear = 0x0, /* 0 */
42 GXand = 0x1, /* src AND dst */
43 GXandReverse = 0x2, /* src AND NOT dst */
44 GXcopy = 0x3, /* src */
45 GXandInverted = 0x4, /* NOT src AND dst */
46 GXnoop = 0x5, /* dst */
47 GXxor = 0x6, /* src XOR dst */
48 GXor = 0x7, /* src OR dst */
49 GXnor = 0x8, /* NOT src AND NOT dst */
50 GXequiv = 0x9, /* NOT src XOR dst */
51 GXinvert = 0xa, /* NOT dst */
52 GXorReverse = 0xb, /* src OR NOT dst */
53 GXcopyInverted = 0xc, /* NOT src */
54 GXorInverted = 0xd, /* NOT src OR dst */
55 GXnand = 0xe, /* NOT src OR NOT dst */
56 GXset = 0xf, /* 1 */
60 // ////////////////////////////////////////////////////////////////////////// //
61 struct GlyphCache {
62 int gidx;
63 Pixmap px;
64 GxRect dims; // x0,y0: image left and top; width,height: real image size; in pixels
65 int advance; // in pixels
66 GlyphCache* prev;
67 GlyphCache* next;
69 @disable this (this);
71 @property bool valid () const pure nothrow @trusted @nogc { pragma(inline, true); return (px != 0); }
73 void clear () {
74 if (px) {
75 gxDeletePixmap(px);
76 px = 0;
77 dims = GxRect.init;
78 advance = 0;
83 // list is sorted by access time; head has the latest access time
84 __gshared GlyphCache* head;
85 __gshared GlyphCache* tail;
86 __gshared GlyphCache*[int] gcache; // by glyph index
87 enum GlyphCacheMaxGlyphs = 256;
90 //TODO: use XImage to transfer bits
91 GlyphCache* wantGlyph (int gidx, FT_BitmapGlyph fgi) {
92 GlyphCache* gcp;
93 auto gcpp = gidx in gcache;
95 if (gcpp !is null) {
96 gcp = *gcpp;
97 // move to list head
98 if (gcp.prev !is null) {
99 gcp.prev.next = gcp.next;
100 if (gcp.next !is null) {
101 gcp.next.prev = gcp.prev;
102 } else {
103 tail = gcp.prev;
105 // add as first
106 gcp.prev = null;
107 gcp.next = head;
108 head = gcp;
110 } else {
111 FT_Bitmap* bitmap = &fgi.bitmap;
112 if (bitmap.pixel_mode != FT_PIXEL_MODE_MONO) return null; // alas
113 if (bitmap.rows < 1 || bitmap.width < 1) return null; // nothing to do
114 // want new glyph
115 if (gcache.length == GlyphCacheMaxGlyphs) {
116 // delete last glyph, reuse it's struct
117 gcp = tail;
118 tail = gcp.prev;
119 tail.next = null;
120 gcache.remove(gcp.gidx);
121 gcp.clear();
122 } else {
123 // create new glyph
124 gcp = new GlyphCache();
127 gcp.gidx = gidx;
129 // draw pixels
130 bool topdown = true;
131 const(ubyte)* src = bitmap.buffer;
132 int spt = bitmap.pitch;
133 if (spt < 0) {
134 topdown = false;
135 spt = -spt;
136 //src += spt*(bitmap.rows-1);
139 gcp.px = gxCreatePixmap1bpp(bitmap.width, bitmap.rows,
140 (int x, int y) {
141 if (!topdown) y = bitmap.rows-y-1;
142 return src[y*spt+x/8]&(0x80>>(x%8));
146 gcp.dims.x0 = fgi.left;
147 gcp.dims.y0 = fgi.top;
148 gcp.dims.width = bitmap.width;
149 gcp.dims.height = bitmap.rows;
150 gcp.advance = cast(int)(fgi.root.advance.x>>16);
152 // add as first
153 gcp.prev = null;
154 gcp.next = head;
155 head = gcp;
156 gcache[gidx] = gcp;
158 return gcp;
162 // ////////////////////////////////////////////////////////////////////////// //
163 //__gshared string chiFontName = "Arial:pixelsize=16";
164 //__gshared string chiFontName = "Verdana:pixelsize=16";
165 __gshared string chiFontName = "Verdana:pixelsize=12";
166 __gshared string chiFontFile;
167 __gshared int fontSize;
168 __gshared int fontHeight;
169 __gshared int fontBaselineOfs;
172 // ////////////////////////////////////////////////////////////////////////// //
173 __gshared ubyte* ttfontdata;
174 __gshared uint ttfontdatasize;
176 __gshared FT_Library ttflibrary;
177 __gshared FTC_Manager ttfcache;
178 __gshared FTC_CMapCache ttfcachecmap;
179 __gshared FTC_ImageCache ttfcacheimage;
182 shared static ~this () {
183 if (ttflibrary) {
184 if (ttfcache) {
185 FTC_Manager_Done(ttfcache);
186 ttfcache = null;
188 FT_Done_FreeType(ttflibrary);
189 ttflibrary = null;
194 enum FontID = cast(FTC_FaceID)1;
197 extern(C) nothrow {
198 void ttfFontFinalizer (void* obj) {
199 import core.stdc.stdlib : free;
200 if (obj is null) return;
201 auto tf = cast(FT_Face)obj;
202 if (tf.generic.data !is ttfontdata) return;
203 if (ttfontdata !is null) {
204 //conwriteln("TTF CACHE: freeing loaded font...");
205 free(ttfontdata);
206 ttfontdata = null;
207 ttfontdatasize = 0;
211 FT_Error ttfFontLoader (FTC_FaceID face_id, FT_Library library, FT_Pointer request_data, FT_Face* aface) {
212 if (face_id == FontID) {
213 try {
214 if (ttfontdata is null) {
215 //conwriteln("TTF CACHE: loading '", chiFontFile, "'...");
216 import core.stdc.stdlib : malloc;
217 auto fl = VFile(chiFontFile);
218 auto fsz = fl.size;
219 if (fsz < 16 || fsz > int.max/8) throw new Exception("invalid ttf size");
220 ttfontdatasize = cast(uint)fsz;
221 ttfontdata = cast(ubyte*)malloc(ttfontdatasize);
222 if (ttfontdata is null) assert(0, "out of memory");
223 fl.rawReadExact(ttfontdata[0..ttfontdatasize]);
225 auto res = FT_New_Memory_Face(library, cast(const(FT_Byte)*)ttfontdata, ttfontdatasize, 0, aface);
226 if (res != 0) throw new Exception("error loading ttf: '"~chiFontFile~"'");
227 (*aface).generic.data = ttfontdata;
228 (*aface).generic.finalizer = &ttfFontFinalizer;
229 } catch (Exception e) {
230 if (ttfontdata !is null) {
231 import core.stdc.stdlib : free;
232 free(ttfontdata);
233 ttfontdata = null;
234 ttfontdatasize = 0;
236 conwriteln("ERROR loading font: ", e.msg);
237 return FT_Err_Cannot_Open_Resource;
239 return FT_Err_Ok;
240 } else {
241 conwriteln("TTF CACHE: invalid font id");
243 return FT_Err_Cannot_Open_Resource;
248 void ttfLoad () nothrow {
249 if (FT_Init_FreeType(&ttflibrary)) assert(0, "can't initialize FreeType");
250 if (FTC_Manager_New(ttflibrary, 0, 0, 0, &ttfFontLoader, null, &ttfcache)) assert(0, "can't initialize FreeType cache manager");
251 if (FTC_CMapCache_New(ttfcache, &ttfcachecmap)) assert(0, "can't initialize FreeType cache manager");
252 if (FTC_ImageCache_New(ttfcache, &ttfcacheimage)) assert(0, "can't initialize FreeType cache manager");
254 FTC_ScalerRec fsc;
255 fsc.face_id = FontID;
256 fsc.width = 0;
257 fsc.height = fontSize;
258 fsc.pixel = 1; // size in pixels
260 FT_Size ttfontsz;
261 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) assert(0, "cannot find FreeType font");
262 fontHeight = cast(int)(ttfontsz.metrics.height>>6); // 26.6
263 fontBaselineOfs = cast(int)((ttfontsz.metrics.height+ttfontsz.metrics.descender)>>6);
264 if (fontHeight < 2 || fontHeight > 128) assert(0, "invalid FreeType font metrics");
266 //conwriteln("TTF CACHE initialized.");
270 void initFontEngine () nothrow {
271 if (ttflibrary is null) {
272 import std.string : fromStringz, toStringz;
273 if (!FcInit()) assert(0, "cannot init fontconfig");
274 FcPattern* pat = FcNameParse(chiFontName.toStringz);
275 if (pat is null) assert(0, "cannot parse font name");
276 if (!FcConfigSubstitute(null, pat, FcMatchPattern)) assert(0, "cannot find fontconfig substitute");
277 FcDefaultSubstitute(pat);
278 // find the font
279 FcResult result;
280 FcPattern* font = FcFontMatch(null, pat, &result);
281 if (font !is null) {
282 char* file = null;
283 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
284 //conwriteln("font file: [", file, "]");
285 chiFontFile = file.fromStringz.idup;
287 double pixelsize;
288 if (FcPatternGetDouble(font, FC_PIXEL_SIZE, 0, &pixelsize) == FcResultMatch) {
289 //conwriteln("pixel size: ", pixelsize);
290 fontSize = cast(int)pixelsize;
293 FcPatternDestroy(pat);
294 // arbitrary limits
295 if (fontSize < 6) fontSize = 6;
296 if (fontSize > 42) fontSize = 42;
297 ttfLoad();
302 // ////////////////////////////////////////////////////////////////////////// //
303 public void utfByDChar (const(char)[] s, scope void delegate (dchar ch) nothrow @trusted dg) nothrow @trusted {
304 if (dg is null) return;
305 Utf8DecoderFast dc;
306 foreach (char ch; s) {
307 if (dc.decode(cast(ubyte)ch)) dg(dc.complete ? dc.codepoint : dc.replacement);
312 public void utfByDCharSPos (const(char)[] s, scope void delegate (dchar ch, usize stpos) nothrow @trusted dg) nothrow @trusted {
313 if (dg is null) return;
314 Utf8DecoderFast dc;
315 usize stpos = 0;
316 foreach (immutable idx, char ch; s) {
317 if (dc.decode(cast(ubyte)ch)) {
318 dg(dc.complete ? dc.codepoint : dc.replacement, stpos);
319 stpos = idx+1;
325 // ////////////////////////////////////////////////////////////////////////// //
327 void drawFTBitmap (int x, int y, in ref FT_Bitmap bitmap, uint clr) nothrow @trusted @nogc {
328 if (bitmap.pixel_mode != FT_PIXEL_MODE_MONO) return; // alas
329 if (bitmap.rows < 1 || bitmap.width < 1) return; // nothing to do
330 if (gxIsTransparent(clr)) return; // just in case
331 // prepare
332 bool topdown = true;
333 const(ubyte)* src = bitmap.buffer;
334 int spt = bitmap.pitch;
335 if (spt < 0) {
336 topdown = false;
337 spt = -spt;
338 src += spt*(bitmap.rows-1);
340 if (!gxIsSolid(clr)) {
341 // let this be slow
342 foreach (immutable int dy; 0..bitmap.rows) {
343 ubyte count = 0, b = 0;
344 auto ss = src;
345 foreach (immutable int dx; 0..bitmap.width) {
346 if (count-- == 0) { count = 7; b = *ss++; } else b <<= 1;
347 if (b&0x80) gxPutPixel(x+dx, y, clr);
349 ++y;
350 if (topdown) src += spt; else src -= spt;
352 return;
354 // check if we can use fastest path
355 auto brc = GxRect(x, y, bitmap.width, bitmap.rows);
356 if (gxClipRect.contains(brc) && GxRect(0, 0, VBufWidth, VBufHeight).contains(brc)) {
357 // yay, the fastest one!
358 uint* dptr = vglTexBuf+y*VBufWidth+x;
359 foreach (immutable int dy; 0..bitmap.rows) {
360 ubyte count = 0, b = 0;
361 auto ss = src;
362 auto curptr = dptr;
363 foreach (immutable int dx; 0..bitmap.width) {
364 if (count-- == 0) { count = 7; b = *ss++; } else b <<= 1;
365 if (b&0x80) *curptr = clr;
366 ++curptr;
368 if (topdown) src += spt; else src -= spt;
369 dptr += VBufWidth;
371 } else {
372 // do it slow
373 foreach (immutable int dy; 0..bitmap.rows) {
374 ubyte count = 0, b = 0;
375 auto ss = src;
376 foreach (immutable int dx; 0..bitmap.width) {
377 if (count-- == 0) { count = 7; b = *ss++; } else b <<= 1;
378 if (b&0x80) gxSetPixel(x+dx, y, clr);
380 ++y;
381 if (topdown) src += spt; else src -= spt;
388 // y is baseline; returns advance
389 int ttfDrawGlyph (Display* dpy, Drawable d, GC gc, bool firstchar, int x, int y, int glyphidx, uint clr) {
390 enum mono = true;
391 if (glyphidx == 0) return 0;
393 FTC_ImageTypeRec fimg;
394 fimg.face_id = FontID;
395 fimg.width = 0;
396 fimg.height = fontSize;
397 static if (mono) {
398 fimg.flags = FT_LOAD_TARGET_MONO|(gxIsTransparent(clr) ? 0 : FT_LOAD_MONOCHROME|FT_LOAD_RENDER);
399 } else {
400 fimg.flags = (gxIsTransparent(clr) ? 0 : FT_LOAD_RENDER);
403 FT_Glyph fg;
404 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyphidx, &fg, null)) return 0;
406 int advdec = 0;
407 if (!gxIsTransparent(clr)) {
408 if (fg.format != FT_GLYPH_FORMAT_BITMAP) return 0;
409 FT_BitmapGlyph fgi = cast(FT_BitmapGlyph)fg;
410 int x0 = x+fgi.left;
412 if (firstchar && fgi.bitmap.width > 0) { x0 -= fgi.left; advdec = fgi.left; }
413 drawFTBitmap(x0, y-fgi.top, fgi.bitmap, clr);
415 auto glp = wantGlyph(glyphidx, fgi);
416 if (glp !is null) {
417 // erase the parts not covered by the mask
418 XSetFunction(dpy, gc, GXand);
419 XSetForeground(dpy, gc, 0);
420 XSetBackground(dpy, gc, ~0);
421 XCopyPlane(dpy, cast(Drawable)glp.px, d, gc, 0, 0, glp.dims.width, glp.dims.height, x0, y-fgi.top, 1);
422 // draw pixels
423 XSetFunction(dpy, gc, GXor);
424 XSetForeground(dpy, gc, clr);
425 XSetBackground(dpy, gc, 0);
426 XCopyPlane(dpy, cast(Drawable)glp.px, d, gc, 0, 0, glp.dims.width, glp.dims.height, x0, y-fgi.top, 1);
427 // done
428 XSetFunction(dpy, gc, GXcopy);
431 return cast(int)(fg.advance.x>>16)-advdec;
435 int ttfGetKerning (int gl0idx, int gl1idx) nothrow @trusted {
436 if (gl0idx == 0 || gl1idx == 0) return 0;
438 FTC_ScalerRec fsc;
439 fsc.face_id = FontID;
440 fsc.width = 0;
441 fsc.height = fontSize;
442 fsc.pixel = 1; // size in pixels
444 FT_Size ttfontsz;
445 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) return 0;
446 if (!FT_HAS_KERNING(ttfontsz.face)) return 0;
448 FT_Vector kk;
449 if (FT_Get_Kerning(ttfontsz.face, gl0idx, gl1idx, FT_KERNING_UNSCALED, &kk)) return 0;
450 if (!kk.x) return 0;
451 auto kadvfrac = FT_MulFix(kk.x, ttfontsz.metrics.x_scale); // 1/64 of pixel
452 return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
456 // ////////////////////////////////////////////////////////////////////////// //
457 public int gxCharWidth (dchar ch) nothrow @trusted {
458 initFontEngine();
460 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
461 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, '\u25A1');
462 if (glyph == 0) return 0;
464 FTC_ImageTypeRec fimg;
465 fimg.face_id = FontID;
466 fimg.width = 0;
467 fimg.height = fontSize;
468 fimg.flags = FT_LOAD_TARGET_MONO;
470 FT_Glyph fg;
471 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null)) return -666;
473 int res = cast(int)(fg.advance.x>>16);
474 return (res > 0 ? res : 0);
478 // ////////////////////////////////////////////////////////////////////////// //
479 // return char width
480 public int gxDrawChar (Display* dpy, Drawable d, GC gc, int x, int y, dchar ch, uint clr, int prevcp=-1) {
481 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
482 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, '\u25A1');
483 if (glyph == 0) return 0;
484 int kadv = ttfGetKerning((prevcp >= 0 ? FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, prevcp) : 0), glyph);
485 return ttfDrawGlyph(dpy, d, gc, false, x+kadv, y+fontBaselineOfs, glyph, clr);
489 // ////////////////////////////////////////////////////////////////////////// //
490 public struct GxKerning {
491 int prevgidx = 0;
492 int wdt = 0;
493 int lastadv = 0;
494 int lastcw = 0;
495 int tabsize = 0;
496 bool firstchar = true;
498 nothrow @trusted:
499 this (int atabsize, bool firstCharIsFull=false) {
500 initFontEngine();
501 firstchar = !firstCharIsFull;
502 if ((tabsize = (atabsize > 0 ? atabsize : 0)) != 0) tabsize = tabsize*gxCharWidth(' ');
505 void reset (int atabsize) {
506 initFontEngine();
507 prevgidx = 0;
508 wdt = 0;
509 lastadv = 0;
510 lastcw = 0;
511 if ((tabsize = (atabsize > 0 ? atabsize : 0)) != 0) tabsize = tabsize*gxCharWidth(' ');
512 firstchar = true;
515 // tab length for current position
516 int tablength () { pragma(inline, true); return (tabsize > 0 ? (wdt/tabsize+1)*tabsize-wdt : 0); }
518 int fixWidthPre (dchar ch) {
519 immutable int prevgl = prevgidx;
520 wdt += lastadv;
521 lastadv = 0;
522 lastcw = 0;
523 prevgidx = 0;
524 if (ch == '\t' && tabsize > 0) {
525 // tab
526 lastadv = lastcw = tablength;
527 firstchar = false;
528 } else {
529 initFontEngine();
530 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
531 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, '\u25A1');
532 if (glyph != 0) {
533 wdt += ttfGetKerning(prevgl, glyph);
535 FTC_ImageTypeRec fimg;
536 fimg.face_id = FontID;
537 fimg.width = 0;
538 fimg.height = fontSize;
539 version(none) {
540 fimg.flags = FT_LOAD_TARGET_MONO;
541 } else {
542 fimg.flags = FT_LOAD_TARGET_MONO|FT_LOAD_MONOCHROME|FT_LOAD_RENDER;
545 FT_Glyph fg;
546 version(none) {
547 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null) == 0) {
548 prevgidx = glyph;
549 lastadv = fg.advance.x>>16;
551 } else {
552 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null) == 0) {
553 int advdec = 0;
554 if (fg.format == FT_GLYPH_FORMAT_BITMAP) {
555 FT_BitmapGlyph fgi = cast(FT_BitmapGlyph)fg;
556 if (firstchar && fgi.bitmap.width > 0) {
557 lastcw = fgi.bitmap.width;
558 advdec = fgi.left;
559 if (lastcw < 1) { advdec = 0; lastcw = cast(int)(fg.advance.x>>16); }
560 } else {
561 lastcw = fgi.left+fgi.bitmap.width;
562 if (lastcw < 1) lastcw = cast(int)(fg.advance.x>>16);
565 prevgidx = glyph;
566 lastadv = cast(int)(fg.advance.x>>16)-advdec;
567 firstchar = false;
572 return wdt;
575 @property int finalWidth () const { pragma(inline, true); return wdt+/*lastadv*/lastcw; }
577 // BUGGY!
578 @property int nextCharOfs () const { pragma(inline, true); return wdt+lastadv; }
580 @property int currOfs () const { pragma(inline, true); return wdt; }
582 @property int nextOfsNoSpacing () const { pragma(inline, true); return wdt+lastcw; }
586 // ////////////////////////////////////////////////////////////////////////// //
587 public struct GxDrawTextOptions {
588 int tabsize = 0;
589 uint clr = gxTransparent;
590 bool firstCharIsFull = false;
592 static pure nothrow @safe @nogc:
593 auto Color (uint aclr) { pragma(inline, true); return GxDrawTextOptions(0, aclr, false); }
594 auto Tab (int atabsize) { pragma(inline, true); return GxDrawTextOptions(atabsize, gxTransparent, false); }
595 auto TabColor (int atabsize, uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, false); }
596 auto TabColorFirstFull (int atabsize, uint aclr, bool fcf) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, fcf); }
597 auto ColorNFC (uint aclr) { pragma(inline, true); return GxDrawTextOptions(0, aclr, true); }
598 auto TabColorNFC (int atabsize, uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, true); }
599 // more ctors?
602 public struct GxDrawTextState {
603 usize spos; // current codepoint starting position
604 usize epos; // current codepoint ending position (exclusive; i.e. *after* codepoint)
605 int curx; // current x (before drawing the glyph)
609 // delegate should return color
610 public int gxDrawTextUtf(R) (Display* dpy, Drawable d, GC gc, in auto ref GxDrawTextOptions opt, int x, int y, auto ref R srng, uint delegate (in ref GxDrawTextState state) nothrow @trusted clrdg=null)
611 if (Imp!"std.range.primitives".isInputRange!R && is(Imp!"std.range.primitives".ElementEncodingType!R == char))
613 // rely on the assumption that font face won't be unloaded while we are in this function
614 initFontEngine();
616 GxDrawTextState state;
618 y += fontBaselineOfs;
620 immutable int tabpix = (opt.tabsize > 0 ? opt.tabsize*gxCharWidth(' ') : 0);
622 FT_Size ttfontsz;
624 int prevglyph = 0;
625 immutable int stx = x;
627 bool dokern = true;
628 bool firstchar = !opt.firstCharIsFull;
629 Utf8DecoderFast dc;
631 while (!srng.empty) {
632 immutable ubyte srbyte = cast(ubyte)srng.front;
633 srng.popFront();
634 ++state.epos;
636 if (dc.decode(srbyte)) {
637 int ch = (dc.complete ? dc.codepoint : dc.replacement);
638 int pgl = prevglyph;
639 prevglyph = 0;
640 state.curx = x;
642 if (opt.tabsize > 0) {
643 if (ch == '\t') {
644 firstchar = false;
645 int wdt = x-stx;
646 state.curx = x;
647 x += (wdt/tabpix+1)*tabpix-wdt;
648 if (clrdg !is null) clrdg(state);
649 state.spos = state.epos;
650 continue;
654 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
655 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, '\u25A1');
656 if (glyph != 0) {
657 // kerning
658 int kadv = 0;
659 if (pgl != 0 && dokern) {
660 if (ttfontsz is null) {
661 FTC_ScalerRec fsc;
662 fsc.face_id = FontID;
663 fsc.width = 0;
664 fsc.height = fontSize;
665 fsc.pixel = 1; // size in pixels
666 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) {
667 dokern = false;
668 ttfontsz = null;
669 } else {
670 dokern = (FT_HAS_KERNING(ttfontsz.face) != 0);
673 if (dokern) {
674 FT_Vector kk;
675 if (FT_Get_Kerning(ttfontsz.face, pgl, glyph, FT_KERNING_UNSCALED, &kk) == 0) {
676 if (kk.x) {
677 auto kadvfrac = FT_MulFix(kk.x, ttfontsz.metrics.x_scale); // 1/64 of pixel
678 kadv = cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
683 x += kadv;
684 uint clr = opt.clr;
685 if (clrdg !is null) { state.curx = x; clr = clrdg(state); }
686 x += ttfDrawGlyph(dpy, d, gc, firstchar, x, y, glyph, clr);
687 firstchar = false;
689 state.spos = state.epos;
690 prevglyph = glyph;
694 return x-stx;
698 public int gxDrawTextUtf() (Display* dpy, Drawable d, GC gc, in auto ref GxDrawTextOptions opt, int x, int y, const(char)[] s, uint delegate (in ref GxDrawTextState state) nothrow @trusted clrdg=null) {
699 static struct StrIterator {
700 private:
701 const(char)[] str;
702 public pure nothrow @trusted @nogc:
703 this (const(char)[] s) { pragma(inline, true); str = s; }
704 @property bool empty () const { pragma(inline, true); return (str.length == 0); }
705 @property char front () const { pragma(inline, true); return (str.length ? str.ptr[0] : 0); }
706 void popFront () { if (str.length) str = str[1..$]; }
709 return gxDrawTextUtf(dpy, d, gc, opt, x, y, StrIterator(s), clrdg);
712 public int gxTextHeightUtf () nothrow @trusted { initFontEngine(); return fontHeight; }
714 public int gxTextWidthUtf (const(char)[] s, int tabsize=0, bool firstCharIsFull=false) nothrow @trusted {
715 auto kern = GxKerning(tabsize, firstCharIsFull);
716 s.utfByDChar(delegate (dchar ch) @trusted { kern.fixWidthPre(ch); });
717 return kern.finalWidth;
721 // ////////////////////////////////////////////////////////////////////////// //
722 public int gxDrawTextUtf() (Display* dpy, Drawable d, GC gc, int x, int y, const(char)[] s, uint clr) { return gxDrawTextUtf(dpy, d, gc, GxDrawTextOptions.Color(clr), x, y, s); }
725 // ////////////////////////////////////////////////////////////////////////// //
726 public int gxDrawTextUtf() (SimpleWindow w, int x, int y, const(char)[] s, uint clr) { return gxDrawTextUtf(w.impl.display, cast(Drawable)w.impl.buffer, w.impl.gc, GxDrawTextOptions.Color(clr), x, y, s); }