Создайте большой PDF-файл из огромного количества данных

Я читаю данные из базы данных, из которой я генерирую HTML DOM. Объем данных огромен, поэтому он не может сразу уместиться в памяти, однако его можно предоставлять по частям.

Я хочу преобразовать полученный HTML в PDF с помощью Flying Saucer:

import org.xhtmlrenderer.pdf.ITextRenderer;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.io.DOMWriter;

OutputStream bodyStream = outputMessage.getBody();

ITextRenderer renderer = new ITextRenderer();

DocumentFactory documentFactory = DocumentFactory.getInstance();
DOMWriter domWriter = new DOMWriter();

Element htmlNode = documentFactory.createElement("html");
Document htmlDocument = documentFactory.createDocument(htmlNode);

int currentLine = 1;
int currentPage = 1;

try {
    while (currentLine <= numberOfLines) {
        currentLine += loadDataToDOM(documentFactory, htmlNode, currentLine, CHUNK_SIZE);

        renderer.setDocument(domWriter.write(htmlDocument), null);
        renderer.layout();

        if (currentPage == 1) {
            // For the first page the PDF writer is created:
            renderer.createPDF(bodyStream, false);
        }
        else {
            // Other documents are appended to current PDF writer:
            renderer.writeNextDocument(currentPage);
        }

        currentPage += renderer.getRootBox().getLayer().getPages().size();
    }

    // Finalise the PDF:
    renderer.finishPDF();
}
catch (DocumentException e) {
    throw new IOException(e);
}
catch (org.dom4j.DocumentException e) {
    throw new IOException(e);
}
finally {
    IOUtils.closeQuietly(bodyStream);
}

Проблема с этим подходом заключается в том, что последняя страница чанка не обязательно полностью заполнена данными. Есть ли решение заполнить пространство? Например, я мог бы подумать о подходе, который будет проверять, что последняя страница не заполнена полностью, а затем отбрасывать ее (не записывать в PDF), а также узнавать, какие данные были отображены на этой странице, и перематывать позицию в базе данных (в примере currentLine) . Было бы неплохо, если бы кто-то мог опубликовать полное решение.


person dma_k    schedule 25.06.2014    source источник
comment
Плохая идея. Сначала вы создаете HTML-код, который занимает много места, а затем используете этот HTML-код для создания PDF-файла. Если память имеет значение, вы должны создать PDF прямо из данных без предварительного создания HTML.   -  person Bruno Lowagie    schedule 25.06.2014
comment
Да, но сколько кода мне потребуется написать для рендеринга HTML с использованием низкоуровневых примитивов iText (moveTo(), lineTo(), beginText())? Теперь у меня 50 строк кода, которыми легко управлять. HTML и CSS знакомы всем. Изменение макета или цветов не проблема. Бруно, я бегло просмотрел вашу книгу iText в действии (большое спасибо за нее!), и уже пугает магия верхних и нижних колонтитулов на странице 430 (глава 14). Я бы с удовольствием использовал com.itextpdf.tool.xml.pipeline.html.HtmlPipeline, но он не поддерживает базовые селекторы CSS, не говоря уже о плавающих блоках.   -  person dma_k    schedule 26.06.2014
comment
Зачем вам использовать низкоуровневые примитивы? Я дам вам несколько указателей на простые примеры в ответе.   -  person Bruno Lowagie    schedule 26.06.2014


Ответы (2)


Как я уже упоминал в комментариях, вы тратите память и время обработки, создавая PDF из источника данных, сначала создавая HTML, а затем конвертируя HTML в PDF. Вы также вводите много ненужной сложности.

В своем комментарии вы упоминаете низкоуровневую функциональность, такую ​​как moveTo() и lineTo(). Действительно, было бы безумием рисовать таблицу с помощью низкоуровневых операций, рисующих каждую строку и каждое слово.

Вы должны использовать класс PdfPTable. Пример ArrayToTable представляет собой очень простой POC, в котором данные поступают в виде List<List<String>>. Код такой же простой:

PdfPTable table = new PdfPTable(8);
table.setWidthPercentage(100);
List<List<String>> dataset = getData();
for (List<String> record : dataset) {
    for (String field : record) {
        table.addCell(field);
    }
}
document.add(table);

Конечно: вы говорите об огромном наборе данных, и в этом случае вы можете не захотеть сначала создавать table в памяти, а затем очищать память при добавлении таблицы в документ. Вы захотите добавить небольшие части стола, пока будете его строить. Именно это происходит в примере MemoryTests. Добавьте эту строку:

table.setComplete(false);

А можно добавлять таблицу понемногу (в примере: каждые 10 строк). Когда вы закончили добавлять ячейки в таблицу, вы должны сделать это:

table.setComplete(true);
document.add(table);

Это добавит последние ряды.

Если вам нужна таблица с повторяющимся верхним и/или нижним колонтитулом, взгляните на таблицы в этом PDF-файле: header_footer_1.pdf

HeaderFooter1 и HeaderFooter2 примеры покажут вам, как это делается.

person Bruno Lowagie    schedule 26.06.2014
comment
Спасибо за подробный ответ, буду признателен. В принципе, я представил данные в виде таблицы (вот пример, в котором границы, а вот не черновая версия) . Каждая ячейка, в свою очередь, может содержать другие текстовые поля с фоном. Если я правильно понимаю, мне нужно представить каждую часть с com.itextpdf.text.Chunk объектом, а затем объединить их в com.itextpdf.text.Phrase? - person dma_k; 30.06.2014
comment
Цветные фоны для произвольных фрагментов текста — это действительно то, чего вы можете добиться либо с помощью «Chunk.setBackground()», либо с помощью функции общий тег (например, если фон не является прямоугольником). Глядя на желаемый результат, я бы не стал использовать PdfPTable. Вместо этого я бы использовал объект ColumnText и Chunk.TABBING для вкладок, разделяющих <xyz> числа и фактические данные. - person Bruno Lowagie; 30.06.2014

Это не ответ на точный вопрос, который вы задали, поэтому, если этот пост бесполезен, я его удалю.

Поскольку документ огромен, вы вполне можете получить наилучшие результаты, отправив данные в виде LaTeX, а затем пропустив их через pdflatex.

Преимущества:

  • Исходный код LaTeX, который вам нужен, легко создать - не сложнее, чем HTML.
  • Вся система TeX предназначена для создания красивых и огромных документов. LaTeX обрабатывается как поток страниц. Количество страниц практически не влияет на требуемые ресурсы оперативной памяти.
  • Вы получаете всю мощь языка набора текста, чтобы ваши страницы выглядели великолепно. Хотите модные заголовки? Красиво расположенные номера страниц? Заголовки разделов? Кликабельное оглавление и т.д. и т.п. Нет проблем.
  • LaTeX доступен бесплатно для всех основных операционных систем.

Недостатки:

  • LaTeX — это собственный исполняемый файл, а не библиотека Java.

Если вас это интересует, могу уточнить детали.

person Gene    schedule 03.07.2014
comment
Я знаю о LaTeX. Есть еще два недостатка: (1) Время обработки. Вызов внешней утилиты требует больших затрат времени. Более того, LaTeX имеет большую экосистему, которая требует времени для загрузки. (2) Добавление в проект еще одной технологии усложняет его обслуживание. HTML более-менее знаком всем. Но такие инструкции, как \rfoot{Page \thepage}, требуют некоторых усилий для изучения. Я предполагаю, что \textbf{\thepage} будет нормально работать внутри определения верхнего/нижнего колонтитула, но более экзотические стили, такие как создание цветного блока, уже выходят за рамки моего понимания того, что просто. - person dma_k; 26.08.2014