diff --git a/binding/HarfBuzzSharp/Blob.cs b/binding/HarfBuzzSharp/Blob.cs index 12888988c05..7c689a19a87 100644 --- a/binding/HarfBuzzSharp/Blob.cs +++ b/binding/HarfBuzzSharp/Blob.cs @@ -2,6 +2,7 @@ using System; using System.IO; +using System.Runtime.InteropServices; namespace HarfBuzzSharp { @@ -70,14 +71,29 @@ public static Blob FromFile (string fileName) public static unsafe Blob FromStream (Stream stream) { - // TODO: check to see if we can avoid the second copy (the ToArray) - - using var ms = new MemoryStream (); - stream.CopyTo (ms); - var data = ms.ToArray (); + if (stream == null) + throw new ArgumentNullException (nameof (stream)); + + // For non-seekable streams, buffer into memory first + if (!stream.CanSeek) { + using var ms = new MemoryStream (); + stream.CopyTo (ms); + ms.Position = 0; + return FromStream (ms); + } - fixed (byte* dataPtr = data) { - return new Blob ((IntPtr)dataPtr, data.Length, MemoryMode.ReadOnly, () => ms.Dispose ()); + var length = (int)(stream.Length - stream.Position); + if (length == 0) + return Empty; + + var dataPtr = Marshal.AllocCoTaskMem (length); + try { + using var ums = new UnmanagedMemoryStream ((byte*)dataPtr, length, length, FileAccess.ReadWrite); + stream.CopyTo (ums); + return new Blob (dataPtr, length, MemoryMode.ReadOnly, () => Marshal.FreeCoTaskMem (dataPtr)); + } catch { + Marshal.FreeCoTaskMem (dataPtr); + throw; } } diff --git a/tests/Tests/HarfBuzzSharp/HBBlobTest.cs b/tests/Tests/HarfBuzzSharp/HBBlobTest.cs index 72c17a602ef..9e14ad674a3 100644 --- a/tests/Tests/HarfBuzzSharp/HBBlobTest.cs +++ b/tests/Tests/HarfBuzzSharp/HBBlobTest.cs @@ -20,10 +20,25 @@ public void ShouldCreateFromFileName() [SkippableFact] public void ShouldCreateFromStream() { - using (var blob = Blob.FromStream(File.Open(Path.Combine(PathToFonts, "Funkster.ttf"), FileMode.Open, FileAccess.Read))) - { - Assert.Equal(236808, blob.Length); - } + using var stream = File.Open(Path.Combine(PathToFonts, "Funkster.ttf"), FileMode.Open, FileAccess.Read); + using var blob = Blob.FromStream(stream); + Assert.Equal(236808, blob.Length); + } + + [SkippableFact] + public void CreateFromStreamIsGCSafe() + { + using var stream = File.Open(Path.Combine(PathToFonts, "Funkster.ttf"), FileMode.Open, FileAccess.Read); + var originalData = new byte[stream.Length]; + stream.Read(originalData, 0, (int)stream.Length); + stream.Position = 0; + + using var blob = Blob.FromStream(stream); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + Assert.Equal(originalData, blob.AsSpan().ToArray()); } [SkippableFact]