Text rendering scope font smooth & 1 px aliased outline support (#3642) https://files.facepunch.com/laylad/1b1811b1/sbox-dev_CExpJeBiL5.png

This commit is contained in:
Layla
2025-12-18 14:47:05 +00:00
committed by GitHub
parent f241f16212
commit 3844a66923
6 changed files with 93 additions and 34 deletions

View File

@@ -209,6 +209,20 @@ public static partial class Gizmo
so.TextFlags = flags;
}
/// <summary>
/// Draw text with a text rendering scope for more text rendering customization.
/// </summary>
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;
}
/// <summary>
/// Draw text on screen at a 3d position
/// </summary>
@@ -220,6 +234,17 @@ public static partial class Gizmo
ScreenText( text, screen + offset, font, size, flags );
}
/// <summary>
/// Draw text on screen at a 3d position with a text rendering scope for more text rendering customization.
/// </summary>
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 );
}
/// <summary>
/// Draw a rect, on the screen
/// </summary>

View File

@@ -9,7 +9,7 @@ public partial class Bitmap
/// </summary>
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 );
}
}

View File

@@ -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();
}

View File

@@ -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,

View File

@@ -20,7 +20,7 @@ public static partial class TextRendering
/// <summary>
/// Create a texture from the scope. The texture will either be a cached version or will be rendered immediately
/// </summary>
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 );
}

View File

@@ -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;