Siv3D Fontの機能を調べてみる


「Siv3D Advent Calendar 2015 」4日目の記事です。
今回はFontクラスの関数を紹介します。
基本的にDraw関数を使うことが多いですが、それ以外にも便利な関数が存在します。



#Fontクラスの注意点
Fontクラスを使う場合、以下の点に注意してください。

- 作成は1度だけにしましょう。
Fontクラスは作成・削除するコストが大きく、作成と削除を繰り返すと処理が遅くなったりします。
かならずwhile(System::update())の前に作成しましょう。

- Fontを使用するクラスに対しては、引数で渡しましょう。
描画処理はすべてconstなので、const Font& fontで渡せばよいです。

#文字列を描画する
Font.draw関数を使います。
drawCenterは指定した座標を中心にするバージョンで大体同じです。

引数は前から文字列,座標,色,ベースラインです。
この引数の並びは、他のfont描画関数も同様です。
サンプルは公式にも多数あるので省略。

#新規にフォントを追加する
パソコンにインストールされていないフォントを使うには、FontManager::Registerを使います。
Example フォルダに "YomogiFont.ttf" というファイル名で「よもぎフォント」があります。
それを登録して Font で扱えるようにします。

```cpp:サンプル
void Main()
{
    if (!FontManager::Register(L"Example/YomogiFont.ttf"))
    {
        return;
    }

    Font font(30, L"よもぎフォント");

    while (System::Update())
    {
        font.draw(text, 0,80);
    }
}
```
※指摘がありましたので、修正しました。

#描画領域を調べる
Font.regionまたはFont.regionCenterを使います。
文字列を描画した場合の描画範囲を返します。
描画範囲を調べたいけど、描画したくない時はこれを使いましょう。

#文字列を画像として取得する
Font.write・Font.overwriteを使うことでImageクラスに書き込むことができます。
writeは書き込み先とのブレンド、overwriteは上書きです。
描画した文字がImageよりも大きい場合、はみ出した部分は無視されます。
また、空のImageを渡すと何も描画されません。
Font.regionで大きさを調べて、Image.resizeで必要な大きさを確保しましょう。

```cpp:writeのサンプル

#include <Siv3D.hpp>
void Main()
{
    Graphics::SetBackground(Palette::Goldenrod);
    Image image[4];

    Font font(30);
    {
        String write1 = L"write（下地なし）";
        const Rect rectW = font.region(write1);
        image[0].resize(rectW.size);
        font.write(image[0], write1, Point::Zero);
    }
    {
        String write2 = L"write(下地あり)";
        const Rect rectW = font.region(write2);
        image[1].resize(rectW.size, s3d::Palette::Blue);
        font.write(image[1], write2, Point::Zero);
    }
    {
        String overwrite1 = L"overwrite(下地なし)";
        const Rect rectO = font.region(overwrite1);
        image[2].resize(rectO.size);
        font.overwrite(image[2], overwrite1);
    }
    {

        String overwrite2 = L"overwrite(下地あり)";
        const Rect rectO = font.region(overwrite2);
        image[3].resize(rectO.size, s3d::Palette::Green);
        font.overwrite(image[3], overwrite2);
    }
    Texture texture[4];
    for (std::size_t i = 0; i < 4; ++i)
    {
        texture[i] = Texture(image[i]);
    }

    while (System::Update())
    {
        Vec2 pos = Vec2::Zero;
        for (const Texture& tex : texture)
        {
            tex.draw(pos);
            pos.y += tex.size.y;
        }
    }
}

```
描画結果
一番上の方法でデータを作成した場合描画されませんが、仕様です。
これはColor(0,0,0,0)の画像に対して描画を行うとブレンドの結果すべてが透明になるからです。
![write.png](https://qiita-image-store.s3.amazonaws.com/0/92869/6f0b0402-bd9d-8d80-84df-e75a7eb96a38.png)


#文字に動きをつける・1文字ずつ描画する
drawKineticで描画の時の細かい挙動を制御できます。
これにはKineticTypography&を引数とする関数オブジェクトを渡して、カスタマイズ用の処理を書きます。
まずはKineticTypographyのメンバを見てみましょう。

```cpp:SivKineticTypography.hppより引用
namespace s3d
{
	struct KineticTypography
	{
		Vec2 pos;           // 次に描画される文字の座標 変更すると、描画位置が変更される
		Vec2 origin;        // drawKineticに最初に渡した座標
		uint32 textLength;  // 文字列の長さ 空白文字などを含める
		uint32 index;       // 次に描画される文字の添え字 String[index] == chである
		Color col;          // 次に描画される文字の色
		wchar ch;           // 次に描画される文字
	};
	using KineticTypographyFunction_t = std::function<void(KineticTypography&)>;
}
```

それぞれ詳しく解説します。
posは文字の座標です。
たとえば"abcde"を描画した場合、座標は前から順に以下のようになります
[0](a)	{x= 0.00 y=20.0 }
[1](b)	{x=33.00 y=8.00 }
[2](c)	{x=65.00 y=20.0 }
[3](d)	{x=94.00 y=8.00 }
[4](e)	{x=128.0 y=20.0 }
このように均等な値にはなりません。直前の文字の大きさによって決まるためです。
Yの値ですが、これは文字の縦の大きさに影響されます。
b,dがy=8.0になっていますが、これらの文字は上に出っ張っている部分があり描画位置を上の方におくためです。
文字が改行などの描画されない文字の場合、drawKineticに渡した関数は実行されません。
chをs3d::IsPrint()やs3d::IsSpace()などで確認しておきましょう。
indexは元の文字列のindexと一致します。
drawKinetic()にはDrawCenterにあたる関数がありませんが、centerRegionで描画領域を取得して直前でSetTransform()[後述]によって位置をずらせば同様の処理もできなくはないです。

[Reputeless](http://qiita.com/Reputeless)さんの記事にルビを表示するサンプルが別にあるのでそちらも参考に。
[Siv3D の 2D 描画テクニック](http://qiita.com/Reputeless/items/a7d4465ad3347c746a90#4-%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AB%E3%83%AB%E3%83%93%E3%82%92%E6%8C%AF%E3%82%8D%E3%81%86)
#変換行列を使って拡大・回転する
描画関数はGraphics2D::SetTransform()で設定した変換行列の影響を受けます。
drawKineticと併用すれば、それぞれの文字に個別に効果を出すこともできます。
以下、SetTransformで実際に動きが変わることを確認できるサンプルです。
なお、Fontの描画はSetTransformの影響を受けますが、各メンバ関数の戻り値は影響を受けません。

```Main.cpp
# include <Siv3D.hpp>
void Main()
{
    const Font font(30);
    Vec2 pos = Window::Center();
    double angle = 0;
    double scale = 1;
    while (System::Update())
    {
        if (Input::KeyUp.clicked){ pos.y -= 10.0; }
        if (Input::KeyDown.clicked){ pos.y += 10.0; }
        if (Input::KeyLeft.clicked){ pos.x -= 10.0; }
        if (Input::KeyRight.clicked){ pos.x += 10.0; }
        if (Input::KeyA.clicked){ angle += 0.1; }
        if (Input::KeyS.clicked){ angle -= 0.1; }
        if (Input::KeyW.clicked){ scale += 0.1; }
        if (Input::KeyZ.clicked){ scale -= 0.1; }
        Graphics2D::SetTransform(
            Mat3x2::Scale(scale, scale)*
            Mat3x2::Rotate( angle )*
            Mat3x2::Translate(pos)
            );
        font.draw(L"行列すごく便利！");
    }
}
```

今回のサンプルではFond.draw()に座標を渡していませんが、これは左上の(0,0)を原点とするためです。
Mat3x2::Scale(scale, scale)とMat3x2::Rotate( angle )は最後の引数にVec2があり、拡大の原点と回転の中心座標を指定できます。デフォルトではVec2::Zero(0,0)が指定されています。
詳しく説明すると変換行列の説明になりますので、拡大縮小行列・回転行列などで検索してください。
文字を画像(Texture)として取得して、Textureの操作として動かす方法もあります。
状況に応じて使い分けてみてください。

#終わりに
Syv3Dのフォントは必要な関数がちゃんと実装されていますので、機能の組み合わせでやりたいことは十分できると思います。
画像に変換したり変換行列を使うという力技での解決も可能です。

次は[agehama_](http://qiita.com/agehama_)さんの記事です。