I fill in a PDF form with PDFBox which I flatten before saving it. The form has a custom font for text and also form fields. When I open the output document (with flattened fields) on a device which does not have this custom font installed, the font of normal text is still correct, but the font of the flattened fields is displayed with a fallback (?) font. On a device which does have installed this custom font, everything looks as expected.
Is there a way to force using the same custom font for all text after flattening the form?
Code (simplified) used for filling in the PDF form with PDFBox:
public class App
{
public static void main(String[] args) throws IOException {
String formTemplate = "src/main/resources/fonts.pdf";
String filledForm = "src/main/resources/fonts_out.pdf";
PDDocument pdfDocument = PDDocument.load(new File(formTemplate));
PDAcroForm acroForm = pdfDocument.getDocumentCatalog().getAcroForm();
acroForm.getField("text").setValue("Same font in form text field (updated with PDFBox)");
acroForm.setNeedAppearances(true);
acroForm.refreshAppearances();
acroForm.flatten();
pdfDocument.save(filledForm);
pdfDocument.close();
}
}
Expected:
Result when font is not installed on system:
Some observations for your PDF (the afore mentioned encoding problems are none - just plain ignorance on my behalf):
The SansDroid font is not embedded into the PDF. This is fixed by replacing the F2
font with the newly embedded F5
font.
The NeedAppearances
flag is set meaning that there is no appearance for the form fields. Any reader must (re)create those. This is not done automatically by PDFBox before flattening so I added this part
To not cause any more warnings about missing fonts I removed the F2 font completely.
I run the original PDF through preflight and it gave me the following warning: "The required key /Subtype is missing. Path: ->Pages->Kids->[0]->Annots->[0]->AP->N " The key does exists however it seems to inicate that there is an error with the appearance of the form field. If I remove the /N dict the error is gone. The stream is "/Tx BMC EMC" - maybe there is some EOL missing? But since the appearance is regenerated anyway the error is gone afterwards.
With the following code the DroidSans font is embedded into the PDF:
File pdf = new File("Fonts.pdf");
final PDDocument document = PDDocument.load(pdf);
FileInputStream fontFile = new FileInputStream(new File("DroidSans.ttf"));
PDFont font = PDType0Font.load(document, fontFile, false);
//1. embedd and register the font (Catalog dict)
PDAcroForm pDAcroForm = document.getDocumentCatalog().getAcroForm();
//create a new font resource
PDResources res = pDAcroForm.getDefaultResources();
if (res == null) res = new PDResources();
COSName fontName = res.add(font);
pDAcroForm.setDefaultResources(res);
//2. Now change the font of form field to the newly added font
PDField field = pDAcroForm.getField("text");
//field.setValue("Same font in form text field (updated with PDFBox)");
COSDictionary dict = field.getCOSObject();
COSString defaultAppearance = (COSString) dict.getDictionaryObject(COSName.DA);
if (defaultAppearance != null){
String currentValue = dict.getString(COSName.DA);
//replace the font - this should be improved with a more general version
dict.setString(COSName.DA,currentValue.replace("F2", fontName.getName()));
//remove F2 completely
COSDictionary resources = res.getCOSObject();
for(Entry<COSName, COSBase> resource : resources.entrySet()) {
if(resource.getKey().equals(COSName.FONT)) {
COSObject fonts = (COSObject)resource.getValue();
COSDictionary fontDict = (COSDictionary)fonts.getObject();
COSName toBeRemoved=null;
for(Entry<COSName, COSBase> item : fontDict.entrySet()) {
if(item.getKey().getName().equals("F2")) {
toBeRemoved = item.getKey();
}
}
if(toBeRemoved!=null) {
fontDict.removeItem(toBeRemoved);
}
}
}
if(pDAcroForm.getNeedAppearances()) {
pDAcroForm.refreshAppearances();
pDAcroForm.setNeedAppearances(false);
}
//Flatten the document
pDAcroForm.flatten();
//Save the document
document.save("Form-Test-Result.pdf");
document.close();
Please note that the above code is quite static - Searching and replacing a font called F2
only works for the supplied PDF in other cases it won't. You have to implement a more generic solution for that...