当前位置: 首页 > 知识库问答 >
问题:

使用pdfbox 2.0.12 java进行多重设计?

姜钧
2023-03-14

我试图在压模上的单个pdf中添加多个签名。我可以添加多个母模。在我的一个案例中,我得到了错误

至少有一个签名无效。还需要使所有签名有效。

我想在一个PDF中添加多个有效符号。请帮帮我。在图中,只有一个符号是有效的,其他符号是无效的,所以让我看看我做错了什么

下面是我的代码快照

public void getSignOnPdf(Map<Integer, byte[]> PdfSigneture1, List<Long> documentIds, List<String> calTimeStamp,
        String originalPdfReadServerPath, String tickImagePath, int serverTime, int pageNumberToInsertStamp,
        String name, String location, String reasonForSign, int xCo_ordinates, int yCo_ordinates,
        int signatureWidth, int signatureHeight, String pdfPassword, String internal_outputFinalPdfPath)
        throws Exception {
    String pdfReadServerPath = null;
    String l_slash = new String();
    String originalPDFPath = new String(originalPdfReadServerPath.trim());
    
    boolean isCorrectPDFOutputPath = false;
    String aspOutputPdfServerPath = null;
    synchronized (this) {
        if ((internal_outputFinalPdfPath != null) && (!internal_outputFinalPdfPath.trim().isEmpty())) {
            System.out.println("[" + EsignCommonFuntion.generateTimeStampForLog()
                    + "] :1-->  outputFinalPdfPath is: " + internal_outputFinalPdfPath);
            if (!(new File(internal_outputFinalPdfPath)).isFile()) {
                isCorrectPDFOutputPath = true;
                aspOutputPdfServerPath = internal_outputFinalPdfPath;
            } else {
                System.out.println("1--> Please provide directory path for outputFinalPdfPath: "
                        .concat(String.valueOf(internal_outputFinalPdfPath)));
            }
        } else {
            System.out.println(" 1--> outputFinalPdfPath is empty or null: "
                    .concat(String.valueOf(internal_outputFinalPdfPath)));
        }
    }
    boolean isPasswordPresent = false;
    String pdfPasswordForEncryption;
    synchronized (this) {
        if ((pdfPassword != null) && (!pdfPassword.trim().isEmpty())) {
            pdfPasswordForEncryption = pdfPassword.trim();
            isPasswordPresent = true;
        } else {
            pdfPasswordForEncryption = null;
        }
        String pdfOriginalName = (new File(originalPDFPath)).getName();
        String pdfAbsolutePath = originalPDFPath.substring(0, originalPDFPath.lastIndexOf(l_slash));
        if (isPasswordPresent) {
            pdfAbsolutePath = getEncryptedPdfName(originalPDFPath, pdfAbsolutePath + l_slash,
                    pdfPasswordForEncryption, pdfOriginalName);
            pdfReadServerPath = new String(pdfAbsolutePath);
        } else {
            pdfReadServerPath = originalPDFPath;
        }
    }
    ArrayList<String> unSignedFilesList = new ArrayList<String>();

    Map<Integer, byte[]> l_PdfSigneture = PdfSigneture1;

    int actual_pageNumForStamp = 1;

    String pdfFileName = (new File(pdfReadServerPath)).getName();

    FileOutputStream fos = null;

    String nameToShowInSignature = name;
    String locationToShowInSignature = location;
    String reasonForSignatureSign = reasonForSign;

    PDDocument documentFinal = null;
    try {
        pdfReadServerPath = pdfReadServerPath.substring(0, pdfReadServerPath.lastIndexOf(l_slash));
        System.out.println("inside getSignOnMethod pdfAbsolutePath:".concat(String.valueOf(pdfReadServerPath)));
        unSignedFilesList.add(pdfFileName);
        System.out.println("inside getSignOnMethod pdfFileName:".concat(String.valueOf(pdfFileName)));
        
        String PDFpath = pdfReadServerPath + l_slash + (String) (unSignedFilesList).get(0);

        System.out.println("Inside for PDFpath: ".concat(String.valueOf(PDFpath)));

        String finalOutputPdfName = ((String) (unSignedFilesList).get(0)).substring(0,
                ((String) (unSignedFilesList).get(0)).lastIndexOf(".")) + "_signedFinal.pdf";

        File outFile2 = null;

        if (isCorrectPDFOutputPath) {
            System.out.println("if condition Final signed PDF ouptut Path: " + aspOutputPdfServerPath + l_slash
                    + finalOutputPdfName);
            outFile2 = new File(aspOutputPdfServerPath + l_slash + finalOutputPdfName);
            fos = new FileOutputStream(outFile2);
        } else {
            outFile2 = new File(pdfReadServerPath + l_slash + outFile2);
            fos = new FileOutputStream(outFile2);
        }

        documentFinal = PDDocument.load(new File(PDFpath));
            
        for (int i = 1; i < 4; i++) {
            FileInputStream image2 = new FileInputStream(tickImagePath);
            
            PDSignature pdsignature = new PDSignature();
            pdsignature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
            pdsignature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);

            Calendar cal = GregorianCalendar.getInstance();
            SimpleDateFormat l_simpleDateFormater = new SimpleDateFormat("yyyyMMdd_HHmmss");
            String timeStamp = (String) calTimeStamp.get(i - 1);

            try {
                cal.setTime(l_simpleDateFormater.parse(timeStamp));
            } catch (ParseException ex) {
                ex.printStackTrace();
            }

            cal.add(12, serverTime);
            pdsignature.setSignDate(cal);
            documentFinal.setDocumentId((Long) documentIds.get(i - 1));

            String dateToShowInSignature = cal.getTime().toString();
            
            Float saveIncrementalObj1 = null;
            saveIncrementalObj1 = new Float((float) xCo_ordinates, (float) yCo_ordinates, (float) signatureWidth,
                    (float) signatureHeight);

            PDRectangle rect = getPDRectangle(documentFinal, saveIncrementalObj1, i);
            PDVisibleSignDesigner visibleSig;
            (visibleSig = new PDVisibleSignDesigner(documentFinal, image2, i)).xAxis(xCo_ordinates)
                    .yAxis(yCo_ordinates).zoom(-95.0F).signatureFieldName("signature");
            
            PDVisibleSigProperties visibleSignatureProp = new PDVisibleSigProperties();
            
            visibleSignatureProp.signerName("name").signerLocation("location").signatureReason("Security")
                    .preferredSize(0).page(i - 1).visualSignEnabled(true).setPdVisibleSignature(visibleSig)
                    .buildSignature();
            try {
                PdfSigneture = new TreeMap<>();
                // PdfSigneture.clear();
                PdfSigneture = l_PdfSigneture;
                
                if (visibleSignatureProp.isVisualSignEnabled()) {
                    this.options = new SignatureOptions();
                    this.options.setVisualSignature(visibleSignatureProp);
                    this.options.setPage(visibleSignatureProp.getPage());
                    this.options.setVisualSignature(
                            getInputStream(documentFinal, i, rect, tickImagePath, nameToShowInSignature,
                                    locationToShowInSignature, dateToShowInSignature, reasonForSignatureSign));
                    documentFinal.addSignature(pdsignature, this, this.options);
                } else {
                    documentFinal.addSignature(pdsignature, this);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        synchronized (this) {
            SaveIncrementalSignObject saveIncrementalSignObject;
            (saveIncrementalSignObject = new SaveIncrementalSignObject()).setFos(fos);
            saveIncrementalSignObject.setPDDocumentFromFile(documentFinal);
            
            saveIncrementalForSign(saveIncrementalSignObject);
        }
    } catch (Exception localException2) {
        System.out.println("Insidemethod -- Exception block" + localException2.getMessage());
        return;
    } finally {
        fos.flush();
        if (fos != null) {
            fos.close();
        }
        documentFinal.close();
    }
}

public static synchronized void saveIncrementalForSign(SaveIncrementalSignObject p_SaveIncrementalObj) {
    PDDocument documentFinal = null;
    try {
        (documentFinal = p_SaveIncrementalObj.getPDDocumentFromFile())
                .saveIncremental(p_SaveIncrementalObj.getFos());
    } catch (Exception e) {
        e.printStackTrace();
        try {
        //              documentFinal.close();
            return;
        } catch (Exception eX) {
            eX.printStackTrace();
            return;
        }
    }
}

共有1个答案

双弘益
2023-03-14

在评论中,您澄清了您想要实现的目标:

我试图将一个签名应用到多个地方。

正如下面第一节所讨论的,这并不是您的代码所做的:您的代码试图在单个修订版中的每个地方应用多个签名,这是不可能的,这里也解释了这一点。

另一方面,PDF规范团队不希望在单个修订版中将单个签名应用于多个位置,并且一些实现这一点的方法已被规范无效,但正如下面第二部分所解释的那样,这是可能的。

您似乎试图在一次传递中应用多个签名:

if (isPasswordPresent) {
    documentFinal = PDDocument.load(new File(PDFpath), pdfPasswordForEncryption);
} else {
    documentFinal = PDDocument.load(new File(PDFpath));
}

for (int i = 1; i < 4; i++) {
    FileInputStream image2 = new FileInputStream(tickImagePath);

    PDSignature pdsignature = new PDSignature();

    [...]

    try {
        [...]

        if (visibleSignatureProp.isVisualSignEnabled()) {
            [...]
            documentFinal.addSignature(pdsignature, this, this.options);
        } else {
            documentFinal.addSignature(pdsignature, this);
        }
    } catch (Exception e) {
        System.out.println("Inside getSignOnPdf sub exception block at addSignature:" + e + "error :" + e.getMessage());
        e.printStackTrace();
    }
}

synchronized (this) {
    [...]
    saveIncrementalForSign(saveIncrementalSignObject);
}

这是行不通的。

在PDF中,在单独的PDF版本中一个接一个地应用多个签名,而不是在同一版本中并行应用:

您可以在这个答案中找到一些背景和从那里引用的文档。

因此,在伪代码中,您必须做的是:

for (int i = 1; i < 4; i++) {
    load current version of the PDF;
    apply the i'th signature;
    save and sign as new current version of the PDF;
}

方法名称<代码>PDDocument。addSignature在这里可能有点误导,因为它可能被认为意味着可能会添加多个签名。事实并非如此;所有签名都将使用其小部件创建为签名字段,但实际上只有最后添加的PDSignature的字段才会被签名,因此只有最后添加的签名字段才会具有合理的值。

@Tilman-可能应该在PDDocument中进行测试。如果加载文档后已添加签名,则addSignature会引发异常

PDF对象从PDF页面上的签名可视化到实际签名(基于CMS的子过滤器中的CMS签名容器)的路径不是即时的。相反,我们有

  • PDF页面,在其注释中引用

为了执行你的实际任务,

将一个签名应用于多个位置,

因此,从具有签名外观的多个页面到单个签名容器似乎有许多选项:

  1. 具有签名可视化的所有页面都指向单个签名字段的同一个小部件注释,值字典包含签名容器

现在让我们看看PDF规范ISO 32000-2。首先,它警告不要使用具有多个可视化的单个签名:

签名在文件中的位置可能影响其法律含义。[…]

如果签名与多个位置相关联,则含义可能会变得模糊不清。

(ISO 32000-2,第12.7.5.5节“签名字段”)

因此,规范试图禁止具有多个可视化的单个签名:

给定的注释字典只能从一页的Annots数组中引用。

(ISO 32000-2,第12.5.2节“注释词典”)

这禁止上述备选案文1。

签名字段不得引用多个注释

(ISO 32000-2,第12.7.5.5节“签名字段”)

这禁止了选项2。

显然,选项3并没有被明确禁止。对于通用表单字段,值对象共享甚至被明确允许,因为表单字段值是可继承的!

因此,严格来说,使用选项3可以创建具有多种可视化效果的签名。

不过,请注意,PDF规范团队显然无意允许他们这样做,很可能是疏忽。因此,您必须考虑到一些即将到来的规范勘误表最终也会禁止选项3。

如果您仍然想尝试,应该可以使用选项3的方法调整或修补PDFBox以创建具有多个可视化的单个签名。

例如,iText,cf.这个答案已经被证明是可能的。

此外,您共享的示例文档使用了此选项。

事实证明,使用PDFBox沿着选项3创建多可视化PDF签名非常容易。尤其是,这比使用iText执行此操作要容易,请参阅上面提到的答案,因为这里的签名值字典是一个用户自己创建和处理的对象,而在iText中,它是在后台创建的,并且是及时的。

人们所要做的就是创建一个PDSignature对象并正常地用它生成一个签名(使用PDDocument.addSignature),然后添加任意数量的其他签名字段,将这些字段的签名值属性设置为在开始时创建的单个PDSignature对象。

E、 g.您可以使用这样的方法添加其他签名字段:

void addSignatureField(PDDocument pdDocument, PDPage pdPage, PDRectangle rectangle, PDSignature signature) throws IOException {
    PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
    List<PDField> acroFormFields = acroForm.getFields();

    PDSignatureField signatureField = new PDSignatureField(acroForm);
    signatureField.setSignature(signature);
    PDAnnotationWidget widget = signatureField.getWidgets().get(0);
    acroFormFields.add(signatureField);

    widget.setRectangle(rectangle);
    widget.setPage(pdPage);

    // from PDVisualSigBuilder.createHolderForm()
    PDStream stream = new PDStream(pdDocument);
    PDFormXObject form = new PDFormXObject(stream);
    PDResources res = new PDResources();
    form.setResources(res);
    form.setFormType(1);
    PDRectangle bbox = new PDRectangle(rectangle.getWidth(), rectangle.getHeight());
    float height = bbox.getHeight();

    form.setBBox(bbox);
    PDFont font = PDType1Font.HELVETICA_BOLD;

    // from PDVisualSigBuilder.createAppearanceDictionary()
    PDAppearanceDictionary appearance = new PDAppearanceDictionary();
    appearance.getCOSObject().setDirect(true);
    PDAppearanceStream appearanceStream = new PDAppearanceStream(form.getCOSObject());
    appearance.setNormalAppearance(appearanceStream);
    widget.setAppearance(appearance);

    try (PDPageContentStream cs = new PDPageContentStream(pdDocument, appearanceStream))
    {
        // show background (just for debugging, to see the rect size + position)
        cs.setNonStrokingColor(Color.yellow);
        cs.addRect(-5000, -5000, 10000, 10000);
        cs.fill();

        float fontSize = 10;
        float leading = fontSize * 1.5f;
        cs.beginText();
        cs.setFont(font, fontSize);
        cs.setNonStrokingColor(Color.black);
        cs.newLineAtOffset(fontSize, height - leading);
        cs.setLeading(leading);
        cs.showText("Signature text");
        cs.newLine();
        cs.showText("some additional Information");
        cs.newLine();
        cs.showText("let's keep talking");
        cs.endText();
    }

    pdPage.getAnnotations().add(widget);
    
    COSDictionary pageTreeObject = pdPage.getCOSObject(); 
    while (pageTreeObject != null) {
        pageTreeObject.setNeedToBeUpdated(true);
        pageTreeObject = (COSDictionary) pageTreeObject.getDictionaryObject(COSName.PARENT);
    }
}

(CreateMultipleVisualizations助手方法)

(此方法实际上基于pdfbox examples工件中的CreateVisibleSignature2.createVisualSignatureTemplate方法,但经过了严格简化,现在用于创建实际签名字段,而不仅仅是复制模板。)

像这样使用

try (   InputStream resource = PDF_SOURCE_STREAM;
        OutputStream result = PDF_TARGET_STREAM;
        PDDocument pdDocument = PDDocument.load(resource)   )
{
    PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
    if (acroForm == null) {
        pdDocument.getDocumentCatalog().setAcroForm(acroForm = new PDAcroForm(pdDocument));
    }
    acroForm.setSignaturesExist(true);
    acroForm.setAppendOnly(true);
    acroForm.getCOSObject().setDirect(true);

    PDRectangle rectangle = new PDRectangle(100, 600, 300, 100);
    PDSignature signature = new PDSignature();
    signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
    signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
    signature.setName("Example User");
    signature.setLocation("Los Angeles, CA");
    signature.setReason("Testing");
    signature.setSignDate(Calendar.getInstance());
    pdDocument.addSignature(signature, this);

    for (PDPage pdPage : pdDocument.getPages()) {
        addSignatureField(pdDocument, pdPage, rectangle, signature);
    }

    pdDocument.saveIncremental(result);
}

(CreateMultipleVisualizations testtestCreateSignatureSusMultipleVisualizations

我们检索一个PDF,在结果文档的每个页面上都有一个签名可视化(还有一个额外的不可见的,因为我有点懒),但只有一个实际的签名值(假设此使用byte[]sign(InputStream)方法实现SignatureInterface)。

不过要小心:

  • PDFBox 3.0.0-SNAPSHOT中已弃用PDSignatureField方法设置签名。您可能最终不得不使用更低级的技术注入PDSignature对象
  • PDF规范团队不需要这种多重可视化签名。有可能它们最终会被禁止
 类似资料:
  • Python 3.4和Pandas 0.15.0 df是一个数据帧,col1是一列。使用下面的代码,我正在检查值10的存在,并将这些值替换为1000。 这是另一个例子。这一次,我将根据索引更改col2中的值。 这两种情况都会产生以下警告: 最后 这将产生一个类似的警告,并附带一个建议: 我不确定我是否理解警告中指出的讨论。编写这三行代码的更好方法是什么? 请注意,这些操作是有效的。

  • 我在重新分解旧代码时遇到了一些麻烦,这是学校的作业,所以...我打算使用工厂设计模式或策略,但我不确定如何在这个特定的场景中使用它们: 我一直在继续,因为我有很多if-else语句,所以我正在考虑使用上面的模式。帮助

  • 如何用vscode选择多行? 在Visual Studio上,您可以按左alt键并选择多行,但它不适用于vscode。

  • 我已经阅读了一些关于如何模拟或模拟HttpClient的文档,但是我不能成功地为我的单元测试复制。到目前为止,我还不能接口HttpClient,所以我选择注入HttpMessageHandler,其想法是设置它,以便返回对客户机的任何调用的响应。

  • Exception:二进制XML文件第30行:二进制XML文件第30行:inflating类Android.support.design.widget.textInputLayout由:Android.view.filflateException:二进制XML文件第30行:inflating类Android.support.design.widget.textInputLayout由:java.l