2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 import arsd
.simpledisplay
;
34 // ////////////////////////////////////////////////////////////////////////// //
35 //__gshared string chiFontName = "Arial:pixelsize=16";
36 //__gshared string chiFontName = "Verdana:pixelsize=16";
37 __gshared string chiFontName
= "Verdana:pixelsize=12";
38 __gshared string chiFontFile
;
39 __gshared
int fontSize
;
40 __gshared
int fontHeight
;
41 __gshared
int fontBaselineOfs
;
44 // ////////////////////////////////////////////////////////////////////////// //
45 __gshared
ubyte* ttfontdata
;
46 __gshared
uint ttfontdatasize
;
48 __gshared FT_Library ttflibrary
;
49 __gshared FTC_Manager ttfcache
;
50 __gshared FTC_CMapCache ttfcachecmap
;
51 __gshared FTC_ImageCache ttfcacheimage
;
54 shared static ~this () {
57 FTC_Manager_Done(ttfcache
);
60 FT_Done_FreeType(ttflibrary
);
66 enum FontID
= cast(FTC_FaceID
)1;
70 void ttfFontFinalizer (void* obj
) {
71 import core
.stdc
.stdlib
: free
;
72 if (obj
is null) return;
73 auto tf
= cast(FT_Face
)obj
;
74 if (tf
.generic
.data
!is ttfontdata
) return;
75 if (ttfontdata
!is null) {
76 //conwriteln("TTF CACHE: freeing loaded font...");
83 FT_Error
ttfFontLoader (FTC_FaceID face_id
, FT_Library library
, FT_Pointer request_data
, FT_Face
* aface
) {
84 if (face_id
== FontID
) {
86 if (ttfontdata
is null) {
87 //conwriteln("TTF CACHE: loading '", chiFontFile, "'...");
88 import core
.stdc
.stdlib
: malloc
;
89 auto fl
= VFile(chiFontFile
);
91 if (fsz
< 16 || fsz
> int.max
/8) throw new Exception("invalid ttf size");
92 ttfontdatasize
= cast(uint)fsz
;
93 ttfontdata
= cast(ubyte*)malloc(ttfontdatasize
);
94 if (ttfontdata
is null) assert(0, "out of memory");
95 fl
.rawReadExact(ttfontdata
[0..ttfontdatasize
]);
97 auto res
= FT_New_Memory_Face(library
, cast(const(FT_Byte
)*)ttfontdata
, ttfontdatasize
, 0, aface
);
98 if (res
!= 0) throw new Exception("error loading ttf: '"~chiFontFile
~"'");
99 (*aface
).generic
.data
= ttfontdata
;
100 (*aface
).generic
.finalizer
= &ttfFontFinalizer
;
101 } catch (Exception e
) {
102 if (ttfontdata
!is null) {
103 import core
.stdc
.stdlib
: free
;
108 conwriteln("ERROR loading font: ", e
.msg
);
109 return FT_Err_Cannot_Open_Resource
;
113 conwriteln("TTF CACHE: invalid font id");
115 return FT_Err_Cannot_Open_Resource
;
120 void ttfLoad () nothrow {
121 if (FT_Init_FreeType(&ttflibrary
)) assert(0, "can't initialize FreeType");
122 if (FTC_Manager_New(ttflibrary
, 0, 0, 0, &ttfFontLoader
, null, &ttfcache
)) assert(0, "can't initialize FreeType cache manager");
123 if (FTC_CMapCache_New(ttfcache
, &ttfcachecmap
)) assert(0, "can't initialize FreeType cache manager");
124 if (FTC_ImageCache_New(ttfcache
, &ttfcacheimage
)) assert(0, "can't initialize FreeType cache manager");
127 fsc
.face_id
= FontID
;
129 fsc
.height
= fontSize
;
130 fsc
.pixel
= 1; // size in pixels
133 if (FTC_Manager_LookupSize(ttfcache
, &fsc
, &ttfontsz
)) assert(0, "cannot find FreeType font");
134 fontHeight
= ttfontsz
.metrics
.height
>>6; // 26.6
135 fontBaselineOfs
= cast(int)((ttfontsz
.metrics
.height
+ttfontsz
.metrics
.descender
)>>6);
136 if (fontHeight
< 2 || fontHeight
> 128) assert(0, "invalid FreeType font metrics");
138 //conwriteln("TTF CACHE initialized.");
142 void initFontEngine () nothrow {
143 if (ttflibrary
is null) {
144 import std
.string
: fromStringz
, toStringz
;
145 if (!FcInit()) assert(0, "cannot init fontconfig");
146 FcPattern
* pat
= FcNameParse(chiFontName
.toStringz
);
147 if (pat
is null) assert(0, "cannot parse font name");
148 if (!FcConfigSubstitute(null, pat
, FcMatchPattern
)) assert(0, "cannot find fontconfig substitute");
149 FcDefaultSubstitute(pat
);
152 FcPattern
* font
= FcFontMatch(null, pat
, &result
);
155 if (FcPatternGetString(font
, FC_FILE
, 0, &file
) == FcResultMatch
) {
156 //conwriteln("font file: [", file, "]");
157 chiFontFile
= file
.fromStringz
.idup
;
160 if (FcPatternGetDouble(font
, FC_PIXEL_SIZE
, 0, &pixelsize
) == FcResultMatch
) {
161 //conwriteln("pixel size: ", pixelsize);
162 fontSize
= cast(int)pixelsize
;
165 FcPatternDestroy(pat
);
167 if (fontSize
< 6) fontSize
= 6;
168 if (fontSize
> 42) fontSize
= 42;
174 // ////////////////////////////////////////////////////////////////////////// //
175 public void utfByDChar (const(char)[] s
, scope void delegate (dchar ch
) nothrow @trusted dg
) nothrow @trusted {
176 if (dg
is null) return;
178 foreach (char ch
; s
) {
179 if (dc
.decode(cast(ubyte)ch
)) dg(dc
.complete ? dc
.codepoint
: dc
.replacement
);
184 public void utfByDCharSPos (const(char)[] s
, scope void delegate (dchar ch
, usize stpos
) nothrow @trusted dg
) nothrow @trusted {
185 if (dg
is null) return;
188 foreach (immutable idx
, char ch
; s
) {
189 if (dc
.decode(cast(ubyte)ch
)) {
190 dg(dc
.complete ? dc
.codepoint
: dc
.replacement
, stpos
);
197 // ////////////////////////////////////////////////////////////////////////// //
198 void drawFTBitmap (int x
, int y
, in ref FT_Bitmap bitmap
, uint clr
)nothrow @trusted @nogc {
199 if (bitmap
.pixel_mode
!= FT_PIXEL_MODE_MONO
) return; // alas
200 if (bitmap
.rows
< 1 || bitmap
.width
< 1) return; // nothing to do
201 if (gxIsTransparent(clr
)) return; // just in case
204 const(ubyte)* src
= bitmap
.buffer
;
205 int spt
= bitmap
.pitch
;
209 src
+= spt
*(bitmap
.rows
-1);
211 if (!gxIsSolid(clr
)) {
213 foreach (immutable int dy
; 0..bitmap
.rows
) {
214 ubyte count
= 0, b
= 0;
216 foreach (immutable int dx
; 0..bitmap
.width
) {
217 if (count
-- == 0) { count
= 7; b
= *ss
++; } else b
<<= 1;
218 if (b
&0x80) gxPutPixel(x
+dx
, y
, clr
);
221 if (topdown
) src
+= spt
; else src
-= spt
;
225 // check if we can use fastest path
226 auto brc
= GxRect(x
, y
, bitmap
.width
, bitmap
.rows
);
227 if (gxClipRect
.contains(brc
) && GxRect(0, 0, VBufWidth
, VBufHeight
).contains(brc
)) {
228 // yay, the fastest one!
229 uint* dptr
= vglTexBuf
+y
*VBufWidth
+x
;
230 foreach (immutable int dy
; 0..bitmap
.rows
) {
231 ubyte count
= 0, b
= 0;
234 foreach (immutable int dx
; 0..bitmap
.width
) {
235 if (count
-- == 0) { count
= 7; b
= *ss
++; } else b
<<= 1;
236 if (b
&0x80) *curptr
= clr
;
239 if (topdown
) src
+= spt
; else src
-= spt
;
244 foreach (immutable int dy
; 0..bitmap
.rows
) {
245 ubyte count
= 0, b
= 0;
247 foreach (immutable int dx
; 0..bitmap
.width
) {
248 if (count
-- == 0) { count
= 7; b
= *ss
++; } else b
<<= 1;
249 if (b
&0x80) gxSetPixel(x
+dx
, y
, clr
);
252 if (topdown
) src
+= spt
; else src
-= spt
;
258 // y is baseline; returns advance
259 int ttfDrawGlyph (bool firstchar
, int scale
, int x
, int y
, int glyphidx
, uint clr
) nothrow @trusted {
261 if (glyphidx
== 0) return 0;
263 FTC_ImageTypeRec fimg
;
264 fimg
.face_id
= FontID
;
266 fimg
.height
= fontSize
*scale
;
268 fimg
.flags
= FT_LOAD_TARGET_MONO|
(gxIsTransparent(clr
) ?
0 : FT_LOAD_MONOCHROME|FT_LOAD_RENDER
);
270 fimg
.flags
= (gxIsTransparent(clr
) ?
0 : FT_LOAD_RENDER
);
274 if (FTC_ImageCache_Lookup(ttfcacheimage
, &fimg
, glyphidx
, &fg
, null)) return 0;
277 if (!gxIsTransparent(clr
)) {
278 if (fg
.format
!= FT_GLYPH_FORMAT_BITMAP
) return 0;
279 FT_BitmapGlyph fgi
= cast(FT_BitmapGlyph
)fg
;
281 if (firstchar
&& fgi
.bitmap
.width
> 0) { x0
-= fgi
.left
; advdec
= fgi
.left
; }
282 drawFTBitmap(x0
, y
-fgi
.top
, fgi
.bitmap
, clr
);
284 return cast(int)(fg
.advance
.x
>>16)-advdec
;
288 int ttfGetKerning (int scale
, int gl0idx
, int gl1idx
) nothrow @trusted {
289 if (gl0idx
== 0 || gl1idx
== 0) return 0;
292 fsc
.face_id
= FontID
;
294 fsc
.height
= fontSize
*scale
;
295 fsc
.pixel
= 1; // size in pixels
298 if (FTC_Manager_LookupSize(ttfcache
, &fsc
, &ttfontsz
)) return 0;
299 if (!FT_HAS_KERNING(ttfontsz
.face
)) return 0;
302 if (FT_Get_Kerning(ttfontsz
.face
, gl0idx
, gl1idx
, FT_KERNING_UNSCALED
, &kk
)) return 0;
304 auto kadvfrac
= FT_MulFix(kk
.x
, ttfontsz
.metrics
.x_scale
); // 1/64 of pixel
305 return cast(int)((kadvfrac
/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
309 // ////////////////////////////////////////////////////////////////////////// //
310 public int gxCharWidthScaled (int scale
, dchar ch
) nothrow @trusted {
311 if (scale
< 1) return 0;
315 int glyph
= FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, ch
);
316 if (glyph
== 0) glyph
= FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, '\u25A1');
317 if (glyph
== 0) return 0;
319 FTC_ImageTypeRec fimg
;
320 fimg
.face_id
= FontID
;
322 fimg
.height
= fontSize
*scale
;
323 fimg
.flags
= FT_LOAD_TARGET_MONO
;
326 if (FTC_ImageCache_Lookup(ttfcacheimage
, &fimg
, glyph
, &fg
, null)) return -666;
328 int res
= fg
.advance
.x
>>16;
329 return (res
> 0 ? res
: 0);
332 public int gxCharWidth (dchar ch
) nothrow @trusted { return gxCharWidthScaled(1, ch
); }
335 public int gxDrawChar (int x
, int y
, dchar ch
, uint fg
, int prevcp
=-1) nothrow @trusted { return gxDrawCharScaled(1, x
, y
, ch
, fg
, prevcp
); }
336 public int gxDrawChar() (in auto ref GxPoint p
, char ch
, uint fg
, int prevcp
=-1) nothrow @trusted { return gxDrawCharScaled(1, p
.x
, p
.y
, ch
, fg
, prevcp
); }
339 // ////////////////////////////////////////////////////////////////////////// //
341 public int gxDrawCharScaled (int scale
, int x
, int y
, dchar ch
, uint clr
, int prevcp
=-1) nothrow @trusted {
342 if (scale
< 1) return 0;
344 int glyph
= FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, ch
);
345 if (glyph
== 0) glyph
= FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, '\u25A1');
346 if (glyph
== 0) return 0;
348 int kadv
= ttfGetKerning(scale
, (prevcp
>= 0 ?
FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, prevcp
) : 0), glyph
);
349 return ttfDrawGlyph(false, scale
, x
+kadv
, y
+fontBaselineOfs
*scale
, glyph
, clr
);
352 public int gxDrawCharScaled() (int scale
, in auto ref GxPoint p
, char ch
, uint fg
, int prevcp
=-1) nothrow @trusted { return gxDrawCharScaled(scale
, p
.x
, p
.y
, ch
, fg
, prevcp
); }
355 // ////////////////////////////////////////////////////////////////////////// //
356 public struct GxKerning
{
363 bool firstchar
= true;
366 this (int atabsize
, int ascale
=1, bool firstCharIsFull
=false) {
368 firstchar
= !firstCharIsFull
;
370 if ((tabsize
= (atabsize
> 0 ? atabsize
: 0)) != 0) tabsize
= tabsize
*gxCharWidthScaled(ascale
, ' ');
373 void reset (int atabsize
) {
379 if ((tabsize
= (atabsize
> 0 ? atabsize
: 0)) != 0) tabsize
= tabsize
*gxCharWidthScaled(scale
, ' ');
383 // tab length for current position
384 int tablength () { pragma(inline
, true); return (tabsize
> 0 ?
(wdt
/tabsize
+1)*tabsize
-wdt
: 0); }
386 int fixWidthPre (dchar ch
) {
387 immutable int prevgl
= prevgidx
;
392 if (ch
== '\t' && tabsize
> 0) {
394 lastadv
= lastcw
= tablength
;
398 int glyph
= FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, ch
);
399 if (glyph
== 0) glyph
= FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, '\u25A1');
401 wdt
+= ttfGetKerning(scale
, prevgl
, glyph
);
403 FTC_ImageTypeRec fimg
;
404 fimg
.face_id
= FontID
;
406 fimg
.height
= fontSize
*scale
;
408 fimg
.flags
= FT_LOAD_TARGET_MONO
;
410 fimg
.flags
= FT_LOAD_TARGET_MONO|FT_LOAD_MONOCHROME|FT_LOAD_RENDER
;
415 if (FTC_ImageCache_Lookup(ttfcacheimage
, &fimg
, glyph
, &fg
, null) == 0) {
417 lastadv
= fg
.advance
.x
>>16;
420 if (FTC_ImageCache_Lookup(ttfcacheimage
, &fimg
, glyph
, &fg
, null) == 0) {
422 if (fg
.format
== FT_GLYPH_FORMAT_BITMAP
) {
423 FT_BitmapGlyph fgi
= cast(FT_BitmapGlyph
)fg
;
424 if (firstchar
&& fgi
.bitmap
.width
> 0) {
425 lastcw
= fgi
.bitmap
.width
;
427 if (lastcw
< 1) { advdec
= 0; lastcw
= fg
.advance
.x
>>16; }
429 lastcw
= fgi
.left
+fgi
.bitmap
.width
;
430 if (lastcw
< 1) lastcw
= fg
.advance
.x
>>16;
434 lastadv
= (fg
.advance
.x
>>16)-advdec
;
443 @property int finalWidth () const { pragma(inline
, true); return wdt+/
*lastadv*/lastcw
; }
446 @property int nextCharOfs () const { pragma(inline
, true); return wdt
+lastadv
; }
448 @property int currOfs () const { pragma(inline
, true); return wdt
; }
450 @property int nextOfsNoSpacing () const { pragma(inline
, true); return wdt
+lastcw
; }
454 // ////////////////////////////////////////////////////////////////////////// //
455 public struct GxDrawTextOptions
{
457 uint clr
= gxTransparent
;
459 bool firstCharIsFull
= false;
461 static pure nothrow @safe @nogc:
462 auto Color (uint aclr
) { pragma(inline
, true); return GxDrawTextOptions(0, aclr
, 1, false); }
463 auto Tab (int atabsize
) { pragma(inline
, true); return GxDrawTextOptions(atabsize
, gxTransparent
, 1, false); }
464 auto TabColor (int atabsize
, uint aclr
) { pragma(inline
, true); return GxDrawTextOptions(atabsize
, aclr
, 1, false); }
465 auto TabColorFirstFull (int atabsize
, uint aclr
, bool fcf
) { pragma(inline
, true); return GxDrawTextOptions(atabsize
, aclr
, 1, fcf
); }
466 auto ScaleTabColor (int ascale
, int atabsize
, uint aclr
) { pragma(inline
, true); return GxDrawTextOptions(atabsize
, aclr
, ascale
, false); }
467 auto ColorNFC (uint aclr
) { pragma(inline
, true); return GxDrawTextOptions(0, aclr
, 1, true); }
468 auto TabColorNFC (int atabsize
, uint aclr
) { pragma(inline
, true); return GxDrawTextOptions(atabsize
, aclr
, 1, true); }
469 auto ScaleTabColorNFC (int ascale
, int atabsize
, uint aclr
) { pragma(inline
, true); return GxDrawTextOptions(atabsize
, aclr
, ascale
, true); }
473 public struct GxDrawTextState
{
474 usize spos
; // current codepoint starting position
475 usize epos
; // current codepoint ending position (exclusive; i.e. *after* codepoint)
476 int curx
; // current x (before drawing the glyph)
480 // delegate should return color
481 public int gxDrawTextUtf(R
) (in auto ref GxDrawTextOptions opt
, int x
, int y
, auto ref R srng
, uint delegate (in ref GxDrawTextState state
) nothrow @trusted clrdg
=null) nothrow @trusted
482 if (Imp
!"std.range.primitives".isInputRange
!R
&& is(Imp
!"std.range.primitives".ElementEncodingType
!R
== char))
484 // rely on the assumption that font face won't be unloaded while we are in this function
485 if (opt
.scale
< 1) return 0;
489 GxDrawTextState state
;
491 y
+= fontBaselineOfs
*opt
.scale
;
493 immutable int tabpix
= (opt
.tabsize
> 0 ? opt
.tabsize
*gxCharWidthScaled(opt
.scale
, ' ') : 0);
498 immutable int stx
= x
;
501 bool firstchar
= !opt
.firstCharIsFull
;
504 while (!srng
.empty
) {
505 immutable ubyte srbyte
= cast(ubyte)srng
.front
;
509 if (dc
.decode(srbyte
)) {
510 int ch
= (dc
.complete ? dc
.codepoint
: dc
.replacement
);
515 if (opt
.tabsize
> 0) {
520 x
+= (wdt
/tabpix
+1)*tabpix
-wdt
;
521 if (clrdg
!is null) clrdg(state
);
522 state
.spos
= state
.epos
;
527 int glyph
= FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, ch
);
528 if (glyph
== 0) glyph
= FTC_CMapCache_Lookup(ttfcachecmap
, FontID
, -1, '\u25A1');
532 if (pgl
!= 0 && dokern
) {
533 if (ttfontsz
is null) {
535 fsc
.face_id
= FontID
;
537 fsc
.height
= fontSize
*opt
.scale
;
538 fsc
.pixel
= 1; // size in pixels
539 if (FTC_Manager_LookupSize(ttfcache
, &fsc
, &ttfontsz
)) {
543 dokern
= (FT_HAS_KERNING(ttfontsz
.face
) != 0);
548 if (FT_Get_Kerning(ttfontsz
.face
, pgl
, glyph
, FT_KERNING_UNSCALED
, &kk
) == 0) {
550 auto kadvfrac
= FT_MulFix(kk
.x
, ttfontsz
.metrics
.x_scale
); // 1/64 of pixel
551 kadv
= cast(int)((kadvfrac
/*+(kadvfrac < 0 ? -32 : 32)*/)>>6);
558 if (clrdg
!is null) { state
.curx
= x
; clr
= clrdg(state
); }
559 x
+= ttfDrawGlyph(firstchar
, opt
.scale
, x
, y
, glyph
, clr
);
562 state
.spos
= state
.epos
;
571 public int gxDrawTextUtf() (in auto ref GxDrawTextOptions opt
, int x
, int y
, const(char)[] s
, uint delegate (in ref GxDrawTextState state
) nothrow @trusted clrdg
=null) nothrow @trusted {
572 static struct StrIterator
{
575 public pure nothrow @trusted @nogc:
576 this (const(char)[] s
) { pragma(inline
, true); str = s
; }
577 @property bool empty () const { pragma(inline
, true); return (str.length
== 0); }
578 @property char front () const { pragma(inline
, true); return (str.length ?
str.ptr
[0] : 0); }
579 void popFront () { if (str.length
) str = str[1..$]; }
582 return gxDrawTextUtf(opt
, x
, y
, StrIterator(s
), clrdg
);
586 public int gxTextWidthScaledUtf (int scale
, const(char)[] s
, int tabsize
=0, bool firstCharIsFull
=false) nothrow @trusted {
587 if (scale
< 1) return 0;
588 auto kern
= GxKerning(tabsize
, scale
, firstCharIsFull
);
589 s
.utfByDChar(delegate (dchar ch
) @trusted { kern
.fixWidthPre(ch
); });
590 return kern
.finalWidth
;
595 // ////////////////////////////////////////////////////////////////////////// //
596 public int gxTextHeightUtf () nothrow @trusted { initFontEngine(); return fontHeight
; }
597 public int gxTextHeightScaledUtf (int scale
) nothrow @trusted { initFontEngine(); return (scale
< 1 ?
0 : fontHeight
*scale
); }
599 public int gxTextWidthUtf (const(char)[] s
, int tabsize
=0, bool firstCharIsFull
=false) nothrow @trusted { return gxTextWidthScaledUtf(1, s
, tabsize
, firstCharIsFull
); }
601 public int gxDrawTextUtf() (int x
, int y
, const(char)[] s
, uint clr
) nothrow @trusted { return gxDrawTextUtf(GxDrawTextOptions
.Color(clr
), x
, y
, s
); }
602 public int gxDrawTextUtf() (in auto ref GxPoint p
, const(char)[] s
, uint clr
) nothrow @trusted { return gxDrawTextUtf(p
.x
, p
.y
, s
, clr
); }
605 public int gxDrawTextOutScaledUtf (int scale
, int x
, int y
, const(char)[] s
, uint clr
, uint clrout
) nothrow @trusted {
606 if (scale
< 1) return 0;
607 auto opt
= GxDrawTextOptions
.ScaleTabColor(scale
, 0, clrout
);
608 foreach (immutable dy
; -1*scale
..2*scale
) {
609 foreach (immutable dx
; -1*scale
..2*scale
) {
610 if (dx || dy
) gxDrawTextUtf(opt
, x
+dx
, y
+dy
, s
);
614 return gxDrawTextUtf(opt
, x
, y
, s
);