mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-01 01:27:13 +02:00
387 lines
14 KiB
C#
387 lines
14 KiB
C#
using System;
|
|
using System.Numerics;
|
|
using System.Text;
|
|
using Robust.Client.ResourceManagement;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Maths;
|
|
|
|
namespace Robust.Client.Graphics
|
|
{
|
|
/// <summary>
|
|
/// Describes an outline drawn around text.
|
|
/// </summary>
|
|
public readonly record struct TextOutline(float Thickness, Color Color)
|
|
{
|
|
/// <summary>
|
|
/// The default text outline.
|
|
/// </summary>
|
|
public static TextOutline Default => new(1f, Color.Black);
|
|
|
|
/// <summary>
|
|
/// Creates an outline from optional settings.
|
|
/// </summary>
|
|
public static TextOutline? FromOverrides(float? thickness, Color? color)
|
|
{
|
|
if (thickness is null && color is null)
|
|
return null;
|
|
|
|
var actualThickness = thickness ?? Default.Thickness;
|
|
return actualThickness > 0
|
|
? new TextOutline(actualThickness, color ?? Default.Color)
|
|
: null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A generic font for rendering of text.
|
|
/// Does not contain properties such as size. Those are specific to children such as <see cref="VectorFont" />
|
|
/// </summary>
|
|
public abstract class Font
|
|
{
|
|
/// <summary>
|
|
/// The maximum amount a glyph goes above the baseline, in pixels.
|
|
/// </summary>
|
|
public abstract int GetAscent(float scale);
|
|
|
|
/// <summary>
|
|
/// The maximum glyph height of a line of text in pixels, not relative to the baseline.
|
|
/// </summary>
|
|
public abstract int GetHeight(float scale);
|
|
|
|
/// <summary>
|
|
/// The maximum amount a glyph drops below the baseline, in pixels.
|
|
/// </summary>
|
|
public abstract int GetDescent(float scale);
|
|
|
|
/// <summary>
|
|
/// The distance between the baselines of two consecutive lines, in pixels.
|
|
/// Basically, if you encounter a new line, this is how much you need to move down the cursor.
|
|
/// </summary>
|
|
public abstract int GetLineHeight(float scale);
|
|
|
|
/// <summary>
|
|
/// The distance between the edges of two consecutive lines, in pixels.
|
|
/// </summary>
|
|
public int GetLineSeparation(float scale)
|
|
{
|
|
return GetLineHeight(scale) - GetHeight(scale);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw a character at a certain baseline position on screen.
|
|
/// </summary>
|
|
/// <param name="handle">The drawing handle to draw to.</param>
|
|
/// <param name="rune">The Unicode code point to draw.</param>
|
|
/// <param name="baseline">The baseline from which to draw the character.</param>
|
|
/// <param name="scale">DPI scale factor to render the font at.</param>
|
|
/// <param name="color">The color of the character to draw.</param>
|
|
/// <param name="fallback">If the character is not available, render "�" instead.</param>
|
|
/// <returns>How much to advance the cursor to draw the next character.</returns>
|
|
public abstract float DrawChar(
|
|
DrawingHandleBase handle, Rune rune, Vector2 baseline, float scale,
|
|
Color color, bool fallback=true);
|
|
|
|
/// <summary>
|
|
/// Draws only the outline of a character and returns its advance.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Rendering outlines separately from glyph fills allows text renderers to batch both passes by texture.
|
|
/// </remarks>
|
|
public abstract float DrawCharOutline(
|
|
DrawingHandleBase handle, Rune rune, Vector2 baseline, float scale,
|
|
TextOutline outline, bool fallback=true);
|
|
|
|
/// <summary>
|
|
/// Gets metrics describing the dimensions and positioning of a single glyph in the font.
|
|
/// </summary>
|
|
/// <param name="rune">The unicode codepoint to fetch the glyph metrics for.</param>
|
|
/// <param name="scale">DPI scale factor to render the font at.</param>
|
|
/// <param name="fallback">
|
|
/// If the character is not available, return data for "�" instead.
|
|
/// This can still fail if the font does not define � itself.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>null</c> if this font does not have a glyph for the specified character,
|
|
/// otherwise the metrics you asked for.
|
|
/// </returns>
|
|
/// <seealso cref="TryGetCharMetrics"/>
|
|
public abstract CharMetrics? GetCharMetrics(Rune rune, float scale, bool fallback=true);
|
|
|
|
/// <summary>
|
|
/// Try-pattern version of <see cref="GetCharMetrics"/>.
|
|
/// </summary>
|
|
public bool TryGetCharMetrics(Rune rune, float scale, out CharMetrics metrics, bool fallback=true)
|
|
{
|
|
var maybe = GetCharMetrics(rune, scale);
|
|
if (maybe.HasValue)
|
|
{
|
|
metrics = maybe.Value;
|
|
return true;
|
|
}
|
|
|
|
metrics = default;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Font type that renders vector fonts such as OTF and TTF fonts from a <see cref="FontResource"/>
|
|
/// </summary>
|
|
public sealed class VectorFont : Font
|
|
{
|
|
public int Size { get; }
|
|
|
|
internal IFontInstanceHandle Handle { get; }
|
|
|
|
public VectorFont(FontResource res, int size)
|
|
{
|
|
Size = size;
|
|
Handle = IoCManager.Resolve<IFontManagerInternal>().MakeInstance(res.FontFaceHandle, size);
|
|
}
|
|
|
|
internal VectorFont(IFontInstanceHandle handle, int size)
|
|
{
|
|
Size = size;
|
|
Handle = handle;
|
|
}
|
|
|
|
public override int GetAscent(float scale) => Handle.GetAscent(scale);
|
|
public override int GetHeight(float scale) => Handle.GetHeight(scale);
|
|
public override int GetDescent(float scale) => Handle.GetDescent(scale);
|
|
public override int GetLineHeight(float scale) => Handle.GetLineHeight(scale);
|
|
|
|
public override float DrawChar(DrawingHandleBase handle, Rune rune, Vector2 baseline, float scale, Color color, bool fallback=true)
|
|
{
|
|
if (!TryGetGlyph(rune, scale, fallback, 0, out var metrics, out var texture, out _))
|
|
return 0;
|
|
|
|
if (texture == null)
|
|
return metrics.Advance;
|
|
|
|
var glyphPosition = baseline + new Vector2(metrics.BearingX, -metrics.BearingY);
|
|
DrawGlyph(handle, texture, glyphPosition, color);
|
|
return metrics.Advance;
|
|
}
|
|
|
|
public override float DrawCharOutline(
|
|
DrawingHandleBase handle, Rune rune, Vector2 baseline, float scale,
|
|
TextOutline outline, bool fallback=true)
|
|
{
|
|
if (!TryGetGlyph(rune, scale, fallback, outline.Thickness, out var metrics, out var texture, out var outlinedGlyph))
|
|
return 0;
|
|
|
|
if (outline.Thickness > 0 && texture != null && outlinedGlyph is { } glyph)
|
|
{
|
|
var outlinePosition = baseline + new Vector2(glyph.Left, -glyph.Top);
|
|
DrawGlyph(handle, glyph.Texture, outlinePosition, outline.Color);
|
|
}
|
|
|
|
return metrics.Advance;
|
|
}
|
|
|
|
private bool TryGetGlyph(
|
|
Rune rune,
|
|
float scale,
|
|
bool fallback,
|
|
float outlineThickness,
|
|
out CharMetrics metrics,
|
|
out Texture? texture,
|
|
out OutlinedGlyph? outlinedGlyph)
|
|
{
|
|
if (Handle.TryGetGlyph(rune, scale, outlineThickness, out metrics, out texture, out outlinedGlyph))
|
|
return true;
|
|
|
|
if (!fallback || Rune.IsWhiteSpace(rune))
|
|
return false;
|
|
|
|
return Handle.TryGetGlyph(new Rune('�'), scale, outlineThickness, out metrics, out texture, out outlinedGlyph);
|
|
}
|
|
|
|
private static void DrawGlyph(DrawingHandleBase handle, Texture texture, Vector2 position, Color color)
|
|
{
|
|
if (handle is DrawingHandleWorld worldhandle)
|
|
worldhandle.DrawTextureRect(texture, Box2.FromDimensions(position, texture.Size), color);
|
|
else
|
|
handle.DrawTexture(texture, position, color);
|
|
}
|
|
|
|
public override CharMetrics? GetCharMetrics(Rune rune, float scale, bool fallback=true)
|
|
{
|
|
var metrics = Handle.GetCharMetrics(rune, scale);
|
|
if (metrics == null && !Rune.IsWhiteSpace(rune) && fallback)
|
|
return Handle.GetCharMetrics(new Rune('�'), scale);
|
|
return metrics;
|
|
}
|
|
}
|
|
|
|
public sealed class StackedFont : Font
|
|
{
|
|
// _main is the "default" font; the top of the Stack.
|
|
public readonly Font _main;
|
|
public readonly Font[] Stack;
|
|
|
|
public StackedFont(params Font[] args)
|
|
{
|
|
if (args.Length < 1)
|
|
throw new ArgumentException("At least one font is required");
|
|
|
|
Stack = args;
|
|
_main = args[0];
|
|
}
|
|
|
|
// All metrics methods use the default font (_main).
|
|
// Technically these could vary between stacked fonts, but that is a case
|
|
// that really should already be avoided for so many other reasons.
|
|
public override int GetAscent(float scale) => _main.GetAscent(scale);
|
|
public override int GetHeight(float scale) => _main.GetHeight(scale);
|
|
public override int GetDescent(float scale) => _main.GetDescent(scale);
|
|
public override int GetLineHeight(float scale) => _main.GetLineHeight(scale);
|
|
|
|
// DrawChar just proxies to the stack, or invokes _main's fallback.
|
|
public override float DrawChar(DrawingHandleBase handle, Rune rune, Vector2 baseline, float scale, Color color, bool fallback=true)
|
|
{
|
|
foreach (var f in Stack)
|
|
{
|
|
var w = f.DrawChar(handle, rune, baseline, scale, color, fallback: false);
|
|
if (w != 0f)
|
|
return w;
|
|
}
|
|
|
|
if (fallback)
|
|
return _main.DrawChar(handle, rune, baseline, scale, color, fallback: true);
|
|
|
|
return 0f;
|
|
}
|
|
|
|
public override float DrawCharOutline(
|
|
DrawingHandleBase handle, Rune rune, Vector2 baseline, float scale,
|
|
TextOutline outline, bool fallback=true)
|
|
{
|
|
foreach (var f in Stack)
|
|
{
|
|
var w = f.DrawCharOutline(handle, rune, baseline, scale, outline, fallback: false);
|
|
if (w != 0f)
|
|
return w;
|
|
}
|
|
|
|
if (fallback)
|
|
return _main.DrawCharOutline(handle, rune, baseline, scale, outline, fallback: true);
|
|
|
|
return 0f;
|
|
}
|
|
|
|
public override CharMetrics? GetCharMetrics(Rune rune, float scale, bool fallback=true)
|
|
{
|
|
foreach (var f in Stack)
|
|
{
|
|
var m = f.GetCharMetrics(rune, scale, fallback: false);
|
|
if (m != null)
|
|
return m;
|
|
}
|
|
|
|
if (!Rune.IsWhiteSpace(rune) && fallback)
|
|
return _main.GetCharMetrics(rune, scale, fallback: true);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public sealed class DummyFont : Font
|
|
{
|
|
public override int GetAscent(float scale) => default;
|
|
public override int GetHeight(float scale) => default;
|
|
public override int GetDescent(float scale) => default;
|
|
public override int GetLineHeight(float scale) => default;
|
|
|
|
public override float DrawChar(DrawingHandleBase handle, Rune rune, Vector2 baseline, float scale, Color color, bool fallback=true)
|
|
{
|
|
// Nada, it's a dummy after all.
|
|
return 0;
|
|
}
|
|
|
|
public override float DrawCharOutline(
|
|
DrawingHandleBase handle, Rune rune, Vector2 baseline, float scale,
|
|
TextOutline outline, bool fallback=true)
|
|
{
|
|
// Nada, it's a dummy after all.
|
|
return 0;
|
|
}
|
|
|
|
public override CharMetrics? GetCharMetrics(Rune rune, float scale, bool fallback=true)
|
|
{
|
|
// Nada, it's a dummy after all.
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Possible values for font weights. Larger values have thicker font strokes.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// These values are based on the <c>usWeightClass</c> property of the OpenType specification:
|
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2#usweightclass
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <seealso cref="ISystemFontFace.Weight"/>
|
|
public enum FontWeight : ushort
|
|
{
|
|
Thin = 100,
|
|
ExtraLight = 200,
|
|
UltraLight = ExtraLight,
|
|
Light = 300,
|
|
SemiLight = 350,
|
|
Normal = 400,
|
|
Regular = Normal,
|
|
Medium = 500,
|
|
SemiBold = 600,
|
|
DemiBold = SemiBold,
|
|
Bold = 700,
|
|
ExtraBold = 800,
|
|
UltraBold = ExtraBold,
|
|
Black = 900,
|
|
Heavy = Black,
|
|
ExtraBlack = 950,
|
|
UltraBlack = ExtraBlack,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Possible slant values for fonts.
|
|
/// </summary>
|
|
/// <seealso cref="ISystemFontFace.Slant"/>
|
|
public enum FontSlant : byte
|
|
{
|
|
// NOTE: Enum values correspond to DWRITE_FONT_STYLE.
|
|
Normal = 0,
|
|
Oblique = 1,
|
|
|
|
// FUN FACT: they're called "italics" because they look like the Leaning Tower of Pisa.
|
|
// Don't fact-check that.
|
|
Italic = 2
|
|
}
|
|
|
|
/// <summary>
|
|
/// Possible values for font widths. Larger values are proportionally wider.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// These values are based on the <c>usWidthClass</c> property of the OpenType specification:
|
|
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2#uswidthclass
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <seealso cref="ISystemFontFace.Width"/>
|
|
public enum FontWidth : ushort
|
|
{
|
|
UltraCondensed = 1,
|
|
ExtraCondensed = 2,
|
|
Condensed = 3,
|
|
SemiCondensed = 4,
|
|
Normal = 5,
|
|
Medium = Normal,
|
|
SemiExpanded = 6,
|
|
Expanded = 7,
|
|
ExtraExpanded = 8,
|
|
UltraExpanded = 9,
|
|
}
|
|
}
|