I have a pdf file with some form field that I want to fill from java. Right now I'm trying to fill just one form which I am finding by its name. My code looks like this:
File file = new File("c:/Testy/luxmed/Skierowanie3.pdf");
PDDocument document = PDDocument.load(file);
PDDocumentCatalog doc = document.getDocumentCatalog();
PDAcroForm Form = doc.getAcroForm();
String formName = "topmostSubform[0].Page1[0].pana_pania[0]";
PDField f = Form.getField(formName);
setField(document, formName, "Artur");
System.out.println("New value 2nd: " + f.getValueAsString());
document.saveIncremental(new FileOutputStream("c:/Testy/luxmed/nowy_pd3.pdf"));
document.close();
and this:
public static void setField(PDDocument pdfDocument, String name, String Value) throws IOException
{
PDDocumentCatalog docCatalog = pdfDocument.getDocumentCatalog();
PDAcroForm acroForm = docCatalog.getAcroForm();
PDField field = acroForm.getField(name);
if (field instanceof PDCheckBox){
field.setValue("Yes");
}
else if (field instanceof PDTextField){
System.out.println("Original value: " + field.getValueAsString());
field.setValue(Value);
System.out.println("New value: " + field.getValueAsString());
}
else{
System.out.println("Nie znaleziono pola");
}
}
As system.out states, the value was set correctly, but in new the generated pdf file, new value is not showing up (original String is presented) so I guess the incremental saving does not work properly. What am I missing?
I use 2.0.2 version of pdfbox, and here is pdf file with which I working: pdf
In general
When saving changes to a PDF as an incremental update with PDFBox 2.0.x, you have to set the property NeedToBeUpdated to true for every PDF object changed. Furthermore, the object must be reachable from the PDF catalog via a chain of references, and each PDF object in this chain also has to have the property NeedToBeUpdated set to true.
This is due to the way PDFBox saves incrementally, starting from the catalog it inspects the NeedToBeUpdated property, and if it is set to true, PDFBox stores the object, and only in this case it recurses deeper into the objects referenced from this object in search for more objects to store.
In particular this implies that some objects unnecessarily have to be marked NeedToBeUpdated, e.g. the PDF catalog itself, and in some cases this even defeats the purpose of the incremental update at large, see below.
In case of the OP's document
Setting the NeedToBeUpdated properties
On one hand one has to extend the setField method to mark the chain of field dictionaries up to and including the changed field and also the appearance:
public static void setField(PDDocument pdfDocument, String name, String Value) throws IOException
{
PDDocumentCatalog docCatalog = pdfDocument.getDocumentCatalog();
PDAcroForm acroForm = docCatalog.getAcroForm();
PDField field = acroForm.getField(name);
if (field instanceof PDCheckBox) {
field.setValue("Yes");
}
else if (field instanceof PDTextField) {
System.out.println("Original value: " + field.getValueAsString());
field.setValue(Value);
System.out.println("New value: " + field.getValueAsString());
}
else {
System.out.println("Nie znaleziono pola");
}
// vvv--- new
COSDictionary fieldDictionary = field.getCOSObject();
COSDictionary dictionary = (COSDictionary) fieldDictionary.getDictionaryObject(COSName.AP);
dictionary.setNeedToBeUpdated(true);
COSStream stream = (COSStream) dictionary.getDictionaryObject(COSName.N);
stream.setNeedToBeUpdated(true);
while (fieldDictionary != null)
{
fieldDictionary.setNeedToBeUpdated(true);
fieldDictionary = (COSDictionary) fieldDictionary.getDictionaryObject(COSName.PARENT);
}
// ^^^--- new
}
(FillInFormSaveIncremental method setField)
On the other hand the main code has to be extended to mark a chain from the catalog to the fields array:
PDDocument document = PDDocument.load(...);
PDDocumentCatalog doc = document.getDocumentCatalog();
PDAcroForm Form = doc.getAcroForm();
String formName = "topmostSubform[0].Page1[0].pana_pania[0]";
PDField f = Form.getField(formName);
setField(document, formName, "Artur");
System.out.println("New value 2nd: " + f.getValueAsString());
// vvv--- new
COSDictionary dictionary = document.getDocumentCatalog().getCOSObject();
dictionary.setNeedToBeUpdated(true);
dictionary = (COSDictionary) dictionary.getDictionaryObject(COSName.ACRO_FORM);
dictionary.setNeedToBeUpdated(true);
COSArray array = (COSArray) dictionary.getDictionaryObject(COSName.FIELDS);
array.setNeedToBeUpdated(true);
// ^^^--- new
document.saveIncremental(new FileOutputStream(...));
document.close();
(FillInFormSaveIncremental test testFillInSkierowanie3)
Beware: for use with generic PDFs one obviously should introduce some null tests...
Opening the result file in Adobe Reader one will unfortunately see that the program complains about changes which disable extended features in the file.
This is due to the quirk in PDFBox' incremental saving that it requires some unnecessary objects in the update section. In particular the catalog is saved there which contains a usage rights signature (the technology granting extended features). The re-saved signature obviously is not at its original position in its original revision anymore. Thus, is invalidated.
Most likely the OP OP wanted to save the PDF incrementally to not break this signature but PDFBox does not permit this. Oh well...
Thus, the only thing one can do is prevent the warning by completely removing the signature.
Removing the usage rights signature
We already have retrieved the catalog object in the additions above, so removing the signature is easy:
COSDictionary dictionary = document.getDocumentCatalog().getCOSObject();
// vvv--- new
dictionary.removeItem(COSName.PERMS);
// ^^^--- new
dictionary.setNeedToBeUpdated(true);
(FillInFormSaveIncremental test testFillInSkierowanie3)
Opening the result file in Adobe Reader one will unfortunately see that the program complains about missing extended features in the file to save it.
This is due to the fact that Adobe Reader requires extended features to save changes to XFA forms, extended features we had to remove in this step.
But the document at hand is a hybrid AcroForm & XFA form document, and Adobe Reader requires no extended features to save AcroForm documents. Thus, all we have to do is remove the XFA form. As our code only sets the AcroForm value, this is a good idea anyways...
Removing the XFA form
We already have retrieved the acroform object in the additions above, so removing the XFA form referenced from there is easy:
dictionary = (COSDictionary) dictionary.getDictionaryObject(COSName.ACRO_FORM);
// vvv--- new
dictionary.removeItem(COSName.XFA);
// ^^^--- new
dictionary.setNeedToBeUpdated(true);
(FillInFormSaveIncremental test testFillInSkierowanie3)
Opening the result file in Adobe Reader one will see that one now can without further ado edit the form and save the file.
Beware, a sufficiently new Adobe Reader version is required for this, earlier versions (up to at least version 9) did require extended features even for saving changes to an AcroForm form
Related
We use pdfbox for in one of our applications.
Some pdfs that are overlaid result in "broken" output and fonts.
Below is the sample code I'm using to overlay pdfs.
The pdfs sometimes have different numbers of pages.
We flatten acroforms and set annotations to read-only.
Pdf page rotation and bbox sizing sometimes set differently (especially from scanners) so we try to correct for this.
PDDocument baseDocument = PDDocument.load(new File("base.pdf"));
PDDocument overlayDocument = PDDocument.load(new File("overlay.pdf"));
Iterator<PDPage> baseDocumentIterator = baseDocument.getPages().iterator();
Iterator<PDPage> overlayIterator = overlayDocument.getPages().iterator();
PDDocument finalOverlayDoc = new PDDocument();
while(baseDocumentIterator.hasNext() && overlayIterator.hasNext()) {
PDPage backing = baseDocumentIterator.next();
//locking annotations per page
List<PDAnnotation> annotations = backing.getAnnotations();
for (PDAnnotation a :annotations) {
a.setLocked(true);
a.setReadOnly(true);
}
// setting size so there's no weird overflow issues
PDRectangle rect = new PDRectangle();
rect.setLowerLeftX(0);
rect.setLowerLeftY(0);
rect.setUpperRightX(backing.getBBox().getWidth());
rect.setUpperRightY(backing.getBBox().getHeight());
backing.setCropBox(rect);
backing.setMediaBox(rect);
backing.setBleedBox(rect);
PDPage pg = overlayIterator.next();
//setting rotation if different. Some scanners cause issues.
if(backing.getRotation()!= pg.getRotation())
{
pg.setRotation(-backing.getRotation());
}
finalOverlayDoc.addPage(pg);
}
finalOverlayDoc.close();
//flatten acroform
PDAcroForm acroForm = baseDocument.getDocumentCatalog().getAcroForm();
if (acroForm != null) {
acroForm.flatten();
acroForm.setNeedAppearances(false);
}
Overlay overlay = new Overlay();
overlay.setOverlayPosition(Overlay.Position.FOREGROUND);
overlay.setInputPDF(baseDocument);
overlay.setAllPagesOverlayPDF(finalOverlayDoc);
Map<Integer, String> ovmap = new HashMap<Integer, String>();
overlay.overlay(ovmap);
PDPageTree allOverlayPages = overlayDocument.getPages();
if(baseDocument.getPages().getCount() < overlayDocument.getPages().getCount()) //Additional pages in the overlay pdf need to be appended to the base pdf.
{
for(int i=baseDocument.getPages().getCount();i<allOverlayPages.getCount(); i++)
{
baseDocument.addPage(allOverlayPages.get(i));
}
}
PDDocument finalDocument = new PDDocument();
for(PDPage p: baseDocument.getPages()){
finalDocument.addPage(p);
}
String filename = "examples/merge_pdf_examples/debug.pdf";
filename = filename + new Date().getTime() + ".pdf";
finalDocument.save(filename);
finalDocument.close();
baseDocument.close();
overlayDocument.close();
There is no error in the PDF file you shared relevant for using Overlay.
It uses one PDF feature which is seldom used, though, the pages inherit resources from their parent node: Page objects in a PDF are arranged in a tree with the actual pages being leaves; a page object in this tree often itself carries all the information defining it but a number of page properties can also be carried by an inner node and inherited by descendant pages unless they override them.
After you shared your code it turns out that you have a preparation step which loses all inherited information: When you generate finalOverlayDoc from overlayDocument you essentially do:
while(overlayIterator.hasNext()) {
PDPage pg = overlayIterator.next();
//setting rotation if different. Some scanners cause issues.
finalOverlayDoc.addPage(pg);
}
(OverlayDocuments test testOverlayPreparationExampleBroken)
Here you only transport the page object itself, losing all inherited properties.
For the document at hand you can fix this by explicitly setting the page resources to the inherited ones:
while(overlayIterator.hasNext()) {
PDPage pg = overlayIterator.next();
pg.setResources(pg.getResources());
//setting rotation if different. Some scanners cause issues.
finalOverlayDoc.addPage(pg);
}
(OverlayDocuments test testOverlayPreparationFixedExampleBroken)
Beware, though: This only explicitly sets the page resources but there also are other page attributes which can be inherited.
I would propose, therefore, that you don't create a new PDDocument at all; instead of moving the overlayDocument pages to finalOverlayDoc only change them in place. If overlayDocument has more pages than baseDocument, you additionally have to remove excess pages from overlayDocument. Then use overlayDocument in overlaying instead of finalOverlayDoc.
Looking further down your code I see you repeat the anti-pattern of moving page objects to other documents without respecting inherited properties again and again. I guess you should completely overhaul that code, removing that anti-pattern.
I have written the following small Java main method. It takes in a (hardcoded for testing purposes!) PDF document I know contains active elements in the form and need to flatten it.
public static void main(String [] args) {
try {
// for testing
Tika tika = new Tika();
String filePath = "<path-to>/<pdf-document-with-active-elements>.pdf";
String fileName = filePath.substring(0, filePath.length() -4);
File file = new File(filePath);
if (tika.detect(file).equalsIgnoreCase("application/pdf")) {
PDDocument pdDocument = PDDocument.load(file);
PDAcroForm pdAcroForm = pdDocument.getDocumentCatalog().getAcroForm();
if (pdAcroForm != null) {
pdAcroForm.flatten();
pdAcroForm.refreshAppearances();
pdDocument.save(fileName + "-flattened.pdf");
}
pdDocument.close();
}
}
catch (Exception e) {
System.err.println("Exception: " + e.getLocalizedMessage());
}
}
What kind of test would assert the File(<path-to>/<pdf-document-with-active-elements>-flattened.pdf) generated by this code would, in fact, be flat?
What kind of test would assert that the file generated by this code would, in fact, be flat?
Load that document anew and check whether it has any form fields in its PDAcroForm (if there is a PDAcroForm at all).
If you want to be thorough, also iterate through the pages and assure that there are no Widget annotations associated to them anymore.
And to really be thorough, additionally determine the field positions and contents before flattening and apply text extraction at those positions to the flattened pdf. This verifies that the form has not merely been dropped but indeed flattened.
my PDFBox throws following error: No glyph for U+0054 in font AAAAAD+FreeSerifBold.
I found several similar threads on stackoverflow but I couldn't fix my problem by them.
My code is similar to code example:
public QuoteWorkerPdf() throws IOException {
// Create PDF with one blank page
document = PDDocument.load(
getClass().getResourceAsStream("data/quote_template.pdf"));
page = (PDPage) document.getDocumentCatalog().getPages().get(0);
printable = new PDFPrintable(document);
// get the document catalog
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
// as there might not be an AcroForm entry a null check is necessary
if (acroForm != null)
{
// Retrieve an individual field and set its value.
PDTextField field = (PDTextField) acroForm.getField( "q2_quotationPrepared" );
field.setValue("TextEntry");
// If a field is nested within the form tree a fully qualified name
// might be provided to access the field.
//field = (PDTextField) acroForm.getField( "fieldsContainer.nestedSampleField" );
//field.setValue("Text Entry");
}
// Save and close the filled out form.
document.save("target/FillFormField.pdf");
}
U+0054 is "T" which is the first letter of the string.
For pdf form creation I use www.jotform.com.
Does anybody know how can I solve this?
Stacktrace:
Exception in thread "main" java.lang.IllegalArgumentException: No glyph for U+0054 in font AAAAAD+FreeSerifBold
at org.apache.pdfbox.pdmodel.font.PDCIDFontType2.encode(PDCIDFontType2.java:363)
at org.apache.pdfbox.pdmodel.font.PDType0Font.encode(PDType0Font.java:398)
at org.apache.pdfbox.pdmodel.font.PDFont.encode(PDFont.java:324)
at org.apache.pdfbox.pdmodel.font.PDFont.getStringWidth(PDFont.java:353)
at org.apache.pdfbox.pdmodel.interactive.form.PlainText$Paragraph.getLines(PlainText.java:174)
at org.apache.pdfbox.pdmodel.interactive.form.PlainTextFormatter.format(PlainTextFormatter.java:182)
at org.apache.pdfbox.pdmodel.interactive.form.AppearanceGeneratorHelper.insertGeneratedAppearance(AppearanceGeneratorHelper.java:508)
at org.apache.pdfbox.pdmodel.interactive.form.AppearanceGeneratorHelper.setAppearanceContent(AppearanceGeneratorHelper.java:364)
at org.apache.pdfbox.pdmodel.interactive.form.AppearanceGeneratorHelper.setAppearanceValue(AppearanceGeneratorHelper.java:237)
at org.apache.pdfbox.pdmodel.interactive.form.PDTextField.constructAppearances(PDTextField.java:263)
at org.apache.pdfbox.pdmodel.interactive.form.PDTerminalField.applyChange(PDTerminalField.java:228)
at org.apache.pdfbox.pdmodel.interactive.form.PDTextField.setValue(PDTextField.java:218)
at aaalabel.diefinder.QuoteWorkerPdf.<init>(QuoteWorkerPdf.java:69)
at aaalabel.diefinder.QuoteWorkerPdf.main(QuoteWorkerPdf.java:114)
This code is tailored to your file. It changes the default appearance string to use a different font. See also this answer that is somewhat related but more general.
The problem with your input file is that the font used in the field is subsetted, so it doesn't have all glyphs you would expect.
PDDocument doc = PDDocument.load(new File("82667884384374 (1).pdf"));
PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm();
PDTextField field = (PDTextField) acroForm.getField("q2_quotationPrepared");
COSName helvName = acroForm.getDefaultResources().add(PDType1Font.HELVETICA); // use different font if you want. Do not subset!
field.setDefaultAppearance("/" + helvName.getName() + " 10 Tf 0 g"); // modifies your existing DA string
field.setValue("TextEntry");
doc.save(new File("82667884384374 (1)-new.pdf"));
doc.close();
I am trying to use a template with predefined styles, fonts and fonts size so when I create the new document it has styles, fonts defined in my template.
This is my code:
private static byte[] joinSections(Document document, List<byte[]> list) throws Exception {
if (list != null && list.size() > 0) {
document.removeAllChildren();
for (byte[] item : list) {
ByteArrayInputStream partInStream = new ByteArrayInputStream(item);
Document target = new Document(partInStream);
// Make the document appear straight after the destination documents content.
target.getFirstSection().getPageSetup().setSectionStart(SectionStart.CONTINUOUS);
document.appendDocument(target, ImportFormatMode.USE_DESTINATION_STYLES);
}
ByteArrayOutputStream bs = new ByteArrayOutputStream();
document.save(bs, SaveFormat.DOCX);
return bs.toByteArray();
}
return null;
}
The document is the template where styles are defined.
The list is the list of paragraphs I want to insert into empty templates. The list is populated before calling this function
I have two issues:
In result document, my styles are applied, but font name and font size are inherited from source paragraphs, even I set to use USE_DESTINATION_STYLES. So for example, if my template has Arial 14 for Heading 1, and source document, from where I extracted that paragraph, for Heading 1 have font Times New Roman, in result document it will be Times New Roman? What I am missing here
What ever I do, I am always getting style Normal_0 in my result document. Not sure how to ignore the source and avoid having Normal_0 in my result document?
Thanks
I am trying to split the single PDF into multiple. Like 10 page document into 10 single page document.
PDDocument source = PDDocument.load(input_file);
PDDocument output = new PDDocument();
PDPage page = source.getPages().get(0);
output.addPage(page);
output.save(file);
output.close();
Here the problem is, the new document's page size is different than original document. So some text are cropped or missing in new document. I am using PDFBox 2.0 and how I can avoid this?
UPDATE:
Thanks #mkl.
Splitter did the magic. Here is the updated working part,
public static void extractAndCreateDocument(SplitMeta meta, PDDocument source)
throws IOException {
File file = new File(meta.getFilename());
Splitter splitter = new Splitter();
splitter.setStartPage(meta.getStart());
splitter.setEndPage(meta.getEnd());
splitter.setSplitAtPage(meta.getEnd());
List<PDDocument> docs = splitter.split(source);
if(docs.size() > 0){
PDDocument output = docs.get(0);
output.save(file);
output.close();
}
}
public class SplitMeta {
private String filename;
private int start;
private int end;
public SplitMeta() {
}
}
Unfortunately the OP has not provided a sample document to reproduce the issue. Thus, I have to guess.
I assume that the issue is based in objects not immediately linked to the page object but inherited from its parents.
In that case using PDDocument.addPage is the wrong choice as this method only adds the given page object to the target document page tree without consideration of inherited stuff.
Instead one should use PDDocument.importPage which is documented as:
/**
* This will import and copy the contents from another location. Currently the content stream is stored in a scratch
* file. The scratch file is associated with the document. If you are adding a page to this document from another
* document and want to copy the contents to this document's scratch file then use this method otherwise just use
* the {#link #addPage} method.
*
* Unlike {#link #addPage}, this method does a deep copy. If your page has annotations, and if
* these link to pages not in the target document, then the target document might become huge.
* What you need to do is to delete page references of such annotations. See
* here for how to do this.
*
* #param page The page to import.
* #return The page that was imported.
*
* #throws IOException If there is an error copying the page.
*/
public PDPage importPage(PDPage page) throws IOException
Actually even this method might not suffice as is as it does not consider all inherited attributes, but looking at the Splitter utility class one gets an impression what one has to do:
PDPage imported = getDestinationDocument().importPage(page);
imported.setCropBox(page.getCropBox());
imported.setMediaBox(page.getMediaBox());
// only the resources of the page will be copied
imported.setResources(page.getResources());
imported.setRotation(page.getRotation());
// remove page links to avoid copying not needed resources
processAnnotations(imported);
making use of the helper method
private void processAnnotations(PDPage imported) throws IOException
{
List<PDAnnotation> annotations = imported.getAnnotations();
for (PDAnnotation annotation : annotations)
{
if (annotation instanceof PDAnnotationLink)
{
PDAnnotationLink link = (PDAnnotationLink)annotation;
PDDestination destination = link.getDestination();
if (destination == null && link.getAction() != null)
{
PDAction action = link.getAction();
if (action instanceof PDActionGoTo)
{
destination = ((PDActionGoTo)action).getDestination();
}
}
if (destination instanceof PDPageDestination)
{
// TODO preserve links to pages within the splitted result
((PDPageDestination) destination).setPage(null);
}
}
// TODO preserve links to pages within the splitted result
annotation.setPage(null);
}
}
As you are trying to split the single PDF into multiple, like 10 page document into 10 single page document, you might want to use this Splitter utility class as is.
Tests
To test those methods I used the output of the PDF Clown sample output AnnotationSample.Standard.pdf because that library heavily depends on inheritance of page tree values. Thus, I copied the content of its only page to a new document using either PDDocument.addPage, PDDocument.importPage, or Splitter like this:
PDDocument source = PDDocument.load(resource);
PDDocument output = new PDDocument();
PDPage page = source.getPages().get(0);
output.addPage(page);
output.save(new File(RESULT_FOLDER, "PageAddedFromAnnotationSample.Standard.pdf"));
output.close();
(CopyPages.java test testWithAddPage)
PDDocument source = PDDocument.load(resource);
PDDocument output = new PDDocument();
PDPage page = source.getPages().get(0);
output.importPage(page);
output.save(new File(RESULT_FOLDER, "PageImportedFromAnnotationSample.Standard.pdf"));
output.close();
(CopyPages.java test testWithImportPage)
PDDocument source = PDDocument.load(resource);
Splitter splitter = new Splitter();
List<PDDocument> results = splitter.split(source);
Assert.assertEquals("Expected exactly one result document from splitting a single page document.", 1, results.size());
PDDocument output = results.get(0);
output.save(new File(RESULT_FOLDER, "PageSplitFromAnnotationSample.Standard.pdf"));
output.close();
(CopyPages.java test testWithSplitter)
Only the final test copied the page faithfully.