【问题标题】:Using fonts in custom signatureAppearance signatures with iText7 breaks PDF/A conformance?在带有 iText7 的自定义 signatureAppearance 签名中使用字体会破坏 PDF/A 的一致性?
【发布时间】:2016-11-30 19:11:23
【问题描述】:

我正在尝试从 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 验证器的另一个错误说: “键 Encoding 的值是 Difference,但必须是 WinAnsiEncoding 或 MacRomanEncoding。”这似乎是同样的问题。

签名本身还可以而且很明显,样式和图像应该是正确的。就是字体好像不太对。

【问题讨论】:

  • 你能分享一个样本签名的PDF进行分析吗?您指出了几个可能会产生影响的省略号,您签署的原始 PDF 也可能会产生影响。
  • 输入文件:SignatureTestDocument-sig.pdf,签名文件:SignatureTestDocument-sig-certification.pdf。请注意,输入文件已由另一个应用程序(嵌入了 Albany 字体)签名。 BAAAAA+LiberationSerif-Bold 和 CAAAAA+LiberationSerif 由生产者 LibreOffice 嵌入。
  • 确实,您提供的签名文档中的 PDF/A-1a 一致性存在某些缺陷。不过,我已经看到在 iText 开发代码中实现了许多修复和改进。因此,我将尝试将您的代码与当前的 7.0.1-SNAPSHOT 一起使用,看看情况是否有所改善。
  • 您可能想尝试将PdfEncodings.WINANSI 用作字体编码而不是“ISO-8859-1”。
  • @mkl 嘿,谢谢,太酷了……虽然 iText7 中不存在 PdfEncodings.WINANSI,但 PdfName.WinAnsiEncoding.getValue() 可以解决问题!有关生成的文件,请参阅 SignatureTestDocument-sig-certification-winansi.pdf。 +1

标签: java pdf itext itext7


【解决方案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 作为 编码 字体字典中的条目。所有符号 TrueType 字体不得在 字体字典及其字体程序的“cmap”表应包含一种编码。

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

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


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

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

OP 使用了PdfName.WinAnsiEncoding.getValue(),它返回一个匹配最新选项的字符串。


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

【讨论】:

  • 从 itext7 7.1.3 版本开始,PdfSigner 构造函数检查 PDF 是否声明了 PdfAConformanceLevel。如果是这样,它会创建一个内部 PdfADocument 而不是 PdfDocument。
猜你喜欢
  • 1970-01-01
  • 2016-03-08
  • 1970-01-01
  • 2018-09-05
  • 2022-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多