diff --git a/QuestPDF/Drawing/DocumentGenerator.cs b/QuestPDF/Drawing/DocumentGenerator.cs index f4d455c..e253c94 100644 --- a/QuestPDF/Drawing/DocumentGenerator.cs +++ b/QuestPDF/Drawing/DocumentGenerator.cs @@ -18,14 +18,16 @@ namespace QuestPDF.Drawing internal static void GeneratePdf(Stream stream, IDocument document) { var metadata = document.GetMetadata(); - var canvas = new PdfCanvas(stream, metadata); + var writeOnlyStream = new WriteOnlyStream(stream); + var canvas = new PdfCanvas(writeOnlyStream, metadata); RenderDocument(canvas, document); } internal static void GenerateXps(Stream stream, IDocument document) { var metadata = document.GetMetadata(); - var canvas = new XpsCanvas(stream, metadata); + var writeOnlyStream = new WriteOnlyStream(stream); + var canvas = new XpsCanvas(writeOnlyStream, metadata); RenderDocument(canvas, document); } diff --git a/QuestPDF/Drawing/PdfCanvas.cs b/QuestPDF/Drawing/PdfCanvas.cs index ca7cf6b..ac703eb 100644 --- a/QuestPDF/Drawing/PdfCanvas.cs +++ b/QuestPDF/Drawing/PdfCanvas.cs @@ -7,7 +7,7 @@ namespace QuestPDF.Drawing internal class PdfCanvas : SkiaDocumentCanvasBase { public PdfCanvas(Stream stream, DocumentMetadata documentMetadata) - : base(SKDocument.CreatePdf(new WriteStreamWrapper(stream), MapMetadata(documentMetadata))) + : base(SKDocument.CreatePdf(stream, MapMetadata(documentMetadata))) { } diff --git a/QuestPDF/Drawing/XpsCanvas.cs b/QuestPDF/Drawing/XpsCanvas.cs index 044f5bf..2c3f65e 100644 --- a/QuestPDF/Drawing/XpsCanvas.cs +++ b/QuestPDF/Drawing/XpsCanvas.cs @@ -7,7 +7,7 @@ namespace QuestPDF.Drawing internal class XpsCanvas : SkiaDocumentCanvasBase { public XpsCanvas(Stream stream, DocumentMetadata documentMetadata) - : base(SKDocument.CreateXps(new WriteStreamWrapper(stream), documentMetadata.RasterDpi)) + : base(SKDocument.CreateXps(stream, documentMetadata.RasterDpi)) { } diff --git a/QuestPDF/Helpers/WriteOnlyStream.cs b/QuestPDF/Helpers/WriteOnlyStream.cs new file mode 100644 index 0000000..e0c4343 --- /dev/null +++ b/QuestPDF/Helpers/WriteOnlyStream.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; + +namespace QuestPDF.Helpers +{ + /// + /// SkiaSharp calls the Position property when generating target document file. + /// If the output stream does not support the Position property, the NullReferenceException is thrown. + /// This wrapper fixes this issue by providing cached Position value (always the end of the stream / current length). + /// Example stream affected: HttpContext.Response.Body + /// + internal class WriteOnlyStream : Stream + { + private readonly Stream InnerStream; + private long StreamLength { get; set; } + + public WriteOnlyStream(Stream stream) + { + if (!stream.CanWrite) + throw new NotSupportedException("Stream cannot be written"); + + InnerStream = stream; + } + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override long Length => StreamLength; + + public override long Position + { + get => StreamLength; + set => throw new NotImplementedException(); + } + + public override void Flush() => InnerStream.Flush(); + + 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 NotImplementedException(); + + public override void Write(byte[] buffer, int offset, int count) + { + InnerStream.Write(buffer, offset, count); + StreamLength += count; + } + } +} diff --git a/QuestPDF/Helpers/WriteStreamWrapper.cs b/QuestPDF/Helpers/WriteStreamWrapper.cs deleted file mode 100644 index e4ce5c9..0000000 --- a/QuestPDF/Helpers/WriteStreamWrapper.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.IO; - -namespace QuestPDF.Helpers -{ - internal class WriteStreamWrapper : Stream - { - private readonly Stream _innerStream; - - private long _length; - - public WriteStreamWrapper(Stream stream) - { - if (!stream.CanWrite) - { - throw new NotSupportedException("Stream cannot be written"); - } - - _innerStream = stream; - } - - public override bool CanRead => false; - - public override bool CanSeek => false; - - public override bool CanWrite => true; - - public override long Length => _length; - - public override long Position { - get => _length; - set => throw new NotImplementedException(); - } - - public override void Flush() - => _innerStream.Flush(); - - 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 NotImplementedException(); - - public override void Write(byte[] buffer, int offset, int count) - { - _innerStream.Write(buffer, offset, count); - _length += count; - } - } -}