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;
}
}
}
Related examples in the same category