Since about march 2003 we've been working on a complete rewrite of the PDF library API. This project came about because the library, like all IT projects, had grown organically since it began and had become overly complex, if not on the outside then certainly on the inside. This complexity made difficult operations (like cloning and moving pages and form fields between PDFs) prone to error.
The new version, which we're releasing a beta of, has less code and is more robust. Key new features include:
In addition the new design will allow further new features to be added, such as Linearization, full OpenType font support, parsing the page contents and so on.
Version 2 of the PDF library API now resides under the package tree
org.faceless.pdf2
. Wrapper classes have been provided under the
original org.faceless.pdf
package which should allow users to run
the version 2.0 code without having to change their API. Note that this will come at
some performance cost, as there is an additional layer of indirection for every method
call. Therefore we do not recommend running these wrapper classes as a long
term solution - they are provided simply to help migration for certain users. The
source for these classes is provided in the hope that it will help users to upgrade
from version 1.2 to 2.0.
For many users all that will be required is to change the line import
org.faceless.pdf.*;
to import org.faceless.pdf2.*;
in their
code and recompile. Any compiler errors should be fixed with reference to the API
changes, which are documented below.
The only API change in beta4 was the removal of the methods in the PDF class dealing with
getting/setting the PDF version, and replacing them with the setOutputProfile
method.
For those upgrading from beta2 to beta3 of the 2.0 series, no API changes have been made other than a couple of methods being added. Beta3 mainly fixes some of the bugs in beta2, and adds a fairly significant speed improvement in the render() method. This version is now passing the worst tests we have so far managed to devise, although we've got a few truly twisted ideas we want to try. We anticipate this will be the final beta.
For those upgrading from beta1 to beta2 of the 2.0 series, here is a summary of the changes made. For those upgrading to a 2.0 beta straight from 1.2 or earlier, see section 3 and section 4.
setCanvas
method has been replaced with the simpler setUnits
method, and the getCanvasWidth
and getCanvasHeight
methods are no longer required. As the difference from 1.2 to 2.0beta1 was very slight, see the description of the change from version 1.0 to see what's involved with these changes.
The rationale behind this change was that the term "canvas" in version 2.0 was confusing, as it's used in two different contexts. Additionally the ability to set (0,0) to somewhere in the middle of the page was, we felt, actually more confusing than allowing the user to do it themselves.
getCanvas
method. This canvas could then be passed in to the drawCanvas method, which unfortunately causes problems as you really shouldn't have access to that object in userspace. It was a last minute change that shouldn't have slipped through. Sorry.
The new way to do things is to use the PDFCanvas(PDFPage)
constructor. This creates a canvas that is a clone of the page, which you can do anything with. So, for example, if in 2.0beta1 you were doing this, change it like so:
- PDFCanvas oldcanvas = oldpage.getCanvas(); + PDFCanvas oldcanvas = new PDFCanvas(oldpage);
A number of changes have been made to the 1.2.x API in order to fit these new features in. Many of these are small and should be changable with minimal effort. Here's a list of the methods or classes that have changed somehow - click on the method for a description. Note this section does not list new features, just the old features that have changed.
setAccessLevel
or setPassword
methods, an
EncryptionHandler
object should be passed in to the
setEncrptionHandler
method. This is still fairly simple. For example, to
create a document that can only be loaded in Acrobat 5 or later and can be printed
in lo-resolution only, the following code could be used:
StandardEncryptionHandler encrypt = new StandardEncryptionHandler(); encrypt.setAcrobat5Level(encrypt.PRINT_LOWRES, encrypt.EXTRACT_ALL, encrypt.CHANGE_ALL); pdf.setEncryptionHandler(encrypt);To create a document that cannot be printed or modified in any way, and can be loaded in Acrobat 3 or later:
StandardEncryptionHandler encrypt = new StandardEncryptionHandler(); encrypt.setAcrobat3Level(false, false, false, false); pdf.setEncryptionHandler(encrypt);To set a password on a document which could be loaded in Acrobat 3 or later:
StandardEncryptionHandler encrypt = new StandardEncryptionHandler(); encrypt.setUserPassword("secret"); pdf.setEncryptionHandler(encrypt);
setOpenAction
and setCloseAction
methods, a
new setAction
method has been added. To switch from one to the other,
change
- pdf.setOpenAction(action) + pdf.setAction(Event.OPEN, action); - pdf.setCloseAction(action) + pdf.setAction(Event.CLOSE, action); - pdfpage.setOpenAction(action) + pdfpage.setAction(Event.OPEN, action); - pdfpage.setCloseAction(action) + pdfpage.setAction(Event.CLOSE, action);The same applies for
getOpenAction
and getCloseAction
.
setOpenFullScreen
method has been merged with the
setViewerPreference
method. Simply change your code from
- pdf.setOpenFullScreen(true) + pdf.setViewerPreference("fullscreen", true);Likewise for the
getOpenFullScreen
method, which should be replaced
with getViewerPreference("fullscreen")
setLayout
method has been changed to allow more flexibility.
Rather than passing in a constant such as PDF.SINGLEPAGE
,
PDF.ONECOLUMN
, PDF.TWOCOLUMNSRIGHT
, or
PDF.TWOCOLUMNSLEFT
, pass in the String "SinglePage", "OneColumn",
"TwoColumnLeft" or "TwoColumnRight". Instead of passing in a boolean to determine
whether to display the bookmarks or not, pass in the string "UseNone" to not display
them, "UseBookmarks" to display the bookmarks, or "UseThumbnails" to display the
thumbnails pane. For example, change:
- pdf.setLayout(PDF.ONECOLUMN, true) + pdf.setLayout("OneColumn", "UseBookmarks")
TrueTypeFont
class has been renamed to the more appropriate
OpenTypeFont
. In addition it's constructor has been changed - before, two
boolean were passed in to decide whether the font was embed and subset. No-one could
ever remember which was which.
Now the constructor takes two arguments - an InputStream
containing the
font, and an integer which determines how many bytes are used for each glyph in the
font. This value must be 1 or 2. For fonts displaying only a subset of the ASCII
character set, a single byte per glyph will be fine. For fonts used to display
non-latin characters like Czech, Cyrillic, Arabic etc., the value "2" should be used.
To control subsetting or embedding, the setSubset
and
setEmbed
methods can be called. The default for both values is
true
, except for fonts used in form fields, which are not subset.
- PDFFont font = new TrueTypeFont(stream, true); + PDFFont font = new OpenTypeFont(stream, 1);
PDFStyle
class has had a number of get() methods removed with no
replacement. This is in anticipation of a future release of the library where the
contents of a page will be parseable. These methods returned values that could be
set by the user, but whose values were not stored in the PDF in any way (for instance,
text alignment is a concept used in a PDF document - all alignment calculations are
done by the library and the text is displayed in the appropriate place).
getLineDashPattern
has been split into getLineDashOn
and getLineDashOff
.
styleClone
method in the PDFStyle class has been replaced with
a new constructor to PDFStyle. Change calls to one of the following options
- PDFStyle newstyle = oldstyle.styleClone(); + PDFStyle newstyle = new PDFStyle(oldstyle); + PDFStyle newstyle = (PDFStyle)oldstyle.clone();
setNext()
.
The new methods, showWidget
and hideWidget
, work on a single
WidgetAnnotation at a time. So, for example, to change one to the other, do:
PDFAction action=PDFAction.showElement(formelement.getAnnotation(0)); List list = formelement.getAnnotations(); for (int i=1;i<list.size();i++) { PDFAction next = PDFAction.showElement(formelement.getAnnotation(i)); action.setNext(next); action=next; }
WidgetAnnotation
,
has been created as a special subclass of PDFAnnotation
to represent
these annotations.
The following changes are important:
FormElement.getAnnotations
method previously returned an
array of PDFAnnotation
objects. Now it returns a List
of
WidgetAnnotation
objects. The FormElement.getAnnotation()
method can be used to retrieve a single annotationPDFAnnotation.getAction
methods actually apply to the field as a whole,
not it's annotations. Getting or setting these actions will now either be via the
WidgetAnnotation.getAction
or FormElement.getAction
methods.
See the API docs for these methods for more detail.FormButton.getValue
method, is now retrievable via
the WidgetAnnotation.getValue
method, as each button annotation can have a
different value. The same applies to the setImage
method.So, for example, to find the location of a fields annotation, change
PDFAnnotation[] annots = element.getAnnotations(); float[] rectangle = annots[0].getRectangle();to
List annots = element.getAnnotations(); float[] rectangle = ((WidgetAnnotation)annots.get(0)).getRectangle(); or even float[] rectangle = element.getAnnotation(0).getRectangle();To create a new PushButton with a submit action and a text label, change
FormButton button = new FormButton(page, 100, 100, 200, 120); button.setValue("Submit"); PDFAnnotation annot = button.getAnnotations()[0]; annot.setEventAction(PDFAnnotation.EVENT_ONCLICK, submitaction);to
FormButton button = new FormButton(page, 100, 100, 200, 120); WidgetAnnotation annot = button.getAnnotation(0); annot.setAction(Event.CLICK, submitaction);
In most documents a checkbox will only have a single annotation, so code can safely be changed like so:
- boolean ischecked = checkbox.getValue(); + boolean ischecked = checkbox.getValue()!=null; - checkbox.setValue(true); + checkbox.setValue(checkbox.getAnnotation(0).getValue());
setStyle
method on each FormElement. These methods have been
replaced with the setTextStyle
and setBackgroundStyle
methods on the WidgetAnnotation
class.
Additionally, for checkbox or radio buttons type type (tick, circle, cross etc.) is now part of the PDFStyle
class - the setFormRadioButtonStyle
and setFormCheckboxStyle
methods have been added. So, some examples. For a radio button, you would change
PDFStyle backstyle = new PDFStyle(); backstyle.setFillColor(Color.gray); backstyle.setLineColor(Color.black); radiobutton.setStyle(FormElement.STYLE_CROSS, Color.red, style);to
PDFStyle backstyle = new PDFStyle(); backstyle.setFillColor(Color.gray); backstyle.setLineColor(Color.black); PDFStyle forestyle = new PDFStyle(); forestyle.setFillColor(Color.red); forestyle.setFormRadioButtonStyle(PDFStyle.FORMRADIOBUTTONSTYLE_CROSS); radiobutton.getAnnotation(0).setTextStyle(forestyle); radiobutton.getAnnotation(0).setBackgroundStyle(backstyle);
isFilename
, isMultiline
, isMultilineScrollable
, isPassword
and their corresponding
setFilename
, setMultiline
, setMultlineScrollable
and setPassword
methods have been replaced with a setType
and getType
method. The MultilineScrollable
methods have been renamed to isScrollable
and setScrollable
, as they apply to single line fields too.
- textfield.setMultiline(true); - textfield.setMultilineScrollable(true); + textfield.setType(FormText.TYPE_MULTLINE); + textfield.setScrollable(true);
setButton
method in the FormRadioButton
class has been renamed to addAnnotation
, to match the other FormElement
classes
- radiobutton.setButton("Visa", page, 100, 100, 120, 120); + radiobutton.addAnnotation("Visa", page, 100, 100, 120, 120);
link
, stamp
, and note
.
In version 2 the AnnotationStamp
, AnnotationLink
and AnnotationNote
classes have been added as subclasses of PDFAnnotation. Each of these has their own constructor and methods that are appropriate to that type of annotation only. Some examples of old and new style include - link annotations:
- PDFAnnotation link = PDFAnnotation.link(action, false); + AnnotationLink link = new AnnotationLink(); + link.setAction(action);Stamp annotations:
- PDFAnnotation stamp = PDFAnnotation.stamp("Confidential"); - stamp.setTextAnnotationLabel("John Smith"); + AnnotationStamp stamp = new AnnotationStamp("stamp.stencil.Confidential", 1); + stamp.setAuthor("John Smith");and Text (now known as Note) annotations:
- PDFAnnotation note = PDFAnnotation.text("John Smith", "Note contents here", false, PDFAnnotation.TEXT_COMMENT); + AnnotationNote note = new AnnotationNote(); + note.setType("Commment"); + note.setAuthor("John Smith"); + note.setContents("Note contents here"); + note.setOpen(false);See the API documentation of the appropriate annotation type for more information
requote
method in the PDFPage
class has been moved to the PDFFont class, which is slightly more appropriate. The locale has to be passed in seperately.
- String requoted = page.requote("This is 'quoted' text"); + String requoted = page.getStyle().getFont().requote("This is 'quoted' text", pdf.getLocale());
addAnnotation
and removeAnnotation
methods have been removed from the PDFPage class, and the getAnnotations
method changed to return a List, which can be altered as required. So, for example
- PDFAnnotation[] annots = page.getAnnotations(); - page.removeAnnotation(oldannot); - page.addAnnotation(newannot); + List annots = page.getAnnotations(); + annots.remove(oldannot); + annots.add(newannot);
scale
and translate
methods have been removed with no replacement. These altered the coordinate system of the page, and could easily lead to confusion. As you can achieve the same results with the setUnits method or a little basic math, these methods have been removed.
- page.translate(100, 100); - page.drawCircle(0, 0, 50); + page.drawCircle(100, 100, 50);
The setCanvas
method in version 1.0 allowed you to do three things - change the origin of the page (to top-left, for example), change the units it's measured in (eg. to millimeters), and to change the window on the page that was used (eg. to place a border around the page, so when you specify (0,0) the cursor was actually moved to (50,50).).
This last function was confusing, hardly ever used and was in any case redundant, as a it's probably easier just to remember to add a constant to each co-ordinate you pass in to a method. Consequently the method has been renamed to setUnits
, and now only allows you to specify the origin and the units the page is measured in. Additionally, the names of those constants have changed slightly - units have a "UNITS_" prefix and the origin has an "ORIGIN_" prefix.
Additionally, the getCanvasWidth
and getCanvasHeight
methods have been removed, as the
canvas width and height will now always be the same as the page width and height. Just replace these methods with getWidth
and getHeight
.
// Most calls to setCanvas simply set the units and the origin. This is basically unchanged: - page.setCanvas(0, 0, page.getWidth(), page.getHeight(), PDFPage.MM, PDFPage.PAGETOP); + page.setUnits(PDFPage.UNITS_MM, PDFPage.ORIGIN_PAGETOP); - page.getCanvasWidth(); + page.getWidth();
If you were calling the old setCanvas method with non-zero values for the first two parameters, simply add those values to any future calls to a draw or path method. For example:
// In the unlikely event you're also changing the position of (0,0) on the page, just // calculate the new coordinates yourself and add them to the draw or path methods. // This code draws a line 50mm from the top-left of the page to 50mm from the bottom-right. - page.setCanvas(50, 50, page.getWidth()-100, page.getHeight()-100, PDFPage.MM, PDFPage.PAGETOP); - page.drawLine(0, 0, page.getCanvasWidth(), page.getCanvasHeight()); + page.setUnits(PDFPage.UNITS_MM, PDFPage.ORIGIN_PAGETOP); + float fiftymm = 50*PDFPage.UNITS_MM; + page.drawLine(fiftymm, fiftymm, page.getWidth()-fiftymm, page.getHeight()-fiftymm);
BarCode
- and the drawBarCode
methods in the PDFPage
class have been reduced to a single method that takes one of these as an argument.
Positioning the barcode is now done in the same way as with an image - two corners of a rectangle are specified. To migrate from version 1, it's best to work out the new positioning parameters to drawBarCode by trial and error.
Note the API for the BarCode class is not finalized yet, so there may be some changes still in store.
- page.drawBarCode(PDFPage.BARCODE_CODE128, "DataHere", 100, 100, true, 1); + Barcode code = new BarCode(Barcode.CODE128, "DataHere"); + code.setShowText(true); + page.drawBarCode(code, 100, 75, 100+code.getWidth(), 125);
drawPage
method. This would copy the contents of the page, and any annotations above it, to the new location.
In version 2, we've introduced the concept of a "PDFCanvas", which is like a java.awt.Canvas. Each page has a canvas (so does each annotation for that matter. In PDF speak, they're called Form XObjects). The canvas does not include the pages annotations.
To simply draw the canvas from one page onto another, do
- page.drawPage(oldpage, 100, 100, 200, 200); + PDFCanvas oldpagecanvas = new PDFCanvas(oldpage.getCanvas()); + page.drawCanvas(oldpagecanvas, 100, 100, 200, 200);Any annotations would have to be recreated manually however. Those calling this method should think carefully about what they're trying to achieve. Are you trying to clone a page? There are now better ways to do this - for example, to add a duplicate of a page to the current PDF, try one of the two lines, which are functionally identical:
pdf.getPages().add(new PDFPage(currentpage)); pdf.newPage(currentpage);