Show More
Commit Description:
Various UI improvements.
Commit Description:
Various UI improvements.
File last commit:
Show/Diff file:
Action:
FNA/src/Graphics/SpriteFont.cs
318 lines | 7.1 KiB | text/x-csharp | CSharpLexer
#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
}
}