switched to GPLv3 ONLY, because i don't trust FSF anymore
[amper.git] / egfx / text.d
blob13591cbd0596ff4d9929d3e6250ffe81b9481e37
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, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 module egfx.text;
17 private:
19 import arsd.simpledisplay;
21 import iv.alice;
22 import iv.bclamp;
23 import iv.cmdcon;
24 import iv.fontconfig;
25 import iv.freetype;
26 import iv.utfutil;
27 import iv.vfs;
29 import egfx.base;
32 // ////////////////////////////////////////////////////////////////////////// //
33 // x and y are always in range; !0 is set
34 public __gshared Pixmap delegate (int wdt, int hgt, scope uint delegate (int x, int y) getPixel) gxCreatePixmap1bpp;
35 public __gshared void delegate (Pixmap px) gxDeletePixmap;
38 // ////////////////////////////////////////////////////////////////////////// //
39 enum {
40 GXclear = 0x0, /* 0 */
41 GXand = 0x1, /* src AND dst */
42 GXandReverse = 0x2, /* src AND NOT dst */
43 GXcopy = 0x3, /* src */
44 GXandInverted = 0x4, /* NOT src AND dst */
45 GXnoop = 0x5, /* dst */
46 GXxor = 0x6, /* src XOR dst */
47 GXor = 0x7, /* src OR dst */
48 GXnor = 0x8, /* NOT src AND NOT dst */
49 GXequiv = 0x9, /* NOT src XOR dst */
50 GXinvert = 0xa, /* NOT dst */
51 GXorReverse = 0xb, /* src OR NOT dst */
52 GXcopyInverted = 0xc, /* NOT src */
53 GXorInverted = 0xd, /* NOT src OR dst */
54 GXnand = 0xe, /* NOT src OR NOT dst */
55 GXset = 0xf, /* 1 */
59 // ////////////////////////////////////////////////////////////////////////// //
60 struct GlyphCache {
61 int gidx;
62 Pixmap px;
63 GxRect dims; // x0,y0: image left and top; width,height: real image size; in pixels
64 int advance; // in pixels
65 GlyphCache* prev;
66 GlyphCache* next;
68 @disable this (this);
70 @property bool valid () const pure nothrow @trusted @nogc { pragma(inline, true); return (px != 0); }
72 void clear () {
73 if (px) {
74 gxDeletePixmap(px);
75 px = 0;
76 dims = GxRect.init;
77 advance = 0;
82 // list is sorted by access time; head has the latest access time
83 __gshared GlyphCache* head;
84 __gshared GlyphCache* tail;
85 __gshared GlyphCache*[int] gcache; // by glyph index
86 enum GlyphCacheMaxGlyphs = 256;
89 //TODO: use XImage to transfer bits
90 GlyphCache* wantGlyph (int gidx, FT_BitmapGlyph fgi) {
91 GlyphCache* gcp;
92 auto gcpp = gidx in gcache;
94 if (gcpp !is null) {
95 gcp = *gcpp;
96 // move to list head
97 if (gcp.prev !is null) {
98 gcp.prev.next = gcp.next;
99 if (gcp.next !is null) {
100 gcp.next.prev = gcp.prev;
101 } else {
102 tail = gcp.prev;
104 // add as first
105 gcp.prev = null;
106 gcp.next = head;
107 head = gcp;
109 } else {
110 FT_Bitmap* bitmap = &fgi.bitmap;
111 if (bitmap.pixel_mode != FT_PIXEL_MODE_MONO) return null; // alas
112 if (bitmap.rows < 1 || bitmap.width < 1) return null; // nothing to do
113 // want new glyph
114 if (gcache.length == GlyphCacheMaxGlyphs) {
115 // delete last glyph, reuse it's struct
116 gcp = tail;
117 tail = gcp.prev;
118 tail.next = null;
119 gcache.remove(gcp.gidx);
120 gcp.clear();
121 } else {
122 // create new glyph
123 gcp = new GlyphCache();
126 gcp.gidx = gidx;
128 // draw pixels
129 bool topdown = true;
130 const(ubyte)* src = bitmap.buffer;
131 int spt = bitmap.pitch;
132 if (spt < 0) {
133 topdown = false;
134 spt = -spt;
135 //src += spt*(bitmap.rows-1);
138 gcp.px = gxCreatePixmap1bpp(bitmap.width, bitmap.rows,
139 (int x, int y) {
140 if (!topdown) y = bitmap.rows-y-1;
141 return src[y*spt+x/8]&(0x80>>(x%8));
145 gcp.dims.x0 = fgi.left;
146 gcp.dims.y0 = fgi.top;
147 gcp.dims.width = bitmap.width;
148 gcp.dims.height = bitmap.rows;
149 gcp.advance = cast(int)(fgi.root.advance.x>>16);
151 // add as first
152 gcp.prev = null;
153 gcp.next = head;
154 head = gcp;
155 gcache[gidx] = gcp;
157 return gcp;
161 // ////////////////////////////////////////////////////////////////////////// //
162 //__gshared string chiFontName = "Arial:pixelsize=16";
163 //__gshared string chiFontName = "Verdana:pixelsize=16";
164 __gshared string chiFontName = "Verdana:pixelsize=12";
165 __gshared string chiFontFile;
166 __gshared int fontSize;
167 __gshared int fontHeight;
168 __gshared int fontBaselineOfs;
171 // ////////////////////////////////////////////////////////////////////////// //
172 __gshared ubyte* ttfontdata;
173 __gshared uint ttfontdatasize;
175 __gshared FT_Library ttflibrary;
176 __gshared FTC_Manager ttfcache;
177 __gshared FTC_CMapCache ttfcachecmap;
178 __gshared FTC_ImageCache ttfcacheimage;
181 shared static ~this () {
182 if (ttflibrary) {
183 if (ttfcache) {
184 FTC_Manager_Done(ttfcache);
185 ttfcache = null;
187 FT_Done_FreeType(ttflibrary);
188 ttflibrary = null;
193 enum FontID = cast(FTC_FaceID)1;
196 extern(C) nothrow {
197 void ttfFontFinalizer (void* obj) {
198 import core.stdc.stdlib : free;
199 if (obj is null) return;
200 auto tf = cast(FT_Face)obj;
201 if (tf.generic.data !is ttfontdata) return;
202 if (ttfontdata !is null) {
203 //conwriteln("TTF CACHE: freeing loaded font...");
204 free(ttfontdata);
205 ttfontdata = null;
206 ttfontdatasize = 0;
210 FT_Error ttfFontLoader (FTC_FaceID face_id, FT_Library library, FT_Pointer request_data, FT_Face* aface) {
211 if (face_id == FontID) {
212 try {
213 if (ttfontdata is null) {
214 //conwriteln("TTF CACHE: loading '", chiFontFile, "'...");
215 import core.stdc.stdlib : malloc;
216 auto fl = VFile(chiFontFile);
217 auto fsz = fl.size;
218 if (fsz < 16 || fsz > int.max/8) throw new Exception("invalid ttf size");
219 ttfontdatasize = cast(uint)fsz;
220 ttfontdata = cast(ubyte*)malloc(ttfontdatasize);
221 if (ttfontdata is null) assert(0, "out of memory");
222 fl.rawReadExact(ttfontdata[0..ttfontdatasize]);
224 auto res = FT_New_Memory_Face(library, cast(const(FT_Byte)*)ttfontdata, ttfontdatasize, 0, aface);
225 if (res != 0) throw new Exception("error loading ttf: '"~chiFontFile~"'");
226 (*aface).generic.data = ttfontdata;
227 (*aface).generic.finalizer = &ttfFontFinalizer;
228 } catch (Exception e) {
229 if (ttfontdata !is null) {
230 import core.stdc.stdlib : free;
231 free(ttfontdata);
232 ttfontdata = null;
233 ttfontdatasize = 0;
235 conwriteln("ERROR loading font: ", e.msg);
236 return FT_Err_Cannot_Open_Resource;
238 return FT_Err_Ok;
239 } else {
240 conwriteln("TTF CACHE: invalid font id");
242 return FT_Err_Cannot_Open_Resource;
247 void ttfLoad () nothrow {
248 if (FT_Init_FreeType(&ttflibrary)) assert(0, "can't initialize FreeType");
249 if (FTC_Manager_New(ttflibrary, 0, 0, 0, &ttfFontLoader, null, &ttfcache)) assert(0, "can't initialize FreeType cache manager");
250 if (FTC_CMapCache_New(ttfcache, &ttfcachecmap)) assert(0, "can't initialize FreeType cache manager");
251 if (FTC_ImageCache_New(ttfcache, &ttfcacheimage)) assert(0, "can't initialize FreeType cache manager");
253 FTC_ScalerRec fsc;
254 fsc.face_id = FontID;
255 fsc.width = 0;
256 fsc.height = fontSize;
257 fsc.pixel = 1; // size in pixels
259 FT_Size ttfontsz;
260 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) assert(0, "cannot find FreeType font");
261 fontHeight = cast(int)(ttfontsz.metrics.height>>6); // 26.6
262 fontBaselineOfs = cast(int)((ttfontsz.metrics.height+ttfontsz.metrics.descender)>>6);
263 if (fontHeight < 2 || fontHeight > 128) assert(0, "invalid FreeType font metrics");
265 //conwriteln("TTF CACHE initialized.");
269 void initFontEngine () nothrow {
270 if (ttflibrary is null) {
271 import std.string : fromStringz, toStringz;
272 if (!FcInit()) assert(0, "cannot init fontconfig");
273 FcPattern* pat = FcNameParse(chiFontName.toStringz);
274 if (pat is null) assert(0, "cannot parse font name");
275 if (!FcConfigSubstitute(null, pat, FcMatchPattern)) assert(0, "cannot find fontconfig substitute");
276 FcDefaultSubstitute(pat);
277 // find the font
278 FcResult result;
279 FcPattern* font = FcFontMatch(null, pat, &result);
280 if (font !is null) {
281 char* file = null;
282 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
283 //conwriteln("font file: [", file, "]");
284 chiFontFile = file.fromStringz.idup;
286 double pixelsize;
287 if (FcPatternGetDouble(font, FC_PIXEL_SIZE, 0, &pixelsize) == FcResultMatch) {
288 //conwriteln("pixel size: ", pixelsize);
289 fontSize = cast(int)pixelsize;
292 FcPatternDestroy(pat);
293 // arbitrary limits
294 if (fontSize < 6) fontSize = 6;
295 if (fontSize > 42) fontSize = 42;
296 ttfLoad();
301 // ////////////////////////////////////////////////////////////////////////// //
302 public void utfByDChar (const(char)[] s, scope void delegate (dchar ch) nothrow @trusted dg) nothrow @trusted {
303 if (dg is null) return;
304 Utf8DecoderFast dc;
305 foreach (char ch; s) {
306 if (dc.decode(cast(ubyte)ch)) dg(dc.complete ? dc.codepoint : dc.replacement);
311 public void utfByDCharSPos (const(char)[] s, scope void delegate (dchar ch, usize stpos) nothrow @trusted dg) nothrow @trusted {
312 if (dg is null) return;
313 Utf8DecoderFast dc;
314 usize stpos = 0;
315 foreach (immutable idx, char ch; s) {
316 if (dc.decode(cast(ubyte)ch)) {
317 dg(dc.complete ? dc.codepoint : dc.replacement, stpos);
318 stpos = idx+1;
324 // ////////////////////////////////////////////////////////////////////////// //
326 void drawFTBitmap (int x, int y, in ref FT_Bitmap bitmap, uint clr) nothrow @trusted @nogc {
327 if (bitmap.pixel_mode != FT_PIXEL_MODE_MONO) return; // alas
328 if (bitmap.rows < 1 || bitmap.width < 1) return; // nothing to do
329 if (gxIsTransparent(clr)) return; // just in case
330 // prepare
331 bool topdown = true;
332 const(ubyte)* src = bitmap.buffer;
333 int spt = bitmap.pitch;
334 if (spt < 0) {
335 topdown = false;
336 spt = -spt;
337 src += spt*(bitmap.rows-1);
339 if (!gxIsSolid(clr)) {
340 // let this be slow
341 foreach (immutable int dy; 0..bitmap.rows) {
342 ubyte count = 0, b = 0;
343 auto ss = src;
344 foreach (immutable int dx; 0..bitmap.width) {
345 if (count-- == 0) { count = 7; b = *ss++; } else b <<= 1;
346 if (b&0x80) gxPutPixel(x+dx, y, clr);
348 ++y;
349 if (topdown) src += spt; else src -= spt;
351 return;
353 // check if we can use fastest path
354 auto brc = GxRect(x, y, bitmap.width, bitmap.rows);
355 if (gxClipRect.contains(brc) && GxRect(0, 0, VBufWidth, VBufHeight).contains(brc)) {
356 // yay, the fastest one!
357 uint* dptr = vglTexBuf+y*VBufWidth+x;
358 foreach (immutable int dy; 0..bitmap.rows) {
359 ubyte count = 0, b = 0;
360 auto ss = src;
361 auto curptr = dptr;
362 foreach (immutable int dx; 0..bitmap.width) {
363 if (count-- == 0) { count = 7; b = *ss++; } else b <<= 1;
364 if (b&0x80) *curptr = clr;
365 ++curptr;
367 if (topdown) src += spt; else src -= spt;
368 dptr += VBufWidth;
370 } else {
371 // do it slow
372 foreach (immutable int dy; 0..bitmap.rows) {
373 ubyte count = 0, b = 0;
374 auto ss = src;
375 foreach (immutable int dx; 0..bitmap.width) {
376 if (count-- == 0) { count = 7; b = *ss++; } else b <<= 1;
377 if (b&0x80) gxSetPixel(x+dx, y, clr);
379 ++y;
380 if (topdown) src += spt; else src -= spt;
387 // y is baseline; returns advance
388 int ttfDrawGlyph (Display* dpy, Drawable d, GC gc, bool firstchar, int x, int y, int glyphidx, uint clr) {
389 enum mono = true;
390 if (glyphidx == 0) return 0;
392 FTC_ImageTypeRec fimg;
393 fimg.face_id = FontID;
394 fimg.width = 0;
395 fimg.height = fontSize;
396 static if (mono) {
397 fimg.flags = FT_LOAD_TARGET_MONO|(gxIsTransparent(clr) ? 0 : FT_LOAD_MONOCHROME|FT_LOAD_RENDER);
398 } else {
399 fimg.flags = (gxIsTransparent(clr) ? 0 : FT_LOAD_RENDER);
402 FT_Glyph fg;
403 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyphidx, &fg, null)) return 0;
405 int advdec = 0;
406 if (!gxIsTransparent(clr)) {
407 if (fg.format != FT_GLYPH_FORMAT_BITMAP) return 0;
408 FT_BitmapGlyph fgi = cast(FT_BitmapGlyph)fg;
409 int x0 = x+fgi.left;
411 if (firstchar && fgi.bitmap.width > 0) { x0 -= fgi.left; advdec = fgi.left; }
412 drawFTBitmap(x0, y-fgi.top, fgi.bitmap, clr);
414 auto glp = wantGlyph(glyphidx, fgi);
415 if (glp !is null) {
416 // erase the parts not covered by the mask
417 XSetFunction(dpy, gc, GXand);
418 XSetForeground(dpy, gc, 0);
419 XSetBackground(dpy, gc, ~0);
420 XCopyPlane(dpy, cast(Drawable)glp.px, d, gc, 0, 0, glp.dims.width, glp.dims.height, x0, y-fgi.top, 1);
421 // draw pixels
422 XSetFunction(dpy, gc, GXor);
423 XSetForeground(dpy, gc, clr);
424 XSetBackground(dpy, gc, 0);
425 XCopyPlane(dpy, cast(Drawable)glp.px, d, gc, 0, 0, glp.dims.width, glp.dims.height, x0, y-fgi.top, 1);
426 // done
427 XSetFunction(dpy, gc, GXcopy);
430 return cast(int)(fg.advance.x>>16)-advdec;
434 int ttfGetKerning (int gl0idx, int gl1idx) nothrow @trusted {
435 if (gl0idx == 0 || gl1idx == 0) return 0;
437 FTC_ScalerRec fsc;
438 fsc.face_id = FontID;
439 fsc.width = 0;
440 fsc.height = fontSize;
441 fsc.pixel = 1; // size in pixels
443 FT_Size ttfontsz;
444 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) return 0;
445 if (!FT_HAS_KERNING(ttfontsz.face)) return 0;
447 FT_Vector kk;
448 if (FT_Get_Kerning(ttfontsz.face, gl0idx, gl1idx, FT_KERNING_UNSCALED, &kk)) return 0;
449 if (!kk.x) return 0;
450 auto kadvfrac = FT_MulFix(kk.x, ttfontsz.metrics.x_scale); // 1/64 of pixel
451 return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
455 // ////////////////////////////////////////////////////////////////////////// //
456 public int gxCharWidth (dchar ch) nothrow @trusted {
457 initFontEngine();
459 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
460 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, '\u25A1');
461 if (glyph == 0) return 0;
463 FTC_ImageTypeRec fimg;
464 fimg.face_id = FontID;
465 fimg.width = 0;
466 fimg.height = fontSize;
467 fimg.flags = FT_LOAD_TARGET_MONO;
469 FT_Glyph fg;
470 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null)) return -666;
472 int res = cast(int)(fg.advance.x>>16);
473 return (res > 0 ? res : 0);
477 // ////////////////////////////////////////////////////////////////////////// //
478 // return char width
479 public int gxDrawChar (Display* dpy, Drawable d, GC gc, int x, int y, dchar ch, uint clr, int prevcp=-1) {
480 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
481 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, '\u25A1');
482 if (glyph == 0) return 0;
483 int kadv = ttfGetKerning((prevcp >= 0 ? FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, prevcp) : 0), glyph);
484 return ttfDrawGlyph(dpy, d, gc, false, x+kadv, y+fontBaselineOfs, glyph, clr);
488 // ////////////////////////////////////////////////////////////////////////// //
489 public struct GxKerning {
490 int prevgidx = 0;
491 int wdt = 0;
492 int lastadv = 0;
493 int lastcw = 0;
494 int tabsize = 0;
495 bool firstchar = true;
497 nothrow @trusted:
498 this (int atabsize, bool firstCharIsFull=false) {
499 initFontEngine();
500 firstchar = !firstCharIsFull;
501 if ((tabsize = (atabsize > 0 ? atabsize : 0)) != 0) tabsize = tabsize*gxCharWidth(' ');
504 void reset (int atabsize) {
505 initFontEngine();
506 prevgidx = 0;
507 wdt = 0;
508 lastadv = 0;
509 lastcw = 0;
510 if ((tabsize = (atabsize > 0 ? atabsize : 0)) != 0) tabsize = tabsize*gxCharWidth(' ');
511 firstchar = true;
514 // tab length for current position
515 int tablength () { pragma(inline, true); return (tabsize > 0 ? (wdt/tabsize+1)*tabsize-wdt : 0); }
517 int fixWidthPre (dchar ch) {
518 immutable int prevgl = prevgidx;
519 wdt += lastadv;
520 lastadv = 0;
521 lastcw = 0;
522 prevgidx = 0;
523 if (ch == '\t' && tabsize > 0) {
524 // tab
525 lastadv = lastcw = tablength;
526 firstchar = false;
527 } else {
528 initFontEngine();
529 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
530 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, '\u25A1');
531 if (glyph != 0) {
532 wdt += ttfGetKerning(prevgl, glyph);
534 FTC_ImageTypeRec fimg;
535 fimg.face_id = FontID;
536 fimg.width = 0;
537 fimg.height = fontSize;
538 version(none) {
539 fimg.flags = FT_LOAD_TARGET_MONO;
540 } else {
541 fimg.flags = FT_LOAD_TARGET_MONO|FT_LOAD_MONOCHROME|FT_LOAD_RENDER;
544 FT_Glyph fg;
545 version(none) {
546 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null) == 0) {
547 prevgidx = glyph;
548 lastadv = fg.advance.x>>16;
550 } else {
551 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null) == 0) {
552 int advdec = 0;
553 if (fg.format == FT_GLYPH_FORMAT_BITMAP) {
554 FT_BitmapGlyph fgi = cast(FT_BitmapGlyph)fg;
555 if (firstchar && fgi.bitmap.width > 0) {
556 lastcw = fgi.bitmap.width;
557 advdec = fgi.left;
558 if (lastcw < 1) { advdec = 0; lastcw = cast(int)(fg.advance.x>>16); }
559 } else {
560 lastcw = fgi.left+fgi.bitmap.width;
561 if (lastcw < 1) lastcw = cast(int)(fg.advance.x>>16);
564 prevgidx = glyph;
565 lastadv = cast(int)(fg.advance.x>>16)-advdec;
566 firstchar = false;
571 return wdt;
574 @property int finalWidth () const { pragma(inline, true); return wdt+/*lastadv*/lastcw; }
576 // BUGGY!
577 @property int nextCharOfs () const { pragma(inline, true); return wdt+lastadv; }
579 @property int currOfs () const { pragma(inline, true); return wdt; }
581 @property int nextOfsNoSpacing () const { pragma(inline, true); return wdt+lastcw; }
585 // ////////////////////////////////////////////////////////////////////////// //
586 public struct GxDrawTextOptions {
587 int tabsize = 0;
588 uint clr = gxTransparent;
589 bool firstCharIsFull = false;
591 static pure nothrow @safe @nogc:
592 auto Color (uint aclr) { pragma(inline, true); return GxDrawTextOptions(0, aclr, false); }
593 auto Tab (int atabsize) { pragma(inline, true); return GxDrawTextOptions(atabsize, gxTransparent, false); }
594 auto TabColor (int atabsize, uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, false); }
595 auto TabColorFirstFull (int atabsize, uint aclr, bool fcf) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, fcf); }
596 auto ColorNFC (uint aclr) { pragma(inline, true); return GxDrawTextOptions(0, aclr, true); }
597 auto TabColorNFC (int atabsize, uint aclr) { pragma(inline, true); return GxDrawTextOptions(atabsize, aclr, true); }
598 // more ctors?
601 public struct GxDrawTextState {
602 usize spos; // current codepoint starting position
603 usize epos; // current codepoint ending position (exclusive; i.e. *after* codepoint)
604 int curx; // current x (before drawing the glyph)
608 // delegate should return color
609 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)
610 if (Imp!"std.range.primitives".isInputRange!R && is(Imp!"std.range.primitives".ElementEncodingType!R == char))
612 // rely on the assumption that font face won't be unloaded while we are in this function
613 initFontEngine();
615 GxDrawTextState state;
617 y += fontBaselineOfs;
619 immutable int tabpix = (opt.tabsize > 0 ? opt.tabsize*gxCharWidth(' ') : 0);
621 FT_Size ttfontsz;
623 int prevglyph = 0;
624 immutable int stx = x;
626 bool dokern = true;
627 bool firstchar = !opt.firstCharIsFull;
628 Utf8DecoderFast dc;
630 while (!srng.empty) {
631 immutable ubyte srbyte = cast(ubyte)srng.front;
632 srng.popFront();
633 ++state.epos;
635 if (dc.decode(srbyte)) {
636 int ch = (dc.complete ? dc.codepoint : dc.replacement);
637 int pgl = prevglyph;
638 prevglyph = 0;
639 state.curx = x;
641 if (opt.tabsize > 0) {
642 if (ch == '\t') {
643 firstchar = false;
644 int wdt = x-stx;
645 state.curx = x;
646 x += (wdt/tabpix+1)*tabpix-wdt;
647 if (clrdg !is null) clrdg(state);
648 state.spos = state.epos;
649 continue;
653 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, ch);
654 if (glyph == 0) glyph = FTC_CMapCache_Lookup(ttfcachecmap, FontID, -1, '\u25A1');
655 if (glyph != 0) {
656 // kerning
657 int kadv = 0;
658 if (pgl != 0 && dokern) {
659 if (ttfontsz is null) {
660 FTC_ScalerRec fsc;
661 fsc.face_id = FontID;
662 fsc.width = 0;
663 fsc.height = fontSize;
664 fsc.pixel = 1; // size in pixels
665 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) {
666 dokern = false;
667 ttfontsz = null;
668 } else {
669 dokern = (FT_HAS_KERNING(ttfontsz.face) != 0);
672 if (dokern) {
673 FT_Vector kk;
674 if (FT_Get_Kerning(ttfontsz.face, pgl, glyph, FT_KERNING_UNSCALED, &kk) == 0) {
675 if (kk.x) {
676 auto kadvfrac = FT_MulFix(kk.x, ttfontsz.metrics.x_scale); // 1/64 of pixel
677 kadv = cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
682 x += kadv;
683 uint clr = opt.clr;
684 if (clrdg !is null) { state.curx = x; clr = clrdg(state); }
685 x += ttfDrawGlyph(dpy, d, gc, firstchar, x, y, glyph, clr);
686 firstchar = false;
688 state.spos = state.epos;
689 prevglyph = glyph;
693 return x-stx;
697 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) {
698 static struct StrIterator {
699 private:
700 const(char)[] str;
701 public pure nothrow @trusted @nogc:
702 this (const(char)[] s) { pragma(inline, true); str = s; }
703 @property bool empty () const { pragma(inline, true); return (str.length == 0); }
704 @property char front () const { pragma(inline, true); return (str.length ? str.ptr[0] : 0); }
705 void popFront () { if (str.length) str = str[1..$]; }
708 return gxDrawTextUtf(dpy, d, gc, opt, x, y, StrIterator(s), clrdg);
711 public int gxTextHeightUtf () nothrow @trusted { initFontEngine(); return fontHeight; }
713 public int gxTextWidthUtf (const(char)[] s, int tabsize=0, bool firstCharIsFull=false) nothrow @trusted {
714 auto kern = GxKerning(tabsize, firstCharIsFull);
715 s.utfByDChar(delegate (dchar ch) @trusted { kern.fixWidthPre(ch); });
716 return kern.finalWidth;
720 // ////////////////////////////////////////////////////////////////////////// //
721 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); }
724 // ////////////////////////////////////////////////////////////////////////// //
725 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); }