1 module dlangui.platforms.ansi_console.consolefont;
2 
3 
4 public import dlangui.core.config;
5 static if (BACKEND_CONSOLE):
6 
7 import dlangui.graphics.fonts;
8 import dlangui.widgets.styles;
9 
10 class ConsoleFont : Font {
11     private Glyph _glyph;
12     this() {
13         _spaceWidth = 1;
14         _glyph.blackBoxX = 1;
15         _glyph.blackBoxY = 1;
16         _glyph.widthPixels = 1;
17         _glyph.widthScaled = 1 << 6;
18         _glyph.originX = 0;
19         _glyph.originY = 0;
20         _glyph.subpixelMode = SubpixelRenderingMode.None;
21         _glyph.glyph = [0];
22     }
23 
24     /// returns font size (as requested from font engine)
25     override @property int size() { return 1; }
26     /// returns actual font height including interline space
27     override @property int height() { return 1; }
28     /// returns font weight
29     override @property int weight() { return 400; }
30     /// returns baseline offset
31     override @property int baseline() { return 0; }
32     /// returns true if font is italic
33     override @property bool italic() { return false; }
34     /// returns font face name
35     override @property string face() { return "console"; }
36     /// returns font family
37     override @property FontFamily family() { return FontFamily.MonoSpace; }
38     /// returns true if font object is not yet initialized / loaded
39     override @property bool isNull() { return false; }
40 
41     /// return true if antialiasing is enabled, false if not enabled
42     override @property bool antialiased() {
43         return false;
44     }
45 
46     /// returns true if font has fixed pitch (all characters have equal width)
47     override @property bool isFixed() {
48         return true;
49     }
50 
51     /// returns true if font is fixed
52     override @property int spaceWidth() {
53         return 1;
54     }
55 
56     /// returns character width
57     override int charWidth(dchar ch) {
58         return 1;
59     }
60 
61     /*******************************************************************************************
62     * Measure text string, return accumulated widths[] (distance to end of n-th character), returns number of measured chars.
63     *
64     * Supports Tab character processing and processing of menu item labels like '&File'.
65     *
66     * Params:
67     *          text = text string to measure
68     *          widths = output buffer to put measured widths (widths[i] will be set to cumulative widths text[0..i])
69     *          maxWidth = maximum width to measure - measure is stopping if max width is reached (pass MAX_WIDTH_UNSPECIFIED to measure all characters)
70     *          tabSize = tabulation size, in number of spaces
71     *          tabOffset = when string is drawn not from left position, use to move tab stops left/right
72     *          textFlags = TextFlag bit set - to control underline, hotkey label processing, etc...
73     * Returns:
74     *          number of characters measured (may be less than text.length if maxWidth is reached)
75     ******************************************************************************************/
76     override int measureText(const dchar[] text, ref int[] widths, int maxWidth = MAX_WIDTH_UNSPECIFIED, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
77         if (text.length == 0)
78             return 0;
79         const dchar * pstr = text.ptr;
80         uint len = cast(uint)text.length;
81         if (widths.length < len)
82             widths.length = len + 1;
83         int x = 0;
84         int charsMeasured = 0;
85         int * pwidths = widths.ptr;
86         int tabWidth = spaceWidth * tabSize; // width of full tab in pixels
87         tabOffset = tabOffset % tabWidth;
88         if (tabOffset < 0)
89             tabOffset += tabWidth;
90         foreach(int i; 0 .. len) {
91             //auto measureStart = std.datetime.Clock.currAppTick;
92             dchar ch = pstr[i];
93             if (ch == '\t') {
94                 // measure tab
95                 int tabPosition = (x + tabWidth - tabOffset) / tabWidth * tabWidth + tabOffset;
96                 while (tabPosition < x + spaceWidth)
97                     tabPosition += tabWidth;
98                 pwidths[i] = tabPosition;
99                 charsMeasured = i + 1;
100                 x = tabPosition;
101                 continue;
102             } else if (ch == '&' && (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.HotKeys | TextFlag.UnderlineHotKeysWhenAltPressed))) {
103                 pwidths[i] = x;
104                 continue; // skip '&' in hot key when measuring
105             }
106             int w = x + 1; // using advance
107             pwidths[i] = w;
108             x += 1;
109             charsMeasured = i + 1;
110             if (x > maxWidth)
111                 break;
112         }
113         return charsMeasured;
114     }
115 
116     /*****************************************************************************************
117     * Draw text string to buffer.
118     *
119     * Params:
120     *      buf =   graphics buffer to draw text to
121     *      x =     x coordinate to draw first character at
122     *      y =     y coordinate to draw first character at
123     *      text =  text string to draw
124     *      color =  color for drawing of glyphs
125     *      tabSize = tabulation size, in number of spaces
126     *      tabOffset = when string is drawn not from left position, use to move tab stops left/right
127     *      textFlags = set of TextFlag bit fields
128     ****************************************************************************************/
129     override void drawText(DrawBuf drawBuf, int x, int y, const dchar[] text, uint color, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
130         if (text.length == 0)
131             return; // nothing to draw - empty text
132         import dlangui.platforms.ansi_console.consoleapp;
133         import dlangui.platforms.ansi_console.dconsole;
134         ANSIConsoleDrawBuf buf = cast(ANSIConsoleDrawBuf)drawBuf;
135         if (!buf)
136             return;
137         if (_textSizeBuffer.length < text.length)
138             _textSizeBuffer.length = text.length;
139         int charsMeasured = measureText(text, _textSizeBuffer, MAX_WIDTH_UNSPECIFIED, tabSize, tabOffset, textFlags);
140         Rect clip = buf.clipRect; //clipOrFullRect;
141         if (clip.empty)
142             return; // not visible - clipped out
143         if (y + height < clip.top || y >= clip.bottom)
144             return; // not visible - fully above or below clipping rectangle
145         int _baseline = baseline;
146         bool underline = (textFlags & TextFlag.Underline) != 0;
147         int underlineHeight = 1;
148         int underlineY = y + _baseline + underlineHeight * 2;
149         buf.console.textColor = ANSIConsoleDrawBuf.toConsoleColor(color);
150         buf.console.backgroundColor = CONSOLE_TRANSPARENT_BACKGROUND;
151         //Log.d("drawText: (", x, ',', y, ") '", text, "', color=", buf.console.textColor);
152         foreach(int i; 0 .. charsMeasured) {
153             dchar ch = text[i];
154             if (ch == '&' && (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.HotKeys | TextFlag.UnderlineHotKeysWhenAltPressed))) {
155                 if (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.UnderlineHotKeysWhenAltPressed))
156                     underline = true; // turn ON underline for hot key
157                 continue; // skip '&' in hot key when measuring
158             }
159             int xx = (i > 0) ? _textSizeBuffer[i - 1] : 0;
160             if (x + xx >= clip.right)
161                 break;
162             if (x + xx < clip.left)
163                 continue; // far at left of clipping region
164 
165             if (underline) {
166                 // draw underline
167                 buf.console.underline = true;
168                 // turn off underline after hot key
169                 if (!(textFlags & TextFlag.Underline)) {
170                     underline = false;
171                     buf.console.underline = false;
172                 }
173             }
174 
175             if (ch == ' ' || ch == '\t')
176                 continue;
177             int gx = x + xx;
178             if (gx < clip.left)
179                 continue;
180             buf.console.setCursor(gx, y);
181             buf.console.writeText(cast(dstring)(text[i .. i + 1]));
182         }
183         buf.console.underline = false;
184     }
185 
186     /*****************************************************************************************
187     * Draw text string to buffer.
188     *
189     * Params:
190     *      buf =   graphics buffer to draw text to
191     *      x =     x coordinate to draw first character at
192     *      y =     y coordinate to draw first character at
193     *      text =  text string to draw
194     *      charProps =  array of character properties, charProps[i] are properties for character text[i]
195     *      tabSize = tabulation size, in number of spaces
196     *      tabOffset = when string is drawn not from left position, use to move tab stops left/right
197     *      textFlags = set of TextFlag bit fields
198     ****************************************************************************************/
199     override void drawColoredText(DrawBuf drawBuf, int x, int y, const dchar[] text, const CustomCharProps[] charProps, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
200         if (text.length == 0)
201             return; // nothing to draw - empty text
202 
203         import dlangui.platforms.ansi_console.consoleapp;
204         import dlangui.platforms.ansi_console.dconsole;
205         ANSIConsoleDrawBuf buf = cast(ANSIConsoleDrawBuf)drawBuf;
206 
207         if (_textSizeBuffer.length < text.length)
208             _textSizeBuffer.length = text.length;
209         int charsMeasured = measureText(text, _textSizeBuffer, MAX_WIDTH_UNSPECIFIED, tabSize, tabOffset, textFlags);
210         Rect clip = buf.clipRect; //clipOrFullRect;
211         if (clip.empty)
212             return; // not visible - clipped out
213         if (y + height < clip.top || y >= clip.bottom)
214             return; // not visible - fully above or below clipping rectangle
215         int _baseline = baseline;
216         uint customizedTextFlags = (charProps.length ? charProps[0].textFlags : 0) | textFlags;
217         bool underline = (customizedTextFlags & TextFlag.Underline) != 0;
218         int underlineHeight = 1;
219         int underlineY = y + _baseline + underlineHeight * 2;
220         buf.console.backgroundColor = CONSOLE_TRANSPARENT_BACKGROUND;
221         foreach(int i; 0 .. charsMeasured) {
222             dchar ch = text[i];
223             uint color = i < charProps.length ? charProps[i].color : charProps[$ - 1].color;
224             buf.console.textColor = ANSIConsoleDrawBuf.toConsoleColor(color);
225             customizedTextFlags = (i < charProps.length ? charProps[i].textFlags : charProps[$ - 1].textFlags) | textFlags;
226             underline = (customizedTextFlags & TextFlag.Underline) != 0;
227             // turn off underline after hot key
228             if (ch == '&' && (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.HotKeys | TextFlag.UnderlineHotKeysWhenAltPressed))) {
229                 // draw underline
230                 buf.console.underline = true;
231                 // turn off underline after hot key
232                 if (!(textFlags & TextFlag.Underline)) {
233                     underline = false;
234                     buf.console.underline = false;
235                 }
236                 continue; // skip '&' in hot key when measuring
237             }
238             int xx = (i > 0) ? _textSizeBuffer[i - 1] : 0;
239             if (x + xx >= clip.right)
240                 break;
241             if (x + xx < clip.left)
242                 continue; // far at left of clipping region
243 
244             if (underline) {
245                 // draw underline
246                 buf.console.underline = true;
247                 // turn off underline after hot key
248                 if (!(customizedTextFlags & TextFlag.Underline)) {
249                     underline = false;
250                     buf.console.underline = false;
251                 }
252             }
253 
254             if (ch == ' ' || ch == '\t')
255                 continue;
256 
257             int gx = x + xx;
258             if (gx < clip.left)
259                 continue;
260             buf.console.setCursor(gx, y);
261             buf.console.writeText(cast(dstring)(text[i .. i + 1]));
262         }
263     }
264 
265     /// measure multiline text with line splitting, returns width and height in pixels
266     override Point measureMultilineText(const dchar[] text, int maxLines = 0, int maxWidth = 0, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
267         SimpleTextFormatter fmt;
268         FontRef fnt = FontRef(this);
269         return fmt.format(text, fnt, maxLines, maxWidth, tabSize, tabOffset, textFlags);
270     }
271 
272     /// draws multiline text with line splitting
273     override void drawMultilineText(DrawBuf buf, int x, int y, const dchar[] text, uint color, int maxLines = 0, int maxWidth = 0, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
274         SimpleTextFormatter fmt;
275         FontRef fnt = FontRef(this);
276         fmt.format(text, fnt, maxLines, maxWidth, tabSize, tabOffset, textFlags);
277         fmt.draw(buf, x, y, fnt, color);
278     }
279 
280 
281     /// get character glyph information
282     override Glyph * getCharGlyph(dchar ch, bool withImage = true) {
283         return &_glyph;
284     }
285 
286     /// clear usage flags for all entries
287     override void checkpoint() {
288         // ignore
289     }
290     /// removes entries not used after last call of checkpoint() or cleanup()
291     override void cleanup() {
292         // ignore
293     }
294     /// clears glyph cache
295     override void clearGlyphCache() {
296         // ignore
297     }
298 
299     override void clear() {
300     }
301 
302     ~this() {
303         clear();
304     }
305 }
306 
307 class ConsoleFontManager : FontManager {
308     this() {
309         _font = new ConsoleFont();
310     }
311 
312     private FontRef _font;
313 
314     /// get font instance best matched specified parameters
315     override ref FontRef getFont(int size, int weight, bool italic, FontFamily family, string face) {
316         return _font;
317     }
318 
319     /// clear usage flags for all entries -- for cleanup of unused fonts
320     override void checkpoint() {
321         // ignore
322     }
323 
324     /// removes entries not used after last call of checkpoint() or cleanup()
325     override void cleanup() {
326         // ignore
327     }
328 
329 }
330 
331