Generowanie prostych dokumentów pdf w Javie

Rozpocznę dzisiejszy wpis od przypomnienia jednego z moich pierwszych mini-projektów w JavaScript/TypeScript: stories.
Jego zadaniem było wygenerowanie statycznej strony HTML zawierającej małe “książeczki z obrazkami”. Każda z książeczek powstaje na podstwie metadanych zawatych w w pliku TOML , którego przykład znajdziesz w podlinkowanym niżej wpisie.

Nowy format
Pomyślałam sobie - przy okazji niedawnej zabawy bibilioteką itext - że mogłabym generować bajki nie tylko w formacie HTML, lecz również jako pliki .pdf. Dotychczas przygotowywałam pdf-y ręcznie, a teraz mogę zaoszczędzić czas
Usiadłam więc i zaczęłam kodować.
Dwa etapy
Pisanie kodu podzieliłam na dwa etapy:
- Parsowanie pliku TOML i budowanie modelu
- Renderowanie pliku .pdf na podstawie modelu
Parsowanie i budowa modelu
Model
Szumnie brzmiący model do dwie klasy:
-
BookPage
reprezentująca stronę; każda strona posiada numer, ścieżkę do obrazka i tekst -
Book
reprezentuje książeczkę: będzie posiadać metadane oraz listę stronObydwie klasy przywołam tu w całości:
|
|
|
|
Biblioteka parsująca TOML
Do parsowania formatu TOML w javie użyłam biblioteki toml4j:
|
|
Główna metoda parsująca
Używam jej w klasie MyTomlParser
: jeśli wszystko pójdzie dobrze, metoda Optional<Book> parse(FileReader fileReader)
zwróci niepusty Optional zawierający Book
:
|
|
przy czym w konstruktorze rekordu Book
wywołana jest metoda parsująca strony.
Parsowanie pojedynczych stron
Mój plik TOML ma taką strukturę, że każda strona książeczki zawiera złożoną strukturę z danymi strony. W API biblioteki toml4j nie znalazłam czystego sposobu budowania złożonych struktur danych, więc użyłam tego, co widać poniżej: iteruję po liście obiektów-map, z których to map dopiero żmudnie buduję obiekt BookPage
(rzutując numer strony na Long
, ścieżkę na String
i obcinając białe znaki z początku i końca tekstu):
|
|
Generowanie pdf
Do generowania .pdf używam klasy o oczywistej nazwie Generator
, której główna metoda generate
zawiera kod generujący:
- stronę tytułową (
createTitlePage
) - a w pętli kolejne strony książeczki (
generateNextPage
)
Książeczki mają pionową orientację i format PAGE_SIZE=PageSize.A5
:
Metoda główna
|
|
API niskopoziomowe
Choć PdfDocument posiada bogate API, jest ono jednak dość niskopoziomowe i dotyczy np.
- dodawania metadanych
- dołączania załączników
- podpisywania dokumentów
- generowania zdarzeń dodania/usunięcia strony
- dostępu do readera i writera
- tworzenia spisu treści
Nic z powyższych nie będzie mi potrzebne, dlatego używam klasy opakowującej (będącej jednocześnie fasadą, za którą schowana jest cała złożoność biblioteki itext): Document
.
API wysokopoziomowe
Obiekt Document
pozwala:
- dodać znacznik końca strony (tj. wstawić nową stronę)
- dodać obraz bądź element blokowy
- ustawić wielkość marginesów
- dodać własny renderer
Elementy blokowe (czyli implementujące interfejs IBlockElement
) to
- akapit (
Paragraph
) - tabela (
Table
) - linia (
LineSeparator
) - div (
Div
) - lista (
List
) - komórka (
Cell
)
Strona tytułowa
Mój pdf jest bardzo prosty i potrzebuję jedynie dodawania obrazków i akapitów z tekstem.
Na przykład, kod generujący stronę tytułową wykorzystuje klasy z biblioteki itext:
Paragraph
który jest kontenerem zawierającym inne elementyText
pozwalający ustawić rozmiar tekstu, jego kolor oraz czcionkęImage
reprezentujacy zasób graficzny orazImageDataFactory
pozwalający na utworzenie zasobu obrazu z podanej ścieżki
|
|
gdzie getImage
tworzy obiekt typu Image
o szerokości równej efektywnej szerokości strony:
|
|
natomiast addFooter
tworzy stopkę z nazwą serii i opcjonalnym nuerem strony; w tej metodzie używam klas z itext:
Table
orazCell
które są odpowiedzialne za magię równego renderowania elementów, dla których można ustawić wyrównanie, kolor tła itd. Tabela jest kolekcją wierszy i kolumn zawierających komórki; te z kolei są rysowane domyślnie z czarną krawędzią, której nie chcę, więc dość nieeleganko się jej pozbywam:
|
|
Rezultat
Alternatywa
Myślę, że warto by było użyć do generowania .pdf biblioteki pdfjs - dzięki temu czytelinicy “książeczek” mieliby możliwość pobrania .pdf-a bezpośrednio z głównej strony.
Kilka słów o itext7
Biblioteka itext jest dość mocno skomercjalizowana: jej strona internetowa promuje zamknięte i płatne narzędzia oraz rozszerzenia (np. ditto - “data-driven, template-based pdf generator”).
Na licencji AGPL wystawione są poszczególne biblioteki niskopoziomowe:
- kernel-x.y.z.jar: low-level functionality
- io-x.y.z.jar: low-level functionality
- layout-x.y.z.jar: high-level functionality
- forms-x.y.z.jar: AcroForms
- pdfa-x.y.z.jar: PDF/A-specific functionality
- pdftest-x.y.z.jar: test helper classes
- barcode-x.y.z.jar: use this if you want to create bar codes
- hyph-x.y.z.jar: use this if you want text to be hyphenated
- font-asian-x.y.z.jar: use this is you need CJK functionality (Chinese / Japanese / Korean)
- sign-x.y.z.jar: use this if you need support for digital signatures
- styled-xml-parser-x.y.z.jar: use this if you need support for SVG or html2pdf
- svg-x.y.z.jar: SVG support
- commons-x.y.z.jar: commons module
Dokmentacja do itext to przede wszystkim:
- przykłady i e-booki - luźna lista artykułów (dość niewygodna do przeglądania)
- kod na GitHub
Podsumowanie
Generowanie prostych dokumentów jest proste. Przykłady na stronie internetowej wystarczą, abby zacząć. Nic złożonego nie generowałam, nie mam więc wystarczającego doświadczenia, aby ocenić, jak bardzo biblioteka itext pomaga/przeszkadza w złożonych scenariuszach (skomplikowany układ, formularze, hasła etc.)
Przy pomocy dobrego IDE, po podpięciu źródeł (a w tym niezłej dokumentacji w kodzie) można dość szybko zorientować się w odpowiedzialności poszczególnych klas i modułów (kod itext7 wydaje mi się solidnie napisany, abstrakcje są ładnie wyodrębnione w postaci interfesów i opisane w JavaDoc).
W świecie Javy jest to chyba najbardziej popularna (i przyjazna programiście) biblioteka do generowania plików .pdf. Możliwe, że w przyszłości jeszcze nie raz po nią sięgnę.
GitHub
Projekt bajki
dostępny jest na GitHubie.