我正在尝试从PDF / A-1A输入文件创建签名的PDF,输出必须保持一致性级别。
必须使用自定义外观添加签名。
如果我沿着下面的代码行进行操作,那么一切都在签名方面起作用,签名正确显示并验证确定。
但PDF / A一致性被不包含所需的toUnicode CMAP的嵌入字体打破。
PdfADocument pdf = ... the doc to be signed
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
PdfReader reader = pdf.getReader();
PrivateKey privateKey = ...
Provider signatureProvider = new BouncyCastleProvider();
Certificate[] signChain = ...
PdfSigner pdfSigner = new PdfSigner(reader, buffer, true);
PdfSignatureAppearance signatureAppearance = pdfSigner.getSignatureAppearance();
signatureAppearance.setReuseAppearance(false);
signatureAppearance.setPageNumber(pdf.getNumberOfPages());
pdfSigner.setFieldName("Custom Signature");
float margin = 35;
Rectangle pageSize = pdf.getLastPage().getMediaBox();
Rectangle signaturePosition = new Rectangle(pageSize.getLeft()+margin,
pageSize.getBottom()+margin,
pageSize.getWidth()-2*margin,
(pageSize.getHeight()-2*margin)/3);
// need to do this before creating any *Canvas object, else the pageRect will be null and the signature invisible
signatureAppearance.setPageRect(signaturePosition);
PdfFont regularFont = PdfFontFactory.createFont("/path/to/truetypefile-regular.ttf", "ISO-8859-1", true);
PdfFont boldFont = PdfFontFactory.createFont("/path/to/truetypefile-bold.ttf", "ISO-8859-1", true);
int fontSize = 10;
PdfFormXObject n0 = signatureAppearance.getLayer0();
PdfCanvas n0Canvas = new PdfCanvas(n0, pdfSigner.getDocument());
PdfFormXObject n2 = signatureAppearance.getLayer2();
Canvas n2Canvas = new Canvas(n2, pdfSigner.getDocument());
if(regularFont != null) {
n2Canvas.setFont(regularFont);
n0Canvas.setFontAndSize(regularFont, fontSize);
}
ImageData imageData = ImageDataFactory.create("/path/to/image.png");
Image image = new Image(imageData);
n2Canvas.add(image);
String layer2Text = ... some lines of text containing newlines and some simple markdown
String[] paragraphs = layer2text.split("\n\n");
for (String text : paragraphs) {
boolean bold = false;
if(text.startsWith("[bold]")) {
bold = true;
text = text.replaceFirst("^\\s*\\[bold\\]\\s*", "");
}
Paragraph p = new Paragraph(text);
p.setFontSize(fontSize);
if(bold) {
p.setFont(boldFont);
}
n2Canvas.add(p);
}
... pdfSigner.setCertificationLevel(PdfSigner.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS);
PrivateKeySignature externalSignature = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA512, signatureProvider.getName());
BouncyCastleDigest externalDigest = new BouncyCastleDigest();
pdfSigner.signDetached(externalDigest, externalSignature, signChain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
所以我认为那里缺少一些东西。嵌入的字体不符合PDF / A,因为它们错过了ToUnicode CMAP密钥。 pdf-tools验证器的另一个错误是: "密钥编码的值是差异,但必须是WinAnsiEncoding或MacRomanEncoding。"这似乎是同样的问题。
签名本身是可以正常的,造型和图像应该是适当的。它只是看起来不行的字体。
答案 0 :(得分:1)
违反PDF / A合规性的触发器是在此处创建字体的方式
PdfFont regularFont = PdfFontFactory.createFont("/path/to/truetypefile-regular.ttf", "ISO-8859-1", true);
PdfFont boldFont = PdfFontFactory.createFont("/path/to/truetypefile-bold.ttf", "ISO-8859-1", true);
或更具体地说,其中使用的编码参数"ISO-8859-1"
。
PDF / A-1规范要求:
6.3.7字符编码
所有非符号TrueType字体都应指定 MacRomanEncoding 或 WinAnsiEncoding 作为 字体词典中的编码条目。所有符号TrueType字体都不应在其中指定编码条目 字体字典及其字体程序' “cmap”表应包含一个编码。
使用编码参数"ISO-8859-1"
导致 MacRomanEncoding 和 WinAnsiEncoding 都没有被指定为
字体词典中的编码条目。相反,该值是仅包含差异条目的字典,其中包含显式映射。
根据PDF / A验证器的不同,这可能会导致不同的错误消息。
对于(我假设)历史原因,在字体创建过程中有一些不同的编码参数值会导致iText使用 WinAnsiEncoding :
""
PdfEncodings.WINANSI
(== "Cp1252"
)"winansi"
(不区分大小写)"winansiencoding"
(不区分大小写) OP使用PdfName.WinAnsiEncoding.getValue()
返回与最新选项匹配的字符串。
虽然这表明iText可用于正确签署PDF / A文档,但可能应引入特定PDFASigner
类强制执行一致性。