Generar PDFs en nuestras aplicaciones Web

Hoy en día muchas aplicaciones requieren la generación de algún tipo de documento que almacenar o mostrar al usuario como pueden ser la generación de facturas, cartas o justificantes, informes u otro tipo de documentos que podamos imaginar.

Generar PDFs en nuestras aplicaciones Web

En el post de hoy vamos a hablar sobre la generación de pdfs en nuestras aplicaciones web. Hoy en día muchas aplicaciones requieren la generación de algún tipo de documento que almacenar o mostrar al usuario como pueden ser la generación de facturas, cartas o justificantes, informes u otro tipo de documentos que podamos imaginar.

Hoy en día se trata de una tarea bastante común y existen distintas librerías para llevarlo a cabo. Por ello hoy vamos a conocer una herramienta que nos permite generar estos pdf a través de código html. Esta herramienta es wkhtmltopdf, una librería open source a través de linea de comandos que permite renderizar html dentro de un documento pdf empleando el motor de renderizado Webkit.

Vamos a ver un ejemplo de generación de un pdf a través de una maquetación html (http://adrianalonso.es/ejemplos/pdf-symfony2/index.html). Para ello vamos a emplear un bundle para el framework Symfony 2 que integra una librería que usa la herramienta wkhtml. Este bundle se llama KNPSnappyBundle y podeis encontrar toda la documentación sobre su instalación en su repositorio de github (https://github.com/KnpLabs/KnpSnappyBundle)

Tras instalar este Bundle podemos emplear el servicio knp_snappy.pdf el cual nos proporciona una serie de métodos para generar un documento pdf a través de un renderizado previo de html. Permite directamente almacenar este documento en el directorio deseado u obtener el output del resultado para poder devolverlo en la response de nuestro Action. En el siguiente código podemos ver un pequeña Action que renderiza nuestra plantilla html y se la pasa como parámetro al generador. Además este generador tiene gran cantidad de parametros para customizar nuestra generación. En el ejemplo he inicializao un array con todos los parámetros disponibles con los que podemos jugar para obtener el resultado deseado.

<?php

namespace Alonsus\ExampleBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class DefaultController extends Controller {

public function indexAction() {

$html = $this->renderView('AlonsusExampleBundle:Default:index.html.twig', array());

$pdf= $this->get('knp_snappy.pdf')->getOutputFromHtml($html, array(

'ignore-load-errors'           => null, // old v0.9

'lowquality'                   => false,

'collate'                      => null,

'no-collate'                   => null,

'cookie-jar'                   => null,

'copies'                       => null,

'dpi'                          => null,

'extended-help'                => null,

'grayscale'                    => false,

'help'                         => null,

'htmldoc'                      => null,

'image-dpi'                    => null,

'image-quality'                => null,

'manpage'                      => null,

'margin-bottom'                => 0,

'margin-left'                  => 0,

'margin-right'                 => 0,

'margin-top'                   => 0,

'orientation'                  => null,

'output-format'                => null,

'page-height'                  => null,

'page-size'                    => "A4",

'page-width'                   => null,

'no-pdf-compression'           => null,

'quiet'                        => null,

'read-args-from-stdin'         => null,

'title'                        => null,

'use-xserver'                  => null,

'version'                      => null,

'dump-default-toc-xsl'         => null,

'dump-outline'                 => null,

'outline'                      => null,

'no-outline'                   => null,

'outline-depth'                => null,

'allow'                        => null,

'background'                   => null,

'no-background'                => null,

'checkbox-checked-svg'         => null,

'checkbox-svg'                 => null,

'cookie'                       => null,

'custom-header'                => null,

'custom-header-propagation'    => null,

'no-custom-header-propagation' => null,

'debug-javascript'             => null,

'no-debug-javascript'          => null,

'default-header'               => null,

'encoding'                     => null,

'disable-external-links'       => null,

'enable-external-links'        => null,

'disable-forms'                => null,

'enable-forms'                 => null,

'images'                       => true,

'no-images'                    => null,

'disable-internal-links'       => null,

'enable-internal-links'        => null,

'disable-javascript'           => null,

'enable-javascript'            => null,

'javascript-delay'             => null,

'load-error-handling'          => null,

'disable-local-file-access'    => null,

'enable-local-file-access'     => null,

'minimum-font-size'            => null,

'exclude-from-outline'         => null,

'include-in-outline'           => null,

'page-offset'                  => null,

'password'                     => null,

'disable-plugins'              => null,

'enable-plugins'               => null,

'post'                         => null,

'post-file'                    => null,

'print-media-type'             => null,

'no-print-media-type'          => null,

'proxy'                        => null,

'radiobutton-checked-svg'      => null,

'radiobutton-svg'              => null,

'run-script'                   => null,

'disable-smart-shrinking'      => true,

'enable-smart-shrinking'       => null,

'stop-slow-scripts'            => null,

'no-stop-slow-scripts'         => null,

'disable-toc-back-links'       => null,

'enable-toc-back-links'        => null,

'user-style-sheet'             => null,

'username'                     => null,

'window-status'                => null,

'zoom'                         => 1.04,

'footer-center'                => null,

'footer-font-name'             => null,

'footer-font-size'             => null,

'footer-html'                  => "<h1>aqui</h1>",

'footer-left'                  => null,

'footer-line'                  => null,

'no-footer-line'               => null ,

'footer-right'                 => null,

'footer-spacing'               => null,

'header-center'                => null,

'header-font-name'             => null,

'header-font-size'             => null,

'header-html'                  => null,

'header-left'                  => null,

'header-line'                  => null,

'no-header-line'               => null,

'header-right'                 => null,

'header-spacing'               => null,

'replace'                      => null,

'disable-dotted-lines'         => null,

'cover'                        => null,

'toc'                          => null,

'toc-depth'                    => null,

'toc-font-name'                => null,

'toc-l1-font-size'             => null,

'toc-header-text'              => null,

'toc-header-font-name'         => null,

'toc-header-font-size'         => null,

'toc-level-indentation'        => null,

'disable-toc-links'            => null,

'toc-text-size-shrink'         => null,

'xsl-style-sheet'              => null,

'redirect-delay'               => null, // old v0.9

));

return new \Symfony\Component\HttpFoundation\Response(

$pdf, 200, array(

'Content-Type' => 'application/pdf',

'Content-Disposition' => 'inline; filename="file.pdf"',

));

}

}

La plantilla que renderizamos es una plantilla twig que puede recibir datos del controlador de tal manera que podemos generar documentos con datos particulares. Además todas las reglas css están embebidas en el propio documento html aunque también existe la posibilidad de externalizar estos documentos avisando al generador.

<html>

<meta charset="utf-8">

<head>

<title>Ejemplo Documento PDF</title>

<style type="text/css">

body { width: 21cm; margin: 0; padding: 0;  }

.page{width: 210mm;height: 297mm;}

table { border-spacing: 0; width: 100%; border: 1px solid black}

.break {page-break-after:always; margin-bottom: 1cm;}

h2 {width: 14cm; height:0.7cm; color: #000000; background-color: #E9EEF1; padding-left: 1.08cm; font-weight: bold; font-size: 0.57cm;}

.header{margin-left:1cm;margin-top:1cm;}

div.left-gray{width: 7cm;height:29.7cm;background-color: #E9EEF1;float:left;}

div.right-white{width: 14cm;height: 29.7cm;background-color: white;float:right;}

.right-white h1{margin-left: 1cm; margin-top: 1cm;color: red;padding-top:1cm}

.right-white p{margin-left: 1cm; width: 12cm;text-align: justify}

.left-gray h1 {margin-top: 1cm;margin-left: 1cm;padding: 0;display: inline}

.left-gray h3 {margin-left: 1cm;padding:0;display:inline;}

</style>

<head>

<body>

<div class="page">

<div class="left-gray">

<img src="default-placeholder.png" style="width:7cm;"

>

<h1>Documento</h1>

<h3>de prueba</h3>

</div>

<div class="right-white">

<h1 style="margin-top:1cm">Titulo de documento</h1>

<h2>Subtitulo de documento</h2>

<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod

tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,

quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo

consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse

cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non

proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>

<h1>Titulo de documento</h1>

<h2>Subtitulo de documento</h2>

<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod

tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,

quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo

consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse

cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non

proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>

<h1>Titulo de documento</h1>

<h2>Subtitulo de documento</h2>

<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod

tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,

quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo

consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse

cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non

proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>

</div>

</div>

<div class="break"></div>

<div class="page">

<div class="left-gray">

<img src="C:\xampp\htdocs\pdfexample\web\images\default-placeholder.png" style="width:7cm;"

>

<h1>Documento</h1>

<h3>de prueba 2</h3>

</div>

<div class="right-white">

<h1 style="margin-top:1cm">Titulo de documento</h1>

<h2>Subtitulo de documento</h2>

<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod

tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,

quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo

consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse

cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non

proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>

<h1>Titulo de documento</h1>

<h2>Subtitulo de documento</h2>

<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod

tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,

quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo

consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse

cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non

proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>

<h1>Titulo de documento</h1>

<h2>Subtitulo de documento</h2>

<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod

tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,

quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo

consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse

cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non

proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>

</div>

</div>

</body>

</html>

El ejemplo completo podéis encontrarlo en el siguiente repositorio de github: https://github.com/adrianalonso/knp-pdf-example

El resultado de la generación del pdf es el siguiente, como podemos observar es fiel al html maquetado y obtenemos el resultado deseado con tan solo conocer tecnologías web que permitan maquetar el documento a nuestro gusto.

pdfdocument

Como vemos generar documentos en PDF desde nuestras aplicaciones desarrolladas con Symfony2 es una tarea muy sencilla y configurable que nos abre gran posibilidades de funcionalidades extra que añadir a nuestras aplicaciones.

Últimos artículos