/*
								+---------------------------------+
								|                                 |
								|    ***   Bitmap font 2   ***    |
								|                                 |
								|  Copyright  © -tHE SWINe- 2007  |
								|                                 |
								|         BitmapFont.cpp          |
								|                                 |
								+---------------------------------+
*/

/**
 *	@file gl2/BitmapFont.cpp
 *	@author -tHE SWINe-
 *	@date 2007
 *	@brief Bitmap font implementation for OpenGL.
 *
 *	@date 2007-06-04
 *
 *	changed bitmap font zoom style so when zoomed to 1 / pixel size, user gets perfectly crisp
 *	letters (in case font rendering functions are passed pixel aligned coordinates)
 *
 *	@date 2007-07-10
 *
 *	added CBitmapFont::SetTextureFilters() to set texture filtering mode in order to improve
 *	rendering of pixel-accurate font
 *	moved function objects sources to BitmapFont.cpp in order to clean-up the header
 *
 *	@date 2007-07-16
 *
 *	updated so now it works with new ÜberLame CGLTexture_2D constructors (texture target
 *	was omitted from constructor parameter list)
 *
 *	added BITMAP_FONT_USE_CLEARTYPE directive for enabling cleartype font support
 *	note (color) cleartype glyphs are converted to greyscale (alpha) but it gives better
 *	results than antialiassed font quality anyway
 *
 *	@date 2007-07-17
 *
 *	added line descent and height font parameters, ie virtual CGLFont::f_LineDescent() const,
 *	virtual CGLFont::f_LineHeight() as well as CGLBitmapFont::f_LineDescent() and
 *	CGLBitmapFont::f_LineHeight()
 *
 *	@date 2007-11-10
 *
 *	improved linux compatibility
 *
 *	@date 2007-12-24
 *
 *	improved linux compatibility by adding posix integer types
 *
 *	@date 2008-03-04
 *
 *	now using Integer.h header
 *
 *	@date 2008-08-08
 *
 *	added \#ifdef for windows 64
 *
 *	@date 2008-09-15
 *
 *	added support for anisotropic texture filtering for font textures
 *
 *	@date 2009-01-11
 *
 *	added the geometry padding feature
 *
 *	@date 2009-01-12
 *
 *	fixed CGLBitmapFont::Create() which didn't specify transparent text
 *	background, resulting in texture space waste (loose glyph bounding boxes)
 *
 *	@date 2009-05-04
 *
 *	fixed mixed windows / linux line endings
 *
 *	@date 2009-05-23
 *
 *	removed all instances of std::vector::reserve and replaced them by stl_ut::Reserve_*
 *
 *	@date 2009-10-20
 *
 *	fixed some warnings when compiling under VC 2005, implemented "Security
 *	Enhancements in the CRT" for VC 2008. compare against MyProjects_2009-10-19_
 *
 */

#include "../NewFix.h"

#include "../CallStack.h"
#include "OpenGL20.h"
#include <stdio.h>
#include <math.h>
#include <vector>
#include <algorithm>
#include "../Vector.h"
#include "OpenGLState.h"
#include "../Tga.h"
#include "Texture.h"
#include "FontTexture.h"
#include "BitmapFont.h"
#include "../StlUtils.h"

/*
 *								=== CGLBitmapFont::CSetTextureFilters ===
 */

class CGLBitmapFont::CSetTextureFilters {
protected:
	GLenum m_n_min_filter, m_n_mag_filter;
	CGLState *m_p_state;

public:
	inline CSetTextureFilters(CGLState *p_state, GLenum n_min_filter, GLenum n_mag_filter)
		:m_n_min_filter(n_min_filter), m_n_mag_filter(n_mag_filter), m_p_state(p_state)
	{}

	inline void operator ()(CGLTexture_2D *p_texture)
	{
		p_texture->r_Parameters(m_p_state).Set_Texture_Min_Filter(m_n_min_filter);
		p_texture->r_Parameters(m_p_state).Set_Texture_Mag_Filter(m_n_mag_filter);
	}
};

/*
 *								=== ~CGLBitmapFont::CSetTextureFilters ===
 */

/*
 *								=== CGLBitmapFont::CSetTextureAniso ===
 */

class CGLBitmapFont::CSetTextureAniso {
protected:
	float m_f_anisotropy;
	CGLState *m_p_state;

public:
	inline CSetTextureAniso(CGLState *p_state, float f_anisotropy)
		:m_f_anisotropy(f_anisotropy), m_p_state(p_state)
	{}

	inline void operator ()(CGLTexture_2D *p_texture)
	{
		p_texture->r_Parameters(m_p_state).Set_Texture_Anisotropy(m_f_anisotropy);
	}
};

/*
 *								=== ~CGLBitmapFont::CSetTextureAniso ===
 */

/*
 *								=== CGLBitmapFont::CDeleteObjects ===
 */

class CGLBitmapFont::CDeleteObjects {
public:
	template <class CObjType>
	inline void operator ()(CObjType *p_object) const
	{
		delete p_object;
	}
};

/*
 *								=== ~CGLBitmapFont::CDeleteObjects ===
 */

/*
 *								=== CGLBitmapFont::CDeref ===
 */

template <class CInnerObject>
class CGLBitmapFont::CDeref {
protected:
	CInnerObject m_object;

public:
	inline CDeref(CInnerObject object)
		:m_object(object)
	{}

	template <class CObjType>
	inline void operator ()(CObjType *p_object)
	{
		m_object(*p_object); // dereference
	}
};

/*
 *								=== ~CGLBitmapFont::CDeref ===
 */

/*
 *								=== CGLBitmapFont::CFindMin_Count ===
 */

class CGLBitmapFont::CFindMin_Count {
protected:
	unsigned char m_n_first_char;
	unsigned char m_n_char_num;
	unsigned char m_n_last_char;

public:
	inline CFindMin_Count()
	{
		m_n_last_char = 0;
		m_n_first_char = 255;
	}

	inline unsigned char n_Min() { return m_n_first_char; }
	inline unsigned char n_Range() { return m_n_last_char - m_n_first_char + 1; }

	inline void operator ()(const TCharacterInfo &r_character_info)
	{
		if(r_character_info.n_code < m_n_first_char)
			m_n_first_char = r_character_info.n_code;
		if(r_character_info.n_code > m_n_last_char)
			m_n_last_char = r_character_info.n_code;
	}
};

/*
 *								=== ~CGLBitmapFont::CFindMin_Count ===
 */

/*
 *								=== CGLBitmapFont::CFillCharLookup ===
 */

class CGLBitmapFont::CFillCharLookup {
protected:
	TCharLookup *m_p_char_lookup;
	CGLTexture_2D *m_p_cur_page;
	bool m_b_flip_texture;
	int m_n_newline_height;
	int m_n_geom_padding;

public:
	inline CFillCharLookup(TCharLookup *p_char_lookup, unsigned char n_first_char,
		CGLTexture_2D *p_cur_page, int n_newline_height, bool b_flip_texture,
		int n_geom_padding)
		:m_p_char_lookup(p_char_lookup - n_first_char), m_p_cur_page(p_cur_page),
		m_b_flip_texture(b_flip_texture), m_n_newline_height(n_newline_height),
		m_n_geom_padding(n_geom_padding)
	{}

	inline void operator ()(const TCharacterInfo &r_character_info)
	{
		TCharLookup *p_cur_lookup = m_p_char_lookup + r_character_info.n_code;

		p_cur_lookup->p_character = &r_character_info;
		p_cur_lookup->p_tex_page = m_p_cur_page;
		p_cur_lookup->n_display_list = glGenLists(1);
		// create a new display list ...

		glNewList(p_cur_lookup->n_display_list, GL_COMPILE);
		if(r_character_info.n_bitmap_width > 0 &&
		   r_character_info.n_bitmap_height > 0) { // do not draw spaces
			float f_tex_scale_u = 1.0f / m_p_cur_page->n_Width();
			float f_tex_scale_v = 1.0f / m_p_cur_page->n_Height();
			Vector2i v_min_texcoord = r_character_info.v_Min() - (1 + m_n_geom_padding);
			Vector2i v_max_texcoord = r_character_info.v_Max(m_n_geom_padding + 1);
			Vector2i v_min_vertex = r_character_info.v_baseline_offset - m_n_geom_padding;
			Vector2i v_max_vertex = v_min_vertex + Vector2i(r_character_info.n_bitmap_width,
				r_character_info.n_bitmap_height) + (2 * m_n_geom_padding + 1);
			// create vertices and texcoords

			float f_jitter_u = -.5f * f_tex_scale_u;
			float f_jitter_v = ((m_b_flip_texture)? .5f : -.5f) * f_tex_scale_v;
			// crispy letters need sampling from texel centers

			if(m_b_flip_texture) {
				++ f_jitter_v;
				f_tex_scale_v = -f_tex_scale_v;
			}
			// apply mirroring on m_b_flip_texture

			glTexCoord2f(v_min_texcoord.x * f_tex_scale_u + f_jitter_u,
				v_min_texcoord.y * f_tex_scale_v + f_jitter_v);
			glVertex2i(v_min_vertex.x, v_min_vertex.y);
			glTexCoord2f(v_max_texcoord.x * f_tex_scale_u + f_jitter_u,
				v_min_texcoord.y * f_tex_scale_v + f_jitter_v);
			glVertex2i(v_max_vertex.x, v_min_vertex.y);
			glTexCoord2f(v_max_texcoord.x * f_tex_scale_u + f_jitter_u,
				v_max_texcoord.y * f_tex_scale_v + f_jitter_v);
			glVertex2i(v_max_vertex.x, v_max_vertex.y);
			glTexCoord2f(v_min_texcoord.x * f_tex_scale_u + f_jitter_u,
				v_max_texcoord.y * f_tex_scale_v + f_jitter_v);
			glVertex2i(v_min_vertex.x, v_max_vertex.y);
			// draw aligned quad
		}
		glEndList();
		// compile display list
	}
};

/*
 *								=== ~CGLBitmapFont::CFillCharLookup ===
 */

/*
 *								=== CGLBitmapFont ===
 */

/*
 *	CGLBitmapFont::CGLBitmapFont()
 *		- default constructor
 */
CGLBitmapFont::CGLBitmapFont()
	:m_b_snap_to_pixels(false), m_p_char_lookup(0), m_f_zoom(1)
{}

/*
 *	CGLBitmapFont::~CGLBitmapFont()
 *		- default destructor
 */
CGLBitmapFont::~CGLBitmapFont()
{
	Free();
}

/*
 *	virtual bool CGLBitmapFont::Load(CGLState *p_state, const char *p_s_font_name)
 *		- loads font from p_s_font_name bitmap (or more bitmaps)
 *		- in case p_s_font_name ends with "_page00.tga", there must me at least
 *		  one more page with characters (ie. "_page01.tga")
 *		- p_state is OpenGL state manager (new textures are created)
 *		- returns true on success, false on failure
 */
bool CGLBitmapFont::Load(CGLState *p_state, const char *p_s_font_name)
{
	Free();
	// make sure we're free

	int n_geom_padding;
	std::vector<int> page_characters; // number of characters per page
	if(strrchr(p_s_font_name, '_') && !strcmp(strrchr(p_s_font_name, '_'), "_page00.tga")) {
		std::string s_filename, s_format_str;
		if(!stl_ut::AssignCStr(s_format_str, p_s_font_name))
			return false;
		_ASSERTE(s_format_str.rfind('_') != std::string::npos);
		s_format_str.erase(s_format_str.rfind('_') + 5);
		if(!stl_ut::AppendCStr(s_format_str, "%02d.tga"))
			return false;
		// create formatting string

		for(int i = 0, n_prev_char_num = 0;; ++ i) {
			if(!stl_ut::Format(s_filename, s_format_str.c_str(), i))
				return false;
			// format filename

			int n_result;
			if((n_result = n_Load_Page(s_filename.c_str(), p_state, n_geom_padding))) {
				if(i < 2 || n_result != -1) // -1 = bitmap not found
					return false;
				break;
			}
			if(!stl_ut::Reserve_1More(page_characters))
				return false;
			page_characters.push_back(m_char_list.size() - n_prev_char_num);
			n_prev_char_num = m_char_list.size();
		}
	} else {
		if(n_Load_Page(p_s_font_name, p_state, n_geom_padding))
			return false;
		_ASSERTE(page_characters.empty());
		if(!stl_ut::Reserve_N(page_characters, 1))
			return false;
		page_characters.push_back(m_char_list.size());
	}
	// load a single texture page or all pages

	CFindMin_Count min_count = std::for_each(m_char_list.begin(),
		m_char_list.end(), CFindMin_Count());
	m_n_first_char = min_count.n_Min();
	m_n_char_num = min_count.n_Range();
	// get minimal code and range of codes

	if(!(m_p_char_lookup = new(std::nothrow) TCharLookup[m_n_char_num]))
		return false;
	memset(m_p_char_lookup, 0, m_n_char_num * sizeof(TCharLookup));
	for(unsigned int i = 0, n_cur_index = 0; i < page_characters.size();
	   n_cur_index += page_characters[i ++]) {
		int n_char_num = page_characters[i];
		std::for_each(m_char_list.begin() + n_cur_index,
			m_char_list.begin() + n_cur_index + n_char_num,
			CFillCharLookup(m_p_char_lookup, m_n_first_char,
			m_texture_list[i], 0, true, n_geom_padding));
	}
	// alloc lookup, fill it and compile display lists

	return true;
}

#ifdef FONT_TEXTURE_CREATE_FONTS

#ifdef BITMAP_FONT_USE_CLEARTYPE

#ifndef CLEARTYPE_QUALITY
#define CLEARTYPE_QUALITY 5
#endif
#ifndef CLEARTYPE_COMPAT_QUALITY
#define CLEARTYPE_COMPAT_QUALITY 6
#endif

/*
 *	static inline unsigned char n_gray2(unsigned char n_r, unsigned char n_g, unsigned char n_b)
 *		- calculate gray value from n_r, n_g, n_b (PAL weights) and square the result
 */
static inline unsigned char n_gray2(unsigned char n_r, unsigned char n_g, unsigned char n_b)
{
	const unsigned short n_r_weight = (unsigned short)(.299f * 0x10000);
	const unsigned short n_g_weight = (unsigned short)(.587f * 0x10000);
	const unsigned short n_b_weight = (unsigned short)(.114f * 0x10000);
	unsigned short n_gray = (n_r * n_r_weight + n_g * n_g_weight + n_b * n_b_weight) >> 16;
	n_gray *= n_gray;
	n_gray >>= 8;
	return (n_gray <= 0xff)? (unsigned char)n_gray : 0xff;
}

#endif // BITMAP_FONT_USE_CLEARTYPE

/*
 *	virtual bool CGLBitmapFont::Create(CGLState *p_state, const char *p_s_font_face,
 *		int n_font_size, int n_font_weight, bool b_italic, bool b_underline,
 *		int n_char_padding, int n_geom_padding)
 *		- creates font at runtime
 *		- p_state is OpenGL state manager (new textures are created)
 *		- returns true on success, false on failure
 */
bool CGLBitmapFont::Create(CGLState *p_state, const char *p_s_font_face, int n_font_size,
	int n_font_weight, bool b_italic, bool b_underline, int n_char_padding,
	int n_geom_padding)
{
	int n_temp_bitmap_size = 256 + n_font_size;

	LOGFONT t_logfont;
	t_logfont.lfHeight = n_font_size;
	t_logfont.lfWidth = 0;
	t_logfont.lfEscapement = 0;
	t_logfont.lfOrientation = 0;
	t_logfont.lfWeight = n_font_weight;
	t_logfont.lfItalic = (b_italic)? TRUE : FALSE;
	t_logfont.lfUnderline = (b_underline)? TRUE : FALSE;
	t_logfont.lfStrikeOut = FALSE;
	t_logfont.lfCharSet = DEFAULT_CHARSET;
	t_logfont.lfOutPrecision = OUT_TT_PRECIS;
	t_logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
#ifdef BITMAP_FONT_USE_CLEARTYPE
	t_logfont.lfQuality = CLEARTYPE_COMPAT_QUALITY;
#else
	t_logfont.lfQuality = ANTIALIASED_QUALITY;
#endif
	t_logfont.lfPitchAndFamily = DEFAULT_PITCH;

#if defined(_MSC_VER) && !defined(__MWERKS__) && _MSC_VER >= 1400
	strncpy_s(t_logfont.lfFaceName, sizeof(t_logfont.lfFaceName), p_s_font_face, 32);
#else // _MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
	strncpy(t_logfont.lfFaceName, p_s_font_face, 32);
#endif // _MSC_VER && !__MWERKS__ && _MSC_VER >= 1400
	// create font description

	HFONT h_font;
	if(!(h_font = CreateFontIndirect(&t_logfont)))
		return false;
	// create font

	CGDI_Bitmap temp_bitmap(n_temp_bitmap_size, n_temp_bitmap_size);
	if(!temp_bitmap.b_State())
		return false;
	HDC h_dc = temp_bitmap.h_GetDC();
	HGDIOBJ h_return_font = SelectObject(h_dc, h_font);
	SetBkMode(h_dc, TRANSPARENT);
	// create temp rendering bitmap

	SIZE t_size;
	GetTextExtentPoint32(h_dc, " ", 1, &t_size);
	TEXTMETRIC t_text_metric;
	GetTextMetrics(h_dc, &t_text_metric);
	m_n_char_descent = -t_text_metric.tmDescent;
	m_n_int_leading = t_text_metric.tmInternalLeading;
	m_n_space_width = t_size.cx;
	m_n_newline_height = t_text_metric.tmHeight + t_text_metric.tmExternalLeading;
	m_n_first_char = t_text_metric.tmFirstChar;
	m_n_char_num = t_text_metric.tmLastChar - t_text_metric.tmFirstChar + 1;
	// get some font info ...

	std::vector<CFontTexturePage*> texture_page_list;
	// texture pages

	GLint n_max_texture_size;
	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &n_max_texture_size);
	if(n_max_texture_size > 256)
		n_max_texture_size = 256; // keep texture coords accurate enough
	// query real max texture size

	bool b_result = false;
	do {
		if(!CFontTexture_Util::Create_CharacterList(m_n_first_char, m_n_char_num,
		   m_char_list, temp_bitmap))
			break;
		// create list of characters ...

		std::sort(m_char_list.begin(), m_char_list.end(),
			CFontTexture_Util::CSortCharacters(n_char_padding));
		// sort them by size (better space utilization)

		if(!CFontTexture_Util::Create_TexturePageList(texture_page_list, m_char_list,
		   n_char_padding, n_geom_padding, n_max_texture_size,
		   m_n_space_width, m_n_newline_height, m_n_char_descent, m_n_int_leading))
			break;
		// create texture pages

		if(!(m_p_char_lookup = new(std::nothrow) TCharLookup[m_n_char_num]))
			break;
		memset(m_p_char_lookup, 0, m_n_char_num * sizeof(TCharLookup));
		// create character lookup array

		_ASSERTE(m_texture_list.empty());
		if(!stl_ut::Reserve_N(m_texture_list, texture_page_list.size()))
			break;
		// alloc texture list

		bool b_failed = false;
		for(int i = 0, n = texture_page_list.size(); i < n; ++ i) {
			CGDI_Bitmap *p_bitmap;
			if(!(p_bitmap = texture_page_list[i]->p_Create_Bitmap(h_font))) {
				b_failed = true;
				break;
			}
			// render bitmap

#ifdef BITMAP_FONT_USE_CLEARTYPE
			for(uint32_t *p_pixel = (uint32_t*)p_bitmap->p_Buffer(), *p_end =
			   ((uint32_t*)p_bitmap->p_Buffer()) + (p_bitmap->n_Width() *
			   p_bitmap->n_Height()); p_pixel != p_end; ++ p_pixel) {
			    *p_pixel = (n_gray2(*p_pixel >> 16, *p_pixel >> 8, *p_pixel) << 24);
			}
			// calculate grey^2 (sharper) and put it to alpha 
#else
			for(uint32_t *p_pixel = (uint32_t*)p_bitmap->p_Buffer(), *p_end =
			   ((uint32_t*)p_bitmap->p_Buffer()) + (p_bitmap->n_Width() *
			   p_bitmap->n_Height()); p_pixel != p_end; ++ p_pixel)
				*p_pixel <<= 8;
			// shift red channel to alpha
#endif

			CGLTexture_2D *p_texture;
			if(!(p_texture = new(std::nothrow) CGLTexture_2D(p_state, p_bitmap->n_Width(),
			   p_bitmap->n_Height(), GL_ALPHA, true, 0, GL_RGBA,
			   GL_UNSIGNED_BYTE, p_bitmap->p_Buffer()))) {
				delete p_bitmap;
				b_failed = true;
				break;
			}
			delete p_bitmap;
			m_texture_list.push_back(p_texture);
			// create texture, put it in the list

			if(CGLExtensionHandler::n_OpenGL_Version() >= 12) {
				p_texture->r_Parameters(p_state).Set_Texture_Wrap_S(GL_CLAMP_TO_EDGE);
				p_texture->r_Parameters(p_state).Set_Texture_Wrap_T(GL_CLAMP_TO_EDGE);
			}
#ifdef LIMIT_MIPMAPS
			int n_max_level = (int)(log(n_char_padding) / log(2)); // max mip level
			p_texture->r_Parameters(p_state).Set_Texture_Max_Level((n_max_level > 1)?
				n_max_level : 1);
#endif // LIMIT_MIPMAPS
			p_texture->r_Parameters(p_state).Set_Texture_Min_Filter(GL_LINEAR_MIPMAP_LINEAR);
			p_texture->r_Parameters(p_state).Set_Texture_Mag_Filter(GL_LINEAR);
			// clamp to edge, max mip-level

			texture_page_list[i]->for_each_character(
				CDeref<CFillCharLookup>(CFillCharLookup(m_p_char_lookup,
				m_n_first_char, p_texture, 0, false, n_geom_padding)));
			// fill character lookup by characters contained in this page
		}
		if(b_failed)
			break;
		// render textures, fill lookup and compile display lists

		b_result = true;
	} while(0);

	std::for_each(texture_page_list.begin(), texture_page_list.end(), CDeleteObjects());
	// delete texture pages

	SelectObject(h_dc, h_return_font);
	temp_bitmap.ReleaseDC();
	// release temp rendering bitmap

	return b_result;
}

#endif // FONT_TEXTURE_CREATE_FONTS

/*
 *	virtual void CGLBitmapFont::Free()
 *		- cleanup font, can't be used anymore
 */
void CGLBitmapFont::Free()
{
	if(m_p_char_lookup) {
		for(int i = 0; i < m_n_char_num; ++ i) {
			if(m_p_char_lookup[i].n_display_list)
				glDeleteLists(m_p_char_lookup[i].n_display_list, 1);
		}
		delete[] m_p_char_lookup;
		m_p_char_lookup = 0;
	}
	m_char_list.clear();
	std::for_each(m_texture_list.begin(), m_texture_list.end(), CDeleteObjects());
	m_texture_list.clear();
}

/*
 *	void CGLBitmapFont::SetTextureFilters(CGLState *p_state,
 *		GLenum n_min_filter = GL_LINEAR_MIPMAP_LINEAR, GLenum n_mag_filter = GL_LINEAR)
 *		- sets texture filtering mode
 *		- p_state is OpenGL state guard
 *		- n_min_filter and n_mag_filter are minification and magnification filters respectively
 *		- useful for setting GL_NEAREST filtering for pixel-accurate fonts
 */
void CGLBitmapFont::SetTextureFilters(CGLState *p_state, GLenum n_min_filter, GLenum n_mag_filter)
{
	std::for_each(m_texture_list.begin(), m_texture_list.end(),
		CSetTextureFilters(p_state, n_min_filter, n_mag_filter));
}

/*
 *	void CGLBitmapFont::SetTextureAnisotropy(CGLState *p_state, float f_anisotropy)
 *		- sets anisotropic texture filtering mode
 *		- p_state is OpenGL state guard
 *		- f_anisotropy is filter anisotropy (1 = disabled, GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT)
 *		- useful when fonts are rendered in 3D environment and (viewed under angle)
 */
void CGLBitmapFont::SetTextureAnisotropy(CGLState *p_state, float f_anisotropy)
{
	std::for_each(m_texture_list.begin(), m_texture_list.end(),
		CSetTextureAniso(p_state, f_anisotropy));
}

/*
 *	virtual void CGLBitmapFont::Zoom(float f_new_size = 1)
 *		- zooms font to a given scale
 */
void CGLBitmapFont::Zoom(float f_new_size /*= 1*/)
{
	m_f_zoom = f_new_size;// / m_n_newline_height;
}

/*
 *	virtual float CGLBitmapFont::f_Zoom() const
 *		- returns font scale
 */
float CGLBitmapFont::f_Zoom() const
{
	return m_f_zoom;// * m_n_newline_height;
}

/*
 *	virtual void CGLBitmapFont::Plot_Character(CGLState *p_state,
 *		Vector2f v_pos, char n_char) const
 *		- plots a single character n_char to position v_pos; p_state is OpenGL state guard
 */
void CGLBitmapFont::Plot_Character(CGLState *p_state, Vector2f v_pos, char n_char) const
{
	if((unsigned char)n_char < m_n_first_char ||
	   (unsigned char)n_char - m_n_first_char >= m_n_char_num)
		return;
	// check for characters out of range

	if(m_b_snap_to_pixels) {
		v_pos.x -= fmod(v_pos.x + m_f_pixel_x * .5f, m_f_pixel_x) - m_f_pixel_x * .5f;
		v_pos.y -= fmod(v_pos.y + m_f_pixel_y * .5f, m_f_pixel_y) - m_f_pixel_y * .5f;
	}

	TCharLookup *p_cur_char = m_p_char_lookup + ((unsigned char)n_char - m_n_first_char);
	if(!p_cur_char->p_tex_page)
		return;
	// undisplayable character

	p_cur_char->p_tex_page->Bind_Enable(p_state);
	// bind proper texture page

	glPushMatrix();
	glTranslatef(v_pos.x, v_pos.y, 0);
	glScalef(m_f_zoom, m_f_zoom, m_f_zoom);
	// set position

	glBegin(GL_QUADS);
	glCallList(p_cur_char->n_display_list);
	glEnd();
	// draw aligned quad

	glPopMatrix();
	// return matrix
}

/*
 *	virtual void CGLBitmapFont::Plot_String(CGLState *p_state, Vector2f v_pos,
 *		const char *p_s_string, int n_length = -1) const
 *		- plots string of n_length characters from p_s_string (in case n_length is -1,
 *		  the whole string is used) to position v_pos; p_state is OpenGL state guard
 *		- note this is faster than calling Plot_Character() in loop can be 
 */
void CGLBitmapFont::Plot_String(CGLState *p_state, Vector2f v_pos,
	const char *p_s_string, int n_length /*= -1*/) const
{
	if(n_length == -1)
		n_length = strlen(p_s_string);

	if(m_b_snap_to_pixels) {
		v_pos.x -= fmod(v_pos.x + m_f_pixel_x * .5f, m_f_pixel_x) - m_f_pixel_x * .5f;
		v_pos.y -= fmod(v_pos.y + m_f_pixel_y * .5f, m_f_pixel_y) - m_f_pixel_y * .5f;
	}

	p_state->EnableTexture2D();

	glPushMatrix();
	glTranslatef(v_pos.x, v_pos.y, 0);
	glScalef(m_f_zoom, m_f_zoom, m_f_zoom);
	// set position

	for(const char *p_end = p_s_string + n_length; p_s_string < p_end; ++ p_s_string) {
		unsigned char n_char = *p_s_string;
		if(n_char == '\n') {
			v_pos.y -= m_n_newline_height * m_f_zoom;
			glPopMatrix();
			glPushMatrix();
			glTranslatef(v_pos.x, v_pos.y, 0);
			glScalef(m_f_zoom, m_f_zoom, m_f_zoom);
			continue;
		} if(n_char == '\r') {
			glPopMatrix();
			glPushMatrix();
			glTranslatef(v_pos.x, v_pos.y, 0);
			glScalef(m_f_zoom, m_f_zoom, m_f_zoom);
			continue;
		} else if(n_char == '\t') {
			glTranslatef(float(4 * m_n_space_width), 0, 0);
			continue;
		}
		// handle ctrl chars

		if(n_char < m_n_first_char || n_char - m_n_first_char >= m_n_char_num) {
			glTranslatef(float(m_n_space_width), 0, 0);
			continue;
		}
		// check for characters out of range

		TCharLookup *p_cur_char = m_p_char_lookup + ((unsigned char)n_char - m_n_first_char);
		if(!p_cur_char->p_tex_page) {
			glTranslatef(float(m_n_space_width), 0, 0);
			continue;
		}
		// undisplayable character

		p_cur_char->p_tex_page->Bind(p_state);
		// bind proper texture page

		glBegin(GL_QUADS);
		glCallList(p_cur_char->n_display_list);
		glEnd();
		// draw aligned quad

		glTranslatef(float(p_cur_char->p_character->n_caret_shift), 0, 0);
	}

	glPopMatrix();
	// return matrix
}

/*
 *	virtual float CGLBitmapFont::f_StringWidth(const char *p_s_string, int n_length = -1) const
 *		- returns width of n_length characters from p_s_string (in case n_length is -1,
 *		  the whole string is used)
 *		- note in case string contains multiple lines of text, the longest line is returned
 */
float CGLBitmapFont::f_StringWidth(const char *p_s_string, int n_length /*= -1*/) const
{
	if(n_length == -1)
		n_length = strlen(p_s_string);

	int n_max_width = 0;
	int n_x = 0;
	for(const char *p_end = p_s_string + n_length; p_s_string < p_end; ++ p_s_string) {
		unsigned char n_char = *p_s_string;

		if(n_char == '\n' || n_char == '\r') {
			n_x = 0;
			continue;
		} else if(n_char == '\t') {
			n_x += 4 * m_n_space_width;
			if(n_max_width < n_x)
				n_max_width = n_x;
			continue;
		}
		// handle ctrl chars

		if(n_char < m_n_first_char || n_char - m_n_first_char >= m_n_char_num) {
			n_x += m_n_space_width;
			if(n_max_width < n_x)
				n_max_width = n_x;
			continue;
		}
		// check for characters out of range

		n_x += m_p_char_lookup[(unsigned char)n_char -
			m_n_first_char].p_character->n_caret_shift;
		if(n_max_width < n_x)
			n_max_width = n_x;
		// real character offset
	}

	return n_max_width * m_f_zoom;
}

/*
 *	virtual float CGLBitmapFont::f_LineHeight() const
 *		- returns complete height of line of text including internal / external leading
 *		- note this should be positive number
 */
float CGLBitmapFont::f_LineHeight() const
{
	return m_n_newline_height * m_f_zoom;
}

/*
 *	virtual float CGLBitmapFont::f_LineDescent() const
 *		- returns line descension, ie. height of character parts under baseline
 *		  (characters sucha as gjpqy)
 *		- note this should be negative number
 */
float CGLBitmapFont::f_LineDescent() const
{
	return m_n_char_descent * m_f_zoom;
}

/*
 *	virtual float CGLBitmapFont::f_InternalLeading() const
 *		- returns internal leading, ie. height of punctation marks (included in character height)
 *		- note this should be negative number
 */
float CGLBitmapFont::f_InternalLeading() const
{
	return m_n_int_leading * m_f_zoom;
}

/*
 *	virtual Vector2f CGLBitmapFont::v_CharacterOffset(const char *p_s_string,
 *		int n_length = -1) const
 *		- returns offset of n_length-th character from p_s_string (in case n_length is -1,
 *		  it's the last character)
 */
Vector2f CGLBitmapFont::v_CharacterOffset(const char *p_s_string, int n_length /*= -1*/) const
{
	if(n_length == -1)
		n_length = strlen(p_s_string);

	Vector2i v_offset(0, 0);
	for(const char *p_end = p_s_string + n_length; p_s_string < p_end; ++ p_s_string) {
		unsigned char n_char = *p_s_string;

		if(n_char == '\n') {
			v_offset.x = 0;
			v_offset.y -= m_n_newline_height;
			continue;
		} else if(n_char == '\r') {
			v_offset.x = 0;
			continue;
		} else if(n_char == '\t') {
			v_offset.x += 4 * m_n_space_width;
			continue;
		}
		// handle ctrl chars

		if(n_char < m_n_first_char || n_char - m_n_first_char >= m_n_char_num) {
			v_offset.x += m_n_space_width;
			continue;
		}
		// check for characters out of range

		v_offset.x += m_p_char_lookup[(unsigned char)n_char -
			m_n_first_char].p_character->n_caret_shift;
		// real character offset
	}

	return Vector2f(v_offset.x * m_f_zoom, v_offset.y * m_f_zoom);
}

/**
 *	@brief loads texture page
 *
 *	Loads a single texture page from file p_s_file_name.
 *
 *	@param[in] p_s_file_name is input image file name
 *	@param[in] p_state is OpenGL state guard
 *	@param[out] r_n_geom_padding will be set to geometry padding in the loaded page
 *
 *	@return Returns 0 on success, -1 in case bitmap loading failed (assumes there
 *		is no such bitmap) and -2 on any other error.
 */
int CGLBitmapFont::n_Load_Page(const char *p_s_file_name,
	CGLState *p_state, int &r_n_geom_padding)
{
	if(!stl_ut::Reserve_1More(m_texture_list))
		return -2;
	// alloc

	TBmp *p_bitmap;
	if(!(p_bitmap = CTgaCodec::p_Load_TGA(p_s_file_name)))
		return -1;
	// load bitmap (return -1 so in case there are more pages the calling routine
	// knows there's no next page and that was cause of the error, instead of
	// insufficient memory or whatever)

	CFontTexturePage tex_page(p_bitmap);
	if(!tex_page.n_Character_Num()) {
		delete[] p_bitmap->p_buffer;
		delete p_bitmap;
		return -2;
	}
	// decode character layout

	r_n_geom_padding = tex_page.n_Geom_Padding();
	m_n_space_width = tex_page.n_Space_Width();
	m_n_newline_height = tex_page.n_Newline_Height();
	m_n_char_descent = tex_page.n_Line_Descent();
	m_n_int_leading = tex_page.n_Internal_Leading();
	// copy params (note they could vary between pages, but the tool doesn't
	// create such a sets)

	for(uint32_t *p_pixel = p_bitmap->p_buffer, *p_end = p_bitmap->p_buffer +
	   (p_bitmap->n_width * p_bitmap->n_height); p_pixel != p_end; ++ p_pixel)
		*p_pixel <<= 8;
	// shift red channel to alpha

	p_state->BindTexture2D(0); // in case texture with the same id as previous texture was
	// deleted, state guard doesn't know and texture data will not be loaded to the correct
	// texture

	CGLTexture_2D *p_texture;
	if(!(p_texture = new(std::nothrow) CGLTexture_2D(p_state, p_bitmap->n_width, p_bitmap->n_height,
	   GL_ALPHA, true, 0, GL_RGBA, GL_UNSIGNED_BYTE, p_bitmap->p_buffer))) {
		delete[] p_bitmap->p_buffer;
		delete p_bitmap;
		return -2;
	}
	delete[] p_bitmap->p_buffer;
	delete p_bitmap;
	// create texture

	p_texture->r_Parameters(p_state).Set_Texture_Wrap_S(GL_CLAMP_TO_EDGE);
	p_texture->r_Parameters(p_state).Set_Texture_Wrap_T(GL_CLAMP_TO_EDGE);
#ifdef LIMIT_MIPMAPS
	int n_max_level = (int)(log(tex_page.n_Padding()) / log(2)); // max mip level
	p_texture->r_Parameters(p_state).Set_Texture_Max_Level((n_max_level > 1)? n_max_level : 1);
#endif // LIMIT_MIPMAPS
	// clamp to edge, max mip-level

	//int n_page_index = m_texture_list.size();
	m_texture_list.push_back(p_texture);
	// insert texture page to the list

	if(!stl_ut::Reserve_NMore(m_char_list, tex_page.n_Character_Num()))
		return -2;
	for(int i = 0, n = tex_page.n_Character_Num(); i < n; ++ i)
		m_char_list.push_back(tex_page.t_Character(i));
	// insert characters to the list

	return 0;
}

/*
 *								=== ~CGLBitmapFont ===
 */
