using System.Collections.Generic; using System.Text; using System; using FontStashSharp.Interfaces; using System.Linq; #if MONOGAME || FNA using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; #elif STRIDE using Stride.Core.Mathematics; using Stride.Graphics; using Texture2D = Stride.Graphics.Texture; #else using System.Drawing; using System.Numerics; using Matrix = System.Numerics.Matrix3x2; using Texture2D = Veldrid.Texture; #endif namespace FontStashSharp { public abstract partial class SpriteFontBase { private static Texture2D _white; /// /// Font Size /// public float FontSize { get; private set; } /// /// Line Height in pixels /// public int LineHeight { get; private set; } protected float RenderFontSizeMultiplicator { get; set; } = 1f; protected SpriteFontBase(float fontSize, int lineHeight) { FontSize = fontSize; LineHeight = lineHeight; } #if MONOGAME || FNA || STRIDE protected internal abstract FontGlyph GetGlyph(GraphicsDevice device, int codepoint); #else protected internal abstract FontGlyph GetGlyph(ITexture2DManager device, int codepoint); #endif internal abstract void PreDraw(TextSource str, out int ascent, out int lineHeight); private void Prepare(Vector2 position, ref Vector2 scale, float rotation, Vector2 origin, out Matrix transformation) { scale /= RenderFontSizeMultiplicator; Utility.BuildTransform(position, scale, rotation, origin, out transformation); } internal virtual Bounds InternalTextBounds(TextSource source, Vector2 position, float characterSpacing, float lineSpacing) { if (source.IsNull) return Bounds.Empty; int ascent, lineHeight; PreDraw(source, out ascent, out lineHeight); var x = position.X; var y = position.Y; y += ascent; float minx, maxx, miny, maxy; minx = maxx = x; miny = maxy = y; float startx = x; FontGlyph prevGlyph = null; while (true) { int codepoint; if (!source.GetNextCodepoint(out codepoint)) break; if (codepoint == '\n') { x = startx; y += lineHeight + lineSpacing; prevGlyph = null; continue; } var glyph = GetGlyph(null, codepoint); if (glyph == null) { continue; } if (prevGlyph != null) { x += characterSpacing; x += GetKerning(glyph, prevGlyph); } var x0 = x + glyph.RenderOffset.X; if (x0 < minx) minx = x0; x += glyph.XAdvance; if (x > maxx) maxx = x; var y0 = y + glyph.RenderOffset.Y; var y1 = y0 + glyph.Size.Y; if (y0 < miny) miny = y0; if (y1 > maxy) maxy = y1; prevGlyph = glyph; } return new Bounds(minx, miny, maxx, maxy); } public Bounds TextBounds(string text, Vector2 position, Vector2? scale = null, float characterSpacing = 0.0f, float lineSpacing = 0.0f) { var bounds = InternalTextBounds(new TextSource(text), position, characterSpacing, lineSpacing); var realScale = scale ?? Utility.DefaultScale; bounds.ApplyScale(realScale / RenderFontSizeMultiplicator); return bounds; } public Bounds TextBounds(StringBuilder text, Vector2 position, Vector2? scale = null, float characterSpacing = 0.0f, float lineSpacing = 0.0f) { var bounds = InternalTextBounds(new TextSource(text), position, characterSpacing, lineSpacing); var realScale = scale ?? Utility.DefaultScale; bounds.ApplyScale(realScale / RenderFontSizeMultiplicator); return bounds; } private List GetGlyphs(TextSource source, Vector2 position, Vector2 origin, Vector2? sourceScale, float characterSpacing, float lineSpacing) { List result = new List(); if (source.IsNull) return result; Matrix transformation; var scale = sourceScale ?? Utility.DefaultScale; Prepare(position, ref scale, 0, origin, out transformation); int ascent, lineHeight; PreDraw(source, out ascent, out lineHeight); var pos = new Vector2(0, ascent); FontGlyph prevGlyph = null; var i = 0; while (true) { int codepoint; if (!source.GetNextCodepoint(out codepoint)) { break; } var rect = new Rectangle((int)pos.X, (int)pos.Y - LineHeight, 0, LineHeight); var xAdvance = 0; if (codepoint == '\n') { pos.X = 0; pos.Y += lineHeight + lineSpacing; prevGlyph = null; } else { var glyph = GetGlyph(null, codepoint); if (glyph != null) { if (prevGlyph != null) { pos.X += characterSpacing; pos.X += GetKerning(glyph, prevGlyph); } rect = glyph.RenderRectangle; rect.Offset((int)pos.X, (int)pos.Y); xAdvance = glyph.XAdvance; pos.X += xAdvance; prevGlyph = glyph; } } // Apply transformation to rect var p = new Vector2(rect.X, rect.Y); p = p.Transform(ref transformation); var s = new Vector2(rect.Width * scale.X, rect.Height * scale.Y); var glyphInfo = new Glyph { Index = i, Codepoint = codepoint, Bounds = new Rectangle((int)p.X, (int)p.Y, (int)s.X, (int)s.Y), XAdvance = xAdvance }; // Add to the result result.Add(glyphInfo); ++i; } return result; } public List GetGlyphs(string text, Vector2 position, Vector2 origin = default(Vector2), Vector2? scale = null, float characterSpacing = 0.0f, float lineSpacing = 0.0f) => GetGlyphs(new TextSource(text), position, origin, scale, characterSpacing, lineSpacing); public List GetGlyphs(StringBuilder text, Vector2 position, Vector2 origin = default(Vector2), Vector2? scale = null, float characterSpacing = 0.0f, float lineSpacing = 0.0f) => GetGlyphs(new TextSource(text), position, origin, scale, characterSpacing, lineSpacing); [Obsolete("Use GetGlyphs")] public List GetGlyphRects(string text, Vector2 position, Vector2 origin = default(Vector2), Vector2? scale = null, float characterSpacing = 0.0f, float lineSpacing = 0.0f) { var glyphs = GetGlyphs(text, position, origin, scale, characterSpacing, lineSpacing); return (from g in glyphs select g.Bounds).ToList(); } [Obsolete("Use GetGlyphs")] public List GetGlyphRects(StringBuilder text, Vector2 position, Vector2 origin = default(Vector2), Vector2? scale = null, float characterSpacing = 0.0f, float lineSpacing = 0.0f) { var glyphs = GetGlyphs(text, position, origin, scale, characterSpacing, lineSpacing); return (from g in glyphs select g.Bounds).ToList(); } public Vector2 MeasureString(string text, Vector2? scale = null, float characterSpacing = 0.0f, float lineSpacing = 0.0f) { var bounds = TextBounds(text, Utility.Vector2Zero, scale, characterSpacing, lineSpacing); return new Vector2(bounds.X2-bounds.X, bounds.Y2-bounds.Y); } public Vector2 MeasureString(StringBuilder text, Vector2? scale = null, float characterSpacing = 0.0f, float lineSpacing = 0.0f) { var bounds = TextBounds(text, Utility.Vector2Zero, scale, characterSpacing, lineSpacing); return new Vector2(bounds.X2-bounds.X, bounds.Y2-bounds.Y); } internal abstract float GetKerning(FontGlyph glyph, FontGlyph prevGlyph); #if MONOGAME || FNA || STRIDE public static Texture2D GetWhite(GraphicsDevice graphicsDevice) #else public static Texture2D GetWhite(ITexture2DManager textureManager) #endif { if (_white != null) { return _white; } #if MONOGAME || FNA || STRIDE _white = Texture2DManager.CreateTexture(graphicsDevice, 1, 1); Texture2DManager.SetTextureData(_white, new Rectangle(0, 0, 1, 1), new byte[] { 255, 255, 255, 255 }); #else _white = textureManager.CreateTexture(1, 1); textureManager.SetTextureData(_white, new Rectangle(0, 0, 1, 1), new byte[] { 255, 255, 255, 255 }); #endif return _white; } } }