pdfrab

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 20, 2026 License: AGPL-3.0 Imports: 22 Imported by: 0

README

pdfrab for Go

PDF/A processing for go

Goals

  • Verification of PDF/A compliance for files
  • Conversion of files to reach PDF/A compliance

Current Progress

PDF/A-1b Validation

PDF/A-1b (ISO 19005-1:2005) verification is implemented and passes both reference test corpora in full:

Clause Area Status
6.1.2 File header
6.1.3 File trailer
6.1.4 Cross-reference table
6.1.5 Document information dictionary
6.1.6 String objects
6.1.7 Stream objects
6.1.8 Indirect objects
6.1.10 Filters
6.1.11 Embedded files
6.1.12 Architectural limits
6.1.13 Optional content
6.2.2 Output intent
6.2.3 Device colour spaces
6.2.4 Image dictionaries
6.2.5–6.2.7 XObjects
6.2.8 ExtGState
6.2.9 Rendering intent
6.2.10 Operators
6.3.2 Font programs
6.3.3 Composite fonts
6.3.4 Font embedding
6.3.5 Font subsets
6.3.6 Font metrics
6.3.7 Font encoding
6.4 Transparency
6.5 Annotations
6.6 Actions
6.7.2 XMP metadata structure
6.7.3 Info/XMP synchronisation
6.7.5 XMP packet header
6.7.8 Extension schemas
6.7.9 XMP well-formedness
6.7.11 PDF/A identifier
6.9 Interactive forms

Selective Check Profiles

Verification can be narrowed to a specific set of rules using VerifyProfile.

Start from the full profile and remove checks
p := pdfrab.PDFA_1B.
    RemoveCheck(pdfrab.Checks.Structure.FileHeaderSignature).
    RemoveCheck(pdfrab.Checks.Font.SimpleNotEmbedded)

res, err := doc.VerifyProfile(p)
Start from an empty profile and add checks
p := pdfrab.PDFA_1B.Clear().
    AddCheck(
        pdfrab.Checks.Transparency.ImageWithSoftMask,
        pdfrab.Checks.Metadata.PDFAIdentifierMissing,
    )

res, err := doc.VerifyProfile(p)
Available check groups
Registry field Spec area
Checks.Structure 6.1.x — file header, trailer, xref, object framing, limits
Checks.Colour 6.2.2 OutputIntent, 6.2.3.x device colours, 6.2.9–10
Checks.Image 6.2.4–6.2.7 image/form/PostScript XObjects
Checks.Transparency 6.2.8 transfer functions, 6.4 soft masks/blend modes/alpha
Checks.Font 6.3.x embedding, subsets, metrics, encoding
Checks.Annotation 6.5.x annotation types and dictionaries
Checks.Action 6.6.x action types and additional actions
Checks.Metadata 6.7.x XMP metadata, extension schemas, PDF/A identifier
Checks.Form 6.9 interactive forms

Use pdfrab.AllChecks() to enumerate all registered checks with their names, descriptions, and clause numbers. pdfrab.CheckByClause("6.3.4", 1) and pdfrab.ChecksForClause("6.3.4") look up checks by clause directly.

Performance

gopdfrab's PDF/A-1b verification performance is (unfairily) measured against the Java-based veraPDF and PDFBox Preflight on the combined Isartor + veraPDF corpora (773 files); see benchmarks/README.md for methodology.

Benchmark gopdfrab vs veraPDF gopdfrab vs PDFBox Preflight
Startup time 222x faster 32x faster
Single file throughput 261x faster 80x faster
Batch throughput 16x faster 12x faster
Batch peak memory 13x smaller 14x smaller
Binary size 5x smaller 4x smaller

Due to JVM startup overhead, the startup time and single file verification throughput are significantly slower for veraPFD and Preflight.

Isartor Compatibility

The Isartor test suite is the old reference test suite for PDF/A-1b document compatibility before the veraPDF project was initiated. If you require PDF/A-1b compatibility based on Isartor for your application, use the Legacy_1B profile.

Getting Started

A full example can be found under main/main.go

Import pdfrab

Import pdfrab for your go project.

import (
  pdfrab "github.com/voidrab/gopdfrab"
)
Initialize Document
doc, err := pdfrab.Open(path)
if err != nil {
  log.Fatal(err)
}
PDF/A Validation
v, err := doc.Verify(pdfrab.A_1B)
if err != nil {
  log.Println(err)
}

if v.Valid {
  fmt.Println("Document is PDF/A-1b compliant")
} else {
  fmt.Println("Document is not PDF/A-1b compliant")
  fmt.Println("Issues:")
  for i, v := range v.Issues {
    fmt.Printf("#%v: %v\n", i+1, v)
  }
}

Finally, close doc.

doc.close()
Verifying Multiple Files

VerifyAll opens, verifies, and closes a batch of files concurrently.

results := pdfrab.VerifyAll(paths, pdfrab.A_1B)
for _, r := range results {
    if r.Err != nil {
        log.Println(r.Path, r.Err)
        continue
    }
    fmt.Println(r.Path, r.Result.Valid)
}
Inspecting Issues

Each PDFError in v.Issues exposes the Check that flagged it, along with its page and underlying messages.

for _, issue := range v.Issues {
    c := issue.Check()
    fmt.Println(c.Clause(), c.Subclause(), c.Name(), c.Description())
    fmt.Println(issue.Page(), issue.Messages())
}

Result has helpers for grouping and summarizing issues:

fmt.Println(v.Summary())          // human-readable report, one line per Check
v.Checks()                        // distinct Checks violated, sorted by clause
v.IssuesByCheck()                 // map[Check][]PDFError
v.IssuesOnPage(1)                 // issues found on page 1 (0 = document-level)
Document Helpers
ok, err := doc.IsPDFA()           // shorthand for Verify(A_1B).Valid

part, level, err := doc.ClaimedConformance() // e.g. "1", "B" — what the file claims, not whether it's valid

xmp, err := doc.XMPMetadata()     // raw XMP packet bytes, decoded to UTF-8

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Checks checksRegistry

Functions

func EqualPDFValue

func EqualPDFValue(a, b PDFValue) bool

Types

type Check

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

Check is a named, selectable PDF/A validation rule, identified by a (clause, subclause) pair and grouped into categories under Checks.

func AllChecks

func AllChecks() []Check

AllChecks returns every registered check in catalog order.

func CheckByClause

func CheckByClause(clause string, subclause int) (c Check, ok bool)

CheckByClause looks up the registered check for a specific (clause, subclause) pair, e.g. CheckByClause("6.3.4", 1). ok is false if no check is registered for that pair.

func ChecksForClause

func ChecksForClause(clause string) []Check

ChecksForClause returns every registered check under the given clause (e.g. "6.3.4"), in catalog order.

func (Check) Clause

func (c Check) Clause() string

Clause returns the PDF/A-1 specification clause number (e.g. "6.1.2").

func (Check) Description

func (c Check) Description() string

Description returns a human-readable summary of what this check enforces.

func (Check) Name

func (c Check) Name() string

Name returns the CamelCase identifier of this check (e.g. "ImageWithSoftMask").

func (Check) Subclause

func (c Check) Subclause() int

Subclause returns the internal sub-rule index within the clause.

type ContentScanner

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

ContentScanner walks a decoded content stream, exposing each operator together with its operands. It reuses the object Lexer for tokenising; bare operators arrive as keyword tokens.

type Cursor

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

func NewCursor

func NewCursor(data []byte) *Cursor

func (*Cursor) ReadLine

func (c *Cursor) ReadLine() (string, bool)

type Document

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

Document represents a PDF file.

func Open

func Open(path string) (*Document, error)

Open initializes the PDF document at path.

func (*Document) ClaimedConformance

func (d *Document) ClaimedConformance() (part, conformance string, err error)

ClaimedConformance returns the PDF/A part and conformance level the document's XMP metadata claims (e.g. "1", "B"), read from the pdfaid namespace. This reflects what the file claims, not whether it actually validates — use Verify or IsPDFA to check actual compliance.

func (*Document) Close

func (d *Document) Close() error

Close ensures the file handle is released.

func (*Document) GetMetadata

func (d *Document) GetMetadata() (map[string]string, error)

GetMetadata extracts info from the Info dictionary.

func (*Document) GetPageCount

func (d *Document) GetPageCount() (int, error)

GetPageCount retrieves the page count.

func (*Document) GetVersion

func (d *Document) GetVersion() (string, error)

GetVersion extracts the PDF version from the Document header

func (*Document) IsPDFA

func (d *Document) IsPDFA() (bool, error)

IsPDFA reports whether the document is valid PDF/A-1b. It is equivalent to calling Verify(A_1B) and checking the result's Valid field, for callers who only need a yes/no answer.

func (*Document) ResolveGraph

func (d *Document) ResolveGraph() (PDFValue, error)

ResolveGraph resolves the PDF object graph, starting from the effective trailer (firstPageTrailer if set, else trailer).

func (*Document) ResolveGraphByPath

func (d *Document) ResolveGraphByPath(path []string) (PDFValue, error)

ResolveGraphByPath resolves the PDF object graph by path, starting from the effective trailer (firstPageTrailer if set, else trailer).

func (*Document) Verify

func (d *Document) Verify(t LevelType) (Result, error)

Verify verifies d to conformance level t.

func (*Document) VerifyProfile

func (d *Document) VerifyProfile(p *Profile) (Result, error)

VerifyProfile verifies d against the checks enabled in profile p.

func (*Document) XMPMetadata

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

XMPMetadata returns the document's raw XMP metadata packet (Root/Metadata), decoded and normalised to UTF-8. It returns an error if the document has no XMP metadata stream, regardless of whether the document otherwise validates as PDF/A.

type FileResult

type FileResult struct {
	Path   string
	Result Result
	Err    error
}

func VerifyAll

func VerifyAll(paths []string, t LevelType) []FileResult

VerifyAll opens and verifies multiple PDF files concurrently against conformance level t.

type LevelType

type LevelType string
const (
	Undefined LevelType = "undefined"
	A_1B      LevelType = "A-1b"
)

type Lexer

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

Lexer holds the state of the current chunk being parsed.

func NewLexer

func NewLexer(r io.Reader) *Lexer

NewLexer creates a lexer for a specific chunk of data.

func NewLexerAt

func NewLexerAt(r io.Reader, offset int64) *Lexer

func (*Lexer) NextToken

func (l *Lexer) NextToken() Token

NextToken returns the next distinct token from the stream.

func (*Lexer) Release

func (l *Lexer) Release()

Release returns l's underlying bufio.Reader to the pool for reuse by a later Lexer. Callers that construct a short-lived Lexer (the common case) should defer Release once the lexer is no longer needed.

func (*Lexer) UnreadToken

func (l *Lexer) UnreadToken(t Token)

type PDFArray

type PDFArray []PDFValue

type PDFBoolean

type PDFBoolean bool

type PDFDict

type PDFDict struct {
	Entries   map[string]PDFValue
	HasStream bool
	// RawStream holds the undecoded bytes of a stream object, retained for
	// content-stream and metadata inspection.
	RawStream []byte
}

func NewPDFDict

func NewPDFDict() PDFDict

type PDFError

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

func (PDFError) Check

func (e PDFError) Check() Check

Check returns the registered Check this violation corresponds to.

func (PDFError) Error

func (e PDFError) Error() string

func (PDFError) IsDocumentLevel

func (e PDFError) IsDocumentLevel() bool

IsDocumentLevel reports whether this violation applies to the document as a whole rather than to a specific page.

func (PDFError) Messages

func (e PDFError) Messages() []string

Messages returns the underlying error messages for this violation.

func (PDFError) ObjectRef

func (e PDFError) ObjectRef() (ref PDFRef, ok bool)

ObjectRef returns the indirect object this violation was reported against, and whether one was recorded. Not every violation is tied to a single object (e.g. file-header or trailer issues), in which case ok is false.

func (PDFError) Page

func (e PDFError) Page() int

Page returns the 1-based page number this violation was found on, or 0 if the violation is document-level (see IsDocumentLevel).

func (PDFError) String

func (e PDFError) String() string

type PDFHexString

type PDFHexString struct{ Value string }

type PDFInteger

type PDFInteger int

type PDFName

type PDFName struct{ Value string }

type PDFReal

type PDFReal float32

type PDFRef

type PDFRef struct {
	ObjNum int
	GenNum int
}

type PDFString

type PDFString struct{ Value string }

type PDFValue

type PDFValue any

type Profile

type Profile struct {
	Level LevelType

	// SkipUnreachableXObjects, when true, suppresses checks on Form XObjects
	// never invoked via Do from a reachable content stream. ISO 19005-1
	// (6.2.3.3, 6.2.10) applies to every Form XObject, so Legacy_1B keeps this
	// false; PDFA_1B sets it true to match veraPDF's lenient interpretation.
	SkipUnreachableXObjects bool
	// contains filtered or unexported fields
}

Profile is a mutable set of enabled PDF/A checks for a conformance level, used by VerifyProfile. Mutators (Clear, AddCheck, RemoveCheck) return a new *Profile, leaving the receiver unchanged.

var Legacy_1B *Profile

Legacy_1B is the strict, fully spec-literal PDF/A-1b profile: every check enabled, every Form XObject checked regardless of reachability. Matches the Isartor suite's interpretation, which is stricter than veraPDF's in places.

var PDFA_1B *Profile

PDFA_1B is the default PDF/A-1b profile, tuned to match veraPDF's interpretation of the spec. Used by Verify(A_1B).

func NewProfile

func NewProfile(level LevelType) *Profile

NewProfile returns an empty profile for the given conformance level.

func (*Profile) AddCheck

func (p *Profile) AddCheck(checks ...Check) *Profile

AddCheck returns a new profile with the given checks added to the enabled set.

func (*Profile) Checks

func (p *Profile) Checks() []Check

Checks returns the list of currently enabled checks in catalog order.

func (*Profile) Clear

func (p *Profile) Clear() *Profile

Clear returns a new profile with the same conformance level but no checks enabled. Behavioral flags (SkipUnreachableXObjects) are preserved.

func (*Profile) Has

func (p *Profile) Has(c Check) bool

Has reports whether check c is currently enabled in this profile.

func (*Profile) RemoveCheck

func (p *Profile) RemoveCheck(checks ...Check) *Profile

RemoveCheck returns a new profile with the given checks removed from the enabled set.

func (*Profile) String

func (p *Profile) String() string

String returns a human-readable summary of the profile.

type Result

type Result struct {
	Type   LevelType
	Valid  bool
	Issues []PDFError
}

func (Result) Checks

func (r Result) Checks() []Check

Checks returns the distinct Checks violated in r.Issues, sorted by clause in numeric (dotted-segment) order, e.g. "6.2.9" before "6.2.10", then by subclause.

func (Result) Count

func (r Result) Count() int

Count returns the number of issues found.

func (Result) IssuesByCheck

func (r Result) IssuesByCheck() map[Check][]PDFError

IssuesByCheck groups r.Issues by their violated Check.

func (Result) IssuesForCheck

func (r Result) IssuesForCheck(c Check) []PDFError

IssuesForCheck returns the issues that correspond to the given registered Check.

func (Result) IssuesOnPage

func (r Result) IssuesOnPage(page int) []PDFError

IssuesOnPage returns the issues found on the given 1-based page number. Pass 0 to get document-level issues (see PDFError.IsDocumentLevel).

func (Result) Summary

func (r Result) Summary() string

Summary returns a human-readable multi-line report: a validity line followed by one line per violated Check with its issue count, in clause order.

type Token

type Token struct {
	Type  TokenType
	Value string
}

Token represents a distinct piece of syntax from the PDF.

type TokenType

type TokenType int
const (
	TokenError TokenType = iota
	TokenEOF
	TokenBoolean
	TokenInteger
	TokenReal
	TokenString
	TokenHexString
	TokenName
	TokenKeyword
	TokenArrayStart
	TokenArrayEnd
	TokenDictStart
	TokenDictEnd
	TokenObjectStart
	TokenObjectEnd
	TokenStreamStart
	TokenStreamEnd
)

type ValidationContext

type ValidationContext struct {
	PageIndex   map[int]int
	CurrentPage int

	// ReachableXObjectPtrs is the set of Entries-map pointers of Form XObjects
	// that are actually invoked (via Do) from page or other reachable content
	// streams. Nil means unknown (treat everything as reachable).
	ReachableXObjectPtrs map[uintptr]bool

	// InvisibleOnlyFontPtrs is the set of font dictionary Entries-map pointers
	// that are used for text showing exclusively under rendering mode 3 or 7
	// (invisible) somewhere in the document, and never under any other mode.
	// Such fonts are never actually rendered, so 6.3.3.2 (CIDToGIDMap), 6.3.5
	// (glyph coverage) and 6.3.6 (advance width consistency) do not apply.
	InvisibleOnlyFontPtrs map[uintptr]bool

	// UsedCharCodes maps a simple (non-composite) font's Entries-map pointer
	// to the set of single-byte character codes actually passed to a
	// text-showing operator somewhere in the document. A subset font's
	// CharSet only needs to list glyphs "used for rendering" (6.3.5); a code
	// with a non-zero Widths entry that is never actually shown need not be
	// present. Nil for a font means no usage info was collected for it.
	UsedCharCodes map[uintptr]map[int]bool

	// UsedCIDs maps a composite CIDFont's descendant-dict Entries-map pointer
	// to the set of CIDs actually passed to a text-showing operator somewhere
	// in the document (decoded as 2-byte big-endian codes, valid for the
	// Identity-H/Identity-V encodings the verifier can decode). A CID listed
	// in the W width array but never shown need not have a glyph in the
	// embedded font program (6.3.5); only collected when the font's Encoding
	// is Identity-H/V — for any other CMap, usage is left unknown so callers
	// fall back to checking every W entry.
	UsedCIDs map[uintptr]map[int]bool
	// contains filtered or unexported fields
}

func (*ValidationContext) Report

func (ctx *ValidationContext) Report(c Check, obj PDFValue, msg string)

Report records a single violation of c against obj.

func (*ValidationContext) ReportErrs

func (ctx *ValidationContext) ReportErrs(c Check, obj PDFValue, errs []error)

ReportErrs records a violation of c against obj carrying multiple underlying error messages.

Directories

Path Synopsis
benchmarks
cmd/gopdfrab-bench command
Command gopdfrab-bench drives the gopdfrab library for benchmarking against other PDF/A verifiers (veraPDF, PDFBox Preflight, a JS validator).
Command gopdfrab-bench drives the gopdfrab library for benchmarking against other PDF/A verifiers (veraPDF, PDFBox Preflight, a JS validator).

Jump to

Keyboard shortcuts

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