Font fallbacks.

This commit is contained in:
2025-06-29 16:51:06 +02:00
parent b3c1db3145
commit 26cb66dbe0
6 changed files with 113 additions and 7 deletions

View File

@@ -0,0 +1,3 @@
[FontSet]
fonts = ["Inter-Regular.ttf", "NotoSansJP-Regular.ttf"]

View File

@@ -54,6 +54,8 @@ public class TestGame : Game
{ {
throw new Exception("Failed to load emitter settings!"); throw new Exception("Failed to load emitter settings!");
} }
_defaultFontSet = new([_font, _jpFont]);
} }
protected override void Ready() protected override void Ready()
@@ -78,7 +80,7 @@ public class TestGame : Game
if (Input.IsActionPressed("accept")) if (Input.IsActionPressed("accept"))
{ {
_container.AddChild(new Label("こんにちは世界!", _jpFont)); _container.AddChild(new Label("こんにちは世界!", _defaultFontSet));
} }
if (Input.IsActionPressed("cancel") && _container.Children.Count != 0) if (Input.IsActionPressed("cancel") && _container.Children.Count != 0)
@@ -126,6 +128,9 @@ public class TestGame : Game
private ResourceRef<ParticleEmitterSettingsResource> _fireEffect; private ResourceRef<ParticleEmitterSettingsResource> _fireEffect;
private ResourceRef<Font> _font; private ResourceRef<Font> _font;
private ResourceRef<Font> _jpFont; private ResourceRef<Font> _jpFont;
private FontSet _defaultFontSet;
private ResourceRef<Sound> _sound; private ResourceRef<Sound> _sound;
private ResourceRef<Texture2d> _icon; private ResourceRef<Texture2d> _icon;

View File

@@ -0,0 +1,45 @@
using System.Diagnostics.CodeAnalysis;
namespace Voile.Resources;
/// <summary>
/// Contains a set of multiple fonts. Used to fetch fonts based on availability of glyphs.
/// </summary>
public class FontSet
{
public FontSet() { }
public FontSet(IEnumerable<ResourceRef<Font>> fonts)
{
_fonts = fonts.ToList();
}
public void AddFont(ResourceRef<Font> font) => _fonts.Add(font);
/// <summary>
/// Tries to get a suitable font that has a given character.
/// </summary>
/// <param name="c">Character to get a suitable font for.</param>
/// <param name="result">Font that contains this character.</param>
/// <returns><c>true</c> if a font that contains this character exists, <c>false</c> otherwise.</returns>
public bool TryGetFontFor(char c, [NotNullWhen(true)] out ResourceRef<Font>? result)
{
result = ResourceRef<Font>.Empty();
foreach (var fontRef in _fonts)
{
if (!fontRef.TryGetValue(out var font))
{
return false;
}
if (font.HasGlyph(c))
{
result = fontRef;
return true;
}
}
return false;
}
private List<ResourceRef<Font>> _fonts = new();
}

View File

@@ -52,6 +52,28 @@ namespace Voile
{ {
Guid = guid; Guid = guid;
} }
public override bool Equals(object? obj)
{
return obj is ResourceRef<T> other && Guid.Equals(other.Guid);
}
public override int GetHashCode()
{
return Guid.GetHashCode();
}
public static bool operator ==(ResourceRef<T>? left, ResourceRef<T>? right)
{
if (ReferenceEquals(left, right)) return true;
if (left is null || right is null) return false;
return left.Guid == right.Guid;
}
public static bool operator !=(ResourceRef<T>? left, ResourceRef<T>? right)
{
return !(left == right);
}
} }
/// <summary> /// <summary>

View File

@@ -231,7 +231,7 @@ namespace Voile.Resources
/// </summary> /// </summary>
/// <param name="path">Path to the resource.</param> /// <param name="path">Path to the resource.</param>
/// <returns>True if a resource at the specified path is loaded, otherwise false.</returns> /// <returns>True if a resource at the specified path is loaded, otherwise false.</returns>
public bool IsResourceLoaded(string path) => _resourcePathMap.ContainsKey(path); public static bool IsResourceLoaded(string path) => _resourcePathMap.ContainsKey(path);
/// <summary> /// <summary>
/// Adds a resource loader associated with a resource type. /// Adds a resource loader associated with a resource type.

View File

@@ -1,6 +1,7 @@
using System.Numerics; using System.Numerics;
using Voile.Input; using Voile.Input;
using Voile.Rendering; using Voile.Rendering;
using Voile.Resources;
namespace Voile.UI.Widgets; namespace Voile.UI.Widgets;
@@ -17,10 +18,26 @@ public class Label : Widget
} }
} }
/// <summary>
/// <see cref="FontSet"/> to use with this label.
/// </summary>
public FontSet FontSet { get; set; } = new();
public Label(string text, ResourceRef<Font> fontOverride) public Label(string text, ResourceRef<Font> fontOverride)
{ {
_text = text; _text = text;
_fontOverride = fontOverride;
FontSet.AddFont(fontOverride);
MarkDirty();
Update();
}
public Label(string text, FontSet fontSet)
{
_text = text;
FontSet = fontSet;
MarkDirty(); MarkDirty();
Update(); Update();
@@ -34,20 +51,34 @@ public class Label : Widget
public override void Render(RenderSystem renderer, Style style) public override void Render(RenderSystem renderer, Style style)
{ {
// TODO: use style here. // TODO: use style here.
if (!_fontOverride.HasValue) return;
renderer.SetTransform(GlobalPosition, Vector2.Zero); renderer.SetTransform(GlobalPosition, Vector2.Zero);
renderer.DrawText(_fontOverride, _text, Color.White); renderer.DrawText(_suitableFont, _text, Color.White);
} }
protected override void OnUpdate() protected override void OnUpdate()
{ {
var font = _fontOverride.Value; ResourceRef<Font> fontRef = ResourceRef<Font>.Empty();
foreach (var c in _text)
{
if (FontSet.TryGetFontFor(c, out var fallbackFont))
{
if (fallbackFont != fontRef)
{
fontRef = fallbackFont;
}
}
}
_suitableFont = fontRef;
var font = _suitableFont.Value;
_textSize = font.Measure(_text); _textSize = font.Measure(_text);
Size = _textSize; Size = _textSize;
} }
private ResourceRef<Font> _fontOverride = ResourceRef<Font>.Empty(); private ResourceRef<Font> _suitableFont = ResourceRef<Font>.Empty();
private string _text = "Hello, World!"; private string _text = "Hello, World!";
private Rect _textSize = Rect.Zero; private Rect _textSize = Rect.Zero;