フォントの変更

テキスト描画の大事な要素にフォントがあります。
フォントは文字の形、デザインのことですが、太字や斜体などの変形や文字サイズなどもフォントの設定で行います。
今度はフォントを別のものに変更してみましょう。

GetStockObject関数

フォントの設定の前に、GetStockObject関数について説明します。

ウィンドウアプリでは、画面に文字や図形などを描画することでプログラムを作ります。
文字を描画するにはフォントを使用しますし、図形を描く場合はペンやブラシを使用します。
このうち、よく使用されるものはシステムがあらかじめ定義しており、これをストックオブジェクト(組み込みオブジェクト、定義済みオブジェクト)と言います。
GetStockObject関数はこのストックオブジェクトを取得する関数です。

HGDIOBJ GetStockObject(
 int i
);
ストックオブジェクトのハンドルを取得する。
(ペン、ブラシ、フォント、パレット)
失敗した場合はNULLを返す。

引数はストックオブジェクトを表す定数です。
ストックオブジェクトはフォント以外にも色々ありますが、フォントの場合は以下の定数が用意されています。

定数 説明
OEM_FIXED_FONT OEM固定幅フォント。
ANSI_FIXED_FONT 固定幅システムフォント
ANSI_VAR_FONT 可変幅システムフォント
SYSTEM_FONT システムフォント。
メニューやダイアログ、テキスト描画などで使用されるデフォルトのフォント。
DEVICE_DEFAULT_FONT デバイス依存フォント。
DEFAULT_GUI_FONT ユーザーインターフェイス用のデフォルトフォント。
SYSTEM_FIXED_FONT 固定幅システムフォント。
Windows3.0以前との互換性のために存在する。

SelectObject関数

GetStockObject関数で取得したストックオブジェクトのハンドル(HGDIOBJ型)は、SelectObject関数でデバイスコンテキストに設定します。

HGDIOBJ SelectObject(
 HDC hdc,
 HGDIOBJ h
);
デバイスコンテキストhdcにストックオブジェクトhを設定する。
(ペン、ブラシ、フォント、パレット)
成功した場合は置き換え前のオブジェクトのハンドルを返す。
失敗した場合はNULLを返す。
hがリージョンの場合は成功した場合にSIMPLEREGION、COMPLEXREGION、NULLREGIONのいずれかを返し、失敗した場合にHGDI_ERRORを返す。

なんだかややこしそうな感じがしますが、使い方自体は簡単です。


#include <windows.h>

//ウィンドウの生成等は省略

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;

	static const WCHAR* str = L"This is a pen.\nあいうえお";

	switch (message)
	{
	case WM_PAINT: //ウィンドウの描画
		GetClientRect(hWnd, &rect);

		hdc = BeginPaint(hWnd, &ps);

		//何も設定しない場合
		rect.top += DrawText(hdc, str, -1, &rect, DT_WORDBREAK);

		//フォントのストックオブジェクトを取得して設定
		HGDIOBJ obj = GetStockObject(ANSI_FIXED_FONT);
		SelectObject(hdc, obj);
		rect.top += DrawText(hdc, str, -1, &rect, DT_WORDBREAK);

		EndPaint(hWnd, &ps);
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

同じ文字列を出力していますが、二つめの出力はフォントを固定幅フォントに変更しています。
フォントの変更1

テキストの色をSetTextColor関数でデバイスコンテキストに設定したように、テキストのフォントをSelectObject関数で設定するわけです。

CreateFont関数

ストックオブジェクトのフォントは簡単に使用できますが、使い勝手はあまりよくありません。
フォントの種類やサイズなどを詳細に設定するにはCreateFont関数を使用します。
この関数は論理フォントを作成します。

論理フォントとは「論理上のフォント」のことで、実際に描画される文字の形(図形としての形状)は持っていません。
「こういう特徴を持ったフォント」という、単なる情報の集まりです。
これに対して物理フォントというものがあり、こちらは実際の文字の形を持っています。

論理フォントをSelectObject関数に渡すと、その情報に最も近い物理フォントをデバイスコンテキストに設定することができます。

HFONT CreateFontW(
 int cHeight,
 int cWidth,
 int cEscapement,
 int cOrientation,
 int cWeight,
 DWORD bItalic,
 DWORD bUnderline,
 DWORD bStrikeOut,
 DWORD iCharSet,
 DWORD iOutPrecision,
 DWORD iClipPrecision,
 DWORD iQuality,
 DWORD iPitchAndFamily,
 LPCWSTR pszFaceName
);
論理フォントを作成する。

この関数は引数が非常に多いですが、いくつかの引数はほぼ毎回同じ設定で使うことができます。
お勧めの設定もあわせて紹介しておきます。

cHeight

cHeightメンバは、文字セルの高さです。
0を指定すると既定値が使用されます。
負数を指定すると、絶対値(マイナス符号を取り去った値)に変換した上で、文字セルの高さから内部レディングを引いた高さとして設定されます。
内部レディングとは、アルファベットのアクセント記号に使用する高さのことです。

文字を既定のサイズで使用する場合は0を、任意のサイズに設定したい場合は正数を指定します。

cWidth

cWidthメンバは、平均の文字幅です。
0を指定すると高さから自動的に最適な値が設定されます。

通常は0を指定します。
文字幅を変更したい場合は任意の値を指定します。

cEscapement

cEscapementメンバは、文字全体の角度です。
単位は1/10度です。

WindowsNT/2000以降のGM_ADVANCEDグラフィックモードでは、次に説明するcOrientationと個別に設定できます。
それ以外のモードではcEscapementcOrientationは同じ値に設定してください。

通常は0を指定します。

cOrientation

cOrientationも文字の角度です。
WindowsNT/2000以降のGM_ADVANCEDグラフィックモードではcEscapementが文字全体の角度を、cOrientationが文字個別の角度を設定します。
それ以外のモードでは両方に同じ値を設定してください。
(文字個別の角度の設定はありません。)

通常は0を指定します。

cWeight

cWeightメンバは、文字の太さです。
0~1000までの数値を指定できるほか、定数を指定することもできます。
0を指定すると既定値が使用されます。

定数
FW_DONTCARE 0
FW_THIN 100
FW_EXTRALIGHT
FW_ULTRALIGHT
200
FW_LIGHT 300
FW_NORMAL
FW_REGULAR
400
FW_MEDIUM 500
FW_SEMIBOLD
FW_DEMIBOLD
600
FW_BOLD 700
FW_EXTRABOLD
FW_ULTRABOLD
800
FW_HEAVY
FW_BLACK
900

たくさん定数が用意されていますが、実際に使用できるのは標準の太さと太字のみです。
つまりFW_DONTCAREFW_NORMALまたはFW_REGULARFW_BOLD以外の値は使用しません。
(使用しても標準の太さか太字になる以外の変化はありません)

bItalic

bItalicメンバは、斜体の設定です。
TRUEを設定すると文字が斜体で表示されます。

bUnderline

bUnderlineメンバは、アンダーライン(下線)の設定です。
TRUEを設定すると文字の下にアンダーラインが引かれます。

bStrikeOut

bStrikeOutメンバは、打ち消し線の設定です。
TRUEを設定すると文字に打ち消し線が引かれます。

iCharSet

iCharSetメンバは、フォントの文字セットです。
以下の定数を指定します。

定数 説明
ANSI_CHARSET ANSI文字セット
DEFAULT_CHARSET デフォルト文字セット
SYMBOL_CHARSET シンボル文字セット
SHIFTJIS_CHARSET Shift_JIS文字セット(日本語)
HANGEUL_CHARSET 韓国語文字セット
GB2312_CHARSET 中国語簡体字文字セット
CHINESEBIG5_CHARSET 中国語繁体字文字セット
OEM_CHARSET OEM文字セット(OS依存)
JOHAB_CHARSET 韓国版Windows文字セット
HEBREW_CHARSET 中東版Windows文字セット
ARABIC_CHARSET 中東版Windows文字セット
GREEK_CHARSET ギリシア語文字セット
TURKISH_CHARSET トルコ語文字セット
VIETNAMESE_CHARSET ベトナム語文字セット
THAI_CHARSET タイ語文字セット
EASTEUROPE_CHARSET 東ヨーロッパ文字セット
RUSSIAN_CHARSET ロシア語文字セット
MAC_CHARSET Macintosh用文字セット
BALTIC_CHARSET バルチック文字セット

日本語環境ならSHIFTJIS_CHARSETを指定しておけば問題ありません。

iOutPrecision

iOutPrecisionメンバは、出力の精度です。
この値は「精度」となっていますが、プログラマが要求するフォント設定に対してどのフォントフォーマットを使用するかの設定です。
以下の定数を指定します。

定数 説明
OUT_DEFAULT_PRECIS 既定の動作に任せる。
OUT_STRING_PRECIS 使用しない。
ただしラスタフォントが列挙される時はこの値が返される。
OUT_CHARACTER_PRECIS 使用しない。
OUT_STROKE_PRECIS 使用しない。
ただしTrueTypeフォントやその他のアウトラインベースのフォント、ベクタフォントが列挙されるときにはこの値が返される。
Win95/98系ではベクタフォントを選択するよう指示する。
OUT_TT_PRECIS 同じ名前のフォントが複数存在する場合はTrueTypeフォントを使用するよう指示する。
OUT_DEVICE_PRECIS 同じ名前のフォントが複数存在する場合はデバイスフォントを使用するよう指示する。
OUT_TT_ONLY_PRECIS TrueTypeフォントだけを選択するよう指示する。
システムにTrueTypeフォントが存在しない場合は既定の動作。
OUT_OUTLINE_PRECIS TrueTypeフォントやその他のアウトラインベースのフォントを選択するように指示する。
Win95/98系では使用できない。

フォントの知識がないと意味が分からないと思いますが、ほとんどの場合で定数OUT_DEFAULT_PRECISを指定しておけば大丈夫です。

iClipPrecision

iClipPrecisionメンバは、文字が描画領域からはみ出している場合に、はみ出した部分を消去する方法の設定です。
以下の定数を指定します。

定数 説明
CLIP_DEFAULT_PRECIS 既定の動作に任せる。
CLIP_CHARACTER_PRECIS 使用しない。
CLIP_STROKE_PRECIS 使用しない。
ただしラスタフォント、ベクタフォント、TrueTypeフォントが列挙されるときにはこの値が返される。
CLIP_MASK 使用しない。
CLIP_LH_ANGLES フォントの回転方向を座標系により決定されるようになる。
このフラグを使用しない場合はデバイスフォントは常に反時計回りに回転し、その他のフォントは座標系の向きに従って回転する。
CLIP_TT_ALWAYS 使用しない。
CLIP_EMBEDDED 読み取り専用の埋め込みフォントを使用する場合はこのフラグを指定する。

これもほとんどの場合でCLIP_DEFAULT_PRECISを指定します。

iQuality

iQualityメンバは、文字の出力品質の設定です。
論理フォントで設定した属性と使用される物理フォントの属性とを、どの程度一致させるかが決定されます。
以下の定数を指定します。

定数 説明
DEFAULT_QUALITY 品質は重視されない。
DRAFT_QUALITY PROOF_QUALITYほどは品質は重視されない。
PROOF_QUALITY 品質は論理フォントの属性と一致させることよりも重視される。
NONANTIALIASED_QUALITY アンチエイリアス処理を行わない。
ANTIALIASED_QUALITY アンチエイリアス処理が可能であり、その必要があれば行う。
CLEARTYPE_QUALITY ClearTypeアンチエイリアス処理を行う。

通常はDEFAULT_QUALITYを指定します。
文字の品質が重視される場合はDRAFT_QUALITYPROOF_QUALITYを使用します。

なお、DEFAULT_QUALITYDRAFT_QUALITYPROOF_QUALITYは、アンチエイリアス処理を行うか否かはシステムの設定に従います。
(コントロールパネルから設定のオン/オフができる)

iPitchAndFamily

iPitchAndFamilyメンバは、文字の幅(ピッチ)とフォントファミリの設定です。
この引数は以下の二つをビット演算の論理和(a | b)で指定します。

1、ピッチ
固定幅/可変幅のことで、文字の種類によって文字幅を適宜変えるか否かの設定です。
例えば「A」という文字と「I」という文字では文字自体の幅が違います。
固定幅はこれに関係なく常に一定の幅を使用します。
(等幅フォントとも呼ばれます)
可変幅は文字幅に合わせて使用する幅を変化させます。
(プロポーショナルフォントと呼ばれます)
一般的に可変幅のほうが読みやすくなりますが、文字の正確な入力が必要な場合などは固定幅のほうが都合がいいこともあります。

2、フォントファミリ
ある基本となる文字の形状のバリエーションをひとまとめにしたものです。
例えばある文字の太字バージョンや斜体バージョンなどを用意しておき、適宜切り替えることで見た目を変更することができます。
この値は指定したフォントが利用できないときの候補として使用されます。

文字幅は以下の定数を指定します。

定数 説明
DEFAULT_PITCH 既定の動作に任せる。
FIXED_PITCH 固定幅
VARIABLE_PITCH 可変幅

フォントファミリは以下の定数を指定します。

定数 説明
FF_DONTCARE デフォルトのフォントを使用する。
FF_ROMAN 可変幅セリフ体(ローマン体)
FF_SWISS 可変幅サンセリフ体
FF_MODERN 固定幅セリフ体/サンセリフ体
FF_SCRIPT 筆記体
FF_DECORATIVE 装飾付きフォント

pszFaceName

pszFaceNameメンバは、フォントの名前を文字列で指定します。
フォント名は終端のNULL文字を含めて32文字以内で指定します。
NULLや空文字を指定すると、これ以外の引数で指定した条件に最初に合致するフォントが選択されます。

なお、使用可能なフォント名はEnumFontFamiliesEx関数で列挙することができます。
ただし使い方が複雑なためここでは説明しません。

DeleteObject関数

CreateFont関数はHFONT型のデータを返します。
これはGDIオブジェクトというものの一種で、SelectObject関数に渡すことでデバイスコンテキストに情報を反映します。

GetStockObject関数で取得したストックオブジェクトの場合は使いっぱなしで良いのですが、CreateFont関数などで自作したGDIオブジェクトは不要になったらDeleteObject関数で削除する必要があります。

BOOL DeleteObject(
 HGDIOBJ ho
);
GDIオブジェクトを削除する。
(ペン、ブラシ、フォント、ビットマップ、リージョン、パレット)
削除後はハンドルは無効になる。

削除したオブジェクトはそれ以降は使用できなくなる(無効なハンドルになる)ので注意してください。
また、デバイスコンテキストに設定中のGDIオブジェクトは削除してはならないという点に注意してください。

なお、HGDIOBJ型はGDIオブジェクトハンドル全般を表す型で、HFONT型はその中のフォントを表す型です。
内部的には同じものなのでそのまま使用できます。
(気になる場合はキャストしてください)

サンプルコード

説明が非常に長くなりましたが、フォントを作成するサンプルコードです。


#include <windows.h>

//ウィンドウの生成等は省略

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;
	HFONT font;
	HGDIOBJ hgdi;

	static const WCHAR* str = L"This is a pen.あいうえお。";

	switch (message)
	{
	case WM_PAINT: //ウィンドウの描画
		GetClientRect(hWnd, &rect);
		hdc = BeginPaint(hWnd, &ps);

		//何も設定しない状態で出力してみる
		rect.top += DrawText(hdc, str, -1, &rect, DT_WORDBREAK);

		font = CreateFont(
			18, 0,					//高さ, 幅
			0, 0,					//角度1, 角度2
			FW_DONTCARE,			//太さ
			FALSE, FALSE, FALSE,	//斜体, 下線, 打消し線
			SHIFTJIS_CHARSET,		//文字セット
			OUT_DEFAULT_PRECIS,		//精度
			CLIP_DEFAULT_PRECIS,	//精度
			DEFAULT_QUALITY,		//品質
			DEFAULT_PITCH | FF_DONTCARE, //ピッチとファミリ
			L"メイリオ");			//フォント名
		hgdi = SelectObject(hdc, font);
		rect.top += DrawText(hdc, str, -1, &rect, DT_WORDBREAK);
		
		SelectObject(hdc, hgdi); //フォントを元に戻す
		DeleteObject(font);	//オブジェクト削除

		font = CreateFont(
			20, 0,					//高さ, 幅
			0, 0,					//角度1, 角度2
			FW_DONTCARE,			//太さ
			FALSE, FALSE, FALSE,	//斜体, 下線, 打消し線
			SHIFTJIS_CHARSET,		//文字セット
			OUT_DEFAULT_PRECIS,		//精度
			CLIP_DEFAULT_PRECIS,	//精度
			DEFAULT_QUALITY,		//品質
			DEFAULT_PITCH | FF_DONTCARE, //ピッチとファミリ
			L"HG行書体");			//フォント名
		hgdi = SelectObject(hdc, font);
		rect.top += DrawText(hdc, str, -1, &rect, DT_WORDBREAK);

		SelectObject(hdc, hgdi); //フォントを元に戻す
		DeleteObject(font);	//オブジェクト削除

		EndPaint(hWnd, &ps);
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

実行結果は以下のようになります。
フォントの変更1

作成したフォントをDeleteObject関数で削除する前に、以前のフォントに戻している点に注目してください。
SelectObject関数は置き換え前に選択されていたGDIオブジェクトを返すので、これを変数に保存しておき、DeleteObject関数実行の直前にこれに戻しておきます。

フォントを変更する場合にCreateFont関数の引数を毎回書くのは面倒なので、いくつかの設定は共通にして、自作関数でまとめてしまうと便利です。
以下はその例です。


#include <windows.h>

//関数プロトタイプ宣言
ATOM MyRegisterClass(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HFONT MyCreateFont(int, LPCWSTR, BOOL);

//ウィンドウの生成等は省略

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;
	HFONT font;
	HGDIOBJ hgdi;

	static const WCHAR* str = L"This is a pen.あいうえお。";

	switch (message)
	{
	case WM_PAINT: //ウィンドウの描画
		GetClientRect(hWnd, &rect);
		hdc = BeginPaint(hWnd, &ps);

		font = MyCreateFont(18, L"メイリオ", FALSE);
		hgdi = SelectObject(hdc, font);
		rect.top += DrawText(hdc, str, -1, &rect, DT_WORDBREAK);
		SelectObject(hdc, hgdi); //フォントを元に戻す
		DeleteObject(font);	//オブジェクト削除

		font = MyCreateFont(20, L"メイリオ", TRUE);
		hgdi = SelectObject(hdc, font);
		rect.top += DrawText(hdc, str, -1, &rect, DT_WORDBREAK);
		SelectObject(hdc, hgdi); //フォントを元に戻す
		DeleteObject(font);	//オブジェクト削除

		EndPaint(hWnd, &ps);
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

//フォントを作成する
//height	= フォントサイズ
//name		= フォント名
//bold		= 太字か否か
//戻り値		= HFONTオブジェクト
HFONT MyCreateFont(long size, LPCWSTR name, BOOL bold)
{
	return CreateFont(
		size, 0,				//高さ, 幅
		0, 0,					//角度1, 角度2
		bold ? FW_BOLD : FW_DONTCARE,	//太さ
		FALSE, FALSE, FALSE,	//斜体, 下線, 打消し線
		SHIFTJIS_CHARSET,		//文字セット
		OUT_DEFAULT_PRECIS,		//精度
		CLIP_DEFAULT_PRECIS,	//精度
		DEFAULT_QUALITY,		//品質
		DEFAULT_PITCH | FF_DONTCARE, //ピッチとファミリ
		name);
}

実行結果は先ほどのサンプルコードと同じです。

論理フォントはあくまでもシステムに要求するフォントの設定です。
希望通りの物理フォントが得られるとは限りません。


//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;
	HFONT font;
	HGDIOBJ hgdi;

	static const WCHAR* str = L"This is a pen.あいうえお。";

	switch (message)
	{
	case WM_PAINT: //ウィンドウの描画
		GetClientRect(hWnd, &rect);
		hdc = BeginPaint(hWnd, &ps);

		font = MyCreateFont(18, L"HG行書体", FALSE);
		hgdi = SelectObject(hdc, font);
		rect.top += DrawText(hdc, str, -1, &rect, DT_WORDBREAK);
		SelectObject(hdc, hgdi);
		DeleteObject(font);	//オブジェクト削除

		font = MyCreateFont(19, L"HG行書体", FALSE);
		hgdi = SelectObject(hdc, font);
		rect.top += DrawText(hdc, str, -1, &rect, DT_WORDBREAK);
		SelectObject(hdc, hgdi);
		DeleteObject(font);	//オブジェクト削除

		font = MyCreateFont(20, L"HG行書体", FALSE);
		hgdi = SelectObject(hdc, font);
		rect.top += DrawText(hdc, str, -1, &rect, DT_WORDBREAK);
		SelectObject(hdc, hgdi);
		DeleteObject(font);	//オブジェクト削除

		font = MyCreateFont(21, L"HG行書体", FALSE);
		hgdi = SelectObject(hdc, font);
		rect.top += DrawText(hdc, str, -1, &rect, DT_WORDBREAK);
		SelectObject(hdc, hgdi);
		DeleteObject(font);	//オブジェクト削除

		EndPaint(hWnd, &ps);
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

フォントサイズを18~21まで1ずつ変化させただけのコードです。
実行してみると二番目と三番目で変化がありません。
これは指定通りのフォントが存在せず、最も近いもので代用されているためです。
フォントの変更2

CreateFontIndirect関数

論理フォントの作成にはCreateFont関数のほかCreateFontIndirect関数を使用することもできます。

HFONT CreateFontIndirect(
 const LOGFONT *lplf
);
論理フォントを作成する。

引数のLOGFONTは構造体で、メンバは以下のようになっています。

typedef struct tagLOGFONTW {
 LONG lfHeight;
 LONG lfWidth;
 LONG lfEscapement;
 LONG lfOrientation;
 LONG lfWeight;
 BYTE lfItalic;
 BYTE lfUnderline;
 BYTE lfStrikeOut;
 BYTE lfCharSet;
 BYTE lfOutPrecision;
 BYTE lfClipPrecision;
 BYTE lfQuality;
 BYTE lfPitchAndFamily;
 WCHAR lfFaceName[LF_FACESIZE];
} LOGFONTW, *PLOGFONTW, *NPLOGFONTW, *LPLOGFONTW;
論理フォントの情報を持つ構造体。

メンバの数も種類もCreateFont関数の引数と全く同じで、値を引数として指定するか構造体メンバとして指定するかの違いです。