更换为wpf
This commit is contained in:
125
Spine/Utils/BinaryReader.cs
Normal file
125
Spine/Utils/BinaryReader.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Spine.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// 二进制骨骼文件读
|
||||
/// </summary>
|
||||
public class BinaryReader(Stream input)
|
||||
{
|
||||
private readonly Stream _input = input;
|
||||
private byte[] _buffer = new byte[32];
|
||||
private readonly byte[] _bytesBigEndian = new byte[8];
|
||||
private readonly List<string> _stringTable = new(32);
|
||||
|
||||
public int Read()
|
||||
{
|
||||
int val = _input.ReadByte();
|
||||
if (val == -1) throw new EndOfStreamException();
|
||||
return val;
|
||||
}
|
||||
|
||||
public byte ReadByte() => (byte)Read();
|
||||
|
||||
public byte ReadUByte() => (byte)Read();
|
||||
|
||||
public sbyte ReadSByte() => (sbyte)ReadByte();
|
||||
|
||||
public bool ReadBoolean() => Read() != 0;
|
||||
|
||||
public float ReadFloat()
|
||||
{
|
||||
if (_input.Read(_bytesBigEndian, 0, 4) < 4) throw new EndOfStreamException();
|
||||
_buffer[3] = _bytesBigEndian[0];
|
||||
_buffer[2] = _bytesBigEndian[1];
|
||||
_buffer[1] = _bytesBigEndian[2];
|
||||
_buffer[0] = _bytesBigEndian[3];
|
||||
return BitConverter.ToSingle(_buffer, 0);
|
||||
}
|
||||
|
||||
public int ReadInt()
|
||||
{
|
||||
if (_input.Read(_bytesBigEndian, 0, 4) < 4) throw new EndOfStreamException();
|
||||
return (_bytesBigEndian[0] << 24)
|
||||
| (_bytesBigEndian[1] << 16)
|
||||
| (_bytesBigEndian[2] << 8)
|
||||
| _bytesBigEndian[3];
|
||||
}
|
||||
|
||||
public long ReadLong()
|
||||
{
|
||||
if (_input.Read(_bytesBigEndian, 0, 8) < 8) throw new EndOfStreamException();
|
||||
return ((long)(_bytesBigEndian[0]) << 56)
|
||||
| ((long)(_bytesBigEndian[1]) << 48)
|
||||
| ((long)(_bytesBigEndian[2]) << 40)
|
||||
| ((long)(_bytesBigEndian[3]) << 32)
|
||||
| ((long)(_bytesBigEndian[4]) << 24)
|
||||
| ((long)(_bytesBigEndian[5]) << 16)
|
||||
| ((long)(_bytesBigEndian[6]) << 8)
|
||||
| (long)(_bytesBigEndian[7]);
|
||||
}
|
||||
|
||||
public int ReadVarInt(bool optimizePositive = true)
|
||||
{
|
||||
byte b = ReadByte();
|
||||
int val = b & 0x7F;
|
||||
if ((b & 0x80) != 0)
|
||||
{
|
||||
b = ReadByte();
|
||||
val |= (b & 0x7F) << 7;
|
||||
if ((b & 0x80) != 0)
|
||||
{
|
||||
b = ReadByte();
|
||||
val |= (b & 0x7F) << 14;
|
||||
if ((b & 0x80) != 0)
|
||||
{
|
||||
b = ReadByte();
|
||||
val |= (b & 0x7F) << 21;
|
||||
if ((b & 0x80) != 0)
|
||||
val |= (ReadByte() & 0x7F) << 28;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 最低位是符号, 根据符号得到全 1 或全 0
|
||||
// 无符号右移, 符号按原样设置在最高位, 其他位与符号异或
|
||||
return optimizePositive ? val : (val >>> 1) ^ -(val & 1);
|
||||
}
|
||||
|
||||
public string? ReadString()
|
||||
{
|
||||
int byteCount = ReadVarInt();
|
||||
switch (byteCount)
|
||||
{
|
||||
case 0: return null;
|
||||
case 1: return "";
|
||||
}
|
||||
byteCount--;
|
||||
if (_buffer.Length < byteCount) _buffer = new byte[byteCount];
|
||||
ReadFully(_buffer, 0, byteCount);
|
||||
return Encoding.UTF8.GetString(_buffer, 0, byteCount);
|
||||
}
|
||||
|
||||
public string? ReadStringRef()
|
||||
{
|
||||
int index = ReadVarInt();
|
||||
return index == 0 ? null : _stringTable[index - 1];
|
||||
}
|
||||
|
||||
public void ReadFully(byte[] buffer, int offset, int length)
|
||||
{
|
||||
while (length > 0)
|
||||
{
|
||||
int count = _input.Read(buffer, offset, length);
|
||||
if (count <= 0) throw new EndOfStreamException();
|
||||
offset += count;
|
||||
length -= count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
140
Spine/Utils/BinaryWriter.cs
Normal file
140
Spine/Utils/BinaryWriter.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Spine.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// 二进制骨骼文件写
|
||||
/// </summary>
|
||||
public class BinaryWriter(Stream output)
|
||||
{
|
||||
private readonly Stream _output = output;
|
||||
private byte[] _buffer = new byte[32];
|
||||
private readonly byte[] _bytesBigEndian = new byte[8];
|
||||
private readonly List<string> _stringTable = new(32);
|
||||
|
||||
/// <summary>
|
||||
/// 构造 Writer, 但是继承其他 Writer 的字符串表
|
||||
/// </summary>
|
||||
public BinaryWriter(Stream output, BinaryWriter writer) : this(output)
|
||||
{
|
||||
_stringTable.AddRange(writer._stringTable);
|
||||
}
|
||||
|
||||
public void Write(int val) => _output.WriteByte((byte)val);
|
||||
|
||||
public void WriteByte(byte val) => _output.WriteByte(val);
|
||||
|
||||
public void WriteUByte(byte val) => _output.WriteByte(val);
|
||||
|
||||
public void WriteSByte(sbyte val) => _output.WriteByte((byte)val);
|
||||
|
||||
public void WriteBoolean(bool val) => _output.WriteByte((byte)(val ? 1 : 0));
|
||||
|
||||
public void WriteFloat(float val)
|
||||
{
|
||||
uint v = BitConverter.SingleToUInt32Bits(val);
|
||||
_bytesBigEndian[0] = (byte)(v >> 24);
|
||||
_bytesBigEndian[1] = (byte)(v >> 16);
|
||||
_bytesBigEndian[2] = (byte)(v >> 8);
|
||||
_bytesBigEndian[3] = (byte)v;
|
||||
_output.Write(_bytesBigEndian, 0, 4);
|
||||
}
|
||||
|
||||
public void WriteInt(int val)
|
||||
{
|
||||
_bytesBigEndian[0] = (byte)(val >> 24);
|
||||
_bytesBigEndian[1] = (byte)(val >> 16);
|
||||
_bytesBigEndian[2] = (byte)(val >> 8);
|
||||
_bytesBigEndian[3] = (byte)val;
|
||||
_output.Write(_bytesBigEndian, 0, 4);
|
||||
}
|
||||
|
||||
public void WriteLong(long val)
|
||||
{
|
||||
_bytesBigEndian[0] = (byte)(val >> 56);
|
||||
_bytesBigEndian[1] = (byte)(val >> 48);
|
||||
_bytesBigEndian[2] = (byte)(val >> 40);
|
||||
_bytesBigEndian[3] = (byte)(val >> 32);
|
||||
_bytesBigEndian[4] = (byte)(val >> 24);
|
||||
_bytesBigEndian[5] = (byte)(val >> 16);
|
||||
_bytesBigEndian[6] = (byte)(val >> 8);
|
||||
_bytesBigEndian[7] = (byte)val;
|
||||
_output.Write(_bytesBigEndian, 0, 8);
|
||||
}
|
||||
|
||||
public void WriteVarInt(int val, bool optimizePositive = true)
|
||||
{
|
||||
// 有符号右移, 会变成全 1 或者全 0 符号
|
||||
// 其他位与符号异或, 符号按原样设置在最低位
|
||||
if (!optimizePositive) val = (val << 1) ^ (val >> 31);
|
||||
|
||||
byte b = (byte)(val & 0x7F);
|
||||
val >>>= 7;
|
||||
if (val != 0)
|
||||
{
|
||||
_output.WriteByte((byte)(b | 0x80));
|
||||
b = (byte)(val & 0x7F);
|
||||
val >>>= 7;
|
||||
if (val != 0)
|
||||
{
|
||||
_output.WriteByte((byte)(b | 0x80));
|
||||
b = (byte)(val & 0x7F);
|
||||
val >>>= 7;
|
||||
if (val != 0)
|
||||
{
|
||||
_output.WriteByte((byte)(b | 0x80));
|
||||
b = (byte)(val & 0x7F);
|
||||
val >>>= 7;
|
||||
if (val != 0)
|
||||
{
|
||||
_output.WriteByte((byte)(b | 0x80));
|
||||
b = (byte)(val & 0x7F);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_output.WriteByte(b);
|
||||
}
|
||||
|
||||
public void WriteString(string? val)
|
||||
{
|
||||
if (val == null)
|
||||
{
|
||||
WriteVarInt(0);
|
||||
return;
|
||||
}
|
||||
if (val.Length == 0)
|
||||
{
|
||||
WriteVarInt(1);
|
||||
return;
|
||||
}
|
||||
int byteCount = System.Text.Encoding.UTF8.GetByteCount(val);
|
||||
WriteVarInt(byteCount + 1);
|
||||
if (_buffer.Length < byteCount) _buffer = new byte[byteCount];
|
||||
System.Text.Encoding.UTF8.GetBytes(val, 0, val.Length, _buffer, 0);
|
||||
WriteFully(_buffer, 0, byteCount);
|
||||
}
|
||||
|
||||
public void WriteStringRef(string? val)
|
||||
{
|
||||
if (val is null)
|
||||
{
|
||||
WriteVarInt(0);
|
||||
return;
|
||||
}
|
||||
int index = _stringTable.IndexOf(val);
|
||||
if (index < 0)
|
||||
{
|
||||
_stringTable.Add(val);
|
||||
index = _stringTable.Count - 1;
|
||||
}
|
||||
WriteVarInt(index + 1);
|
||||
}
|
||||
|
||||
public void WriteFully(byte[] buffer, int offset, int length) => _output.Write(buffer, offset, length);
|
||||
}
|
||||
}
|
||||
72
Spine/Utils/ImplementationResolver.cs
Normal file
72
Spine/Utils/ImplementationResolver.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Spine.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供关联的实现标识
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">标记类型</typeparam>
|
||||
public interface IImplementationKeyAttribute<TKey>
|
||||
{
|
||||
/// <summary>
|
||||
/// 实现类类型标记
|
||||
/// </summary>
|
||||
TKey ImplementationKey { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可以使用反射查找基类关联的所有实现类
|
||||
/// </summary>
|
||||
/// <typeparam name="TBase">所有实现类的基类型</typeparam>
|
||||
/// <typeparam name="TAttr">实现类类型属性标记类型</typeparam>
|
||||
/// <typeparam name="TKey">实现类类型标记类型</typeparam>
|
||||
public abstract class ImplementationResolver<TBase, TAttr, TKey>
|
||||
where TAttr : Attribute, IImplementationKeyAttribute<TKey>
|
||||
where TKey : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// 实现类型缓存
|
||||
/// </summary>
|
||||
private static readonly Dictionary<TKey, Type> _implementationTypes = [];
|
||||
|
||||
static ImplementationResolver()
|
||||
{
|
||||
var baseType = typeof(TBase);
|
||||
var impTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => baseType.IsAssignableFrom(t) && !t.IsAbstract);
|
||||
foreach (var type in impTypes)
|
||||
{
|
||||
foreach (var attr in type.GetCustomAttributes<TAttr>())
|
||||
{
|
||||
var key = attr.ImplementationKey;
|
||||
if (_implementationTypes.ContainsKey(key))
|
||||
throw new InvalidOperationException($"Multiple implementations found for key: {key}");
|
||||
_implementationTypes[key] = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断某种类型是否实现
|
||||
/// </summary>
|
||||
public static bool HasImplementation(TKey key) => _implementationTypes.ContainsKey(key);
|
||||
|
||||
/// <summary>
|
||||
/// 根据实现类键和参数创建实例
|
||||
/// </summary>
|
||||
/// <param name="impKey"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
protected static TBase CreateInstance(TKey impKey, params object?[]? args)
|
||||
{
|
||||
if (!_implementationTypes.TryGetValue(impKey, out var type))
|
||||
throw new NotImplementedException($"Not implemented type for {typeof(TBase)}: {impKey}");
|
||||
return (TBase)Activator.CreateInstance(type, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
123
Spine/Utils/SFMLBlendMode.cs
Normal file
123
Spine/Utils/SFMLBlendMode.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SFML.Graphics;
|
||||
|
||||
namespace Spine.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// SFML 混合模式, 预乘模式下输入和输出的像素值都是预乘的
|
||||
/// </summary>
|
||||
public static class SFMLBlendMode
|
||||
{
|
||||
///// <summary>
|
||||
///// Normal Blend, 无预乘, 仅在 dst.a 是 1 时得到正确结果, 其余情况是有偏结果
|
||||
///// <para>当 <c>src.c < dst.c</c> 时, 结果偏大, 例如 src 是半透明纯黑, dst 是全透明纯白</para>
|
||||
///// <para>当 <c>src.c > dst.c</c> 时, 结果偏小, 例如 src 是半透明纯白, dst 是全透明纯黑</para>
|
||||
///// <code>
|
||||
///// res.c = src.c * src.a + dst.c * (1 - src.a)
|
||||
///// res.a = src.a * 1 + dst.a * (1 - src.a)
|
||||
///// </code>
|
||||
///// </summary>
|
||||
//public static readonly BlendMode Normal = new(
|
||||
// BlendMode.Factor.SrcAlpha,
|
||||
// BlendMode.Factor.OneMinusSrcAlpha,
|
||||
// BlendMode.Equation.Add,
|
||||
// BlendMode.Factor.One,
|
||||
// BlendMode.Factor.OneMinusSrcAlpha,
|
||||
// BlendMode.Equation.Add
|
||||
//);
|
||||
|
||||
///// <summary>
|
||||
///// Additive Blend, 无预乘, 仅在 dst.a 是 1 时得到正确结果, 其余情况是有偏结果
|
||||
///// <para>当 <c>src.a + dst.a >= 1</c> 时, 结果偏大, 例如 src 是不透明纯黑, dst 是全透明纯白</para>
|
||||
///// <para>当 <c>src.a + dst.a < 1</c> 时, 结果偏差方式类似 <see cref="Normal"/>, 均可假设 dst 是全透明纯白进行判断</para>
|
||||
///// <code>
|
||||
///// res.c = src.c * src.a + dst.c * 1
|
||||
///// res.a = src.a * 1 + dst.a * 1
|
||||
///// </code>
|
||||
///// </summary>
|
||||
//public static readonly BlendMode Additive = new(
|
||||
// BlendMode.Factor.SrcAlpha,
|
||||
// BlendMode.Factor.One,
|
||||
// BlendMode.Equation.Add,
|
||||
// BlendMode.Factor.One,
|
||||
// BlendMode.Factor.One,
|
||||
// BlendMode.Equation.Add
|
||||
//);
|
||||
|
||||
/// <summary>
|
||||
/// Normal Blend with PremultipliedAlpha
|
||||
/// <code>
|
||||
/// [res.c * res.a] = [src.c * src.a] * 1 + [dst.c * dst.a] * (1 - src.a)
|
||||
/// res.a = src.a * 1 + dst.a * (1 - src.a)
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public static readonly BlendMode NormalPma = new(
|
||||
BlendMode.Factor.One,
|
||||
BlendMode.Factor.OneMinusSrcAlpha,
|
||||
BlendMode.Equation.Add,
|
||||
BlendMode.Factor.One,
|
||||
BlendMode.Factor.OneMinusSrcAlpha,
|
||||
BlendMode.Equation.Add
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Additive Blend with PremultipliedAlpha
|
||||
/// <code>
|
||||
/// [res.c * res.a] = [src.c * src.a] * 1 + [dst.c * dst.a] * 1
|
||||
/// res.a = src.a * 1 + dst.a * 1
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public static readonly BlendMode AdditivePma = new(
|
||||
BlendMode.Factor.One,
|
||||
BlendMode.Factor.One,
|
||||
BlendMode.Equation.Add,
|
||||
BlendMode.Factor.One,
|
||||
BlendMode.Factor.One,
|
||||
BlendMode.Equation.Add
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Multiply Blend with PremultipliedAlpha
|
||||
/// <code>
|
||||
/// res.c = src.c * dst.c + dst.c * (1 - src.a)
|
||||
/// res.a = src.a * 1 + dst.a * (1 - src.a)
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public static readonly BlendMode MultiplyPma = new(
|
||||
BlendMode.Factor.DstColor,
|
||||
BlendMode.Factor.OneMinusSrcAlpha,
|
||||
BlendMode.Equation.Add,
|
||||
BlendMode.Factor.One,
|
||||
BlendMode.Factor.OneMinusSrcAlpha,
|
||||
BlendMode.Equation.Add
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Screen Blend with PremultipliedAlpha Only
|
||||
/// <code>
|
||||
/// res.c = src.c * 1 + dst.c * (1 - src.c) = 1 - [(1 - src.c)(1 - dst.c)]
|
||||
/// res.a = src.a * 1 + dst.a * (1 - src.a)
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public static readonly BlendMode ScreenPma = new(
|
||||
BlendMode.Factor.One,
|
||||
BlendMode.Factor.OneMinusSrcColor,
|
||||
BlendMode.Equation.Add,
|
||||
BlendMode.Factor.One,
|
||||
BlendMode.Factor.OneMinusSrcAlpha,
|
||||
BlendMode.Equation.Add
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// 仅源像素混合模式
|
||||
/// </summary>
|
||||
public static readonly BlendMode SourceOnly = new(
|
||||
BlendMode.Factor.One,
|
||||
BlendMode.Factor.Zero
|
||||
);
|
||||
}
|
||||
}
|
||||
76
Spine/Utils/SFMLShader.cs
Normal file
76
Spine/Utils/SFMLShader.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SFML.Graphics;
|
||||
|
||||
namespace Spine.Utils
|
||||
{
|
||||
public static class SFMLShader
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于非预乘纹理的 fragment shader, 乘上了插值后的透明度用于实现透明度变化(插值预乘), 并且输出预乘后的像素值
|
||||
/// </summary>
|
||||
private const string FRAGMENT_VertexAlpha =
|
||||
"uniform sampler2D t;" +
|
||||
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
|
||||
"p.rgb *= p.a * gl_Color.a;" +
|
||||
"gl_FragColor = gl_Color * p; }"
|
||||
;
|
||||
|
||||
/// <summary>
|
||||
/// 用于预乘纹理的 fragment shader, 乘上了插值后的透明度用于实现透明度变化(插值预乘)
|
||||
/// </summary>
|
||||
private const string FRAGMENT_VertexAlphaPma =
|
||||
"uniform sampler2D t;" +
|
||||
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
|
||||
"p.rgb *= gl_Color.a;" +
|
||||
"gl_FragColor = gl_Color * p; }"
|
||||
;
|
||||
|
||||
/// <summary>
|
||||
/// 预乘转非预乘 fragment shader
|
||||
/// </summary>
|
||||
private const string FRAGMENT_InvPma =
|
||||
"uniform sampler2D t;" +
|
||||
"void main() { vec4 p = texture(t, gl_TexCoord[0].xy);" +
|
||||
"if (p.a > 0) p.rgb /= max(max(max(p.r, p.g), p.b), p.a);" +
|
||||
"gl_FragColor = p; }"
|
||||
;
|
||||
|
||||
/// <summary>
|
||||
/// 考虑了顶点透明度变化的着色器, 输入是非预乘纹理像素, 输出是预乘像素
|
||||
/// </summary>
|
||||
public static Shader? VertexAlpha { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 考虑了顶点透明度变化的着色器, 输入和输出均是预乘像素值
|
||||
/// </summary>
|
||||
public static Shader? VertexAlphaPma { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 反预乘着色器, 用于得到正确透明度的非预乘画面
|
||||
/// </summary>
|
||||
public static Shader? InversePma { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 加载 Shader, 可能会存在异常导致着色器加载失败, 会在日志中输出信息
|
||||
/// </summary>
|
||||
static SFMLShader()
|
||||
{
|
||||
try
|
||||
{
|
||||
VertexAlpha = Shader.FromString(null, null, FRAGMENT_VertexAlpha);
|
||||
VertexAlphaPma = Shader.FromString(null, null, FRAGMENT_VertexAlphaPma);
|
||||
InversePma = Shader.FromString(null, null, FRAGMENT_InvPma);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
logger.Trace(ex.ToString());
|
||||
logger.Error("Failed to init shaders, {0}", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Spine/Utils/SpineImplementationAttribute.cs
Normal file
22
Spine/Utils/SpineImplementationAttribute.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Spine.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Spine 实现类标记
|
||||
/// </summary>
|
||||
/// <param name="major">主版本号</param>
|
||||
/// <param name="minor">次版本号</param>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class SpineImplementationAttribute(int major, int minor) : Attribute, IImplementationKeyAttribute<string>
|
||||
{
|
||||
/// <summary>
|
||||
/// 与 <c><see cref="SpineVersion.Tag"/></c> 相同格式的字符串
|
||||
/// </summary>
|
||||
public string ImplementationKey { get; } = $"{major}.{minor}";
|
||||
}
|
||||
}
|
||||
99
Spine/Utils/Utf8Validator.cs
Normal file
99
Spine/Utils/Utf8Validator.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Spine.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// UTF8 格式检测工具类
|
||||
/// </summary>
|
||||
public static class Utf8Validator
|
||||
{
|
||||
/// <summary>
|
||||
/// 判断某段数据是否是 UTF8 格式, 会忽略尾部不完整数据
|
||||
/// </summary>
|
||||
public static bool IsUtf8(byte[] data, int maxLength = 1024)
|
||||
{
|
||||
int length = Math.Min(data.Length, maxLength);
|
||||
|
||||
int start = 0;
|
||||
if (length >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF)
|
||||
{
|
||||
start = 3;
|
||||
}
|
||||
|
||||
int expectedContinuationBytes = 0;
|
||||
for (int i = start; i < length; i++)
|
||||
{
|
||||
byte currentByte = data[i];
|
||||
|
||||
if (expectedContinuationBytes == 0)
|
||||
{
|
||||
if ((currentByte & 0x80) == 0x00)
|
||||
{
|
||||
// 0xxxxxxx,ASCII 字符
|
||||
continue;
|
||||
}
|
||||
|
||||
// 计算需要的续字节数
|
||||
int needed;
|
||||
if ((currentByte & 0xE0) == 0xC0)
|
||||
{
|
||||
// 110xxxxx,1 个续字节
|
||||
if (currentByte == 0xC0 || currentByte == 0xC1)
|
||||
return false; // 避免过长编码
|
||||
needed = 1;
|
||||
}
|
||||
else if ((currentByte & 0xF0) == 0xE0)
|
||||
{
|
||||
// 1110xxxx,2 个续字节
|
||||
needed = 2;
|
||||
}
|
||||
else if ((currentByte & 0xF8) == 0xF0)
|
||||
{
|
||||
// 11110xxx,3 个续字节
|
||||
if (currentByte > 0xF4)
|
||||
return false; // 超出 Unicode 范围
|
||||
needed = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 非法的起始字节
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果剩余字节不足以完成这个字符,就当作“尾部不完整”,跳出主循环
|
||||
if (i + needed >= length)
|
||||
break;
|
||||
|
||||
// 否则进入续字节检查
|
||||
expectedContinuationBytes = needed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 检查续字节(10xxxxxx)
|
||||
if ((currentByte & 0xC0) != 0x80)
|
||||
return false;
|
||||
expectedContinuationBytes--;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果在跳出时,expectedContinuationBytes>0,说明我们跳过了一些尾部续字节,
|
||||
// 本着“忽略尾部不完整字符”的原则,仍然返回 true
|
||||
return expectedContinuationBytes == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断某个文件是否是 UTF8 格式, 会忽略尾部不完整数据
|
||||
/// </summary>
|
||||
public static bool IsUtf8(string path, int maxLength = 1024)
|
||||
{
|
||||
using var stream = File.OpenRead(path);
|
||||
byte[] data = new byte[maxLength];
|
||||
var actualLength = stream.Read(data, 0, data.Length);
|
||||
return IsUtf8(data, actualLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user