我正在尝试替换PDF
一个文本中的内容,但字母'X'没有被替换.
public static void main(String[] args) { String DEST = "/home/diego/Documentos/teste.pdf"; try { PdfReader reader = new PdfReader("termoAdesaoCartao.pdf"); PdfDictionary dictionary = reader.getPageN(1); PdfObject object = dictionary.getDirectObject(PdfName.CONTENTS); if (object instanceof PRStream) { PRStream stream = (PRStream)object; byte[] data = PdfReader.getStreamBytes(stream); stream.setData(new String(data).replace("Nome Completo", "A-B-C-D-E-F-G-H-I-J-K-L-M-N-O-P-Q-R-S-T-U-V-W-X-Y-Z").getBytes()); } PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(DEST)); stamper.close(); reader.close(); } catch (IOException | DocumentException e) { e.printStackTrace(); } }
基本上OP的方法一般都行不通.他的代码有两个主要的误解:
他假设可以使用单个字符编码将完整的内容流转换byte[]
为String
(使用显示运算符的文本的所有字符串参数).
这个假设是错误的:每个字体可能有自己的编码,因此如果在同一页面上使用多个字体,则显示运算符的不同文本的字符串操作数中的相同字节值可能表示完全不同的字符.实际上,字体甚至不需要包含到字符的映射,它们只需要将数值映射到字形绘制指令.
参看 9.4.3 文本显示运营商在ISO 32000-1:
文本显示运算符的字符串操作数应解释为标识要绘制的字形的字符代码序列.
使用简单字体时,字符串的每个字节都应被视为单独的字符代码.然后应在字体编码中查找字符代码以选择字形,如9.6.6"字符编码"中所述.
使用复合字体(PDF 1.2),可以使用多字节代码来选择字形.在这种情况下,字符串的一个或多个连续字节应被视为单个字符代码.代码长度和从代码到字形的映射在称为CMap的数据结构中定义,
简单的PDF生成器通常只使用标准编码(这些编码是ASCII'并且可能会产生类似OP的假设)但是有越来越多的非简单PDF生成器......
他假设他可以简单地编辑文本显示操作符的字符串操作数,匹配的字形将显示在PDF查看器中.
这个假设是错误的:字体通常只支持相当有限的字符集,而显示运算符的文本只使用单个字体,即当前选择的字体.如果一个操作符的字符串参数中的代码替换了另一个没有字体匹配字形的操作符,那么最多只能看到一个间隙!
虽然完整的字体通常至少包含一个种类的所有字符(例如,拉丁字母及其所有西欧变化)字形,PDF允许嵌入字体部分,cf.section 9.6.4 字体子在ISO 32000-1:
PDF文档可能包含Type 1和TrueType字体的子集.
同时,此选项通常仅用于嵌入现有文本中实际使用的字形的绘制指令.因此,如果嵌入字体包含一些相同类型的字符,则不能指望它们.可能有一个字形A
,C
但不是B
.
不幸的是,OP没有提供他的样本PDF.但症状是:
他的电话replace("Nome Completo", "A-B-C-D-E-F-G-H-I-J-K-L-M-N-O-P-Q-R-S-T-U-V-W-X-Y-Z")
有所作为,如他的截图所示
以及他对Viacheslav Vedenin答案的评论
在文本之前
(Nome Completo)Tj
和之后(A-B-C-D-E-F-G-H-I-J-K-L-M-N-O-P-Q-R-S-T-U-V-W-X-Y-Z)Tj
但有些代码没有显示为预期的字形,也可以在上面的屏幕截图中看到
指出上述两个主要错误假设中的后一个使OP的代码失败的方向:很可能有问题的字体使用标准编码(可能是WinAnsiEncoding)但只是部分嵌入,特别是没有大写字母K
,W
,X
和Y
.
OP(已经使用iText的用户)可以使用以下iText概念,而不是盲目地编辑内容流:
文本提取类也可用于提取文本坐标,cf stackoverflow上的多个答案,特别是他想要替换的文本的边界矩形;
iText xtra库类PdfCleanUpProcessor
可用于删除该边界矩形中存在的所有内容;
该PdfStamper.getOverContent()
则可以使用在这些坐标正确地添加新的内容.
这可能听起来很复杂,但这会解决OP方法中可见的一些额外的小误解.