Not surprisingly, Java has facilities for writing to a printer.

To get something to the printer–or rather, a printer driver–we start with an instance of PrinterJob, which is the principal class that controls printing. We get an instance of PrinterJob like so:

        PrinterJob job = PrinterJob.getPrinterJob();

And then we assign the job an implementation of the Printable interface, which you write and whose job it is to “paint” each page. Then we ask the PrinterJob to bring up the printer dialog, where the user selects his or her printing preferences and clicks either Ok or Cancel. (This is the only interactive programming we’ll be doing in this course.) If the user clicks Ok, the job continues.

        // Identify the Printable implementation instance.
        job.setPrintable(myPrintable);

        // Display the print dialog. If the user clicks Ok, the call returns
        // True; otherwise it returns False.

        if (!job.printDialog()) {
            return;
        }

        // Tell the job to print.

        job.print();

The Printable interface has a single method:

public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException

The print driver calls this method each time a page needs to be added to the print output. The pageIndex value indicates which page is to be painted; the first page of the document is page 0.

The print() method returns one of two constant values:

  • PAGE_EXISTS, indicating the page should be printed;
  • NO_SUCH_PAGE, indicating it should not.

There’s no knowing the order in which print() will be called for any given page, or how many times per page. So to make sure multiple instances of a given page are consistent, you mustn’t do anything to the data being printed that might cause multiple copies of any given page to be rendered differently on successive calls.

To illustrate printing, we’ve created a sample project which you can download here to import to your workspace. It prints the text of Lincoln’s Gettysburg Address, plus a photograph of Lincoln, on page 1. On page 2, it will print three shapes just to illustrate how that’s done.

public class GettysburgAddress {
    private String text;
    private BufferedImage lincolnImage;

    public GettysburgAddress() throws Exception {
        File inputFile = new File("data", "gettysburg-address.txt");
        BufferedReader rdr = new BufferedReader(new FileReader(inputFile));
        StringBuilder buf = new StringBuilder();
        String line;
        while ((line = rdr.readLine()) != null) {
            buf.append(line + " ");
        }
        rdr.close();
        
        // Replace all multiple spaces with a single space.
        text = buf.toString().trim().replaceAll("  *?", " ");
        
        File imageFile = new File("data", "lincoln.jpg");
        lincolnImage = ImageIO.read(imageFile);
    }
    
    public String getText() {
        return text;
    }
    
    public BufferedImage getImage() {
        return lincolnImage;
    }
    
    public String[] getWords() {
        String [] words = text.split(" ");
        return words;
    }
}

The getWords() method is for convenience. Our project has two classes for producing printout: GettysburgPrinter, which we’re looking at now; and GettysburgPdfWriter for writing a PDF, which we’ll look at later. As you’ll see, they’ll each need the text broken into words.

We’ll print using class GettysburgPrinter, which implements Printable and starts like this:

public class GettysburgPrinter implements Printable {
    private static final int DEFAULT_MARGIN = 72;
...
    private GettysburgAddress address;

    public static void main(String[] args) throws Exception {
        GettysburgAddress address = new GettysburgAddress();
        GettysburgPrinter printer = new GettysburgPrinter(address);
        printer.printAddress();

        System.out.println("Done");
    }

    public GettysburgPrinter(GettysburgAddress address) {
        this.address = address;
    }

    public void printAddress() throws Exception {
        PrinterJob job = PrinterJob.getPrinterJob();
        job.setPrintable(this);

        if (!job.printDialog()) {
            return;
        }

        job.print();
    }

    @Override
    public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
        // Ensure there's at least a one-inch margin on each edge.
        double margin = pageFormat.getImageableX();
        {
            Paper paper = pageFormat.getPaper();

            if (pageFormat.getImageableX() < DEFAULT_MARGIN || pageFormat.getImageableY() < DEFAULT_MARGIN) {
                margin = DEFAULT_MARGIN;
            }

            paper.setImageableArea(margin, margin, paper.getWidth() - margin * 2, //
                    paper.getHeight() - margin * 2);
            pageFormat.setPaper(paper);
        }

margin will be the margin we’ll establish from each edge of the printed page. Here we’ve set margin to start at pageFormat.getImageableX()–essentially the left margin we’ve been provided. But if either the current left or top margin (pageFormat.getImageableY()) is smaller than DEFAULT_MARGIN (which is 72 points), we’ll set the margin to DEFAULT_MARGIN. We’ll then set the printable area of the Paper to have our selected margin on each edge and replace the PageFormat’s paper.

So before we continue, let’s learn about PageFormat and Paper.

PageFormat

Your print() method receives an instance of PageFormat, which determines (what else?) the format of the printed page. Its methods tell you everything you need to know:

MethodFunction
double getHeight()
double getWidth()
Returns the height and width respectively, in points (1/72 inch) of the page.
double getImageableHeight()
double getImageableWidth()
Returns the height and width respectively, in points (1/72 inch) of the imageable area of page–i.e., the area within the margins.
double getImageableX()
double getImageableY()
Return the x and y coordinates, respectively, of the upper left corner of the imageable area of the Paper object associated with this PageFormat.
int getOrientation()
void setOrientation(int orientation)
Gets and sets, respectively, the orientation of the page:
PageFormat.PORTRAIT, PageFormat.LANDSCAPE, or PageFormat.REVERSE_LANDSCAPE.
Paper getPaper()
void setPaper(Paper paper)
Gets and sets respectively, the Paper associated with the PageFormat.
Methods of PageFormat

Paper

You might notice that there are no methods for setting the dimensions or printable area of the page. These are the province of the associated Paper object.

Paper has a few methods of its own.

MethodFunction
double getHeight()
double getWidth()
Returns the height and width respectively, in points (1/72 inch) of the page.
void setSize(double width, double height)Sets the width and height of the paper in points.
void setImageableArea(double x, double y, double width, double height)Establishes a rectangle within which printing occurs. Values are expressed in points. Thus x and y are the top and left margins; getWidth() – x – width is the bottom margin, and getHeight() – y – height is the right margin.
double getImageableX()
double getImageableY()
Returns the x and y coordinates, respectively, of the top left of the imageable area, in points.
double getImageableWidth()
double getImageableHeight()
Returns the width and height, respectively, of the top left of the imageable area, in points.
Methods of Paper

By default, a newly created Paper object defines an 8 1/2 x 11 sheet of paper with 1-inch margins. But you can create a Paper with different properties and replace the PageFormat’s original, as shown above.

The next thing we’ll do is signal our Graphics object to move its coordinate system origin to where the new imageable area begins…

        int imageableX = ((Double) pageFormat.getImageableX()).intValue();
        int imageableY = ((Double) pageFormat.getImageableY()).intValue();
        graphics.translate(imageableX, imageableY); 

And now, the rest of our print() method:

    private static final int IMAGE_WIDTH_IN_POINTS = 108;
...
        switch (pageIndex) {
        case 0: {
            printPage1(graphics, pageFormat);
            return PAGE_EXISTS;
        }
        case 1: {
            printPage2(graphics, pageFormat);
            return PAGE_EXISTS;
        }
        default: {
            return NO_SUCH_PAGE;
        }
        }
    }

Wasn’t that easy? If pageIndex is zero, we call printPage1() to print the first page and return PAGE_EXISTS; if it’s 1, we call printPage2() to print the second page and return PAGE_EXISTS, and for any other value we return NO_SUCH_PAGE. Done!

Well, not quite done. There’s the matter of drawing on the page, which we explore in the next section.