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 filePlutoprint.html_to_pdf("<h1>Hello World</h1>","output.pdf")# HTML string to PNG filePlutoprint.html_to_png("<h1>Hello World</h1>","output.png")# URL to PDFPlutoprint.url_to_pdf("https://example.com","output.pdf")# Output to a stream (StringIO, File, etc.)io=StringIO.newPlutoprint.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.configuredo |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 resolutionconfig.ignore_path=nil# String, Regexp, or Proc to skip pathsconfig.ignore_request=nil# Proc receiving Rack::Request to skip requestsconfig.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"usePlutoprint::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.configuredo |config|
# Skip paths starting with /adminconfig.ignore_path="/admin"# Skip paths matching a patternconfig.ignore_path=/\/api\//# Skip based on the full requestconfig.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.configuredo |config|
config.use_pdf_middleware=trueconfig.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:
classReportsController < ApplicationControllerdefshow@pdf=request.env["plutoprint.middleware"]renderlayout: "print"if@pdfendendOverride options for specific actions without affecting the global config:
classReportsController < ApplicationControllerbefore_action:set_landscape_pdf,only: [:wide_report]privatedefset_landscape_pdfPlutoprint.set_options(request,size: :a4,margins: {top: "0.5in"})endendThis 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.configuredo |config|
config.use_pdf_middleware=trueconfig.options={margin: {top: "0.75in",right: "0.75in",bottom: "0.75in",left: "0.75in"},scale: 0.8,emulate_media: "screen"}end# After (PlutoPrint):Plutoprint.configuredo |config|
config.use_pdf_middleware=trueconfig.options={margins: {top: "0.75in",right: "0.75in",bottom: "0.75in",left: "0.75in"},media: :screen,user_style: "html { zoom: 0.8; }"}endRemove
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_countbook.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.