请原谅我!我在爪哇很穷。
我错在哪里请指正,差在哪里请改进!
我正尝试使用PDFBox对动态创建的pdf进行数字签名,并使用以下程序:
程序中的任务:
(i)创建模板PDF
(ii)更新ByteRange、xref、startxref
(iii)构造用于签名创建的原始文档
(iv)创建分离式信封数字签名
(v)通过连接原始文档第一部分、分离式签名和原始PDF第二部分来构造数字签名的PDF文档
观察:
(i)PdffileOutputStream.Write(DocumentOutputStream.TobyteArray());创建带有可见签名的模板PDF文档。
(ii)它创建了一些PDF签名的文档,但有错误(a)无效的令牌和(b)几个解析器错误
(现在在MKL的指导下更正!)
请就以下内容向我提出建议:
(i)如何在Layer2的可见签名中添加签名文本。
package digitalsignature;
import java.awt.geom.AffineTransform;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.security.Signature;
import java.util.ArrayList;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.SignerInfoGenerator;
import org.bouncycastle.cms.SignerInfoGeneratorBuilder;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.Store;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.X509Certificate;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.xobject.PDJpeg;
import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectForm;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSSignedGenerator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class AffixSignature {
String path = "D:\\reports\\";
String onlyFileName = "";
String pdfExtension = ".pdf";
String pdfFileName = "";
String pdfFilePath = "";
String signedPdfFileName = "";
String signedPdfFilePath = "";
String ownerPassword = "";
String tempSignedPdfFileName = "";
String tempSignedPdfFilePath = "";
String userPassword = "";
String storePath = "resources/my.p12";
String entryAlias = "signerCert";
String keyStorePassword = "password";
ByteArrayOutputStream documentOutputStream = null;
private Certificate[] certChain;
private static BouncyCastleProvider BC = new BouncyCastleProvider();
int offsetContentStart = 0;
int offsetContentEnd = 0;
int secondPartLength = 0;
int offsetStartxrefs = 0;
String contentString = "";
OutputStream signedPdfFileOutputStream;
OutputStream pdfFileOutputStream;
public AffixSignature() {
try {
SimpleDateFormat timeFormat = new SimpleDateFormat("hh_mm_ss");
onlyFileName = "Report_" + timeFormat.format(new Date());
pdfFileName = onlyFileName + ".pdf";
pdfFilePath = path + pdfFileName;
File pdfFile = new File(pdfFilePath);
pdfFileOutputStream = new FileOutputStream(pdfFile);
signedPdfFileName = "Signed_" + onlyFileName + ".pdf";
signedPdfFilePath = path + signedPdfFileName;
File signedPdfFile = new File(signedPdfFilePath);
signedPdfFileOutputStream = new FileOutputStream(signedPdfFile);
String tempFileName = "Temp_Report_" + timeFormat.format(new Date());
String tempPdfFileName = tempFileName + ".pdf";
String tempPdfFilePath = path + tempPdfFileName;
File tempPdfFile = new File(tempPdfFilePath);
OutputStream tempSignedPdfFileOutputStream = new FileOutputStream(tempPdfFile);
PDDocument document = new PDDocument();
PDDocumentCatalog catalog = document.getDocumentCatalog();
PDPage page = new PDPage(PDPage.PAGE_SIZE_A4);
PDPageContentStream contentStream = new PDPageContentStream(document, page);
PDFont font = PDType1Font.HELVETICA;
Map<String, PDFont> fonts = new HashMap<String, PDFont>();
fonts = new HashMap<String, PDFont>();
fonts.put("F1", font);
// contentStream.setFont(font, 12);
contentStream.setFont(font, 12);
contentStream.beginText();
contentStream.moveTextPositionByAmount(100, 700);
contentStream.drawString("DIGITAL SIGNATURE TEST");
contentStream.endText();
contentStream.close();
document.addPage(page);
//To Affix Visible Digital Signature
PDAcroForm acroForm = new PDAcroForm(document);
catalog.setAcroForm(acroForm);
PDSignatureField sf = new PDSignatureField(acroForm);
PDSignature pdSignature = new PDSignature();
page.getAnnotations().add(sf.getWidget());
pdSignature.setName("sign");
pdSignature.setByteRange(new int[]{0, 0, 0, 0});
pdSignature.setContents(new byte[4 * 1024]);
pdSignature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
pdSignature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
pdSignature.setName("NAME");
pdSignature.setLocation("LOCATION");
pdSignature.setReason("SECURITY");
pdSignature.setSignDate(Calendar.getInstance());
List<PDField> acroFormFields = acroForm.getFields();
sf.setSignature(pdSignature);
sf.getWidget().setPage(page);
COSDictionary acroFormDict = acroForm.getDictionary();
acroFormDict.setDirect(true);
acroFormDict.setInt(COSName.SIG_FLAGS, 3);
acroFormFields.add(sf);
PDRectangle frmRect = new PDRectangle();
// float[] frmRectParams = {lowerLeftX,lowerLeftY,upperRightX,upperRight};
// float[] frmRectLowerLeftUpperRightCoordinates = {5f, page.getMediaBox().getHeight() - 50f, 100f, page.getMediaBox().getHeight() - 5f};
float[] frmRectLowerLeftUpperRightCoordinates = {5f, 5f, 205f, 55f};
frmRect.setUpperRightX(frmRectLowerLeftUpperRightCoordinates[2]);
frmRect.setUpperRightY(frmRectLowerLeftUpperRightCoordinates[3]);
frmRect.setLowerLeftX(frmRectLowerLeftUpperRightCoordinates[0]);
frmRect.setLowerLeftY(frmRectLowerLeftUpperRightCoordinates[1]);
sf.getWidget().setRectangle(frmRect);
COSArray procSetArr = new COSArray();
procSetArr.add(COSName.getPDFName("PDF"));
procSetArr.add(COSName.getPDFName("Text"));
procSetArr.add(COSName.getPDFName("ImageB"));
procSetArr.add(COSName.getPDFName("ImageC"));
procSetArr.add(COSName.getPDFName("ImageI"));
String signImageFilePath = "resources/sign.JPG";
File signImageFile = new File(signImageFilePath);
InputStream signImageStream = new FileInputStream(signImageFile);
PDJpeg img = new PDJpeg(document, signImageStream);
PDResources holderFormResources = new PDResources();
PDStream holderFormStream = new PDStream(document);
PDXObjectForm holderForm = new PDXObjectForm(holderFormStream);
holderForm.setResources(holderFormResources);
holderForm.setBBox(frmRect);
holderForm.setFormType(1);
PDAppearanceDictionary appearance = new PDAppearanceDictionary();
appearance.getCOSObject().setDirect(true);
PDAppearanceStream appearanceStream = new PDAppearanceStream(holderForm.getCOSStream());
appearance.setNormalAppearance(appearanceStream);
sf.getWidget().setAppearance(appearance);
acroFormDict.setItem(COSName.DR, holderFormResources.getCOSDictionary());
PDResources innerFormResources = new PDResources();
PDStream innerFormStream = new PDStream(document);
PDXObjectForm innerForm = new PDXObjectForm(innerFormStream);
innerForm.setResources(innerFormResources);
innerForm.setBBox(frmRect);
innerForm.setFormType(1);
String innerFormName = holderFormResources.addXObject(innerForm, "FRM");
PDResources imageFormResources = new PDResources();
PDStream imageFormStream = new PDStream(document);
PDXObjectForm imageForm = new PDXObjectForm(imageFormStream);
imageForm.setResources(imageFormResources);
byte[] AffineTransformParams = {1, 0, 0, 1, 0, 0};
AffineTransform affineTransform = new AffineTransform(AffineTransformParams[0], AffineTransformParams[1], AffineTransformParams[2], AffineTransformParams[3], AffineTransformParams[4], AffineTransformParams[5]);
imageForm.setMatrix(affineTransform);
imageForm.setBBox(frmRect);
imageForm.setFormType(1);
String imageFormName = innerFormResources.addXObject(imageForm, "n");
String imageName = imageFormResources.addXObject(img, "img");
innerForm.getResources().getCOSDictionary().setItem(COSName.PROC_SET, procSetArr);
page.getCOSDictionary().setItem(COSName.PROC_SET, procSetArr);
innerFormResources.getCOSDictionary().setItem(COSName.PROC_SET, procSetArr);
imageFormResources.getCOSDictionary().setItem(COSName.PROC_SET, procSetArr);
holderFormResources.getCOSDictionary().setItem(COSName.PROC_SET, procSetArr);
String holderFormComment = "q 1 0 0 1 0 0 cm /" + innerFormName + " Do Q \n";
String innerFormComment = "q 1 0 0 1 0 0 cm /" + imageFormName + " Do Q\n";
String imgFormComment = "q " + 100 + " 0 0 50 0 0 cm /" + imageName + " Do Q\n";
appendRawCommands(holderFormStream.createOutputStream(), holderFormComment);
appendRawCommands(innerFormStream.createOutputStream(), innerFormComment);
appendRawCommands(imageFormStream.createOutputStream(), imgFormComment);
documentOutputStream = new ByteArrayOutputStream();
document.save(documentOutputStream);
document.close();
tempSignedPdfFileOutputStream.write(documentOutputStream.toByteArray());
generateSignedPdf();
} catch (Exception e) {
e.printStackTrace();
}
}
public void appendRawCommands(OutputStream os, String commands) throws IOException {
os.write(commands.getBytes("ISO-8859-1"));
os.close();
}
public void generateSignedPdf() {
try {
//Find the Initial Byte Range Offsets
String docString = new String(documentOutputStream.toByteArray(), "ISO-8859-1");
offsetContentStart = (documentOutputStream.toString().indexOf("Contents <") + 10 - 1);
offsetContentEnd = (documentOutputStream.toString().indexOf("000000>") + 7);
secondPartLength = (documentOutputStream.size() - documentOutputStream.toString().indexOf("000000>") - 7);
//Calculate the Updated ByteRange
String initByteRange = "";
if (docString.indexOf("/ByteRange [0 1000000000 1000000000 1000000000]") > 0) {
initByteRange = "/ByteRange [0 1000000000 1000000000 1000000000]";
} else if (docString.indexOf("/ByteRange [0 0 0 0]") > 0) {
initByteRange = "/ByteRange [0 0 0 0]";
} else {
System.out.println("No /ByteRange Token is Found!");
System.exit(1);
}
String interimByteRange = "/ByteRange [0 " + offsetContentStart + " " + offsetContentEnd + " " + secondPartLength + "]";
int byteRangeLengthDifference = interimByteRange.length() - initByteRange.length();
offsetContentStart = offsetContentStart + byteRangeLengthDifference;
offsetContentEnd = offsetContentEnd + byteRangeLengthDifference;
String finalByteRange = "/ByteRange [0 " + offsetContentStart + " " + offsetContentEnd + " " + secondPartLength + "]";
byteRangeLengthDifference += interimByteRange.length() - finalByteRange.length();
//Replace the ByteRange
docString = docString.replace(initByteRange, finalByteRange);
//Update xref Table
int xrefOffset = docString.indexOf("xref");
int startObjOffset = docString.indexOf("0000000000 65535 f") + "0000000000 65535 f".length() + 1;
int trailerOffset = docString.indexOf("trailer") - 2;
String initialXrefTable = docString.substring(startObjOffset, trailerOffset);
int signObjectOffset = docString.indexOf("/Type /Sig") - 3;
String updatedXrefTable = "";
while (initialXrefTable.indexOf("n") > 0) {
String currObjectRefEntry = initialXrefTable.substring(0, initialXrefTable.indexOf("n") + 1);
String currObjectRef = currObjectRefEntry.substring(0, currObjectRefEntry.indexOf(" 00000 n"));
int currObjectOffset = Integer.parseInt(currObjectRef.trim().replaceFirst("^0+(?!$)", ""));
if ((currObjectOffset + byteRangeLengthDifference) > signObjectOffset) {
currObjectOffset += byteRangeLengthDifference;
int currObjectOffsetDigitsCount = Integer.toString(currObjectOffset).length();
currObjectRefEntry = currObjectRefEntry.replace(currObjectRefEntry.substring(currObjectRef.length() - currObjectOffsetDigitsCount, currObjectRef.length()), Integer.toString(currObjectOffset));
updatedXrefTable += currObjectRefEntry;
} else {
updatedXrefTable += currObjectRefEntry;
}
initialXrefTable = initialXrefTable.substring(initialXrefTable.indexOf("n") + 1);
}
//Replace with Updated xref Table
docString = docString.substring(0, startObjOffset).concat(updatedXrefTable).concat(docString.substring(trailerOffset));
//Update startxref
int startxrefOffset = docString.indexOf("startxref");
//Replace with Updated startxref
docString = docString.substring(0, startxrefOffset).concat("startxref\n".concat(Integer.toString(xrefOffset))).concat("\n%%EOF\n");
//Construct Original Document For Signature by Removing Temporary Enveloped Detached Signed Content(000...000)
contentString = docString.substring(offsetContentStart + 1, offsetContentEnd - 1);
String docFirstPart = docString.substring(0, offsetContentStart);
String docSecondPart = docString.substring(offsetContentEnd);
String docForSign = docFirstPart.concat(docSecondPart);
//Generate Signature
pdfFileOutputStream.write(docForSign.getBytes("ISO-8859-1"));
File keyStorefile = new File(storePath);
InputStream keyStoreInputStream = new FileInputStream(keyStorefile);
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(keyStoreInputStream, keyStorePassword.toCharArray());
certChain = keyStore.getCertificateChain(entryAlias);
PrivateKey privateKey = (PrivateKey) keyStore.getKey(entryAlias, keyStorePassword.toCharArray());
List<Certificate> certList = new ArrayList<Certificate>();
certList = Arrays.asList(certChain);
Store store = new JcaCertStore(certList);
// String algorithm="SHA1WithRSA";
// String algorithm="SHA2WithRSA";
String algorithm = "MD5WithRSA";
//String algorithm = "DSA";
//Updated Sign Method
CMSTypedData msg = new CMSProcessableByteArray(docForSign.getBytes("ISO-8859-1"));
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
/* Build the SignerInfo generator builder, that will build the generator... that will generate the SignerInformation... */
SignerInfoGeneratorBuilder signerInfoBuilder = new SignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
//JcaContentSignerBuilder contentSigner = new JcaContentSignerBuilder("SHA2withRSA");
JcaContentSignerBuilder contentSigner = new JcaContentSignerBuilder(algorithm);
contentSigner.setProvider(BC);
SignerInfoGenerator signerInfoGenerator = signerInfoBuilder.build(contentSigner.build(privateKey), new X509CertificateHolder(certList.get(0).getEncoded()));
generator.addSignerInfoGenerator(signerInfoGenerator);
generator.addCertificates(store);
CMSSignedData signedData = generator.generate(msg, false);
String apHexEnvelopedData = org.apache.commons.codec.binary.Hex.encodeHexString(signedData.getEncoded()).toUpperCase();
//Construct Content Tag Data
contentString = apHexEnvelopedData.concat(contentString).substring(0, contentString.length());
contentString = "<".concat(contentString).concat(">");
//Construct Signed Document
String signedDoc = docFirstPart.concat(contentString).concat(docSecondPart);
//Write Signed Document to File
signedPdfFileOutputStream.write(signedDoc.getBytes("ISO-8859-1"));
signedPdfFileOutputStream.close();
signedDoc = null;
} catch (Exception e) {
throw new RuntimeException("Error While Generating Signed Data", e);
}
}
public static void main(String[] args) {
AffixSignature affixSignature = new AffixSignature();
}
}
虽然这些提示最初是作为对原问题的评论提出的,但现在有必要作为一种答复提出:
虽然有太多的代码需要在不花费大量时间的情况下进行审查和修复,并且最初没有示例PDF是一个障碍,但快速扫描代码就会发现一些问题:
>
appendrawCommands(xxxFormStream.createOutputStream(),YYY)
调用很可能会导致PDFBox出现问题:为同一表单多次创建输出流可能是一个问题,并且在表单之间来回切换也是一个问题。
此外,在写入同一流的多个字符串之间似乎没有空格,从而产生未知的Qq操作符。此外,appendrawcommands
方法使用PDF所不具备的UTF-8。
GenerateSignedDocument
很可能会造成很大的损害,因为它假设它可以像处理文本文件一样处理PDF。一般情况并非如此。
OP最终提供的示例结果PDF允许精确定位一些实际实现的问题:
>
比较两个文档(report_08_05_23.pdf和signed_report_08_05_23.pdf)的字节,我们会发现有许多不需要的更改,乍一看,特别是用问号替换某些字节。这是由于使用ByteArrayOutputStream.ToString()
轻松地对文档进行操作,并最终将其改回Byte[]
。
例如。参见ByteArrayOutputStream.ToString()
的JavaDocs
* <p> This method always replaces malformed-input and unmappable-character
* sequences with the default replacement string for the platform's
* default character set. The {@linkplain java.nio.charset.CharsetDecoder}
* class should be used when more control over the decoding process is
* required.
某些字节值不代表平台默认字符集中的字符,因此被转换为Unicode替换字符,并在最终转换为byte[]
时变为0x3F(问号的ASCII代码)。这一更改会扼杀压缩流内容,包括内容流和图像流。
要解决此问题,必须使用byte
和byte[]
操作,而不是这里的string
操作。
流8 0在它的XObject资源中引用了自己,这可能会让任何pdf查看器感到恶心。请不要这样循环往复。
签名无法验证。因此,本文也对其进行了评述。
>
检查签名容器可以发现它是错误的:尽管签名是adbe.pkcs7.detached,但签名容器嵌入了数据。看看代码,原因就清楚了:
CMSSignedData sigData = generator.generate(msg, true);
true
参数要求BC嵌入msg
数据。
开始查看签名代码后,另一个问题变得显而易见:上面的msg
数据不仅仅是一个摘要,它们已经是一个签名:
Signature signature = Signature.getInstance(algorithm, BC);
signature.initSign(privateKey);
signature.update(docForSign.getBytes());
CMSTypedData msg = new CMSProcessableByteArray(signature.sign());
这是错误的,因为后面创建的SignerInfoGenerator
用于创建实际签名。
编辑:在修复或至少解决了前面提到的问题之后,Adobe Reader仍然不接受签名。因此,再次查看代码并:
OP构造此ByteRange值
String finalByteRange = "/ByteRange [0 " + offsetContentStart + " " + offsetContentEnd + " " + secondPartLength + "]";
和更高版本
String docFirstPart = docString.substring(0, offsetContentStart + 1);
String docSecondPart = docString.substring(offsetContentEnd - 1);
+1
和-1
旨在使这些文档部分也包括封装签名字节的<
和。但是OP还使用这些字符串来构造签名数据:
String docForSign = docFirstPart.concat(docSecondPart);
这是错误的,带符号的字节不包含<
和>
。因此,稍后计算的哈希值也是错误的,Adobe Reader有充分的理由假设文档被操纵了。
话虽如此,但偶尔也会出现一些其他问题:
操作插入的字节范围如下所示:
String interimByteRange = "/ByteRange [0 " + offsetContentStart + " " + offsetContentEnd + " " + secondPartLength + "]";
int byteRangeLengthDifference = interimByteRange.length() - initByteRange.length();
offsetContentStart = offsetContentStart + byteRangeLengthDifference;
offsetContentEnd = offsetContentEnd + byteRangeLengthDifference;
String finalByteRange = "/ByteRange [0 " + offsetContentStart + " " + offsetContentEnd + " " + secondPartLength + "]";
byteRangeLengthDifference += interimByteRange.length() - finalByteRange.length();
//Replace the ByteRange
docString = docString.replace(initByteRange, finalByteRange);
每隔一段时间offsetContentStart
或offsetContentEnd
将略低于大约10^n,之后略高于10^n。这条线
byteRangeLengthDifference += interimByteRange.length() - finalByteRange.length();
试图弥补这一点,但finalbyterange
(最终插入到文档中)仍然包含未校正值。
以类似的方式插入xref起始的表示,如下所示
docString = docString.substring(0, startxrefOffset).concat("startxref\n".concat(Integer.toString(xrefOffset))).concat("\n%%EOF\n");
也可能比以前长,这使得字节范围(事先计算的)不能覆盖整个文档。
此外,使用整个文档的文本搜索来查找相关PDF对象的偏移量
offsetContentStart = (documentOutputStream.toString().indexOf("Contents <") + 10 - 1);
offsetContentEnd = (documentOutputStream.toString().indexOf("000000>") + 7);
...
int xrefOffset = docString.indexOf("xref");
...
int startxrefOffset = docString.indexOf("startxref");
对于泛型文档将失败。例如。如果文档中已经有先前的签名,则很可能会这样识别错误的索引。
主要内容:创建一个空的PDF文档,实例现在让我们了解如何使用PDFBox库创建PDF文档。 创建一个空的PDF文档 可以通过实例化类来创建一个空的PDF文档。使用这个类的方法将文档保存在所需的位置。 以下是创建一个空的PDF文档的步骤。 第1步: 创建空白文档 包中的类是PDF文档的内存中表示形式。 因此,通过实例化这个类,可以创建一个空的,如下面的代码块所示。 第2步: 保存文档 创建文档后,需要将此文档保存在所需的路径中,可以使用
我正在尝试从Java结果集创建PDF报告。如果报告只有一页,我在这里就没有问题了。这个问题源于这样一个事实:这份报告可能有一到十页长。现在,我要创建一个单页文档: 所以我的问题是,如何根据需要动态创建页面。有没有一个面向对象的答案盯着我看,而我就是看不见?
问题内容: 我需要创建一个PDF,其中将包含执行状态报告,其中状态将以表格结构显示。是否可以使用PDFBOX API生成pdf表格式? 以下是一些用于创建新PDF文档的示例代码: 问题答案: 试试这个: 只需在函数中调用此方法
我想用pdfbox创建一个PDF(https://pdfbox.apache.org/cookbook/documentcreation.html)然而,pdfbox似乎没有像OpenOffice这样的文本编辑器那样提供动态文本布局机制(使用块格式、居中文本、换行符等预定义文本格式的自动文本流)。 是否有任何Java库在pdfbox之上或与之分离?或者你有任何可用的免费代码吗?
问题内容: 如何使用iText签名pdf?我正在通过此LINK进行操作, 但不了解my_private_key.pfx。我真的需要数字签名证书吗?请澄清一下。提前致谢。 问题答案: 您在问题中提到的文档很好。您必须创建数字签名文件。 该链接具有使用PKCS文件和签署PDF文档的工具。它声称使用iText,因此您应该能够理解这些步骤。源代码在这里
我正在迁移一些代码(最初使用iText)来使用PdfBox进行PDF合并。除了创建PDF包或文件夹,一切都很好。我不得不承认,直到现在我才意识到它的存在。 这是我的代码片段(使用iText): 我需要这个,但与PdfBox。 我正在研究两者的 API 和文档,但找不到解决方案。任何帮助都会很棒。 附言。如果我给人留下印象,我需要在iText中解决方案,我需要它在PdfBox中,因为迁移是从iTex