|
The original Java Development Kit had no support for printing at all. It was not possible to print from applets, and you had to get a third-party library if you wanted to print in an application. JDK 1.1 introduced very lightweight printing support, just enough to produce simple printouts, as long as you were not too particular about the print quality. The 1.1 printing model was designed to allow browser vendors to print the surface of an applet as it appears on a web page (which, however, the browser vendors have not embraced). JDK 1.2 introduced the beginnings of a robust printing model that is fully integrated with 2D graphics, and JDK 1.3 provided minor improvements. JDK 1.4 adds important enhancements, such as discovery of printer features and streaming print jobs for server-side print management. In this section, we show you how you can easily print a drawing on a single sheet of paper, how you can manage a multipage printout, and how you can benefit from the elegance of the Java 2D imaging model and easily generate a print preview dialog box. NOTE
Graphics PrintingIn this section, we tackle what is probably the most common printing situation: to print a 2D graphic. Of course, the graphic can contain text in various fonts or even consist entirely of text. To generate a printout, you take care of these two tasks:
The Printable interface has a single method: int print(Graphics g, PageFormat format, int page) That method is called whenever the print engine needs to have a page formatted for printing. Your code draws the text and image that are to be printed onto the graphics context. The page format tells you the paper size and the print margins. The page number tells you which page to render. To start a print job, you use the PrinterJob class. First, you call the static getPrinterJob method to get a print job object. Then set the Printable object that you want to print. Printable canvas = . . .; PrinterJob job = PrinterJob.getPrinterJob(); job.setPrintable(canvas); CAUTION
Before starting the print job, you should call the printDialog method to display a print dialog box (see Figure 7-34). That dialog box gives the user a chance to select the printer to be used (in case multiple printers are available), the page range that should be printed, and various printer settings. Figure 7-34. A cross-platform print dialog boxYou collect printer settings in an object of a class that implements the PrintRequestAttributeSet interface to the printDialog method. The JDK provides a HashPrintRequestAttributeSet class for that purpose. HashPrintRequestAttributeSet attributes = new HashPrintRequestAttributeSet(); Add attribute settings and pass the attributes object to the printDialog method. The printDialog method returns true if the user clicked OK and false if the user canceled the dialog box. If the user accepted, call the print method of the PrinterJob class to start the printing process. The print method may throw a PrinterException. Here is the outline of the printing code: if (job.printDialog(attributes)) { try { job.print(attributes); } catch (PrinterException exception) { . . . } } NOTE
During printing, the print method of the PrinterJob class makes repeated calls to the print method of the Printable object associated with the job. Because the job does not know how many pages you want to print, it simply keeps calling the print method. As long as the print method returns the value Printable.PAGE_EXISTS, the print job keeps producing pages. When the print method returns Printable.NO_SUCH_PAGE, the print job stops. CAUTION
Therefore, the print job doesn't have an accurate page count until after the printout is complete. For that reason, the print dialog box can't display the correct page range and instead displays a page range of "Pages 1 to 1." You will see in the next section how to avoid this blemish by supplying a Book object to the print job. During the printing process, the print job repeatedly calls the print method of the Printable object. The print job is allowed to make multiple calls for the same page. You should therefore not count pages inside the print method but always rely on the page number parameter. There is a good reason why the print job may call the print method repeatedly for the same page. Some printers, in particular dot-matrix and inkjet printers, use banding. They print one band at a time, advance the paper, and then print the next band. The print job may use banding even for laser printers that print a full page at a timeit gives the print job a way of managing the size of the spool file. If the print job needs the Printable object to print a band, then it sets the clip area of the graphics context to the requested band and calls the print method. Its drawing operations are clipped against the band rectangle, and only those drawing elements that show up in the band are rendered. Your print method need not be aware of that process, with one caveat: It should not interfere with the clip area. CAUTION
The PageFormat parameter of the print method contains information about the printed page. The methods getWidth and getHeight return the paper size, measured in points. One point is 1/72 of an inch. (An inch equals 25.4 millimeters.) For example, A4 paper is approximately 595 by 842 points, and U.S. letter-size paper is 612 by 792 points. Points are a common measurement in the printing trade in the United States. Much to the chagrin of the rest of the world, the printing package uses point units for two purposes. Paper sizes and paper margins are measured in points. And the default unit for all print graphics contexts is one point. You can verify that in the example program at the end of this section. The program prints two lines of text that are 72 units apart. Run the example program and measure the distance between the baselines. They are exactly 1 inch or 25.4 millimeters apart. The getWidth and getHeight methods of the PageFormat class give you the complete paper size. Not all of the paper area is printable. Users typically select margins, and even if they don't, printers need to somehow grip the sheets of paper on which they print and therefore have a small unprintable area around the edges. The methods getImageableWidth and getImageableHeight tell you the dimensions of the area that you can actually fill. However, the margins need not be symmetrical, so you must also know the top-left corner of the imageable area (see Figure 7-35), which you obtain by the methods getImageableX and getImageableY. Figure 7-35. Page format measurementsTIP
If you want your users to choose the settings for the page margins or to switch between portrait and landscape orientation without setting other printing attributes, then you can call the pageDialog method of the PrinterJob class: PageFormat format = job.pageDialog(attributes); NOTE
Example 7-12 shows how to render the same set of shapes on the screen and on the printed page. A subclass of JPanel implements the Printable interface. Both the paintComponent and the print methods call the same method to carry out the actual drawing. class PrintPanel extends JPanel implements Printable { public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; drawPage(g2); } public int print(Graphics g, PageFormat pf, int page) throws PrinterException { if (page >= 1) return Printable.NO_SUCH_PAGE; Graphics2D g2 = (Graphics2D) g; g2.translate(pf.getImageableX(), pf.getImageableY()); drawPage(g2); return Printable.PAGE_EXISTS; } public void drawPage(Graphics2D g2) { // shared drawing code goes here . . . } . . . } CAUTION
This example displays and prints the same image as Example 7-6 on page 498, namely, the outline of the message "Hello, World" that is used as a clipping area for a pattern of lines (see Figure 7-22 on page 497). Click the Print button to start printing, or click the Page setup button to open the page setup dialog box. Example 7-12 shows the code..
Example 7-12. PrintTest.java[View full width] 1. import java.awt.*; 2. import java.awt.event.*; 3. import java.awt.font.*; 4. import java.awt.geom.*; 5. import java.awt.print.*; 6. import java.util.*; 7. import javax.print.*; 8. import javax.print.attribute.*; 9. import javax.swing.*; 10. 11. /** 12. This program demonstrates how to print 2D graphics 13. */ 14. public class PrintTest 15. { 16. public static void main(String[] args) 17. { 18. JFrame frame = new PrintTestFrame(); 19. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 20. frame.setVisible(true); 21. } 22. } 23. 24. /** 25. This frame shows a panel with 2D graphics and buttons 26. to print the graphics and to set up the page format. 27. */ 28. class PrintTestFrame extends JFrame 29. { 30. public PrintTestFrame() 31. { 32. setTitle("PrintTest"); 33. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 34. 35. canvas = new PrintPanel(); 36. add(canvas, BorderLayout.CENTER); 37. 38. attributes = new HashPrintRequestAttributeSet(); 39. 40. JPanel buttonPanel = new JPanel(); 41. JButton printButton = new JButton("Print"); 42. buttonPanel.add(printButton); 43. printButton.addActionListener(new 44. ActionListener() 45. { 46. public void actionPerformed(ActionEvent event) 47. { 48. try 49. { 50. PrinterJob job = PrinterJob.getPrinterJob(); 51. job.setPrintable(canvas); 52. if (job.printDialog(attributes)) 53. job.print(attributes); 54. } 55. catch (PrinterException e) 56. { 57. JOptionPane.showMessageDialog(PrintTestFrame.this, e); 58. } 59. } 60. }); 61. 62. JButton pageSetupButton = new JButton("Page setup"); 63. buttonPanel.add(pageSetupButton); 64. pageSetupButton.addActionListener(new 65. ActionListener() 66. { 67. public void actionPerformed(ActionEvent event) 68. { 69. PrinterJob job = PrinterJob.getPrinterJob(); 70. job.pageDialog(attributes); 71. } 72. }); 73. 74. add(buttonPanel, BorderLayout.NORTH); 75. } 76. 77. private PrintPanel canvas; 78. private PrintRequestAttributeSet attributes; 79. 80. private static final int DEFAULT_WIDTH = 300; 81. private static final int DEFAULT_HEIGHT = 300; 82. } 83. 84. /** 85. This panel generates a 2D graphics image for screen display 86. and printing. 87. */ 88. class PrintPanel extends JPanel implements Printable 89. { 90. public void paintComponent(Graphics g) 91. { 92. super.paintComponent(g); 93. Graphics2D g2 = (Graphics2D) g; 94. drawPage(g2); 95. } 96. 97. public int print(Graphics g, PageFormat pf, int page) 98. throws PrinterException 99. { 100. if (page >= 1) return Printable.NO_SUCH_PAGE; 101. Graphics2D g2 = (Graphics2D) g; 102. g2.translate(pf.getImageableX(), pf.getImageableY()); 103. g2.draw(new Rectangle2D.Double(0, 0, pf.getImageableWidth(), pf .getImageableHeight())); 104. 105. drawPage(g2); 106. return Printable.PAGE_EXISTS; 107. } 108. 109. /** 110. This method draws the page both on the screen and the 111. printer graphics context. 112. @param g2 the graphics context 113. */ 114. public void drawPage(Graphics2D g2) 115. { 116. FontRenderContext context = g2.getFontRenderContext(); 117. Font f = new Font("Serif", Font.PLAIN, 72); 118. GeneralPath clipShape = new GeneralPath(); 119. 120. TextLayout layout = new TextLayout("Hello", f, context); 121. AffineTransform transform = AffineTransform.getTranslateInstance(0, 72); 122. Shape outline = layout.getOutline(transform); 123. clipShape.append(outline, false); 124. 125. layout = new TextLayout("World", f, context); 126. transform = AffineTransform.getTranslateInstance(0, 144); 127. outline = layout.getOutline(transform); 128. clipShape.append(outline, false); 129. 130. g2.draw(clipShape); 131. g2.clip(clipShape); 132. 133. final int NLINES =50; 134. Point2D p = new Point2D.Double(0, 0); 135. for (int i = 0; i < NLINES; i++) 136. { 137. double x = (2 * getWidth() * i) / NLINES; 138. double y = (2 * getHeight() * (NLINES - 1 - i)) 139. / NLINES; 140. Point2D q = new Point2D.Double(x, y); 141. g2.draw(new Line2D.Double(p, q)); 142. } 143. } 144. } java.awt.print.Printable 1.2
java.awt.print.PrinterJob 1.2
java.awt.print.PageFormat 1.2
Multiple-Page PrintingIn practice, you usually shouldn't pass a raw Printable object to a print job. Instead, you should obtain an object of a class that implements the Pageable interface. The Java platform supplies one such class, called Book. A book is made up of sections, each of which is a Printable object. You make a book by adding Printable objects and their page counts. Book book = new Book(); Printable coverPage = . . .; Printable bodyPages = . . .; book.append(coverPage, pageFormat); // append 1 page book.append(bodyPages, pageFormat, pageCount); Then, you use the setPageable method to pass the Book object to the print job. printJob.setPageable(book); Now the print job knows exactly how many pages to print. Then, the print dialog box displays an accurate page range, and the user can select the entire range or subranges. CAUTION
From your perspective as a programmer, the biggest challenge about using the Book class is that you must know how many pages each section will have when you print it. Your Printable class needs a layout algorithm that computes the layout of the material on the printed pages. Before printing starts, invoke that algorithm to compute the page breaks and the page count. You can retain the layout information so you have it handy during the printing process. You must guard against the possibility that the user has changed the page format. If that happens, you must recompute the layout, even if the information that you want to print has not changed. Example 7-13 shows how to produce a multipage printout. This program prints a message in very large characters on a number of pages (see Figure 7-39). You can then trim the margins and tape the pages together to form a banner. Figure 7-39. A bannerThe layoutPages method of the Banner class computes the layout. We first lay out the message string in a 72-point font. We then compute the height of the resulting string and compare it with the imageable height of the page. We derive a scale factor from these two measurements. When printing the string, we magnify it by that scale factor. CAUTION
The getPageCount method of the Banner class first calls the layout method. Then it scales up the width of the string and divides it by the imageable width of each page. The quotient, rounded up to the next integer, is the page count. It sounds like it might be difficult to print the banner since characters can be broken across multiple pages. However, thanks to the power of the Java 2D API, this turns out not to be a problem at all. When a particular page is requested, we simply use the translate method of the Graphics2D class to shift the top-left corner of the string to the left. Then, we set a clip rectangle that equals the current page (see Figure 7-40). Finally, we scale the graphics context with the scale factor that the layout method computed. Figure 7-40. Printing a page of a bannerThis example shows the power of transformations. The drawing code is kept simple, and the transformation does all the work of placing the drawing at the appropriate place. Finally, the clip cuts away the part of the image that falls outside the page. In the next section, you will see another compelling use of transformations, to display a print preview. Print PreviewMost professional programs have a print preview mechanism that lets you look at your pages on the screen so that you won't waste paper on a printout that you don't like. The printing classes of the Java platform do not supply a standard "print preview" dialog box, but it is easy to design your own (see Figure 7-41). In this section, we show you how. The PrintPreviewDialog class in Example 7-13 is completely genericyou can reuse it to preview any kind of printout. Figure 7-41. A print preview dialog boxTo construct a PrintPreviewDialog, you supply either a Printable or a Book, together with a PageFormat object. The surface of the dialog box contains a PrintPreviewCanvas. As you use the Next and Previous buttons to flip through the pages, the paintComponent method calls the print method of the Printable object for the requested page. Normally, the print method draws the page context on a printer graphics context. However, we supply the screen graphics context, suitably scaled so that the entire printed page fits inside a small screen rectangle. float xoff = . . .; // left of page float yoff = . . .; // top of page float scale = . . .; // to fit printed page onto screen g2.translate(xoff, yoff); g2.scale(scale, scale); Printable printable = book.getPrintable(currentPage); printable.print(g2, pageFormat, currentPage); The print method never knows that it doesn't actually produce printed pages. It simply draws onto the graphics context, thereby producing a microscopic print preview on the screen. This is a compelling demonstration of the power of the Java 2D imaging model. Example 7-13 contains the code for the banner printing program and the print preview dialog box. Type "Hello, World!" into the text field and look at the print preview, then print the banner. Example 7-13. BookTest.java[View full width] 1. import java.awt.*; 2. import java.awt.event.*; 3. import java.awt.font.*; 4. import java.awt.geom.*; 5. import java.awt.print.*; 6. import java.util.*; 7. import javax.print.*; 8. import javax.print.attribute.*; 9. import javax.swing.*; 10. 11. /** 12. This program demonstrates the printing of a multipage 13. book. It prints a "banner," by blowing up a text string 14. to fill the entire page vertically. The program also 15. contains a generic print preview dialog box. 16. */ 17. public class BookTest 18. { 19. public static void main(String[] args) 20. { 21. JFrame frame = new BookTestFrame(); 22. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 23. frame.setVisible(true); 24. } 25. } 26. 27. /** 28. This frame has a text field for the banner text and 29. buttons for printing, page setup, and print preview. 30. */ 31. class BookTestFrame extends JFrame 32. { 33. public BookTestFrame() 34. { 35. setTitle("BookTest"); 36. 37. text = new JTextField(); 38. add(text, BorderLayout.NORTH); 39. 40. attributes = new HashPrintRequestAttributeSet(); 41. 42. JPanel buttonPanel = new JPanel(); 43. 44. JButton printButton = new JButton("Print"); 45. buttonPanel.add(printButton); 46. printButton.addActionListener(new 47. ActionListener() 48. { 49. public void actionPerformed(ActionEvent event) 50. { 51. try 52. { 53. PrinterJob job = PrinterJob.getPrinterJob(); 54. job.setPageable(makeBook()); 55. if (job.printDialog(attributes)) 56. { 57. job.print(attributes); 58. } 59. } 60. catch (PrinterException e) 61. { 62. JOptionPane.showMessageDialog( 63. BookTestFrame.this, e); 64. } 65. } 66. }); 67. 68. JButton pageSetupButton = new JButton("Page setup"); 69. buttonPanel.add(pageSetupButton); 70. pageSetupButton.addActionListener(new 71. ActionListener() 72. { 73. public void actionPerformed(ActionEvent event) 74. { 75. PrinterJob job = PrinterJob.getPrinterJob(); 76. pageFormat = job.pageDialog(attributes); 77. } 78. }); 79. 80. JButton printPreviewButton = new JButton("Print preview"); 81. buttonPanel.add(printPreviewButton); 82. printPreviewButton.addActionListener(new 83. ActionListener() 84. { 85. public void actionPerformed(ActionEvent event) 86. { 87. PrintPreviewDialog dialog = new PrintPreviewDialog(makeBook()); 88. dialog.setVisible(true); 89. } 90. }); 91. 92. add(buttonPanel, BorderLayout.SOUTH); 93. pack(); 94. } 95. 96. /** 97. Makes a book that contains a cover page and the 98. pages for the banner. 99. */ 100. public Book makeBook() 101. { 102. if (pageFormat == null) 103. { 104. PrinterJob job = PrinterJob.getPrinterJob(); 105. pageFormat = job.defaultPage(); 106. } 107. Book book = new Book(); 108. String message = text.getText(); 109. Banner banner = new Banner(message); 110. int pageCount = banner.getPageCount((Graphics2D)getGraphics(), pageFormat); 111. book.append(new CoverPage(message + " (" + pageCount + " pages)"), pageFormat); 112. book.append(banner, pageFormat, pageCount); 113. return book; 114. } 115. 116. private JTextField text; 117. private PageFormat pageFormat; 118. private PrintRequestAttributeSet attributes; 119. } 120. 121. /** 122. A banner that prints a text string on multiple pages. 123. */ 124. class Banner implements Printable 125. { 126. /** 127. Constructs a banner 128. @param m the message string 129. */ 130. public Banner(String m) 131. { 132. message = m; 133. } 134. 135. /** 136. Gets the page count of this section. 137. @param g2 the graphics context 138. @param pf the page format 139. @return the number of pages needed 140. */ 141. public int getPageCount(Graphics2D g2, PageFormat pf) 142. { 143. if (message.equals("")) return 0; 144. FontRenderContext context = g2.getFontRenderContext(); 145. Font f = new Font("Serif", Font.PLAIN, 72); 146. Rectangle2D bounds = f.getStringBounds(message, context); 147. scale = pf.getImageableHeight() / bounds.getHeight(); 148. double width = scale * bounds.getWidth(); 149. int pages = (int)Math.ceil(width / pf.getImageableWidth()); 150. return pages; 151. } 152. 153. public int print(Graphics g, PageFormat pf, int page) 154. throws PrinterException 155. { 156. Graphics2D g2 = (Graphics2D)g; 157. if (page > getPageCount(g2, pf)) 158. return Printable.NO_SUCH_PAGE; 159. g2.translate(pf.getImageableX(), pf.getImageableY()); 160. 161. drawPage(g2, pf, page); 162. return Printable.PAGE_EXISTS; 163. } 164. 165. public void drawPage(Graphics2D g2, PageFormat pf, int page) 166. { 167. if (message.equals("")) return; 168. page--; // account for cover page 169. 170. drawCropMarks(g2, pf); 171. g2.clip(new Rectangle2D.Double(0, 0, pf.getImageableWidth(), pf .getImageableHeight())); 172. g2.translate(-page * pf.getImageableWidth(), 0); 173. g2.scale(scale, scale); 174. FontRenderContext context = g2.getFontRenderContext(); 175. Font f = new Font("Serif", Font.PLAIN, 72); 176. TextLayout layout = new TextLayout(message, f, context); 177. AffineTransform transform = AffineTransform.getTranslateInstance(0, layout .getAscent()); 178. Shape outline = layout.getOutline(transform); 179. g2.draw(outline); 180. } 181. 182. /** 183. Draws 1/2" crop marks in the corners of the page. 184. @param g2 the graphics context 185. @param pf the page format 186. */ 187. public void drawCropMarks(Graphics2D g2, PageFormat pf) 188. { 189. final double C = 36; // crop mark length = 1/2 inch 190. double w = pf.getImageableWidth(); 191. double h = pf.getImageableHeight(); 192. g2.draw(new Line2D.Double(0, 0, 0, C)); 193. g2.draw(new Line2D.Double(0, 0, C, 0)); 194. g2.draw(new Line2D.Double(w, 0, w, C)); 195. g2.draw(new Line2D.Double(w, 0, w - C, 0)); 196. g2.draw(new Line2D.Double(0, h, 0, h - C)); 197. g2.draw(new Line2D.Double(0, h, C, h)); 198. g2.draw(new Line2D.Double(w, h, w, h - C)); 199. g2.draw(new Line2D.Double(w, h, w - C, h)); 200. } 201. 202. private String message; 203. private double scale; 204. } 205. 206. /** 207. This class prints a cover page with a title. 208. */ 209. class CoverPage implements Printable 210. { 211. /** 212. Constructs a cover page. 213. @param t the title 214. */ 215. public CoverPage(String t) 216. { 217. title = t; 218. } 219. 220. public int print(Graphics g, PageFormat pf, int page) 221. throws PrinterException 222. { 223. if (page >= 1) return Printable.NO_SUCH_PAGE; 224. Graphics2D g2 = (Graphics2D)g; 225. g2.setPaint(Color.black); 226. g2.translate(pf.getImageableX(), pf.getImageableY()); 227. FontRenderContext context = g2.getFontRenderContext(); 228. Font f = g2.getFont(); 229. TextLayout layout = new TextLayout(title, f, context); 230. float ascent = layout.getAscent(); 231. g2.drawString(title, 0, ascent); 232. return Printable.PAGE_EXISTS; 233. } 234. 235. private String title; 236. } 237. 238. /** 239. This class implements a generic print preview dialog. 240. */ 241. class PrintPreviewDialog extends JDialog 242. { 243. /** 244. Constructs a print preview dialog. 245. @param p a Printable 246. @param pf the page format 247. @param pages the number of pages in p 248. */ 249. public PrintPreviewDialog(Printable p, PageFormat pf, int pages) 250. { 251. Book book = new Book(); 252. book.append(p, pf, pages); 253. layoutUI(book); 254. } 255. 256. /** 257. Constructs a print preview dialog. 258. @param b a Book 259. */ 260. public PrintPreviewDialog(Book b) 261. { 262. layoutUI(b); 263. } 264. 265. /** 266. Lays out the UI of the dialog. 267. @param book the book to be previewed 268. */ 269. public void layoutUI(Book book) 270. { 271. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 272. 273. canvas = new PrintPreviewCanvas(book); 274. add(canvas, BorderLayout.CENTER); 275. 276. JPanel buttonPanel = new JPanel(); 277. 278. JButton nextButton = new JButton("Next"); 279. buttonPanel.add(nextButton); 280. nextButton.addActionListener(new 281. ActionListener() 282. { 283. public void actionPerformed(ActionEvent event) 284. { 285. canvas.flipPage(1); 286. } 287. }); 288. 289. JButton previousButton = new JButton("Previous"); 290. buttonPanel.add(previousButton); 291. previousButton.addActionListener(new 292. ActionListener() 293. { 294. public void actionPerformed(ActionEvent event) 295. { 296. canvas.flipPage(-1); 297. } 298. }); 299. 300. JButton closeButton = new JButton("Close"); 301. buttonPanel.add(closeButton); 302. closeButton.addActionListener(new 303. ActionListener() 304. { 305. public void actionPerformed(ActionEvent event) 306. { 307. setVisible(false); 308. } 309. }); 310. 311. add(buttonPanel, BorderLayout.SOUTH); 312. } 313. 314. private PrintPreviewCanvas canvas; 315. 316. private static final int DEFAULT_WIDTH = 300; 317. private static final int DEFAULT_HEIGHT = 300; 318. } 319. 320. /** 321. The canvas for displaying the print preview. 322. */ 323. class PrintPreviewCanvas extends JPanel 324. { 325. /** 326. Constructs a print preview canvas. 327. @param b the book to be previewed 328. */ 329. public PrintPreviewCanvas(Book b) 330. { 331. book = b; 332. currentPage = 0; 333. } 334. 335. public void paintComponent(Graphics g) 336. { 337. super.paintComponent(g); 338. Graphics2D g2 = (Graphics2D)g; 339. PageFormat pageFormat = book.getPageFormat(currentPage); 340. 341. double xoff; // x offset of page start in window 342. double yoff; // y offset of page start in window 343. double scale; // scale factor to fit page in window 344. double px = pageFormat.getWidth(); 345. double py = pageFormat.getHeight(); 346. double sx = getWidth() - 1; 347. double sy = getHeight() - 1; 348. if (px / py < sx / sy) // center horizontally 349. { 350. scale = sy / py; 351. xoff = 0.5 * (sx - scale * px); 352. yoff = 0; 353. } 354. else // center vertically 355. { 356. scale = sx / px; 357. xoff = 0; 358. yoff = 0.5 * (sy - scale * py); 359. } 360. g2.translate((float)xoff, (float)yoff); 361. g2.scale((float)scale, (float)scale); 362. 363. // draw page outline (ignoring margins) 364. Rectangle2D page = new Rectangle2D.Double(0, 0, px, py); 365. g2.setPaint(Color.white); 366. g2.fill(page); 367. g2.setPaint(Color.black); 368. g2.draw(page); 369. 370. Printable printable = book.getPrintable(currentPage); 371. try 372. { 373. printable.print(g2, pageFormat, currentPage); 374. } 375. catch (PrinterException e) 376. { 377. g2.draw(new Line2D.Double(0, 0, px, py)); 378. g2.draw(new Line2D.Double(px, 0, 0, py)); 379. } 380. } 381. 382. /** 383. Flip the book by the given number of pages. 384. @param by the number of pages to flip by. Negative 385. values flip backwards. 386. */ 387. public void flipPage(int by) 388. { 389. int newPage = currentPage + by; 390. if (0 <= newPage && newPage < book.getNumberOfPages()) 391. { 392. currentPage = newPage; 393. repaint(); 394. } 395. } 396. 397. private Book book; 398. private int currentPage; 399. } java.awt.print.PrinterJob 1.2
java.awt.print.Book 1.2
Print ServicesSo far, you have seen how to print 2D graphics. However, the printing API introduced in JDK 1.4 affords far greater flexibility. The API defines a number of data types and lets you find print services that are able to print them. Among the data types:
The data themselves can be stored in a source of bytes or characters such as an input stream, a URL, or an array. A document flavor describes the combination of a data source and a data type. The DocFlavor class defines a number of inner classes for the various data sources. Each of the inner classes defines constants to specify the flavors. For example, the constant DocFlavor.INPUT_STREAM.GIF describes a GIF image that is read from an input stream. Table 7-3 lists the combinations.
Suppose you want to print a GIF image that is located in a file. First find out whether there is a print service that is capable of handling the task. The static lookupPrintServices method of the PrintServiceLookup class returns an array of PrintService objects that can handle the given document flavor. DocFlavor flavor = DocFlavor.INPUT_STREAM.GIF; PrintService[] services = PrintServiceLookup.lookupPrintServices(flavor, null); The second parameter of the lookupPrintServices method is null to indicate that we don't want to constrain the search by specifying printer attributes. We cover attributes in the next section. NOTE
If the lookup yields an array with more than one element, you select from the listed print services. You can call the getName method of the PrintService class to get the printer names, and then let the user choose. Next, get a document print job from the service: DocPrintJob job = services[i].createPrintJob(); For printing, you need an object that implements the Doc interface. The JDK supplies a class SimpleDoc for that purpose. The SimpleDoc constructor requires the data source object, the document flavor, and an optional attribute set. For example, InputStream in = new FileInputStream(fileName); Doc doc = new SimpleDoc(in, flavor, null); Finally, you are ready to print: job.print(doc, null); As before, the null parameter can be replaced by an attribute set. Note that this printing process is quite different from that of the preceding section. There is no user interaction through print dialog boxes. For example, you can implement a server-side printing mechanism in which users submit print jobs through a web form. The program in Example 7-14 demonstrates how to use a print service to print an image file. Example 7-14. PrintServiceTest.java[View full width] 1. import java.awt.*; 2. import java.awt.event.*; 3. import java.awt.image.*; 4. import java.io.*; 5. import java.util.*; 6. import javax.print.*; 7. import javax.swing.*; 8. 9. /** 10. This program demonstrates the use of print services. The program lets you print a GIF image 11. to any of the print services that support the GIF document flavor. 12. */ 13. public class PrintServiceTest 14. { 15. public static void main(String[] args) 16. { 17. JFrame frame = new PrintServiceFrame(); 18. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 19. frame.setVisible(true); 20. } 21. } 22. 23. /** 24. This frame displays the image to be printed. It contains 25. menus for opening an image file, printing, and selecting 26. a print service. 27. */ 28. class PrintServiceFrame extends JFrame 29. { 30. public PrintServiceFrame() 31. { 32. setTitle("PrintServiceTest"); 33. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 34. 35. // set up menu bar 36. JMenuBar menuBar = new JMenuBar(); 37. setJMenuBar(menuBar); 38. 39. JMenu menu = new JMenu("File"); 40. menuBar.add(menu); 41. 42. JMenuItem openItem = new JMenuItem("Open"); 43. menu.add(openItem); 44. openItem.addActionListener(new 45. ActionListener() 46. { 47. public void actionPerformed(ActionEvent event) 48. { 49. openFile(); 50. } 51. }); 52. 53. JMenuItem printItem = new JMenuItem("Print"); 54. menu.add(printItem); 55. printItem.addActionListener(new 56. ActionListener() 57. { 58. public void actionPerformed(ActionEvent event) 59. { 60. printFile(); 61. } 62. }); 63. 64. JMenuItem exitItem = new JMenuItem("Exit"); 65. menu.add(exitItem); 66. exitItem.addActionListener(new 67. ActionListener() 68. { 69. public void actionPerformed(ActionEvent event) 70. { 71. System.exit(0); 72. } 73. }); 74. 75. menu = new JMenu("Printer"); 76. menuBar.add(menu); 77. DocFlavor flavor = DocFlavor.INPUT_STREAM.GIF; 78. addPrintServices(menu, flavor); 79. 80. // use a label to display the images 81. label = new JLabel(); 82. add(label); 83. } 84. 85. /** 86. Adds print services to a menu 87. @param menu the menu to which to add the services 88. @param flavor the flavor that the services need to support 89. */ 90. public void addPrintServices(JMenu menu, DocFlavor flavor) 91. { 92. PrintService[] services = PrintServiceLookup.lookupPrintServices(flavor, null); 93. ButtonGroup group = new ButtonGroup(); 94. for (int i = 0; i < services.length; i++) 95. { 96. final PrintService service = services[i]; 97. JRadioButtonMenuItem item = new JRadioButtonMenuItem(service.getName()); 98. menu.add(item); 99. if (i == 0) 100. { 101. item.setSelected(true); 102. currentService = service; 103. } 104. group.add(item); 105. item.addActionListener(new 106. ActionListener() 107. { 108. public void actionPerformed(ActionEvent event) 109. { 110. currentService = service; 111. } 112. }); 113. } 114. } 115. 116. /** 117. Open a GIF file and display the image. 118. */ 119. public void openFile() 120. { 121. // set up file chooser 122. JFileChooser chooser = new JFileChooser(); 123. chooser.setCurrentDirectory(new File(".")); 124. 125. // accept all files ending with .gif 126. chooser.setFileFilter(new 127. javax.swing.filechooser.FileFilter() 128. { 129. public boolean accept(File f) 130. { 131. return f.getName().toLowerCase().endsWith(".gif") || f.isDirectory(); 132. } 133. 134. public String getDescription() { return "GIF Images"; } 135. }); 136. 137. // show file chooser dialog 138. int r = chooser.showOpenDialog(PrintServiceFrame.this); 139. 140. // if image file accepted, set it as icon of the label 141. if(r == JFileChooser.APPROVE_OPTION) 142. { 143. fileName = chooser.getSelectedFile().getPath(); 144. label.setIcon(new ImageIcon(fileName)); 145. } 146. } 147. 148. /** 149. Print the current file using the current print service. 150. */ 151. public void printFile() 152. { 153. try 154. { 155. if (fileName == null) return; 156. if (currentService == null) return; 157. FileInputStream in = new FileInputStream(fileName); 158. DocFlavor flavor = DocFlavor.INPUT_STREAM.GIF; 159. Doc doc = new SimpleDoc(in, flavor, null); 160. DocPrintJob job = currentService.createPrintJob(); 161. job.print(doc, null); 162. } 163. catch (FileNotFoundException e) 164. { 165. JOptionPane.showMessageDialog(this, e); 166. } 167. catch (PrintException e) 168. { 169. JOptionPane.showMessageDialog(this, e); 170. } 171. } 172. 173. private JLabel label; 174. private String fileName; 175. private PrintService currentService; 176. private static final int DEFAULT_WIDTH = 300; 177. private static final int DEFAULT_HEIGHT = 400; 178. } javax.print.PrintServiceLookup 1.4
javax.print.PrintService 1.4
javax.print.DocPrintJob 1.4
javax.print.SimpleDoc 1.4
Stream Print ServicesA print service sends print data to a printer. A stream print service generates the same print data but instead sends them to a stream, perhaps for delayed printing or because the print data format can be interpreted by other programs. In particular, if the print data format is PostScript, then it is useful to save the print data to a file because many programs can process PostScript files. JDK 1.4 includes a stream print service that can produce PostScript output from images and 2D graphics. You can use that service on all systems, even if there are no local printers. Enumerating stream print services is a bit more tedious than locating regular print services. You need both the DocFlavor of the object to be printed and the MIME type of the stream output. You then get a StreamPrintServiceFactory array of factories. DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE; String mimeType = "application/postscript"; StreamPrintServiceFactory[] factories = StreamPrintServiceFactory.lookupStreamPrintServiceFactories(flavor, mimeType); The StreamPrintServiceFactory class has no methods that would help us distinguish any one factory from another, so we just take factories[0]. We call the getPrintService method with an output stream parameter to get a StreamPrintService object. OutputStream out = new FileOutputStream(fileName); StreamPrintService service = factories[0].getPrintService(out); The StreamPrintService class is a subclass of PrintService. To produce a printout, simply follow the steps of the preceding section. Example 7-15 prints the "Hello, World" graphic from the preceding examples to a PostScript file (see Figure 7-42). Example 7-15. StreamPrintServiceTest.java[View full width] 1. import java.awt.*; 2. import java.awt.event.*; 3. import java.awt.font.*; 4. import java.awt.geom.*; 5. import java.awt.print.*; 6. import java.io.*; 7. import java.util.*; 8. import javax.print.*; 9. import javax.print.attribute.*; 10. import javax.swing.*; 11. 12. 13. /** 14. This program demonstrates the use of a stream print service. 15. It prints a 2D graphic to a PostScript file. 16. */ 17. public class StreamPrintServiceTest 18. { 19. public static void main(String[] args) 20. { 21. JFrame frame = new StreamPrintServiceFrame(); 22. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 23. frame.setVisible(true); 24. } 25. } 26. 27. /** 28. This frame shows a panel with 2D graphics and buttons 29. to print the graphics to a PostScript file and to set up 30. the page format. 31. */ 32. class StreamPrintServiceFrame extends JFrame 33. { 34. public StreamPrintServiceFrame() 35. { 36. setTitle("StreamPrintServiceTest"); 37. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 38. 39. canvas = new PrintPanel(); 40. add(canvas, BorderLayout.CENTER); 41. 42. attributes = new HashPrintRequestAttributeSet(); 43. 44. JPanel buttonPanel = new JPanel(); 45. JButton printButton = new JButton("Print"); 46. buttonPanel.add(printButton); 47. printButton.addActionListener(new 48. ActionListener() 49. { 50. public void actionPerformed(ActionEvent event) 51. { 52. String fileName = getFile(); 53. if (fileName != null) 54. printPostScript(fileName); 55. } 56. }); 57. 58. JButton pageSetupButton = new JButton("Page setup"); 59. buttonPanel.add(pageSetupButton); 60. pageSetupButton.addActionListener(new 61. ActionListener() 62. { 63. public void actionPerformed(ActionEvent event) 64. { 65. PrinterJob job = PrinterJob.getPrinterJob(); 66. job.pageDialog(attributes); 67. } 68. }); 69. 70. add(buttonPanel, BorderLayout.NORTH); 71. } 72. 73. /** 74. Allows the user to select a PostScript file. 75. @return the file name, or null if the user didn't 76. select a file. 77. */ 78. public String getFile() 79. { 80. // set up file chooser 81. JFileChooser chooser = new JFileChooser(); 82. chooser.setCurrentDirectory(new File(".")); 83. 84. // accept all files ending with .ps 85. chooser.setFileFilter(new 86. javax.swing.filechooser.FileFilter() 87. { 88. public boolean accept(File f) 89. { 90. return f.getName().toLowerCase().endsWith(".ps") || f.isDirectory(); 91. } 92. 93. public String getDescription() { return "PostScript Files"; } 94. }); 95. 96. // show file chooser dialog 97. int r = chooser.showSaveDialog(this); 98. 99. // if file accepted, return path 100. if(r == JFileChooser.APPROVE_OPTION) 101. return chooser.getSelectedFile().getPath(); 102. else 103. return null; 104. } 105. 106. /** 107. Prints the 2D graphic to a PostScript file. 108. @param fileName the name of the PostScript file 109. */ 110. public void printPostScript(String fileName) 111. { 112. try 113. { 114. DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE; 115. String mimeType = "application/postscript"; 116. StreamPrintServiceFactory[] factories = 117. StreamPrintServiceFactory.lookupStreamPrintServiceFactories(flavor, mimeType); 118. 119. FileOutputStream out = new FileOutputStream(fileName); 120. if (factories.length == 0) return; 121. StreamPrintService service = factories[0].getPrintService(out); 122. 123. Doc doc = new SimpleDoc(canvas, flavor, null); 124. DocPrintJob job = service.createPrintJob(); 125. job.print(doc, attributes); 126. } 127. catch (FileNotFoundException e) 128. { 129. JOptionPane.showMessageDialog(this, e); 130. } 131. catch (PrintException e) 132. { 133. JOptionPane.showMessageDialog(this, e); 134. } 135. 136. } 137. 138. private PrintPanel canvas; 139. private PrintRequestAttributeSet attributes; 140. 141. private static final int DEFAULT_WIDTH = 300; 142. private static final int DEFAULT_HEIGHT = 300; 143. } 144. 145. /** 146. This panel generates a 2D graphics image for screen display 147. and printing. 148. */ 149. class PrintPanel extends JPanel implements Printable 150. { 151. public void paintComponent(Graphics g) 152. { 153. super.paintComponent(g); 154. Graphics2D g2 = (Graphics2D) g; 155. drawPage(g2); 156. } 157. 158. public int print(Graphics g, PageFormat pf, int page) 159. throws PrinterException 160. { 161. if (page >= 1) return Printable.NO_SUCH_PAGE; 162. Graphics2D g2 = (Graphics2D) g; 163. g2.translate(pf.getImageableX(), pf.getImageableY()); 164. g2.draw(new Rectangle2D.Double(0, 0, pf.getImageableWidth(), pf .getImageableHeight())); 165. 166. drawPage(g2); 167. return Printable.PAGE_EXISTS; 168. } 169. 170. /** 171. This method draws the page both on the screen and the 172. printer graphics context. 173. @param g2 the graphics context 174. */ 175. public void drawPage(Graphics2D g2) 176. { 177. FontRenderContext context = g2.getFontRenderContext(); 178. Font f = new Font("Serif", Font.PLAIN, 72); 179. GeneralPath clipShape = new GeneralPath(); 180. 181. TextLayout layout = new TextLayout("Hello", f, context); 182. AffineTransform transform = AffineTransform.getTranslateInstance(0, 72); 183. Shape outline = layout.getOutline(transform); 184. clipShape.append(outline, false); 185. 186. layout = new TextLayout("World", f, context); 187. transform = AffineTransform.getTranslateInstance(0, 144); 188. outline = layout.getOutline(transform); 189. clipShape.append(outline, false); 190. 191. g2.draw(clipShape); 192. g2.clip(clipShape); 193. 194. final int NLINES =50; 195. Point2D p = new Point2D.Double(0, 0); 196. for (int i = 0; i < NLINES; i++) 197. { 198. double x = (2 * getWidth() * i) / NLINES; 199. double y = (2 * getHeight() * (NLINES - 1 - i)) 200. / NLINES; 201. Point2D q = new Point2D.Double(x, y); 202. g2.draw(new Line2D.Double(p, q)); 203. } 204. } 205. } Figure 7-42. Viewing a PostScript file javax.print.StreamPrintServiceFactory 1.4
Printing AttributesThe print service API contains a complex set of interfaces and classes to specify various kinds of attributes. There are four important groups of attributes. The first two specify requests to the printer.
The other two attributes contain information about the printer and job status.
To describe the various attributes there is an interface Attribute with subinterfaces: PrintRequestAttribute DocAttribute PrintServiceAttribute PrintJobAttribute SupportedValuesAttribute Individual attribute classes implement one or more of these interfaces. For example, objects of the Copies class describe the number of copies of a printout. That class implements both the PrintRequestAttribute and the PrintJobAttribute interfaces. Clearly, a print request may contain a request for multiple copies. Conversely, an attribute of the print job may be how many of these copies were actually printed. That number might be lower, perhaps because of printer limitations or because the printer ran out of paper. The SupportedValuesAttribute interface indicates that an attribute value does not reflect actual request or status data but rather the capability of a service. For example, the CopiesSupported class implements the SupportedValuesAttribute interface. An object of that class might describe that a printer supports 1 through 99 copies of a printout. Figure 7-43 shows a class diagram of the attribute hierarchy. Figure 7-43. The attribute hierarchyIn addition to the interfaces and classes for individual attributes, the print service API defines interfaces and classes for attribute sets. A superinterface, AttributeSet, has four subinterfaces: PrintRequestAttributeSet DocAttributeSet PrintServiceAttributeSet PrintJobAttributeSet Each of these interfaces has an implementing class, yielding the five classes: HashAttributeSet HashPrintRequestAttributeSet HashDocAttributeSet HashPrintServiceAttributeSet HashPrintJobAttributeSet Figure 7-44 shows a class diagram of the attribute set hierarchy. Figure 7-44. The attribute set hierarchyFor example, you construct a print request attribute set like this: PrintRequestAttributeSet attributes = new HashPrintRequestAttributeSet(); After constructing the set, you are freed from worry about the Hash prefix. Why have all these interfaces? They make it possible to check for correct attribute usage. For example, a DocAttributeSet accepts only objects that implement the DocAttribute interface. Any attempt to add another attribute results in a runtime error. An attribute set is a specialized kind of map, where the keys are of type Class and the values belong to a class that implements the Attribute interface. For example, if you insert an object new Copies(10) into an attribute set, then its key is the Class object Copies.class. That key is called the category of the attribute. The Attribute interface declares a method Class getCategory() that returns the category of an attribute. The Copies class defines the method to return the object Copies.class, but it isn't a requirement that the category be the same as the class of the attribute. When an attribute is added to an attribute set, the category is extracted automatically. You just add the attribute value: attributes.add(new Copies(10)); If you subsequently add another attribute with the same category, it overwrites the first one. To retrieve an attribute, you need to use the category as the key, for example, AttributeSet attributes = job.getAttributes(); Copies copies = (Copies) attribute.get(Copies.class); Finally, attributes are organized by the values they can have. The Copies attribute can have any integer value. The Copies class extends the IntegerSyntax class that takes care of all integer-valued attributes. The getValue method returns the integer value of the attribute, for example, int n = copies.getValue(); The classes TextSyntax DateTimeSyntax URISyntax encapsulate a string, date and time, or URI (Uniform Resource Identifier). Finally, many attributes can take a finite number of values. For example, the PrintQuality attribute has three settings: draft, normal, and high. They are represented by three constants: PrintQuality.DRAFT PrintQuality.NORMAL PrintQuality.HIGH Attribute classes with a finite number of values extend the EnumSyntax class, which provides a number of convenience methods to set up these enumerations in a typesafe manner. You need not worry about the mechanism when using such an attribute. Simply add the named values to attribute sets: attributes.add(PrintQuality.HIGH); Here is how you check the value of an attribute: if (attributes.get(PrintQuality.class) == PrintQuality.HIGH) . . . Table 7-4 lists the printing attributes. The second column lists the superclass of the attribute class (for example, IntegerSyntax for the Copies attribute) or the set of enumeration values for the attributes with a finite set of values. The last four columns indicate whether the attribute class implements the DocAttribute (DA), PrintJobAttribute (PJA), PrintRequestAttribute (PRA), and PrintServiceAttribute (PSA) interfaces.
NOTE
NOTE
javax.print.attribute.Attribute 1.4
javax.print.attribute.AttributeSet 1.4
javax.print.PrintService 1.4
javax.print.DocPrintJob 1.4
This concludes our discussion on printing. You now know how to print 2D graphics and other document types, how to enumerate printers and stream print services, and how to set and retrieve attributes. Next, we turn to two important user interface issues, the clipboard and support for the drag-and-drop mechanism. |
|