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