From e196d5f50f9ddd3f751572c395b029870cab9745 Mon Sep 17 00:00:00 2001 From: "H. Utku Maden" Date: Sun, 12 Oct 2025 20:23:56 +0300 Subject: [PATCH] Implemented data: ser/des. --- ReFuel.Gltf/GltfBuffer.cs | 2 +- ReFuel.Gltf/GltfImage.cs | 12 ++-- ReFuel.Gltf/IO/DataUriStream.cs | 99 +++++++++++++++++++++++++++++++++ ReFuel.Gltf/IO/GltfUriReader.cs | 62 --------------------- 4 files changed, 106 insertions(+), 69 deletions(-) create mode 100755 ReFuel.Gltf/IO/DataUriStream.cs delete mode 100755 ReFuel.Gltf/IO/GltfUriReader.cs diff --git a/ReFuel.Gltf/GltfBuffer.cs b/ReFuel.Gltf/GltfBuffer.cs index f19f779..0588df1 100755 --- a/ReFuel.Gltf/GltfBuffer.cs +++ b/ReFuel.Gltf/GltfBuffer.cs @@ -37,7 +37,7 @@ namespace ReFuel.Gltf return File.OpenRead(path); } case "data": - return new GltfUriReader(Uri); + return DataUriStream.FromUri(Uri.OriginalString); default: if (provider != null) return provider.OpenFile(pwd, Uri) ?? throw new FileNotFoundException(Uri.ToString()); diff --git a/ReFuel.Gltf/GltfImage.cs b/ReFuel.Gltf/GltfImage.cs index b2ce082..90f2290 100644 --- a/ReFuel.Gltf/GltfImage.cs +++ b/ReFuel.Gltf/GltfImage.cs @@ -33,7 +33,7 @@ namespace ReFuel.Gltf internal GltfImage(GltfDocument document) : base(document) { } - + public Stream Open(string? pwd = null, IGltfStreamProvider? provider = null) { if (Uri == null) @@ -52,16 +52,16 @@ namespace ReFuel.Gltf } else { - string path = Path.Combine(pwd ?? string.Empty, Uri.LocalPath); + string path = Path.Combine(pwd ?? string.Empty, Uri.LocalPath); return File.OpenRead(path); } case "data": - return new GltfUriReader(Uri); + return DataUriStream.FromUri(Uri.OriginalString); } } - + // otherwise - + if (provider != null) return provider.OpenFile(pwd, Uri) ?? throw new FileNotFoundException(Uri.ToString()); else @@ -102,4 +102,4 @@ namespace ReFuel.Gltf throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/ReFuel.Gltf/IO/DataUriStream.cs b/ReFuel.Gltf/IO/DataUriStream.cs new file mode 100755 index 0000000..67011b9 --- /dev/null +++ b/ReFuel.Gltf/IO/DataUriStream.cs @@ -0,0 +1,99 @@ +using System.Text; + +namespace ReFuel.Gltf.IO +{ + public class DataUriStream : MemoryStream + { + public string? MediaType { get; set; } + + public DataUriStream() + { + } + + public DataUriStream(byte[] array) : base(array, true) + { + } + + public DataUriStream(int capacity) : base(capacity) + { + } + + public string ToUri(Encoding? encoding = null) + { + // Not the most efficient implementation here but it works. + StringBuilder builder = new StringBuilder((int)Length * 4 / 3); + ReadOnlySpan buffer = GetBuffer().AsSpan()[..(int)Length]; + + if (MediaType != null) + { + builder.Append(MediaType); + } + + if (encoding == null) + { + builder.Append(";base64,"); + builder.Append(Convert.ToBase64String(buffer)); + } + else + { + builder.Append(';'); + builder.Append(encoding.WebName); + builder.Append(','); + builder.Append(Uri.EscapeDataString(encoding.GetString(buffer))); + } + + return builder.ToString(); + } + + public static DataUriStream FromUri(ReadOnlySpan uri) + { + if (uri.CompareTo("data:", StringComparison.InvariantCulture) != 0) + { + throw new Exception("Expected a data uri."); + } + + int commaIndex = uri.IndexOf(','); + + string? mediaType = null; + string dataType = "base64"; + ReadOnlySpan data; + + if (commaIndex != -1) + { + ReadOnlySpan header = uri[5..commaIndex]; + int lastSemicolon = header.LastIndexOf(';'); + ReadOnlySpan mediaTypeSpan = header[..lastSemicolon]; + ReadOnlySpan dataTypeSpan = header[(lastSemicolon + 1)..]; + + mediaType = mediaTypeSpan.ToString(); + dataType = dataTypeSpan.ToString(); + data = uri[(commaIndex + 1)..]; + } + else + { + data = uri[5..]; + } + + + DataUriStream stream; + if (dataType == "base64") + { + stream = new DataUriStream(data.Length * 3 / 4 + 1) { MediaType = mediaType }; + + if (!Convert.TryFromBase64Chars(data, stream.GetBuffer(), out int bytesWritten)) + { + throw new Exception("Base64 conversion failed."); + } + + stream.SetLength(bytesWritten); + } + else + { + Encoding encoding = Encoding.GetEncoding(dataType); + stream = new DataUriStream(encoding.GetBytes(Uri.UnescapeDataString(data.ToString()))); + } + + return stream; + } + } +} diff --git a/ReFuel.Gltf/IO/GltfUriReader.cs b/ReFuel.Gltf/IO/GltfUriReader.cs deleted file mode 100755 index 66fa7f5..0000000 --- a/ReFuel.Gltf/IO/GltfUriReader.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace ReFuel.Gltf.IO -{ - public class GltfUriReader : Stream - { - public Uri Uri { get; } - public string Type { get; } - public string Encoding { get; } - - private string data; - private int index = 0; - - public GltfUriReader(Uri uri) - { - if (uri.Scheme != "data") - throw new Exception("Expected a URI with 'data:' scheme."); - - Uri = uri; - data = uri.AbsolutePath; - index = data.IndexOf(',') + 1; - - if (index == 0) - { - throw new Exception("No data string."); - } - } - - public override bool CanRead => true; - - public override bool CanSeek => true; - - public override bool CanWrite => false; - - public override long Length => throw new NotImplementedException(); - - public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - - public override void Flush() - { - throw new NotSupportedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - } -}