Impresión Avanzada

La sección anterior explicó cómo imprimir componentes sencillos y cubría las técnicas que se pueden usar para imprimir capturas de pantalla. Sin embargo, si queremos imprimir más que un componente por cada página, o su nuestro componentes es mayor que el tamaño de una página, necesitamos hacer algún trabajo adicional dentro del método print. Esta sección explica qué necesitamos hacer y concluye con un ejemplo de cómo imprimir los contenidos de un componente JTable.

Varios Componentes por Página

Hay veces cuando imprimimos un componente en una página que no se cubre las necesidades de impresión que queremos. Por ejemplo, podríamos queren incluir una cabecera o un pie en cada página de impresión con un número de página -- algo que no es necesario mostrar en la pantalla.

Desafortunadamente, imprimir múltiples componentes sobre una página no es tán sencillo como añadir llamadas a paint porque cada llamada sobreescribe la salida de la llamada anterior.

La clave para imprimir más de un componente en un página, es usar los métodos translate(double, double) y setClip de la clase Graphics2D.

El método translate mueve un lápiz imaginario a la siguiente posición de la salida de impresión donde el componente puede ser dibujado y luego imprimido. Hay dos métodos translate en la clase Graphics2D. Para imprimir múltiples componentes necesitamos el que toma dos argumentos double porque este método permite posiconamiento relativo. Debemos asegurarnos de forzar cualquier valor entero a double o float. El posicionamiento relativo en este contexto significa que las llamadas anteriores a translate son tenidas en cuenta cuando se calqula el nuevo punto de traslado.

El método setClip se usa para restringir que el componente sea pintado, y por lo tanto, imprimido, en el área especificada. Esto nos permite imprimir múltiples componentes en una página moviendo el lápiz imaginario a diferentes puntos de la página y luego pintándo cada componente en el área recortada.

Ejemplo

Podemos reemplazar el método print de los ejemplos printbutton.java Abstract Window Toolkit (AWT) y Swing con el siguiente código para añadir un mensaje en el pie de página de Company Confidential.
public int print(Graphics g, PageFormat pf, int pi) 
		throws PrinterException {

    if (pi >= 1) {
       return Printable.NO_SUCH_PAGE;
    }

    Graphics2D g2 = (Graphics2D) g;
    Font f= Font.getFont("Courier");
    double height=pf.getImageableHeight();
    double width=pf.getImageableWidth();
         
    g2.translate(pf.getImageableX(), 
                 pf.getImageableY());
    g2.setColor(Color.black);
    g2.drawString("Company Confidential", (int)width/2, 
	(int)height-g2.getFontMetrics().getHeight());
    g2.translate(0f,0f);
    g2.setClip(0,0,(int)width, 
	(int)(height-g2.getFontMetrics().getHeight()*2));
    paint (g2);
    return Printable.PAGE_EXISTS;
}   
En el nuevo método print, el contexto Graphics2D es recortado antes de llamar al método paint del padre JButton. Esto evita que el método JButton paint sobreescriba el botón de la página. El método translate se usa para apuntan el método JButton paint a que empieza el paint con un desplazamiento de 0,0 desde la parte visible de la página. el área visible ya está calculada mediante una llamada anterior a translate:
  g2.translate(pf.getImageableX(), pf.getImageableY());
Para más componentes, podríamos necesitar configurar el color de fondo para ver los resultados. En este ejemplo el color de texto se imprimió en negro.

Métodos Útiles para Llamar en el Método print

Los siguientes métodos son útiles para calcular el número de páginas requeridas y para hacer que un componente se reduzca hasta entrar en una página:

Métodos PageFormat:

getImageableHeight()
devuelve la altura de la página que podemos usar para imprimir la salida.

getImageableWidth()
devuelve la anchura de la página que podemos usar para imprimir la salida.

Método Graphics2D:

scale(xratio, yratio)
escala el conexto gráfico 2D a este tamaño. Un ratío de uno mantiene el tamaño, menos de uno reduce el tamaño del contexto gráfico.

Componentes Mayores de una Página

El API de impresión de Java " tiene un API Book que proporciona el concepto de páginas. Sin embargo, este API sólo añade objetos printables a una colecciónde objetos printables. No calcula las rupturas de página ni expande componentes sobre múltiples páginas

Cuando imprimimos un sólo componente en una página, sólo tenemos que chequear que el valor del índice es mayor o igual que uno y devolver NO_SUCH_PAGE cuando se alcanza este valor.

Para imprimir multiples páginas, tenemos que calcular el número de páginas necesarias para contener el componente. Podemos calcular el número total de páginas necesarias dividiendo el espacio ocupado por el componente por el valor devuelto por el método getImageableHeight. Una vez calculado el número total de páginas, podemos ejecutar el siguiente chequeo dentro del método print:

  if (pageIndex >=TotalPages) {
	return NO_SUCH_PAGE;
  }
El marco de trabajo de impresión llama al método print multiples veces hasta que pageIndex sea menor o igual que TotalPages. Todo lo que necesitamos hacer es crear una nueva página para del mismo componente encada bucle print. Esto se puede hacer tratando la página impresa como una ventana deslizante sobre el componente. La parte del componente que se está imprimiendo es seleccionada por una llamada a translate para marcar la parte superior de la página y una llama a setClip para marcar la parte inferior de la página. el siguiente diagrama ilustra este proceso.

El lado izquierdo del diagrama representa la página enviada a la impresora. El lado LEFT contiene la longitud del componente que está siendo imprimido en el método print. La primera página puede ser representada de esta forma:

Luego la ventana de la página impresa se desliza a lo largo del componente para imprimir la segunda página, con el índice uno.

Este proceso continúa hasta que se alcanza la última página.

Imprimir un Componente JTable

La clase Report.java usa muchos de técnicas avanzadas cubiertas en esta sección para imprimir los datos y la cabecera de un componente JTable que expande muchas páginas. La salida de impresión también inlcuye un pié de página con el número de ésta.

Este diagrama muestra como sería la impresión:

import javax.swing.*;
import javax.swing.table.*;
import java.awt.print.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.Dimension;

public class Report implements Printable{
  JFrame frame;
  JTable tableView;

  public Report() {
    frame = new JFrame("Sales Report");
    frame.addWindowListener(new WindowAdapter() {
    public void windowClosing(WindowEvent e) {
      System.exit(0);}});

    final String[] headers = {"Description", "open price", 
	"latest price", "End Date", "Quantity"};
    final Object[][] data = {
        {"Box of Biros", "1.00", "4.99", new Date(), 
          new Integer(2)},
        {"Blue Biro", "0.10", "0.14", new Date(), 
          new Integer(1)},
        {"legal pad", "1.00", "2.49", new Date(), 
          new Integer(1)},
        {"tape", "1.00", "1.49", new Date(), 
          new Integer(1)},
        {"stapler", "4.00", "4.49", new Date(), 
          new Integer(1)},
        {"legal pad", "1.00", "2.29", new Date(), 
          new Integer(5)}
    };

    TableModel dataModel = new AbstractTableModel() {
        public int getColumnCount() { 
          return headers.length; }
        public int getRowCount() { return data.length;}
        public Object getValueAt(int row, int col) {
        	return data[row][col];}
        public String getColumnName(int column) {
         	return headers[column];}
        public Class getColumnClass(int col) {
                return getValueAt(0,col).getClass();}
        public boolean isCellEditable(int row, int col) {
                return (col==1);}
        public void setValueAt(Object aValue, int row, 
                      int column) {
                data[row][column] = aValue;
       }
     };

     tableView = new JTable(dataModel);
     JScrollPane scrollpane = new JScrollPane(tableView);

     scrollpane.setPreferredSize(new Dimension(500, 80));
     frame.getContentPane().setLayout(
               new BorderLayout());
     frame.getContentPane().add(
               BorderLayout.CENTER,scrollpane);
     frame.pack();
     JButton printButton= new JButton();

     printButton.setText("print me!");

     frame.getContentPane().add(
               BorderLayout.SOUTH,printButton);

     // for faster printing turn double buffering off

     RepaintManager.currentManager(
     	frame).setDoubleBufferingEnabled(false);

     printButton.addActionListener( new ActionListener(){
        public void actionPerformed(ActionEvent evt) {
          PrinterJob pj=PrinterJob.getPrinterJob();
          pj.setPrintable(Report.this);
          pj.printDialog();
          try{ 
            pj.print();
          }catch (Exception PrintException) {}
          }
        });

        frame.setVisible(true);
     }

     public int print(Graphics g, PageFormat pageFormat, 
        int pageIndex) throws PrinterException {
     	Graphics2D  g2 = (Graphics2D) g;
     	g2.setColor(Color.black);
     	int fontHeight=g2.getFontMetrics().getHeight();
     	int fontDesent=g2.getFontMetrics().getDescent();

     	//leave room for page number
     	double pageHeight = 
     	  pageFormat.getImageableHeight()-fontHeight;
     	double pageWidth = 
     	  pageFormat.getImageableWidth();
     	double tableWidth = (double) 
          tableView.getColumnModel(
          ).getTotalColumnWidth();
     	double scale = 1; 
     	if (tableWidth >= pageWidth) {
		scale =  pageWidth / tableWidth;
	}

     	double headerHeightOnPage=
                 tableView.getTableHeader(
                 ).getHeight()*scale;
     	double tableWidthOnPage=tableWidth*scale;

     	double oneRowHeight=(tableView.getRowHeight()+
                      tableView.getRowMargin())*scale;
     	int numRowsOnAPage=
              (int)((pageHeight-headerHeightOnPage)/
                                  oneRowHeight);
     	double pageHeightForTable=oneRowHeight*
     	                            numRowsOnAPage;
     	int totalNumPages= 
     	      (int)Math.ceil((
                (double)tableView.getRowCount())/
                                    numRowsOnAPage);
     	if(pageIndex>=totalNumPages) {
                      return NO_SUCH_PAGE;
     	}

     	g2.translate(pageFormat.getImageableX(), 
                       pageFormat.getImageableY());
//bottom center
     	g2.drawString("Page: "+(pageIndex+1),
     	    (int)pageWidth/2-35, (int)(pageHeight
     	    +fontHeight-fontDesent));

     	g2.translate(0f,headerHeightOnPage);
     	g2.translate(0f,-pageIndex*pageHeightForTable);

     	//If this piece of the table is smaller 
     	//than the size available,
     	//clip to the appropriate bounds.
     	if (pageIndex + 1 == totalNumPages) {
           int lastRowPrinted = 
                 numRowsOnAPage * pageIndex;
           int numRowsLeft = 
                 tableView.getRowCount() 
                 - lastRowPrinted;
           g2.setClip(0, 
             (int)(pageHeightForTable * pageIndex),
             (int) Math.ceil(tableWidthOnPage),
             (int) Math.ceil(oneRowHeight * 
                               numRowsLeft));
     	}
     	//else clip to the entire area available.
     	else{    
             g2.setClip(0, 
             (int)(pageHeightForTable*pageIndex), 
             (int) Math.ceil(tableWidthOnPage),
             (int) Math.ceil(pageHeightForTable));        
     	}

     	g2.scale(scale,scale);
     	tableView.paint(g2);
     	g2.scale(1/scale,1/scale);
     	g2.translate(0f,pageIndex*pageHeightForTable);
     	g2.translate(0f, -headerHeightOnPage);
     	g2.setClip(0, 0,
     	  (int) Math.ceil(tableWidthOnPage), 
          (int)Math.ceil(headerHeightOnPage));
     	g2.scale(scale,scale);
     	tableView.getTableHeader().paint(g2);
     	//paint header at top

     	return Printable.PAGE_EXISTS;
   }

   public static void main(String[] args) {
	new Report();
   }
}

Imprimir un Informe de Ventas

La clase Applet SalesReport.java imprime un informe de ventas con filas que expánden sobre múltiples páginas con números en la parte inferior de cada página. Aquí se vé la aplicación cuando se lanza:

Necesitamos este fichero de policía para lanzar el applet:

grant {
  permission java.lang.RuntimePermission 
                         "queuePrintJob";
};
Para lanzar el applet asumiendo un fichero de policía llamado printpol y una página HTML llamada SalesReport.html, teclearemos:
  appletviewer -J-Djava.security.policy=
                 printpol SalesReport.html
El diagrama muestra cómo se verá la impresión del informe:


Ozito