text: /*
     Copyright (C) 2001-2006, William Joseph.
     All Rights Reserved.
  
     This file is part of GtkRadiant.
  
     GtkRadiant 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 2 of the License, or
     (at your option) any later version.
  
     GtkRadiant 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 GtkRadiant; if not, write to the Free Software
     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
   */
  
  #if !defined( INCLUDED_CONVERT_H )
  #define INCLUDED_CONVERT_H
  
  /// \file
  /// \brief Character encoding conversion.
  
  #include "debugging/debugging.h"
  #include <algorithm>
  #include <glib.h>
  
  #include "character.h"
  
  /// \brief Returns the number of bytes required to represent \p character in UTF-8 encoding.
  inline std::size_t utf8_character_length( const char* character ){
  	if ( ( *character & 0xE0 ) == 0xC0 ) { // 110xxxxx
  		return 2;
  	}
  	else if ( ( *character & 0xF0 ) == 0xE0 ) { // 1110xxxx
  		return 3;
  	}
  	else if ( ( *character & 0xF8 ) == 0xF0 ) { // 11110xxx
  		return 4;
  	}
  	else if ( ( *character & 0xFC ) == 0xF8 ) { // 111110xx
  		return 5;
  	}
  	else if ( ( *character & 0xFE ) == 0xFC ) { // 1111110x
  		return 6;
  	}
  	ERROR_MESSAGE( "" );
  	return 0;
  }
  
  struct UTF8Character
  {
  	const char* buffer;
  	std::size_t length;
  	UTF8Character() : buffer( 0 ), length( 0 )
  	{
  	}
  	UTF8Character( const char* bytes ) : buffer( bytes ), length( utf8_character_length( bytes ) )
  	{
  	}
  };
  
  inline bool operator < ( const UTF8Character &self, const UTF8Character &other )
  {
  	return std::lexicographical_compare( self.buffer, self.buffer + self.length, other.buffer, other.buffer + other.length );
  }
  
  /// \brief Writes \p c to \p ostream in Hex form. Useful for debugging.
  template<typename TextOutputStreamType>
  inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const UTF8Character& c ){
  	for ( const char* p = c.buffer; p != c.buffer + c.length; ++p )
  	{
  		ostream << HexChar( *p );
  	}
  	return ostream;
  }
  
  
  
  /// \brief The character-set encoding for the current C locale.
  ///
  /// Obtain the global instance with globalCharacterSet().
  class CharacterSet
  {
  	const char* m_charSet;
  public:
  	CharacterSet()
  	{
  		if ( g_get_charset( &m_charSet ) != FALSE ) {
  			m_charSet = 0;
  		}
  	}
  	bool isUTF8() const {
  		return m_charSet == 0;
  	}
  	const char* get() const {
  		return m_charSet;
  	}
  };
  
  typedef LazyStatic<CharacterSet> GlobalCharacterSet;
  
  /// \brief Returns the global instance of CharacterSet.
  inline CharacterSet& globalCharacterSet(){
  	return GlobalCharacterSet::instance();
  }
  
  
  class UTF8CharacterToExtendedASCII
  {
  public:
  	UTF8Character m_utf8;
  	char m_c;
  	UTF8CharacterToExtendedASCII() : m_c( '\0' )
  	{
  	}
  	UTF8CharacterToExtendedASCII( const UTF8Character &utf8, char c ) : m_utf8( utf8 ), m_c( c )
  	{
  	}
  };
  
  inline bool operator < ( const UTF8CharacterToExtendedASCII &self, const UTF8CharacterToExtendedASCII &other )
  {
  	return self.m_utf8 < other.m_utf8;
  }
  
  inline std::size_t extended_ascii_to_index( char c ){
  	return static_cast<std::size_t>( c & 0x7F );
  }
  
  inline char extended_ascii_for_index( std::size_t i ){
  	return static_cast<char>( i | 0x80 );
  }
  
  /// \brief The active extended-ascii character set encoding.
  /// Performs UTF-8 encoding and decoding of extended-ascii characters.
  ///
  /// Obtain the global instance with globalExtendedASCIICharacterSet().
  class ExtendedASCIICharacterSet
  {
  	typedef char UTF8CharBuffer[6];
  	UTF8CharBuffer m_converted[128];
  	UTF8Character m_decodeMap[128];
  	UTF8CharacterToExtendedASCII m_encodeMap[128];
  public:
  	ExtendedASCIICharacterSet()
  	{
  		if ( !globalCharacterSet().isUTF8() ) {
  			GIConv descriptor = g_iconv_open( "UTF-8", globalCharacterSet().get() );
  			for ( std::size_t i = 1; i < 128; ++i )
  			{
  				char c = extended_ascii_for_index( i );
  				char* inbuf = &c;
  				std::size_t inbytesleft = 1;
  				char* outbuf = m_converted[i];
  				std::size_t outbytesleft = 6;
  				if ( g_iconv( descriptor, &inbuf, &inbytesleft, &outbuf, &outbytesleft ) != (size_t)( -1 ) ) {
  					UTF8Character utf8( m_converted[i] );
  					m_decodeMap[i] = utf8;
  					m_encodeMap[i] = UTF8CharacterToExtendedASCII( utf8, c );
  				}
  			}
  			g_iconv_close( descriptor );
  			std::sort( m_encodeMap, m_encodeMap + 128 );
  		}
  	}
  	/// \brief Prints the (up to) 128 characters in the current extended-ascii character set.
  	/// Useful for debugging.
  	void print() const {
  		globalOutputStream() << "UTF-8 conversion required from charset: " << globalCharacterSet().get() << "\n";
  		for ( std::size_t i = 1; i < 128; ++i )
  		{
  			if ( m_decodeMap[i].buffer != 0 ) {
  				globalOutputStream() << extended_ascii_for_index( i ) << " = " << m_decodeMap[i] << "\n";
  			}
  		}
  	}
  	/// \brief Returns \p c decoded from extended-ascii to UTF-8.
  	/// \p c must be an extended-ascii character.
  	const UTF8Character& decode( char c ) const {
  		ASSERT_MESSAGE( !globalCharacterSet().isUTF8(), "locale is utf8, no conversion required" );
  		ASSERT_MESSAGE( !char_is_ascii( c ), "decode: ascii character" );
  		ASSERT_MESSAGE( m_decodeMap[extended_ascii_to_index( c )].buffer != 0, "decode: invalid character: " << HexChar( c ) );
  		return m_decodeMap[extended_ascii_to_index( c )];
  	}
  	/// \brief Returns \p c encoded to extended-ascii from UTF-8.
  	/// \p c must map to an extended-ascii character.
  	char encode( const UTF8Character& c ) const {
  		ASSERT_MESSAGE( !globalCharacterSet().isUTF8(), "locale is utf8, no conversion required" );
  		ASSERT_MESSAGE( !char_is_ascii( *c.buffer ), "encode: ascii character" );
  		std::pair<const UTF8CharacterToExtendedASCII*, const UTF8CharacterToExtendedASCII*> range
  			= std::equal_range( m_encodeMap, m_encodeMap + 128, UTF8CharacterToExtendedASCII( c, 0 ) );
  		ASSERT_MESSAGE( range.first != range.second, "encode: invalid character: " << c );
  		return ( *range.first ).m_c;
  	}
  };
  
  typedef LazyStatic<ExtendedASCIICharacterSet> GlobalExtendedASCIICharacterSet;
  
  /// \brief Returns the global instance of ExtendedASCIICharacterSet.
  inline ExtendedASCIICharacterSet& globalExtendedASCIICharacterSet(){
  	return GlobalExtendedASCIICharacterSet::instance();
  }
  
  class ConvertUTF8ToLocale
  {
  public:
  	StringRange m_range;
  	ConvertUTF8ToLocale( const char* string ) : m_range( StringRange( string, string + strlen( string ) ) )
  	{
  	}
  	ConvertUTF8ToLocale( const StringRange &range ) : m_range( range )
  	{
  	}
  };
  
  /// \brief Writes \p convert to \p ostream after encoding each character to extended-ascii from UTF-8.
  template<typename TextOutputStreamType>
  inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const ConvertUTF8ToLocale& convert ){
  	if ( globalCharacterSet().isUTF8() ) {
  		return ostream << convert.m_range;
  	}
  
  	for ( const char* p = convert.m_range.first; p != convert.m_range.last; )
  	{
  		if ( !char_is_ascii( *p ) ) {
  			UTF8Character c( p );
  			ostream << globalExtendedASCIICharacterSet().encode( c );
  			p += c.length;
  		}
  		else
  		{
  			ostream << *p++;
  		}
  	}
  	return ostream;
  }
  
  
  class ConvertLocaleToUTF8
  {
  public:
  	StringRange m_range;
  	ConvertLocaleToUTF8( const char* string ) : m_range( StringRange( string, string + strlen( string ) ) )
  	{
  	}
  	ConvertLocaleToUTF8( const StringRange &range ) : m_range( range )
  	{
  	}
  };
  
  /// \brief Writes \p convert to \p ostream after decoding each character from extended-ascii to UTF-8.
  template<typename TextOutputStreamType>
  inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const ConvertLocaleToUTF8& convert ){
  	if ( globalCharacterSet().isUTF8() ) {
  		return ostream << convert.m_range;
  	}
  
  	for ( const char* p = convert.m_range.first; p != convert.m_range.last; ++p )
  	{
  		if ( !char_is_ascii( *p ) ) {
  			UTF8Character c( globalExtendedASCIICharacterSet().decode( *p ) );
  			ostream.write( c.buffer, c.length );
  		}
  		else
  		{
  			ostream << *p;
  		}
  	}
  	return ostream;
  }
  
  
  #endif
  