pdf

package
v0.0.0-...-7f5ad21 Latest Latest
Warning

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

Go to latest
Published: Aug 22, 2026 License: LGPL-2.1 Imports: 22 Imported by: 0

Documentation

Overview

Package pdf implements the subset of ISO 32000-1/2 that DSS's PAdES support needs: a lenient reader for the whole corpus of signed PDFs DSS validates, and an append-only incremental writer for the ones it produces.

Provenance

Upstream DSS gets its PDF object model from a third party — Apache PDFBox (org.apache.pdfbox:pdfbox:3.0.7, driven by dss-pades-pdfbox's PdfBoxDocumentReader / PdfBoxSignatureService) — so, exactly as PORTING.md prescribes for BouncyCastle-replacement machinery, this package has no Java class to mirror one-to-one and therefore lives under internal/. It imports the standard library only and never imports a DSS package.

Everything here is scoped by what dss-pades-pdfbox actually calls; see internal/pdf/DESIGN.md §0.1 for the enumerated contract. It is deliberately not a general-purpose PDF library: no rasterisation, no content-stream interpretation, no image filters, no full-rewrite serializer, no PDF/A, linearization or tagged PDF.

The reader's leniency is not "best effort": every tolerance is a copy of a specific pdfbox 3.0.7 recovery path (pdfbox runs isLenient=true by default and Loader.loadPDF never turns it off, so lenient is the only mode), enumerated in DESIGN.md §2.7 and pinned by a unit test named for its rule ID. Behavioural divergence from pdfbox is a bug even when pdfbox is the one behaving oddly — the /ByteRange arithmetic DSS performs downstream depends on reproducing it.

Layering

pades  (ported Java classes: PAdESUtils, PdfSigDictWrapper, SingleDssDict, ByteRange, …)
  │  imports
  ▼
internal/pdf      ← this package. stdlib only.

This package knows nothing about CMS, certificates, OCSP or ETSI; it hands []byte upward. In particular it parses /ByteRange into []int64 and stops there: eu.europa.esig.dss.pades.validation.ByteRange owns validate() and getLength().

Index

Constants

View Source
const DefaultContentSize = 9472

DefaultContentSize is the reserved /Contents size in bytes (R17), matching both PAdESSignatureParameters.signatureSize and SignatureOptions.DEFAULT_SIGNATURE_SIZE upstream.

Variables

View Source
var (
	// ErrNotPDF is returned when no %PDF- marker is found in the first 1024 bytes.
	ErrNotPDF = errors.New("pdf: missing %PDF- header")
	// ErrBrokenCatalog is the port of pdfbox's
	// IOException("Page tree root must be a dictionary"): the one defect pdfbox
	// does not recover from, and neither do we.
	ErrBrokenCatalog = errors.New("pdf: page tree root must be a dictionary")
	// ErrInvalidPassword maps onto upstream InvalidPasswordException.
	ErrInvalidPassword = errors.New("pdf: invalid password")
	// ErrUnsupportedSecurityHandler is returned for any /Filter other than /Standard.
	ErrUnsupportedSecurityHandler = errors.New("pdf: unsupported security handler")
	// ErrUnsupportedFilter is what a *FilterError unwraps to.
	ErrUnsupportedFilter = errors.New("pdf: unsupported stream filter")
	// ErrLimitExceeded reports a resource guard (MaxObjects, MaxDepth,
	// MaxStreamSize). These are ours, not pdfbox's; see DESIGN.md §2.7.
	ErrLimitExceeded = errors.New("pdf: resource limit exceeded")
	// ErrNoSuchObject is returned by Document.Object for an absent key. Note that
	// Resolve never returns it: a dangling reference resolves to Null{} (§2.7 O3).
	ErrNoSuchObject = errors.New("pdf: object not found")
	// ErrSignatureAlreadyAdded ports PDDocument.addSignature's IllegalStateException.
	ErrSignatureAlreadyAdded = errors.New("pdf: only one signature may be added per increment")
	// ErrContentsTooLarge is returned when the CMS does not fit the reserved space.
	ErrContentsTooLarge = errors.New("pdf: CMS does not fit the reserved /Contents space")
	// ErrByteRangeTooLarge is returned when the formatted /ByteRange exceeds the
	// 35 reserved bytes (R18).
	ErrByteRangeTooLarge = errors.New("pdf: /ByteRange does not fit the reserved space")
)
View Source
var ErrEncryptedWriteUnsupported = errors.New("pdf: writing an increment into an encrypted document requires the crypt.go encryptor")

ErrEncryptedWriteUnsupported is returned by NewUpdater for an encrypted source document that has no usable security handler.

Functions

func ApplyPredictor

func ApplyPredictor(data []byte, predictor, colors, bpc, columns int, warn *[]Warning) []byte

ApplyPredictor undoes /Predictor: 1 = none, 2 = TIFF, 10..15 = PNG (each row carries its own filter-type byte, so a declared 15 still dispatches per row over 0..4). Row length is ceil(Colors*BPC*Columns/8); bpp = ceil(Colors*BPC/8). A truncated final row is zero-padded and warned about.

func ContentsRange

func ContentsRange(br []int64) ([2]int64, error)

ContentsRange returns the span of the /Contents hex string including its < >:

contents = [br[0]+br[1], br[2])

func Decode

func Decode(raw []byte, filters []Name, parms []*Dict, warn *[]Warning) ([]byte, error)

Decode applies the filter chain named by /Filter with the parameters in /DecodeParms to raw. Unsupported filters yield a *FilterError. Any recovered defect appends to warn.

func DecodeTextString

func DecodeTextString(b []byte) string

DecodeTextString renders a PDF text string as Go text, reproducing PDFBox's COSString#getString: a UTF-16BE byte-order mark selects UTF-16BE, a UTF-16LE one selects UTF-16LE (PDFBox accepts it although the PDF specification does not define it), and anything else is PDFDocEncoding - see codeToUnicode above for why that is not the same as Latin-1.

func EncodeName

func EncodeName(n Name) []byte

EncodeName renders a name under R5: the byte is emitted verbatim only if it is in [A-Za-z0-9+\-_@*$;.], else as '#' plus two uppercase hex digits. This is deliberately stricter than ISO 32000-1 (PDFBOX-2073) — '!', ',', '~' and '\” are escaped even though the spec allows them literally.

func EncodeString

func EncodeString(s String) []byte

EncodeString renders a string under R6: hex form when any byte is >= 0x80 or is CR or LF, or when Hex is set; otherwise literal form escaping only '(', ')' and '\'. No octal escape is ever emitted.

func FlateDecode

func FlateDecode(raw []byte, warn *[]Warning) []byte

FlateDecode reproduces pdfbox FlateFilterDecoderStream exactly: it discards the first two bytes, inflates as raw DEFLATE with no zlib-header and no Adler-32 validation, and on a corrupt stream returns the bytes decoded so far with a WarnFlateTruncated warning and no error.

Do not "fix" this to use compress/zlib: the corpus contains non-conforming headers and truncated checksums that zlib.NewReader rejects and pdfbox accepts.

func FlateEncode

func FlateEncode(data []byte) []byte

FlateEncode produces zlib-wrapped DEFLATE at the fixed compression level 6. Output is byte-stable for a given toolchain but NOT across Go releases — Go 1.27 changed compress/flate's encoder output — which is fine: nothing signs or pins these compressed bytes (signatures cover ByteRanges and decompressed content), only self-consistency within one produced revision. The zlib wrapper is written by hand rather than taken from compress/zlib so the header bytes are pinned here and cannot drift.

func FormatDate

func FormatDate(t time.Time) string

FormatDate renders t as a PDF date string "D:YYYYMMDDHHmmSS+HH'mm'", the format pdfbox's DateConverter.toString produces. A zero UTC offset is written "+00'00'", never "Z".

func FormatReal

func FormatReal(v float64) string

FormatReal renders v the way pdfbox writes a COSFloat (R8): Java's Float.toString, and — when that produced exponent notation — the BigDecimal(s).stripTrailingZeros().toPlainString() expansion of it. NaN and both infinities render as "0.0" rather than producing a syntactically invalid PDF number.

Note that reals we merely echo never reach this function: Real.Raw is written verbatim, which is what makes round-tripping /Rect [0.0 0.0 595.276 841.89] byte-exact.

func ReplaceContents

func ReplaceContents(doc []byte, cms []byte) ([]byte, error)

ReplaceContents is the port of PAdESUtils.replaceSignature for the cached to-be-signed path: it finds the single all-zero hex placeholder in doc and substitutes cms, lowercase-hex encoded. It errors when zero or more than one placeholder is present.

The scanner is upstream's, byte for byte: a '<' arms a suspicion, a run of exactly len(hex(cms)) '0' bytes after it is the placeholder, any other byte disarms it, and the reserved bytes beyond the CMS keep their '0's — so the document's length never changes.

func SignedRanges

func SignedRanges(br []int64) ([2][2]int64, error)

SignedRanges returns the two covered spans as [start,end) pairs:

signed = [br[0], br[0]+br[1]) u [br[2], br[2]+br[3])

Types

type Annotation

type Annotation struct {
	Key      ObjectKey
	Dict     *Dict
	Rect     Rect
	Name     string // /T
	Signed   bool   // /V present
	Hidden   bool   // /F bit 2
	NoRotate bool   // /F bit 5
}

Annotation is one /Annots entry.

type Array

type Array []Object

Array is a PDF array.

type Bool

type Bool bool

Bool is a PDF boolean.

type DSSDictionary

type DSSDictionary struct {
	Certs []TokenRef
	CRLs  []TokenRef
	OCSPs []TokenRef
	VRI   []VRIEntry // omitted from output when empty
}

DSSDictionary is the document security store to write.

type Dict

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

Dict is an insertion-ordered PDF dictionary. Order is part of the contract.

func DictOf

func DictOf(kv ...any) *Dict

DictOf builds a dictionary from alternating Name, Object pairs. It panics on a malformed pair — it is a construction helper for tests and for the writer, not a parser entry point.

func NewDict

func NewDict() *Dict

NewDict returns an empty dictionary.

func (*Dict) Clone

func (d *Dict) Clone() *Dict

Clone returns a shallow copy: the value objects are shared.

func (*Dict) Delete

func (d *Dict) Delete(key Name)

Delete removes key, preserving the order of the remaining entries.

func (*Dict) GetRaw

func (d *Dict) GetRaw(key Name) Object

GetRaw returns the stored, unresolved value, or nil when the key is absent.

func (*Dict) Has

func (d *Dict) Has(key Name) bool

Has reports whether key is present.

func (*Dict) Keys

func (d *Dict) Keys() []Name

Keys returns the keys in insertion order, as a fresh slice.

func (*Dict) Len

func (d *Dict) Len() int

Len reports the number of entries.

func (*Dict) Set

func (d *Dict) Set(key Name, v Object)

Set stores v under key. An existing key is updated in place and keeps its position, matching LinkedHashMap.put.

func (*Dict) String

func (d *Dict) String() string

String renders the dictionary for diagnostics. It is not the writer's output format; see writer.go for that.

type Document

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

Document is a parsed PDF file.

func Open

func Open(r io.ReaderAt, size int64, opts *Options) (*Document, error)

Open parses the document behind r. size is the length of the source.

func OpenBytes

func OpenBytes(b []byte, opts *Options) (*Document, error)

OpenBytes parses b. The slice is retained, not copied: the writer needs the original bytes verbatim (R: §3.1) and callers must not mutate it.

func (*Document) AcroForm

func (d *Document) AcroForm() (*Dict, bool)

AcroForm returns /Root /AcroForm.

func (*Document) Annotations

func (d *Document) Annotations(page int) ([]Annotation, error)

Annotations returns the annotations of a 1-based page.

func (*Document) Bytes

func (d *Document) Bytes() ([]byte, error)

Bytes returns the whole source. The slice is the document's own; callers must not modify it.

func (*Document) Catalog

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

Catalog returns the document catalog.

func (*Document) Close

func (d *Document) Close() error

Close releases nothing: Document holds no OS resources. It exists so callers can treat it like upstream's PDDocument.

func (*Document) Encryption

func (d *Document) Encryption() *Encryption

Encryption describes the security handler, or nil when the document is not encrypted.

func (*Document) Get

func (d *Document) Get(dict *Dict, key Name) Object

Get resolves dict[key]. It returns nil when the key is absent.

func (*Document) GetArray

func (d *Document) GetArray(dict *Dict, key Name) (Array, bool)

GetArray returns dict[key] as an array.

func (*Document) GetBool

func (d *Document) GetBool(dict *Dict, key Name) (bool, bool)

GetBool returns dict[key] as a boolean.

func (*Document) GetDate

func (d *Document) GetDate(dict *Dict, key Name) (time.Time, bool)

GetDate parses a PDF date string "D:YYYYMMDDHHmmSSOHH'mm'" leniently: any truncation from the right is accepted, as is a missing "D:" prefix.

func (*Document) GetDict

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

GetDict returns dict[key] as a dictionary. A stream counts as a dictionary only through GetStream; pdfbox's getCOSDictionary behaves the same way.

func (*Document) GetInt

func (d *Document) GetInt(dict *Dict, key Name) (int64, bool)

GetInt returns dict[key] as an integer. A real is truncated, as COSNumber.intValue does.

func (*Document) GetName

func (d *Document) GetName(dict *Dict, key Name) (Name, bool)

GetName returns dict[key] as a name.

func (*Document) GetReal

func (d *Document) GetReal(dict *Dict, key Name) (float64, bool)

GetReal returns dict[key] as a float.

func (*Document) GetStream

func (d *Document) GetStream(dict *Dict, key Name) (*Stream, bool)

GetStream returns dict[key] as a stream.

func (*Document) GetString

func (d *Document) GetString(dict *Dict, key Name) ([]byte, bool)

GetString returns dict[key]'s bytes.

func (*Document) HasHybridXRef

func (d *Document) HasHybridXRef() bool

HasHybridXRef reports whether any section carried /XRefStm.

func (*Document) HeaderVersion

func (d *Document) HeaderVersion() float32

HeaderVersion is the version from %PDF-x.y.

func (*Document) HighestObjectNumber

func (d *Document) HighestObjectNumber() int64

HighestObjectNumber is the maximum object number over all xref sections, including free entries and /Size - 1 (R16).

func (*Document) ID

func (d *Document) ID() [2][]byte

ID returns the two /ID strings; missing elements are nil.

func (*Document) IndexRef

func (d *Document) IndexRef(a Array, i int) (ObjectKey, bool)

IndexRef is the array-element form: upstream's PdfArray.getObjectKey(i).

func (*Document) Info

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

Info returns the document information dictionary, or (nil, nil) when absent.

func (*Document) IsEncrypted

func (d *Document) IsEncrypted() bool

IsEncrypted reports whether the document carries an /Encrypt dictionary.

func (*Document) NumberOfPages

func (d *Document) NumberOfPages() int

NumberOfPages is the number of leaves in the page tree.

func (*Document) Object

func (d *Document) Object(k ObjectKey) (Object, error)

Object returns the object stored under k.

func (*Document) ObjectKeys

func (d *Document) ObjectKeys() []ObjectKey

ObjectKeys returns every key in the resolved xref, ascending by Num then Gen. Free entries are not included, matching COSDocument.getXrefTable().

func (*Document) Page

func (d *Document) Page(page int) (*Dict, ObjectKey, error)

Page returns the 1-based page dictionary and its object key.

func (*Document) PageBox

func (d *Document) PageBox(page int) (Rect, error)

PageBox returns the page's /MediaBox, inherited through /Parent. The default is US Letter, as PDPage.getMediaBox does.

func (*Document) PageRotation

func (d *Document) PageRotation(page int) int

PageRotation returns /Rotate normalised into {0,90,180,270}.

func (*Document) Permissions

func (d *Document) Permissions() Permissions

Permissions returns the decoded /P bits. An unencrypted document grants everything, matching AccessPermission's default.

func (*Document) RawStreamData

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

RawStreamData returns the decrypted but still encoded stream bytes. This is what DefaultPdfObjectModificationsFinder.compareDictStreams compares.

func (*Document) RawStreamSize

func (d *Document) RawStreamSize(s *Stream) int64

RawStreamSize is the length of the raw bytes, or -1 when s is nil.

func (*Document) RefAt

func (d *Document) RefAt(dict *Dict, key Name) (ObjectKey, bool)

RefAt returns the key of the indirect reference stored at key, if it is one. This is upstream's PdfDict.getObjectKey.

func (*Document) Resolve

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

Resolve follows Ref chains. A dangling reference resolves to Null{}, never an error (§2.7 O3) — visitFromDictionary upstream skips nil-valued entries, and a hard error here would fail documents pdfbox parses.

func (*Document) Revisions

func (d *Document) Revisions() []Revision

Revisions returns the %%EOF revision list of the source document. The result is computed once and cached.

func (*Document) SignatureCoversWholeDocument

func (d *Document) SignatureCoversWholeDocument(sd SignatureDictionary) bool

SignatureCoversWholeDocument reproduces PdfBoxDocumentReader.isSignatureCoversWholeDocument including its arithmetic, which is not the obvious one:

(br[1]-br[0]) + (br[2]-br[1]-br[0]) + br[3] == fileLength

Do not "fix" the formula, or documents upstream reports as fully covered will stop matching.

func (*Document) SignatureDictionaries

func (d *Document) SignatureDictionaries() ([]SignatureDictionary, error)

SignatureDictionaries returns one entry per distinct /V signature dictionary, deduplicated by object key exactly as PdfBoxDocumentReader does with sigDictObject.getKey().getNumber().

func (*Document) SignatureFields

func (d *Document) SignatureFields() ([]SignatureField, error)

SignatureFields reproduces PDDocument.getSignatureFields().

func (*Document) Size

func (d *Document) Size() int64

Size is the length of the source document in bytes.

func (*Document) StartXref

func (d *Document) StartXref() int64

StartXref is the offset read from the last startxref, or -1.

func (*Document) StreamData

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

StreamData returns the decrypted, fully decoded stream bytes.

func (*Document) Trailer

func (d *Document) Trailer() *Dict

Trailer returns the resolved trailer dictionary.

func (*Document) Version

func (d *Document) Version() float32

Version is the catalog's /Version when present, else the header version.

func (*Document) Warnings

func (d *Document) Warnings() []Warning

Warnings returns every recovered defect, in the order they were noticed.

func (*Document) XRefSections

func (d *Document) XRefSections() []XRefSection

XRefSections returns the /Prev chain, newest first.

type Encryption

type Encryption struct {
	Handler   Name // always "Standard" (others are rejected at Open)
	V, R      int
	KeyLength int
	StmF      Name // "StdCF" | "Identity"
	StrF      Name
	CFM       Name // "V2" | "AESV2" | "AESV3" | "None"
}

Encryption describes the document's security handler as configured.

type FilterError

type FilterError struct{ Filter Name }

FilterError names the filter that could not be decoded. It unwraps to ErrUnsupportedFilter, so errors.Is(err, ErrUnsupportedFilter) holds.

func (*FilterError) Error

func (e *FilterError) Error() string

func (*FilterError) Unwrap

func (e *FilterError) Unwrap() error

type Integer

type Integer int64

Integer is a PDF integer. Literals exceeding int64 are clamped with a warning.

type Name

type Name string

Name is a PDF name, stored already unescaped (no leading slash).

const (
	FilterFlate     Name = "FlateDecode"
	FilterLZW       Name = "LZWDecode"
	FilterASCIIHex  Name = "ASCIIHexDecode"
	FilterASCII85   Name = "ASCII85Decode"
	FilterRunLength Name = "RunLengthDecode"
	FilterCrypt     Name = "Crypt" // /Identity only; anything else -> ErrUnsupportedFilter
)

The filters we decode.

type Null

type Null struct{}

Null is the PDF null object. A dangling indirect reference resolves to Null{}, never to an error (DESIGN.md §2.7 O3).

type Object

type Object interface {
	// contains filtered or unexported methods
}

Object is the sum type of PDF object kinds: Null, Bool, Integer, Real, String, Name, Array, *Dict, *Stream, Ref.

type ObjectKey

type ObjectKey struct {
	Num int64
	Gen uint16
}

ObjectKey identifies an indirect object slot. It is the analogue of upstream's PdfObjectKey / PdfBoxObjectKey. The zero value means "no key". It is a distinct type from Ref on purpose: Ref is a value inside the object graph, ObjectKey identifies a slot.

func (ObjectKey) IsZero

func (k ObjectKey) IsZero() bool

IsZero reports whether k is the zero key.

func (ObjectKey) String

func (k ObjectKey) String() string

String renders the key as upstream's PdfObjectKey.toString does: "12 0".

type Options

type Options struct {
	// Password is tried as the user password, then as the owner password.
	Password []byte
	// Random supplies AES initialisation vectors on write. nil means crypto/rand.
	Random io.Reader

	MaxObjects    int   // default 500000
	MaxDepth      int   // default 512
	MaxStreamSize int64 // default 512 << 20
}

Options configures Open. The zero value is valid: every limit falls back to its default.

type Permissions

type Permissions struct {
	Raw              int32
	OwnerAccess      bool // the owner password matched
	CanModify        bool // bit 4
	CanModifyAnnots  bool // bit 6
	CanFillInForm    bool // bit 9
	CanPrint         bool // bit 3
	CanExtract       bool // bit 5
	CanAssemble      bool // bit 11
	CanPrintFaithful bool // bit 12
}

Permissions is the decoded /P bitfield plus which password matched.

type Placeholder

type Placeholder struct {
	SigKey   ObjectKey
	FieldKey ObjectKey
}

Placeholder names the objects AddSignature created.

type Real

type Real struct {
	Val float64
	Raw string
}

Real carries the literal it was parsed from; Raw is "" for values built in memory. See R8: the writer echoes Raw verbatim when it is set.

type Rect

type Rect struct{ MinX, MinY, MaxX, MaxY float64 }

Rect is a PDF rectangle, normalised so Min <= Max on both axes.

func RectFromArray

func RectFromArray(a Array) (Rect, bool)

RectFromArray reads a rectangle from a four-number array, normalising the corners. The array must already have had its elements resolved.

func (Rect) Array

func (r Rect) Array() Array

Array renders the rectangle as a four-element PDF array of in-memory reals.

func (Rect) Height

func (r Rect) Height() float64

Height is MaxY-MinY.

func (Rect) Width

func (r Rect) Width() float64

Width is MaxX-MinX.

type Ref

type Ref struct {
	Num int64
	Gen uint16
}

Ref is an indirect reference appearing as a value inside the object graph.

func (Ref) Key

func (r Ref) Key() ObjectKey

Key returns the slot the reference points at.

type Result

type Result struct {
	Bytes          []byte // original || increment
	OriginalLength int64
	// ByteRange is zero-valued when the update carries no signature.
	ByteRange           [4]int64
	ContentsOffset      int64 // absolute offset of the '<'
	ContentsLength      int64 // reserved bytes including '<' and '>'
	StartXref           int64
	XRefStyle           XRefStyle
	HighestObjectNumber int64
}

Result is the laid-out increment, before the CMS is inserted.

func (*Result) InsertContents

func (r *Result) InsertContents(cms []byte) error

InsertContents writes cms as uppercase hex into the reserved /Contents span (R17), leaving the unused reserved bytes as ASCII '0'. It returns ErrContentsTooLarge when cms does not fit.

Upstream is not self-consistent here and we deliberately are not either: COSWriter.writeExternalSignature uses pdfbox's Hex.getBytes (uppercase) while PAdESUtils.replaceSignature uses Utils.toHex (lowercase). Both are legal PDF hex strings, both parse, and each function below matches the upstream path it ports. See DESIGN.md §3.6.

func (*Result) SignedData

func (r *Result) SignedData() io.Reader

SignedData is the byte stream the CMS must be computed over: exactly the two spans named by ByteRange. When the update carries no signature the whole output is returned, since there is no excluded span.

type Revision

type Revision struct {
	Index int
	End   int64
}

Revision is one %%EOF-delimited prefix: the revision is bytes [0, End).

func ScanRevisions

func ScanRevisions(r io.Reader) ([]Revision, error)

ScanRevisions reproduces PAdESUtils.extractRevisions byte for byte, including its %%EOF + EOL lookahead. It does not walk /Prev, and it deliberately counts %%EOF sequences that occur inside object data: PAdESUtils.getPreviousRevision picks the candidate whose length is the largest below byteRange[0]+byteRange[1], so a different revision list changes which document DSS reports as the signed original.

type SignatureDictionary

type SignatureDictionary struct {
	Key       ObjectKey
	Dict      *Dict
	Type      Name // "Sig" | "DocTimeStamp" | ""
	Filter    Name
	SubFilter Name
	Contents  []byte  // decoded string bytes, i.e. the DER CMS
	ByteRange []int64 // verbatim, length not forced to 4
	Fields    []int   // indices into the SignatureFields slice that point here
}

SignatureDictionary is the raw /V content. All ETSI semantics live in `pades`.

type SignatureField

type SignatureField struct {
	Key        ObjectKey
	Dict       *Dict
	Name       string    // fully-qualified /T, dot-joined
	Value      *Dict     // /V, nil for an empty field
	ValueKey   ObjectKey // key of the /V reference; zero when /V is direct or absent
	Widgets    []*Dict
	WidgetKeys []ObjectKey
	Page       int // 0 when the widget is not on any page
	Rect       Rect
	Lock       *Dict // /Lock, nil when absent
}

SignatureField is one /FT /Sig field of the AcroForm.

type SignatureOptions

type SignatureOptions struct {
	Type        Name // "Sig" (default) or "DocTimeStamp"
	Filter      Name // default "Adobe.PPKLite"
	SubFilter   Name // e.g. "ETSI.CAdES.detached", "ETSI.RFC3161"
	ContentSize int  // reserved /Contents bytes; default DefaultContentSize
	SignerName  string
	Reason      string
	Location    string
	ContactInfo string
	SigningTime time.Time // /M; the zero value omits the key
	AppName     string    // /Prop_Build /App /Name
	FieldID     string    // fill this existing empty field; "" creates a new one
	Page        int       // 1-based; used only when creating a field
	Rect        Rect      // the zero Rect creates an invisible field
	Appearance  *Stream   // /AP /N; nil for an invisible field
	DocMDP      int       // 1..3; 0 = none
	Lock        *Dict     // /Lock of the target field, drives FieldMDP
	DocumentID  []byte    // second element of /ID
}

SignatureOptions describes the signature dictionary and the field it goes in.

type Stream

type Stream struct {
	Dict *Dict
	Raw  []byte
	// Offset and Length locate Raw in the source file; both are 0 for streams
	// built in memory.
	Offset int64
	Length int64
	// contains filtered or unexported fields
}

Stream is a PDF stream: its dictionary plus its still-encoded, still-encrypted bytes.

func NewStream

func NewStream(d *Dict, raw []byte) *Stream

NewStream builds an in-memory stream. Offset and Length stay 0.

type String

type String struct {
	Bytes []byte
	Hex   bool
}

String is a PDF string. Hex records the source form and forces hex on output.

type TokenRef

type TokenRef struct {
	Key  ObjectKey // non-zero reuses an existing object; zero writes a new stream
	Data []byte    // DER; ignored when Key is non-zero
}

TokenRef is one validation-data token: either a reference to an object the document already carries, or the DER bytes of a new one.

type Updater

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

Updater accumulates an incremental update over a Document. The Document's bytes are never modified; Write emits original||increment.

func NewUpdater

func NewUpdater(d *Document) (*Updater, error)

NewUpdater starts an incremental update over d.

func (*Updater) Add

func (u *Updater) Add(obj Object) ObjectKey

Add allocates a key and schedules obj to be written under it.

func (*Updater) AddSignature

func (u *Updater) AddSignature(opts SignatureOptions) (*Placeholder, error)

AddSignature installs a signature dictionary with placeholder /Contents and /ByteRange, wires it into a signature field, the AcroForm and (for a new field) the page's /Annots. It returns ErrSignatureAlreadyAdded on a second call (R19), matching PDDocument.addSignature's IllegalStateException: multiple signatures are multiple increments.

func (*Updater) Alloc

func (u *Updater) Alloc() ObjectKey

Alloc reserves the next object number (R16) without writing anything.

func (*Updater) Catalog

func (u *Updater) Catalog() *Dict

Catalog returns a mutable clone of the catalog, already scheduled for writing. Repeated calls return the same instance.

func (*Updater) Put

func (u *Updater) Put(k ObjectKey, obj Object)

Put schedules obj to be written under an existing key, replacing that object in this revision. It never touches the original bytes.

func (*Updater) Scheduled

func (u *Updater) Scheduled() []ObjectKey

Scheduled reports the keys that will be written, ascending (R15).

func (*Updater) SetDSSDictionary

func (u *Updater) SetDSSDictionary(dss DSSDictionary) error

SetDSSDictionary writes /Root /DSS and marks the catalog updated. Order is the caller's; nothing is sorted or deduplicated here.

func (*Updater) Update

func (u *Updater) Update(k ObjectKey) (Object, error)

Update returns a mutable clone of an existing object, already scheduled. Repeated calls for one key return the same instance, so two features editing the same dictionary compose instead of clobbering each other.

func (*Updater) Write

func (u *Updater) Write() (*Result, error)

Write lays out the increment and patches /ByteRange (R18). /Contents still holds the placeholder.

Object write order is ascending object number. This is a deliberate deviation from pdfbox (R15): COSWriter drains an ArrayDeque seeded from a HashSet, so its order is not reproducible across runs, and reproducing it would mean reproducing a bug. Offsets in the xref come from the actual write positions, so nothing about validity changes. We also never emit an object stream on write — every new object is a plain "N G obj".

type VRIEntry

type VRIEntry struct {
	Name  string // uppercase base-16 SHA-1 of the signature, computed by pades
	Certs []TokenRef
	CRLs  []TokenRef
	OCSPs []TokenRef
	TU    time.Time
	TS    []byte
}

VRIEntry is one /VRI sub-dictionary.

type Warning

type Warning struct {
	Code    WarningCode
	Offset  int64
	Message string
}

Warning is one recovered defect. Offset is the byte offset in the source file where the defect was noticed, or -1 when it is not tied to a position.

type WarningCode

type WarningCode string

WarningCode classifies a recovered defect. Codes are stable across releases; the oracle compares codes, never message text.

const (
	WarnHeaderGarbage      WarningCode = "header-garbage"
	WarnHeaderVersion      WarningCode = "header-version-default"
	WarnMissingEOF         WarningCode = "missing-eof"
	WarnXRefOffsetRepaired WarningCode = "xref-offset-repaired"
	WarnXRefBruteForce     WarningCode = "xref-brute-force"
	WarnXRefEntryInvalid   WarningCode = "xref-entry-invalid"
	WarnXRefStmSkipped     WarningCode = "xrefstm-skipped"
	WarnObjectHeaderFixed  WarningCode = "object-header-fixed"
	WarnDanglingReference  WarningCode = "dangling-reference"
	WarnStreamLengthFixed  WarningCode = "stream-length-fixed"
	WarnStreamEndFixed     WarningCode = "stream-end-fixed"
	WarnFlateTruncated     WarningCode = "flate-truncated"
	WarnPredictorTruncated WarningCode = "predictor-truncated"
	WarnObjStmBroken       WarningCode = "objstm-broken"
	WarnKidRemoved         WarningCode = "kid-removed"
	// WarnNumberClamped records an integer literal that exceeded int64 (§2.1).
	WarnNumberClamped WarningCode = "number-clamped"
	// WarnLexer records a tolerated lexical defect (bad hex digit, malformed #XX).
	WarnLexer WarningCode = "lexer"
)

type Writer

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

Writer emits PDF syntax under the byte-determinism rules R1..R14 of DESIGN.md. It tracks line state so WriteEOL is suppressed at a line start (R2).

func NewWriter

func NewWriter(w io.Writer) *Writer

NewWriter returns a Writer positioned at offset 0.

func (*Writer) Err

func (w *Writer) Err() error

Err reports the first error seen. Every write is a no-op after it is set.

func (*Writer) Pos

func (w *Writer) Pos() int64

Pos reports the current absolute byte offset.

func (*Writer) WriteEOL

func (w *Writer) WriteEOL() error

WriteEOL writes the end-of-line byte \n unless the last byte written was already an EOL written by WriteEOL (R1, R2). Without this suppression every dictionary gains a blank line and no golden matches.

func (*Writer) WriteIndirect

func (w *Writer) WriteIndirect(k ObjectKey, o Object) error

WriteIndirect writes "N G obj … endobj" (R10).

func (*Writer) WriteObject

func (w *Writer) WriteObject(o Object) error

WriteObject writes o in PDF syntax, dispatching on its kind. Composite values are written direct: an indirect value is a Ref, never a *Dict.

func (*Writer) WriteRaw

func (w *Writer) WriteRaw(b []byte) error

WriteRaw writes b verbatim.

type XRefSection

type XRefSection struct {
	// Offset is the byte offset of `xref`, or of the `N G obj` of the xref stream.
	Offset    int64
	Style     XRefStyle
	Trailer   *Dict
	Prev      int64 // -1 when absent
	XRefStm   int64 // hybrid: /XRefStm offset, -1 when absent
	Entries   int   // entries contributed by this section
	Recovered bool  // true when the offset had to be repaired (§2.7 X3)
}

XRefSection is one hop of the /Prev chain.

type XRefStyle

type XRefStyle uint8

XRefStyle distinguishes the two section forms.

const (
	XRefTable XRefStyle = iota + 1
	XRefStream
)

func (XRefStyle) String

func (s XRefStyle) String() string

Jump to

Keyboard shortcuts

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