Printable Document : Print « 2D Graphics GUI « Java

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

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)
      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)

        // 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.

      // 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)

        // 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());

      // 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)

      // 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) {

      // 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);
      p.setImageableArea(printY, printX, pageLength, printWidth);
    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(; // Print with black ink
    g.drawString(numString, // Display the string
        (int) (printX + (printWidth - numBounds.getWidth()) / 2),
        (int) ((printY - numBounds.getHeight()) / 2 + metrics
    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) {
      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;


Related examples in the same category

1.The Printing code which implements Printable
2.Print an Image to print directly
3.Simplest SWT Print ExampleSimplest SWT Print Example
4.Print in Java 2: PrinterJob
5.Print in Java: page format and document
6.Print in Java: Multi page
7.Print in Java 5
8.Print in Java 6
9.Simple Book for printingSimple Book for printing
10.Shapes PrintShapes Print
11.Display the print dialog and print
12.Print the printable area outlinePrint the printable area outline
13.Print the text file and print preview themPrint the text file and print preview them
14.Printable demoPrintable demo
15.Print Swing componentsPrint Swing components
17.Another print demoAnother print demo
18.Book demoBook demo
19.Printing the Combined-Java 1.2-and-1.4 WayPrinting the Combined-Java 1.2-and-1.4 Way
20.Printing the Java 1.4 Way
21.Prompting for a Printer
22.Printing the Java 1.1 WayPrinting the Java 1.1 Way
24.PrintFile -- Print a file named on the command linePrintFile -- Print a file named on the command line
25.Print to the standard output
26.PrintPanel is the base for an open-ended series of classesPrintPanel is the base for an open-ended series of classes
27.Pageable TextPageable Text
28.The area of the printable area
29.The area of the actual page
30.Printing Pages with Different Formats
31.Setting the Orientation of a Printed Page
32.Print Dialog: change the default printer settings(default printer, number of copies, range of pages)
33.Printing to a File
34.Listening for Print Service Status Changes
35.Print Image
36.Overriding the Default Action of a JTextComponent
37.Displaying the Page Format Dialog: changes the default page format such as orientation and paper size.
38.Printable Component
39.Create PageFormats on a higher level
40.Printing of a multi-page bookPrinting of a multi-page book