使用 PDFBox 编辑 pdf 中的内容会从 pdf 中删除最后一行
Posted
技术标签:
【中文标题】使用 PDFBox 编辑 pdf 中的内容会从 pdf 中删除最后一行【英文标题】:Editing content in pdf using PDFBox removes last line from pdf 【发布时间】:2015-03-23 20:54:04 【问题描述】:我正在尝试使用 Java 中的 PDFBox 编辑 pdf 的某些内容。问题是,每当我编辑 pdf 中的任何字符串并尝试使用 Adobe Reader 打开它时,最后一行都不会出现在新呈现的 pdf 中。
当我尝试直接从浏览器顶部打开呈现的 pdf 时,我能够看到最后一行。但是,它以不同的格式编码。我正在使用以下代码来编辑 pdf 的内容:
PDDocument doc = PDDocument.load(FileName);
PDPage page = (PDPage) doc.getDocumentCatalog().getAllPages().get(0);
PDStream contents = page.getContents();
PDFStreamParser parser = new PDFStreamParser(contents.getStream());
parser.parse();
List<Object> tokens = parser.getTokens();
for (int j = 0; j < tokens.size(); j++)
Object next = tokens.get(j);
if (next instanceof PDFOperator)
PDFOperator op = (PDFOperator) next;
if (op.getOperation().equals("Tj"))
COSString previous = (COSString) tokens.get(j - 1);
String string = previous.getString();
string = string.replace("@ordnum&", (null != data.getOrderNumber()?data.getOrderNumber():""));
string = string.replace("@shipid&", (null != data.getShipmentId()?data.getShipmentId():""));
string = string.replace("@customer&", (null != data.getCustomerNumber()?data.getCustomerNumber():""));
string = string.replace("@fromname&", (null != data.getFromName()?data.getFromName():""));
tokens.set(j - 1, new COSString(string.trim()));
编辑 pdf 会删除显示“有问题?...”的行。这里有什么问题?我做错了吗?
谢谢。
【问题讨论】:
我做错了什么吗? - 是的:您认为COSString
的内容是易于编辑的字符串。一般来说,他们不是。它们可能具有自定义的单字节或多字节编码。在您的情况下,底线是唯一使用特殊编码进行编码的底线。而你的编辑会破坏它,
但是我没有对那个 COSString 做任何事情。当我尝试打印字符串时,我看不到那条线。那么它不应该按原样出现吗?如果没有,我该如何处理/处理?我是 PDFBox 新手,所以对它了解不多。
但我没有对那个 COSString 做任何事情 - 是的,你做了。您将其转换为 Java 字符串 (previous.getString()
),您可以对其进行修剪并从 (new COSString(string.trim())
) 构建一个新的 COSString,以替换原始字符串。在某些编码的情况下,这可能会完全破坏字符串。 如果没有,我该如何处理/处理 - PDF 内容根本不应该像这样编辑,无论是在 PDFBox 还是在其他 PDF 库中。考虑使用 PDF 表单域。
【参考方案1】:
为什么最后一行无效
首先你必须知道,PDF 中的字符串有两种根本不同的情况
外部内容流,例如文档属性的作者和关键字,以及 inside 内容流表示来自要绘制的某种字体的字形序列。前一种类型使用 PDFDocEncoding(类似于 Latin1)或带有前导字节顺序的 UTF-16BE 编码 方法COSString.getString
和构造函数COSString(String)
就是为这种字符串设计的。
后一种类型使用为要呈现此字符串的 PDF 字体定义的编码进行编码。这可能是一些标准化编码,如 WinAnsiEncoding(类似于 Latin1)或 UniGB-UTF16-H(Adobe-GB1 字符集的 Unicode (UTF-16BE) 编码)。但它也可能是一些自定义的单字节或多字节编码。标准化和自定义的多字节编码都没有字节顺序标记。
在 PDF 的页面内容流中,大多数字符串使用 WinAnsiEncoding(因为这是它们字体的编码)。因为 WinAnsiEncoding 和 PDFDocEncoding 非常相似,所以您使用的 PDFDocEncoding COSString
方法和构造函数非常适合它们。
不过,最后一行是使用 Identity-H 编码的,它是 2 字节 CID 的水平身份映射,即直接引用一个 2 字节编码字体程序中的字符ID,没有那个字体程序没有任何意义。
由于此字符串不以字节顺序标记开头,COSString.getString
假定它使用单字节编码 PDFDocEncoding,因此为每个原始的两字节 PDF 字符串创建两个 Java 字符串字符特点。由于其中一些字符的字符值超出了实际有效的 PDFDocEncoding 范围,因此构造函数 COSString(String)
创建了一个 PDF 字符串,其中每个中间 Java 字符都使用一个两字节 UTF-16BE 字符;此外还添加了一个字节顺序标记。
因此,原始 PDF 字符串(以十六进制书写)
002b004400590048000300540058004800560057004c0052005100560022000300260052
005100570044004600570003005800560003004400570003004b0057005700530056001d
00120012005a005a005a005600110046004c0057005500580056004f0044005100480011
004600520050001200460052005100570044004600570010005800560012
修改后
FEFF002B0000004400000059000000480000000300000054000000580000004800000056
000000570000004C00000052000000510000005600000022000000030000002600000052
000000510000005700000044000000460000005700000003000000580000005600000003
0000004400000057000000030000004B00000057000000570000005300000056000002DB
00000012000000120000005A0000005A0000005A0000005600000011000000460000004C
000000570000005500000058000000560000004F00000044000000510000004800000011
000000460000005200000050000000120000004600000052000000510000005700000044
0000004600000057000000100000005800000056
根据 PDF 查看器的不同,这可能会产生不同的效果。你的原话
例如可能会传播得非常广泛:
或完全消失
因此,简而言之,如果您需要编辑这样的 PDF,请确保您只编辑使用类似 Latin1 编码的 PDF 字符串。
如果您还需要编辑不同编码的 PDF 字符串,请使用COSString
方法getBytes
将它们提取为byte[]
,以适用于相关编码的方式编辑此数组,然后创建一个新的COSString
从使用构造函数 COSString(byte[])
编辑的字节。
但即使这样也不是一个好主意。
一般情况下编辑流的问题
在编辑这样的流时,还有许多其他陷阱在等着你
而不是例如
(@customer&) Tj
您的信息流可能包含
(@cust) Tj
(omer&) Tj
或
[(@cust) -6 (omer&) ] TJ
甚至
(omer&) Tj
-62 0 Td
(@cust) Tj
因此,如果新模板使用稍微不同的表示,突然替换可能不起作用。
字体只能部分嵌入。如果不包括替换字符的字形,它们将被绘制为间隙。
您编辑的文本绘制操作可能依赖于前一个使用特定宽度。然后你的替换可以破坏以前的布局。
...
本质上,在通用文档中正确编辑流是非常困难的。
你还能做什么
您可以使用 AcroForm 表单字段,而不是像 @customer&
这样的内容占位符。
表单字段具有名称并且可以被它们识别。填写它们不会改变内容中的任何内容。
如果您不希望人们事后编辑您的 PDF 表单域,您可以将它们标记为只读,甚至将它们拼合到内容中。
【讨论】:
非常感谢您提供详细解释的答案。 好久没问这个问题了,还有一个问题。正如您所建议的,我使用 AcroFroms 填充动态文本并使其只读。问题是,当我将 pdf 作为电子邮件发送时,iphone / ipad 上的人无法看到来自内容。看起来这是一个已知问题 (forums.adobe.com/thread/1216563)。对此有何建议? 扁平化 pdf 有帮助吗?我找不到任何用于展平 pdf 的好代码。你能给我提供任何扁平化pdf的链接吗?我查看了此链接***.com/questions/14454387/…,但它们都没有为我正常工作。 扁平化表单会有所帮助,是的,但我还没有使用 pdfbox 做到这一点。你的表单元素有外观流吗? 我不确定那是什么。我有带有样式(例如字体和大小)的空测试字段,我用纯文本替换。我看到 PDFClown 内置了扁平化支持。但这从系统上的文件中读取。我有一个字节数组,它看起来不像 PDFClowm 支持直接从字节读取。以上是关于使用 PDFBox 编辑 pdf 中的内容会从 pdf 中删除最后一行的主要内容,如果未能解决你的问题,请参考以下文章
使用 PDFBOX 填写 PDF 表单中的多个字段并在填写后锁定编辑 pdf 文档