Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Migrating from wicked_pdf

A mapping and a set of notes for moving a Rails application from wicked_pdf, and wkhtmltopdf behind it, to the sghtmltopdf gem.

For the options themselves, see the wkhtmltopdf option mapping and Migrating from wkhtmltopdf. This page covers only what differs as seen from Rails and Ruby.

The smallest possible change

# Gemfile
- gem "wicked_pdf"
- gem "wkhtmltopdf-binary"
+ gem "sghtmltopdf"

Your controllers should keep working as they are.

def show
  respond_to do |format|
    format.pdf { render pdf: "invoice", template: "invoices/show", layout: "pdf" }
  end
end

No external process is started any more, so wicked_pdf’s exe_path, which pointed at the wkhtmltopdf binary, is no longer needed.

Where the configuration goes

# config/initializers/sghtmltopdf.rb
Sghtmltopdf.configure do |c|
  c.page_size   = "A4"
  c.margin_top  = "20mm"
  c.gothic_font = Rails.root.join("vendor/fonts/NotoSansJP-Regular.ttf")
end

This is the counterpart of wicked_pdf’s WickedPdf.config = {...}. The global configuration is merged first and the arguments to render win.

How the option names map

wicked_pdf takes nested hashes such as margin: {top: 10}, while sghtmltopdf takes a flat hash whose keys are the CLI flag names, with _ standing for -, so page_size: is --page-size. The option definitions live in one place in Rust, and the Ruby side keeps no whitelist of its own.

wicked_pdfsghtmltopdfNotes
pdf: "name"The sameThe file name; .pdf is appended for you
template:, layout:, locals:, formats:The samePassed straight through to Rails view rendering
disposition:, filename:, status:The samedisposition is inline by default
show_as_html: trueThe sameFor debugging; returns the HTML instead of a PDF
page_size: "A4"page_size: "A4"
page_height:, page_width:The samePass a string with a unit, such as "210mm"
orientation: "Landscape"The same
margin: {top: 10, bottom: 10}margin_top: "10mm", margin_bottom: "10mm"A bare number means mm in wicked_pdf, so state the unit
dpi:, zoom:The same
grayscale: trueThe same
background: falseno_background: true
encoding: "UTF-8"The same
title:The samePDF metadata
user_style_sheet:The sameAn array of paths is accepted too
no_pdf_compression: trueThe same
cover: "shared/cover"cover: <file path>A path to an HTML file, not a template name; see below
toc: {}toc: trueAdjust the appearance with toc_header_text: and friends
header: {left:, center:, right:}header_left:, header_center:, header_right:
header: {html: {template: "..."}}header_html: <file path>As above
header: {line: true, spacing: 5, font_name:, font_size:}header_line: true, header_spacing: 5, header_font_name:, header_font_size:The footer works the same way
outline: {}PDF outlines are not supported
disable_javascript, javascript_delay, window_statusJavaScript is never executed; that is a deliberate non-goal
print_media_typePrint media is always assumed
lowquality, viewport_size, disable_smart_shrinkingSpecific to WebKit
exe_path, wkhtmltopdfNo external process is used
extraA raw command line string is not accepted; use the individual keys

Passing a key that is not supported raises Sghtmltopdf::UsageError at render time, with the reason; nothing is ignored silently.

wicked_pdf takes a Rails template name and renders it internally, whereas --cover, --header-html, and --footer-html take file paths, so that they join the same path as the CLI. To use a Rails template, render it yourself and write it to a temporary file.

def show
  header = Tempfile.new(["header", ".html"])
  header.write(render_to_string(template: "invoices/header", layout: false))
  header.flush

  render pdf: "invoice", template: "invoices/show", header_html: header.path
ensure
  header&.close!
end

View helpers

wicked_pdfsghtmltopdf
wicked_pdf_stylesheet_link_tagsghtmltopdf_stylesheet_link_tag
wicked_pdf_image_tagsghtmltopdf_image_tag
wicked_pdf_asset_pathsghtmltopdf_asset_path, which returns nil if nothing is found
wicked_pdf_javascript_include_tag— (unnecessary, since JavaScript is not executed)
wicked_pdf_asset_base64— (unnecessary, since local files can be read directly)

A plain stylesheet_link_tag or image_tag works as it is, as long as the assets have been precompiled into public/. Rendering does not go through an HTTP server, so a URL such as /assets/… is resolved as a local file against --base-url, which defaults to Rails.root/public under Rails.

Where the assets are not in public/ yet, as in development, use sghtmltopdf_stylesheet_link_tag, which inlines the CSS into a <style> element.

<%= sghtmltopdf_stylesheet_link_tag "pdf" %>

Differences in the defaults

  • Margins: wkhtmltopdf uses 10mm left and right, sghtmltopdf 1in (96px) on all four sides. State margin_* explicitly to keep the same look
  • CLI options versus @page in CSS: in wkhtmltopdf the CLI wins, in sghtmltopdf @page wins and the options are initial values
  • Ranges
  • Remote fetching: retrieving http(s) assets is off by default. Turn it on with allow_remote_assets: true if you need it

Fonts

wkhtmltopdf depends on the system font configuration, whereas sghtmltopdf lets you name fonts with gothic_font, serif_font, and mono_font. For Japanese output it is safer to state them, so that you are not at the mercy of what a container happens to have.

Sghtmltopdf.configure do |c|
  c.gothic_font = Rails.root.join("vendor/fonts/NotoSansJP-Regular.ttf")
end

Moving the work to another process, which wicked_pdf could not do

wicked_pdf starts a wkhtmltopdf process for every request, while the sghtmltopdf gem converts inside your application process; the GVL is released during the heavy work, so other Puma threads keep running. If you still do not want to spend the application’s CPU on it, server_url delegates to a separate sghtmltopdf server process.

Sghtmltopdf.configure { |c| c.server_url = "http://pdf.internal:8080" }

Only one URL is accepted, on the assumption that load balancing happens in front of it, in nginx or a Kubernetes Service. If the server cannot be reached you get Sghtmltopdf::ServerError; it does not fall back to converting locally.

In server mode, options that take a local path, such as base_url, allow, and the font settings, cannot be given per request; they are only settable when the server starts. The defaults supplied by the Railtie are dropped automatically, but anything you set explicitly with configure produces a 400 and a UsageError, so move those to the server’s startup options.

Not there yet

  • Merging PDFs and PDF outlines are not supported

Streaming the output, on the other hand, is something wicked_pdf never had; pass a block to render. Combined with ActionController::Live, pages can be written to the response as they are finalised. See Ruby / Rails