我正在尝试使用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会删除“有疑问?...”的行。这里有什么问题?我做错了吗?
感谢。
答案 0 :(得分:4)
首先,你必须要知道PDF中的字符串存在两种根本不同的情况
前一种类型使用 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的水平身份映射,即直接引用a的双字节编码字体程序中的字符ID没有任何意义,没有该字体程序。
由于此字符串不以字节顺序标记开头,COSString.getString
假定它使用单字节编码 PDFDocEncoding ,因此为每个原始双字节创建两个Java字符串字符PDF字符串字符。由于某些字符的字符值超出实际有效的 PDFDocEncoding 范围,构造函数COSString(String)
会创建一个PDF字符串,其中每个中间Java字符使用一个双字节表示 UTF-16BE 字符;此外,还添加了一个字节顺序标记。
因此,原始PDF字符串(以十六进制书写)
002b004400590048000300540058004800560057004c0052005100560022000300260052
005100570044004600570003005800560003004400570003004b0057005700530056001d
00120012005a005a005a005600110046004c0057005500580056004f0044005100480011
004600520050001200460052005100570044004600570010005800560012
编辑后
FEFF002B0000004400000059000000480000000300000054000000580000004800000056
000000570000004C00000052000000510000005600000022000000030000002600000052
000000510000005700000044000000460000005700000003000000580000005600000003
0000004400000057000000030000004B00000057000000570000005300000056000002DB
00000012000000120000005A0000005A0000005A0000005600000011000000460000004C
000000570000005500000058000000560000004F00000044000000510000004800000011
000000460000005200000050000000120000004600000052000000510000005700000044
0000004600000057000000100000005800000056
根据PDF查看器,这可能会产生不同的效果。你原来的一行
e.g。可能会变得非常广泛:
或完全消失
因此,简而言之,如果您需要编辑这样的PDF,请确保您只编辑类似Latin1的PDF字符串。
如果您还需要编辑不同编码的PDF字符串,请使用byte[]
方法COSString
将其提取为getBytes
,以适用于相关编码的方式编辑此数组,以及使用构造函数COSString
从编辑的字节创建新的COSString(byte[])
。
但即便如此也不是一个好主意。
在编辑像
这样的流时,还有很多其他的陷阱在等着你而不是像。
(@customer&) Tj
您的信息流可能包含
(@cust) Tj
(omer&) Tj
或
[(@cust) -6 (omer&) ] TJ
甚至
(omer&) Tj
-62 0 Td
(@cust) Tj
因此,如果新模板使用略有不同的表示,则突然替换可能不起作用。
字体可能只是部分嵌入。如果未包含替换字符的字形,则会将它们绘制为间隙。
您编辑的文字绘图操作可能依赖于前者使用特定宽度。然后,您的替换可以破坏以前的布局。
...
实质上,在通用文档中正确编辑流是非常困难的。
您可以使用AcroForm表单字段代替内容持有者,例如您的@customer&
。
表单字段具有名称,可以被它们识别。填写它们不会改变内容中的任何内容。
如果您之后不想让人们编辑您的PDF表单字段,您可以将它们标记为只读,甚至将它们展平为内容。