Go to Hackademy website

Générer des PDF depuis votre application Rails

Alexandre Salaun

Posté par Alexandre Salaun dans les catégories back

Dans la longue liste des fonctionnalités demandées lors du développement d’une application Rails il est parfois mentionné le fait de devoir générer des fichier PDF. Ceci est surtout vrai avec des sites e-commerce par exemple, où il faut générer des factures, des devis, des bons de commande… Dans cet article, nous allons donc voir comment générer ces fichiers et quels outils utiliser pour se simplifier la tâche car oui, comme pour presque toutes les fonctionnalités dans Rails, il y a une gem (même plusieurs) pour ça.

Wkhtmltopdf

Wkhtmltopdf n’est pas une gem rails mais un utilitaire proposant des commandes shell afin de convertir des pages HTML en document PDF.

Pour l’installer, il est possible d’utiliser brew si vous êtes sous mac :

brew install wkhtmltopdf

ou de l’installer via apt-get sous Linux :

apt-get install wkhtmltopdf

Attention certaines dépendances sont nécessaires au bon fonctionnement de cet outil, suivez bien les instructions lors de l’installation. Afin de vérifier que l’installation s’est bien passée, vous pouvez lancer la commande suivante :

wkhtmltopdf –help

Une fois que Wkhtmltopdf est installé, vous pouvez convertir des pages HTML en document PDF :

wkhtmltopdf www.google.fr google.pdf

Voici un aperçu de ce que cela donne :

Wkhtmltopdf Google

Dans une application Rails, il est possible d’exécuter des commandes shell donc vous pouvez tout à fait créer vos vues HTML comme vous le feriez pour n’importe quelle page et ensuite les convertir pour obtenir des PDFs. Cependant, ce n’est pas la solution la plus pratique, ni la plus agréable. Il existe donc des gems qui utilisent elles-mêmes Wkhtmltopdf pour générer les PDF de façon à ce que se soit plus simple pour vous.

Wicked PDF

La première de ces gems est Wicked PDF. Elle vous permet de créer des vues dans votre application qui seront ensuite converties en PDF. Cette gem utilise Wkhtmltopdf donc il est nécessaire de l’avoir auparavant installé ou bien d’utiliser une autre gem qui inclura tous les binaires nécessaires.

gem 'wkhtmltopdf-binary' # si Wkhtmltopdf n'est pas déjà installé
gem 'wicked_pdf'

Lancez ensuite la commande bundle install afin d’ajouter les gems à l’application.

Il est parfois nécessaire de spécifier le chemin vers l’exécutable de Wkhtmltopdf si il ne figure pas dans le path de votre machine ou de votre serveur. Pour cela, il suffit de créer un initializer et d’y ajouter le chemin :

WickedPdf.config = {
  exe_path: '/usr/local/bin/wkhtmltopdf'
  # à remplacer par le chemin correct sur votre machine
}

Maintenant que l’installation est finalisée, nous allons pouvoir utiliser Wicked PDF pour générer nos documents. Dans votre controller, vous devez ajouter le format PDF aux actions souhaitées. Dans noter cas, nous ajoutons le format PDF à l’action show :

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end

  def show
    respond_to do |format|
      format.html { render :show }
      format.pdf { 
        render :pdf => "show", :layout => 'pdf.html'
      }
    end
  end
end

Ce controller peut donc renvoyer un PDF pour l’action show si le format “pdf” est spécifié. Il faut ajouter, dans le répertoire app/views/layouts, un fichier pdf.html.haml qui servira de layout aux fichiers PDFs générés. Ensuite, créez un fichier show.pdf.haml et ajoutez-le dans le répertoire app/views/posts comme vous le feriez pour simple une vue HTML. Comme vous pouvez le voir, le fichier a pour extension .pdf.haml (il est aussi possible d’utiliser erb : .pdf.erb).

Le fichier de layout des fichiers PDFs, pdf.html.haml, ressmble fortement au layout de n’importe quelle application :

%html
  %head
    %meta{ "http-equiv" => "content-type", content: "text/html; charset=utf-8" }
  %body
    #header
      Ma Société test

    #content
      = yield

    #footer
      © Ma société test

La vue quant à elle est identique à n’importe quelle vue de l’application :

%h2 Articles

%h3= @post.title

.post-content
  = @post.content

Voici ce que vous devez obtenir :

PDF Wicked PDF

Vous avez donc générer un fichier PDF, cependant, le style reste très sommaire. Il est possible d’ajouter des fichiers CSS et des images comme vous pourriez le faire pour une vue HTML. Wicked PDF propose des helpers équivalents à stylesheet_link_tag ou javascript_include_tag :

%html
  %head
    = wicked_pdf_stylesheet_link_tag "pdf"
    %meta{ "http-equiv" => "content-type", content: "text/html; charset=utf-8" }
    = wicked_pdf_javascript_include_tag "pdf"
  %body
    #header
      = wicked_pdf_image_tag 'logo.png'
      Ma Société test

    #content
      = yield

    #footer
      © Ma société test

Vous pouvez donc simplement styler votre PDF avec un fichier CSS :

body {
  height: 240mm;
}
h3 {
  color: #9E9E9E;
}
#header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
}
#header img {
  display: inline-block;
}
#header p.company {
  vertical-align: top;
  display: inline-block;
  margin-left: 10px;
  font-size: 20px;
}
#content {
  margin-top: 100px;
}
#footer {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  text-align: center;
}

Vous obtenez donc un PDF avec une image et un style appliqué. À vous maintenant de personnaliser le fichier comme vous le souhaitez.

PDF Wicked PDF

Wicked PDF propose un nombre conséquent d’options afin de personnaliser le PDF généré, elles sont disponibles sur la page Github.

Prawn

Prawn est une autre solution permettant de générer des PDFs depuis une paplication Rails.

Pour l’installer, il suffit d’ajouter la gem au Gemfile :

gem 'prawn'

et de créer un initializer afin d’inclure Prawn dans l’application :

require "prawn"

Contrairement à Wicked PDF, Prawn ne permet pas de générer une vue afin de la convertir en PDF. Il est nécessaire de créer une classe pour pouvoir générer le document au format PDF avec le contenu souhaité comme suit :

class PostDocument < Prawn::Document
  def to_pdf(post)
    text "Article : #{post.title}", :size => 30, :style => :bold
    move_down(30)
    text "#{post.content}"
    move_down(10)
    text "Auteur : #{post.author.fullname}", :size => 16, :style => :bold

    render
  end
end

Cette classe sera appelée depuis votre controller afin de générer le PDF :

class PostsController < ApplicationController
  ...

  def show
    @post = Post.find params[:id]
    pdf_content = PostDocument.new(@post).to_pdf

    respond_to do |format|
      format.pdf do
        send_data pdf_content, filename: "post_#{@post.id}.pdf", type: "application/pdf", disposition: "inline"
      end
    end
  end
end

Voici le PDF généré par Prawn :

PDF Wicked PDF

Avec Prawn, il n’est pas possible d’utiliser de fichier CSS car ce n’est pas une vue qui est créé pour générer le PDF, tous les éléments doivent être “stylisé” directement dans la classe en passant en option les attributs souhaités comme on peut le voir ici :

text "Article : #{post.title}", :size => 30, :style => :bold

On définit la taille de la police, le style… il est possible de définir d’autres attributs. Le manuel de Prawn permet de voir tout ce qu’il est possible de faire avec, néanmoins, je trouve plus pratique de créer une vue comme pour une page lambda pour qu’ensuite elle soit transformée en PDF.

Il existe un Railscast concernant Prawn que vous pouvez visualiser pour plus d’informations.

Prince

Prince est un outil permettant, à l’instar de Wkhtmltopdf, de convertir des documents HTML en PDF. Un guide d’installation de Prince est disponible après avoir téléchargé les fichiers nécessaires sur la page de téléchargement du site.

Après avoir installé Prince, vous devez ajouté la gem princely à votre Gemfile :

gem 'princely'

Prince est donc maintenant installé et utilisable depuis votre application Rails.

Comme pour Wicked PDF, vous allez, dans le controller spécifier un certain nombre d’options permettant de générer le PDF telles que le template à utiliser, le layout et les fichiers CSS si besoin…

class PdfController < ApplicationController
  ...

  def show
    @post = Post.find params[:id]

    respond_to do |format|
      format.pdf do
        render pdf:         "article_#{@post.id}.pdf",
               template:    "views/posts/show.pdf.haml",
               stylesheets: ["pdfs/global.css"],
               layout:      "layouts/pdf.html.haml",
               disposition: "inline"
      end
    end
  end
end

Pour l’exemple, j’ai repris la même vue que pour Wicked PDF :

%h2 Articles

%h3= @post.title

.post-content
  = @post.content

En ce qui concerne le layout, j’ai retiré les appels aux helpers de Wicked PDF mais là encore c’est sensiblement la même chose :

%html
  %head
    %meta{ "http-equiv" => "content-type", content: "text/html; charset=utf-8" }
  %body
    #header
      = image_tag 'rails.png'
      %p.company Ma Société test

    #content
      = yield

    #footer
      © Ma société test

Le fichier CSS a, lui aussi, été légèrement modifié :

h3 {
  color: #9E9E9E;
}

#header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
}

#header img {
  display: inline-block;
}

#header p.company {
  vertical-align: 30px;
  display: inline-block;
  margin-left: 10px;
  font-size: 20px;
}

#content {
  margin-top: 200px;
}

#footer {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  text-align: center;
}

Attention, il faut bien passé en paramètres à Princely des fichiers CSS contenant le style et non pas le manifest.

Voici le fichier PDF généré par Princely :

PDF Wicked PDF

Prince ajoute son logo en haut à droite du fichier généré dans la version gratuite, il existe des versions payantes pour un usage commerciale.

Conclusion

Il existe donc plusieurs solutions afin de générer des fichiers PDFs depuis une application Rails, chacun proposant un choix assez large d’options. J’ai pour habitude d’utiliser Wicked PDF, je suis donc preneur des retours d’expériences que vous avez sur ces différents outils (et même ceux non cités ci-dessus).

Vous pouvez voir sur Ruby Toolbox une liste de gem disponibles pour la génération de PDFs.

L’équipe Synbioz.

Libres d’être ensemble.

Articles connexes

Une brève histoire d'Elixir et Erlang/OTP

31/01/2019

Je développe depuis plusieurs années en Ruby. Depuis mon arrivée chez Synbioz, j’expérimente en plus avec Elixir de façon assez naturelle. En quoi Elixir est-il différent, me demanderez-vous ? Pour...

Écrire une thread pool en Ruby

10/01/2019

Pouvoir exécuter plusieurs tâches en parallèle, que ce soit dans un script ou une application, peut être vraiment très utile, surtout dans le cas où le traitement de ces tâches peut être très long....

Translation temporelle

31/05/2018

Cette semaine, je me suis essayé à un nouveau format d’article qui se présente sous la forme d’une nouvelle de Science-Fiction. Je tiens en passant à remercier Valentin pour ses illustrations....

Authentifier l'accès à vos ressources avec Dragonfly

11/05/2017

Pour ceux qui ne connaissent pas Dragonfly, c’est une application Rack qui peut être utilisée seule ou via un middleware. Le fait que ce soit une application Rack la rend compatible avec toutes les...