如何解压/解缩PDF流

3
使用2016-W4 pdf,其中有2个大流(第1页和第2页),以及许多其他对象和较小的流。我试图压缩流以便处理源数据,但是遇到了困难。我只能得到损坏的输入和无效的校验和错误。
我编写了一个测试脚本来帮助调试,并从文件中提取出较小的流进行测试。
这里是原始pdf中的两个流和它们的长度对象:
流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编辑器进行操作(在streamendstream之间排除回车符)。

我尝试过以下两种方式:

  • compress/flate (rfc-1951) – (删除前两个字节(CMF, FLG))
  • compress/zlib (rfc-1950)

我将流转换为[]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文件可以在查看器中打开)


1
你确定 Vim 显示并复制了正确的字节吗?你应该从十六进制编辑器(例如检查 Hecate)中获取数据。 - icza
@icza - 如果您想将其作为答案发布,我会给您信用 =) - Justin
3个回答

4

二进制数据永远不应该从文本编辑器中复制或保存。虽然有些情况下可能会成功,但这只会雪上加霜。

你最终从PDF中“挖掘出来”的数据很可能与实际存在于PDF中的数据不同。你应该从十六进制编辑器中获取数据(例如尝试使用hecate),或编写一个严格处理文件为二进制的简单应用程序来保存它。

提示1:

二进制数据显示在多行中。二进制数据不包含回车符,那是文本控制字符。如果有回车符,那就意味着编辑器将其解释为文本,因此某些代码/字符被“消耗”以开始新行。多个序列可能被解释为相同的换行符(例如\n\r\n)。通过排除它们,您已经存在数据损失,通过包含它们,您可能已经有了不同的序列。如果数据被解释并显示为文本,则可能会出现更多问题,因为有更多的控制字符,并且一些字符在显示时可能不会出现。

提示2:

当使用flateReaderFn时,第二个示例的解码成功(完成而没有错误)。这意味着“你走上了正确的道路”,但成功取决于实际数据是什么以及文本编辑器对其进行了多大程度的“扭曲”。


2

好的,坦白说...

我一直在尝试理解deflate压缩算法,却完全忽略了Vim没有正确地将流内容保存到新文件中这个事实。所以我花了很多时间阅读RFC文档,并深入挖掘Go compress/...包的内部,认为问题出在我的代码上。

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

+1 @icza,那正是我的问题所在。

最终还是好的,因为我对整个过程有了更好的理解,如果一开始就成功了,我可能无法获得这样的收获。


0

从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的原始开发人员。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接