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*/;
31 import iv
.egra
.gfx
.config
;
32 import iv
.egra
.gfx
.base
;
34 version (DigitalMars
) {
36 version = EGRA_GFX_TEXT_ASM_ALLOWED
;
41 // ////////////////////////////////////////////////////////////////////////// //
42 enum ReplacementChar
= 0xFFFD;
43 //__gshared string egraFontName = "Arial:pixelsize=16";
44 //__gshared string egraFontName = "Verdana:pixelsize=16";
45 public __gshared string egraFontName
= "Verdana:weight=bold:pixelsize=16";
46 public __gshared
int egraDefaultFontSize
= 0;
47 __gshared string egraFontFile
;
48 __gshared
int fontSize
;
49 __gshared
int fontHeight
;
50 __gshared
int fontBaselineOfs
;
53 // ////////////////////////////////////////////////////////////////////////// //
54 __gshared
ubyte* ttfontdata
;
55 __gshared
uint ttfontdatasize
;
57 __gshared FT_Library ttflibrary
;
58 __gshared FTC_Manager ttfcache
;
59 __gshared FTC_CMapCache ttfcachecmap
;
60 __gshared FTC_ImageCache ttfcacheimage
;
63 shared static ~this () {
66 FTC_Manager_Done(ttfcache
);
69 FT_Done_FreeType(ttflibrary
);
75 enum FontID
= cast(FTC_FaceID
)1;
79 void ttfFontFinalizer (void* obj
) {
80 import core
.stdc
.stdlib
: free
;
81 if (obj
is null) return;
82 auto tf
= cast(iv
.freetype
.FT_Face
)obj
;
83 if (tf
.generic
.data
!is ttfontdata
) return;
84 if (ttfontdata
!is null) {
85 version(aliced
) conwriteln("TTF CACHE: freeing loaded font...");
92 FT_Error
ttfFontLoader (FTC_FaceID face_id
, FT_Library library
, FT_Pointer request_data
, iv
.freetype
.FT_Face
* aface
) {
93 if (face_id
== FontID
) {
95 if (ttfontdata
is null) {
96 conwriteln("TTF CACHE: loading '", egraFontFile
, "'...");
97 import core
.stdc
.stdlib
: malloc
;
98 auto fl
= VFile(egraFontFile
);
100 if (fsz
< 16 || fsz
> int.max
/8) throw new Exception("invalid ttf size");
101 ttfontdatasize
= cast(uint)fsz
;
102 ttfontdata
= cast(ubyte*)malloc(ttfontdatasize
);
103 if (ttfontdata
is null) { import core
.exception
: onOutOfMemoryErrorNoGC
; onOutOfMemoryErrorNoGC(); }
104 fl
.rawReadExact(ttfontdata
[0..ttfontdatasize
]);
106 auto res
= FT_New_Memory_Face(library
, cast(const(FT_Byte
)*)ttfontdata
, ttfontdatasize
, 0, aface
);
107 if (res
!= 0) throw new Exception("error loading ttf: '"~egraFontFile
~"'");
108 (*aface
).generic
.data
= ttfontdata
;
109 (*aface
).generic
.finalizer
= &ttfFontFinalizer
;
110 } catch (Exception e
) {
111 if (ttfontdata
!is null) {
112 import core
.stdc
.stdlib
: free
;
117 version(aliced
) conwriteln("ERROR loading font: ", e
.msg
);
118 return FT_Err_Cannot_Open_Resource
;
122 version(aliced
) conwriteln("TTF CACHE: invalid font id");
124 return FT_Err_Cannot_Open_Resource
;
129 void ttfLoad () nothrow {
130 if (FT_Init_FreeType(&ttflibrary
)) assert(0, "can't initialize FreeType");
131 if (FTC_Manager_New(ttflibrary
, 0, 0, 0, &ttfFontLoader
, null, &ttfcache
)) assert(0, "can't initialize FreeType cache manager");
132 if (FTC_CMapCache_New(ttfcache
, &ttfcachecmap
)) assert(0, "can't initialize FreeType cache manager");
133 if (FTC_ImageCache_New(ttfcache
, &ttfcacheimage
)) assert(0, "can't initialize FreeType cache manager");
136 fsc
.face_id
= FontID
;
138 fsc
.height
= fontSize
;
139 fsc
.pixel
= 1; // size in pixels
142 if (FTC_Manager_LookupSize(ttfcache
, &fsc
, &ttfontsz
)) assert(0, "cannot find FreeType font");
143 fontHeight
= cast(int)ttfontsz
.metrics
.height
>>6; // 26.6
144 fontBaselineOfs
= cast(int)((ttfontsz
.metrics
.height
+ttfontsz
.metrics
.descender
)>>6);
145 if (fontHeight
< 2 || fontHeight
> 128) assert(0, "invalid FreeType font metrics");
147 version(aliced
) conwriteln("TTF CACHE initialized.");
151 void initFontEngine () nothrow {
152 if (ttflibrary
is null) {
153 import std
.string
: fromStringz
/*, toStringz*/;
154 import std
.internal
.cstring
: tempCString
;
155 if (!FcInit()) assert(0, "cannot init fontconfig");
156 iv
.fontconfig
.FcPattern
* pat
= FcNameParse(egraFontName
.tempCString
);
157 if (pat
is null) assert(0, "cannot parse font name");
158 if (!FcConfigSubstitute(null, pat
, FcMatchPattern
)) assert(0, "cannot find fontconfig substitute");
159 FcDefaultSubstitute(pat
);
161 iv
.fontconfig
.FcResult result
;
162 iv
.fontconfig
.FcPattern
* font
= FcFontMatch(null, pat
, &result
);
165 if (FcPatternGetString(font
, FC_FILE
, 0, &file
) == FcResultMatch
) {
166 version(aliced
) conwriteln("font file: [", file
, "]");
167 egraFontFile
= file
.fromStringz
.idup
;
170 if (FcPatternGetDouble(font
, FC_PIXEL_SIZE
, 0, &pixelsize
) == FcResultMatch
) {
171 version(aliced
) conwriteln("pixel size: ", pixelsize
);
172 fontSize
= cast(int)pixelsize
;
175 FcPatternDestroy(pat
);
177 if (fontSize
< 6) fontSize
= 6;
178 if (fontSize
> 42) fontSize
= 42;
179 if (egraDefaultFontSize
> 0) fontSize
= egraDefaultFontSize
;
185 // ////////////////////////////////////////////////////////////////////////// //
186 public void utfByDChar (const(char)[] s
, scope void delegate (dchar ch
) nothrow @safe dg
) nothrow @safe {
187 if (dg
is null || s
.length
== 0) return;
189 foreach (char ch
; s
) {
190 if (dc
.decode(cast(ubyte)ch
)) dg(dc
.complete ? dc
.codepoint
: dc
.replacement
);
195 public void utfByDCharSPos (const(char)[] s
, scope void delegate (dchar ch
, usize stpos
) nothrow @safe dg
) nothrow @safe {
196 if (dg
is null || s
.length
== 0) return;
199 foreach (immutable idx
, char ch
; s
) {
200 if (dc
.decode(cast(ubyte)ch
)) {
201 dg(dc
.complete ? dc
.codepoint
: dc
.replacement
, stpos
);
208 // ////////////////////////////////////////////////////////////////////////// //
209 private void drawMonoBMP (int x
, int y
, int wdt
, int hgt
, const(ubyte)* bmp
, in usize bpitch
, in uint clr
) nothrow @trusted @nogc {
210 immutable int origWdt
= wdt
;
212 int leftSkip
, topSkip
;
213 if (!gxClipRect
.clipHVStripes(ref x
, ref y
, ref wdt
, ref hgt
, &leftSkip
, &topSkip
)) return;
214 if (!GxRect(0, 0, VBufWidth
, VBufHeight
).clipHVStripes(ref x
, ref y
, ref wdt
, ref hgt
, &leftSkip
, &topSkip
)) return;
216 // skip unused top part
217 if (topSkip
) bmp
+= bpitch
*cast(uint)topSkip
;
219 bmp
+= cast(usize
)(leftSkip
>>3);
222 if (gxIsSolid(clr
) && leftSkip
== 0) {
223 // yay, the fastest one!
224 enum RenderBitMixin
= `if (b&0x80) *curptr = clr; ++curptr; b <<= 1;`;
225 uint* dptr
= vglTexBuf
+y
*VBufWidth
+x
;
227 const(ubyte)* ss
= bmp
;
236 mixin(RenderBitMixin
);
237 mixin(RenderBitMixin
);
238 mixin(RenderBitMixin
);
239 mixin(RenderBitMixin
);
240 mixin(RenderBitMixin
);
241 mixin(RenderBitMixin
);
242 mixin(RenderBitMixin
);
243 mixin(RenderBitMixin
);
249 //assert(left >= 0 && left < 8);
253 final switch (left
) {
254 case 7: mixin(RenderBitMixin
); goto case;
255 case 6: mixin(RenderBitMixin
); goto case;
256 case 5: mixin(RenderBitMixin
); goto case;
257 case 4: mixin(RenderBitMixin
); goto case;
258 case 3: mixin(RenderBitMixin
); goto case;
259 case 2: mixin(RenderBitMixin
); goto case;
260 case 1: if (b
&0x80) *curptr
= clr
; break;
264 //while (left--) { mixin(RenderBitMixin); }
267 dptr
+= cast(usize
)VBufWidth
;
272 if (gxIsSolid(clr
)) {
273 // yay, the fast one!
274 enum RenderBitMixin
= `if (b&0x80) *curptr = clr; ++curptr; b <<= 1;`;
275 uint* dptr
= vglTexBuf
+y
*VBufWidth
+x
;
276 int ldraw
= 8-leftSkip
;
277 if (ldraw
> wdt
) ldraw
= wdt
;
279 const(ubyte)* ss
= bmp
;
282 ubyte b
= cast(ubyte)((*ss
++)<<cast(ubyte)(leftSkip
&0x07u
));
283 foreach (; 0..ldraw
) { mixin(RenderBitMixin
); }
285 int left
= wdt
-ldraw
;
292 mixin(RenderBitMixin
);
293 mixin(RenderBitMixin
);
294 mixin(RenderBitMixin
);
295 mixin(RenderBitMixin
);
296 mixin(RenderBitMixin
);
297 mixin(RenderBitMixin
);
298 mixin(RenderBitMixin
);
299 mixin(RenderBitMixin
);
305 //assert(left >= 0 && left < 8);
309 final switch (left
) {
310 case 7: mixin(RenderBitMixin
); goto case;
311 case 6: mixin(RenderBitMixin
); goto case;
312 case 5: mixin(RenderBitMixin
); goto case;
313 case 4: mixin(RenderBitMixin
); goto case;
314 case 3: mixin(RenderBitMixin
); goto case;
315 case 2: mixin(RenderBitMixin
); goto case;
316 case 1: if (b
&0x80) *curptr
= clr
; break;
320 //while (left--) { mixin(RenderBitMixin); }
323 dptr
+= cast(usize
)VBufWidth
;
332 const(ubyte)* ss
= bmp
;
333 ubyte count
= 1, b
= void;
339 if (!b || b
== 0xff) {
340 if (b
) gxHLine(cx
, y
, (left
>= 8 ?
8 : left
), clr
);
350 if (b
&0x80) gxPutPixel(cx
, y
, clr
);
360 // ////////////////////////////////////////////////////////////////////////// //
361 void drawFTBitmap (int x
, int y
, in ref FT_Bitmap bitmap
, in uint clr
) nothrow @trusted @nogc {
362 if (bitmap
.pixel_mode
!= FT_PIXEL_MODE_MONO
) return; // alas
363 if (bitmap
.rows
< 1 || bitmap
.width
< 1) return; // nothing to do
364 if (gxIsTransparent(clr
)) return; // just in case
366 const(ubyte)* src
= bitmap
.buffer
;
368 if (bitmap
.pitch
>= 0) {
369 bpitch
= cast(usize
)bitmap
.pitch
;
371 bpitch
= cast(usize
)0-cast(usize
)(-bitmap
.pitch
);
372 src
+= bpitch
*(cast(uint)bitmap
.rows
-1u);
374 drawMonoBMP(x
, y
, bitmap
.width
, bitmap
.rows
, src
, bpitch
, clr
);
378 // y is baseline; returns advance
379 int ttfDrawGlyph (bool firstchar
, int scale
, int x
, int y
, int glyphidx
, uint clr
) nothrow @trusted @nogc {
381 if (glyphidx
== 0) return 0;
383 FTC_ImageTypeRec fimg
;
384 fimg
.face_id
= FontID
;
386 fimg
.height
= fontSize
*scale
;
388 fimg
.flags
= FT_LOAD_TARGET_MONO|
(gxIsTransparent(clr
) ?
0 : FT_LOAD_MONOCHROME|FT_LOAD_RENDER
);
390 fimg
.flags
= (gxIsTransparent(clr
) ?
0 : FT_LOAD_RENDER
);
394 if (FTC_ImageCache_Lookup(ttfcacheimage
, &fimg
, glyphidx
, &fg
, null)) return 0;
397 if (!gxIsTransparent(clr
)) {
398 if (fg
.format
!= FT_GLYPH_FORMAT_BITMAP
) return 0;
399 FT_BitmapGlyph fgi
= cast(FT_BitmapGlyph
)fg
;
401 if (firstchar
&& fgi
.bitmap
.width
> 0) { x0
-= fgi
.left
; advdec
= fgi
.left
; }
402 drawFTBitmap(x0
, y
-fgi
.top
, fgi
.bitmap
, clr
);
404 return cast(int)(fg
.advance
.x
>>16)-advdec
;
408 int ttfGetKerning (int scale
, int gl0idx
, int gl1idx
) nothrow @trusted @nogc {
409 if (gl0idx
== 0 || gl1idx
== 0) return 0;
412 fsc
.face_id
= FontID
;
414 fsc
.height
= fontSize
*scale
;
415 fsc
.pixel
= 1; // size in pixels
418 if (FTC_Manager_LookupSize(ttfcache
, &fsc
, &ttfontsz
)) return 0;
419 if (!FT_HAS_KERNING(ttfontsz
.face
)) return 0;
422 if (FT_Get_Kerning(ttfontsz
.face
, gl0idx
, gl1idx
, FT_KERNING_UNSCALED
, &kk
)) return 0;
424 auto kadvfrac
= FT_MulFix(kk
.x
, ttfontsz
.metrics
.x_scale
); // 1/64 of pixel
425 return cast(int)((kadvfrac
/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
429 // ////////////////////////////////////////////////////////////////////////// //
430 public int gxCharWidthScaled (int scale
, in dchar ch
) nothrow @trusted {
431 if (scale
< 1) return 0;
433 if (ttflibrary
is null) initFontEngine();
435 int glyph
= FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, ch
);
436 if (glyph
== 0) glyph
= FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, ReplacementChar
);
437 if (glyph
== 0) return 0;
439 FTC_ImageTypeRec fimg
;
440 fimg
.face_id
= FontID
;
442 fimg
.height
= fontSize
*scale
;
443 fimg
.flags
= FT_LOAD_TARGET_MONO
;
446 if (FTC_ImageCache_Lookup(ttfcacheimage
, &fimg
, glyph
, &fg
, null)) return -666;
448 immutable int res
= cast(int)fg
.advance
.x
>>16;
449 return (res
> 0 ? res
: 0);
452 public int gxCharWidth (dchar ch
) nothrow @trusted { pragma(inline
, true); return gxCharWidthScaled(1, ch
); }
455 public int gxDrawChar (int x
, int y
, dchar ch
, uint fg
, int prevcp
=-1) nothrow @trusted { pragma(inline
, true); return gxDrawCharScaled(1, x
, y
, ch
, fg
, prevcp
); }
456 public int gxDrawChar() (in auto ref GxPoint p
, char ch
, uint fg
, int prevcp
=-1) nothrow @trusted { pragma(inline
, true); return gxDrawCharScaled(1, p
.x
, p
.y
, ch
, fg
, prevcp
); }
459 // ////////////////////////////////////////////////////////////////////////// //
461 public int gxDrawCharScaled (int scale
, int x
, int y
, dchar ch
, uint clr
, int prevcp
=-1) nothrow @trusted {
462 if (scale
< 1) return 0;
464 if (ttflibrary
is null) initFontEngine();
466 int glyph
= FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, ch
);
467 if (glyph
== 0) glyph
= FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, ReplacementChar
);
468 if (glyph
== 0) return 0;
470 int kadv
= ttfGetKerning(scale
, (prevcp
>= 0 ?
FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, prevcp
) : 0), glyph
);
471 return ttfDrawGlyph(false, scale
, x
+kadv
, y
+fontBaselineOfs
*scale
, glyph
, clr
);
474 public int gxDrawCharScaled() (int scale
, in auto ref GxPoint p
, char ch
, uint fg
, int prevcp
=-1) nothrow @trusted { pragma(inline
, true); return gxDrawCharScaled(scale
, p
.x
, p
.y
, ch
, fg
, prevcp
); }
477 // ////////////////////////////////////////////////////////////////////////// //
478 public struct GxKerning
{
485 bool firstchar
= true;
488 this (int atabsize
, int ascale
=1, bool firstCharIsFull
=false) {
489 if (ttflibrary
is null) initFontEngine();
490 firstchar
= !firstCharIsFull
;
492 if ((tabsize
= (atabsize
> 0 ? atabsize
: 0)) != 0) tabsize
= tabsize
*gxCharWidthScaled(ascale
, ' ');
495 void reset (int atabsize
) {
496 if (ttflibrary
is null) initFontEngine();
501 if ((tabsize
= (atabsize
> 0 ? atabsize
: 0)) != 0) tabsize
= tabsize
*gxCharWidthScaled(scale
, ' ');
505 // tab length for current position
506 int tablength () pure const @nogc { pragma(inline
, true); return (tabsize
> 0 ?
(wdt
/tabsize
+1)*tabsize
-wdt
: 0); }
508 int fixWidthPre (dchar ch
) {
509 immutable int prevgl
= prevgidx
;
514 if (ch
== '\t' && tabsize
> 0) {
516 lastadv
= lastcw
= tablength
;
519 if (ttflibrary
is null) initFontEngine();
520 int glyph
= FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, ch
);
521 if (glyph
== 0) glyph
= FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, ReplacementChar
);
523 wdt
+= ttfGetKerning(scale
, prevgl
, glyph
);
525 FTC_ImageTypeRec fimg
;
526 fimg
.face_id
= FontID
;
528 fimg
.height
= fontSize
*scale
;
530 fimg
.flags
= FT_LOAD_TARGET_MONO
;
532 fimg
.flags
= FT_LOAD_TARGET_MONO|FT_LOAD_MONOCHROME|FT_LOAD_RENDER
;
537 if (FTC_ImageCache_Lookup(ttfcacheimage
, &fimg
, glyph
, &fg
, null) == 0) {
539 lastadv
= fg
.advance
.x
>>16;
542 if (FTC_ImageCache_Lookup(ttfcacheimage
, &fimg
, glyph
, &fg
, null) == 0) {
544 if (fg
.format
== FT_GLYPH_FORMAT_BITMAP
) {
545 FT_BitmapGlyph fgi
= cast(FT_BitmapGlyph
)fg
;
546 if (firstchar
&& fgi
.bitmap
.width
> 0) {
547 lastcw
= fgi
.bitmap
.width
;
549 if (lastcw
< 1) { advdec
= 0; lastcw
= cast(int)fg
.advance
.x
>>16; }
551 lastcw
= fgi
.left
+fgi
.bitmap
.width
;
552 if (lastcw
< 1) lastcw
= cast(int)fg
.advance
.x
>>16;
556 lastadv
= (cast(int)fg
.advance
.x
>>16)-advdec
;
565 @property int finalWidth () pure const @nogc { pragma(inline
, true); return wdt+/
*lastadv*/lastcw
; }
568 @property int nextCharOfs () pure const @nogc { pragma(inline
, true); return wdt
+lastadv
; }
570 @property int currOfs () pure const @nogc { pragma(inline
, true); return wdt
; }
572 @property int nextOfsNoSpacing () pure const @nogc { pragma(inline
, true); return wdt
+lastcw
; }
576 // ////////////////////////////////////////////////////////////////////////// //
577 public struct GxDrawTextOptions
{
579 uint clr
= gxTransparent
;
581 bool firstCharIsFull
= false;
583 static nothrow @safe @nogc:
584 auto Color (in uint aclr
) { pragma(inline
, true); return GxDrawTextOptions(0, aclr
, 1, false); }
585 auto Tab (in int atabsize
) { pragma(inline
, true); return GxDrawTextOptions(atabsize
, gxTransparent
, 1, false); }
586 auto TabColor (in int atabsize
, in uint aclr
) { pragma(inline
, true); return GxDrawTextOptions(atabsize
, aclr
, 1, false); }
587 auto TabColorFirstFull (in int atabsize
, in uint aclr
, in bool fcf
) { pragma(inline
, true); return GxDrawTextOptions(atabsize
, aclr
, 1, fcf
); }
588 auto ScaleTabColor (in int ascale
, in int atabsize
, in uint aclr
) { pragma(inline
, true); return GxDrawTextOptions(atabsize
, aclr
, ascale
, false); }
589 auto ColorNFC (in uint aclr
) { pragma(inline
, true); return GxDrawTextOptions(0, aclr
, 1, true); }
590 auto TabColorNFC (in int atabsize
, in uint aclr
) { pragma(inline
, true); return GxDrawTextOptions(atabsize
, aclr
, 1, true); }
591 auto ScaleTabColorNFC (in int ascale
, in int atabsize
, in uint aclr
) { pragma(inline
, true); return GxDrawTextOptions(atabsize
, aclr
, ascale
, true); }
595 public struct GxDrawTextState
{
596 usize spos
; // current codepoint starting position
597 usize epos
; // current codepoint ending position (exclusive; i.e. *after* codepoint)
598 int curx
; // current x (before drawing the glyph)
602 // delegate should return color
603 public int gxDrawTextUtf(R
) (in auto ref GxDrawTextOptions opt
, int x
, int y
, auto ref R srng
, uint delegate (in ref GxDrawTextState state
) nothrow @safe clrdg
=null) nothrow @trusted
604 if (Imp
!"std.range.primitives".isInputRange
!R
&& is(Imp
!"std.range.primitives".ElementEncodingType
!R
== char))
606 // rely on the assumption that font face won't be unloaded while we are in this function
607 if (opt
.scale
< 1) return 0;
609 if (ttflibrary
is null) initFontEngine();
611 GxDrawTextState state
;
613 y
+= fontBaselineOfs
*opt
.scale
;
615 immutable int tabpix
= (opt
.tabsize
> 0 ? opt
.tabsize
*gxCharWidthScaled(opt
.scale
, ' ') : 0);
620 immutable int stx
= x
;
623 bool firstchar
= !opt
.firstCharIsFull
;
626 while (!srng
.empty
) {
627 immutable ubyte srbyte
= cast(ubyte)srng
.front
;
631 if (dc
.decode(srbyte
)) {
632 int ch
= (dc
.complete ? dc
.codepoint
: dc
.replacement
);
637 if (opt
.tabsize
> 0) {
642 x
+= (wdt
/tabpix
+1)*tabpix
-wdt
;
643 if (clrdg
!is null) clrdg(state
);
644 state
.spos
= state
.epos
;
649 int glyph
= FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, ch
);
650 if (glyph
== 0) glyph
= FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, ReplacementChar
);
654 if (pgl
!= 0 && dokern
) {
655 if (ttfontsz
is null) {
657 fsc
.face_id
= FontID
;
659 fsc
.height
= fontSize
*opt
.scale
;
660 fsc
.pixel
= 1; // size in pixels
661 if (FTC_Manager_LookupSize(ttfcache
, &fsc
, &ttfontsz
)) {
665 dokern
= (FT_HAS_KERNING(ttfontsz
.face
) != 0);
670 if (FT_Get_Kerning(ttfontsz
.face
, pgl
, glyph
, FT_KERNING_UNSCALED
, &kk
) == 0) {
672 auto kadvfrac
= FT_MulFix(kk
.x
, ttfontsz
.metrics
.x_scale
); // 1/64 of pixel
673 kadv
= cast(int)((kadvfrac
/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
680 if (clrdg
!is null) { state
.curx
= x
; clr
= clrdg(state
); }
681 x
+= ttfDrawGlyph(firstchar
, opt
.scale
, x
, y
, glyph
, clr
);
684 state
.spos
= state
.epos
;
693 public int gxDrawTextUtf() (in auto ref GxDrawTextOptions opt
, int x
, int y
, const(char)[] s
, uint delegate (in ref GxDrawTextState state
) nothrow @safe clrdg
=null) nothrow @trusted {
694 static struct StrIterator
{
697 public nothrow @trusted @nogc:
698 this (const(char)[] s
) { pragma(inline
, true); str = s
; }
699 @property bool empty () pure const { pragma(inline
, true); return (str.length
== 0); }
700 @property char front () pure const { pragma(inline
, true); return (str.length ?
str.ptr
[0] : 0); }
701 void popFront () { if (str.length
) str = str[1..$]; }
704 if (s
.length
== 0) return 0;
705 return gxDrawTextUtf(opt
, x
, y
, StrIterator(s
), clrdg
);
709 public int gxTextWidthScaledUtf (int scale
, const(char)[] s
, int tabsize
=0, bool firstCharIsFull
=false) nothrow @trusted {
710 if (scale
< 1 || s
.length
== 0) return 0;
711 auto kern
= GxKerning(tabsize
, scale
, firstCharIsFull
);
712 s
.utfByDChar(delegate (dchar ch
) @trusted { kern
.fixWidthPre(ch
); });
713 return kern
.finalWidth
;
718 // ////////////////////////////////////////////////////////////////////////// //
719 public int gxTextHeightUtf () nothrow @trusted { if (ttflibrary
is null) initFontEngine(); return fontHeight
; }
720 public int gxTextHeightScaledUtf (int scale
) nothrow @trusted { if (ttflibrary
is null) initFontEngine(); return (scale
< 1 ?
0 : fontHeight
*scale
); }
722 public int gxTextBaseLineUtf () nothrow @trusted { if (ttflibrary
is null) initFontEngine(); return fontBaselineOfs
; }
723 public int gxTextUnderLineUtf () nothrow @trusted { if (ttflibrary
is null) initFontEngine(); return fontBaselineOfs
+2; }
725 public int gxTextWidthUtf (const(char)[] s
, int tabsize
=0, bool firstCharIsFull
=false) nothrow @trusted { return gxTextWidthScaledUtf(1, s
, tabsize
, firstCharIsFull
); }
727 public int gxDrawTextUtf() (int x
, int y
, const(char)[] s
, uint clr
) nothrow @safe { return gxDrawTextUtf(GxDrawTextOptions
.Color(clr
), x
, y
, s
); }
728 public int gxDrawTextUtf() (in auto ref GxPoint p
, const(char)[] s
, uint clr
) nothrow @safe { return gxDrawTextUtf(p
.x
, p
.y
, s
, clr
); }
731 public int gxDrawTextOutScaledUtf (int scale
, int x
, int y
, const(char)[] s
, uint clr
, uint clrout
) nothrow @trusted {
732 if (scale
< 1 || s
.length
== 0) return 0;
734 if (scale
== 1 && gxIsTransparent(clrout
)) return gxDrawTextUtf(x
, y
, s
, clr
);
736 auto opt
= GxDrawTextOptions
.ScaleTabColor(scale
, 0, clrout
);
737 if (!gxIsTransparent(clrout
)) {
739 foreach (immutable dy
; -1*scale
..2*scale
) {
740 foreach (immutable dx
; -1*scale
..2*scale
) {
741 if (dx || dy
) gxDrawTextUtf(opt
, x
+dx
, y
+dy
, s
);
745 immutable int origWdt
= gxTextWidthScaledUtf(scale
, s
);
746 immutable int origHgt
= gxTextHeightScaledUtf(scale
);
747 immutable int oldFontSize
= fontSize
;
748 scope(exit
) fontSize
= oldFontSize
;
750 immutable int outWdt
= gxTextWidthScaledUtf(scale
, s
);
751 immutable int outHgt
= gxTextHeightScaledUtf(scale
);
752 gxDrawTextUtf(opt
, x
-(outWdt
-origWdt
)/2, y
-(outHgt
-origHgt
)/2, s
);
757 return gxDrawTextUtf(opt
, x
, y
, s
);