首页 文章

使用zlib从pdf中提取文本

提问于
浏览
0

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

  • 3

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

    • 将重新压缩的流写入输出文件后,添加“\ nendstr”并继续输入此字符串的大小,7,输入缓冲区中源流末尾之外的字符,最有可能阻止查看“流“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"关键字之间添加行尾,参见 . 第7.3.8.1节:

    在数据之后和endstream之前应该有一个行尾标记

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

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

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

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

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

    缺少一些文字或图形 .

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

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

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

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

    • 您假设每个PDF流都被缩小 . 这种假设是错误的,参见PDF specification中的表5 .

    您的代码基本上会丢弃它无法膨胀的所有流 . 这也可能是您观察到这一点的原因

    缺少一些文字或图形 .

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

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

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

  • 2

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

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

  • 1

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

    这是指向http://www.adobe.com/devnet/pdf/pdf_reference.html的链接

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

相关问题