Show fallback glyph if its not present in the font, add Japanese font to TestGame.

This commit is contained in:
2025-06-29 16:21:34 +02:00
parent 9bc9810c8f
commit b3c1db3145
5 changed files with 66 additions and 5 deletions

Binary file not shown.

View File

@@ -42,6 +42,11 @@ public class TestGame : Game
} }
if (!ResourceManager.TryLoad("fonts/NotoSansJP-Regular.ttf", out _jpFont))
{
}
ResourceManager.TryLoad("icon.png", out _icon); ResourceManager.TryLoad("icon.png", out _icon);
ResourceManager.TryLoad("sounds/test_sound_mono.ogg", out _sound); ResourceManager.TryLoad("sounds/test_sound_mono.ogg", out _sound);
@@ -73,7 +78,7 @@ public class TestGame : Game
if (Input.IsActionPressed("accept")) if (Input.IsActionPressed("accept"))
{ {
_container.AddChild(new Label("Hello, World!", _font)); _container.AddChild(new Label("こんにちは世界!", _jpFont));
} }
if (Input.IsActionPressed("cancel") && _container.Children.Count != 0) if (Input.IsActionPressed("cancel") && _container.Children.Count != 0)
@@ -120,6 +125,7 @@ public class TestGame : Game
private int _emitterId; private int _emitterId;
private ResourceRef<ParticleEmitterSettingsResource> _fireEffect; private ResourceRef<ParticleEmitterSettingsResource> _fireEffect;
private ResourceRef<Font> _font; private ResourceRef<Font> _font;
private ResourceRef<Font> _jpFont;
private ResourceRef<Sound> _sound; private ResourceRef<Sound> _sound;
private ResourceRef<Texture2d> _icon; private ResourceRef<Texture2d> _icon;

View File

@@ -244,6 +244,12 @@ namespace Voile.Rendering
LoadFont(font); LoadFont(font);
} }
if (font.Dirty && font.Handle != -1)
{
UnloadFont(font);
LoadFont(font);
}
var rayFont = _fontPool[font.Handle]; var rayFont = _fontPool[font.Handle];
Raylib.DrawTextPro(rayFont, text, transformPosition, transformPivot, transformRotation, font.Size, 0.0f, VoileColorToRaylibColor(color)); Raylib.DrawTextPro(rayFont, text, transformPosition, transformPivot, transformRotation, font.Size, 0.0f, VoileColorToRaylibColor(color));
@@ -268,10 +274,10 @@ namespace Voile.Rendering
{ {
Raylib_cs.Font fontRay; Raylib_cs.Font fontRay;
string ext = ".ttf"; // TODO: don't use a hardcoded extension. string ext = ".ttf";
int fontChars = 250; // TODO: control this dynamically to not load the entire font. int fontChars = font.Codepoints.Count;
fontRay = Raylib.LoadFontFromMemory(ext, font.Buffer, font.Size, null, fontChars); fontRay = Raylib.LoadFontFromMemory(ext, font.Buffer, font.Size, font.Codepoints.ToArray(), fontChars);
Raylib.GenTextureMipmaps(ref fontRay.Texture); Raylib.GenTextureMipmaps(ref fontRay.Texture);
Raylib.SetTextureFilter(fontRay.Texture, TextureFilter.Bilinear); Raylib.SetTextureFilter(fontRay.Texture, TextureFilter.Bilinear);
@@ -281,6 +287,14 @@ namespace Voile.Rendering
font.Handle = _fontPool.Count - 1; font.Handle = _fontPool.Count - 1;
} }
private void UnloadFont(Font font)
{
var fontRay = _fontPool[font.Handle];
Raylib.UnloadFont(fontRay);
_fontPool.RemoveAt(font.Handle);
}
private void LoadTexture(Texture2d texture) private void LoadTexture(Texture2d texture)
{ {
Image image = new(); Image image = new();

View File

@@ -20,7 +20,7 @@ public struct Glyph
/// <summary> /// <summary>
/// Represents font data. /// Represents font data.
/// </summary> /// </summary>
public class Font : Resource, IDisposable public class Font : Resource, IUpdatableResource, IDisposable
{ {
/// <summary> /// <summary>
/// Internal handle for the font. If it got successfully loaded into the GPU, the value will be other than -1. /// Internal handle for the font. If it got successfully loaded into the GPU, the value will be other than -1.
@@ -31,6 +31,8 @@ public class Font : Resource, IDisposable
public byte[]? Buffer { get; private set; } public byte[]? Buffer { get; private set; }
public long BufferSize { get; set; } public long BufferSize { get; set; }
public bool Dirty => _dirty;
internal float UnitsPerEm; internal float UnitsPerEm;
internal nint FacePtr; internal nint FacePtr;
@@ -131,14 +133,31 @@ public class Font : Resource, IDisposable
public Glyph GetGlyph(char character) public Glyph GetGlyph(char character)
{ {
if (!HasGlyph(character))
{
_glyphs.TryGetValue('?', out var defGlyph);
return defGlyph;
}
if (_glyphs.TryGetValue(character, out var glyph)) if (_glyphs.TryGetValue(character, out var glyph))
return glyph; return glyph;
var loaded = LoadGlyph(character); var loaded = LoadGlyph(character);
_glyphs[character] = loaded; _glyphs[character] = loaded;
_dirty = true;
return loaded; return loaded;
} }
public bool HasGlyph(char character)
{
unsafe
{
var face = (FT_FaceRec_*)FacePtr;
return FT_Get_Char_Index(face, character) != 0;
}
}
private unsafe Glyph LoadGlyph(char character) private unsafe Glyph LoadGlyph(char character)
{ {
var face = (FT_FaceRec_*)FacePtr; var face = (FT_FaceRec_*)FacePtr;
@@ -163,5 +182,11 @@ public class Font : Resource, IDisposable
}; };
} }
public void MarkUpdated()
{
_dirty = false;
}
private bool _dirty;
private Dictionary<int, Glyph> _glyphs = new(); private Dictionary<int, Glyph> _glyphs = new();
} }

View File

@@ -69,4 +69,20 @@ namespace Voile
Path = path; Path = path;
} }
} }
/// <summary>
/// Represents a Resource that requires systems to react to its changes.
/// </summary>
public interface IUpdatableResource
{
/// <summary>
/// Gets a value indicating whether this resource's state has changed and needs to be reloaded.
/// </summary>
bool Dirty { get; }
/// <summary>
/// Marks this resource as updated.
/// </summary>
void MarkUpdated();
}
} }