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