Help us understand the problem. What is going on with this article?

OpenSiv3D の新機能 Glyph を使う

More than 1 year has passed since last update.

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

従来のテキスト描画

開始位置と色を指定して Font(text)draw() します。
1.png

# 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 つずつ描画していきます。

# 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 の中身から見ていきましょう。

struct Glyph
{
    TextureRegion texture;

    char32 codePoint;

    Point offset;

    int32 xAdvance;

    int32 index;
};

texture は、Siv3D エンジン内で確保されている、文字がたくさん格納された Texture から、該当する文字の部分を切り抜いた TextureRegion です。これを単純に描画するだけでも文字は出せますが、位置はめちゃくちゃです。
⚠️ わかりやすいように Texture の背景を白く塗っています。
2.png

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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 を使って面白いテキスト表現を探求してみてください。

Reputeless
小さくなってもコードはモダン!▼ 未定義動作なしの名コーダー!▼ 実装はいつも 3 つ!!(GCC/Clang/MSVC)▼ OpenSiv3D, cppmap 作者
https://ryo-suzuki-contact.github.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした