Gerando PDFs A Partir de HTML com Rails
() translation by (you can also view the original English article)
Há inúmeras formas de gerar PDFs no Ruby e no Rails. Como você já conhece HTML e CSS, então usaremos o PDFKit para gerar arquivos PDFs usando HTML das visões e estilos padrões do Rails.
Introdução ao PDFKit
Internamente, o PDFKit usa o wkhtmltopdf (WebKit HTML to PDF), um mecanismo que pega HTML e CSS, renderiza-os usando Webkit e retorna-os como PDFs de alta qualidade.
Para começar, instalemos o wkhtmltopdf. Podemos baixar o binário ou instalá-lo através do Brew no Mac ou do repositório da sua versão Linux.
Também é preciso instalar a gem pdfkit
e executar o seguinte código Ruby para gerar um PDF com o texto "Hello Envato!".
1 |
require "pdfkit" |
2 |
|
3 |
kit = PDFKit.new(<<-HTML) |
4 |
<p>Hello Envato!</p>
|
5 |
HTML
|
6 |
|
7 |
kit.to_file("hello.pdf") |
Criamos um arquivo, chamado hello.pdf, com o texto no topo.



PDFKit também gera PDFs a partir de uma URL. Se quisermos gerar um PDF do Google, podemos executar:
1 |
require "pdfkit" |
2 |
|
3 |
PDFKit.new('https://www.google.com', :page_size => 'A3').to_file('google.pdf') |
Como vemos, especificamos page_size
—por padrão, A4 é usado. Veja a lista completa de opções.



Estilizando Seu PDF Usando CSS
Mais cedo, mencionamos que geraremos arquivos PDF usando HTML e CSS. Nesse exemplo, adicionei um pouco de CSS para estilizar o HTML de um fatura simples:
1 |
require "pdfkit" |
2 |
|
3 |
kit = PDFKit.new(<<-HTML) |
4 |
<style>
|
5 |
* {
|
6 |
color: grey;
|
7 |
}
|
8 |
h1 {
|
9 |
text-align: center;
|
10 |
color: black;
|
11 |
margin-bottom: 100px;
|
12 |
}
|
13 |
.notes {
|
14 |
margin-top: 100px;
|
15 |
}
|
16 |
|
17 |
table {
|
18 |
width: 100%;
|
19 |
}
|
20 |
th {
|
21 |
text-align: left;
|
22 |
color: black;
|
23 |
padding-bottom: 15px;
|
24 |
}
|
25 |
</style>
|
26 |
|
27 |
<h1>Envato Invoice</h1>
|
28 |
|
29 |
<table>
|
30 |
<thead>
|
31 |
<tr>
|
32 |
<th>Description</th>
|
33 |
<th>Price</th>
|
34 |
</tr>
|
35 |
</thead>
|
36 |
<tbody>
|
37 |
<tr>
|
38 |
<td>Monthly Subscription to Tuts+</td>
|
39 |
<td>$15</td>
|
40 |
</tr>
|
41 |
</tbody>
|
42 |
</table>
|
43 |
|
44 |
<div class="notes">
|
45 |
<p><strong>Notes:</strong> This invoice was paid on the 23rd of March 2016 using your credit card ending on 1234.</p>
|
46 |
</div>
|
47 |
HTML
|
48 |
|
49 |
kit.to_file("envato_invoice.pdf") |
Geraremos o arquivo envato_invoice.pdf se executarmos o script. A foto mostra o resultado do exemplo:



Como vemos, o PDFKit é bem fácil de usar, se já conhecemos HTML e CSS. Podemos customizar ou estilizar o documento o quanto quisermos.
Usando PDFKit A Partir de uma Aplicação Rails
Agora, vejamos como usar o PDFKit em uma aplicação Rails, para gerarmos arquivos PDF dinamicamente, usando dados de nossos modelos. Nessa seção, construiremos uma aplicação Rails simples para gerar a "Fatura Envato", dinamicamente. Criemos uma nova aplicação rails e adicionemos três modelos:
1 |
$ rails new envato_invoices
|
2 |
$ cd envato_invoices |
3 |
|
4 |
$ rails generate model invoice date:date client notes |
5 |
$ rails generate model line_item description price:float invoice:references
|
6 |
|
7 |
$ rake db:migrate
|
Agora, adicionemos alguns exemplos na base de dados. Adicione o código abaixo no arquivo db/seeds.rb
.
1 |
line_items = LineItem.create([ |
2 |
{ description: 'Tuts+ Subscription April 2016', price: 15.0 }, |
3 |
{ description: 'Ruby eBook', price: 9.90} ]) |
4 |
Invoice.create( |
5 |
client: 'Pedro Alonso', |
6 |
total: 24.90, |
7 |
line_items: line_items, |
8 |
date: Date.new(2016, 4, 1)) |
Execute rake db:seed
em seu terminal para adicionar uma fatura exemplo à base de dados.
Também queremos uma lista de faturas e o detalhamento ddas mesmas, então, usando geradores Rails, execute rails generate controller Invoices index show
para criar o controlador e as visões.
app/controllers/invoices_controller.rb
1 |
class InvoicesController < ApplicationController |
2 |
def index |
3 |
@invoices = Invoice.all |
4 |
end
|
5 |
|
6 |
def show |
7 |
@invoice = Invoice.find(params[:id]) |
8 |
end
|
9 |
end
|
app/views/invoices/index.html.erb
1 |
<h1>Invoices</h1> |
2 |
<ul>
|
3 |
<% @invoices.each do |invoice| %> |
4 |
<li>
|
5 |
<%= link_to "#{invoice.id} - #{invoice.client} - #{invoice.date.strftime("%B %d, %Y")} ", invoice_path(invoice) %> |
6 |
</li>
|
7 |
<% end %> |
8 |
</ul>
|
Precisamos modificar as rotas do Rails para direcionar para InvoicesController
por padrão, então, editemos config/routes.rb
:
1 |
Rails.application.routes.draw do |
2 |
root to: 'invoices#index' |
3 |
|
4 |
resources :invoices, only: [:index, :show] |
5 |
end
|
Inicie um servidor com rails server
e navegue até localhost:3000 para ver a lista de faturas.



app/views/invoices/show.html.erb
1 |
<div class="invoice"> |
2 |
<h1>Envato Invoice</h1> |
3 |
|
4 |
<h3>To: <%= @invoice.client %></h3> |
5 |
<h3>Date: <%= @invoice.date.strftime("%B %d, %Y") %></h3> |
6 |
|
7 |
<table>
|
8 |
<thead>
|
9 |
<tr>
|
10 |
<th>Description</th> |
11 |
<th>Price</th> |
12 |
</tr>
|
13 |
</thead>
|
14 |
<tbody>
|
15 |
<% @invoice.line_items.each do |line_item| %> |
16 |
<tr>
|
17 |
<td><%= line_item.description %></td> |
18 |
<td><%= number_to_currency(line_item.price) %></td> |
19 |
</tr>
|
20 |
<% end %> |
21 |
<tr class="total"> |
22 |
<td style="text-align: right">Total: </td> |
23 |
<td><%= number_to_currency(@invoice.total) %></span></td> |
24 |
</tr>
|
25 |
</tbody>
|
26 |
</table>
|
27 |
|
28 |
<% if @invoice.notes %> |
29 |
<div class="notes"> |
30 |
<p><strong>Notes:</strong> <%= @invoice.notes %></p> |
31 |
</div>
|
32 |
<% end %> |
33 |
</div>
|
O CSS para a página de detalhes dessa fatura pode ser movido para app/assets/stylesheets/application.scss.
1 |
.invoice { |
2 |
width: 700px; |
3 |
max-width: 700px; |
4 |
border: 1px solid grey; |
5 |
margin: 50px; |
6 |
padding: 50px; |
7 |
|
8 |
h1 { |
9 |
text-align: center; |
10 |
margin-bottom: 100px; |
11 |
}
|
12 |
.notes { |
13 |
margin-top: 100px; |
14 |
}
|
15 |
|
16 |
table { |
17 |
width: 90%; |
18 |
text-align: left; |
19 |
}
|
20 |
th { |
21 |
padding-bottom: 15px; |
22 |
}
|
23 |
|
24 |
.total td { |
25 |
font-size: 20px; |
26 |
font-weight: bold; |
27 |
padding-top: 25px; |
28 |
}
|
29 |
}
|
Então, ao clicarmos em uma fatura na listagem principal, veremos seu detalhamento:



Agora, estamos prontos para adicionar a funcionalidade de visualizar ou baixar faturas como PDF, à nossa aplicação Rails.
Classe InvoicePdf para Lidar com Renderização de PDF
Para renderizar as faturas como PDF, precisamos adicionar 3 gems ao Gemfile: PDFKit, render_anywhere e wkhtmltopdf-binary. Por padrão, Rails renderiza visões apenas em controladores, mas usando render_anywhere
, podemos renderizá-las em modelos do domínio ou em serviços em segundo plano.
1 |
gem 'pdfkit' |
2 |
gem 'render_anywhere' |
3 |
gem 'wkhtmltopdf-binary' |
Para não poluir nosso controlador com muita lógica, criaremos uma nova classe InvoicePdf
dentro de app/models
para envolver a lógica de geração do PDF.
1 |
require "render_anywhere" |
2 |
|
3 |
class InvoicePdf |
4 |
include RenderAnywhere |
5 |
|
6 |
def initialize(invoice) |
7 |
@invoice = invoice |
8 |
end
|
9 |
|
10 |
def to_pdf |
11 |
kit = PDFKit.new(as_html, page_size: 'A4') |
12 |
kit.to_file("#{Rails.root}/public/invoice.pdf") |
13 |
end
|
14 |
|
15 |
def filename |
16 |
"Invoice #{invoice.id}.pdf" |
17 |
end
|
18 |
|
19 |
private
|
20 |
|
21 |
attr_reader :invoice |
22 |
|
23 |
def as_html |
24 |
render template: "invoices/pdf", layout: "invoice_pdf", locals: { invoice: invoice } |
25 |
end
|
26 |
end
|
Essa classe receberá a fatura a ser renderizada por parâmetro, em seu construtor. O método privado as_html
lê as visões invoices/pdf
e layout_pdf
, que serão usadas para gerar o HTML necessário para a renderização do PDF. Por último, o método to_pdf
usa o PDFKit para salvar o PDF no diretório public do Rails.
Provavelmente, você gerará um nome dinâmico para o PDF para que não seja sobrescrito por acidente em sua aplicação real. Talvez até queira salvar o arquivo na AWS S3 ou em um diretório privado, mas essas ações estão além desse tutorial.
/app/views/invoices/pdf.html.erb
1 |
<div class="invoice"> |
2 |
<h1>Envato Invoice</h1> |
3 |
|
4 |
<h3>To: <%= invoice.client %></h3> |
5 |
<h3>Date: <%= invoice.date.strftime("%B %d, %Y") %></h3> |
6 |
|
7 |
<table>
|
8 |
<thead>
|
9 |
<tr>
|
10 |
<th>Description</th>
|
11 |
<th>Price</th>
|
12 |
</tr>
|
13 |
</thead>
|
14 |
<tbody>
|
15 |
<% invoice.line_items.each do |line_item| %>
|
16 |
<tr>
|
17 |
<td><%= line_item.description %></td> |
18 |
<td><%= number_to_currency(line_item.price) %></td> |
19 |
</tr>
|
20 |
<% end %>
|
21 |
<tr class="total"> |
22 |
<td style="text-align: right">Total: </td> |
23 |
<td><%= number_to_currency(invoice.total) %></span></td> |
24 |
</tr> |
25 |
</tbody> |
26 |
</table> |
27 |
|
28 |
<% if invoice.notes %> |
29 |
<div class="notes">
|
30 |
<p><strong>Notes:</strong> <%= invoice.notes %></p> |
31 |
</div>
|
32 |
<% end %>
|
33 |
</div>
|
/app/views/layouts/invoice_pdf.erb
1 |
<!DOCTYPE html> |
2 |
<html> |
3 |
<head> |
4 |
<title>Envato Invoices</title> |
5 |
<style>
|
6 |
<%= Rails.application.assets.find_asset('application.scss').to_s %>
|
7 |
</style> |
8 |
</head> |
9 |
<body>
|
10 |
<%= yield %>
|
11 |
</body> |
12 |
</html> |
Algo a se notar nesse arquivo de layout é que renderizamos os estilos dentro dele. WkHtmlToPdf funciona melhor se fizermos desse jeito.
DownloadsController para Renderizar o PDF da Fatura
Agora, precisamos de uma rota e um controlador que chame a classe InvoicePdf
para enviar o arquivo PDF para o navegador. Então, edite o arquivo config/routes.rb
, assim:
1 |
Rails.application.routes.draw do |
2 |
root to: "invoices#index" |
3 |
|
4 |
resources :invoices, only: [:index, :show] do |
5 |
resource :download, only: [:show] |
6 |
end
|
7 |
end
|
Se usarmos rake routes
, veremos a lista de rotas disponíveis na aplicação:
1 |
Prefix Verb URI Pattern Controller#Action |
2 |
root GET / invoices#index |
3 |
invoice_download GET /invoices/:invoice_id/download(.:format) downloads#show |
4 |
invoices GET /invoices(.:format) invoices#index |
5 |
invoice GET /invoices/:id(.:format) invoices#show |
Crie app/controllers/downloads_controller.rb
:
1 |
class DownloadsController < ApplicationController |
2 |
|
3 |
def show |
4 |
respond_to do |format| |
5 |
format.pdf { send_invoice_pdf } |
6 |
end
|
7 |
end
|
8 |
|
9 |
private
|
10 |
|
11 |
def invoice_pdf |
12 |
invoice = Invoice.find(params[:invoice_id]) |
13 |
InvoicePdf.new(invoice) |
14 |
end
|
15 |
|
16 |
def send_invoice_pdf |
17 |
send_file invoice_pdf.to_pdf, |
18 |
filename: invoice_pdf.filename, |
19 |
type: "application/pdf", |
20 |
disposition: "inline" |
21 |
end
|
22 |
end
|
Como podemos ver, quando a requisição buscar um arquivo PDF, o send_invoice_pdf
a processará. O método invoice_pdf
encontrará a fatura usando o ID e criará uma instância de InvoicePdf. Então, send_invoice_pdf
chamará o método to_pdf
, enviando o arquivo gerado para o navegador.
Outra coisa a se notar é que estamos passando disposition: "inline"
para send_file
. Esse parâmetro envia o arquivo para o navegador, que o mostrará na tela. Se quiser forçar o download do arquivo, você precisa passar disposition: "attachment"
.
Adicione um botão de download para a fatura na visão app/views/invoices/show.html.erb
:
1 |
<%= link_to "Download PDF", |
2 |
invoice_download_path(@invoice, format: "pdf"),
|
3 |
target: "_blank",
|
4 |
class: "download" %>
|
Execute a aplicação, navegue pelos detalhes da fatura, clique em download e veja uma nova abar mostrando a fatura em PDF.



Renderizando PDF como HTML Durante Desenvolvimento
Ao criar o código HTML para seu PDF, é preciso gerar um novo PDF toda vez que quiser testar uma mudança. Algumas vezes, isso pode ser lento. Por isso, pode ser bem útil poder visualizar o HTML gerado que será convertido em PDF. Apenas precisamos editar o arquivo /app/controllers/downloads_controller.rb
:
1 |
class DownloadsController < ApplicationController |
2 |
|
3 |
def show |
4 |
respond_to do |format| |
5 |
format.pdf { send_invoice_pdf } |
6 |
|
7 |
if Rails.env.development? |
8 |
format.html { render_sample_html } |
9 |
end
|
10 |
end
|
11 |
end
|
12 |
|
13 |
private
|
14 |
|
15 |
def invoice |
16 |
Invoice.find(params[:invoice_id]) |
17 |
end
|
18 |
|
19 |
def invoice_pdf |
20 |
InvoicePdf.new(invoice) |
21 |
end
|
22 |
|
23 |
def send_invoice_pdf |
24 |
send_file invoice_pdf.to_pdf, |
25 |
filename: invoice_pdf.filename, |
26 |
type: "application/pdf", |
27 |
disposition: "inline" |
28 |
end
|
29 |
|
30 |
def render_sample_html |
31 |
render template: "invoices/pdf", layout: "invoice_pdf", locals: { invoice: invoice } |
32 |
end
|
33 |
end
|
Agora, o método show
também lida com requisições HTML em modo de desenvolvimento. A rota para a fatura em PDF seria algo assim: http://localhost:3000/invoices/1/download.pdf. Se altararmos para http://localhost:3000/invoices/1/download.html, podemos ver o HTML usado para gerar o PDF da fatura.
Dado o código acima, gerar arquivos PDF usando Ruby on Rails é bem simples e direto, assumindo que seja familiar com a lingugem Ruby e a framework Rails. Talvez, o melhor aspecto desse processo é que não é preciso aprender uma nova linguagem ou coisas específicas sobre geração de PDF.
Espero que esse tutorial tenha sido útli. Por favor, faça quaisquer perguntas, comentários ou feedback na seção de comentário e ficarei feliz em responder!