iText或iTextSharp基本文本编辑

时间:2014-02-07 00:46:58

标签: c# pdf itext

我可以通过多种方式从PDF中的页面中提取文本:

String pageText = PdfTextExtractor.GetTextFromPage(reader, i);

这可用于获取页面上的任何文字。

可替换地:

byte[] contentBytes = iTextSharp.text.pdf.parser.ContentByteUtils.GetContentBytesForPage(reader, i);

可能性是无穷无尽的。

现在,我想删除/编辑某个字词,例如明确的单词,敏感的信息(在他们身上放置黑盒子显然是一个坏主意:)或者PDF中的任何内容(这只是简单的文本)。我可以使用上面的方法找到这个词。我可以算一下它的出现等等......

我不关心布局,或者PDF并不是真正意图以这种方式操纵。

我只想知道是否有一种机制可以让我以这种方式操纵PDF的原始内容。你可以说我正在寻找“SetContentBytesForPage()”......

3 个答案:

答案 0 :(得分:4)

如果要更改页面内容,则仅更改页面的内容流是不够的。页面可能包含对包含要删除的内容的表单XObject的引用。

次要问题包括图像。例如:假设您的文档包含已经过OCR的扫描文档。在这种情况下,仅删除(矢量)文本是不够的,您还需要操纵图像中的(像素)文本。

假设您的次要问题不存在,您需要采用双重方法:

  1. 从页面中获取内容作为文本,以检测哪些页面中有您要删除的名称或单词。
  2. 递归循环遍历所有内容流以查找该文本并重写那些没有该文本的内容流。
  3. 从你的问题,我认为你已经解决了问题1.解决问题2并不是那么微不足道。在我的书的第15章中,我有一个示例,其中提取文本返回“Hello World”,但当您查看内容流时,您会看到:

    BT
    /F1 12 Tf
    88.66 367 Td
    (ld) Tj
    -22 0 Td
    (Wor) Tj
    -15.33 0 Td
    (llo) Tj
    -15.33 0 Td
    (He) Tj
    ET
    

    在您从此流代码段中删除“Hello World”之前,您需要一些启发式方法,以便您的程序能够识别此语法中的文本。

    找到文本后,您需要重写该流。如需灵感,您可以查看itext-xtra包中的OCG remover functionality

    长话短说:如果您的PDF相对简单,那就是:可以在不同的内容流(页面内容和Form XObject内容)中轻松检测到文本,然后只需要在一些字符串操作后重写这些流

    我已经为您创建了一个名为ReplaceStream的简单示例,它将"Hello World"替换为PDF中的"HELLO WORLD"

    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);
        if (object instanceof PRStream) {
            PRStream stream = (PRStream)object;
            byte[] data = PdfReader.getStreamBytes(stream);
            stream.setData(new String(data).replace("Hello World", "HELLO WORLD").getBytes());
        }
        PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
        stamper.close();
        reader.close();
    }
    

    一些警告:

    • 我检查object是否为流。它也可以是流的数组。在这种情况下,您需要遍历该数组。
    • 我不检查是否为页面定义了表单XObject。
    • 我认为可以在PDF语法中轻松检测到Hello World
    • ...

    在现实生活中,PDF文件从未如此简单,并且随着文档中使用的每个特殊功能,项目的复杂性将大大增加。

答案 1 :(得分:3)

Bruno代码的C#等价物:

static void manipulatePdf(String src, String dest)
    {
        PdfReader reader = new PdfReader(src);
        PdfDictionary dict = reader.GetPageN(1);
        PdfObject pdfObject = dict.GetDirectObject(PdfName.CONTENTS);
        if (pdfObject.IsStream()) {
            PRStream stream = (PRStream)pdfObject;
            byte[] data = PdfReader.GetStreamBytes(stream);
            stream.SetData(System.Text.Encoding.ASCII.GetBytes(System.Text.Encoding.ASCII.GetString(data).Replace("Hello World", "HELLO WORLD")));
        }
        FileStream outStream = new FileStream(dest, FileMode.Create);
        PdfStamper stamper = new PdfStamper(reader, outStream);
        reader.Close();
    }

如果结果仍然包含错误,我会更新。

答案 2 :(得分:0)

在我之前的C#代码和Bruno的注释中,GetDirectObject(PdfName.CONTENTS)也可能返回一个数组而不是一个数据流:在我的特殊情况下,结果证明这是真的。

对于IsArray(),返回的PdfObject返回“true”。我检查过,数组元素都是PdfIndirectReference。

进一步了解API会产生两个有用的信息:

  1. PdfIndirectReference有一个“Number”属性,引导您到另一个PdfObject。
  2. 您可以使用reader.GetPdfObject(int ref)访问引用的对象,其中ref是IndirectReferenceObject的“Number”属性
  3. 从那里开始,您将获得一个新的PdfObject,您可以使用IsStream()进行检查,并根据之前发布的代码进行修改。

    所以它适用于此(请注意,这很快,很脏,但它适用于我的特定用途......):

          // Get the contents of my page...
          PdfObject pdfObject = pageDict.GetDirectObject(PdfName.CONTENTS);
    
          // Check that this is, in fact, an array or something else...
          if (pdfObject.IsArray())
          {
              PdfArray streamArray = pageDict.GetAsArray(PdfName.CONTENTS);
    
              for (int j = 0; j < streamArray.Size; j++)
                 {
                      PdfIndirectReference arrayEl = (PdfIndirectReference)streamArray[j];
    
                      PdfObject refdObj = reader.GetPdfObject(arrayEl.Number);
    
                      if (refdObj.IsStream())
                         {
                            PRStream stream = (PRStream)refdObj;
                            byte[] data = PdfReader.GetStreamBytes(stream);
                            stream.SetData(System.Text.Encoding.ASCII.GetBytes(System.Text.Encoding.ASCII.GetString(data).Replace(targetedText, newText)));
                         }
                  }
    
           }