#include "Stdafx.h"
#include "Viewer.h"
#include "Types.h"

namespace Mix{ namespace Tool{ namespace Font{

Viewer::Viewer( System::IntPtr handle ) :
m_hWnd( NULL ),
m_hWndDC( NULL ),
m_hBmp( NULL ),
m_pBmpBits( NULL ),
m_hBmpDC( NULL ),
m_BmpWidth( 0 ),
m_BmpHeight( 0 ),
m_BmpSize( 0 ),
m_Style( gcnew System::String( L"" ) ),
m_Size( 24 ),
m_Weight( 400 ),
m_Effects( Mix::Tool::Font::Effects::None ),
m_BorderSize( 1 ),
m_FontSize( m_Size ),
m_MaxBorderSize( m_BorderSize ),
m_ForeColor( 0x00000000 ),
m_hFont( NULL ),
m_hGridPen( NULL ),
m_HNum( 0 ),
m_VNum( 0 ),
m_ScrollMax( 0 ),
m_ScrollPos( 0 ),
m_pGlyphBuff( NULL ),
m_GlyphBuffSize( 0 ),
m_pGlyphImage( NULL ),
m_GlyphImageSize( 0 )
{
	pin_ptr<void*> ppBmpBits = &m_pBmpBits;

	BITMAPINFO bi;
	RECT rect;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 描画ウィンドウ
	////////////////////////////////////////////////////////////////////////////////////////////////////

	m_hWnd = static_cast<HWND>( handle.ToPointer() );
	m_hWndDC = ::GetDC( m_hWnd );

	if( m_hWndDC == NULL )
	{
		throw gcnew System::Exception();
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ビットマップの作成
	////////////////////////////////////////////////////////////////////////////////////////////////////

	::GetWindowRect( ::GetDesktopWindow(), &rect );

	::ZeroMemory( &bi, sizeof( BITMAPINFO ) );

	bi.bmiHeader.biSize = sizeof( BITMAPINFOHEADER );
	bi.bmiHeader.biWidth = rect.right - rect.left;
	bi.bmiHeader.biHeight = rect.bottom - rect.top;
	bi.bmiHeader.biPlanes = 1;
	bi.bmiHeader.biBitCount = 32;
	bi.bmiHeader.biCompression = BI_RGB;

	m_hBmp = ::CreateDIBSection( m_hWndDC, &bi, DIB_RGB_COLORS, ppBmpBits, NULL, 0 );
	if( m_hBmp == NULL )
	{
		::ReleaseDC( m_hWnd, m_hWndDC );
		throw gcnew System::Exception();
	}

	m_hBmpDC = ::CreateCompatibleDC( m_hWndDC );
	if( m_hBmpDC == NULL )
	{
		::DeleteObject( m_hBmp );
		::ReleaseDC( m_hWnd, m_hWndDC );
		throw gcnew System::Exception();
	}

	if( ::SelectObject( m_hBmpDC, m_hBmp ) == HGDI_ERROR )
	{
		::DeleteDC( m_hBmpDC );
		::DeleteObject( m_hBmp );
		::ReleaseDC( m_hWnd, m_hWndDC );
		throw gcnew System::Exception();
	}

	m_BmpWidth = bi.bmiHeader.biWidth;
	m_BmpHeight = bi.bmiHeader.biHeight;
	m_BmpSize = m_BmpWidth * m_BmpHeight;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ペンの作成
	////////////////////////////////////////////////////////////////////////////////////////////////////

	m_hGridPen = CreatePen( PS_SOLID, 1, Viewer::GRID_COLOR );
	if( m_hGridPen == NULL )
	{
		::DeleteDC( m_hBmpDC );
		::DeleteObject( m_hBmp );
		::ReleaseDC( m_hWnd, m_hWndDC );
		throw gcnew System::Exception();
	}
}

Viewer::~Viewer( void )
{
	if( m_pGlyphImage != NULL )
	{
		delete [] m_pGlyphImage;
		m_pGlyphImage = NULL;
	}

	if( m_pGlyphBuff != NULL )
	{
		delete [] m_pGlyphBuff;
		m_pGlyphBuff = NULL;
	}

	if( m_hGridPen != NULL )
	{
		::DeleteObject( m_hGridPen );
		m_hGridPen = NULL;
	}

	if( m_hFont != NULL )
	{
		::DeleteObject( m_hFont );
		m_hFont = NULL;
	}

	if( m_hBmpDC != NULL )
	{
		::DeleteDC( m_hBmpDC );
		m_hBmpDC = NULL;
	}

	if( m_hBmp != NULL )
	{
		::DeleteObject( m_hBmp );
		m_hBmp = NULL;
	}

	::ReleaseDC( m_hWnd, m_hWndDC );
}

System::String^ Viewer::Style::get( void )
{
	return ( System::String^ )m_Style->Clone();
}

void Viewer::Style::set( System::String^ value )
{
	m_Style = ( value != nullptr )? ( System::String^ )value->Clone() : gcnew System::String( L"" );

	UpdateFont();
	Update();
}

int Viewer::Size::get( void )
{
	return static_cast<int>( m_Size );
}

void Viewer::Size::set( int value )
{
	m_Size = ( value >= Viewer::MIN_SIZE )? static_cast<int>( value ) : 1;

	UpdateFont();
	Update();
}

int Viewer::Weight::get( void )
{
	return static_cast<int>( m_Weight );
}

void Viewer::Weight::set( int value )
{
	if( value < FW_THIN )
	{
		m_Weight = FW_THIN;
	}
	else if( value > FW_HEAVY )
	{
		m_Weight = FW_HEAVY;
	}
	else
	{
		m_Weight = static_cast<unsigned int>( value );
	}

	UpdateFont();
	Update();
}

Mix::Tool::Font::Effects Viewer::Effects::get( void )
{
	return m_Effects;
}

void Viewer::Effects::set( Mix::Tool::Font::Effects value )
{
	m_Effects = value;
	m_ForeColor = ( ( m_Effects & Mix::Tool::Font::Effects::Bordering ) == Mix::Tool::Font::Effects::Bordering )? 0x000000FF : 0x00000000;

	UpdateFont();
	Update();
}

int Viewer::BorderSize::get( void )
{
	return static_cast<int>( m_BorderSize );
}

void Viewer::BorderSize::set( int value )
{
	if( this->MinBorderSize > value )
	{
		m_BorderSize = static_cast<unsigned int>( this->MinBorderSize );
	}
	else if( this->MaxBorderSize < value )
	{
		m_BorderSize = static_cast<unsigned int>( this->MaxBorderSize );
	}
	else
	{
		m_BorderSize = static_cast<unsigned int>( value );
	}

	if( ( m_Effects & Mix::Tool::Font::Effects::Bordering ) == Mix::Tool::Font::Effects::Bordering )
	{
		UpdateFont();
		Update();
	}
}

int Viewer::MinBorderSize::get( void )
{
	return Viewer::MIN_BORDER_SIZE;
}

int Viewer::MaxBorderSize::get( void )
{
	return m_MaxBorderSize;
}

int Viewer::ScrollMaximum::get( void )
{
	return static_cast<int>( m_ScrollMax );
}

int Viewer::ScrollValue::get( void )
{
	return static_cast<int>( m_ScrollPos );
}

void Viewer::ScrollValue::set( int value )
{
	UINT temp = ( value >= 0 )? static_cast<UINT>( value ) : 0;

	m_ScrollPos = ( m_ScrollMax < temp )? m_ScrollMax : temp;
}

void Viewer::Update( void )
{
	if( m_hFont == NULL )
	{
		return;
	}

	RECT rect;
	UINT width;
	UINT height;
	UINT numLine;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 描画に必要な情報を設定
	////////////////////////////////////////////////////////////////////////////////////////////////////

	::GetClientRect( m_hWnd, &rect );

	width = rect.right - rect.left;
	height = rect.bottom - rect.top;

	m_HNum = max( 1, width / m_Size );
	m_VNum = max( 1, ( rect.bottom - rect.top ) / m_Size );

	numLine = Mix::Tool::Font::CHARACTER_COUNT / m_HNum;
	if( ( Mix::Tool::Font::CHARACTER_COUNT % m_HNum ) != 0 )
	{
		numLine++;
	}

	if( m_VNum < numLine )
	{
		m_ScrollMax = numLine - m_VNum;

		if( m_ScrollMax < m_ScrollPos )
		{
			m_ScrollPos = m_ScrollMax;
		}
	}
	else
	{
		m_ScrollMax = 0;
		m_ScrollPos = 0;
	}
}

void Viewer::Draw( void )
{
	if( m_hFont == NULL )
	{
		return;
	}

	RECT rect;
	UINT width;
	UINT height;

	UINT* pBG;
	UINT* pBGEnd;

	HGDIOBJ hOldFont;

	UINT charIndex;
	UINT charEnd;
	UINT cx;
	UINT cy;

	::GetClientRect( m_hWnd, &rect );

	width = rect.right - rect.left;
	height = rect.bottom - rect.top;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 背景を塗りつぶす
	////////////////////////////////////////////////////////////////////////////////////////////////////

	pBG = reinterpret_cast<UINT*>( m_pBmpBits ) + ( m_BmpHeight - height ) * m_BmpWidth - ( m_BmpWidth - width );
	pBGEnd = pBG + ( m_BmpWidth * height );

	while( pBG != pBGEnd )
	{
		*pBG++ = 0x00FFFFFF;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// グリッドを描画
	////////////////////////////////////////////////////////////////////////////////////////////////////

	HPEN hOldPen = ( HPEN )SelectObject( m_hBmpDC, m_hGridPen );

	for( UINT lx = 0; lx < ( m_HNum + 1 ); lx++ )
	{
		for( UINT ly = 0; ly < ( m_VNum + 1 ); ly++ )
		{
			POINT p1;
			POINT p2;

			p1.x = lx * m_Size;
			p1.y = ly * m_Size;

			p2.x = p1.x + width;
			p2.y = p1.y;
			MoveToEx( m_hBmpDC, p1.x, p1.y, NULL );
			LineTo( m_hBmpDC, p2.x, p2.y );

			p2.x = p1.x;
			p2.y = p1.y + height;
			MoveToEx( m_hBmpDC, p1.x, p1.y, NULL );
			LineTo( m_hBmpDC, p2.x, p2.y );
		}
	}

	SelectObject( m_hBmpDC, hOldPen );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// テキストを描画
	////////////////////////////////////////////////////////////////////////////////////////////////////

	charIndex = m_HNum * m_ScrollPos;
	charEnd = charIndex + ( m_HNum * m_VNum );

	cx = 0;
	cy = 0;

	if( Mix::Tool::Font::CHARACTER_COUNT < charEnd )
	{
		charEnd = charEnd - ( charEnd - Mix::Tool::Font::CHARACTER_COUNT );
	}

	hOldFont = ::SelectObject( m_hBmpDC, m_hFont );

	while( charIndex < charEnd )
	{
		DrawFont( cx * m_Size, cy * m_Size, Mix::Tool::Font::CHARACTER_TABLE[charIndex] );

		cx++;
		if( cx >= m_HNum )
		{
			cx = 0;
			cy++;
		}

		charIndex++;
	}

	::SelectObject( m_hBmpDC, hOldFont );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// フリップ
	////////////////////////////////////////////////////////////////////////////////////////////////////

	::BitBlt( m_hWndDC, 0, 0, ( rect.right - rect.left ), ( rect.bottom - rect.top ), m_hBmpDC, 0, 0, SRCCOPY );
}

void Viewer::UpdateFont( void )
{
	if( m_hFont != NULL )
	{
		::DeleteObject( m_hFont );
		m_hFont = NULL;
	}

	DWORD dwItalic = ( ( m_Effects & Mix::Tool::Font::Effects::Italic ) == Mix::Tool::Font::Effects::Italic );
	pin_ptr<const wchar_t> pStyle = PtrToStringChars( m_Style );

	if( ( m_Effects & Mix::Tool::Font::Effects::Bordering ) == Mix::Tool::Font::Effects::Bordering )
	{
		unsigned int oldMaxBorderSize = m_MaxBorderSize;

		m_FontSize = m_Size - m_BorderSize * 2 - 1;
		m_MaxBorderSize = m_Size / 2 - 1;

		if( oldMaxBorderSize != m_MaxBorderSize )
		{
			this->BorderRangeChanged( this, gcnew System::EventArgs() );
		}
	}
	else
	{
		m_FontSize = m_Size;
	}

	m_hFont = ::CreateFontW(	m_FontSize,
								0,
								0,
								0,
								m_Weight,
								dwItalic,
								FALSE,
								FALSE,
								SHIFTJIS_CHARSET,
								OUT_DEFAULT_PRECIS,
								CLIP_DEFAULT_PRECIS,
								PROOF_QUALITY,
								( FIXED_PITCH | FF_MODERN ),
								pStyle );
}

void Viewer::DrawFont( UINT x, UINT y, UINT code )
{
	static const UINT PITCH_SHIFT = 2;

	GLYPHMETRICS gm;
	TEXTMETRIC tm;

	INT borderSize;

	UINT glyphBuffSize;

	UINT glyphImageWidth;
	UINT glyphImageHeight;
	UINT glyphImageSize;

	UINT dx;
	UINT dy;
	UINT width;
	UINT height;
	UINT right;
	UINT bottom;
	UINT pitch;

	BYTE* pSrcBase;
	BYTE* pSrc;

	BYTE* pImgBase;
	BYTE* pImgEnd;
	BYTE* pImg;

	UINT* pBmpBase;
	UINT* pBmpEnd;
	UINT* pBmp;

	BYTE alpha;

	UINT br;
	UINT bg;
	UINT bb;

	UINT r;
	UINT g;
	UINT b;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// グリフ : 情報の取得
	////////////////////////////////////////////////////////////////////////////////////////////////////

	::ZeroMemory( &gm, sizeof( gm ) );
	::ZeroMemory( &tm, sizeof( tm ) );

	borderSize = ( ( m_Effects & Mix::Tool::Font::Effects::Bordering ) == Mix::Tool::Font::Effects::Bordering )? static_cast<INT>( m_BorderSize ) : 0;

	glyphBuffSize = ::GetGlyphOutline( m_hBmpDC, code, GGO_GRAY8_BITMAP, &gm, NULL, NULL, &Mix::Tool::Font::MAT );
	if( glyphBuffSize == GDI_ERROR )
	{
		return;
	}

	glyphImageWidth = gm.gmBlackBoxX + borderSize * 4;
	glyphImageHeight = gm.gmBlackBoxY + borderSize * 4;
	glyphImageSize = glyphImageWidth * glyphImageHeight;

	if( ::GetTextMetrics( m_hBmpDC, &tm ) == FALSE )
	{
		return;
	}

//	dx = static_cast<UINT>( gm.gmptGlyphOrigin.x );
	dx = static_cast<UINT>( gm.gmptGlyphOrigin.x ) + ( m_Size - gm.gmCellIncX ) / 2;
	if( ( x + dx ) >= m_BmpWidth )
	{
		return;
	}

//	dy = static_cast<UINT>( tm.tmAscent - gm.gmptGlyphOrigin.y );
	dy = static_cast<UINT>( tm.tmAscent - gm.gmptGlyphOrigin.y ) + borderSize;
	if( ( y + dy ) >= m_BmpHeight )
	{
		return;
	}

	width = gm.gmBlackBoxX;
	right = x + dx + width;
	if( right >= m_BmpWidth )
	{
		int temp = static_cast<int>( width ) - static_cast<int>( right - width );
		if( temp <= 0 )
		{
			return;
		}

		width = temp;

//		width = width - ( right - width );
//		if( width == 0 )
//		{
//			return;
//		}
	}

	height = gm.gmBlackBoxY;
	bottom = y + dy + height;
	if( bottom >= m_BmpHeight )
	{
		int temp = static_cast<int>( height ) - static_cast<int>( bottom - m_BmpHeight );
		if( temp <= 0 )
		{
			return;
		}

		height = temp;

//		height = height - ( bottom - m_BmpHeight );
//		if( height == 0 )
//		{
//			return;
//		}
	}

	pitch = ( ( gm.gmBlackBoxX >> PITCH_SHIFT ) << 2 );
	pitch += ( ( gm.gmBlackBoxX % 4 ) == 0 )? 0 : 4;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// グリフ : バッファの取得
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( m_pGlyphBuff != NULL )
	{
		if( m_GlyphBuffSize < glyphBuffSize )
		{
			delete [] m_pGlyphBuff;
			m_pGlyphBuff = new BYTE[glyphBuffSize];
		}
	}
	else
	{
		m_pGlyphBuff = new BYTE[glyphBuffSize];
	}

	if( m_pGlyphBuff != NULL )
	{
		m_GlyphBuffSize = glyphBuffSize;
		ZeroMemory( m_pGlyphBuff, m_GlyphBuffSize );
	}
	else
	{
		m_GlyphBuffSize = 0;
		return;
	}

	glyphBuffSize = ::GetGlyphOutline( m_hBmpDC, code, GGO_GRAY8_BITMAP, &gm, m_GlyphBuffSize, m_pGlyphBuff, &Mix::Tool::Font::MAT );
	if( glyphBuffSize == GDI_ERROR )
	{
		return;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// グリフ : ソース(グリフ)からイメージの作成
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( m_pGlyphImage != NULL )
	{
		if( m_GlyphImageSize < glyphImageSize )
		{
			delete [] m_pGlyphImage;
			m_pGlyphImage = new BYTE[glyphImageSize];
		}
	}
	else
	{
		m_pGlyphImage = new BYTE[glyphImageSize];
	}

	if( m_pGlyphImage != NULL )
	{
		m_GlyphImageSize = glyphImageSize;
		ZeroMemory( m_pGlyphImage, m_GlyphImageSize );
	}
	else
	{
		m_GlyphImageSize = 0;
		return;
	}

	pSrcBase = &( m_pGlyphBuff[0] );
	pImgBase = m_pGlyphImage + glyphImageWidth * ( borderSize * 2 ) + ( borderSize * 2 );

	for( UINT yy = 0; yy < height; yy++ )
	{
		pSrc = pSrcBase;

		pImg = pImgBase;
		pImgEnd = pImg + width;

		while( pImg != pImgEnd )
		{
			*pImg = ( *pSrc * 255 ) >> 6;

			pSrc++;
			pImg++;
		}

		pSrcBase += pitch;
		pImgBase += glyphImageWidth;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// グリフ : イメージをビットマップに書き込む
	////////////////////////////////////////////////////////////////////////////////////////////////////

	pImgBase = m_pGlyphImage + glyphImageWidth * borderSize + borderSize;
	pBmpBase = reinterpret_cast<UINT*>( m_pBmpBits ) + ( m_BmpHeight - ( y + dy ) ) * m_BmpWidth - ( m_BmpWidth - ( x + dx ) );

	if( borderSize > 0 )
	{
		for( UINT yy = 0; yy < glyphImageHeight - borderSize * 2; yy++ )
		{
			pImg = pImgBase;

			pBmp = pBmpBase;
			pBmpEnd = pBmp + glyphImageWidth - borderSize * 2;

			while( pBmp != pBmpEnd )
			{
				alpha = *pImg;

				br = ( ( *pBmp ) >> 16 ) & 0x000000FF;
				bg = ( ( *pBmp ) >>  8 ) & 0x000000FF;
				bb = ( ( *pBmp )       ) & 0x000000FF;

				if( alpha == 0xFF )
				{
					//フォアグラウンド
					r = m_ForeColor;
					g = m_ForeColor;
					b = m_ForeColor;
				}
				else
				{
					if( alpha > 0 )
					{
						//フォアグラウンド → ボーダー
						r = ( m_ForeColor * alpha ) / 255;
						g = r;
						b = r;
					}
					else
					{
						//ボーダー → バックグラウンド
						UINT total = 0;
						BYTE white = 0;

						for( INT ox = -borderSize; ox <= +borderSize; ox++ )
						{
							for( INT oy = -borderSize; oy <= +borderSize; oy++ )
							{
								BYTE temp = *( pImg + ( static_cast<INT>( glyphImageWidth ) * oy ) + ox );

								total += temp;
								white = max( white, temp );
							}
						}

						if( total > 0 )
						{
							r = ( br * ( 255 - white ) ) / 255;
							g = ( bg * ( 255 - white ) ) / 255;
							b = ( bb * ( 255 - white ) ) / 255;

/*							if( white == 0x00 )
							{
								r = br;
								g = bg;
								b = bb;
							}
							else
							{
								r = ( br * ( 255 - white ) ) / 255;
								g = ( bg * ( 255 - white ) ) / 255;
								b = ( bb * ( 255 - white ) ) / 255;
							}
*/						}
						else
						{
							r = br;
							g = bg;
							b = bb;
						}
					}
				}

				*pBmp++ = ( r << 16 ) | ( g << 8 ) | b;
				pImg++;
			}

			pImgBase += glyphImageWidth;
			pBmpBase -= m_BmpWidth;
		}
	}
	else
	{
		for( UINT yy = 0; yy < glyphImageHeight - borderSize * 2; yy++ )
		{
			pImg = pImgBase;

			pBmp = pBmpBase;
			pBmpEnd = pBmp + glyphImageWidth - borderSize * 2;

			while( pBmp != pBmpEnd )
			{
				alpha = *pImg;

				br = ( ( *pBmp ) >> 16 ) & 0x000000FF;
				bg = ( ( *pBmp ) >>  8 ) & 0x000000FF;
				bb = ( ( *pBmp )       ) & 0x000000FF;

				if( alpha == 0xFF )
				{
					//フォアグラウンド
					r = m_ForeColor;
					g = m_ForeColor;
					b = m_ForeColor;
				}
				else if( alpha == 0x00 )
				{
					//バックグラウンド
					r = br;
					g = bb;
					b = bg;
				}
				else
				{
					//フォアグラウンド → バックグラウンド
					r = ( br * ( 255 - alpha ) + m_ForeColor * alpha ) / 255;
					g = ( bg * ( 255 - alpha ) + m_ForeColor * alpha ) / 255;
					b = ( bb * ( 255 - alpha ) + m_ForeColor * alpha ) / 255;
				}

				*pBmp++ = ( r << 16 ) | ( g << 8 ) | b;
				pImg++;
			}

			pImgBase += glyphImageWidth;
			pBmpBase -= m_BmpWidth;
		}
	}
}

}}}
