Search code examples
.netstreamitextfilestreammemorystream

Stream is not filled when using a MemoryStream for PdfWriter output from iTextSharp


I have the following issue.

This code works fine

private void ModifyAndSavePDF(Stream sourceFile, string text)
{
    using (var pdfWriterStream = new FileStream(@"d:/temp/test.pdf", FileMode.Create))
    {
        var reader = new PdfReader(sourceFile);
        var document = new Document(reader.GetPageSizeWithRotation(1));
        var writer = PdfWriter.GetInstance(document, pdfWriterStream);
        document.Open();
        for (var i = 1; i <= reader.NumberOfPages; i++)
        {
            document.NewPage();
            var importedPage = writer.GetImportedPage(reader, i);
            var contentByte = writer.DirectContent;
            //some more PDF editing stuff here. Not relevant.
        }
        document.Close();
        writer.Close();
        reader.Close();
    }
}

This works fine. The sourceFile stream contains around 200Kb and the saved pdf looks exactly as I expect. One thing I note is that before document.Close() the length of the pdfWriterStream stream is only around 800 bytes.

My problem is that I don't want to save this to a file on the disk, I would like to have the output as a MemoryStream instead. That however I can't get to work as I expect. My first attempt was this:

private Stream ModifyAndSavePDF(Stream sourceFile, string text)
{
    using (var pdfWriterStream = new MemoryStream())
    {
        var reader = new PdfReader(sourceFile);
        var document = new Document(reader.GetPageSizeWithRotation(1));
        var writer = PdfWriter.GetInstance(document, pdfWriterStream);
        document.Open();
        for (var i = 1; i <= reader.NumberOfPages; i++)
        {
            document.NewPage();
            var importedPage = writer.GetImportedPage(reader, i);
            var contentByte = writer.DirectContent;
            //some more PDF editing stuff here. Not relevant.
        }
        document.Close();
        writer.Close();
        reader.Close();
    }
    return pdfWriterStream;
}

This won't work of course, since when I call document.Close(), the pdfWriterStreamis also disposed, and I can't read the contents of the stream.

Second attempt:

private Stream ModifyAndSavePDF(Stream sourceFile, string text)
{
    var result = new MemoryStream();
    using (var pdfWriterStream = new MemoryStream())
    {
        var reader = new PdfReader(sourceFile);
        var document = new Document(reader.GetPageSizeWithRotation(1));
        var writer = PdfWriter.GetInstance(document, pdfWriterStream);
        document.Open();
        for (var i = 1; i <= reader.NumberOfPages; i++)
        {
            document.NewPage();
            var importedPage = writer.GetImportedPage(reader, i);
            var contentByte = writer.DirectContent;
            //some more PDF editing stuff here. Not relevant.
        }
        pdfWriterStream.Position = 0;
        pdfWriterStream.CopyTo(result);
        document.Close();
        writer.Close();
        reader.Close();
    }
    return result;
}

This gets me the problem as the first code listed. pdfWriterStream at this point only consist of around 800 bytes, and when copied to result that also only get's these 800 bytes and not the entire file.

So it seems like document.Close() flushes the file to the buffer, and then disposes it. So I think I need to do some operation before I copy the pdfWriterStream to result, but I can't figure out what.


Solution

  • First of all, if you want to return a MemoryStream object from a method, don't put it into a using clause in that very method: When leaving that using block, the stream object is disposed, so the caller of your method gets a closed stream which he won't be happy about.

    Second, if you don't want the stream a PdfWriter writes to to be closed when the associated Document is closed, simply set the PdfWriter attribute CloseStream to false.

    Thus:

    private Stream ModifyAndSavePDF(Stream sourceFile, string text)
    {
        var pdfWriterStream = new MemoryStream();
        var reader = new PdfReader(sourceFile);
        var document = new Document(reader.GetPageSizeWithRotation(1));
        var writer = PdfWriter.GetInstance(document, pdfWriterStream);
        writer.CloseStream = false;
        document.Open();
        for (var i = 1; i <= reader.NumberOfPages; i++)
        {
            document.NewPage();
            var importedPage = writer.GetImportedPage(reader, i);
            var contentByte = writer.DirectContent;
            //some more PDF editing stuff here. Not relevant.
        }
        document.Close();
        reader.Close();
    
        return pdfWriterStream;
    }
    

    As an aside, you do not need to close the PdfWriter, it is implicitly closed when the associated Document is closed.

    And another aside, at first glance your method looks like you copy some original PDF and apply some changes to it. Usually (depending on the exact changes you want to apply, that is) one should do this using a PdfStamper, not a plain PdfWriter.