用到的场景:聊天系统。
NGUI版本:3.5.6。
已支持的功能,静态(位图)字体的图文混排。
需要实现的:动态字体的图文混排。
实现思路:用两个Label,一个只负责渲染文字,一个负责渲染图片。用isSymbol区分是渲染图片还是渲染文字。渲染文字的跳过符号的顶点和uv的添加,渲染图片的跳过正常字符的顶点和uv添加。
修改NGUIText.cs的代码如下:
static public void Print (string text, BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols)
{
if (string.IsNullOrEmpty(text)) return;
int indexOffset = verts.size;
Prepare(text);
// Start with the white tint
mColors.Add(Color.white);
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;
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;
}
else
{
if (symbolFont != null && useWgSymbol)
{
uvRect = symbolFont.uvRect;
invX = uvRect.width / symbolFont.texWidth;
invY = uvRect.height / symbolFont.texHeight;
}
}
for (int i = 0; i < textLength; ++i)
{
ch = text[i];
prevX = x;
// New line character -- skip to the next line
if (ch == '
')
{
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))
{
Color fc = tint * mColors[mColors.size - 1];
uc = fc;
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) > rectWidth)
{
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 (isSymbol || !useWgSymbol)
{
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;
if (isSymbol || !useWgSymbol)
{
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)
{
if (isSymbol || !useWgSymbol)
{
for (int b = 0; b < 4; ++b) cols.Add(uc);
}
}
else
{
Color32 col = Color.white;
col.a = uc.a;
if (isSymbol || !useWgSymbol)
{
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;
}
}
float y0 = glyph.v0.y;
float y1 = glyph.v1.y;
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) > rectWidth)
{
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 (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 (ch == ' ') continue;
// Texture coordinates
if (uvs != null)
{
if (bitmapFont != null)
{
glyph.u0.x = uvRect.xMin + invX * glyph.u0.x;
glyph.u1.x = uvRect.xMin + invX * glyph.u1.x;
glyph.u0.y = uvRect.yMax - invY * glyph.u0.y;
glyph.u1.y = uvRect.yMax - invY * glyph.u1.y;
}
if (!isSymbol || !useWgSymbol)
{
for (int j = 0, jmax = (bold ? 4 : 1); j < jmax; ++j)
{
if (glyph.rotatedUVs)
{
uvs.Add(glyph.u0);
uvs.Add(new Vector2(glyph.u1.x, glyph.u0.y));
uvs.Add(glyph.u1);
uvs.Add(new Vector2(glyph.u0.x, glyph.u1.y));
}
else
{
uvs.Add(glyph.u0);
uvs.Add(new Vector2(glyph.u0.x, glyph.u1.y));
uvs.Add(glyph.u1);
uvs.Add(new Vector2(glyph.u1.x, glyph.u0.y));
}
}
}
}
// Vertex colors
if (cols != null)
{
if (glyph.channel == 0 || glyph.channel == 15)
{
if (gradient)
{
float min = sizePD + y0 / fontScale;
float max = sizePD + y1 / fontScale;
min /= sizePD;
max /= sizePD;
s_c0 = Color.Lerp(gb, gt, min);
s_c1 = Color.Lerp(gb, gt, max);
if (!isSymbol || !useWgSymbol)
{
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
{
if (!isSymbol || !useWgSymbol)
{
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;
}
if (!isSymbol || !useWgSymbol)
{
Color32 c = col;
for (int j = 0, jmax = (bold ? 16 : 4); j < jmax; ++j)
cols.Add(c);
}
}
}
if (!isSymbol || !useWgSymbol)
{
// 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 = a + (italic ? fontSize * 0.1f * ((v1y - v0y) / fontSize) : 0f);
verts.Add(new Vector3(v0x - slant, v0y + b));
verts.Add(new Vector3(v0x + slant, v1y + b));
verts.Add(new Vector3(v1x + slant, v1y + b));
verts.Add(new Vector3(v1x - slant, v0y + b));
}
}
}
// Underline and strike-through contributed by Rudy Pangestu.
if (underline || strikethrough)
{
GlyphInfo dash = GetGlyph(strikethrough ? '-' : '_', prev);
if (dash == null) continue;
if (!isSymbol || !useWgSymbol)
{
if (uvs != null)
{
if (bitmapFont != null)
{
dash.u0.x = uvRect.xMin + invX * dash.u0.x;
dash.u1.x = uvRect.xMin + invX * dash.u1.x;
dash.u0.y = uvRect.yMax - invY * dash.u0.y;
dash.u1.y = uvRect.yMax - invY * dash.u1.y;
}
float cx = (dash.u0.x + dash.u1.x) * 0.5f;
float cy = (dash.u0.y + dash.u1.y) * 0.5f;
uvs.Add(new Vector2(cx, cy));
uvs.Add(new Vector2(cx, cy));
uvs.Add(new Vector2(cx, cy));
uvs.Add(new Vector2(cx, cy));
}
}
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 (!isSymbol || !useWgSymbol)
{
verts.Add(new Vector3(prevX, v0y));
verts.Add(new Vector3(prevX, v1y));
verts.Add(new Vector3(x, v1y));
verts.Add(new Vector3(x, v0y));
}
Color tint2 = uc;
if (strikethrough)
{
tint2.r *= 0.5f;
tint2.g *= 0.5f;
tint2.b *= 0.5f;
}
tint2.a *= 0.75f;
Color32 uc2 = tint2;
if (!isSymbol || !useWgSymbol)
{
cols.Add(uc2);
cols.Add(uc);
cols.Add(uc);
cols.Add(uc2);
}
}
}
}
if (alignment != Alignment.Left && indexOffset < verts.size)
{
Align(verts, indexOffset, x - finalSpacingX);
indexOffset = verts.size;
}
mColors.Clear();
}