You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
452 lines
17 KiB
C#
452 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Xml;
|
|
|
|
namespace Otter {
|
|
/// <summary>
|
|
/// Font used for loading premade textures of characters, usually arcade fonts and stuff like that.
|
|
/// Currently supports fonts created with BMFont, Littera, and CBFG.
|
|
/// </summary>
|
|
public class BitmapFont : BaseFont {
|
|
|
|
public Texture Texture { get; private set; }
|
|
|
|
public int CharCodeOffset = 32; // most fonts start at Ascii code 32
|
|
public int CharacterWidth = 8;
|
|
public int CharacterHeight = 8;
|
|
public int LineSpacing;
|
|
public bool EnableKerning = true;
|
|
|
|
public int CharacterOffsetX = 0;
|
|
public int CharacterOffsetY = 0;
|
|
|
|
public string FontData { get; private set; }
|
|
public BitmapFontDataType DataType { get; private set; }
|
|
|
|
public BitmapFont(Texture texture) {
|
|
Texture = texture;
|
|
DataType = BitmapFontDataType.None;
|
|
}
|
|
|
|
public BitmapFont(Texture texture, int charWidth, int charHeight, int charOffset = 32) : this(texture) {
|
|
CharacterWidth = charWidth;
|
|
CharacterHeight = charHeight;
|
|
CharCodeOffset = charOffset;
|
|
|
|
LineSpacing = CharacterHeight;
|
|
}
|
|
|
|
public BitmapFont(BitmapFontConfig config) : this(config.Texture) {
|
|
CharacterWidth = config.CharacterWidth;
|
|
CharacterHeight = config.CharacterHeight;
|
|
CharCodeOffset = config.CharCodeOffset;
|
|
CharacterOffsetX = config.CharacterOffsetX;
|
|
CharacterOffsetY = config.CharacterOffsetY;
|
|
|
|
LineSpacing = config.LineSpacing;
|
|
}
|
|
|
|
public BitmapFont(string source) : this(new Texture(source)) { }
|
|
|
|
/// <summary>
|
|
/// Loads the data to render the bitmap text with from a file.
|
|
/// </summary>
|
|
/// <param name="path">The path to the file that contains the data.</param>
|
|
/// <param name="dataType">The type of data.</param>
|
|
/// <returns>The BitmapFont</returns>
|
|
public BitmapFont LoadDataFile(string path, BitmapFontDataType dataType) {
|
|
return LoadData(File.ReadAllText(path), dataType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the data to render the bitmap text with.
|
|
/// </summary>
|
|
/// <param name="data">The data.</param>
|
|
/// <param name="dataType">Type of the data.</param>
|
|
/// <returns>The BitmapFont</returns>
|
|
public BitmapFont LoadData(string data, BitmapFontDataType dataType) {
|
|
FontData = data;
|
|
DataType = dataType;
|
|
|
|
switch (DataType) {
|
|
case BitmapFontDataType.Littera:
|
|
|
|
var xmlDoc = new XmlDocument();
|
|
xmlDoc.LoadXml(data);
|
|
|
|
foreach (XmlNode n in xmlDoc.SelectNodes("//common")) {
|
|
LineSpacing = n.AttributeInt("lineHeight");
|
|
}
|
|
|
|
foreach (XmlNode n in xmlDoc.SelectNodes("//char")) {
|
|
var c = new CharData();
|
|
c.CharacterId = n.AttributeInt("id");
|
|
c.Width = n.AttributeInt("width");
|
|
c.Height = n.AttributeInt("height");
|
|
c.X = n.AttributeInt("x");
|
|
c.Y = n.AttributeInt("y");
|
|
c.OffsetX = n.AttributeInt("xoffset");
|
|
c.OffsetY = n.AttributeInt("yoffset");
|
|
c.Advance = n.AttributeInt("xadvance");
|
|
c.Character = (char)c.CharacterId;
|
|
if (!charData.ContainsKey(c.Character)) {
|
|
charData.Add(c.Character, c);
|
|
}
|
|
}
|
|
|
|
foreach (XmlNode n in xmlDoc.SelectNodes("//kerning")) {
|
|
var first = (char)n.AttributeInt("first");
|
|
var second = (char)n.AttributeInt("second");
|
|
var amount = n.AttributeInt("amount");
|
|
|
|
AddKerning(first, second, amount);
|
|
}
|
|
|
|
break;
|
|
|
|
case BitmapFontDataType.BMFontXml:
|
|
|
|
xmlDoc = new XmlDocument();
|
|
xmlDoc.LoadXml(data);
|
|
|
|
foreach (XmlNode n in xmlDoc.SelectNodes("//common")) {
|
|
LineSpacing = n.AttributeInt("lineHeight");
|
|
}
|
|
|
|
foreach (XmlNode n in xmlDoc.SelectNodes("//char")) {
|
|
var c = new CharData();
|
|
c.CharacterId = n.AttributeInt("id");
|
|
c.Width = n.AttributeInt("width");
|
|
c.Height = n.AttributeInt("height");
|
|
c.X = n.AttributeInt("x");
|
|
c.Y = n.AttributeInt("y");
|
|
c.OffsetX = n.AttributeInt("xoffset");
|
|
c.OffsetY = n.AttributeInt("yoffset");
|
|
c.Advance = n.AttributeInt("xadvance");
|
|
c.Character = (char)c.CharacterId;
|
|
if (!charData.ContainsKey(c.Character)) {
|
|
charData.Add(c.Character, c);
|
|
}
|
|
}
|
|
|
|
foreach (XmlNode n in xmlDoc.SelectNodes("//kerning")) {
|
|
var first = (char)n.AttributeInt("first");
|
|
var second = (char)n.AttributeInt("second");
|
|
var amount = n.AttributeInt("amount");
|
|
|
|
AddKerning(first, second, amount);
|
|
}
|
|
|
|
break;
|
|
|
|
case BitmapFontDataType.BMFontText:
|
|
|
|
var lines = data.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
|
foreach (var line in lines) {
|
|
var entries = line.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
|
|
var header = entries[0];
|
|
|
|
var textData = new Dictionary<string, string>();
|
|
|
|
switch (header) {
|
|
case "info":
|
|
|
|
// Dont need any of this for now.
|
|
|
|
break;
|
|
|
|
case "common":
|
|
|
|
foreach (var e in entries) {
|
|
if (!e.Contains('=')) continue;
|
|
|
|
textData.Add(GetKeyValueFromString(e)[0], GetKeyValueFromString(e)[1]);
|
|
}
|
|
|
|
LineSpacing = int.Parse(textData["lineHeight"]);
|
|
|
|
break;
|
|
|
|
case "char":
|
|
|
|
var c = new CharData();
|
|
|
|
foreach (var e in entries) {
|
|
if (!e.Contains('=')) continue;
|
|
|
|
textData.Add(GetKeyValueFromString(e)[0], GetKeyValueFromString(e)[1]);
|
|
}
|
|
|
|
c.CharacterId = int.Parse(textData["id"]);
|
|
c.Character = (char)int.Parse(textData["id"]);
|
|
c.Width = int.Parse(textData["width"]);
|
|
c.Height = int.Parse(textData["height"]);
|
|
c.X = int.Parse(textData["x"]);
|
|
c.Y = int.Parse(textData["y"]);
|
|
c.OffsetX = int.Parse(textData["xoffset"]);
|
|
c.OffsetY = int.Parse(textData["yoffset"]);
|
|
c.Advance = int.Parse(textData["xadvance"]);
|
|
|
|
if (!charData.ContainsKey(c.Character)) {
|
|
charData.Add(c.Character, c);
|
|
}
|
|
|
|
break;
|
|
|
|
case "kerning":
|
|
|
|
foreach (var e in entries) {
|
|
if (!e.Contains('=')) continue;
|
|
|
|
textData.Add(GetKeyValueFromString(e)[0], GetKeyValueFromString(e)[1]);
|
|
}
|
|
|
|
AddKerning((char)int.Parse(textData["first"]), (char)int.Parse(textData["second"]), int.Parse(textData["amount"]));
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
case BitmapFontDataType.CodeheadCSV:
|
|
|
|
lines = data.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
var csv = new Dictionary<string, string>();
|
|
|
|
foreach(var line in lines) {
|
|
csv.Add(GetKeyValueFromString(line, ',')[0], GetKeyValueFromString(line, ',')[1]);
|
|
}
|
|
|
|
var imgWidth = int.Parse(csv["Image Width"]);
|
|
var startChar = int.Parse(csv["Start Char"]);
|
|
var imgCellWidth = int.Parse(csv["Cell Width"]);
|
|
var imgCellHeight = int.Parse(csv["Cell Height"]);
|
|
|
|
var charsPerRow = (int)Util.Floor(imgWidth / imgCellWidth);
|
|
|
|
for (var i = 0; i < 256; i++) {
|
|
var c = new CharData();
|
|
|
|
c.Width = int.Parse(csv[string.Format("Char {0} Base Width", i)]);
|
|
c.OffsetX = int.Parse(csv[string.Format("Char {0} X Offset", i)]);
|
|
c.OffsetY = int.Parse(csv[string.Format("Char {0} Y Offset", i)]);
|
|
var charPos = i - startChar;
|
|
c.X = Util.TwoDeeX(charPos, charsPerRow) * imgCellWidth;
|
|
c.Y = Util.TwoDeeY(charPos, charsPerRow) * imgCellHeight;
|
|
var widthOffset = int.Parse(csv[string.Format("Char {0} Width Offset", i)]);
|
|
c.Height = int.Parse(csv["Font Height"]);
|
|
c.Advance = c.Width + widthOffset;
|
|
c.Character = (char)i;
|
|
|
|
LineSpacing = c.Height;
|
|
|
|
if (!charData.ContainsKey(c.Character)) {
|
|
charData.Add(c.Character, c);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case BitmapFontDataType.Shoebox:
|
|
|
|
lines = data.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
|
foreach (var line in lines) {
|
|
var entries = line.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
|
|
var header = entries[0];
|
|
|
|
var textData = new Dictionary<string, string>();
|
|
|
|
switch (header) {
|
|
case "info":
|
|
|
|
break;
|
|
|
|
case "common":
|
|
|
|
foreach (var e in entries) {
|
|
if (!e.Contains('=')) continue;
|
|
|
|
textData.Add(GetKeyValueFromString(e)[0], GetKeyValueFromString(e)[1]);
|
|
}
|
|
|
|
LineSpacing = int.Parse(textData["lineHeight"]);
|
|
|
|
break;
|
|
|
|
case "char":
|
|
|
|
var c = new CharData();
|
|
|
|
foreach (var e in entries) {
|
|
if (!e.Contains('=')) continue;
|
|
|
|
textData.Add(GetKeyValueFromString(e)[0], GetKeyValueFromString(e)[1]);
|
|
}
|
|
|
|
c.CharacterId = int.Parse(textData["id"]);
|
|
c.Character = (char)int.Parse(textData["id"]);
|
|
c.Width = int.Parse(textData["width"]);
|
|
c.Height = int.Parse(textData["height"]);
|
|
c.X = int.Parse(textData["x"]);
|
|
c.Y = int.Parse(textData["y"]);
|
|
c.OffsetX = int.Parse(textData["xoffset"]);
|
|
c.OffsetY = int.Parse(textData["yoffset"]);
|
|
c.Advance = int.Parse(textData["xadvance"]);
|
|
|
|
charData.Add(c.Character, c);
|
|
|
|
break;
|
|
|
|
case "kerning":
|
|
|
|
foreach (var e in entries) {
|
|
if (!e.Contains('=')) continue;
|
|
|
|
textData.Add(GetKeyValueFromString(e)[0], GetKeyValueFromString(e)[1]);
|
|
}
|
|
|
|
AddKerning((char)int.Parse(textData["first"]), (char)int.Parse(textData["second"]), int.Parse(textData["amount"]));
|
|
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
string[] GetKeyValueFromString(string str, char delim = '=') {
|
|
string[] result = new string[2];
|
|
|
|
result[0] = str.Substring(0, str.IndexOf(delim));
|
|
result[1] = str.Substring(str.IndexOf(delim) + 1, str.Length - str.IndexOf(delim) - 1);
|
|
|
|
return result;
|
|
}
|
|
|
|
Dictionary<char, Dictionary<char, int>> kerningPairs = new Dictionary<char, Dictionary<char, int>>();
|
|
Dictionary<char, CharData> charData = new Dictionary<char, CharData>();
|
|
|
|
internal override SFML.Graphics.Glyph GetGlyph(char c, int size, bool bold) {
|
|
|
|
var rect = new SFML.Graphics.IntRect();
|
|
var bounds = new SFML.Graphics.FloatRect();
|
|
var advance = 0;
|
|
|
|
if (DataType == BitmapFontDataType.None) {
|
|
|
|
var width = Texture.Width;
|
|
var height = Texture.Height;
|
|
|
|
var offsetChar = (int)c - CharCodeOffset;
|
|
rect.Left = Util.TwoDeeX(offsetChar * CharacterWidth, width) + CharacterOffsetX;
|
|
rect.Top = Util.TwoDeeY(offsetChar * CharacterHeight, width) * CharacterHeight + CharacterOffsetY;
|
|
|
|
rect.Width = CharacterWidth;
|
|
rect.Height = CharacterHeight;
|
|
|
|
bounds.Top = 0;
|
|
bounds.Left = 0;
|
|
bounds.Width = CharacterWidth;
|
|
bounds.Height = CharacterHeight;
|
|
|
|
advance = CharacterWidth;
|
|
|
|
}
|
|
else {
|
|
|
|
var data = charData[c];
|
|
|
|
rect.Left = data.X;
|
|
rect.Top = data.Y;
|
|
rect.Width = data.Width;
|
|
rect.Height = data.Height;
|
|
|
|
bounds.Left = data.OffsetX;
|
|
bounds.Top = data.OffsetY;
|
|
bounds.Width = data.Width;
|
|
bounds.Height = data.Height;
|
|
|
|
advance = data.Advance;
|
|
}
|
|
|
|
return new SFML.Graphics.Glyph() {
|
|
Advance = advance,
|
|
Bounds = bounds,
|
|
TextureRect = rect
|
|
};
|
|
}
|
|
|
|
internal override float GetLineSpacing(int size) {
|
|
return LineSpacing;
|
|
}
|
|
|
|
public override float GetKerning(char first, char second, int characterSize) {
|
|
if (!EnableKerning) return 0;
|
|
|
|
if (!kerningPairs.ContainsKey(first)) {
|
|
return 0;
|
|
}
|
|
if (!kerningPairs[first].ContainsKey(second)) {
|
|
return 0;
|
|
}
|
|
return kerningPairs[first][second];
|
|
}
|
|
|
|
void AddKerning(char first, char second, int amount) {
|
|
if (!kerningPairs.ContainsKey(first)) {
|
|
kerningPairs.Add(first, new Dictionary<char, int>());
|
|
}
|
|
if (!kerningPairs[first].ContainsKey(second)) {
|
|
kerningPairs[first].Add(second, amount);
|
|
}
|
|
kerningPairs[first][second] = amount;
|
|
}
|
|
|
|
internal override Texture GetTexture(int size) {
|
|
return Texture;
|
|
}
|
|
}
|
|
|
|
class CharData {
|
|
|
|
public char Character;
|
|
public int CharacterId;
|
|
public int X;
|
|
public int Y;
|
|
public int Width;
|
|
public int Height;
|
|
public int Advance;
|
|
public int OffsetX;
|
|
public int OffsetY;
|
|
|
|
}
|
|
|
|
public class BitmapFontConfig {
|
|
public Texture Texture;
|
|
public int CharacterWidth;
|
|
public int CharacterHeight;
|
|
public int CharCodeOffset = 32;
|
|
public int CharacterOffsetX;
|
|
public int CharacterOffsetY;
|
|
public int LineSpacing;
|
|
}
|
|
|
|
public enum BitmapFontDataType {
|
|
None, // Just plain old monospaced characters.
|
|
BMFontText, // http://www.angelcode.com/products/bmfont/
|
|
BMFontXml, // http://www.angelcode.com/products/bmfont/
|
|
Littera, // http://kvazars.com/littera/
|
|
CodeheadCSV, // http://www.codehead.co.uk/cbfg/
|
|
Shoebox // http://renderhjs.net/shoebox/bitmapFont.htm
|
|
}
|
|
}
|