soli-pdf

The render_pdf binary

The gem does no PDF work itself. It runs render_pdf, a standalone renderer written in Rust that is built and published with every Soli release. This page explains how the gem finds it, how to supply your own, and how to use it without Ruby.

#How the gem finds it

Soli::PDF::Binary.path resolves the executable on every render, in this order:

  1. Soli::PDF.binary_path = '...', if set.
  2. ENV['SOLI_PDF_BIN'], if set.
  3. The cached download: <cache>/soli-pdf/<BINARY_VERSION>/render-pdf-<os>-<arch>/render_pdf, where <cache> is $XDG_CACHE_HOME or ~/.cache. If it isn't there yet, the gem downloads it first.

For 1 and 2, the file must exist and be executable. If it doesn't, the gem raises Soli::PDF::BinaryNotFound and never falls back to a download. A typo in a production path fails loudly instead of quietly pulling a binary from the internet.

#The download

The first render on a machine without the cached copy:

  1. builds the asset name from the host, for example render-pdf-linux-amd64. The OS is linux, darwin or windows, and the CPU is amd64 or arm64;
  2. fetches https://github.com/solisoft/soli_lang/releases/download/v2.9.0/<asset>.tar.gz, following up to five redirects;
  3. fetches <asset>.tar.gz.sha256 and compares it with the SHA-256 of the archive. On a mismatch it raises DownloadError and keeps nothing;
  4. unpacks the archive with tar into a temporary directory next to the install directory, then moves it into place in one step. A crash halfway never leaves a broken install.

The archive contains the executable and a fonts/ directory with Titillium Web and JetBrains Mono.

~/.cache/soli-pdf/2.9.0/render-pdf-linux-amd64/
├── render_pdf
└── fonts/
    ├── TitilliumWeb-Regular.ttf  …Bold, Italic, BoldItalic
    └── JetBrainsMono-Regular.ttf …Bold, Italic

#Fonts next to the binary

On every render, the gem appends --font-dir <dir>/fonts when a fonts/ directory sits next to the executable. This is how the bundled fonts reach the renderer, and it also applies to a binary you supply. Copy the archive as a whole, or pass fonts:. Without any font, a render fails with no usable fonts found.

#Using your own build

Build the renderer from the Soli repository:

git clone https://github.com/solisoft/soli_lang
cd soli_lang/pdf
cargo build --release --bin render_pdf
mkdir -p /opt/render-pdf && cp target/release/render_pdf /opt/render-pdf/ && cp -R fonts /opt/render-pdf/fonts

Then point the gem at it:

export SOLI_PDF_BIN=/opt/render-pdf/render_pdf

#Running it without Ruby

render_pdf is a regular command-line tool. The gem runs it like this:

render_pdf --template /tmp/soli-pdf123.json --data - -o - --title "Invoice F-42" \
           --font-dir ~/.cache/soli-pdf/2.9.0/render-pdf-linux-amd64/fonts < data.json > invoice.pdf
FlagMeaning
--template <path>The layout template. Required.
--data <path>The data document. Required unless --invoice is given.
--invoice <path>A typed invoice, used instead of --data. Cannot be combined with --data or --xml.
--xml <path>Factur-X CII XML to embed. The output becomes PDF/A-3b.
--profile <name>Factur-X profile: minimum, basicwl, basic, en16931 (default) or extended.
-o, --out <path>Output file. Required. - writes the PDF to standard output.
--no-imagesDo not fetch http(s) images.
--title, --author, --subjectDocument metadata.
--stationery <path>Letterhead PDF drawn beneath every page.
--attach <path>Embed a file. Repeatable.
--password, --owner-passwordAES-128 encryption.
--font-dir <path>A font directory. Repeatable. Defaults to ./fonts and ./font.

- means standard input for one of --template, --data and --invoice. On success the exit status is 0, and each warning is printed to standard error as a warning: … line. On failure, the binary prints error: … and exits with a non-zero status.

That makes it usable from any language, or from a shell script:

render_pdf --template invoice.template.json --data invoice.data.json -o invoice.pdf

#Performance and concurrency

Each render starts one short-lived process, so renders don't share state. It is safe to render from many threads or processes at once. The only shared state in the gem is Soli::PDF.last_warnings. See Soli::PDF.

Process start-up and font loading happen on every call. For bulk jobs, such as a month of statements, render in parallel background jobs rather than in a web request.