|

Laboratorio: creando documentos PDF con Zend Framework

El otro día contábamos cómo eran las clases del framework de Zend para crear documentos PDF, también decíamos que el ejemplo de uso iría otro día, pues aquí vamos a explicar cómo realizar un PDF con una imagen como cabecera y un texto que se indique por un formulario.

También comentaba que según lo visto, el framework de Zend me gustaba bastante, pero debo decir que la documentación está un poco verde y que algunas de las opciones que parecen activadas no lo están (para darme cuenta de esto tuve que indagar por el código fuente).

pdf-ejemplo.png

El crear un PDF parece a primera vista algo sencillo a simple vista (¡qué optimista!), pensaba que sería algo como: crear documento, crear página y ponerte a escribir. Todo bien hasta que tienes que escribir, porque más que escribir tienes que maquetar, decir que texto va en tal lugar, no vale con escribir un párrafo que directamente y que el Acrobat Reader lo coloque como pensamos que debería ir. No, tenemos que ir calculando lo que va a ocupar la línea, y si no se sale de la página, escribirla.

Para empezar es necesario explicar como funciona la clase PDF o cómo creo yo que funciona, ya que la documentación no está del todo detallada y la ingeniería inversa ha sido fundamental para enterarme de cómo iba esto. Como he dicho anteriormente, hay que pensar en un documento en PDF como si fuera maquetación y no edición, no se puede decir (o al menos mediante estas clases) escribo este párrafo y luego otro. Los párrafos se deben dividir en líneas, calculando cuánto debe ocupar dentro de la página PDF y luego ir escribiéndolas una a una y cuando se nos acabe la página crearnos otra nueva.

Ahora el problema viene cuando queremos saber cuánto ocupa una línea de texto, para saberlo necesitamos dos cosas, saber el ancho del PDF y saber lo que ocupa cada carácter que vamos a escribir.

El ancho del PDF es sencillo de saber, según el tipo de página, pues tendrá unas medidas u otras, además viene indicado en la documentación. En nuestro caso se trata de un DIN-A4 y las medidas de la página son 595×842. Las unidades en lo que se mide no lo tengo nada claro, pero en mi “yo interno” les llamo unidades PDF.

Bastante más difícil es el tema de las fuentes. Las fuentes se dividen en glyphs, que son cada uno de los caracteres que se muestran con la fuente, y lógicamente cada glyph tiene su medida, no ocupa lo mismo la I que la W. Existe una función que te devuelve el ancho del glyph (widthForGlyph), pero solo está implementado para las fuentes que vienen por defecto, cuando metemos un TTF propio o le ponemos nosotros el tamaño de cada glyph o tomamos un tamaño por defecto. Esperemos que la próxima versión incluya está funcionalidad.

pdf-glyphs.png

Y como no hay dos sin tres, pues el tercer problema con el que me encontré es saber que relación hay entre las medidas PDF y las medidas glyph, y después de buscar y buscar en Google llegué a la conclusión de que no hay nada mejor que la “cuenta la vieja”. Explico el método escribí una serie de W (que es la que más ocupa) hasta el final de la página, le añadí I (que es la que menos ocupa) hasta rellenar todo el ancho de la página. Multipliqué el número de W por su ancho y lo sumé al número de I por su tamaño. El resultado lo dividí por el ancho de la página y me dió la relación que hay entre la “medida PDF” y la medida glyph. Con esto ya lo tengo todo para saber cual es el ancho de una línea.

Una vez descubierto la relación de medidas entre la página PDF y la fuente, el resto es bastante sencillo: tenemos un array en el que vamos almacenando el ancho de los glyphs que van apareciendo, ir tratando el texto, calculando las líneas, teniendo en cuenta que no corten palabras y ejecutando los retornos de carro. A parte habrá un cursor que nos indicará la posición vertical dentro de la página y cuando pasemos el alto de la página, creemos una nueva y continuemos por ella.

Después de tanta explicación, creo que no habrá mucho problema para entender el código.

<?php
header("Content-Type: application/pdf");
//header("Content-Disposition: attachment; filename=\"prueba.pdf\"");
require_once 'Zend/Pdf.php';
function creaPagina() {
global $pdf;
global $tamaño_letra;
global $font;
$pdf->pages[] = ($pagina = $pdf->newPage('A4'));
// Creamos el estilo de la fuente
$estilo = new Zend_Pdf_Style();
$estilo->setFillColor(new Zend_Pdf_Color_RGB(0, 0, 0));
// Fuente Helvetica
$pagina->setFont($font, $tamaño_letra);
$pagina->setStyle($estilo);
return $pagina;
}
$pdf_glyphs = 0.0119853355894; // Proporción entre medidas entre pdf y glyphs (medidas para ttf)
$margen = 20;
$tamaños = array();
$y_linea = $margen;
$tamaño_letra = 12; // 12pt = 1em
$proporcion_letra = $tamaño_letra/12; // 12pt = 1em
$alto_letra = 12*$proporcion_letra;
$font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_TIMES);
// Creamos el documento PDF
$pdf = new Zend_Pdf();
// Creamos una página
$page = creaPagina();
// Obtenemos el ancho y alto de la página
$ancho  = $page->getWidth();
$alto = $page->getHeight();
// Dibujamos la cabecera
$imagen = Zend_Pdf_ImageFactory::factory('sentidoweb.png');
$ancho_imagen = 50;
$alto_imagen = 50;
$page->drawImage($imagen, $margen, $alto-$y_linea-$alto_imagen, $margen+$ancho_imagen, $alto-$y_linea);
// Actualizo posición línea
$y_linea += $alto_imagen+$alto_letra;
// Escribimos el texto
$texto = $_POST["texto"];
$pos_texto = 0; // Para recorrer el texto
$linea = ""; // Para ir formando la linea actual
$long_linea = 0; // Longitud de la línea actual
$último_espacio = 0; // Posición del último espacio encontrado en la línea
$inicio_linea = 0; // Posición dentro del texto del inicio de la línea
$retorno_carro = false; // Para saber si hay un retorno de carro
// Ancho de la página en unidades glyphs
$ancho_pagina_glyphs = ($ancho-2*$margen) / $pdf_glyphs;
// Voy calculando las líneas y las escribo una a una
for ($i=0; $i<strlen($texto); $i++) {
// Si no está guardado el tamaño de la letra lo almacenamos
if (isset($tamaños[$texto[$i]]) === false) {
$tamaños[$texto[$i]] = $font->widthForGlyph(
$font->cmap->glyphNumberForCharacter(
ord($texto[$i]))) * $proporcion_letra;
}
// Tratamos los retornos de carro
if ($texto[$i] == "\n") { // || $texto[$i] == "\r") {
// Realizamos el retorno de carro
$retorno_carro = true;
}
// Si la longitud de la linea más el tamaño del
// caracter (glyph) es menor que el tamaño máximo
// de la página entonces sigo creando la línea
// en otro caso escribo la línea hasta el último
// espacio encontrado para no cortar las palabras
if ($long_linea + $tamaños[$texto[$i]] < $ancho_pagina_glyphs && !$retorno_carro) {
// Se puede añadir una letra
// recalculo la longitud de la linea
$long_linea += $tamaños[$texto[$i]];
// Añado el caracter actual a la línea
$linea .= $texto[$i];
// Si es espacio o tabulador, almanceno la posicion
// para no tener que cortar las palabras
if ($texto[$i] == " " ||
//$texto[$i] == "\n" ||
$texto[$i] == "\t") {
$ultimo_espacio = $i-$inicio_linea;
}
} else {
// Se debe dibujar la línea
// Si es retorno de carro bajo una linea mas
if ($retorno_carro) {
$ultimo_espacio = $i-$inicio_linea+1;
}
// Si el espacio no es el ultimo caracter de la linea
// paso la palabra actual a la siguiente linea
$resto = "";
if ($ultimo_espacio+$inicio_linea <= $i && $ultimo_espacio != 0) {
// Cojo la última palabra para no recortarla
$resto = substr($linea, $ultimo_espacio, strlen($linea)-$ultimo_espacio);
// Quito la ultima palabra de la linea
$linea = substr($linea, 0, $ultimo_espacio);
}
// Escribo el texto
$page->drawText(trim($linea), $margen, $alto-$y_linea);
// Incremento la posicion Y de la linea frente a la pagina
$y_linea += $alto_letra*($retorno_carro? 2:1);
// Salto de página
if ($y_linea > $alto-2*$margen) {
$page = creaPagina();
$y_linea = $margen+$alto_letra;
}
// Calculo el tamaño de la linea que llevo despues de dibujar
$long_linea = 0;
// Si es retorno de carro empiezo una linea,
// si no es asi, calculo el corte de palabra
if ($retorno_carro) {
$linea = "";
$inicio_linea = $i;
} else {
for ($j=0; $j<strlen($resto); $j++) {
$long_linea += $tamaños[$resto[$j]];
}
$long_linea += $tamaños[$texto[$i]];
// Añado a la linea el caracter actual
$linea = $resto.$texto[$i];
// Posicion del inicio de la linea frente al texto general
$inicio_linea = $i-strlen($resto);
}
$retorno_carro = false;
$ultimo_espacio = 0;
}
}
echo $pdf->render();
?>

Código

Similar Posts