Skip to content
Print Content Streams

Print Content Streams

A page’s content stream is the program that draws it: a flat sequence of operators with operands, in PostScript-like postfix order. Printing it is how you find out why something appears where it does, or which operator a renderer is choking on. UniPDF gives you the stream as a string, and contentstream turns that string into a list of operations you can walk.

Doing it

page, err := pdfReader.GetPage(pageNum)
if err != nil {
    return err
}

contents, err := page.GetAllContentStreams()
if err != nil {
    return err
}
fmt.Println(contents)

parser := contentstream.NewContentStreamParser(contents)
operations, err := parser.Parse()
if err != nil {
    return err
}

for i, op := range *operations {
    fmt.Printf("Operation %d: %s - Params: %v\n", i+1, op.Operand, op.Params)
}

Each ContentStreamOperation has an Operand string and a Params slice of core.PdfObject, so the parameters come back typed: /Font2 10 Tf parses to operand Tf with a PdfObjectName and a PdfObjectInteger.

Contents may be a single stream or an array of them, and the spec says the array is to be treated as if the streams were concatenated. GetContentStreams() returns them separately as []string; GetAllContentStreams() joins them with a space and is the one to use before parsing. Joining the slice yourself with no separator can fuse the last token of one stream to the first token of the next and change the meaning of the stream.

Limitations

The page content stream is not the whole page. Anything drawn through a form XObject - which is how templates, stamps and often headers are built - appears as a single Do operator, and its own content stream has to be fetched separately with resources.GetXObjectFormByName(name) and then GetContentStream(). Annotation appearance streams are separate too, and never appear here at all. Summarize images shows the recursive walk.

Parse returns the operations it managed to read together with any error, so a truncated or malformed stream still gives you a partial list to look at. Check the error, but do not discard the result.

Coordinates in the operators are in unrotated page space, before the page’s Rotate entry is applied, and relative to the media box origin rather than the top left corner. A page with a non-zero rotation or a media box that does not start at 0 0 will not have operator coordinates matching what you measure on screen.

Run the example

listContentStreams takes an input path and an optional page number, and prints the concatenated stream for that page followed by the full operator list. Without a page number it does this for every page, which gets long.

git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/analysis
go run pdf_print_content_streams.go input.pdf [page]

If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.

View the full source

Sample output

Page 1
Page 1 has 1 content streams:
q
1 0 0 1 61.2 720.8 cm
BT
0 0 0 rg
/Font2 10 Tf
[(page)] TJ
ET
Q

=== Full list
Operation 1: q - Params: []
Operation 2: cm - Params: [1 0 0 1 61.200000 720.800000]
Operation 3: BT - Params: []
Operation 4: rg - Params: [0 0 0]
Operation 5: Tf - Params: [Font2 10]
Operation 6: TJ - Params: [[page]]
Operation 7: ET - Params: []
Operation 8: Q - Params: []
Last updated on