3

我正在尝试从 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。” 这似乎是同样的问题。

签名本身还可以而且很明显,样式和图像应该是适当的。只是字体似乎不太好。

4

1 回答 1

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 字体都应指定MacRomanEncodingWinAnsiEncoding作为字体字典中 Encoding条目的值。所有符号 TrueType 字体都不应在字体字典中指定Encoding条目,并且它们的字体程序的“cmap”表应仅包含一种编码。

encoding 参数的使用"ISO-8859-1"导致MacRomanEncodingWinAnsiEncoding都没有被指定为 字体字典中Encoding条目的值。相反,该值是一个字典,其中仅包含一个包含显式映射的差异条目。

根据 PDF/A 验证器,这可能会导致不同的错误消息。


由于(我假设)历史原因,在字体创建期间有一些不同的编码参数值导致 iText 使用WinAnsiEncoding

  • ""
  • PdfEncodings.WINANSI( == "Cp1252")
  • "winansi"(不区分大小写)
  • "winansiencoding"(不区分大小写)

使用的 OPPdfName.WinAnsiEncoding.getValue()返回与最新选项匹配的字符串。


虽然这表明 iText 可用于正确签署 PDF/A 文档,但PDFASigner可能应该引入一个强制一致性的特定类。

于 2016-08-04T10:05:35.137 回答