フォールバックフォントを適用した文字単位での自由描画
#Font #Glyph #GlyphCluster
以下に示す画像のように、英数字のみのフォント(黄色)に対し、フォールバックフォントとして日本語も網羅したフォント(水色)を設定し、文字単位での自由描画をする方法について。
https://gyazo.com/43ba6961ec49840c72c584afd4e07448
Siv3D チュートリアル - 文字単位での自由描画 で解説されている Font::getGlyphs() を使用する方法では、Font::addFallback() で設定したフォールバックフォントが適用されません。そこで、Font::getGlyphClusters() で得られる GlyphCluster::fontIndex からフォールバックフォントを取得し、Font::getGlyphByGlyphIndex() によりグリフを取得して描画しています。
Siv3D Discord に投稿された raclamusi 氏のコードを参考にさせていただきました(大変勉強になりました)。
https://discordapp.com/channels/443310697397354506/998714158621147237/1272846496680771584
フォールバックフォントとして 8×12ドット日本語フォント「k8x12」 を使用させていただきました。
このサンプルのために作成したビットマップフォント "px7812" は GitHub で公開しています。
https://github.com/voidproc/font-px7812
code: font_fallback.cpp
# include <Siv3D.hpp> // Siv3D v0.6.16
// あるフォントに対するフォールバックフォントのリスト
struct FontFallbackList
{
AssetName name;
Array<AssetName> fontNames;
};
// このプログラムでアセットに登録される各フォントに対するフォールバックフォントのテーブル
struct FontFallbackTable
{
HashTable<AssetName, FontFallbackList> fallbackInfo;
// フォントをテーブルに追加
// 自身を0番目のフォールバックとして登録しておく
void addFont(const AssetName& name)
{
fallbackInfoname.fontNames.push_back(name);
}
// フォントに対しフォールバックを設定
// 1番目以降のフォールバックに登録
void addFallback(AssetNameView name, const AssetName& nameFallback)
{
fallbackInfoname.fontNames.push_back(nameFallback);
}
};
void LoadFont(FontFallbackTable& fontFallbackTable)
{
// フォントアセットに登録するFontAssetDataを作成
const auto makeFontAssetData = [](int32 size, FilePathView path) {
auto pData = std::make_unique<FontAssetData>();
pData->font = Font{ FontMethod::Bitmap, size, path, FontStyle::Bitmap };
return pData;
};
// フォントを作成
auto fontData_px7812 = makeFontAssetData(12, U"font/px7812.ttf");
fontFallbackTable.addFont(U"px7812");
auto fontData_k8x12 = makeFontAssetData(12, U"font/k8x12L.ttf");
fontFallbackTable.addFont(U"k8x12");
// フォールバックを設定
fontData_px7812->font.addFallback(fontData_k8x12->font);
fontFallbackTable.addFallback(U"px7812", U"k8x12");
// アセットに登録
FontAsset::Register(U"px7812", std::move(fontData_px7812));
FontAsset::Register(U"k8x12", std::move(fontData_k8x12));
}
// 文字単位での自由描画でフォールバックを考慮する
// ※改行を考慮していない
//
// Siv3D Discord に投稿された raclamusi 氏のコードを参考にさせていただきました:
// https://discordapp.com/channels/443310697397354506/998714158621147237/1272846496680771584
void DrawText(AssetNameView fontName, const FontFallbackTable& fontFallbackTable, StringView text, const Vec2& pos, const Color& color)
{
const Vec2 basePos = pos;
Vec2 penPos{ basePos.asPoint() };
const auto clusters = FontAsset(fontName).getGlyphClusters(text, UseFallback::Yes, Ligature::No);
const std::array<ColorF, 2> colors = { color, Palette::Cyan };
for (const auto& cluster : clusters)
{
// GlyphCluster::fontIndexとFontFallbackTableからフォールバックフォントを得る
const auto font = FontAsset(fontFallbackTable.fallbackInfo.at(fontName).fontNamescluster.fontIndex);
// フォールバックフォントのグリフ
const auto glyph = font.getGlyphByGlyphIndex(cluster.glyphIndex);
const Vec2 offset = glyph.getOffset();
// 自由描画の例として、フォールバック先により文字色を変えてみる
const auto textColor = colorscluster.fontIndex;
const auto shadowColor = textColor.withA(0.4);
// 影
glyph.texture.draw(penPos + offset + Vec2::One(), shadowColor);
// テキスト本体
glyph.texture.draw(penPos + offset, textColor);
penPos.x += glyph.xAdvance;
}
}
void Main()
{
Scene::SetBackground(ColorF{ 0.05 });
Scene::SetTextureFilter(TextureFilter::Nearest);
constexpr Size SceneSize{ 300, 200 };
Window::Resize(SceneSize);
Scene::SetResizeMode(ResizeMode::Keep);
Window::Resize(SceneSize * 3);
// フォントアセットの登録、フォールバックフォントの設定
FontFallbackTable fontFallbackTable;
LoadFont(fontFallbackTable);
while (System::Update())
{
auto text = U"英数字のみのフォント\"px7812\"から、\"k8x12L\"へフォールバック"_sv;
const auto region = FontAsset(U"px7812")(text).regionAt(Scene::Center());
DrawText(U"px7812", fontFallbackTable, text, region.pos, Palette::Yellow);
}
}