sghtmltopdf
Ruby binding for sghtmltopdf, an HTML-to-PDF renderer written in Rust that does not depend on Chromium, WebKit, or Gecko.
The engine runs inside your process through a native extension (magnus + rb-sys) — no subprocess, no temporary files — and releases the GVL while rendering, so other Puma threads keep running.
Documentation · Repository · CHANGELOG
Install
# Gemfile
gem "sghtmltopdf"
Precompiled native gems are published for x86_64-linux, aarch64-linux, x86_64-linux-musl, aarch64-linux-musl, and arm64-darwin.
There is no build step on those platforms.
Elsewhere (Intel Mac, Windows) the gem cannot run in-process — the source gem does not carry the Rust core and will refuse to build with an explanatory message.
Point those environments at a separate sghtmltopdf server process instead; see Delegating to a server.
Requires Ruby >= 3.2.
Usage
pdf = Sghtmltopdf.render("<h1>Invoice</h1>", page_size: "A4", margin_top: "20mm")
Option names are the CLI long options without -- and with - replaced by _, so --page-size A4 becomes page_size: "A4".
The option reference lists all of them.
Write straight to a file (written to a temporary file and renamed on success, so a failure never leaves a broken PDF behind), or take the bytes in chunks:
Sghtmltopdf.render_to_file(html, "invoice.pdf", page_size: "A4")
Sghtmltopdf.render(html) { |bytes| io.write(bytes) }
Rails
Adding the gem is enough; the Railtie wires everything up, and nothing is loaded when Rails is absent.
# config/initializers/sghtmltopdf.rb
Sghtmltopdf.configure do |c|
c.page_size = "A4"
c.gothic_font = Rails.root.join("vendor/fonts/NotoSansJP-Regular.ttf")
end
A :pdf renderer is registered, in the spirit of wicked_pdf — the same keys, so an existing controller often needs no change at all:
class InvoicesController < ApplicationController
def show
render pdf: "invoice", # filename; ".pdf" is appended
template: "invoices/show",
layout: "pdf",
page_size: "A4", margin_top: "20mm"
end
end
View-rendering keys (template, layout, locals, …) go to render_to_string, response keys (filename, disposition, status) go to send_data, show_as_html: true returns the HTML instead of a PDF, and everything else is passed to the converter.
Converter keys are flat CLI flag names, so wicked_pdf's nested margin: {top: 10} becomes margin_top: "10mm" (with the unit spelled out); the migration guide maps every key one by one.
Assets
PDF rendering does not go through the HTTP server, so /assets/… URLs are resolved as local files: the Railtie defaults base_url to Rails.root/public and restricts local reads to Rails.root via allow.
That is enough for a precompiled production app; in development, these helpers inline the asset instead:
<%= sghtmltopdf_stylesheet_link_tag "pdf" %>
<%= sghtmltopdf_image_tag "logo.png" %>
Streaming the response
To send pages as soon as their layout is final, pass a block and use ActionController::Live — this also makes Rack::Timeout and Thread#kill effective at chunk boundaries:
class InvoicesController < ApplicationController
include ActionController::Live
def show
response.headers["Content-Type"] = "application/pdf"
html = render_to_string(template: "invoices/show", layout: "pdf")
Sghtmltopdf.render(html) { |bytes| response.stream.write(bytes) }
ensure
response.stream.close
end
end
Delegating to a server
If the gem cannot run where your app runs, or you would rather not spend the app's CPU on rendering, set server_url and the same calls are delegated over HTTP to a separate sghtmltopdf server process.
Sghtmltopdf.configure { |c| c.server_url = "http://pdf:8080" }
The official Docker image runs that server and bundles Japanese fonts.
License
MIT License (LICENSE).