This document was generated from gnujpdf.texi with:
Copyright (C) 2001 Eric Z. Beard, ericzbeard@hotmail.com
Note that this is not the license for the software itself, it is the license
for this document. The license for the software is in the source.
Copyright © 2001 Eric Z. Beard, ericzbeard@hotmail.com
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.1
or any later version published by the Free Software Foundation;
Free Documentation License".
For a complete copy of the license, see
http://www.gnu.org/copyleft/fdl.html
The gnu.jpdf
package was written primarily for use from the middle tier
of a web-application, on a Java enabled web server or application server. A
common requirement in many applications is the ability to view and/or print
dynamically generated documents, either one by one or in a batch mode. Since
many companies use PDF as the standard document format, it makes sense to
combine viewing and printing of information into that one format.
The problem that arises is the lack of solid batch-printing capability for PDF. There are several windows GUI's that claim to handle this effectively, but for a Java programmer who needs to print out 1000 invoices from a Unix box, and then store the invoices so they can be viewed by anyone in the company with an interest in them, a Windows program is, to put it kindly, limiting.
There are many existing applications out there that use Java to generate PDF
files, and it may be that one of them better suits your needs. There are
enterprise solutions such as JReport (http://www.jinfonet.com
), and
flexible packages such as itext (http://www.lowagie.com/iText/
), that
can also generate rtf files and other formats. If you need anything but PDF,
GNUjpdf is not for you.
What GNUjpdf offers is a familiar API: java.awt.Graphics. All operations that draw to a PDF file in this package do so through a subclass of Graphics, so that one set of methods that operate on the abstract Graphics class can process AWT graphics. Your document can be viewed in an applet or swing application, then printed using PrintJob.getGraphics(), and then converted to PDF using PDFJob.getGraphics().
The package also offers support for basic features of PDF, such as outlines and
annotations, but the real strength is in the duplication of the Graphics API.
Every time you call a method such as drawString()
or drawImage()
,
instead of drawing to a screen graphics context, the GNUjpdf code writes bytes
to an output stream (your PDF file) in Adobe's whacky binary format.
So, if you are a developer who has a basic familiarity with the Java graphics classes, and you need to print out and store dynamically generated documents, GNUjpdf could be for you.
This version of GNUjpdf runs on the Java 2 platform (which includes JDK 1.2, 1.3 and the new 1.4). It does NOT support JDK 1.1.
Visit http://java.sun.com
to get a copy of the latest JDK.
For Adobe Acrobat Reader(TM), go to http://www.adobe.com
. Adobe owns
the copyright to the PDF format. They provide the reader for free and it's Ok
to write programs that read and write their format. For licensing details
check out Adobe's site.
Unix users beware! You may be happily developing away on your desktop with this code and then get a rude surprise when you install it on a server: there probably isn't a graphics context on the big blue box in the server room, since it's not hooked up to a monitor and there isn't even an X server installed, much less running. You will need a virtual framebuffer, such as Xvfb. It has become pretty popular lately to use a servlet engine for dynamic image generation, so Xvfb comes installed with newer versions of some Linux distributions such as RedHat, but you may be out of luck with Solaris. Xvfb binaries are sometimes hard to find and install. Try these links:
http://tmap.pmel.noaa.gov/home/ferret/FAQ/graphics/Solaris_Xvfb.html
ftp://ferret.wrc.noaa.gov
http://starbase.neosoft.com/~claird/comp.windows.x/Xvfb.html
If you downloaded the binary distribution, all you have to do is unpack it and put the class files in your classpath.
On a Unix machine, something like this would be the simplest way:
$ gunzip -c gnujpdf.tar.gz | tar xvf - $ cd gnujpdf/lib $ cp gnujpdf.jar $JAVA_HOME/jre/lib/ext/
On Windows, something like this is more appropriate:
c:> format c:
If that's just not an option (it's not a perfect world, after all), unpack the
tar.gz file using the appropriate Windows utility and put the jar file into you
rclasspath. The $JAVA_HOME\jre\lib\ext
directory will automatically
place the jar into you classpath.
If you are compiling from source, all the java files are in the same folder, so it's really simple:
$ cd $GNUJPDF/src/gnu/jpdf $ javac -d $CLASES_DIR *.java
Just make sure you put the generated classes into your classpath.
Just in case you don't know what the GNU LGPL is, here's a quick rundown:
The GNU (GNU's Not Unix) project has several popular licenses used for all software written as 'open source'. The GPL (GNU General Public License) and the LGPL (Lesser General Public License) are the two primary ones. What the LGPL means, basically, is that you are free to use the code in any project, wether it be free software or proprietary (which you sell for a profit), but you cannot close the source to this package. You must keep it in the public domain and publish any changes you make to it, prominently diplaying the LGPL lecense, which you must continue to use for this section of you code.
So, if you are selling 'Widget A' that uses GNUjpdf to save PDF files, you don't have to show the world your code for Widget A, and you can sell Widget A, but you do have to document the fact that you are using GNUjpdf, and give people a way to access the source code. You CANNOT close the GNUjpdf source and then sell that.
GNUjpdf is NOT licensed under the more restrictive GPL. The GPL requires that any project in which you use its code must be also licensed under the GPL and fully open source. The GPL is normally used for standalone tools that you may use to develop or deploy your code, but is not an integral part of it. For example, you may use GNU Emacs to do all of your source code editing, but the license has no effect on the license for your proprietary software, regardless of the fact that you used Emacs to type it all in. On the other hand, if you developed a GUI skin for emacs, you could not close up the source and sell your code along with GNU Emacs.
This library started out as RetepPDF, by a guy named Peter T. Mount, whom I have yet to speak with. When researching existing projects to fill the need for the PDF functionality, I found RetepPdf on sourceforge and tried to get in touch with Peter, but never had any luck. The package hadn't been touched in a while, and I wanted to improve it, so I opened a new project on sourceforge.
So far I have received a few dozen emails from people who are either using or evaluating GNUjpdf, along with some bug fixes and feature requests, so I guess starting a new project was worth it. All credit for the code base goes to Peter. I have since cleaned things up a little bit, added support for drawing ovals and images, and added word wrap to a class I wrote called BoundingBox. Besides the images, BoundingBox is really my big contribution to this thing so far. Word wrap has been one of the biggest requests, because without it you are forced to break up your strings manually and draw them one by one: BoundingBox breaks up your strings for you and provides a consistent way of drwaing out the broken up strings inside a pre-determined box.
I have only done basic functionality testing on BoundingBox, so I am curious to see if others can (hopefully) make sense of my code and help me tweak it so it is flexible.
The best source of information for a programmer wishing to use
this simple API is the source code in PDFTest.java
. It
demonstrates a simple application that displays various
formatting and simultaneously writes a pdf file that will be an
identical copy of what is seen on the screen.
The starting point for creating any PDF document with this
library is the PDFJob class.
PDFJob job = new PDFJob(fileOutputStream);
The fileOutputStream is normally a stream initialized with the
name of the pdf you wish to generate, such as "test.pdf". A
PDFGraphics object can be obtained from the job by calling:
Graphics pdfGraphics = job.getGraphics();
This Graphics object can be passed into the same methods used to draw to the screen. Most of the common methods in java.awt.Graphics have been implemented (with a few important exceptions - this is a beta product, so there is still plenty of work to be done - see the source code for more specifics). When calling methods such as drawString(..) or drawImage(..), what is actually happening is that the PDFGraphics object is writing the necessary markup to the output stream.
A new pdf page is initialized by disposing of the exisiting
Graphics object and getting a new one from the job.
pdfGraphics.dispose(); pdfGraphics = job.getGraphics();
Any Graphics operations will now be made on a new page in the pdf
document. When the document is finished, the job must be closed
out:
pdfGraphics.dispose(); job.end();
And the fileOutputStream will need to be closed properly as well, as this is not guaranteed to be taken care of by the PDF classes.
BoundingBox
is a subclass of java.awt.Rectangle
. It simplifies the
placement of Strings within a canvas area where the placement of objects is
absolute.
A BoundingBox is just a Rectangle that knows how to find the coordinates for a String based on the desired alignment and FontMetrics. For each new String, a new child BoundingBox is made that can be subtracted from the original BoundingBox so new Strings can be added
One of the more helpful features of this class is the string wrap feature of
getStringBounds
. The box returned by that method will contain an array
of strings that have been broken down to fit the box. The box's coordinates and
size will reflect the size of the entire group of strings if it is laid out as
expected. Using the returned box and iterating through the array of strings
from top to bottom, getting new bounding boxes for each one (with upper left
alignment and no padding) will result in the correct string wrap.
This part of BoundingBox
is currently undergoing lots of changes, since
I am actively using it in a project and new requirements keep popping up.
Also, I am finding plenty of repetetive tasks that logically belong in the
class. BoundingBox
can now draw strings for you, and several new methods
for doing this are all I use to put strings on the page.
Here is an example of using BoundingBox to break down a String. After the code fragment I will analyze each line in detail.
Point boxUpperLeft = new Point(60, 60); Dimension boxSize = new Dimension(200, 200); Font f = new Font("TimesRoman", Font.PLAIN, 16); g.setFont(f); FontMetrics fm = g.getFontMetrics(f); BoundingBox box = new BoundingBox(boxUpperLeft, boxSize); String string = "Hello World! this is a really long string"; int padding = 10; BoundingBox child = null; try { child = box.getStringBounds(string, BoundingBox.HORIZ_ALIGN_CENTER, BoundingBox.VERT_ALIGN_BOTTOM, fm, padding); child.drawWrappedString(g, fm, padding, BoundingBox.HORIZ_ALIGN_CENTER); } catch (StringTooLongException stle) { stle.printStackTrace(); return; } g.drawRect(60, 60, 200, 200); g.drawRect((int)child.getLocation().getX() + 60, (int)child.getLocation().getY() + 60, (int)child.getSize().getWidth(), (int)child.getSize().getHeight()); box.subtract(child, BoundingBox.SUBTRACT_FROM_BOTTOM); child = null;
This code would be inside a method that accepted a Graphics object, so that any
one of our possible Graphics objects could be used (AWT, PrintJob, or PDF).
The first thing we do is define the size of our box:
Point boxUpperLeft = new Point(60, 60); Dimension boxSize = new Dimension(200, 200);
We then initialize our FontMetrics object with the Font we will use for
display:
Font f = new Font("TimesRoman", Font.PLAIN, 16); g.setFont(f); FontMetrics fm = g.getFontMetrics(f);
The first box we make will be the 'parent' box of the area in which we will
draw the strings:
BoundingBox box = new BoundingBox(boxUpperLeft, boxSize);
We then set up the String we want to break down and declare a child BoundingBox
for the String:
String string = "Hello World! this is a really long string"; int padding = 10; BoundingBox child = null;
When we get the child BoundingBox containing the String, we need to put the
call in a try-catch block because the getStringBounds
method throws an
exception if the String is simply too big to fit in the target box.
try { child = box.getStringBounds(string, BoundingBox.HORIZ_ALIGN_CENTER, BoundingBox.VERT_ALIGN_BOTTOM, fm, padding);
Notice the parameters we used to make the child box--BoundingBox has constants for left, center and right alignment horizontally, and top, center and bottom vertically.
Next we use the child box to draw the String, checking for the exception that could be thrown. Notice that you must use the same padding you used to create the child box, and you must specify how you want the Strings horizontally aligned. This horizontal alignment might be different from the one you used above, since the one above is how the entire box is to be aligned, while the one below will control how the strings are aligned within that box.
child.drawWrappedString(g, fm, padding, BoundingBox.HORIZ_ALIGN_CENTER); } catch (StringTooLongException stle) { stle.printStackTrace(); // Do something more robust here }
Next we will draw a box to the Graphics context to outline our string and
demonstrate the correct breakdown of the String. We can only do this if the
calls above succeeded, so make sure you either end execution of the method when
they fail or check for a null pointer before attempting to use the child box.
In some cases it may be appropriate to continue making the document, leaving a
blank space where the last string should have appeared.
g.drawRect(60, 60, 200, 200); g.drawRect((int)child.getLocation().getX() + 60, (int)child.getLocation().getY() + 60, (int)child.getSize().getWidth(), (int)child.getSize().getHeight());
Once you are done with the child, you can 'subtract' it from the parent, reducing the size of the parent and preparing for the next child box to be added.
// Again, check to make sure child != null if you didn't end execution on // the possible exception box.subtract(child, BoundingBox.SUBTRACT_FROM_BOTTOM); child = null;
The constants used to determine how to subtract the child (such as
BoundingBox.SUBTRACT_FROM_BOTTOM
) indicate the side of the page you want
to leave remaining after the subtraction. In this case, the rectangle are at
the 'BOTTOM' of the page under the child string is left, and a new child added
to it will appear directly under the one subtracted. Note that subtracting
from a side with no room leaves you with a non-existent box that can't be used
anymore.
This code fragment was taken with litte modification from PDFTest. Make sure
you get the source code and play around with the class to get the hang of the
interface.
A few extra heplful methods in BoundingBox
are:
drawChoppedString(..)
--if the string would have wrapped lines, this
method only draws the first line and chops the end of it off to fit width. Use
this with the overridden call to getStringBounds
with the last argument
as false
so that the exception will not be thrown when the strings
overrun the boundaries.
drawWrappedStringTruncate(..)
--similar to the method above, it wraps
strings in the box until the bottom of the box is reached, at which time it
just returns without throwing the exception.
(I'm trying to write the docs at the same time I'm making these changes so please let me know if something is out of sync)