egra: some agg mini optimisations (rendering, hittest)
[iv.d.git] / skeletons / fonts / sdpygl_ft_cached.d
blob2f0f046fde73d9a8c4ecad45dcca0ba44a77ee26
1 import arsd.color;
2 import arsd.simpledisplay;
4 //import iv.bclamp;
5 import iv.cmdcon;
6 import iv.cmdcongl;
7 import iv.utfutil;
8 import iv.vfs;
10 import iv.freetype;
13 // ////////////////////////////////////////////////////////////////////////// //
14 public struct GxPoint {
15 public:
16 int x, y; ///
18 pure nothrow @safe @nogc:
19 this() (in auto ref GxPoint p) { pragma(inline, true); x = p.x; y = p.y; } ///
20 this (int ax, int ay) { pragma(inline, true); x = ax; y = ay; } ///
21 void opAssign() (in auto ref GxPoint p) { pragma(inline, true); x = p.x; y = p.y; } ///
22 bool opEquals() (in auto ref GxPoint p) const { pragma(inline, true); return (p.x == x && p.y == y); } ///
23 ///
24 int opCmp() (in auto ref GxPoint p) const {
25 pragma(inline, true);
26 if (auto d0 = y-p.y) return (d0 < 0 ? -1 : 1);
27 else if (auto d1 = x-p.x) return (d1 < 0 ? -1 : 1);
28 else return 0;
32 public struct GxRect {
33 public:
34 int x0, y0; ///
35 int width = -1; // <0: invalid rect
36 int height = -1; // <0: invalid rect
38 alias left = x0; ///
39 alias top = y0; ///
40 alias right = x1; ///
41 alias bottom = y1; ///
43 ///
44 string toString () const @trusted nothrow {
45 if (valid) {
46 import core.stdc.stdio : snprintf;
47 char[128] buf = void;
48 return buf[0..snprintf(buf.ptr, buf.length, "(%d,%d)-(%d,%d)", x0, y0, x0+width-1, y0+height-1)].idup;
49 } else {
50 return "(invalid-rect)";
54 pure nothrow @safe @nogc:
55 ///
56 this() (in auto ref GxRect rc) { pragma(inline, true); x0 = rc.x0; y0 = rc.y0; width = rc.width; height = rc.height; } ///
58 ///
59 this (int ax0, int ay0, int awidth, int aheight) {
60 pragma(inline, true);
61 x0 = ax0;
62 y0 = ay0;
63 width = awidth;
64 height = aheight;
67 ///
68 this() (in auto ref GxPoint xy0, int awidth, int aheight) {
69 pragma(inline, true);
70 x0 = xy0.x;
71 y0 = xy0.y;
72 width = awidth;
73 height = aheight;
76 ///
77 this() (in auto ref GxPoint xy0, in auto ref GxPoint xy1) {
78 pragma(inline, true);
79 x0 = xy0.x;
80 y0 = xy0.y;
81 width = xy1.x-xy0.x+1;
82 height = xy1.y-xy0.y+1;
85 void opAssign() (in auto ref GxRect rc) { pragma(inline, true); x0 = rc.x0; y0 = rc.y0; width = rc.width; height = rc.height; } ///
86 bool opEquals() (in auto ref GxRect rc) const { pragma(inline, true); return (rc.x0 == x0 && rc.y0 == y0 && rc.width == width && rc.height == height); } ///
87 ///
88 int opCmp() (in auto ref GxRect p) const {
89 if (auto d0 = y0-rc.y0) return (d0 < 0 ? -1 : 1);
90 if (auto d1 = x0-rc.x0) return (d1 < 0 ? -1 : 1);
91 if (auto d2 = width*height-rc.width*rc.height) return (d2 < 0 ? -1 : 1);
92 return 0;
95 @property bool valid () const { pragma(inline, true); return (width >= 0 && height >= 0); } ///
96 @property bool invalid () const { pragma(inline, true); return (width < 0 || height < 0); } ///
97 @property bool empty () const { pragma(inline, true); return (width <= 0 || height <= 0); } /// invalid rects are empty
99 void invalidate () { pragma(inline, true); width = height = -1; } ///
101 @property GxPoint lefttop () const { pragma(inline, true); return GxPoint(x0, y0); } ///
102 @property GxPoint righttop () const { pragma(inline, true); return GxPoint(x0+width-1, y0); } ///
103 @property GxPoint leftbottom () const { pragma(inline, true); return GxPoint(x0, y0+height-1); } ///
104 @property GxPoint rightbottom () const { pragma(inline, true); return GxPoint(x0+width-1, y0+height-1); } ///
106 alias topleft = lefttop; ///
107 alias topright = righttop; ///
108 alias bottomleft = leftbottom; ///
109 alias bottomright = rightbottom; ///
111 @property int x1 () const { pragma(inline, true); return (width > 0 ? x0+width-1 : x0-1); } ///
112 @property int y1 () const { pragma(inline, true); return (height > 0 ? y0+height-1 : y0-1); } ///
114 @property void x1 (in int val) { pragma(inline, true); width = val-x0+1; } ///
115 @property void y1 (in int val) { pragma(inline, true); height = val-y0+1; } ///
118 bool inside() (in auto ref GxPoint p) const {
119 pragma(inline, true);
120 return (width >= 0 && height >= 0 ? (p.x >= x0 && p.y >= y0 && p.x < x0+width && p.y < y0+height) : false);
123 /// ditto
124 bool inside (in int ax, in int ay) const {
125 pragma(inline, true);
126 return (width >= 0 && height >= 0 ? (ax >= x0 && ay >= y0 && ax < x0+width && ay < y0+height) : false);
129 /// is `r` inside `this`?
130 bool inside() (in auto ref GxRect r) const {
131 pragma(inline, true);
132 return
133 !empty && !r.empty &&
134 r.x >= x0 && r.y >= y0 &&
135 r.x1 <= x1 && r.y1 <= y1;
138 /// is `r` and `this` overlaps?
139 bool overlap() (in auto ref GxRect r) const {
140 pragma(inline, true);
141 return
142 !empty && !r.empty &&
143 x <= r.x1 && r.x <= x1 && y <= r.y1 && r.y <= y1;
146 /// extend `this` so it will include `r`
147 void include() (in auto ref GxRect r) {
148 pragma(inline, true);
149 if (!r.empty) {
150 if (empty) {
151 x0 = r.x;
152 y0 = r.y;
153 width = r.width;
154 height = r.height;
155 } else {
156 if (r.x < x0) x0 = r.x;
157 if (r.y < y0) y0 = r.y;
158 if (r.x1 > x1) x1 = r.x1;
159 if (r.y1 > y1) y1 = r.y1;
164 /// clip `this` so it will not be larger than `r`
165 bool intersect() (in auto ref GxRect r) {
166 if (r.invalid || invalid) { width = height = -1; return false; }
167 if (r.empty || empty) { width = height = 0; return false; }
168 if (r.y1 < y0 || r.x1 < x0 || r.x0 > x1 || r.y0 > y1) { width = height = 0; return false; }
169 // rc is at least partially inside this rect
170 if (x0 < r.x0) x0 = r.x0;
171 if (y0 < r.y0) y0 = r.y0;
172 if (x1 > r.x1) x1 = r.x1;
173 if (y1 > r.y1) y1 = r.y1;
174 assert(!empty); // yeah, always
175 return true;
179 void shrinkBy (int dx, int dy) {
180 pragma(inline, true);
181 if ((dx || dy) && valid) {
182 x0 += dx;
183 y0 += dy;
184 width -= dx*2;
185 height -= dy*2;
190 void growBy (int dx, int dy) {
191 pragma(inline, true);
192 if ((dx || dy) && valid) {
193 x0 -= dx;
194 y0 -= dy;
195 width += dx*2;
196 height += dy*2;
201 void set (int ax0, int ay0, int awidth, int aheight) {
202 pragma(inline, true);
203 x0 = ax0;
204 y0 = ay0;
205 width = awidth;
206 height = aheight;
210 void moveLeftTopBy (int dx, int dy) {
211 pragma(inline, true);
212 x0 += dx;
213 y0 += dy;
214 width -= dx;
215 height -= dy;
218 alias moveTopLeftBy = moveLeftTopBy; /// ditto
221 void moveRightBottomBy (int dx, int dy) {
222 pragma(inline, true);
223 width += dx;
224 height += dy;
227 alias moveBottomRightBy = moveRightBottomBy; /// ditto
230 void moveBy (int dx, int dy) {
231 pragma(inline, true);
232 x0 += dx;
233 y0 += dy;
237 void moveTo (int nx, int ny) {
238 pragma(inline, true);
239 x0 = nx;
240 y0 = ny;
244 * clip (x,y,len) stripe to this rect
246 * Params:
247 * x = stripe start (not relative to rect)
248 * y = stripe start (not relative to rect)
249 * len = stripe length
251 * Returns:
252 * x = fixed x
253 * len = fixed length
254 * leftSkip = how much cells skipped at the left side
255 * result = false if stripe is completely clipped out
257 * TODO:
258 * overflows
260 bool clipStripe (ref int x, int y, ref int len, out int leftSkip) const {
261 if (empty) return false;
262 if (len <= 0 || y < y0 || y >= y0+height || x >= x0+width) return false;
263 if (x < x0) {
264 // left clip
265 if (x+len <= x0) return false;
266 len -= (leftSkip = x0-x);
267 x = x0;
269 if (x+len >= x0+width) {
270 // right clip
271 len = x0+width-x;
272 assert(len > 0); // yeah, always
274 return true;
277 /// ditto
278 bool clipStripe (ref int x, int y, ref int len) const {
279 pragma(inline, true);
280 int dummy = void;
281 return clipStripe(x, y, len, dummy);
286 // ////////////////////////////////////////////////////////////////////////// //
287 private import iv.bclamp;
289 //public enum GLTexType = GL_RGBA;
290 public enum GLTexType = GL_BGRA;
292 private __gshared int VBufWidth = 800;
293 private __gshared int VBufHeight = 600;
295 public __gshared uint* vglTexBuf = null; // OpenGL texture buffer
296 public __gshared uint vglTexId = 0;
299 public @property int winWidth () nothrow @trusted @nogc { pragma(inline, true); return VBufWidth; }
300 public @property int winHeight () nothrow @trusted @nogc { pragma(inline, true); return VBufHeight; }
303 // ////////////////////////////////////////////////////////////////////////// //
304 public void gxResize (int wdt, int hgt) nothrow @trusted {
305 if (wdt < 1) wdt = 1;
306 if (hgt < 1) hgt = 1;
307 if (wdt > 16384) wdt = 16384;
308 if (hgt > 16384) hgt = 16384;
309 if (vglTexBuf is null || wdt != VBufWidth || hgt != VBufHeight || vglTexId == 0) {
310 import core.stdc.stdlib : realloc;
311 VBufWidth = wdt;
312 VBufHeight = hgt;
313 vglTexBuf = cast(uint*)realloc(vglTexBuf, wdt*hgt*vglTexBuf[0].sizeof);
314 if (vglTexBuf is null) assert(0, "VGL: out of memory");
315 vglTexBuf[0..wdt*hgt] = 0;
317 if (gxRebuildScreenCB !is null) {
318 gxClipReset();
319 try {
320 gxRebuildScreenCB();
321 } catch (Exception e) {
322 conwriteln("SCREEN REBUILD ERROR: ", e.msg);
326 enum wrapOpt = GL_REPEAT;
327 enum filterOpt = GL_NEAREST; //GL_LINEAR;
328 enum ttype = GL_UNSIGNED_BYTE;
330 if (vglTexId) glDeleteTextures(1, &vglTexId);
331 vglTexId = 0;
332 glGenTextures(1, &vglTexId);
333 if (vglTexId == 0) assert(0, "VGL: can't create screen texture");
335 glBindTexture(GL_TEXTURE_2D, vglTexId);
336 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapOpt);
337 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapOpt);
338 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterOpt);
339 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterOpt);
341 //GLfloat[4] bclr = 0.0;
342 //glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, bclr.ptr);
344 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VBufWidth, VBufHeight, 0, GLTexType, GL_UNSIGNED_BYTE, vglTexBuf);
349 public void gxUpdateTexture () nothrow @trusted @nogc {
350 if (vglTexId) {
351 glBindTexture(GL_TEXTURE_2D, vglTexId);
352 glTexSubImage2D(GL_TEXTURE_2D, 0, 0/*x*/, 0/*y*/, VBufWidth, VBufHeight, GLTexType, GL_UNSIGNED_BYTE, vglTexBuf);
353 //glBindTexture(GL_TEXTURE_2D, 0);
358 public void gxBlitTexture () nothrow @trusted @nogc {
359 if (!vglTexId) return;
361 glMatrixMode(GL_PROJECTION); // for ortho camera
362 glLoadIdentity();
363 glViewport(0, 0, VBufWidth, VBufHeight);
364 glOrtho(0, VBufWidth, VBufHeight, 0, -1, 1); // top-to-bottom
365 glMatrixMode(GL_MODELVIEW);
366 glLoadIdentity();
368 glEnable(GL_TEXTURE_2D);
369 glDisable(GL_LIGHTING);
370 glDisable(GL_DITHER);
371 glDisable(GL_DEPTH_TEST);
372 glDisable(GL_BLEND);
373 //glDisable(GL_STENCIL_TEST);
375 immutable w = VBufWidth;
376 immutable h = VBufHeight;
378 glColor4f(1, 1, 1, 1);
379 glBindTexture(GL_TEXTURE_2D, vglTexId);
380 //scope(exit) glBindTexture(GL_TEXTURE_2D, 0);
381 glBegin(GL_QUADS);
382 glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0); // top-left
383 glTexCoord2f(1.0f, 0.0f); glVertex2i(w, 0); // top-right
384 glTexCoord2f(1.0f, 1.0f); glVertex2i(w, h); // bottom-right
385 glTexCoord2f(0.0f, 1.0f); glVertex2i(0, h); // bottom-left
386 glEnd();
390 // ////////////////////////////////////////////////////////////////////////// //
391 // mix dc with ARGB (or ABGR) col; dc A is ignored (removed)
392 public uint gxColMix (uint dc, uint col) pure nothrow @trusted @nogc {
393 pragma(inline, true);
394 immutable uint a = 256-(col>>24); // to not loose bits
395 //immutable uint dc = (da)&0xffffff;
396 dc &= 0xffffff;
397 immutable uint srb = (col&0xff00ff);
398 immutable uint sg = (col&0x00ff00);
399 immutable uint drb = (dc&0xff00ff);
400 immutable uint dg = (dc&0x00ff00);
401 immutable uint orb = (drb+(((srb-drb)*a+0x800080)>>8))&0xff00ff;
402 immutable uint og = (dg+(((sg-dg)*a+0x008000)>>8))&0x00ff00;
403 return orb|og;
407 // ////////////////////////////////////////////////////////////////////////// //
408 private template isGoodRGBInt(T) {
409 import std.traits : Unqual;
410 alias TT = Unqual!T;
411 enum isGoodRGBInt =
412 is(TT == ubyte) ||
413 is(TT == short) || is(TT == ushort) ||
414 is(TT == int) || is(TT == uint) ||
415 is(TT == long) || is(TT == ulong);
419 // ////////////////////////////////////////////////////////////////////////// //
420 public uint gxrgb(T0, T1, T2) (T0 r, T1 g, T2 b) pure nothrow @trusted @nogc if (isGoodRGBInt!T0 && isGoodRGBInt!T1 && isGoodRGBInt!T2) {
421 pragma(inline, true);
422 return (clampToByte(r)<<16)|(clampToByte(g)<<8)|clampToByte(b);
426 public template gxRGB(int r, int g, int b) {
427 enum gxRGB = (clampToByte(r)<<16)|(clampToByte(g)<<8)|clampToByte(b);
430 public template gxRGBA(int r, int g, int b, int a) {
431 enum gxRGBA = (clampToByte(a)<<24)|(clampToByte(r)<<16)|(clampToByte(g)<<8)|clampToByte(b);
435 // ////////////////////////////////////////////////////////////////////////// //
436 public __gshared GxRect gxClipRect = GxRect(0, 0, 65535, 65535);
438 private struct GxClipSave {
439 GxRect rc;
440 ~this () const nothrow @trusted @nogc {
441 pragma(inline, true);
442 gxClipRect = rc;
446 public GxClipSave gxClipSave () nothrow @trusted @nogc {
447 pragma(inline, true);
448 return GxClipSave(gxClipRect);
451 public void gxClipRestore() (in auto ref GxClipSave cs) nothrow @trusted @nogc {
452 pragma(inline, true);
453 gxClipRect = cs.rc;
456 public void gxClipReset () nothrow @trusted @nogc {
457 pragma(inline, true);
458 gxClipRect = GxRect(0, 0, VBufWidth, VBufHeight);
462 // ////////////////////////////////////////////////////////////////////////// //
463 public void gxClearScreen (uint clr) nothrow @trusted @nogc {
464 pragma(inline, true);
465 vglTexBuf[0..VBufWidth*VBufHeight+4] = clr;
469 public void gxPutPixel (int x, int y, uint c) nothrow @trusted @nogc {
470 pragma(inline, true);
471 if (x >= 0 && y >= 0 && x < VBufWidth && y < VBufHeight && (c&0xff000000) != 0xff000000 && gxClipRect.inside(x, y)) {
472 uint* dp = cast(uint*)(cast(ubyte*)vglTexBuf)+y*VBufWidth+x;
473 *dp = gxColMix(*dp, c);
478 public void gxPutPixel() (in auto ref GxPoint p, uint c) nothrow @trusted @nogc {
479 pragma(inline, true);
480 if (p.x >= 0 && p.y >= 0 && p.x < VBufWidth && p.y < VBufHeight && (c&0xff000000) != 0xff000000 && gxClipRect.inside(p)) {
481 uint* dp = cast(uint*)(cast(ubyte*)vglTexBuf)+p.y*VBufWidth+p.x;
482 *dp = gxColMix(*dp, c);
487 // ////////////////////////////////////////////////////////////////////////// //
488 public void gxHLine (int x, int y, int w, uint clr) nothrow @trusted @nogc {
489 if (w < 1 || y < 0 || y >= VBufHeight || x >= VBufWidth) return;
490 if (x < 0) {
491 if (x+w <= 0) return;
492 w += x;
493 x = 0;
494 assert(w > 0);
496 if (x+w > VBufWidth) {
497 w = VBufWidth-x;
498 assert(w > 0);
500 while (w-- > 0) gxPutPixel(x++, y, clr);
503 public void gxHLine() (in auto ref GxPoint p, int w, uint clr) nothrow @trusted @nogc { gxHLine(p.x, p.y, w, clr); }
505 public void gxVLine (int x, int y, int h, uint clr) nothrow @trusted @nogc {
506 if (h < 1 || x < 0 || x >= VBufWidth || y >= VBufHeight) return;
507 if (y < 0) {
508 if (y+h <= 0) return;
509 h += y;
510 y = 0;
511 assert(h > 0);
513 if (y+h > VBufHeight) {
514 h = VBufHeight-y;
515 assert(h > 0);
517 while (h-- > 0) gxPutPixel(x, y++, clr);
520 public void gxVLine() (in auto ref GxPoint p, int h, uint clr) nothrow @trusted @nogc { gxVLine(p.x, p.y, h, clr); }
522 public void gxFillRect (int x, int y, int w, int h, uint clr) nothrow @trusted @nogc {
523 if (w < 1 || h < 1 || x >= VBufWidth || y >= VBufHeight) return;
524 while (h-- > 0) gxHLine(x, y++, w, clr);
527 public void gxFillRect() (in auto ref GxRect rc, uint clr) nothrow @trusted @nogc {
528 gxFillRect(rc.x0, rc.y0, rc.width, rc.height, clr);
531 public void gxDrawRect (int x, int y, int w, int h, uint clr) nothrow @trusted @nogc {
532 if (w < 1 || h < 1 || x >= VBufWidth || y >= VBufHeight) return;
533 gxHLine(x, y, w, clr);
534 gxHLine(x, y+h-1, w, clr);
535 gxVLine(x, y+1, h-2, clr);
536 gxVLine(x+w-1, y+1, h-2, clr);
539 public void gxDrawRect() (in auto ref GxRect rc, uint clr) nothrow @trusted @nogc {
540 gxDrawRect(rc.x0, rc.y0, rc.width, rc.height, clr);
544 // ////////////////////////////////////////////////////////////////////////// //
545 public __gshared void delegate () gxRebuildScreenCB;
547 public void gxRebuildScreen () nothrow {
548 if (gxRebuildScreenCB !is null) {
549 gxClipReset();
550 try {
551 gxRebuildScreenCB();
552 } catch (Exception e) {
553 conwriteln("SCREEN REBUILD ERROR: ", e.msg);
555 gxUpdateTexture();
560 // ////////////////////////////////////////////////////////////////////////// //
561 __gshared string ttffilename = "~/ttf/ms/verdana.ttf";
562 __gshared ubyte* ttfontdata;
563 __gshared uint ttfontdatasize;
565 __gshared FT_Library ttflibrary;
566 __gshared FTC_Manager ttfcache;
567 __gshared FTC_CMapCache ttfcachecmap;
568 __gshared FTC_ImageCache ttfcacheimage;
570 shared static ~this () {
571 conwriteln("closing...");
572 if (ttflibrary) {
573 if (ttfcache) {
574 conwriteln("freeing cache...");
575 //FTC_Manager_Reset(ttfcache);
576 FTC_Manager_Done(ttfcache);
577 ttfcache = null;
579 conwriteln("closing library...");
580 FT_Done_FreeType(ttflibrary);
581 ttflibrary = null;
586 enum FontID = cast(FTC_FaceID)1;
588 extern(C) nothrow {
589 void ttfFontFinalizer (void* obj) {
590 import core.stdc.stdlib : free;
591 if (obj is null) return;
592 auto tf = cast(FT_Face)obj;
593 if (tf.generic.data !is ttfontdata) return;
594 if (ttfontdata !is null) {
595 conwriteln("TTF CACHE: freeing loaded font...");
596 free(ttfontdata);
597 ttfontdata = null;
598 ttfontdatasize = 0;
602 FT_Error ttfFontLoader (FTC_FaceID face_id, FT_Library library, FT_Pointer request_data, FT_Face* aface) {
603 if (face_id == FontID) {
604 try {
605 if (ttfontdata is null) {
606 conwriteln("TTF CACHE: loading '", ttffilename, "'...");
607 import core.stdc.stdlib : malloc;
608 auto fl = VFile(ttffilename);
609 auto fsz = fl.size;
610 if (fsz < 16 || fsz > int.max/8) throw new Exception("invalid ttf size");
611 ttfontdatasize = cast(uint)fsz;
612 ttfontdata = cast(ubyte*)malloc(ttfontdatasize);
613 if (ttfontdata is null) assert(0, "out of memory");
614 fl.rawReadExact(ttfontdata[0..ttfontdatasize]);
616 auto res = FT_New_Memory_Face(library, cast(const(FT_Byte)*)ttfontdata, ttfontdatasize, 0, aface);
617 if (res != 0) throw new Exception("error loading ttf: '"~ttffilename~"'");
618 (*aface).generic.data = ttfontdata;
619 (*aface).generic.finalizer = &ttfFontFinalizer;
620 } catch (Exception e) {
621 if (ttfontdata !is null) {
622 import core.stdc.stdlib : free;
623 free(ttfontdata);
624 ttfontdata = null;
625 ttfontdatasize = 0;
627 conwriteln("ERROR loading font: ", e.msg);
628 return FT_Err_Cannot_Open_Resource;
630 return FT_Err_Ok;
631 } else {
632 conwriteln("TTF CACHE: invalid font id");
634 return FT_Err_Cannot_Open_Resource;
638 void ttfLoad () {
639 if (FT_Init_FreeType(&ttflibrary)) assert(0, "can't initialize FreeType");
640 if (FTC_Manager_New(ttflibrary, 0, 0, 0, &ttfFontLoader, null, &ttfcache)) assert(0, "can't initialize FreeType cache manager");
641 if (FTC_CMapCache_New(ttfcache, &ttfcachecmap)) assert(0, "can't initialize FreeType cache manager");
642 if (FTC_ImageCache_New(ttfcache, &ttfcacheimage)) assert(0, "can't initialize FreeType cache manager");
643 conwriteln("TTF CACHE initialized.");
647 float ttfGetPixelHeightScale (FT_Face ttfont, float size) {
648 pragma(inline, true);
649 return size/(ttfont.ascender-ttfont.descender);
653 // returns advance to next x
654 int ttfRenderGlyphBitmap(bool mono=true) (FTC_FaceID ttfontid, int x, int y, int codepoint, int size, uint clr, int prevcp=-1) {
655 import core.stdc.stdlib : malloc;
657 int glyph = FTC_CMapCache_Lookup(ttfcachecmap, ttfontid, -1, codepoint);
658 if (glyph == 0) return -666;
660 int kadv = 0;
661 if (prevcp != -1) {
662 int glp = FTC_CMapCache_Lookup(ttfcachecmap, ttfontid, -1, prevcp);
663 if (glp != 0) {
664 FT_Face ttface;
665 //if (FTC_Manager_LookupFace(ttfcache, ttfontid, &ttface)) return -666;
667 FTC_ScalerRec fsc;
668 fsc.face_id = ttfontid;
669 fsc.width = 0;
670 fsc.height = size;
671 fsc.pixel = 1; // size in pixels
673 FT_Size ttfontsz;
674 if (FTC_Manager_LookupSize(ttfcache, &fsc, &ttfontsz)) return -666;
675 ttface = ttfontsz.face;
676 conwriteln("INTERLINE: ", ttfontsz.metrics.height>>6);
678 if (FT_HAS_KERNING(ttface)) {
679 FT_Vector kk;
680 if (FT_Get_Kerning(ttface, glp, glyph, FT_KERNING_UNSCALED, &kk) == 0) {
681 auto kadvfrac = FT_MulFix(kk.x, ttfontsz.metrics.x_scale); // 1/64 of pixel
682 kadv = cast(int)(kadvfrac+(kadvfrac < 0 ? -32 : 32)>>6);
683 version(none) {
684 conwriteln("kerning: prevcp=", prevcp, "; codepoint=", codepoint, "; kk.x=", kk.x, "; kadv=", kadv);
685 if (kadv) {
686 gxVLine(x, y-50, 50, gxRGB!(0, 255, 0));
687 gxVLine(x+kadv, y-50, 50, gxRGB!(255, 0, 0));
695 static if (mono) enum exflags = FT_LOAD_MONOCHROME/*|FT_LOAD_NO_AUTOHINT*/; else enum exflags = 0/*|FT_LOAD_NO_AUTOHINT*/;
697 FTC_ImageTypeRec fimg;
698 fimg.face_id = ttfontid;
699 fimg.width = 0;
700 fimg.height = size;
701 fimg.flags = FT_LOAD_RENDER|exflags;
703 FT_Glyph fg;
704 if (FTC_ImageCache_Lookup(ttfcacheimage, &fimg, glyph, &fg, null)) return -666;
705 if (fg.format != FT_GLYPH_FORMAT_BITMAP) return -666;
706 FT_BitmapGlyph fgi = cast(FT_BitmapGlyph)fg;
708 auto adv = fg.advance.x>>16;
709 adv += kadv;
711 auto src = fgi.bitmap.buffer;
712 auto spt = fgi.bitmap.pitch;
713 if (spt < 0) spt = -spt;
714 int x0 = x+fgi.left+kadv;
715 int y0 = y-fgi.top;
716 static if (mono) {
717 foreach (immutable int dy; 0..fgi.bitmap.rows) {
718 ubyte count = 0, b = 0;
719 auto s = src;
720 foreach (immutable int dx; 0..fgi.bitmap.width) {
721 if (count-- == 0) { count = 7; b = *s++; } else b <<= 1;
722 if (b&0x80) gxPutPixel(x0+dx, y0+dy, clr);
724 src += spt;
726 } else {
727 foreach (immutable int dy; 0..fgi.bitmap.rows) {
728 auto s = src;
729 foreach (immutable int dx; 0..fgi.bitmap.width) {
730 immutable ubyte b = *s++;
731 if (b&0x80) gxPutPixel(x0+dx, y0+dy, (clr&0xff_ff_ff)|((255-b)<<24));
733 src += spt;
737 return adv;
741 // ////////////////////////////////////////////////////////////////////////// //
742 void ttfDrawTextUtf (int x, int y, const(char)[] str, uint fg) {
743 Utf8DecoderFast ud;
744 int prevcp = -1;
745 enum FontHeight = 14;
746 //immutable float scale = ttfGetPixelHeightScale(ttfont, FontHeight);
747 foreach (char ch; str) {
748 if (ud.decode(cast(ubyte)ch)) {
749 int dc = (ud.complete ? ud.codepoint : ud.replacement);
750 int adv = ttfRenderGlyphBitmap!true(FontID, x, y, dc, FontHeight, fg, prevcp);
751 if (adv != -666) {
752 x += adv;
754 prevcp = dc;
760 // ////////////////////////////////////////////////////////////////////////// //
761 void main (string[] args) {
762 conRegVar!ttffilename("ttf_file", "ttf font file name");
764 sdpyWindowClass = "SDPY WINDOW";
765 glconShowKey = "M-Grave";
767 conProcessQueue(); // load config
768 conProcessArgs!true(args);
770 ttfLoad();
772 gxRebuildScreenCB = delegate () {
773 gxClearScreen(gxRGB!(0, 0, 0));
774 gxDrawRect(10, 10, VBufWidth-20, VBufHeight-20, gxRGB!(255, 127, 0));
775 ttfDrawTextUtf(100, 100, "Hello, пизда!", gxRGB!(255, 255, 0));
778 auto sdwin = new SimpleWindow(VBufWidth, VBufHeight, "My D App", OpenGlOptions.yes, Resizablity.allowResizing);
779 glconMainWindow = sdwin;
780 //sdwin.hideCursor();
782 static if (is(typeof(&sdwin.closeQuery))) {
783 sdwin.closeQuery = delegate () { concmd("quit"); glconPostDoConCommands(); };
786 sdwin.addEventListener((GLConScreenRebuildEvent evt) {
787 if (sdwin.closed) return;
788 if (isQuitRequested) { sdwin.close(); return; }
789 gxRebuildScreen();
790 sdwin.redrawOpenGlSceneNow();
793 sdwin.addEventListener((GLConScreenRepaintEvent evt) {
794 if (sdwin.closed) return;
795 if (isQuitRequested) { sdwin.close(); return; }
796 sdwin.redrawOpenGlSceneNow();
799 sdwin.addEventListener((GLConDoConsoleCommandsEvent evt) {
800 bool sendAnother = false;
801 bool prevVisible = isConsoleVisible;
803 consoleLock();
804 scope(exit) consoleUnlock();
805 conProcessQueue();
806 sendAnother = !conQueueEmpty();
808 if (sdwin.closed) return;
809 if (isQuitRequested) { sdwin.close(); return; }
810 if (sendAnother) glconPostDoConCommands();
811 if (prevVisible || isConsoleVisible) glconPostScreenRepaintDelayed();
815 sdwin.windowResized = delegate (int wdt, int hgt) {
816 if (sdwin.closed) return;
817 glconResize(wdt, hgt);
818 gxResize(wdt, hgt);
819 //glconPostScreenRebuild();
820 gxRebuildScreen();
823 sdwin.redrawOpenGlScene = delegate () {
824 if (sdwin.closed) return;
827 consoleLock();
828 scope(exit) consoleUnlock();
829 if (!conQueueEmpty()) glconPostDoConCommands();
832 // draw main screen
834 glClearColor(0, 0, 0, 0);
835 glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_ACCUM_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
836 glViewport(0, 0, sdwin.width, sdwin.height);
838 gxBlitTexture();
839 glconDraw();
841 //if (isQuitRequested()) sdwin.glconPostEvent(new QuitEvent());
844 sdwin.visibleForTheFirstTime = delegate () {
845 sdwin.setAsCurrentOpenGlContext();
846 glconInit(sdwin.width, sdwin.height);
847 gxResize(sdwin.width, sdwin.height);
848 gxRebuildScreen();
849 sdwin.redrawOpenGlSceneNow();
852 sdwin.eventLoop(0,
853 delegate () {
854 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
855 if (sdwin.closed) return;
856 if (isQuitRequested) { sdwin.close(); return; }
858 delegate (KeyEvent event) {
859 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
860 if (sdwin.closed) return;
861 if (isQuitRequested) { sdwin.close(); return; }
862 if (glconKeyEvent(event)) { glconPostScreenRepaint(); return; }
863 if (event.pressed && event == "Escape") { concmd("quit"); return; }
865 delegate (MouseEvent event) {
866 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
867 if (sdwin.closed) return;
869 delegate (dchar ch) {
870 if (sdwin.closed) return;
871 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
872 if (glconCharEvent(ch)) { glconPostScreenRepaint(); return; }
875 flushGui();
876 conProcessQueue(int.max/4);