iTextSharp v5 - Как вы объединяете PDF-файлы в памяти? - PullRequest
1 голос
/ 06 марта 2020

У меня проблемы со слиянием PDF-файлов в памяти. У меня есть 2 потока памяти, основной и компонентный поток, идея состоит в том, что при создании каждого компонента PDF байты компонента PDF добавляются в главный поток. В самом конце всех компонентов у нас есть байтовый массив в формате PDF.

У меня есть код ниже, но ничего не копируется в мой masterStream. Я думаю, что проблема связана с CopyPagesTo, но я недостаточно знаком, и документацию / примеры трудно найти.

byte[] updated;
using (MemoryStream masterMemoryStream = new MemoryStream())
{
    masterStream.WriteTo(masterMemoryStream);

    // Read from master stream (ie. all existing components)
    masterMemoryStream.Position = 0;
    using (iText.Kernel.Pdf.PdfWriter masterPdfWriter = new iText.Kernel.Pdf.PdfWriter(masterMemoryStream))
    using (iText.Kernel.Pdf.PdfDocument masterPdfDocument = new iText.Kernel.Pdf.PdfDocument(masterPdfWriter))
    {


        using (MemoryStream componentMemoryStream = new MemoryStream())
        {
            componentStream.WriteTo(componentMemoryStream);

            // Read from new component
            componentMemoryStream.Position = 0;
            using (iText.Kernel.Pdf.PdfReader componentPdfReader = new iText.Kernel.Pdf.PdfReader(componentMemoryStream))
            using (iText.Kernel.Pdf.PdfDocument componentPdfDocument = new iText.Kernel.Pdf.PdfDocument(componentPdfReader))
            {
                // Copy pages from component into master
                componentPdfDocument.CopyPagesTo(1, componentPdfDocument.GetNumberOfPages(), masterPdfDocument);
            }
        }
    }

    updated = masterMemoryStream.GetBuffer();
}

// Write updates to master stream?
masterStream.SetLength(0);
using (MemoryStream temp = new MemoryStream(updated))
    temp.WriteTo(masterStream);

Ответ

Это ответ mkl с некоторыми из мои исправления:

using (MemoryStream temporaryStream = new MemoryStream())
{
    masterStream.Position = 0;
    componentStream.Position = 0;
    using (PdfDocument combinedDocument = new PdfDocument(new PdfReader(masterStream), new PdfWriter(temporaryStream)))
    using (PdfDocument componentDocument = new PdfDocument(new PdfReader(componentStream)))
    {
        componentDocument.CopyPagesTo(1, componentDocument.GetNumberOfPages(), combinedDocument);
    }
    byte[] temporaryBytes = temporaryStream.ToArray();
    masterStream.Position = 0;
    masterStream.SetLength(temporaryBytes.Length);
    masterStream.Capacity = temporaryBytes.Length;
    masterStream.Write(temporaryBytes, 0, temporaryBytes.Length);
}

1 Ответ

2 голосов
/ 06 марта 2020

В вашем коде есть ряд проблем. Сначала я дам вам рабочую версию, а затем go в отношении проблем в вашем коде.

Рабочая версия (с важным ограничением)

Вы можете объединить два файла PDF, указанных в MemoryStream экземпляров masterStream и componentStream и получить результат в том же MemoryStream экземпляре masterStream следующим образом:

using (MemoryStream temporaryStream = new MemoryStream())
{
    masterStream.Position = 0;
    componentStream.Position = 0;
    using (PdfDocument combinedDocument = new PdfDocument(new PdfReader(masterStream), new PdfWriter(temporaryStream)))
    using (PdfDocument componentDocument = new PdfDocument(new PdfReader(componentStream)))
    {
        componentDocument.CopyPagesTo(1, componentDocument.GetNumberOfPages(), combinedDocument);
    }
    byte[] temporaryBytes = temporaryStream.ToArray();
    masterStream.Position = 0;
    masterStream.Capacity = temporaryBytes.Length;
    masterStream.Write(temporaryBytes, 0, temporaryBytes.Length);
    masterStream.Position = 0;
}

Ограничение заключается в том, что вы должны иметь создание экземпляра masterStream с возможностью расширения емкость ; класс MemoryStream имеет ряд конструкторов, только некоторые из которых создают такой расширяемый экземпляр, в то время как другие создают неизменяемые экземпляры. Подробнее см. здесь .

Проблемы в вашей концепции и коде

Объединение файлов PDF не приводит к действительному объединенному PDF

Вы описываете свою концепцию как это

идея состоит в том, что, когда каждый компонент PDF создается, байты компонента PDF добавляются в основной поток

Это не работает, хотя Формат PDF не позволяет объединять PDF-файлы, просто объединяя их. В частности, (активные) объекты в PDF-файле имеют номер идентификатора, который должен быть уникальным в PDF-файле, конкатенация приведет к созданию файла с неуникальными идентификаторами объекта; PDF-файлы содержат структуры перекрестных ссылок, которые сопоставляют каждый идентификатор объекта с его смещением от начала файла, при объединении все эти смещения будут неправильными для добавленных PDF-файлов; кроме того, в PDF должен быть один root объект, из которого прямые или косвенные ссылки на другие объекты, конкатенация приведет к множеству root объектов.

Запись и немедленная перезапись

В вашем коде у вас есть

masterStream.WriteTo(masterMemoryStream);

// Read from master stream (ie. all existing components)
masterMemoryStream.Position = 0;
using (iText.Kernel.Pdf.PdfWriter masterPdfWriter = new iText.Kernel.Pdf.PdfWriter(masterMemoryStream))

Здесь вы пишете содержимое от masterStream до masterMemoryStream, затем устанавливаете позицию masterMemoryStream в начало и создаете экземпляр PdfWriter, который начинает писать там. Т.е. ваша оригинальная копия masterStream содержимого будет перезаписана, конечно, не то, что вы хотели.

Использование MemoryStream.GetBuffer

MemoryStream.GetBuffer не только возвращает данные, записанные в MemoryStream по конструкции, но весь буфер; т. е. может быть много tra sh байтов после фактического PDF в том, что вы извлекаете здесь

updated = masterMemoryStream.GetBuffer();

Это может привести к тому, что процессоры PDF, пытающиеся обработать полученные PDF-файлы, не смогут открыть файл: PDFs у них указатель на последние перекрестные ссылки в их конце, поэтому, если у вас есть tra sh байт после фактического конца вашего PDF, процессоры PDF могут не найти этот указатель.

PS

Как выяснилось в комментариях, приведенный выше код отлично работает в случае постоянно растущей длины потока (что обычно происходит в случае использования под рукой), но в целом нужно ограничить размер потока перед записью нового контента, например, так :

...
masterStream.Position = 0;
masterStream.SetLength(temporaryBytes.Length); // <<<<
masterStream.Capacity = temporaryBytes.Length;
...
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...