Trabalhar com PDF é um problema em quase todas as linguagens. Existem algumas alternativas, mas quase sempre o processo é trabalhoso ou as ferramentas são caras demais. E é justamente pensando em resolver este problema que o Gregory Brown criou o Prawn, uma biblioteca Ruby extremamente simples de usar, muito rápida e completa, como você pode conferir neste artigo.

Instalando o Prawn

O Prawn está disponível no RubyGems e para instalá-lo, você só precisa executar o comando abaixo.

$ gem install prawn
Successfully installed prawn-0.11.1
1 gem installed

Se você quer usar o Prawn com o Ruby on Rails, lembre-se de adicionar a gem ao arquivo Gemfile.

source :rubygems
 
gem "rails", "3.0.6"
gem "prawn", "0.8.4"

Existe também o Prawnto, biblioteca que adiciona um novo handler de template, permitindo criar views com a extensão .prawn, mas que não cheguei a testar.

Criando PDFs

Eu usei o Prawn para gerar certificados para os workshops do HOWTO, muito solicitados por estudantes que querem usar a carga horária do workshop para fins acadêmicos. A intenção era gerar um PDF como este:

Certificado gerado com Prawn

Para criar um novo documento, você possui algumas alternativas. Você pode utilizar o método Prawn.generate(path, &block), que irá salvar o PDF gerando no caminho indicado, ou pode simplesmente instanciar um novo objeto da classe Prawn::Document. Nós vamos utilizar o segundo modo, já que iremos utilizar o objeto PDF para escrever testes (Sim! É possível testar PDF, como você vai ver à seguir).

Vamos criar uma classe Certificate, que vai permitir organizar melhor o nosso código.

class Certificate
  attr_accessor :path
 
  def initialize(path = nil)
       @path = path
     end
 
  # irá salvar o arquivo no caminho indicado
  def save; end
 
  # irá montar o PDF, propriamente dito
  def pdf; end
end

Neste exemplo, vou abstrair o modo como os dados são obtidos, para simplificar. Primeiro, vamos definir algumas opções de como o PDF será montado como tamanho e imagem de fundo. Estas opções serão armazenadas em uma constante.

class Certificate
  # ...
 
  PDF_OPTIONS = {
    :page_size   => "A5",
    :page_layout => :landscape,
    :background  => "public/images/cert_bg.png",
    :margin      => [40, 75]
  }
end

Agora, podemos montar o nosso PDF. Como você pode perceber, é bastante simples.

class Certificate
  # ...
 
  def pdf
    Prawn::Document.new(PDF_OPTIONS) do |pdf|
      pdf.fill_color "40464e"
      pdf.text "Ruby Metaprogramming", :size => 40, :style => :bold, :align => :center
 
      pdf.move_down 30
      pdf.text "Certificado", :size => 24, :align => :center, :style => :bold
 
      pdf.move_down 30
      pdf.text "Certificamos que <b>Nando Vieira</b> participou...", :inline_format => true
 
      pdf.move_down 15
      pdf.text "São Paulo, #{I18n.l(Time.now, :format => :short_date)}."
 
      pdf.move_down 30
      pdf.font Rails.root.join("fonts/custom.ttf")
      pdf.text "howto", :size => 24
 
      pdf.move_up 5
      pdf.font "Helvetica"
      pdf.text "http://howtocode.com.br", :size => 10
    end
  end
end

Os método Prawn::Document#move_up e Prawn::Document#move_down permitem alterar o posicionamento de objetos sem alterar o fluxo do documento. Já o método Prawn::Document#font permite definir uma fonte disponível no Prawn ou um caminho de uma fonte personalizada. Note também que o método Prawn::Document#text aceita uma opção :inline_format, que permite formatar o modo como o texto é exibido (negrito, itálico, etc).

Agora, podemos adicionar o método Certificate#save, que será responsável por salvar o PDF no caminho especificado no atributo Certificate#path.

class Certificate
  # ...
 
  def save
    pdf.render_file(path)
  end
end

Veja como é simples salvar um PDF; basta executar o método Prawn::Document#save, passando o caminho.

Como você viu, é muito simples gerar PDFs. Acesse a documentação para saber o que é possível fazer com o Prawn. Você pode adicionar imagens, tabelas, posicionar elementos, dentre muitas outras coisas.

Escrevendo testes

O mais interessante de todo esse processo é que é possível escrever testes validando o conteúdo gerado. O próprio Gregory Brown criou uma biblioteca chamada PDF Inspector, que embora ainda não tenha sido lançada como uma RubyGem, já pode ser usada através do repositório Git.

Abra o arquivo Gemfile e adicione o PDF Inspector no grupo de testes.

group :test do
  gem "pdf-inspector", "~> 1.0.0",
    :require => "pdf/inspector",
    :git => "https://github.com/sandal/pdf-inspector.git"
end

O exemplo abaixo mostra como você pode usar o RSpec para verificar PDFs.

describe Certificate do
  let(:cert) { Certificate.new("/tmp/certificate.pdf") }
  let(:pdf) { cert.pdf }
  let(:text) { PDF::Inspector::Text.analyze(pdf.render).strings.join(" ").squish }
 
  it "includes workshop name" do
    text.should contain("Ruby Metaprogramming")
  end
end

O método PDF::Inspector::Text#strings retorna um array de strings que, pelo que pude perceber, varia de acordo com quebras de linha e, por isso, preferi retornar uma única string normalizada.

O Ruby possui uma excelente alternativa para gerar PDFs que, felizmente, é bastante simples de usar. O código completo deste exemplo está disponível em https://gist.github.com/920904.

Comentários #

#1 Gabriel Sobrinho disse:
15 Abr 11, 10:13AM

Nando,

Não acha mais fácil usar o PDFKit e escrever HTML + CSS? :)

#2 Nando Vieira disse:
15 Abr 11, 10:34AM

Nesse caso eu prefiro usar o Prawn. Não tenho que ficar lidando com templates nem nada disso.

#3 Leo disse:
18 Abr 11, 08:48AM

Nando, uma vez tentamos usar o Prawn mas não conseguimos usar o alinhamento justificado. Você já testou isso? Conhece uma solução?

#4 Nando Vieira disse:
18 Abr 11, 02:28PM

Particularmente, não gosto de textos justificados e, por isso, nem cheguei a testar! :)

#5 Lucas Catón disse:
23 Abr 11, 03:49PM

Minha opinião sobre o Prawnto: não recomendo!

Primeiro fator: de 2009 pra cá, ele só teve 5 commits - https://github.com/smecsia/prawnto/commits/master.

Além disso, depois do lançamento do Rails 3, ele demorou uns 4 meses pra ganhar suporte.

Outro fator importante, é que testar a geração dos PDFs com o Prawnto fica beeem mais difícil (ou impossível), do que fazer apenas com o Prawn.

E pra finalizar, o Prawn sozinho já é ótimo. Pra que simplificar o que já é simples?!

Valeu Nando, abraço!

#6 Vécio Peixoto disse:
06 Jun 11, 04:56PM

Nando, com o Prawn é possível alterar o conteúdo de um PDF, como por exemplo acrescentar uma linha no rodapé, ou colocar uma marca d'água, em um PDF préexistente? Caso não Prawn não consiga, conheces alguma gem que possa fazer isso em rails?

#7 Nando Vieira disse:
06 Jun 11, 06:36PM

Até onde sei, o Prawn só permite criar novos documentos. Talvez você consiga fazer isso com o Ghostscript.

#8 Victor Hugo Souza disse:
19 Jun 11, 02:26AM

Nando, tentei usar seu modelo de código pra gerar os certificados através de uma lista de nomes que coloquei em uma array mas cada PDF sai com uma configuração.

http://pastie.org/2090209

Dos tres nomes na Array os dois primeiros saem em Retrato(Portrait) e só o ultimo sai em Landscape que foi a configuração q eu coloquei.

Sabe oq pode ter ocorrido?

#9 Nando Vieira disse:
20 Jun 11, 12:10AM

Isso é um bug do Prawn que eu reportei, inclusive.

https://github.com/sandal/prawn/issues/232

Não sei se a correção já foi publicada. Para corrigir, basta fazer um dup das opções: Prawn::Document.new(PDF_OPTIONS.dup).

#10 Filipe Ricardo disse:
09 Fev 12, 07:10PM

Olá Nando,

da versão 0.8.4 para a 0.11.1 tem um ganho significativo de velocidade?

eu acho um tanto lento a geração de um pdf com muitas páginas, piora um pouco com o uso de tabelas,
tem uma dica para turbinar o prawn, ou sabe um outro gerador de relatório em pdf rápido?

Deixe um comentário





Não é aceito código HTML: adicione-o no pastie.org ou paste.milk-it.net e poste apenas o link.

Se este é seu primeiro comentário, ele terá que ser aprovado antes de ser exibido.

JavaScript Avançado

O JavaScript é a única linguagem que muitos acreditam saber sem nunca terem parado para realmente aprendê-la. Neste workshop rápido você entenderá de verdade todos os conceitos avançados do JavaScript em 4 horas puramente práticas.

Saiba mais Fechar

Conheça também o HOWTO