주인장 공지입니다. Lu's…〃 Diary。

반갑습니다. 루사인이라고 합니다.


1. 책 리뷰 & 프로그래밍 일기 블로그입니다.

2. 가끔 성과 글도 올라와요.

3. @Lusain_Kim 에 상주 중.



환영합니다.

[Direct2D/DWrite]설치되지 않은 폰트 설치하지 않고 사용하기 Lu's…〃 Programing。

AddFontMemResourceEx DWrite CreateTextFormat 등에서 적용되지 않는다.

 

DWrite CreateTextFormat 함수는 번째 인자로 fontCollection 받는데, 값이 nullptr이면 시스템 폰트에서 번째 인자로 주는 fontFamilyName 검색한다.

 

DWrite에서 시스템 폰트 이외의 폰트를 사용하는 방법은 폰트 컬렉션을 만드는 방법이다.

 

폰트 컬렉션을 만드는 방법은 크게 가지로 나눌 있는데, 하나는 Windows 10 이상의 환경에서만 사용 가능한 방법과 이전의 버전에서 사용 가능한 방법이다.

 

 

 

  1. Windows 10 이전 버전 호환을 위한 방법

 

Windows 7/8.1에서 사용되는 방법은 Windows 7 Windows SDKSample에 있는데… 지금 찾아보니 페이지가 중국 MS 기술문서 말고는 누가 Github 아카이빙한 말고는 찾겠더라.

 

https://github.com/pauldotknopf/WindowsSDK7-Samples/tree/master/multimedia/DirectWrite/CustomFont

 

예제는 폰트 파일에서 불러오는 아니라 .rc 파일에 있는 폰트를 불러오는 시나리오다. 만약 폰트를 파일에서 불러오고 싶으면 CreateFontFileReference 함수를 써서 reference key 알아낸 이를 사용하여 CreateCustomFontFileReference함수를 호출하여 custom file file reference 만드는 방법이 있다. 만약 rc 파일을 사용한다면 과정은 생략해도 된다.

 

 간단한 스텝에 대해 설명하자면,

 

  1. ResourceFontContext 객체를 선언한다.
     
  2. CreateFontCollection 함수를 호출한다.
    번째 인자는 Font Collection Key이고 번째 인자는 key size이다.

    여기서 알아둘 점은, key 리소스 ID 또는 CustomFontFileReference에서 설정한 ID 값이다.
     
  3. 번째 인자로 넘긴 fontCollection 객체를 활용하여 폰트를 만든다.

 

 

 

방법이 귀찮고 어려운 이유는, CustomFontCollection만들기 위해 필요한 IDWriteFontCollectionLoader 객체를 결국 사용자가 직접 구현을 줘야 한다는 점이다.

 

이러한 문제는 Windows 10 되면서 매우 간단하게 해결이 되었다.

 

 

 

  1. Windows 10 이상의 버전에서 사용하는 방법

 

말도 필요 없을 정도로 간단해졌다.

 

Windows 10부터 추가된 IDWriteFactory3 객체에FontSet 관련 API 들어가서, 더는FontCollectionLoader 객체를 직접 만들어줄 필요가 없어졌다.

 

사실 버전의 폰트 추가 방법에 대해서는 MS 공식문서가 되어 있어서 굳이 포스팅할 필요는 없지만, 간단히 소개해보자 한다.

 

우선 공식 문서 : https://docs.microsoft.com/en-us/windows/desktop/directwrite/custom-font-sets-win10

 

이번에는 리소스에 저장된 폰트는 설명을 생략한다. 공식 문서의 InMemoryFont 참고.

https://docs.microsoft.com/en-us/windows/desktop/directwrite/custom-font-sets-win10#creating-a-custom-font-set-using-font-data-loaded-into-memory

 

 

그리고 포스트에서 설명하는 IDWriteFactory5 객체에 대해서이다. IDWriteFactory3 객체에 대해서는 공식 문서를 참고하기 바란다.

 

가지 필요한 클래스들을 열거해보겠다.


IDWriteFontFile

FontFileReference 얻기 위해 필요한 객체

IDWriteFontSetBuilder1

FontFile 추가하여 FontSet 만드는 Builder 객체

IDWriteFontSet

FontCollection 바꿔먹을

 

최종적으로 FontCollection 만들어지면 개의 객체는 유지되지 않아도 괜찮다. , InMemoryFont 위한 IDWriteInMemoryFontFileLoader 객체의 경우에는 따로 가지고 있다가 객체 소멸 UnregisterFontFileLoader 호출해야한다.

 

 

 

아까 Windows 7/8.1과는 다르게 정말 편해졌다.

 

  ComPtr<IDWriteFontFile> fontFileReference;

 dwFactory->CreateFontFileReference(L"MyFont.ttf", nullptr, &fontFileReference);
 
 ComPtr<IDWriteFontSetBuilder1> fontSetBuilder;
  dwFactory->CreateFontSetBuilder(&fontSetBuilder);
 
  fontSetBuilder->AddFontFile(fontFileReference.Get());
 
 ComPtr<IDWriteFontSet> customFontSet;
  fontSetBuilder->CreateFontSet(&customFontSet);
 
  dwFactory->CreateFontCollectionFromFontSet(

       customFontSet.Get()

    , &m_pdwFontCollection

    );

 

 

 

+) 추가

 

이렇게 추가한 폰트의 이름을 모를 있다. 예를 들어 "TFont"인줄 알았는데 "Test Font"라고 입력해야만 해당 폰트를 찾을 수도 있고. 이럴 때를 위한 폰트 이름을 찾는 방법이다.

 

여기서 폰트이름을 TCHAR[65] 설정했는데, 사실 이건 내가 몰라서 이렇게 지은 것이다. 수도 있다. 적절히 정하자.

 

원리는 단순하다. 아까 만든 FontCollection에서 FontFamily 얻어내 이름들을 얻어올 것이다.

 

  ComPtr<IDWriteFontFamily> fontFamily;
 ComPtr<IDWriteLocalizedStrings> localizedFontName;
 TCHAR c_styleFontName[65];

  m_pdwFontCollection->GetFontFamily(0, &fontFamily);
  fontFamily->GetFamilyNames(&localizedFontName);
  localizedFontName->GetString(0, c_styleFontName, 65);

 

만약 여러 개의 폰트를 FontCollection 추가했다면, FontCollection GetCount 등의 함수가 있으니 적절히 참고하자.

 


[winapi] 설치되지 않은 폰트 설치하지 않고 사용하기 미분류

 CreateFont 기본적으로 시스템에 설치된 폰트를 찾는다. 하지만 사용자가 무슨 폰트를 설치했는지도 모르고, 그렇다고 함부로 사용자 컴퓨터에 ' 폰트 설치해주세요 ㅎㅎ' 할 수 없다. 당장 대부분의 프로그램에서 사용자가 설치하지 않은 폰트로 프로그램에 글자를 적는다. 어떻게?

 

 당연히 MS에서는 이에 대한 방법을 모두 구현해 두셨고, 포스트를 찾는 사람이라면 방법을 찾았거나 너무 어려워서 간단한 샘플을 원하리라 생각한다.

 

일단 windows GDI부터 확인해보자.

 

GDI에서는 관련된 함수가 개다.

 

AddFontMemResourceEx

 

RemoveFontMemResourceEx

 이름도 정말 정직하다. 메모리 영역에 폰트를 추가한다는 뜻이고, 프로그램이 종료되면 폰트는 사용할 없게 된다. 해제 역시 하면 좋지만 하지 않더라도 프로그램이 종료하며 동시에 해제된다.

 

사용 방법은 간단한 편이다. AddFontMemResourceEx 인자는 4개인데, 다음과 같다.

 

HANDLE AddFontMemResourceEx(

PVOID pFileView,

DWORD cjSize,

PVOID pvResrved,

DWORD *pNumFonts

);

 

출처:<https://docs.microsoft.com/ko-kr/windows/desktop/api/wingdi/nf-wingdi-addfontmemresourceex>

 

자세한 인자 설명은 출처를 참고하면 된다. 간단한 설명방법만 알려주자면, 준비물은 하나이다. 폰트파일.

 

 폰트파일의 데이터를 통째로 읽어 들이고, 크기를 알아야한다. 그것이 pFileView cjSize이다. pvResrved 시스템 예약으로 무조건 0(NULL)값을 줘야 하고, pNumFonts는 현재 폰트가 설치된 개수를 반환하는 포인터 값이다.

 

// v : 폰트 데이터를 담은 std::verctor

auto  date = reinterpret_cast<char*>(v.data());

auto  size = static_cast<DWORD>(v.size());

HANDLE out = AddFontMemResourceEx(data, size, 0, &nInstalled);

 

 이렇게 폰트를 설치하면 후는 자유롭게 사용이 가능하다.




문제는 DirectX 계열에서는 이렇게 해도 폰트를 찾지 못한다. 이 부분은 다음에 다루는 것으로.


[C++] 표준 라이브러리를 사용하여 숫자 세 자리마다 쉼표 붙이기 Lu's…〃 Programing。

  기능을 숫자 형식(numeric format)이라고 하겠다

 출처는 MSDN | https://msdn.microsoft.com/ko-kr/goglobal/bb688127.aspx

 

 매우 많은 목적으로, 숫자를 표기할 자리마다 쉼표를 붙인다. MSDN에서는 숫자 형식이라고 하던데, 표준 명칭인지는 모르겠다. 일단 문서는 해당 단어를 사용한다.

 

우선 숫자 형식에 대해 알아보자.

한국에서는 당연하게 정수부에 1000 단위로 쉼표를 붙이고 소수점을 표시하기 위해 정수부가 끝나고 마침표를 찍는다. 하지만 독일에서는 반대다! 1000 단위로 마침표를 찍고, 소수점을 표시하기 위해 쉼표를 찍는다.

 

 대체로 프로그래밍을 배우는 초기 단계에서 해당 기능을 구현해봤겠지만 이런 보편적인 기능은 왠지 표준에 있을 같다. 그런 생각을 하고 찾아보니 정말 있더라.

 

 

  

 국가나 문화에 따라 달라지는 것이 있다. 통화 단위, 시간 표시, 그리고 숫자 형식. 외에도 달라지는 것이 있을 있기 때문에 C++ 표준위원회에서는 std::locale이라는 클래스로 포맷들을 캡슐화 두었다. 포맷(통화 단위, 시간 표시, 숫자 형식) facet이라고 부른다. facet locale "설치"하는 식으로 사용할 있다.

 

 어떻게 설치하느냐, 그것은 정말로 간단하다.

 

    // 기본 로케일 생성

    std::locale loc"ko" };

    // facet 설치하여 새로운 로케일을 만든다.

    std::locale new_loclocnew std::num_put<char>{} };

 

  new_loc std::num_put<char> 설치한 로케일이다. 줄로 쓰기 위해 loc new_loc 안에서 생성해도 된다.

 

new_loc 사용하기 위해서는 std::streambuf 연결할 필요가 있다. 어떻게 하느냐, imbue 함수를 사용한다.


 예를 들어, std::cout 설치한다고 하면 코드는 다음과 같다.

 

    std::locale loc"ko" };
    std::locale new_loclocnew std::num_put<char>{} };
    std::cout.imbue(new_loc);

 

물론, 숫자 형식을 cout 말고 다른 곳에 쓰려는 사람이 많을 것이다. 경우에는 std::ostringstream 이용하자.

 

template<typename T>
std::
string ConvertNumericFormat(std::localelocT numeric)
{
    static_assert(std::is_arithmetic<T>::value, "numeric format is only number.");
 
    using stream_t = std::ostringstream;
    using iter_t = stream_t::_Iter;
 
    stream_t stream;
    stream.imbue(loc);


    std::use_facet<std::num_put<char>>(loc).put(iter_t(stream), stream' 'numeric);
    return stream.str();
}

 



 마지막으로, 성능은 책임지지 않는다. 국제화를 고려하지 않는 경우라면 숫자 형식을 구현하기에는 번잡스럽고 표준 함수로만 구현하고 싶은 경우에나 만하지, 실시간으로 점수가 갱신되어야 하는 게임 같은 곳에 사용하기에는 성능이 허용 가능한지 체크해보는 것이 좋을 것이다.


[WIN32] 멀티바이트가 아닌 인코딩된 문자열을 콘솔에 표시할 때 Lu's…〃 Programing。

최근, char*로 반환되는 문자열의 인코딩이 UTF-8인 경우를 맞딱뜨렸다. 이걸 UTF-8 인코딩 된 파일로 저장해서 읽으면 잘 읽어지는데 콘솔로 출력하니 인코딩이 깨지더라.

std::ios는 imbue라는 locale 설정 함수가 있다. 이를 상속받은 std::ostream, std::cout 등도 동일한 함수가 있고 이를 사용하면 ios에 들어간 데이터를 해당 로케일로 인코딩하여 보여준다. 그런데 이게 안 되더라!

  char* recv_msg; // char*로 반환된 UTF-8 문자

  std::cout.imbue(std::locale{ "ko_KR.UTF-8" }); // 한글 UTF-8 인코딩
  std::cout << recv_msg << std::endl;

이런 식으로 코딩하면 안 나온다!



 한참만에 이리 뒤지고 저리 굴러서 정답을 알아냈는데, 일단 어떻게 굴렀는지를 얘기해본다. 참고로 다 실패했다.

1. recv_msg를 wchar_t, char16_t, char32_t 등으로 형변환하여 읽어 봄
2. imbue 말고 setlocale 사용
3. codecvt_utf8 등을 이용하여 utf-8 문자로 강제 변환

이렇게 해도 안 되던 걸 어느 사이트에서 해답을 찾았는데, Win32 함수이다.

  SetConsoleOutputCP(CP_UTF8);

함수를 호출하니 잘 되더라. 이 함수는 Console Output의 코드페이지(CP)를 설정하는 함수인데, char*라지만 이미 UTF-8 인코딩이 된 문자열이었기 때문에 Console Output의 CP만 바꾸면 되지 않았나 싶다.

 정확히는 아직도 모르겠다. 정답을 아시는 분은 부디 댓글로 남겨주세요...

1 2 3 4 5 6 7 8 9 10 다음