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

如何确定PDF的PreferredSignatureSize并最小化签名后文件体积

问题1:正确设置SignatureOptions.PreferredSignatureSize

核心原理

这个参数的作用是在PDF中预留给签名数据的字节空间,设置过小会导致签名数据无法写入(抛出空间不足异常),设置过大则会凭空增加PDF体积。注意:签名数据的大小和原PDF文档体积无关,主要取决于你的证书链长度、签名算法(如RSA密钥长度)以及签名属性的多少,你之前观察到的“大文档适用、小文档不适用”本质是小文档预留过大空间的浪费更明显。

正确设置方法

方法1:动态计算实际所需空间

先模拟生成签名数据,获取其真实长度,再预留10%-20%的缓冲空间,避免因签名属性微小变化导致空间不足:

// 在签名接口外先模拟生成签名数据,计算实际大小
byte[] dummyContent = new byte[100]; // 用任意内容模拟,不影响计算结果
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
X509Certificate cert = (X509Certificate) certificateChain[0];
gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));

JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256withRSA");
contentSignerBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME);

gen.addSignerInfoGenerator(
    new JcaSignerInfoGeneratorBuilder(
        new JcaDigestCalculatorProviderBuilder()
            .setProvider(BouncyCastleProvider.PROVIDER_NAME)
            .build())
        .build(contentSignerBuilder.build(privateKey), cert));

CMSProcessableByteArray msg = new CMSProcessableByteArray(dummyContent);
CMSSignedData signedData = gen.generate(msg, true);
int actualSignatureSize = signedData.getEncoded().length;
int preferredSize = actualSignatureSize + (actualSignatureSize / 5); // 预留20%缓冲

然后将代码中signatureOptions.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 1000);替换为signatureOptions.setPreferredSignatureSize(preferredSize);。

方法2:保守固定值设置

如果不想提前计算,可以先设置DEFAULT_SIGNATURE_SIZE * 2的固定值,若出现空间不足异常,再逐步增大。但推荐方法1,能精准控制预留空间,减少体积浪费。


问题2:最小化签名后的PDF体积

1. 优化可见签名图片

你代码中生成的PNG图片是体积膨胀的主要原因之一,可做以下优化:

  • 改用JPEG格式(无需完全透明时,设置白色背景):
// 生成不透明背景的图片,改用JPEG保存
BufferedImage image = new BufferedImage(150, 50, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, 150, 50);
// 后续文字绘制逻辑不变,适当缩小字体适配尺寸
g2d.setFont(new Font("Arial", Font.PLAIN, 8));
g2d.drawString("Signed by: " + signatureName, 10, 20);
g2d.drawString("Location: " + signatureLocation, 10, 35);
g2d.dispose();
  • 压缩JPEG质量:保存时设置压缩参数,在清晰度和体积间平衡:
tempImageFile = File.createTempFile("signature", ".jpg");
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(0.7f); // 0.7的质量足够清晰,体积可减少60%以上
try (FileOutputStream fos = new FileOutputStream(tempImageFile)) {
    writer.setOutput(ImageIO.createImageOutputStream(fos));
    writer.write(null, new IIOImage(image, null, null), param);
}
writer.dispose();

2. 精准设置PreferredSignatureSize

如问题1所述,避免预留过大的空白空间,这能直接减少PDF中无效的占位字节。

3. 优化签名数据

  • 精简证书链:如果证书链中包含普遍信任的根证书,可以只添加用户证书和中间证书,减少签名数据体积;
  • 精简签名属性:默认的messageDigest和signingTime属性已满足需求,不要添加多余的自定义属性。

4. 保留增量保存

你的代码已经使用document.saveIncremental(baos),这是正确的——增量保存只添加签名相关数据,不会重写整个PDF文档,比全量保存(save())体积小很多,务必保留。


整合优化后的关键代码片段

// 生成压缩后的JPEG签名图片
BufferedImage image = new BufferedImage(150, 50, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, 150, 50);
g2d.setColor(Color.BLACK);
g2d.setFont(new Font("Arial", Font.PLAIN, 8));
g2d.drawString("Signed by: " + signatureName, 10, 20);
g2d.drawString("Location: " + signatureLocation, 10, 35);
g2d.dispose();

// 保存带压缩的JPEG图片
tempImageFile = File.createTempFile("signature", ".jpg");
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(0.7f);
try (FileOutputStream fos = new FileOutputStream(tempImageFile)) {
    writer.setOutput(ImageIO.createImageOutputStream(fos));
    writer.write(null, new IIOImage(image, null, null), param);
}
writer.dispose();

// 动态计算签名所需空间
byte[] dummyContent = new byte[100];
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
X509Certificate cert = (X509Certificate) certificateChain[0];
gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));
JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256withRSA");
contentSignerBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME);
gen.addSignerInfoGenerator(
    new JcaSignerInfoGeneratorBuilder(
        new JcaDigestCalculatorProviderBuilder()
            .setProvider(BouncyCastleProvider.PROVIDER_NAME)
            .build())
        .build(contentSignerBuilder.build(privateKey), cert));
CMSProcessableByteArray msg = new CMSProcessableByteArray(dummyContent);
CMSSignedData signedData = gen.generate(msg, true);
int actualSize = signedData.getEncoded().length;
int preferredSize = actualSize + (actualSize / 5);

// 设置签名选项
signatureOptions = new SignatureOptions();
signatureOptions.setVisualSignature(visibleSignProperties.getVisibleSignature());
signatureOptions.setPreferredSignatureSize(preferredSize);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 14:25:55