使用 PDFBox 为 flattend PDF 表单嵌入字体
Embed fonts for flattend PDF form with PDFBox
我用 PDFBox 填写了一个 PDF 表单,我在保存之前将其展平。该表单具有用于文本和表单字段的自定义字体。当我在没有安装此自定义字体的设备上打开输出文档(带有扁平化字段)时,普通文本的字体仍然正确,但扁平化字段的字体显示为后备(?)字体。在安装了此自定义字体的设备上,一切看起来都符合预期。
有没有办法在展开表单后强制对所有文本使用相同的自定义字体?
使用PDFBox填写PDF表单的代码(简体):
public class App
{
public static void main(String[] args) throws IOException {
String formTemplate = "src/main/resources/fonts.pdf";
String filledForm = "src/main/resources/fonts_out.pdf";
PDDocument pdfDocument = PDDocument.load(new File(formTemplate));
PDAcroForm acroForm = pdfDocument.getDocumentCatalog().getAcroForm();
acroForm.getField("text").setValue("Same font in form text field (updated with PDFBox)");
acroForm.setNeedAppearances(true);
acroForm.refreshAppearances();
acroForm.flatten();
pdfDocument.save(filledForm);
pdfDocument.close();
}
}
预计:
系统未安装字体时的结果:
对您的 PDF 的一些观察(前面提到的编码问题是 none - 只是代表我的无知):
SansDroid 字体未嵌入到 PDF 中。这是通过用新嵌入的 F5
字体替换 F2
字体来解决的。
设置了 NeedAppearances
标志意味着表单域没有外观。任何 reader 都必须(重新)创建它们。这在展平之前不会由 PDFBox 自动完成,所以我添加了这部分
为了不再引起有关丢失字体的警告,我完全删除了 F2 字体。
我通过预检 运行 原始 PDF,它给了我以下警告:“缺少必需的键/子类型。路径:->页面-> Kids->[0]->Annots->[0]->AP->N “密钥确实存在,但它似乎表明表单字段的外观存在错误。如果我删除 /N dict,错误就消失了。流是“/Tx BMC EMC”——也许缺少一些 EOL?但是由于无论如何都会重新生成外观,所以之后错误就消失了。
使用以下代码将 DroidSans 字体嵌入到 PDF 中:
File pdf = new File("Fonts.pdf");
final PDDocument document = PDDocument.load(pdf);
FileInputStream fontFile = new FileInputStream(new File("DroidSans.ttf"));
PDFont font = PDType0Font.load(document, fontFile, false);
//1. embedd and register the font (Catalog dict)
PDAcroForm pDAcroForm = document.getDocumentCatalog().getAcroForm();
//create a new font resource
PDResources res = pDAcroForm.getDefaultResources();
if (res == null) res = new PDResources();
COSName fontName = res.add(font);
pDAcroForm.setDefaultResources(res);
//2. Now change the font of form field to the newly added font
PDField field = pDAcroForm.getField("text");
//field.setValue("Same font in form text field (updated with PDFBox)");
COSDictionary dict = field.getCOSObject();
COSString defaultAppearance = (COSString) dict.getDictionaryObject(COSName.DA);
if (defaultAppearance != null){
String currentValue = dict.getString(COSName.DA);
//replace the font - this should be improved with a more general version
dict.setString(COSName.DA,currentValue.replace("F2", fontName.getName()));
//remove F2 completely
COSDictionary resources = res.getCOSObject();
for(Entry<COSName, COSBase> resource : resources.entrySet()) {
if(resource.getKey().equals(COSName.FONT)) {
COSObject fonts = (COSObject)resource.getValue();
COSDictionary fontDict = (COSDictionary)fonts.getObject();
COSName toBeRemoved=null;
for(Entry<COSName, COSBase> item : fontDict.entrySet()) {
if(item.getKey().getName().equals("F2")) {
toBeRemoved = item.getKey();
}
}
if(toBeRemoved!=null) {
fontDict.removeItem(toBeRemoved);
}
}
}
if(pDAcroForm.getNeedAppearances()) {
pDAcroForm.refreshAppearances();
pDAcroForm.setNeedAppearances(false);
}
//Flatten the document
pDAcroForm.flatten();
//Save the document
document.save("Form-Test-Result.pdf");
document.close();
请注意上面的代码是相当静态的——搜索和替换名为 F2
的字体只对提供的 PDF 有效,在其他情况下它不会。您必须为此实施更通用的解决方案...
PDFont font = PDType0Font.load(document, fontFile, false);
我认为最后一个参数 ('false') 将所有字符嵌入到字体中。
当使用像日文字体这样的大字体时,这会产生一个大尺寸的 pdf。
所以,我尝试了以下代码,它对我有用。
(* Scala, PDFBox 2.0.20)
// val font = PDType0Font.load(document, fontFile, true);
// form.flatten()
// hack for embed minimul font?
val page = new PDPage(PDRectangle.A6) // any page size.
val stream = new PDPageContentStream(document, page)
stream.beginText()
stream.setFont(font, 0)
stream.showText(allChars) // `allChars` are inputed all characters in forms in the creating pdf.
stream.endText()
stream.close()
// NOTE: I did NOT add the page to the PDF but worked.
// Save the document ~
我用 PDFBox 填写了一个 PDF 表单,我在保存之前将其展平。该表单具有用于文本和表单字段的自定义字体。当我在没有安装此自定义字体的设备上打开输出文档(带有扁平化字段)时,普通文本的字体仍然正确,但扁平化字段的字体显示为后备(?)字体。在安装了此自定义字体的设备上,一切看起来都符合预期。
有没有办法在展开表单后强制对所有文本使用相同的自定义字体?
使用PDFBox填写PDF表单的代码(简体):
public class App
{
public static void main(String[] args) throws IOException {
String formTemplate = "src/main/resources/fonts.pdf";
String filledForm = "src/main/resources/fonts_out.pdf";
PDDocument pdfDocument = PDDocument.load(new File(formTemplate));
PDAcroForm acroForm = pdfDocument.getDocumentCatalog().getAcroForm();
acroForm.getField("text").setValue("Same font in form text field (updated with PDFBox)");
acroForm.setNeedAppearances(true);
acroForm.refreshAppearances();
acroForm.flatten();
pdfDocument.save(filledForm);
pdfDocument.close();
}
}
预计:
系统未安装字体时的结果:
对您的 PDF 的一些观察(前面提到的编码问题是 none - 只是代表我的无知):
SansDroid 字体未嵌入到 PDF 中。这是通过用新嵌入的
F5
字体替换F2
字体来解决的。设置了
NeedAppearances
标志意味着表单域没有外观。任何 reader 都必须(重新)创建它们。这在展平之前不会由 PDFBox 自动完成,所以我添加了这部分为了不再引起有关丢失字体的警告,我完全删除了 F2 字体。
我通过预检 运行 原始 PDF,它给了我以下警告:“缺少必需的键/子类型。路径:->页面-> Kids->[0]->Annots->[0]->AP->N “密钥确实存在,但它似乎表明表单字段的外观存在错误。如果我删除 /N dict,错误就消失了。流是“/Tx BMC EMC”——也许缺少一些 EOL?但是由于无论如何都会重新生成外观,所以之后错误就消失了。
使用以下代码将 DroidSans 字体嵌入到 PDF 中:
File pdf = new File("Fonts.pdf");
final PDDocument document = PDDocument.load(pdf);
FileInputStream fontFile = new FileInputStream(new File("DroidSans.ttf"));
PDFont font = PDType0Font.load(document, fontFile, false);
//1. embedd and register the font (Catalog dict)
PDAcroForm pDAcroForm = document.getDocumentCatalog().getAcroForm();
//create a new font resource
PDResources res = pDAcroForm.getDefaultResources();
if (res == null) res = new PDResources();
COSName fontName = res.add(font);
pDAcroForm.setDefaultResources(res);
//2. Now change the font of form field to the newly added font
PDField field = pDAcroForm.getField("text");
//field.setValue("Same font in form text field (updated with PDFBox)");
COSDictionary dict = field.getCOSObject();
COSString defaultAppearance = (COSString) dict.getDictionaryObject(COSName.DA);
if (defaultAppearance != null){
String currentValue = dict.getString(COSName.DA);
//replace the font - this should be improved with a more general version
dict.setString(COSName.DA,currentValue.replace("F2", fontName.getName()));
//remove F2 completely
COSDictionary resources = res.getCOSObject();
for(Entry<COSName, COSBase> resource : resources.entrySet()) {
if(resource.getKey().equals(COSName.FONT)) {
COSObject fonts = (COSObject)resource.getValue();
COSDictionary fontDict = (COSDictionary)fonts.getObject();
COSName toBeRemoved=null;
for(Entry<COSName, COSBase> item : fontDict.entrySet()) {
if(item.getKey().getName().equals("F2")) {
toBeRemoved = item.getKey();
}
}
if(toBeRemoved!=null) {
fontDict.removeItem(toBeRemoved);
}
}
}
if(pDAcroForm.getNeedAppearances()) {
pDAcroForm.refreshAppearances();
pDAcroForm.setNeedAppearances(false);
}
//Flatten the document
pDAcroForm.flatten();
//Save the document
document.save("Form-Test-Result.pdf");
document.close();
请注意上面的代码是相当静态的——搜索和替换名为 F2
的字体只对提供的 PDF 有效,在其他情况下它不会。您必须为此实施更通用的解决方案...
PDFont font = PDType0Font.load(document, fontFile, false);
我认为最后一个参数 ('false') 将所有字符嵌入到字体中。 当使用像日文字体这样的大字体时,这会产生一个大尺寸的 pdf。 所以,我尝试了以下代码,它对我有用。
(* Scala, PDFBox 2.0.20)
// val font = PDType0Font.load(document, fontFile, true);
// form.flatten()
// hack for embed minimul font?
val page = new PDPage(PDRectangle.A6) // any page size.
val stream = new PDPageContentStream(document, page)
stream.beginText()
stream.setFont(font, 0)
stream.showText(allChars) // `allChars` are inputed all characters in forms in the creating pdf.
stream.endText()
stream.close()
// NOTE: I did NOT add the page to the PDF but worked.
// Save the document ~