OpenSiv3D の新機能 Glyph を使う

OpenSiv3D では、`Font` を使ってテキストを描画する際、`Glyph`(グリフ) というクラスで表現される、個々の文字に関する情報を利用して、文字の配置や色などを自在にカスタマイズできます。

## 従来のテキスト描画
開始位置と色を指定して `Font(text)` を `draw()` します。
![1.png](https://qiita-image-store.s3.amazonaws.com/0/30255/e48750d2-f774-aa9d-3c5f-5fe22027f479.png)

```cpp
# include <Siv3D.hpp> // OpenSiv3D v0.2.0

void Main()
{
	Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));

	const Font font(40);

	while (System::Update())
	{
		font(U"Siv3D プログラミング").draw(20, 20, Palette::Black);
	}
}
```

## Glyph を使ったテキスト描画
range-based for で `Font(text)` の `Glyph` を 1 つずつ描画していきます。

```cpp
# include <Siv3D.hpp> // OpenSiv3D v0.2.0

void Main()
{
	Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));

	const Font font(40);

	while (System::Update())
	{
		Vec2 pos(20, 20);

		for (const auto& glyph : font(U"Siv3D プログラミング"))
		{
			glyph.texture.draw(pos + glyph.offset, Palette::Black);

			pos.x += glyph.xAdvance;
		}
	}
}
```

## Glyph の基本

`Glyph` を使ったテキスト描画では、文字ごとに渡される TextureRegion や文字コード (char32 型)、オフセット、字間などの情報を使って、自前で 1 文字ずつ描画します。文字の位置の調整や改行も、すべて自分で実装しなければなりません（といっても、そんなに難しいことではありません）
まずは Glyph の中身から見ていきましょう。

```cpp
struct Glyph
{
	TextureRegion texture;

	char32 codePoint;

	Point offset;

	int32 xAdvance;

	int32 index;
};
```
`texture` は、Siv3D エンジン内で確保されている、文字がたくさん格納された Texture から、該当する文字の部分を切り抜いた TextureRegion です。これを単純に描画するだけでも文字は出せますが、位置はめちゃくちゃです。
⚠️ わかりやすいように Texture の背景を白く塗っています。
![2.png](https://qiita-image-store.s3.amazonaws.com/0/30255/5cfb319c-823c-a20f-2bf5-4d01a0a9d6d7.png)

```cpp
# include <Siv3D.hpp> // OpenSiv3D v0.2.0

void Main()
{
	Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));

	const Font font(40);

	while (System::Update())
	{
		Vec2 pos(20, 20);

		for (const auto& glyph : font(U"Siv3D プログラミング"))
		{
			glyph.texture.draw(pos, Palette::Black);

			pos.x += 40; // 適当な間隔
		}
	}
}
```

`texture` の描画位置を、それぞれの文字において `offset` だけずらすと、ベースラインがそろいます。
![3.png](https://qiita-image-store.s3.amazonaws.com/0/30255/b8a83e06-34c0-fc24-658d-352152624abe.png)

```cpp
# include <Siv3D.hpp> // OpenSiv3D v0.2.0

void Main()
{
	Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));

	const Font font(40);

	while (System::Update())
	{
		Vec2 pos(20, 20);

		for (const auto& glyph : font(U"Siv3D プログラミング"))
		{
			glyph.texture.draw(pos + glyph.offset, Palette::Black); // オフセットを足す

			pos.x += 40;
		}
	}
}
```

次の文字の書き始めの位置を決めるときには、`xAdvance` だけ字間を空けます。これで正しい位置に表示できました。
![4.png](https://qiita-image-store.s3.amazonaws.com/0/30255/49411f80-d730-7260-7228-323edf5f8dcd.png)

```cpp

# include <Siv3D.hpp> // OpenSiv3D v0.2.0

void Main()
{
	Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));

	const Font font(40);

	while (System::Update())
	{
		Vec2 pos(20, 20);

		for (const auto& glyph : font(U"Siv3D プログラミング"))
		{
			glyph.texture.draw(pos + glyph.offset, Palette::Black);

			pos.x += glyph.xAdvance; // xAdvance だけ字間を空ける
		}
	}
}
```

次は改行に対応させましょう。
`codePoint` は文字そのものを UTF-32 (char32 型) で表した値です。
`if (glyph.codePoint == U'あ')` のようにして、ある文字の時にだけ特別な処理をさせることができます。
ここでは、改行文字の場合にフォントの高さ分だけ y 座標を進めて x 座標をリセットするようにしましょう。
![11.png](https://qiita-image-store.s3.amazonaws.com/0/30255/ad4fe49d-7989-06e0-18db-6cbd2ca313c9.png)

```cpp
# include <Siv3D.hpp> // OpenSiv3D v0.2.0

void Main()
{
	Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));

	const Font font(40);

	while (System::Update())
	{
		const Vec2 basePos(20, 20);

		Vec2 pos(basePos);

		for (const auto& glyph : font(U"Siv3D プログラミング\n改行にも対応"))
		{
			if (glyph.codePoint == U'\n')
			{
				pos.x = basePos.x;

				pos.y += font.height();

				continue;
			}

			glyph.texture.draw(pos + glyph.offset, Palette::Black);

			pos.x += glyph.xAdvance;
		}
	}
}
```

ここまででは扱いませんでしたが、`index` は、描こうとする `Glyph` がテキスト全体で何番目の文字であるかを表す 0 から始まるインデックス番号です。次の章からは `index` も使った Glyph の応用例を見ていきましょう。

## 応用例 1: 字間を広げる
`pos.x` に加算する値を大きくし、字間を広げます。
![5.png](https://qiita-image-store.s3.amazonaws.com/0/30255/e10cd6d4-25f1-a533-1594-c0061a142735.png)

```cpp
# include <Siv3D.hpp> // OpenSiv3D v0.2.0

void Main()
{
	Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));

	const Font font(40);

	while (System::Update())
	{
		const Vec2 basePos(20, 20);

		Vec2 pos(basePos);

		for (const auto& glyph : font(U"Siv3D プログラミング\nGlyph を使おう"))
		{
			if (glyph.codePoint == U'\n')
			{
				pos.x = basePos.x;

				pos.y += font.height();

				continue;
			}

			glyph.texture.draw(pos + glyph.offset, Palette::Black);

			pos.x += glyph.xAdvance + 8; // 字間を大きくする
		}
	}
}
```

## 応用例 2: 文字の色を変える
`index` を使って虹色のテキストを作ります。
![6.png](https://qiita-image-store.s3.amazonaws.com/0/30255/3b44d45b-14d6-5a5c-952b-4e59fea1441e.png)

```cpp
# include <Siv3D.hpp> // OpenSiv3D v0.2.0

void Main()
{
	Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));

	const Font font(40);

	while (System::Update())
	{
		const Vec2 basePos(20, 20);

		Vec2 pos(basePos);

		for (const auto& glyph : font(U"Siv3D プログラミング\nGlyph を使おう"))
		{
			if (glyph.codePoint == U'\n')
			{
				pos.x = basePos.x;

				pos.y += font.height();

				continue;
			}

			glyph.texture.draw(pos + glyph.offset, HSV(glyph.index * 20)); // 色を文字ごとに変える

			pos.x += glyph.xAdvance;
		}
	}
}
```

## 応用例 3: ゆっくりと文字を出現させる
`Stopwatch` との組み合わせで、文字が少しずつフェードインするアニメーションを作ります。
![7.png](https://qiita-image-store.s3.amazonaws.com/0/30255/eca0950d-5226-6f4e-c2a8-cd2a50c026ba.png)

```cpp
# include <Siv3D.hpp> // OpenSiv3D v0.2.0

void Main()
{
	Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));

	const Font font(40);

	Stopwatch stopwatch(true);

	while (System::Update())
	{
		const Vec2 basePos(20, 20);

		Vec2 pos(basePos);

		const double t = stopwatch.sF();

		for (const auto& glyph : font(U"Siv3D プログラミング\nGlyph を使おう"))
		{
			if (glyph.codePoint == U'\n')
			{
				pos.x = basePos.x;

				pos.y += font.height();

				continue;
			}

			const ColorF color(0.0, Math::Saturate(t - glyph.index * 0.2));

			glyph.texture.draw(pos + glyph.offset, color);

			pos.x += glyph.xAdvance;
		}
	}
}
```


## 応用例 4: 異なるフォントを文の途中で使う
特定の文字の描画に、異なる `Font` を使います。
`Font::getGlyph(char32)` を使うと、指定した文字の Glyph を作成して取得できます。
![8.png](https://qiita-image-store.s3.amazonaws.com/0/30255/9ebcfb6e-71f5-da63-9045-238387756733.png)

```cpp
# include <Siv3D.hpp> // OpenSiv3D v0.2.0

void Main()
{
	Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));

	const Font font(40);

	const Font fontBold(40, Typeface::Black);

	while (System::Update())
	{
		const Vec2 basePos(20, 20);

		Vec2 pos(basePos);

		for (const auto& glyph : font(U"Siv3D プログラミング\nGlyph を使おう"))
		{
			if (glyph.codePoint == U'\n')
			{
				pos.x = basePos.x;

				pos.y += font.height();

				continue;
			}

			if (glyph.index < 5)
			{
				const auto glyph2 = fontBold.getGlyph(glyph.codePoint);

				glyph2.texture.draw(pos + glyph2.offset, Palette::Black);

				pos.x += glyph2.xAdvance;
			}
			else
			{
				glyph.texture.draw(pos + glyph.offset, Palette::Black);

				pos.x += glyph.xAdvance;
			}
		}
	}
}
```


## 応用例 5: 特定の文字を別の画像に置き換える
特定の文字を別の画像の表示に置き換えます。
次のプログラムでは `@` の文字を Siv3D-kun の画像に置き換えて表示します。
![9.png](https://qiita-image-store.s3.amazonaws.com/0/30255/0bcc8472-48b3-8243-95c6-442332bb5355.png)

```cpp
# include <Siv3D.hpp> // OpenSiv3D v0.2.0

void Main()
{
	Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));

	const Font font(40);

	const Texture texture(U"example/siv3d-kun.png", TextureDesc::Mipped);

	while (System::Update())
	{
		const Vec2 basePos(20, 20);

		Vec2 pos(basePos);

		for (const auto& glyph : font(U"Siv3D くん @ のアイコン"))
		{
			if (glyph.codePoint == U'\n')
			{
				pos.x = basePos.x;

				pos.y += font.height();

				continue;
			}
			else if (glyph.codePoint == U'@')
			{
				const RoundRect rect(pos, font.height(), font.height(), 8);

				rect.draw(Palette::Skyblue);
				
				rect(texture(90, 5, 110)).draw();

				pos.x += rect.w;

				continue;
			}

			glyph.texture.draw(pos + glyph.offset, Palette::Black);

			pos.x += glyph.xAdvance;
		}
	}
}
```

## もっと！

`Glyph` を応用すると、Markdown や HTML のように、文中の記号でフォントのスタイルを変更するマークアップ言語を実装できます。
例えば、次のプログラムでは、`$` で囲んだ文字の色をオレンジにします。
![10.png](https://qiita-image-store.s3.amazonaws.com/0/30255/95707d31-44b8-2c69-f8cd-ddc5104a4b7d.png)

```cpp
# include <Siv3D.hpp> // OpenSiv3D v0.2.0

void Main()
{
	Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));

	const Font font(40);

	bool isBalck = true;

	while (System::Update())
	{
		const Vec2 basePos(20, 20);

		Vec2 pos(basePos);

		for (const auto& glyph : font(U"$Siv3D$ プログラミング\n$Glyph$ を使おう"))
		{
			if (glyph.codePoint == U'\n')
			{
				pos.x = basePos.x;

				pos.y += font.height();

				continue;
			}
			else if (glyph.codePoint == U'$')
			{
				isBalck = !isBalck;

				continue;
			}

			glyph.texture.draw(pos + glyph.offset, isBalck ? Palette::Black : Palette::Orange);

			pos.x += glyph.xAdvance;
		}
	}
}
```

より実用的なマークアップの実装をしようとすると長くなるので、また別の記事で紹介したいと思います。
みなさんも新機能 `Glyph` を使って面白いテキスト表現を探求してみてください。