Skip to content

Commit

Permalink
Introduce the color token iterator
Browse files Browse the repository at this point in the history
  • Loading branch information
mbasaglia committed Aug 5, 2015
1 parent a495322 commit f5cfad6
Show file tree
Hide file tree
Showing 11 changed files with 532 additions and 808 deletions.
2 changes: 1 addition & 1 deletion daemon/src/common/Color.cpp
Expand Up @@ -148,7 +148,7 @@ int color_s::to_4bit() const
else if ( cmax >= 120 )
color = 7;

if ( cmax >= 164 )
if ( ( cmax + cmin ) / 2 >= 164 )
color |= 0b1000; // bright
}
else
Expand Down
207 changes: 186 additions & 21 deletions daemon/src/common/Color.h
Expand Up @@ -34,9 +34,12 @@ Maryland 20850 USA.
#define COMMON_COLOR_H_

#include <cstdint>
#include <iterator>
#include <limits>
#include <string>

#include "../engine/qcommon/q_unicode.h"

namespace Color {

/*
Expand Down Expand Up @@ -265,7 +268,6 @@ extern vec4_t colorMdOrange;
extern vec4_t colorMdBlue;

#define Q_COLOR_ESCAPE '^'
#define Q_COLOR_HEX 'x'

#define COLOR_BLACK '0'
#define COLOR_RED '1'
Expand Down Expand Up @@ -327,29 +329,10 @@ Checks that a string is in the form ^xHHH (where H is a hex digit)
template<class String>
inline bool Q_IsHexColorString( String&& p )
{
return p[0] == Q_COLOR_ESCAPE && p[1] == Q_COLOR_HEX
return p[0] == Q_COLOR_ESCAPE && p[1] == 'x'
&& ishex(p[2]) && ishex(p[3]) && ishex(p[4]);
}

/*
================
ColorFromHexString
Creates a color from a string, assumes Q_IsHexColorString(p)
================
*/
template<class String>
color_s ColorFromHexString( String&& p )
{
// Note: [0-15]*17 = [0-255]
return {
color_s::component_type(gethex(p[2])*17),
color_s::component_type(gethex(p[3])*17),
color_s::component_type(gethex(p[4])*17),
color_s::limits_type::max()
};
}

/*
================
Q_SkipColorString
Expand Down Expand Up @@ -382,6 +365,188 @@ template<class CharT>
return Q_SkipColorString(p, p);
}

template<class charT>
class BasicToken
{
public:
enum TokenType {
INVALID,
CHARACTER,
ESCAPE,
COLOR,
DEFAULT_COLOR,
};

BasicToken() = default;

BasicToken( const charT* begin, const charT* end, TokenType type )
: begin( begin ),
end( end ),
type( type )
{}

BasicToken( const charT* begin, const charT* end, const color_s& color )
: begin( begin ),
end( end ),
type( COLOR ),
color( color )
{}

const charT* Begin() const
{
return begin;
}

const charT* End() const
{
return end;
}

std::size_t Size() const
{
return end - begin;
}

TokenType Type() const
{
return type;
}

color_s Color() const
{
return color;
}

explicit operator bool() const
{
return type != INVALID && begin && begin < end;
}

private:

const charT* begin = nullptr;
const charT* end = nullptr;
TokenType type = INVALID;
color_s color;

};

class TokenAdvanceOne
{
public:
template<class CharT>
constexpr int operator()(const CharT*) const { return 1; }
};

class TokenAdvanceUtf8
{
public:
int operator()(const char* c) const
{
return Q_UTF8_Width(c);
}
};

template<class CharT, class TokenAdvanceT = TokenAdvanceOne>
class BasicTokenIterator
{
public:
using value_type = BasicToken<CharT>;
using reference = const value_type&;
using pointer = const value_type*;
using iterator_category = std::input_iterator_tag;
using difference_type = int;

BasicTokenIterator() = default;

explicit BasicTokenIterator( const CharT* input )
{
token = NextToken( input );
}

reference operator*() const
{
return token;
}

pointer operator->() const
{
return &token;
}

BasicTokenIterator& operator++()
{
token = NextToken( token.End() );
return *this;
}

BasicTokenIterator operator++(int)
{
auto copy = *this;
token = NextToken( token.End() );
return copy;
}

bool operator==( const BasicTokenIterator& rhs ) const
{
return token.Begin() == rhs.token.Begin();
}

bool operator!=( const BasicTokenIterator& rhs ) const
{
return token.Begin() != rhs.token.Begin();
}

void Skip( difference_type bytes )
{
if ( bytes != 0 )
{
token = NextToken( token.Begin() + bytes );
}
}

private:
value_type NextToken(const CharT* input)
{
if ( !input || *input == '\0' )
{
return value_type();
}

if ( input[0] == '^' )
{
if ( input[1] == '^' )
{
return value_type( input, input+2, value_type::ESCAPE );
}
else if ( input[1] == COLOR_NULL )
{
return value_type( input, input+2, value_type::DEFAULT_COLOR );
}
else if ( input[1] >= '0' && input[1] < 'p' )
{
return value_type( input, input+2, color_s( char( input[1] ) ) );
}
else if ( input[1] == 'x' && ishex( input[2] ) && ishex( input[3] ) && ishex( input[4] ) )
{
return value_type( input, input+5, color_s(
gethex( input[2] ) * 17,
gethex( input[3] ) * 17,
gethex( input[4] ) * 17,
1
) );
}
}

return value_type( input, input + TokenAdvanceT()( input ), value_type::CHARACTER );
}

value_type token;
};

using Token = BasicToken<char>;
using TokenIterator = BasicTokenIterator<char, TokenAdvanceUtf8>;

} // namespace Color

#endif // COMMON_COLOR_H_
47 changes: 19 additions & 28 deletions daemon/src/engine/client/cl_console.cpp
Expand Up @@ -549,7 +549,6 @@ If no console is visible, the text will appear at the top of the game window
bool CL_InternalConsolePrint( const char *text )
{
int y;
int c, i;
int wordLen = 0;

// for some demos we don't want to ever show anything on the console
Expand Down Expand Up @@ -581,40 +580,40 @@ bool CL_InternalConsolePrint( const char *text )
cgvm.CGameConsoleLine( text );
}

Color::color_s color( CONSOLE_COLOR );
Color::color_s console_color( CONSOLE_COLOR );
Color::color_s color = console_color;

while ( ( c = *text & 0xFF ) != 0 )
for ( Color::TokenIterator it ( text ); *it; ++it )
{
if ( Color::Q_IsColorString( text ) )
if ( it->Type() == Color::Token::COLOR )
{
color = Color::color_s( text[ 1 ] == COLOR_NULL ? CONSOLE_COLOR : text[ 1 ] );
text += 2;
color = it->Color();
color.a = console_color.a;
continue;
}
else if ( Color::Q_IsHexColorString( text ) )
else if ( it->Type() == Color::Token::DEFAULT_COLOR )
{
color = Color::ColorFromHexString(text);
text += 5;
color = console_color;
continue;
}

if ( !wordLen )
{
// count word length
for ( i = 0; ; ++wordLen )
for ( Color::TokenIterator i = it; *i; ++i )
{
if ( text[ i ] <= ' ' && text[ i ] >= 0 )
if ( i->Type() == Color::Token::ESCAPE )
{
break;
wordLen++;
}
if ( Color::Q_SkipColorString( text + i, i ) )
continue;
if ( text[ i ] == Q_COLOR_ESCAPE && text[ i + 1 ] == Q_COLOR_ESCAPE )
else if ( i->Type() == Color::Token::CHARACTER )
{
++i;
if ( std::isspace( *i->Begin() ) )
{
break;
}
wordLen++;
}

i += Q_UTF8_Width( text + i );
}

// word wrap
Expand All @@ -625,7 +624,7 @@ bool CL_InternalConsolePrint( const char *text )
}
}

switch ( c )
switch ( *it->Begin() )
{
case '\n':
Con_Linefeed( );
Expand All @@ -635,18 +634,11 @@ bool CL_InternalConsolePrint( const char *text )
consoleState.horizontalCharOffset = 0;
break;

case Q_COLOR_ESCAPE:
if ( text[ 1 ] == Q_COLOR_ESCAPE )
{
++text;
}
/* no break */

default: // display character and advance
y = consoleState.currentLine % consoleState.maxScrollbackLengthInLines;
// rain - sign extension caused the character to carry over
// into the color info for high ascii chars; casting c to unsigned
consoleState.text[ y * consoleState.textWidthInChars + consoleState.horizontalCharOffset ].ch = Q_UTF8_CodePoint( text );
consoleState.text[ y * consoleState.textWidthInChars + consoleState.horizontalCharOffset ].ch = Q_UTF8_CodePoint( it->Begin() );
consoleState.text[ y * consoleState.textWidthInChars + consoleState.horizontalCharOffset ].ink = color;
++consoleState.horizontalCharOffset;
if ( wordLen > 0 )
Expand All @@ -662,7 +654,6 @@ bool CL_InternalConsolePrint( const char *text )
break;
}

text += Q_UTF8_Width( text );
}

return true;
Expand Down

0 comments on commit f5cfad6

Please sign in to comment.