Mega Code Archive

 
Categories / Java / 2D Graphics GUI
 

Printable Document

/*  * Copyright (c) 2000 David Flanagan.  All rights reserved.  * This code is from the book Java Examples in a Nutshell, 2nd Edition.  * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.  * You may study, use, and modify it for any non-commercial purpose.  * You may distribute it non-commercially as long as you retain this notice.  * For a commercial use license, or to purchase the book (recommended),  * visit http://www.davidflanagan.com/javaexamples2.  */ import java.awt.Color; import java.awt.Container; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.Shape; import java.awt.font.LineMetrics; import java.awt.geom.Rectangle2D; import java.awt.print.PageFormat; import java.awt.print.Pageable; import java.awt.print.Paper; import java.awt.print.Printable; import java.util.ArrayList; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.EditorKit; import javax.swing.text.Element; import javax.swing.text.JTextComponent; import javax.swing.text.Position; import javax.swing.text.View; import javax.swing.text.ViewFactory; import PrintableDocument.ParentView; /**  * This class implements the Pageable and Printable interfaces and allows the  * contents of any JTextComponent to be printed using the java.awt.print  * printing API.  */ public class PrintableDocument implements Pageable, Printable {   View root; // The root View to be printed   PageFormat format; // Paper plus page orientation   int numPages; // How many pages in the document   double printX, printY; // coordinates of upper-left of print area   double printWidth; // Width of the printable area   double printHeight; // Height of the printable area   Rectangle drawRect; // The rectangle in which the document is painted   // How lenient are we with the bottom margin in widow and orphan prevention?   static final double MARGIN_ADJUST = .97;   // The font we use for printing page numbers   static final Font headerFont = new Font("Serif", Font.PLAIN, 12);   /**    * This constructor allows printing the contents of any JTextComponent using    * a default PageFormat    */   public PrintableDocument(JTextComponent textComponent) {     this(textComponent, new PageFormat());   }   /**    * This constructor allows the contents of any JTextComponent to be printed,    * using any specified PageFormat object    */   public PrintableDocument(JTextComponent textComponent, PageFormat format) {     // Remember the page format, and ask it for the printable area     this.format = format;     this.printX = format.getImageableX();     this.printY = format.getImageableY();     this.printWidth = format.getImageableWidth();     this.printHeight = format.getImageableHeight();     double paperWidth = format.getWidth();     // Get the document and its root Element from the text component     Document document = textComponent.getDocument();     Element rootElement = document.getDefaultRootElement();     // Get the EditorKit and its ViewFactory from the text component     EditorKit editorKit = textComponent.getUI().getEditorKit(textComponent);     ViewFactory viewFactory = editorKit.getViewFactory();     // Use the ViewFactory to create a root View object for the document     // This is the object we'll print.     root = viewFactory.create(rootElement);     // The Swing text architecture requires us to call setParent() on     // our root View before we use it for anything. In order to do this,     // we need a View object that can serve as the parent. We use a     // custom implementation defined below.     root.setParent(new ParentView(root, viewFactory, textComponent));     // Tell the view how wide the page is; it has to format itself     // to fit within this width. The height doesn't really matter here     root.setSize((float) printWidth, (float) printHeight);     // Now that the view has formatted itself for the specified width,     // Ask it how tall it is.     double documentHeight = root.getPreferredSpan(View.Y_AXIS);     // Set up the rectangle that tells the view where to draw itself     // We'll use it in other methods of this class.     drawRect = new Rectangle((int) printX, (int) printY, (int) printWidth,         (int) documentHeight);     // Now if the document is taller than one page, we have to     // figure out where the page breaks are.     if (documentHeight > printHeight)       paginate(root, drawRect);     // Once we've broken it into pages, figure out how man pages.     numPages = pageLengths.size() + 1;   }   // This is the starting offset of the page we're currently working on   double pageStart = 0;   /**    * This method loops through the children of the specified view, recursing    * as necessary, and inserts pages breaks when needed. It makes a    * rudimentary attempt to avoid "widows" and "orphans".    */   protected void paginate(View v, Rectangle2D allocation) {     // Figure out how tall this view is, and tell it to allocate     // that space among its children     double myheight = v.getPreferredSpan(View.Y_AXIS);     v.setSize((float) printWidth, (float) myheight);     // Now loop through each of the children     int numkids = v.getViewCount();     for (int i = 0; i < numkids; i++) {       View kid = v.getView(i); // this is the child we're working with       // Figure out its size and location       Shape kidshape = v.getChildAllocation(i, allocation);       if (kidshape == null)         continue;       Rectangle2D kidbox = kidshape.getBounds2D();       // This is the Y coordinate of the bottom of the child       double kidpos = kidbox.getY() + kidbox.getHeight() - pageStart;       // If this is the first child of a group, then we want to ensure       // that it doesn't get left by itself at the bottom of a page.       // I.e. we want to prevent "widows"       if ((numkids > 1) && (i == 0)) {         // If it is not near the end of the page, then just move         // on to the next child         if (kidpos < printY + printHeight * MARGIN_ADJUST)           continue;         // Otherwise, the child is near the bottom of the page, so         // break the page before this child and place this child on         // the new page.         breakPage(kidbox.getY());         continue;       }       // If this is the last child of a group, we don't want it to       // appear by itself at the top of a new page, so allow it to       // squeeze past the bottom margin if necessary. This helps to       // prevent "orphans"       if ((numkids > 1) && (i == numkids - 1)) {         // If it fits normally, just move on to the next one         if (kidpos < printY + printHeight)           continue;         // Otherwise, if it fits with extra space, then break the         // at the end of the group         if (kidpos < printY + printHeight / MARGIN_ADJUST) {           breakPage(allocation.getY() + allocation.getHeight());           continue;         }       }       // If the child is not the first or last of a group, then we use       // the bottom margin strictly. If the child fits on the page,       // then move on to the next child.       if (kidpos < printY + printHeight)         continue;       // If we get here, the child doesn't fit on this page. If it has       // no children, then break the page before this child and continue.       if (kid.getViewCount() == 0) {         breakPage(kidbox.getY());         continue;       }       // If we get here, then the child did not fit on the page, but it       // has kids of its own, so recurse to see if any of those kids       // will fit on the page.       paginate(kid, kidbox);     }   }   // For a document of n pages, this list stores the lengths of pages   // 0 through n-2. The last page is assumed to have a full length   ArrayList pageLengths = new ArrayList();   // For a document of n pages, this list stores the starting offset of   // pages 1 through n-1. The offset of page 0 is always 0   ArrayList pageOffsets = new ArrayList();   /**    * Break a page at the specified Y coordinate. Store the necessary    * information into the pageLengths and pageOffsets lists    */   void breakPage(double y) {     double pageLength = y - pageStart - printY;     pageStart = y - printY;     pageLengths.add(new Double(pageLength));     pageOffsets.add(new Double(pageStart));   }   /** Return the number of pages. This is a Pageable method. */   public int getNumberOfPages() {     return numPages;   }   /**    * Return the PageFormat object for the specified page. This implementation    * uses the computed length of the page in the returned PageFormat object.    * The PrinterJob will use this as a clipping region, which will prevent    * extraneous parts of the document from being drawn in the top and bottom    * margins.    */   public PageFormat getPageFormat(int pagenum) {     // On the last page, just return the user-specified page format     if (pagenum == numPages - 1)       return format;     // Otherwise, look up the height of this page and return an     // appropriate PageFormat.     double pageLength = ((Double) pageLengths.get(pagenum)).doubleValue();     PageFormat f = (PageFormat) format.clone();     Paper p = f.getPaper();     if (f.getOrientation() == PageFormat.PORTRAIT)       p.setImageableArea(printX, printY, printWidth, pageLength);     else       p.setImageableArea(printY, printX, pageLength, printWidth);     f.setPaper(p);     return f;   }   /**    * This Printable method returns the Printable object for the specified    * page. Since this class implements both Pageable and Printable, it just    * returns this.    */   public Printable getPrintable(int pagenum) {     return this;   }   /**    * This is the basic Printable method that prints a specified page    */   public int print(Graphics g, PageFormat format, int pageIndex) {     // Return an error code on attempts to print past the end of the doc     if (pageIndex >= numPages)       return NO_SUCH_PAGE;     // Cast the Graphics object so we can use Java2D operations     Graphics2D g2 = (Graphics2D) g;     // Display a page number centered in the area of the top margin.     // Set a new clipping region so we can draw into the top margin     // But remember the original clipping region so we can restore it     Shape originalClip = g.getClip();     g.setClip(new Rectangle(0, 0, (int) printWidth, (int) printY));     // Compute the header to display, measure it, then display it     String numString = "- " + (pageIndex + 1) + " -";     Rectangle2D numBounds = // Get the width and height of the string     headerFont.getStringBounds(numString, g2.getFontRenderContext());     LineMetrics metrics = // Get the ascent and descent of the font     headerFont.getLineMetrics(numString, g2.getFontRenderContext());     g.setFont(headerFont); // Set the font     g.setColor(Color.black); // Print with black ink     g.drawString(numString, // Display the string         (int) (printX + (printWidth - numBounds.getWidth()) / 2),         (int) ((printY - numBounds.getHeight()) / 2 + metrics             .getAscent()));     g.setClip(originalClip); // Restore the clipping region     // Figure out the staring position of the page within the document     double pageStart = 0.0;     if (pageIndex > 0)       pageStart = ((Double) pageOffsets.get(pageIndex - 1)).doubleValue();     // Scroll so that the appropriate part of the document is lined up     // with the upper-left corner of the page     g2.translate(0.0, -pageStart);     // Now paint the entire document. The PrinterJob will have     // established a clipping region, so that only the desired portion     // of the document will actually be drawn on this sheet of paper.     root.paint(g, drawRect);     // Finally return a success code     return PAGE_EXISTS;   }   /**    * This inner class is a concrete implementation of View, with a couple of    * key method implementations. An instance of this class is used as the    * parent of the root View object we want to print    */   static class ParentView extends View {     ViewFactory viewFactory; // The ViewFactory for the hierarchy of views     Container container; // The Container for the hierarchy of views     public ParentView(View v, ViewFactory viewFactory, Container container) {       super(v.getElement());       this.viewFactory = viewFactory;       this.container = container;     }     // These methods return key pieces of information required by     // the View hierarchy.     public ViewFactory getViewFactory() {       return viewFactory;     }     public Container getContainer() {       return container;     }     // These methods are abstract in View, so we've got to provide     // dummy implementations of them here, even though they're never used.     public void paint(Graphics g, Shape allocation) {     }     public float getPreferredSpan(int axis) {       return 0.0f;     }     public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {       return 0;     }     public Shape modelToView(int pos, Shape a, Position.Bias b)         throws BadLocationException {       return a;     }   } }