Java pdfbox 1.8.8的视觉签名
我正在尝试生成带有可视签名和pdfbox的PDF。我有两条流,pdfbox似乎只能处理文件。没有三个临时文件,我没能成功。我可以从中看出API已经改变了,但它仍然处理文件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
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) {
}
}