使用zlib从pdf中提取文本

时间:2013-06-10 15:16:48

标签: c++ objective-c macos pdf zlib

我正在使用该函数在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);

3 个答案:

答案 0 :(得分:3)

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

假设您有一个包含内容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流中用另一个String替换一个String,你就会看几周的额外编程。

答案 1 :(得分:2)

您的代码中存在多个问题,其效果在您对Bruno回答的评论中提供的示例newpdf.pdf中可见:

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

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

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

    一个。在PDF中有三种类型的有效行尾标记,一个LINE FEED(0x0A),一个CARRIAGE RETURN(0x0D),或CARRIAGE RETURN和LINE FEED对(0x0D 0x0A),以及这些中的任何一个结束行标记可以在" endstream"之前。在输入缓冲区;在上面进一步计算压缩流结束的代码中,忽略单个CARRIAGE RETURN变量,在这里忽略2字节变量;而且:

    PDF specification甚至不需要,只是建议在流的结尾和" endstream"之间添加一个行尾。关键字,cf。第7.3.8.1节:

      

    数据之后和 endstream

    之前应该有一个行尾标记

    这已经破坏了示例文件中的第一个流,在该流文件中源文件没有行尾标记,因此,您的结果将替换原始" endstream"用" \ nendstram"。这实际上经常在您的样本中发生。

  2. 您完全忽略其字典中的PDF流包含一个包含流长度的条目,参见PDF specification中的第7.3.8.2节:

      

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

    即使您只进行解压缩和重新压缩,您的操作也可能会改变压缩流的长度。因此,您必须更新长度条目。这无疑使你的任务变得更加困难,因为该字典在之前是流。此外,在类似源文件的情况下,该条目甚至可能不直接包含该值,而是在文件中引用间接对象其他地方

    这会破坏文件中的第二个流,声称它的长度为8150字节,但相差大约200个字节。任何PDF查看器都可能认为文件中该流的内容只有8150字节长,因此忽略那些尾随200字节的内容。这很可能是你观察到

    的原因
      

    缺少某些文字或图片。

  3. 您完全忽略PDF具有交叉引用表或流(或者甚至可能是它们的链),参见PDF specification中的第7.5.4节:

      

    交叉引用表包含允许随机访问文件中的间接对象的信息,以便无需读取整个文件即可找到任何特定对象。该表应包含每个间接对象的单行条目,指定该对象在文件正文中的字节偏移量。 (从PDF 1.5开始,部分或全部交叉引用信息也可以包含在交叉引用流中;参见7.5.8,"交叉引用流。")

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

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

  4. 您假设每个PDF流都已缩减。这种假设是错误的,参见PDF specification中的表格5。

    您的代码基本上会丢弃所有无法通知的流。这也可能是您观察到

    的原因
      

    缺少某些文字或图片。

  5. 您假设序列" stream"在PDF中明确指示流的开始。这是错误的,该序列也可以在其他环境中轻松使用。

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

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

答案 2 :(得分:1)

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

此处是指向http://www.adobe.com/devnet/pdf/pdf_reference.html

的链接

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