Advertisement

TWEAKING PRINT API FOR TEXT COMPONENT IN JAVA

HTML clipboard

Printing text documents created in Java is done using the default set of methods in Java. But, this API requires a few enhancements to produce better looking printouts resembling professional books. This article describes a tweak on the default print API which could print chapter name as header for left side pages, book name as the header for right side pages, page numbers (with offset) as footer on all pages except the first. After reading this article, you can start incorporating more customizations to the default print API.

INTRODUCTION

Java provides a rich list of components for editing plain and formatted text. All the text related components in the Java swing library originate from JTextComponent contained in the package javax.swing.text. Some of the most commonly used components in the swing library are JTextField, JTextArea, JEditorPane and JTextPane. Less prominent components like JPasswordField also extend the JTextComponent. The JTextComponent provides a handful of overloaded methods (called print) for printing with various options, including header and footer, with page numbers.

Recently, I created a small document editor for my friend, which used the default print method provided by JTextPane for generating printout. The overloaded methods for printing JTextComponent could produce header and footer with page numbers. But, what will happen if you want to edit a book, which spans multiple text files? The default print API starts page number from one for each document (no provision to offset the start number), which makes the resulting printout less professional in appearance. 

Moreover, the default print method does not print header and footer within the top and bottom margins, but eats up space in the area meant for the main document itself. This is not the behaviour in the case of standard word processors, which print the header on top margin and footer on bottom margin.

Moreover, most of the serious publications like books and reports have chapter heading on the header of left side page and book/report title on the header of right side page. It is better if one could draw lines to separate the header, text body and footer in the printout.

This article provides an enhancement to the default print API for JTextComponent in the form of a class called TextComponentPrinter, which takes any instance of JTextComponent and prints it using a header with chapter name on left pages and book title on right pages and footer with centred page number. Two horizontal lines are drawn for separating header, body and footer.

DEFAULT PRINTING METHODS OF JTextComponent
There are three overloaded methods (named print) for printing JTextComponent. Table 1 shows the overloaded print methods and their behaviour. The third print method in the list takes additional arguments for choosing the initial printer service (if the default printer service of the system is not acceptable) and the attributes to be used for printing. The attributes may control the values to be used in the print dialog and the options to be used for printing.

Table 1 Overloaded Print Methods in JTextComponent

Sl. No.

Overloaded Method

Printing behaviour

Print Dialog

Header

Footer

 

Interactive Printing

1.

print()

Yes

No

No

Yes

2.

print(java.text.MessageFormat header,                     java.text.MessageFormat footer)

Yes

Yes

Yes

Yes

3.

print(java.text.MessageFormat header,

                     java.text.MessageFormat footer,

                     boolean showPrintDialog,

                     javax.print.PrintService service,                javax.print.attribute.PrintRequestAttributeSet attributes,

                     boolean interactive)

Optional – based on third argument

Yes

Yes

Optional – based on last argument

The print methods complete the entire job which ranges from displaying a dialog to sending the data to the printer. For customization of printing, JTextComponent provides a method called getPrintable(java.text.MessageFormat header, java.text.MessageFormat footer), which returns a printable object, but provides more leverage on how the actual printing is done.

LITTLE BIT ABOUT PRINTING FROM JAVA

Printing from Java requires acquaintance with atleast five classes in the package java.awt.print, viz., Paper, PageFormat, Printable, Pageable and PrinterJob. A brief introduction to these classes would be appropriate before starting the work on customization of printing.

Paper class represents the paper dimensions to be used for printing. After creating a paper instance using the default argumentless constructor, one should call the methods setSize(double totalWidth, double totalHeight) to set the paper size and setImageableArea(double x, double y, double printableWidth, double printableHeight) to set the left margin, top margin, width of printing and height of printing respectively. The double values used in the argument should be in point measurement (one inch is equal to 72 points and 1 cm is equal to 28.3464566929 points).

PageFormat combines paper size and orientation. Hence, after creating an instance of PageFormat using the default argumentless constructor, one should call setPaper(Paper) method for setting the paper and printable area. Then, the paper orientation should be set using the method setOrientation(int orientation). The arguments to be used for setOrientation method are provided through the static fields PageFormat.PORTRAIT, PageFormat.LANDSCAPE and PageFormat.REVERSE_LANDSCAPE.

Printable is an interface which provides a single method named print(java.awt.Graphics gatewayToPrinter, PageFormat page, int pageNumber). The first argument to this method provides the link to the physical printer using Graphics object. Anything drawn on this Graphics object will be physically drawn by the printer on paper. The second argument is already known. The third argument for the print method is the page number of the document, which starts from 0. This interface should be implemented by the class wishing to print documents.

Pageable is yet another interface for controlling more details relating to printing. Pageable interface comes with three methods: getNumberOfPages() which should return the actual number of pages available for printing (the implementing class should pre-calculate the number of pages), getPageFormat(int pageNumber) for getting the page settings for a particular page of the document and getPrintable(int pageNumber) for getting the Printable object for the particular page. In most of the documents, PageFormat and Printable remain the same for all page numbers. But, in complex situations, they might vary to suit the needs of layout of paper and printing style. 

Finally, we are ready to meet the almighty class PrinterJob, for which the above four classes are subservient. PrinterJob needs to be instantiated by calling the static method getPrinterJob() of the class. After getting a PrinterJob instance, one has to set either the Printable by calling the method setPrintable(Printable p) or Pageable by calling the method setPageable(Pageable p). If Printable is set, PrinterJob does not know how many pages are available for printing when displaying print dialog and it prints all pages using the same PageFormat. If Pageable is set, PrinterJob knows the number of pages to be printed and controls the layout of paper for each page.

Finally, calling printDialog() method of PrinterJob displays a printing dialog and calling print() method prints the job at hand. All this is good if we need not change the page format or other properties using the print dialog. But, if we want to change the print properties using the dialog, it is better to use the overloaded counterparts of the same two methods, which take javax.print.attribute.PrintRequestAttributeSet as the argument, which is an interface. Hence, create an instance of javax.print.attribute.HashPrintRequestAttributeSet, which is a class implementing that interface and pass it as argument to the printDialog and print methods.

I hope I have not confused you too much by describing such a voluminous topic on the print API in Java within a few paragraphs. If you want better details on the printing API in Java, please read the article Printing from Java Programs published in January 2006 issue of Developer IQ (pages 51-57).

METHODOLOGY FOR CUSTOMIZED PRINTING
For customized printing of a JTextComponent, it is better to call the getPrintable(java.text.MessageFormat header, java.text.MessageFormat footer) method with two null arguments for header and footer values. This provides the core for printing the document at hand in the printable area of the paper.

The customization work starts by creating a Pageable class, which counts the number of pages available for printing. The class should implement the three abstract methods of Pageable interface getNumberOfPages(), getPageFormat(int page) and getPrintable(int page). In the present case, the same class should implement the Printable interface. If the class implements Printable interface, each page is printed by calling the print method with the appropriate PageFormat and page number. The new class should hand over the core printing work to the printable object obtained from JTextComponent and then print the header, footer and other things on the same Graphics object to beautify the page.

Hence, a customized printer class should implement both the Pageable and Printable interfaces and keep and instance of Printable obtained from JTextComponent ready for doing the core job.

TextComponentPrinter FOR ENHANCED PRINTING

A class called TextComponentPrinter extending both Pageable and Printable interfaces is developed to carryout the customized printing work. The constructor takes a JTextComponent, header1 for left side pages, header2 for right side pages, font to be used for header, PageFormat and the number for the first page of the printed document.

After doing the initialization of variables, the constructor calls getPrintable method of JTextComponent with two null arguments for header and footer respectively. This provides a Printable object which will use the entire printable area without reserving anything for header and footer. The Printable object is stored in a variable called pr.

The constructor calls the method named countPages to count the number of pages to be printed. This counting is done by repeatedly calling the print method of pr object from a for loop until it returns Printable.NO_SUCH_PAGE. The page count is stored in a variable named numberOfPages.

//TextComponentPrinter.java
import java.awt.print.*;
import javax.swing.text.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;

public class TextComponentPrinter implements Pageable, Printable
{
PageFormat pf = new PageFormat();
int numberOfPages = 0;
JTextComponent tc = null;
Printable pr = null;
String header1, header2;
Font fnt = null;
int pageOffset = 0;

public TextComponentPrinter(JTextComponent tc, String header1, String header2, Font fnt, PageFormat pf, int offset) {
this.tc = tc;
if(pf != null)
this.pf = pf;
this.header1 = header1;
this.header2 = header2;
this.fnt = fnt;
pageOffset = offset;
pr = tc.getPrintable(null, null);
countPages();
}
public Printable getPrintable(int i) { 
return this;
}
public int getNumberOfPages() { 
return numberOfPages;
}
public PageFormat getPageFormat(int i) {
return pf;
}
public int print(Graphics g, PageFormat p, int i) throws PrinterException {
int y = (int)p.getImageableY(), x = (int)p.getImageableX(), 
w = (int)p.getImageableWidth(), h = (int)p.getImageableHeight();
int ret = pr.print(g, p, i);
g.setClip(0,0,(int)p.getWidth(), (int)p.getHeight());

if(i != 0)
drawHeaderAndFooter(i, g, p);
return ret;
}
private void drawHeaderAndFooter(int i, Graphics g, PageFormat p) {
int y = (int)p.getImageableY(), x = (int)p.getImageableX(), 
w = (int)p.getImageableWidth(), h = (int)p.getImageableHeight();
g.setColor(Color.black);
g.setFont(fnt);
String t = i%2==1?header1:header2;
FontMetrics fm = g.getFontMetrics();
int startX = i%2==1?x:x+w-fm.stringWidth(t);
g.drawLine(x, y, x+w, y);
g.drawString(t, startX, y-fm.getMaxDescent()-3);

g.setFont(new Font("Times New Roman", Font.PLAIN, 12));
fm = g.getFontMetrics();
java.awt.geom.Rectangle2D r = fm.getMaxCharBounds(g);
String n = ""+(i+pageOffset);
g.drawLine(x,y+h, x+w, y+h);
g.drawString(n, x+(w-fm.stringWidth(n))/2, y+h+(int)r.getHeight()+3);
}
private void countPages() {
try {
java.awt.image.BufferedImage bi = new java.awt.image.BufferedImage(2,2,
java.awt.image.BufferedImage.TYPE_INT_RGB);
Graphics g = bi.getGraphics();
for(numberOfPages = 0; pr.print(g, pf, numberOfPages) == pr.PAGE_EXISTS; numberOfPages++) ;
}
catch(Exception ex) { ex.printStackTrace(); }
}
}


The print method (required since Printable interface is extended) provides customized printing. The first page of the file does not carry any header or footer (as is the case for each chapter of a book). Header and footer for the subsequent pages are drawn using drawHeaderAndFooter method in the class which takes page number, Graphics object and PageFormat.

The print method contained in the variable pr is called to get the central portion of the paper printed using the text contained in the JTextComponent. The method setClip sets the entire paper eligible for drawing operations. 

Normally, setClip takes the left margin, right margin, printable width and printable height and makes it impossible to draw anything outside the clipped area on the Graphics object. Without calling this method, anything drawn on the header or footer part might not be visible. The reason is that the print method of JTextComponent might call setClip method to restrict all drawing operations to the central printable portion of the Graphics object.

The method drawHeaderAndFooter draws a line below the top margin and prints the header text based on whether the page is a left page or right page. Usually, chapters in a book start from right page and hence, pages bearing even number are treated as right pages and those bearing odd number as left pages. The left pages show chapter heading (header1) and right pages show title of book (header2) using the font passed as argument to the constructor.

The footer is demarcated by drawing a horizontal line above the bottom margin. Page number is printed using Times New Roman font below the demarcation line. The custom font meant for header was not used for page numbers, simply because fonts in regional languages try to render numbers in complex ways – it could be better to have numbers in traditional Indo-Arabic form. (Try out local language numberings if you want).

TESTING THE NEW PRINTING SYSTEM
The printing API was tested using a JTextPane (which inherits JEditorPane, which in turn comes from JTextComponent) and loading a html page on it before printing. Chapter heading was set to Ratio and Proportion and title of the book was set to A Complete Course in Arithmetic and font for headers was set to Arial Bold. The start page number was set to 168. 

An HTML document downloaded from the web was used for testing the print API. The HTML file name was passed into the constructor of java.io.File class, on which toURI and toURL methods were called in sequence to get the java.net.URL instance. The source code and the HTML file are available in the CDROM accompanying current issue of Developer IQ magazine.

// TextPrinterTest.java
import java.awt.print.*;
import java.awt.event.*;
import javax.swing.*;

public class TextPrinterTest extends JFrame implements ActionListener
{
JTextPane tp = new JTextPane();

public TextPrinterTest() {
super("Test of Customized Text Printing"); 
tp.setContentType("text/html");
try { tp.setPage(new java.io.File("Ratio-and-Proportion.htm").toURI().toURL()); }
catch(Exception ex) { ex.printStackTrace(); }
this.getContentPane().add(new JScrollPane(tp), "Center");

JButton b = new JButton("Print");
b.addActionListener(this);
JPanel p = new JPanel(new java.awt.FlowLayout());
p.add(b);
this.getContentPane().add(p, "North");
this.setSize(this.getToolkit().getScreenSize());
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}

public void actionPerformed(ActionEvent ae) {
if(!ae.getActionCommand().equals("Print")) return;
try {
//Obtain Printer job instance
PrinterJob pj = PrinterJob.getPrinterJob();
//create attribute set instance for storing the changes in printing parameters
javax.print.attribute.HashPrintRequestAttributeSet att = 
new javax.print.attribute.HashPrintRequestAttributeSet();
//Shows a print dialog and prints if the job is not cancelled
if(pj.printDialog(att)) {
TextComponentPrinter tcp = new TextComponentPrinter(tp, 
"Ratio and Proportion", "A Complete Course in Arithmetic", 
new java.awt.Font("Arial Bold",java.awt.Font.PLAIN, 12), 
pj.getPageFormat(att), 168);
pj.setPageable(tcp);
pj.print(att);
}
}
catch(Exception ex) { ex.printStackTrace(); }
}
public static void main(String arg[]) {
new TextPrinterTest();
}
}




The Frame displayed on running the above code is shown in Fig.1. The printing operation starts on pressing the Print button. The source code for carrying out the printing is contained within the method actionPerformed, which is given good level of comments for making the purpose of each line of code clear.

Pressing Print button creates a printing attribute holder called att using javax.print.attribute.HashPrintRequestAttributeSet class. A print dialog is displayed (Fig.2). If the print dialog is disposed by pressing Print button in the dialog, the actual printing commences. The document was printed into a PDF file by choosing Primo PDF as the printer.

The resulting pages of the PDF document are shown in figures 3 to 6. Note that there is no header or footer on the first page of the document (Fig.3), that the header shows chapter title with page number 169 in footer on the second page, which is a left side page (Fig.4) and that the header contains title of book with page number 170 in footer on the third page (Fig.5), which is a right side page. Fig.6 shows the left side and right side pages together, as seen in a book.











HAPPY PRINTING 


The print API presented in TextComponentPrinter class provides an enhancement to the default printing abilities of JTextComponent. The header and footer of the document become more professional. You might create your own enhancements like drawing your company logo on the header. Try to incorporate more professional features to the default print API of JTextComponent. Happy printing!

About Author

V. Nagaradjane is a freelance programmer. He may be contacted at nagaradjanev@rediffmail.com








Added on June 29, 2010 Comment

Comments

Post a comment