首页 > 其他分享 >fnt文件解析器

fnt文件解析器

时间:2023-06-28 23:44:55浏览次数:52  
标签:解析器 文件 fnt int value break item case var

 用于解析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

相关文章

  • epub文件解压
    <dependency><groupId>com.positiondev.epublib</groupId><artifactId>epublib-core</artifactId><version>3.1</version></dependency><!--html解析--><dependency><groupId>org.jsoup<......
  • Linux - Docker日志文件清理
    1.容器日志文件默认存放路径:/var/lib/docker,docker日志文件后缀是containerID+"-json.log",     查看各个日志文件大小:   1) ls-lh$(find/var/lib/docker/containers/-name*-json.log)       查询结果sample:  -rw-r-----1rootroot2......
  • inotifywait 监控文件修改实时格式化文件
    我们在学习laravel过程中,从文档网页复制代码,会有一些比较麻烦的问题。以《Laravel10中文文档》|LaravelChina社区(learnku.com)为例 直接点复制按钮会带后,想测试的代码的话,还有处理use语句选中复制时,网站会向剪贴板追加来源信息,还得手动处理。 这些手动处理......
  • 在vue文件中使用 deep深度选择器
    使用场景有的时候我们需要在父组件中去修改第三方组件或者子组件的样式就会使用到deep深度选择器。比如:App组件中定义了.title的样式,也想让Test子组件中的.title也应用对应的样式App.vue<template><divclass="app"><h1>app组件</h1><divclass="title">这是app......
  • 为什么在 C++ 中,类的静态成员变量需要在源文件中进行定义?
    为什么在C++中,类的静态成员变量需要在源文件中进行定义?类的静态成员变量需要在源文件中进行定义,以便在链接阶段能够正确地分配内存并为其分配地址。当你在类的头文件中声明一个静态成员变量时,这只是告诉编译器该变量存在,并将在某处定义。这是因为头文件通常被包含在多个源文件......
  • 头文件
        ......
  • gitignore 忽略文件不生效处理
    在git中如果想忽略掉某个文件,不让这个文件提交到版本库中,可以使用修改根目录中.gitignore文件的方法。但是有时候在项目开发过程中,因为忘记加上.gitignore忽略文件,导致编译的代码已经上传到Git服务器上面了,这时候即使把Git仓库上面的文件夹删掉了也没有用,因为已经被Gittrack......
  • webuploader http大文件断点续传上传
    ​ javaweb上传文件上传文件的jsp中的部分上传文件同样可以使用form表单向后端发请求,也可以使用ajax向后端发请求    1.通过form表单向后端发送请求         <formid="postForm"action="${pageContext.request.contextPath}/UploadServlet"method="post"e......
  • 给ansible的hosts文件加密及使用方法
    转: https://blog.csdn.net/Li_haiyu/article/details/125774440  ......
  • C#保存doc文件
    1、使用office组件(Microsoft.Office.Interop.Word)https://www.cnblogs.com/Joyce-mi7/p/17445396.html2、使用免费的 Spire.Dochttps://www.cnblogs.com/HoFei/p/17425140.html3、使用DocX插件https://blog.csdn.net/zhanfu2905/article/details/68948002https://www.cnblo......