By examining this example you’ll learn how to work with:
You can find the entire source code for all com.lowagie.collections.* classes here, but you’ll need this page to know where to start. Let’s deal with this example step by step.
We could use our filmfestival database to get all the movies directed by Stanley Kubrick, but because this example doesn’t need much data, I opted to work with a plain text file of which each line is a pipe-delimited record: kubrick_movies.txt:
Killer's Kiss|1955|67|kubrick01.jpg|2006-01-26 Killing, The|1956|85|kubrick02.jpg|2006-01-26 Paths of Glory|1957|87|kubrick03.jpg|2006-01-26 Spartacus|1960|184|kubrick04.jpg|2002-07-19 Lolita|1962|147|kubrick05.jpg|2000-11-30 Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb|1964|90|kubrick06.jpg|2000-11-30 2001: A Space Odyssey|1968|143|kubrick07.jpg|2000-11-30 Clockwork Orange, A|1971|131|kubrick08.jpg|2000-11-30 Barry Lyndon|1975|178|kubrick09.jpg|2000-11-30 Shining, The|1980|115|kubrick10.jpg|2000-11-30 Full Metal Jacket|1987|112|kubrick11.jpg|2000-11-30 Eyes Wide Shut|1999|153|kubrick12.jpg|2000-11-30
As with the other database examples, you will find some classes to query this data in a database package:
HashSet, including some hard-coded records: one describing the Kubrick box set (not a real movie, just a box of DVDs) and a record for the documentary Stanley Kubrick: A Life in Pictures.
The KubrickMovies class has a main method that allows you to test the functionality and find out what’s in this small database. First it gets the movies in the text file, and prints them to the System.out, then it prints the two hard-coded records to the System.out.
public static void main(String[] args) { KubrickMovies movies = KubrickMovies.getInstance(); KubrickMovie movie; for (Iterator i = movies.getKubrickMovies(MovieComparator.YEAR).iterator(); i.hasNext(); ) { movie = (KubrickMovie)i.next(); System.out.println(movie); } System.out.println(); System.out.println(movies.getKubrickDocumentary()); System.out.println(movies.getKubrickBox()); }
This is the result:
Killer's Kiss (1955): 67 minutes The Killing (1956): 85 minutes Paths of Glory (1957): 87 minutes Spartacus (1960): 184 minutes Lolita (1962): 147 minutes Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb (1964): 90 minutes 2001: A Space Odyssey (1968): 143 minutes A Clockwork Orange (1971): 131 minutes Barry Lyndon (1975): 178 minutes The Shining (1980): 115 minutes Full Metal Jacket (1987): 112 minutes Eyes Wide Shut (1999): 153 minutes Stanley Kubrick: A Life in Pictures (2001): 142 minutes Stanley Kubrick Box (0): 0 minutes
We’ll use this data, and different resources to create different documents. We’ll bundle these documents in a portable collection soon, but let’s start by having a look at the documents first.
The code to generate the different documents that will be bundled in a portable collection can be found here. Let’s have a look at the different documents. In the next section you’ll deal with them in more detail.
MoviePage.All these classes implement the PDFCreationInterface, an interface that only has 2 methods:
We’ll use these methods in the static method PortableCollectionExample.createPdf():
public static void createPdf(PDFCreationInterface collection) throws IOException, DocumentException { FileOutputStream fos = new FileOutputStream(PATH + collection.getFilename()); fos.write(collection.createPdf()); fos.flush(); fos.close(); }
For instance, we’ll create the master file kubrick_collection.pdf like this:
public static void main(String[] args) { try { PortableCollectionExample.createPdf(new KubrickCollection()); } catch (IOException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } }
You’ll discover that step 1, 2, 3, and 5 are identical in the createPdf() method of each document generating class:
//step 1 Document document = new Document(); // step 2 ByteArrayOutputStream baos = new ByteArrayOutputStream(); PdfWriter.getInstance(document, baos); // step 3 document.open(); // step 4 ... // step 5 document.close(); return baos.toByteArray();
In step 4, we’ll add the content, and in some cases define the document as a portable collection.
Note that you’ll also find some helper classes to add a file attachment annotation, a local destination, and an action in a cell event. These helper classes were put in a separate events package.
Now that you know where to find the data about Kubrick movies, and now that you have determined all the documents you’ll put in the collections, let’s deal with the different documents one by one, looking at the implementation of step 4 in the createPdf() method.
On each movie page, you are going to add a Paragraph (the movie title) and a PdfPTable (with some info). The table will have the movie poster on one side, and the year, duration, and DVD acquisition date on the other side. Nothing special, just iText business as usual:
Paragraph p = new Paragraph(movie.getFullTitle(true), FontFactory.getFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED, 16)); document.add(p); document.add(Chunk.NEWLINE); PdfPTable table = new PdfPTable(WIDTHS); table.addCell(Image.getInstance(KubrickMovies.PATH + movie.getPoster())); PdfPCell cell = new PdfPCell(); cell.addElement(new Paragraph("Year: " + movie.getYear())); cell.addElement(new Paragraph("Duration: " + movie.getDuration())); cell.addElement(new Paragraph("DVD bought on: " + movie.getDvdAcquired())); table.addCell(cell); document.add(table);
You are going to make 12 of these pages: kubrick01.pdf, kubrick02.pdf, ..., kubrick12.pdf. However, you are going to add an extra chunk with a special action:
PdfDestination dest = new PdfDestination(PdfDestination.FIT); dest.addFirst(new PdfNumber(1)); PdfTargetDictionary target = new PdfTargetDictionary(false); target.setAdditionalPath(new PdfTargetDictionary(false)); Chunk chunk = new Chunk("Go to original document"); PdfAction action = PdfAction.gotoEmbedded(null, target, dest, false); chunk.setAction(action); document.add(chunk);
With class PdfDestination you define a destination on a page. You add the page number of this page with method addFirst() (in this case we want to go to page 1 and make sure the page fits the viewer window).
Then you create a PdfTargetDictionary. This defines a target document for an embedded GoTo. The target can be a parent (child = false) or a child document (child = true). In this case, you want a GoTo to the parent document or the parent document (mind the method setAdditionalPath). You can add paths to go to siblings by combining a path to the parent with a path to a child. You’ll make an example where the target is a child soon.
Finally you create the PdfAction using the PdfDestination and the PdfTargetDictionary. You don’t specify a filename (hence the null; an embedded file can only have one parent), and you don’t want the file to open in a new window (as indicated with the parameter false).
This clickable Chunk saying Go to original document doesn’t make any sense if you generate the documents as standalone PDF files. Nothing will happen if you click on the link. But once you’ll add these documents as embedded files to another PDF (maybe a portable collection), this action will allow you to jump to kubrick_collection.pdf, which is the parent of the kubrick_movies.pdf document, a PDF document generated using class MoviesByKubrick.
In the document kubrick_movies.pdf, you’ll find the 12 PDF documents inside the portable collection:
In Adobe Reader (8 and higher), you will find an overview of all the single movie pages ordered by year (in descending order), but you can easily change this order, and sort them by name, duration, and so on. If you click on the cover sheet button, you’ll find a PDF file saying “This document contains a collection of PDFs, one per Stanley Kubrick movie.”
How was this done?
If you have a PDF document with attachments (of any kind), the easiest way to turn this document into a portable collection is by adding these two lines to step 4:
PdfCollection collection = new PdfCollection(PdfCollection.HIDDEN); writer.setCollection(collection);
By chosing HIDDEN as initial view, the list of items will be hidden. These are the possible values when creating a collection:
In this example, you show the items in detail, and you tell the document it should open with the ‘Eyes Wide Shut’ page as initial document:
PdfCollection collection = new PdfCollection(PdfCollection.DETAILS); collection.setInitialDocument("Eyes Wide Shut");
Next you are going to define a collection schema.
In this example you construct the collection schema and you add the different collections fields in a separate method. With methods setOrder() and setVisible() you specify the order of the fields in the overview, and whether the field has to be visible or not.
private static PdfCollectionSchema getCollectionSchema() { PdfCollectionSchema schema = new PdfCollectionSchema(); PdfCollectionField description = new PdfCollectionField(SIZE_CAPTION, PdfCollectionField.SIZE); description.setOrder(4); schema.addField(SIZE_FIELD, description); PdfCollectionField filename = new PdfCollectionField(FILE_CAPTION, PdfCollectionField.FILENAME); filename.setVisible(false); schema.addField(FILE_FIELD, filename); PdfCollectionField title = new PdfCollectionField(TITLE_CAPTION, PdfCollectionField.TEXT); title.setOrder(1); schema.addField(TITLE_FIELD, title); PdfCollectionField duration = new PdfCollectionField(DURATION_CAPTION, PdfCollectionField.NUMBER); duration.setOrder(2); schema.addField(DURATION_FIELD, duration); PdfCollectionField year = new PdfCollectionField(YEAR_CAPTION, PdfCollectionField.NUMBER); year.setOrder(0); schema.addField(YEAR_FIELD, year); PdfCollectionField dvd = new PdfCollectionField(DVD_CAPTION, PdfCollectionField.DATE); dvd.setOrder(3); schema.addField(DVD_FIELD, dvd); return schema; }
You add this schema to the collection object.
PdfCollectionSchema schema = getCollectionSchema(); collection.setSchema(schema);
Note that there are different types of collection fields.
Three types identify the types of fields in the collection item or collection subitem dictionary:
Five types refer to file-related fields:
When creating our file specification, we’ll reuse the schema object to add the collection items of type text, date and number; the file related fields are defined automatically from the file specification.
You may also define a sort field and sort order. In this case we are going to sort on the year field in descending order.
PdfCollectionSchema schema = getCollectionSchema(); collection.setSchema(schema); PdfCollectionSort sort = new PdfCollectionSort(YEAR_FIELD); sort.setSortOrder(false); collection.setSort(sort); writer.setCollection(collection);
In the last line of the code snippet, you added the collection to the writer, you can now start adding documents to the collection.
You will add your 12 separate movie documents to the PDF as embedded files. You will get an instance of the KubrickMovies object, get a MoviePage instance, and create a PdfFileSpecification with method fileEmbedded using the filename, bytes and description. The description is used for the key in the EmbeddedFiles name tree (in the Names dictionary specified in the document catalog). You can use this key when you set the initial page or create a GoToE action.
PdfFileSpecification fs; PdfCollectionItem item; KubrickMovie movie; MoviePage page; KubrickMovies movies = KubrickMovies.getInstance(); for (Iterator i = movies.getKubrickMovies(MovieComparator.YEAR).iterator(); i.hasNext(); ) { movie = (KubrickMovie)i.next(); page = new MoviePage(movie); fs = PdfFileSpecification.fileEmbedded(writer, null, page.getFilename(), page.createPdf()); fs.addDescription(movie.getFullTitle(false), false); item = new PdfCollectionItem(schema); item.addItem(TITLE_FIELD, movie.getTitle()); if (movie.getArticle() != null) { item.setPrefix(TITLE_FIELD, movie.getArticle()); } item.addItem(DVD_FIELD, movie.getDvd()); item.addItem(DURATION_FIELD, movie.getDuration()); item.addItem(YEAR_FIELD, movie.getYear()); fs.addCollectionItem(item); writer.addFileAttachment(fs); }
The collection items are filled in after creating a PdfCollectionItem object that knows about the schema. Note that we didn’t use the method getFullTitle() for the title field. We added the title without the article (for instance ‘Killing’) and then we used setPrefix() to add the article (for instance ‘The’). This way, when the documents are sorted by title, the movie ‘The Killing’ will be between ‘Killer’s Kiss’ and ‘Lolita’, not after ‘Spartacus’ in spite of the fact that ‘The Killing’ starts with a ‘T’.
We add the collection item object to the file specification, and add the file specification to the writer.
Congratulations! You have just created your first portable collection!
There’s nothing special about the PDF document listing the movies discussed in the documentary Stanley Kubrick: A Life in Pictures: kubrick_documentary.pdf
When you look at Documentary.java, you’ll see it’s just a document with a Paragraph, a List (with the 12 movie titles), and the GoToE action we already discussed (it only works if the document is added to a collection). The only difference with the previous example, is that you now use a named destination (”documentary”) instead of a PdfDestination.
PdfTargetDictionary target = new PdfTargetDictionary(false); Chunk chunk = new Chunk("Go to original document"); PdfAction action = PdfAction.gotoEmbedded(null, target, "documentary", false, false); chunk.setAction(action); document.add(chunk);
The next example however teaches us some new concepts.
Just like with the Documentary document, we’ll add a Paragraph and a List, but now we’ll also add the movie posters (JPEG images) as attachments (with a PushPin symbol). We’ll also define the PDF as a portable collection with a tiled initial view.
This time you don’t worry about a schema, sort order, or collection items. You just let Adobe Viewer decide how to order them (by default, they are ordered the way the attachments were added) and what information to show (the file name, description, modification time, and size).
writer.setCollection(new PdfCollection(PdfCollection.TILE)); document.add(new Paragraph("This is a list of Kubrick movies in my DVD collection.")); KubrickMovie movie; PdfAnnotation annot; KubrickMovies kubrick = KubrickMovies.getInstance(); Set movies = kubrick.getKubrickMovies( MovieComparator.DVD, MovieComparator.TITLE, MovieComparator.YEAR, MovieComparator.DURATION); movies.add(kubrick.getKubrickDocumentary()); List list = new List(List.UNORDERED, 20); ListItem item; Chunk chunk; for (Iterator i = movies.iterator(); i.hasNext(); ) { movie = (KubrickMovie)i.next(); annot = PdfAnnotation.createFileAttachment(writer, null, movie.getFullTitle(false), null, KubrickMovies.PATH + movie.getPoster(), movie.getPoster()); item = new ListItem(movie.getFullTitle(false)); item.add("\u00a0\u00a0"); chunk = new Chunk("\u00a0\u00a0\u00a0\u00a0"); chunk.setAnnotation(annot); item.add(chunk); item.add("\u00a0\u00a0("); item.add(movie.getDvdAcquired()); item.add(")"); list.add(item); } document.add(list);
If you click on the PushPin symbol, the image attachment will open in an application that is associated with JPEG images. If you click on an entry in the collection (one of the items in the left hand side panel), the image will open in the viewer:
You can return to the PDF shown in the previous image by clicking on the cover sheet icon.
You now have a complete series of documents related to Stanley Kubrick. Let’s add your database file kubrick_movies.txt and a picture of Stanley Kubrick kubrick.jpg and add all this to your ultimate portable Kubrick collection.
You have created a portable collection (detailed view) with PDF files (kubrick_movies.pdf) and a portable collection (tiled view) with JPEG images (my_dvd_collection.pdf). Now we are going to create a portable collection (hidden) with different types of files.
In our collection schema, we define the file type (JPEG, PDF, TXT) as a text field and the filename as a filename field (obviously).
private static PdfCollectionSchema getCollectionSchema() { PdfCollectionSchema schema = new PdfCollectionSchema(); PdfCollectionField type = new PdfCollectionField(TYPE_CAPTION, PdfCollectionField.TEXT); type.setOrder(0); schema.addField(TYPE_FIELD, type); PdfCollectionField filename = new PdfCollectionField(FILE_CAPTION, PdfCollectionField.FILENAME); filename.setOrder(1); schema.addField(FILE_FIELD, filename); return schema; }
You create our portable collection, and this time you pass an array (not just one field) as sort order:
PdfCollection collection = new PdfCollection(PdfCollection.HIDDEN); PdfCollectionSchema schema = getCollectionSchema(); collection.setSchema(schema); PdfCollectionSort sort = new PdfCollectionSort(KEYS); collection.setSort(sort); writer.setCollection(collection);
In this example the constant KEYS is defined like this:
public String[] KEYS = { TYPE_FIELD, FILE_FIELD };
In human language: you are first going to sort by the file type, then by the filename.
The Kubrick Collection is a DVD box with 10 DVDs. I bought it in the year 2000. In the PDF document kubrick_collection.pdf, you add an image of the Kubrick box. You get the kubrick box record with method getKubrickBox(). When looping over all the movies, you can compare the DVD acquisition date of each individual movie with the acquisition date of the box. That was only the movies in the box will be added to the list.
Image img = Image.getInstance(KubrickMovies.PATH + "kubrick00.jpg"); document.add(img); KubrickMovies kubrick = KubrickMovies.getInstance(); KubrickMovie box = kubrick.getKubrickBox(); KubrickMovie movie; Set movies = kubrick.getKubrickMovies(MovieComparator.DVD, MovieComparator.TITLE, MovieComparator.YEAR, MovieComparator.DURATION); List list = new List(List.UNORDERED, 20); ListItem item; PdfAction action = null; PdfDestination dest = new PdfDestination(PdfDestination.FIT); dest.addFirst(new PdfNumber(1)); PdfTargetDictionary intermediate; PdfTargetDictionary target; Chunk chunk; for (Iterator i = movies.iterator(); i.hasNext(); ) { movie = (KubrickMovie)i.next(); if (movie.getDvd().equals(box.getDvd())) { item = new ListItem(movie.getFullTitle(false)); target = new PdfTargetDictionary(true); target.setEmbeddedFileName(movie.getFullTitle(false)); intermediate = new PdfTargetDictionary(true); intermediate.setFileAttachmentPage(1); intermediate.setFileAttachmentIndex(1); intermediate.setAdditionalPath(target); action = PdfAction.gotoEmbedded(null, intermediate, dest, true); chunk = new Chunk(" (see info)"); chunk.setAction(action); item.add(chunk); list.add(item); } } document.add(list);
What’s special in this code snippet? You create a PdfTargetDictionary for a child document. You refer to it by the embedded file name. This is a single movie page in the kubrick_movies.pdf document. But currently you aren’t creating this document, you’re creating its parent. So you have to add this target as an additional path to another target. You create an intermediate child PdfTargetDictionary that refers to the first file attachment added to page 2 (it says 1 in the code because we start counting at 0) of the kubrick_collection.pdf document.
You’ll be adding these attachments soon.
First, you’ll add some files to the collection that aren’t PDFs: a JPEG (the picture of Stanley Kubrick) and a TXT file (the file we used as database).
PdfCollectionItem collectionitem = new PdfCollectionItem(schema); PdfFileSpecification fs; fs = PdfFileSpecification.fileEmbedded(writer, KubrickMovies.PATH + "kubrick.jpg", "kubrick.jpg", null); fs.addDescription("Stanley Kubrick", false); collectionitem.addItem(TYPE_FIELD, "JPEG"); fs.addCollectionItem(collectionitem); writer.addFileAttachment(fs); fs = PdfFileSpecification.fileEmbedded(writer, KubrickMovies.PATH + "kubrick_movies.txt", "kubrick_database.txt", null, false); fs.addDescription("Kubrick database", false); collectionitem.addItem(TYPE_FIELD, "TXT"); fs.addCollectionItem(collectionitem); writer.addFileAttachment(fs);
On the second page, you’ll add a PdfPTable with the PDF document you’ve created before as attachment. Let’s have a look at the way the first cell is added.
byte[] pdf; PdfReader reader; Image page; PdfPCell cell; PdfPTable table = new PdfPTable(1); pdf = new MoviesByKubrick().createPdf(); reader = new PdfReader(pdf); page = Image.getInstance(writer.getImportedPage(reader, 1)); page.scalePercent(30); cell = new PdfPCell(page, false); fs = PdfFileSpecification.fileEmbedded(writer, null, MoviesByKubrick.FILENAME, pdf); collectionitem.addItem(TYPE_FIELD, "PDF"); fs.addCollectionItem(collectionitem); target = new PdfTargetDictionary(true); target.setFileAttachmentPagename("movies"); target.setFileAttachmentName("The movies of Stanley Kubrick"); cell.setCellEvent(new PdfActionEvent(writer, PdfAction.gotoEmbedded(null, target, dest, true))); cell.setCellEvent(new FileAttachmentEvent(writer, fs, "The movies of Stanley Kubrick")); cell.setCellEvent(new LocalDestinationEvent(writer, "movies")); table.addCell(cell);
For this example, you create the PDF, and you read the byte[] with PdfReader to get an Image of the first page. You’ll use that image to create a Cell.
You’ll use the same byte array to add the file as an attachment (the PushPin will be positioned by a cell event). When you click on the PushPin the attachment will open in a new Adobe Viewer window.
In another cell event, you also add a GoToE action. When you click the cell content, the document will be openened differently. Try the example to see what happens.
Finally you use a third cell event to define a local destination. You refered to one of those local destinations in the documentary.pdf document.
The rest of the code of step 4 is very similar to what you’ve done before:
pdf = new Documentary().createPdf(); reader = new PdfReader(pdf); page = Image.getInstance(writer.getImportedPage(reader, 1)); page.scalePercent(30); cell = new PdfPCell(page, false); fs = PdfFileSpecification.fileEmbedded(writer, null, Documentary.FILENAME, pdf); fs.put(PdfName.MODDATE, new PdfDate()); collectionitem.addItem(TYPE_FIELD, "PDF"); fs.addCollectionItem(collectionitem); target = new PdfTargetDictionary(true); target.setFileAttachmentPagename("documentary"); target.setFileAttachmentName("Stanley Kubrick: A Life in Pictures"); cell.setCellEvent(new PdfActionEvent(writer, PdfAction.gotoEmbedded(null, target, dest, true))); cell.setCellEvent(new FileAttachmentEvent(writer, fs, "Stanley Kubrick: A Life in Pictures")); cell.setCellEvent(new LocalDestinationEvent(writer, "documentary")); table.addCell(cell); pdf = new MyDvdCollection().createPdf(); reader = new PdfReader(pdf); page = Image.getInstance(writer.getImportedPage(reader, 1)); page.scalePercent(30); cell = new PdfPCell(page, false); fs = PdfFileSpecification.fileEmbedded(writer, null, MyDvdCollection.FILENAME, pdf); fs.put(PdfName.MODDATE, new PdfDate()); collectionitem.addItem(TYPE_FIELD, "PDF"); fs.addCollectionItem(collectionitem); target = new PdfTargetDictionary(true); target.setFileAttachmentPagename("dvds"); target.setFileAttachmentName("My Stanley Kubrick DVDs"); cell.setCellEvent(new PdfActionEvent(writer, PdfAction.gotoEmbedded(null, target, dest, true))); cell.setCellEvent(new FileAttachmentEvent(writer, fs, "My Stanley Kubrick DVDs")); cell.setCellEvent(new LocalDestinationEvent(writer, "dvds")); table.addCell(cell); document.add(table);
And this completes our Kubrick Collection example. Compile and execute this example and you’ll get one big PDF file containing all the PDFs we’ve discussed so far: kubrick_collection.pdf. Let’s have a look at the document.
When you open the portable collection, this is what you see on the first page (the cover sheet):
The entries in the collection are hidden for the moment, you only see the top bar with some icons you can click to change the view. On the first page, you see a list of movies. By clicking on (see info), one of the movie pages (a grand child of the collection) is opened:
You already created a similar page as a standalone PDF document. When you clicked on the chunk ‘go to original document’ nothing happened. Now when you click on the same chunk in the document that was opened as the grand child of your collection, the parent of the parent is opened.
Let’s have a look at the other documents added to the collection. Click on the icon that opens the tiled view. A list with one JPG, three PDF files and one TXT is shown (ordered by file type, the PDFs are ordered by filename). When you click on the JPEG, the image is shown in Adobe Reader:
The first PDF file with the Documentary is an ordinary PDF file. Click on the icon in the tiled view, and the document opens in Adobe Reader.
The other two PDF files inside the collection are portable collections themselves. Apparently portable collections can’t be nested. You have to open them in a separate window:
Text files aren’t rendered in Adobe Reader, you can either open them in the appropriate application (for instance Notepad), or save them to disk.
The cover sheet you made wasn’t really a one page PDF document. You made a second page with a 1 column table. Clicking on the PushPin opens the attached document. Clicking on the imported page shown in the table cell, performs a GoToE action.
Note that when you open the documentary PDF, you can always return to the Kubrick PDF (the portable collection) by clicking on the chunk ‘Go to original document’.
This conludes the Kubrick Collection example.
In this example we made a portable collection with some information (PDF files, images,...) about movies by Stanley Kubrick. The content of these PDF files wasn’t really interesting, but the example is important if you want to learn more about file attachments, embedded files, GoToE actions, and portable collections of course.
The example demonstrates different possibilities. “The intent of portable collections is to present, sort, and search collections of related documents, such as email archives, photo collections, and engineering bid sets.” I hope this example inspires you to make more use of portable collections.