/************************************************************************* ** Message.cpp ** ** ** ** This file is part of dvisvgm -- a fast DVI to SVG converter ** ** Copyright (C) 2005-2024 Martin Gieseking ** ** ** ** This program is free software; you can redistribute it and/or ** ** modify it under the terms of the GNU General Public License as ** ** published by the Free Software Foundation; either version 3 of ** ** the License, or (at your option) any later version. ** ** ** ** This program is distributed in the hope that it will be useful, but ** ** WITHOUT ANY WARRANTY; without even the implied warranty of ** ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** ** GNU General Public License for more details. ** ** ** ** You should have received a copy of the GNU General Public License ** ** along with this program; if not, see . ** *************************************************************************/ #include #include #include #include #include #include "Message.hpp" #include "Terminal.hpp" using namespace std; MessageStream::MessageStream (std::ostream &os) noexcept : _os(&os), _nl(true) { Terminal::init(os); } MessageStream::~MessageStream () { if (_os && Message::COLORIZE) Terminal::finish(*_os); } void MessageStream::putChar (char c, ostream &os) { switch (c) { case '\r': os << '\r'; _nl = true; _col = 1; return; case '\n': if (!_nl) { _col = 1; _nl = true; os << '\n'; } return; default: if (_nl) { os << string(_indent, ' '); _col += _indent; } else { const int cols = Terminal::columns(); if (cols > 0 && _col >= cols) { #ifndef _WIN32 // move cursor to next line explicitly (not necessary in Windows/DOS terminal) os << '\n'; #endif os << string(_indent, ' '); _col = _indent+1; } else _col++; } _nl = false; if (c != '\n') os << c; } } MessageStream& MessageStream::operator << (const char *str) { if (_os && str) { const char *first = str; while (*first) { const char *last = strchr(first, '\n'); if (!last) last = first+strlen(first)-1; #ifndef _WIN32 // move cursor to next line explicitly (not necessary in Windows/DOS terminal) const int cols = Terminal::columns(); int len = last-first+1; if (cols > 0 && _col+len > cols && _indent+len <= cols) putChar('\n', *_os); #endif while (first <= last) putChar(*first++, *_os); first = last+1; } } return *this; } MessageStream& MessageStream::operator << (const char &c) { if (_os) putChar(c, *_os); return *this; } void MessageStream::indent (bool reset) { if (reset) _indent = 0; _indent += 2; } void MessageStream::outdent (bool all) { if (all) _indent = 0; else if (_indent > 0) _indent -= 2; } void MessageStream::clearline () { if (_os) { int cols = Terminal::columns(); *_os << '\r' << string(cols ? cols-1 : 79, ' ') << '\r'; _nl = true; _col = 1; } } static MessageStream nullStream; static MessageStream messageStream(cerr); ////////////////////////////// // maximal verbosity int Message::LEVEL = Message::MESSAGES | Message::WARNINGS | Message::ERRORS; bool Message::COLORIZE = false; bool Message::_initialized = false; Message::Color Message::_classColors[9]; /** Returns the stream for usual messages. */ MessageStream& Message::mstream (bool prefix, MessageClass mclass) { init(); MessageStream *ms = (LEVEL & MESSAGES) ? &messageStream : &nullStream; if (COLORIZE && ms && ms->os()) { Terminal::fgcolor(_classColors[mclass].foreground, *ms->os()); Terminal::bgcolor(_classColors[mclass].background, *ms->os()); } if (prefix) *ms << "\nMESSAGE: "; return *ms; } /** Returns the stream for warning messages. */ MessageStream& Message::wstream (bool prefix) { init(); MessageStream *ms = (LEVEL & WARNINGS) ? &messageStream : &nullStream; if (COLORIZE && ms && ms->os()) { Terminal::fgcolor(_classColors[MC_WARNING].foreground, *ms->os()); Terminal::bgcolor(_classColors[MC_WARNING].background, *ms->os()); } if (prefix) *ms << "\nWARNING: "; return *ms; } /** Returns the stream for error messages. */ MessageStream& Message::estream (bool prefix) { init(); MessageStream *ms = (LEVEL & ERRORS) ? &messageStream : &nullStream; if (COLORIZE && ms && ms->os()) { Terminal::fgcolor(_classColors[MC_ERROR].foreground, *ms->os()); Terminal::bgcolor(_classColors[MC_ERROR].background, *ms->os()); } if (prefix) *ms << "\nERROR: "; return *ms; } /** Returns the output stream for user messages * @param[in] always ignore verbosity settings if true */ MessageStream& Message::ustream (bool always) { init(); MessageStream *ms = (always || (LEVEL & USERMESSAGES)) ? &messageStream : &nullStream; return *ms; } static bool colorchar2int (char colorchar, int *val) { colorchar = tolower(colorchar); if (colorchar >= '0' && colorchar <= '9') *val = int(colorchar-'0'); else if (colorchar >= 'a' && colorchar <= 'f') *val = int(colorchar-'a'+10); else if (colorchar == '*') *val = -1; else return false; return true; } /** Initializes the Message class. Sets the colors for each message set. * The colors can be changed via environment variable DVISVGM_COLORS. Its * value must be a sequence of color entries of the form gg:BF where the * two-letter ID gg specifies a message set, B the hex digit of the * background, and F the hex digit of the foreground/text color. * Color codes: * - 1: red, 2: green, 4: blue * - 0-7: dark colors * - 8-F: light colors * - *: default color * Example: pn:01 sets page number messages to red on black background */ void Message::init () { if (_initialized || !Message::COLORIZE) return; // set default message colors _classColors[MC_ERROR] = Color(Terminal::RED, true); _classColors[MC_WARNING] = Color(Terminal::YELLOW); _classColors[MC_PAGE_NUMBER] = Color(Terminal::BLUE, true); _classColors[MC_PAGE_SIZE] = Color(Terminal::MAGENTA); _classColors[MC_PAGE_WRITTEN] = Color(Terminal::GREEN); _classColors[MC_STATE] = Color(Terminal::CYAN); _classColors[MC_TRACING] = Color(Terminal::BLUE); _classColors[MC_PROGRESS] = Color(Terminal::MAGENTA); if (const char *color_str = getenv("DVISVGM_COLORS")) { unordered_map classes = { {"er", MC_ERROR}, {"wn", MC_WARNING}, {"pn", MC_PAGE_NUMBER}, {"ps", MC_PAGE_SIZE}, {"fw", MC_PAGE_WRITTEN}, {"sm", MC_STATE}, {"tr", MC_TRACING}, {"pi", MC_PROGRESS}, }; const char *p=color_str; // skip leading whitespace while (isspace(*p)) ++p; // iterate over color assignments while (strlen(p) >= 5) { auto it = classes.find(string(p, 2)); if (it != classes.end() && p[2] == '=') { int bgcolor, fgcolor; if (colorchar2int(p[3], &bgcolor) && colorchar2int(p[4], &fgcolor)) { _classColors[it->second].background = bgcolor; _classColors[it->second].foreground = fgcolor; } } p += 5; // skip trailing characters in a malformed entry while (*p && !isspace(*p) && *p != ':' && *p != ';') ++p; // skip separation characters while (isspace(*p) || *p == ':' || *p == ';') ++p; } } _initialized = true; }