用于解析BMFont软件生成的fnt文件
using System; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; public class FntParse { public struct Kerning { public int first; public int second; public int amount; } private int m_ImageWidth; private int m_ImageHeight; private string[] m_ImageNames; private string m_FontName; private int m_FontSize; private int m_LineHeight; private int m_LineBaseHeight; private CharacterInfo[] m_CharInfos; private Kerning[] m_Kernings; public int imageWidth { get { return m_ImageWidth; } } public int imageHeight { get { return m_ImageHeight; } } public int imageCount { get { return m_ImageNames.Length; } } public string GetImageName(int index) { return m_ImageNames[index]; } public string fontName { get { return m_FontName; } } public int fontSize { get { return m_FontSize; } } public int lineHeight { get { return m_LineHeight; } } public int lineBaseHeight { get { return m_LineBaseHeight; } } public CharacterInfo[] charInfos { get { return m_CharInfos; } } public Kerning[] kernings { get { return m_Kernings; } } public static FntParse Parse(string text) { FntParse parse = null; if (text.StartsWith("info")) { parse = new FntParse(); parse.ParseText(text); } return parse; } private Regex m_KVRegexPattern; private bool m_PrintLine = true; private int m_LineNum; public void ParseText(string content) { m_KVRegexPattern = new Regex(@"(\S+)=""?([\w-.]+)""?"); var lines = content.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); m_LineNum = 0; //第1行 ParseInfoLine(lines[m_LineNum++]); //第2行 ParseCommonLine(lines[m_LineNum++]); //字体图片多张的话就有多行 for (int i = 0; i < m_ImageNames.Length; i++) { ParsePageLine(lines[m_LineNum++]); } // don't use count of chars, count is incorrect if has space //ParseCharsOrKerningCount(ref lines[3]); m_LineNum++; //chars行跳过 List<CharacterInfo> list = new List<CharacterInfo>(); //chars count之后是char int maxLineNum = lines.Length; while (m_LineNum < maxLineNum) { if (ParseCharLine(lines[m_LineNum], ref list)) m_LineNum++; else break; } m_CharInfos = list.ToArray(); // skip empty line while (m_LineNum < maxLineNum) { if (string.IsNullOrEmpty(lines[m_LineNum])) m_LineNum++; else break; } // kernings if (m_LineNum < maxLineNum) { int count = ParseCharsOrKerningCount(lines[m_LineNum++]); if (count > 0) { m_Kernings = new Kerning[count]; for (var i = 0; i < count; i++) { if (ParseKerningLine(lines[m_LineNum], ref m_Kernings[i])) m_LineNum++; else break; } }; } } public void PrintInfo() { Debug.Log($"font:{m_FontName}, fontSize:{m_FontSize}, lineHeight:{m_LineHeight}, lineBase:{m_LineBaseHeight}"); Debug.Log($"image size:{m_ImageWidth}, {m_ImageHeight}"); for (var i = 0; i < m_ImageNames.Length; ++i) { Debug.Log($"{i}, image name: {m_ImageNames[i]}"); } Debug.Log($"chars: {m_CharInfos.Length}"); for (var i = 0; i < m_CharInfos.Length; ++i) { var charInfo = m_CharInfos[i]; Debug.Log($"char: {charInfo.index}"); } if (null != m_Kernings) { Debug.Log($"kernings: {m_Kernings.Length}"); for (var i = 0; i < m_Kernings.Length; ++i) { var ker = m_Kernings[i]; Debug.Log($"kerning: {ker.first}, {ker.second}, {ker.amount}"); } } } private void ParseInfoLine(string infoLine) { MatchCollection result = m_KVRegexPattern.Matches(infoLine); for (var i = 0; i < result.Count; ++i) { Match item = result[i]; if (3 == item.Groups.Count) { var key = item.Groups[1].Value; var value = item.Groups[2].Value; //Debug.Log($"info: key:'{key}', value:'{value}'"); switch (key) { case "face": m_FontName = value; break; case "size": m_FontSize = int.Parse(value); break; } } else { /* Debug.Log($"groups: {item.Groups.Count}"); for (var j = 0; j < item.Groups.Count; ++j) { Group g = item.Groups[j]; Debug.Log($"{j}: v:{g.Value}, index:{g.Index}, len:{g.Length}, succ:{g.Success}"); } */ } } } private void ParseCommonLine(string commonLine) { MatchCollection result = m_KVRegexPattern.Matches(commonLine); for (var i = 0; i < result.Count; ++i) { Match item = result[i]; if (3 == item.Groups.Count) { var key = item.Groups[1].Value; var value = item.Groups[2].Value; switch (key) { case "lineHeight": m_LineHeight = int.Parse(value); break; case "base": m_LineBaseHeight = int.Parse(value); break; //基线在Ascent向下多少距离的敌方 case "scaleW": m_ImageWidth = int.Parse(value); break; case "scaleH": m_ImageHeight = int.Parse(value); break; case "pages": var num = int.Parse(value); m_ImageNames = new string[num]; if (num > 1) Debug.LogWarning($"more than 1 font Images, only support 1 Image"); break; } } } } private void ParsePageLine(string pageLine) { int pageId = -1; string fileName = ""; MatchCollection result = m_KVRegexPattern.Matches(pageLine); for (var i = 0; i < result.Count; ++i) { Match item = result[i]; if (3 == item.Groups.Count) { var key = item.Groups[1].Value; var value = item.Groups[2].Value; //Debug.Log($"page: key='{key}', value='{value}'"); switch (key) { case "file": fileName = value; break; case "id": pageId = int.Parse(value); break; } } } if (-1 != pageId) m_ImageNames[pageId] = fileName; } private int ParseCharsOrKerningCount(string line) { MatchCollection result = m_KVRegexPattern.Matches(line); for (var i = 0; i < result.Count; ++i) { Match item = result[i]; if (3 == item.Groups.Count) { var key = item.Groups[1].Value; var value = item.Groups[2].Value; switch (key) { case "count": return int.Parse(value); } } } return 0; } private bool ParseCharLine(string charLine, ref List<CharacterInfo> list) { //if (m_PrintLine) Debug.Log($"{m_LineNum}: {charLine}"); if (!charLine.StartsWith("char ")) return false; int id = 0, x = 0, y = 0, width = 0, height = 0; int xoffset = 0, yoffset = 0, xadvance = 0; int page = 0; MatchCollection result = m_KVRegexPattern.Matches(charLine); for (var i = 0; i < result.Count; ++i) { Match item = result[i]; if (3 == item.Groups.Count) { var key = item.Groups[1].Value; var value = item.Groups[2].Value; //Debug.Log($"char: key:'{key}', value:'{value}'"); switch (key) { case "id": id = int.Parse(value); break; case "x": x = int.Parse(value); break; case "y": y = int.Parse(value); break; case "width": width = int.Parse(value); break; case "height": height = int.Parse(value); break; case "xoffset": xoffset = int.Parse(value); break; case "yoffset": yoffset = int.Parse(value); break; case "xadvance": xadvance = int.Parse(value); break; case "page": page = int.Parse(value); break; } } } list.Add(CreateCharInfo(id, x, y, width, height, xoffset, yoffset, xadvance, page)); return true; } private bool ParseKerningLine(string kerningLine, ref Kerning kerning) { if (!kerningLine.StartsWith("kerning")) return false; MatchCollection result = m_KVRegexPattern.Matches(kerningLine); for (var i = 0; i < result.Count; ++i) { Match item = result[i]; if (3 == item.Groups.Count) { var key = item.Groups[1].Value; var value = item.Groups[2].Value; //Debug.Log($"kerning: key:'{key}', value:'{value}'"); switch (key) { case "first": kerning.first = int.Parse(value); break; case "second": kerning.second = int.Parse(value); break; case "amount": kerning.amount = int.Parse(value); break; } } } return true; } /// <summary> /// 从fnt中的char行, 创建一个CharacterInfo对象 /// </summary> /// <param name="id">ascii码或unicode码</param> /// <param name="x">字体图片中的x坐标, 左上角为(0, 0)</param> /// <param name="y">字体图片中的y坐标, 左上角为(0, 0)</param> /// <param name="width">字符图片宽度</param> /// <param name="height">字符图片高度</param> /// <param name="xoffset">渲染字体时x方向的调整, 正表示向右, 负表示向左</param> /// <param name="yoffset">渲染字体时y方向的调整, 基于Ascent调整(正表示向下, 负表示向上)</param> /// <param name="xadvance">渲染字体时字体的宽度, 下一个字符从x+=xadvance处开始</param> /// <param name="page">在哪张字体图片上</param> /// <returns></returns> private CharacterInfo CreateCharInfo(int id, int x, int y, int width, int height, int xoffset, int yoffset, int xadvance, int page = 0) { Rect uv = new Rect(); uv.x = (float)x / m_ImageWidth + page; uv.y = (float)y / m_ImageHeight; uv.width = (float)width / m_ImageWidth; uv.height = (float)height / m_ImageHeight; //BMFont: 贴图原点在左上角, 以左上角为min右下角为max //Unity: 贴图原点在左下角, 以左下角为min右上角为max uv.y = 1f - uv.y - uv.height; Rect vert = new Rect(); //左上角和右下角 vert.xMin = xoffset; vert.xMax = xoffset + width; #if UNITY_5_0 || UNITY_5_1 || UNITY_5_2 // unity 5.0 can not support baseline for vert.yMin = -yoffset; vert.yMax = -yoffset - height; #else vert.yMin = m_LineBaseHeight + (-yoffset); //BMFont中正为向下调整, Unity中负为向下 vert.yMax = m_LineBaseHeight + (-yoffset) - height; #endif CharacterInfo charInfo = new CharacterInfo(); charInfo.index = id; #if UNITY_5_3_OR_NEWER || UNITY_5_3 || UNITY_5_2 //对应uv charInfo.uvBottomLeft = new Vector2(uv.xMin, uv.yMin); charInfo.uvBottomRight = new Vector2(uv.xMax, uv.yMin); charInfo.uvTopLeft = new Vector2(uv.xMin, uv.yMax); charInfo.uvTopRight = new Vector2(uv.xMax, uv.yMax); //对应vert charInfo.minX = (int)vert.xMin; charInfo.maxX = (int)vert.xMax; charInfo.minY = (int)vert.yMax; charInfo.maxY = (int)vert.yMin; //一般就是Ascent加上y方向的修正 charInfo.bearing = (int)vert.x; //xoffset //对应width charInfo.advance = xadvance; #else #pragma warning disable 618 charInfo.uv = uv; charInfo.vert = vert; charInfo.width = xadvance; #pragma warning restore 618 #endif return charInfo; } }
参考
GitHub - litefeel/Unity-BitmapFontImporter: An unity editor extension for bitmap font.
标签:解析器,文件,fnt,int,value,break,item,case,var From: https://www.cnblogs.com/sailJs/p/17511623.html