soli-pdf

Render options

Everything after template: and data: in a Soli::PDF.render call is an option. Each one maps to a render_pdf command-line flag. Any other key raises ArgumentError.

Soli::PDF.render(
  template: template,
  data: data,
  title: 'Statement ST-2025-0947',
  author: 'Northwind Trading Co.',
  password: 'demo',
  attachments: ['exports/statement.csv'],
  images: false
)

#Reference

OptionTypeFlagEffect
title:String--titleDocument title in the PDF metadata, shown by viewers in the window title. Also used in the Factur-X metadata.
author:String--authorDocument author in the PDF metadata.
subject:String--subjectDocument subject in the PDF metadata.
password:String--passwordEncrypts the PDF with AES-128. This password is required to open it.
owner_password:String--owner-passwordThe owner password, which lifts restrictions. It defaults to password.
stationery:String (path)--stationeryA letterhead PDF drawn beneath every page.
attachments:Array of paths--attach (repeated)Files embedded in the PDF's attachments panel.
fonts:Array of directories--font-dir (repeated)Extra font directories, loaded along with the bundled fonts.
images:false--no-imagesNever fetches http(s) images. See Images.
profile:String--profileThe Factur-X profile, used with xml: or invoice:. See Factur-X.

Values are converted with to_s, so a Pathname works wherever a path is expected. An option set to nil is left out.

Note Relative paths (stationery:, attachments:, fonts:) are resolved against the current working directory of your Ruby process. That is the app root under Rails, but not always in a job runner or cron. Pass absolute paths, for example Rails.root.join(...), to be safe.

#Metadata

title:, author: and subject: fill the PDF's document information. Viewers show the title in the tab or window bar instead of the file name, and search tools index it. Always set title:: it costs nothing.

#Password protection

pdf = Soli::PDF.render(template: template, data: data,
                       password: customer.birth_date.strftime('%d%m%Y'))

The PDF is encrypted with AES-128, and opening it requires the password. owner_password: sets a second password that grants full rights. When you leave it out, it is the same as password:.

The account statement example is rendered with password: 'demo'. Download it to try.

Warning Encryption is incompatible with Factur-X. PDF/A, which Factur-X builds on, forbids encryption, so combining password: with xml: or invoice: makes the render fail with a RenderError.

#Letterhead stationery

Soli::PDF.render(template: template, data: data,
                 stationery: Rails.root.join('app/pdf/letterhead.pdf'))

Your template's content is drawn on top of an existing PDF. Page 1 of the output uses the letterhead's first page. Later pages use its second page when it has one, and the first page otherwise. That lets you have a full letterhead on page 1 and a lighter continuation sheet after it. The letterhead is scaled to the output page size.

This is the easiest way to reuse a letterhead a designer made in another tool. Leave room for it with the template's margins and header_height options. A missing file makes the render fail.

#Attachments

Soli::PDF.render(template: template, data: data,
                 attachments: ['tmp/timesheet.csv', 'tmp/terms.pdf'])

The files are embedded in the PDF and appear in the viewer's attachments panel. Each attachment is named after its file's base name, and its MIME type is guessed from the extension. Attachments work together with Factur-X: your files sit next to the embedded factur-x.xml.

#Fonts

The renderer ships with Titillium Web (regular, bold, italic, bold italic) and JetBrains Mono (regular, bold, italic). The gem passes the bundled font directory automatically. Use fonts: to add more:

Soli::PDF.render(template: template, data: data,
                 fonts: [Rails.root.join('app/pdf/fonts')])

A directory holds TrueType or OpenType files. The template's fonts key names the main text family, for example "fonts": ["titillium"]. Every other font loaded from the directories becomes a fallback for the characters the main one lacks. Adding a directory with a CJK font such as Noto Sans JP is enough to print Japanese names. A character that no loaded font covers is dropped and reported in Soli::PDF.last_warnings. See Text & paragraphs.

#Images

Warning With render_pdf 2.9.0, the binary the gem downloads today, only data: URIs load: http(s) URLs and file:// paths are skipped with no image-source policy installed. The next release loads local files under your process's working directory and http(s) images from public addresses, and still refuses the rest. See Layout & drawing for the policy. The render never fails because of an image.

images: false passes --no-images, which tells the renderer never to fetch http(s) images: renders stay offline and reproducible. Local files and data: URIs still load.

A data: URI works for SVG and for raster images (PNG, JPEG, WebP, GIF). Build it in Ruby and put it in the template, because an image's value is not interpolated: ${company.logo} would be read literally.

require 'base64'

LOGO = "data:image/svg+xml;base64,#{Base64.strict_encode64(File.read('app/pdf/logo.svg'))}"

template = JSON.parse(File.read('app/pdf/invoice.template.json'))
template['content'].unshift({ 'type' => 'image', 'value' => LOGO, 'width' => 120, 'alt' => 'Logo' })

Soli::PDF.render(template: template, data: data)

#Options that live in the template

Some settings belong to the document, not to the call, so they go in the template's options object: page size, orientation, margins, header height, background, watermark, and accessible (tagged) output. See Template structure.