Ruby bindings for the PlutoBook rendering engine. Convert HTML, XML, SVG, and images into high-quality PDFs and PNG images — without a browser.
PlutoPrint Ruby is a native C extension. No headless Chrome, no Node.js, no subprocess overhead.
Note: PlutoPrint Ruby (
plutoprint-ruby) is maintained by Ajaya Agrawalla / ClearStack Inc and is not affiliated with or endorsed by the PlutoBook project.
Prerequisites: The PlutoBook C library must be installed on your system.
# macOS (Homebrew)
brew install plutobook
# Ubuntu/Debian
sudo apt-get install libplutobook-devThen add the gem:
gem "plutoprint-ruby"Or install directly:
gem install plutoprint-rubyIf PlutoBook is installed in a non-standard location:
gem install plutoprint-ruby -- --with-plutobook-dir=/path/to/plutobookrequire "plutoprint"
# HTML string to PDF file
Plutoprint.html_to_pdf("<h1>Hello World</h1>", "output.pdf")
# HTML string to PNG file
Plutoprint.html_to_png("<h1>Hello World</h1>", "output.png")
# URL to PDF
Plutoprint.url_to_pdf("https://example.com", "output.pdf")
# Output to a stream (StringIO, File, etc.)
io = StringIO.new
Plutoprint.html_to_pdf("<h1>Hello</h1>", io)
pdf_bytes = io.stringPlutoprint.html_to_pdf(
"<h1>Report</h1>",
"report.pdf",
size: :letter,
margins: :wide,
media: :print,
user_style: "body { font-family: serif; }",
title: "My Report",
author: "Jane Doe"
)Use Plutoprint.configure to set project-wide defaults. This works in any Ruby application — Rails, Sinatra, plain scripts.
Plutoprint.configure do |config|
config.use_pdf_middleware = true # Enable .pdf URL interception (default: true)
config.use_png_middleware = false # Enable .png URL interception (default: false)
config.root_url = nil # Override base URL for asset resolution
config.ignore_path = nil # String, Regexp, or Proc to skip paths
config.ignore_request = nil # Proc receiving Rack::Request to skip requests
config.options = {
size: :letter,
margins: {
top: "0.75in",
right: "0.75in",
bottom: "0.75in",
left: "0.75in"
},
media: :screen,
user_style: "html { zoom: 0.8; }"
}
end| Option | Type | Default | Description |
|---|---|---|---|
size |
Symbol | :a4 |
Page size: :a3, :a4, :a5, :b4, :b5, :letter, :legal, :ledger |
margins |
Symbol or Hash | :normal |
Preset (:none, :narrow, :normal, :moderate, :wide) or hash with sides |
media |
Symbol | :print |
CSS media type: :print or :screen |
user_style |
String | nil |
CSS injected before rendering |
user_script |
String | nil |
JavaScript passed to PlutoBook |
Margin hash accepts unit strings ("0.75in", "19mm", "54pt", "2cm", "100px") or numeric point values:
{ top: "0.75in", right: "19mm", bottom: "54pt", left: "2cm" }Options merge in order — each tier overrides the previous:
- Gem defaults —
:a4,:normalmargins,:printmedia - Initializer — your
Plutoprint.configureblock - Per-request — controller-level overrides via
Plutoprint.set_options
Deep merge is used for nested hashes like margins, so overriding one side preserves the others.
For Sinatra, Hanami, Roda, or any Rack app, add the middleware explicitly:
require "plutoprint"
require "plutoprint/rack/middleware"
use Plutoprint::Rack::MiddlewareAny request ending in .pdf is intercepted: the extension is stripped, the HTML response is rendered normally, then converted to PDF and returned with Content-Type: application/pdf.
The same works for .png when use_png_middleware is enabled.
Plutoprint.configure do |config|
# Skip paths starting with /admin
config.ignore_path = "/admin"
# Skip paths matching a pattern
config.ignore_path = /\/api\//
# Skip based on the full request
config.ignore_request = ->(request) { request.params["skip_pdf"] }
endAdd the gem to your Gemfile:
gem "plutoprint-ruby"Create an initializer at config/initializers/plutoprint.rb:
Plutoprint.configure do |config|
config.use_pdf_middleware = true
config.options = {
size: :letter,
margins: {
top: "0.75in",
right: "0.75in",
bottom: "0.75in",
left: "0.75in"
},
media: :screen,
user_style: "html { zoom: 0.8; }"
}
endThat's it. The Railtie auto-registers the middleware. No config.middleware.use needed.
The middleware sets request.env["plutoprint.middleware"] to true when a .pdf request is being processed:
class ReportsController < ApplicationController
def show
@pdf = request.env["plutoprint.middleware"]
render layout: "print" if @pdf
end
endOverride options for specific actions without affecting the global config:
class ReportsController < ApplicationController
before_action :set_landscape_pdf, only: [:wide_report]
private
def set_landscape_pdf
Plutoprint.set_options(request, size: :a4, margins: {top: "0.5in"})
end
endThis is thread-safe — options are stored in the request env (per-request, per-thread).
PlutoPrint can replace Grover as your PDF engine. Key differences:
| Aspect | Grover | PlutoPrint |
|---|---|---|
| Engine | Headless Chrome (Puppeteer) | PlutoBook (native C) |
| JS execution | Full browser JS | Limited |
| CSS support | Full Chrome CSS | CSS 2.1 + some CSS 3 |
| Memory | Heavy (Chrome process) | Lightweight |
| Startup | Slow (browser launch) | Instant |
| Dependencies | Node.js + Puppeteer | PlutoBook C library |
- Replace the gem in your Gemfile:
# Remove:
gem "grover"
# Add:
gem "plutoprint-ruby"- Replace the initializer:
# config/initializers/grover.rb → config/initializers/plutoprint.rb
# Before (Grover):
Grover.configure do |config|
config.use_pdf_middleware = true
config.options = {
margin: { top: "0.75in", right: "0.75in", bottom: "0.75in", left: "0.75in" },
scale: 0.8,
emulate_media: "screen"
}
end
# After (PlutoPrint):
Plutoprint.configure do |config|
config.use_pdf_middleware = true
config.options = {
margins: { top: "0.75in", right: "0.75in", bottom: "0.75in", left: "0.75in" },
media: :screen,
user_style: "html { zoom: 0.8; }"
}
end-
Remove
require 'grover'fromconfig/application.rb(PlutoPrint's Railtie handles everything). -
Remove any manual
config.middleware.use Grover::Middlewarelines. -
Update controller PDF detection:
# Before:
@pdf = request.env["Rack-Middleware-Grover"]
# After:
@pdf = request.env["plutoprint.middleware"]- Guard any
window.print()JavaScript in print layouts — PlutoPrint doesn't execute JavaScript like Chrome does:
<% unless @pdf %>
<script>window.print();</script>
<% end %>- CSS rendering: PlutoPrint supports CSS 2.1 with some CSS 3. Complex flexbox/grid layouts may render differently. Simple table-based print layouts work well.
- JavaScript: PlutoPrint has limited JS support compared to Chrome. Guard browser-specific JS behind
unless @pdf. - Scale: Grover's
scale: 0.8maps to PlutoPrint'suser_style: "html { zoom: 0.8; }". - Media type: Grover's
emulate_media: "screen"maps to PlutoPrint'smedia: :screen.
PlutoPrint includes a command-line tool:
# HTML file to PDF
plutoprint convert input.html output.pdf
# With options
plutoprint convert input.html output.pdf --size letter --margins wide
# Custom margins
plutoprint convert input.html output.pdf --margin-top 0.75in --margin-right 0.5in
# To PNG
plutoprint convert input.html output.png --width 800 --height 600
# With custom CSS
plutoprint convert input.html output.pdf --user-style "body { color: navy; }"
# From URL
plutoprint convert https://example.com output.pdf
# Version info
plutoprint version
plutoprint infoPlutoprint.html_to_pdf(html, output, size:, margins:, media:, user_style:, user_script:, **metadata)
Plutoprint.html_to_png(html, output, size:, margins:, media:, width:, height:, user_style:, user_script:)
Plutoprint.url_to_pdf(url, output, size:, margins:, media:, user_style:, user_script:, **metadata)
Plutoprint.url_to_png(url, output, size:, margins:, media:, width:, height:, user_style:, user_script:)output can be a file path (String) or any IO-like object (StringIO, File).
book = Plutoprint::Book.new(size: :letter, margins: :normal, media: :print)
book.load_html(html, user_style, user_script, base_url)
book.load_url(url, user_style, user_script)
book.page_count
book.set_metadata(:title, "My Document")
book.set_metadata(:author, "Jane Doe")
book.write_to_pdf("output.pdf")
book.write_to_pdf_stream(io)
book.write_to_png("output.png", width, height)
book.write_to_png_stream(io, width, height):none, :a3, :a4, :a5, :b4, :b5, :letter, :legal, :ledger
| Preset | Top | Right | Bottom | Left |
|---|---|---|---|---|
:none |
0 | 0 | 0 | 0 |
:narrow |
0.5in | 0.5in | 0.5in | 0.5in |
:normal |
1in | 1in | 1in | 1in |
:moderate |
1in | 0.75in | 1in | 0.75in |
:wide |
1in | 2in | 1in | 2in |
book.set_metadata(:title, "Document Title")
book.set_metadata(:author, "Author Name")
book.set_metadata(:subject, "Subject")
book.set_metadata(:keywords, "ruby, pdf")
book.set_metadata(:creator, "PlutoPrint Ruby")
book.set_metadata(:creation_date, Time.now.iso8601)
book.set_metadata(:modification_date, Time.now.iso8601)# Install dependencies
bundle install
# Compile the C extension
bundle exec rake compile
# Run tests
bundle exec rspec
# Run linter
bundle exec standardrb
# Run everything (compile + test + lint)
bundle exec rake- plutoprint (Python) — Python bindings for PlutoBook, the inspiration for this Ruby gem's API design
- Grover — HTML-to-PDF via headless Chrome; inspired this gem's configuration pattern, Rack middleware, and Rails integration approach
MIT License. See LICENSE for details.