Quantcast
Channel: Community | MonoGame - Latest topics
Viewing all articles
Browse latest Browse all 6821

SpriteFont to .Cs Writer

$
0
0

@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

Read full topic


Viewing all articles
Browse latest Browse all 6821

Trending Articles