Colori in console

Introduzione

L'esigenza di emettere del testo colorato deriva dalla volontà di rendere più evidente l'esito dei test in GUT.

API Windows

Poiché le console Windows non supportano le sequenze ANSI[1], la modifica dei colori dei caratteri dev'essere gestita attraverso l'API Win32[2]. L'obiettivo è modificare il colore del testo per mezzo di appositi token:

std::cout << "default text color" << red << "red-colored text" << std::endl;

L'API Win32 definisce due device di output per ogni console, STD_OUTPUT_HANDLE e STD_ERROR_HANDLE rispettivamente. Entrambe corrispondono normalmente allo screen-buffer attivo della console. È facile verificare che – almeno su Windows XP – insistendo sullo stesso screen-buffer, le modifiche fatte su un dispositivo si riflettono sull'altro:

#include <iostream>
#include <windows.h>

int main() {

  HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);

  std::cout << "this is the cout's default color" << std::endl;
  SetConsoleTextAttribute(output, FOREGROUND_RED|FOREGROUND_INTENSITY);
  std::cout << "cout's foreground color has been set to red" << std::endl;

  HANDLE error = GetStdHandle(STD_ERROR_HANDLE);

  std::cerr << "this is the cerr's default color" << std::endl;
  SetConsoleTextAttribute(error, FOREGROUND_BLUE|FOREGROUND_INTENSITY);
  std::cerr << "cerr's foreground color has been set to blue" << std::endl;

  std::cout << "is the cout's foreground color still set to blue?" << std::endl;

  return 0;
}

// output:

Implementazione

L'idea iniziale prevedeva la definizione di due oggetti – OutputConsole e ErrorConsole – indistinguibili da cout e cerr rispettivamente. Alcuni esperimenti hanno dimostrato che la soluzione più semplice consiste invece nell'overloading dell'operatore std::ostream::operator<< (cfr. [3] per le peculiarità dell'API WriteConsole rispetto alla ridirezione su file).

Considerato l'obiettivo prefissato, i cambiamenti di colore comandati sui due flussi cout e cerr sono stati gestiti attraverso il solo dispositivo STD_OUTPUT_HANDLE:

#include <iostream>
#include <windows.h>

namespace color
{

std::ostream& black(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    0);
  return os;
}

std::ostream& navy(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    FOREGROUND_BLUE);
  return os;
}

std::ostream& green(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    FOREGROUND_GREEN);
  return os;
}

std::ostream& teal(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    FOREGROUND_GREEN|FOREGROUND_BLUE);
  return os;
}

std::ostream& maroon(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    FOREGROUND_RED);
  return os;
}

std::ostream& purple(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    FOREGROUND_RED|FOREGROUND_BLUE);
  return os;
}

std::ostream& olive(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    FOREGROUND_RED|FOREGROUND_GREEN);
  return os;
}

std::ostream& silver(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE);
  return os;
}

std::ostream& gray(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    FOREGROUND_INTENSITY);
  return os;
}

std::ostream& blue(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    FOREGROUND_BLUE|FOREGROUND_INTENSITY);
  return os;
}

std::ostream& lime(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    FOREGROUND_GREEN|FOREGROUND_INTENSITY);
  return os;
}

std::ostream& aqua(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY);
  return os;
}

std::ostream& red(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    FOREGROUND_RED|FOREGROUND_INTENSITY);
  return os;
}

std::ostream& fuchsia(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    FOREGROUND_RED|FOREGROUND_BLUE|FOREGROUND_INTENSITY);
  return os;
}

std::ostream& yellow(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_INTENSITY);
  return os;
}

std::ostream& white(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY);
  return os;
}

} // namespace color

int main() {

  std::cout << color::black   << "black"   << std::endl;
  std::cout << color::navy    << "navy"    << std::endl;
  std::cout << color::green   << "green"   << std::endl;
  std::cout << color::teal    << "teal"    << std::endl;
  std::cout << color::maroon  << "maroon"  << std::endl;
  std::cout << color::purple  << "purple"  << std::endl;
  std::cout << color::olive   << "olive"   << std::endl;
  std::cout << color::silver  << "silver"  << std::endl;
  std::cout << color::gray    << "gray"    << std::endl;
  std::cout << color::blue    << "blue"    << std::endl;
  std::cout << color::lime    << "lime"    << std::endl;
  std::cout << color::aqua    << "aqua"    << std::endl;
  std::cout << color::red     << "red"     << std::endl;
  std::cout << color::fuchsia << "fuchsia" << std::endl;
  std::cout << color::yellow  << "yellow"  << std::endl;
  std::cout << color::white   << "white"   << std::endl;

  return 0;
}

// output:

Quel che manca è la possibilità di ripristinare il colore originale del testo; a ciò si rimedia ricordandosi gli attributi originali della console e predisponendo un nuovo manipulator che li ripristini all'occorrenza:

namespace color
{

class Console_ {
  HANDLE handle_;
  WORD defaultAttrs_;
  static const WORD mask_ =
    FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY;
public:
  Console_()
   : handle_(GetStdHandle(STD_OUTPUT_HANDLE))
   , defaultAttrs_(getAttrs()) {
  }
  void setColor(WORD color) {
    SetConsoleTextAttribute(handle_, (getAttrs() & ~mask_) | color);
  }
  void resetColors() {
    SetConsoleTextAttribute(handle_, defaultAttrs_);
  }
protected:
  WORD getAttrs() {
    CONSOLE_SCREEN_BUFFER_INFO info;
    GetConsoleScreenBufferInfo(handle_, &info);
    return info.wAttributes;
  }
};

Console_& theConsole() {
  static Console_ console;
  return console;
}

void setColor_(WORD color) {
  theConsole().setColor(color);
}

void resetColors_() {
  theConsole().resetColors();
}

std::ostream& black(std::ostream& os) {
  SetConsoleTextAttribute(
    GetStdHandle(STD_OUTPUT_HANDLE),
    0);
  setColor_(0);
  return os;
}

// ...

std::ostream& reset(std::ostream& os) {
  resetColors_();
  return os;
}

} // namespace color

int main() {

  std::cout << color::black   << "black"   << std::endl;
  // ...
  std::cout << color::white   << "white"   << std::endl;
  std::cout << color::reset   << "default" << std::endl;

  return 0;
}

// output:

Note

  1. Sarebbe probabilmente opportuno forzare il flush dello stream prima di modificare i colori della console; ad oggi non ne ho ancora sperimentato la necessità.
  2. Per assicurare il ripristino dei colori iniziali della console al termine del programma basta una chiamata a resetColors nel distruttore di Console_:
    class Console_ {
      // ...
    public:
      Console_()
       : handle_(GetStdHandle(STD_OUTPUT_HANDLE))
       , defaultAttrs_(getAttrs()) {
      }
      ~Console_() {
        resetColors();
      }
      // ...
    };
    
  3. L'API SetConsoleTextAttribute consente anche la modifica del colore dello sfondo, qui non sfruttata.

Riferimenti

  1. "ANSI escape code". Wikipedia. <http://en.wikipedia.org/wiki/ANSI_escape_code>. Visitato il 29 Novembre 2012.
  2. "Console Reference". MSDN. <http://msdn.microsoft.com/en-us/library/ms682087.aspx>. Visitato il 29 Novembre 2012.
  3. "WriteConsole fails if it is used with a standard handle that is redirected to a file". MSDN. <http://msdn.microsoft.com/en-us/library/windows/desktop/ms687401%28v=vs.85%29.aspx>. Visitato il 29 Novembre 2012.

Pagina modificata il 05/12/2012