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: []