iText或iTextSharp基本文本编辑

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

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

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

或者:

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

可能性是无穷无尽的。

现在我想删除/编辑某个单词,例如显式单词,敏感信息(将黑盒放在它们上面显然是一个坏主意:)或者PDF中的任何单词(简单和仅文本)。 我可以使用上面的方法找到这个词。 我可以算一下它的出现等等……

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

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

如果要更改页面的内容,则仅更改页面的内容流是不够的。 页面可能包含对包含要删除的内容的表单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 

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

找到文本后,需要重写流。 为了获得灵感,您可以查看itext-xtra包中的OCG移除function 。

简而言之:如果您的PDF相对简单,那就是:可以在不同的内容流(页面内容和Form 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是否是一个流。 它也可以是一系列流。 在这种情况下,您需要遍历该数组。
  • 我不检查是否为页面定义了表单XObjects。
  • 我假设可以在PDF语法中轻松检测到Hello World

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

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

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

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

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

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

  1. PdfIndirectReference有一个“Number”属性,引导你到另一个PdfObject。
  2. 您可以使用reader.GetPdfObject(int ref)访问引用的对象,其中ref是IndirectReferenceObject的“Number”属性

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