|
|
#region License
|
|
|
/* FNA - XNA4 Reimplementation for Desktop Platforms
|
|
|
* Copyright 2009-2020 Ethan Lee and the MonoGame Team
|
|
|
*
|
|
|
* Released under the Microsoft Public License.
|
|
|
* See LICENSE for details.
|
|
|
*/
|
|
|
#endregion
|
|
|
|
|
|
#region Using Statements
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Collections.ObjectModel;
|
|
|
using System.Text;
|
|
|
#endregion
|
|
|
|
|
|
namespace Microsoft.Xna.Framework.Graphics
|
|
|
{
|
|
|
// http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.spritefont.aspx
|
|
|
public sealed class SpriteFont
|
|
|
{
|
|
|
#region Public Properties
|
|
|
|
|
|
public ReadOnlyCollection<char> Characters
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public char? DefaultCharacter
|
|
|
{
|
|
|
get;
|
|
|
set;
|
|
|
}
|
|
|
|
|
|
public int LineSpacing
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
return lineSpacing;
|
|
|
}
|
|
|
set
|
|
|
{
|
|
|
lineSpacing = value;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public float Spacing
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
return spacing;
|
|
|
}
|
|
|
set
|
|
|
{
|
|
|
spacing = value;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Internal Variables
|
|
|
|
|
|
/* I've had a bunch of games use reflection on SpriteFont to get
|
|
|
* this data. Keep these names as they are for XNA4 accuracy!
|
|
|
* -flibit
|
|
|
*/
|
|
|
internal Texture2D textureValue;
|
|
|
internal List<Rectangle> glyphData;
|
|
|
internal List<Rectangle> croppingData;
|
|
|
internal List<Vector3> kerning;
|
|
|
internal List<char> characterMap;
|
|
|
|
|
|
/* If, by chance, you're seeing this and thinking about using
|
|
|
* reflection to access the fields:
|
|
|
* Don't.
|
|
|
* To date, one (1) game is using the fields directly,
|
|
|
* even though the properties are publicly accessible.
|
|
|
* Not even FNA uses the fields directly.
|
|
|
* -ade
|
|
|
*/
|
|
|
internal int lineSpacing;
|
|
|
internal float spacing;
|
|
|
|
|
|
/* This is not a part of the spec as far as we know, but we
|
|
|
* added this because it's WAY faster than going to characterMap
|
|
|
* and calling IndexOf on each character.
|
|
|
*/
|
|
|
internal Dictionary<char, int> characterIndexMap;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Internal Constructor
|
|
|
|
|
|
internal SpriteFont(
|
|
|
Texture2D texture,
|
|
|
List<Rectangle> glyphBounds,
|
|
|
List<Rectangle> cropping,
|
|
|
List<char> characters,
|
|
|
int lineSpacing,
|
|
|
float spacing,
|
|
|
List<Vector3> kerningData,
|
|
|
char? defaultCharacter
|
|
|
) {
|
|
|
Characters = new ReadOnlyCollection<char>(characters.ToArray());
|
|
|
DefaultCharacter = defaultCharacter;
|
|
|
LineSpacing = lineSpacing;
|
|
|
Spacing = spacing;
|
|
|
|
|
|
textureValue = texture;
|
|
|
glyphData = glyphBounds;
|
|
|
croppingData = cropping;
|
|
|
kerning = kerningData;
|
|
|
characterMap = characters;
|
|
|
|
|
|
characterIndexMap = new Dictionary<char, int>(characters.Count);
|
|
|
for (int i = 0; i < characters.Count; i += 1)
|
|
|
{
|
|
|
characterIndexMap[characters[i]] = i;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Public MeasureString Methods
|
|
|
|
|
|
public Vector2 MeasureString(string text)
|
|
|
{
|
|
|
/* FIXME: This method is a duplicate of MeasureString(StringBuilder)!
|
|
|
* The only difference is how we iterate through the string.
|
|
|
* -flibit
|
|
|
*/
|
|
|
if (text == null)
|
|
|
{
|
|
|
throw new ArgumentNullException("text");
|
|
|
}
|
|
|
if (text.Length == 0)
|
|
|
{
|
|
|
return Vector2.Zero;
|
|
|
}
|
|
|
|
|
|
// FIXME: This needs an accuracy check! -flibit
|
|
|
|
|
|
Vector2 result = Vector2.Zero;
|
|
|
float curLineWidth = 0.0f;
|
|
|
float finalLineHeight = LineSpacing;
|
|
|
bool firstInLine = true;
|
|
|
|
|
|
foreach (char c in text)
|
|
|
{
|
|
|
// Special characters
|
|
|
if (c == '\r')
|
|
|
{
|
|
|
continue;
|
|
|
}
|
|
|
if (c == '\n')
|
|
|
{
|
|
|
result.X = Math.Max(result.X, curLineWidth);
|
|
|
result.Y += LineSpacing;
|
|
|
curLineWidth = 0.0f;
|
|
|
finalLineHeight = LineSpacing;
|
|
|
firstInLine = true;
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
/* Get the List index from the character map, defaulting to the
|
|
|
* DefaultCharacter if it's set.
|
|
|
*/
|
|
|
int index;
|
|
|
if (!characterIndexMap.TryGetValue(c, out index))
|
|
|
{
|
|
|
if (!DefaultCharacter.HasValue)
|
|
|
{
|
|
|
throw new ArgumentException(
|
|
|
"Text contains characters that cannot be" +
|
|
|
" resolved by this SpriteFont.",
|
|
|
"text"
|
|
|
);
|
|
|
}
|
|
|
index = characterIndexMap[DefaultCharacter.Value];
|
|
|
}
|
|
|
|
|
|
/* For the first character in a line, always push the width
|
|
|
* rightward, even if the kerning pushes the character to the
|
|
|
* left.
|
|
|
*/
|
|
|
Vector3 cKern = kerning[index];
|
|
|
if (firstInLine)
|
|
|
{
|
|
|
curLineWidth += Math.Abs(cKern.X);
|
|
|
firstInLine = false;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
curLineWidth += Spacing + cKern.X;
|
|
|
}
|
|
|
|
|
|
/* Add the character width and right-side bearing to the line
|
|
|
* width.
|
|
|
*/
|
|
|
curLineWidth += cKern.Y + cKern.Z;
|
|
|
|
|
|
/* If a character is taller than the default line height,
|
|
|
* increase the height to that of the line's tallest character.
|
|
|
*/
|
|
|
int cCropHeight = croppingData[index].Height;
|
|
|
if (cCropHeight > finalLineHeight)
|
|
|
{
|
|
|
finalLineHeight = cCropHeight;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Calculate the final width/height of the text box
|
|
|
result.X = Math.Max(result.X, curLineWidth);
|
|
|
result.Y += finalLineHeight;
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
public Vector2 MeasureString(StringBuilder text)
|
|
|
{
|
|
|
/* FIXME: This method is a duplicate of MeasureString(string)!
|
|
|
* The only difference is how we iterate through the StringBuilder.
|
|
|
* We don't use ToString() since it generates garbage.
|
|
|
* -flibit
|
|
|
*/
|
|
|
if (text == null)
|
|
|
{
|
|
|
throw new ArgumentNullException("text");
|
|
|
}
|
|
|
if (text.Length == 0)
|
|
|
{
|
|
|
return Vector2.Zero;
|
|
|
}
|
|
|
|
|
|
// FIXME: This needs an accuracy check! -flibit
|
|
|
|
|
|
Vector2 result = Vector2.Zero;
|
|
|
float curLineWidth = 0.0f;
|
|
|
float finalLineHeight = LineSpacing;
|
|
|
bool firstInLine = true;
|
|
|
|
|
|
for (int i = 0; i < text.Length; i += 1)
|
|
|
{
|
|
|
char c = text[i];
|
|
|
|
|
|
// Special characters
|
|
|
if (c == '\r')
|
|
|
{
|
|
|
continue;
|
|
|
}
|
|
|
if (c == '\n')
|
|
|
{
|
|
|
result.X = Math.Max(result.X, curLineWidth);
|
|
|
result.Y += LineSpacing;
|
|
|
curLineWidth = 0.0f;
|
|
|
finalLineHeight = LineSpacing;
|
|
|
firstInLine = true;
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
/* Get the List index from the character map, defaulting to the
|
|
|
* DefaultCharacter if it's set.
|
|
|
*/
|
|
|
int index;
|
|
|
if (!characterIndexMap.TryGetValue(c, out index))
|
|
|
{
|
|
|
if (!DefaultCharacter.HasValue)
|
|
|
{
|
|
|
throw new ArgumentException(
|
|
|
"Text contains characters that cannot be" +
|
|
|
" resolved by this SpriteFont.",
|
|
|
"text"
|
|
|
);
|
|
|
}
|
|
|
index = characterIndexMap[DefaultCharacter.Value];
|
|
|
}
|
|
|
|
|
|
/* For the first character in a line, always push the width
|
|
|
* rightward, even if the kerning pushes the character to the
|
|
|
* left.
|
|
|
*/
|
|
|
Vector3 cKern = kerning[index];
|
|
|
if (firstInLine)
|
|
|
{
|
|
|
curLineWidth += Math.Abs(cKern.X);
|
|
|
firstInLine = false;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
curLineWidth += Spacing + cKern.X;
|
|
|
}
|
|
|
|
|
|
/* Add the character width and right-side bearing to the line
|
|
|
* width.
|
|
|
*/
|
|
|
curLineWidth += cKern.Y + cKern.Z;
|
|
|
|
|
|
/* If a character is taller than the default line height,
|
|
|
* increase the height to that of the line's tallest character.
|
|
|
*/
|
|
|
int cCropHeight = croppingData[index].Height;
|
|
|
if (cCropHeight > finalLineHeight)
|
|
|
{
|
|
|
finalLineHeight = cCropHeight;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Calculate the final width/height of the text box
|
|
|
result.X = Math.Max(result.X, curLineWidth);
|
|
|
result.Y += finalLineHeight;
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
}
|
|
|
}
|
|
|
|