Разработчикам сайтов и мобильных приложений часто нужно управлять подготовкой PDF-страниц к выводу на печать или их отправкой клиентам на почту.
У PDF-файлов есть полный контроль над отображением текста и графическими изображениями на странице. К сожалению, библиотеки для генерации динамически заполняющихся PDF-файлов не входят в стандартный инструментарий PHP, JS (Web), Java или Swift (Android и iOS соответственно). В этой статье хочу вам рассказать об опенсорс-решении для генерации PDF-файлов.

JasperReports — это открытая Java-библиотека для генерации динамически заполняющихся файлов. В ней есть много инструментов для создания сложных отчетных форм, в том числе в формате PDF, но также доступны и другие форматы: RTF, DOCX, HTML, XLS, XLS, CSV и XML. Иными словами, достаточно разработать одну форму, сделать одну верстку — и можно будет экспортировать его в любой из вышеперечисленных форматов.
Есть еще хорошие библиотеки, например PDFLib (коммерческая версия) для PHP и ее версия с открытыми исходными кодами PDFLib-Lite. Правда, библиотека довольно дорогая, а облегченная версия распространяется только в исходных кодах, и при ее установке в среде разработки это ограничение может стать проблемой.
PDFbox — еще одна библиотека Java с открытым исходным кодом для работы с документами PDF. Она позволяет создавать новые PDF-документы, управлять существующими документами с возможностью извлекать контент из них. Но у нее нет UI (User Interface), в отличие от JasperReports.
Думаю, JasperReports особо полезен в больших проектах, связанных с отчетностью и не только в PDF-формате. Здесь есть все необходимое, чтобы внедрить ее в ваш проект: простое создание сложных отчетных форм, UI для удобной верстки, серверное приложение и простая интеграция с фронтом.
В статье я раскрою следующие темы:
Чтобы начать использовать JasperReports в вашем проекте, необходимо скачать два приложения: JaspersoftStudio — далее будем называть его рабочей средой — и JasperServer — будем называть серверным приложением.
JaspersoftStudio — среда разработки на основе Eclipse со встроенной Java-библиотекой JasperReports, где разрабатываются динамические или статичные PDF-файлы: например, билеты, квитанции, договора, аналитические чарты и другие.
JasperServer — это серверное приложение, куда из JaspersoftStudio деплоятся и хранятся файлы. К ним можно получить доступ из мобильного или веб-приложения. В JasperServer есть UI, с его помощью можно просматривать отчеты, создавать учетные записи для разных пользователей и давать им соответствующие доступы. Также можно настроить рассылку на электронную почту (Scheduler).
Скачать и установить приложения можно по ссылкам выше. После сетапа двух приложений необходимо установить соединение из рабочей среды к серверному приложению.
Servers → Create JasperReports Server Connection → Указать предпочитаемое имя сервера, URL, логин и пароль. Нажмите Test Connection, чтобы проверить, что соединение с сервером установлено. Если видите надпись Successful — едем дальше.

Мы установили среду разработки, сервер и наладили соединение между ними. Теперь давайте создадим примитивный динамически заполняющийся PDF-файл, который при его запуске (генерации) будет брать данные из PostgreSQL, и задеплоим его на серверное приложение.
В первую очередь следует прикрутить к рабочей среде источник данных (в нашем случае — PostgreSQL), откуда PDF будет брать данные. Затем приступим к разработке нашего первого PDF-файла.
Data Adapters → Create Data Adapter → Database JDBS Connection и указываем данные соединения:

Нажимаем на уже знакомую нам кнопку Test и при успешном соединении (Successful) с БД — Finish.
Из этой БД PDF будет заполняться в рабочей среде. Так как мы планируем задеплоить наш первый PDF-файл еще и на сервер, то давайте прикрутим тот же источник данных к серверному приложению:
Data Sources → Add Resource → Data Source и повторяем все из пункта выше.
Теперь можно приступать к созданию PDF. Исходники в JasperReports хранятся в формате JRXML — это XML с зашитыми тегами и атрибутами, с которыми работает API JasperReports.
Нажимаем File → New → Jasper Report → Blank A4 → Указываем имя JRXML-файла → Finish.

После создания нового проекта вы увидите следующую картину:

Семь разных блоков — для каждого блока прописано свое поведение, отличающееся от других. Более подробно об этом можно почитать в документации. Нажав на Source можно увидеть структуру этих блоков:
Итак, давайте удалим пять лишних блоков и оставим только два: Title и Detail. В этом нам поможет кнопка Delete (Windows) или Backspace (OS X).
Теперь добавим два элемента. Добавить новый элемент можно двумя способами: прописать контейнер в XML-структуре (кнопка Source) либо перетащить нужный элемент из правого верхнего окна Pallette — Static Text, где будут названия полей и Text Field, внутрь которого зашьем переменные поля, вытащенные из БД:
Запрос в БД можно написать в теге queryString либо нажать на кнопку DataSet and Query editor dialog. После этого откроется новое окно, где необходимо выбрать источник данных (1), написать запрос (2) и объявить переменные поля. Кнопка Read Fields (3) прочитает все поля автоматически при валидном запросе. Чтобы просмотреть данные, нужно нажать на Data preview (4).

Отлично! Мы получили четыре поля с типом String, теперь можем совершать с ними практически любые манипуляции. Для примера мы просто выведем их списком и пропишем небольшую логику.
Напечатаем названия нужных полей в Static Text элементах и поместим их в контейнер Title. Переменные поля укажем в Text Field элементах в контейнере Detail, так как они будут множиться. Наш PDF будет выводить имя, город и адрес электронной почты. Чтобы не было совсем скучно, давайте в Text Field элементе пропишем простую логику, используя четвертое поле — пол клиента, Sex.
Сделаем следующее: если клиент — женщина, то перед именем будем добавлять Mrs., если мужчина — Mr. Для этого используем тернарный оператор Java:
Нажав на Preview рядом с кнопкой Source, можно увидеть результат:

Как видно на скриншоте, PDF успешно собрался: забрал все значения и применил логику, правильно проставив Mrs. и Mr.
Еще заведем входной параметр City, чтобы была возможность фильтровать данные по городам. Это можно сделать либо нажав Parameters → Create Parameter в окне Outline, либо добавив новый тег parameter с атрибутами name и class:
Осталось только добавить параметр в SQL-запрос:
Передадим в параметр City значение San Francisco (о том, как это сделать, расскажу в следующем пункте) и нажмем Data Preview для просмотра результата.

PDF собрался, успешно отфильтровав данные. Едем дальше
Так как у нас уже есть динамически заполняющийся PDF-файл, можем задеплоить его на сервер для дальнейшей интеграции с нашими фронтенд-приложениями. Для этого нажимаем кнопку Publish Report to JasperReports Server → двойным кликом открываем сервер → Выбираем серверную папку, куда нужно загрузить PDF (в нашем случае — reports) → Next → Data Source from Repository → выбираем источник данных, который создали ранее на серверном приложении → Finish.
В JasperReports API входит своя RESTful-реализация для взаимодействия клиента с сервером — REST v2. Если вам она не подходит, можете использовать простой протокол доступа к объектам — SOAP.
Мы рассмотрим REST v2.
Доступны основные четыре метода для действий CRUD (Create-Read-Update-Delete): GET (получить), POST (добавить, изменить, удалить), PUT (добавить, заменить), DELETE (удалить). Вся подробная информация имеется в документации по ссылкам выше.
Мы рассмотрим более распространенный и актуальный для этой статьи метод GET.
Выше — синхронный запрос, с помощью которого можно получить выходные данные файла (готовый PDF) в одном запросе-ответе (с асинхронным вызовом можно ознакомиться здесь).
Думаю, с хостом и портом все понятно, а /reports/path/to/report — это URI того файла, который вызывается. Так как мы задеплоили исходник PDF-файла (Example.jrxml) в серверную папку reports, заполненный вариант URI будет таким: /reports/reports/Example.
format — это формат (в нашем случае PDF).
arguments — параметры.
Выше мы добавили параметр City, его и передадим в запросе со значением San Francisco для фильтрации данных по этому городу.
Если вызов делается не с авторизованной зоны, нужно добавить еще два параметра/атрибута: j_username и j_password (логпасс для авторизации). По умолчанию логин и пароль на сервере — jasperadmin.
Таким образом, получаем следующий URL:
Так мы получим уже сформированный PDF. Например, при вызове этого URL через адресную строку браузера файл должен скачаться автоматически.
Может возникнуть необходимость от��бразить PDF картинкой. Например, если клиент хочет просто просмотреть файл, можно показать документ в PNG-формате, если же хочет скачать его, то в PDF.
На примере Java с использованием библиотеки PDFbox рассмотрим, как можно из внешнего приложения сгенерировать и забрать PDF-файл, а затем сконвертировать его в PNG.
Ниже — класс PullPDF с одним методом, принимающим в качестве аргумента URL-адрес.
Можно получить тот же результат, используя, например, Spring Framework. Но я постарался показать универсальный способ, который можно применить и в Android, и в вебе при работе с Java.
Если вы хотите автоматизировать генерацию простого чека для интернет-магазина и у вас ограничено время на его создание, то я рекомендую использовать нативный инструментарий вашего проекта или знакомый вам фреймворк. Поскольку затраты времени на установку JasperReports, ознакомление с документацией для разработки более комплексных отчетных форм, могут не оправдать себя. В остальных случаях JasperReports — хорошее опенсорс-решение для автоматизации отчетности.
У PDF-файлов есть полный контроль над отображением текста и графическими изображениями на странице. К сожалению, библиотеки для генерации динамически заполняющихся PDF-файлов не входят в стандартный инструментарий PHP, JS (Web), Java или Swift (Android и iOS соответственно). В этой статье хочу вам рассказать об опенсорс-решении для генерации PDF-файлов.

JasperReports — это открытая Java-библиотека для генерации динамически заполняющихся файлов. В ней есть много инструментов для создания сложных отчетных форм, в том числе в формате PDF, но также доступны и другие форматы: RTF, DOCX, HTML, XLS, XLS, CSV и XML. Иными словами, достаточно разработать одну форму, сделать одну верстку — и можно будет экспортировать его в любой из вышеперечисленных форматов.
Есть еще хорошие библиотеки, например PDFLib (коммерческая версия) для PHP и ее версия с открытыми исходными кодами PDFLib-Lite. Правда, библиотека довольно дорогая, а облегченная версия распространяется только в исходных кодах, и при ее установке в среде разработки это ограничение может стать проблемой.
PDFbox — еще одна библиотека Java с открытым исходным кодом для работы с документами PDF. Она позволяет создавать новые PDF-документы, управлять существующими документами с возможностью извлекать контент из них. Но у нее нет UI (User Interface), в отличие от JasperReports.
Думаю, JasperReports особо полезен в больших проектах, связанных с отчетностью и не только в PDF-формате. Здесь есть все необходимое, чтобы внедрить ее в ваш проект: простое создание сложных отчетных форм, UI для удобной верстки, серверное приложение и простая интеграция с фронтом.
В статье я раскрою следующие темы:
- Установка среды разработки и серверного приложения.
- Создание PDF-файла автоматически заполняющегося из базы данных.
- Интеграция серверного приложения с фронтендом для получения созданного PDF.
Чтобы начать использовать JasperReports в вашем проекте, необходимо скачать два приложения: JaspersoftStudio — далее будем называть его рабочей средой — и JasperServer — будем называть серверным приложением.
JaspersoftStudio — среда разработки на основе Eclipse со встроенной Java-библиотекой JasperReports, где разрабатываются динамические или статичные PDF-файлы: например, билеты, квитанции, договора, аналитические чарты и другие.
JasperServer — это серверное приложение, куда из JaspersoftStudio деплоятся и хранятся файлы. К ним можно получить доступ из мобильного или веб-приложения. В JasperServer есть UI, с его помощью можно просматривать отчеты, создавать учетные записи для разных пользователей и давать им соответствующие доступы. Также можно настроить рассылку на электронную почту (Scheduler).
Настраиваем рабочую среду и серверное приложение
Скачать и установить приложения можно по ссылкам выше. После сетапа двух приложений необходимо установить соединение из рабочей среды к серверному приложению.
Servers → Create JasperReports Server Connection → Указать предпочитаемое имя сервера, URL, логин и пароль. Нажмите Test Connection, чтобы проверить, что соединение с сервером установлено. Если видите надпись Successful — едем дальше.

Создаем динамически заполняющийся PDF и публикуем его
Мы установили среду разработки, сервер и наладили соединение между ними. Теперь давайте создадим примитивный динамически заполняющийся PDF-файл, который при его запуске (генерации) будет брать данные из PostgreSQL, и задеплоим его на серверное приложение.
В первую очередь следует прикрутить к рабочей среде источник данных (в нашем случае — PostgreSQL), откуда PDF будет брать данные. Затем приступим к разработке нашего первого PDF-файла.
Data Adapters → Create Data Adapter → Database JDBS Connection и указываем данные соединения:
- JDBC Driver — PostgreSQL (org.postgresql.Driver). Если нет драйвера вашего СУБД, можно установить нужный драйвер во вкладке Driver Classpath.
- JDBC URL — он состоит их хоста, порта и названия БД.
- Username и password — логпасс от вашей учетной записи СУБД.

Нажимаем на уже знакомую нам кнопку Test и при успешном соединении (Successful) с БД — Finish.
Из этой БД PDF будет заполняться в рабочей среде. Так как мы планируем задеплоить наш первый PDF-файл еще и на сервер, то давайте прикрутим тот же источник данных к серверному приложению:
Data Sources → Add Resource → Data Source и повторяем все из пункта выше.
Теперь можно приступать к созданию PDF. Исходники в JasperReports хранятся в формате JRXML — это XML с зашитыми тегами и атрибутами, с которыми работает API JasperReports.
Нажимаем File → New → Jasper Report → Blank A4 → Указываем имя JRXML-файла → Finish.

После создания нового проекта вы увидите следующую картину:

Семь разных блоков — для каждого блока прописано свое поведение, отличающееся от других. Более подробно об этом можно почитать в документации. Нажав на Source можно увидеть структуру этих блоков:
<?xml version="1.0" encoding="UTF-8"?>
<jasperReport
xmlns="http://jasperreports.sourceforge.net/jasperreports"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="Example" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="ae9517f6-ff0b-41bb-a8dc-82196190e940">
<queryString>
<![CDATA[]]>
</queryString>
<background>
<band splitType="Stretch"/>
</background>
<title>
<band height="79" splitType="Stretch"/>
</title>
<pageHeader>
<band height="35" splitType="Stretch"/>
</pageHeader>
<columnHeader>
<band height="61" splitType="Stretch"/>
</columnHeader>
<detail>
<band height="125" splitType="Stretch"/>
</detail>
<columnFooter>
<band height="45" splitType="Stretch"/>
</columnFooter>
<pageFooter>
<band height="54" splitType="Stretch"/>
</pageFooter>
<summary>
<band height="42" splitType="Stretch"/>
</summary>
</jasperReport>
Итак, давайте удалим пять лишних блоков и оставим только два: Title и Detail. В этом нам поможет кнопка Delete (Windows) или Backspace (OS X).
Теперь добавим два элемента. Добавить новый элемент можно двумя способами: прописать контейнер в XML-структуре (кнопка Source) либо перетащить нужный элемент из правого верхнего окна Pallette — Static Text, где будут названия полей и Text Field, внутрь которого зашьем переменные поля, вытащенные из БД:
<?xml version="1.0" encoding="UTF-8"?>
<jasperReport
xmlns="http://jasperreports.sourceforge.net/jasperreports"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="Example" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="ae9517f6-ff0b-41bb-a8dc-82196190e940">
<queryString>
<![CDATA[]]>
</queryString>
<background>
<band splitType="Stretch"/>
</background>
<title>
<band height="30" splitType="Stretch">
<staticText>
<reportElement x="0" y="0" width="100" height="30" uuid="7b697ed9-f52a-483c-965e-f0b2dc6130c1"/>
<text>
<![CDATA[Static Text]]>
</text>
</staticText>
</band>
</title>
<detail>
<band height="169" splitType="Stretch">
<textField>
<reportElement x="0" y="0" width="100" height="20" uuid="41002e0b-ddb2-4e4b-a049-10810ab51208"/>
<textFieldExpression>
<![CDATA["Text Field"]]>
</textFieldExpression>
</textField>
</band>
</detail>
</jasperReport>Запрос в БД можно написать в теге queryString либо нажать на кнопку DataSet and Query editor dialog. После этого откроется новое окно, где необходимо выбрать источник данных (1), написать запрос (2) и объявить переменные поля. Кнопка Read Fields (3) прочитает все поля автоматически при валидном запросе. Чтобы просмотреть данные, нужно нажать на Data preview (4).

Отлично! Мы получили четыре поля с типом String, теперь можем совершать с ними практически любые манипуляции. Для примера мы просто выведем их списком и пропишем небольшую логику.
Напечатаем названия нужных полей в Static Text элементах и поместим их в контейнер Title. Переменные поля укажем в Text Field элементах в контейнере Detail, так как они будут множиться. Наш PDF будет выводить имя, город и адрес электронной почты. Чтобы не было совсем скучно, давайте в Text Field элементе пропишем простую логику, используя четвертое поле — пол клиента, Sex.
Сделаем следующее: если клиент — женщина, то перед именем будем добавлять Mrs., если мужчина — Mr. Для этого используем тернарный оператор Java:
<textFieldExpression>
<![CDATA[$F{sex}.equals( "male" )?"Mr. "+$F{name}:"Mrs. "+$F{name}]]>
</textFieldExpression> Нажав на Preview рядом с кнопкой Source, можно увидеть результат:

Как видно на скриншоте, PDF успешно собрался: забрал все значения и применил логику, правильно проставив Mrs. и Mr.
Еще заведем входной параметр City, чтобы была возможность фильтровать данные по городам. Это можно сделать либо нажав Parameters → Create Parameter в окне Outline, либо добавив новый тег parameter с атрибутами name и class:
<parameter name="City" class="java.lang.String"/>Осталось только добавить параметр в SQL-запрос:
SELECT
Id, name, sex, city, email
FROM users WHERE city = $P{City}
Передадим в параметр City значение San Francisco (о том, как это сделать, расскажу в следующем пункте) и нажмем Data Preview для просмотра результата.

PDF собрался, успешно отфильтровав данные. Едем дальше
Так как у нас уже есть динамически заполняющийся PDF-файл, можем задеплоить его на сервер для дальнейшей интеграции с нашими фронтенд-приложениями. Для этого нажимаем кнопку Publish Report to JasperReports Server → двойным кликом открываем сервер → Выбираем серверную папку, куда нужно загрузить PDF (в нашем случае — reports) → Next → Data Source from Repository → выбираем источник данных, который создали ранее на серверном приложении → Finish.
Интеграция с фронтендом
В JasperReports API входит своя RESTful-реализация для взаимодействия клиента с сервером — REST v2. Если вам она не подходит, можете использовать простой протокол доступа к объектам — SOAP.
Мы рассмотрим REST v2.
Доступны основные четыре метода для действий CRUD (Create-Read-Update-Delete): GET (получить), POST (добавить, изменить, удалить), PUT (добавить, заменить), DELETE (удалить). Вся подробная информация имеется в документации по ссылкам выше.
Мы рассмотрим более распространенный и актуальный для этой статьи метод GET.
http://<host>:<port>/jasperserver[pro]/rest_v2/reports/path/to/report.<format>?<arguments> Выше — синхронный запрос, с помощью которого можно получить выходные данные файла (готовый PDF) в одном запросе-ответе (с асинхронным вызовом можно ознакомиться здесь).
Думаю, с хостом и портом все понятно, а /reports/path/to/report — это URI того файла, который вызывается. Так как мы задеплоили исходник PDF-файла (Example.jrxml) в серверную папку reports, заполненный вариант URI будет таким: /reports/reports/Example.
format — это формат (в нашем случае PDF).
arguments — параметры.
Выше мы добавили параметр City, его и передадим в запросе со значением San Francisco для фильтрации данных по этому городу.
Если вызов делается не с авторизованной зоны, нужно добавить еще два параметра/атрибута: j_username и j_password (логпасс для авторизации). По умолчанию логин и пароль на сервере — jasperadmin.
Таким образом, получаем следующий URL:
http://localhost:8080/jasperserver/rest_v2/reports/reports/Example.PDF?city=San Francisco&j_username=jasperadmin&j_password=jasperadminТак мы получим уже сформированный PDF. Например, при вызове этого URL через адресную строку браузера файл должен скачаться автоматически.
Может возникнуть необходимость от��бразить PDF картинкой. Например, если клиент хочет просто просмотреть файл, можно показать документ в PNG-формате, если же хочет скачать его, то в PDF.
На примере Java с использованием библиотеки PDFbox рассмотрим, как можно из внешнего приложения сгенерировать и забрать PDF-файл, а затем сконвертировать его в PNG.
Ниже — класс PullPDF с одним методом, принимающим в качестве аргумента URL-адрес.
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Base64;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.tools.imageio.ImageIOUtil;
public class PullPDF {
public String convertPDF2PNG(String valuefromParam) throws IOException {
BufferedInputStream input_file = new BufferedInputStream(new URL(valuefromParam).openStream());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
PDDocument document = PDDocument.load(input_file);
PDFRenderer pdfRenderer = new PDFRenderer(document);
for (int page = 0; page < document.getNumberOfPages(); ++page) {
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
ImageIOUtil.writeImage(bim, "png", baos);
baos.flush();
byte[] encodedBytes = Base64.getEncoder().encode(baos.toByteArray());
valuefromParam = new String(encodedBytes);
}
} catch (Exception e) {
}
return valuefromParam;
}
}Можно получить тот же результат, используя, например, Spring Framework. Но я постарался показать универсальный способ, который можно применить и в Android, и в вебе при работе с Java.
Заключение
Если вы хотите автоматизировать генерацию простого чека для интернет-магазина и у вас ограничено время на его создание, то я рекомендую использовать нативный инструментарий вашего проекта или знакомый вам фреймворк. Поскольку затраты времени на установку JasperReports, ознакомление с документацией для разработки более комплексных отчетных форм, могут не оправдать себя. В остальных случаях JasperReports — хорошее опенсорс-решение для автоматизации отчетности.
