2017-06-01 79 views
3

Digital text with text and background image我想數字簽名PDF文件使用Java中的PDFBox與可見文本出現在類似於在Acrobat中手動創建時創建的頁面上。如圖所示(一個只有快照我正在尋找,另一個也有數字簽名的細節),這個例子顯示了使用圖像文件進行簽名。怎麼做?在Java中使用PDFBox,如何創建可見的數字簽名與文本

Digital signature with text on PDF

+0

你有任何代碼可以告訴我們嗎?你看到的任何錯誤?請閱讀[詢問](https://stackoverflow.com/help/asking),看看如何提出關於SO的好問題。 –

+0

您是否看過源代碼下載中的CreateVisibleSignature.java示例? –

+0

我已經提到了CreateVisibleSignature.java,它使用圖像,並且沒有與簽名者相關的文本被打印在頁面上,但是,我想打印文本,類似於我附加的屏幕截圖。其中acrobat圖像在具有水印效果的背景中。 – adi

回答

2

此代碼將包含在即將到來的2.0.9版本PDFBox的的樣本中。另請參閱PDFBOX-3198中的討論。它更加靈活,可以包含文本和圖像,或者只包含其中的一種,或矢量圖形,無論您想要什麼。

/** 
* This is a second example for visual signing a pdf. It doesn't use the "design pattern" influenced 
* PDVisibleSignDesigner, and doesn't create its complex multilevel forms described in the Adobe 
* document 
* <a href="https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PPKAppearances.pdf">Digital 
* Signature Appearances</a>, because this isn't required by the PDF specification. See the 
* discussion in December 2017 in PDFBOX-3198. 
* 
* @author Vakhtang Koroghlishvili 
* @author Tilman Hausherr 
*/ 
public class CreateVisibleSignature2 extends CreateSignatureBase 
{ 
    private SignatureOptions signatureOptions; 
    private boolean lateExternalSigning = false; 
    private File imageFile; 

    /** 
    * Initialize the signature creator with a keystore (pkcs12) and pin that 
    * should be used for the signature. 
    * 
    * @param keystore is a pkcs12 keystore. 
    * @param pin is the pin for the keystore/private key 
    * @throws KeyStoreException if the keystore has not been initialized (loaded) 
    * @throws NoSuchAlgorithmException if the algorithm for recovering the key cannot be found 
    * @throws UnrecoverableKeyException if the given password is wrong 
    * @throws CertificateException if the certificate is not valid as signing time 
    * @throws IOException if no certificate could be found 
    */ 
    public CreateVisibleSignature2(KeyStore keystore, char[] pin) 
      throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, IOException, CertificateException 
    { 
     super(keystore, pin); 
    } 

    public File getImageFile() 
    { 
     return imageFile; 
    } 

    public void setImageFile(File imageFile) 
    { 
     this.imageFile = imageFile; 
    } 

    public boolean isLateExternalSigning() 
    { 
     return lateExternalSigning; 
    } 

    /** 
    * Set late external signing. Enable this if you want to activate the demo code where the 
    * signature is kept and added in an extra step without using PDFBox methods. This is disabled 
    * by default. 
    * 
    * @param lateExternalSigning 
    */ 
    public void setLateExternalSigning(boolean lateExternalSigning) 
    { 
     this.lateExternalSigning = lateExternalSigning; 
    } 

    /** 
    * Sign pdf file and create new file that ends with "_signed.pdf". 
    * 
    * @param inputFile The source pdf document file. 
    * @param signedFile The file to be signed. 
    * @param humanRect rectangle from a human viewpoint (coordinates start at top left) 
    * @param tsaUrl optional TSA url 
    * @throws IOException 
    */ 
    public void signPDF(File inputFile, File signedFile, Rectangle2D humanRect, String tsaUrl) throws IOException 
    { 
     this.signPDF(inputFile, signedFile, humanRect, tsaUrl, null); 
    } 

    /** 
    * Sign pdf file and create new file that ends with "_signed.pdf". 
    * 
    * @param inputFile The source pdf document file. 
    * @param signedFile The file to be signed. 
    * @param humanRect rectangle from a human viewpoint (coordinates start at top left) 
    * @param tsaUrl optional TSA url 
    * @param signatureFieldName optional name of an existing (unsigned) signature field 
    * @throws IOException 
    */ 
    public void signPDF(File inputFile, File signedFile, Rectangle2D humanRect, String tsaUrl, String signatureFieldName) throws IOException 
    { 
     if (inputFile == null || !inputFile.exists()) 
     { 
      throw new IOException("Document for signing does not exist"); 
     } 

     setTsaUrl(tsaUrl); 

     // creating output document and prepare the IO streams. 
     FileOutputStream fos = new FileOutputStream(signedFile); 

     try (PDDocument doc = PDDocument.load(inputFile)) 
     { 
      int accessPermissions = SigUtils.getMDPPermission(doc); 
      if (accessPermissions == 1) 
      { 
       throw new IllegalStateException("No changes to the document are permitted due to DocMDP transform parameters dictionary"); 
      } 
      // Note that PDFBox has a bug that visual signing on certified files with permission 2 
      // doesn't work properly, see PDFBOX-3699. As long as this issue is open, you may want to 
      // be careful with such files. 

      PDSignature signature = null; 
      PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(); 
      PDRectangle rect = null; 

      // sign a PDF with an existing empty signature, as created by the CreateEmptySignatureForm example. 
      if (acroForm != null) 
      { 
       signature = findExistingSignature(acroForm, signatureFieldName); 
       if (signature != null) 
       { 
        rect = acroForm.getField(signatureFieldName).getWidgets().get(0).getRectangle(); 
       } 
      } 

      if (signature == null) 
      { 
       // create signature dictionary 
       signature = new PDSignature(); 
      } 

      if (rect == null) 
      { 
       rect = createSignatureRectangle(doc, humanRect); 
      } 

      // Optional: certify 
      // can be done only if version is at least 1.5 and if not already set 
      // doing this on a PDF/A-1b file fails validation by Adobe preflight (PDFBOX-3821) 
      // PDF/A-1b requires PDF version 1.4 max, so don't increase the version on such files. 
      if (doc.getVersion() >= 1.5f && accessPermissions == 0) 
      { 
       SigUtils.setMDPPermission(doc, signature, 2); 
      } 

      if (acroForm != null && acroForm.getNeedAppearances()) 
      { 
       // PDFBOX-3738 NeedAppearances true results in visible signature becoming invisible 
       // with Adobe Reader 
       if (acroForm.getFields().isEmpty()) 
       { 
        // we can safely delete it if there are no fields 
        acroForm.getCOSObject().removeItem(COSName.NEED_APPEARANCES); 
        // note that if you've set MDP permissions, the removal of this item 
        // may result in Adobe Reader claiming that the document has been changed. 
        // and/or that field content won't be displayed properly. 
        // ==> decide what you prefer and adjust your code accordingly. 
       } 
       else 
       { 
        System.out.println("/NeedAppearances is set, signature may be ignored by Adobe Reader"); 
       } 
      } 

      // default filter 
      signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); 

      // subfilter for basic and PAdES Part 2 signatures 
      signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED); 

      signature.setName("Name"); 
      signature.setLocation("Location"); 
      signature.setReason("Reason"); 

      // the signing date, needed for valid signature 
      signature.setSignDate(Calendar.getInstance()); 

      // do not set SignatureInterface instance, if external signing used 
      SignatureInterface signatureInterface = isExternalSigning() ? null : this; 

      // register signature dictionary and sign interface 
      signatureOptions = new SignatureOptions(); 
      signatureOptions.setVisualSignature(createVisualSignatureTemplate(doc, 0, rect)); 
      signatureOptions.setPage(0); 
      doc.addSignature(signature, signatureInterface, signatureOptions); 

      if (isExternalSigning()) 
      { 
       System.out.println("Signing externally " + signedFile.getName()); 
       ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fos); 
       // invoke external signature service 
       byte[] cmsSignature = sign(externalSigning.getContent()); 

       // Explanation of late external signing (off by default): 
       // If you want to add the signature in a separate step, then set an empty byte array 
       // and call signature.getByteRange() and remember the offset signature.getByteRange()[1]+1. 
       // you can write the ascii hex signature at a later time even if you don't have this 
       // PDDocument object anymore, with classic java file random access methods. 
       // If you can't remember the offset value from ByteRange because your context has changed, 
       // then open the file with PDFBox, find the field with findExistingSignature() or 
       // PODDocument.getLastSignatureDictionary() and get the ByteRange from there. 
       // Close the file and then write the signature as explained earlier in this comment. 
       if (isLateExternalSigning()) 
       { 
        // this saves the file with a 0 signature 
        externalSigning.setSignature(new byte[0]); 

        // remember the offset (add 1 because of "<") 
        int offset = signature.getByteRange()[1] + 1; 

        // now write the signature at the correct offset without any PDFBox methods 
        try (RandomAccessFile raf = new RandomAccessFile(signedFile, "rw")) 
        { 
         raf.seek(offset); 
         raf.write(Hex.getBytes(cmsSignature)); 
        } 
       } 
       else 
       { 
        // set signature bytes received from the service and save the file 
        externalSigning.setSignature(cmsSignature); 
       } 
      } 
      else 
      { 
       // write incremental (only for signing purpose) 
       doc.saveIncremental(fos); 
      } 
     } 

     // Do not close signatureOptions before saving, because some COSStream objects within 
     // are transferred to the signed document. 
     // Do not allow signatureOptions get out of scope before saving, because then the COSDocument 
     // in signature options might by closed by gc, which would close COSStream objects prematurely. 
     // See https://issues.apache.org/jira/browse/PDFBOX-3743 
     IOUtils.closeQuietly(signatureOptions); 
    } 

    private PDRectangle createSignatureRectangle(PDDocument doc, Rectangle2D humanRect) 
    { 
     float x = (float) humanRect.getX(); 
     float y = (float) humanRect.getY(); 
     float width = (float) humanRect.getWidth(); 
     float height = (float) humanRect.getHeight(); 
     PDPage page = doc.getPage(0); 
     PDRectangle pageRect = page.getCropBox(); 
     PDRectangle rect = new PDRectangle(); 
     // signing should be at the same position regardless of page rotation. 
     switch (page.getRotation()) 
     { 
      case 90: 
       rect.setLowerLeftY(x); 
       rect.setUpperRightY(x + width); 
       rect.setLowerLeftX(y); 
       rect.setUpperRightX(y + height); 
       break; 
      case 180: 
       rect.setUpperRightX(pageRect.getWidth() - x); 
       rect.setLowerLeftX(pageRect.getWidth() - x - width); 
       rect.setLowerLeftY(y); 
       rect.setUpperRightY(y + height); 
       break; 
      case 270: 
       rect.setLowerLeftY(pageRect.getHeight() - x - width); 
       rect.setUpperRightY(pageRect.getHeight() - x); 
       rect.setLowerLeftX(pageRect.getWidth() - y - height); 
       rect.setUpperRightX(pageRect.getWidth() - y); 
       break; 
      case 0: 
      default: 
       rect.setLowerLeftX(x); 
       rect.setUpperRightX(x + width); 
       rect.setLowerLeftY(pageRect.getHeight() - y - height); 
       rect.setUpperRightY(pageRect.getHeight() - y); 
       break; 
     } 
     return rect; 
    } 

    // create a template PDF document with empty signature and return it as a stream. 
    private InputStream createVisualSignatureTemplate(PDDocument srcDoc, int pageNum, PDRectangle rect) throws IOException 
    { 
     try (PDDocument doc = new PDDocument()) 
     { 
      PDPage page = new PDPage(srcDoc.getPage(pageNum).getMediaBox()); 
      doc.addPage(page); 
      PDAcroForm acroForm = new PDAcroForm(doc); 
      doc.getDocumentCatalog().setAcroForm(acroForm); 
      PDSignatureField signatureField = new PDSignatureField(acroForm); 
      PDAnnotationWidget widget = signatureField.getWidgets().get(0); 
      List<PDField> acroFormFields = acroForm.getFields(); 
      acroForm.setSignaturesExist(true); 
      acroForm.setAppendOnly(true); 
      acroForm.getCOSObject().setDirect(true); 
      acroFormFields.add(signatureField); 

      widget.setRectangle(rect); 

      // from PDVisualSigBuilder.createHolderForm() 
      PDStream stream = new PDStream(doc); 
      PDFormXObject form = new PDFormXObject(stream); 
      PDResources res = new PDResources(); 
      form.setResources(res); 
      form.setFormType(1); 
      PDRectangle bbox = new PDRectangle(rect.getWidth(), rect.getHeight()); 
      float height = bbox.getHeight(); 
      Matrix initialScale = null; 
      switch (srcDoc.getPage(pageNum).getRotation()) 
      { 
       case 90: 
        form.setMatrix(AffineTransform.getQuadrantRotateInstance(1)); 
        initialScale = Matrix.getScaleInstance(bbox.getWidth()/bbox.getHeight(), bbox.getHeight()/bbox.getWidth()); 
        height = bbox.getWidth(); 
        break; 
       case 180: 
        form.setMatrix(AffineTransform.getQuadrantRotateInstance(2)); 
        break; 
       case 270: 
        form.setMatrix(AffineTransform.getQuadrantRotateInstance(3)); 
        initialScale = Matrix.getScaleInstance(bbox.getWidth()/bbox.getHeight(), bbox.getHeight()/bbox.getWidth()); 
        height = bbox.getWidth(); 
        break; 
       case 0: 
       default: 
        break; 
      } 
      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(doc, appearanceStream)) 
      { 
       // for 90° and 270° scale ratio of width/height 
       // not really sure about this 
       // why does scale have no effect when done in the form matrix??? 
       if (initialScale != null) 
       { 
        cs.transform(initialScale); 
       } 

       // show background (just for debugging, to see the rect size + position) 
       cs.setNonStrokingColor(Color.yellow); 
       cs.addRect(-5000, -5000, 10000, 10000); 
       cs.fill(); 

       // show background image 
       // save and restore graphics if the image is too large and needs to be scaled 
       cs.saveGraphicsState(); 
       cs.transform(Matrix.getScaleInstance(0.25f, 0.25f)); 
       PDImageXObject img = PDImageXObject.createFromFileByExtension(imageFile, doc); 
       cs.drawImage(img, 0, 0); 
       cs.restoreGraphicsState(); 

       // show text 
       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 very wide line 1)"); 
       cs.newLine(); 
       cs.showText("(Signature very wide line 2)"); 
       cs.newLine(); 
       cs.showText("(Signature very wide line 3)"); 
       cs.endText(); 
      } 

      // no need to set annotations and /P entry 

      ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
      doc.save(baos); 
      return new ByteArrayInputStream(baos.toByteArray()); 
     } 
    } 

    // Find an existing signature (assumed to be empty). You will usually not need this. 
    private PDSignature findExistingSignature(PDAcroForm acroForm, String sigFieldName) 
    { 
     PDSignature signature = null; 
     PDSignatureField signatureField; 
     if (acroForm != null) 
     { 
      signatureField = (PDSignatureField) acroForm.getField(sigFieldName); 
      if (signatureField != null) 
      { 
       // retrieve signature dictionary 
       signature = signatureField.getSignature(); 
       if (signature == null) 
       { 
        signature = new PDSignature(); 
        // after solving PDFBOX-3524 
        // signatureField.setValue(signature) 
        // until then: 
        signatureField.getCOSObject().setItem(COSName.V, signature); 
       } 
       else 
       { 
        throw new IllegalStateException("The signature field " + sigFieldName + " is already signed."); 
       } 
      } 
     } 
     return signature; 
    } 

    /** 
    * Arguments are 
    * [0] key store 
    * [1] pin 
    * [2] document that will be signed 
    * [3] image of visible signature 
    * 
    * @param args 
    * @throws java.security.KeyStoreException 
    * @throws java.security.cert.CertificateException 
    * @throws java.io.IOException 
    * @throws java.security.NoSuchAlgorithmException 
    * @throws java.security.UnrecoverableKeyException 
    */ 
    public static void main(String[] args) throws KeyStoreException, CertificateException, 
      IOException, NoSuchAlgorithmException, UnrecoverableKeyException 
    { 
     // generate with 
     // keytool -storepass 123456 -storetype PKCS12 -keystore file.p12 -genkey -alias client -keyalg RSA 
     if (args.length < 4) 
     { 
      usage(); 
      System.exit(1); 
     } 

     String tsaUrl = null; 
     // External signing is needed if you are using an external signing service, e.g. to sign 
     // several files at once. 
     boolean externalSig = false; 
     for (int i = 0; i < args.length; i++) 
     { 
      if (args[i].equals("-tsa")) 
      { 
       i++; 
       if (i >= args.length) 
       { 
        usage(); 
        System.exit(1); 
       } 
       tsaUrl = args[i]; 
      } 
      if (args[i].equals("-e")) 
      { 
       externalSig = true; 
      } 
     } 

     File ksFile = new File(args[0]); 
     KeyStore keystore = KeyStore.getInstance("PKCS12"); 
     char[] pin = args[1].toCharArray(); 
     keystore.load(new FileInputStream(ksFile), pin); 

     File documentFile = new File(args[2]); 

     CreateVisibleSignature2 signing = new CreateVisibleSignature2(keystore, pin.clone()); 

     signing.setImageFile(new File(args[3])); 

     File signedDocumentFile; 
     String name = documentFile.getName(); 
     String substring = name.substring(0, name.lastIndexOf('.')); 
     signedDocumentFile = new File(documentFile.getParent(), substring + "_signed.pdf"); 

     signing.setExternalSigning(externalSig); 

     // Set the signature rectangle 
     // Although PDF coordinates start from the bottom, humans start from the top. 
     // So a human would want to position a signature (x,y) units from the 
     // top left of the displayed page, and the field has a horizontal width and a vertical height 
     // regardless of page rotation. 
     Rectangle2D humanRect = new Rectangle2D.Float(100, 200, 150, 50); 

     signing.signPDF(documentFile, signedDocumentFile, humanRect, tsaUrl, "Signature1"); 
    } 

    /** 
    * This will print the usage for this program. 
    */ 
    private static void usage() 
    { 
     System.err.println("Usage: java " + CreateVisibleSignature2.class.getName() 
       + " <pkcs12-keystore-file> <pin> <input-pdf> <sign-image>\n" + "" + 
          "options:\n" + 
          " -tsa <url> sign timestamp using the given TSA server\n"+ 
          " -e   sign using external signature creation scenario"); 
    } 

} 
+0

比前面的例子好得多。我只是還想知道是否很難讓用戶在* current *文檔中創建一個'PDAppearanceStream',要簽名的文檔,並將其用作簽名外觀。這可能是一個很好的功能,可以從另一個文檔導入簽名外觀,但*必須以這種方式看起來有點麻煩,特別是如果您想共享文檔中已有的資源,或者如果您想包含一些標記信息... – mkl

+0

@mkl是的,我意識到這一點。我也不喜歡它。我討厭任何複雜性。但是現在,在我對addSignature()進行重大更改或添加一個新方法(例如,)之前,我更願意等待這些代碼的反饋(希望能夠消除複雜性)。 addDirectSignature()。 –

相關問題