OpenTypeフォントにも望み通りの扁平・長体をかける

1.CreateFontIndirectではうまくいかない?

Windowsで、フォントに任意の扁平(長体)をかけて描画するには、CreateFontIndirect(もしくはCreateFont)Apiを使えばいい、はずだった。90年代のプログラミング本にはそうした記述があった記憶がある。が、現在、実際にはOpenTypeフォントや一部のTrueTypeフォントでは、うまくいかない。

OpenTypeフォントなら今時はDirectWriteを使うべきなのではという声もあるだろうが、20世紀からある由緒正しい(?)Windows Apiを使って何とかしてみよう、というお題。
なお、以下の記述は、Windows Apiの関連仕様をすべて正しく把握できている保証はなく、試行錯誤した経験則と推測によるもの。(突っ込み歓迎)

とりあえず試してみる。
基本的にはCreateFontIndirectに渡す適切なLOGFONT構造体を作るだけ。以下、扁平に必要なLOGFONT構造体を作る最低限のコード。

実際にVclアプリケーションで「MS ゴシック」と「游ゴシック」で試してみる。

フォント名以外、全く同一の指定をしているはずなのに、結果はこうなってしまう。

右側の「游ゴシック」の方は、明らかに扁平率が違う。
しかも、天地の描画位置も微妙にずれている。等間隔に線を描画してみると分かる。

2.TEXTMETRIC構造体にない3メンバ

さて、”おかしな扁平” “ずれた位置”になってしまうことに関係しそうなのは、TEXTMETRIC構造体になく、NEWTEXTMETRICEX構造体には存在する以下の3メンバ。
Microsoftの解説は英語しかないが、google翻訳で和訳。

メンバ 解説(Google翻訳)
ntmSizeEM フォントの正方形のサイズを指定します。 この値は概念上の単位(つまり、フォントが設計された単位)になります。
ntmCellHeight フォントの概念的な単位での高さを指定します。 この値は、ntmSizeEMメンバーの値と比較する必要があります。
ntmAvgWidth フォントの平均文字幅を概念上の単位で指定します。 この値は、ntmSizeEMメンバーの値と比較する必要があります。

分かったような分からないような説明だが、ともかく、このNEWTEXTMETRIC構造体の値は、昔ながらのApi EnumFontFamiliesExのコールバック関数EnumFontFamProcで取得できる。

コードは後回しにして、先の2フォントの値はこうなっている。

MS ゴシック 游ゴシック
ntmSizeEM 256 2048
ntmCellHeight 256 2636
ntmAvgWidth 128 1972

MSゴシックの場合はシンプルだ。ntmAvgWidthはntmSizeEMのちょうど半分。ntmSizeEMとntmCellHeightの値が全く同じ。
一方、游ゴシックはntmAvgWidthがntmSizeEMの半分より大きい。ntmCellHeightもtmSizeEMの値を上回る。

先ほどの描画結果とにらみ合わせてみると…。

以下のように推察される。
ntmAvgWidth……ntmSizeEMと同じサイズにしたい場合、この値をlfWidthに指定して初めて正方になる
ntmCellHeight……ntmSizeEMと同じサイズを指定した場合、描画時に、この値分の領域を上下に取る(つまりずれる)

3.3メンバを考慮した指定と描画

つまり、フォントに、望み通りの扁平(長体)をかけ、望み通りの位置に描画するには
(1)LOGFONTの指定時に、lfWidthにntmSizeEMとntmAvgWidthを考慮した値を指定する
(2)描画時の天地位置に、ntmSizeEMとntmCellHeightを考慮した値を指定する
ことが必要になる。

以下、サンプルコード。フォントごとにこれらの値を取得しておき、LOGFONT構造体作成時および描画時に値を調整する。必要な値はVclFormのコンストラクタで取得している。(丸め処理等は省略)

LOGFONT作成関数は以下のように書き換える

実際に描画するコードでは位置調整をする。

実行してみると、望み通りの扁平描画になる。

天地位置も正常。

あらゆるOpenTypeフォントでうまくいくのかどうかは不明だが、手持ちのOpenTypeフォントでは上手くいっている。
C++Builder10.2.3、Windows10で確認。