ugui学习 - 字形、文本排版信息

Text的文本排版是通过TextGenerator来实现的,它把排版的细节封装在c++层了,我们无法看到,但可以在c#层获取到排版后的详细信息,包括:

每个字形(Glyph)的排版信息,行的排版信息等。

通过把排版后的信息打印出来,我们就可以大致了解排版的原理

using UnityEditor;
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Text))]
public class TestTextGen : MonoBehaviour
{

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            var text = GetComponent<Text>();
            PrintFontInfo(text.font);
            Debug.Log($"unitPerPixel: {1 / text.pixelsPerUnit}, {text.pixelsPerUnit}");

            var textGen = text.cachedTextGenerator;
            PrintTextGenInfo(text.text, 1/text.pixelsPerUnit, textGen);
        }
    }

    static void PrintFontInfo(Font f)
    {
        Debug.Log($"===== font:{f.name}");
        Debug.Log($"fontSize:{f.fontSize}, dynamic:{f.dynamic}, ascent:{f.ascent}, lineHeight:{f.lineHeight}");
        
#if UNITY_EDITOR
        SerializedObject so = new SerializedObject(f);
        float fontSize = so.FindProperty("m_FontSize").floatValue;
        float ascent = so.FindProperty("m_Ascent").floatValue;
        float lineSpacing = so.FindProperty("m_LineSpacing").floatValue;
        float descent = 0;
        SerializedProperty sp_Descent = so.FindProperty("m_Descent");
        if (sp_Descent != null)
            descent = sp_Descent.floatValue;
        Debug.Log($"fontSize:{fontSize}, ascent:{ascent}, lineSpacing:{lineSpacing}, descent:{descent}");
#endif

        var fontMat = f.material;
        if (f.material)
        {
            Texture fontTex = fontMat.mainTexture;
            if (fontTex)
                Debug.Log($"texture:{fontTex.name}, size:({fontTex.width}, {fontTex.height})");
            else
                Debug.Log($"no font Texture");
        }
        else
        {
            Debug.Log($"no font mat");
        }

        Debug.Log($"=====");
    }

    static void PrintTextGenInfo(string text, float unitsPerPixel, TextGenerator textGen)
    {
        Debug.Log($"========== TextGenInfo");

        var chs = textGen.characters;
        Debug.Log($"charCount:{chs.Count}, {textGen.characterCount}, visible:{textGen.characterCountVisible}");
        for (int i = 0; i < chs.Count; ++i)
        {
            var ch = chs[i];
            if (i < text.Length)
                Debug.Log($"ch_{i}: '{text[i]}', w:{ch.charWidth * unitsPerPixel}, cursoPos:{(ch.cursorPos * unitsPerPixel).ToStr()}");
            else
                Debug.Log($"ch_{i}: w:{ch.charWidth * unitsPerPixel}, cursoPos:{(ch.cursorPos * unitsPerPixel).ToStr()}");
        }

        //cursorPos和verts_lt的坐标区别是: cursorPos不会超出Text限定框、不会字符重叠, y方向是贴着lineTop,
        var verts = textGen.verts;
        Debug.Log($"vertCount:{verts.Count}, {textGen.vertexCount}");
        for (var i = 0; i < verts.Count; i += 4)
        {
            var lt = verts[i].position * unitsPerPixel;
            var rt = verts[i + 1].position * unitsPerPixel;
            var rb = verts[i + 2].position * unitsPerPixel;
            var rl = verts[i + 3].position * unitsPerPixel;
            var chIndex = i / 4;
            Debug.Log($"quad_{chIndex}: lt:{lt.ToStr()}, rt:{rt.ToStr()}, rb:{rb.ToStr()}, rl:{rl.ToStr()}");
        }

        var lines = textGen.lines;
        Debug.Log($"lineCount:{lines.Count}, {textGen.lineCount}");
        float lastBottomY = 0;
        for (int i = 0; i < lines.Count; ++i)
        {
            var l = lines[i];
            float topY = l.topY * unitsPerPixel;
            float height = l.height * unitsPerPixel;
            float leading = l.leading * unitsPerPixel;
            float bottomY = topY - height;
            if (i > 0)
            {
                float leading2 = topY - lastBottomY;
                Debug.Log($"line_{i}: charIndex:{l.startCharIdx}, lineLeading:{leading}_{leading2}, lineHeight:{height}, line_topY:{topY}, line_bottomY:{bottomY}");
            }
            else
            {
                Debug.Log($"line_{i}: charIndex:{l.startCharIdx}, lineLeading:{leading}, lineHeight:{height}, line_topY:{topY}, line_bottomY:{bottomY}");
            }
            lastBottomY = bottomY;
        }

        Debug.Log($"==========");
    }

}

注意:TextGenerator获取到的坐标,大小等信息要乘unitsPerPixel(即: 1 / text.pixelsPerUnit),来从pixel转换成unit单位的,至于为啥这么做,Text的源码实现里是这么做的。

转换后的值,才能与其他像素单位的值做加减。

 

排版示例1

绿色的线为行top, 蓝色的线为行bottom, 红色的线为基线

1) ascent: 行top位置往下移动ascent的距离就是基线所在位置

2) descent: 基线位置往下移动descent的距离就是行bottom所在的位置

3) line height: 行top和行bottom间的距离就是行高

4) line leading: 下一行top和上一行bottom间的距离就是行间距

计算方式:line leading的大小 = math.ceil(font.lineSpacing * Text.lineSpacing) - lineHeight

 

排版示例2

 

TextGenerator的API

排版

TextGenerator.Populate(text, settings); //执行排版
TextGenerator.PopulateWithErrors(text, settings, gameObject);

TextGenerator.GetPreferredWidth(text, settings); //排版并返回测算的宽度
TextGenerator.GetPreferredHeight(text, settings);
 
//Populate和GetPreferredXxx内部都会调用Font.RequestCharactersInTexture,在字体贴图上渲染字符图片

TextGenerator.Invalidate(); //丢弃当前的排版结果

获取信息

TextGenerator.rectExtents;
TextGenerator.fontSizeUsedForBestFit;

//字符信息
TextGenerator.characterCount;
TextGenerator.characterCountVisible;
TextGenerator.characters; //获取缓存好的字符信息List
TextGenerator.GetCharacters; //重新获取字符信息List
TextGenerator.GetCharactersArray;

//渲染用的顶点信息
TextGenerator.vertexCount;
TextGenerator.verts;
TextGenerator.GetVertices;
TextGenerator.GetVerticesArray;

//行信息
TextGenerator.lineCount;
TextGenerator.lines;
TextGenerator.GetLines;
TextGenerator.GetLinesArray;

 

posted @ 2023-08-12 23:44  yanghui01  阅读(90)  评论(0编辑  收藏  举报