Java Printing

Intruducción a Java Printing

Imprimir todo Java 2D

Java Printing del JDK1.2 fue diseñado para poder imprir el set completo de graficos de la API de Java 2D. A pesar de que las capacidades de la impresora y el host sean sobrepasadas por Java 2D este requerimiento significo que la API de printing en algunas situaciones nescesitaria rasterizar los graficos de Java 2D en el computador.

Sin Spool

Para rasterizar los trabajos de impresion en el computador un sistema de impresion graba las primitivas graficas en un spool de disco. Asi el archivo de spool es enviado a la impresora. El problema es que ni AWT ni Java 2D proveen un formato de spool para sus primitivas. Esto combinado con el apretado calendario de desarrollo de JDK1.2 significo que una solución sin spool fuera implementada para soportar la rasterización de los trabajos de impresion en el computador. Asi la API de printing hace llamadas a la aplicación para rasterizar cada banda.

Que no es la API de printing

Un sistema de impresión consiste de varias capas como se muestra en la figura 1. La aplicación interactua con el sistema de impresion por dos razones: para elegir un impresora y generar las páginas impresas.


fig.1

Elegir la impresora se conoce como Printer Discovery que le permite a la aplicación conseguir un a lista de impresoras diponibles y sus capacidades y elegir una para las siguientes operaciones.

Printer Imaging consiste en el formateo y dibujo de cada página y la conversión de las operaciones de impresion a un formato soportado por la impresoar elegida es el trabajo del Driver de la impresora.

La Printing API de Java entrega la capa de Printer Imaging, permitiendo el formateo y dibujo del contenido de las paginas a la aplicación.

Una aplicación puede obtener información de la imprsora actual e imprimir hacia ella usando la API pero, esta no cuenta con una versión completa de la capa Printer Discovery. Un diálogo de impresión le permite al usuario cambiar la impresora actual, pero una aplicación no puede hacer esto programáticamente.

Por otro lado la API entrega una capa de Printer Driver dado que cada JDK reimplementa el imaging a la capa de traducción de la impresora según el port.

En la figura 2 se ve el diagrama de la API de Java Printing


fig.2


El modelo de Java Printing

Los aspectos claves del modelo de Java Printing
  • la impresion se realiza en un proceso página a página incluso si sólo se imprime una sola página.
  • la impresión es manejada por un solo proceso que es lanzado por tu código.
  • ese proceso le pasa una página en blanco cada vez a un obejto separado que devuelve la pagina formateada.
  • la página completada es enviada a la impresora por el proceso de administración de la impresión.
  • este administrador deja de imprimir cuando una página no es fleshed out o si la corrida de impresión es cancelada.
  • PrinterJob

    Como crear un proceso de administración de impresión.
    A diferencia de otros paquetes de Java un objeto PrinterJob no se crea usando la clausula new, a cambio la clase PrinterJob contiene un método estático que se debe usar para crear una instancia de la clase

    PrinterJob job = PrinterJob.getPriterJob();

    El job no está listo para realizar ninguna tarea hasta que pueda usar los servicios de otro objeto que formatee las páginas.

    Printable

    El rol de un objeto que implementa esta interfaz es el de formatear una página en blanco que ha recibido de un PrintJob, simplemente lo hace y retorna un código de estado indicando si tuvo éxito. El PrintJob llamará a este objeto una o más veces para cada página que se deba imprimir. Así el PrintJob nunca sabe cuantas páginas imprimirá en total.

    class PrintObject implements Printable {
      public int print (Graphics g, PageFormat f, int pageIndex) {
        if (pageIndex == 0) {
          g.drawString("1", 100,100); // Draw the letter 1 at pixel coordinates (100,100)
          return PAGE_EXISTS;
        }else
          return NO_SUCH_PAGE;
      }
    }

    Este objeto no hace en realidad ningún tipo de impresión, sólo llena la página para que el PrintJob lo imprima. Chequea el índice de página que se le entrega que empieza con 0 y retorna PAGE_EXISTS indicandole al PrintJob que puede imprimirla y retorna NO_SUCH_PAGE para indicarle que deje de imprimir.

    Mostrandole el Formateador al PrintJob

    Para pasar una instancia de la clase que implementa la interfaz Printable, hacia el PrintJob se usa el siguiente metodo:

    job.setPrintable( new PrintObject());

    Ajustando las opciones de impresion

    Por norma, antes de realmente imprimir algo, se muestra una ventana con el dialogo de impresión, que le permite al usuario elegir el formato de la página, la impresora, detalles de tinta, etc.

    job.printDialog();

    Dependiente de la plataforma se despliega el cuadro de dialogo de la impresora en el que se permite elegir la impresora y algunas opciones de calidad de impresión.

    Adicionalmente se puede desplegar el PageFormat Dialog que sirve para darle formato a la pagina a imprimir. En algunos cuadros de dialogo se puede configurar ademas parametros de las clases PageFormat y Paper.


    Esto simplifica mucho el trabajo del programador, ya que no solo se entrega un dialogo de impresion completo y se despliega en pantalla, además, las opciones son almacenadas dentro del PrintJob

    Este dialogo retorna TRUE si el usuario presiona OK, o FALSE si presiona cancelar. Una vez obtenido el OK, se llama al metodo print() del PrintJob que se encargara de llamar a el metodo print de la interfaz printable tantas veces como sea necesario para realizar la impresion física de cada pagina.



    Ejemplo simple

    En este ejemplo se implementa Printable definiendo dos paginas, la primera con un rectangulo, la segunda con un circulo



    
    import java.awt.*;
    import java.awt.print.*;
    import java.awt.geom.*;
    
    // Define a class that is called per page that needs printing. It implements
    // the one and only method in the Printable interface : print. Note that
    // this is quite separate from the PrinterJob class print() method.
    //
    // This method does not actually do any printing. All it does is write text
    // and/or graphics onto the passed page (graphics context). The calling
    // printer job object will then pass this page to the printer. 
    
    class PrintObject implements Printable
    {
       public int print (Graphics g, PageFormat f, int pageIndex)
       {
          Graphics2D g2 = (Graphics2D) g;  // Allow use of Java 2 graphics on
                                           // the print pages :
    
          // A rectangle that shows the printable area of the page, allowing
          // for margins all round. To be drawn on the first page (index = 0).
          Rectangle2D rect = new Rectangle2D.Double(f.getImageableX(),
                                                    f.getImageableY(),
                                                    f.getImageableWidth(),
                                                    f.getImageableHeight());
    
          // A simple circle to go on the second page (index = 1).
          Ellipse2D circle = new Ellipse2D.Double(100,100,100,100);
    
          switch (pageIndex)
          {
             case 0 : g2.setColor(Color.black);   // Page 1 : print a rectangle
                      g2.draw(rect);
                      return PAGE_EXISTS;
             case 1 : g2.setColor(Color.red);     // Page 2 : print a circle
                      g2.draw(circle);
                      return PAGE_EXISTS;
             default: return NO_SUCH_PAGE;        // No other pages
          }
       }
    }
    
    public class Sample1
    {
       public static void main (String[] args)
       {
          // Create an object that will hold all print parameters, such as
          // page size, printer resolution. In addition, it manages the print
          // process (job).
          PrinterJob job = PrinterJob.getPrinterJob();
    
          // It is first called to tell it what object will print each page.
          job.setPrintable(new PrintObject());
    
          // Then it is called to display the standard print options dialog.
          if (job.printDialog())
          {
             // If the user has pressed OK (printDialog returns true), then go
             // ahead with the printing. This is started by the simple call to
             // the job print() method. When it runs, it calls the page print
             // object for page index 0. Then page index 1, 2, and so on
             // until NO_SUCH_PAGE is returned.
          try { job.print(); }
             catch (PrinterException e) { System.out.println(e); }
          }
       }
    }
    


    La clase Book y la interfaz Pageable

    En vez de usar el metodo setPrintable() para imprimir, es mejor usar setPageable() por que es mas puro y poderoso.
    La interfaz Pageable le permite a la API solicitar a la aplicacion el numero de paginas a imprimir, y a obtener para cada página una tupla con el PageFormat y el Printable. Esto le da la habilidad a la API de imprimir las paginas en cualquier orden, multiples veces, con separadores y otras funcionalidades adicionales.

    La API de printing, entrega una implementacion concreta de la interfaz Pageable con la clase Book, que es apropiada para la mayoria de las aplicaciones, pero la interfaz puede ser directamente implementada para ajustarse a la estructura interna del documento deseado.
    Una vez que se crea una instancia de Book, se le anexan un par de objetos para representar cada página del documento, describiendo las dimensiones y orientacion de la página y el pintor asociado.

    Book book = new Book();
    Book.append(new PrintBlank(), new PageFormat());
    printerJob.setPageable(book);
    


    Ejemplos

    PrintBlank

    
    import java.awt.*;
    import java.awt.print.*;
    
    // This example shows how to use the PrinterJob
    // and Book classes.
    
    public class PrintBlank implements Printable {
      //Print a single, blank page.
      static public void main(String args[]) {
      // Get the representation of the current printer and
      //the current print job.
        PrinterJob printerJob = PrinterJob.getPrinterJob();
        // Build a book containing pairs of page painters (Printables)
        // and PageFormats. This example has a single page.
        Book book = new Book();
        book.append(new PrintBlank(), new PageFormat());
        //  Set the object to be printed (the Book) into the PrinterJob.
        // Doing this before bringing up the print dialog allows the
        // print dialog to correctly display the page range to be printed
        // and to dissallow any print settings not appropriate for the
        // pages to be printed.
        printerJob.setPageable(book);
        //  Show the print dialog to the user. This is an optional step
        // and need not be done if the application wants to perform
        // 'quiet' printing. If the user cancels the print dialog then false
        // is returned. If true is returned we go ahead and print.
        boolean doPrint = printerJob.printDialog();
        if (doPrint) {
          try {
            printerJob.print();
          } catch (PrinterException exception) {
            System.err.println("Printing error: " + exception);
          }
        }
      }
      // Print a blank page.
      public int print(Graphics g, PageFormat format, int pageIndex) {
      //  Do the page drawing here. This example does not do any
      // drawing and therefore blank pages are generated:
      // "This Page Intentionally Left Blank"
      return Printable.PAGE_EXISTS;
      }
    }
    

    PrintText

    
    import java.awt.*;
    import java.awt.font.*;
    import java.awt.geom.*;
    import java.awt.print.*;
    import java.text.*;
    
    // The PrintText application expands on the
    // PrintExample application in that it images
    // text on to the single page printed.
    public class PrintText implements Printable {
      //The text to be printed.
      private static final String mText =
      "Four score and seven years ago our fathers brought forth on this "
      + "continent a new nation, conceived in liberty and dedicated to the "
      + "proposition that all men are created equal. Now we are engaged in "
      + "a great civil war, testing whether that nation or any nation so "
      + "conceived and so dedicated can long endure. We are met on a great "
      + "battlefield of that war. We have come to dedicate a portion of "
      + "that field as a final resting-place for those who here gave their "
      + "lives that that nation might live. It is altogether fitting and "
      + "proper that we should do this. But in a larger sense, we cannot "
      + "dedicate, we cannot consecrate, we cannot hallow this ground. "
      + "The brave men, living and dead who struggled here have consecrated "
      + "it far above our poor power to add or detract. The world will "
      + "little note nor long remember what we say here, but it can never "
      + "forget what they did here. It is for us the living rather to be "
      + "dedicated here to the unfinished work which they who fought here "
      + "have thus far so nobly advanced. It is rather for us to be here "
      + "dedicated to the great task remaining before us--that from these "
      + "honored dead we take increased devotion to that cause for which "
      + "they gave the last full measure of devotion--that we here highly "
      + "resolve that these dead shall not have died in vain, that this "
      + "nation under God shall have a new birth of freedom, and that "
      + "government of the people, by the people, for the people shall "
      + "not perish from the earth.";
      // Our text in a form for which we can obtain a
      // AttributedCharacterIterator.
      private static final AttributedString mStyledText = new
      AttributedString(mText);
      // Print a single page containing some sample text.
      static public void main(String args[]) {
      // Get the representation of the current printer and
      // the current print job.
      PrinterJob printerJob = PrinterJob.getPrinterJob();
      // Build a book containing pairs of page painters (Printables)
      // and PageFormats. This example has a single page containing
      // text.
      Book book = new Book();
      book.append(new PrintText(), new PageFormat());
      // Set the object to be printed (the Book) into the PrinterJob.
      // Doing this before bringing up the print dialog allows the
      // print dialog to correctly display the page range to be printed
      // and to dissallow any print settings not appropriate for the
      // pages to be printed.
      printerJob.setPageable(book);
      // Show the print dialog to the user. This is an optional step
      // and need not be done if the application wants to perform
      // 'quiet' printing. If the user cancels the print dialog then false
      // is returned. If true is returned we go ahead and print.
      boolean doPrint = printerJob.printDialog();
      if (doPrint) {
        try {
          printerJob.print();
        } catch (PrinterException exception) {
          System.err.println("Printing error: " + exception);
        }
      }
      }
      // Print a page of text.
      public int print(Graphics g, PageFormat format, int pageIndex) {
        // We'll assume that Jav2D is available.
        Graphics2D g2d = (Graphics2D) g;
        // Move the origin from the corner of the Paper to the corner
        // of the imageable area.
        g2d.translate(format.getImageableX(), format.getImageableY());
        // Set the text color.
        g2d.setPaint(Color.black);
        // Use a LineBreakMeasurer instance to break our text into
        // lines that fit the imageable area of the page.
        Point2D.Float pen = new Point2D.Float();
        AttributedCharacterIterator charIterator = mStyledText.getIterator();
        LineBreakMeasurer measurer = new LineBreakMeasurer(charIterator,
        g2d.getFontRenderContext());
        float wrappingWidth = (float) format.getImageableWidth();
        while (measurer.getPosition() < charIterator.getEndIndex()) {
          TextLayout layout = measurer.nextLayout(wrappingWidth);
          pen.y += layout.getAscent();
          float dx = layout.isLeftToRight()? 0 :
    	             (wrappingWidth - layout.getAdvance());
          layout.draw(g2d, pen.x + dx, pen.y);
          pen.y += layout.getDescent() + layout.getLeading();
        }
        return Printable.PAGE_EXISTS;
      }
    }