gnujpdf

This document was generated from gnujpdf.texi with:

$ makeinfo --html gnujpdf.html I've edited the result a bit to clean it up. Maybe somebody could email me if they know some makeinfo tricks that will improve the format a bit. I'd be particulary interested in how to produce the output as a series of pages instead of one big one, how to get the table of contents to automatically appear at the beginning of the document, and how to get the links to show up as actual links, not just text.

Table of Contents


Node:Copying, Next:, Previous:Top, Up:Top

Copying

Copyright

Copyright (C) 2001 Eric Z. Beard, ericzbeard@hotmail.com

License

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



Node:Introduction, Next:, Previous:Copying, Up:Top

Introduction

Who needs this package?

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.

System Requirements

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

Installation

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.

GNU LGPL

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.


Node:Background, Next:, Previous:Introduction, Up:Top

Background

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.


Node:Examples, Next:, Previous:Background, Up:Top

Examples

Basic Functionality

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

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)


Node:Index, Previous:Examples, Up:Top

Index