在带有 iText7 的自定义签名外观签名中使用字体会破坏 PDF/A 一致性?

Using fonts in custom signatureAppearance signatures with iText7 breaks PDF/A conformance?

我正在尝试从 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 验证器的另一个错误说: "The value of the key Encoding is Difference but must be WinAnsiEncoding or MacRomanEncoding." 这好像是同一个问题。

签名本身很好,很明显,样式和图像都应有的位置。就是字体好像不太行

违反 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 Character encodings

All non-symbolic TrueType fonts shall specify MacRomanEncoding or WinAnsiEncoding as the value of the Encoding entry in the font dictionary. All symbolic TrueType fonts shall not specify an Encoding entry in the font dictionary, and their font programs' “cmap” tables shall contain exactly one encoding.

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

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


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

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

OP 使用 PdfName.WinAnsiEncoding.getValue() 其中 returns 匹配最新选项的字符串。


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