カスタムグリフソース
Prometeでは、IGlyphSource を実装することで、独自の方法で文字の形(グリフ)を用意し、Font として利用できます。
ここではグリフソースの仕組みと、作り方・使い方を解説します。
グリフソースとは
Section titled “グリフソースとは”Font クラスは、「グリフの供給元(グリフソース)」と「サイズやスタイルなどの描画設定」を組み合わせたものです。
TTF/OTF ファイルを読み込んだ場合も、内部では FreeType を使ったグリフソースが使われています。
グリフソースが担当するのは、「コードポイント(文字の番号)から、その文字のグリフを取り出す」ことだけです。 行の折り返しや整列、縁取りといった処理はエンジン側が行うため、グリフソースで実装する必要はありません。
画像フォントを使う場合
Section titled “画像フォントを使う場合”格子状に文字を並べた画像からビットマップフォントを作りたい場合は、自分で IGlyphSource を実装する必要はありません。
標準の BitmapGlyphSource.FromGrid を使いましょう。
using Promete.Graphics.Fonts;
// 8x8 ピクセルのセルに、左上から順に文字が並んだ画像を読み込むvar source = BitmapGlyphSource.FromGrid("assets/font.png", (8, 8), " !\"#$%&'()*+,-./0123456789");
// グリフソースを Font に変換する(ドット絵なのでアンチエイリアスは無効にする)var font = Font.FromGlyphSource(source, 16, FontStyle.Normal, false);
var text = new Text("123", font);BitmapGlyphSource は、指定したフォントサイズを元の大きさの整数倍に丸めて拡大するため、ドットが崩れません。
上の例では 8 ピクセルの画像を 16 ピクセルで使っているので、ちょうど 2 倍に拡大されます。
IGlyphSource を実装する
Section titled “IGlyphSource を実装する”画像以外の方法でグリフを用意したい場合は、IGlyphSource インターフェースを実装します。
実装するメンバーは次のとおりです。
| メンバー | 役割 |
|---|---|
SourceId | グリフソースを識別する ID。GlyphSourceId.Next() で採番します |
GetMetrics(options) | アセンダー・ディセンダー・行の高さを返します |
TryGetGlyph(codepoint, options, out glyph) | 文字のグリフ情報(送り幅など)を返します。持っていない文字なら false を返します |
Rasterize(glyph, options) | グリフを画像(GlyphBitmap)に変換します |
GetKerning(left, right, options) | 2 文字間のカーニング量を返します。対応しない場合は 0 を返します |
Dispose() | 保持しているリソースを解放します(IDisposable) |
options(GlyphRenderOptions)には、フォントサイズ(Size)などの描画設定が入っています。
サンプル:英数字を四角形で描くグリフソース
Section titled “サンプル:英数字を四角形で描くグリフソース”すべての英数字を、白く塗りつぶした四角形として描画する簡単な例です。
using System;using Promete.Graphics.Fonts;
public class BoxGlyphSource : IGlyphSource{ // ID は必ず GlyphSourceId.Next() で採番する public int SourceId { get; } = GlyphSourceId.Next();
public FontMetrics GetMetrics(in GlyphRenderOptions options) { // ベースラインより上に 8 割、下に 2 割の高さを持つ return new FontMetrics(options.Size * 0.8f, -options.Size * 0.2f, options.Size); }
public bool TryGetGlyph(int codepoint, in GlyphRenderOptions options, out GlyphInfo glyph) { // スペースから「~」までの ASCII 文字だけを扱う if (codepoint is < 0x20 or > 0x7E) { glyph = default; return false; }
glyph = new GlyphInfo { Source = this, GlyphIndex = (uint)codepoint, Codepoint = codepoint, Advance = options.Size * 0.6f, }; return true; }
public GlyphBitmap Rasterize(in GlyphInfo glyph, in GlyphRenderOptions options) { // スペースは描画するものがない if (glyph.Codepoint == ' ') return GlyphBitmap.Empty;
var width = (int)(options.Size * 0.5f); var height = (int)(options.Size * 0.7f);
// RGBA8888 形式。すべて 255 にすると白い不透明な四角形になる var pixels = new byte[width * height * 4]; Array.Fill(pixels, (byte)255);
// Bearing はベースラインから画像の左上までの位置。上方向はマイナス return new GlyphBitmap(pixels, (width, height), (0, -height)); }
public float GetKerning(int left, int right, in GlyphRenderOptions options) { return 0; }
public void Dispose() { }}実装のポイント
Section titled “実装のポイント”SourceIdは、グリフの画像をキャッシュする際の目印になります。自分で数値を決めず、必ずGlyphSourceId.Next()で採番してくださいRasterizeが返す画像は RGBA8888 形式です。単色の文字は RGB を白にして、アルファ値で濃さを表します。文字色はTextのColorで着色されますGlyphBitmapのBearingは、ベースライン上の原点から画像の左上までのずれです。Y 軸は下向きが正なので、ベースラインより上に描く場合はマイナスの値になりますRasterizeは、グリフを初めて描画するときにだけ呼ばれます。結果はエンジン側でキャッシュされます- 縁取りはエンジン側で
Rasterizeの結果から生成されるため、グリフソースで対応する必要はありません
Fontとして利用する
Section titled “Fontとして利用する”作成したグリフソースは、Font.FromGlyphSource で Font に変換して使います。
public class GameScene : Scene{ private BoxGlyphSource _source;
public override void OnStart() { _source = new BoxGlyphSource(); var font = Font.FromGlyphSource(_source, 24);
var text = new Text("Hello", font).Location(16, 16); Root.Add(text); }
public override void OnDestroy() { // 使い終わったグリフソースは破棄する _source.Dispose(); }}他のフォントと組み合わせる
Section titled “他のフォントと組み合わせる”グリフソースは、既存のフォントと組み合わせることもできます。
font.WithFallback(source)
フォントが持っていない文字を、sourceで補いますfont.WithOverride(source)sourceを優先して参照します。外字を差し込む場合に使います
// 名前を付けてアイコン画像を登録するvar icons = new BitmapGlyphSource(16);icons.Register("heart", "assets/heart.png");
// 本文のフォントに外字を重ねるvar text = new Text("", Font.GetDefault(16).WithOverride(icons));text.UseRichText = true;text.Content = "ライフ <tex=heart><tex=heart><tex=heart>";名前を付けて登録したグリフは、PTML の <tex=名前> で本文に差し込めます。詳しくはPTML(リッチテキスト)を参照してください。
- 持っていない文字に対して
TryGetGlyphがfalseを返した場合、その文字は描画されません Font.FromGlyphSourceに渡したグリフソースは自動では破棄されません。不要になったらDispose()を呼んでください- 画像から作るビットマップフォントや外字であれば、
BitmapGlyphSourceを使うのが手軽です