When you want to convert a java.awt.print.Printable to a pdf file, your first course of action might be to Google for "java printable pdf". If you do that though, all you get is links about how to send a pdf file to the (hardware) printer in java or how to "read" a pdf file in Java. No one seemed to have written an article about how to convert a java.awt.print.Printable to a pdf file. This is that article.
The java code in this article requires the single itext jar to work. I used itext-5.0.2.jar, but I'm sure newer versions will also work.
Update: Some people informed me that as of version 5, itext does not have a business-friendly license anymore. Therefore, I also tested this solution with itext-2.1.7.jar and I am happy to report that this works just fine, the 2.1.7 jar is drop-in compatible. End of update.
This example works for A4 documents only, but once you understand the code, it is fairly trivial to adapt it to any size you want.
So, without further ado, here's the code! I'm going to explain 2 fundamental elements that make it work afterwards.
PdfPrinter.java:
FontCreator.java:
Okay, so what makes this code work? If you've studied it, you will have noticed 2 things: the fact that we print the document twice and the fact that we get the fonts from files. I'm going to explain both of them.
Running the print twice
A characteristic of the java.awt.print.Printable class is that the return value of the print method tells the caller whether the print that was just requested (by calling the method) was actually valid (PAGE_EXISTS) or that the document has ended (NO_SUCH_PAGE). In other words, we only know whether a page existed after we've printed it. The Document class from itext however, requires a new page to be created before it is printed. So, we have to create a new page in the pdf Document before we know whether it even exists! This way, we always end up with a blank page at the end of a document. Therefore, the entire print sequence is run twice. Once to count the number of pages and once to use that number to prevent an extra page from being created. Granted, the first run didn't have to include everything, but since an extra millisecond didn't really matter in my case, I kept the code as short as possible. If you want to optimize it, then by all means do!
Getting the fonts from files
A nasty thing that I ran into was that itext treats fonts just a little bit different from how java.awt.print.Printable treats them. The widths and heights of the characters are just a little bit different between them, even if using the exact same parameters (style, size, bold/italic, etc). This leads to problems when you use FontMetrics.stringWidth(), for example.
The solution here is to make both of them not use their own fonts but the fonts from font-files that are packaged with the jar. In your implementation of java.awt.print.Printable, you have to use the FontCreator to create the java.awt.Fonts from the files instead of just using the java.awt.Font constructor. We also need the PdfFontMapper in the PdfPrinter to map those java.awt.Fonts to itext's own BaseFont class. The PdfFontMapper uses the same font-files to create the itext BaseFonts, so that itext and java.awt.print.Printable now use exactly the same fonts. Problem solved!
The code that retrieves the file names for the different fonts is a little duplicated between the two. If you have many fonts that you want to use, you may want to optimize this.
So that's it. Now, you can easily convert your java.awt.print.Printables to pdf files and use them for whatever you like!
great
ReplyDelete