using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using MLEM.Formatting.Codes; namespace MLEM.Font { /// /// Represents a font with additional abilities. /// /// public abstract class GenericFont { /// /// This field holds a special, private use area code point for a one em space. /// This is a character that isn't drawn, but has the same width as . /// It is mainly used for . /// public const char OneEmSpace = '\uF8FF'; /// /// The bold version of this font. /// public abstract GenericFont Bold { get; } /// /// The italic version of this font. /// public abstract GenericFont Italic { get; } /// public abstract float LineHeight { get; } /// public abstract void DrawString(SpriteBatch batch, string text, Vector2 position, Color color); /// public abstract void DrawString(SpriteBatch batch, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth); /// public abstract void DrawString(SpriteBatch batch, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth); /// public abstract void DrawString(SpriteBatch batch, StringBuilder text, Vector2 position, Color color); /// public abstract void DrawString(SpriteBatch batch, StringBuilder text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth); /// public abstract void DrawString(SpriteBatch batch, StringBuilder text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth); /// protected abstract Vector2 MeasureChar(char c); /// /// Draws a string with the given text alignment. /// /// The sprite batch to use /// The string to draw /// The position of the top left corner of the string /// The alignment to use /// The color to use public void DrawString(SpriteBatch batch, string text, Vector2 position, TextAlign align, Color color) { this.DrawString(batch, text, position, align, color, 0, Vector2.Zero, Vector2.One, SpriteEffects.None, 0); } /// public void DrawString(SpriteBatch batch, string text, Vector2 position, TextAlign align, Color color, float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth) { this.DrawString(batch, text, position, align, color, rotation, origin, new Vector2(scale), effects, layerDepth); } /// public void DrawString(SpriteBatch batch, string text, Vector2 position, TextAlign align, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { switch (align) { case TextAlign.Center: case TextAlign.CenterBothAxes: var (w, h) = this.MeasureString(text); position.X -= w / 2; if (align == TextAlign.CenterBothAxes) position.Y -= h / 2; break; case TextAlign.Right: position.X -= this.MeasureString(text).X; break; } this.DrawString(batch, text, position, color, rotation, origin, scale, effects, layerDepth); } /// public Vector2 MeasureString(string text) { var size = Vector2.Zero; if (text.Length <= 0) return size; var xOffset = 0F; foreach (var c in text) { switch (c) { case '\n': xOffset = 0; size.Y += this.LineHeight; break; case OneEmSpace: xOffset += this.LineHeight; break; default: xOffset += this.MeasureChar(c).X; break; } // increase x size if this line is the longest if (xOffset > size.X) size.X = xOffset; } // include the last line's height too! size.Y += this.LineHeight; return size; } /// /// Truncates a string to a given width. If the string's displayed area is larger than the maximum width, the string is cut off. /// Optionally, the string can be cut off a bit sooner, adding the at the end instead. /// /// The text to truncate /// The maximum width, in display pixels based on the font and scale /// The scale to use for width measurements /// If the string should be truncated from the back rather than the front /// The characters to add to the end of the string if it is too long /// The truncated string, or the same string if it is shorter than the maximum width public string TruncateString(string text, float width, float scale, bool fromBack = false, string ellipsis = "") { var total = new StringBuilder(); var ellipsisWidth = this.MeasureString(ellipsis).X * scale; for (var i = 0; i < text.Length; i++) { if (fromBack) { total.Insert(0, text[text.Length - 1 - i]); } else { total.Append(text[i]); } if (this.MeasureString(total.ToString()).X * scale + ellipsisWidth >= width) { if (fromBack) { return total.Remove(0, 1).Insert(0, ellipsis).ToString(); } else { return total.Remove(total.Length - 1, 1).Append(ellipsis).ToString(); } } } return total.ToString(); } /// /// Splits a string to a given maximum width, adding newline characters between each line. /// Also splits long words. /// /// The text to split into multiple lines /// The maximum width that each line should have /// The scale to use for width measurements /// The split string, containing newline characters at each new line public string SplitString(string text, float width, float scale) { var total = new StringBuilder(); foreach (var line in text.Split('\n')) { var curr = new StringBuilder(); foreach (var word in line.Split(' ')) { if (this.MeasureString(word).X * scale >= width) { if (curr.Length > 0) { total.Append(curr).Append('\n'); curr.Clear(); } var wordBuilder = new StringBuilder(); for (var i = 0; i < word.Length; i++) { wordBuilder.Append(word[i]); if (this.MeasureString(wordBuilder.ToString()).X * scale >= width) { total.Append(wordBuilder.ToString(0, wordBuilder.Length - 1)).Append('\n'); wordBuilder.Remove(0, wordBuilder.Length - 1); } } curr.Append(wordBuilder).Append(' '); } else { curr.Append(word).Append(' '); if (this.MeasureString(curr.ToString()).X * scale >= width) { var len = curr.Length - word.Length - 1; if (len > 0) { total.Append(curr.ToString(0, len)).Append('\n'); curr.Remove(0, len); } } } } total.Append(curr).Append('\n'); } return total.ToString(0, total.Length - 2); } } /// /// An enum that represents the text alignment options for /// public enum TextAlign { /// /// The text is aligned as normal /// Left, /// /// The position passed represents the center of the resulting string in the x axis /// Center, /// /// The position passed represents the right edge of the resulting string /// Right, /// /// The position passed represents the center of the resulting string, both in the x and y axes /// CenterBothAxes } }