Skip to content
This repository was archived by the owner on Apr 29, 2021. It is now read-only.

Commit 389b63a

Browse files
author
Yuncong Zhang
committed
Render Emoji from texture loaded from image.
1 parent 63014be commit 389b63a

File tree

10 files changed

+358
-24
lines changed

10 files changed

+358
-24
lines changed

Runtime/painting/text_span.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ public class TextSpan : DiagnosticableTree, IEquatable<TextSpan> {
1313

1414
public readonly TextStyle style;
1515
public readonly string text;
16+
public List<string> splitedText;
1617
public readonly List<TextSpan> children;
1718
public readonly GestureRecognizer recognizer;
1819
public readonly HoverRecognizer hoverRecognizer;
1920

2021
public TextSpan(string text = "", TextStyle style = null, List<TextSpan> children = null,
2122
GestureRecognizer recognizer = null, HoverRecognizer hoverRecognizer = null) {
2223
this.text = text;
24+
this.splitedText = !string.IsNullOrEmpty(text) ? EmojiUtils.splitBySurrogatePair(text) : null;
2325
this.style = style;
2426
this.children = children;
2527
this.recognizer = recognizer;
@@ -28,13 +30,24 @@ public TextSpan(string text = "", TextStyle style = null, List<TextSpan> childre
2830

2931
public void build(ParagraphBuilder builder, float textScaleFactor = 1.0f) {
3032
var hasStyle = this.style != null;
33+
3134
if (hasStyle) {
3235
builder.pushStyle(this.style, textScaleFactor);
3336
}
34-
35-
if (!string.IsNullOrEmpty(this.text)) {
36-
builder.addText(this.text);
37+
if (this.splitedText != null) {
38+
if (this.splitedText.Count == 1 && !EmojiUtils.isSurrogatePairStart(this.splitedText[0][0])) {
39+
builder.addText(this.splitedText[0]);
40+
}
41+
else {
42+
TextStyle style = this.style ?? new TextStyle();
43+
for (int i = 0; i < this.splitedText.Count; i++) {
44+
builder.pushStyle(style, textScaleFactor);
45+
builder.addText(this.splitedText[i]);
46+
builder.pop();
47+
}
48+
}
3749
}
50+
3851

3952
if (this.children != null) {
4053
foreach (var child in this.children) {

Runtime/ui/painting/canvas_impl.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using System;
22
using System.Collections.Generic;
33
using Unity.UIWidgets.foundation;
4+
using Unity.UIWidgets.material;
45
using UnityEngine;
56
using UnityEngine.Rendering;
7+
using Material = UnityEngine.Material;
68

79
namespace Unity.UIWidgets.ui {
810
public class PictureFlusher {
@@ -723,17 +725,27 @@ void _drawTextBlob(TextBlob textBlob, Offset offset, Paint paint) {
723725
var font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font;
724726
var fontSizeToLoad = Mathf.CeilToInt(style.UnityFontSize * scale);
725727
var subText = textBlob.text.Substring(textBlob.textOffset, textBlob.textSize);
726-
font.RequestCharactersInTextureSafe(subText, fontSizeToLoad, style.UnityFontStyle);
727728

728-
var tex = font.material.mainTexture;
729+
Texture tex = null;
730+
bool alpha = !EmojiUtils.isSurrogatePairStart(subText[0]);
731+
if(alpha) {
732+
font.RequestCharactersInTextureSafe(subText, fontSizeToLoad, style.UnityFontStyle);
733+
tex = font.material.mainTexture;
734+
}
729735

730736
Action<Paint> drawMesh = (Paint p) => {
731737
if (!this._applyClip(textBlobBounds)) {
732738
return;
733739
}
734740

735741
var layer = this._currentLayer;
736-
layer.draws.Add(CanvasShader.texAlpha(layer, p, mesh, tex));
742+
743+
if(alpha) layer.draws.Add(CanvasShader.texAlpha(layer, p, mesh, tex));
744+
else {
745+
Paint paintWithWhite= new Paint(p);
746+
paintWithWhite.color = Colors.white;
747+
layer.draws.Add(CanvasShader.tex(layer, paintWithWhite, mesh.resovleMesh(), EmojiUtils.image));
748+
}
737749
};
738750

739751
if (paint.maskFilter != null && paint.maskFilter.sigma != 0) {

Runtime/ui/painting/txt/mesh_generator.cs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using Unity.UIWidgets.foundation;
34
using UnityEngine;
45

56
namespace Unity.UIWidgets.ui {
@@ -132,8 +133,41 @@ public MeshMesh resovleMesh() {
132133
this._resolved = true;
133134

134135
var style = this.textBlob.style;
135-
var fontInfo = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle);
136+
137+
var text = this.textBlob.text;
136138
var key = new MeshKey(this.textBlob.instanceId, this.scale);
139+
if (EmojiUtils.isSurrogatePairStart(text[this.textBlob.textOffset])) {
140+
D.assert(this.textBlob.textSize == 2);
141+
142+
char a = text[this.textBlob.textOffset], b = text[this.textBlob.textOffset+1];
143+
D.assert(EmojiUtils.isSurrogatePairEnd(b));
144+
145+
var pos = this.textBlob.positions[0];
146+
var vert = new List<Vector3> {
147+
new Vector3(pos.x, pos.y - style.fontSize, 0),
148+
new Vector3(pos.x + style.fontSize, pos.y - style.fontSize, 0),
149+
new Vector3(pos.x + style.fontSize, pos.y, 0),
150+
new Vector3(pos.x, pos.y, 0),
151+
};
152+
var tri = new List<int> {
153+
0, 1, 2, 0, 2, 3,
154+
};
155+
var code = EmojiUtils.decodeSurrogatePair(a, b);
156+
var uvRect = EmojiUtils.getUVRect(code);
157+
var uvCoord = new List<Vector2> {
158+
uvRect.bottomLeft.toVector(),
159+
uvRect.bottomRight.toVector(),
160+
uvRect.topRight.toVector(),
161+
uvRect.topLeft.toVector(),
162+
};
163+
MeshMesh meshMesh = new MeshMesh(null, vert, tri, uvCoord);
164+
_meshes[key] = new MeshInfo(key, meshMesh, 0);
165+
166+
this._mesh = meshMesh.transform(this.matrix);
167+
return this._mesh;
168+
}
169+
170+
var fontInfo = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle);
137171

138172
_meshes.TryGetValue(key, out var meshInfo);
139173
if (meshInfo != null && meshInfo.textureVersion == fontInfo.textureVersion) {
@@ -144,7 +178,6 @@ public MeshMesh resovleMesh() {
144178

145179
var font = fontInfo.font;
146180
var length = this.textBlob.textSize;
147-
var text = this.textBlob.text;
148181
var fontSizeToLoad = Mathf.CeilToInt(style.UnityFontSize * this.scale);
149182

150183
var vertices = new List<Vector3>(length * 4);

Runtime/ui/txt/emoji.cs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Unity.UIWidgets.foundation;
5+
using UnityEngine;
6+
using UnityEngine.Windows;
7+
8+
namespace Unity.UIWidgets.ui {
9+
public class EmojiUtils {
10+
11+
static Image _image;
12+
13+
public static Image image {
14+
get {
15+
if (_image == null || _image.texture == null) {
16+
try {
17+
_image = new Image(
18+
Resources.Load<Texture2D>("Emoji")
19+
);
20+
}
21+
catch (Exception e) {
22+
_image = null;
23+
}
24+
}
25+
26+
return _image;
27+
}
28+
}
29+
30+
public static readonly Dictionary<uint, int> emojiLookupTable = new Dictionary<uint, int> {
31+
{0x1F60A, 0},
32+
{0x1F60B, 1},
33+
{0x1F60D, 2},
34+
{0x1F60E, 3},
35+
{0x1F600, 4},
36+
{0x1F601, 5},
37+
{0x1F602, 6},
38+
{0x1F603, 7},
39+
{0x1F604, 8},
40+
{0x1F605, 9},
41+
{0x1F606, 10},
42+
{0x1F61C, 11},
43+
{0x1F618, 12},
44+
{0x1F62D, 13},
45+
{0x1F60C, 14},
46+
{0x1F61E, 15},
47+
};
48+
49+
public const int rowCount = 4;
50+
public const int colCount = 4;
51+
52+
public static Rect getUVRect(uint code) {
53+
bool exist = emojiLookupTable.TryGetValue(code, out int index);
54+
if (exist) {
55+
return Rect.fromLTWH(
56+
(index % colCount) * (1.0f / colCount),
57+
(rowCount - 1 - (index / colCount)) * (1.0f / rowCount),
58+
1.0f / colCount, 1.0f / rowCount);
59+
}
60+
61+
Debug.LogWarning($"Unrecognized unicode for emoji {code:x}");
62+
return Rect.fromLTWH(0, 0, 0, 0);
63+
}
64+
65+
public static void encodeSurrogatePair(uint character, out char a, out char b) {
66+
uint code;
67+
D.assert(0x10000 <= character && character <= 0x10FFFF);
68+
code = (character - 0x10000);
69+
a = (char) (0xD800 | (code >> 10));
70+
b = (char) (0xDC00 | (code & 0x3FF));
71+
}
72+
73+
public static uint decodeSurrogatePair(char a, char b) {
74+
uint code;
75+
D.assert(0xD800 <= a && a <= 0xDBFF);
76+
D.assert(0xDC00 <= b && b <= 0xDFFF);
77+
code = 0x10000;
78+
code += (uint) ((a & 0x03FF) << 10);
79+
code += (uint) (b & 0x03FF);
80+
return code;
81+
}
82+
83+
public static bool isSurrogatePairStart(uint c) {
84+
return 0xD800 <= c && c <= 0xDBFF;
85+
}
86+
87+
public static bool isSurrogatePairEnd(uint c) {
88+
return 0xDC00 <= c && c <= 0xDFFF;
89+
}
90+
91+
public static List<string> splitBySurrogatePair(string text) {
92+
int start = 0;
93+
List<string> list = new List<string>();
94+
for (int i = 0; i < text.Length; i++) {
95+
if (i < text.Length - 1 && isSurrogatePairStart(text[i]) && isSurrogatePairEnd(text[i + 1])) {
96+
if (i > start) {
97+
list.Add(text.Substring(start, i - start));
98+
}
99+
100+
start = i + 2;
101+
list.Add(text.Substring(i, 2));
102+
i++;
103+
}
104+
}
105+
106+
if (start < text.Length) {
107+
list.Add(text.Substring(start));
108+
}
109+
110+
return list;
111+
}
112+
}
113+
}

Runtime/ui/txt/emoji.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Runtime/ui/txt/layout.cs

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,30 @@ public void doLayout(float offset, TextBuff buff, int start, int count, TextStyl
3737
this._positions.reset(count);
3838
this._advance = 0;
3939
this._bounds = default;
40-
41-
var font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font;
42-
font.RequestCharactersInTextureSafe(buff.text, style.UnityFontSize, style.UnityFontStyle);
43-
44-
int wordstart = start == buff.size
45-
? start
46-
: LayoutUtils.getPrevWordBreakForCache(buff, start + 1);
47-
int wordend;
48-
for (int iter = start; iter < start + count; iter = wordend) {
49-
wordend = LayoutUtils.getNextWordBreakForCache(buff, iter);
50-
int wordCount = Mathf.Min(start + count, wordend) - iter;
51-
this.layoutWord(offset, iter - start, buff.subBuff(wordstart, wordend - wordstart),
52-
iter - wordstart, wordCount, style, font);
53-
wordstart = wordend;
40+
41+
Font font;
42+
43+
if (EmojiUtils.isSurrogatePairStart(buff.text[start])) {
44+
D.assert(count == 2);
45+
D.assert(EmojiUtils.isSurrogatePairEnd(buff.text[start+1]));
46+
47+
this.layoutEmoji(style);
48+
}
49+
else {
50+
font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font;
51+
font.RequestCharactersInTextureSafe(buff.text, style.UnityFontSize, style.UnityFontStyle);
52+
53+
int wordstart = start == buff.size
54+
? start
55+
: LayoutUtils.getPrevWordBreakForCache(buff, start + 1);
56+
int wordend;
57+
for (int iter = start; iter < start + count; iter = wordend) {
58+
wordend = LayoutUtils.getNextWordBreakForCache(buff, iter);
59+
int wordCount = Mathf.Min(start + count, wordend) - iter;
60+
this.layoutWord(offset, iter - start, buff.subBuff(wordstart, wordend - wordstart),
61+
iter - wordstart, wordCount, style, font);
62+
wordstart = wordend;
63+
}
5464
}
5565
this._count = count;
5666
}
@@ -117,6 +127,52 @@ void layoutWord(float offset, int layoutOffset,
117127

118128
this._advance = x;
119129
}
130+
131+
void layoutEmoji(TextStyle style) {
132+
float x = this._advance;
133+
float letterSpace = style.letterSpacing;
134+
float letterSpaceHalfLeft = letterSpace * 0.5f;
135+
float letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft;
136+
137+
x += letterSpaceHalfLeft;
138+
this._advances[0] += letterSpaceHalfLeft;
139+
140+
var minX = 0 + x;
141+
var maxX = style.fontSize + x;
142+
var minY = -style.fontSize;
143+
var maxY = 0;
144+
145+
if (this._bounds.width <= 0 || this._bounds.height <= 0) {
146+
this._bounds = UnityEngine.Rect.MinMaxRect(
147+
minX, minY, maxX, maxY);
148+
} else {
149+
if (minX < this._bounds.x) {
150+
this._bounds.x = minX;
151+
}
152+
if (minY < this._bounds.y) {
153+
this._bounds.y = minY;
154+
}
155+
if (maxX > this._bounds.xMax) {
156+
this._bounds.xMax = maxX;
157+
}
158+
if (maxY > this._bounds.yMax) {
159+
this._bounds.yMax = maxY;
160+
}
161+
}
162+
163+
this._positions[0] = x;
164+
float advance = style.fontSize;
165+
x += advance;
166+
167+
this._advances[0] += advance;
168+
this._advances[0] += letterSpaceHalfRight;
169+
x += letterSpaceHalfRight;
170+
171+
this._advances[1] = 0;
172+
this._positions[1] = x;
173+
174+
this._advance = x;
175+
}
120176

121177
public void setTabStops(TabStops tabStops) {
122178
this._tabStops = tabStops;

Runtime/ui/txt/layout_utils.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ public static int getNextWordBreakForCache(TextBuff buff, int offset) {
5252
}
5353

5454
public static bool isWordBreakAfter(ushort c) {
55-
if (isWordSpace(c) || (c >= 0x2000 && c <= 0x200a) || c == 0x3000) {
55+
if (isWordSpace(c) || (c >= 0x2000 && c <= 0x200a) || c == 0x3000 || EmojiUtils.isSurrogatePairEnd(c)) {
5656
// spaces
5757
return true;
5858
}
5959
return false;
6060
}
6161

6262
public static bool isWordBreakBefore(ushort c) {
63-
return isWordBreakAfter(c) || (c >= 0x3400 && c <= 0x9fff);
63+
return isWordBreakAfter(c) || (c >= 0x3400 && c <= 0x9fff) || EmojiUtils.isSurrogatePairStart(c);
6464
}
6565

6666
}

Tests/Editor/Paragraph.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ RenderBox text() {
117117
"This is FontStyle.italic And 发撒放豆腐sad 发生的 Bold Text This is FontStyle.italic And Bold Text\n\n"),
118118
new TextSpan(style: new TextStyle(fontSize: 18),
119119
text: "FontSize 18: Get a named matrix value from the shader."),
120+
new TextSpan(style: new TextStyle(fontSize: 14),
121+
text: "Emoji \ud83d\ude0a\ud83d\ude0b\ud83d\ude0d\ud83d\ude0e\ud83d\ude00"),
122+
new TextSpan(style: new TextStyle(fontSize: 18),
123+
text: "\ud83d\ude01\ud83d\ude02\ud83d\ude03\ud83d\ude04\ud83d\ude05"),
124+
new TextSpan(style: new TextStyle(fontSize: 24),
125+
text: "\ud83d\ude06\ud83d\ude1C\ud83d\ude18\ud83d\ude2D\ud83d\ude0C\ud83d\ude1E"),
120126
new TextSpan(style: new TextStyle(fontSize: 14),
121127
text: "FontSize 14"),
122128
})));

Tests/Resources/Emoji.png

71.6 KB
Loading

0 commit comments

Comments
 (0)