Search Unity

Custom Font System - The Start

Discussion in 'Immediate Mode GUI (IMGUI)' started by UnityDigger, Dec 2, 2011.

  1. UnityDigger

    UnityDigger

    Joined:
    Nov 20, 2011
    Posts:
    79
    Hi there.

    (...to not be lost in space...)

    - Is there a way to draw text in custom way?
    - Now i know how.

    $cfs.png

    Shared code (this will be used in both editor- and game-mode):
    Code (csharp):
    1.  
    2. public class CharacterInfo
    3. {
    4.     public int index; // character no (e.g. 32 for space)
    5.     public float width; // character width
    6.     public Rect uv; // uv coords
    7.     public Rect vert; // vertices info
    8. }
    9.  
    10. public class FontInfo
    11. {
    12.     public string Name;
    13.     public float Kerning;
    14.     public float LineSpacing;
    15.     public float Ascent;
    16.     public int ConvertCase;
    17.     public Dictionary<int, CharacterInfo> CharacterRects = new Dictionary<int, CharacterInfo> ();
    18.  
    19.     public FontInfo(){}
    20.  
    21.     public FontInfo(string xml)
    22.     {
    23.         XmlDocument doc = new XmlDocument();
    24.         doc.LoadXml(xml);
    25.         XmlNode root = doc.SelectSingleNode("cfs");
    26.         var attrs = root.Attributes;
    27.         Name = attrs["name"].Value;
    28.         Kerning = float.Parse(attrs["kerning"].Value,NumberFormatInfo.InvariantInfo);
    29.         LineSpacing = float.Parse(attrs["linespacing"].Value,NumberFormatInfo.InvariantInfo);
    30.         Ascent = float.Parse(attrs["ascent"].Value,NumberFormatInfo.InvariantInfo);
    31.         ConvertCase = int.Parse(attrs["convertcase"].Value,NumberFormatInfo.InvariantInfo);
    32.         var nodes = root.SelectNodes("char");
    33.         foreach(XmlNode node in nodes)
    34.         {
    35.             var ci = new CharacterInfo();
    36.             ci.index = int.Parse( node.Attributes["index"].Value,NumberFormatInfo.InvariantInfo);
    37.             ci.width = float.Parse( node.Attributes["width"].Value,NumberFormatInfo.InvariantInfo);
    38.             ci.uv = TextToRect(node.Attributes["uv"].Value);
    39.             ci.vert = TextToRect(node.Attributes["vert"].Value);
    40.             CharacterRects.Add(ci.index,ci);
    41.         }
    42.     }
    43.  
    44.     public CharacterInfo GetInfo(int index)
    45.     {
    46.         CharacterInfo result;
    47.         return CharacterRects.TryGetValue(index,out result) ? result : null;
    48.     }
    49.  
    50.     public string ToXml()
    51.     {
    52.         StringBuilder sb = new StringBuilder();
    53.         sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "<cfs name=\"{0}\" kerning=\"{1}\" linespacing=\"{2}\" ascent=\"{3}\" convertcase=\"{4}\">",Name, Kerning, LineSpacing, Ascent, ConvertCase));
    54.         foreach (var ci in CharacterRects.Values)
    55.         {
    56.             sb.AppendLine(string.Format("<char index=\"{0}\" width=\"{1}\" uv=\"{2}\" vert=\"{3}\" />",ci.index,ci.width,RectToText(ci.uv),RectToText(ci.vert)));
    57.         }
    58.         sb.AppendLine("</cfs>");
    59.         return sb.ToString();
    60.     }
    61.  
    62.     static string RectToText(Rect rect)
    63.     {
    64.         return string.Format(CultureInfo.InvariantCulture, "{0};{1};{2};{3}",rect.x,rect.y,rect.width,rect.height);
    65.     }
    66.  
    67.     static Rect TextToRect(string text)
    68.     {
    69.         var args = text.Split(';');
    70.         return new Rect(float.Parse(args[0],NumberFormatInfo.InvariantInfo),
    71.             float.Parse(args[1],NumberFormatInfo.InvariantInfo),
    72.             float.Parse(args[2],NumberFormatInfo.InvariantInfo),
    73.             float.Parse(args[3],NumberFormatInfo.InvariantInfo));
    74.     }
    75. }
    76.  
    Now the trick (editor code):
    Code (csharp):
    1.  
    2.     [MenuItem("CFS/Extract Selected Font")]
    3.     public static void ExtractSelectedFont()
    4.     {
    5.         var font = Selection.activeObject as Font;
    6.         if (font==null)
    7.         {
    8.           EditorUtility.DisplayDialog("CFS","Please, select font in project tree","OK");
    9.           return;
    10.         }
    11.  
    12.         var dst = EditorUtility.SaveFilePanelInProject("Saving file","cfs-font","xml","Please enter a file name to save cfs data");
    13.         Debug.Log(dst);
    14.         var fontinfo = GetFontInfo(font);
    15.  
    16.         File.WriteAllText(dst,fontinfo.ToXml());
    17.  
    18.         AssetDatabase.ImportAsset(dst);
    19.  
    20.         dst = dst.Replace(".xml",".png");
    21.  
    22.  
    23.         var tex = font.material.mainTexture as Texture2D;
    24.         var pixels = tex.GetPixels32();
    25.         for (int i=0; i<pixels.Length; i++)
    26.           pixels[i] = new Color32(255,255,255,pixels[i].a);
    27.  
    28.         tex = new Texture2D(tex.width,tex.height,TextureFormat.ARGB32,false);
    29.         tex.SetPixels32(pixels);
    30.         tex.Apply();
    31.  
    32.         File.WriteAllBytes(dst , tex.EncodeToPNG());
    33.  
    34.         Object.DestroyImmediate(tex);
    35.  
    36.         AssetDatabase.ImportAsset(dst);
    37.     }
    38.  
    39.     static FontInfo GetFontInfo(Font font)
    40.     {
    41.         var fi = new FontInfo();
    42.         var ser = new SerializedObject (font);
    43.         fi.Name = ser.FindProperty("m_Name").stringValue;
    44.         fi.Kerning = ser.FindProperty("m_Kerning").floatValue;
    45.         fi.LineSpacing = ser.FindProperty("m_LineSpacing").floatValue;
    46.         fi.Ascent = ser.FindProperty("m_LineSpacing").floatValue;
    47.         fi.ConvertCase = ser.FindProperty("m_ConvertCase").intValue;
    48.         int size = ser.FindProperty("m_CharacterRects.Array.size").intValue;
    49.         for(int i=0; i<size; i++)
    50.         {
    51.             var ci = new CharacterInfo();
    52.             ci.index = ser.FindProperty("m_CharacterRects.Array.data["+i+"].index").intValue;
    53.             ci.uv = ser.FindProperty("m_CharacterRects.Array.data["+i+"].uv").rectValue;
    54.             ci.vert = ser.FindProperty("m_CharacterRects.Array.data["+i+"].vert").rectValue;
    55.             ci.width = ser.FindProperty("m_CharacterRects.Array.data["+i+"].width").floatValue;
    56.             fi.CharacterRects.Add(ci.index,ci);
    57.         }
    58.         return fi;
    59.     }
    60.  

    How to use (3 draw calls example):

    1.Generate XML and Texture
    2.Create material and assign generated texture, also assign shader "Particles/Alpha Blended"
    3.Assign material to FontMaterial
    4.Assign xml to FontXml

    Code (csharp):
    1.  
    2. public class NewBehaviourScript : MonoBehaviour
    3. {
    4.     public Material FontMaterial;
    5.     public TextAsset FontXml;
    6.  
    7.     private FontInfo fi;
    8.  
    9.     void Start ()
    10.     {
    11.         fi = new FontInfo(FontXml.text);
    12.     }
    13.  
    14.     void Update ()
    15.     {
    16.        
    17.     }
    18.  
    19.     void OnGUI ()
    20.     {
    21.         if (Event.current.type == EventType.Repaint)
    22.         {
    23.             //TestTextUsingDrawTexture (new Rect (0, 0, Screen.width, 50), "1234567890 HELLO WORLD (Graphics.DrawTexture) very unoptimized!");
    24.             TestTextUsingGL (new Rect (0, 50, Screen.width, 50), "1234567890 HELLO WORLD (simple)");
    25.             TestTextUsingGLBounce (new Rect (0, 100, Screen.width, 50), "1234567890 HELLO WORLD (bounce)");
    26.             TestColoredTextUsingGL (new Rect (0, 150, Screen.width, 50), "1234567890 HELLO WORLD (colors)");
    27.         }
    28.     }
    29.  
    30.     void TestTextUsingDrawTexture (Rect rect, string text)
    31.     {
    32.         float x = rect.x;
    33.         float y = rect.y;
    34.         var tex = FontMaterial.mainTexture;
    35.         for (int i = 0; i < text.Length; i++)
    36.         {
    37.             int index = text[i];
    38.             var ci = fi.GetInfo(index);
    39.             if (ci != null)
    40.             {
    41.                 var screenRect = new Rect (x + ci.vert.x, y - ci.vert.y, ci.vert.width, -ci.vert.height);
    42.                 var uvRect = ci.uv;
    43.                 Graphics.DrawTexture (screenRect, tex, uvRect, 0, 0, 0, 0, Color.gray, FontMaterial);
    44.                 x += ci.width;
    45.             }
    46.             else
    47.             {
    48.                 x += fi.Ascent;
    49.             }
    50.         }
    51.     }
    52.  
    53.     void TestTextUsingGL (Rect rect, string text)
    54.     {
    55.         GL.PushMatrix();
    56.         GL.LoadPixelMatrix();
    57.         FontMaterial.SetPass(0);
    58.         GL.Begin(GL.QUADS);
    59.  
    60.         GL.Color(Color.yellow);
    61.  
    62.         float x = rect.x;
    63.         float y = rect.y;
    64.  
    65.         for (int i = 0; i < text.Length; i++)
    66.         {
    67.             int index = text[i];
    68.             var ci = fi.GetInfo(index);
    69.             if (ci != null)
    70.             {
    71.                 var scrRect = new Rect (x + ci.vert.x,  (Screen.height-y) - (-ci.vert.height - ci.vert.y) , ci.vert.width, -ci.vert.height);
    72.                 var texRect = ci.uv;
    73.  
    74.                 GL.TexCoord2(texRect.xMin, texRect.yMin);
    75.                 GL.Vertex3(scrRect.xMin,scrRect.yMin,0);
    76.  
    77.                 GL.TexCoord2(texRect.xMin, texRect.yMax);
    78.                 GL.Vertex3(scrRect.xMin,scrRect.yMax,0);
    79.  
    80.                 GL.TexCoord2(texRect.xMax, texRect.yMax);
    81.                 GL.Vertex3(scrRect.xMax,scrRect.yMax,0);
    82.  
    83.                 GL.TexCoord2(texRect.xMax, texRect.yMin);
    84.                 GL.Vertex3(scrRect.xMax,scrRect.yMin,0);
    85.  
    86.                 x += ci.width;
    87.             }
    88.             else
    89.             {
    90.                 x += fi.Ascent;
    91.             }
    92.         }
    93.  
    94.  
    95.         GL.End();
    96.  
    97.         GL.PopMatrix();
    98.     }
    99.  
    100.     void TestTextUsingGLBounce (Rect rect, string text)
    101.     {
    102.         GL.PushMatrix();
    103.         GL.LoadPixelMatrix();
    104.         FontMaterial.SetPass(0);
    105.         GL.Begin(GL.QUADS);
    106.  
    107.         GL.Color(Color.white);
    108.  
    109.         float x = rect.x;
    110.         float y = rect.y;
    111.  
    112.         for (int i = 0; i < text.Length; i++)
    113.         {
    114.             int index = text[i];
    115.             var ci = fi.GetInfo(index);
    116.             if (ci != null)
    117.             {
    118.                 var scrRect = new Rect (x + ci.vert.x,  (Screen.height-y) - (-ci.vert.height - ci.vert.y) , ci.vert.width, -ci.vert.height);
    119.                 var texRect = ci.uv;
    120.  
    121.                 var bounce = (i1)==0? 0 : 3;
    122.  
    123.                 GL.TexCoord2(texRect.xMin, texRect.yMin);
    124.                 GL.Vertex3(scrRect.xMin,scrRect.yMin+bounce,0);
    125.  
    126.                 GL.TexCoord2(texRect.xMin, texRect.yMax);
    127.                 GL.Vertex3(scrRect.xMin,scrRect.yMax+bounce,0);
    128.  
    129.                 GL.TexCoord2(texRect.xMax, texRect.yMax);
    130.                 GL.Vertex3(scrRect.xMax,scrRect.yMax+bounce,0);
    131.  
    132.                 GL.TexCoord2(texRect.xMax, texRect.yMin);
    133.                 GL.Vertex3(scrRect.xMax,scrRect.yMin+bounce,0);
    134.  
    135.                 x += ci.width;
    136.             }
    137.             else
    138.             {
    139.                 x += fi.Ascent;
    140.             }
    141.         }
    142.  
    143.  
    144.         GL.End();
    145.  
    146.         GL.PopMatrix();
    147.     }
    148.  
    149.     void TestColoredTextUsingGL (Rect rect, string text)
    150.     {
    151.         GL.PushMatrix();
    152.         GL.LoadPixelMatrix();
    153.         FontMaterial.SetPass(0);
    154.         GL.Begin(GL.QUADS);
    155.  
    156.         float x = rect.x;
    157.         float y = rect.y;
    158.  
    159.         for (int i = 0; i < text.Length; i++)
    160.         {
    161.             int index = text[i];
    162.             var ci = fi.GetInfo(index);
    163.             if (ci != null)
    164.             {
    165.                 var scrRect = new Rect (x + ci.vert.x,  (Screen.height-y) - (-ci.vert.height - ci.vert.y) , ci.vert.width, -ci.vert.height);
    166.                 var texRect = ci.uv;
    167.  
    168.                 var color = (i1)==0 ? Color.red : Color.green;
    169.  
    170.                 GL.Color(color);
    171.  
    172.                 GL.TexCoord2(texRect.xMin, texRect.yMin);
    173.                 GL.Vertex3(scrRect.xMin,scrRect.yMin,0);
    174.  
    175.                 GL.TexCoord2(texRect.xMin, texRect.yMax);
    176.                 GL.Vertex3(scrRect.xMin,scrRect.yMax,0);
    177.  
    178.                 GL.TexCoord2(texRect.xMax, texRect.yMax);
    179.                 GL.Vertex3(scrRect.xMax,scrRect.yMax,0);
    180.  
    181.                 GL.TexCoord2(texRect.xMax, texRect.yMin);
    182.                 GL.Vertex3(scrRect.xMax,scrRect.yMin,0);
    183.  
    184.                 x += ci.width;
    185.             }
    186.             else
    187.             {
    188.                 x += fi.Ascent;
    189.             }
    190.         }
    191.  
    192.  
    193.         GL.End();
    194.  
    195.         GL.PopMatrix();
    196.     }
    197. }
    198.  
     
  2. wipeer

    wipeer

    Joined:
    Mar 23, 2009
    Posts:
    13
    Hi,
    can you explain how to make your code work?

    I am little confused here. :eek:

    TY
     
  3. UnityDigger

    UnityDigger

    Joined:
    Nov 20, 2011
    Posts:
    79
    Hi. Well... It's just code snippets... not for beginners ;-)
     
  4. Map-Builder

    Map-Builder

    Joined:
    Aug 29, 2012
    Posts:
    11
    Hi,

    All your functions are generating a screen space coordinates texture. Please, can you know a way to directly get the texture to reuse on a mesh instead of displaying it directly on the screen. Sorry, but I'm not familiar with GL operation.

    I'm sure, it's a way faster than GetPixels.

    Thanks.
     
  5. RamzaB

    RamzaB

    Joined:
    Nov 4, 2012
    Posts:
    42
    Wow man, it works !
    I just have to understand how the code works now :D
    Thank you very much for sharing this.