FontRenderer: Basic Rich Text processing and lazy text building

This commit is contained in:
Simon Lübeß
2024-03-16 23:35:12 +01:00
parent f9ea705e77
commit 96609a6c31
5 changed files with 148 additions and 45 deletions
@@ -477,6 +477,18 @@ namespace GlitchyEditor.EditWindows
private static void ShowTextRendererComponentEditor(Entity entity, TextRendererComponent* textRendererComponent) private static void ShowTextRendererComponentEditor(Entity entity, TextRendererComponent* textRendererComponent)
{ {
StartNewProperty("Rich text");
ImGui.AttachTooltip("If checked, the text will be interpreted as rich text. This means, that you can use tags to change the style of the text.");
bool isRichText = textRendererComponent.IsRichText;
if (ImGui.Checkbox("##rich_text", &isRichText))
{
textRendererComponent.IsRichText = isRichText;
textRendererComponent.NeedsRebuild = true;
}
StartNewProperty("Text"); StartNewProperty("Text");
String text = textRendererComponent.[Friend]_text; String text = textRendererComponent.[Friend]_text;
@@ -498,6 +510,8 @@ namespace GlitchyEditor.EditWindows
{ {
text.Length = length; text.Length = length;
} }
textRendererComponent.NeedsRebuild = true;
} }
} }
+83 -30
View File
@@ -135,6 +135,49 @@ namespace GlitchyEngine.Renderer.Text
case InvertCase; case InvertCase;
} }
class StyleStack<T>
{
private append List<T> _stack = .();
public this(T bottomValue)
{
_stack.Add(bottomValue);
}
public void Push(T value)
{
_stack.Add(value);
}
public T CurrentValue()
{
return _stack.Back;
}
public T Pop()
{
if (_stack.Count == 1)
return _stack[0];
return _stack.PopBack();
}
/// If popValue is true, the given value will not be pushed and the current top of the stack will be poped.
/// If popValaue is false, the given value will be pushed and returned.
public T PushButPopIfTrue(T value, bool popValue)
{
if (popValue)
{
return Pop();
}
else
{
Push(value);
return value;
}
}
}
public static PreparedText PrepareText(Font font, StringView text, float fontSize, Color fontColor = .White, Color bitmapColor = .White, float lineSpaceScale = 1.0f, TextDirection direction = .LeftToRight) public static PreparedText PrepareText(Font font, StringView text, float fontSize, Color fontColor = .White, Color bitmapColor = .White, float lineSpaceScale = 1.0f, TextDirection direction = .LeftToRight)
{ {
Debug.Profiler.ProfileRendererFunction!(); Debug.Profiler.ProfileRendererFunction!();
@@ -158,11 +201,11 @@ namespace GlitchyEngine.Renderer.Text
hb_buffer_t* buf = hb_buffer_create(); hb_buffer_t* buf = hb_buffer_create();
// Stack to keep track of the text direction. // Stack to keep track of the text direction.
List<TextDirection> textDirectionStack = scope .(); StyleStack<TextDirection> textDirectionStack = scope .(direction);
textDirectionStack.Add(direction);
List<TextCase> textCaseStack = scope .(); StyleStack<TextCase> textCaseStack = scope .(.RetainCase);
textCaseStack.Add(.RetainCase);
StyleStack<bool> richTextStack = scope .(true);
int movedLines = 0; int movedLines = 0;
@@ -185,7 +228,7 @@ namespace GlitchyEngine.Renderer.Text
// Set the script, language and direction of the buffer. // Set the script, language and direction of the buffer.
// TODO: change direction, script, etc. // TODO: change direction, script, etc.
TextDirection direction = textDirectionStack.Back; TextDirection direction = textDirectionStack.CurrentValue();
switch (direction) switch (direction)
{ {
@@ -237,7 +280,7 @@ namespace GlitchyEngine.Renderer.Text
bool escapeNextChar = false; bool escapeNextChar = false;
for (char32 char in text.DecodedChars) mainEnumerator: for (char32 char in text.DecodedChars)
{ {
defer defer
{ {
@@ -271,14 +314,14 @@ namespace GlitchyEngine.Renderer.Text
// LEFT-TO-RIGHT ISOLATE // LEFT-TO-RIGHT ISOLATE
FlushShapeBuffer(); FlushShapeBuffer();
textDirectionStack.Add(.RightToLeft); textDirectionStack.Push(.RightToLeft);
continue; continue;
case '\u{2067}': case '\u{2067}':
// RIGHT-TO-LEFT ISOLATE // RIGHT-TO-LEFT ISOLATE
FlushShapeBuffer(); FlushShapeBuffer();
textDirectionStack.Add(.RightToLeft); textDirectionStack.Push(.RightToLeft);
continue; continue;
// TODO: FIRST STRONG ISOLATE (U+2068) // TODO: FIRST STRONG ISOLATE (U+2068)
@@ -286,8 +329,7 @@ namespace GlitchyEngine.Renderer.Text
// POP DIRECTIONAL ISOLATE // POP DIRECTIONAL ISOLATE
FlushShapeBuffer(); FlushShapeBuffer();
if (textDirectionStack.Count > 1) textDirectionStack.Pop();
textDirectionStack.PopBack();
continue; continue;
case '\\': case '\\':
@@ -331,6 +373,30 @@ namespace GlitchyEngine.Renderer.Text
if (isEndTag) if (isEndTag)
tagText.Remove(0); tagText.Remove(0);
tagText.ToLower();
if (tagText == "rt" || tagText == "richtext")
{
richTextStack.PushButPopIfTrue(true, isEndTag);
// Skip the tag in the main enumerator
@char.NextIndex = endIndex;
continue;
}
if (tagText == "pt" || tagText == "plaintext")
{
richTextStack.PushButPopIfTrue(false, isEndTag);
// Skip the tag in the main enumerator
@char.NextIndex = endIndex;
continue;
}
// Only process the other tags if we currently use rich text processing
if (!richTextStack.CurrentValue())
break;
switch(tagText) switch(tagText)
{ {
case "b", "bold": case "b", "bold":
@@ -348,37 +414,24 @@ namespace GlitchyEngine.Renderer.Text
// Text case control // Text case control
case "lc", "lowercase": case "lc", "lowercase":
if (isEndTag && textCaseStack.Count > 0) textCaseStack.PushButPopIfTrue(.LowerCase, isEndTag);
textCaseStack.PopBack();
else
textCaseStack.Add(.LowerCase);
case "uc", "uppercase": case "uc", "uppercase":
if (isEndTag && textCaseStack.Count > 0) if (isEndTag)
textCaseStack.PopBack(); textCaseStack.PushButPopIfTrue(.UpperCase, isEndTag);
else
textCaseStack.Add(.UpperCase);
case "rc", "retaincase": case "rc", "retaincase":
if (isEndTag && textCaseStack.Count > 0) textCaseStack.PushButPopIfTrue(.RetainCase, isEndTag);
textCaseStack.PopBack();
else
textCaseStack.Add(.RetainCase);
case "ic", "invertcase": case "ic", "invertcase":
if (isEndTag && textCaseStack.Count > 0) textCaseStack.PushButPopIfTrue(.InvertCase, isEndTag);
textCaseStack.PopBack();
else
textCaseStack.Add(.InvertCase);
} }
// Skip the tag in the main enumerator // Skip the tag in the main enumerator
@char.NextIndex = endIndex; @char.NextIndex = endIndex;
continue; continue;
} }
escapeNextChar = false; escapeNextChar = false;
switch (textCaseStack.Back) switch (textCaseStack.CurrentValue())
{ {
case .RetainCase: case .RetainCase:
// Nothing to do // Nothing to do
@@ -1,11 +1,20 @@
using System; using System;
using static GlitchyEngine.Renderer.Text.FontRenderer;
namespace GlitchyEngine.World.Components; namespace GlitchyEngine.World.Components;
enum TextRendererFlags
{
IsRichText = 1,
NeedsRebuild = 2
}
struct TextRendererComponent : IDisposableComponent struct TextRendererComponent : IDisposableComponent
{ {
private String _text; private String _text;
private PreparedText _preparedText;
private bool _isRichText; private TextRendererFlags _flags;
public StringView Text public StringView Text
{ {
@@ -19,14 +28,27 @@ struct TextRendererComponent : IDisposableComponent
} }
} }
public PreparedText PreparedText
{
get => _preparedText;
set mut => SetReference!(_preparedText, value);
}
public bool IsRichText public bool IsRichText
{ {
get => _isRichText; get => _flags.HasFlag(.IsRichText);
set mut => _isRichText = value; set mut => Enum.SetFlagConditionally(ref _flags, .IsRichText, value);
}
public bool NeedsRebuild
{
get => _flags.HasFlag(.NeedsRebuild);
set mut => Enum.SetFlagConditionally(ref _flags, .NeedsRebuild, value);
} }
public void Dispose() public void Dispose()
{ {
delete _text; delete _text;
_preparedText?.ReleaseRef();
} }
} }
+24
View File
@@ -10,6 +10,8 @@ using GlitchyEngine.Math;
using GlitchyEngine.Scripting.Classes; using GlitchyEngine.Scripting.Classes;
using GlitchyEngine.Serialization; using GlitchyEngine.Serialization;
using GlitchyEngine.World.Components; using GlitchyEngine.World.Components;
using GlitchyEngine.Renderer.Text;
using static GlitchyEngine.Renderer.Text.FontRenderer;
namespace GlitchyEngine.World namespace GlitchyEngine.World
{ {
@@ -55,6 +57,8 @@ namespace GlitchyEngine.World
cameraEntity cameraEntity
}; };
Font _font ~ _.ReleaseRef();
/// Gets or sets the name of the scene. /// Gets or sets the name of the scene.
public StringView Name public StringView Name
@@ -117,6 +121,8 @@ namespace GlitchyEngine.World
scene.InitPolygonCollider2D(e, collider); scene.InitPolygonCollider2D(e, collider);
} }
}); });
_font = new Font(@"C:\Windows\Fonts\arial.ttf", 24);
} }
public ~this() public ~this()
@@ -768,6 +774,22 @@ namespace GlitchyEngine.World
} }
} }
private void BuildTexts()
{
for (let (entity, textRenderer) in _ecsWorld.Enumerate<TextRendererComponent>())
{
if (textRenderer.NeedsRebuild || textRenderer.PreparedText == null)
{
using (PreparedText preparedText = FontRenderer.PrepareText(_font, textRenderer.Text, 24, .Black))
{
textRenderer.PreparedText = preparedText;
}
textRenderer.NeedsRebuild = false;
}
}
}
public void Update(GameTime gameTime, UpdateMode mode) public void Update(GameTime gameTime, UpdateMode mode)
{ {
Debug.Profiler.ProfileRendererFunction!(); Debug.Profiler.ProfileRendererFunction!();
@@ -807,6 +829,8 @@ namespace GlitchyEngine.World
_destroyQueue.Clear(); _destroyQueue.Clear();
} }
BuildTexts();
} }
/// Creates a new Entity with the given name. /// Creates a new Entity with the given name.
+2 -12
View File
@@ -25,10 +25,6 @@ class SceneRenderer
public RenderTargetGroup CompositeTarget => _compositeTarget; public RenderTargetGroup CompositeTarget => _compositeTarget;
Font _font ~ _.ReleaseRef();
FontRenderer.PreparedText _smallLinesInfo;
public this() public this()
{ {
RenderTargetGroupDescription desc = .(100, 100, RenderTargetGroupDescription desc = .(100, 100,
@@ -52,8 +48,6 @@ class SceneRenderer
_cameraTarget.[Friend]Identifier = "Camera Target"; _cameraTarget.[Friend]Identifier = "Camera Target";
_gammaCorrectEffect = Content.LoadAsset("Resources/Shaders/GammaCorrect.hlsl"); _gammaCorrectEffect = Content.LoadAsset("Resources/Shaders/GammaCorrect.hlsl");
_font = new Font(@"C:\Windows\Fonts\arial.ttf", 24);
} }
/// Sets the size of the viewport into which the scene will be rendered. /// Sets the size of the viewport into which the scene will be rendered.
@@ -214,14 +208,10 @@ class SceneRenderer
for (var (entity, transform, text, editorFlags) in Scene._ecsWorld.Enumerate<TransformComponent, TextRendererComponent, EditorFlagsComponent>()) for (var (entity, transform, text, editorFlags) in Scene._ecsWorld.Enumerate<TransformComponent, TextRendererComponent, EditorFlagsComponent>())
{ {
if (editorFlags.Flags.HasFlag(.HideInScene)) if (editorFlags.Flags.HasFlag(.HideInScene) || text.PreparedText == null)
continue; continue;
_smallLinesInfo = FontRenderer.PrepareText(_font, text.Text, 24, .Black); FontRenderer.DrawText(text.PreparedText, transform.WorldTransform * Matrix.Scaling(1.0f / 24.0f));
FontRenderer.DrawText(_smallLinesInfo, transform.WorldTransform * Matrix.Scaling(1.0f / 24.0f));
_smallLinesInfo.ReleaseRef();
} }
//FontRenderer.DrawText(_smallLinesInfo, 0, 0); //FontRenderer.DrawText(_smallLinesInfo, 0, 0);