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