#220 PDFKit
- Download:
- source codeProject Files in Zip (109 KB)
- mp4Full Size H.264 Video (19.4 MB)
- m4vSmaller H.264 Video (10.9 MB)
- webmFull Size VP8 Video (24.7 MB)
- ogvFull Size Theora Video (26.8 MB)
Ci sono una serie di buone librerie disponibili per generare file PDF in Ruby. Una delle più popolari è Prawn, che è sicuramente un ottimo modo per generare PDF da zero in Ruby e che è stato trattato nell’episodio 153 [guardalo, leggilo]. In alcune situazioni, tuttavia, può essere più semplice generare un PDF da un documento HTML già esistente, ed è ciò che faremo vedere in questo episodio.
La creazione di documenti PDF a partire dall’HTML non è un’idea nuova, ma fino ad ora la maggior parte delle soluzioni disponibili era a pagamento. Di recente c’è stata parecchia attività sull’argomento, grazie ad una soluzione proposta da Jared Pace e del suo nuovo PDFKit gem. Questo gem dipende da wkhtmltopdf, uno strumento che usa il motore di rendering WebKit per generare documenti PDF, per cui, per installare PDFkit, dovrete innanzitutto installare wkhtmltopdf. PDFKit è distribuito in bundle con wkhtmltopdf, per cui, a seconda del vostro ambiente, ciò potrebbe non essere necessario.
Il gem PDFKit si installa al solito modo:
$ gem install pdfkit
Una volta installato, PDFKit è in grado di generare un file PDF puntando semplicemente ad un file o ad un sito web. In alternativa, il gem viene distribuito anche come middleware rack, una soluzione che può essere sfuttata per generare PDF di qualsiasi pagina di un sito, semplicemente aggiungendo .pdf
all’ URL. Useremo questo approccio a middleware nella nostra applicazione dimostrativa.
Creazione di fatture in PDF
L’applicazione che useremo è la stessa già usata nell’episodio su Prawn. Di sotto è mostrata la pagina dell’ordine di una semplice applicazione di e-commerce. Vogliamo aggiungere un link a questa pagina che permetta di avere una versione PDF dell’ordine da scaricare e useremo PDFKit per farlo.
La prima cosa da fare è aggiungere un riferimento a PDFkit nella nostra applicazione. Dal momento che si tratta di una applicazione Rails 3, modifichiamo il Gemfile
per farlo:
gem "pdfkit"
Poi, per assicurarci che il gem venga installato, lanciamo:
$ bundle install
Poi dobbiamo aggiungere il middleware. In un’applicazione Rails 3, questo lo si fa nel file /config/application.rb
. (Se avessimo dovuto farlo in un’applicazione Rails 2, invece, saremmo dovuti andare nel file di configurazione dell’ambiente.) Modifichiamo il file affinchè l’applicazione usi il middleware PDFKit:
require File.expand_path('../boot', __FILE__) require 'rails/all' # Auto-require default libraries and those for the current Rails environment. Bundler.require :default, Rails.env module Store class Application < Rails::Application config.secret_token = '6f22fa632e18b338b4babfa5fca632f5454fc97317cb52f372fa0f0fdd7f4d5bd95a060ff412c7230627b5c17906c9762c09208624bc1ab97f8d5344d8d4f467' config.filter_parameters << :password config.middleware.use "PDFKit::Middleware" end end
Per vedere quali middleware sta usando la nostra applicazione, possiamo lanciare il comando rake middleware
come facciamo ora per sincerarci che il nostro PDFKit sia nella lista dei middleware:
$ rake middleware (in /Users/asalicetti/rails/apps_for_asciicasts/ep220/store) use ActionDispatch::Static use Rack::Lock use ActiveSupport::Cache::Strategy::LocalCache use Rack::Runtime use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::RemoteIp use Rack::Sendfile use ActionDispatch::Callbacks use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use Rack::MethodOverride use ActionDispatch::Head use PDFKit::Middleware run Store::Application.routes
PDFKit::Middleware
è presente nell’elenco, per cui possiamo andare avanti. Ora possiamo aggiungere .pdf
a qualunque URL della nostra applicazione per ottenere una versione PDF di quella pagina. Proviamolo nella pagina degli ordini:
Tutto qua. Per ora non abbiamo dovuto fare praticamente nulla per ottenere una versione PDF dell’ordine. Potrebbe essere prodotta meglio, certamente, ma comunque abbiamo già una buona base da cui partire.
Miglioriamo l’aspetto dell’ordine in PDF
La prima modifica che faremo all’ordine sarà rimuovere lo sfondo blu dalla versione PDF. Dobbiamo poter identificare una versione della risorsa in PDF, in modo tale da poter applicargli uno stile diverso. Possiamo fare tutto ciò cambiando la chiamata al middleware, affinchè PDFKit utilizzi il media type print:
config.middleware.use "PDFKit::Middleware", :print_media_type => true
Nel file di layout della nostra applicazione abbiamo un stylesheet_link_tag
che include un tag application.css
. Per default questo foglio di stile ha un media type pari a screen
, che indica che, viste le recenti modifiche al middleware PDFKit, nessuno degli stili presenti in tale foglio di stile verrà applicato alla versione PDF. Modifichiamo il stylesheet_link_tag
e impostiampogli all
come media type, affinchè tutti gli stili vengano applicati sia alla versione della pagina da monitor (HTML), sia a quella da stampa:
<%= stylesheet_link_tag "application", :media => 'all' %>
Potremmo creare (sarebbe meglio dal punto di vista dell’ordine) un foglio di stile a parte per il formato da stampa, ma in questo episodio includiamo le regole per il print media nello stesso foglio di stile contenente anche gli altri stili CSS. In fondo a questo file, possiamo aggiungere una regola media type e qualsiasi regola definita all’interno di questa sezione si applicherà esclusivamente ai PDF. Per rimuovere lo sfondo blu dalla versione PDF, aggiungiamo il seguente CSS in fondo al foglio di stile della nostra applicazione:
@media print { body { background-color: #FFF; } #container { width: auto; margin: 0; padding: 0; border: none; } }
Al ricaricamento del PDF del nostro ordine, ora lo sfondo blu sarà scomparso.
Questo PDF è ora molto simile a quello creato nell’episodio su Prawn, ma abbiamo dovuto scrivere molto meno codice per ottenere lo stesso risultato che non se avessimo fatto la stessa cosa con Prawn. Prawn presenta ancora dei vantaggi, comunque, dal momento che ci permette di avere molto più controllo sul modo in cui viene generato il PDF, in particolare quando si prova a creare documenti multi pagina. Se la tabella degli ordini dovesse debordare su più pagine, cominceremmo ad avere delle difficoltà a controllare il modo in cui le intestazioni e i piè di pagina vengono mostrati su ciascuna pagina.
Controllare le interruzioni di pagina
PDFkit ci fornisce un po’ di controllo sulle interruzioni di pagina. Aggiungendo un paio di paragrafi di testo in cima all’ordine, in modo tale da far slittare in basso la tabella sottostante, vedremo che quest’ultima viene divisa su due pagine:
Possiamo evitare questo comportamento semplicemente agendo sugli stili CSS, in modo da fare aggiungere un’interruzione di pagina subito prima della tabella, così che questa appaia sempre in cima a una nuova pagina. Usiamo la regola page-break-before
per ottenere questo risultato:
@media print { body { background-color: #FFF; } #container { width: auto; margin: 0; padding: 0; border: none; } #line_items { page-break-before: always; } }
Quando ricarichiamo il documento PDF, la tabella apparirà, come volevamo, in una pagina a parte:
Dunque, abbiamo un minimo di controllo sulle interruzioni di pagina con PDFkit, ma per la generazione di documenti più complessi è meglio affidarsi a Prawn.
C’è un’ultima modifica che vogliamo fare al documento dell’ordine per concludere questo episodio. Aggiungiamo un link in fondo alla pagina HTML dell’ordine che punti alla versione PDF. Per prima cosa aggiungiamo un link al file PDF in fondo alla pagina:
<p><%= link_to "Download Invoice (PDF)", order_path(@order, :format => "pdf") %></p>
Questa modifica ci fornisce un link dalla maschera HTML al corrispettivo PDF, ma il link comparirà erroneamente anche nel file PDF. Possiamo nasconderlo sul PDF, fornendo un id all’elemento paragrafo che contiene il link:
<p id="pdf_link"><%= link_to "Download Invoice (PDF)", order_path(@order, :format => "pdf") %></p>
e modificando il foglio di stile dell’applicazione in modo tale da nascondere il collegamento alla versione PDF:
@media print { body { background-color: #FFF; } #container { width: auto; margin: 0; padding: 0; border: none; } #line_items { page-break-before: always; } #pdf_link { display: none; } }
Ora la versione HTML dell’ordine mostrerà il link alla versione PDF, ma in quest’ultima non comparirà più, come desideravamo.
Una alternativa
Prima di chiudere l’episodio, citiamo un’alternativa a PDFkit, Wicked PDF. Questo plugin usa un approccio diverso rispetto a quello adottato da PDFkit, pur usando anch’esso wkhtmltopdf. Wicked PDF non è un middleware, ma usa un renderer Rails. Wicked PDF ci fornisce un maggiore controllo su quali action possano essere renderizzate in formato PDF, per cui vale la pena di tenerlo in considerazione in una eventuale scelta.
E’ tutto per questo episodio. Abbiamo a questo punto trattato due metodologie piuttosto diverse fra loro per la generazione di documenti PDF all’interno di applicazioni Rails, ciascuna con i propri pro e contro. Sebbene PDFkit sia uno strumento estremamente semplice per la generazione di PDF a partire dall’HTML, Prawn ci fornisce di contro un maggior controllo al prezzo della minor semplicità d’uso. Entrambi gli approcci hanno il loro dominio applicativo e meritano pertanto di essere parimenti presi in considerazione.