Project Members

Mike Schoonover, hardware & software
Zenaido Delgado, UT alpha testing
Trey Brown, technical
Daryl Bolen, controls
Rick Girndt, ultrasonic
Curt Irvin, hardware & software
Fiona Zhang, software
You (yes, you!)

Contributing Companies

TRECO IRNDT


Documents

Java Source Code
Rabbit Source Code
DSP Source Code
Schematics

Technical Reference

Eagle Board Layout Tutorial and Project Design Guidelines

ICAP/4 RX Tutorial

Tools

Ultrasonic


Issue List

Things we are working on adding or fixing!

click here


Meta Data

Quips, Quotes, & Definitions


 

 

 

The

   Capulin

         Project

 

Printing is Hard


April 16, 2010              Printing is Hard

Many, if not most, charting systems on the market do a poor job of printing.  They often do not accommodate different printers very well, sometimes require special printer drivers, and rarely allow the printout to be scaled for the best clarity.

Most of these shortcomings are due to the fact that the printing features of most software development packages are very complicated and the documentation is obfuscated.  Printing a simple picture or a small amount of text is easy - anything beyond that is always difficult.  Sometimes third-party software packages can be used to ease the task, but the Capulin Project developers are averse to such solutions because they make future upgrades problematic (read more on that).

The printing feature for the Capulin Project is especially difficult because of the flexibility allowed in regard to the number and length of charts.  The software must be able to scale, separate, and handle multiple page prints for a nearly endless variety of layouts.

Like other languages, Java offers some nice printing features - but things get complicated in a hurry.  Without researching the problem on the Internet, implementing a complex print function would be even harder.  Even so, it seems that few people have delved deeply into the issue.  The information which follows on this page provides a detailed explanation of the problems encountered with printing in Java and the solutions which were implemented.

Author: Mike Schoonover

(add comment)


Java Printing Overview

There are two main parts to Java's printing features:

The Print Service is mostly used for streaming a document from disk to the printer.  It also provides some interaction with the printers which is unavailable with the basic PrinterJob.

From the Java Tutorial and information from various websites the following basic code is suggested:

(click here to skip past the code and read more...)

General Printing Example

 

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.print.*;
import javax.print.attribute.standard.PrinterResolution;
import javax.print.attribute.standard.PrintQuality;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.HashPrintRequestAttributeSet;
import java.awt.geom.AffineTransform;

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// class Viewer
//

public class Viewer extends Object implements Printable {

//  ...

// add a constructor as necessary

// ....

//-----------------------------------------------------------------------------
// Viewer::startPrint
//
// Starts the printing process.
//

void startPrint()
{

//force format to landscape
PageFormat pf = new PageFormat();
pf.setOrientation(PageFormat.LANDSCAPE);

PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();

//some attributes are added by instantiating
PrinterResolution pR = new PrinterResolution(600, 600, PrinterResolution.DPI);
aset.add(pR);

//some attributes cannot be instantiated as their constructors are protected
//or abstract - these are used by adding their static member variables
aset.add(PrintQuality.NORMAL);

PrinterJob job = PrinterJob.getPrinterJob();

//set this object to handle the print calls from the printing system and
//tells it to use the page format pf
job.setPrintable(this, pf);

//display dialog allowing user to setup the printer

if (job.printDialog()) {
    try {
        //start printing - Java will call the print function of the object
        //specified in the call to job.setPrintable which must implement the
        //Printable interface
        job.print(aset);
        }
    catch (PrinterException e) {
        displayErrorMessage("Error sending to printer.");
        }
    }//if (job.printDialog()) 

}//end of Viewer::startPrint
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Viewer::print
//
// Catches calls from the printing system for requests to render images
// for printing.
//

@Override
public int print(Graphics g, PageFormat pPF, int page) throws
PrinterException

{

Graphics2D g2 = (Graphics2D) g;

//the transform from the PageFormat object rotates and shifts the final output
//before printing
//(use the next line to access the PageFormat transform)
//AffineTransform aT = new AffineTransform(pPF.getMatrix());

//The transform from the Graphics2D object specifies the scaling and rotation
//to be applied when rendering.
//If portrait mode is used, then scaleX and scaleY of the transform matrix will
//be non-zero and shearX and shearY will be zero. If landscape mode is used,
//the opposite will be true.
//There seems to be no way to retrieve the default DPI.   It can instead be
//calculated from the scale Java has applied to the Graphics2D object.
//Regardless of the printer's DPI, Java applies a scale to the transform so
//that 72 pixels on the screen will be one inch on the paper (72 ppi). If you
//change the DPI, then Java will change the scaling so that the image is still
//printed at the same size. The print texture will look different, but the
//image size will remain the same. To counteract Java's scaling, the scaling
//can be undone as shown a few lines down.

//get a copy of the Graphics2D transform so we can deduce the scale
AffineTransform gAT = g2.getTransform();
double scaleX = gAT.getScaleX();
double shearX = gAT.getShearX();

//With no scaling, scaleX or shearX would be 1.0 depending on the paper
//orientation. Java scales the matrix so that 72 pixels will be one inch on the
//paper. Thus if the printer DPI is 600, scaleX or shearX will be 8.333
//(600 / 72). The scale can be removed by scaling by the inverse. Whichever
//value is non-zero will be the scale. The absolute value must be taken
//because the value can be negative for certain rotations.

//get the inverse of the scale already applied by Java
double unscale;
if (scaleX != 0) unscale = Math.abs(1 / scaleX);
else unscale = Math.abs(1 / shearX);

if (page > 0) { // we have only one page, and 'page' is zero-based
    return NO_SUCH_PAGE; //returning this signals end of printing
    }

// User (0,0) is typically outside the imageable area, so we must
// translate by the X and Y values in the PageFormat to avoid clipping - this
// will take into account margins, headers, and footers.

// translate before unscaling to position the printout properly on the paper
g2.translate(pPF.getImageableX(), pPF.getImageableY());

//remove the scale applied by Java - now the print will be at one pixel per dot
g2.scale(unscale, unscale);

//apply desired scaling here - could be values set by user
g2.scale(2, 2);

//disable double buffering for components to improve scaling and print speed
disableDoubleBuffering(someSwingComponent);

//you can call a component's print function
someSwingComponent.print(g2);

enableDoubleBuffering(someSwingComponent);

//  and/or call a function which draws
doSomeDrawing(g2);

// tell the caller that page is printable
return PAGE_EXISTS;

}//end of Viewer::print
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Viewer::disableDoubleBuffering
//
// Disables double buffering for Component pC and all components it might
// contain.
//
// This should be done before rendering to a print graphic object so that
// scaling works properly and unnecessary data is not sent to the print spooler.
//
// This concept by Marty Hall and Bob Evans.
// http://www.apl.jhu.edu/~hall/java/Swing-Tutorial/Swing-Tutorial-Printing.html
//
// See also Viewer::disableDoubleBuffering
//

public static void disableDoubleBuffering(Component c) {

RepaintManager currentManager = RepaintManager.currentManager(c);
currentManager.setDoubleBufferingEnabled(false);

}//end of Viewer::disableDoubleBuffering
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Viewer::enableDoubleBuffering
//
// Enables double buffering for Component pC and all components it might
// contain.
//
// This should be done after rendering to a printer graphics object.
//
// This concept by Marty Hall and Bob Evans.
// http://www.apl.jhu.edu/~hall/java/Swing-Tutorial/Swing-Tutorial-Printing.html
//
// See also Viewer::disableDoubleBuffering
//

public static void enableDoubleBuffering(Component c)
{

RepaintManager currentManager = RepaintManager.currentManager(c);
currentManager.setDoubleBufferingEnabled(true);

}//end of Viewer::enableDoubleBuffering
//-----------------------------------------------------------------------------

}//end of class Viewer
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

The Print Dialog

Java provides two methods to display a print dialog so that the user can view and change print settings:  the native dialog and the cross platform dialog.

For the native dialog, settings can be made programmatically before opening the dialog so that the user will see the presets.  However, if the user makes changes using the printer properties button, these changes may not be accurately reflected back to the Java print system.  The printer will receive the changes, but the program will not accurately receive information regarding the changes.  This makes the native dialog somewhat unpredictable and therefore unreliable.

Using the Native Print Dialog

Here is a code snippet to preset some values and open a native print dialog.  Note how the native version is invoked by calling printDialog with no parameters:

//Using the Native Print Dialog
//(using the cross platform dialog is preferred over this method)

//here are some imports related to printing
//other non-printing related imports may be needed to compile

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.print.*;
import javax.print.attribute.standard.PrinterResolution;
import javax.print.attribute.standard.PrintQuality;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.standard.MediaSizeName;
import java.awt.geom.AffineTransform;

//...code to define class and functions...

//code inside function which initiates printing...

//force format to landscape
PageFormat pf = new PageFormat();
pf.setOrientation(PageFormat.LANDSCAPE);

//select the paper size according to what the user has selected
//this will set the paper size in the print dialog (the sizes will be displayed
// on the dialog as the named sizes Letter, Legal, and A4)
//note that yourVariable is just a variable which is controlled by a user
//input control

double width  612, length = 792;
if (yourVariable.contains("8-1/2 x 11")){
    width = 612; length = 792; //letter size in 1/72ths of an inch
    }
if (yourVariable.contains("8-1/2 x 14")){
    width = 612; length = 1008; //legal size in 1/72ths of an inch
    }
if (yourVariable.contains("A4")){
    width = 595.44; length = 841.68; //A4 size in 1/72ths of an inch
    }

//note that the printing system will not recalculate the imageable area for
//paper - this must also be done here (not shown in this example)

Paper paper = new Paper();
paper.setSize(width, length);
pf.setPaper(paper);

PrinterJob job = PrinterJob.getPrinterJob();

//set this object to handle the print calls from the printing system and
//tells it to use the page format pf
job.setPrintable(this, pf);

//display dialog allowing user to setup the printer
if (job.printDialog()) {
    try {
        //start printing - Java will call the print function of the object
        //specified in the call to job.setPrintable which must implement the
        //Printable interface
        job.print();
        }
    catch (PrinterException e) {
        displayErrorMessage("Error sending to printer.");
        }
    }

//now the print function of the Printable interface class which was passed to setPrintable will
//called
//see first code example for the actual printing function

When the paper size is set, the margins must be accounted for and the paper's imageable x, y, width, and height must be calculated and set.  Java does not derive these things from the paper size or printer settings.

Using the Cross Platform Print Dialog (this is the preferred method)

The cross platform print dialog seems to work better because Java gets feedback from the user changes.

Note that some of the Java documentation implies that this method only works with classes which implement the Pageable interface, but the tested code here works fine with the Printable interface.

 

 

 


back to main project page