Win32 Tips

記述の正確さは保証しません。
もし誤りがありましたらご指摘いただけると幸いです。

Index


GetGlyphOutline関数でフォントのアウトラインデータを取得する

Win32APIのGetGlyphOutline関数は大きく2つの使い方がある。
  • フォントをビットマップにアンチエイリアス付きでラスタライズしたデータを得る
  • フォントのアウトラインの頂点座標情報を得る
前者の使い方は "GetGlyphOutline" でネットで検索すると Direct3Dでアンチエイリアスされたテキストを表示する方法としていろいろと紹介されてるのでここでは割愛し、 後者の方法を説明する。

フォントのアウトラインデータ

WindowsではアウトラインフォントはTrueTypeとOpenTypeの2つが使われている。 OpenTypeは中身はTrueTypeまたはPostScriptのいずれかなので、中身としてはTrueTypeまたはPostScriptとなる。 フォントファイルの構造を気にする必要はないが、
  • TrueTypeは2次スプラインで曲線を記述する
  • PostScriptは3次ベジェスプラインで曲線を記述する
ということは知ってる必要がある。

GetGlyphOutline関数のパラメータ

さてここで MSDNライブラリのGetGlyphOutline関数のドキュメント を見てみる。

引数 uFormat

uFormatの引数でどんなデータを取り出すかを指定する・・・のだが、このMSDNの記述、しょっぱなで痛い翻訳ミスをやっている。

↓MSDN日本語版の翻訳を間違った記述
意味
GGO_BEZIER線データを、4 次スプライン形式ではなく 3 次ベジェスプラインとして取得します。

4次スプラインって何??? と思ってMSDN英語版を見てみると、該当部分はの記述は
Windows 2000/XP: The function retrieves the curve data as a cubic Bézier spline (not in quadratic spline format).
となっていて、「2次スプライン形式ではなく、3次ベジェスプラインとして取得します。」が正しい訳であることがわかる。 Quadraticはクアッドだから4次だ、なーんて何も考えずに訳したのだと思うが、Quadraticは "四角形" →「平方の」という意味で「2次」が正しい。
これでようやくこの GGO_BEZIER フラグは
TrueTypeフォントだった場合その2次スプラインデータをPostScriptと同じ3次ベジェスプラインに変換してから出力するよ
という意味であることが判る。2次スプラインと3次ベジェスプラインの両方を処理するのが面倒な場合これをセットするとベジェだけ扱えばいいので少し楽できる。
できるだけ生のアウトラインデータが欲しい場合、一応 GGO_NATIVE と GGO_UNHINTED もセットする。

引数 lpgm

GLYPHMETRICS構造体の変数を用意してそのアドレスを渡すと、関数からそのグリフの幅とかの情報が返ってくる。

引数 lpmat2

変換を行わない場合でも必要がない場合でもここには必ず変換行列が設定されていなければならない。何も変換が必要ない場合は単位行列を作成してそのアドレスを渡す。

GetGlyphOutline関数呼び出しサンプル

ぐだぐだ文章を書くよりもコードを眺めた方がてっとり早いと思うので、GetGryphOutline関数呼び出しの部分の例を挙げる。
 UINT		targetChar = 'A';	// アウトラインを取得したい文字
 GLYPHMETRICS	gm;		// グリフ情報(関数の結果としてここに格納される)
 memset( &gm, 0, sizeof(gm) );
 const MAT2	mat2 = {{0,1},{0,0},{0,0},{0,1}};	// 何も変換を行わないために単位行列を用意

 DWORD rSize = ::GetGlyphOutline(	// 最初のGetGryphOutline関数はcbBufferパラメータに0を渡し、
    hDC, targetChar,			// 結果のデータを格納するためのサイズだけを返してもらう
    GGO_BEZIER|GGO_NATIVE|GGO_UNHINTED,
    &gm,
    0,
    NULL,
    &mat2 );

 if( rSize == GDI_ERROR ){	// 失敗した場合 GDI_ERRORが返る(0でないので注意)
  /*
    targetCharが全角または半角のスペース文字だった場合、GetGlyphOutline関数は何故か無条件にGDI_ERROR を返す。
    スペース文字だったからGDI_ERRORが返ってきたのか、本当にエラーが起きたからGDI_ERRORが返ってきたのか、は、
    どの文字の場合に無条件にGDI_ERRORが返ってくるのか、という完全なリストが無いとわからない。
    (全角、半角スペースだけだとは断言できない)
  */
  // ここでエラー処理と、スペース文字だった場合の処理を行う
 }
 else{ // 正しいバッファサイズが返ってきた
  char	*buff = new char[rSize];	// 得られたサイズでバッファを生成する
  DWORD rSize2 = GetGlyphOutline(	// バッファのサイズと先頭アドレスを渡し、
     hDC,				// アウトラインのデータをそこに取得する
     targetChar,
     GGO_BEZIER|GGO_NATIVE|GGO_UNHINTED,
     &gm,
     rSize,
     buff
     &mat2 );

  // ここで得られたグリフの処理を行う

  delete[] buff; // バッファ解放
 }
バッファサイズ及びバッファのアドレスにそれぞれ0とNULLを渡してGetGlyphOutline関数を呼ぶと、出力するデータのサイズを返してくれる。 そのサイズでバッファを生成し、今度はそのサイズとバッファの先頭アドレスを渡してもう一度 GetGlyphOutline関数を呼ぶ。
これが基本的な流れ。

コメント中にも書いたが、何故か GetGlyphOutline関数はスペース文字の入力を受け付けず GDI_ERRORを返してしまうのでスペース文字を与えてしまう可能性がある場合は何らかの対策が必要になる。

バッファからデータを取り出す

GetGlyphOutline関数から受け取るのはひとかたまりのデータで、そこから必要なデータを切り出していく。

FIXED構造体

GetGlyphOutline関数から得られるアウトラインの座標情報は FIXEDという固定小数点で格納されている。
FIXEDから double へは、
     double(fixed.value) + double(fixed.fract)/65536.0
で変換できる。
(などと偉そうに言ってるけど、ネットで他の人が書いてくれたことのウケウリ。)

POINTFX構造体

FIXED型のメンバ変数 x および y を持つ、座標を表す構造体。
この FIXED や POINTFX構造体は Win32で定義されてる構造体なのですが使い方その他文献があまりにも少なすぎる。困ったものだ。

バッファの大まかな構造

一つの文字は、一つ以上のポリゴンで構成されている。例えば、「E」は一つのポリゴン、「A」は真ん中の穴があるために2つのポリゴンからなる。 GetGlyphOutlineで得られるバッファは、大きくこのポリゴン毎に構成される。 バッファ全体に対してのヘッダ情報は無く、処理するためには先頭のポリゴンから逐次的に見て行く必要がある。
全体の構造
ポリゴン1
ポリゴン2
・・・
ひとつのポリゴンは、頭に TTPOLYGONHEADER型のヘッダがあり、その後に複数の TTPOLYGONCURVE型が続く。
一つのポリゴンの構造
TTPOLYGONHEADER
TTPOLYGONCURVE
TTPOLYGONCURVE
TTPOLYGONCURVE
・・・
TTPOLYGONCURVEの大きさはそこに含まれる頂点の数によって可変。
ひとつのTTPOLYGONCURVEには直線、2次スプライン、3次ベジェスプラインのいずれか1種類が格納される。 同じ種類であれば複数の曲線(または直線)をひとつのTTPOLYGONCURVEに格納できる。
フォントによっては、律儀にひとつのTTPOLYGONCURVEにベジェ曲線をひとつづつ入れるものもあれば(MS明朝とか)、 ひとつのTTPOLYGONCURVEにどかっと沢山のベジェ曲線を入れるものもある。

座標情報のお約束事

TTPOLYGONHEADER と TTPOLYGONCURVE の説明の前に、格納された座標情報の「つなげ方」についての簡単なお約束事を。
ポリゴンの回転方向
ポリゴンは、「進行方向右側が文字の内側」になるように格納されている。アウトラインのポリゴンは右回り、穴のポリゴンは左回り。
起点から開始
TTPOLYGONHEADERに最初の起点が入っていて、そこからスタートする。またTTPOLYGONCURVEの最後の点が次のTTPOLYGONCURVEの起点になる。
最初の点に戻らない場合がある
たいていの文字は最後の点が最初の起点まで戻ってくるが、たまに最後の点が最初の点に戻らない場合がある。 (直線だけで構成された文字の場合にたまに遭遇する)
その場合最後の点と最初の点を結ぶ線分を補完する。
(つづく)
copyright©2008 dendrocopos All rights reserved.