Gerando PDFs no Ruby com Prawn


Leia em 3 minutos

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.