浏览代码

Render Emoji from texture loaded from image.

/main
Yuncong Zhang 5 年前
当前提交
5d2c9dab
共有 10 个文件被更改,包括 562 次插入23 次删除
  1. 19
      Runtime/painting/text_span.cs
  2. 18
      Runtime/ui/painting/canvas_impl.cs
  3. 37
      Runtime/ui/painting/txt/mesh_generator.cs
  4. 82
      Runtime/ui/txt/layout.cs
  5. 4
      Runtime/ui/txt/layout_utils.cs
  6. 6
      Tests/Editor/Paragraph.cs
  7. 113
      Runtime/ui/txt/emoji.cs
  8. 11
      Runtime/ui/txt/emoji.cs.meta
  9. 205
      Tests/Resources/Emoji.png
  10. 90
      Tests/Resources/Emoji.png.meta

19
Runtime/painting/text_span.cs


public readonly TextStyle style;
public readonly string text;
public List<string> splitedText;
public readonly List<TextSpan> children;
public readonly GestureRecognizer recognizer;
public readonly HoverRecognizer hoverRecognizer;

this.text = text;
this.splitedText = !string.IsNullOrEmpty(text) ? EmojiUtils.splitBySurrogatePair(text) : null;
this.style = style;
this.children = children;
this.recognizer = recognizer;

public void build(ParagraphBuilder builder, float textScaleFactor = 1.0f) {
var hasStyle = this.style != null;
if (!string.IsNullOrEmpty(this.text)) {
builder.addText(this.text);
if (this.splitedText != null) {
if (this.splitedText.Count == 1 && !EmojiUtils.isSurrogatePairStart(this.splitedText[0][0])) {
builder.addText(this.splitedText[0]);
}
else {
TextStyle style = this.style ?? new TextStyle();
for (int i = 0; i < this.splitedText.Count; i++) {
builder.pushStyle(style, textScaleFactor);
builder.addText(this.splitedText[i]);
builder.pop();
}
}
if (this.children != null) {
foreach (var child in this.children) {

18
Runtime/ui/painting/canvas_impl.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.material;
using Material = UnityEngine.Material;
namespace Unity.UIWidgets.ui {
public class PictureFlusher {

var font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font;
var fontSizeToLoad = Mathf.CeilToInt(style.UnityFontSize * scale);
var subText = textBlob.text.Substring(textBlob.textOffset, textBlob.textSize);
font.RequestCharactersInTextureSafe(subText, fontSizeToLoad, style.UnityFontStyle);
var tex = font.material.mainTexture;
Texture tex = null;
bool alpha = !EmojiUtils.isSurrogatePairStart(subText[0]);
if(alpha) {
font.RequestCharactersInTextureSafe(subText, fontSizeToLoad, style.UnityFontStyle);
tex = font.material.mainTexture;
}
Action<Paint> drawMesh = (Paint p) => {
if (!this._applyClip(textBlobBounds)) {

var layer = this._currentLayer;
layer.draws.Add(CanvasShader.texAlpha(layer, p, mesh, tex));
if(alpha) layer.draws.Add(CanvasShader.texAlpha(layer, p, mesh, tex));
else {
Paint paintWithWhite= new Paint(p);
paintWithWhite.color = Colors.white;
layer.draws.Add(CanvasShader.tex(layer, paintWithWhite, mesh.resovleMesh(), EmojiUtils.image));
}
};
if (paint.maskFilter != null && paint.maskFilter.sigma != 0) {

37
Runtime/ui/painting/txt/mesh_generator.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.foundation;
using UnityEngine;
namespace Unity.UIWidgets.ui {

this._resolved = true;
var style = this.textBlob.style;
var fontInfo = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle);
var text = this.textBlob.text;
if (EmojiUtils.isSurrogatePairStart(text[this.textBlob.textOffset])) {
D.assert(this.textBlob.textSize == 2);
char a = text[this.textBlob.textOffset], b = text[this.textBlob.textOffset+1];
D.assert(EmojiUtils.isSurrogatePairEnd(b));
var pos = this.textBlob.positions[0];
var vert = new List<Vector3> {
new Vector3(pos.x, pos.y - style.fontSize, 0),
new Vector3(pos.x + style.fontSize, pos.y - style.fontSize, 0),
new Vector3(pos.x + style.fontSize, pos.y, 0),
new Vector3(pos.x, pos.y, 0),
};
var tri = new List<int> {
0, 1, 2, 0, 2, 3,
};
var code = EmojiUtils.decodeSurrogatePair(a, b);
var uvRect = EmojiUtils.getUVRect(code);
var uvCoord = new List<Vector2> {
uvRect.bottomLeft.toVector(),
uvRect.bottomRight.toVector(),
uvRect.topRight.toVector(),
uvRect.topLeft.toVector(),
};
MeshMesh meshMesh = new MeshMesh(null, vert, tri, uvCoord);
_meshes[key] = new MeshInfo(key, meshMesh, 0);
this._mesh = meshMesh.transform(this.matrix);
return this._mesh;
}
var fontInfo = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle);
_meshes.TryGetValue(key, out var meshInfo);
if (meshInfo != null && meshInfo.textureVersion == fontInfo.textureVersion) {

var font = fontInfo.font;
var length = this.textBlob.textSize;
var text = this.textBlob.text;
var fontSizeToLoad = Mathf.CeilToInt(style.UnityFontSize * this.scale);
var vertices = new List<Vector3>(length * 4);

82
Runtime/ui/txt/layout.cs


this._positions.reset(count);
this._advance = 0;
this._bounds = default;
var font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font;
font.RequestCharactersInTextureSafe(buff.text, style.UnityFontSize, style.UnityFontStyle);
int wordstart = start == buff.size
? start
: LayoutUtils.getPrevWordBreakForCache(buff, start + 1);
int wordend;
for (int iter = start; iter < start + count; iter = wordend) {
wordend = LayoutUtils.getNextWordBreakForCache(buff, iter);
int wordCount = Mathf.Min(start + count, wordend) - iter;
this.layoutWord(offset, iter - start, buff.subBuff(wordstart, wordend - wordstart),
iter - wordstart, wordCount, style, font);
wordstart = wordend;
Font font;
if (EmojiUtils.isSurrogatePairStart(buff.text[start])) {
D.assert(count == 2);
D.assert(EmojiUtils.isSurrogatePairEnd(buff.text[start+1]));
this.layoutEmoji(style);
}
else {
font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font;
font.RequestCharactersInTextureSafe(buff.text, style.UnityFontSize, style.UnityFontStyle);
int wordstart = start == buff.size
? start
: LayoutUtils.getPrevWordBreakForCache(buff, start + 1);
int wordend;
for (int iter = start; iter < start + count; iter = wordend) {
wordend = LayoutUtils.getNextWordBreakForCache(buff, iter);
int wordCount = Mathf.Min(start + count, wordend) - iter;
this.layoutWord(offset, iter - start, buff.subBuff(wordstart, wordend - wordstart),
iter - wordstart, wordCount, style, font);
wordstart = wordend;
}
}
this._count = count;
}

x += letterSpaceHalfRight;
}
}
this._advance = x;
}
void layoutEmoji(TextStyle style) {
float x = this._advance;
float letterSpace = style.letterSpacing;
float letterSpaceHalfLeft = letterSpace * 0.5f;
float letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft;
x += letterSpaceHalfLeft;
this._advances[0] += letterSpaceHalfLeft;
var minX = 0 + x;
var maxX = style.fontSize + x;
var minY = -style.fontSize;
var maxY = 0;
if (this._bounds.width <= 0 || this._bounds.height <= 0) {
this._bounds = UnityEngine.Rect.MinMaxRect(
minX, minY, maxX, maxY);
} else {
if (minX < this._bounds.x) {
this._bounds.x = minX;
}
if (minY < this._bounds.y) {
this._bounds.y = minY;
}
if (maxX > this._bounds.xMax) {
this._bounds.xMax = maxX;
}
if (maxY > this._bounds.yMax) {
this._bounds.yMax = maxY;
}
}
this._positions[0] = x;
float advance = style.fontSize;
x += advance;
this._advances[0] += advance;
this._advances[0] += letterSpaceHalfRight;
x += letterSpaceHalfRight;
this._advances[1] = 0;
this._positions[1] = x;
this._advance = x;
}

4
Runtime/ui/txt/layout_utils.cs


}
public static bool isWordBreakAfter(ushort c) {
if (isWordSpace(c) || (c >= 0x2000 && c <= 0x200a) || c == 0x3000) {
if (isWordSpace(c) || (c >= 0x2000 && c <= 0x200a) || c == 0x3000 || EmojiUtils.isSurrogatePairEnd(c)) {
// spaces
return true;
}

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

6
Tests/Editor/Paragraph.cs


new TextSpan(style: new TextStyle(fontSize: 18),
text: "FontSize 18: Get a named matrix value from the shader."),
new TextSpan(style: new TextStyle(fontSize: 14),
text: "Emoji \ud83d\ude0a\ud83d\ude0b\ud83d\ude0d\ud83d\ude0e\ud83d\ude00"),
new TextSpan(style: new TextStyle(fontSize: 18),
text: "\ud83d\ude01\ud83d\ude02\ud83d\ude03\ud83d\ude04\ud83d\ude05"),
new TextSpan(style: new TextStyle(fontSize: 24),
text: "\ud83d\ude06\ud83d\ude1C\ud83d\ude18\ud83d\ude2D\ud83d\ude0C\ud83d\ude1E"),
new TextSpan(style: new TextStyle(fontSize: 14),
text: "FontSize 14"),
})));
}

113
Runtime/ui/txt/emoji.cs


using System;
using System.Collections.Generic;
using System.Linq;
using Unity.UIWidgets.foundation;
using UnityEngine;
using UnityEngine.Windows;
namespace Unity.UIWidgets.ui {
public class EmojiUtils {
static Image _image;
public static Image image {
get {
if (_image == null || _image.texture == null) {
try {
_image = new Image(
Resources.Load<Texture2D>("Emoji")
);
}
catch (Exception e) {
_image = null;
}
}
return _image;
}
}
public static readonly Dictionary<uint, int> emojiLookupTable = new Dictionary<uint, int> {
{0x1F60A, 0},
{0x1F60B, 1},
{0x1F60D, 2},
{0x1F60E, 3},
{0x1F600, 4},
{0x1F601, 5},
{0x1F602, 6},
{0x1F603, 7},
{0x1F604, 8},
{0x1F605, 9},
{0x1F606, 10},
{0x1F61C, 11},
{0x1F618, 12},
{0x1F62D, 13},
{0x1F60C, 14},
{0x1F61E, 15},
};
public const int rowCount = 4;
public const int colCount = 4;
public static Rect getUVRect(uint code) {
bool exist = emojiLookupTable.TryGetValue(code, out int index);
if (exist) {
return Rect.fromLTWH(
(index % colCount) * (1.0f / colCount),
(rowCount - 1 - (index / colCount)) * (1.0f / rowCount),
1.0f / colCount, 1.0f / rowCount);
}
Debug.LogWarning($"Unrecognized unicode for emoji {code:x}");
return Rect.fromLTWH(0, 0, 0, 0);
}
public static void encodeSurrogatePair(uint character, out char a, out char b) {
uint code;
D.assert(0x10000 <= character && character <= 0x10FFFF);
code = (character - 0x10000);
a = (char) (0xD800 | (code >> 10));
b = (char) (0xDC00 | (code & 0x3FF));
}
public static uint decodeSurrogatePair(char a, char b) {
uint code;
D.assert(0xD800 <= a && a <= 0xDBFF);
D.assert(0xDC00 <= b && b <= 0xDFFF);
code = 0x10000;
code += (uint) ((a & 0x03FF) << 10);
code += (uint) (b & 0x03FF);
return code;
}
public static bool isSurrogatePairStart(uint c) {
return 0xD800 <= c && c <= 0xDBFF;
}
public static bool isSurrogatePairEnd(uint c) {
return 0xDC00 <= c && c <= 0xDFFF;
}
public static List<string> splitBySurrogatePair(string text) {
int start = 0;
List<string> list = new List<string>();
for (int i = 0; i < text.Length; i++) {
if (i < text.Length - 1 && isSurrogatePairStart(text[i]) && isSurrogatePairEnd(text[i + 1])) {
if (i > start) {
list.Add(text.Substring(start, i - start));
}
start = i + 2;
list.Add(text.Substring(i, 2));
i++;
}
}
if (start < text.Length) {
list.Add(text.Substring(start));
}
return list;
}
}
}

11
Runtime/ui/txt/emoji.cs.meta


fileFormatVersion: 2
guid: 7b8684b4e16cc4bf0a63f2da388d2227
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

205
Tests/Resources/Emoji.png

之前 之后
宽度: 512  |  高度: 512  |  大小: 72 KiB

90
Tests/Resources/Emoji.png.meta


fileFormatVersion: 2
guid: 7a63b2e6e2c2045edb6a0be310ef2472
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: -1
wrapV: -1
wrapW: -1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存