【问题标题】:iText or iTextSharp rudimentary text editiText 或 iTextSharp 基本文本编辑
【发布时间】:2014-03-04 05:50:20
【问题描述】:

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

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

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

或者:

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

可能性无穷无尽。

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

我不关心布局,也不关心 PDF 并不是真的要以这种方式操作的事实。

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

【问题讨论】:

    标签: c# pdf itext


    【解决方案1】:

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

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

    假设你的次要问题不存在,你需要一个双重方法:

    1. 以文本形式从页面中获取内容,以检测哪些页面中有您要删除的名称或字词。
    2. 递归循环遍历所有内容流以查找该文本并在没有该文本的情况下重写这些内容流。

    根据您的问题,我假设您已经解决了问题 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
    

    在您从这个流 sn-p 中删除“Hello World”之前,您需要一些启发式方法,以便您的程序能够识别这种语法中的文本。

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

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

    我为您制作了一个名为 ReplaceStream 的简单示例,它将 PDF 中的 "Hello World" 替换为 "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 是否是一个流。它也可以是一个 array 流。在这种情况下,您需要遍历该数组。
    • 我不检查是否有为页面定义的表单 XObjects。
    • 我认为Hello World 可以在 PDF 语法中轻松检测到。
    • ...

    在现实生活中,PDF 从未如此简单,您的项目的复杂性会随着文档中使用的每个特殊功能而急剧增加。

    【讨论】:

    • 您好布鲁诺,感谢您的更新。我已经做了很多研究(顺便说一句,经常碰到你的帖子:),我确实意识到 PDF 是多么复杂。对我来说幸运的是,我的 PDF 中出现的大多数违规字符串看起来像这样:... (STRINGTOREMOVE) Tj ...我通过查看 PDFVole 中的原始对象来检查这一点。这就是为什么我认为完全摆脱字符串可能相对简单。但是,对于 iTextSharp API,对内容流进行“写入”访问的“正确方法”让我望而却步。我可以阅读所有内容,但修改和回写让我望而却步......
    • 好的,请稍等。我会给你举个例子(在 Java 中,但它会给你一个开始的地方)。
    • 我已经修改了我的答案。请注意,您还可以使用 iText RUPS 查看 PDF:itextpdf.com/product/itext_rups
    • 不错!我不知何故完全忽略了您可以在 PRStream 上调用 SetData() 的事实……我的错……我将在下面添加 C# 等效项。
    • 谢谢,我赞成。我会用它来做进一步的参考。
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      在我之前的 C# 代码和 Bruno 的评论中,GetDirectObject(PdfName.CONTENTS) 还不如返回一个数组而不是一个流:在我的特殊情况下,事实证明这是真的。

      p>

      返回的 PdfObject 为 IsArray() 返回了“真”。我查了一下,数组元素都是 PdfIndirectReference。

      进一步查看 API 会发现两个有用的信息:

      1. PdfIndirectReference 有一个“数字”属性,可将您带到另一个 PdfObject。
      2. 您可以使用 reader.GetPdfObject(int ref) 获取引用的对象,其中 ref 是 IndirectReferenceObject 的“数字”属性

      从那里开始,您将获得一个新的 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)));
                           }
                    }
      
             }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-09-01
        • 2021-09-18
        • 1970-01-01
        • 2017-01-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多