Add pdf-invoice module: one invoice in OpenPDF 3.0.5, iText Core 9.8.0 and Flying Saucer 10.7.0, Devanagari shaping checked by rendering, streamed vs buffered download, subset sizes and timings
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_017ic4b6Kg9RWSTJzyz6KP9X
This commit is contained in:
Executable
+80
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regenerates everything under output/. Needs JDK 25, Maven, curl, poppler (pdftoppm, pdftotext, pdffonts),
|
||||
# python3 with Pillow built with libraqm. Network is needed once for Maven and for 00-versions-and-licences.txt.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
mkdir -p output/pdf output/png
|
||||
JAR=target/pdf-invoice-1.0.0.jar
|
||||
HOST="$(java -version 2>&1 | grep -v JAVA_TOOL | head -1) | $(nproc) vCPU | $(awk '/MemTotal/ {printf "%d MB RAM", $2/1024}' /proc/meminfo)"
|
||||
|
||||
scripts/licences.sh > output/00-versions-and-licences.txt || echo "licence check skipped (offline?)"
|
||||
|
||||
mvn -q -B test # writes output/01, 04, 05
|
||||
mvn -q -B -DskipTests package
|
||||
|
||||
serve() { # serve <port> : start the packaged jar, wait until it answers
|
||||
setsid nohup java -jar "$JAR" --server.port="$1" > "target/app-$1.log" 2>&1 < /dev/null &
|
||||
echo $! > "target/app-$1.pid"
|
||||
for _ in $(seq 1 60); do curl -s -o /dev/null "localhost:$1/invoices/openpdf.pdf" && return 0; sleep 1; done
|
||||
echo "app did not start" >&2; return 1
|
||||
}
|
||||
stop() { kill "$(cat "target/app-$1.pid")" 2>/dev/null || true; }
|
||||
|
||||
P=$((20000 + RANDOM % 10000))
|
||||
serve "$P"
|
||||
ENGINES="openpdf itext flyingsaucer openpdf-jdk openpdf-jdk-actualtext flyingsaucer-shaped"
|
||||
|
||||
for e in $ENGINES; do
|
||||
curl -s -o "output/pdf/$e.pdf" "localhost:$P/invoices/$e.pdf"
|
||||
curl -s -o "output/pdf/probe-$e.pdf" "localhost:$P/invoices/$e.pdf?probe=true"
|
||||
done
|
||||
|
||||
{
|
||||
echo "# HTTP: status and headers per engine (curl -D -), then streaming vs buffered, then a failure after rendering"
|
||||
echo "# $HOST"
|
||||
for e in $ENGINES; do
|
||||
echo "--- GET /invoices/$e.pdf"
|
||||
curl -s -D - -o /dev/null "localhost:$P/invoices/$e.pdf" | tr -d '\r' | grep -i -E '^HTTP|^content-type|^content-disposition|^content-length|^transfer-encoding'
|
||||
done
|
||||
echo "--- GET /invoices/openpdf.pdf?mode=buffered"
|
||||
curl -s -D - -o /dev/null "localhost:$P/invoices/openpdf.pdf?mode=buffered" | tr -d '\r' | grep -i -E '^HTTP|^content-length|^transfer-encoding'
|
||||
echo "--- GET /invoices/openpdf.pdf?disposition=inline"
|
||||
curl -s -D - -o /dev/null "localhost:$P/invoices/openpdf.pdf?disposition=inline" | tr -d '\r' | grep -i -E '^HTTP|^content-disposition'
|
||||
echo "--- GET /invoices/nope.pdf"
|
||||
curl -s -D - -o /dev/null "localhost:$P/invoices/nope.pdf" | tr -d '\r' | grep -i -E '^HTTP'
|
||||
echo "--- GET /invoices/openpdf.pdf?failAtEnd=true (streamed: headers are already gone when the failure happens)"
|
||||
curl -s -D - -o target/fail-stream.pdf -w "curl exit=%{exitcode} bytes_received=%{size_download}\n" "localhost:$P/invoices/openpdf.pdf?failAtEnd=true" 2>&1 | tr -d '\r' | grep -i -E '^HTTP|^content-length|^transfer-encoding|curl exit' || true
|
||||
echo "--- GET /invoices/openpdf.pdf?failAtEnd=true&mode=buffered (rendered into memory first)"
|
||||
curl -s -D - -o target/fail-buffered.bin -w "curl exit=%{exitcode} bytes_received=%{size_download}\n" "localhost:$P/invoices/openpdf.pdf?failAtEnd=true&mode=buffered" 2>&1 | tr -d '\r' | grep -i -E '^HTTP|^content-length|curl exit' || true
|
||||
} > output/02-endpoints.txt
|
||||
|
||||
{
|
||||
echo "# pdffonts per engine (poppler): name, type, embedded, subset"
|
||||
for e in $ENGINES; do echo "--- $e ($(stat -c %s "output/pdf/$e.pdf") bytes)"; pdffonts "output/pdf/$e.pdf"; done
|
||||
echo; echo "# Producer/Creator lines the libraries write into the PDF (pdfinfo)"
|
||||
for e in openpdf itext flyingsaucer; do echo "--- $e"; pdfinfo "output/pdf/$e.pdf" | grep -E '^(Title|Creator|Producer)'; done
|
||||
} > output/03-pdffonts-and-producer.txt
|
||||
|
||||
stop "$P"
|
||||
|
||||
# the FOP variant needs Apache FOP (121 jars with Batik) on the classpath: Maven profile fop
|
||||
mvn -q -B -Pfop -DskipTests package
|
||||
P2=$((20000 + RANDOM % 10000))
|
||||
serve "$P2"
|
||||
curl -s -o output/pdf/openpdf-fop.pdf "localhost:$P2/invoices/openpdf-fop.pdf"
|
||||
curl -s -o output/pdf/probe-openpdf-fop.pdf "localhost:$P2/invoices/openpdf-fop.pdf?probe=true"
|
||||
stop "$P2"
|
||||
mvn -q -B -DskipTests package # back to the default jar
|
||||
|
||||
for e in openpdf itext flyingsaucer openpdf-jdk-actualtext flyingsaucer-shaped openpdf-fop; do
|
||||
pdftoppm -r 110 -png -singlefile -H 800 "output/pdf/$e.pdf" "output/png/invoice-$e"
|
||||
done
|
||||
python3 scripts/compare.py output/pdf output/png src/main/resources/fonts/NotoSansDevanagari-Regular.ttf
|
||||
|
||||
{
|
||||
echo "# in-process timing, $HOST"
|
||||
echo "# indicative: shared sandbox, one JVM, one run per line"
|
||||
java -jar "$JAR" --spring.profiles.active=bench --spring.main.web-application-type=none \
|
||||
--bench.n="${BENCH_N:-500}" --bench.engines=openpdf,itext,flyingsaucer,openpdf-jdk-actualtext,flyingsaucer-shaped 2>/dev/null | grep -E '^#|^[a-z]'
|
||||
} > output/06-bench.txt
|
||||
echo done
|
||||
Reference in New Issue
Block a user