在我之前的 SO 问题PDFBox 1.8.10: Fill and Sign PDF generated invalid signatures 我解释了我如何未能填写并随后使用 PDFBox 1.8.10 签署 PDF 文档。在得到一些善意的帮助后,我现在继续研究同一主题。从doc_v2.pdf开始(文件链接如下!),我填写并签名,生成 doc_v2_fillsigned.pdf (一次性完成,逐步保存)。我再次打开已编辑的文档(再次使用 PDFBox)并尝试填写另一个字段。
然后保存文档会导致以下堆栈跟踪:
Exception in thread "main" java.lang.NullPointerException
at org.apache.pdfbox.pdmodel.interactive.form.PDAppearance.calculateFontSize(PDAppearance.java:930)
at org.apache.pdfbox.pdmodel.interactive.form.PDAppearance.setAppearanceValue(PDAppearance.java:359)
at org.apache.pdfbox.pdmodel.interactive.form.PDVariableText.setValue(PDVariableText.java:131)
at com.c10n.scalibur.ehealthdemo.examples.PdfEditor.fill(PdfEditor.java:100)
at com.c10n.scalibur.ehealthdemo.examples.SignPdf_ProfileLayer.start(SignPdf_ProfileLayer.java:66)
at com.c10n.scalibur.ehealthdemo.examples.SignPdf_ProfileLayer.main(SignPdf_ProfileLayer.java:28)
我在失败的填充运行中做了什么:
File curentDocument new File("doc_v2_fillsigned.pdf);
File newDocument = new File("doc_v2_fillsigned_filled.pdf);
String fieldName ="New Emergency Contact";
String value="test";
PDDocument doc = null;
try(FileOutputStream fos = new FileOutputStream(newDocument)){
try(FileInputStream fis = new FileInputStream(currentDocument);){
int c;
while ((c = fis.read(buffer)) != -1) {
fos.write(buffer, 0, c);
}
}
doc = PDDocument.load(currentDocument);
PDDocumentCatalog catalog = doc.getDocumentCatalog();
catalog.getCOSObject().setNeedToBeUpdate(true);
catalog.getPages().getCOSObject().setNeedToBeUpdate(true);
PDAcroForm form = catalog.getAcroForm();
form.getCOSObject().setNeedToBeUpdate(true);
form.getDefaultResources().getCOSObject().setNeedToBeUpdate(true);
PDField field = form.getField(fieldName);
field.setValue(value); // here the exception occurs.
// What should happen afterwards:
field.getCOSObject().setNeedToBeUpdate(true);
field.getAcroForm().getCOSObject().setNeedToBeUpdate(true);
((COSDictionary) field.getDictionary().getDictionaryObject("AP")).getDictionaryObject("N").setNeedToBeUpdate(true);
try(FileInputStream fis = new FileInputStream(newDocument)){
doc.saveIncremental(fis, fos);
}
}finally{
if(null != doc){
doc.close();
doc=null;
}
}
文件:
空文件:https ://www.dropbox.com/s/xf5pb0ng8k9zd4i/doc_v2.pdf?dl=0
填充和签名的实例:https ://www.dropbox.com/s/s8295tfyjpe1l4l/doc_v2_fillsigned.pdf?dl=0
同样,欢迎任何帮助解决这个问题!
更新:
mkl 在评论中询问了创建 fillsigned pdf 的代码。到目前为止,我了解到,只有签名就足够了,然后让上面的填充代码失败。所以这里是我的签名代码中的一些例外:
@Override
public byte[] sign(InputStream data) throws SignatureException, IOException {
CMSTypedDataInputStream input = new CMSTypedDataInputStream(data);
try {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(certData);
X509Certificate signCert = (X509Certificate)certFactory.generateCertificate(in);
ContentSigner signer = new MyContentSigner(profile);
SignerInfoGenerator i = new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
.build(signer,signCert);
Store<?> certStore = new JcaCertStore(Collections.singletonList(signCert));
CMSSignedDataGenerator cmsGen = new CMSSignedDataGenerator();
cmsGen.addCertificates(certStore);
cmsGen.addSignerInfoGenerator(i);
CMSSignedData signedData = cmsGen.generate(input);
byte[] result =signedData.getEncoded();
return result;
} catch (Exception e) {
e.printStackTrace();
throw new SignatureException(e);
}
}
这将创建签名,使用 BouncyCastle 1.52,代码从
public void sign(SignatureInterface signer, String signatureFieldName, int pageNumber, String location, String reason, boolean lock) throws IOException, SignatureException{
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); // default filter
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED); // for visible sigs!
signature.setLocation(location);
signature.setReason(reason);
signature.setSignDate(Calendar.getInstance());
SignatureOptions options = makeSignatureVisible(signature,signatureFieldName, pageNumber, lock );
doc.addSignature(signature, signer, options);
}
它使用以下方法,在某个签名字段中生成可见签名,在此处添加图片并正确放置:
SignatureOptions makeSignatureVisible( PDSignature signature, String fieldName, int pageNumber, boolean lock) throws IOException{
PDDocumentCatalog catalog = doc.getDocumentCatalog();
catalog.getCOSObject().setNeedToBeUpdate(true);
catalog.getPages().getCOSObject().setNeedToBeUpdate(true);
PDAcroForm form = catalog.getAcroForm();
form.getCOSObject().setNeedToBeUpdate(true);
form.getDefaultResources().getCOSObject().setNeedToBeUpdate(true);
PDSignatureField field = (PDSignatureField) form.getField(fieldName);
field.setSignature(signature);
field.setReadonly(lock);
FileInputStream image = new FileInputStream("MUniverse_Signature.jpg");
PDVisibleSignDesigner visibleSig = new PDVisibleSignDesigner(newDocument.getName(), image, 1);
PDRectangle area = getFieldArea(field);
float max_width = area.getWidth();
float max_height = area.getHeight();
float scale = 1;
if(max_height < visibleSig.getHeight()){
scale = max_height / visibleSig.getHeight();
System.out.println("scale: "+scale);
}
if(max_width < scale*visibleSig.getWidth()){
scale = max_width / visibleSig.getWidth();
System.out.println("scale: "+scale);
}
float zoom = ((scale-1)*100);
visibleSig.zoom(zoom);
PDPage page = (PDPage) doc.getDocumentCatalog().getAllPages().get(pageNumber);
visibleSig.coordinates(area.getLowerLeftX(),page.getMediaBox().getHeight()-area.getUpperRightY());
visibleSig.signatureFieldName(fieldName);
PDVisibleSigProperties signatureProperties = new PDVisibleSigProperties();
signatureProperties.signerName("name").signerLocation("location").signatureReason("Security")
.visualSignEnabled(true).setPdVisibleSignature(visibleSig).buildSignature();
SignatureOptions options = new SignatureOptions();
options.setVisualSignature(signatureProperties);
return options;
}
我怀疑这些片段不是必需的,并且在尝试用增量保存填充 pdf 时,应用 PDFBox 附带的签名示例会导致相同的冲突。
问候,
丹尼尔