如何 decompress/deflate PDF 流

How to decompress/deflate PDF Stream

使用 2016-W4 pdf,它有 2 个大流 (第 1 和第 2 页),以及一堆其他对象和较小的流。我正在尝试缩小流,以处理源数据,但我很挣扎。我只能得到损坏的输入和无效的校验和错误。

我已经编写了一个测试脚本来帮助调试,并从文件中提取了较小的流来进行测试。

这里有来自原始 pdf 的 2 个流,以及它们的长度对象:

流 1:

149 0 obj
<< /Length 150 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType
1 /BBox [0 0 8 8] /Resources 151 0 R >>
stream
x+TT(T0�B ,JUWÈS0Ð37±402V(NFJS�þ¶
«
endstream
endobj
150 0 obj
42
endobj

流 2

142 0 obj
<< /Length 143 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType
1 /BBox [0 0 0 0] /Resources 144 0 R >>
stream
x+T�ç�ã
endstream
endobj
143 0 obj
11
endobj

我只将 stream 内容复制到 Vim 中的新文件中(不包括 stream 之后和 [=17= 之前的回车 returns ]).

我都试过了:

我已将以下内容的流转换为 []byte

package main

import (
    "bytes"
    "compress/flate"
    "compress/gzip"
    "compress/zlib"
    "fmt"
    "io"
    "os"
)

var (
    flateReaderFn = func(r io.Reader) (io.ReadCloser, error) { return flate.NewReader(r), nil }
    zlibReaderFn  = func(r io.Reader) (io.ReadCloser, error) { return zlib.NewReader(r) }
)

func deflate(b []byte, skip, length int, newReader func(io.Reader) (io.ReadCloser, error)) {
    // rfc-1950
    // --------
    //   First 2 bytes
    //   [120, 1] - CMF, FLG
    //
    //   CMF: 120
    //     0111 1000
    //     ↑    ↑
    //     |    CM(8) = deflate compression method
    //     CINFO(7)   = 32k LZ77 window size
    //
    //   FLG: 1
    //     0001 ← FCHECK
    //            (CMF*256 + FLG) % 31 == 0
    //             120 * 256 + 1 = 30721
    //                             30721 % 31 == 0

    stream := bytes.NewReader(b[skip:length])
    r, err := newReader(stream)
    if err != nil {
        fmt.Println("\nfailed to create reader,", err)
        return
    }

    n, err := io.Copy(os.Stdout, r)
    if err != nil {
        if n > 0 {
            fmt.Print("\n")
        }
        fmt.Println("\nfailed to write contents from reader,", err)
        return
    }
    fmt.Printf("%d bytes written\n", n)
    r.Close()
}

func main() {
    //readerFn, skip := flateReaderFn, 2 // compress/flate RFC-1951, ignore first 2 bytes
    readerFn, skip := zlibReaderFn, 0 // compress/zlib RFC-1950, ignore nothing

    //                                                                                                ⤹ This is where the error occurs: `flate: corrupt input before offset 19`.
    stream1 := []byte{120, 1, 43, 84, 8, 84, 40, 84, 48, 0, 66, 11, 32, 44, 74, 85, 8, 87, 195, 136, 83, 48, 195, 144, 51, 55, 194, 177, 52, 48, 50, 86, 40, 78, 70, 194, 150, 74, 83, 8, 4, 0, 195, 190, 194, 182, 10, 194, 171, 10}
    stream2 := []byte{120, 1, 43, 84, 8, 4, 0, 1, 195, 167, 0, 195, 163, 10}

    fmt.Println("----------------------------------------\nStream 1:")
    deflate(stream1, skip, 42, readerFn) // flate: corrupt input before offset 19

    fmt.Println("----------------------------------------\nStream 2:")
    deflate(stream2, skip, 11, readerFn) // invalid checksum
}

我确定我在某处做错了什么,只是我不太明白。

(pdf 确实会在查看器中打开)

好了,表白时间...

我太专注于理解 deflate 以至于我完全忽略了 Vim 没有将流内容正确保存到新文件中这一事实。所以我花了很多时间阅读 RFC,并深入了解 Go compress/... 包的内部结构,假设问题出在我的代码上。

在我发布问题后不久,我尝试阅读整个 PDF,找到 stream/endstream 位置,然后通过 deflate 推送它。当我看到内容在屏幕上滚动时,我意识到我犯了一个愚蠢的错误。

+1 @icza,这正是我的问题。

结果很好,因为我对整个过程有了更好的理解,而不是第一次绕圈就成功了。

二进制数据应该永远不会从文本编辑器中复制/保存。可能会有成功的情况,只是火上浇油

您最终从 PDF "mined out" 获得的数据很可能与 在 PDF 中的实际数据不同。您应该从十六进制编辑器中获取数据(例如尝试 hecate 获取新的东西),或者编写一个简单的应用程序来保存它(严格将文件作为二进制文件处理)。

提示 #1:

显示的二进制数据分布在多行中。二进制数据不包含回车returns,那是一个文本控件。如果是,那意味着编辑器 did 将其解释为文本,因此一些代码/字符 "consumed" 开始新行。多个序列可能被解释为相同的换行符(例如 \n\r\n)。通过排除它们,您已经处于数据丢失状态,通过包括它们,您可能已经有了不同的序列。并且如果将数据解释并显示为文本,则可能会出现更多问题,因为控制字符较多,并且显示时可能不会显示某些字符。

提示#2:

当使用flateReaderFn时,解码第二个例子成功(无错误完成)。这意味着 "you were barking up the right tree",但成功取决于实际数据是什么以及文本编辑器在多大程度上 "distorted"。

根据使用的过滤器,从 PDF 中提取对象可能会很棘手。过滤器还可以有其他需要正确处理的选项。

对于有兴趣提取对象而不关心过程的底层细节的人。

要从 PDF 中获取单个对象并对其进行解码,可以按如下方式完成:

package main

import (
    "fmt"
    "os"
    "strconv"

    "github.com/unidoc/unipdf/v3/core"
    "github.com/unidoc/unipdf/v3/model"
)


func main() {
    objNum := 149 // Get object 149
    err := inspectPdfObject("input.pdf", objNum)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        os.Exit(1)
    }
}

func inspectPdfObject(inputPath string, objNum int) error {
    f, err := os.Open(inputPath)
    if err != nil {
        return err
    }

    defer f.Close()

    pdfReader, err := model.NewPdfReader(f)
    if err != nil {
        return err
    }

    isEncrypted, err := pdfReader.IsEncrypted()
    if err != nil {
        return err
    }

    if isEncrypted {
        // If encrypted, try decrypting with an empty one.
        // Can also specify a user/owner password here by modifying the line below.
        auth, err := pdfReader.Decrypt([]byte(""))
        if err != nil {
            fmt.Printf("Decryption error: %v\n", err)
            return err
        }
        if !auth {
            fmt.Println(" This file is encrypted with opening password. Modify the code to specify the password.")
            return nil
        }
    }

    obj, err := pdfReader.GetIndirectObjectByNumber(objNum)
    if err != nil {
        return err
    }

    fmt.Printf("Object %d: %s\n", objNum, obj.String())

    if stream, is := obj.(*core.PdfObjectStream); is {
        decoded, err := core.DecodeStream(stream)
        if err != nil {
            return err
        }
        fmt.Printf("Decoded:\n%s", decoded)
    } else if indObj, is := obj.(*core.PdfIndirectObject); is {
        fmt.Printf("%T\n", indObj.PdfObject)
        fmt.Printf("%s\n", indObj.PdfObject.String())
    }

    return nil
}

完整示例:pdf_get_object.go

披露:我是UniPDF的原始开发者。