Como era de esperar esto es perfectamente realizable desde iText dándonos la flexibilidad necesaria para generar un encabezado de la forma en que deseemos, para el ejemplo actual lo mantendremos lo mas simple posible, la idea es que sea fácil de comprender el funcionamiento básico y que métodos son los que se llaman.
Figura 1 - Proyecto encabezado |
Teoría de Eventos.
La forma en que se realiza el conteo de pagina y la generación de encabezados es muy similar a la forma en que se manejan los eventos en la librería Swing, esto significa que una vez que ocurra una situación o evento especifico, como el cambio de pagina, se invoca un método especifico de un objecto indicado.
La limitante de esto es que dicho objecto debe ser una instancia de una clase descendiente de la clase que la librería usa para manejar los eventos esto, aunque parezca, arbitrario es necesario para garantizar que nunca se llame un método inexistente.
Ya tras esta breve introducción a como se manejan los eventos de pagina podemos pasar a implementar la clase que manejara los eventos de pagina de nuestro documento.
La forma en que se realiza el conteo de pagina y la generación de encabezados es muy similar a la forma en que se manejan los eventos en la librería Swing, esto significa que una vez que ocurra una situación o evento especifico, como el cambio de pagina, se invoca un método especifico de un objecto indicado.
La limitante de esto es que dicho objecto debe ser una instancia de una clase descendiente de la clase que la librería usa para manejar los eventos esto, aunque parezca, arbitrario es necesario para garantizar que nunca se llame un método inexistente.
Ya tras esta breve introducción a como se manejan los eventos de pagina podemos pasar a implementar la clase que manejara los eventos de pagina de nuestro documento.
Manejando eventos de pagina.
Comenzaremos creando una nueva clase para manejar los dos eventos de pagina que nos interesan en este momento, el evento onDocumentOpen onEndPage y onCloseDocument, los cuales se activan al abrirse el documento, completarse una pagina y cerrar el documento, respectivamente.
El código de la clase puede verse a continuación, tras lo cual se explicaran que hace y como funciona cada función.
Comenzaremos creando una nueva clase para manejar los dos eventos de pagina que nos interesan en este momento, el evento onDocumentOpen onEndPage y onCloseDocument, los cuales se activan al abrirse el documento, completarse una pagina y cerrar el documento, respectivamente.
El código de la clase puede verse a continuación, tras lo cual se explicaran que hace y como funciona cada función.
package mx.com.hashSoft;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.ExceptionConverter;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.Phrase;
/**
* Clase que maneja los eventos de pagina necesarios para agregar un encabezado y conteo de paginas a un documento.
* El encabezado, definido en onEndPage, consiste en una tabla con 3 celdas que contienen:
* Frase del encabezado | pagina <numero de pagina> de | total de paginas, con una linea horizontal separando el
* encabezado del texto
*
* Referencia: http://itextpdf.com/examples/iia.php?id=104
*
* @author David
*/
public class Cabecera extends PdfPageEventHelper {
private String encabezado;
PdfTemplate total;
/**
* Crea el objecto PdfTemplate el cual contiene el numero total de
* paginas en el documento
*/
public void onOpenDocument(PdfWriter writer, Document document) {
total = writer.getDirectContent().createTemplate(30, 16);
}
/**
* Esta es el metodo a llamar cuando ocurra el evento <b>onEndPage</b>, es en este evento
* donde crearemos el encabeazado de la pagina con los elementos indicados.
*/
public void onEndPage(PdfWriter writer, Document document) {
PdfPTable table = new PdfPTable(3);
try {
// Se determina el ancho y altura de la tabla
table.setWidths(new int[]{24, 24, 2});
table.setTotalWidth(527);
table.setLockedWidth(true);
table.getDefaultCell().setFixedHeight(20);
// Borde de la celda
table.getDefaultCell().setBorder(Rectangle.BOTTOM);
table.addCell(encabezado);
table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_RIGHT);
table.addCell(String.format("Pagina %010d de", writer.getPageNumber()));
PdfPCell cell = new PdfPCell(Image.getInstance(total));
cell.setBorder(Rectangle.BOTTOM);
table.addCell(cell);
// Esta linea escribe la tabla como encabezado
table.writeSelectedRows(0, -1, 34, 803, writer.getDirectContent());
}
catch(DocumentException de) {
throw new ExceptionConverter(de);
}
}
/**
* Realiza el conteo de paginas al momento de cerrar el documento
*/
public void onCloseDocument(PdfWriter writer, Document document) {
ColumnText.showTextAligned(total, Element.ALIGN_LEFT, new Phrase(String.valueOf(writer.getPageNumber() - 1)), 2, 2, 0);
}
// Getter and Setters
public String getEncabezado() {
return encabezado;
}
public void setEncabezado(String encabezado) {
this.encabezado = encabezado;
}
}
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.ExceptionConverter;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.Phrase;
/**
* Clase que maneja los eventos de pagina necesarios para agregar un encabezado y conteo de paginas a un documento.
* El encabezado, definido en onEndPage, consiste en una tabla con 3 celdas que contienen:
* Frase del encabezado | pagina <numero de pagina> de | total de paginas, con una linea horizontal separando el
* encabezado del texto
*
* Referencia: http://itextpdf.com/examples/iia.php?id=104
*
* @author David
*/
public class Cabecera extends PdfPageEventHelper {
private String encabezado;
PdfTemplate total;
/**
* Crea el objecto PdfTemplate el cual contiene el numero total de
* paginas en el documento
*/
public void onOpenDocument(PdfWriter writer, Document document) {
total = writer.getDirectContent().createTemplate(30, 16);
}
/**
* Esta es el metodo a llamar cuando ocurra el evento <b>onEndPage</b>, es en este evento
* donde crearemos el encabeazado de la pagina con los elementos indicados.
*/
public void onEndPage(PdfWriter writer, Document document) {
PdfPTable table = new PdfPTable(3);
try {
// Se determina el ancho y altura de la tabla
table.setWidths(new int[]{24, 24, 2});
table.setTotalWidth(527);
table.setLockedWidth(true);
table.getDefaultCell().setFixedHeight(20);
// Borde de la celda
table.getDefaultCell().setBorder(Rectangle.BOTTOM);
table.addCell(encabezado);
table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_RIGHT);
table.addCell(String.format("Pagina %010d de", writer.getPageNumber()));
PdfPCell cell = new PdfPCell(Image.getInstance(total));
cell.setBorder(Rectangle.BOTTOM);
table.addCell(cell);
// Esta linea escribe la tabla como encabezado
table.writeSelectedRows(0, -1, 34, 803, writer.getDirectContent());
}
catch(DocumentException de) {
throw new ExceptionConverter(de);
}
}
/**
* Realiza el conteo de paginas al momento de cerrar el documento
*/
public void onCloseDocument(PdfWriter writer, Document document) {
ColumnText.showTextAligned(total, Element.ALIGN_LEFT, new Phrase(String.valueOf(writer.getPageNumber() - 1)), 2, 2, 0);
}
// Getter and Setters
public String getEncabezado() {
return encabezado;
}
public void setEncabezado(String encabezado) {
this.encabezado = encabezado;
}
}
Notara un objecto PdfTemplate en la clase, junto con los métodos get y set respectivos, este es el objecto usado para poder realizar el conteo de paginas.
onOpenDocument
Esta función inicializa el objecto PdfTemplate, no hacen falta mas detalles sobre esta.
onEndPage
Esta función se llama *después* de que se a completado una pagina, permitiendo preparar la siguiente.
En este caso deseamos que las paginas tengan un encabezado con las forma:
<encabezado> Pagina <numero de pagina con 10 dígitos> de <total de paginas>
Para lograr esto crearemos una tabla de 1 x 3 en la cual se colocara cada uno de los elementos de la cabecera, esto se hace con el objecto table y las múltiples llamadas al método addCell
Notara tres métodos que no se han visto en la entrada de tablas, setBorder, setHorizontalAligment y writeSelectedRows, lo que hacen estas funciones es indicar el borde de la tabla que deseamos se dibuje, como deseamos que se alinen los elementos de la tabla y que se escriba directamente respectivamente.
Ya con esto se generara el encabezado deseado.
onCloseDocument
Es desde esta función donde se realiza el calculo de paginas
get/setEncabezado
Indica Que frase deseamos en el encabezado
Seguro podría parecerle raro que algunas funciones tengan nombres en ingles, específicamente las que comienzan con on, esto no es solo por alguna convención, de hecho es vital para el funcionamiento, lo que se esta haciendo es redefinir los métodos on de la superclase PdfPageEventHelper, esto es necesario para que sea posible usar esta clase para manejar los eventos, como veremos en la siguiente clase.
Esta función inicializa el objecto PdfTemplate, no hacen falta mas detalles sobre esta.
onEndPage
Esta función se llama *después* de que se a completado una pagina, permitiendo preparar la siguiente.
En este caso deseamos que las paginas tengan un encabezado con las forma:
<encabezado> Pagina <numero de pagina con 10 dígitos> de <total de paginas>
Para lograr esto crearemos una tabla de 1 x 3 en la cual se colocara cada uno de los elementos de la cabecera, esto se hace con el objecto table y las múltiples llamadas al método addCell
Notara tres métodos que no se han visto en la entrada de tablas, setBorder, setHorizontalAligment y writeSelectedRows, lo que hacen estas funciones es indicar el borde de la tabla que deseamos se dibuje, como deseamos que se alinen los elementos de la tabla y que se escriba directamente respectivamente.
Ya con esto se generara el encabezado deseado.
onCloseDocument
Es desde esta función donde se realiza el calculo de paginas
get/setEncabezado
Indica Que frase deseamos en el encabezado
Seguro podría parecerle raro que algunas funciones tengan nombres en ingles, específicamente las que comienzan con on, esto no es solo por alguna convención, de hecho es vital para el funcionamiento, lo que se esta haciendo es redefinir los métodos on de la superclase PdfPageEventHelper, esto es necesario para que sea posible usar esta clase para manejar los eventos, como veremos en la siguiente clase.
Creando Documento
Ahora mostraremos como se usa la clase que acabamos de crear con ayuda de esta clase, cuya función es generar un documento de 100 paginas, de modo que se demuestre que la numeración si se lleva a cabo de forma correcta.
A continuación puede verse el código de dicha clase:
A continuación puede verse el código de dicha clase:
package mx.com.hashSoft;
import java.io.FileOutputStream;
import java.io.IOException;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfWriter;
public class Documento {
/**
* Crea un documento con encabezado y conteo de
* paginas, para este ejemplo se crean 100 paginas
* @param filename Nombre del archivo
*/
public void createPdf(String filename) throws IOException, DocumentException
{
Document document = new Document(PageSize.A4, 36, 36, 54, 36);
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(filename));
Cabecera encabezado = new Cabecera();
Paragraph parrafo;
int i;
encabezado.setEncabezado("Prueba de encabezado en iText");
// indicamos que objecto manejara los eventos al escribir el Pdf
writer.setPageEvent(encabezado);
document.open();
//Creamos una cantidad significativa de paginas para probar el encabezado
for(i=0; i < 100; i++)
{
parrafo = new Paragraph("Esta es una de las paginas de prueba de nuestro programa, es la pagina numero 0x" + String.format("%03X", i+42));
parrafo.setAlignment(Element.ALIGN_CENTER);
document.add(parrafo);
document.newPage();
}
document.close();
}
static public void main(String[] args)
{
Documento doc = new Documento();
try{
// Creamos el documento Pdf
doc.createPdf("documento.pdf");
}catch(DocumentException ed)
{
System.err.println("Error al crear el documento Pdf");
}
catch(IOException ex)
{
System.err.println("Error General de Entrada/Salida");
}
}
}
import java.io.FileOutputStream;
import java.io.IOException;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfWriter;
public class Documento {
/**
* Crea un documento con encabezado y conteo de
* paginas, para este ejemplo se crean 100 paginas
* @param filename Nombre del archivo
*/
public void createPdf(String filename) throws IOException, DocumentException
{
Document document = new Document(PageSize.A4, 36, 36, 54, 36);
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(filename));
Cabecera encabezado = new Cabecera();
Paragraph parrafo;
int i;
encabezado.setEncabezado("Prueba de encabezado en iText");
// indicamos que objecto manejara los eventos al escribir el Pdf
writer.setPageEvent(encabezado);
document.open();
//Creamos una cantidad significativa de paginas para probar el encabezado
for(i=0; i < 100; i++)
{
parrafo = new Paragraph("Esta es una de las paginas de prueba de nuestro programa, es la pagina numero 0x" + String.format("%03X", i+42));
parrafo.setAlignment(Element.ALIGN_CENTER);
document.add(parrafo);
document.newPage();
}
document.close();
}
static public void main(String[] args)
{
Documento doc = new Documento();
try{
// Creamos el documento Pdf
doc.createPdf("documento.pdf");
}catch(DocumentException ed)
{
System.err.println("Error al crear el documento Pdf");
}
catch(IOException ex)
{
System.err.println("Error General de Entrada/Salida");
}
}
}
Ya que la función main se limita unicamente a llamar a createPdf y capturar cualquier error nos enfocaremos en este ultimo método.
Notara que el método createPdf crea e inicializa un objecto PdfWriter, como se realizo en los ejemplos de entradas anteriores, pero esta vez se llama a un método muy particular pasandole como argumento un objecto de la clase Cabecera que creamos anteriormente.
writer.setPageEvent(encabezado);
Esta linea es la clave de todo, en esta le indicamos al objecto writer que objecto se encargara de manejar los eventos, este método toma por argumento un objecto tipo PdfPageEventHelper o un *descendiente* de esta clase y llama a los métodos en el momento adecuando.
Tras esto el flujo del método es bastante simple una vez abierto el documento, lo cual es el evento onOpenDocument, hacemos un ciclo de 100 repeticiones agregando un párrafo a la pagina e indicamos que deseamos comience una nueva pagina con document.newPage, lo que a su vez es genera el evento onPageEnd y llama a la función adecuada del objecto que maneja los eventos.
Al concluir cerramos el documento, causando el evento onCloseDocument.
Solo para aclarar las llamadas a los métodos de la clase Cabecera se realizan automáticamente cuando se llega a la condición adecuada.
Si corre el programa, recordando preparar el proyecto como se mostró en la entrada iText, Generación de archivo Pdf en Java , obtendrá una salida como la figura 2
writer.setPageEvent(encabezado);
Esta linea es la clave de todo, en esta le indicamos al objecto writer que objecto se encargara de manejar los eventos, este método toma por argumento un objecto tipo PdfPageEventHelper o un *descendiente* de esta clase y llama a los métodos en el momento adecuando.
Tras esto el flujo del método es bastante simple una vez abierto el documento, lo cual es el evento onOpenDocument, hacemos un ciclo de 100 repeticiones agregando un párrafo a la pagina e indicamos que deseamos comience una nueva pagina con document.newPage, lo que a su vez es genera el evento onPageEnd y llama a la función adecuada del objecto que maneja los eventos.
Al concluir cerramos el documento, causando el evento onCloseDocument.
Solo para aclarar las llamadas a los métodos de la clase Cabecera se realizan automáticamente cuando se llega a la condición adecuada.
Si corre el programa, recordando preparar el proyecto como se mostró en la entrada iText, Generación de archivo Pdf en Java , obtendrá una salida como la figura 2
Figura 2 - Documento resultante |
Notara que el mensaje tiene la numeración en hexadecimal y desfasada con respecto al conteo de paginas, mas que nada para demostrar que el contenido y la numeración de pagina son independientes uno de otro.
Con esto puede ver que, si bien su uso no es del todo intuitivo, las posibilidades de los eventos de pagina de iText ciertamente valen el esfuerzo ya que, así como creamos un encabezado, no me extrañaría que se pudiera agregar algo mas elaborado, como imágenes o fechas o datos mucho mas elaborados.
Espero que esta información fuera de utilidad y nos vemos en otra entrada.
Con esto puede ver que, si bien su uso no es del todo intuitivo, las posibilidades de los eventos de pagina de iText ciertamente valen el esfuerzo ya que, así como creamos un encabezado, no me extrañaría que se pudiera agregar algo mas elaborado, como imágenes o fechas o datos mucho mas elaborados.
Espero que esta información fuera de utilidad y nos vemos en otra entrada.
[…] Como crear un encabezado con iText […]
ResponderEliminar