使用zlib从pdf中提取文本

Extract text from pdf using zlib

本文关键字:提取 取文本 pdf zlib 使用      更新时间:2023-10-16

我使用该函数在pdf文件中查找文本并将该文本替换为另一个文本。问题是,当我进行膨胀,然后更改文本和收缩时,在最终的pdf中有时会遗漏一些文本或图形。这是一个错误在我的代码或zlib库不支持这个压缩或什么?

// Open the PDF source file:
FILE *pdfFile = fopen([sourceFile cStringUsingEncoding:NSUTF8StringEncoding], "rb");
if (pdfFile) {
// Get the file length:
int fseekres = fseek(pdfFile, 0, SEEK_END);
if (fseekres != 0) {
fclose(pdfFile);
return nil;
}
long filelen = ftell(pdfFile);
fseekres = fseek(pdfFile, 0, SEEK_SET);
if (fseekres != 0) {
fclose(pdfFile);
return nil;
}
char *buffer = new char[filelen];
size_t actualread = fread(buffer, filelen, 1, pdfFile);
if (actualread != 1) {
fclose(pdfFile);
return nil;
}
bool morestreams = true;
while (morestreams) {
size_t streamstart = [self findStringInBuffer:buffer search:(char *)"stream" buffersize:filelen];
size_t streamend = [self findStringInBuffer:buffer search:(char *)"endstream" buffersize:filelen];
[self saveFile:buffer len:streamstart + 7 fileName:[destFile cStringUsingEncoding:NSUTF8StringEncoding]];
if (streamstart > 0 && streamend > streamstart) {
streamstart += 6;
if (buffer[streamstart] == 0x0d && buffer[streamstart + 1] == 0x0a) {
streamstart += 2;
} else if (buffer[streamstart] == 0x0a) {
streamstart++;
}
if (buffer[streamend - 2] == 0x0d && buffer[streamend - 1] == 0x0a) {
streamend -= 2;
} else if (buffer[streamend - 1] == 0x0a) {
streamend--;
}
size_t outsize = (streamend - streamstart) * 10;
char *output = new char[outsize];
z_stream zstrm;
zstrm.zalloc = Z_NULL;
zstrm.zfree = Z_NULL;
zstrm.opaque = Z_NULL;
zstrm.avail_in = (uint)(streamend - streamstart + 1);
zstrm.avail_out = (uint)outsize;
zstrm.next_in = (Bytef *)(buffer + streamstart);
zstrm.next_out = (Bytef *)output;
int rsti = inflateInit(&zstrm);
if (rsti == Z_OK) {
int rst2 = inflate(&zstrm, Z_FINISH);
inflateEnd(&zstrm);
if (rst2 >= 0) {
size_t totout = zstrm.total_out;
//search and replace text code here
size_t coutsize = (streamend - streamstart + 1) * 10;
char *coutput = new char[coutsize];
z_stream c_stream;
c_stream.zalloc = Z_NULL;
c_stream.zfree = Z_NULL;
c_stream.opaque = Z_NULL;
c_stream.total_out = 0;
c_stream.avail_in = (uint)totout;
c_stream.avail_out = (uint)coutsize;
c_stream.next_in = (Bytef *)output;
c_stream.next_out = (Bytef *)coutput;
rsti = deflateInit(&c_stream, Z_DEFAULT_COMPRESSION);
if (rsti == Z_OK) {
rsti = deflate(&c_stream, Z_FINISH);
deflateEnd(&c_stream);
if (rsti >= 0) {
[self saveFile:coutput len:c_stream.total_out fileName:[destFile cStringUsingEncoding:NSUTF8StringEncoding]];
}
}
delete [] coutput; coutput = 0;
[self saveFile:(char *)"nendstr" len:7 fileName:[destFile cStringUsingEncoding:NSUTF8StringEncoding]];
}
}
delete[] output; output = 0;
buffer += streamend + 7;
filelen = filelen - (streamend + 7);
} else {
morestreams = false;
}
}
[self saveFile:buffer len:filelen fileName:[destFile cStringUsingEncoding:NSUTF8StringEncoding]];
}
fclose(pdfFile);

你认为文本可以在内容流中找到的假设是错误的。

假设您有一个内容为Hello World的PDF。然后你可以有一个像这样的流:

q
BT
36 806 Td
0 -18 Td
/F1 12 Tf
(Hello World!)Tj
0 0 Td
ET
Q

但是它也可以是这样的:

Q
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
q

您的代码将在前一个流中检测到单词"Hello",但在后一个流中将忽略它。

PDF查看器将以完全相同的方式呈现两个流:您将在完全相同的位置看到"Hello World"。

有时字符串被分成更小的部分,你经常会发现文本数组来引入字距,等等…这是PDF中的所有标准做法。

PDF不是一种适合编辑的格式。我并不是说这是不可能的,但是如果你想要满足PDF流中一个字符串替换为另一个字符串的要求,你需要花费几周的额外编程时间。

您的代码中有多个问题,其影响在您在Bruno的回答的注释中提供的示例newpdf.pdf中可见:

  1. 将重新压缩的流写入输出文件后,在输入缓冲区中添加"nendstr"并继续这个字符串的大小,7个字符,超出源流的末尾,最有可能防止看到"endstream"中的"stream"作为下一个流的开始:

    [self saveFile:(char *)"nendstr" len:7 fileName:[destFile cStringUsingEncoding:NSUTF8StringEncoding]];
    [...]
    buffer += streamend + 7;
    

    添加该字符串的问题是,您假设输入缓冲区中的"结束流"前面正好有一个NEWLINE (0x0A)字节。这个假设是错误的,因为

    。在PDF中,有三种类型的有效行结束标记,单个换行(0x0A),单个回车(0x0D)或回车和换行对(0x0D 0x0A),并且这些行结束标记中的任何一种都可以位于输入缓冲区中的"结束流"之前;在上面计算压缩流结束的代码中,您忽略了单个CARRIAGE RETURN变量,这里您忽略了2字节的变量;此外:

    b。PDF规范甚至不要求,而只是建议在流的结尾和"endstream"关键字之间添加一个结束行,参见章节7.3.8.1:

    数据之后和endstream之前应该有一个行结束标记

    这已经中断了示例文件中的第一个流,其中源文件在那里没有行结束标记,因此您的结果将原始的"endstream"替换为" nendstream"。这实际上在您的示例中经常发生。

  2. 你完全忽略了PDF流在它的字典中包含一个包含流长度的条目,参见PDF规范的第7.3.8.2节:

    每个流字典都有一个长度指示PDF文件中有多少字节用于流的数据的条目。

    您的操作,即使您只是解压缩和重新压缩,也可能改变压缩流的长度。因此,您必须更新Length条目。不可否认,这使您的任务变得更加困难,因为该字典位于流之前。此外,在像源文件这样的情况下,该条目甚至可能不直接包含值,而是引用文件中某处的间接对象

    这打破了文件中的第二个流,它声称它是8150字节长,但实际上是200字节长。任何PDF查看器都可以假设文件中该流的内容只有8150字节长,因此忽略后面200字节的内容。这很可能就是为什么您观察到

    部分文字或图形缺失

  3. 你完全忽略了PDF有一个交叉引用表或流(甚至可能是它们的链),参见PDF规范中的7.5.4节:

    交叉引用表包含允许随机访问文件中的间接对象的信息,这样就不需要读取整个文件来定位任何特定的对象。该表应为每个间接对象包含一行条目,指定该对象在文件主体中的字节偏移量。(从PDF 1.5开始,部分或全部的交叉引用信息可以包含在交叉引用流中;参见7.5.8,"交叉引用流")

    您的操作,即使您只是解压缩和重新压缩,也可能改变压缩流的长度。因此,您必须更新交叉引用表中所有以下对象的偏移量。

    由于结果文件中第二个流的大小已经不同,因此该文件中只有极少数交叉引用项是正确的。

  4. 您假设每个PDF流都被压缩。这个假设是错误的,参见PDF规范中的表5。

    你的代码基本上放弃了所有它不能膨胀的流。这也可能是您观察到

    部分文字或图形缺失

  5. 您假设PDF中的序列"stream"明确表示流的开始。这是错误的,该序列也可以很容易地在其他上下文中使用。

  6. 您假设PDF中流开始后的第一个序列"endstream"明确表示该流的结束。这是错误的,该序列也可能是流内容的一部分。必须使用Length的值流字典中的条目。

此外,您似乎假设您遇到的每个流仍然用于生成的PDF。事实并非如此。特别是在增量更新的情况下(参见PDF规范中的第7.5.6节),文件中可能会有许多对象不再使用。虽然这并不一定会破坏结果文件的语法,但您的更改(如果它们相互依赖)在语义上是不正确的。

我想你必须阅读文本如何存储在PDF文件中,

这里是一个链接到规范http://www.adobe.com/devnet/pdf/pdf_reference.html

第九节文本是理解的关键。