/*
* There are a few things you need to do to get this working.
* Firstly I suggest putting the prototype for the cprintf()'s in
* your main headerfile. Add something like this.
*
* int cprintf ( char *buf, char *ptr, ... ) __attribute__ ((format(printf, 2, 3)));
* int cnprintf ( char *buf, int maxlen, char *ptr, ... ) __attribute__ ((format(printf, 3, 4)));
*
* Secondly, you may want to add cprintf.c to your Makefile.
*
* And finally you may want to edit the ansi tag strings found
* below, so they fit the ansi tags you allow on your mud.
*
* cprintf.c by Brian Graversen
* ----------------------------
* web: http://www.daimi.au.dk/~jobo/dystopia/
* mail: jobo@daimi.au.dk
*/
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
/*
* should also include your main headerfile,
* I assume it's merc.h. We need this for stuff
* like TRUE, FALSE, MAX_STRING_LENGTH, etc.
*/
#include "merc.h"
#define _NO_STRING_LIMIT_ -1
/*
* Just edit these three defines to make sure cprintf()
* works with your ansi color code. The COLOR_TAG is
* the pretag that comes before the ansitag, on most muds
* this is '{' or '#'. Secondly, add all the valid color
* tags to the ANSI_STRING, if #R is an ansi tag on your
* mud, then you must add R to ANSI_STRING, just do this
* for all the valid uses. Now, do you have certain non-ansi
* tags that are converted into something else? For instance
* some muds like to use '#-' as a replacement for '~', others,
* may use '##' as a replacement for '#'. These special conversion
* tags should be added to REPLACE_STRING.
*
* The following settings works with dystopia 1.4/1.4CE.
*/
#define _NO_STRING_LIMIT_ -1
#define COLOR_TAG '#'
#define BG_COLOR_TAG '^'
#define ANSI_STRING "0123456789bBcCdDefFgGiIlLnNopPrRsuvVwWyYxz()@/."
#define REPLACE_STRING "-#+^"
/*
* local procedures
*/
char *string_restrict ( char *str, int size );
int collen ( const char *str );
int _cprintf ( char *buf, int maxlen, char *ptr, va_list ap );
/*
* Acts like sprintf(), but doesn't break alignment
* due to colors. It only supports %d and %s. Returns
* the amount of chars copied.
*/
int cprintf(char *buf, char *ptr, ...)
{
va_list ap;
va_start(ap, ptr);
return _cprintf(buf, _NO_STRING_LIMIT_, ptr, ap);
}
/*
* Just as cprintf(), but safer, since you can restrict
* the maximum amount of copied chars. It will return
* the amount of copied chars, unless the output was
* truncated due to reaching maxlen before it was done
* copying the entire string, in which case it will return -1.
*/
int cnprintf(char *buf, int maxlen, char *ptr, ...)
{
va_list ap;
va_start(ap, ptr);
return _cprintf(buf, maxlen, ptr, ap);
}
int _cprintf(char *buf, int maxlen, char *ptr, va_list ap)
{
char dirty[100];
char *s;
int i, copied = 0;
bool bEnd = FALSE;
while(*ptr != '\0')
{
bool reverse = FALSE;
int size = 0, max_size = 0, j = 0;
bEnd = FALSE;
switch(*ptr)
{
default:
*buf++ = *ptr++;
if (++copied == maxlen)
goto done_copied;
break;
case '%':
/* should we align this in reverse ? */
if (*(ptr + 1) == '-')
{
ptr++;
reverse = TRUE;
}
/* get the size, if any */
while (isdigit(*(ptr + 1)))
{
size *= 10;
size += *(++ptr) - '0';
}
/* any size restrictions ? */
if (*(ptr + 1) == '.')
{
ptr++;
while (isdigit(*(ptr + 1)))
{
max_size *= 10;
max_size += *(++ptr) - '0';
}
}
switch(*(++ptr))
{
default:
*buf++ = '%';
if (++copied == maxlen)
goto done_copied;
break;
case 's':
s = va_arg(ap, char *);
s = string_restrict(s, max_size);
size -= collen(s);
if (!reverse)
{
while(size-- > 0)
{
*buf++ = ' ';
if (++copied == maxlen)
goto done_copied;
}
}
while(*s != '\0')
{
*buf++ = *s++;
if (!reverse && *s == '\0')
bEnd = TRUE;
if (++copied == maxlen)
goto done_copied;
}
if (reverse)
{
while(size-- > 0)
{
*buf++ = ' ';
if (size == 0)
bEnd = TRUE;
if (++copied == maxlen)
goto done_copied;
}
}
ptr++;
break;
case 'd':
i = va_arg(ap, int);
/* a little trick to see how long the number is */
sprintf(dirty, "%d", i);
size -= strlen(dirty);
if (!reverse)
{
while(size-- > 0)
{
*buf++ = ' ';
if (++copied == maxlen)
goto done_copied;
}
}
while (dirty[j] != '\0')
{
*buf++ = dirty[j++];
if (!reverse && dirty[j] == '\0')
bEnd = TRUE;
if (++copied == maxlen)
goto done_copied;
}
if (reverse)
{
while(size-- > 0)
{
*buf++ = ' ';
if (size == 0)
bEnd = TRUE;
if (++copied == maxlen)
goto done_copied;
}
}
ptr++;
break;
}
break;
}
}
/*
* this is our jumppoint, we use a goto for cleaner code,
* some people may argue that one should never use goto's
* while others will argue that refusing to use goto's no
* matter what, can result in code that's horrible to read.
*/
done_copied:
*buf = '\0';
/* if the output was truncated, we return -1 */
if (*ptr != '\0' && (*(++ptr) != '\0' || !bEnd))
copied = -1;
/* clean up */
va_end(ap);
/* return how much we copied */
return copied;
}
/*
* This nifty little function calculates the length of a
* string without the color tags. If you use other tags
* than those mentioned here, then you should add them.
*/
int collen(const char *str)
{
int len = 0;
while (*str != '\0')
{
int i = 0, j = 0;
bool found = FALSE;
switch(*str)
{
default:
len++, str++;
break;
case BG_COLOR_TAG:
str++;
while (ANSI_STRING[i] != '\0' && !found)
{
if (ANSI_STRING[i] == *str)
{
str++;
found = TRUE;
}
i++;
}
while (REPLACE_STRING[j] != '\0' && !found)
{
if (REPLACE_STRING[j] == *str)
{
len++, str++;
found = TRUE;
}
j++;
}
if (!found)
len++;
break;
case COLOR_TAG:
str++;
while (ANSI_STRING[i] != '\0' && !found)
{
if (ANSI_STRING[i] == *str)
{
str++;
found = TRUE;
}
i++;
}
while (REPLACE_STRING[j] != '\0' && !found)
{
if (REPLACE_STRING[j] == *str)
{
len++, str++;
found = TRUE;
}
j++;
}
if (!found)
len++;
break;
}
}
return len;
}
/*
* This nifty little function will return the
* longest possible prefix of 'str' that can
* be displayed in 'size' characters on a mud
* client. (ie. it ignores ansi chars).
*/
char *string_restrict(char *str, int size)
{
static char buf[MAX_STRING_LENGTH] = { '\0' };
char *ptr = buf;
int len = 0;
bool done = FALSE;
/* no size restrictions, we just return the string */
if (size == 0)
return str;
while (*str != '\0' && !done)
{
int i = 0, j = 0;
bool found = FALSE;
switch(*str)
{
default:
if (++len > size)
{
done = TRUE;
break;
}
*ptr++ = *str++;
break;
case COLOR_TAG:
str++;
while (ANSI_STRING[i] != '\0' && !found)
{
if (ANSI_STRING[i] == *str)
{
*ptr++ = COLOR_TAG;
*ptr++ = *str++;
found = TRUE;
}
i++;
}
while (REPLACE_STRING[j] != '\0' && !found)
{
if (REPLACE_STRING[j] == *str)
{
if (++len > size)
{
done = TRUE;
break;
}
*ptr++ = COLOR_TAG;
*ptr++ = *str++;
found = TRUE;
}
j++;
}
if (!found)
{
if (++len > size)
{
done = TRUE;
break;
}
*ptr++ = COLOR_TAG;
}
break;
}
}
*ptr = '\0';
return buf;
}