Extend functionality of printf (Add colours)

  • Thread starter Tomás Ó hÉilidhe
  • Start date
T

Tomás Ó hÉilidhe

Firstly... before the guns go off... I realise that the C Standard
doesn't mention anything about the existence of colour, which is why
I'm writing a small little cross-platform library for setting the
console text colour. It'll have a function like as follows:

void SetCon(char const background,char const foreground)
{
/* Platform-specific code in here for setting
the console text and background colour */
}

OK, now onto the good stuff. What I want to do is extend the
functionality of printf so that you can set colours. For instance,
let's say you wanted "Hello World", with the Hello in red and the
World in yellow, well you would do the following:

cprintf("^brHello^byWorld);

The ^br means "black background, red foreground".
The ^by means "black background, yellow foreground".

The trick is though, that I want this to be usable just like printf,
so you might have:

cprintf("^brMy dog is ^by%u ^bryears old", some_unsigned_variable);

I aim to achieve this by using variable-length argument lists in
conjunction with vprintf.

My first crack at it is as follows, but it segfaults on my Linux
machine here:

#define SPECIAL_CHAR ((char)'^')

#include <stdarg.h>
#include <stdio.h>

void SetCon(char const background,char const foreground)
{
/* Platform-specific code in here for setting
the console text and background colour */
}

void cfprintf(FILE *out,char *str,...)
{
va_list args;
char *p;

va_start(args,str);

for (p = str; *p; ++p)
{
if (*p == SPECIAL_CHAR)
{
*p = 0;
vfprintf(out,str,args);

SetCon(p[1],p[2]); /* My cross-platform function */

p += 2;

str = p+1;
}
}

if (p != str) /* No point in sending a null string to vfprintf */
{
vfprintf(out,str,args);
}

va_end(args);
}

int main(void)
{
cfprintf(stdout,"blah^brblah");
return 0;
}

OK so I have a "va_list" object and I pass it to vprintf. As you can
see from my code, I was under the assumption that vprintf would
increment through the arguments, and that these increments would be
evident in my own function (i.e. I wouldn't have to call va_arg
redundantly to step through the arguments that have already been dealt
with).

Is my assumption wrong, are the increments performed by vprintf not
observed in my own function? (I want to ask before I go through the
pain in the ass procedure of doing all the "va_arg" calls for every
kind of type).
 
N

Nick Keighley

Firstly... before the guns go off... I realise that the C Standard
doesn't mention anything about the existence of colour, which is why
I'm writing a small little cross-platform library for setting the
console text colour. It'll have a function like as follows:

void SetCon(char const background,char const foreground)
{
    /* Platform-specific code in here for setting
       the console text and background colour     */

}

OK, now onto the good stuff. What I want to do is extend the
functionality of printf so that you can set colours. For instance,
let's say you wanted "Hello World", with the Hello in red and the
World in yellow, well you would do the following:

    cprintf("^brHello^byWorld);

The ^br means "black background, red foreground".
The ^by means "black background, yellow foreground".

The trick is though, that I want this to be usable just like printf,
so you might have:

   cprintf("^brMy dog is ^by%u ^bryears old", some_unsigned_variable);

I aim to achieve this by using variable-length argument lists in
conjunction with vprintf.

My first crack at it is as follows, but it segfaults on my Linux
machine here:

#define SPECIAL_CHAR ((char)'^')

#include <stdarg.h>
#include <stdio.h>

void SetCon(char const background,char const foreground)
{
    /* Platform-specific code in here for setting
       the console text and background colour     */

}

void cfprintf(FILE *out,char *str,...)
{
    va_list args;
    char *p;

    va_start(args,str);

    for (p = str; *p; ++p)
    {
        if (*p == SPECIAL_CHAR)
        {
            *p = 0;

isn't that potentially modifying a string literal?
            vfprintf(out,str,args);

            SetCon(p[1],p[2]);  /* My cross-platform function */

            p += 2;

            str = p+1;
        }
    }

    if (p != str) /* No point in sending a null string to vfprintf */
    {
        vfprintf(out,str,args);
    }

    va_end(args);

}

int main(void)
{
    cfprintf(stdout,"blah^brblah");
    return 0;

}

OK so I have a "va_list" object and I pass it to vprintf. As you can
see from my code, I was under the assumption that vprintf would
increment through the arguments, and that these increments would be
evident in my own function (i.e. I wouldn't have to call va_arg
redundantly to step through the arguments that have already been dealt
with).

Is my assumption wrong, are the increments performed by vprintf not
observed in my own function? (I want to ask before I go through the
pain in the ass procedure of doing all the "va_arg" calls for every
kind of type).

I don't know
 
M

maverik

Is my assumption wrong, are the increments performed by vprintf not
observed in my own function? (I want to ask before I go through the
pain in the ass procedure of doing all the "va_arg" calls for every
kind of type).

To answer your question: you may read 7.19.6.8. of the ISO/IEC C99
Standard. So it says:

[quoute]
The vfprintf function is equivalent to fprintf, with the variable
argument list
replaced by arg, which shall have been initialized by the va_start
macro (and
possibly subsequent va_arg calls). The vfprintf function does not
invoke the
va_end macro.

EXAMPLE The following shows the use of the vfprintf function in a
general error-reporting routine.
#include <stdarg.h>
#include <stdio.h>
void error(char *function_name, char *format, ...) {
va_list args;
va_start(args, format);

fprintf(stderr, "ERROR in %s: ", function_name);

vfprintf(stderr, format, args);
va_end(args);
}
[/quoute]

So, no need to call va_arg(), just not to forgive va_end().
 
T

Tomás Ó hÉilidhe

isn't that potentially modifying a string literal?


Ah yes, you're right, I'll change my main code as follows:

char str[] = ".........";
cfprintf(stdout,str);
 
T

Tomás Ó hÉilidhe

So, no need to call va_arg(), just not to forgive va_end().


Sorry but I don't see how you can deduce that, I don't think that
excerpt clarifies whether the "va_arg" calls made inside vfprintf will
have an observable effect in my own function.

And if I don't need to call va_arg, then why's my program segfaulting?
Does anyone spot a bug?
 
M

maverik

Sorry but I don't see how you can deduce that, I don't think that
excerpt clarifies whether the "va_arg" calls made inside vfprintf will
have an observable effect in my own function.

And if I don't need to call va_arg, then why's my program segfaulting?
Does anyone spot a bug?

Sorry, I miss tne note (from the same document):

As the functions vfprintf, vfscanf, vprintf, vscanf, vsnprintf, vspri
vsscanf invoke the va_arg macro, the value of arg after the return is
indeterminate.
 
K

Kenny McCormack

Firstly... before the guns go off... I realise that the C Standard
doesn't mention anything about the existence of colour, which is why
I'm writing a small little cross-platform library for setting the
console text colour. It'll have a function like as follows:

First, I haven't analyzed your code in depth, and I don't really care
about the minutiae. I leave that to the pedants^Wlanguage lawyers.

However, if I were doing this, I'd do it the other way around. That is,
I'd call one of the vs*tf*() functions once, at the beginnning, to
expand out the %(s). Then, I'd go back and parse the string - handling
the color coding stuff via my function, and printf'ing the rest.
 
B

Bart van Ingen Schenau

OK so I have a "va_list" object and I pass it to vprintf. As you can
see from my code, I was under the assumption that vprintf would
increment through the arguments, and that these increments would be
evident in my own function (i.e. I wouldn't have to call va_arg
redundantly to step through the arguments that have already been dealt
with).

Is my assumption wrong, are the increments performed by vprintf not
observed in my own function? (I want to ask before I go through the
pain in the ass procedure of doing all the "va_arg" calls for every
kind of type).

Yes, your assumption that the va_list object is in a usable state
after calling vprintf() is incorrect.
The standard does not guarantee anything here, because some
implementations will use 'pass by value' semantics for va_list
objects, while others use 'pass by reference/pointer' semantics.

Your best bet for writing the cprintf() routine is to first print
everything (including the color markup) to a string and then output
that string while interpreting the color markup.

Bart v Ingen Schenau
 
B

Bill Reid

Tomás Ó hÉilidhe said:
Firstly... before the guns go off... I realise that the C Standard
doesn't mention anything about the existence of colour, which is why
I'm writing a small little cross-platform library for setting the
console text colour.

Just curious...have you ever heard of something called "conio.h"
and functions like "cprintf()"? Definitely NOT a C "standard" library,
just wondering if you've heard of it, you might want to check it out
for possible linking/defines on "some" platforms...
 
T

Tomás Ó hÉilidhe

However, if I were doing this, I'd do it the other way around.  That is,
I'd call one of the vs*tf*() functions once, at the beginnning, to
expand out the %(s).  Then, I'd go back and parse the string - handling
the color coding stuff via my function, and printf'ing the rest.


OK so let's say I start off with an invocation as follows:

cprintf(stdout,"I'll ^brhave %u ^byand %u.",7,63);

First of all, I remove the markers so that it's:
"I'll have %u and %u."

I'll have to keep note that the address of 'h' is where my first
marker was, and that the address of 'a' is where my second marker was.

Next I use sprintf to turn "I'll have %u and %u." into "I'll have 7
and 63".

One problem is that I don't know how many characters "%u" will compute
to, I mean in the case of 7 it was one character but in the case of 63
it was two characters. This will screw up the address I previously had
for the second marker.

I suppose though, I could compare the strings from the end backwards
so that I'll now where the marker was (...if you catch what I mean).

Anyone got any ideas?
 
T

Tomás Ó hÉilidhe

OK so let's say I start off with an invocation as follows:

cprintf(stdout,"I'll ^brhave %u ^byand %u.",7,63);

First of all, I remove the markers so that it's:
    "I'll have %u and %u."


Forget all that crap I just wrote, I'm just after realising that
there's no reason why I can't leave the markers in for the call to
sprintf, and then take them out later.
 
K

Kenny McCormack

Forget all that crap I just wrote, I'm just after realising that
there's no reason why I can't leave the markers in for the call to
sprintf, and then take them out later.

Right. Saves me the trouble of flaming you...
 
P

Phil Carmody

Tomás Ó hÉilidhe said:
Forget all that crap I just wrote, I'm just after realising that
there's no reason why I can't leave the markers in for the call to
sprintf, and then take them out later.

Beware - if you're ever sprintfing externally-supplied strings,
then they could include console control codes. I don't know if
you'd want that or not.

Phil
 
T

Tomás Ó hÉilidhe

Just curious...have you ever heard of something called "conio.h"

There's an easier way. For instance on Linux, you can do:

printf("\033[37mHello!");

They call them escape sequences or something like that.

I've already got the code written and working for Linux, and now I'm
writing it for Windows. I had thought that under Windows I might be
able to do:

printf("$e[37mHello!");

but I tried it just there and it didn't work (as far as I know it
worked in DOS). I know under Windows there's a Win32 API function
called "SetConsoleTextAttribute", but I was hoping the above would
work because I'm not too keen on explicitly linking with gdi32.lib,
nor am I keen on including the whore of a file that is <windows.h>.
Anyone know another way of changing the console text colour in
Windows?

Just as an aside, I'm also writing a cross-platform library for
dealing with raw sockets. I pretty much have it simplified to four
functions:

OpenRawsock
SendEthernetFrame
RecvEthernetFrame
CloseRawsock

So far I have it working for Linux and Windows (The Linux version used
Berkeley Sockets and the Windows version uses pcap because WinSock no
longer allows raw sockets). Anyone who's interested can e-mail me.
 
R

Richard Tobin

Tomás Ó hÉilidhe said:
There's an easier way. For instance on Linux, you can do:

printf("\033[37mHello!");

They call them escape sequences or something like that.

This isn't a specifically Linux feature. It's a feature of the
terminal emulator (or real terminal) that you're using. Xterm (in
"VT" mode) uses ANSI escape sequences, which are what you're
describing.

-- Richard
 
K

Keith Thompson

Tomás Ó hÉilidhe said:
There's an easier way. For instance on Linux, you can do:

printf("\033[37mHello!");

They call them escape sequences or something like that.

This isn't a specifically Linux feature. It's a feature of the
terminal emulator (or real terminal) that you're using. Xterm (in
"VT" mode) uses ANSI escape sequences, which are what you're
describing.

Note that "ANSI" here refers to the ANSI standard that describes the
behavior of VT100-compatible display terminals (including emulators);
it's completely separate from the ANSI (and/or ISO) C standard.
 
T

Tomás Ó hÉilidhe

I'm writing a small little cross-platform library for setting the
console text colour.

You use the TOE_PLATFORM macro to choose your platform. 1 is Unix. 2
is Windows. Here's what I've got so far. Any comments are welcomed:

#define SPECIAL_CHAR ('^')

#include <stdarg.h> /* For variable length argument lists */
#include <stdio.h> /* vsprintf, printf */

#if TOE_PLATFORM == 1 /* Unix */

void SetCon(char const background,char const foreground)
{
char str[] = "\033[1;3z;4zm";

char const *pi = &foreground;

char *ps = str+5;

for (;;)
{
switch(*pi)
{
case 'b': *ps = '0'; break;
case 'r': *ps = '1'; break;
case 'g': *ps = '2'; break;
case 'y': *ps = '3'; break;
case 'B': *ps = '4'; break;
case 'm': *ps = '5'; break;
case 'c': *ps = '6'; break;
case 'w': *ps = '7'; break;
}

if (pi == &background) break;

pi = &background;
ps += 3;
}

printf(str);
}

#include <stdlib.h> /* system("clear"); */

void ClearCon(char const background,char const foreground)
{
SetCon(background,foreground);
system("clear");
}

#elif TOE_PLATFORM == 2 /* Windows */

#include <windows.h>

void SetCon(char const background,char const foreground)
{
WORD attribs = FOREGROUND_INTENSITY | BACKGROUND_INTENSITY;

switch(foreground)
{
case 'b': attribs &= ~(long unsigned)FOREGROUND_INTENSITY; break;
case 'r': attribs |= FOREGROUND_RED; break;
case 'g': attribs |= FOREGROUND_GREEN; break;
case 'y': attribs |= FOREGROUND_RED|FOREGROUND_GREEN; break;
case 'B': attribs |= FOREGROUND_BLUE; break;
case 'm': attribs |= FOREGROUND_RED|FOREGROUND_BLUE; break;
case 'c': attribs |= FOREGROUND_BLUE|FOREGROUND_GREEN; break;
case 'w': attribs |= FOREGROUND_RED|FOREGROUND_BLUE|
FOREGROUND_GREEN; break;
}

switch(background)
{
case 'b': attribs &= ~(long unsigned)BACKGROUND_INTENSITY; break;
case 'r': attribs |= BACKGROUND_RED; break;
case 'g': attribs |= BACKGROUND_GREEN; break;
case 'y': attribs |= BACKGROUND_RED|BACKGROUND_GREEN; break;
case 'B': attribs |= BACKGROUND_BLUE; break;
case 'm': attribs |= 0; break;
case 'c': attribs |= BACKGROUND_BLUE|BACKGROUND_GREEN; break;
case 'w': attribs |= BACKGROUND_RED|BACKGROUND_BLUE|
BACKGROUND_GREEN; break;
}

SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),attribs);
}

#include <stdlib.h> /* system("cls"); */

void ClearCon(char const background,char const foreground)
{
SetCon(background,foreground);
system("cls");
}

#else

#error "TOE_PLATFORM not set properly"

#endif

void cprintf(char const *str,...)
{
va_list args;
char buf[2048], *pbuf = buf;
char *p;

/* First use vsprintf */

va_start(args,str);

vsprintf(buf,str,args);

/* Now deal with the markers */

for (p = buf; *p; ++p)
{
if (*p == SPECIAL_CHAR)
{
*p = 0;

printf(pbuf);

SetCon(p[1],p[2]);

p += 2;

pbuf = p+1;
}
}

printf(pbuf);

va_end(args);
}

Anyone got any ideas to get rid of "char buf[2048]"?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top