Creating and editing PDF 2.0 Documents
In July 2017 PDF 2.0 was released which added a bunch of useful features to PDFs such as AES-256 encryption, Unicode passwords, associated files, and more. Later that year we released version 7.1.0 which brought PDF 2.0 support to iText!
This page details how to create a PDF 2.0 document using iText, and how to take advantage of the new associated files feature in the PDF 2.0 specification.
Creating a PDF 2.0 Document
Creating a PDF 2.0 document with iText is simple. All you have to do is set the PDF version number to be 2.0:
PdfWriter writer = new PdfWriter(OUTPUT_FILE, new WriterProperties().setPdfVersion(PdfVersion.PDF_2_0));
PdfDocument pdfDocument = new PdfDocument(writer);
pdfDocument.setTagged();
Document document = new Document(pdfDocument);
document.add(new Paragraph("Hello world!"));
document.close();
PdfWriter writer = new PdfWriter(OUTPUT_FILE, new WriterProperties().SetPdfVersion(PdfVersion.PDF_2_0));
PdfDocument pdfDocument = new PdfDocument(writer);
pdfDocument.SetTagged();
Document document = new Document(pdfDocument);
document.Add(new Paragraph("Hello world!"));
document.Close();
iText is more than capable of editing an existing PDF 2.0 document and doing things like adding a signature or adding additional content. Let's create a PDF that documents the evolution of the iText logo. Here are some of the older versions of the iText logo:
Let's create a portfolio that details the evolution of our logo. Each page will have a separate version of the logo, as well as an associated file attachment (new in PDF 2.0!) that maps each page to the embedded image.
String[] logoFilePaths = {
"C:\\iText\\logos\\itext_logo_v1.png",
"C:\\iText\\logos\\itext_logo_v2.png",
"C:\\iText\\logos\\itext_logo_v3.png",
};
PdfWriter writer = new PdfWriter(OUTPUT_FILE, new WriterProperties().setPdfVersion(PdfVersion.PDF_2_0));
PdfDocument pdfDocument = new PdfDocument(writer);
Document document = new Document(pdfDocument);
for (int x = 0; x < logoFilePaths.length; x++) {
String path = logoFilePaths[x];
String description = "iText logo version: " + (x + 1);
document.add(new Paragraph(description));
Image image = new Image(ImageDataFactory.create(path)).scaleAbsolute(100, 100);
document.add(image);
PdfFileSpec fileSpec = PdfFileSpec.createEmbeddedFileSpec(pdfDocument, path, "logo_v" + (x + 1) + ".png", PdfName.ApplicationOctetStream);
pdfDocument.addAssociatedFile(description, fileSpec);
pdfDocument.getPage(x + 1).addAssociatedFile(fileSpec);
if (x != logoFilePaths.length - 1) {
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
}
document.close();
String[] logoFilePaths = {
"C:\\iText\\logos\\itext_logo_v1.png",
"C:\\iText\\logos\\itext_logo_v2.png",
"C:\\iText\\logos\\itext_logo_v3.png",
};
PdfWriter writer = new PdfWriter(OUTPUT_FILE, new WriterProperties().SetPdfVersion(PdfVersion.PDF_2_0));
PdfDocument pdfDocument = new PdfDocument(writer);
Document document = new Document(pdfDocument);
for (int x = 0; x < logoFilePaths.Length; x++) {
String path = logoFilePaths[x];
String description = "iText logo version: " + (x + 1);
document.Add(new Paragraph(description));
Image image = new Image(ImageDataFactory.Create(path)).ScaleAbsolute(100, 100);
document.Add(image);
PdfFileSpec fileSpec = PdfFileSpec.CreateEmbeddedFileSpec(pdfDocument, path, "logo_v" + (x + 1) + ".png", PdfName.ApplicationOctetStream);
pdfDocument.AddAssociatedFile(description, fileSpec);
pdfDocument.GetPage(x + 1).addAssociatedFile(fileSpec);
if (x != logoFilePaths.Length - 1) {
document.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
}
document.Close();
Something is missing though- the latest and greatest iText logo:
Luckily, with iText appending a new page with our logo and embedded images is simple:
PdfReader reader = new PdfReader(INPUT_FILE);
PdfWriter writer = new PdfWriter(OUTPUT_FILE, new WriterProperties().setPdfVersion(PdfVersion.PDF_2_0));
PdfDocument pdfDocument = new PdfDocument(reader, writer);
Document document = new Document(pdfDocument);
String logoPath = "C:\\iText\\logos\\itext_logo_recent.png";
pdfDocument.addNewPage();
document.add(new AreaBreak(AreaBreakType.LAST_PAGE));
int logoNumber = pdfDocument.getNumberOfPages();
String description = "iText logo version: " + logoNumber;
document.add(new Paragraph(description));
document.add(new Image(ImageDataFactory.create(logoPath)));
PdfFileSpec fileSpec = PdfFileSpec.createEmbeddedFileSpec(pdfDocument, logoPath, "logo_v" + logoNumber + ".png", PdfName.ApplicationOctetStream);
pdfDocument.addAssociatedFile(description, fileSpec);
pdfDocument.getPage(logoNumber).addAssociatedFile(fileSpec);
document.close();
PdfReader reader = new PdfReader(INPUT_FILE);
PdfWriter writer = new PdfWriter(OUTPUT_FILE, new WriterProperties().SetPdfVersion(PdfVersion.PDF_2_0));
PdfDocument pdfDocument = new PdfDocument(reader, writer);
Document document = new Document(pdfDocument);
String logoPath = "C:\\iText\\logos\\itext_logo_recent.png";
pdfDocument.AddNewPage();
document.Add(new AreaBreak(AreaBreakType.LAST_PAGE));
int logoNumber = pdfDocument.GetNumberOfPages();
String description = "iText logo version: " + logoNumber;
document.Add(new Paragraph(description));
document.Add(new Image(ImageDataFactory.Create(logoPath)));
PdfFileSpec fileSpec = PdfFileSpec.CreateEmbeddedFileSpec(pdfDocument, logoPath, "logo_v" + logoNumber + ".png", PdfName.ApplicationOctetStream);
pdfDocument.AddAssociatedFile(description, fileSpec);
pdfDocument.GetPage(logoNumber).AddAssociatedFile(fileSpec);
document.Close();
Consuming a PDF 2.0 Document
We can read in a PDF 2.0 document just like any PDF in iText through the PdfReader
object. Since the document we just created above has files associated with each page we can take advantage of the new getAssociatedFiles()
function of a PdfPage
object to get all the files associated with that page. From there we can easily loop each attachment to extract the files that are associated with each page.
PdfReader reader = new PdfReader(INPUT_FILE);
PdfWriter writer = new PdfWriter(OUTPUT_FILE, new WriterProperties().setPdfVersion(PdfVersion.PDF_2_0));
PdfDocument pdfDocument = new PdfDocument(reader, writer);
for (int x = 1; x <= pdfDocument.getNumberOfPages(); x++) {
PdfPage page = pdfDocument.getPage(x);
PdfArray associatedFiles = page.getAssociatedFiles(false);
for (int i = 0; i < associatedFiles.size(); i++) {
PdfDictionary attachment = associatedFiles.getAsDictionary(i);
String fileName = attachment.getAsString(PdfName.F).getValue();
byte[] fileBytes = attachment.getAsDictionary(PdfName.EF).getAsStream(PdfName.F).getBytes();
Files.write(new File("C:\\iTextLogos\\" + fileName).toPath(), fileBytes);
}
}
PdfReader reader = new PdfReader(INPUT_FILE);
PdfWriter writer = new PdfWriter(OUTPUT_FILE, new WriterProperties().SetPdfVersion(PdfVersion.PDF_2_0));
PdfDocument pdfDocument = new PdfDocument(reader, writer);
for (int x = 1; x <= pdfDocument.GetNumberOfPages(); x++) {
PdfPage page = pdfDocument.GetPage(x);
PdfArray associatedFiles = page.GetAssociatedFiles(false);
for (int i = 0; i < associatedFiles.Size(); i++) {
PdfDictionary attachment = associatedFiles.GetAsDictionary(i);
String fileName = attachment.GetAsString(PdfName.F).GetValue();
byte[] fileBytes = attachment.GetAsDictionary(PdfName.EF).GetAsStream(PdfName.F).GetBytes();
File.WriteAllBytes("C:\\iTextLogos\\" + fileName, fileBytes);
}
}
If we look at the contents of the C:/iTextLogos directory we see that our new attachments were properly extracted!