using System;
using System.Numerics;
using System.Text;
using Robust.Client.ResourceManagement;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics
{
///
/// A generic font for rendering of text.
/// Does not contain properties such as size. Those are specific to children such as
///
public abstract class Font
{
///
/// The maximum amount a glyph goes above the baseline, in pixels.
///
public abstract int GetAscent(float scale);
///
/// The maximum glyph height of a line of text in pixels, not relative to the baseline.
///
public abstract int GetHeight(float scale);
///
/// The maximum amount a glyph drops below the baseline, in pixels.
///
public abstract int GetDescent(float scale);
///
/// 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.
///
public abstract int GetLineHeight(float scale);
///
/// The distance between the edges of two consecutive lines, in pixels.
///
public int GetLineSeparation(float scale)
{
return GetLineHeight(scale) - GetHeight(scale);
}
///
/// Draw a character at a certain baseline position on screen.
///
/// The drawing handle to draw to.
/// The Unicode code point to draw.
/// The baseline from which to draw the character.
/// DPI scale factor to render the font at.
/// The color of the character to draw.
/// If the character is not available, render "�" instead.
/// How much to advance the cursor to draw the next character.
public abstract float DrawChar(
DrawingHandleBase handle, Rune rune, Vector2 baseline, float scale,
Color color, bool fallback=true);
///
/// Gets metrics describing the dimensions and positioning of a single glyph in the font.
///
/// The unicode codepoint to fetch the glyph metrics for.
/// DPI scale factor to render the font at.
///
/// If the character is not available, return data for "�" instead.
/// This can still fail if the font does not define � itself.
///
///
/// null if this font does not have a glyph for the specified character,
/// otherwise the metrics you asked for.
///
///
public abstract CharMetrics? GetCharMetrics(Rune rune, float scale, bool fallback=true);
///
/// Try-pattern version of .
///
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;
}
}
///
/// Font type that renders vector fonts such as OTF and TTF fonts from a
///
public sealed class VectorFont : Font
{
public int Size { get; }
internal IFontInstanceHandle Handle { get; }
public VectorFont(FontResource res, int size)
{
Size = size;
Handle = IoCManager.Resolve().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)
{
var metrics = Handle.GetCharMetrics(rune, scale);
if (!metrics.HasValue)
{
if (fallback && !Rune.IsWhiteSpace(rune))
{
rune = new Rune('�');
metrics = Handle.GetCharMetrics(rune, scale);
if (!metrics.HasValue)
return 0;
}
else
return 0;
}
var texture = Handle.GetCharTexture(rune, scale);
if (texture == null)
{
return metrics.Value.Advance;
}
baseline += new Vector2(metrics.Value.BearingX, -metrics.Value.BearingY);
if(handle is DrawingHandleWorld worldhandle)
worldhandle.DrawTextureRect(texture, Box2.FromDimensions(baseline, texture.Size), color);
else
handle.DrawTexture(texture, baseline, color);
return metrics.Value.Advance;
}
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 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 CharMetrics? GetCharMetrics(Rune rune, float scale, bool fallback=true)
{
// Nada, it's a dummy after all.
return null;
}
}
///
/// Possible values for font weights. Larger values have thicker font strokes.
///
///
///
/// These values are based on the usWeightClass property of the OpenType specification:
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2#usweightclass
///
///
///
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,
}
///
/// Possible slant values for fonts.
///
///
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
}
///
/// Possible values for font widths. Larger values are proportionally wider.
///
///
///
/// These values are based on the usWidthClass property of the OpenType specification:
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2#uswidthclass
///
///
///
public enum FontWidth : ushort
{
UltraCondensed = 1,
ExtraCondensed = 2,
Condensed = 3,
SemiCondensed = 4,
Normal = 5,
Medium = Normal,
SemiExpanded = 6,
Expanded = 7,
ExtraExpanded = 8,
UltraExpanded = 9,
}
}