Skip to main content

HexaPDF Ruby Tutorial: PDF Generation for Rails

· 14 min read
Michał Szymanowski
Michał Szymanowski
PDFBolt Co-Founder

Ruby on Rails PDF generation with HexaPDF library tutorial

Modern Ruby on Rails applications often require sophisticated PDF processing that extends beyond basic document creation. HexaPDF stands out as a comprehensive, pure Ruby solution. It addresses complex requirements through its dual-purpose architecture. Unlike traditional libraries that focus exclusively on PDF generation, HexaPDF provides complete PDF lifecycle management – from programmatic document creation to advanced manipulation of existing files. Its feature set includes digital signatures, encryption, form handling, and document merging, making it particularly valuable for enterprise Rails applications with demanding document workflows.

What is HexaPDF? Ruby PDF Library Overview

HexaPDF is a pure Ruby library designed for comprehensive PDF document creation and manipulation. Built from the ground up with performance and ease of use in mind, HexaPDF offers a complete solution for working with PDF files in Ruby applications. Unlike many alternatives that require external dependencies, HexaPDF is implemented entirely in Ruby, ensuring consistent behavior across different environments and platforms.

HexaPDF Advantages for Rails PDF Processing

HexaPDF distinguishes itself through its comprehensive feature set that addresses both simple and complex PDF generation requirements:

  • Low-level Canvas API for precise control over document elements and manual positioning.
  • Complete PDF manipulation allowing modification, merging, and enhancement of existing PDFs.
  • Rich text formatting with comprehensive typography control and font management.
  • Advanced security features including encryption, digital signatures, and access controls.
  • Interactive form support with full AcroForm implementation and annotation handling.
  • Performance optimization through efficient memory management and streaming capabilities.
Important Distinction
  • HexaPDF is designed for programmatic PDF creation, not HTML to PDF conversion. It provides a Ruby API for building PDF documents from scratch or manipulating existing ones.
  • If you need HTML to PDF conversion, consider specialized solutions like HTML to PDF API or browser-based tools.

Getting Started with HexaPDF in Ruby on Rails

HexaPDF's pure Ruby implementation makes integration with Rails applications straightforward and reliable. Let's explore the setup process and create our first PDF document.

System Requirements
  • HexaPDF requires Ruby 2.6.0 or newer.
  • Minimal dependencies: Only one Ruby gem dependency – geom2d.
  • Pure Ruby: No external binaries, or specific frameworks required.

HexaPDF Gem Installation

Add HexaPDF to your Rails application by including it in your Gemfile:

gem 'hexapdf'

Then install the gem:

bundle install
Licensing Considerations

HexaPDF operates under a dual licensing model:

  • AGPL v3 License: Free for open-source projects where you provide source code access.
  • Commercial License: Required for proprietary applications where source code cannot be shared.

Create Our First PDF with HexaPDF

Let's create a simple PDF generator in Rails to demonstrate HexaPDF's basic capabilities:

# app/controllers/pdfs_controller.rb
class PdfsController < ApplicationController
def generate
# Create new PDF document
doc = HexaPDF::Document.new
page = doc.pages.add
canvas = page.canvas

# Set font family and size
canvas.font('Helvetica', size: 28)

# Add text at specific coordinates
canvas.text("Welcome to HexaPDF!", at: [50, 750])

pdf_data = doc.write_to_string

# Send PDF to browser
send_data pdf_data,
filename: "hexapdf_introduction.pdf",
type: "application/pdf",
disposition: "inline"
end
end

Add the corresponding route:

# config/routes.rb
Rails.application.routes.draw do
get 'generate_pdf', to: 'pdfs#generate'
end

This basic example demonstrates HexaPDF's canvas-based approach to PDF creation, providing precise control over element positioning and styling.

Working with Text using HexaPDF

HexaPDF provides extensive text formatting capabilities that enable professional document creation with sophisticated typography control.

Text Formatting Options in HexaPDF

This example demonstrates HexaPDF's core text formatting features including font variants (bold, italic, bold-italic), colored text, and text alignment options.

View text formatting example
# app/controllers/pdfs_controller.rb
class PdfsController < ApplicationController
def generate_pdf
require 'hexapdf'

doc = HexaPDF::Document.new
canvas = doc.pages.add.canvas

# Document title
canvas.font('Helvetica', size: 24, variant: :bold)
canvas.text("HexaPDF Text Formatting Options", at: [50, 750])

y_position = 700

# Font variants
canvas.font('Helvetica', size: 18, variant: :bold)
canvas.text("Bold text", at: [50, y_position])
y_position -= 30

canvas.font('Helvetica', variant: :italic)
canvas.text("Italic text", at: [50, y_position])
y_position -= 30

canvas.font('Helvetica', variant: :bold_italic)
canvas.text("Bold italic combination", at: [50, y_position])
y_position -= 40

# Text with color (RGB values from 0 to 1)
canvas.fill_color(0.8, 0.2, 0.2) # Red color
canvas.font('Helvetica', size: 18)
canvas.text("Red text example", at: [50, y_position])
y_position -= 30

# Blue text
canvas.fill_color(0.2, 0.4, 0.8)
canvas.text("Blue colored text", at: [50, y_position])
y_position -= 40

# Reset to black for alignment demonstration
canvas.fill_color(0, 0, 0)
canvas.font('Helvetica', size: 12)

# Text alignment demonstration
sample_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."

width = 500
height = 80
y_base = y_position - 20

tf = doc.layout.text_fragments(sample_text, font: doc.fonts.add("Helvetica"))
tl = HexaPDF::Layout::TextLayouter.new

[:left, :center, :right].each_with_index do |align, index|
x = 50
y = y_base - index * (height)

# Label for alignment type
canvas.font('Helvetica', size: 14, variant: :bold)
canvas.text("#{align.to_s.capitalize} alignment:", at: [x, y + 15])

# Apply alignment and render text
canvas.font('Helvetica', size: 10)
tl.style.text_align(align)
tl.fit(tf, width, height).draw(canvas, x, y)
end

pdf_data = doc.write_to_string

send_data pdf_data,
filename: "text_formatting.pdf",
type: "application/pdf",
disposition: "inline"
end
end

Here's the output:

 Ruby on Rails - HexaPDF: PDF document demonstrating text formatting options

Custom Font Integration in HexaPDF

HexaPDF provides excellent support for both standard PDF fonts and custom TrueType fonts. This example demonstrates how to integrate custom font variants into your PDF documents.

View custom font integration example
# app/controllers/pdfs_controller.rb
class PdfsController < ApplicationController
def generate_pdf
require 'hexapdf'

doc = HexaPDF::Document.new
canvas = doc.pages.add.canvas

# Document title
canvas.font('Helvetica', size: 24, variant: :bold)
canvas.text("HexaPDF Custom Font Examples", at: [50, 750])

# Method 1: direct font file path (use the full path)
canvas.font(Rails.root.join('app/assets/fonts/Ubuntu-Regular.ttf').to_s, size: 20)
canvas.text("Text in Ubuntu font", at: [50, 700])

# Method 2: font mapping configuration (configure families and variants)
doc.config['font.map'] = {
'CustomFont' => {
italic: Rails.root.join('app/assets/fonts/Ubuntu-Italic.ttf').to_s,
bold: Rails.root.join('app/assets/fonts/Ubuntu-Bold.ttf').to_s
}
}

canvas.font('CustomFont', variant: :bold)
canvas.text("Bold Ubuntu font", at: [50, 650])

canvas.font('CustomFont', variant: :italic)
canvas.text("Italic Ubuntu font", at: [50, 600])

pdf_data = doc.write_to_string
send_data pdf_data,
filename: "custom_fonts.pdf",
type: "application/pdf",
disposition: "inline"
end
end

Using Custom Fonts with HexaPDF in Rails

  1. Create a fonts directory: Add your custom fonts to app/assets/fonts.

  2. Download fonts from Google Fonts and place the TTF files in the folder:

app/assets/fonts/
├── Ubuntu-Regular.ttf
├── Ubuntu-Bold.ttf
├── Ubuntu-Italic.ttf
└── Ubuntu-BoldItalic.ttf
  1. Register the font family in your HexaPDF code: Use HexaPDF’s font mapping configuration to define variants like regular, bold, and italic for your custom font family.

Here's the output:

HexaPDF custom font integration in Ruby on Rails PDF generation

Working with Images and Graphics using HexaPDF

HexaPDF provides comprehensive image handling capabilities with support for JPEG, PNG, and PDF formats, along with flexible positioning and scaling options.

HexaPDF Image Handling

This example demonstrates basic image placement, scaling, and positioning techniques using HexaPDF's canvas API.

View complete image example
# app/controllers/pdfs_controller.rb
class PdfsController < ApplicationController
def generate_pdf
require 'hexapdf'

doc = HexaPDF::Document.new
canvas = doc.pages.add.canvas

# Document title
canvas.font('Helvetica', size: 24, variant: :bold)
canvas.text("HexaPDF Image Integration Examples", at: [50, 780])

y_position = 740

begin
# Basic image placement
image_path = Rails.root.join('app/assets/images/sample.jpg')

if File.exist?(image_path)
# Auto-sizing based on image dimensions
canvas.font('Helvetica', size: 14, variant: :bold)
canvas.text("Auto-sized image:", at: [50, y_position])
y_position -= 45

canvas.image(image_path.to_s, at: [50, y_position - 250])
y_position -= 320

# Fixed width with proportional height
canvas.font('Helvetica', size: 14, variant: :bold)
canvas.text("Fixed width (200px):", at: [50, y_position])
y_position -= 25

canvas.image(image_path.to_s, at: [50, y_position - 135], width: 200)
y_position -= 180

# Explicit dimensions (may distort aspect ratio)
canvas.font('Helvetica', size: 14, variant: :bold)
canvas.text("Custom dimensions (150px x 60px)", at: [50, y_position])
y_position -= 25

canvas.image(image_path.to_s, at: [50, y_position - 60], width: 150, height: 60)

else
canvas.font('Helvetica', size: 12)
canvas.text("Image not found at app/assets/images/sample.jpg", at: [50, y_position])
end

rescue => e
canvas.font('Helvetica', size: 12)
canvas.text("Error loading image: #{e.message}", at: [50, y_position])
end

pdf_data = doc.write_to_string
send_data pdf_data,
filename: "hexapdf_images.pdf",
type: "application/pdf",
disposition: "inline"
end
end

Here's the output:

HexaPDF image integration showing various image placement and scaling options in Ruby on Rails PDF generation

Creating Tables with HexaPDF

HexaPDF provides robust table creation capabilities through its TableBox layout system, allowing you to create structured data presentations with automatic formatting and page breaks.

HexaPDF Table Example

This example demonstrates how to create simple table using HexaPDF's built-in table functionality.

View table creation example
# app/controllers/pdfs_controller.rb
class PdfsController < ApplicationController
def generate_pdf
require 'hexapdf'

# Create PDF with table using Composer
pdf_data = StringIO.new

HexaPDF::Composer.create(pdf_data) do |composer|
composer.text("HexaPDF Table Example", font_size: 24,
font: 'Helvetica bold',)

# Simple table with basic data
table_data = [
['Product', 'Price', 'Stock'],
['Laptop', '$999.99', '15'],
['Mouse', '$29.99', '45'],
['Keyboard', '$79.99', '23']
]

# Create table with fixed column widths
composer.table(table_data, column_widths: [120, 100, 80],
margin: [20, 0, 0, 0],
cell_style: {
padding: 8,
border: { width: 1, color: 'cccccc' }
})
end

pdf_data.rewind
send_data pdf_data.read,
filename: "hexapdf_table.pdf",
type: "application/pdf",
disposition: "inline"
end
end

Here's the output:

HexaPDF table creation in Ruby on Rails PDF generation

HexaPDF Security Features

HexaPDF provides comprehensive security features including password-based encryption, digital signatures, and granular permission controls.

HexaPDF Security Implementation

This example demonstrates how to create a password-protected PDF with restricted permissions using HexaPDF's encryption capabilities. The document will require a password to open and will have specific limitations on user actions.

View security example
# app/controllers/pdfs_controller.rb
class PdfsController < ApplicationController
def generate_pdf
require 'hexapdf'

doc = HexaPDF::Document.new
canvas = doc.pages.add.canvas

# Document header
canvas.font('Helvetica', size: 24, variant: :bold)
canvas.fill_color(0.8, 0.2, 0.2)
canvas.text("CONFIDENTIAL DOCUMENT", at: [120, 750])

# Add some content
canvas.fill_color(0, 0, 0)
canvas.font('Helvetica', size: 16)
canvas.text("This document contains sensitive information", at: [50, 700])

# Apply encryption with maximum security restrictions
doc.encrypt(
algorithm: :aes, # AES encryption (recommended)
key_length: 128, # 128-bit key for broad compatibility
user_password: "user123", # Password required to open document
owner_password: "admin456", # Password for full access/permissions
permissions: [] # Empty array = no permissions allowed
)

pdf_data = doc.write_to_string

send_data pdf_data,
filename: "confidential_doc.pdf",
type: "application/pdf",
disposition: "attachment" # Force download
end
end

Available Permission Options

HexaPDF allows fine-grained control over document permissions through the permissions parameter. You can specify which actions users are allowed to perform on the encrypted PDF document.

permissions: [
:print, # Allow printing the document
:modify_content, # Allow content modification
:copy_content, # Allow copying text and images
:modify_annotation, # Allow adding/editing annotations
:fill_in_forms, # Allow filling in form fields
:extract_content, # Allow content extraction
:assemble_document, # Allow page reorganization
:high_quality_print # Allow high-quality printing
]

HexaPDF Invoice Generator: Complete Rails Tutorial

Now we'll create a comprehensive invoice generation system that demonstrates HexaPDF's advanced capabilities in a real business scenario. This example showcases professional invoice layout, precise positioning, and advanced typography features.

What we'll build:

  • Professional invoice layout with company branding and precise positioning.
  • Manual table creation using HexaPDF's canvas API for complete control.
  • Financial calculations including subtotals, tax, and totals.
  • Service Object architecture following Rails best practices.

Project Structure

Organize your Rails application with proper separation of concerns:

app/
├── controllers/
│ └── invoices_controller.rb
├── services/
│ └── hexapdf_invoice_service.rb
└── assets/
└── images/
└── company_logo.png # Optional

Step 1: Create the HexaPDF Invoice Service

The service class demonstrates HexaPDF's canvas API for precise control over document layout, including manual table creation, advanced typography, and professional styling.

Click to view the complete HexaPDF invoice service
# app/services/hexapdf_invoice_service.rb
class HexapdfInvoiceService
def initialize(invoice_data)
@invoice = invoice_data
# Auto-generate invoice number if not provided
@invoice[:number] ||= generate_invoice_number

# Start from top
@y = 780
@margin_left = 50
end

def generate
require 'hexapdf'

# Create new PDF document
doc = HexaPDF::Document.new
canvas = doc.pages.add.canvas

# Build invoice sections in order from top to bottom
add_header(canvas)
add_company_info(canvas)
add_client_info(canvas)
add_invoice_details(canvas)
add_line_items_table(canvas)
add_totals_section(canvas)
add_payment_info(canvas)
add_footer(canvas)

doc.write_to_string
end

private

# Define consistent brand colors
def light_green
[0.94, 1.0, 0.94]
end

def dark_green
[0.2, 0.35, 0.2]
end

# Generate unique invoice number
def generate_invoice_number
current_time = Time.current
year = current_time.strftime("%y")
month = current_time.strftime("%m")
random_number = rand(1000..9999)
"INV-#{year}#{month}-#{random_number}"
end

# Add header section
def add_header(canvas)
# Draw full-width header background
canvas.fill_color(*light_green)
canvas.rectangle(0, @y - 40, 595, 100)
canvas.fill

# Attempt to load and display company logo
begin
logo_path = Rails.root.join('app/assets/images/company_logo.png')
if File.exist?(logo_path)
canvas.image(logo_path.to_s, at: [@margin_left, @y - 20], width: 80)
end
rescue
# Continue without logo if file not found
end

# Draw INVOICE title
canvas.fill_color(*dark_green)
canvas.font('Helvetica', size: 36, variant: :bold)
canvas.text("INVOICE", at: [400, @y])

# Reset fill color to black
canvas.fill_color(0, 0, 0)
@y -= 80
end

# Add company information section
def add_company_info(canvas)
company = @invoice[:company]

canvas.font('Helvetica', size: 14, variant: :bold)
canvas.text(company[:name], at: [@margin_left, @y])

# Company contact details
canvas.font('Helvetica', size: 11)
canvas.text(company[:address], at: [@margin_left, @y -= 20])
canvas.text("#{company[:city]}, #{company[:state]} #{company[:zip]}", at: [@margin_left, @y -= 20])
canvas.text("Phone: #{company[:phone]}", at: [@margin_left, @y -= 20])
canvas.text("Email: #{company[:email]}", at: [@margin_left, @y -= 20])

end

# Add client billing information section
def add_client_info(canvas)
client = @invoice[:client]

# BILL TO
canvas.font('Helvetica', size: 14, variant: :bold)
canvas.text("BILL TO:", at: [@margin_left, @y -= 40])

# Client contact details
canvas.font('Helvetica', size: 11)
canvas.text(client[:name], at: [@margin_left, @y -= 20])
canvas.text(client[:address], at: [@margin_left, @y -= 20])
canvas.text("#{client[:city]}, #{client[:state]} #{client[:zip]}", at: [@margin_left, @y -= 20])
canvas.text("Phone: #{client[:phone]}", at: [@margin_left, @y -= 20])
canvas.text("Email: #{client[:email]}", at: [@margin_left, @y -= 20])
end

# Add invoice details
def add_invoice_details(canvas)
y_base = 700

# Labels for invoice information
canvas.font('Helvetica', size: 12, variant: :bold)
canvas.text("Invoice Number:", at: [340, y_base])
canvas.text("Issue Date:", at: [340, y_base - 20])
canvas.text("Due Date:", at: [340, y_base - 40])

# Values for invoice information
canvas.font('Helvetica', size: 11)
canvas.text(@invoice[:number], at: [460, y_base])
canvas.text(@invoice[:issue_date], at: [460, y_base - 20])
canvas.text(@invoice[:due_date], at: [460, y_base - 40])
end

# Create itemized services/products table
def add_line_items_table(canvas)
@y -= 60
table_width = 495
row_height = 30

# Draw table header
canvas.fill_color(*dark_green)
canvas.rectangle(@margin_left, @y, table_width, row_height)
canvas.fill

# Table header text
canvas.fill_color(1.0, 1.0, 1.0)
canvas.font('Helvetica', size: 11, variant: :bold)
canvas.text("Description", at: [60, @y + 10])
canvas.text("Qty", at: [315, @y + 10])
canvas.text("Price", at: [395, @y + 10])
canvas.text("Amount", at: [480, @y + 10])

# Draw each line item row
@y -= row_height
@invoice[:line_items].each_with_index do |item, index|
# Alternate row background colors
if index.even?
canvas.fill_color(0.97, 0.97, 0.97)
canvas.rectangle(@margin_left, @y, table_width, row_height)
canvas.fill
end

# Reset text color to black
canvas.fill_color(0, 0, 0)
canvas.font('Helvetica', size: 10)

# Truncate long descriptions to fit in column
description = item[:description].length > 35 ?
"#{item[:description][0..32]}..." :
item[:description]

# Draw row data in columns
canvas.text(description, at: [60, @y + 10])
canvas.text(item[:quantity].to_s, at: [320, @y + 10])
canvas.text("$#{sprintf('%.2f', item[:rate])}", at: [395, @y + 10])
canvas.text("$#{sprintf('%.2f', item[:total])}", at: [480, @y + 10])

@y -= row_height + 1
end

# Draw table bottom border
canvas.stroke_color(*dark_green)
canvas.line_width(2)
canvas.line(@margin_left, @y + row_height, 545, @y + row_height)
canvas.stroke
end

# Add financial totals section (subtotal, tax, total)
def add_totals_section(canvas)

# Calculate financial totals
subtotal = @invoice[:line_items].sum { |item| item[:total] }
tax_rate = @invoice[:tax_rate]
tax = subtotal * tax_rate
total = subtotal + tax

# Draw background box for totals
canvas.fill_color(0.96, 0.96, 0.96)
canvas.rectangle(345, @y - 85, 200, 85)
canvas.fill

# Reset text color and font for calculations
canvas.fill_color(0, 0, 0)
canvas.font('Helvetica', size: 12)

# Display subtotal
canvas.text("Subtotal:", at: [360, @y - 20])
canvas.text("$#{sprintf('%.2f', subtotal)}", at: [480, @y - 20])

# Display tax amount
canvas.text("Tax (#{(tax_rate * 100).round(1)}%):", at: [360, @y - 40])
canvas.text("$#{sprintf('%.2f', tax)}", at: [480, @y - 40])

# Draw separator line above total
canvas.stroke_color()
canvas.line_width(1)
canvas.line(355, @y - 50, 540, @y - 50)
canvas.stroke

# Display final total
canvas.font('Helvetica', size: 14, variant: :bold)
canvas.fill_color(*dark_green)
canvas.text("TOTAL:", at: [360, @y - 70])
canvas.text("$#{sprintf('%.2f', total)}", at: [470, @y - 70])

# Reset color
canvas.fill_color(0, 0, 0)
end

# Add payment instructions
def add_payment_info(canvas)
payment = @invoice[:payment_info]

# Payment information header
canvas.font('Helvetica', size: 12, variant: :bold)
canvas.text("Payment Information", at: [@margin_left, @y -= 10])

# Bank and payment details
canvas.font('Helvetica', size: 10)
canvas.text("Bank: #{payment[:bank_name]}", at: [@margin_left, @y -= 20])
canvas.text("Account: #{payment[:account_number]}", at: [@margin_left, @y -= 20])
canvas.text("Routing: #{payment[:routing_number]}", at: [@margin_left, @y -= 20])
canvas.text("Terms: #{payment[:terms]}", at: [@margin_left, @y -= 20])
end

# Add footer with company info and thank you message
def add_footer(canvas)
y_base = 60
# Draw separator line above footer
canvas.stroke_color(0.7, 0.7, 0.7)
canvas.line_width(1)
canvas.line(@margin_left, y_base, 545, y_base)
canvas.stroke

# Thank you message in brand color
canvas.font('Helvetica', size: 11, variant: :bold)
canvas.fill_color(*dark_green)
thank_you = "Thank you for your business!"
thank_you_width = thank_you.length * 6
x_center_thanks = (595 - thank_you_width) / 2
canvas.text(thank_you, at: [x_center_thanks, y_base -= 20])

# Company contact information in footer
canvas.font('Helvetica', size: 10)
canvas.fill_color(0.45, 0.45, 0.45)

company = @invoice[:company]
footer_text = "#{company[:name]} | #{company[:phone]} | #{company[:email]}"

# Center the footer text horizontally
text_width = footer_text.length * 5
x_center = (595 - text_width) / 2
canvas.text(footer_text, at: [x_center, y_base - 20])
end
end

Step 2: Create the Controller with Sample Data

The controller provides a complete working example with comprehensive sample data.

Click to view the controller implementation
# app/controllers/invoices_controller.rb
class InvoicesController < ApplicationController
def generate_hexapdf_invoice
# Example invoice data
invoice_data = {
issue_date: Date.current.strftime('%B %d, %Y'),
due_date: 30.days.from_now.strftime('%B %d, %Y'),
tax_rate: 0.08, # 8% tax rate

# Company details
company: {
name: "Example Software Solutions",
address: "123 Infinite Loopback Dr",
city: "Nulltown",
state: "ZZ",
zip: "40404",
phone: "(800) 555-0000",
email: "[email protected]"
},

# Client details
client: {
name: "404 Not Found Solutions",
address: "42 Byte Lane",
city: "Debug City",
state: "DE",
zip: "42424",
phone: "(424) 424-4242",
email: "[email protected]"
},

# Invoice items (products/services)
line_items: [
{
description: "HexaPDF Integration and Setup",
quantity: 40,
rate: 125.00,
total: 5000.00
},
{
description: "Custom Document Templates Design",
quantity: 16,
rate: 150.00,
total: 2400.00
},
{
description: "PDF Security Implementation",
quantity: 12,
rate: 200.00,
total: 2400.00
},
{
description: "Performance Optimization and Testing",
quantity: 20,
rate: 175.00,
total: 3500.00
},
{
description: "Documentation and Training",
quantity: 8,
rate: 100.00,
total: 800.00
}
],

# Payment information (bank details)
payment_info: {
bank_name: "Continental Business Bank",
account_number: "9876543210123456",
routing_number: "021000021",
terms: "Net 30"
}
}

# Generate PDF using HexaPDF service
pdf_service = HexapdfInvoiceService.new(invoice_data)
pdf_data = pdf_service.generate

# Timestamp for filename
file_timestamp = Time.now.strftime('%Y%m%d%H%M%S')

send_data pdf_data,
filename: "invoice_#{file_timestamp}.pdf",
type: "application/pdf",
disposition: "inline" # Display in browser
end
end

Step 3: Rails Routes Configuration

Add the invoice generation route to your Rails application:

# config/routes.rb
Rails.application.routes.draw do
get 'generate_hexapdf_invoice', to: 'invoices#generate_hexapdf_invoice'

# Optional: Add a root route for easy access
root 'invoices#generate_hexapdf_invoice'
end

Step 4: Testing HexaPDF Invoice Generator

Test your invoice generator by navigating to:

http://localhost:3000/generate_hexapdf_invoice

Features Showcased in This Invoice Generator:

  • Direct control over positioning, fonts, and layout.
  • Branded headers, color schemes, and typography hierarchy.
  • Precise control over table styling and alternating rows.
  • Automatic calculations, invoice numbering, and formatting.
  • Clean, testable, and maintainable code architecture.
  • Clean Rails controller integration and service architecture.

Here's the invoice output:

Professional business invoice with company branding, detailed line items table, and financial calculations generated using HexaPDF in Ruby on Rails

Rails PDF Generation Alternatives to HexaPDF

When HexaPDF's programmatic approach isn't suitable, consider these Rails PDF solutions:

SolutionBest Use CaseLearn More
GroverModern HTML to PDF conversion using Chrome's rendering engine, ideal for complex layouts.HTML to PDF with Grover
WickedPDFTraditional HTML to PDF conversion for simple layouts, suitable for basic styling.HTML to PDF with WickedPDF
PrawnPure Ruby programmatic PDF creation with simple API, best for straightforward document layouts.PDF Generation with Prawn
PDFBolt APICloud-based HTML to PDF service supporting modern CSS and JavaScript, eliminates server setup and handles complex rendering.HTML to PDF API Docs

Conclusion

HexaPDF provides a comprehensive solution for Ruby PDF generation, offering capabilities that extend well beyond basic document creation. Its pure Ruby implementation, sophisticated layout engine, and extensive PDF manipulation features make it a strong choice for Rails applications requiring advanced document workflows.

The combination of performance optimization, robust security features, and existing PDF manipulation distinguishes HexaPDF from simpler PDF libraries. Whether you're building document management systems, generating complex business reports, or implementing secure document workflows, HexaPDF provides the programmatic control and flexibility needed for professional applications.

For Rails developers seeking programmatic PDF creation with enterprise-grade capabilities, HexaPDF offers powerful features within the Ruby ecosystem. When you need HTML to PDF conversion, consider specialized solutions like PDFBolt HTML to PDF API for high-fidelity rendering with minimal infrastructure requirements.

Done. Your PDFs are now as solid as your test suite (hopefully). 💪