1.3 基础属性
溢出类型
效果类型
字体类型
对齐方式
图文样式
渐变
边距
支持富文本
多行和最高行数
1.4 基本原理
NGUIText.Update(false)
:更新最终字形的大小、字符间隔finalSpacingX
、 一行高度finalLineHeight
和是否用符文等信息。NGUIText.WrapText(mText, out mProcessedText, true);
:
finalSpacingX
、 一行高度finalLineHeight
得到显示rect大小NGUIText.WrapText(mText, out mProcessedText, true);
判断目前字体大小是否能显示全部文字,直至满足条件或者字体大小不能再减小。NGUIText.WrapText(mText, out mProcessedText, true);
得到最终显示的文字。NGUIText.WrapText(mText, out mProcessedText, true);
得到最终显示的文字。并调用NGUIText.CalculatePrintedSize(mProcessedText);
得到显示的大小,重新计算widget的宽高2.1.3 代码
void ProcessText (bool legacyMode, bool full)
{
if (!isValid) return;
mChanged = true;
shouldBeProcessed = false;
float regionX = mDrawRegion.z - mDrawRegion.x;
float regionY = mDrawRegion.w - mDrawRegion.y;
NGUIText.rectWidth = legacyMode ? (mMaxLineWidth != 0 ? mMaxLineWidth : 1000000) : width;
NGUIText.rectHeight = legacyMode ? (mMaxLineHeight != 0 ? mMaxLineHeight : 1000000) : height;
NGUIText.regionWidth = (regionX != 1f) ? Mathf.RoundToInt(NGUIText.rectWidth * regionX) : NGUIText.rectWidth;
NGUIText.regionHeight = (regionY != 1f) ? Mathf.RoundToInt(NGUIText.rectHeight * regionY) : NGUIText.rectHeight;
mPrintedSize = Mathf.Abs(legacyMode ? Mathf.RoundToInt(cachedTransform.localScale.x) : defaultFontSize);
mScale = 1f;
if (NGUIText.regionWidth < 1 || NGUIText.regionHeight < 0)
{
mProcessedText = "";
return;
}
#if DYNAMIC_FONT
bool isDynamic = (trueTypeFont != null);
if (isDynamic && keepCrisp)
{
UIRoot rt = root;
if (rt != null) mDensity = (rt != null) ? rt.pixelSizeAdjustment : 1f;
}
else mDensity = 1f;
#endif
if (full) UpdateNGUIText();
if (mOverflow == Overflow.ResizeFreely)
{
NGUIText.rectWidth = 1000000;
NGUIText.regionWidth = 1000000;
}
if (mOverflow == Overflow.ResizeFreely || mOverflow == Overflow.ResizeHeight)
{
NGUIText.rectHeight = 1000000;
NGUIText.regionHeight = 1000000;
}
if (mPrintedSize > 0)
{
#if DYNAMIC_FONT
bool adjustSize = keepCrisp;
#endif
for (int ps = mPrintedSize; ps > 0; --ps)
{
#if DYNAMIC_FONT
// Adjust either the size, or the scale
if (adjustSize)
{
mPrintedSize = ps;
NGUIText.fontSize = mPrintedSize;
}
else
#endif
{
mScale = (float)ps / mPrintedSize;
#if DYNAMIC_FONT
NGUIText.fontScale = isDynamic ? mScale : ((float)mFontSize / mFont.defaultSize) * mScale;
#else
NGUIText.fontScale = ((float)mFontSize / mFont.defaultSize) * mScale;
#endif
}
NGUIText.Update(false);
// Wrap the text
bool fits = NGUIText.WrapText(mText, out mProcessedText, true);
if (mOverflow == Overflow.ShrinkContent && !fits)
{
if (--ps > 1) continue;
else break;
}
else if (mOverflow == Overflow.ResizeFreely)
{
mCalculatedSize = NGUIText.CalculatePrintedSize(mProcessedText);
mWidth = Mathf.Max(minWidth, Mathf.RoundToInt(mCalculatedSize.x));
if (regionX != 1f) mWidth = Mathf.RoundToInt(mWidth / regionX);
mHeight = Mathf.Max(minHeight, Mathf.RoundToInt(mCalculatedSize.y));
if (regionY != 1f) mHeight = Mathf.RoundToInt(mHeight / regionY);
if ((mWidth & 1) == 1) ++mWidth;
if ((mHeight & 1) == 1) ++mHeight;
}
else if (mOverflow == Overflow.ResizeHeight)
{
mCalculatedSize = NGUIText.CalculatePrintedSize(mProcessedText);
mHeight = Mathf.Max(minHeight, Mathf.RoundToInt(mCalculatedSize.y));
if (regionY != 1f) mHeight = Mathf.RoundToInt(mHeight / regionY);
if ((mHeight & 1) == 1) ++mHeight;
}
else
{
mCalculatedSize = NGUIText.CalculatePrintedSize(mProcessedText);
}
// Upgrade to the new system
if (legacyMode)
{
width = Mathf.RoundToInt(mCalculatedSize.x);
height = Mathf.RoundToInt(mCalculatedSize.y);
cachedTransform.localScale = Vector3.one;
}
break;
}
}
else
{
cachedTransform.localScale = Vector3.one;
mProcessedText = "";
mScale = 1f;
}
if (full)
{
NGUIText.bitmapFont = null;
#if DYNAMIC_FONT
NGUIText.dynamicFont = null;
#endif
}
}
2.2 OnFill
2.2.1 流程
UpdateNGUIText
:更新设置当前UILabel的属性给NGUITextNGUIText.Print(text, verts, uvs, cols)
:根据显示的文本填入几何数据到缓存中ApplyOffset
:根据Pivot类型调整顶点位置ApplyShadow
增加阴影处理,ApplyShadow的作用是将之前填入的当前UILabel最终显示的文本顶点、UV、Color数据重新填入一次,其中顶点做根据UILabel的mEffectDistance的属性做相应的偏差处理,Effect.Shadow是偏右下,因此呈现出阴影效果。2.2.2 代码
public override void OnFill (BetterList verts, BetterList uvs, BetterList cols)
{
if (!isValid) return;
int offset = verts.size;
Color col = color;
col.a = finalAlpha;
if (mFont != null && mFont.premultipliedAlphaShader) col = NGUITools.ApplyPMA(col);
if (QualitySettings.activeColorSpace == ColorSpace.Linear)
{
col.r = Mathf.GammaToLinearSpace(col.r);
col.g = Mathf.GammaToLinearSpace(col.g);
col.b = Mathf.GammaToLinearSpace(col.b);
}
string text = processedText;
int start = verts.size;
UpdateNGUIText();
NGUIText.tint = col;
NGUIText.Print(text, verts, uvs, cols);
NGUIText.bitmapFont = null;
#if DYNAMIC_FONT
NGUIText.dynamicFont = null;
#endif
// Center the content within the label vertically
Vector2 pos = ApplyOffset(verts, start);
// Effects don't work with packed fonts
if (mFont != null && mFont.packedFontShader) return;
// Apply an effect if one was requested
if (effectStyle != Effect.None)
{
int end = verts.size;
pos.x = mEffectDistance.x;
pos.y = mEffectDistance.y;
ApplyShadow(verts, uvs, cols, offset, end, pos.x, -pos.y);
if ((effectStyle == Effect.Outline) || (effectStyle == Effect.Outline8))
{
offset = end;
end = verts.size;
ApplyShadow(verts, uvs, cols, offset, end, -pos.x, pos.y);
offset = end;
end = verts.size;
ApplyShadow(verts, uvs, cols, offset, end, pos.x, pos.y);
offset = end;
end = verts.size;
ApplyShadow(verts, uvs, cols, offset, end, -pos.x, -pos.y);
if (effectStyle == Effect.Outline8)
{
offset = end;
end = verts.size;
ApplyShadow(verts, uvs, cols, offset, end, -pos.x, 0);
offset = end;
end = verts.size;
ApplyShadow(verts, uvs, cols, offset, end, pos.x, 0);
offset = end;
end = verts.size;
ApplyShadow(verts, uvs, cols, offset, end, 0, pos.y);
offset = end;
end = verts.size;
ApplyShadow(verts, uvs, cols, offset, end, 0, -pos.y);
}
}
}
if (onPostFill != null)
onPostFill(this, offset, verts, uvs, cols);
}
NGUIText.Print(text, verts, uvs, cols)
:(普通字符的处理、图文字符的处理),顶点、UV和Color数据分别按照左下、左上、右上、右下顺序填入缓存中。Font.RequestCharactersInTexture
刷新所需字符的纹理处理图文字符
处理普通字符
GetGlyph(ch, prev);
获取字形信息,根据使用的是bitmapFont还是dynamicFont计算得到字形数据GlyphInfo,里面包含字形的顶点坐标,uv坐标和颜色channel通道设置。
public class GlyphInfo
{
public Vector2 v0;
public Vector2 v1;
public Vector2 u0;
public Vector2 u1;
public Vector2 u2;
public Vector2 u3;
public float advance = 0f;
public int channel = 0;
}
有上下标
sizeShrinkage
都缩小,缩小顶点范围如果宽度不够 换行处理
若字符为空格:若BBCode对应是下划线,则替换为“下划线”;若对应中划线,都不对应,则continue
处理纹理坐标:添加纹理坐标到缓存中
根据GlyphInfo的channel值-》处理顶点颜色,填入Color信息
根据粗体斜体处理顶点坐标
处理下划线或中划线
3.1.2 代码
static public void Print (string text, BetterList verts, BetterList uvs, BetterList cols)
{
if (string.IsNullOrEmpty(text)) return;
int indexOffset = verts.size;
Prepare(text);
// Start with the white tint
mColors.Add(Color.white);
mAlpha = 1f;
int ch = 0, prev = 0;
float x = 0f, y = 0f, maxX = 0f;
float sizeF = finalSize;
Color gb = tint * gradientBottom;
Color gt = tint * gradientTop;
Color32 uc = tint;
int textLength = text.Length;
Rect uvRect = new Rect();
float invX = 0f, invY = 0f;
float sizePD = sizeF * pixelDensity;
// Advanced symbol support contributed by Rudy Pangestu.
bool subscript = false;
int subscriptMode = 0; // 0 = normal, 1 = subscript, 2 = superscript
bool bold = false;
bool italic = false;
bool underline = false;
bool strikethrough = false;
bool ignoreColor = false;
const float sizeShrinkage = 0.75f;
float v0x;
float v1x;
float v1y;
float v0y;
float prevX = 0;
if (bitmapFont != null)
{
uvRect = bitmapFont.uvRect;
invX = uvRect.width / bitmapFont.texWidth;
invY = uvRect.height / bitmapFont.texHeight;
}
for (int i = 0; i < textLength; ++i)
{
ch = text[i];
prevX = x;
// New line character -- skip to the next line
if (ch == '\n')
{
if (x > maxX) maxX = x;
if (alignment != Alignment.Left)
{
Align(verts, indexOffset, x - finalSpacingX);
indexOffset = verts.size;
}
x = 0;
y += finalLineHeight;
prev = 0;
continue;
}
// Invalid character -- skip it
if (ch < ' ')
{
prev = ch;
continue;
}
// Color changing symbol
if (encoding && ParseSymbol(text, ref i, mColors, premultiply, ref subscriptMode, ref bold,
ref italic, ref underline, ref strikethrough, ref ignoreColor))
{
Color fc;
if (ignoreColor)
{
fc = mColors[mColors.size - 1];
fc.a *= mAlpha * tint.a;
}
else
{
fc = tint * mColors[mColors.size - 1];
fc.a *= mAlpha;
}
uc = fc;
for (int b = 0, bmax = mColors.size - 2; b < bmax; ++b)
fc.a *= mColors[b].a;
if (gradient)
{
gb = gradientBottom * fc;
gt = gradientTop * fc;
}
--i;
continue;
}
// See if there is a symbol matching this text
BMSymbol symbol = useSymbols ? GetSymbol(text, i, textLength) : null;
if (symbol != null)
{
v0x = x + symbol.offsetX * fontScale;
v1x = v0x + symbol.width * fontScale;
v1y = -(y + symbol.offsetY * fontScale);
v0y = v1y - symbol.height * fontScale;
// Doesn't fit? Move down to the next line
if (Mathf.RoundToInt(x + symbol.advance * fontScale) > regionWidth)
{
if (x == 0f) return;
if (alignment != Alignment.Left && indexOffset < verts.size)
{
Align(verts, indexOffset, x - finalSpacingX);
indexOffset = verts.size;
}
v0x -= x;
v1x -= x;
v0y -= finalLineHeight;
v1y -= finalLineHeight;
x = 0;
y += finalLineHeight;
prevX = 0;
}
verts.Add(new Vector3(v0x, v0y));
verts.Add(new Vector3(v0x, v1y));
verts.Add(new Vector3(v1x, v1y));
verts.Add(new Vector3(v1x, v0y));
x += finalSpacingX + symbol.advance * fontScale;
i += symbol.length - 1;
prev = 0;
if (uvs != null)
{
Rect uv = symbol.uvRect;
float u0x = uv.xMin;
float u0y = uv.yMin;
float u1x = uv.xMax;
float u1y = uv.yMax;
uvs.Add(new Vector2(u0x, u0y));
uvs.Add(new Vector2(u0x, u1y));
uvs.Add(new Vector2(u1x, u1y));
uvs.Add(new Vector2(u1x, u0y));
}
if (cols != null)
{
if (symbolStyle == SymbolStyle.Colored)
{
for (int b = 0; b < 4; ++b) cols.Add(uc);
}
else
{
Color32 col = Color.white;
col.a = uc.a;
for (int b = 0; b < 4; ++b) cols.Add(col);
}
}
}
else // No symbol present
{
GlyphInfo glyph = GetGlyph(ch, prev);
if (glyph == null) continue;
prev = ch;
if (subscriptMode != 0)
{
glyph.v0.x *= sizeShrinkage;
glyph.v0.y *= sizeShrinkage;
glyph.v1.x *= sizeShrinkage;
glyph.v1.y *= sizeShrinkage;
if (subscriptMode == 1)
{
glyph.v0.y -= fontScale * fontSize * 0.4f;
glyph.v1.y -= fontScale * fontSize * 0.4f;
}
else
{
glyph.v0.y += fontScale * fontSize * 0.05f;
glyph.v1.y += fontScale * fontSize * 0.05f;
}
}
v0x = glyph.v0.x + x;
v0y = glyph.v0.y - y;
v1x = glyph.v1.x + x;
v1y = glyph.v1.y - y;
float w = glyph.advance;
if (finalSpacingX < 0f) w += finalSpacingX;
// Doesn't fit? Move down to the next line
if (Mathf.RoundToInt(x + w) > regionWidth)
{
if (x == 0f) return;
if (alignment != Alignment.Left && indexOffset < verts.size)
{
Align(verts, indexOffset, x - finalSpacingX);
indexOffset = verts.size;
}
v0x -= x;
v1x -= x;
v0y -= finalLineHeight;
v1y -= finalLineHeight;
x = 0;
y += finalLineHeight;
prevX = 0;
}
if (IsSpace(ch))
{
if (underline)
{
ch = '_';
}
else if (strikethrough)
{
ch = '-';
}
}
// Advance the position
x += (subscriptMode == 0) ? finalSpacingX + glyph.advance :
(finalSpacingX + glyph.advance) * sizeShrinkage;
// No need to continue if this is a space character
if (IsSpace(ch)) continue;
// Texture coordinates
if (uvs != null)
{
if (bitmapFont != null)
{
glyph.u0.x = uvRect.xMin + invX * glyph.u0.x;
glyph.u2.x = uvRect.xMin + invX * glyph.u2.x;
glyph.u0.y = uvRect.yMax - invY * glyph.u0.y;
glyph.u2.y = uvRect.yMax - invY * glyph.u2.y;
glyph.u1.x = glyph.u0.x;
glyph.u1.y = glyph.u2.y;
glyph.u3.x = glyph.u2.x;
glyph.u3.y = glyph.u0.y;
}
for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
{
uvs.Add(glyph.u0);
uvs.Add(glyph.u1);
uvs.Add(glyph.u2);
uvs.Add(glyph.u3);
}
}
// Vertex colors
if (cols != null)
{
if (glyph.channel == 0 || glyph.channel == 15)
{
if (gradient)
{
float min = sizePD + glyph.v0.y / fontScale;
float max = sizePD + glyph.v1.y / fontScale;
min /= sizePD;
max /= sizePD;
s_c0 = Color.Lerp(gb, gt, min);
s_c1 = Color.Lerp(gb, gt, max);
for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
{
cols.Add(s_c0);
cols.Add(s_c1);
cols.Add(s_c1);
cols.Add(s_c0);
}
}
else
{
for (int j = 0, jmax = (bold ? 16 : 4); j < jmax; ++j)
cols.Add(uc);
}
}
else
{
// Packed fonts come as alpha masks in each of the RGBA channels.
// In order to use it we need to use a special shader.
//
// Limitations:
// - Effects (drop shadow, outline) will not work.
// - Should not be a part of the atlas (eastern fonts rarely are anyway).
// - Lower color precision
Color col = uc;
col *= 0.49f;
switch (glyph.channel)
{
case 1: col.b += 0.51f; break;
case 2: col.g += 0.51f; break;
case 4: col.r += 0.51f; break;
case 8: col.a += 0.51f; break;
}
Color32 c = col;
for (int j = 0, jmax = (bold ? 16 : 4); j < jmax; ++j)
cols.Add(c);
}
}
// Bold and italic contributed by Rudy Pangestu.
if (!bold)
{
if (!italic)
{
verts.Add(new Vector3(v0x, v0y));
verts.Add(new Vector3(v0x, v1y));
verts.Add(new Vector3(v1x, v1y));
verts.Add(new Vector3(v1x, v0y));
}
else // Italic
{
float slant = fontSize * 0.1f * ((v1y - v0y) / fontSize);
verts.Add(new Vector3(v0x - slant, v0y));
verts.Add(new Vector3(v0x + slant, v1y));
verts.Add(new Vector3(v1x + slant, v1y));
verts.Add(new Vector3(v1x - slant, v0y));
}
}
else // Bold
{
for (int j = 0; j < 4; ++j)
{
float a = mBoldOffset[j * 2];
float b = mBoldOffset[j * 2 + 1];
float slant = (italic ? fontSize * 0.1f * ((v1y - v0y) / fontSize) : 0f);
verts.Add(new Vector3(v0x + a - slant, v0y + b));
verts.Add(new Vector3(v0x + a + slant, v1y + b));
verts.Add(new Vector3(v1x + a + slant, v1y + b));
verts.Add(new Vector3(v1x + a - slant, v0y + b));
}
}
// Underline and strike-through contributed by Rudy Pangestu.
if (underline || strikethrough)
{
GlyphInfo dash = GetGlyph(strikethrough ? '-' : '_', prev);
if (dash == null) continue;
if (uvs != null)
{
if (bitmapFont != null)
{
dash.u0.x = uvRect.xMin + invX * dash.u0.x;
dash.u2.x = uvRect.xMin + invX * dash.u2.x;
dash.u0.y = uvRect.yMax - invY * dash.u0.y;
dash.u2.y = uvRect.yMax - invY * dash.u2.y;
}
float cx = (dash.u0.x + dash.u2.x) * 0.5f;
for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
{
uvs.Add(new Vector2(cx, dash.u0.y));
uvs.Add(new Vector2(cx, dash.u2.y));
uvs.Add(new Vector2(cx, dash.u2.y));
uvs.Add(new Vector2(cx, dash.u0.y));
}
}
if (subscript && strikethrough)
{
v0y = (-y + dash.v0.y) * sizeShrinkage;
v1y = (-y + dash.v1.y) * sizeShrinkage;
}
else
{
v0y = (-y + dash.v0.y);
v1y = (-y + dash.v1.y);
}
if (bold)
{
for (int j = 0; j < 4; ++j)
{
float a = mBoldOffset[j * 2];
float b = mBoldOffset[j * 2 + 1];
verts.Add(new Vector3(prevX + a, v0y + b));
verts.Add(new Vector3(prevX + a, v1y + b));
verts.Add(new Vector3(x + a, v1y + b));
verts.Add(new Vector3(x + a, v0y + b));
}
}
else
{
verts.Add(new Vector3(prevX, v0y));
verts.Add(new Vector3(prevX, v1y));
verts.Add(new Vector3(x, v1y));
verts.Add(new Vector3(x, v0y));
}
if (gradient)
{
float min = sizePD + dash.v0.y / fontScale;
float max = sizePD + dash.v1.y / fontScale;
min /= sizePD;
max /= sizePD;
s_c0 = Color.Lerp(gb, gt, min);
s_c1 = Color.Lerp(gb, gt, max);
for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
{
cols.Add(s_c0);
cols.Add(s_c1);
cols.Add(s_c1);
cols.Add(s_c0);
}
}
else
{
for (int j = 0, jmax = (bold ? 16 : 4); j < jmax; ++j)
cols.Add(uc);
}
}
}
}
if (alignment != Alignment.Left && indexOffset < verts.size)
{
Align(verts, indexOffset, x - finalSpacingX);
indexOffset = verts.size;
}
mColors.Clear();
}