13
\$\begingroup\$

I have the following header included in some of my projects so that I can add a little color to the terminal output. Here is how it would be used:

fprintf(stdout, "Recognized text: %s\n", text ?: RED_TEXT("No text recognized."));

color.h:

/**
 * @file color.h
 * @brief Defines all of the ANSI terminal escape codes that modify the color of text.
 */

#ifndef COLOR_H
#define COLOR_H

#define BLACK_TEXT(x) "\033[30;1m" x "\033[0m"
#define RED_TEXT(x) "\033[31;1m" x "\033[0m"
#define GREEN_TEXT(x) "\033[32;1m" x "\033[0m"
#define YELLOW_TEXT(x) "\033[33;1m" x "\033[0m"
#define BLUE_TEXT(x) "\033[34;1m" x "\033[0m"
#define MAGENTA_TEXT(x) "\033[35;1m" x "\033[0m"
#define CYAN_TEXT(x) "\033[36;1m" x "\033[0m"
#define WHITE_TEXT(x) "\033[37;1m" x "\033[0m"

#define BOLD_BLACK_TEXT(x) "\033[1m\033[30m;1m" x "\033[0m"
#define BOLD_RED_TEXT(x) "\033[1m\033[31m;1m" x "\033[0m"
#define BOLD_GREEN_TEXT(x) "\033[1m\033[32m;1m" x "\033[0m"
#define BOLD_YELLOW_TEXT(x) "\033[1m\033[33m;1m" x "\033[0m"
#define BOLD_BLUE_TEXT(x) "\033[1m\033[34m;1m" x "\033[0m"
#define BOLD_MAGENTA_TEXT(x) "\033[1m\033[35m;1m" x "\033[0m"
#define BOLD_CYAN_TEXT(x) "\033[1m\033[36m;1m" x "\033[0m"
#define BOLD_WHITE_TEXT(x) "\033[1m\033[37m;1m" x "\033[0m"

#endif // COLOR_H
\$\endgroup\$
4
  • 2
    \$\begingroup\$ Adding colour to the terminal is specific to the terminal. That is why we have ncurses which abstracts away the terminal specific codes. Or if you want to go low level you can look up the values dynamically in terminfo DB that should be part of your distribution. \$\endgroup\$ Aug 20, 2014 at 23:42
  • \$\begingroup\$ What's the ?: operator? \$\endgroup\$ Aug 21, 2014 at 7:26
  • \$\begingroup\$ @200_success It is a DRYer identical version of text ? text : RED_TEXT("No text recognized."). \$\endgroup\$
    – syb0rg
    Aug 21, 2014 at 20:16
  • 1
    \$\begingroup\$ I see. Apparently, it's a GNU extension. \$\endgroup\$ Aug 21, 2014 at 20:22

3 Answers 3

3
\$\begingroup\$

For better encapsulation, portability and the possibility of testing the output with isatty(), I would define a "color printf" function:

enum Color {
    COLOR_RED,
    COLOR_BLUE,
    COLOR_WHITE,
    ...
};

// Add this if GCC/Clang: 
//   __attribute__((format(printf, 3, 4)))
// to get format string validation at compile time.
void color_printf(FILE * stream, enum Color color, const char * fmt, ...) 
{
    va_list vaList;
    assert(stream != NULL);

    // If printing to a file, don't clutter the output with 
    // control characters.
    if (!isatty(fileno(stream)))
    {
        va_start(vaList, fmt);
        vfprintf(stream, fmt, vaList);
        va_end(vaList);
        return;
    }

    // Print to console with color:
    switch (color)
    {
    case COLOR_RED :
        fprintf(stream, "\033[31;1m");
        va_start(vaList, fmt);
        vfprintf(stream, fmt, vaList);
        va_end(vaList);
        fprintf(stream, "\033[0m");
        break;

        // and so on...
    }
}
\$\endgroup\$
4
  • \$\begingroup\$ I like this idea, but I think it would be more practical to implement the colors as format identifiers, albeit making this approach a bit harder. \$\endgroup\$
    – syb0rg
    Jun 20, 2016 at 20:33
  • 2
    \$\begingroup\$ @syb0rg, a custom format specifier or set of control character(s) is nice too. The main benefit is that it makes easier to define new colors and layouts via data only. The downside is that you need an extra layer of parsing before the output. If I recall, that's how they did it in the in-game console of the Quakes and the first DOOMs. The ^ char followed by a number changed the text color, e.g.: ^1some text^0. \$\endgroup\$
    – glampert
    Jun 21, 2016 at 2:28
  • \$\begingroup\$ Would you happen to have a link to the source code doing that? I know those games are open source now, but I can't seem to locate where they colorize their output. \$\endgroup\$
    – syb0rg
    Jun 21, 2016 at 19:03
  • 1
    \$\begingroup\$ @syb0rg, I might be mistaken about that feature being present in the older games, but it is used starting from DOOM3, at least. Take a look at the constants here. Check out the functions ColorIndex and ColorForIndex in the string class as well. In the Console class you'll find the calls setting the text color. \$\endgroup\$
    – glampert
    Jun 21, 2016 at 21:17
7
\$\begingroup\$

As @LokiAstari says, hard-coding these escape strings into your program is a bad idea, as the escape sequences vary according to the type of terminal. There are libraries such as ncurses and terminfo that do just that.

These macros only work when their arguments are string literals, which may be surprising to callers.

There is also a lot of repetition, much of it caused by lack of orthogonality.

If you wanted to "keep it simple" and do this anyway, you would be better off with

#define COLOR_RESET  "\033[0m"
#define BOLD         "\033[1m"
#define BLACK_TEXT   "\033[30;1m"
#define RED_TEXT     "\033[31;1m"
#define GREEN_TEXT   "\033[32;1m"
#define YELLOW_TEXT  "\033[33;1m"
#define BLUE_TEXT    "\033[34;1m"
#define MAGENTA_TEXT "\033[35;1m"
#define CYAN_TEXT    "\033[36;1m"
#define WHITE_TEXT   "\033[37;1m"

… to be used like

fprintf(stdout, "Recognized text: %s\n", text ? text : (RED_TEXT "No text recognized." COLOR_RESET));

For colour reset use "\033[0;m" instead of "\033[0,"

\$\endgroup\$
2
\$\begingroup\$

The most annoying problem with ANSI escapes is that they make a redirected output pretty much unreadable. Try to run your_program > output.txt and then view output.txt in your favourite editor.

GNU utilities which do coloring usually test output stream for isatty; of course doing so in a compile time is impossible, so I don't think that macro approach is correct.

A C++ solution would be a custom iomanip. I don't see an equally elegant way with a plain C.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.