diff --git a/engine/Sandbox.Engine/Editor/Gizmos/Draw/Draw.cs b/engine/Sandbox.Engine/Editor/Gizmos/Draw/Draw.cs index d6595692..e617c0cd 100644 --- a/engine/Sandbox.Engine/Editor/Gizmos/Draw/Draw.cs +++ b/engine/Sandbox.Engine/Editor/Gizmos/Draw/Draw.cs @@ -209,6 +209,20 @@ public static partial class Gizmo so.TextFlags = flags; } + /// + /// Draw text with a text rendering scope for more text rendering customization. + /// + public void ScreenText( TextRendering.Scope text, Vector2 pos, TextFlag flags = TextFlag.LeftTop ) + { + var so = Active.FindOrCreate( $"text", () => new TextSceneObject( World ) ); + + so.TextBlock = text; + so.Transform = Transform.Zero; + so.ScreenPos = pos; + so.Bounds = BBox.FromPositionAndSize( 0, float.MaxValue ); + so.TextFlags = flags; + } + /// /// Draw text on screen at a 3d position /// @@ -220,6 +234,17 @@ public static partial class Gizmo ScreenText( text, screen + offset, font, size, flags ); } + /// + /// Draw text on screen at a 3d position with a text rendering scope for more text rendering customization. + /// + public void ScreenText( TextRendering.Scope text, Vector3 worldPos, Vector2 offset, TextFlag flags = TextFlag.LeftTop ) + { + if ( !Camera.ToScreen( worldPos, out var screen ) ) + return; + + ScreenText( text, screen + offset, flags ); + } + /// /// Draw a rect, on the screen /// diff --git a/engine/Sandbox.Engine/Resources/Textures/Bitmap/Bitmap.Text.cs b/engine/Sandbox.Engine/Resources/Textures/Bitmap/Bitmap.Text.cs index 343a9326..023a87cc 100644 --- a/engine/Sandbox.Engine/Resources/Textures/Bitmap/Bitmap.Text.cs +++ b/engine/Sandbox.Engine/Resources/Textures/Bitmap/Bitmap.Text.cs @@ -9,7 +9,7 @@ public partial class Bitmap /// public void DrawText( TextRendering.Scope scope, Rect rect, TextFlag flags = TextFlag.Center ) { - using var block = new TextRendering.TextBlock( scope, new Vector2( 0, 0 ), flags, FontSmooth.Always ); + using var block = new TextRendering.TextBlock( scope, new Vector2( 0, 0 ), flags ); block.Render( _canvas, rect ); } } diff --git a/engine/Sandbox.Engine/Systems/Render/TextRendering/TextRendering.Scope.cs b/engine/Sandbox.Engine/Systems/Render/TextRendering/TextRendering.Scope.cs index 9b4e1e54..35e43d78 100644 --- a/engine/Sandbox.Engine/Systems/Render/TextRendering/TextRendering.Scope.cs +++ b/engine/Sandbox.Engine/Systems/Render/TextRendering/TextRendering.Scope.cs @@ -34,6 +34,7 @@ public static partial class TextRendering [JsonInclude] public float WordSpacing; [JsonInclude] public Rendering.FilterMode FilterMode; + [JsonInclude] public UI.FontSmooth FontSmooth; // this seems stupid, but it's like this because a list would be a pain // and also would be a bunch of bullshit to hashcode. This is fast. @@ -67,6 +68,7 @@ public static partial class TextRendering hc.Add( Shadow ); hc.Add( ShadowUnder ); hc.Add( FilterMode ); + hc.Add( FontSmooth ); return hc.ToHashCode(); } diff --git a/engine/Sandbox.Engine/Systems/Render/TextRendering/TextRendering.TextBlock.cs b/engine/Sandbox.Engine/Systems/Render/TextRendering/TextRendering.TextBlock.cs index 20d05e8f..9e28ab63 100644 --- a/engine/Sandbox.Engine/Systems/Render/TextRendering/TextRendering.TextBlock.cs +++ b/engine/Sandbox.Engine/Systems/Render/TextRendering/TextRendering.TextBlock.cs @@ -14,47 +14,31 @@ public static partial class TextRendering public TextFlag Flags; public Vector2 Clip; - public FontSmooth Smooth; public bool IsEmpty; public RealTimeSince TimeSinceUsed; - TextRendering.Scope Scope; - - + Scope _scope; Margin _effectMargin = default; - public TextBlock( string text, Color color, string font, float size, int fontWeight, Vector2 clip, TextFlag flag, FontSmooth? smooth = FontSmooth.Auto ) + public TextBlock( Scope scope, Vector2 clip, TextFlag flag ) { Assert.False( Application.IsHeadless ); Flags = flag; Clip = clip; - Smooth = smooth ?? FontSmooth.Auto; - - Initialize( new TextRendering.Scope { Text = text, TextColor = color, FontName = font, FontSize = size, FontWeight = fontWeight } ); - } - - public TextBlock( Scope scope, Vector2 clip, TextFlag flag, FontSmooth? smooth = FontSmooth.Auto ) - { - Assert.False( Application.IsHeadless ); - - Flags = flag; - Clip = clip; - Smooth = smooth ?? FontSmooth.Auto; Initialize( scope ); } public TextBlock() { - } - internal void Initialize( TextRendering.Scope scope ) + internal void Initialize( Scope scope ) { - Scope = scope; - IsEmpty = string.IsNullOrEmpty( Scope.Text ); + _scope = scope; + IsEmpty = string.IsNullOrEmpty( _scope.Text ); _effectMargin = default; @@ -134,13 +118,13 @@ public static partial class TextRendering } var style = new Topten.RichTextKit.Style(); - Scope.ToStyle( style ); + _scope.ToStyle( style ); - block.AddText( Scope.Text, style ); + block.AddText( _scope.Text, style ); var o = new Topten.RichTextKit.TextPaintOptions { - Edging = Smooth switch + Edging = _scope.FontSmooth switch { FontSmooth.Never => SKFontEdging.Alias, _ => SKFontEdging.Antialias, @@ -190,9 +174,9 @@ public static partial class TextRendering // Build text block // var style = new Topten.RichTextKit.Style(); - Scope.ToStyle( style ); + _scope.ToStyle( style ); - block.AddText( IsEmpty ? "." : Scope.Text, style ); + block.AddText( IsEmpty ? "." : _scope.Text, style ); // // Build Text @@ -215,7 +199,7 @@ public static partial class TextRendering { var o = new Topten.RichTextKit.TextPaintOptions { - Edging = Smooth switch + Edging = _scope.FontSmooth switch { FontSmooth.Never => SKFontEdging.Alias, _ => SKFontEdging.Antialias, diff --git a/engine/Sandbox.Engine/Systems/Render/TextRendering/TextRendering.cs b/engine/Sandbox.Engine/Systems/Render/TextRendering/TextRendering.cs index 9ccd4d99..92d04801 100644 --- a/engine/Sandbox.Engine/Systems/Render/TextRendering/TextRendering.cs +++ b/engine/Sandbox.Engine/Systems/Render/TextRendering/TextRendering.cs @@ -20,7 +20,7 @@ public static partial class TextRendering /// /// Create a texture from the scope. The texture will either be a cached version or will be rendered immediately /// - public static Texture GetOrCreateTexture( in Scope scope, Vector2 clip = default, TextFlag flag = TextFlag.LeftTop, FontSmooth smooth = FontSmooth.Auto ) + public static Texture GetOrCreateTexture( in Scope scope, Vector2 clip = default, TextFlag flag = TextFlag.LeftTop ) { if ( Application.IsHeadless ) return Texture.Invalid; @@ -31,7 +31,6 @@ public static partial class TextRendering hc.Add( scope ); hc.Add( clip ); hc.Add( flag ); - hc.Add( smooth ); // TextManager is caching this right now var tb = GetOrCreateTextBlock( hc.ToHashCode(), out bool created ); @@ -40,7 +39,6 @@ public static partial class TextRendering { tb.Clip = clip; tb.Flags = flag; - tb.Smooth = FontSmooth.Auto; tb.Initialize( scope ); } diff --git a/engine/ThirdParty/Topten.RichTextKit/FontRun.cs b/engine/ThirdParty/Topten.RichTextKit/FontRun.cs index 8cb536f7..f683c548 100644 --- a/engine/ThirdParty/Topten.RichTextKit/FontRun.cs +++ b/engine/ThirdParty/Topten.RichTextKit/FontRun.cs @@ -538,9 +538,6 @@ namespace Topten.RichTextKit glyphVOffset = Style.FontSize * 0.1f; } - // Get glyph positions - var glyphPositions = GlyphPositions.ToArray(); - // Create the font if ( _font == null ) { @@ -872,6 +869,16 @@ namespace Topten.RichTextKit using var effectPaint = new SKPaint(); foreach ( var effect in Style.TextEffects ) { + // Aliased 1 pixel outline needs to be rendered in a different way to look good. + if ( ctx.Options.Edging == SKFontEdging.Alias && + effect.PaintStyle == SKPaintStyle.StrokeAndFill && + effect.Width == 1 ) + { + PaintPixelOutline( ctx, effect.Color ); + + continue; + } + effectPaint.Style = effect.PaintStyle; effectPaint.StrokeWidth = effect.Width; effectPaint.StrokeJoin = effect.StrokeJoin; @@ -885,6 +892,49 @@ namespace Topten.RichTextKit } } + static readonly SKPoint[] PixelOutlineOffsets = + { + new( -1, 0 ), + new( 1, 0 ), + new( 0, -1 ), + new( 0, 1 ), + new( -1, -1 ), + new( -1, 1 ), + new( 1, -1 ), + new( 1, 1 ), + }; + + unsafe void PaintPixelOutline( PaintTextContext ctx, SKColor color ) + { + using var pixelPaint = new SKPaint + { + Color = color, + IsAntialias = false, + }; + + // Override font edging. + var edging = _font.Edging; + _font.Edging = SKFontEdging.Alias; + + fixed ( ushort* pGlyphs = Glyphs.Underlying ) + { + var textBlob = SKTextBlob.CreatePositioned( + (IntPtr)(pGlyphs + Glyphs.Start), + Glyphs.Length * sizeof( ushort ), + SKTextEncoding.GlyphId, + _font, + GlyphPositions.AsSpan() ); + + foreach ( var o in PixelOutlineOffsets ) + { + ctx.Canvas.DrawText( textBlob, o.X, o.Y, pixelPaint ); + } + } + + // Restore font edging. + _font.Edging = edging; + } + SKTextBlob _textBlob; SKFont _font;