... 멋지게 실패했습니다.

아니 사실 실패라기보단..거의 포기에 가깝네요. 여러가지 한계가 너무 많이보여서요.
해결 방법도 찾았고 나름 간단한 방법으로 해결이 가능하다는 사실도 알고있지만.. 의욕이 너무 떨어지네요.

뭐 일단 만들어 볼 생각이긴 합니다.
그러나 폰트를 설치 안하고 직접 읽어들여서 사용하는건 좀 귀찮은 일이 되버려서(...) 아마 폰트를 직접 읽어들이는 방식은 아닐것 같습니다.

일단 폰트를 직접 로드하는 방식에 약간의 문제가 발생했습니다.
마지막 글자가 ' '<- 공백 문자라면 이것은 길이로 치지 않는다는 것. 그래서 커서를 옮길 때 마지막 문자가 공백일 경우 커서가 안옮겨집니다...
제가 원하는건 그런게 아니므로... 일단 이 방법은 잠시 포기해야 할 것 같습니다.

그렇다고 폰트를 읽어들여서 하는 방법이 없냐 라고 묻는다면 있습니다.
Microsoft Windows SDK 7 Sample 소스코드에 이 해결책이 있습니다. 바로 커스텀 폰트 컬렉션 인터페이스를 상속받아 이 인터페이스를 직접 구현해야하는데
구현해야할 인터페이스가 3개가 있습니다.

안타깝게도 제가 IUnknown에 대해 몰라서(쩝...) 소스코들를 봐도 이게 대체 뭐하는건지 알 수가 없네요.

두 번째 문제는 커서의 위치가 일정하지 않다는 것 입니다.
영문의 폰트에 보면 Metrics에 descent 와 ascent라는게 있는데 이 문제 때문에 Drawing 영역의 Top Bottom 위치가 일정하게 잡히지 않습니다.
그래서 글자를 입력할 때 마다 커서의 위치가 바뀌는 문제가 발생합니다 ㅜ_ㅜ...(f와 j... 이 두개의 글자가 제일 말썽이더군요..)


사진을 모시면 아시겠지만 마지막 j를 타이핑 한 후 커서가 진짜 조금 내려간 모습이 보입니다.

일단 이 방법도 사용은 가능하지만, 마지막 띄어쓰기가 보여지지 않는다는게 너무 찝찝해서(...)
일단 이 방법 말고 이미 설치된 폰트를 이용해서 출력하는 방법을 이용해 보려구요.
MS Sample 프로젝트 라이선스도 한번 확인해 본 다음에 그대로 써도 된다면 그 방식도 한번 써 보려고 합니다.

조금 시간이 더 걸리겠군요 ㅇㅅㅇ.; 일단 애니나 라노베좀 보면서 멘탈좀 정화하다가 다시 시도하겠습니다.

혹시 참고할 만한 자료가 있다면 알려주시면 감사하겠습니다.

참고 사이트

http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=50&MAEULNo=20&no=687984&ref=687984
http://support.microsoft.com/kb/74299
http://msdn.microsoft.com/en-us/library/windows/desktop/ms533820%28v=vs.85%29.aspx
http://msdn.microsoft.com/ko-kr/library/y505zzfw%28v=vs.110%29.aspx
http://msdn.microsoft.com/ko-kr/library/xwf9s90b%28v=vs.110%29.aspx
http://cx5software.sakura.ne.jp/blog/2011/01/18/directwrite-metrics/
http://msdn.microsoft.com/en-us/library/windows/desktop/dd941785%28v=vs.85%29.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/dd941710%28v=vs.85%29.aspx
http://msdn.microsoft.com/ko-kr/library/windows/desktop/dd756583%28v=vs.85%29.aspx
http://www.gamedev.net/topic/309327-direct3dfont-using-local-folder-ttf/

기타 등등.

업데이트 내용

Version: 5.0-4493 Update 3

(2014/07/24)

Improvements

  1. Improved the stability of volume expansion.

Fixed Issues

  1. Fixed two SAMBA vulnerabilities which allowed remote attackers to use the weaknesses to perform DoS attacks (CVE-2014-0244, CVE-2014-3493).
  2. Fixed an issue which prevented users from being able to log into DSM after completing QuickConnect Wizard after a fresh DSM installation.
  3. Fixed an issue causing slow response time in DSM on the early version of RS10613xs+ when too many concurrent connections were active.



핵놀 유저들은 Nanoboot-5.0.3.1 DSM 5.0-4493 X64 기준으로


제어판 - 업데이트 및 복원 - 다운로드까지만 누룬다.

SSH 접근해서

sed 's/flashupdateDeb/flashupdateDeb1/' /autoupd@te.info > /autoupd@te.info1 

mv /autoupd@te.info1 /autoupd@te.info

라고 입력 후 업데이트를 누른다.


그럼 끝!



우와. 포스팅량이 늘었군요. 자축 자축.
에... 2부에서 예고했던데로 커서를 그려보고 왔습니다.
사실 만드는건 한 시간도 안걸렸는데 놀다보니(...) 포스팅이 좀 늦어졌네요.
별것도 아닌데 3부를 괜히 날려먹는거 아닌가? 몇 부작으로 갈 생각이지? 라는 생각은 저 멀리 다른 차원으로 집어던져버리고 빨리 코드를 살펴보겠습니다.

일단 간단하게 한개만 인클루드 해보죠.

#include <usp10.h>

좋습니다. 자 이제 함수 하나를 만들어 보겠습니다.

// 귀찮으니 전역 변수로 때워봅시다.
SCRIPT_CONTROL ScriptControl;
SCRIPT_STATE ScriptState;
SCRIPT_STRING_ANALYSIS analysis = nullptr;

void InitScriptStringAnalysis(HDC hdc, std::wstring str)
{
	if ( analysis == nullptr )
	{
		ZeroMemory(&ScriptControl, sizeof(ScriptControl));
		ZeroMemory(&ScriptState, sizeof(ScriptState));
		ScriptApplyDigitSubstitution(nullptr, &ScriptControl, &ScriptState);
	}
	else
		ScriptStringFree(&analysis);

	ScriptStringAnalyse(hdc, str.c_str(), str.length() + 1,
						str.length() * 1.5 + 16,
						-1,
						SSA_BREAK | SSA_GLYPHS | SSA_FALLBACK | SSA_LINK,
						0,
						&ScriptControl,
						&ScriptState,
						nullptr,
						nullptr,
						nullptr,
						&analysis);
}


으음. 뭐. 그럭저럭 테스트 해볼만 한 함수가 나왔습니다.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;
	static HIMC imeID = nullptr;
	static std::wstring str;

	wchar_t debug[256] = { 0 };
	static int x = 0;

	RECT rect = { 0, 0, 0, 0 };

	switch ( message )
	{
		case WM_ACTIVATE:
			if ( imeID == nullptr )
			{
				imeID = ImmCreateContext();
				ImmAssociateContext(hWnd, imeID);
			}
			break;
		case WM_IME_STARTCOMPOSITION:
			break;
		case WM_IME_CHAR:
			str.push_back((wchar_t)wParam);
			InvalidateRect(hWnd, NULL, TRUE);
			break;
		case WM_CHAR:
			if ( (wchar_t)wParam == '\b' )
			{
				str.pop_back();
			}
			else
			{
				str.push_back((wchar_t)wParam);
			}
			InvalidateRect(hWnd, NULL, true);
			break;
		case WM_PAINT:
			hdc = BeginPaint(hWnd, &ps);
			// DT_CALCRECT옵션은 출력할 글자의 범위를 구하는 옵션입니다.
			// 따라서, 아래 함수를 호출하게 되면 rect변수에 출력할 글자의 범위를 구해서 던져줍니다.
			// 여기에서 우리는 top와 bottom만 필요합니다. 전체 width 는 필요 없으니까요.
			DrawText(hdc, str.empty() ? L"A":str.c_str(), -1, &rect, DT_CALCRECT | DT_CENTER);
			DrawText(hdc, str.c_str(), str.length(), &rect, DT_CENTER);

			// 이 함수를 호출하게 되면 ScriptStringCPtoX를 사용하기 위한
			// SCRIPT_STRING_ANALYSIS 를 얻게됩니다. 자세한 내용은 MSDN을 참고해주세요.
			InitScriptStringAnalysis(hdc, str.c_str());
			ScriptStringCPtoX(analysis, str.length(), false, &nTrail);

			// 커서를 그립니다. DrawText에서 얻어온 글자의 출력 범위를 top와 bottom 으로 하고,
			// ScriptStringCPtoX에서 얻어온 마지막 글자 인덱스의 위치를 left로 하고 여기서 1을 더해 width가 1인
			// 사각형을 그립니다.
			Rectangle(hdc, x, rect.top, x + 1, rect.bottom);
			wsprintf(debug, L"커서 위치 : %d %d %d %d", x, rect.top, x + 1, rect.bottom);
			SetWindowText(hWnd, debug);

			EndPaint(hWnd, &ps);
			break;
		case WM_DESTROY:
			ImmDestroyContext(imeID);
			PostQuitMessage(0);
			break;
		// 코드가 길어져서 기본 프로시저 함수에서 수정되지 않은 이벤트는 쓰지 않았습니다.
	}
	return 0;
}

자 그럼 사진을 하나 투척해보죠.


짞짞짞.

으음. 하지만 커서가 안움직이니 뭔가 허전하군요. 명색의 커서인데 움직여야 하지 않겠어요? 움직였으면 움직인 자리에 무언가 입력하거나 삭제도 되야죠.
으음.. 한번 해보죠. 재밋을거같으니.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	// 기존 변수 생략
	static int nowPosition = 0;

	switch ( message )
	{
		case WM_ACTIVATE:
			if ( imeID == nullptr )
			{
				imeID = ImmCreateContext();
				ImmAssociateContext(hWnd, imeID);
			}
			break;
		case WM_IME_STARTCOMPOSITION:
			break;
		case WM_IME_CHAR:
		case WM_CHAR:
			if ( (wchar_t)wParam == '\b' )
			{
				if ( nowPosition == str.length() )
				{
					str.erase(nowPosition - 1);
				}
				else
				{
					// 중간 문자를 삭제하기 위한 코드입니다.
					const wchar_t* strBack = str.c_str();
					strBack = &strBack[nowPosition];

					str.erase(nowPosition - 1);
					str.append(strBack);
				}
				nowPosition = nowPosition - 1 < 0 ? 0:nowPosition - 1;
			}
			else
			{
				// 중간에 글자를 삽입하기 위해 insert로 바꾸었습니다.
				str.insert(nowPosition, (wchar_t*)&wParam);
				nowPosition = nowPosition + 1 > str.length() ? str.length():nowPosition + 1;
			}
			InvalidateRect(hWnd, NULL, true);
			break;
		case WM_KEYDOWN:
			if ( (int)wParam == VK_LEFT )
			{
				nowPosition = nowPosition - 1 < 0 ? 0:nowPosition - 1;
				InvalidateRect(hWnd, NULL, true);
			}
			else if ( (int)wParam == VK_RIGHT )
			{
				nowPosition = nowPosition + 1 > str.length() ? str.length():nowPosition + 1;
				InvalidateRect(hWnd, NULL, true);
			}
			break;
		case WM_PAINT:
			hdc = BeginPaint(hWnd, &ps);
			DrawText(hdc, str.empty() ? L"A":str.c_str(), -1, &rect, DT_CALCRECT | DT_CENTER);
			DrawText(hdc, str.c_str(), str.length(), &rect, DT_CENTER);


			InitScriptStringAnalysis(hdc, str.c_str());
			ScriptStringCPtoX(analysis, nowPosition, false, &x);

			Rectangle(hdc, x, rect.top, x + 1, rect.bottom);
			wsprintf(debug, L"커서 위치 : %d %d %d %d", x, rect.top, x + 1, rect.bottom);
			SetWindowText(hWnd, debug);
			EndPaint(hWnd, &ps);
			break;
		case WM_DESTROY:
			ImmDestroyContext(imeID);
			PostQuitMessage(0);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

이 기세라면! 마우스로 클릭해서 커서를 옮기는 것도 가능할거같군요. 확인해 보는것도 좋겠지만 이번 편은 ScriptString를 테스트해보기 위한 편이었으니 그냥 안할려고 합니다 ㅇㅅ.
작동되는거 확인했으니 됐죠 뭐 ㅋㅅㅋ.

이번에는 GDI로 직접 EditBox를 만들어 보았습니다. 코드는 좀 지저분하지만 어떠신지요?
중복된 코드를 지우기 위해서 WM_IME_CHAR에 keybd_event로 VK_RIGHT를 누르게 해 보았는데 왜인지 모르겠지만 한글 타이핑에 문제가 생기더군요.
원인을 모르겠네요. 아시는분 알려주시면 감사하겟습니다(_ _ )

그런고로! 다음에는 DirectX11에 직접 적용시켜 볼 차례군요.
자 그럼 다음시간에! (언제가 될지 모르겠지만 노력해 보겠습니다..)

커서 위치에 주목!

이전 글에서 글자를 출력 했으니 글자를 입력하는 방법을 알아야겠죠. EditBox를 만들어야 하는데 입력이 안되면 쓰나요.
누구나 Windows 프로그래밍을 처음 배울 때 키보드에서 입력하면 영어만 입력되는 아주 간단하지만 매우 의미있는 프로그램을 만들어 본 적이 있을거라 믿습니다.
허나 제가 원하는건 저번 시간에도 말했듯이 채팅이 되는 온라인 게임이란 말입니다. 근대 영어만 입력되면 쓰나요. 요즘 초등학생들이 영어 실력이 워낙 뛰어나다 보니 영어만 입력되도 상관 없을지도 모르겠지만 제가 영어를 못해서 한국어를 써야한다 이 말입니다. 그 뿐입니까? 해외 사용자도 배려해서(영어를 쓰면 되겠네요) 중국어라던가 일본어라던가도 입력 가능하게 해야한다 이말입니다!(그러니까 영어를 쓰면 된다고.)

그럼 어떻게 영어말고(어이) 다른 나라 언어를 입력 할 수 있을까요?
우리가 사용하는 EditBox는 IME라는걸 이용해서 글자를 입력받습니다.
좋습니다. 그럼 우리도 EditBox를 만드는 거니 이 녀석을 사용해보죠.

IME를 Windows에서 이용하기 위해서는 imm.h와 imm32.lib가 필요합니다. Windows.h를 인클루드 하시면 기본적으로 포함되어 있습니다.

자 그럼 잠시 살펴보죠.
일단 Win32 프로젝트를 하나 만듭니다. 귀찮으니 자동 생성 코드를 사용해서 만듭니다.(어차피 테스트하고 버릴거기 때문에 상관 없습니다.)

윈도우 프로시져 콜백 함수를 보시죠. 으음. IME를 언제 프로그램에 등록하면 좋을까요? 보통 EditBox는 창에 포커스 상태일 때 입력을 받습니다.
Focus 상태일 때 메시지를 MSDN에서 확인하는건 테스트 프로그램 만들 때 너무 귀찮으니 그냥 ACTIVATE 메시지를 이용해서 해보죠.(너무 대충이잖아 wwwwwww)

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;
	static HIMC imeID = nullptr;
	static std::wstring str;

	switch ( message )
	{
		case WM_ACTIVATE:
			if ( imeID == nullptr )
			{
				imeID = ImmCreateContext();
				ImmAssociateContext(hWnd, imeID);
			}
			break;
		case WM_IME_STARTCOMPOSITION:
			break;
		case WM_IME_CHAR:
			str.push_back((wchar_t)wParam);
			SetWindowText(hWnd, str.c_str());
			break;
		case WM_CHAR:
			if ( (wchar_t)wParam == '\b' )
			{
				str.pop_back();
			}
			else
			{
				str.push_back((wchar_t)wParam);
			}
			SetWindowText(hWnd, str.c_str());
			break;
		case WM_DESTROY:
			ImmDestroyContext(imeID);
			PostQuitMessage(0);
			break;
		// 나머지 메시지는 변경사항 없으므로 생략.
	}
	return 0;
}

발로 짜도 저것보단 잘 나오겠군요. 뭐 쓰고 버릴 코드니까 간단하게 테스트해보죠.

잘 되는군요. 중간에 일본어로 바꿔서 입력해도 잘 됩니다. 글자 변환도 잘 되구요.


이건 덤 입니다. 다음에 사용할 ScriptString 관련 함수들을 사용해서 간단히 테스트한거죠.
짤막하게 설명하자면 지금 Client 좌표계 기준으로 X=79 좌표에 '.'이 있는데 이 인덱스가 7이라는겁니다.
이게 왜 필요할까요?
마우스로 클릭하면 커서가 옮겨져야죠. 저희는 Client의 어떤 좌표에 어떤 글자가 써져있는지 알아야하니까요.


여기서 글을 끝내기로 하겠습니다 ㅇㅅㅇ.
다음에는 DirectX 프로그램에 실제로 적용해 보기 전에 GDI+로 커서를 구현해보도록 하겠습니다.

DirectX9와 10은  DXUT에 기본적으로 EditBox가 들어있습니다. 물론 아름답지 못하지만 나름 커스터마이징이 가능하더군요.
그 외에 약간의 버그같지 않은 버그라고 하면 한/영키로 한영 전환을 해도 여전히 인디게이터에 '가'라고 표시된다는 거랄까요 ㅋㅅㅋ.
이건 전에 고쳤었는데 굳이 소스를 보관할 가치를 못느껴서 포맷할때 같이 버렸던걸로 기억합니다. 조금 아쉽네요.

그래서 이번 주제는... DirectX11에도 EditBox를 만들어보자 입니다.
미리 말씀드리지만 이 글은 완결이 안날 수도 있습니다. 에 그러니까 갑자기 글이 뚝 끊기고 엉뚱한 XBMC 버그가 고쳐지는 글이 올라올지도 모르고 시작에서 멈출 수 있다는거죠. 또한 만약 막 DX에 입문하신 분이 희망찬 마음을 가지고 이 글을 읽기 시작하실까봐요... 사전에 일러두자면 저도 입문자라(...) 많은 도움은 드리지 못할거란 사실은 인지해 주셨으면합니다.

아니, DirectX에 왜 EditBox가 필요하죠? DX로 뭘 만들 작정이십니까?
EditBox를 DX에서 만든다고 하면 저런 질문이 먼저 들어오더군요. 뭐 보통 선배분들께 조언을 구한지라 저렇게 존댓말이 아니었지만요 ㅋㅅㅋ.
이유는 간단합니다. 채팅해야죠. 제가 원하는건 채팅이 되는 온라인게임이라 이말입니다.
그리고 한 가지 이유가 더 있는데 나중에 트위터랑 연동시켜서 귀여운 캐릭터가(...) 멘션을 알려주거나 작성한 트윗을 보내주는 등의 기능을 만들겁니다.
그 언제 만들어 질지 모르는 프로그램을 위해서(귀여운 캐릭터 모델이 있으면 완성될지도.) 한 번 노력해보죠.

왜 OpenGL이 아닌가요?
이유는 간단합니다. 제가 DirectX를 졸라 좋아하거든요. 뭐 미련맞은 짓이죠. 그래서 위에 써놓았지만, 이 글은 중간에 멈출 수도 있습니다.
OpenGL을 쓰면 EditBox를 구현할 필요가 없냐 라고 묻는다면 역시 그건 또 아니지만 뭔가 다른 방법이 있지 않을까 싶습니다.

자 그럼 혼자 질문하고 혼자 답하는 재미없는 글은 그만쓰고 어서 코드를 살펴보죠.

제가 이 글을 쓰기 위해 준비한 준비물은 다음과 같습니다.

DirectX SDK Sample - CustomUI
Direct2D - DirectWrite.
DirectX11.

자 그럼, 궁극적으로 EditBox는 뭐를 해야 할까요?
물론 글씨를 편집해야죠. 그러기 위해선 일단 글자를 출력해야합니다.

글씨를 출력한다. DirectX에서 글씨의 출력은 어떻게 해야 하는 걸까요?
렌더링을 해야합니다. 텍스쳐를 만들고 그 위에 글씨를 써서 렌더링 해야 합니다.

지금 '여보쇼, 이게 무슨 강아지 엿 씹어 먹는 소리야? 한글이 몇 글자 인줄 알아? unicodmap에서 hangul을 검색해서 나오는 카테고리의 모든 글자 수를 더하면 자그마치 11,524자나 된다고. 이걸언제 다 텍스쳐로 만들어서 로드하고 있어?' 라고 생각하신분 계시면 블로그 닫기 전에 잠시만 기다려주세요.
막 구글검색 하다가 미처 발견하지 못하신것 같은데 '순수하게' DirectX11만 사용한다면 당연히 그런 삽질을 해야겠지만 MS가 그렇게 멍청하게 DX를 만들어 놓진 않았습니다.
DXGI(DirectX Graphics infrastructure)를 사용하면 동기화된 공유 Surface를 만들 수 있다 이겁니다.

으음. 제가 개 엿씹어 먹는 수준의 설명을 하고있는것 같으니 이쯤에서 그냥 링크를 하나 뿌리죠. 이 링크 하나면 모든 설명이 끝납니다.
(절대로 코드를 올리기가 귀찮은게 아닐거라고 제 자신을 속여ㅂ...)

http://www.braynzarsoft.net/index.php?p=D3D11FONT

저 링크에 이 글의 모든게 들어있습니다. 사실 저는 윗 글을 보고 만들었거든요.
뭐 윗 글을 살짝 변형해서 설치 된 폰트가 아닌 어딘가의 경로에 별도로 있는 폰트를 로드 가능하게 수정했지만 그리 어려운 작업은 아닙니다.

영어가 보기 귀찮으시다면 어쩔수 없지만 이대로 글을 맺자니 사진도 없고 뭔가 허전하니 일단 뭐라도 써보죠.

위 링크에 들어가서 영어 말고 코드를 대충 읽고 오시면 대략적으로 다음과 같은 방법으로 진행된다는걸 아실겁니다.

DX11 디바이스를 생성합니다. 뭐 기타 등등의 렌더링에 필요한 초기화는 여러분이 만드신(혹은 쓰시는) 프레임워크나 라이브러리에 아주 잘 만들어놓여져있겠죠.
DX10 디바이스를 생성합니다. 뭐 렌더링 할건 아니구 공유 텍스쳐를 만들때 잠깐 사용되니 생성만 하시면 됩니다.
DX11 텍스쳐를 만듭니다.
위에서 만든 텍스쳐를 이용해 DX10과의 공유 텍스쳐를 생성합니다.
D2DFactroy를 하나 만들어서 DXGI의 Surface를 렌더 타겟으로 지정한 ID2DRenterTarget 객체를 하나 만듭니다.
ID2DRenterTarget을 이용해서 공유 텍스쳐에 글씨를 그리고
마지막으로 DirectX11 디바이스 컨텍스트에 텍스쳐를 3번째 단계에서 만든 텍스쳐로 설정하고 사각형 하나를 렌더링합니다.

끝.

왜 DX10 디바이스를 생성할까요? 위 링크에서 보면 마이크로소프트에서 충분한 종류의 DirectX11과 직접적으로 작동하는 Direct2D를 만들지 않았다고합니다.ㅜㅜ
뭐 그러니 DX10 디바이스를 생성해서 잠시 쓰는거죠 ㅇㅅㅇ.

프레임을 생각하면(이게 의외로 프레임 저하 현상이 심각합니다.) 초기 한번만 텍스쳐를 만들고 그 이후로는 만들어진 텍스쳐를 사용하는게 유리할 것 같습니다.
그러니까 어떤 문자를 처음 띄울 때는 텍스쳐를 생성하고 그 이후에는 만들어진 텍스쳐를 계속해서 사용하는거죠.

자 그럼 2부가 만들어지길 바라며 글을 마치죠.

사용된 폰트는 구글에서 배포한 NotoSansCJK-Regular.OTF입니다. 어떻게 설치되지 않은 폰트를 로드하는지는 다음에 시간나면 써보겠습니다. 하지만 Direct2D를 이용하면 어렵지 않은 일이니 Visual Studio의 인텔리센스 기능을 이용해서 잠시 함수를 뒤적거리시다가 삘 꽂히는 함수 하나를 발견 하신 다음에  MSDN을 뒤져보면 금방 찾으실 거라 믿습니다.

구글에서 한국어 중국어 일본어를 합친 폰트를 어제인가 오늘 배포했습니다.
애니 볼 때 자막 폰트 깨지는게 싫었던 저는 아주 기쁜 마음으로 이 폰트를 받았습니다.

하지만...
XBMC에서 OTF폰트를 불러오질 못하더군요;; 왜죠 왜죠 왜죠?

문제가 뭘까요?
소스코드를 열어봅니다.

GUIFontManager.cpp에 아래와 같은 코드가 있군요.

CGUIFont* GUIFontManager::LoadTTF(const CStdString& strFontName, const CStdString& strFilename, 
		color_t textColor, color_t shadowColor, const int iSize, const int iStyle, bool border, float lineSpacing,
		float aspect, const RESOLUTION_INFO *sourceRes, bool preserveAspect);
// 뭐가 이렇게 인자가 많아 -ㅅ-;

여기서 제 표정이 한번 굳었는데요;
정말 TTF만 로드되는거야..? ㅠㅅㅠ

아무래도 OTF 글꼴 로드 하는 부분은 제가 직접 만들 수 있는 실력이 없기때문에 좀 많이 좌절하다가 문득
OpenGL도 그렇고 DirectX도 그렇고 OTF 지원하는데 왜 TTF만 로드해? 그럴리가 없잖아!

자 좀 더 뒤져봅니다. 다음과 같은 문장을 발견하게 됩니다.

if (!fontName.empty() && URIUtils::HasExtension(fileName, ".ttf"))
	if (!fontName.empty() && IsFontExtension(fileName)){
		// TODO: Why do we tolower() this shit?
		CStdString strFontFileName = fileName;
	}
	fontNode = fontNode->NextSibling("font");
}

우리 고결하신 XBMC 개발자 님들께서 왜 저런 코드를 짜셨을까요....
저보다 똑똑한 분들이니 뭔가 이유가 있겠지만 지금 저에게 중요한건 그게 아니라 OTF폰트를 어케든 써먹어야겠다는겁니다.

그래서 코드를 고쳤습니다. 자세한 내용은 아래 Yobi에서 XBMC를 참고해주세요.
그리고 또한 여기서 수정된 버전의 바이너리를 윈도우용으로만 배포합니다.

다운로드

GPL V2 라이선스에 의거하여 배포합니다.
소스코드 : https://dev.kudryavka.moe:3001/hayandev/XBMC

이 빌드는 @64b5b84562755425e69d027c773bf51aeda3abdb(2014-07-03) 커밋을 마지막으로 한 XBMC Git 소스코드를 받아서 수정한 빌드입니다.

수정 내역 :
Synology WebDAV에 접속이 안되는 문제를 해결했습니다.
OTF 폰트 로드 문제를 해결했습니다.



(OTF 폰트 자막 캡쳐)


Windows 8 의 토스트 알림이 너무 맘에들어서 이걸로 무언가 개발해보려고 시작한거십니다.
Visual Studio 2012 이상의 버전이 필요합니다. 확인해 주세요.

Visual Studio 를 실행해서 C# 프로젝트 템플릿을 보면.. 스토어 앱에는 여러가지가 있는데 정작 Desktop App는 Windows 8 이상 버전만 지원하는 프로젝트를 디폴트로 생성 할 수 있는 무언가가 없습니다. (데스크탑 앱좀 챙겨달라구!!)

뭐 그냥 아무 프로젝트나 눌러서 만드시면 됩니다. 일단 만들어야 무언가 시작되니까요.


그 다음 Visual Studio가 아닌 기타 타 편집기로 csproj 프로젝트를 열어보면 XML 형식의 옵션들이 막 보이는데
PropertyGroup<TargetPlatformVersion>8.0</TargetPlatformVersion> 을 추가해주세요.

그 다음 다시 Visual Studio 로 돌아오면 다시 로드하겠냐고 물어보는데 당연히 모두 예를 눌러줍니다.

참조에서 C:\Program Files (x86)\Windows Kits\8.1\References\CommonConfiguration\Neutral\Windows.winmd 를 추가 할 수 있으면 성공입니다.


이후 기타 등등의 필요한 winmd 파일이 있으시면 참조 추가해서 마음껏 쓰시면 됩니다.

winmd에 관한 설명은 MSDN을 참고해주세요.

샘플.




수정된 버전을 다운로드 하시려면 http://youtil.wo.tc/112 <- 이쪽으로.

결론부터 드리자면 해결했습니다.
원인 파악은 아직 제대로 되지 않았습니다. Windows용 curl library의 버그가 아닐까합니다.

여기서 재미있는 사실은 IP로 접속하면 신기하게도 WebDAV서버에 접속이 가능하다는 사실입니다!(뭐 이런 X같은!)


무슨 기업도 아니고 공인 기관도 아니고 일반 사용자가 돈 더 내고 고정 아이피를 사용해야하는 이유가 있겠습니까...
DDNS 켜놓고 도메인을 하나 파두는게 진리거늘...
아이피가 바뀔 때마다 도메인이 어디로 연결되나 확인하고 XBMC에서 IP를 바꿔줘야 한다니 귀찮기 짝이없는 일이라고 생각되었습니다.

그래서 소스코드를 받았습니다. XBMC는 오픈소스더군요.

으음... 조금 뒤적뒤적 거리다보니 WebDAV로 연결을 시도하는 부분을 어렵지 않게 찾을 수 있었습니다.
XBMC에서 WebDAV를 연결할 때 하는 작업 중 하나는 http 프로토콜에 접속을 시도해서 결과 값을 얻어오는 작업이더군요.
하지만 접속이 안되니까... 접속 시도할 때 쓰는 라이브러리가 curl 이길래 curl 버전을 올려보았습니다.

결과는 똑같더군요 ㄱ-; 대략 멘탈이 저 하늘로 날아가고 있는 중이었습니다.

코드를 뒤지다가 다음과 같은 주석을 발견했습니다.

// never verify peer, we don't have any certificates to do this
g_curlInterface.easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0);
g_curlInterface.easy_setopt(h, CURLOPT_SSL_VERIFYHOST, 0);

으음.. 좋습니다. SSL의 인증서 문제는 무조건 피해 갈 수 있군요. 아니 뭐 애초에 davs://아이피주소/ 로 연결 시도해서 되면 이미 끝난거지만.

자 그럼 저 같은 초보자가 curl library를 건들여봤자 좋은 꼴은 못볼거고 야매로 고쳐보도록 하죠.

여기서 부터는 개발쪽 분야를 지망하시고 C++을 배운지 마침 하루밖에 지나지 않은 어제 헬로 월드 띄우고 오오오 했던 분들을 위한 설명입니다.
위에서도 말했지만 방법이 너무 야메라 소스코드고 뭐고 올릴 맘이 없네요;

소스코드를 받습니다. Git에 있습니다. https://github.com/xbmc/xbmc <- 주소.
컴파일을 시도해봅니다. 어떻게 윈도우에서 컴파일 하나요? <- 를 참고하시기 바랍니다.

자 컴파일을 했으면 이제 몇가지 에러를 잡아보죠.
위 도큐멘트를 따라 진행하셨다면 아마 다음과 같은 오류가 나올 것 입니다.

AddonModuleXbmc*.cpp 를 찾을 수 없습니다.

 XBMC 프로젝트의 interfaces 필터 안에 swing 라는 필터가 있는데 이곳에 *.i 라는 파일이 있습니다.
마우스 오른쪽 클릭 후 컴파일을 눌러주세요. 만약 해결되지 않는다면 배치파일을 수정하셔야 하는데 경로 문제일 가능성이 높습니다.
tools/codegenerator/GenerateSWIGBindings.bat 파일을 편집기로 열어 수정해주세요.

자 컴파일이 완료되었으면 XBMC에서 URL을 관리하는 클래스를 찾아봅니다.
솔루션 프로젝트에서 URL을 검색해보니 URL.h 와 URL.cpp가 똭! 나오네요. 자 이 두 개 파일을 수정해 봅시다.

URL.h

class CURL
{
public:
	//중략
	void ChangeHostNameToIpAddress();
protected:
	//생략
}

URL.cpp

void CURL::ChangeHostNameToIpAddress()
{
	hostent* hostentPtr = nullptr;
	hostentPtr = gethostbyname(m_strHostName);

	if ( hostentPtr != nullptr )
	{
		m_strHostName.clear();
		m_strHostName.append(inet_ntoa(*(in_addr*)hostentPtr->h_addr));
	}
}

자 이제 CurlFile.cpp로 가봅시다.

CurlFile.cpp

bool CCurlFile::Open(const CURL& url)
{
	m_opened = true;
	m_seekable = true;

	CURL url2(url);
	// 추가
	url2.ChangeHostNameToIpAddress();
	//----
	ParseAndCorrectUrl(url2);

	std::string redactPath = CURL::GetRedacted(m_url);
	CLog::Log(LOGDEBUG, "CurlFile::Open(%p) %s", (void*)this, redactPath.c_str());

	ASSERT(!(!m_state->m_easyHandle ^ !m_state->m_multiHandle));
	if( m_state->m_easyHandle == NULL )
		g_curlInterface.easy_aquire(url2.GetProtocol(), url2.GetHostName(), &m_state->m_easyHandle, &m_state->m_multiHandle );
	// 중략..
	return true;
}


컴파일 및 실행 후 즐기시면 됩니다.



Windows Server 2012 R2 Hyper-V에다가 XPEnology 설치하기 - 준비편에서 이어지는 글입니다.


Hyper-V를 실행 그리고 새로 만들기 - 가상 컴퓨터를 누른다.


원하는 이름과 경로를 설정


1세대로 설정


메모리는 개인적으로 써 봤을 때 1GB 만으로도 충분했으나 부족할꺼 같은 사람들은 2GB로 할당해도된다.


네트워킹 구성은 임의적으로 이름을 저렇게 설정한건데 대부분 고를 수 있는 어댑터가 한개일꺼고 그걸 그냥 추가해주면된다.


여기서 약간 중요한데 준비편에서 미리 만들어둔 부트로더 VHD 파일을 백업을 꼭! 해준다.

(DSM 설치시 포맷 영향으로 부트로더가 날라가는 현상 덕분에 백업한걸로 교체가 필요하다)

백업을 했다면 이제 만든 부트로더 VHD 파일을 연결해준다.


이제 마침을 누르면 준비는 거의 끝난다.


이제 다시 Hyper-V 설정창에서 몇가지를 더 손봐야한다.


프로세스를 자신이 쓰는 PC 사양에 맞게 바꿔준다.

HP N54L 사용자들은 2개를 추천한다.


IDE 컨트롤러 1로 가서 제거 버튼을 누른다.


SCSI 컨트롤러로 가서 VHD 생성 후 연결을 해줘야한다.

마침을 누르고 적용을하면 끗


이제 부팅을하고 기다리면 스샷처럼 IP가 보일텐데 이제 브라우저로 접속하면 설치 페이지가 나타난다!


할당 받은 사설 IP:5000로 접속


최신 DSM 다운로드 및 설치(권장)


비밀번호 입력 후 지금 설치


확인을 누른다.


설치가 끝난 후 아마 이렇게 뜨면서 부팅이 안될텐데 준비편에서 만든 부팅용 부트로더 VHD 파일을 지우고 백업했던 파일로 교체해준다.


그러면 다시 부팅이 된 후 설치가 완료 되었다고 나오는데 대충 넘기고 로그인 후 제어판에 가서 업데이트를하면 한방에 문제 없이 업데이트가 된다!


사실 새벽 3시 쯤이라서 마무리를 대충하긴 했는데 대부분 아무 문제 없이 설치를 할꺼라고 믿는다.

이제 핵놀로지를 이용해서 하고 싶은 일을 하면 끗!

시작하기 전에...

HP MicroServer N54L 구입자들을 위한 글이긴 한데 다른 PC라도 이 방법대로 설치가 가능하니 참조하셔도됩니다.

PG님 블로그에 있는 글을 어느정도 수정해서 쓴 글이므로 자세한 정보는 직접 가서 보는것도 좋습니다.

아마 일부 글은 PG님 블로그 링크를 걸어서 넘어갈 예정입니다.

Windows Server 및 Hyper-V 설치는 알아서 하세요!


XPENOLOGY를 HYPER-V에 설치하기 A-Z 1.준비편

http://blog.mystor.net/archives/540

XPENOLOGY를 HYPER-V에 설치하기 A-Z 2.설치편

http://blog.mystor.net/archives/547

XPENOLOGY를 HYPER-V에 설치하기 A-Z 3.DSM 업데이트편

http://blog.mystor.net/archives/574

----------------------------------------------------------------------------------------------------------

일단 Synology NAS 제품이 아닌 다른 기기에 Synology[각주:1]를 설치하기 위해서는 해킨처럼 부트로더가 필요합니다.

현재 나와있는 부트로더는 두가지인데 - Nanoboot, Gnoboot

이 글에서는 현재 DSM 5.0-4493 Update 2까지 한방에 업데이트가 가능한 Nanoboot 설치를 하도록 하겠습니다.


먼저 사이트로 가서 Nanoboot-5.0.3.1 DSM 5.0-4493 X64 img 이미지를 받습니다.

http://www.xpenology.nl/boot-images/


그 다음에는 img 이미지를 수정에 필요한 winimage 프로그램을 받습니다.

http://www.winimage.com/download.htm


winimage 실행 후 img 파일을 열어보시면 밑에 스샷처럼 나옵니다.


boot\grub 이동 후 보시면 여러개 파일이 있는데 스샷처럼 저 파일 3개를 꺼내옵니다. (드래그하면됨)


menu_debug.lst, menu_install.lst, menu_nano.lst 파일을 모두 열어서 sn=XXXXXXXXX 어쩌고 적힌걸 모두

sn=8CKIN00001 <- 이걸로 바꿔줍니다.

그리고 다시 드래그해서 이번에는 반대로 덮어씌우기를 하시고 저장을 누르시면 끝납니다.


이제 img 파일을 VHD 파일로 변환을 해줘야하는데 PG님 블로그 글을 참조하면 됩니다.

IMG를 VHD로 변환하기 포스트 링크


Windows Server 2012 R2 Hyper-V에다가 XPEnology 설치하기 - 설치 & 업데이트편

  1. 핵놀로지 (XPEnology) [본문으로]

+ Recent posts