Help us shape the future of Go! Take the 2026 Go Developer Survey

reader

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2026 License: BSD-3-Clause Imports: 17 Imported by: 17

README

reader

CI Go Reference Go Report Card Coverage

A pure-Go, zero-C PDF reader — the parsing half of go-pdfkit. Where pdfkit authors new PDF 1.7, this module takes existing PDF bytes apart: lexer, object model, cross-reference tables and streams, stream filters, the standard security handler, the page tree, and content streams. It also writes the same object graph back out, which is what every operation on an existing file needs. Nothing outside the Go standard library is required, so it builds for GOOS=js/wasm and every 64-bit architecture the fleet targets.

Status

Landing in waves. This module currently provides the object layer and the document structure:

  • Lexer — the full PDF object syntax: integers and reals in the shapes producers actually write, literal strings with their escapes, hex strings, names with #xx escapes, arrays, dictionaries, streams, and keywords.
  • Objects — Null, Bool, Integer, Real, String, Name, Array, Dict, Stream, and indirect Ref, with the N G R / N G obj lookahead resolved the way the specification requires.
  • Filters — FlateDecode, LZWDecode (with PDF's EarlyChange, which the standard library's LZW does not implement), ASCIIHexDecode, ASCII85Decode, RunLengthDecode, plus the PNG and TIFF predictors. Image filters (DCTDecode, JPXDecode, CCITTFaxDecode, JBIG2Decode) are reported rather than applied, so an image consumer gets the encoded bytes.
  • Cross-references — classic tables, cross-reference streams, object streams, /Prev chains, and the /XRefStm of a hybrid file, newest definition winning.
  • Repair — a file whose tables are missing, truncated or simply wrong is rebuilt by scanning it for object headers, trailers and, failing those, for a catalogue; a file that kept its pages but lost its catalogue gets one. This is not an exceptional path: it is what makes a reader usable.
  • Documents — Open, object resolution with cycle and recursion guards, the trailer, the catalogue, and the page tree with the four attributes a page inherits from its ancestors.
  • Encryption — the standard security handler in every revision, both ways: reading RC4 at 40 and 128 bits, AESV2, and the AES-256 of /R 5 and /R 6, with crypt filters, /Identity and /EncryptMetadata; and writing AES-256 or AES-128, with permissions, a user password and an owner password. A password is tried as the user password and as the owner password, and Open uses the empty one, so a file protected only against editing opens with no password at all. and steps over rubbish rather than losing the operations around it, with inline images read whole. Where an inline image ends is the one genuinely ambiguous thing in a content stream, since its data may spell EI itself; the length is computed from the image's own geometry where it can be, and otherwise every candidate EI is tried until the data before one actually decodes.
  • Writing — the exact inverse of the parser: objects rendered in PDF syntax with stable dictionary ordering and strings in whichever of the two forms is shorter, and a Writer that copies whole object graphs out of one or more documents, renumbering as it goes, then lays down a cross-reference table and a trailer. NewPackedWriter instead puts what it can into compressed object streams and ends in a cross-reference stream, which is what makes a file small. Measured against a corpus of 118 863 real PDFs — Matplotlib, cairo, pdfTeX, Ghostscript, Adobe, R, Apache FOP, PDF 1.3 through 1.7 — Open succeeds on 118 833 of them and finds 138 337 pages, in 8 seconds, with no panics. Of the thirty it refuses, twenty-seven are PNG files with a .pdf extension, one is PostScript, and two are PDFs truncated past recovery.

The one encrypted PDF the corpus holds — /V 4 /R 4, written by macOS Quartz — decrypts to readable metadata and a valid content stream, which is the only genuinely independent check of the key derivation there is here; the other revisions are round-tripped against a test encryptor written from the same algorithms.

The content-stream tokeniser reads 1 536 769 753 operations across those 138 337 pages in a minute and a half, with no panics, no page failing to decode, and no operator outside the seventy the format defines — beyond four arc and six nan written by a producer that was simply wrong.

Rewriting is checked the same way: every one of the 118 833 files that open is copied object by object into a new file, which is then re-read and compared on page count, media boxes and the bytes of every content stream. All 118 833 match, 35.5 GB in and 35.2 GB out, in twenty-eight seconds — and all 118 833 match again when the packed writer is used instead, which brings the output down to 94.6% of the input.

Next wave: the operations built on all of this.

Install

go get github.com/go-pdfkit/reader

Testing

go test -covermode=set ./...

CI gates on exact 100% statement coverage, go vet, and a cross-compile across linux/{amd64,arm64,riscv64,loong64,ppc64le,s390x}, js/wasm, darwin/arm64 and windows/amd64.

License

BSD-3-Clause — see LICENSE. Copyright the go-pdfkit/reader authors.

Documentation

Overview

Package reader takes existing PDF bytes apart: the object syntax, the cross-reference machinery, stream filters, and the document structure built on top of them. It is the parsing counterpart of github.com/go-pdfkit/pdfkit, which writes PDF.

Nothing outside the Go standard library is used, so the package builds for js/wasm and for every architecture the fleet targets.

Index

Constants

This section is empty.

Variables

View Source
var ErrUnsupportedEncryption = errors.New("reader: unsupported security handler")

ErrUnsupportedEncryption is returned for a security handler this package does not implement — anything other than the standard one.

View Source
var ErrWrongPassword = errors.New("reader: the password does not open this file")

ErrWrongPassword is returned when a file is encrypted and neither the password given nor the empty one opens it.

Functions

func AppendObject

func AppendObject(dst []byte, o Object) []byte

AppendObject writes an object in PDF syntax, appending to dst. Dictionary keys are written in order, so the same object always produces the same bytes — which is what makes a rewritten file comparable with the one it came from.

func FormatObject

func FormatObject(o Object) []byte

FormatObject renders an object in PDF syntax.

func ImageFilter

func ImageFilter(n Name) bool

ImageFilter reports whether a filter yields an encoded image rather than a byte stream. Decode stops at one of these and hands the caller the still encoded bytes, because decoding them is an image decoder's job.

func ParseIndirectObject

func ParseIndirectObject(b []byte, resolve Resolver) (Ref, Object, int, error)

ParseIndirectObject parses "N G obj … endobj" from the start of b, returning the reference it defines, the object itself, and how many bytes it consumed. A stream's /Length may be an indirect reference; resolve supplies it, and a nil or unhelpful resolve falls back to scanning for the endstream keyword.

func ToBool

func ToBool(o Object) (bool, bool)

ToBool reports the value of a boolean object.

func ToFloat

func ToFloat(o Object) (float64, bool)

ToFloat reports the value of any numeric object.

func ToInt

func ToInt(o Object) (int64, bool)

ToInt reports the value of an integer object, and of a real whose value is a whole number — files do write "3.0" where an integer belongs.

func ToString

func ToString(o Object) ([]byte, bool)

ToString reports the bytes of a string object.

Types

type Array

type Array []Object

Array is a PDF array.

func ToArray

func ToArray(o Object) (Array, bool)

ToArray reports the elements of an array object.

func (Array) Kind

func (Array) Kind() Kind

Kind implements Object.

type Bool

type Bool bool

Bool is a PDF boolean.

func (Bool) Kind

func (Bool) Kind() Kind

Kind implements Object.

type ContentScanner

type ContentScanner struct {
	// contains filtered or unexported fields
}

A ContentScanner walks a content stream one operation at a time. A stream with rubbish in it yields the operations around the rubbish rather than nothing at all, which is what a renderer needs; ContentScanner.Err reports the first thing that did not parse.

func NewContentScanner

func NewContentScanner(data []byte) *ContentScanner

NewContentScanner reads operations from a decoded content stream.

func (*ContentScanner) Err

func (s *ContentScanner) Err() error

Err reports the first malformed token the scan stepped over, if any.

func (*ContentScanner) Next

func (s *ContentScanner) Next() (Operation, bool)

Next returns the next operation, and false once the stream is exhausted.

type Dict

type Dict map[Name]Object

Dict is a PDF dictionary.

func ToDict

func ToDict(o Object) (Dict, bool)

ToDict reports the dictionary of a dictionary object, or a stream's own dictionary — a stream is a dictionary everywhere a dictionary is expected.

func (Dict) Get

func (d Dict) Get(k Name) Object

Get returns the entry, or Null when it is absent. The value may still be a Ref; use Resolve to follow it.

func (Dict) Kind

func (Dict) Kind() Kind

Kind implements Object.

type Document

type Document struct {
	// contains filtered or unexported fields
}

A Document is a parsed PDF file: its cross-reference information, its trailer, and lazy access to every object in it.

func Open

func Open(b []byte) (*Document, error)

Open parses the cross-reference information of a PDF file held in memory, using the empty password. A file whose tables are damaged is rebuilt by scanning it, so Open fails only when there is no usable document structure at all, or when the file is encrypted and the empty password does not open it — OpenWithPassword then does.

func OpenWithPassword

func OpenWithPassword(b []byte, password string) (*Document, error)

OpenWithPassword is Open with a password to try. The password is tried both as the user password and as the owner password, the empty one as well.

func (*Document) Catalog

func (d *Document) Catalog() (Dict, error)

Catalog returns the document catalogue the trailer's /Root names.

func (*Document) DecodeStream

func (d *Document) DecodeStream(s *Stream) ([]byte, Name, error)

DecodeStream applies a stream's filter chain, resolving any indirect decode parameters against this document.

func (*Document) Encrypted

func (d *Document) Encrypted() bool

Encrypted reports whether the file declares an /Encrypt dictionary.

func (*Document) Get

func (d *Document) Get(ref Ref) (Object, error)

Get returns the object an indirect reference names, or Null when the file does not define it.

func (*Document) GetDict

func (d *Document) GetDict(from Dict, key Name) (Dict, bool)

GetDict resolves an entry of a dictionary to a dictionary.

func (*Document) Page

func (d *Document) Page(i int) (Dict, error)

Page returns the i'th page's dictionary, counting from one, with the attributes it inherits from its ancestors filled in.

func (*Document) PageContent

func (d *Document) PageContent(i int) ([]byte, error)

PageContent returns the decoded content stream of the i'th page, counting from one. A page whose /Contents is an array has its streams joined, as the specification requires, with a newline between them.

func (*Document) PageCount

func (d *Document) PageCount() int

PageCount reports how many pages the document has.

func (*Document) PageOperations

func (d *Document) PageOperations(i int) ([]Operation, error)

PageOperations tokenises the i'th page's content stream, counting from one.

func (*Document) PageRef

func (d *Document) PageRef(i int) (Ref, bool)

PageRef returns the reference of the i'th page, counting from one.

func (*Document) Repaired

func (d *Document) Repaired() bool

Repaired reports whether the document's structure was rebuilt by scanning the file rather than read from its cross-reference tables.

func (*Document) Resolve

func (d *Document) Resolve(o Object) (Object, error)

Resolve follows indirect references until it reaches a direct object.

func (*Document) Resolver

func (d *Document) Resolver() Resolver

Resolver returns the function that turns this document's indirect references into objects, for Resolve and Decode.

func (*Document) Trailer

func (d *Document) Trailer() Dict

Trailer returns the file's trailer dictionary, the newest value of every key across the chain of cross-reference sections.

func (*Document) Version

func (d *Document) Version() string

Version reports the PDF version the header declares, "1.7" and the like.

type Encryption added in v0.3.0

type Encryption struct {
	UserPassword  string
	OwnerPassword string
	Permissions   Permissions

	// AES128 asks for the older method, which readers before 2008 understand.
	// The default is AES-256, which is what a file should use today.
	AES128 bool
}

An Encryption says how a file is to be protected.

Two people can open it: whoever knows the user password, subject to the permissions, and whoever knows the owner password, subject to nothing. An empty user password means the file opens without one and the permissions are all it says.

type InlineImage

type InlineImage struct {
	Dict Dict
	Raw  []byte
}

An InlineImage is the dictionary and the still-encoded bytes of a BI … ID … EI sequence. Its dictionary uses the abbreviated keys inline images are written with; InlineImage.Expanded gives the ordinary spelling.

func (*InlineImage) Expanded

func (im *InlineImage) Expanded() Dict

Expanded returns the image's dictionary with the abbreviated keys and colour space names written out, so it can be read like any image XObject.

type Integer

type Integer int64

Integer is a PDF integer. Numbers too large for an int64 are parsed as Real, which is what every other reader does.

func (Integer) Kind

func (Integer) Kind() Kind

Kind implements Object.

type Kind

type Kind uint8

Kind names the eight basic PDF object types plus the two the file format adds on top of them: streams and indirect references.

const (
	KindNull Kind = iota
	KindBool
	KindInteger
	KindReal
	KindString
	KindName
	KindArray
	KindDict
	KindStream
	KindRef
)

The object kinds. Object.Kind reports one of these.

func (Kind) String

func (k Kind) String() string

String names the kind, for error messages.

type Name

type Name string

Name is a PDF name with its #xx escapes resolved and the leading slash dropped, so /Type is Name("Type").

func Decode

func Decode(d Dict, raw []byte, resolve Resolver) ([]byte, Name, error)

Decode applies a stream dictionary's filter chain to raw. It returns the decoded bytes and, when the chain ends in an image filter, that filter's name together with the bytes still encoded in it.

func DecodeStream

func DecodeStream(s *Stream, resolve Resolver) ([]byte, Name, error)

DecodeStream is Decode for a parsed stream.

func ToName

func ToName(o Object) (Name, bool)

ToName reports the value of a name object.

func (Name) Kind

func (Name) Kind() Kind

Kind implements Object.

type Null

type Null struct{}

Null is the PDF null object. A missing dictionary entry resolves to Null rather than to a nil Object, so callers never have to nil-check.

func (Null) Kind

func (Null) Kind() Kind

Kind implements Object.

type Object

type Object interface {
	Kind() Kind
}

An Object is any value the PDF object syntax can express. The concrete types are Null, Bool, Integer, Real, String, Name, Array, Dict, Ref and *Stream; Kind tells them apart without a type switch.

func ParseObject

func ParseObject(b []byte) (Object, int, error)

ParseObject parses a single direct object from the start of b and reports how many bytes it consumed.

func Resolve

func Resolve(o Object, r Resolver) (Object, error)

Resolve follows indirect references until it reaches a direct object. A nil object, an absent entry and an unresolvable reference all yield Null.

type Operation

type Operation struct {
	Operator string
	Operands []Object
	Image    *InlineImage
}

An Operation is one step of a content stream: an operator and the operands that precede it. An inline image is reported as the operator "BI" with its dictionary and data attached, since the three keywords it spans are one thing.

func Operations

func Operations(data []byte) ([]Operation, error)

Operations tokenises a whole content stream. The error it returns describes what did not parse; the operations it returns are still worth having.

type Permissions added in v0.3.0

type Permissions uint32

Permissions say what a reader may do with a file it can open with the user password. They are advisory — nothing enforces them but the reader's own good manners — and a file opened with the owner password ignores them entirely.

const (
	PermPrint         Permissions = 1 << 2
	PermModify        Permissions = 1 << 3
	PermCopy          Permissions = 1 << 4
	PermAnnotate      Permissions = 1 << 5
	PermFillForms     Permissions = 1 << 8
	PermExtract       Permissions = 1 << 9 // for accessibility
	PermAssemble      Permissions = 1 << 10
	PermPrintFaithful Permissions = 1 << 11 // at full resolution

	// AllPermissions grants every one of them.
	AllPermissions = PermPrint | PermModify | PermCopy | PermAnnotate |
		PermFillForms | PermExtract | PermAssemble | PermPrintFaithful
)

The permissions a standard security handler can express. Anything not granted is refused; AllPermissions grants everything.

type Real

type Real float64

Real is a PDF real number.

func (Real) Kind

func (Real) Kind() Kind

Kind implements Object.

type Ref

type Ref struct {
	Num int
	Gen int
}

Ref is an indirect reference: the "12 0 R" that stands in for an object stored elsewhere in the file.

func (Ref) Kind

func (Ref) Kind() Kind

Kind implements Object.

func (Ref) String

func (r Ref) String() string

String renders the reference the way it appears in a file.

type Resolver

type Resolver func(Ref) (Object, error)

A Resolver fetches the object an indirect reference names. A document supplies one; a nil Resolver makes every reference resolve to Null, which is what a caller parsing an isolated fragment wants.

type Stream

type Stream struct {
	Dict Dict
	Raw  []byte
}

Stream is a PDF stream: a dictionary plus the raw, still-encoded bytes between the stream and endstream keywords. Decode applies the filter chain.

func ToStream

func ToStream(o Object) (*Stream, bool)

ToStream reports the stream object.

func (*Stream) Kind

func (*Stream) Kind() Kind

Kind implements Object.

type String

type String []byte

String is a PDF string, already unescaped: the bytes a literal or hex string denotes, with no interpretation of their text encoding.

func (String) Kind

func (String) Kind() Kind

Kind implements Object.

type SyntaxError

type SyntaxError struct {
	Offset int
	Msg    string
}

A SyntaxError reports malformed PDF syntax and where it was found, so a repair pass can restart from a known offset instead of giving up.

func (*SyntaxError) Error

func (e *SyntaxError) Error() string

Error implements the error interface.

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

A Writer builds a PDF file out of objects. Numbers are handed out by Writer.Reserve, objects are written with Writer.Put, and Writer.Finish adds the cross-reference table and the trailer.

func NewPackedWriter added in v0.3.0

func NewPackedWriter(version string) *Writer

NewPackedWriter starts a file that puts what it can into compressed object streams and ends in a cross-reference stream. That is smaller — usually by a lot, since a file is mostly small dictionaries — and has been readable since PDF 1.5, which is 2003; the version in the header is raised to 1.5 if it is lower.

func NewWriter

func NewWriter(version string) *Writer

NewWriter starts a file with the given version in its header, "1.7" when the version is empty. Objects are written one after another and the file ends in a cross-reference table, which every reader that has ever existed can read.

func (*Writer) Add

func (w *Writer) Add(o Object) Ref

Add reserves a number, writes the object under it and returns the reference.

func (*Writer) Copy

func (w *Writer) Copy(d *Document, o Object) Object

Copy writes an object from a document into this file, following every indirect reference it reaches and renumbering as it goes, so that objects from several documents can live side by side. An object already copied from the same document keeps the number it was given.

func (*Writer) Encrypt added in v0.3.0

func (w *Writer) Encrypt(e Encryption)

Encrypt protects everything written from here on. It must be called before anything is written, and the file it produces is not byte-for-byte reproducible: encryption needs randomness, by design.

func (*Writer) Err

func (w *Writer) Err() error

Err reports the first thing that went wrong while building the file.

func (*Writer) Finish

func (w *Writer) Finish(trailer Dict) ([]byte, error)

Finish writes the cross-reference table and the trailer and returns the file. /Size is filled in; the caller supplies /Root and whatever else the trailer needs.

func (*Writer) Put

func (w *Writer) Put(ref Ref, o Object)

Put writes an object under a reserved number.

func (*Writer) Reserve

func (w *Writer) Reserve() Ref

Reserve hands out an object number that nothing has been written to yet.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL