You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache PDFBox生成PDF/A-2b不通过VeraPDF校验的问题求解

解决PDFBox生成PDF/A-2b时VeraPDF校验失败的问题

第一个错误:元数据流不符合要求(Rule 6.6.2.1 test 1)

问题分析

你生成的XMP元数据开头带有UTF-8 BOM(),导致VeraPDF无法正确识别为符合ISO 32000-1要求的元数据流。这是PDFBox的XmpSerializer默认行为——它会在序列化XMP时添加BOM,但PDF/A标准要求XMP元数据应使用无BOM的UTF-8编码。

修复方案

修改XMP序列化逻辑,使用Writer指定无BOM的UTF-8编码输出,同时显式设置元数据流的Subtype为XML:

// 替换原有的XMP序列化代码段
cosStream.setName(COSName.SUBTYPE, COSName.XML);
try (Writer cosXMPWriter = new OutputStreamWriter(cosStream.createOutputStream(), StandardCharsets.UTF_8)) {
    XMPMetadata xmp = XMPMetadata.createXMPMetadata();
    // 原有的XMP schema配置代码保持不变
    new XmpSerializer().serialize(xmp, cosXMPWriter, true);
    document.getDocumentCatalog().setMetadata(new PDMetadata(cosStream));
}

第二个错误:DeviceGray色彩空间使用不符合要求(Rule 6.2.4.3 test 4)

问题分析

PDFBox绘制文本时默认使用DeviceGray色彩空间,但PDF/A-2b规范要求:使用DeviceGray时,必须满足两个条件之一:

  • 已设置设备无关的DefaultGray色彩空间
  • 文档包含PDF/A输出意图(OutputIntent)

你的代码既未配置默认色彩空间,也未添加输出意图,因此校验失败。

修复方案

推荐添加PDF/A输出意图(更符合规范的完整要求),步骤如下:

  1. 获取sRGB ICC配置文件(可从开源渠道获取,或使用系统自带的配置文件)
  2. 在代码中添加输出意图:
// 加载sRGB ICC配置文件
File iccFile = new File("sRGB.icc"); // 替换为你的ICC文件实际路径
try (FileInputStream iccStream = new FileInputStream(iccFile)) {
    PDOutputIntent outputIntent = new PDOutputIntent(document, iccStream);
    outputIntent.setInfo("sRGB IEC61966-2.1");
    outputIntent.setOutputCondition("sRGB IEC61966-2.1");
    outputIntent.setOutputConditionIdentifier("sRGB");
    outputIntent.setRegistryName("http://www.color.org");
    document.getDocumentCatalog().addOutputIntent(outputIntent);
}

若不想使用输出意图,也可以设置设备无关的DefaultGray色彩空间:

PDColorSpace defaultGray = PDICCBased.load(document, new FileInputStream("sRGB.icc"));
document.getDocumentCatalog().setDefaultGray(defaultGray);

修正后的完整代码

import org.apache.pdfbox.pdmodel.*;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
import org.apache.pdfbox.pdmodel.xmp.*;
import com.adobe.xmp.XMPMetadata;
import com.adobe.xmp.impl.XmpSerializer;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;

public class PDFA2bGenerator {
    public static void main(String[] args) throws Exception {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
             PDDocument document = new PDDocument(); 
             COSStream cosStream = new COSStream()) {
            PDPage page = new PDPage();
            document.addPage(page);

            // 设置文档基础信息
            PDDocumentInformation docInfo = new PDDocumentInformation();
            docInfo.setTitle("Name");
            docInfo.setCreator("Creator");
            docInfo.setSubject("Subject");
            document.setDocumentInformation(docInfo);

            // 生成无BOM的XMP元数据
            cosStream.setName(COSName.SUBTYPE, COSName.XML);
            try (Writer xmpWriter = new OutputStreamWriter(cosStream.createOutputStream(), StandardCharsets.UTF_8)) {
                XMPMetadata xmp = XMPMetadata.createXMPMetadata();
                
                PDFAIdentificationSchema pdfaSchema = xmp.createAndAddPFAIdentificationSchema();
                pdfaSchema.setPart(2);
                pdfaSchema.setConformance("B");
                
                DublinCoreSchema dcSchema = xmp.createAndAddDublinCoreSchema();
                dcSchema.setTitle("Name");
                dcSchema.addCreator("Creator");
                dcSchema.setDescription("Subject");
                
                XMPBasicSchema basicSchema = xmp.createAndAddXMPBasicSchema();
                Calendar now = Calendar.getInstance();
                basicSchema.setCreateDate(now);
                basicSchema.setModifyDate(now);
                basicSchema.setMetadataDate(now);
                basicSchema.setCreatorTool("Creator Tool");

                new XmpSerializer().serialize(xmp, xmpWriter, true);
                document.getDocumentCatalog().setMetadata(new PDMetadata(cosStream));
            }

            // 设置查看器偏好
            PDViewerPreferences prefs = new PDViewerPreferences(new COSDictionary());
            prefs.setDisplayDocTitle(true);
            document.getDocumentCatalog().setViewerPreferences(prefs);

            // 添加PDF/A输出意图(解决色彩空间问题)
            File iccFile = new File("sRGB.icc"); // 替换为实际路径
            try (FileInputStream iccStream = new FileInputStream(iccFile)) {
                PDOutputIntent outputIntent = new PDOutputIntent(document, iccStream);
                outputIntent.setInfo("sRGB IEC61966-2.1");
                outputIntent.setOutputCondition("sRGB IEC61966-2.1");
                outputIntent.setOutputConditionIdentifier("sRGB");
                outputIntent.setRegistryName("http://www.color.org");
                document.getDocumentCatalog().addOutputIntent(outputIntent);
            }

            // 加载字体并绘制文本
            File fontFile = new File("C:\\Windows\\Fonts\\arial.ttf");
            PDType0Font font = PDType0Font.load(document, fontFile);

            try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
                contentStream.beginText();
                contentStream.setFont(font, 12);
                contentStream.newLineAtOffset(100, 700);
                contentStream.showText("Hello PDF/A-2b World!");
                contentStream.endText();
            }

            // 保存并校验
            document.save(baos);
            try (PDFAParser parser = Foundries.defaultInstance().createParser(new ByteArrayInputStream(baos.toByteArray()), PDFAFlavour.PDFA_2_B)) {
                PDFAValidator validator = Foundries.defaultInstance().createValidator(PDFAFlavour.PDFA_2_B, false);
                ValidationResult result = validator.validate(parser);
                System.out.println(result.isCompliant()); // 预期输出true
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者SirNight

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 20:44:54