作成日: 2003/03/16

Windows XP での全角固定ピッチフォントの仕様変更について

概要

Windows 上でフォントを作成する際には、作成したいフォントの情報(高さや幅、太さなど)を API に渡すのですが、 フォントの平均文字幅には 0 を指定しておき、条件に最も近い値を選択してもらうようにしていることが多いと思います。 ところが、Windows XP では全角固定ピッチフォント("MS ゴシック" や "MS 明朝" といったフォント) の文字幅を選択する 内部仕様が変わってしまったため、特定のフォントサイズにおいては、これまでの Windows と Windows XP では画面上の表示が 違ってしまうようになりました。これは MS のサポート技術情報 417434 でも公開されており、対応方法も一応付記されています。 なお、以下の説明では VC++ 6 を使用しています。

フォントの大きさについて

一般的な Windows のアプリケーションでは、ユーザに選択させるフォントサイズの単位としてポイントを使っています。 ポイントは活版印刷からきた単位で、1 ポイント ≒ 1/72 インチ です。特に Windows 上では 1 ポイント = 1/72 インチ だと考えてかまいません。
しかし、アプリケーションから利用するフォント関連の API は、フォントサイズの単位として論理単位を使います。 このため、アプリケーションプログラムがユーザの指定したポイント数のフォントを扱うときには、 ポイント数を論理単位に変換してから、フォント関連の API に渡さなければなりません。
以下では、描画モードが MM_TEXT マッピングモードの場合のみ説明します。 MM_TEXT マッピングモードは Windows のデフォルトのマッピングモードで、論理単位 = ピクセル数となります。 とりあえず、以下の説明の中では、論理単位という言葉がでてきたら、ピクセル数のことだと考えてください。
いきなりですが、ここで画面解像度(dpi: dot per inch)の話をしておきます。 画面解像度は Windows が画面を表示する際に 1 インチを何ドット(=ピクセル)で表示するかを表した数値です。 画面のプロパティなどでも変更できますが、Windows をインストールしたデフォルトのままだと、 96 dpi (1 インチあたり 96 ピクセル) が一般的です。
では、例えば、画面解像度が 96 dpi の時に、12 ポイントのフォントが欲しいとすると、 フォント関連の API に渡す論理単位(=ピクセル数)はいくつになるでしょうか。 1 ポイントは 1/72 インチですので、12 ポイントだと 1/72 × 12 = 12/72 インチになります。 これをピクセルに直すと、96 dpi の解像度では 1 インチにつき 96 ピクセルで表しますので、 12/72 インチを表現するには 96 × 12/72 = 1152/72 ピクセル必要となります。 1152/72 = 1152 ÷ 72 = 16 となり、API には 16(ピクセル) を渡せばよいことになります。
なお、この計算にうってつけの API がありますので、これを利用すると便利です。

int MulDiv(
  int nNumber,       // 符号付き 32 ビット被乗数
  int nNumerator,    // 符号付き 32 ビット乗数
  int nDenominator   // 符号付き 32 ビット除数
);

この API は nNumber × nNumerator ÷ nDenominator を計算し、最も近い整数に丸めた(切り上げまたは切り捨てした) 値を返します。フォントのピクセル数を求める場合、MulDiv(ポイント数, 1 インチあたりのピクセル数, 72) として 計算した値を利用できます。
※インチだのポイントだの日本人からすると聞き慣れない単位ですが、Windows は "舶来品" ゆえ 仕方のないところでしょうか (*_*)。

フォント作成のための API

フォントの作成には CreateFont() または CreateFontIndirect() を使用します。この 2 つの API は 引数の指定方法が違うだけで、まったく同じ機能を持ちます。CreateFont() は作成するフォントの情報を引数として 1 つずつ渡します。 CreateFontIndirect() では作成するフォントの情報を LOGFONT 構造体に設定してから、引数として渡します。 なお、作成されるフォントは論理フォントと呼ばれ、実際に画面に表示するフォントと 1 対 1 に対応するわけではありません (詳細は後で説明します)。
以下に CreateFont() の MSDN の記述を抜粋して掲載します(一部変更してあります)。 なお、これら API の詳細な使用方法については MSDN 以外にも、ネットにたくさんの情報が存在しますので探してみてください。

HFONT CreateFont(
  int nHeight,   // フォントの高さ
  int nWidth,   // 平均文字幅
  int nEscapement,   // 文字送り方向の角度
  int nOrientation,   // ベースラインの角度
  int fnWeight,   // フォントの太さ
  DWORD fdwItalic,   // 斜体にするかどうか
  DWORD fdwUnderline,   // 下線を付けるかどうか
  DWORD fdwStrikeOut,   // 取り消し線を付けるかどうか
  DWORD fdwCharSet,   // 文字セットの識別子
  DWORD fdwOutputPrecision,   // 出力精度
  DWORD fdwClipPrecision,   // クリッピング精度
  DWORD fdwQuality,   // 出力品質
  DWORD fdwPitchAndFamily,   // ピッチとファミリ
  LPCTSTR lpszFace   // フォント名
);

関数が成功すると、論理フォントのハンドル(HFONT 型の値)が返されます。失敗時には NULL が返されます。

nHeight
フォントの文字セルまたは文字の高さを論理単位で指定します。
文字の高さとは文字セルの高さから内部レディングの高さを引いたものです。 内部レディングとはアクセント記号などを表示するためのスペースです。 下の図でみると文字セル高さが (a)、文字の高さが (b) の部分となります。

フォントの高さについて

文字セル高さを指定する場合、指定したい値をそのまま渡します。 文字の高さを指定する場合は、指定したい値を負にした値を渡します。 0 を渡すとデフォルト値が使われます。 フォントの大きさについて で説明したサイズを使う場合、 文字の高さとして指定します。そのため、CreateFont() には負の値として渡さなければなりません。 MSDN には以下のように出ています。
nHeight = -MulDiv(PointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);
PointSize に必要なフォントのポイントサイズを渡します。 GetDeviceCaps(hDC, LOGPIXELSY) は画面の垂直方向での論理インチ当たりのピクセル数を得ています。これは、 Y 方向の画面解像度(dpi)を表します。

nWidth
フォントの平均文字幅を論理単位で指定します。0 を指定すると、条件に最も近い値が選択されます。

nEscapement
文字送りの方向と X 軸との角度を 10 分の 1 度単位で指定します。文字送りの方向とは、テキスト行のベースラインの方向のことです。 この値を指定して、文字列を斜めに出力することができます。

nOrientation
各文字のベースラインと X 軸との角度を 10 分の 1 度単位で指定します。 この値を指定して、個々の文字の向きを変えることができます。

※nEscapement と nOrientation を独立して指定できるのは Windows NT 系の OS を使用しており、 かつ、事前に SetGraphicsMode() に GM_ADVANCED を指定して呼び出している場合のみです。 それ以外の場合は、文字送りの方向と文字の向きの両方ともが nEscapement パラメータによって決まりますので nEscapement と nOrientation には同じ値を指定します。

fnWeight
フォントの太さを表す 0 から 1000 までの範囲内の値を指定します。 たとえば、400 を指定すると標準の太さになり、700 を指定すると太字になります。0 を指定すると、既定の太さが選択されます。
便利に利用できる定数が以下のように用意されています。

定数    定数値
FW_DONTCARE0
FW_THIN100
FW_EXTRALIGHT200
FW_ULTRALIGHTFW_EXTRALIGHT と同じ
FW_LIGHT300
FW_NORMAL400
FW_REGULARFW_NORMAL と同じ
FW_MEDIUM500
FW_SEMIBOLD600
FW_DEMIBOLDFW_SEMIBOLD と同じ
FW_BOLD700
FW_EXTRABOLD800
FW_ULTRABOLDFW_EXTRABOLD と同じ
FW_HEAVY900
FW_BLACKFW_HEAVY と同じ

fdwItalic
斜体にするかどうかを指定します。TRUE を指定すると、斜体になります。

fdwUnderline
下線を付けるかどうかを指定します。TRUE を指定すると、下線付きになります。

fdwStrikeOut
取り消し線を付けるかどうかを指定します。TRUE を指定すると、取り消し線が付きます。

fdwCharSet
フォントの文字セットを指定します。次の値が定義されています。

ANSI_CHARSET(Windows 文字セット)
BALTIC_CHARSET
CHINESEBIG5_CHARSET
DEFAULT_CHARSET(指定なし)
EASTEUROPE_CHARSET
GB2312_CHARSET
GREEK_CHARSET
HANGUL_CHARSET
MAC_CHARSET
OEM_CHARSET(OEM 文字セット)
RUSSIAN_CHARSET
SHIFTJIS_CHARSET(シフト JIS 文字セット)
SYMBOL_CHARSET
TURKISH_CHARSET

OEM 文字セット(OEM_CHARSET)の文字は、オペレーティングシステムに依存します。
フォントの作成において確実に一貫性のある結果を得るためには、OEM_CHARSET と DEFAULT_CHARSET は指定しないでください。 パラメータ lpszFace にフォント名を指定する場合は、 必ず fdwCharSet パラメータの値を lpszFace パラメータに指定するフォントの文字セットと 一致させてください。

fdwOutputPrecision
出力精度を指定します。出力精度は、実際の出力が、要求されたフォントの高さ、幅、文字の向き、文字送りの方向、ピッチ、 およびフォント名にどの程度一致していなければならないかを示します。
次の値のいずれかを指定します。

定数     意味
OUT_CHARACTER_PRECIS 使用しません。
OUT_DEFAULT_PRECIS 既定の動作に任せます。
OUT_DEVICE_PRECIS 同じ名前のフォントが複数あった場合は、デバイスフォントを選択するよう指示します。
OUT_OUTLINE_PRECIS Windows NT/2000: TrueType フォントやその他のアウトラインベースのフォントを選択するよう指示します。
OUT_RASTER_PRECIS 同じ名前のフォントが複数あった場合は、ラスタフォントを選択するよう指示します。
OUT_STRING_PRECIS 使用しません(ただし、ラスタフォントが列挙されるときには、この値が返されます)。
OUT_STROKE_PRECIS Windows NT/2000: 使用しません(ただし、TrueType フォントやその他のアウトラインベースのフォント、 ベクタフォントが列挙されるときには、この値が返されます)。
Windows 95/98: ベクタフォントを選択するよう指示します(TrueType フォントやベクタフォントが列挙されるときにも、 この値が返されます)。
OUT_TT_ONLY_PRECIS TrueType フォントだけを選択するよう指示します。 システムに TrueType フォントが組み込まれていないときは、既定の動作になります。
OUT_TT_PRECIS 同じ名前のフォントが複数あった場合は、TrueType フォントを選択するよう指示します。

指定した名前のフォントがオペレーティングシステム上に複数あった場合のフォント選択方法の制御には、 OUT_DEVICE_PRECIS、OUT_RASTER_PRECIS、および OUT_TT_PRECIS を使用できます。 たとえば、Symbol という名前のフォントにラスタ形式と TrueType 形式の両方があった場合、 OUT_TT_PRECIS を指定しておくと、TrueType 形式の方が選択されます。OUT_TT_PRECIS を指定すると、 必ず TrueType フォントが選択されます。 このため、指定したフォント名と同名の TrueType フォントが存在しない場合には、 ほかのフォント名の TrueType フォントで代用されます。

fdwClipPrecision
クリッピング精度を指定します。クリッピング精度とは、文字の一部がクリッピング領域の外にはみ出たときに、 その文字をクリップする方法を定義するものです。
次の値のいずれか、またはその組み合わせを指定できます。

定数     意味
CLIP_DEFAULT_PRECIS 既定の動作に任せます。
CLIP_CHARACTER_PRECIS 使用しません。
CLIP_STROKE_PRECIS 使用しません(ただし、ラスタフォント、ベクタフォント、TrueType フォントが列挙されるときには、この値が返されます)。
Windows NT/2000: 互換性のために、フォントを列挙するときには必ずこの値が返されます。
CLIP_MASK 使用しません。
CLIP_EMBEDDED 読み取り専用の埋め込みフォントを使用するには、このフラグを指定しなければなりません。
CLIP_LH_ANGLES この値を使うと、すべてのフォントの回転方向が座標系の方向(右手座標系か左手座標系か)によって決められるようになります。
この値を指定しない場合は、デバイスフォントは常に反時計回りに、その他のフォントは座標系の方向に従って回転します。
座標系の方向の詳細については、nOrientation パラメータの説明を参照してください。
CLIP_TT_ALWAYS 使用しません。

fdwQuality
出力品質を指定します。出力品質とは、GDI が、論理フォントの属性と実際の物理フォントの属性とを どの程度まで一致させなければならないかを定義するものです。
次の値のいずれを指定します。

定数     意味
ANTIALIASED_QUALITY Windows NT 4.0 以降: アンチエイリアス処理(スムージング)がサポートされているフォントについては、 サイズが小さすぎたり大きすぎる場合には、フォントにアンチエイリアス処理が施されます。
Plus!をインストールした Windows 95 以降: そのフォントでアンチエイリアス処理がサポートされていることに加えて、 ディスプレイが 8 ビット(256 色) 以上表示でき、単一平面デバイスでなくてはなりません。 また、パレットディスプレイであってはならず、複数ディスプレイモニタのセットアップになっていてはなりません。 さらに、DIBSection で使用するよりも前に、 スクリーンデバイスコンテキストでその TrueType フォントを選択しておかなければなりません。 以上の条件が満たされていなければ、アンチエイリアス処理は行われません。
DEFAULT_QUALITY フォントの文字品質は重視されません。
DRAFT_QUALITY フォントの文字品質は、PROOF_QUALITY を使用したときほどは重視されません。 GDI のラスタフォントについては、スケーリングが可能になり、使用可能なフォントサイズが増えますが、 品質はかなり低くなる場合もあります。 必要であれば、太字、斜体、下線付き、取り消し線付きなどのフォントが合成されます。
NONANTIALIASED_QUALITY Plus!をインストールした Windows 95、Windows 98、Windows NT 4.0、および Windows 2000: アンチエイリアス処理は一切行われません。
PROOF_QUALITY フォントの文字品質が、論理フォントの属性を正確に一致させることよりも重視されます。 GDI のラスタフォントについては、スケーリングが可能になり、最もサイズの近いフォントが選択されます。 PROOF_QUALITY を指定した場合、選択されたフォントのサイズは正確には一致しない場合がありますが、 フォントの品質は高く、文字の形が歪むことはありません。 必要であれば、太字、斜体、下線付き、取り消し線付きなどのフォントが合成されます。

ANTIALIASED_QUALITY も NONANTIALIASED_QUALITY も指定しなければ、 ユーザーがコントロールパネルで[スクリーンフォントの縁を滑らかにする]を選択した場合にしか、 フォントのアンチエイリアス処理は行われません。

fdwPitchAndFamily
フォントのピッチとファミリを指定します。下位 2 ビットでフォントのピッチを指定し、 上位 4 ビットでフォントファミリを指定します。

ピッチの指定には次の値のいずれかを使います。

DEFAULT_PITCH(既定)
FIXED_PITCH(固定幅)
VARIABLE_PITCH(可変幅)

フォントファミリの指定には次の値のいずれかを使います。

定数     意味
FF_DECORATIVE 装飾付きフォントです。Old English フォントなどがあります。
FF_DONTCARE ファミリを指定しません。または、ファミリが不明です。
FF_MODERN 固定ストローク幅を持つ、セリフ(文字のひげ飾り)付きまたはセリフなしのフォントです。 Pica、Elite、Courier Newなどがあります。
FF_ROMAN 可変ストローク幅を持つセリフ付きフォントです。MS Serif などがあります。
FF_SCRIPT 手書き風のフォントです。Script、Cursive などがあります。
FF_SWISS 可変ストローク幅を持つセリフなしフォントです。MS Sans Serif などがあります。

fdwPitchAndFamily パラメータには、通常、ピッチ定数とファミリ定数を OR 演算子で組み合わせて指定します。
フォントファミリは、フォントの見た目を定義します。lpszFace で指定した フォント名のフォントが利用できないときに、フォントの選択に使われます。

lpszFace
フォントの名前が入った '\0' で終わる文字列へのポインタを指定します。 文字列の長さは、終端の '\0' 文字も含めて 32 文字以下にしなければなりません。
NULL ポインタや空の文字列へのポインタを指定すると、 ほかのパラメータで指定した条件に合うフォントの中から最初に見つかったフォントが選択されます。
表示に使われる実フォントの選択

フォント作成のための API で、 論理フォントは実際に画面に表示するフォントと 1 対 1 に対応するわけではないと説明しました。これは一体どういう意味でしょうか。
作成した論理フォントを使って画面に文字を表示するためには、表示用のデバイスコンテキストに選択する必要があります。 このとき、Windows は指定した論理フォントにもっとも近い実フォントを選択します。つまり、実際にデバイスコンテキストに 選択してみるまで、表示に使われる本当のフォントはわかりません。
仮に、CreateFont() のパラメータ指定で、フォント名に "MS ゴシック" と指定していたとしても、 もし、"MS ゴシック" が OS にインストールされていない(まず、ありえませんが)場合は、 Windows はその他のパラメータで指定されている条件にもっとも近い実フォントを選択しようとします。

Windows XP で変更されたフォントの選択方法

MS のサポート技術情報 417434 を基に、Windows のフォント選択の仕様をみてみます。

フォントを管理する GDI の内部ではフォントのサイズはピクセル値の単位で管理されています。 例えば、固定ピッチの 15 ポイントのフォントを必要とする場合、例えば 96 dpi の画面上では、 フォントの大きさについて で説明した計算方法に基づき、

15(ポイント) × 96 dpi ÷ 72 = 20(ピクセル)

として、20 ピクセルのフォントが使われます。このとき、全角文字の幅は 20 ピクセルとなります。 また、半角文字の幅は全角文字の幅の半分なので、10 ピクセルとなり、全角文字の幅は、半角文字の幅の 丁度 2 倍となります。この場合には、従来の Windows と Windows XP では同じ大きさの実フォントが 選択されるため、表示に違いは起こりません。

15 ポイントの "MS ゴシック" フォントを Windows Me と Windows XP 上で表示してみました。

Windows Me

Windows Me で表示

Windows XP

Windows XP で表示

次に、固定ピッチの 14 ポイントのフォントを必要とする場合、同じく 96 dpi の画面上では、

14(ポイント) × 96 dpi ÷ 72 = 18.666.. ≒ 19(ピクセル)

として計算されますが、ピクセル値は整数でなければならないため 18.666.. が切り上げられて、19 ピクセルのフォントが使われます。 このとき、全角文字の幅は 19 ピクセルとなります。半角文字の幅は全角文字の半分なので、9.5 ピクセルとしたいところですが、 やはり整数しか扱えないため、Windows の内部では 10 ピクセルの幅の半角文字が選択されます。 このとき、

半角文字の幅(10ピクセル) × 2 ≒ 全角文字の幅(19ピクセル)

となってしまいます。 14 ポイントの "MS ゴシック" フォントを Windows Me と Windows XP 上で表示してみました。

Windows Me

Windows Me で表示

Windows XP

Windows XP で表示

従来の Windows では、計算値をそのまま表示に反映するため、全角文字が半角文字の丁度 2 倍とならず、 アプリケーション側で文字幅を考慮し調整して表示しなければなりませんでした。
Windows XP では、表示に使われる実フォントの選択 のための内部仕様が、 全角固定ピッチフォントに限り、従来の Windows とは違う仕様となっています。 このため、Windows XP では上記のような場合でも、全角文字幅が半角文字幅の丁度 2 倍になるように調整され、 アプリケーション側でこうした調整を行う必要がなくなりました。 その代わり、従来の Windows 上で固定ピッチでレイアウトされたアプリケーションを Windows XP 上で実行すると、 文字がずれたり、文字の末尾が切れてしまったりする可能性がでてきてしまったのです。

※上記の実行例で、Windows XP は Home Edition が手元にないので、Professional Edition 上で実行しています。 また、Windows Me は VMware 上で実行したものです。

従来の Windows と Windows XP の両方をサポートする場合の対応策

MS のサポート技術情報 417434 では、以下のような対応策を挙げています。

  1. 可能な限り固定ピッチフォントではなく、"MS Pゴシック" などのプロポーショナルフォントを使用する
  2. 可能な限り、文字の表示領域に余裕を持たせる
  3. 文字の表示位置が重要である場合には、個々の文字ごとに表示位置を明確に指定する
  4. Visual Basic のフォーム編集や、Visual C++ のダイアログリソースの編集など開発ツールを使用して ウィンドウやダイアログを設計する際には、配置するテキストフィールドやボタンで使用されるフォントが 固定ピッチのフォントではないか、また従来の Windows と Windows XP で大きさの異なるフォントサイズを指定していないか 確認する
  5. 必要であれば、フォントのサイズを指定する際に、最終的に使用される文字のポイントサイズを判断し、 それに応じた処理を行うようにする

上記のうち、1, 2, 4 は固定ピッチフォントの表示がずれることを抑制するための対応策ではなく、ずれることを前提として レイアウトするか、または、仕様の変わっていないプロポーショナルフォントを利用しなさいということのようです。 3 でやれば確実に個々の文字を指定の場所に表示することができそうですが、プログラムの手間を考えると 個人的にはあまりやりたくありません。 5 は表示に使われる実フォントのサイズを考慮して、フォントサイズを明確に指示しなさいということだと思います。 以下では、5 の方法について考えてみます。

フォント作成時に幅を指定する

今までのサンプルでは、論理フォントを作成する際に、フォントの平均文字幅の指定(nWidth) に 0 を指定して、Windows に適当なフォントを選択させていました。ここで平均文字幅指定を正確に行えば、 ずれを防ぐことができそうです。 また、Windows XP の表示を変更するのは難しそうなので、Windows XP での表示方法に 今までの Windows 上での表示をあわせるようにしたいと思います。

ずれる条件は以下のようになるでしょうか。

  • OS が Windows XP ではない。
  • 固定ピッチフォントで、全角文字の幅が半角文字の幅のちょうど 2 倍になっていない場合。

この条件を満たす場合にのみ、全角文字幅が半角文字幅のちょうど 2 倍になるように幅を指定して フォントを作り直すことで対応することにします。

まず、OS が Windows XP か、それ以外の Windows かを得なければなりません。これには GetVersionEx() を使います。 例えば、以下のようにします。

// OS が Windows XP か、それ以外かを得る。
OSVERSIONINFO verinf;
verinf.dwOSVersionInfoSize = sizeof(verinf);
GetVersionEx(&verinf);
BOOL isXP = (verinf.dwMajorVersion == 5 && verinf.dwMinorVersion == 1) ? TRUE : FALSE;

GetVersionEx() から得られる OSVERSIONINFO のメンバ dwMajorVersiondwMinorVersion には それぞれ、OS のメジャーバージョン、マイナーバージョンが設定されます。Windows XP では、 メジャーバージョンは 5、マイナーバージョンは 1 が得られます。

次に、表示に使われる実フォントの、ピッチ、文字幅の情報が必要です。 実フォントの情報は論理フォントをデバイスコンテキストに選択してから GetTextMetrics() を使って取得することにします。 例えば、以下のようにします。

HFONT hFontTest = CreateFont(...);

// フォントをデバイスコンテキストに選択する。
HFONT hFontPrev = (HFONT) SelectObject(hDC, hFontTest);

// 選択された実フォントの情報を得る。
TEXTMETRIC tm;
GetTextMetrics(hDC, &tm);

GetTextMetrics() から得られる TEXTMETRIC のメンバ tmPitchAndFamily にはフォントのピットとファミリの情報が 設定されます。tmPitchAndFamily に TMPF_FIXED_PITCH ビットが設定されていない場合は固定ピッチフォントです (MSDN の誤植かと思ったのですが、実際に値を調べてみると確かに TMPF_FIXED_PITCH ビットが設定されているときは プロポーショナルフォント、設定されていないときは固定ピッチフォントが選択されていました)。
また、フォントが固定ピッチフォントの場合、メンバ tmAveCharWidth には半角文字幅が、 tmMaxCharWidth には全角文字幅が、それぞれ設定されます。

描画の処理は以下のようにします。

  1. OS が Windows XP かどうかを得ます (サンプルでは、描画時に毎回この処理を行っていますが、実際のプログラムでは 描画時ではなく、プログラムの起動時などに 1 回だけ行い、どこかに記録しておく方が良いと思います)。
  2. 論理フォントを作成します。このときは、平均文字幅は 0 を指定して、 Windows に適当なフォントを選択させるようにします。
  3. 論理フォントをデバイスコンテキストに選択してから、実フォントの情報を得ます。
  4. 実フォントが固定ピッチフォントであり、かつ、全角文字幅が半角文字幅の丁度 2 倍でない場合のみ、 平均文字幅を指定して新しく論理フォントを作り直します。指定する平均文字幅には、取得した実フォント情報の 平均文字幅 に 1 を足した値とします。 というのは、Windows XP で変更されたフォントの選択方法 でみたように、全角文字幅が奇数で 2 で割り切れない場合に、半角文字幅との関係がおかしくなっています。 平均文字幅を 1 大きくすることで、1 つ大きいサイズの全角文字幅が選択されるようになり、その値は 偶数になるはずです。偶数になれば 2 で割り切れますので、半角文字幅には全角文字幅の丁度半分の 値が選択されるはずです。
    新しく論理フォントを作り直した場合は、それをデバイスコンテキストに選択しておきます。
  5. 文字を表示します。上で新しく論理フォントを作成した場合はそのフォントが、作成していなかった場合は 最初に作ったフォントで、描画が行われます。
  6. デバイスコンテキストからフォントを選択解除してから、論理フォントを削除します。 新しく論理フォントを作成し直した場合は、そのフォントについても削除します。

上記にしたがって作成したプログラムの Windows Me と Windows XP での実行結果を以下に示します。

Windows Me

Windows Me で表示

Windows XP

Windows XP で表示

Windows Me での全角文字の幅が丁度半角文字の 2 倍になっています。そして、幅に関してのみいえば Windows XP と同じように表示されています。 フォントサイズによっては少し太めになっているのが気になりますが、これは論理フォントの平均文字幅を指定したことにより、 Windows が指定幅にあわせてフォントを調整したためです。 上記では "MS ゴシック" しかテストしていませんが、その他の TrueType フォントでも同じように 幅調整され、全角文字幅が半角文字幅の丁度 2 倍に表示されると思います。
ただし、フォントが TrueType でない場合の結果はおそらく思い通りにはならないでしょう。 幅調整がきかず、同じ大きさのフォントが表示されるだけになってしまうと思います。

その他

サポート技術情報 417434 には記載されていませんが、Windows XP の開発段階でのホワイトペーパーには、 以下のような暫定的な回避策が記載されていました。
ただし、この方法は推奨されておらず、また、Windows XP でのフォントの選択方法を従来の Windows の仕様に戻してしまいますので、 Windows XP のフォントの選択仕様にあわせて作成・変更されたアプリケーションの表示を乱してしまう可能性があります。
もし、試されたとしても、一切の責任を負いませんので、適用する場合は各自の責任においてのみ行ってください。 念のため、方法だけでなく回避策適用のガイドラインや注意事項もあわせて引用しておきます。

Windows XP での全角固定ピッチフォントの仕様変更 - ホワイトペーパー - 最終更新日 2001 年 9 月 17 日 より引用

Windows XP で用意される暫定回避策とガイドライン

Windows XP では、以下のレジストリ値を設定すれば、従来の Windows® と同様の仕様で固定ピッチフォントのサイズが選択されます。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
NT\CurrentVersion\GRE_Initialize
  "Jpn98FixPitch"=dword:00000001

この設定はシステム全体に適用されるため、設定適用後、新しい Windows XP の仕様に合わせて実装されたアプリケーションでは、 文字の表示や印刷で不具合が発生する可能性があります。これらのサイドエフェクトを最小限にするため、 この設定を用いる場合、次のガイドラインに従ってください。

  1. この暫定的な回避策は、既存のアプリケーションに対してのみ用いてください。 現在開発中、もしくはこれから開発されるアプリケーションに対しましては、 先に述べました「汎用的なアプリケーションの実装」に従い、Windows XP に対応してください。 このレジストリの設定をアプリケーションのインストーラーが無条件に設定するようなことはしないでください。
  2. アプリケーションベンダーが、この回避策を使用して自社のソフトウェア製品の問題を回避しようとするときには、 この回避策を使用することによる影響(例:変更がシステム全体に適用される等)を十分にユーザに通知してください。
  3. アプリケーションベンダーが、この回避策を使用して自社のソフトウェア製品の問題を回避しようとするときには、 この回避策を使用する・しないをエンドユーザーが選択できるようにしてください。
  4. ユーザが手動でレジストリの設定を変更した場合、私どもではそのレジストリの編集の結果によるいかなる問題に対して、 保証はいたしかねます。

注意: この設定は Windows XP でのみ有効な一時的な回避策であり、今後のWindows® のバージョンでも保証されるものではありません。

サンプルソース
dispsample.cpp (4.89 KB)
Windows XP で変更されたフォントの選択方法 で使用した フォント表示のサンプルプログラムです。
フォントに固定ピッチフォントである "MS ゴシック" を指定し、フォントサイズは 14 ポイントで計算し、 フォントの平均文字幅の指定(nWidth)には 0 を渡して、 Windows 側で適当なフォント幅を選択させるようにしています。 また、表示されたフォントの幅が確認しやすいように、100 ピクセルごとに縦に線を引いてあります。
dispfixed.cpp (6.39 KB)
フォント作成時に幅を指定する で使用した フォント表示のサンプルプログラムです。
フォントに固定ピッチフォントである "MS ゴシック" を指定し、フォントサイズを 8 ポイントから 1 つずつ 増やしながら表示します。 これも、表示されたフォントの幅が確認しやすいように、100 ピクセルごとに縦に線を引いてあります。
参考文献

プログラミング Windows 第5版(上)、(下) Charles Petzold 著 株式会社ロングテール/長尾高弘 訳
株式会社アスキー ISBN 4-7561-3600-1、4-7561-3601-X