2019-08-06 14:20:11 +02:00
using System.Text ;
2020-03-28 22:25:06 +01:00
using Microsoft.Xna.Framework ;
2019-08-06 14:20:11 +02:00
using Microsoft.Xna.Framework.Graphics ;
2020-09-28 20:38:56 +02:00
using MLEM.Misc ;
2019-08-06 14:20:11 +02:00
2020-03-28 22:25:06 +01:00
namespace MLEM.Font {
2020-05-20 23:59:40 +02:00
/// <summary>
/// Represents a font with additional abilities.
/// <seealso cref="GenericSpriteFont"/>
/// </summary>
2020-09-28 20:38:56 +02:00
public abstract class GenericFont : GenericDataHolder {
2019-08-06 14:20:11 +02:00
2020-06-20 01:18:27 +02:00
/// <summary>
2020-12-08 01:43:52 +01:00
/// This field holds the unicode representation of a one em space.
2020-06-20 01:18:27 +02:00
/// This is a character that isn't drawn, but has the same width as <see cref="LineHeight"/>.
2020-12-08 01:43:52 +01:00
/// Whereas a regular <see cref="SpriteFont"/> would have to explicitly support this character for width calculations, generic fonts implicitly support it in <see cref="MeasureString"/>.
2020-06-20 01:18:27 +02:00
/// </summary>
2020-12-08 01:43:52 +01:00
public const char OneEmSpace = ' \ u2003 ' ;
2020-07-01 14:30:47 +02:00
/// <summary>
/// This field holds the unicode representation of a non-breaking space.
/// Whereas a regular <see cref="SpriteFont"/> would have to explicitly support this character for width calculations, generic fonts implicitly support it in <see cref="MeasureString"/>.
/// </summary>
public const char Nbsp = ' \ u00A0 ' ;
2020-06-20 01:18:27 +02:00
2020-05-20 23:59:40 +02:00
/// <summary>
/// The bold version of this font.
/// </summary>
2020-05-17 00:10:29 +02:00
public abstract GenericFont Bold { get ; }
2020-05-20 23:59:40 +02:00
/// <summary>
/// The italic version of this font.
/// </summary>
2020-05-17 00:10:29 +02:00
public abstract GenericFont Italic { get ; }
2020-05-20 23:59:40 +02:00
///<inheritdoc cref="SpriteFont.LineSpacing"/>
2020-03-28 22:25:06 +01:00
public abstract float LineHeight { get ; }
2019-08-25 19:07:45 +02:00
2020-05-20 23:59:40 +02:00
///<inheritdoc cref="SpriteBatch.DrawString(SpriteFont,string,Vector2,Color,float,Vector2,float,SpriteEffects,float)"/>
2020-03-28 22:25:06 +01:00
public abstract void DrawString ( SpriteBatch batch , string text , Vector2 position , Color color ) ;
2020-05-20 23:59:40 +02:00
///<inheritdoc cref="SpriteBatch.DrawString(SpriteFont,string,Vector2,Color,float,Vector2,float,SpriteEffects,float)"/>
2020-03-28 22:25:06 +01:00
public abstract void DrawString ( SpriteBatch batch , string text , Vector2 position , Color color , float rotation , Vector2 origin , float scale , SpriteEffects effects , float layerDepth ) ;
2020-05-20 23:59:40 +02:00
///<inheritdoc cref="SpriteBatch.DrawString(SpriteFont,string,Vector2,Color,float,Vector2,float,SpriteEffects,float)"/>
2020-03-28 22:25:06 +01:00
public abstract void DrawString ( SpriteBatch batch , string text , Vector2 position , Color color , float rotation , Vector2 origin , Vector2 scale , SpriteEffects effects , float layerDepth ) ;
2020-05-20 23:59:40 +02:00
///<inheritdoc cref="SpriteBatch.DrawString(SpriteFont,string,Vector2,Color,float,Vector2,float,SpriteEffects,float)"/>
2020-03-28 22:25:06 +01:00
public abstract void DrawString ( SpriteBatch batch , StringBuilder text , Vector2 position , Color color ) ;
2020-05-20 23:59:40 +02:00
///<inheritdoc cref="SpriteBatch.DrawString(SpriteFont,string,Vector2,Color,float,Vector2,float,SpriteEffects,float)"/>
2020-03-28 22:25:06 +01:00
public abstract void DrawString ( SpriteBatch batch , StringBuilder text , Vector2 position , Color color , float rotation , Vector2 origin , float scale , SpriteEffects effects , float layerDepth ) ;
2020-05-20 23:59:40 +02:00
///<inheritdoc cref="SpriteBatch.DrawString(SpriteFont,string,Vector2,Color,float,Vector2,float,SpriteEffects,float)"/>
2020-03-28 22:25:06 +01:00
public abstract void DrawString ( SpriteBatch batch , StringBuilder text , Vector2 position , Color color , float rotation , Vector2 origin , Vector2 scale , SpriteEffects effects , float layerDepth ) ;
2019-09-05 18:15:51 +02:00
2020-06-20 01:18:27 +02:00
///<inheritdoc cref="SpriteFont.MeasureString(string)"/>
2020-06-20 12:12:34 +02:00
protected abstract Vector2 MeasureChar ( char c ) ;
2020-06-04 22:18:53 +02:00
2020-05-20 23:59:40 +02:00
/// <summary>
/// Draws a string with the given text alignment.
/// </summary>
/// <param name="batch">The sprite batch to use</param>
/// <param name="text">The string to draw</param>
/// <param name="position">The position of the top left corner of the string</param>
/// <param name="align">The alignment to use</param>
/// <param name="color">The color to use</param>
2020-04-11 15:32:01 +02:00
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 ) ;
}
2020-05-20 23:59:40 +02:00
///<inheritdoc cref="DrawString(SpriteBatch,string,Vector2,TextAlign,Color)"/>
2020-04-11 15:32:01 +02:00
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 ) ;
}
2020-05-20 23:59:40 +02:00
///<inheritdoc cref="DrawString(SpriteBatch,string,Vector2,TextAlign,Color)"/>
2020-04-11 15:32:01 +02:00
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 :
2021-03-12 20:47:57 +01:00
var ( w , h ) = this . MeasureString ( text ) * scale ;
2020-04-11 15:32:01 +02:00
position . X - = w / 2 ;
if ( align = = TextAlign . CenterBothAxes )
position . Y - = h / 2 ;
break ;
case TextAlign . Right :
2021-03-12 20:47:57 +01:00
position . X - = this . MeasureString ( text ) . X * scale . X ;
2020-04-11 15:32:01 +02:00
break ;
}
this . DrawString ( batch , text , position , color , rotation , origin , scale , effects , layerDepth ) ;
}
2020-06-20 01:18:27 +02:00
///<inheritdoc cref="SpriteFont.MeasureString(string)"/>
public Vector2 MeasureString ( string text ) {
var size = Vector2 . Zero ;
2020-06-20 15:42:36 +02:00
if ( text . Length < = 0 )
return size ;
2020-06-20 01:18:27 +02:00
var xOffset = 0F ;
foreach ( var c in text ) {
2020-06-20 12:12:34 +02:00
switch ( c ) {
case '\n' :
xOffset = 0 ;
size . Y + = this . LineHeight ;
break ;
case OneEmSpace :
xOffset + = this . LineHeight ;
break ;
2020-07-01 14:30:47 +02:00
case Nbsp :
xOffset + = this . MeasureChar ( ' ' ) . X ;
break ;
2020-06-20 12:12:34 +02:00
default :
xOffset + = this . MeasureChar ( c ) . X ;
break ;
2020-06-20 01:18:27 +02:00
}
// 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 ;
}
2020-05-20 23:59:40 +02:00
/// <summary>
/// Truncates a string to a given width. If the string's displayed area is larger than the maximum width, the string is cut off.
2020-10-06 20:14:57 +02:00
/// Optionally, the string can be cut off a bit sooner, adding the <paramref name="ellipsis"/> at the end instead.
2020-05-20 23:59:40 +02:00
/// </summary>
/// <param name="text">The text to truncate</param>
/// <param name="width">The maximum width, in display pixels based on the font and scale</param>
/// <param name="scale">The scale to use for width measurements</param>
/// <param name="fromBack">If the string should be truncated from the back rather than the front</param>
/// <param name="ellipsis">The characters to add to the end of the string if it is too long</param>
/// <returns>The truncated string, or the same string if it is shorter than the maximum width</returns>
2020-04-11 15:44:55 +02:00
public string TruncateString ( string text , float width , float scale , bool fromBack = false , string ellipsis = "" ) {
2019-09-05 18:15:51 +02:00
var total = new StringBuilder ( ) ;
2020-04-11 15:44:55 +02:00
var ellipsisWidth = this . MeasureString ( ellipsis ) . X * scale ;
2019-09-05 18:15:51 +02:00
for ( var i = 0 ; i < text . Length ; i + + ) {
if ( fromBack ) {
total . Insert ( 0 , text [ text . Length - 1 - i ] ) ;
} else {
total . Append ( text [ i ] ) ;
}
2020-06-20 01:18:27 +02:00
if ( this . MeasureString ( total . ToString ( ) ) . X * scale + ellipsisWidth > = width ) {
2020-04-11 15:47:33 +02:00
if ( fromBack ) {
return total . Remove ( 0 , 1 ) . Insert ( 0 , ellipsis ) . ToString ( ) ;
} else {
return total . Remove ( total . Length - 1 , 1 ) . Append ( ellipsis ) . ToString ( ) ;
}
}
2019-09-05 18:15:51 +02:00
}
return total . ToString ( ) ;
}
2020-05-20 23:59:40 +02:00
/// <summary>
/// Splits a string to a given maximum width, adding newline characters between each line.
2020-12-31 17:22:51 +01:00
/// Also splits long words and supports zero-width spaces.
2020-05-20 23:59:40 +02:00
/// </summary>
/// <param name="text">The text to split into multiple lines</param>
/// <param name="width">The maximum width that each line should have</param>
/// <param name="scale">The scale to use for width measurements</param>
/// <returns>The split string, containing newline characters at each new line</returns>
2020-03-28 22:25:06 +01:00
public string SplitString ( string text , float width , float scale ) {
2019-08-25 19:07:45 +02:00
var total = new StringBuilder ( ) ;
2019-08-24 00:07:54 +02:00
foreach ( var line in text . Split ( '\n' ) ) {
2019-08-25 19:07:45 +02:00
var curr = new StringBuilder ( ) ;
2021-04-14 00:49:33 +02:00
foreach ( var word in line . Split ( ' ' ) ) {
2020-03-28 22:25:06 +01:00
if ( this . MeasureString ( word ) . X * scale > = width ) {
2020-02-06 02:27:21 +01:00
if ( curr . Length > 0 ) {
2020-02-08 18:25:49 +01:00
total . Append ( curr ) . Append ( '\n' ) ;
2020-02-06 02:27:21 +01:00
curr . Clear ( ) ;
}
var wordBuilder = new StringBuilder ( ) ;
for ( var i = 0 ; i < word . Length ; i + + ) {
wordBuilder . Append ( word [ i ] ) ;
2020-03-28 22:25:06 +01:00
if ( this . MeasureString ( wordBuilder . ToString ( ) ) . X * scale > = width ) {
2020-02-06 02:27:21 +01:00
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 ( ' ' ) ;
2020-03-28 22:25:06 +01:00
if ( this . MeasureString ( curr . ToString ( ) ) . X * scale > = width ) {
2020-02-06 02:27:21 +01:00
var len = curr . Length - word . Length - 1 ;
if ( len > 0 ) {
2020-02-08 18:24:14 +01:00
total . Append ( curr . ToString ( 0 , len ) ) . Append ( '\n' ) ;
2020-02-06 02:27:21 +01:00
curr . Remove ( 0 , len ) ;
}
2019-09-06 11:25:31 +02:00
}
2019-08-24 00:07:54 +02:00
}
2019-08-06 14:20:11 +02:00
}
2020-02-08 18:24:14 +01:00
total . Append ( curr ) . Append ( '\n' ) ;
2019-08-06 14:20:11 +02:00
}
2020-03-19 03:50:01 +01:00
return total . ToString ( 0 , total . Length - 2 ) ;
2019-08-06 14:20:11 +02:00
}
}
2020-04-11 15:32:01 +02:00
2020-05-21 12:53:42 +02:00
/// <summary>
/// An enum that represents the text alignment options for <see cref="GenericFont.DrawString(SpriteBatch,string,Vector2,TextAlign,Color)"/>
/// </summary>
2020-04-11 15:32:01 +02:00
public enum TextAlign {
2020-05-21 12:53:42 +02:00
/// <summary>
/// The text is aligned as normal
/// </summary>
2020-04-11 15:32:01 +02:00
Left ,
2020-05-21 12:53:42 +02:00
/// <summary>
/// The position passed represents the center of the resulting string in the x axis
/// </summary>
2020-04-11 15:32:01 +02:00
Center ,
2020-05-21 12:53:42 +02:00
/// <summary>
/// The position passed represents the right edge of the resulting string
/// </summary>
2020-04-11 15:32:01 +02:00
Right ,
2020-05-21 12:53:42 +02:00
/// <summary>
/// The position passed represents the center of the resulting string, both in the x and y axes
/// </summary>
2020-04-11 15:32:01 +02:00
CenterBothAxes
}
2019-08-06 14:20:11 +02:00
}