ugui学习 - 字形度量, 文本排版

发布时间 2023-08-12 23:49:33作者: yanghui01

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

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

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

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, 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, 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}, cursoPos:{ch.cursorPos.ToStr()}");
            else
                Debug.Log($"ch_{i}: w:{ch.charWidth}, cursoPos:{ch.cursorPos.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;
            var rt = verts[i + 1].position;
            var rb = verts[i + 2].position;
            var rl = verts[i + 3].position;
            Debug.Log($"quad_{i/4}: lt:{lt.ToStr()}, rt:{rt.ToStr()}, rb:{rb.ToStr()}, rl:{rl.ToStr()}");
        }

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

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

}

 

排版示例1

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

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

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

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

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

 

排版示例2