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;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!