/*
								+--------------------------------+
								|                                |
								|  ***  OpenGL bitmap font  ***  |
								|                                |
								|  Copyright © -tHE SWINe- 2010  |
								|                                |
								|        BitmapFont2.cpp         |
								|                                |
								+--------------------------------+
*/

/**
 *	@file BitmapFont2.cpp
 *	@author -tHE SWINe-
 *	@brief a simple OpenGL bitmap font implementation
 *	@date 2010
 */

#include "NewFix.h"
#include "CallStack.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <vector>
#include <string>
#include <set>
#include <map>
#include <algorithm>
#include "BitmapFont2.h"

using namespace bitmapfont2;
// some objects are contained in this particular namespace to avoid
// pollutiong the global namespace with overly generic names

/**
 *								=== CGLBitmapFont2::CCharacterMap ===
 */

bool CGLBitmapFont2::CCharacterMap::Create(const CBitmapFont::CPageList &r_page_list, int n_geom_padding)
{
	for(size_t i = 0; i < direct_lookup_Size; ++ i)
		m_t_lookup[i].n_code = -1; // "unused"
	m_char_map.clear(); // not necessary

	CBitmapFont::CPageList::const_iterator p_page_it = r_page_list.begin(), p_end_it = r_page_list.end();
	for(size_t n_page = 0; p_page_it != p_end_it; ++ p_page_it, ++ n_page) {
		const std::vector<CBitmapFont::CPageList::value_type::TItemRefType> &r_char_list = (*p_page_it).r_ItemList();
		for(size_t i = 0, n = r_char_list.size(); i < n; ++ i) {
			bitmapfont2::TGlyphInfo t_info = *r_char_list[i];
			int n_code = t_info.n_code;
			_ASSERTE(n_page <= INT_MAX);
			t_info.n_code = int(n_page);
			// reuse TCharacterInfo::n_code as page id

#ifdef __GLFONT_PRECRUNCH_GLYPH_GEOMETRY
			PreCrunch(t_info, n_geom_padding);
#endif // __GLFONT_PRECRUNCH_GLYPH_GEOMETRY

			if(n_code >= 0 && n_code < direct_lookup_Size)
				m_t_lookup[n_code] = t_info;
			else {
				try {
					m_char_map.insert(std::pair<const int,
						bitmapfont2::TGlyphInfo>(n_code, t_info));
				} catch(std::bad_alloc&) {
					return false;
				}
			}
			// add it to the list
		}
	}

	return true;
}

#ifdef __GLFONT_PRECRUNCH_GLYPH_GEOMETRY

void CGLBitmapFont2::CCharacterMap::PreCrunch(bitmapfont2::TGlyphInfo &r_t_info, int n_geom_padding)
{
	if(r_t_info.n_bitmap_width > 0 && r_t_info.n_bitmap_height > 0) {
		r_t_info.n_bitmap_width += 2 * n_geom_padding;
		r_t_info.n_bitmap_height += 2 * n_geom_padding;
		// include padding in bitmap size

		r_t_info.n_baseline_offset_x -= n_geom_padding;
		r_t_info.n_baseline_offset_y -= n_geom_padding;
		// shift baseline against the padding direction

		r_t_info.n_bitmap_pos_x -= n_geom_padding;
		r_t_info.n_bitmap_pos_y -= n_geom_padding;
		// and bitmap position as well
	}
}

#endif // __GLFONT_PRECRUNCH_GLYPH_GEOMETRY

const bitmapfont2::TGlyphInfo *CGLBitmapFont2::CCharacterMap::p_Get_CharacterInfo(int n_codepoint) const
{
	if(n_codepoint >= 0 && n_codepoint < direct_lookup_Size) {
		if(m_t_lookup[n_codepoint].n_code >= 0)
			return &m_t_lookup[n_codepoint];
		return 0;
	}
	// use direct lookup for low codes (typically to resolve us-english)

	std::map<int, bitmapfont2::TGlyphInfo>::const_iterator p_map_it = m_char_map.find(n_codepoint);
	if(p_map_it != m_char_map.end())
		return &(*p_map_it).second;
	return 0;
}

/**
 *								=== ~CGLBitmapFont2::CCharacterMap ===
 */

/**
 *								=== CGLBitmapFont2::CTextureArrayBuilder ===
 */

CGLBitmapFont2::CTextureArrayBuilder::CTextureArrayBuilder(bool b_generate_mipmaps, GLenum n_internal_format,
	bool b_use_npot, const CBitmapFont &r_bitmap_font, uint32_t n_blank_color)
	:m_n_internal_format(n_internal_format), m_b_use_npot(b_use_npot),
	m_b_generate_mipmaps(b_generate_mipmaps), m_r_bitmap_font(r_bitmap_font),
	m_p_texture(0), m_n_blank_color(n_blank_color)
{}

bool CGLBitmapFont2::CTextureArrayBuilder::operator ()(size_t n_page, size_t n_page_num, const TBmp &r_t_bitmap)
{
	if(!n_page) {
		_ASSERTE(!m_p_texture);
		const bitmapfont2::TFontInfo &r_t_font_info = m_r_bitmap_font.t_Font_Info();
		// get font info

		int n_tex_width = r_t_font_info.n_max_page_width;
		int n_tex_height = r_t_font_info.n_max_page_height;
		if(!m_b_use_npot) {
			n_tex_width = n_Make_POT(n_tex_width);
			n_tex_height = n_Make_POT(n_tex_height);
		}
		int n_tex_layer_num = r_t_font_info.n_page_num;
		// get texture geometry

		float f_max_mip_level = 1 + log(float(min(r_t_font_info.n_glyph_geom_padding,
			r_t_font_info.n_glyph_tex_padding))) / log(2.0f);
		// calculate maximal safe mip level

		if(!(m_p_texture = new(std::nothrow) CGLTexture_2D_Array(n_tex_width,
		   n_tex_height, n_tex_layer_num, m_n_internal_format, m_b_generate_mipmaps, GL_RGBA, // format must be specified for GLES
		   GL_UNSIGNED_BYTE)))
			return false;
#ifndef __BITMAP_FONT_GLES20
		if(m_b_generate_mipmaps)
			m_p_texture->Set_Max_Level(int(f_max_mip_level + .5f)); // not in GLES20
#endif // !__BITMAP_FONT_GLES20
		// alloc the texture, but submit no data

		m_p_texture->Set_Wrap_S(GL_CLAMP_TO_EDGE);
		m_p_texture->Set_Wrap_T(GL_CLAMP_TO_EDGE);
		// avoids artifacts for letters at the edges
	} else {
		if(!m_p_texture)
			return false;
		// the texture must be allocated
	}
	_ASSERTE(size_t(m_p_texture->n_Layer_Num()) >= n_page_num);
	// make sure the texture is allocated

	m_p_texture->Bind();
	// make sure the texture is bound (that is probably not necessary)

	if(r_t_bitmap.n_width < m_p_texture->n_Width() ||
	   r_t_bitmap.n_height < m_p_texture->n_Height()) {
		TBmp *p_blank_layer;
		if(!(p_blank_layer = TBmp::p_CreateBitmap(m_p_texture->n_Width(), m_p_texture->n_Height())))
			return false;
		p_blank_layer->Clear(m_n_blank_color);
		// alloc a block of memory and clear it
		// @todo - only clear the bottom-right "L" in the texture, only alloc as much memory as needed for that

		_ASSERTE(n_page <= INT_MAX);
#ifdef __BITMAP_FONT_GLES20
		glTexSubImage3DOES(m_p_texture->n_Target(), 0, 0, 0, int(n_page), p_blank_layer->n_width,
			p_blank_layer->n_height, 1, GL_RGBA, GL_UNSIGNED_BYTE, p_blank_layer->p_buffer);
#else // __BITMAP_FONT_GLES20
		glTexSubImage3D(m_p_texture->n_Target(), 0, 0, 0, int(n_page), p_blank_layer->n_width,
			p_blank_layer->n_height, 1, GL_RGBA, GL_UNSIGNED_BYTE, p_blank_layer->p_buffer);
#endif // __BITMAP_FONT_GLES20
		// clear this level with black color

		p_blank_layer->Delete();
		// delete the block of memory
	}
	// in case the bitmap doesn't fill the whole texture, clear it manually (could also use a FBO)

	_ASSERTE(n_page <= INT_MAX);
#ifdef __BITMAP_FONT_GLES20
	glTexSubImage3DOES(m_p_texture->n_Target(), 0, 0, 0, int(n_page), r_t_bitmap.n_width,
		r_t_bitmap.n_height, 1, GL_RGBA, GL_UNSIGNED_BYTE, r_t_bitmap.p_buffer);
#else // __BITMAP_FONT_GLES20
	glTexSubImage3D(m_p_texture->n_Target(), 0, 0, 0, int(n_page), r_t_bitmap.n_width,
		r_t_bitmap.n_height, 1, GL_RGBA, GL_UNSIGNED_BYTE, r_t_bitmap.p_buffer);
#endif // __BITMAP_FONT_GLES20
	// specify a single layer at a time

	if(n_page + 1 == n_page_num && m_b_generate_mipmaps)
		glGenerateMipmap(m_p_texture->n_Target());
	// refresh mipmaps after uploading the whole texture

	return true;
}


/*inline CGLTexture_2D_Array *CGLBitmapFont2::CTextureArrayBuilder::p_Get_Texture()
{
	return m_p_texture;
}*/ // in header

/**
 *								=== ~CGLBitmapFont2::CTextureArrayBuilder ===
 */

/**
 *								=== CGLBitmapFont2::CFakeTextureArrayBuilder ===
 */

CGLBitmapFont2::CFakeTextureArrayBuilder::CFakeTextureArrayBuilder(bool b_generate_mipmaps, GLenum n_internal_format,
	bool b_use_npot, const CBitmapFont &r_bitmap_font, uint32_t n_blank_color)
	:m_n_internal_format(n_internal_format), m_b_use_npot(b_use_npot),
	m_b_generate_mipmaps(b_generate_mipmaps), m_r_bitmap_font(r_bitmap_font),
	m_p_texture(0), m_n_blank_color(n_blank_color)
{}

bool CGLBitmapFont2::CFakeTextureArrayBuilder::operator ()(size_t n_page, size_t n_page_num, const TBmp &r_t_bitmap)
{
	if(!n_page) {
		_ASSERTE(!m_p_texture);
		const bitmapfont2::TFontInfo &r_t_font_info = m_r_bitmap_font.t_Font_Info();
		// get font info

		int n_tex_width = r_t_font_info.n_max_page_width;
		int n_tex_height = r_t_font_info.n_max_page_height;
		if(!m_b_use_npot) {
			n_tex_width = n_Make_POT(n_tex_width);
			n_tex_height = n_Make_POT(n_tex_height);
		}
		int n_tex_layer_num = r_t_font_info.n_page_num;
		// get texture geometry

		float f_max_mip_level = 1 + log(float(min(r_t_font_info.n_glyph_geom_padding,
			r_t_font_info.n_glyph_tex_padding))) / log(2.0f);
		// calculate maximal safe mip level

		if(n_tex_layer_num > 1)
			return false;
		// can do just a single layer textures

		if(!(m_p_texture = new(std::nothrow) CGLTexture_2D(n_tex_width,
		   n_tex_height, m_n_internal_format, m_b_generate_mipmaps, GL_RGBA, // format must be specified for GLES
		   GL_UNSIGNED_BYTE)))
			return false;
#ifndef __BITMAP_FONT_GLES20
		if(m_b_generate_mipmaps)
			m_p_texture->Set_Max_Level(int(f_max_mip_level + .5f)); // not in GLES20
#endif // !__BITMAP_FONT_GLES20
		// alloc the texture, but submit no data

		m_p_texture->Set_Wrap_S(GL_CLAMP_TO_EDGE);
		m_p_texture->Set_Wrap_T(GL_CLAMP_TO_EDGE);
		// avoids artifacts for letters at the edges
	} else {
		if(!m_p_texture)
			return false;
		// the texture must be allocated
	}
	// make sure the texture is allocated

	m_p_texture->Bind();
	// make sure the texture is bound (that is probably not necessary)

	if(r_t_bitmap.n_width < m_p_texture->n_Width() ||
	   r_t_bitmap.n_height < m_p_texture->n_Height()) {
		TBmp *p_blank_layer;
		if(!(p_blank_layer = TBmp::p_CreateBitmap(m_p_texture->n_Width(), m_p_texture->n_Height())))
			return false;
		p_blank_layer->Clear(m_n_blank_color);
		// alloc a block of memory and clear it
		// @todo - only clear the bottom-right "L" in the texture, only alloc as much memory as needed for that

		glTexSubImage2D(m_p_texture->n_Target(), 0, 0, 0, p_blank_layer->n_width,
			p_blank_layer->n_height, GL_RGBA, GL_UNSIGNED_BYTE, p_blank_layer->p_buffer);
		// clear this level with black color

		p_blank_layer->Delete();
		// delete the block of memory
	}
	// in case the bitmap doesn't fill the whole texture, clear it manually (could also use a FBO)

	glTexSubImage2D(m_p_texture->n_Target(), 0, 0, 0, r_t_bitmap.n_width,
		r_t_bitmap.n_height, GL_RGBA, GL_UNSIGNED_BYTE, r_t_bitmap.p_buffer);
	// specify a single layer at a time

	if(n_page + 1 == n_page_num && m_b_generate_mipmaps)
		glGenerateMipmap(m_p_texture->n_Target());
	// refresh mipmaps after uploading the whole texture

	return true;
}

/*inline CGLTexture_2D_Array *CGLBitmapFont2::CFakeTextureArrayBuilder::p_Get_Texture()
{
	return m_p_texture;
}*/ // in header

/**
 *								=== ~CGLBitmapFont2::CFakeTextureArrayBuilder ===
 */

/**
 *								=== CGLBitmapFont2 ===
 */

CGLBitmapFont2::CGLBitmapFont2()
	:m_p_texture(0), m_n_mode(GL_TRIANGLES), m_b_allow_degenerate(false),
	m_n_primitive_restart_index(0xffff), m_n_extra_vertex_element_num(0)
{}

CGLBitmapFont2::~CGLBitmapFont2()
{
	if(m_p_texture) {
		if(b_IsTexture2D(m_p_texture->n_Target()))
			delete (CGLTexture_2D*)m_p_texture;
		else {
			_ASSERTE(b_IsTexture3D(m_p_texture->n_Target()));
			delete m_p_texture;
		}
	}
}

bool CGLBitmapFont2::Create(const char *p_s_font_face, int n_font_size,
	CBitmapFont &r_bitmap_font, bool b_generate_mipmaps, GLenum n_internal_format,
	bool b_use_npot, const char *p_s_store_filename)
{
	if(m_p_texture) {
		if(m_p_texture) {
			if(b_IsTexture2D(m_p_texture->n_Target()))
				delete (CGLTexture_2D*)m_p_texture;
			else {
				_ASSERTE(b_IsTexture3D(m_p_texture->n_Target()));
				delete m_p_texture;
			}
		}
		m_p_texture = 0;
	}
	// dispose of the old texture, if any

	CTextureArrayBuilder tex_builder(b_generate_mipmaps,
		n_internal_format, b_use_npot, r_bitmap_font, 0); // black with zero alpha as blank color
	if(!p_s_store_filename) {
		if(!r_bitmap_font.Create(p_s_font_face, n_font_size, tex_builder, false))
			return false;
	} else {
		CPassThroughBitmapWriter<CTextureArrayBuilder> writer(p_s_store_filename, tex_builder);
		if(!r_bitmap_font.Create(p_s_font_face, n_font_size, writer, false))
			return false;
		// create the font and save the pages at the same time (to p_s_store_filename)
	}
	m_p_texture = tex_builder.p_Get_Texture();
	// load the bitmap font and build the texture

	m_t_font_info = r_bitmap_font.t_Font_Info();
	// get font info

	if(!m_character_map.Create(r_bitmap_font.r_Page_List(), m_t_font_info.n_glyph_geom_padding))
		return false;
	// create character map

	m_n_replacement_char = 0x20;
	// reset the replacement character to space

	return true;
}

bool CGLBitmapFont2::Create(const char *p_s_font_face, int n_font_size, bool b_generate_mipmaps /*= false*/,
	GLenum n_internal_format /*= CGLTextureLoader::n_Pixel_InternalFormat(1, 8, false)*/,
	bool b_use_npot /*= false*/, const char *p_s_store_filename /*= 0*/)
{
	CBitmapFont bitmap_font;

	bitmap_font.Enable_MetaBlock_Allocation(p_s_store_filename != 0);
	// only alloc metadata if they're not being stored

	bitmap_font.Set_Max_PageSize(CGLTexture::n_Max_Size());
	// configure the bitmap font (config beyond defaults)

	return Create(p_s_font_face, n_font_size, bitmap_font, b_generate_mipmaps,
		n_internal_format, b_use_npot, p_s_store_filename);
	// use the other function to build the font
}

bool CGLBitmapFont2::Load(const char *p_s_filename, bool b_generate_mipmaps,
	GLenum n_internal_format, bool b_use_npot, uint32_t n_blank_color)
{
	if(m_p_texture) {
		if(m_p_texture) {
			if(b_IsTexture2D(m_p_texture->n_Target()))
				delete (CGLTexture_2D*)m_p_texture;
			else {
				_ASSERTE(b_IsTexture3D(m_p_texture->n_Target()));
				delete m_p_texture;
			}
		}
		m_p_texture = 0;
	}
	// dispose of the old texture, if any

	CBitmapFont bitmap_font;
	CTextureArrayBuilder tex_builder(b_generate_mipmaps,
		n_internal_format, b_use_npot, bitmap_font, n_blank_color);
	if(!bitmap_font.Load(p_s_filename, tex_builder, false, n_blank_color))
		return false;
	m_p_texture = tex_builder.p_Get_Texture();
	// load the bitmap font and build the texture

	m_t_font_info = bitmap_font.t_Font_Info();
	// get font info

	if(!m_character_map.Create(bitmap_font.r_Page_List(), m_t_font_info.n_glyph_geom_padding))
		return false;
	// create character map

	m_n_replacement_char = 0x20;
	// reset the replacement character to space

	return true;
}

bool CGLBitmapFont2::Load(CBitmapFont::CBitmapProducer &r_bitmap_producer, bool b_generate_mipmaps /*= false*/,
	GLenum n_internal_format /*= CGLTextureLoader::n_Pixel_InternalFormat(1, 8, false)*/,
	bool b_use_npot /*= false*/, uint32_t n_blank_color /*= 0*/) // t_odo - move to .cpp
{
	if(m_p_texture) {
		if(m_p_texture) {
			if(b_IsTexture2D(m_p_texture->n_Target()))
				delete (CGLTexture_2D*)m_p_texture;
			else {
				_ASSERTE(b_IsTexture3D(m_p_texture->n_Target()));
				delete m_p_texture;
			}
		}
		m_p_texture = 0;
	}
	// dispose of the old texture, if any

	CBitmapFont bitmap_font;
	CTextureArrayBuilder tex_builder(b_generate_mipmaps,
		n_internal_format, b_use_npot, bitmap_font, n_blank_color);
	if(!bitmap_font.Load(r_bitmap_producer, tex_builder, false, n_blank_color))
		return false;
	m_p_texture = tex_builder.p_Get_Texture();
	// load the bitmap font and build the texture

	m_t_font_info = bitmap_font.t_Font_Info();
	// get font info

	if(!m_character_map.Create(bitmap_font.r_Page_List(), m_t_font_info.n_glyph_geom_padding))
		return false;
	// create character map

	m_n_replacement_char = 0x20;
	// reset the replacement character to space

	return true;
}

bool CGLBitmapFont2::Load_2D(CBitmapFont::CBitmapProducer &r_bitmap_producer, bool b_generate_mipmaps /*= false*/,
	GLenum n_internal_format /*= CGLTextureLoader::n_Pixel_InternalFormat(1, 8, false)*/,
	bool b_use_npot /*= false*/, uint32_t n_blank_color /*= 0*/)
{
	if(m_p_texture) {
		if(m_p_texture) {
			if(b_IsTexture2D(m_p_texture->n_Target()))
				delete (CGLTexture_2D*)m_p_texture;
			else {
				_ASSERTE(b_IsTexture3D(m_p_texture->n_Target()));
				delete m_p_texture;
			}
		}
		m_p_texture = 0;
	}
	// dispose of the old texture, if any

	CBitmapFont bitmap_font;
	CFakeTextureArrayBuilder tex_builder(b_generate_mipmaps,
		n_internal_format, b_use_npot, bitmap_font, n_blank_color);
	if(!bitmap_font.Load(r_bitmap_producer, tex_builder, false, n_blank_color))
		return false;
	m_p_texture = tex_builder.p_Get_Texture();
	// load the bitmap font and build the texture

	m_t_font_info = bitmap_font.t_Font_Info();
	// get font info

	if(!m_character_map.Create(bitmap_font.r_Page_List(), m_t_font_info.n_glyph_geom_padding))
		return false;
	// create character map

	m_n_replacement_char = 0x20;
	// reset the replacement character to space

	return true;
}

bool CGLBitmapFont2::Load_2D(const char *p_s_filename, bool b_generate_mipmaps /*= false*/,
	GLenum n_internal_format /*= CGLTextureLoader::n_Pixel_InternalFormat(1, 8, false)*/,
	bool b_use_npot /*= false*/, uint32_t n_blank_color /*= 0*/)
{
	if(m_p_texture) {
		if(m_p_texture) {
			if(b_IsTexture2D(m_p_texture->n_Target()))
				delete (CGLTexture_2D*)m_p_texture;
			else {
				_ASSERTE(b_IsTexture3D(m_p_texture->n_Target()));
				delete m_p_texture;
			}
		}
		m_p_texture = 0;
	}
	// dispose of the old texture, if any

	CBitmapFont bitmap_font;
	CFakeTextureArrayBuilder tex_builder(b_generate_mipmaps,
		n_internal_format, b_use_npot, bitmap_font, n_blank_color);
	if(!bitmap_font.Load(p_s_filename, tex_builder, false, n_blank_color))
		return false;
	m_p_texture = tex_builder.p_Get_Texture();
	// load the bitmap font and build the texture

	m_t_font_info = bitmap_font.t_Font_Info();
	// get font info

	if(!m_character_map.Create(bitmap_font.r_Page_List(), m_t_font_info.n_glyph_geom_padding))
		return false;
	// create character map

	m_n_replacement_char = 0x20;
	// reset the replacement character to space

	return true;
}

bool CGLBitmapFont2::Set_Mode(GLenum n_mode, bool b_allow_degenerate, int n_primitive_restart)
{
	if(n_mode != GL_TRIANGLES && n_mode != GL_TRIANGLE_STRIP)
		return false;

	m_n_mode = n_mode;
	m_b_allow_degenerate = b_allow_degenerate;
	m_n_primitive_restart_index = n_primitive_restart;

	return true;
}

bool CGLBitmapFont2::Set_ReplacementChar(int n_replacement_char)
{
	if(n_replacement_char == ' ') {
		m_n_replacement_char = n_replacement_char;
		return true;
	}
	// blank space is always usable

	const bitmapfont2::TGlyphInfo *p_char;
	if(!(p_char = m_character_map.p_Get_CharacterInfo(n_replacement_char)))
		return false;
	m_n_replacement_char = n_replacement_char;
	// other replacement chars must be present

	return true;
}

size_t CGLBitmapFont2::n_Printable_Char_Num(const char *p_s_utf8_begin, const char *p_s_utf8_end) const
{
	const uint8_t *p_s_utf8_8 = (const uint8_t*)p_s_utf8_begin;
	const uint8_t *p_s_utf8_8_end = (const uint8_t*)p_s_utf8_end;
	_ASSERTE(p_s_utf8_8 <= p_s_utf8_8_end);

	size_t n_print_char_num = 0;
	if(p_s_utf8_8_end - p_s_utf8_8 > 4) {
		const uint8_t *p_s_utf8_8_end4 = p_s_utf8_8_end - 4;
		_ASSERTE(p_s_utf8_8 < p_s_utf8_8_end4); // not equal, it wouldn't make sense to prepare and then do zero work

		while(p_s_utf8_8 < p_s_utf8_8_end4) { // not !=, it can get past!
			int n_size;
			int n_code = CUniConv::n_UTF8_Code(p_s_utf8_8, 4, n_size); // in this loop there are always 4
			p_s_utf8_8 += n_size;
			_ASSERTE(p_s_utf8_8 <= p_s_utf8_8_end);
			// decode a single character

			if(n_code < 0)
				return 0;
			// do not print invalid unicode strings

			if(b_Is_Glyph(n_code)) {
				const bitmapfont2::TGlyphInfo *p_char_info;
				if(m_n_replacement_char != 0x20)
					++ n_print_char_num; // any character will be replaced by a non-empty one
				else if((p_char_info = m_character_map.p_Get_CharacterInfo(n_code)) &&
				   p_char_info->n_bitmap_width > 0 && p_char_info->n_bitmap_height > 0)
					++ n_print_char_num; // character is present in the map (and in texture somewhere)
			}
			// note there are some character sequences ("named character sequences")
			// which are displayed as a single character. those are ignored here.
		}
	}

	while(p_s_utf8_8 != p_s_utf8_8_end) {
		int n_size;
		int n_code = CUniConv::n_UTF8_Code(p_s_utf8_8, (p_s_utf8_8_end - p_s_utf8_8), n_size); // t_odo - could change the loop to loop for range where there are always at least 4 chars, and then for the rest
		p_s_utf8_8 += n_size;
		_ASSERTE(p_s_utf8_8 <= p_s_utf8_8_end);
		// decode a single character

		if(n_code < 0)
			return 0;
		// do not print invalid unicode strings

		if(b_Is_Glyph(n_code)) {
			const bitmapfont2::TGlyphInfo *p_char_info;
			if(m_n_replacement_char != 0x20)
				++ n_print_char_num; // any character will be replaced by a non-empty one
			else if((p_char_info = m_character_map.p_Get_CharacterInfo(n_code)) &&
			   p_char_info->n_bitmap_width > 0 && p_char_info->n_bitmap_height > 0)
				++ n_print_char_num; // character is present in the map (and in texture somewhere)
		}
		// note there are some character sequences ("named character sequences")
		// which are displayed as a single character. those are ignored here.
	}
	// go through the string

	return n_print_char_num;
	// return number of characters ín the string, not counting control characters,
	// blanks and undisplayable characters (in case replacement character is blank)
}

size_t CGLBitmapFont2::n_Printable_Char_Num(const char *p_s_utf8) const
{
	const uint8_t *p_s_utf8_8 = (const uint8_t*)p_s_utf8;
	size_t n_print_char_num = 0;
	while(*p_s_utf8_8) {
		int n_size;
		int n_code = CUniConv::n_UTF8_Code(p_s_utf8_8, 4, n_size); // this is easy, the terminating null will cause n_UTF8_Code() to fail before reading out of bounds
		p_s_utf8_8 += n_size;
		// decode a single character

		if(n_code < 0)
			return 0;
		// do not print invalid unicode strings

		if(b_Is_Glyph(n_code)) {
			const bitmapfont2::TGlyphInfo *p_char_info;
			if(m_n_replacement_char != 0x20)
				++ n_print_char_num; // any character will be replaced by a non-empty one
			else if((p_char_info = m_character_map.p_Get_CharacterInfo(n_code)) &&
			   p_char_info->n_bitmap_width > 0 && p_char_info->n_bitmap_height > 0)
				++ n_print_char_num; // character is present in the map (and in texture somewhere)
		}
		// note there are some character sequences ("named character sequences")
		// which are displayed as a single character. those are ignored here.
	}
	// go through the string

	return n_print_char_num;
	// return number of characters ín the string, not counting control characters,
	// blanks and undisplayable characters (in case replacement character is blank)
}

bool CGLBitmapFont2::Generate_Geometry(float *p_position, int n_position_stride,
	float *p_texcoord, int n_texcoord_stride, size_t &r_n_vertex_space,
	uint32_t n_index_offset, uint32_t *p_index, size_t &r_n_index_space,
	const char *p_s_utf8_begin, const char *p_s_utf8_end,
	Vector2f v_position, float f_zoom) const
{
	// . fail in case there is not enough space in the buffers
	if(!m_p_texture)
		return false;
	const float f_tex_scale_x = 1.0f / m_p_texture->n_Width();
	const float f_tex_scale_y = 1.0f / m_p_texture->n_Height();
	// get texture scale

	size_t n_emit_space = min(r_n_vertex_space / 4, ((m_n_mode == GL_TRIANGLES)? r_n_index_space / 6 :
		(r_n_index_space > 4)? 1 + (r_n_index_space - 4) / ((m_b_allow_degenerate)? 6 : 5) : 0));
	// calculate how much characters can be emitted

	_ASSERTE(n_emit_space == r_n_vertex_space / 4 ||
		(r_n_index_space >= n_Index_Num(n_emit_space) &&
		r_n_index_space <= n_Index_Num(n_emit_space + 1)));
	// in case n_emit_space is index bound, make sure number of indices is calculated correctly

	size_t n_char_emit = 0;
	// number of chars emitted

	Vector2f v_origin(v_position);
	// remember the origin

	if(!n_position_stride)
		n_position_stride = 2 * sizeof(float);
	if(!n_texcoord_stride)
		n_texcoord_stride = 3 * sizeof(float);
	// fill-in default strides (note that strides that are not aligned
	// to multiple of four are allowed and sane; consider vertices with
	// 5 float texcoord / position + 3 ubyte color)

	bool b_odd_even_flip = m_n_mode == GL_TRIANGLE_STRIP && m_b_allow_degenerate;
	bool b_flip = false;
	// even-odd character flipping for shorter degenerate edges

	const uint8_t *p_s_utf8_8 = (const uint8_t*)p_s_utf8_begin;
	const uint8_t *p_s_end_8 = (const uint8_t*)p_s_utf8_end;
	while(p_s_utf8_8 < p_s_end_8) { // can use less than, will need to subtract them anyway
		int n_size;
		int n_code = CUniConv::n_UTF8_Code(p_s_utf8_8, p_s_end_8 - p_s_utf8_8, n_size); // have to calculate the number of bytes remaining
		//int n_code = CUniConv::n_UTF8_Code(p_s_utf8_8, 4, n_size); // can use 4 with null terminated, it will fail on the null character
		p_s_utf8_8 += n_size;
		_ASSERTE(p_s_utf8_8 <= p_s_end_8); // should never shift over the end
		// decode a single character

		if(n_code < 0) {				
			r_n_vertex_space = n_char_emit * 4;
			r_n_index_space = n_Index_Num(n_char_emit);
			return false;
		}
		// do not print invalid unicode strings

		if(b_Is_Glyph(n_code)) {
			_ASSERTE(n_emit_space >= n_char_emit);
			if(n_char_emit == n_emit_space) {
				r_n_vertex_space = n_char_emit * 4;
				r_n_index_space = n_Index_Num(n_char_emit);
				return false; // @t_odo - or use a different return code? // this is ok
			}
			// not enough space in the buffers

			const bitmapfont2::TGlyphInfo *p_char_info;
			if(!(p_char_info = m_character_map.p_Get_CharacterInfo(n_code))) {
				if(m_n_replacement_char == 0x20) {
					v_position.x += m_t_font_info.n_space_width * f_zoom;
					// just shift the caret

					_ASSERTE(!p_char_info);
					// want to have p_char_info == 0
				} else
					p_char_info = m_character_map.p_Get_CharacterInfo(m_n_replacement_char);
			}
			// try to fetch character info

			if(p_char_info && p_char_info->n_bitmap_width > 0 && p_char_info->n_bitmap_height > 0) {
				int n_geom_padding = m_t_font_info.n_glyph_geom_padding;
#ifdef __GLFONT_PRECRUNCH_GLYPH_GEOMETRY
				Vector2i v_bitmap_size_gpad(p_char_info->n_bitmap_width, p_char_info->n_bitmap_height);

				Vector2i v_min_position(p_char_info->n_baseline_offset_x, p_char_info->n_baseline_offset_y);
				Vector2i v_max_position = v_min_position + v_bitmap_size_gpad;

				Vector2i v_min_texcoord(p_char_info->n_bitmap_pos_x, p_char_info->n_bitmap_pos_y);
				Vector2i v_max_texcoord = v_min_texcoord + ((p_char_info->b_rotation)?
					v_bitmap_size_gpad.v_yx() : v_bitmap_size_gpad);
#else // __GLFONT_PRECRUNCH_GLYPH_GEOMETRY
				Vector2i v_bitmap_size_gpad(p_char_info->n_bitmap_width, p_char_info->n_bitmap_height);
				v_bitmap_size_gpad += 2 * n_geom_padding;

				Vector2i v_baseline_off(p_char_info->n_baseline_offset_x, p_char_info->n_baseline_offset_y);
				Vector2i v_min_position = v_baseline_off - n_geom_padding;

				Vector2i v_max_position = v_min_position + v_bitmap_size_gpad;

				Vector2i v_bitmap_pos(p_char_info->n_bitmap_pos_x, p_char_info->n_bitmap_pos_y);
				Vector2i v_min_texcoord = v_bitmap_pos - n_geom_padding;

				Vector2i v_max_texcoord = v_min_texcoord + ((p_char_info->b_rotation)?
					v_bitmap_size_gpad.v_yx() : v_bitmap_size_gpad);
#endif // __GLFONT_PRECRUNCH_GLYPH_GEOMETRY

				// this could be made simpler by pre-crunching TGlyphInfos (but still using them)

				if(b_flip) {
					//_ASSERTE(m_n_mode == GL_TRIANGLE_STRIP); // does not have to be true anymore, can flip on demand
					std::swap(v_min_position.y, v_max_position.y);
					if(p_char_info->b_rotation)
						std::swap(v_min_texcoord.x, v_max_texcoord.x);
					else
						std::swap(v_min_texcoord.y, v_max_texcoord.y);
				}
				// flip odd characters upside-down to make degenerate tristrip joins shorter

				p_position[0] = v_position.x + v_min_position.x * f_zoom;
				p_position[1] = v_position.y + v_min_position.y * f_zoom;
				p_position = (float*)(((uint8_t*)p_position) + n_position_stride); // stride is in basic machine units
				p_position[0] = v_position.x + v_max_position.x * f_zoom;
				p_position[1] = v_position.y + v_min_position.y * f_zoom;
				p_position = (float*)(((uint8_t*)p_position) + n_position_stride); // stride is in basic machine units
				p_position[0] = v_position.x + v_max_position.x * f_zoom;
				p_position[1] = v_position.y + v_max_position.y * f_zoom;
				p_position = (float*)(((uint8_t*)p_position) + n_position_stride); // stride is in basic machine units
				p_position[0] = v_position.x + v_min_position.x * f_zoom;
				p_position[1] = v_position.y + v_max_position.y * f_zoom; // @note - zoom could be implemented on GPU
				p_position = (float*)(((uint8_t*)p_position) + n_position_stride); // stride is in basic machine units
				// write positions

				// texture array layers have all the same size
				// t_odo - save the largest page size to TFontInfo (as n_max_page_width / height)
				// t_odo - automatically read textures here, let user specify format / pot or npot

				int n_page = p_char_info->n_code;
				if(p_char_info->b_rotation) {
					p_texcoord[0] = v_max_texcoord.x * f_tex_scale_x;
					p_texcoord[1] = v_max_texcoord.y * f_tex_scale_y;
					p_texcoord[2] = float(n_page);
					p_texcoord = (float*)(((uint8_t*)p_texcoord) + n_texcoord_stride); // stride is in basic machine units
					p_texcoord[0] = v_max_texcoord.x * f_tex_scale_x;
					p_texcoord[1] = v_min_texcoord.y * f_tex_scale_y;
					p_texcoord[2] = float(n_page);
					p_texcoord = (float*)(((uint8_t*)p_texcoord) + n_texcoord_stride); // stride is in basic machine units
					p_texcoord[0] = v_min_texcoord.x * f_tex_scale_x;
					p_texcoord[1] = v_min_texcoord.y * f_tex_scale_y;
					p_texcoord[2] = float(n_page);
					p_texcoord = (float*)(((uint8_t*)p_texcoord) + n_texcoord_stride); // stride is in basic machine units
					p_texcoord[0] = v_min_texcoord.x * f_tex_scale_x;
					p_texcoord[1] = v_max_texcoord.y * f_tex_scale_y;
					p_texcoord[2] = float(n_page);
					p_texcoord = (float*)(((uint8_t*)p_texcoord) + n_texcoord_stride); // stride is in basic machine units
					// rotate 90 degrees left
				} else {
					p_texcoord[0] = v_min_texcoord.x * f_tex_scale_x;
					p_texcoord[1] = v_max_texcoord.y * f_tex_scale_y;
					p_texcoord[2] = float(n_page);
					p_texcoord = (float*)(((uint8_t*)p_texcoord) + n_texcoord_stride); // stride is in basic machine units
					p_texcoord[0] = v_max_texcoord.x * f_tex_scale_x;
					p_texcoord[1] = v_max_texcoord.y * f_tex_scale_y;
					p_texcoord[2] = float(n_page);
					p_texcoord = (float*)(((uint8_t*)p_texcoord) + n_texcoord_stride); // stride is in basic machine units
					p_texcoord[0] = v_max_texcoord.x * f_tex_scale_x;
					p_texcoord[1] = v_min_texcoord.y * f_tex_scale_y;
					p_texcoord[2] = float(n_page);
					p_texcoord = (float*)(((uint8_t*)p_texcoord) + n_texcoord_stride); // stride is in basic machine units
					p_texcoord[0] = v_min_texcoord.x * f_tex_scale_x;
					p_texcoord[1] = v_min_texcoord.y * f_tex_scale_y;
					p_texcoord[2] = float(n_page);
					p_texcoord = (float*)(((uint8_t*)p_texcoord) + n_texcoord_stride); // stride is in basic machine units
				}
				// write texcoords

				_ASSERTE(n_char_emit <= UINT32_MAX / 4 && n_index_offset <= UINT32_MAX - n_char_emit * 4 &&
					n_index_offset + n_char_emit * 4 <= UINT32_MAX - 3);
				if(m_n_mode == GL_TRIANGLES) {
					//_ASSERTE(!b_flip); // does not have to be true anymore, can flip on demand
					// there should be no flip with triangles

					p_index[0] = n_index_offset + uint32_t(n_char_emit) * 4 + 0;	// 3 *-------* 2
					p_index[1] = n_index_offset + uint32_t(n_char_emit) * 4 + 1;	//   |      /|
					p_index[2] = n_index_offset + uint32_t(n_char_emit) * 4 + 2;	//   |    /  |
					p_index[3] = n_index_offset + uint32_t(n_char_emit) * 4 + 0;	//   |  /    |
					p_index[4] = n_index_offset + uint32_t(n_char_emit) * 4 + 2;	//   |/      |
					p_index[5] = n_index_offset + uint32_t(n_char_emit) * 4 + 3;	// 0 *-------* 1
					p_index += 6;
				} else if(m_n_mode == GL_TRIANGLE_STRIP) {
					if(n_char_emit) {
						if(m_b_allow_degenerate) {
							p_index[0] = n_index_offset + (uint32_t(n_char_emit) - 1) * 4 + 1; // b_flip doesn't matter
							p_index[1] = n_index_offset + uint32_t(n_char_emit) * 4 + 3; // b_flip doesn't matter
							p_index += 2;
							// repeat the last vertex from the previous strip
							// and the first vertex from this strip to restart
							// it (forming a degenerate strip)
						} else {
							p_index[0] = /*n_index_offset +*/ m_n_primitive_restart_index; // no offset here!
							++ p_index;
							// use primitive restart index to restart the strip
						}
					}
					// generate join for tristrips

					p_index[0] = n_index_offset + uint32_t(n_char_emit) * 4 + 3; // b_flip doesn't matter
					if(b_flip) {
						p_index[1] = n_index_offset + uint32_t(n_char_emit) * 4 + 2;
						p_index[2] = n_index_offset + uint32_t(n_char_emit) * 4 + 0;
					} else {
						p_index[1] = n_index_offset + uint32_t(n_char_emit) * 4 + 0;
						p_index[2] = n_index_offset + uint32_t(n_char_emit) * 4 + 2;
					}
					p_index[3] = n_index_offset + uint32_t(n_char_emit) * 4 + 1; // b_flip doesn't matter
					/*
					 * 3 *-------* 2	 0 *-------* 1
					 *   |      /|		   |\      |
					 *   |    /  |		   |  \    |
					 *   |  /    |		   |    \  |
					 *   |/      |		   |      \|
					 * 0 *-------* 1	 3 *-------* 2
					 *    no-flip			 flip
					 */

					p_index += 4;
					if(b_odd_even_flip)
						b_flip = !b_flip;
				}
				// emit vertex indices

				++ n_char_emit;
				v_position.x += p_char_info->n_caret_shift * f_zoom;
			}
			// generate geometry for the character
		} else if(n_code == ' ') {
			v_position.x += m_t_font_info.n_space_width * f_zoom;
			// just shift the caret
		} else if(n_code == '\t') {
			v_position.x += m_t_font_info.n_space_width * f_zoom * 4; // @todo - reach the next tab-stop instead of just shifting the text four spaces
			// just shift the caret (four spaces)
		} else if(n_code == '\n') {
			v_position.x = v_origin.x; // return left
			v_position.y -= m_t_font_info.n_newline_height * f_zoom;

			//if(b_odd_even_flip) // sets flip to false, so it doesn't really matter
				b_flip = false; // this makes flips accross newlines shorter
		} else {
			v_position.x += m_t_font_info.n_space_width * f_zoom;
			// just shift the caret (but this is not a space, it's an unknown character)
			// note this may as well be zero-width space/joiner
		}
		// @todo - support more unicode formatting characters
		// @todo - support different text layouts (now it's left-align)
	}
	// loop through the string, decode utf-8

	r_n_vertex_space = n_char_emit * 4;
	r_n_index_space = n_Index_Num(n_char_emit);
	// output number of generated coordinates / indices

	return true;
}

bool CGLBitmapFont2::Generate_Geometry(std::vector<float> &r_t3v2_vertex_list,
	uint32_t n_index_offset, std::vector<uint32_t> &r_index_list, const char *p_s_utf8_begin,
	const char *p_s_utf8_end, Vector2f v_position, float f_zoom) const // t_odo - move this to .cpp
{
	/*if(m_n_mode == GL_TRIANGLE_STRIP && m_b_allow_degenerate)
		return false;*/
	// this does not handle connecting degenerate tristrips (t_odo?)

	size_t n_printable_num = n_Printable_Char_Num(p_s_utf8_begin, p_s_utf8_end);
	if(!n_printable_num)
		return true; // another job well done
	// count printable characters

	_ASSERTE(m_n_extra_vertex_element_num >= 0);
	const size_t n_vertex_size = n_Vertex_Size();
	const size_t n_vertex_element_num = n_vertex_size / sizeof(float);
	// calculate the number of vertex elements

	size_t n_vertex_org = r_t3v2_vertex_list.size();
	size_t n_index_org = r_index_list.size();
	try {
		r_t3v2_vertex_list.resize(n_vertex_org + n_Vertex_Num(n_printable_num) * n_vertex_element_num);
		size_t n_connector_size = (r_index_list.empty() || m_n_mode == GL_TRIANGLES)? 0 :
			(!m_b_allow_degenerate)? 1 : 2;
		r_index_list.resize(n_index_org + (n_Index_Num(n_printable_num) + n_connector_size));
		// alloc the lists

		if(n_connector_size) {
			if(n_connector_size == 1) { // would be 2 for degenerate
				r_index_list[n_index_org] = m_n_primitive_restart_index;
				++ n_index_org;
			} else {
				_ASSERTE(n_connector_size == 2); // a degenerate tristrip
				_ASSERTE(n_index_org > 0);
				r_index_list[n_index_org] = r_index_list[n_index_org - 1]; // the last vertex of the previous mesh
				r_index_list[n_index_org + 1] = n_index_offset; // the first vertex of this mesh
				n_index_org += 2;
				// repeat the last and the first vertices to form a degenerate connecting strip

				// todo - test this!
			}
		}
		// handle connecting more tristrips (needs to restart the triangle strip)
	} catch(std::bad_alloc&) {
		return false;
	}
	// allocate the lists

	size_t n_vertex_space = (r_t3v2_vertex_list.size() - n_vertex_org) / n_vertex_element_num;
	size_t n_index_space = r_index_list.size() - n_index_org;
	_ASSERTE(n_vertex_size <= INT_MAX); // stride in OpenGL is also only int (and limited to much less actually)
	bool b_result = Generate_Geometry(&r_t3v2_vertex_list[n_vertex_org + 3 + m_n_extra_vertex_element_num],
		int(n_vertex_size), &r_t3v2_vertex_list[n_vertex_org + m_n_extra_vertex_element_num],
		int(n_vertex_size), n_vertex_space, n_index_offset, &r_index_list[n_index_org],
		n_index_space, p_s_utf8_begin, p_s_utf8_end, v_position, f_zoom);
	// generate the geometry

	_ASSERTE(n_vertex_space == (r_t3v2_vertex_list.size() - n_vertex_org) / n_vertex_element_num);
	_ASSERTE(n_index_space == r_index_list.size() - n_index_org);
	// make sure the buffers are used as expected

	return b_result;
}

Vector3f CGLBitmapFont2::v_Text_Size(const char *p_s_utf8_begin,
	const char *p_s_utf8_end, float f_zoom) const
{
	float f_max_width = 0;
	Vector2f v_position(0, 0);
	// remember the origin

	const uint8_t *p_s_utf8_8 = (const uint8_t*)p_s_utf8_begin;
	const uint8_t *p_s_end_8 = (const uint8_t*)p_s_utf8_end;
	while(p_s_utf8_8 < p_s_end_8) { // can use less than, will need to subtract them anyway
		int n_size;
		int n_code = CUniConv::n_UTF8_Code(p_s_utf8_8, p_s_end_8 - p_s_utf8_8, n_size); // have to calculate the number of bytes remaining
		//int n_code = CUniConv::n_UTF8_Code(p_s_utf8_8, 4, n_size); // can use 4 with null terminated, it will fail on the null character
		p_s_utf8_8 += n_size;
		_ASSERTE(p_s_utf8_8 <= p_s_end_8); // should never shift over the end
		// decode a single character

		if(n_code < 0) {
			v_position.x += m_t_font_info.n_space_width * f_zoom; // treat as space
			continue;
		}
		// do not print invalid unicode strings

		if(b_Is_Glyph(n_code)) {
			const bitmapfont2::TGlyphInfo *p_char_info;
			if(!(p_char_info = m_character_map.p_Get_CharacterInfo(n_code))) {
				if(m_n_replacement_char == 0x20) {
					v_position.x += m_t_font_info.n_space_width * f_zoom;
					// just shift the caret

					_ASSERTE(!p_char_info);
					// want to have p_char_info == 0
				} else
					p_char_info = m_character_map.p_Get_CharacterInfo(m_n_replacement_char);
			}
			// try to fetch character info

			if(p_char_info && p_char_info->n_bitmap_width > 0 && p_char_info->n_bitmap_height > 0)
				v_position.x += p_char_info->n_caret_shift * f_zoom;
			// generate geometry for the character
		} else if(n_code == ' ') {
			v_position.x += m_t_font_info.n_space_width * f_zoom;
		} else if(n_code == '\t') {
			v_position.x += m_t_font_info.n_space_width * f_zoom * 4; // @todo - reach the next tab-stop instead of just shifting the text four spaces
		} else if(n_code == '\n') {
			if(v_position.x > f_max_width)
				f_max_width = v_position.x;
			v_position.x = 0; // return left
			v_position.y -= m_t_font_info.n_newline_height * f_zoom;
		} else {
			v_position.x += m_t_font_info.n_space_width * f_zoom;
			// just shift the caret (but this is not a space, it's an unknown character)
			// note this may as well be zero-width space/joiner
		}
	}
	// loop through the string, decode utf-8

	if(v_position.x > f_max_width)
		f_max_width = v_position.x;
	// remember the maximum width of the text

	return Vector3f(v_position, f_max_width);
}

// @note this doesn't attempt to detect surrogates as n_code is already decoded character (should not result in a surrogate)
inline bool CGLBitmapFont2::b_Is_Glyph(int n_code)
{
	return !(n_code >= 0 && n_code < 0x1f) && n_code != 0x20 && // C0, space
		!(n_code >= 0x7f && n_code <= 0xa0) && // C0 (0x7f), C1
		!(n_code >= 0x200b && n_code <= 0x200f) && // zero-width characters, separators, bidirectional ordering codes
		!(n_code >= 0x2028 && n_code <= 0x202f) && n_code != 0x205f && // layout controls
		!(n_code >= 0x2065 && n_code <= 0x206f) && n_code != 0xfeff && // deprecated format characters, BOM
		!(n_code >= 0xfff0 && n_code <= 0xffff); // replacement characters, noncharacters
	// based on Unicode Chapter 13: Special Areas and Format Characters (2011-07-13)
}

/**
 *								=== ~CGLBitmapFont2 ===
 */
