WIP: measure text in Font.

This commit is contained in:
2025-06-29 15:10:37 +02:00
parent 6b108ba56c
commit 0ec4e45c38
4 changed files with 70 additions and 26 deletions

View File

@@ -56,10 +56,10 @@ public class TestGame : Game
Input.AddInputMapping("reload", new IInputAction[] { new KeyInputAction(KeyboardKey.R) }); Input.AddInputMapping("reload", new IInputAction[] { new KeyInputAction(KeyboardKey.R) });
_emitterId = _particleSystem.CreateEmitter(Renderer.WindowSize / 2, _fireEffect); _emitterId = _particleSystem.CreateEmitter(Renderer.WindowSize / 2, _fireEffect);
_fillContainer.AddChild(_marginContainer); // _fillContainer.AddChild(_container);
_marginContainer.AddChild(_container); // _marginContainer.AddChild(_container);
_uiSystem.AddElement(_fillContainer); _uiSystem.AddElement(_container);
} }
@@ -73,7 +73,7 @@ public class TestGame : Game
if (Input.IsActionPressed("accept")) if (Input.IsActionPressed("accept"))
{ {
_container.AddChild(new RectangleWidget(new Rect(32.0f, 32.0f), MathUtils.RandomColor())); _container.AddChild(new Label("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", _font));
} }
if (Input.IsActionPressed("cancel") && _container.Children.Count != 0) if (Input.IsActionPressed("cancel") && _container.Children.Count != 0)
@@ -123,7 +123,7 @@ public class TestGame : Game
private ResourceRef<Sound> _sound; private ResourceRef<Sound> _sound;
private ResourceRef<Texture2d> _icon; private ResourceRef<Texture2d> _icon;
private FlexContainer _container = new(minimumSize: new Rect(64.0f, 64.0f), new()) private FlexContainer _container = new(minimumSize: new Rect(256.0f, 256.0f), new())
{ {
Anchor = Anchor.Center, Anchor = Anchor.Center,
Direction = FlexDirection.Column, Direction = FlexDirection.Column,
@@ -133,6 +133,8 @@ public class TestGame : Game
Gap = 8.0f Gap = 8.0f
}; };
[NotNull] private Label _label;
private FillContainer _fillContainer = new(); private FillContainer _fillContainer = new();
private MarginContainer _marginContainer = new(new Margin(32.0f)) private MarginContainer _marginContainer = new(new Margin(32.0f))
{ {

View File

@@ -34,6 +34,8 @@ public class Font : Resource, IDisposable
internal nint FacePtr; internal nint FacePtr;
internal nint LibraryPtr; internal nint LibraryPtr;
internal List<int> Codepoints => _glyphs.Keys.ToList();
public Font(string path, byte[] buffer) : base(path) public Font(string path, byte[] buffer) : base(path)
{ {
Buffer = buffer; Buffer = buffer;
@@ -62,12 +64,38 @@ public class Font : Resource, IDisposable
} }
} }
public void Measure(string text) /// <summary>
/// Measures a given string using the font metrics.
/// </summary>
/// <param name="text">Text to measure.</param>
/// <returns>A <see cref="Rect"/> with the sizes of a given text using this font.</returns>
public Rect Measure(string text)
{ {
if (string.IsNullOrEmpty(text))
return Rect.Zero;
float totalWidth = 0;
float maxAscent = 0;
float maxDescent = 0;
foreach (char c in text) foreach (char c in text)
{ {
var glyph = GetGlyph(c);
totalWidth += glyph.Advance;
float ascent = glyph.Bearing.Y;
float descent = glyph.Height - glyph.Bearing.Y;
if (ascent > maxAscent)
maxAscent = ascent;
if (descent > maxDescent)
maxDescent = descent;
} }
float totalHeight = maxAscent + maxDescent;
return new Rect(totalWidth, totalHeight);
} }
public Glyph GetGlyph(char character) public Glyph GetGlyph(char character)
@@ -80,31 +108,29 @@ public class Font : Resource, IDisposable
return loaded; return loaded;
} }
internal void GetGlyphBoundingBox(Glyph glyph)
{
}
private unsafe Glyph LoadGlyph(char character) private unsafe Glyph LoadGlyph(char character)
{ {
var face = (FT_FaceRec_*)FacePtr; var face = (FT_FaceRec_*)FacePtr;
// TODO: for now, we're loading glyphs for metrics, but when implementing WebGPU rendering, we want to somehow render them to SDF or bitmap. // TODO: for now, we're loading glyphs for metrics, but when implementing WebGPU rendering, we want to somehow render them to SDF or bitmap.
FT_Error error = FT_Load_Char(face, character, FT_LOAD_NO_BITMAP); FT_Error error = FT_Set_Pixel_Sizes(face, 0, (uint)Size);
error = FT_Load_Char(face, character, FT_LOAD_NO_BITMAP);
if (error != 0) if (error != 0)
throw new Exception($"Failed to load glyph for '{character}'"); throw new Exception($"Failed to load glyph for '{character}'");
var glyph = face->glyph; var glyph = face->glyph;
var bitmap = glyph->bitmap; var bitmap = glyph->bitmap;
var metrics = glyph->metrics;
return new Glyph return new Glyph
{ {
Width = bitmap.width, Width = metrics.width >> 6,
Height = bitmap.rows, Height = metrics.height >> 6,
Bearing = new Vector2(glyph->bitmap_left, glyph->bitmap_top), Bearing = new Vector2(metrics.horiBearingX >> 6, metrics.horiBearingY >> 6),
Advance = (int)glyph->advance.x >> 6, Advance = (int)glyph->advance.x >> 6,
}; };
} }
private Dictionary<char, Glyph> _glyphs = new(); private Dictionary<int, Glyph> _glyphs = new();
} }

View File

@@ -65,7 +65,12 @@ namespace Voile.Resources
var resource = ResourceManager.LoadedResources[resourceGuid]; var resource = ResourceManager.LoadedResources[resourceGuid];
ResourceManager.RemoveResource(resourceGuid); ResourceManager.RemoveResource(resourceGuid);
resource.Dispose();
if (resource is IDisposable disposable)
{
disposable.Dispose();
}
return true; return true;
} }

View File

@@ -6,24 +6,29 @@ namespace Voile.UI.Widgets;
public class Label : Widget public class Label : Widget
{ {
public override Rect MinimumSize => throw new NotImplementedException(); public override Rect MinimumSize => _textSize;
public string Text { get; set; } = "Hello World!"; public string Text
public Label(string text)
{ {
Text = text; get => _text; set
{
_text = value;
MarkDirty();
}
} }
public Label(string text, ResourceRef<Font> fontOverride) public Label(string text, ResourceRef<Font> fontOverride)
{ {
Text = text; _text = text;
_fontOverride = fontOverride; _fontOverride = fontOverride;
MarkDirty();
Update();
} }
protected override void OnInput(UIInputContext action) protected override void OnInput(UIInputContext action)
{ {
throw new NotImplementedException();
} }
public override void Render(RenderSystem renderer, Style style) public override void Render(RenderSystem renderer, Style style)
@@ -31,13 +36,19 @@ public class Label : Widget
// TODO: use style here. // TODO: use style here.
if (!_fontOverride.HasValue) return; if (!_fontOverride.HasValue) return;
renderer.SetTransform(GlobalPosition, Vector2.Zero); renderer.SetTransform(GlobalPosition, Vector2.Zero);
renderer.DrawText(_fontOverride, Text, Color.White); renderer.DrawText(_fontOverride, _text, Color.White);
} }
protected override void OnUpdate() protected override void OnUpdate()
{ {
throw new NotImplementedException(); var font = _fontOverride.Value;
_textSize = font.Measure(_text);
Size = _textSize;
} }
private ResourceRef<Font> _fontOverride = ResourceRef<Font>.Empty(); private ResourceRef<Font> _fontOverride = ResourceRef<Font>.Empty();
private string _text = "Hello, World!";
private Rect _textSize = Rect.Zero;
} }