aboutsummaryrefslogtreecommitdiffstats
path: root/src/gwin/gwin_console.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gwin/gwin_console.c')
-rw-r--r--src/gwin/gwin_console.c809
1 files changed, 809 insertions, 0 deletions
diff --git a/src/gwin/gwin_console.c b/src/gwin/gwin_console.c
new file mode 100644
index 00000000..4c17034d
--- /dev/null
+++ b/src/gwin/gwin_console.c
@@ -0,0 +1,809 @@
+/*
+ * This file is subject to the terms of the GFX License. If a copy of
+ * the license was not distributed with this file, you can obtain one at:
+ *
+ * http://ugfx.org/license.html
+ */
+
+/**
+ * @file src/gwin/gwin_console.c
+ * @brief GWIN sub-system console code.
+ */
+
+#include "gfx.h"
+
+#if GFX_USE_GWIN && GWIN_NEED_CONSOLE
+
+#include <string.h>
+
+#include "gwin_class.h"
+
+#define GWIN_CONSOLE_USE_CLEAR_LINES TRUE // Clear each line before using it
+#define GWIN_CONSOLE_USE_FILLED_CHARS FALSE // Use filled characters instead of drawn characters
+#define GWIN_CONSOLE_BUFFER_SCROLLING TRUE // Use the history buffer to scroll when it is available
+
+// Our control flags
+#define GCONSOLE_FLG_NOSTORE (GWIN_FIRST_CONTROL_FLAG<<0)
+#define GCONSOLE_FLG_OVERRUN (GWIN_FIRST_CONTROL_FLAG<<1)
+
+// Meaning of our attribute bits.
+#define ESC_REDBIT 0x01
+#define ESC_GREENBIT 0x02
+#define ESC_BLUEBIT 0x04
+#define ESC_USECOLOR 0x08
+#define ESC_UNDERLINE 0x10
+#define ESC_BOLD 0x20
+
+/*
+ * Stream interface implementation. The interface is write only
+ */
+
+#if GFX_USE_OS_CHIBIOS && GWIN_CONSOLE_USE_BASESTREAM
+ #define Stream2GWindow(ip) ((GHandle)(((char *)(ip)) - (size_t)(&(((GConsoleObject *)0)->stream))))
+
+ static size_t GWinStreamWrite(void *ip, const uint8_t *bp, size_t n) { gwinPutCharArray(Stream2GWindow(ip), (const char *)bp, n); return RDY_OK; }
+ static size_t GWinStreamRead(void *ip, uint8_t *bp, size_t n) { (void)ip; (void)bp; (void)n; return 0; }
+ static msg_t GWinStreamPut(void *ip, uint8_t b) { gwinPutChar(Stream2GWindow(ip), (char)b); return RDY_OK; }
+ static msg_t GWinStreamGet(void *ip) {(void)ip; return RDY_OK; }
+ static msg_t GWinStreamPutTimed(void *ip, uint8_t b, systime_t time) { (void)time; gwinPutChar(Stream2GWindow(ip), (char)b); return RDY_OK; }
+ static msg_t GWinStreamGetTimed(void *ip, systime_t timeout) { (void)ip; (void)timeout; return RDY_OK; }
+ static size_t GWinStreamWriteTimed(void *ip, const uint8_t *bp, size_t n, systime_t time) { (void)time; gwinPutCharArray(Stream2GWindow(ip), (const char *)bp, n); return RDY_OK; }
+ static size_t GWinStreamReadTimed(void *ip, uint8_t *bp, size_t n, systime_t time) { (void)ip; (void)bp; (void)n; (void)time; return 0; }
+
+ struct GConsoleWindowVMT_t {
+ _base_asynchronous_channel_methods
+ };
+
+ static const struct GConsoleWindowVMT_t GWindowConsoleVMT = {
+ GWinStreamWrite,
+ GWinStreamRead,
+ GWinStreamPut,
+ GWinStreamGet,
+ GWinStreamPutTimed,
+ GWinStreamGetTimed,
+ GWinStreamWriteTimed,
+ GWinStreamReadTimed
+ };
+#endif
+
+#if GWIN_CONSOLE_ESCSEQ
+ // Convert escape sequences to attributes
+ static bool_t ESCtoAttr(char c, uint8_t *pattr) {
+ uint8_t attr;
+
+ attr = pattr[0];
+ switch(c) {
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ attr &= ~(ESC_REDBIT|ESC_GREENBIT|ESC_BLUEBIT);
+ attr |= (c - '0') | ESC_USECOLOR;
+ break;
+ case 'C':
+ attr &= ~(ESC_REDBIT|ESC_GREENBIT|ESC_BLUEBIT|ESC_USECOLOR);
+ break;
+ case 'u':
+ attr |= ESC_UNDERLINE;
+ break;
+ case 'U':
+ attr &= ~ESC_UNDERLINE;
+ break;
+ case 'b':
+ attr |= ESC_BOLD;
+ break;
+ case 'B':
+ attr &= ~ESC_BOLD;
+ break;
+ default:
+ return FALSE;
+ }
+ if (attr == pattr[0])
+ return FALSE;
+ pattr[0] = attr;
+ return TRUE;
+ }
+
+ static color_t ESCPrintColor(GConsoleObject *gcw) {
+ switch(gcw->currattr & (ESC_REDBIT|ESC_GREENBIT|ESC_BLUEBIT|ESC_USECOLOR)) {
+ case (ESC_USECOLOR):
+ return Black;
+ case (ESC_USECOLOR|ESC_REDBIT):
+ return Red;
+ case (ESC_USECOLOR|ESC_GREENBIT):
+ return Green;
+ case (ESC_USECOLOR|ESC_REDBIT|ESC_GREENBIT):
+ return Yellow;
+ case (ESC_USECOLOR|ESC_BLUEBIT):
+ return Blue;
+ case (ESC_USECOLOR|ESC_REDBIT|ESC_BLUEBIT):
+ return Magenta;
+ case (ESC_USECOLOR|ESC_GREENBIT|ESC_BLUEBIT):
+ return Cyan;
+ case (ESC_USECOLOR|ESC_REDBIT|ESC_GREENBIT|ESC_BLUEBIT):
+ return White;
+ default:
+ return gcw->g.color;
+ }
+ }
+#else
+ #define ESCPrintColor(gcw) ((gcw)->g.color)
+#endif
+
+#if GWIN_CONSOLE_USE_HISTORY
+ static void HistoryDestroy(GWindowObject *gh) {
+ #define gcw ((GConsoleObject *)gh)
+
+ // Deallocate the history buffer if required.
+ if (gcw->buffer) {
+ gfxFree(gcw->buffer);
+ gcw->buffer = 0;
+ }
+
+ #undef gcw
+ }
+
+ static void HistoryRedraw(GWindowObject *gh) {
+ #define gcw ((GConsoleObject *)gh)
+
+ // No redrawing if there is no history
+ if (!gcw->buffer)
+ return;
+
+ // We are printing the buffer - don't store it again
+ gh->flags |= GCONSOLE_FLG_NOSTORE;
+
+ #if !GWIN_CONSOLE_USE_CLEAR_LINES
+ // Clear the screen
+ gdispGFillArea(gh->display, gh->x, gh->y, gh->width, gh->height, gh->bgcolor);
+ #endif
+
+ // Reset the cursor
+ gcw->cx = 0;
+ gcw->cy = 0;
+
+ // Reset the current attributes
+ #if GWIN_CONSOLE_ESCSEQ
+ gcw->currattr = gcw->startattr;
+ #endif
+
+ // Print the buffer
+ gwinPutCharArray(gh, gcw->buffer, gcw->bufpos);
+
+ #if GWIN_CONSOLE_USE_CLEAR_LINES
+ // Clear the remaining space
+ {
+ coord_t y;
+
+ y = gcw->cy;
+ if (gcw->cx)
+ y += gdispGetFontMetric(gh->font, fontHeight);
+ if (y < gh->height)
+ gdispGFillArea(gh->display, gh->x, gh->y+y, gh->width, gh->height-y, gh->bgcolor);
+ }
+ #endif
+
+ // Turn back on storing of buffer contents
+ gh->flags &= ~GCONSOLE_FLG_NOSTORE;
+
+ #undef gcw
+ }
+
+ /**
+ * Put a character into our history buffer
+ */
+ static void putCharInBuffer(GConsoleObject *gcw, char c) {
+ // Only store if we need to
+ if (!gcw->buffer || (gcw->g.flags & GCONSOLE_FLG_NOSTORE))
+ return;
+
+ // Do we have enough space in the buffer
+ if (gcw->bufpos >= gcw->bufsize) {
+ char *p, *ep;
+ size_t dp;
+
+ /**
+ * This should never really happen except if the user has changed the window
+ * size without turning off and then on the buffer. Even then it is unlikely
+ * because of our conservative allocation strategy.
+ * If it really is needed we scroll one line to make some space. We also mark
+ * it is an overrun so that if asked to really scroll later we know we already have.
+ * Note we only use one bit to indicate an overrun, so an overrun of more
+ * than one line will lead to some interesting scrolling and refreshing
+ * effects.
+ */
+
+ // Remove one line from the start
+ ep = gcw->buffer+gcw->bufpos;
+ for(p = gcw->buffer; p < ep && *p != '\n'; p++) {
+ #if GWIN_CONSOLE_ESCSEQ
+ if (*p == 27)
+ ESCtoAttr(p[1], &gcw->startattr);
+ #endif
+ }
+
+ // Was there a newline?
+ if (*p != '\n')
+ p = gcw->buffer; // Oops - no newline, just delete one char
+ else
+ gcw->g.flags |= GCONSOLE_FLG_OVERRUN; // Mark the overrun
+
+ // Delete the data
+ dp = ++p - gcw->buffer; // Calculate the amount to to be removed
+ gcw->bufpos -= dp; // Calculate the new size
+ if (gcw->bufpos)
+ memcpy(gcw->buffer, p, gcw->bufpos); // Move the rest of the data
+ }
+
+ // Save the character
+ gcw->buffer[gcw->bufpos++] = c;
+ }
+
+ /**
+ * Scroll the history buffer by one line
+ */
+ static void scrollBuffer(GConsoleObject *gcw) {
+ char *p, *ep;
+ size_t dp;
+
+ // Only scroll if we need to
+ if (!gcw->buffer || (gcw->g.flags & GCONSOLE_FLG_NOSTORE))
+ return;
+
+ // If a buffer overrun has been marked don't scroll as we have already
+ if ((gcw->g.flags & GCONSOLE_FLG_OVERRUN)) {
+ gcw->g.flags &= ~GCONSOLE_FLG_OVERRUN;
+ return;
+ }
+
+ // Remove one line from the start
+ ep = gcw->buffer+gcw->bufpos;
+ for(p = gcw->buffer; p < ep && *p != '\n'; p++) {
+ #if GWIN_CONSOLE_ESCSEQ
+ if (*p == 27)
+ ESCtoAttr(p[1], &gcw->startattr);
+ #endif
+ }
+
+ // Was there a newline, if not delete everything.
+ if (*p != '\n') {
+ gcw->bufpos = 0;
+ return;
+ }
+
+ // Delete the data
+ dp = ++p - gcw->buffer; // Calculate the amount to to be removed
+ gcw->bufpos -= dp; // Calculate the new size
+ if (gcw->bufpos)
+ memcpy(gcw->buffer, p, gcw->bufpos); // Move the rest of the data
+ }
+
+ /**
+ * Clear the history buffer
+ */
+ static void clearBuffer(GConsoleObject *gcw) {
+
+ // Only clear if we need to
+ if (!gcw->buffer || (gcw->g.flags & GCONSOLE_FLG_NOSTORE))
+ return;
+
+ gcw->bufpos = 0;
+ }
+
+#else
+ #define putCharInBuffer(gcw, c)
+ #define scrollBuffer(gcw)
+ #define clearBuffer(gcw)
+#endif
+
+static void AfterClear(GWindowObject *gh) {
+ #define gcw ((GConsoleObject *)gh)
+ gcw->cx = 0;
+ gcw->cy = 0;
+ clearBuffer(gcw);
+ #if GWIN_CONSOLE_ESCSEQ
+ gcw->startattr = gcw->currattr;
+ #endif
+ #undef gcw
+}
+
+static const gwinVMT consoleVMT = {
+ "Console", // The classname
+ sizeof(GConsoleObject), // The object size
+ #if GWIN_CONSOLE_USE_HISTORY
+ HistoryDestroy, // The destroy routine (custom)
+ HistoryRedraw, // The redraw routine (custom)
+ #else
+ 0, // The destroy routine
+ 0, // The redraw routine (default)
+ #endif
+ AfterClear, // The after-clear routine
+};
+
+GHandle gwinGConsoleCreate(GDisplay *g, GConsoleObject *gc, const GWindowInit *pInit) {
+ if (!(gc = (GConsoleObject *)_gwindowCreate(g, &gc->g, pInit, &consoleVMT, 0)))
+ return 0;
+
+ #if GFX_USE_OS_CHIBIOS && GWIN_CONSOLE_USE_BASESTREAM
+ gc->stream.vmt = &GWindowConsoleVMT;
+ #endif
+
+ #if GWIN_CONSOLE_USE_HISTORY
+ gc->buffer = 0;
+ #if GWIN_CONSOLE_HISTORY_ATCREATE
+ gwinConsoleSetBuffer(&gc->g, TRUE);
+ #endif
+ #endif
+
+ gc->cx = 0;
+ gc->cy = 0;
+
+ #if GWIN_CONSOLE_ESCSEQ
+ gc->startattr = gc->currattr = 0;
+ gc->escstate = 0;
+ #endif
+
+ gwinSetVisible((GHandle)gc, pInit->show);
+
+ return (GHandle)gc;
+}
+
+#if GFX_USE_OS_CHIBIOS && GWIN_CONSOLE_USE_BASESTREAM
+ BaseSequentialStream *gwinConsoleGetStream(GHandle gh) {
+ if (gh->vmt != &consoleVMT)
+ return 0;
+
+ return (BaseSequentialStream *)&(((GConsoleObject *)(gh))->stream);
+ }
+#endif
+
+#if GWIN_CONSOLE_USE_HISTORY
+ bool_t gwinConsoleSetBuffer(GHandle gh, bool_t onoff) {
+ #define gcw ((GConsoleObject *)gh)
+
+ if (gh->vmt != &consoleVMT)
+ return FALSE;
+
+ // Do we want the buffer turned off?
+ if (!onoff) {
+ if (gcw->buffer) {
+ gfxFree(gcw->buffer);
+ gcw->buffer = 0;
+ }
+ return FALSE;
+ }
+
+ // Is the buffer already on?
+ if (gcw->buffer)
+ return TRUE;
+
+ // Get the number of characters that fit in the x direction
+ #if GWIN_CONSOLE_HISTORY_AVERAGING
+ gcw->bufsize = gh->width / ((2*gdispGetFontMetric(gh->font, fontMinWidth)+gdispGetFontMetric(gh->font, fontMaxWidth))/3);
+ #else
+ gcw->bufsize = gh->width / gdispGetFontMetric(gh->font, fontMinWidth);
+ #endif
+ gcw->bufsize++; // Allow space for a newline on each line.
+
+ // Multiply by the number of lines
+ gcw->bufsize *= gh->height / gdispGetFontMetric(gh->font, fontHeight);
+
+ // Allocate the buffer
+ if (!(gcw->buffer = gfxAlloc(gcw->bufsize)))
+ return FALSE;
+
+ // All good!
+ gh->flags &= ~GCONSOLE_FLG_OVERRUN;
+ gcw->bufpos = 0;
+ return TRUE;
+
+ #undef gcw
+ }
+#endif
+
+/*
+ * We can get into gwinPutChar() 2 ways -
+ * 1. when the user calls us, and
+ * 2. when the redraw uses us to redraw the display.
+ * When called by option 2 we MUST not try to obtain a draw session
+ * as we already have one.
+ *
+ * We use these macro's below to make sure we do that safely
+ */
+#define DrawStart(gh) ((gh->flags & GCONSOLE_FLG_NOSTORE) || _gwinDrawStart(gh))
+#define DrawEnd(gh) { if (!(gh->flags & GCONSOLE_FLG_NOSTORE)) _gwinDrawEnd(gh); }
+
+void gwinPutChar(GHandle gh, char c) {
+ #define gcw ((GConsoleObject *)gh)
+ uint8_t width, fy;
+
+ if (gh->vmt != &consoleVMT || !gh->font)
+ return;
+
+ fy = gdispGetFontMetric(gh->font, fontHeight);
+
+ #if GWIN_CONSOLE_ESCSEQ
+ /**
+ * Handle escape sequences
+ * ESC color Change subsequent text color
+ * color: "0" = black, "1" = red, "2" = green, "3" = yellow, "4" = blue,
+ * "5" = magenta, "6" = cyan, "7" = white
+ * ESC C Revert subsequent text color to the window default
+ * ESC u Turn on underline
+ * ESC U Turn off underline
+ * ESC b Turn on bold
+ * ESC B Turn off bold
+ * ESC J Clear the window
+ */
+ switch (gcw->escstate) {
+ case 1:
+ gcw->escstate = 0;
+ if (ESCtoAttr(c, &gcw->currattr)) {
+ if (gcw->cx == 0 && gcw->cy == 0)
+ gcw->startattr = gcw->currattr;
+ else {
+ putCharInBuffer(gcw, 27);
+ putCharInBuffer(gcw, c);
+ }
+ } else {
+ switch(c) {
+ case 'J':
+ // Clear the console and reset the cursor
+ clearBuffer(gcw);
+ if (DrawStart(gh)) {
+ gdispGFillArea(gh->display, gh->x, gh->y, gh->width, gh->height, gh->bgcolor);
+ DrawEnd(gh);
+ }
+ gcw->cx = 0;
+ gcw->cy = 0;
+ gcw->startattr = gcw->currattr;
+ break;
+ }
+ }
+ return;
+ }
+ #endif
+
+ /**
+ * Special Characters:
+ *
+ * Carriage returns and line feeds (\r & \n) are handled in unix terminal cooked mode; that is,
+ * line feeds perform both actions and carriage-returns are ignored.
+ *
+ * if GWIN_CONSOLE_ESCSEQ is turned on then ESC is trapped ready for the escape command.
+ *
+ * All other characters are treated as printable.
+ */
+ switch (c) {
+ case '\n':
+ // clear to the end of the line
+ #if GWIN_CONSOLE_USE_CLEAR_LINES
+ if (gcw->cx == 0 && gcw->cy+fy < gh->height && DrawStart(gh)) {
+ gdispGFillArea(gh->display, gh->x, gh->y + gcw->cy, gh->width, fy, gh->bgcolor);
+ DrawEnd(gh);
+ }
+ #endif
+ // update the cursor
+ gcw->cx = 0;
+ gcw->cy += fy;
+ putCharInBuffer(gcw, '\n');
+ // We use lazy scrolling here and only scroll when the next char arrives
+ return;
+
+ case '\r':
+ // gcw->cx = 0;
+ return;
+
+ #if GWIN_CONSOLE_ESCSEQ
+ case 27: // ESC
+ gcw->escstate = 1;
+ return;
+ #endif
+ }
+
+ // Characters with no width are ignored
+ if (!(width = gdispGetCharWidth(c, gh->font)))
+ return;
+
+ // Allow space for (very crude) bold
+ #if GWIN_CONSOLE_ESCSEQ
+ if ((gcw->currattr & ESC_BOLD))
+ width++;
+ #endif
+
+ // Do we need to go to the next line to fit this character?
+ if (gcw->cx + width >= gh->width) {
+ gcw->cx = 0;
+ gcw->cy += fy;
+ putCharInBuffer(gcw, '\n');
+ }
+
+ // Do we need to scroll to fit this character?
+ if (gcw->cy + fy > gh->height) {
+ #if GWIN_CONSOLE_USE_HISTORY && GWIN_CONSOLE_BUFFER_SCROLLING
+ if (gcw->buffer) {
+ // Scroll the buffer and then redraw using the buffer
+ scrollBuffer(gcw);
+ if (DrawStart(gh)) {
+ HistoryRedraw(gh);
+ DrawEnd(gh);
+ }
+ } else
+ #endif
+ #if GDISP_NEED_SCROLL
+ {
+ // Scroll the console using hardware
+ scrollBuffer(gcw);
+ if (DrawStart(gh)) {
+ gdispGVerticalScroll(gh->display, gh->x, gh->y, gh->width, gh->height, fy, gh->bgcolor);
+ DrawEnd(gh);
+ }
+
+ // Set the cursor to the start of the last line
+ gcw->cx = 0;
+ gcw->cy = (((coord_t)(gh->height/fy))-1)*fy;
+ }
+ #else
+ {
+ // Clear the console and reset the cursor
+ clearBuffer(gcw);
+ if (DrawStart(gh)) {
+ gdispGFillArea(gh->display, gh->x, gh->y, gh->width, gh->height, gh->bgcolor);
+ DrawEnd(gh);
+ }
+ gcw->cx = 0;
+ gcw->cy = 0;
+ #if GWIN_CONSOLE_ESCSEQ
+ gcw->startattr = gcw->currattr;
+ #endif
+ }
+ #endif
+ }
+
+ // Save the char
+ putCharInBuffer(gcw, c);
+
+ // Draw the character
+ if (DrawStart(gh)) {
+
+ // If we are at the beginning of a new line clear the line
+ #if GWIN_CONSOLE_USE_CLEAR_LINES
+ if (gcw->cx == 0)
+ gdispGFillArea(gh->display, gh->x, gh->y + gcw->cy, gh->width, fy, gh->bgcolor);
+ #endif
+
+ #if GWIN_CONSOLE_USE_FILLED_CHARS
+ gdispGFillChar(gh->display, gh->x + gcw->cx, gh->y + gcw->cy, c, gh->font, ESCPrintColor(gcw), gh->bgcolor);
+ #else
+ gdispGDrawChar(gh->display, gh->x + gcw->cx, gh->y + gcw->cy, c, gh->font, ESCPrintColor(gcw));
+ #endif
+
+ #if GWIN_CONSOLE_ESCSEQ
+ // Draw the underline
+ if ((gcw->currattr & ESC_UNDERLINE))
+ gdispGDrawLine(gh->display, gh->x + gcw->cx, gh->y + gcw->cy + fy - gdispGetFontMetric(gh->font, fontDescendersHeight),
+ gh->x + gcw->cx + width + gdispGetFontMetric(gh->font, fontCharPadding), gh->y + gcw->cy + fy - gdispGetFontMetric(gh->font, fontDescendersHeight),
+ ESCPrintColor(gcw));
+ // Bold (very crude)
+ if ((gcw->currattr & ESC_BOLD))
+ gdispGDrawChar(gh->display, gh->x + gcw->cx + 1, gh->y + gcw->cy, c, gh->font, ESCPrintColor(gcw));
+ #endif
+
+ DrawEnd(gh);
+ }
+
+ // Update the cursor
+ gcw->cx += width + gdispGetFontMetric(gh->font, fontCharPadding);
+
+ #undef gcw
+}
+
+void gwinPutString(GHandle gh, const char *str) {
+ while(*str)
+ gwinPutChar(gh, *str++);
+}
+
+void gwinPutCharArray(GHandle gh, const char *str, size_t n) {
+ while(n--)
+ gwinPutChar(gh, *str++);
+}
+
+#include <stdarg.h>
+
+#define MAX_FILLER 11
+#define FLOAT_PRECISION 100000
+
+static char *ltoa_wd(char *p, long num, unsigned radix, long divisor) {
+ int i;
+ char *q;
+
+ if (!divisor) divisor = num;
+
+ q = p + MAX_FILLER;
+ do {
+ i = (int)(num % radix);
+ i += '0';
+ if (i > '9')
+ i += 'A' - '0' - 10;
+ *--q = i;
+ num /= radix;
+ } while ((divisor /= radix) != 0);
+
+ i = (int)(p + MAX_FILLER - q);
+ do {
+ *p++ = *q++;
+ } while (--i);
+
+ return p;
+}
+
+#if GWIN_CONSOLE_USE_FLOAT
+ static char *ftoa(char *p, double num) {
+ long l;
+ unsigned long precision = FLOAT_PRECISION;
+
+ l = num;
+ p = ltoa_wd(p, l, 10, 0);
+ *p++ = '.';
+ l = (num - l) * precision;
+ return ltoa_wd(p, l, 10, precision / 10);
+ }
+#endif
+
+void gwinPrintf(GHandle gh, const char *fmt, ...) {
+ va_list ap;
+ char *p, *s, c, filler;
+ int i, precision, width;
+ bool_t is_long, left_align;
+ long l;
+ #if GWIN_CONSOLE_USE_FLOAT
+ float f;
+ char tmpbuf[2*MAX_FILLER + 1];
+ #else
+ char tmpbuf[MAX_FILLER + 1];
+ #endif
+
+ if (gh->vmt != &consoleVMT || !gh->font)
+ return;
+
+ va_start(ap, fmt);
+ while (TRUE) {
+ c = *fmt++;
+ if (c == 0) {
+ va_end(ap);
+ return;
+ }
+ if (c != '%') {
+ gwinPutChar(gh, c);
+ continue;
+ }
+
+ p = tmpbuf;
+ s = tmpbuf;
+ left_align = FALSE;
+ if (*fmt == '-') {
+ fmt++;
+ left_align = TRUE;
+ }
+ filler = ' ';
+ if (*fmt == '.') {
+ fmt++;
+ filler = '0';
+ }
+ width = 0;
+
+ while (TRUE) {
+ c = *fmt++;
+ if (c >= '0' && c <= '9')
+ c -= '0';
+ else if (c == '*')
+ c = va_arg(ap, int);
+ else
+ break;
+ width = width * 10 + c;
+ }
+ precision = 0;
+ if (c == '.') {
+ while (TRUE) {
+ c = *fmt++;
+ if (c >= '0' && c <= '9')
+ c -= '0';
+ else if (c == '*')
+ c = va_arg(ap, int);
+ else
+ break;
+ precision = precision * 10 + c;
+ }
+ }
+ /* Long modifier.*/
+ if (c == 'l' || c == 'L') {
+ is_long = TRUE;
+ if (*fmt)
+ c = *fmt++;
+ }
+ else
+ is_long = (c >= 'A') && (c <= 'Z');
+
+ /* Command decoding.*/
+ switch (c) {
+ case 'c':
+ filler = ' ';
+ *p++ = va_arg(ap, int);
+ break;
+ case 's':
+ filler = ' ';
+ if ((s = va_arg(ap, char *)) == 0)
+ s = "(null)";
+ if (precision == 0)
+ precision = 32767;
+ for (p = s; *p && (--precision >= 0); p++);
+ break;
+ case 'D':
+ case 'd':
+ if (is_long)
+ l = va_arg(ap, long);
+ else
+ l = va_arg(ap, int);
+ if (l < 0) {
+ *p++ = '-';
+ l = -l;
+ }
+ p = ltoa_wd(p, l, 10, 0);
+ break;
+ #if GWIN_CONSOLE_USE_FLOAT
+ case 'f':
+ f = (float) va_arg(ap, double);
+ if (f < 0) {
+ *p++ = '-';
+ f = -f;
+ }
+ p = ftoa(p, f);
+ break;
+ #endif
+ case 'X':
+ case 'x':
+ c = 16;
+ goto unsigned_common;
+ case 'U':
+ case 'u':
+ c = 10;
+ goto unsigned_common;
+ case 'O':
+ case 'o':
+ c = 8;
+ unsigned_common:
+ if (is_long)
+ l = va_arg(ap, long);
+ else
+ l = va_arg(ap, int);
+ p = ltoa_wd(p, l, c, 0);
+ break;
+ default:
+ *p++ = c;
+ break;
+ }
+
+ i = (int)(p - s);
+ if ((width -= i) < 0)
+ width = 0;
+ if (left_align == FALSE)
+ width = -width;
+ if (width < 0) {
+ if (*s == '-' && filler == '0') {
+ gwinPutChar(gh, *s++);
+ i--;
+ }
+ do {
+ gwinPutChar(gh, filler);
+ } while (++width != 0);
+ }
+ while (--i >= 0)
+ gwinPutChar(gh, *s++);
+ while (width) {
+ gwinPutChar(gh, filler);
+ width--;
+ }
+ }
+}
+
+#endif /* GFX_USE_GWIN && GWIN_NEED_CONSOLE */
+
+