Thursday, August 21, 2008

Printing questions

Q:What are the differences between the various printing APIs I see in the JDK?

A. These are the printing APIs in the JDK:

1.1 (aka AWT) printing - java.awt.PrintJob

A UI component-oriented printing API
Can print only the 1.1 "Graphics" API, not "Graphics2D" extensions
2D printing - java.awt.print.PrinterJob

A more flexible API for printing documents
Can print all of Java 2D.
Callback printing model
Integrates with javax.print
Java Printing Service - javax.print packages

Adds discovery of printers and capabilities
Adds ability to specify job behaviour through attributes
Adds ability to install custom print services
In summary 1.1/AWT printing can be used for many basic cases but has largely been superseded by 2D printing (as of J2SE 1.2), and when the javax.print package was added in J2SE 1.4 it was made largely complementary to 2D printing and integrated with it for ease of us. So most applications which are focused on rendering to a printer will find it more natural to centre around PrinterJob.

So the presently recommended way to print for most applications is to use java.awt.print.PrinterJob which has methods which leverage javax.print to enumerate printers, and can accept printing attributes from javax.print which define job printing behaviour such as duplex printing.


Q Why is it so hard to print from Swing?

A. In most cases what this turns out to mean is:

"how can I print all a JTable?"
"how can I just ask Swing to print the contents of a text component (rather than
have to work hard to do all the pagination of the rendering myself)?"
These have now been addressed: J2SE 5.0 added direct printing support on JTable, see: http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/JTable.html#print()

In addition, Java SE adds the same capability with javax.swing.JTextComponent.print().


Q: What are the causes of large spool files from java.awt.print.PrinterJob and how can I avoid them?

A: This was much more of a problem in earlier releases but as of J2SE 5.0 most applications should not see a problem. Some of it is platform specific and in general things are better on Windows than Solaris and Linux.

Printing using non opaque colours, gradient paints, and custom paints can cause individual pages (not the whole print job) to be generated as a raster at device resolution. This can be mitigated in a few ways:

The application printing code could avoid using these.
The application could render a selected area which uses these to an offscreen opaque BufferedImage and then draw that BufferedImage to the printer graphics.
the application could lower the device resolution of the print job which may be higher than needed, using the PrinterResolution attribute.
Printing Text on Solaris with non-Postscript standard fonts. In many cases the font requested by an application cannot be expected to be available to a Postscript printer, and the JDK has no way to determine the capabilities of the destination Postscript printer anyway. So often the JDK will generate text as stroked and filled outlines. This is particularly a problem in locales where there is no standard Postscript font support.

For English locales however, Latin text in the standard logical fonts such as Dialog, SansSerif, Serif are mapped directly to Postscript printer fonts and should generate compact Postscript. The same happens in the Japanese locale. Also fonts which are "compatible" such as the TrueType font "Times New Roman" which can be mapped to the Postscript "Times Roman" font will be OK. But keeping to the "logical" fonts is usually the simplest way of getting the most compact output.


Q: When printing using java.awt.PrinterJob, why does it print each page at least twice (and sometimes much more than that)?

A: The root of this is that Java 2D printing needs to be able to print everything that Java 2D can render to the screen, and that includes translucent colours, images etc which cannot always be printed directly in Postscript or GDI except when printing everything as one big image, so the implementation tries to avoid this by calling first to discover the rendering that needs to be done for the page. If its simple opaque rendering then only one more call is needed to render the page. If there are translucent colours then multiple calls are done for "bands" down the page to limit the size of the image being generated and hence constrain peak memory usage.


Q How do I keep the information from getting cut off from the top and left sides of the page when I print using the Java 2D printing API?

Because many printers cannot print on the entire paper surface, the PageFormat specifies the imageable area of the page: this is the portion of the page in which it's safe to render. The specification of the imageable area does not alter the coordinate system; it is provided so that the contents of the page can be rendered so that they don't extend into the area where the printer can't print. If you find that information is clipped from your page, you might need to translate the information to the imageable area of the page, as shown in this sample:

public int print(Graphics g, PageFormat pf, int pageIndex)
throws PrinterException
{
Graphics2D g2 = (Graphics2D)g;
g2.translate(pf.getImageableX(),
pf.getImageableY() + 72);
...
}

Q: Why are my 1-pixel wide lines repositioned when I print them? How do I correct this?

A: For horizontal or vertical 1-pixel wide line fills, it is generally better to use fillRect rather than drawLine. The rounding of the drawLine method moves lines by half a device pixel, which make the lines appear more consistent whether or not antialiasing is applied. Therefore, if you have a 1-pixel wide line, the line is moved by half of its width. Because this adjustment occurs at the device-space level, your lines can move by half of a line width on printouts from high-resolution printers.


Q: How do I trouble shoot printing problems in Windows?

First, make sure that you have the latest printer driver. It is advisable to use the printer vendor's version of the driver and you can generally do this by downloading it online at their website. If you have the latest driver and are still having problems, sometimes a simple change in the printer's setting or Window's advanced spool options will fix your problem. You can also go to http://java.sun.com and search the bug archive for similar problem. A workaround may be available there. If not, please file a bug.


Q: How do I scale an image to fit a page?

Here is a sample code that uses the page's imageable width and height to scale an image.


public int print(Graphics graphics, PageFormat pageFormat, int
pageIndex) throws PrinterException
{
if(pageIndex > 0)
return Printable.NO_SUCH_PAGE;
Graphics2D g2d = (Graphics2D) graphics;

//Set us to the upper left corner
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
AffineTransform at = new AffineTransform();
at.translate(0,0);

//We need to scale the image properly so that it fits on one page.
double xScale = pageFormat.getImageableWidth() / m_image.getWidth();
double yScale = pageFormat.getImageableHeight() / m_image.getHeight();
// Maintain the aspect ratio by taking the min of those 2 factors and
using it to scale both dimensions.
double aspectScale = Math.min(xScale, yScale);

g2d.drawRenderedImage(m_image, at);
return Printable.PAGE_EXISTS;
}

Q: When I print using PDF or HTML using the corresponding DocFlavor, how come it is printed as garbage or plain text?

First, do not assume that any of the defined DocFlavors are supported. Use the PrintService's isDocFlavorSupported to find out which flavors are supported. If you find out that it is not supported but know that your printer can handle this then you may go ahead and print it using the DocFlavor.xxx.AUTOSENSE. This is equivalent to printing a raw data. If on the other hand your printer does not support it you need to write your own PrintService which handles the conversion from PDF/HTML to the printer language e.g. PostScript.

Q: Why is Java unable to print to my CUPS 1.2 printers ?

As noted in the CUPS 1.2 releases notes http://www.cups.org/documentation.php/whatsnew.html CUPS now supports Unix Domain Sockets :

Networking

/Domain Sockets;/
CUPS now supports the much faster UNIX domain sockets for local printing
As a consequence, by default CUPS on Unix/Linux is now configured to listen for connections on both
localhost:631 via a TCP/IP socket connection - this is the same as in
previous releases.

a unix domain socket at /var/run/cups/cups.sock
So far so good, except that the unix domain socket is now the default and the CUPS API call cupsServer() now returns the unix domain socket pat /var/run/cups/cups.sock instead of "localhost".

This can be an incompatible change for any pre-1.2 aware client which retrieves that name and expects to be able to use to open a TCP/IP connection.

In particular this breaks Sun's JDK (aka Java/aka Java SE, aka JRE) versions 1.5 and Java SE 6. ie all releases to date (Dec 2006) which connects to the CUPS IPP server to provide access to platform printing support. The symptom may be no printers found, or printers 'not accepting jobs'. This will be patched in the next available minor update releases of each of JDK 1.5 and JDK6. The Sun bug to track this can be viewed at http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6500903 and the updated releases in which this is fixed will then be viewable there. The expectation is that this will be fixed in JDK6 update 1 (1.6.0_01) and JDK 1.5 update 12 (1.5.0_12).

Until then there are several workaround options for configuring CUPS 1.2 to work with JDK

1. System-wide :

Edit the cups configuration file, usually /etc/cups/cupsd.conf, and locate
and comment out using a # the line that listens on the domain socket. It should look like this when you are done:

# Listen /var/run/cups/cups.sock
Ensure the line to listen on TCP/IP port 631 is present and UNcommented :
Listen localhost:631

Finally save the file and restart cups
sh /etc/init.d/cups restart

2. Per-user workaround #1 Add a ~/.cups/client.conf file with a "ServerName localhost" line. This will make the CUPS API library prefer localhost as the server to report

3. Per-user workaround #2 Set the CUPS_SERVER environment variable to localhost so that the CUPS API library will use and report that : CUPS_SERVER=localhost; export CUPS_SERVER

This approach could if necessary be set only in the environment in which your Java printing application is run and its effects confined to that.

There is one additional step to the workaround for at least some systems. JDK looks for"libcups.so". This is usually a symlink to the specific version, eg "libcups.so.2" but on systems without a developer package it may not exist. The solution is to locate the cup library on your system and if necessary create a symbolic link. For example

cd /usr/lib
ln -s libcups.so.2 libcups.so

0 comments: