【问题标题】:Replace string in PDF file using Itext but letter X not replace使用 Itext 替换 PDF 文件中的字符串,但字母 X 不替换
【发布时间】:2019-03-13 21:42:36
【问题描述】:

我正在尝试在一个文本中替换 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();
    }

}

【问题讨论】:

  • 字体很可能只是部分嵌入,大写字母 K、W、X 和 Y 没有嵌入。只有在非常幸运的情况下才能像您一样编辑内容。
  • 此外,正如 Viacheslav Vedenin 的回答中所指出的,在没有明确选择编码的情况下对字符串进行编码和解码通常是一个坏主意。但是假设 PDF 页面的内容流是 utf-8 编码的也好不了多少。一般来说,为整个内容流假设单一编码甚至没有意义,必须使用为各个内容流部分选择的字体的编码。
  • @DiegoMacario 你说你有同样的问题。因此,也许您可​​以在您的用例中提供一个示例 PDF 代表。关于 OP 的 PDF,我们只能猜测并查看所描述的症状指向的位置。一个实际的样本将允许展示。
  • @Diego 我得到了文件的字符集 - 我不确定我是否理解正确。正如我在回答中所解释的,通常 没有单个字符集 可以用来解码内容流。我要求提供该文件以检查是否有解决方法。
  • @DiegoMacario 如果您碰巧找到了表演者,请随时询问更多详细信息。

标签: java string pdf itext


【解决方案1】:

我修改了一下找到的代码,它的工作原理如下

public static final String SRC = "C:/tmp/244558.pdf";
public static final String DEST = "C:/tmp/244558-2.pdf";

public static void main(String[] args) throws IOException, DocumentException {
    File file = new File(DEST);
    file.getParentFile().mkdirs();
    new Main().manipulatePdf(SRC, DEST);
}

public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
    PdfReader reader = new PdfReader(src);
    PdfDictionary dict = reader.getPageN(1);
    PdfObject object = dict.getDirectObject(PdfName.CONTENTS);
    PdfArray refs = null;
    if (dict.get(PdfName.CONTENTS).isArray()) {
        refs = dict.getAsArray(PdfName.CONTENTS);
    } else if (dict.get(PdfName.CONTENTS).isIndirect()) {
        refs = new PdfArray(dict.get(PdfName.CONTENTS));
    }
    for (int i = 0; i < refs.getArrayList().size(); i++) {
        PRStream stream = (PRStream) refs.getDirectObject(i);
        byte[] data = PdfReader.getStreamBytes(stream);
        stream.setData(new String(data).replace("Data replace", "Data").getBytes());
    }
    PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
    stamper.close();
    reader.close();
}

【讨论】:

    【解决方案2】:

    一般

    基本上,OP 的方法通常行不通。他的代码建立在两个主要误解之上:

    • 他假设可以使用单个字符编码将完整的内容流从byte[] 转换为String(所有字符串参数都显示运算符清晰可见)。

      这个假设是错误的:每种字体可能有自己的编码,所以如果在同一页面上使用多种字体,不同文本显示操作符的字符串操作数中的相同字节值可能代表完全不同的字符。实际上字体甚至不需要包含到字符的映射,它们只需要将数值映射到字形绘制指令。

      参照。 ISO 32000-1 中的第 9.4.3 节 文本显示运算符

      文本显示运算符的字符串操作数应被解释为标识要绘制的字形的字符代码序列。

      对于简单字体,字符串的每个字节都应被视为一个单独的字符代码。然后应在字体的编码中查找字符代码以选择字形,如 9.6.6 “字符编码”中所述。

      对于复合字体 (PDF 1.2),可以使用多字节代码来选择字形。在这种情况下,字符串的一个或多个连续字节应被视为单个字符代码。代码长度和从代码到字形的映射在称为 CMap 的数据结构中定义,

      简单的 PDF 生成器通常只使用标准编码(它们是 ASCII'ish 并且可能会产生类似 OP 的假设),但是那里有越来越多的非简单 PDF 生成器...

    • 他假设他可以简单地编辑文本显示运算符的字符串操作数,并且匹配的字形将显示在 PDF 查看器中。

      这个假设是错误的:字体通常只支持相当有限的字符集,并且显示操作符的文本只使用一种字体,即当前选择的字体。如果将此类运算符的字符串参数中的代码替换为字体中没有匹配字形的不同代码,则最多会看到一个间隙!

      虽然完整的字体通常至少包含一种类型的所有字符的字形(例如带有所有西欧变体的拉丁字母),但 PDF 允许部分嵌入字体,参见第 9.6.4 节字体子集ISO 32000-1:

      PDF 文档可能包含 Type 1 和 TrueType 字体的子集。

      同时,此选项通常仅用于为现有文本中实际使用的字形嵌入绘画指令。因此,如果嵌入的字体包含相同类型的所有字符,则不能指望它们。 AC 可能有一个字形,但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),但只是部分嵌入,特别是没有大写字母KWXY

    如何正确操作

    OP(已经在使用 iText)可以使用以下 iText 概念,而不是盲目地编辑内容流:

    • 文本提取类也可用于提取文本坐标,参见 stackoverflow 上的多个答案,特别是他要替换的文本的边界矩形;
    • iText xtra 库类PdfCleanUpProcessor 可用于删除该边界矩形中存在的所有内容;
    • 然后可以使用PdfStamper.getOverContent() 在这些坐标处正确添加新内容。

    这听起来可能很复杂,但这可以解决 OP 方法中可见的一些额外的小误解。

    【讨论】:

      【解决方案3】:

      尝试使用代替

      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());
      

      以下代码

      stream.setData(new String(data, "UTF8").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("UTF8"));
      

      根据this post in Oracle manual 使用 new String(data) 和 getBytes() 可能会导致一些错误:

      字节编码和字符串

      如果字节数组包含非 Unicode 文本,您可以将文本转换为 带有 String 构造方法之一的 Unicode。相反,你 可以将 String 对象转换为非 Unicode 的字节数组 使用 String.getBytes 方法的字符。当调用任何一个 这些方法,您将编码标识符指定为 参数。

      以下示例在 UTF-8 和 统一码。 UTF-8 是一种安全的 Unicode 传输格式 UNIX 文件系统。该示例的完整源代码在文件中 StringConverter.java.

      更新: 如果它不起作用,您可以替换代码

      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());
      

      编码

      byte[] data = PdfReader.getStreamBytes(stream);
      String str = new String(data);
      System.out.printLn(str);
      String newStr = str.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"); 
      System.out.printLn(newStr);
      stream.setData(newStr.getBytes());
      

      然后写下你在控制台中显示的内容?

      【讨论】:

      • 文本之前是(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 。我没有把所有文件都放到很多文本中
      • 虽然在不明确选择编码的情况下对字符串进行编码和解码确实是个坏主意,但假设 PDF 页面的内容流进行 utf-8 编码是非常有创意的。
      • 我用这个InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(new File("termoAdesaoCartao.pdf"))); String charset = inputStreamReader.getEncoding(); 得到了我的charset 并返回ISO8859_1,现在控制台显示(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 但没有保存这封信。
      猜你喜欢
      • 1970-01-01
      • 2012-05-31
      • 1970-01-01
      • 1970-01-01
      • 2016-07-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多