文章摘要
本文以NGUI UILabel为例,解析了文本渲染的核心原理。UILabel通过精确计算每个字符的位置坐标(包括起始点、字符宽度、换行处理等),将文字整齐地呈现在屏幕上。关键流程包括:1)根据对齐方式和控件尺寸确定起始点;2)查询字体贴图获取每个字符的宽度和偏移;3)动态计算换行位置;4)处理特殊样式。源码分析展示了如何遍历文本、计算顶点坐标、处理对齐和换行。整个过程就像一个自动排版系统,确保每个字符都能精准定位,最终实现美观的文本显示效果。
一、形象比喻
想象你在做一本杂志的排版员,要把一段话整齐地印在一页纸上。你会怎么做?
- 一行一行排:你先决定每行能放多少字,遇到边界就换行。
- 一个字一个字贴:每贴一个字,你都要量好它的左上角应该放在哪儿。
- 遇到特殊情况:比如有的字要加粗、变色、加阴影,你要提前预留好空间。
UILabel 就像这样一个“自动排版员”,它会把每个字精确地“贴”到屏幕上的正确位置。
二、技术原理
1. 起始点
- UILabel 会根据对齐方式(左对齐、居中、右对齐)和控件的尺寸,确定每一行的起始坐标(比如左上角)。
2. 字符宽度
- 每个字符在字体贴图里都有自己的宽度(有的字宽,有的字窄)。
- UILabel 会查找每个字符的 advance(前进宽度),决定下一个字的起点。
3. 换行处理
- 如果当前行放不下下一个字,UILabel 会自动换到下一行,重新计算起点。
4. 特殊样式
- 如果有缩进、行间距、描边等,都会在计算坐标时加上偏移。
三、源码片段(简化版)
float x = startX; // 当前行的起始x坐标
float y = startY; // 当前行的y坐标
for (int i = 0; i < text.Length; ++i)
{
char c = text[i];
BMGlyph glyph = font.GetGlyph(c);
// 计算当前字符的左下角坐标
float vx0 = x + glyph.offsetX;
float vy0 = y - glyph.offsetY;
// 计算右上角
float vx1 = vx0 + glyph.width;
float vy1 = vy0 - glyph.height;
// 记录顶点(vx0, vy0)...(vx1, vy1)
// 移动x到下一个字符位置
x += glyph.advance;
// 如果x超出行宽,换行
if (x > maxLineWidth)
{
x = startX;
y -= lineHeight;
}
}
四、再举个例子
假设你要渲染“你好,世界!”:
- UILabel 先确定第一行的起点,比如(0, 0)。
- “你”字贴在(0, 0)。
- “好”字贴在(0 + “你”字宽, 0)。
- “,”贴在(0 + “你”字宽 + “好”字宽, 0)。
- 以此类推,直到一行放不下,y坐标减去一行高度,x回到起点,开始新的一行。
五、总结一句话
UILabel 就像一个聪明的排版机器人,拿着尺子和字模,把每个字精确地贴在屏幕上的正确位置,保证文本整齐美观。
下面我会以NGUI UILabel的源码为基础,详细展示“计算每个字符的位置”相关的核心代码,并配合注释说明。
1. 相关核心成员
UILabel 主要依赖于字体(UIFont/BMFont)、文本内容、控件尺寸、对齐方式等信息。
// 伪代码/简化版
string mProcessedText; // 处理后的文本
UIFont mFont; // 字体对象
float mLineWidth; // 行宽
float mLineHeight; // 行高
int mMaxLineCount; // 最大行数
NGUIText.Alignment mAlignment; // 对齐方式
2. 计算字符位置的详细流程
2.1 文本分行与排版
NGUI 通过 NGUIText.WrapText 方法将文本分行,并计算每行的内容和宽度。
// NGUIText.cs 伪代码
public static bool WrapText(string text, out List<string> lines, out List<float> lineWidths)
{
lines = new List<string>();
lineWidths = new List<float>();
float currentLineWidth = 0f;
string currentLine = "";
for (int i = 0; i < text.Length; ++i)
{
char c = text[i];
BMGlyph glyph = font.GetGlyph(c);
float glyphWidth = glyph.advance;
// 判断是否超出行宽
if (currentLineWidth + glyphWidth > mLineWidth)
{
lines.Add(currentLine);
lineWidths.Add(currentLineWidth);
currentLine = "";
currentLineWidth = 0f;
}
currentLine += c;
currentLineWidth += glyphWidth;
}
// 添加最后一行
if (currentLine.Length > 0)
{
lines.Add(currentLine);
lineWidths.Add(currentLineWidth);
}
return true;
}
2.2 计算每个字符的具体位置
在 UILabel.OnFill 或 NGUIText.Print 方法中,遍历每一行、每个字符,计算顶点坐标。
// NGUIText.cs Print方法核心片段
public static void Print(string text, List<Vector3> verts, List<Vector2> uvs, List<Color32> cols)
{
float y = startY; // 顶部起点
for (int lineIndex = 0; lineIndex < lines.Count; ++lineIndex)
{
string line = lines[lineIndex];
float x = startX;
// 对齐方式处理
if (alignment == Alignment.Center)
x += (mLineWidth - lineWidths[lineIndex]) * 0.5f;
else if (alignment == Alignment.Right)
x += (mLineWidth - lineWidths[lineIndex]);
for (int i = 0; i < line.Length; ++i)
{
char c = line[i];
BMGlyph glyph = font.GetGlyph(c);
// 字形偏移
float vx0 = x + glyph.offsetX;
float vy0 = y - glyph.offsetY;
float vx1 = vx0 + glyph.width;
float vy1 = vy0 - glyph.height;
// 添加顶点
verts.Add(new Vector3(vx0, vy0));
verts.Add(new Vector3(vx0, vy1));
verts.Add(new Vector3(vx1, vy1));
verts.Add(new Vector3(vx1, vy0));
// 添加UV
uvs.Add(glyph.uvBottomLeft);
uvs.Add(glyph.uvTopLeft);
uvs.Add(glyph.uvTopRight);
uvs.Add(glyph.uvBottomRight);
// 添加颜色
cols.Add(currentColor);
cols.Add(currentColor);
cols.Add(currentColor);
cols.Add(currentColor);
// 移动到下一个字符位置
x += glyph.advance;
}
// 换到下一行
y -= mLineHeight;
}
}
2.3 字形数据结构
// BMGlyph 结构体
public class BMGlyph
{
public int index; // 字符索引
public int x, y; // 在字体贴图上的位置
public int width, height; // 字形宽高
public int offsetX, offsetY; // 渲染时的偏移
public int advance; // 下一个字符的起始x偏移
public Vector2 uvBottomLeft, uvTopLeft, uvTopRight, uvBottomRight; // UV坐标
}
3. 关键点说明
- 每个字符的x/y坐标 = 当前行起点 + 字形偏移 + 前面所有字符的advance累加
- 对齐方式会影响每行的起始x坐标
- 换行时,y坐标减去一行高度
- 特殊字符(如空格、制表符、换行符)有特殊advance和处理逻辑
- 多语言时,字体贴图和advance会有差异,但原理一致
4. 形象总结
UILabel就像一个“自动贴字机”,
- 先把每行能放多少字算好,
- 再一个个字“贴”到合适的位置,
- 每贴一个字都要查字模、量尺寸、算偏移,
- 保证每个字都整整齐齐地排在屏幕上。
5772

被折叠的 条评论
为什么被折叠?



