Files
SpineViewer/SpineRuntimes/SpineRuntime21/SkeletonBinary.cs

664 lines
23 KiB
C#

/******************************************************************************
* Spine Runtimes Software License
* Version 2.1
*
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable and
* non-transferable license to install, execute and perform the Spine Runtimes
* Software (the "Software") solely for internal use. Without the written
* permission of Esoteric Software (typically granted by licensing Spine), you
* may not (a) modify, translate, adapt or otherwise create derivative works,
* improvements of the Software or develop new applications using the Software
* or (b) remove, delete, alter or obscure any trademarks or any copyright,
* trademark, patent or other intellectual property or proprietary rights
* notices on or in the Software, including any copy thereof. Redistributions
* in binary or source form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.IO;
using System.Collections.Generic;
#if WINDOWS_STOREAPP
using System.Threading.Tasks;
using Windows.Storage;
#endif
namespace SpineRuntime21 {
public class SkeletonBinary {
public const int TIMELINE_SCALE = 0;
public const int TIMELINE_ROTATE = 1;
public const int TIMELINE_TRANSLATE = 2;
public const int TIMELINE_ATTACHMENT = 3;
public const int TIMELINE_COLOR = 4;
public const int TIMELINE_FLIPX = 5;
public const int TIMELINE_FLIPY = 6;
public const int CURVE_LINEAR = 0;
public const int CURVE_STEPPED = 1;
public const int CURVE_BEZIER = 2;
private AttachmentLoader attachmentLoader;
public float Scale { get; set; }
private char[] chars = new char[32];
private byte[] buffer = new byte[4];
public SkeletonBinary (params Atlas[] atlasArray)
: this(new AtlasAttachmentLoader(atlasArray)) {
}
public SkeletonBinary (AttachmentLoader attachmentLoader) {
if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null.");
this.attachmentLoader = attachmentLoader;
Scale = 1;
}
#if WINDOWS_STOREAPP
private async Task<SkeletonData> ReadFile(string path) {
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) {
SkeletonData skeletonData = ReadSkeletonData(input);
skeletonData.Name = Path.GetFileNameWithoutExtension(path);
return skeletonData;
}
}
public SkeletonData ReadSkeletonData (String path) {
return this.ReadFile(path).Result;
}
#else
public SkeletonData ReadSkeletonData (String path) {
#if WINDOWS_PHONE
using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path)))
{
#else
using (var input = new BufferedStream(new FileStream(path, FileMode.Open))) {
#endif
SkeletonData skeletonData = ReadSkeletonData(input);
skeletonData.name = Path.GetFileNameWithoutExtension(path);
return skeletonData;
}
}
#endif
public SkeletonData ReadSkeletonData (Stream input) {
if (input == null) throw new ArgumentNullException("input cannot be null.");
float scale = Scale;
var skeletonData = new SkeletonData();
skeletonData.hash = ReadString(input);
if (skeletonData.hash.Length == 0) skeletonData.hash = null;
skeletonData.version = ReadString(input);
if (skeletonData.version.Length == 0) skeletonData.version = null;
skeletonData.width = ReadFloat(input);
skeletonData.height = ReadFloat(input);
bool nonessential = ReadBoolean(input);
if (nonessential) {
skeletonData.imagesPath = ReadString(input);
if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null;
}
// Bones.
for (int i = 0, n = ReadInt(input, true); i < n; i++) {
String name = ReadString(input);
BoneData parent = null;
int parentIndex = ReadInt(input, true) - 1;
if (parentIndex != -1) parent = skeletonData.bones[parentIndex];
BoneData boneData = new BoneData(i, name, parent);
boneData.x = ReadFloat(input) * scale;
boneData.y = ReadFloat(input) * scale;
boneData.scaleX = ReadFloat(input);
boneData.scaleY = ReadFloat(input);
boneData.rotation = ReadFloat(input);
boneData.length = ReadFloat(input) * scale;
boneData.flipX = ReadBoolean(input);
boneData.flipY = ReadBoolean(input);
boneData.inheritScale = ReadBoolean(input);
boneData.inheritRotation = ReadBoolean(input);
if (nonessential) ReadInt(input); // Skip bone color.
skeletonData.bones.Add(boneData);
}
// IK constraints.
for (int i = 0, n = ReadInt(input, true); i < n; i++) {
IkConstraintData ikConstraintData = new IkConstraintData(ReadString(input));
for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++)
ikConstraintData.bones.Add(skeletonData.bones[ReadInt(input, true)]);
ikConstraintData.target = skeletonData.bones[ReadInt(input, true)];
ikConstraintData.mix = ReadFloat(input);
ikConstraintData.bendDirection = ReadSByte(input);
skeletonData.ikConstraints.Add(ikConstraintData);
}
// Slots.
for (int i = 0, n = ReadInt(input, true); i < n; i++) {
String slotName = ReadString(input);
BoneData boneData = skeletonData.bones[ReadInt(input, true)];
SlotData slotData = new SlotData(i, slotName, boneData);
int color = ReadInt(input);
slotData.r = ((color & 0xff000000) >> 24) / 255f;
slotData.g = ((color & 0x00ff0000) >> 16) / 255f;
slotData.b = ((color & 0x0000ff00) >> 8) / 255f;
slotData.a = ((color & 0x000000ff)) / 255f;
slotData.attachmentName = ReadString(input);
slotData.additiveBlending = ReadBoolean(input);
skeletonData.slots.Add(slotData);
}
// Default skin.
Skin defaultSkin = ReadSkin(input, "default", nonessential);
if (defaultSkin != null) {
skeletonData.defaultSkin = defaultSkin;
skeletonData.skins.Add(defaultSkin);
}
// Skins.
for (int i = 0, n = ReadInt(input, true); i < n; i++)
skeletonData.skins.Add(ReadSkin(input, ReadString(input), nonessential));
// Events.
for (int i = 0, n = ReadInt(input, true); i < n; i++) {
EventData eventData = new EventData(ReadString(input));
eventData.Int = ReadInt(input, false);
eventData.Float = ReadFloat(input);
eventData.String = ReadString(input);
skeletonData.events.Add(eventData);
}
// Animations.
for (int i = 0, n = ReadInt(input, true); i < n; i++)
ReadAnimation(ReadString(input), input, skeletonData);
skeletonData.bones.TrimExcess();
skeletonData.slots.TrimExcess();
skeletonData.skins.TrimExcess();
skeletonData.events.TrimExcess();
skeletonData.animations.TrimExcess();
skeletonData.ikConstraints.TrimExcess();
return skeletonData;
}
/** @return May be null. */
private Skin ReadSkin (Stream input, String skinName, bool nonessential) {
int slotCount = ReadInt(input, true);
if (slotCount == 0) return null;
Skin skin = new Skin(skinName);
for (int i = 0; i < slotCount; i++) {
int slotIndex = ReadInt(input, true);
for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) {
String name = ReadString(input);
skin.AddAttachment(slotIndex, name, ReadAttachment(input, skin, name, nonessential));
}
}
return skin;
}
private Attachment ReadAttachment (Stream input, Skin skin, String attachmentName, bool nonessential) {
float scale = Scale;
String name = ReadString(input);
if (name == null) name = attachmentName;
switch ((AttachmentType)input.ReadByte()) {
case AttachmentType.region: {
String path = ReadString(input);
if (path == null) path = name;
RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path);
if (region == null) return null;
region.Path = path;
region.x = ReadFloat(input) * scale;
region.y = ReadFloat(input) * scale;
region.scaleX = ReadFloat(input);
region.scaleY = ReadFloat(input);
region.rotation = ReadFloat(input);
region.width = ReadFloat(input) * scale;
region.height = ReadFloat(input) * scale;
int color = ReadInt(input);
region.r = ((color & 0xff000000) >> 24) / 255f;
region.g = ((color & 0x00ff0000) >> 16) / 255f;
region.b = ((color & 0x0000ff00) >> 8) / 255f;
region.a = ((color & 0x000000ff)) / 255f;
region.UpdateOffset();
return region;
}
case AttachmentType.boundingbox: {
BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
if (box == null) return null;
box.vertices = ReadFloatArray(input, scale);
return box;
}
case AttachmentType.mesh: {
String path = ReadString(input);
if (path == null) path = name;
MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
if (mesh == null) return null;
mesh.Path = path;
mesh.regionUVs = ReadFloatArray(input, 1);
mesh.triangles = ReadShortArray(input);
mesh.vertices = ReadFloatArray(input, scale);
mesh.UpdateUVs();
int color = ReadInt(input);
mesh.r = ((color & 0xff000000) >> 24) / 255f;
mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
mesh.a = ((color & 0x000000ff)) / 255f;
mesh.HullLength = ReadInt(input, true) * 2;
if (nonessential) {
mesh.Edges = ReadIntArray(input);
mesh.Width = ReadFloat(input) * scale;
mesh.Height = ReadFloat(input) * scale;
}
return mesh;
}
case AttachmentType.skinnedmesh: {
String path = ReadString(input);
if (path == null) path = name;
SkinnedMeshAttachment mesh = attachmentLoader.NewSkinnedMeshAttachment(skin, name, path);
if (mesh == null) return null;
mesh.Path = path;
float[] uvs = ReadFloatArray(input, 1);
int[] triangles = ReadShortArray(input);
int vertexCount = ReadInt(input, true);
var weights = new List<float>(uvs.Length * 3 * 3);
var bones = new List<int>(uvs.Length * 3);
for (int i = 0; i < vertexCount; i++) {
int boneCount = (int)ReadFloat(input);
bones.Add(boneCount);
for (int nn = i + boneCount * 4; i < nn; i += 4) {
bones.Add((int)ReadFloat(input));
weights.Add(ReadFloat(input) * scale);
weights.Add(ReadFloat(input) * scale);
weights.Add(ReadFloat(input));
}
}
mesh.bones = bones.ToArray();
mesh.weights = weights.ToArray();
mesh.triangles = triangles;
mesh.regionUVs = uvs;
mesh.UpdateUVs();
int color = ReadInt(input);
mesh.r = ((color & 0xff000000) >> 24) / 255f;
mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
mesh.a = ((color & 0x000000ff)) / 255f;
mesh.HullLength = ReadInt(input, true) * 2;
if (nonessential) {
mesh.Edges = ReadIntArray(input);
mesh.Width = ReadFloat(input) * scale;
mesh.Height = ReadFloat(input) * scale;
}
return mesh;
}
}
return null;
}
private float[] ReadFloatArray (Stream input, float scale) {
int n = ReadInt(input, true);
float[] array = new float[n];
if (scale == 1) {
for (int i = 0; i < n; i++)
array[i] = ReadFloat(input);
} else {
for (int i = 0; i < n; i++)
array[i] = ReadFloat(input) * scale;
}
return array;
}
private int[] ReadShortArray (Stream input) {
int n = ReadInt(input, true);
int[] array = new int[n];
for (int i = 0; i < n; i++)
array[i] = (input.ReadByte() << 8) + input.ReadByte();
return array;
}
private int[] ReadIntArray (Stream input) {
int n = ReadInt(input, true);
int[] array = new int[n];
for (int i = 0; i < n; i++)
array[i] = ReadInt(input, true);
return array;
}
private void ReadAnimation (String name, Stream input, SkeletonData skeletonData) {
var timelines = new List<Timeline>();
float scale = Scale;
float duration = 0;
// Slot timelines.
for (int i = 0, n = ReadInt(input, true); i < n; i++) {
int slotIndex = ReadInt(input, true);
for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) {
int timelineType = input.ReadByte();
int frameCount = ReadInt(input, true);
switch (timelineType) {
case TIMELINE_COLOR: {
ColorTimeline timeline = new ColorTimeline(frameCount);
timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
float time = ReadFloat(input);
int color = ReadInt(input);
float r = ((color & 0xff000000) >> 24) / 255f;
float g = ((color & 0x00ff0000) >> 16) / 255f;
float b = ((color & 0x0000ff00) >> 8) / 255f;
float a = ((color & 0x000000ff)) / 255f;
timeline.SetFrame(frameIndex, time, r, g, b, a);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]);
break;
}
case TIMELINE_ATTACHMENT: {
AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
timeline.slotIndex = slotIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input));
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount - 1]);
break;
}
}
}
}
// Bone timelines.
for (int i = 0, n = ReadInt(input, true); i < n; i++) {
int boneIndex = ReadInt(input, true);
for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) {
int timelineType = input.ReadByte();
int frameCount = ReadInt(input, true);
switch (timelineType) {
case TIMELINE_ROTATE: {
RotateTimeline timeline = new RotateTimeline(frameCount);
timeline.boneIndex = boneIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input));
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount * 2 - 2]);
break;
}
case TIMELINE_TRANSLATE:
case TIMELINE_SCALE: {
TranslateTimeline timeline;
float timelineScale = 1;
if (timelineType == TIMELINE_SCALE)
timeline = new ScaleTimeline(frameCount);
else {
timeline = new TranslateTimeline(frameCount);
timelineScale = scale;
}
timeline.boneIndex = boneIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input)
* timelineScale);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]);
break;
}
case TIMELINE_FLIPX:
case TIMELINE_FLIPY: {
FlipXTimeline timeline = timelineType == TIMELINE_FLIPX ? new FlipXTimeline(frameCount) : new FlipYTimeline(
frameCount);
timeline.boneIndex = boneIndex;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
timeline.SetFrame(frameIndex, ReadFloat(input), ReadBoolean(input));
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount * 2 - 2]);
break;
}
}
}
}
// IK timelines.
for (int i = 0, n = ReadInt(input, true); i < n; i++) {
IkConstraintData ikConstraint = skeletonData.ikConstraints[ReadInt(input, true)];
int frameCount = ReadInt(input, true);
IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount);
timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint);
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input));
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]);
}
// FFD timelines.
for (int i = 0, n = ReadInt(input, true); i < n; i++) {
Skin skin = skeletonData.skins[ReadInt(input, true)];
for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) {
int slotIndex = ReadInt(input, true);
for (int iii = 0, nnn = ReadInt(input, true); iii < nnn; iii++) {
Attachment attachment = skin.GetAttachment(slotIndex, ReadString(input));
int frameCount = ReadInt(input, true);
FFDTimeline timeline = new FFDTimeline(frameCount);
timeline.slotIndex = slotIndex;
timeline.attachment = attachment;
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
float time = ReadFloat(input);
float[] vertices;
int vertexCount;
if (attachment is MeshAttachment)
vertexCount = ((MeshAttachment)attachment).vertices.Length;
else
vertexCount = ((SkinnedMeshAttachment)attachment).weights.Length / 3 * 2;
int end = ReadInt(input, true);
if (end == 0) {
if (attachment is MeshAttachment)
vertices = ((MeshAttachment)attachment).vertices;
else
vertices = new float[vertexCount];
} else {
vertices = new float[vertexCount];
int start = ReadInt(input, true);
end += start;
if (scale == 1) {
for (int v = start; v < end; v++)
vertices[v] = ReadFloat(input);
} else {
for (int v = start; v < end; v++)
vertices[v] = ReadFloat(input) * scale;
}
if (attachment is MeshAttachment) {
float[] meshVertices = ((MeshAttachment)attachment).vertices;
for (int v = 0, vn = vertices.Length; v < vn; v++)
vertices[v] += meshVertices[v];
}
}
timeline.SetFrame(frameIndex, time, vertices);
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[frameCount - 1]);
}
}
}
// Draw order timeline.
int drawOrderCount = ReadInt(input, true);
if (drawOrderCount > 0) {
DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount);
int slotCount = skeletonData.slots.Count;
for (int i = 0; i < drawOrderCount; i++) {
int offsetCount = ReadInt(input, true);
int[] drawOrder = new int[slotCount];
for (int ii = slotCount - 1; ii >= 0; ii--)
drawOrder[ii] = -1;
int[] unchanged = new int[slotCount - offsetCount];
int originalIndex = 0, unchangedIndex = 0;
for (int ii = 0; ii < offsetCount; ii++) {
int slotIndex = ReadInt(input, true);
// Collect unchanged items.
while (originalIndex != slotIndex)
unchanged[unchangedIndex++] = originalIndex++;
// Set changed items.
drawOrder[originalIndex + ReadInt(input, true)] = originalIndex++;
}
// Collect remaining unchanged items.
while (originalIndex < slotCount)
unchanged[unchangedIndex++] = originalIndex++;
// Fill in unchanged items.
for (int ii = slotCount - 1; ii >= 0; ii--)
if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex];
timeline.SetFrame(i, ReadFloat(input), drawOrder);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]);
}
// Event timeline.
int eventCount = ReadInt(input, true);
if (eventCount > 0) {
EventTimeline timeline = new EventTimeline(eventCount);
for (int i = 0; i < eventCount; i++) {
float time = ReadFloat(input);
EventData eventData = skeletonData.events[ReadInt(input, true)];
Event e = new Event(eventData);
e.Int = ReadInt(input, false);
e.Float = ReadFloat(input);
e.String = ReadBoolean(input) ? ReadString(input) : eventData.String;
timeline.SetFrame(i, time, e);
}
timelines.Add(timeline);
duration = Math.Max(duration, timeline.frames[eventCount - 1]);
}
timelines.TrimExcess();
skeletonData.animations.Add(new Animation(name, timelines, duration));
}
private void ReadCurve (Stream input, int frameIndex, CurveTimeline timeline) {
switch (input.ReadByte()) {
case CURVE_STEPPED:
timeline.SetStepped(frameIndex);
break;
case CURVE_BEZIER:
timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input));
break;
}
}
private sbyte ReadSByte (Stream input) {
int value = input.ReadByte();
if (value == -1) throw new EndOfStreamException();
return (sbyte)value;
}
private bool ReadBoolean (Stream input) {
return input.ReadByte() != 0;
}
private float ReadFloat (Stream input) {
buffer[3] = (byte)input.ReadByte();
buffer[2] = (byte)input.ReadByte();
buffer[1] = (byte)input.ReadByte();
buffer[0] = (byte)input.ReadByte();
return BitConverter.ToSingle(buffer, 0);
}
private int ReadInt (Stream input) {
return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte();
}
private int ReadInt (Stream input, bool optimizePositive) {
int b = input.ReadByte();
int result = b & 0x7F;
if ((b & 0x80) != 0) {
b = input.ReadByte();
result |= (b & 0x7F) << 7;
if ((b & 0x80) != 0) {
b = input.ReadByte();
result |= (b & 0x7F) << 14;
if ((b & 0x80) != 0) {
b = input.ReadByte();
result |= (b & 0x7F) << 21;
if ((b & 0x80) != 0) {
b = input.ReadByte();
result |= (b & 0x7F) << 28;
}
}
}
}
return optimizePositive ? result : ((result >> 1) ^ -(result & 1));
}
private string ReadString (Stream input) {
int charCount = ReadInt(input, true);
switch (charCount) {
case 0:
return null;
case 1:
return "";
}
charCount--;
char[] chars = this.chars;
if (chars.Length < charCount) this.chars = chars = new char[charCount];
// Try to read 7 bit ASCII chars.
int charIndex = 0;
int b = 0;
while (charIndex < charCount) {
b = input.ReadByte();
if (b > 127) break;
chars[charIndex++] = (char)b;
}
// If a char was not ASCII, finish with slow path.
if (charIndex < charCount) ReadUtf8_slow(input, charCount, charIndex, b);
return new String(chars, 0, charCount);
}
private void ReadUtf8_slow (Stream input, int charCount, int charIndex, int b) {
char[] chars = this.chars;
while (true) {
switch (b >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
chars[charIndex] = (char)b;
break;
case 12:
case 13:
chars[charIndex] = (char)((b & 0x1F) << 6 | input.ReadByte() & 0x3F);
break;
case 14:
chars[charIndex] = (char)((b & 0x0F) << 12 | (input.ReadByte() & 0x3F) << 6 | input.ReadByte() & 0x3F);
break;
}
if (++charIndex >= charCount) break;
b = input.ReadByte() & 0xFF;
}
}
}
}