Files
spring-boot-demo/pdf-invoice/README.md
T

56 lines
3.9 KiB
Markdown

# pdf-invoice
Companion code for **[Generate PDF Invoices in Spring Boot: OpenPDF vs iText vs Flying Saucer (HTML to PDF)](https://ankurm.com/spring-boot-generate-pdf-invoice-openpdf-itext-flying-saucer/)**.
Spring Boot 4.1.2, JDK 25, OpenPDF 3.0.5, iText Core 9.8.0 (AGPL 3 or commercial), Flying Saucer 10.7.0, Thymeleaf 3.1.5, Apache PDFBox 3.0.8 (tests only). No database, no Docker.
One invoice, rendered by three libraries, with Marathi and Hindi text in Noto Sans Devanagari (SIL OFL 1.1) embedded:
| Engine name | Code | What it is |
|---|---|---|
| `openpdf` | `OpenPdfInvoiceWriter` | (a) OpenPDF's programmatic API, defaults |
| `itext` | `ITextInvoiceWriter` | (b) iText Core 9.8.0, programmatic API, defaults |
| `flyingsaucer` | `FlyingSaucerInvoiceWriter`, `templates/invoice.html` | (c) Thymeleaf template to XHTML to Flying Saucer, defaults |
| `openpdf-jdk`, `openpdf-jdk-actualtext` | `OpenPdfInvoiceWriter` with `Shaping.JDK` | OpenPDF's `GlyphLayoutManager`: text laid out by the JDK's own shaper |
| `openpdf-fop` | `OpenPdfInvoiceWriter` with `Shaping.FOP` | OpenPDF's optional Apache FOP glyph engine (only with `mvn -Pfop`) |
| `flyingsaucer-shaped` | `FlyingSaucerInvoiceWriter` with `shaped=true` | Flying Saucer with OpenPDF's static `LayoutProcessor` switched on |
`InvoiceController` serves `GET /invoices/{engine}.pdf` with `StreamingResponseBody` (default) or a buffered `byte[]` (`?mode=buffered`), a Marathi
`Content-Disposition` file name, `?probe=true` for an invoice that is only a shaping test string, and `?failAtEnd=true` to see what a failure after rendering looks like.
`Bench` (profile `bench`) times N invoices per engine in one JVM.
```bash
export JAVA_HOME=/path/to/jdk-25
mvn -DskipTests package
java -jar target/pdf-invoice-1.0.0.jar # then: curl -o invoice.pdf localhost:8080/invoices/openpdf.pdf
./scripts/run-all.sh # tests + every capture below; rewrites output/ (about 4 minutes)
```
`scripts/run-all.sh` needs poppler (`pdftoppm`, `pdftotext`, `pdffonts`, `pdfinfo`) and Python with Pillow built with libraqm (for the reference line in the comparison image).
Everything in `output/` was produced by that script; the numbers in `06-bench.txt` are indicative (shared 2-vCPU sandbox, one run per line).
## Captured output
| File | What it holds |
|---|---|
| `output/00-versions-and-licences.txt` | licence declarations read from the POMs, sentences from iText's AGPL page, latest GA versions |
| `output/01-text-extraction.txt` | PDFBox text extraction of each engine's invoice (`PdfContentTest`) |
| `output/02-endpoints.txt` | status and headers for every engine, streamed vs buffered, a failure after rendering |
| `output/03-pdffonts-and-producer.txt` | `pdffonts` per engine and the Producer line each library writes |
| `output/04-glyph-counts.txt` | glyph codes written into the page, per engine (`GlyphCountTest`) |
| `output/05-layoutprocessor-trap.txt` | what the static `LayoutProcessor` does to other OpenPDF writers in the same JVM |
| `output/06-bench.txt` | time per invoice, PDF size, subset vs whole font |
| `output/png/shaping-comparison.png` | the probe string drawn by each engine under a Pillow/libraqm reference line |
| `output/png/invoice-*.png` | the first 800 pixel rows of each engine's invoice at 110 dpi |
| `output/pdf/*.pdf` | the PDFs themselves (`probe-*.pdf` are the shaping probe invoices) |
## Not covered here
iText's pdfCalligraph add-on (proprietary licence, needs a licence key, so it was not run), PDF/A and PDF/UA conformance, digital signatures, multi-page invoices and page headers/footers,
and Microsoft Word / Adobe Acrobat / mobile viewers (rendering was checked with poppler's `pdftoppm` only).
## Fonts
`src/main/resources/fonts/` holds static Regular and Bold instances of Noto Sans Devanagari, cut from the variable font with `scripts/make_fonts.py`
(source and licence in the script's header and in `OFL.txt`).