Java pdfbox 1.8.8的视觉签名

Java pdfbox 1.8.8的视觉签名,java,digital-signature,pdfbox,Java,Digital Signature,Pdfbox,我正在尝试生成带有可视签名和pdfbox的PDF。我有两条流,pdfbox似乎只能处理文件。没有三个临时文件,我没能成功。我可以从中看出API已经改变了,但它仍然处理文件 public void signPdf(InputStream originalPdf, OutputStream signedPdf, InputStream image, float x, float y, String name, String location, String reaso

我正在尝试生成带有可视签名和pdfbox的PDF。我有两条流,pdfbox似乎只能处理文件。没有三个临时文件,我没能成功。我可以从中看出API已经改变了,但它仍然处理文件

public void signPdf(InputStream originalPdf, OutputStream signedPdf,
        InputStream image, float x, float y,
        String name, String location, String reason) {

    File temp = null;
    File temp2 = null;
    File scratchFile = null;
    RandomAccessFile randomAccessFile = null;
    OutputStream tempOut = null;
    InputStream tempIn = null;
    try {
        /* Copy original to temporary file */
        temp = File.createTempFile("signed1", ".tmp");
        tempOut = new FileOutputStream(temp);
        copyStream(originalPdf, tempOut);
        tempOut.close();

        /* Read temporary file to second temporary file and stream */
        tempIn = new FileInputStream(temp);
        temp2 = File.createTempFile("signed2", ".tmp");
        tempOut = new FileOutputStream(temp2);
        copyStream(tempIn, tempOut);
        tempIn.close();
        tempIn = new FileInputStream(temp2);

        scratchFile = File.createTempFile("signed3", ".bin");
        randomAccessFile = new RandomAccessFile(scratchFile, "rw");

        /* Read temporary file */
        PDDocument document = PDDocument.load(temp, randomAccessFile);
        document.getCurrentAccessPermission().setCanModify(false);

        PDSignature signature = new PDSignature();
        signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
        signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
        signature.setName(name);
        signature.setLocation(location);
        signature.setReason(reason);
        signature.setSignDate(Calendar.getInstance());

        PDVisibleSignDesigner signatureDesigner = new PDVisibleSignDesigner(
                document, image, document.getNumberOfPages());
        signatureDesigner.xAxis(250).yAxis(60).zoom(-90).signatureFieldName("signature");

        PDVisibleSigProperties signatureProperties = new PDVisibleSigProperties();
        signatureProperties.signerName(name).signerLocation(location)
                .signatureReason(reason).preferredSize(0).page(1)
                .visualSignEnabled(true).setPdVisibleSignature(signatureDesigner)
                .buildSignature();

        SignatureOptions options = new SignatureOptions();
        options.setVisualSignature(signatureProperties);

        document.addSignature(signature, dataSigner, options);

        /* Sign */
        document.saveIncremental(tempIn, tempOut);
        document.close();
        tempIn.close();

        /* Copy temporary file to an output stream */
        tempIn = new FileInputStream(temp2);
        copyStream(tempIn, signedPdf);
    } catch (IOException e) {
        logger.error("PDF signing failure", e);
    } catch (COSVisitorException e) {
        logger.error("PDF creation failure", e);
    } catch (SignatureException e) {
        logger.error("PDF signing failure", e);
    } finally {
        closeStream(originalPdf);
        closeStream(signedPdf);
        closeStream(randomAccessFile);
        closeStream(tempOut);
        deleteTempFile(temp);
        deleteTempFile(temp2);
        deleteTempFile(scratchFile);
    }
}

private void deleteTempFile(File tempFile) {
    if (tempFile != null && tempFile.exists() && !tempFile.delete()) {
        tempFile.deleteOnExit();
    }
}

private void closeStream(Closeable is) {
    if (is!= null) {
        try {
            is.close();
        } catch (IOException e) {
            logger.error("failure", e);
        }
    }
}

private void copyStream(InputStream is, OutputStream os) throws IOException {
    byte[] buffer = new byte[1024];
    int c;
    while ((c = is.read(buffer)) != -1) {
        os.write(buffer, 0, c);
    }
    is.close();
}
除了文件疯狂,我在签名上没有看到任何文字。结果如下所示:

这就是它的样子,当我使用itext库做类似的事情时

为什么视觉签名表示中缺少名称、位置和原因?如何修复此问题?

为什么视觉签名表示中缺少名称、位置和原因? 它们不在那里,因为它们没有被画出来

iText表示可视化签名的默认方式是将这些信息添加到可视化中

PDFBox'
PDVisibleSigBuilder
表示可视化签名的默认方式没有此类信息

两者都不是对的,都只是违约

人们应该寻找此类信息的标准场所毕竟是签名面板

我怎样才能解决这个问题? 签名可视化的实际内容由
PDVisibleSigBuilder
实例在
signatureProperties.buildSignature()期间创建:

因此,通过替换

    signatureProperties.signerName(name).signerLocation(location)
            .signatureReason(reason).preferredSize(0).page(1)
            .visualSignEnabled(true).setPdVisibleSignature(signatureDesigner)
            .buildSignature();
在你的代码中

    signatureProperties.signerName(name).signerLocation(location)
            .signatureReason(reason).preferredSize(0).page(1)
            .visualSignEnabled(true).setPdVisibleSignature(signatureDesigner);

    PDFTemplateBuilder builder = new ExtSigBuilder();
    PDFTemplateCreator creator = new PDFTemplateCreator(builder);
    signatureProperties.setVisibleSignature(creator.buildPDF(signatureProperties.getPdVisibleSignature()));
对于此
PDVisibleSigBuilder
类的自定义版本
ExtSigBuilder
,您可以在那里绘制任何需要的内容,例如:

class ExtSigBuilder extends PDVisibleSigBuilder
{
    String fontName;

    public void createImageForm(PDResources imageFormResources, PDResources innerFormResource,
            PDStream imageFormStream, PDRectangle formrect, AffineTransform affineTransform, PDJpeg img)
            throws IOException
    {
        super.createImageForm(imageFormResources, innerFormResource, imageFormStream, formrect, affineTransform, img);

        PDFont font = PDType1Font.HELVETICA;
        fontName = getStructure().getImageForm().getResources().addFont(font);

        logger.info("Added font to image form: " + fontName);
    }

    public void injectAppearanceStreams(PDStream holderFormStream, PDStream innterFormStream, PDStream imageFormStream,
            String imageObjectName, String imageName, String innerFormName, PDVisibleSignDesigner properties)
            throws IOException
    {
        super.injectAppearanceStreams(holderFormStream, innterFormStream, imageFormStream, imageObjectName, imageName, innerFormName, properties);

        String imgFormComment = "q " + 100 + " 0 0 50 0 0 cm /" + imageName + " Do Q\n";
        String text = "BT /" + fontName + " 10 Tf (Hello) Tj ET\n";
        appendRawCommands(getStructure().getImageFormStream().createOutputStream(), imgFormComment + text);

        logger.info("Added text commands to image form: " + text);
    }
}
在Helvetica中,在图像表单(表单实际显示某些内容)的左下角上方以10的大小写入“Hello”


PS:在我看来,这背后的面向对象的结构应该彻底改变。

在签名者身上显示文本有时看起来不太好。对于我来说,我根据要显示的文本创建新图像。并将其与签名图像合并。我可以为签名图像添加背景(公司名称水印)

以下是代码,用于创建带有文本和背景的新签字人图像:

public class ImageSignatory {

public static void main(String[] args) {
    DateFormat df = new SimpleDateFormat("MM.dd.yyyy");
    Date today = Calendar.getInstance().getTime();
    String reportDate = df.format(today);
    String text = "Signature eletronic Company AA DUC NGUYEN  - Date " + reportDate;
    String background = "background.png";
    String signImage = "sign.jpg";
    String textImage = createImageFromText(text);
    String imageResultURL = null ;
    try {

        String mergedImage = mergeTwoImage(signImage, textImage);
        imageResultURL = addBackgroundToImage(background, mergedImage);
    } catch (Exception ex) {

    }

}

public static String StringDivider(String s) {

    StringBuilder sb = new StringBuilder(s);
    int i = 0;
    while ((i = sb.indexOf(" ", i + 30)) != -1) {
        sb.replace(i, i + 1, "\n");
    }
    return sb.toString();

}

public static BufferedImage toBufferedImage(Image img) {
    if (img instanceof BufferedImage) {
        return (BufferedImage) img;
    }
    BufferedImage bimage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
    Graphics2D bGr = bimage.createGraphics();
    bGr.drawImage(img, 0, 0, null);
    bGr.dispose();
    return bimage;
}

public static String addBackgroundToImage(String backgroundPATH, String imagePath) {
    try {
        String imageResult = "result.jpg";
        Image backgroundImage = ImageIO.read(new File(backgroundPATH));
        int width = backgroundImage.getWidth(null);
        int height = backgroundImage.getHeight(null);

        Image signAndText = ImageIO.read(new File(imagePath));
        signAndText = signAndText.getScaledInstance(width, height, Image.SCALE_SMOOTH);
        signAndText = toBufferedImage(signAndText);

        BufferedImage combined = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics g2 = combined.getGraphics();
        g2.drawImage(backgroundImage, 0, 0, null);
        g2.drawImage(signAndText, 0, 0, null);
        g2.dispose();
        ImageIO.write(combined, "JPG", new File(imageResult));
        return imageResult;
    } catch (IOException ex) {
        return null;
    }
}

public static String mergeTwoImage(String first, String second) {
    try {
        String tempFileName = "merged_image.png";
        Image signatoryImage = ImageIO.read(new File(first));
        Image addtionalTextImage = ImageIO.read(new File(second));
        float ratio = (float) signatoryImage.getWidth(null) / (float) addtionalTextImage.getWidth(null);
        addtionalTextImage = addtionalTextImage.getScaledInstance((int) (ratio * (float) addtionalTextImage.getWidth(null)), (int) (ratio * (float) addtionalTextImage.getHeight(null)), Image.SCALE_SMOOTH);
        addtionalTextImage = toBufferedImage(addtionalTextImage);
        BufferedImage combinedTemp = new BufferedImage(signatoryImage.getWidth(null), signatoryImage.getHeight(null) + (int) (ratio * (float) addtionalTextImage.getHeight(null)), BufferedImage.TYPE_INT_ARGB);

        Graphics g = combinedTemp.getGraphics();
        g.drawImage(signatoryImage, 0, 0, null);
        g.drawImage(addtionalTextImage, 0, signatoryImage.getHeight(null), null);
        g.dispose();
        ImageIO.write(combinedTemp, "PNG", new File(tempFileName));
        return tempFileName;
    } catch (IOException ex) {
        return null;
    }
}

public static String createImageFromText(String text) {
    String tempFileName = "text.png";
    text = StringDivider(text);
    String[] textParts = text.split("\n");

    BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2d = img.createGraphics();
    Font font = new Font("Arial", Font.PLAIN, 48);
    g2d.setFont(font);
    FontMetrics fm = g2d.getFontMetrics();
    int width = 0;
    for (String textPart : textParts) {
        int tempWidth = fm.stringWidth(textPart);
        if (tempWidth > width) {
            width = tempWidth;
        }
    }
    width += 10;
    int oneLineHeight = fm.getHeight();
    int height = (oneLineHeight) * textParts.length;
    g2d.dispose();

    img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    g2d = img.createGraphics();
    g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
    g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
    g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
    g2d.setFont(font);
    fm = g2d.getFontMetrics();
    g2d.setColor(Color.BLACK);
    int index = 0;
    for (String textPart : textParts) {
        g2d.drawString(textPart, 5, (oneLineHeight) * index + fm.getAscent());
        index++;
    }

    g2d.dispose();
    try {
        ImageIO.write(img, "PNG", new File(tempFileName));
    } catch (IOException ex) {
        return null;
    }
    return tempFileName;
}

 public static void removeFile(String fileName) {
    try {
        File file = new File(fileName);
        file.delete();
    } catch (Exception ex) {

    }
}

}

我可以看出itext和pdfbox生成的PDF命令有区别,但我不是PDF格式专家。还没有。因此,我将感谢任何帮助。您应该在
PDVisibleSignDesigner.zoom()
javadoc中说,图像被缩放到原始图像的
(100-N)%
,其中
N
是该方法的一个参数。它实际上应该是
(100+N)%
。很难说,但从源头上看,它的想法似乎是覆盖
PDVisibleSigProperties.buildSignature()
。否则,
PDVisibleSigProperties.signerName()
PDVisibleSigProperties.signerLocation()
PDVisibleSigProperties.signatureReason()
没有任何意义,也没有在任何地方使用。
PDVisibleSigProperties#signerName
和其他提到的属性甚至在PDFBox中都没有使用(从版本1.8.10开始)。这些属性在PDF签名字段值中有相应的条目;如果实施得当,可以在Adobe的签名面板中看到。
public class ImageSignatory {

public static void main(String[] args) {
    DateFormat df = new SimpleDateFormat("MM.dd.yyyy");
    Date today = Calendar.getInstance().getTime();
    String reportDate = df.format(today);
    String text = "Signature eletronic Company AA DUC NGUYEN  - Date " + reportDate;
    String background = "background.png";
    String signImage = "sign.jpg";
    String textImage = createImageFromText(text);
    String imageResultURL = null ;
    try {

        String mergedImage = mergeTwoImage(signImage, textImage);
        imageResultURL = addBackgroundToImage(background, mergedImage);
    } catch (Exception ex) {

    }

}

public static String StringDivider(String s) {

    StringBuilder sb = new StringBuilder(s);
    int i = 0;
    while ((i = sb.indexOf(" ", i + 30)) != -1) {
        sb.replace(i, i + 1, "\n");
    }
    return sb.toString();

}

public static BufferedImage toBufferedImage(Image img) {
    if (img instanceof BufferedImage) {
        return (BufferedImage) img;
    }
    BufferedImage bimage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
    Graphics2D bGr = bimage.createGraphics();
    bGr.drawImage(img, 0, 0, null);
    bGr.dispose();
    return bimage;
}

public static String addBackgroundToImage(String backgroundPATH, String imagePath) {
    try {
        String imageResult = "result.jpg";
        Image backgroundImage = ImageIO.read(new File(backgroundPATH));
        int width = backgroundImage.getWidth(null);
        int height = backgroundImage.getHeight(null);

        Image signAndText = ImageIO.read(new File(imagePath));
        signAndText = signAndText.getScaledInstance(width, height, Image.SCALE_SMOOTH);
        signAndText = toBufferedImage(signAndText);

        BufferedImage combined = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics g2 = combined.getGraphics();
        g2.drawImage(backgroundImage, 0, 0, null);
        g2.drawImage(signAndText, 0, 0, null);
        g2.dispose();
        ImageIO.write(combined, "JPG", new File(imageResult));
        return imageResult;
    } catch (IOException ex) {
        return null;
    }
}

public static String mergeTwoImage(String first, String second) {
    try {
        String tempFileName = "merged_image.png";
        Image signatoryImage = ImageIO.read(new File(first));
        Image addtionalTextImage = ImageIO.read(new File(second));
        float ratio = (float) signatoryImage.getWidth(null) / (float) addtionalTextImage.getWidth(null);
        addtionalTextImage = addtionalTextImage.getScaledInstance((int) (ratio * (float) addtionalTextImage.getWidth(null)), (int) (ratio * (float) addtionalTextImage.getHeight(null)), Image.SCALE_SMOOTH);
        addtionalTextImage = toBufferedImage(addtionalTextImage);
        BufferedImage combinedTemp = new BufferedImage(signatoryImage.getWidth(null), signatoryImage.getHeight(null) + (int) (ratio * (float) addtionalTextImage.getHeight(null)), BufferedImage.TYPE_INT_ARGB);

        Graphics g = combinedTemp.getGraphics();
        g.drawImage(signatoryImage, 0, 0, null);
        g.drawImage(addtionalTextImage, 0, signatoryImage.getHeight(null), null);
        g.dispose();
        ImageIO.write(combinedTemp, "PNG", new File(tempFileName));
        return tempFileName;
    } catch (IOException ex) {
        return null;
    }
}

public static String createImageFromText(String text) {
    String tempFileName = "text.png";
    text = StringDivider(text);
    String[] textParts = text.split("\n");

    BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2d = img.createGraphics();
    Font font = new Font("Arial", Font.PLAIN, 48);
    g2d.setFont(font);
    FontMetrics fm = g2d.getFontMetrics();
    int width = 0;
    for (String textPart : textParts) {
        int tempWidth = fm.stringWidth(textPart);
        if (tempWidth > width) {
            width = tempWidth;
        }
    }
    width += 10;
    int oneLineHeight = fm.getHeight();
    int height = (oneLineHeight) * textParts.length;
    g2d.dispose();

    img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    g2d = img.createGraphics();
    g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
    g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
    g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
    g2d.setFont(font);
    fm = g2d.getFontMetrics();
    g2d.setColor(Color.BLACK);
    int index = 0;
    for (String textPart : textParts) {
        g2d.drawString(textPart, 5, (oneLineHeight) * index + fm.getAscent());
        index++;
    }

    g2d.dispose();
    try {
        ImageIO.write(img, "PNG", new File(tempFileName));
    } catch (IOException ex) {
        return null;
    }
    return tempFileName;
}

 public static void removeFile(String fileName) {
    try {
        File file = new File(fileName);
        file.delete();
    } catch (Exception ex) {

    }
}