@willmotil wrote:
The following class lets you pass a Content loaded spritefont instance to its constructor to deconstruct it. Then call Write and pass it a full file path without a extension though.
The output is a text file with a .cs extension that can be loaded as a class file into a new monogame project that is basically a hardcoded instance of a spritefont.
This could be useful as a small default spritefont which doesn't need to be loaded from the pipeline such that it could be included in your common utility's or engine that is just automatically there.
https://github.com/willmotil/MonoGame-SpriteFont-HardEncoder-To-Class
Edit took out my path write methods...
using System; using System.IO; using System.Text; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace SpriteFontEditToClassSomethingOrOther { public class SpriteFontManipulator { /// <summary> /// This holds the deconstructed sprite font data including a pixel color array. /// </summary> public SpriteFontData spriteFontData; private SpriteFont spriteFont; private SpriteFontToClassFileEncoderDecoder spriteFontToClassWriter = new SpriteFontToClassFileEncoderDecoder(); /// <summary> /// Give this a sprite font to deconstruct it to data. /// </summary> public SpriteFontManipulator(SpriteFont spriteFont) { this.spriteFont = spriteFont; // how to handle disposing the fonts texture in this particular case im not sure. spriteFontData = new SpriteFontData(); DeConstruct(spriteFontData); } /// <summary> /// Call this to write the font the file will be saved to the full path file /// This writes the file as a cs text file that can be loaded by another project as a actual c sharp class file into visual studio. /// When that generated class is instantiated and load is called on it, it then returns a hardcoded spritefont. /// </summary> public void WriteFileAsCs(string fullFilePath) { spriteFontToClassWriter.WriteFile(fullFilePath, spriteFontData); } // Deconstructs a spritefont. private void DeConstruct(SpriteFontData spriteFontData) { spriteFontData.fontTexture = spriteFont.Texture; spriteFontData.lineHeightSpaceing = spriteFont.LineSpacing; spriteFontData.spaceing = spriteFont.Spacing; spriteFontData.dgyphs = spriteFont.GetGlyphs(); spriteFontData.defaultglyph = new SpriteFont.Glyph(); if (spriteFont.DefaultCharacter.HasValue) { spriteFontData.defaultchar = (char)(spriteFont.DefaultCharacter.Value); spriteFontData.defaultglyph = spriteFontData.dgyphs[spriteFontData.defaultchar]; } else { // we could create a default value from like a pixel in the sprite font and add the glyph. } foreach (var item in spriteFontData.dgyphs) { spriteFontData.glyphBounds.Add(item.Value.BoundsInTexture); spriteFontData.glyphCroppings.Add(item.Value.Cropping); spriteFontData.glyphCharacters.Add(item.Value.Character); spriteFontData.glyphKernings.Add(new Vector3(item.Value.LeftSideBearing, item.Value.Width, item.Value.RightSideBearing)); } spriteFontData.numberOfGlyphs = spriteFontData.glyphCharacters.Count; spriteFontData.width = spriteFont.Texture.Width; spriteFontData.height = spriteFont.Texture.Height; Color[] colorarray = new Color[spriteFont.Texture.Width * spriteFont.Texture.Height]; spriteFont.Texture.GetData<Color>(colorarray); //,0, width* height List<Color> pixels = new List<Color>(); foreach (var c in colorarray) pixels.Add(c); spriteFontData.pixelColorData = pixels; } /// <summary> /// Holds the sprite fonts data including the pixel data. /// </summary> public class SpriteFontData { public Texture2D fontTexture; public List<Color> pixelColorData; public int width; public int height; public int numberOfGlyphs = 0; public Dictionary<char, SpriteFont.Glyph> dgyphs; public SpriteFont.Glyph defaultglyph; public float spaceing = 0; public int lineHeightSpaceing = 0; public char defaultchar = '*'; public List<Rectangle> glyphBounds = new List<Rectangle>(); public List<Rectangle> glyphCroppings = new List<Rectangle>(); public List<char> glyphCharacters = new List<char>(); public List<Vector3> glyphKernings = new List<Vector3>(); // left width right and side bering } /// <summary> /// Encoding and Decoding methods however the decoder is placed within the output class file. /// This allows the output class to be decoupled from the pipeline or even a ttf. /// This class doesn't have to be called unless you wish to do editing on a spriteFontData Instance. /// Then rewrite the encoded data to file later at a time of your own choosing /// </summary> public class SpriteFontToClassFileEncoderDecoder { StringBuilder theCsText = new StringBuilder(); public List<byte> rleEncodedByteData = new List<byte>(); public int width = 0; public int height = 0; // this is just a spacing thing to show the data a bit easier on the eyes. int dividerAmount = 10; int dividerCharSingleValueAmount = 50; int dividerSingleValueAmount = 200; private int pixels = 0; private int bytestallyed = 0; private int bytedataCount = 0; public void WriteFile(string fullFilePath, SpriteFontData sfData) { var filename = Path.GetFileNameWithoutExtension(fullFilePath); var colordata = sfData.pixelColorData; width = sfData.width; height = sfData.height; rleEncodedByteData = EncodeColorArrayToDataRLE(colordata, sfData.width, sfData.height); var charsAsString = CharArrayToStringClassFormat("chars", sfData.glyphCharacters.ToArray()); var boundsAsString = RectangleToStringClassFormat("bounds", sfData.glyphBounds.ToArray()); var croppingsAsString = RectangleToStringClassFormat("croppings", sfData.glyphCroppings.ToArray()); var kerningsAsString = Vector3ToStringClassFormat("kernings", sfData.glyphKernings.ToArray()); var rleDataAsString = ByteArrayToStringClassFormat("rleByteData", rleEncodedByteData.ToArray()); theCsText.Append( "\n//" + "\n// This file is programatically generated this class is hard coded instance data for a instance of a spritefont." + "\n// Use the LoadHardCodeSpriteFont to load it." + "\n// Be aware i believe you should dispose its texture in game1 unload as this won't have been loaded thru the content manager." + "\n//" + "\n//"); theCsText .Append("\n using System;") .Append("\n using System.Text;") .Append("\n using System.Collections.Generic;") .Append("\n using Microsoft.Xna.Framework;") .Append("\n using Microsoft.Xna.Framework.Graphics;") .Append("\n using Microsoft.Xna.Framework.Input;") .Append("\n") .Append("\n namespace Microsoft.Xna.Framework") .Append("\n {") .Append("\n ") .Append("\n public class SpriteFontAsClassFile_").Append(filename) .Append("\n {") .Append("\n ") .Append("\n int width=").Append(width).Append(";") .Append("\n int height=").Append(height).Append(";") .Append("\n char defaultChar = Char.Parse(\"").Append(sfData.defaultchar).Append("\");") .Append("\n int lineHeightSpaceing =").Append(sfData.lineHeightSpaceing).Append(";") .Append("\n float spaceing =").Append(sfData.spaceing).Append(";") .Append("\n ") .Append("\n public SpriteFont LoadHardCodeSpriteFont(GraphicsDevice device)") .Append("\n {") .Append("\n Texture2D t = DecodeToTexture(device, rleByteData, width, height);") .Append("\n return new SpriteFont(t, bounds, croppings, chars, lineHeightSpaceing, spaceing, kernings, defaultChar);") .Append("\n }") .Append("\n ") .Append("\n private Texture2D DecodeToTexture(GraphicsDevice device, List<byte> rleByteData, int _width, int _height)") .Append("\n {") .Append("\n Color[] colData = DecodeDataRLE(rleByteData);") .Append("\n Texture2D tex = new Texture2D(device, _width, _height);") .Append("\n tex.SetData<Color>(colData);") .Append("\n return tex;") .Append("\n }") .Append("\n ") .Append("\n private Color[] DecodeDataRLE(List<byte> rleByteData)") .Append("\n {") .Append("\n List <Color> colAry = new List<Color>();") .Append("\n for (int i = 0; i < rleByteData.Count; i++)") .Append("\n {") .Append("\n var val = (rleByteData[i] & 0x7F) * 2;") .Append("\n if (val > 252)") .Append("\n val = 255;") .Append("\n Color color = new Color();") .Append("\n if (val > 0)") .Append("\n color = new Color(val, val, val, val);") .Append("\n if ((rleByteData[i] & 0x80) > 0)") .Append("\n {") .Append("\n var runlen = rleByteData[i + 1];") .Append("\n for (int j = 0; j < runlen; j++)") .Append("\n colAry.Add(color);") .Append("\n i += 1;") .Append("\n }") .Append("\n colAry.Add(color);") .Append("\n }") .Append("\n return colAry.ToArray();") .Append("\n }") .Append("\n ") .Append("\n ").Append(charsAsString) .Append("\n ").Append(boundsAsString) .Append("\n ").Append(croppingsAsString) .Append("\n ").Append(kerningsAsString) .Append("\n ") .Append("\n // pixelsCompressed: ").Append(pixels).Append(" bytesTallied: ").Append(bytestallyed).Append(" byteDataCount: ").Append(bytedataCount) .Append("\n ").Append(rleDataAsString) .Append("\n ") .Append("\n ") .Append("\n }") .Append("\n ") .Append("\n }") // end of namespace .Append("\n ").Append(" ") ; //MgPathFolderFileOps.WriteStringToFile(fullFilePath, theCsText.ToString()); File.WriteAllText(fullFilePath, theCsText.ToString()); } public string CharArrayToStringClassFormat(string variableName, char[] c) { string s = "\n // Item count = " + c.Length + "\n List <char> " + variableName + " = new List<char> " + "\n {" + "\n " ; int divider = 0; for (int i = 0; i < c.Length; i++) { divider++; if (divider > dividerCharSingleValueAmount) { divider = 0; s += "\n "; } //s += $"new char(\"{c[i]})\""; s += "(char)" + (int)c[i] + ""; if (i < c.Length - 1) s += ","; } s += "\n };"; return s; } public string RectangleToStringClassFormat(string variableName, Rectangle[] r) { string s = "\n List < Rectangle > " + variableName + " = new List<Rectangle>" + "\n {" + "\n " ; int divider = 0; for (int i = 0; i < r.Length; i++) { divider++; if (divider > dividerAmount) { divider = 0; s += "\n "; } s += $"new Rectangle({r[i].X},{r[i].Y},{r[i].Width},{r[i].Height})"; if (i < r.Length - 1) s += ","; } s += "\n };"; return s; } public string Vector3ToStringClassFormat(string variableName, Vector3[] v) { string s = "\n List<Vector3> " + variableName + " = new List<Vector3>" + "\n {" + "\n " ; int divider = 0; for (int i = 0; i < v.Length; i++) { divider++; if (divider > dividerAmount) { divider = 0; s += "\n "; } s += $"new Vector3({v[i].X},{v[i].Y},{v[i].Z})"; if (i < v.Length - 1) s += ","; } s += "\n };"; return s; } public string ByteArrayToStringClassFormat(string variableName, byte[] b) { string s = "\n List<byte> " + variableName + " = new List<byte>" + "\n {" + "\n " ; int divider = 0; for (int i = 0; i < b.Length; i++) { divider++; if (divider > dividerSingleValueAmount) { divider = 0; s += "\n "; } s += b[i]; if (i < b.Length - 1) s += ","; } s += "\n };"; return s; } /// <summary> /// Turns the pixel data into run length encode text for a cs file. /// Technically this is only compressing the alpha byte of a array. /// I could pull out my old bit ziping algorithm but that is probably overkill. /// </summary> public List<byte> EncodeColorArrayToDataRLE(List<Color> colorArray, int pkdcolaryWidth, int pkdcolaryHeight) { List<byte> rleAry = new List<byte>(); int colaryIndex = 0; int colaryLen = colorArray.Count; int templen = pkdcolaryWidth * pkdcolaryHeight; int pixelsAccountedFor = 0; while (colaryIndex < colaryLen) { var colorMasked = (colorArray[colaryIndex].A / 2); //(pkdcolAry[colaryIndex].A & 0xFE); var encodedValue = colorMasked; var runLength = 0; // find the run length for this pixel. for (int i = 1; i < 255; i++) { var indiceToTest = colaryIndex + i; if (indiceToTest < colaryLen) { var testColorMasked = (colorArray[indiceToTest].A / 2); //(pkdcolAry[indiceToTest].A & 0xFE); if (testColorMasked == colorMasked) runLength = i; else i = 256; // break on diff } else i = 256; // break on maximum run length } Console.WriteLine("colaryIndex: " + colaryIndex + " rleAry index " + rleAry.Count + " Alpha: " + colorMasked * 2 + " runLength: " + runLength); if (runLength > 0) { encodedValue += 0x80; if (colaryIndex < colaryLen) { rleAry.Add((byte)encodedValue); rleAry.Add((byte)runLength); pixelsAccountedFor += 1 + runLength; } else throw new Exception("bug check index write out of bounds"); colaryIndex = colaryIndex + 1 + runLength; } else { if (colaryIndex < colaryLen) { rleAry.Add((byte)encodedValue); pixelsAccountedFor += 1; } else throw new Exception("Encoding bug check index write out of bounds"); colaryIndex = colaryIndex + 1; } } // pixels = pixelsAccountedFor; bytestallyed = pixelsAccountedFor * 4; bytedataCount = rleAry.Count; Console.WriteLine("EncodeColorArrayToDataRLE: rleAry.Count " + rleAry.Count + " pixels accounted for " + pixelsAccountedFor + " bytes tallied " + pixelsAccountedFor * 4); return rleAry; } /// <summary> /// This decodes the cs files hard coded rle pixel data to a texture. /// </summary> public Texture2D DecodeRleDataToTexture(GraphicsDevice device, List<byte> rleByteData, int _width, int _height) { Color[] colData = DecodeRleDataToPixelData(rleByteData); Texture2D tex = new Texture2D(device, _width, _height); tex.SetData<Color>(colData); return tex; } /// <summary> /// Decodes the class file hardcoded rle byte data to a color array. /// </summary> private Color[] DecodeRleDataToPixelData(List<byte> rleByteData) { List<Color> colAry = new List<Color>(); for (int i = 0; i < rleByteData.Count; i++) { var val = (rleByteData[i] & 0x7F) * 2; if (val > 252) val = 255; Color color = new Color(); if (val > 0) color = new Color(val, val, val, val); if ((rleByteData[i] & 0x80) > 0) { var runlen = rleByteData[i + 1]; for (int j = 0; j < runlen; j++) colAry.Add(color); i += 1; } colAry.Add(color); } return colAry.ToArray(); } } } }
It's possible to edit the deconstructed spritefont information as well or make a editor that works on the SpriteFontData instance. However this wont turn a spritefont into a actual ttf or anything like that.
Here is a example
The below post shows the result of using the above SpriteFontManipulator class to write the basic char set of the tahoma font to a class file that will recreate that spritefont from itself.
Posts: 1
Participants: 1