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_pdf | sghtmltopdf | Notes |
|---|---|---|
pdf: "name" | The same | The file name; .pdf is appended for you |
template:, layout:, locals:, formats: | The same | Passed straight through to Rails view rendering |
disposition:, filename:, status: | The same | disposition is inline by default |
show_as_html: true | The same | For debugging; returns the HTML instead of a PDF |
page_size: "A4" | page_size: "A4" | |
page_height:, page_width: | The same | Pass 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: true | The same | |
background: false | no_background: true | |
encoding: "UTF-8" | The same | |
title: | The same | PDF metadata |
user_style_sheet: | The same | An array of paths is accepted too |
no_pdf_compression: true | The same | |
cover: "shared/cover" | cover: <file path> | A path to an HTML file, not a template name; see below |
toc: {} | toc: true | Adjust 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_status | — | JavaScript is never executed; that is a deliberate non-goal |
print_media_type | — | Print media is always assumed |
lowquality, viewport_size, disable_smart_shrinking | — | Specific to WebKit |
exe_path, wkhtmltopdf | — | No external process is used |
extra | — | A 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.
HTML for the cover, header, and footer
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_pdf | sghtmltopdf |
|---|---|
wicked_pdf_stylesheet_link_tag | sghtmltopdf_stylesheet_link_tag |
wicked_pdf_image_tag | sghtmltopdf_image_tag |
wicked_pdf_asset_path | sghtmltopdf_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
@pagein CSS: in wkhtmltopdf the CLI wins, in sghtmltopdf@pagewins and the options are initial values - Ranges
- Remote fetching: retrieving
http(s)assets is off by default. Turn it on withallow_remote_assets: trueif 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