#include "cairo_font.h"

cairo_font::cairo_font(IMLangFontLink2* fl, HFONT hFont, int size )
{
	init();
	m_font_link = fl;
	if(m_font_link)
	{
		m_font_link->AddRef();
	}
	m_size = size;
	set_font(hFont);
}

cairo_font::cairo_font(IMLangFontLink2* fl, LPCWSTR facename, int size, int weight, BOOL italic, BOOL strikeout, BOOL underline )
{
	init();
	m_size = size;
	m_font_link = fl;
	if(m_font_link)
	{
		m_font_link->AddRef();
	}

	LOGFONT lf;
	ZeroMemory(&lf, sizeof(lf));
	if(!lstrcmpi(facename, L"monospace"))
	{
		wcscpy_s(lf.lfFaceName, LF_FACESIZE, L"Courier New");
	} else if(!lstrcmpi(facename, L"serif"))
	{
		wcscpy_s(lf.lfFaceName, LF_FACESIZE, L"Times New Roman");
	} else if(!lstrcmpi(facename, L"sans-serif"))
	{
		wcscpy_s(lf.lfFaceName, LF_FACESIZE, L"Arial");
	} else if(!lstrcmpi(facename, L"fantasy"))
	{
		wcscpy_s(lf.lfFaceName, LF_FACESIZE, L"Impact");
	} else if(!lstrcmpi(facename, L"cursive"))
	{
		wcscpy_s(lf.lfFaceName, LF_FACESIZE, L"Comic Sans MS");
	} else
	{
		wcscpy_s(lf.lfFaceName, LF_FACESIZE, facename);
	}

	lf.lfHeight			= -size;
	lf.lfWeight			= weight;
	lf.lfItalic			= italic;
	lf.lfCharSet		= DEFAULT_CHARSET;
	lf.lfOutPrecision	= OUT_DEFAULT_PRECIS;
	lf.lfClipPrecision	= CLIP_DEFAULT_PRECIS;
	lf.lfQuality		= DEFAULT_QUALITY;
	lf.lfStrikeOut		= strikeout;
	lf.lfUnderline		= underline;

	HFONT fnt = CreateFontIndirect(&lf);
	set_font(fnt);
}

cairo_font::~cairo_font()
{
	if(m_font_face)
	{
		cairo_font_face_destroy(m_font_face);
	}
	for(size_t i = 0; i < m_linked_fonts.size(); i++)
	{
		if(m_linked_fonts[i]->hFont)
		{
			m_font_link->ReleaseFont(m_linked_fonts[i]->hFont);
		}
		if(m_linked_fonts[i]->font_face)
		{
			cairo_font_face_destroy(m_linked_fonts[i]->font_face);
		}
	}
	m_linked_fonts.clear();
	if(m_font_link)
	{
		m_font_link->AddRef();
	}
	if(m_hFont)
	{
		DeleteObject(m_hFont);
	}
}

void cairo_font::show_text( cairo_t* cr, int x, int y, const char* str )
{
	lock();
	text_chunk::vector chunks;
	split_text(str, chunks);
	cairo_set_font_size(cr, m_size);
	cairo_move_to(cr, x, y);
	for(size_t i = 0; i < chunks.size(); i++)
	{
		if(chunks[i]->font)
		{
			cairo_set_font_face(cr, chunks[i]->font->font_face);
		} else
		{
			cairo_set_font_face(cr, m_font_face);
		}
		cairo_show_text(cr, chunks[i]->text);
	}
	unlock();

	if(m_bUnderline)
	{
		int tw = text_width(cr, chunks);

		lock();
		cairo_set_line_width(cr, 1);
		cairo_move_to(cr, x, y + 1.5);
		cairo_line_to(cr, x + tw, y + 1.5);
		cairo_stroke(cr);
		unlock();
	}
	if(m_bStrikeOut)
	{
		int tw = text_width(cr, chunks);

		cairo_font_metrics fm;
		get_metrics(cr, &fm);

		int ln_y = y - fm.x_height / 2;

		lock();
		cairo_set_line_width(cr, 1);
		cairo_move_to(cr, x, (double) ln_y - 0.5);
		cairo_line_to(cr, x + tw, (double) ln_y - 0.5);
		cairo_stroke(cr);
		unlock();
	}

	free_text_chunks(chunks);
}

void cairo_font::split_text( const char* src, text_chunk::vector& chunks )
{
	auto str = cairo_font::utf8_to_wchar(src);
	const wchar_t* str_start = str.c_str();

	int cch = str.length();

	HDC hdc = GetDC(NULL);
	SelectObject(hdc, m_hFont);
	HRESULT hr = S_OK;
	while(cch > 0)
	{
		DWORD dwActualCodePages;
		long cchActual;
		if(m_font_link)
		{
			hr = m_font_link->GetStrCodePages(str.c_str(), cch, m_font_code_pages, &dwActualCodePages, &cchActual);
		} else
		{
			hr = S_FALSE;
		}
		
		if(hr != S_OK)
		{
			break;
		}
		
		text_chunk* chk = new text_chunk;

		int sz = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), cchActual, chk->text, 0, NULL, NULL) + 1;
		chk->text = new CHAR[sz];
		sz = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), cchActual, chk->text, sz, NULL, NULL);
		chk->text[sz] = 0;
		chk->font = NULL;

		if(!(dwActualCodePages & m_font_code_pages))
		{
			for(linked_font::vector::iterator i = m_linked_fonts.begin(); i != m_linked_fonts.end(); i++)
			{
				if((*i)->code_pages == dwActualCodePages)
				{
					chk->font = (*i);
					break;
				}
			}
			if(!chk->font)
			{
				linked_font* lkf = new linked_font;
				lkf->code_pages	= dwActualCodePages;
				lkf->hFont		= NULL;
				m_font_link->MapFont(hdc, dwActualCodePages, 0, &lkf->hFont);
				if (lkf->hFont)
				{
					lkf->font_face = create_font_face(lkf->hFont);
					m_linked_fonts.push_back(lkf);
				}
				else
				{
					delete lkf;
					lkf = NULL;
				}
				chk->font = lkf;
			}
		}

		chunks.push_back(chk);

		cch -= cchActual;
		str += cchActual;
	}

	if(hr != S_OK)
	{
		text_chunk* chk = new text_chunk;

		int sz = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, chk->text, 0, NULL, NULL) + 1;
		chk->text = new CHAR[sz];
		sz = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, chk->text, sz, NULL, NULL);
		chk->text[sz] = 0;
		chk->font = NULL;
		chunks.push_back(chk);
	}

	ReleaseDC(NULL, hdc);
}

void cairo_font::free_text_chunks( text_chunk::vector& chunks )
{
	for(size_t i = 0; i < chunks.size(); i++)
	{
		delete chunks[i];
	}
	chunks.clear();
}

cairo_font_face_t* cairo_font::create_font_face( HFONT fnt )
{
	LOGFONT lf;
	GetObject(fnt, sizeof(LOGFONT), &lf);
	return cairo_win32_font_face_create_for_logfontw(&lf);
}

double cairo_font::text_width(cairo_t* cr, const char* str)
{
	text_chunk::vector chunks;
	split_text(str, chunks);

	double ret = text_width(cr, chunks);

	free_text_chunks(chunks);

	return ret;
}

double cairo_font::text_width( cairo_t* cr, text_chunk::vector& chunks )
{
	lock();
	cairo_set_font_size(cr, m_size);
	double ret = 0;
	for(size_t i = 0; i < chunks.size(); i++)
	{
		if(chunks[i]->font)
		{
			cairo_set_font_face(cr, chunks[i]->font->font_face);
		} else
		{
			cairo_set_font_face(cr, m_font_face);
		}
		cairo_text_extents_t ext;
		cairo_text_extents(cr, chunks[i]->text, &ext);
		ret += ext.x_advance;
	}
	unlock();

	return ret;
}

void cairo_font::get_metrics(cairo_t* cr, cairo_font_metrics* fm )
{
	lock();
	cairo_set_font_face(cr, m_font_face);
	cairo_set_font_size(cr, m_size);
	cairo_font_extents_t ext;
	cairo_font_extents(cr, &ext);

	cairo_text_extents_t tex_x;
	cairo_text_extents(cr, "x", &tex_x);

	cairo_text_extents_t tex_0;
	cairo_text_extents(cr, "0", &tex_0);

	fm->ascent		= (int) ext.ascent;
	fm->descent		= (int) ext.descent;
	fm->height		= (int) (ext.ascent + ext.descent);
	fm->x_height	= (int) tex_x.height;
	fm->ch_width	= (int) tex_0.width;
	unlock();
}

void cairo_font::set_font( HFONT hFont )
{
	clear();

	m_hFont				= hFont;
	m_font_face			= create_font_face(m_hFont);
	m_font_code_pages	= 0;
	if(m_font_link)
	{
		HDC hdc = GetDC(NULL);
		SelectObject(hdc, m_hFont);
		m_font_link->GetFontCodePages(hdc, m_hFont, &m_font_code_pages);
		ReleaseDC(NULL, hdc);
	}
	LOGFONT lf;
	GetObject(m_hFont, sizeof(LOGFONT), &lf);
	m_bUnderline	= lf.lfUnderline;
	m_bStrikeOut	= lf.lfStrikeOut;
}

void cairo_font::clear()
{
	if(m_font_face)
	{
		cairo_font_face_destroy(m_font_face);
		m_font_face = NULL;
	}
	for(size_t i = 0; i < m_linked_fonts.size(); i++)
	{
		if(m_linked_fonts[i]->hFont && m_font_link)
		{
			m_font_link->ReleaseFont(m_linked_fonts[i]->hFont);
		}
		if(m_linked_fonts[i]->font_face)
		{
			cairo_font_face_destroy(m_linked_fonts[i]->font_face);
		}
	}
	m_linked_fonts.clear();
	if(m_hFont)
	{
		DeleteObject(m_hFont);
		m_hFont = NULL;
	}
}

void cairo_font::init()
{
	m_hFont				= NULL;
	m_font_face			= NULL;
	m_font_link			= NULL;
	m_font_code_pages	= 0;
	m_size				= 0;
	m_bUnderline		= FALSE;
	m_bStrikeOut		= FALSE;
}

std::wstring cairo_font::utf8_to_wchar(const std::string& src )
{
	if (src.empty()) return std::wstring();

	int len = (int) src.size();
	wchar_t* str = new wchar_t[len + 1];
	MultiByteToWideChar(CP_UTF8, 0, src.c_str(), -1, str, len + 1);
	std::wstring ret(str);
	delete str;
	return ret;
}

std::string cairo_font::wchar_to_utf8(const std::wstring& src)
{
	if(src.empty()) return std::string();

	int len = WideCharToMultiByte(CP_UTF8, 0, src.c_str(), -1, NULL, 0, NULL, NULL);
	char* str = new char[len];
	WideCharToMultiByte(CP_UTF8, 0, src.c_str(), -1, str, len, NULL, NULL);
	std::string ret(str);
	delete str;
	return ret;
}
