I'm currently trying to devise a way to kern bitmap fonts in XNA 4.0. The two easiest ways I've found are to use either BMFontGen or BMFont.
I chose to work with BMFontGen, as it was made specifically for XNA, although it's extremely old and finicky. So, my quest to update it took a while, but I eventually got it to compile and work on the PC. Then, when it didn't work on the 360, I got pretty angry.
I'd just spent all that time, and for naught!
So, then I did some Google-fu and found out that the XmlNodeList and XmlNode classes are no longer options for the 360 in 4.0. Then, I came here, got great replies, and set about reading. Which, leads me to this post.
Now, before I seek any more help, I've learned that the better question is this:
Is there an easier way to do whatever it is I'm doing?
Thus, I will ask:
Is there an easier, better way to have kerned bitmap fonts in XNA?
If the answer is no, then I'm afraid I need some more help.
For reference, here's the areas of my CustomFont class that work great on the PC, but not so great on the 360. It's pretty long, so if you want to help me and end up actually reading it I'll be very grateful for your time spent.
private void Init(ContentManager cm, string strFontFilename)
{
m_content = cm;
m_sb = null;
m_sbOverride = null;
m_dictBitmapID2BitmapInfo = new Dictionary<int, BitmapInfo>();
m_dictBitmapID2Texture = new Dictionary<int, Texture2D>();
m_dictUnicode2GlyphInfo = new Dictionary<char, GlyphInfo>();
m_dictKern = new Dictionary<char, Dictionary<char, sbyte>>();
XmlDocument xd = new XmlDocument();
// load the XML file from wherever it is - filesystem or embedded
if (System.IO.File.Exists(strFontFilename))
{
// all files mentioned in the font descriptor file are relative to the parent directory of the font file.
// record the path to this directory.
m_strPath = System.IO.Path.GetDirectoryName(strFontFilename);
if (m_strPath != "")
m_strPath += @"/";
m_strFilename = strFontFilename; // relative path + filename
m_fLoadFromResource = false;
xd.Load(strFontFilename);
}
else
{
// look in the assembly resources
bool fFoundResource = false;
string strEmbeddedPath, strEmbeddedName;
ConvertFilePath2EmbeddedPath(strFontFilename, out strEmbeddedPath, out strEmbeddedName);
System.IO.Stream ios = Assembly.GetExecutingAssembly().GetManifestResourceStream(strEmbeddedPath + strEmbeddedName);
if (ios != null)
{
m_strPath = strEmbeddedPath; // path to resource
m_strFilename = strEmbeddedName; // resource name
m_fLoadFromResource = true;
fFoundResource = true;
xd.Load(ios);
}
if (!fFoundResource)
throw new System.Exception(String.Format("Unable to find font named '{0}'.", strFontFilename));
}
m_strName = "";
LoadFontXML(xd.ChildNodes);
// if the font doesn't define a name, create one from the filename
if (m_strName == "")
m_strName = System.IO.Path.GetFileNameWithoutExtension(strFontFilename);
// add this font to the list of active fonts
m_dictBitmapFonts.Add(m_strName, this);
}
/// <summary>
/// Load the font data from an XML font descriptor file
/// </summary>
/// <param name="xnl">XML node list containing the entire font descriptor file</param>
private void LoadFontXML(XmlNodeList xnl)
{
foreach (XmlNode xn in xnl)
{
if (xn.Name == "font")
{
m_strName = GetXMLAttribute(xn, "name");
m_nBase = Int32.Parse(GetXMLAttribute(xn, "base"));
m_nHeight = Int32.Parse(GetXMLAttribute(xn, "height"));
LoadFontXML_font(xn.ChildNodes);
}
}
}
/// <summary>
/// Load the data from the "font" node
/// </summary>
/// <param name="xnl">XML node list containing the "font" node's children</param>
private void LoadFontXML_font(XmlNodeList xnl)
{
foreach (XmlNode xn in xnl)
{
if (xn.Name == "bitmaps")
LoadFontXML_bitmaps(xn.ChildNodes);
if (xn.Name == "glyphs")
LoadFontXML_glyphs(xn.ChildNodes);
if (xn.Name == "kernpairs")
LoadFontXML_kernpairs(xn.ChildNodes);
}
}
/// <summary>
/// Load the data from the "bitmaps" node
/// </summary>
/// <param name="xnl">XML node list containing the "bitmaps" node's children</param>
private void LoadFontXML_bitmaps(XmlNodeList xnl)
{
foreach (XmlNode xn in xnl)
{
if (xn.Name == "bitmap")
{
string strID = GetXMLAttribute(xn, "id");
string strFilename = GetXMLAttribute(xn, "name");
string strSize = GetXMLAttribute(xn, "size");
string[] aSize = strSize.Split('x');
// if the ContentManager is being used, then we may need to strip off the .png extension
// to generate the correct asset name.
if (m_content != null && strFilename.EndsWith(@".png"))
strFilename = strFilename.Remove(strFilename.Length - 4, 4);
BitmapInfo bminfo;
bminfo.strFilename = strFilename;
bminfo.nX = Int32.Parse(aSize[0]);
bminfo.nY = Int32.Parse(aSize[1]);
m_dictBitmapID2BitmapInfo[Int32.Parse(strID)] = bminfo;
}
}
}
/// <summary>
/// Load the data from the "glyphs" node
/// </summary>
/// <param name="xnl">XML node list containing the "glyphs" node's children</param>
private void LoadFontXML_glyphs(XmlNodeList xnl)
{
foreach (XmlNode xn in xnl)
{
if (xn.Name == "glyph")
{
string strChar = GetXMLAttribute(xn, "ch");
string strBitmapID = GetXMLAttribute(xn, "bm");
string strLoc = GetXMLAttribute(xn, "loc");
string strSize = GetXMLAttribute(xn, "size");
string strAW = GetXMLAttribute(xn, "aw");
string strLSB = GetXMLAttribute(xn, "lsb");
string strForceWhite = GetXMLAttribute(xn, "forcewhite");
if (strLoc == "")
strLoc = GetXMLAttribute(xn, "origin"); // obsolete - use loc instead
string[] aLoc = strLoc.Split(',');
string[] aSize = strSize.Split('x');
GlyphInfo ginfo = new GlyphInfo();
ginfo.nBitmapID = UInt16.Parse(strBitmapID);
ginfo.pxLocX = Byte.Parse(aLoc[0]);
ginfo.pxLocY = Byte.Parse(aLoc[1]);
ginfo.pxWidth = Byte.Parse(aSize[0]);
ginfo.pxHeight = Byte.Parse(aSize[1]);
ginfo.pxAdvanceWidth = Byte.Parse(strAW);
ginfo.pxLeftSideBearing = SByte.Parse(strLSB);
ginfo.nFlags = 0;
ginfo.nFlags |= (strForceWhite == "true" ? GlyphFlags.ForceWhite : GlyphFlags.None);
m_dictUnicode2GlyphInfo[strChar[0]] = ginfo;
}
}
}
/// <summary>
/// Load the data from the "kernpairs" node
/// </summary>
/// <param name="xnl">XML node list containing the "kernpairs" node's children</param>
private void LoadFontXML_kernpairs(XmlNodeList xnl)
{
foreach (XmlNode xn in xnl)
{
if (xn.Name == "kernpair")
{
string strLeft = GetXMLAttribute(xn, "left");
string strRight = GetXMLAttribute(xn, "right");
string strAdjust = GetXMLAttribute(xn, "adjust");
char chLeft = strLeft[0];
char chRight = strRight[0];
// create a kern dict for the left char if needed
if (!m_dictKern.ContainsKey(chLeft))
m_dictKern[chLeft] = new Dictionary<char, sbyte>();
// add the right char to the left char's kern dict
Dictionary<char, sbyte> kern2 = m_dictKern[chLeft];
kern2[chRight] = SByte.Parse(strAdjust);
}
}
}
/// <summary>
/// Get the XML attribute value
/// </summary>
/// <param name="n">XML node</param>
/// <param name="strAttr">Attribute name</param>
/// <returns>Attribute value, or the empty string if the attribute doesn't exist</returns>
private static string GetXMLAttribute(XmlNode n, string strAttr)
{
XmlAttribute attr = n.Attributes.GetNamedItem(strAttr) as XmlAttribute;
if (attr != null)
return attr.Value;
return "";
}
So, here's my question(s), in a smaller chunk for those that want that:
- Am I using the easiest way to kern bitmap fonts in XNA?
- If I am, can someone help me convert the posted code from using XML classes to LINQ classes?
If you require more information or background, don't hesitate to ask.
Thanks in advance for your time spent in assisting me.