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

pdf

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2026 License: MIT Imports: 22 Imported by: 4

README

pdf

Go Reference CI

Robust PDF text extraction in pure Go — with glyph positions, hardened against real-world (and hostile) files.

Zero dependencies. Built entirely on Go's standard library.

import "github.com/giraffesyo/pdf"

doc, err := pdf.Extract(ctx, readerAt, size)
if err != nil { ... }

text := doc.Text()             // whole document, plain text
page := doc.Pages[0]
line := page.Text()            // per-page reconstruction
glyphs := page.Glyphs          // text, origin, advance, direction, size; Baseline(), Quad()

Page.Text reconstructs reading order from direction-aware glyph baselines, and word boundaries are recovered from glyph gaps and font metrics — so PDFs that encode no space characters at all still come out readable. Rotated and vertical runs retain their reading direction. Page.TextIn and Page.GlyphsIn extract a rectangular region.

Extraction options and diagnostics

Extract is the permissive, all-pages convenience API. ExtractWithOptions adds passwords, page ranges, strict mode, configurable resource limits, artifact filtering, layout strategies, named CMap resolution, document extras, and an optional OCR handoff:

doc, err := pdf.ExtractWithOptions(ctx, readerAt, size, pdf.Options{
    Password: "secret",
    Pages:    []pdf.PageRange{{First: 2, Last: 5}},
    Strict:   true,
    Layout:   pdf.LayoutOptions{Mode: pdf.LayoutColumns},

    IgnoreArtifacts:    true,
    IncludeMetadata:    true,
    IncludeOutlines:    true,
    IncludeAnnotations: true,
    IncludeFormValues:  true,
})

Ligature presentation forms (fi, fl, ff, ffi, ffl, ſt, st) fold to their letter sequences by default, as pdftotext and pdf.js do, so extracted text stays searchable; set PreserveLigatures: true to keep the raw codepoints.

Permissive extraction retains recoverable output and records conditions that may have made it incomplete in Document.Warnings and Page.Warnings. Strict mode returns the partial document with a *pdf.StrictError at the first such condition. pdf.ErrPasswordRequired is available for errors.Is.

For large files, ExtractPages invokes a callback one page at a time and does not retain page glyphs in the returned document:

summary, err := pdf.ExtractPages(ctx, readerAt, size, opts, func(page pdf.Page) error {
    consume(page)
    return nil
})

The core remains dependency-free. Applications can implement pdf.OCR to handle pages whose content streams produce no text. The callback receives the original io.ReaderAt, file size, page number, and page geometry, so it can invoke the renderer/OCR engine appropriate for the application.

Why another PDF text extractor

This package grew out of converting large corpora of real-world documents with ledongthuc/pdf, whose built-in text extraction breaks on files that are common in the wild:

Real-world file ledongthuc/pdf this package
Google Docs exports (text inside Form XObjects) extracts nothing full text
Word exports (Identity-H fonts + ToUnicode CMaps) U+FFFD soup decoded
Type3 fonts with ToUnicode but an /Encoding dict control-char garbage decoded
Td/T*-positioned text (most PDFs) positions lost positioned
Array token split across /Contents segments lexer hangs forever handled
Decompression bombs, stalled filter chains unbounded CPU/memory hard budgets
Cyclic page trees stack overflow (crash) rejected upfront
Inline images (BI/ID/EI) derails the lexer skipped cleanly

Everything is implemented here, from ISO 32000 directly, with no external dependencies: the object layer (cross-reference tables and streams, object streams, hybrid references, stream filters, and standard-handler decryption), the standard font encodings and Adobe Glyph List, and everything from the content stream down — lexer, interpreter, text state machine, CTM/Form-XObject handling, variable-width ToUnicode and encoding CMaps, horizontal/vertical CID metrics, embedded TrueType/CFF/Type1 fallbacks, and tagged-PDF /ActualText.

Undecodable glyphs (no ToUnicode, no standard encoding) are dropped, not emitted as garbage, so image-only or outlined-text PDFs yield empty output you can detect, instead of plausible-looking noise.

Ligature presentation forms (fi, fl, ff, ffi, ffl, ſt, st) — which TeX-generated PDFs reach through /Differences glyph names — fold to their letter sequences, as pdftotext and pdf.js do, so extracted text stays searchable.

Limitations

  • No OCR: scanned pages and text converted to vector outlines have no text to extract unless the caller supplies an OCR implementation.
  • Layout reconstruction is heuristic. Direction-aware, content-order, and column modes are available, but highly irregular tables may still require application-specific analysis of glyph quads.
  • Identity-H and Identity-V CMaps are built in. Other named predefined CMaps are loaded through Options.CMapResolver; embedded CMap streams and usecmap inheritance are parsed natively.
  • Image filters (DCT/JPX/CCITT/JBIG2) are not decoded — text extraction itself does not need them. An OCR implementation may use its own renderer.

Benchmarks

The benchmarks/ directory is a separate module (so its dependencies stay out of this one) that compares extraction against ledongthuc/pdf and rsc.io/pdf over identical synthetic corpora. The corpus checks both whether the expected text is recovered and whether extraction completes safely. A comparator that returns incorrect text, errors, or panics is excluded from the performance run for that fixture.

Results below were measured on 2026-07-23 from the current worktree based on commit 4d13aa9, using Go 1.26.4 on macOS 26.5.1 (darwin/arm64, Apple M5 Pro). Values are one run of go test -bench . -benchmem -run '^$' -count=1 ./...; rerun on your own hardware before making a performance decision.

Corpus this package ledongthuc/pdf rsc.io/pdf
simple ok ok incorrect text
Form XObject ok incorrect text incorrect text
Flate-compressed content ok ok incorrect text
xref stream ok ok incorrect text
object stream ok ok incorrect text
RC4-encrypted ok unsupported unsupported

Latency (ns/op):

Corpus this package ledongthuc/pdf
simple 13,306 14,478
Form XObject 16,187 incorrect text
Flate content 20,431 20,407
xref stream 13,879 14,337
object stream 14,702 22,756
RC4-encrypted 34,550 unsupported

Memory (B/op) and allocations (allocs/op):

Corpus this package B/op ledongthuc/pdf B/op this package allocs/op ledongthuc/pdf allocs/op
simple 63,184 62,960 162 368
Form XObject 67,560 incorrect text 207 incorrect text
Flate content 108,624 107,888 180 386
xref stream 64,088 62,880 166 365
object stream 64,584 100,336 185 541
RC4-encrypted 95,808 unsupported 324 unsupported

To reproduce the support matrix and performance measurements:

cd benchmarks
go test -run TestCompetitorComparison -v ./...   # support matrix
go test -bench . -benchmem -run '^$' -count=1 ./... # raw results above
# For a less noisy local comparison, repeat and analyze the output:
go test -bench . -benchmem -run '^$' -count=5 ./...

The corpus is synthetic and deliberately small: it measures parser and extractor behavior, not throughput on a representative production document set. It does show that this package recovers the expected text from every fixture, including Form XObjects, xref streams, object streams, and encrypted files.

Concurrency

Extract reconstructs the pages of a long document in parallel, on cloned readers that share the cross-reference table and decryption key but resolve objects independently, and Document.Text reconstructs their text the same way. Output does not depend on it: pages, glyphs, and warnings come out in the same order either way. Options.Concurrency sets the number of workers — zero, the default, picks one per processor up to a cap and falls back to sequential extraction for short documents, and one extracts sequentially. Strict extraction, OCR, and CMapResolver always run sequentially, so a caller's own code is never invoked concurrently.

Regression corpus

testdata/corpus/ holds real documents — LaTeX, Word, and scanning-pipeline output with embedded Type1, TrueType, and CFF fonts over multiple pages — under redistributable licenses (see testdata/corpus/NOTICE.md). TestCorpusGolden pins Document.Text for each file in testdata/corpus/golden/, so a decoding or layout change is a reviewable diff; accept an intentional change with go test -run TestCorpusGolden -update. TestCorpusAllocationBudget pins bytes and allocations per extraction for each file — stable across machines, unlike timing — in testdata/corpus/golden/budget.json, and fails when a file exceeds its budget by more than 15% (go test -run TestCorpusAllocationBudget -update accepts a deliberate change). BenchmarkCorpus measures Extract plus Document.Text over the same files, and CI fails a pull request that regresses it against its base. Documents that cannot be committed can be benchmarked locally with PDF_CORPUS_DIR=/path/to/pdfs go test -bench '^BenchmarkCorpus$' -run '^$' ..

pdftest

github.com/giraffesyo/pdf/pdftest builds minimal synthetic PDFs for tests (in the spirit of httptest): document skeletons, composite fonts, ToUnicode CMaps, Form XObjects, segmented content streams. This package's own regression suite is built on it — every failure pattern above is reproduced synthetically, with fuzzing on top.

License

MIT. Embeds the Adobe Glyph List (Apache-2.0) for glyph-name decoding.

Documentation

Overview

Package pdf extracts text and direction-aware glyph geometry from PDF files.

Extract returns per-page glyph quads and baselines; Page.Text reconstructs plain text by clustering direction-compatible glyphs into lines and inserting word spacing derived from glyph gaps and font metrics.

The package is pure Go with no external dependencies. It implements the PDF object layer (cross-reference tables and streams, object streams, stream filters, and password-aware standard-handler decryption) natively per ISO 32000, and on top of it a content-stream lexer, interpreter, and font decoder tuned for real-world files: it descends into Form XObjects (where e.g. Google Docs exports keep their text), honors /ToUnicode even alongside an /Encoding dictionary, tracks positions through every text operator, honors tagged-PDF replacement text, never loops on tokens straddling /Contents array segments, and reports bounded partial extraction caused by decompression bombs, stalled filter chains, or work limits.

Index

Constants

This section is empty.

Variables

View Source
var ErrPasswordRequired = crypt.ErrPasswordRequired

ErrPasswordRequired reports that an encrypted document could not be unlocked with the supplied password.

Functions

This section is empty.

Types

type Annotation added in v0.3.0

type Annotation struct {
	Subtype     string
	Rect        Rect
	Contents    string
	Title       string
	URL         string
	Destination Destination
}

Annotation contains text-bearing annotation and link information.

type CMapResolver added in v0.3.0

type CMapResolver func(name string) ([]byte, error)

CMapResolver supplies a named predefined CMap that is not built into the package. The returned bytes use normal PDF CMap syntax.

type Destination added in v0.3.0

type Destination struct {
	Name string
	Page int
	X    float64
	Y    float64
	Zoom float64
}

Destination identifies a named destination or a destination on a page.

type Document

type Document struct {
	PageCount  int
	Pages      []Page
	Metadata   Metadata
	Outlines   []Outline
	FormFields []FormField
	Warnings   []Warning
}

Document is the extraction result for a whole file.

func Extract

func Extract(ctx context.Context, r io.ReaderAt, size int64) (*Document, error)

Extract parses the PDF in r and extracts every page's glyphs. Malformed pages yield whatever was decoded before the failure; a malformed file returns an error. The context is checked between pages and periodically while processing page operators and glyphs.

func ExtractPages added in v0.3.0

func ExtractPages(
	ctx context.Context,
	r io.ReaderAt,
	size int64,
	opts Options,
	yield func(Page) error,
) (*Document, error)

ExtractPages extracts pages one at a time and calls yield without retaining them in the returned Document. Document metadata, page count, form fields, outlines, and accumulated warnings are still returned.

func ExtractWithOptions added in v0.3.0

func ExtractWithOptions(ctx context.Context, r io.ReaderAt, size int64, opts Options) (*Document, error)

ExtractWithOptions extracts selected pages and document extras according to opts. In strict mode it returns the partial document together with the first warning as a *StrictError.

func (*Document) Text

func (d *Document) Text() string

Text reconstructs the whole document's plain text: pages separated by blank lines, empty pages skipped.

type FormField added in v0.3.0

type FormField struct {
	Name          string
	AlternateName string
	Type          string
	Value         string
	Page          int
}

FormField is one terminal AcroForm field.

type Glyph

type Glyph struct {
	Text    string  // decoded text, non-empty
	X, Y    float64 // origin: where the baseline starts
	Advance float64 // displacement along the baseline in page units
	Size    float64 // effective font size

	// Direction is the unit direction the baseline runs in. Ascent and
	// Descent are the vectors from the baseline to the far and near
	// edges of the glyph box, preserving rotation, skew, and vertical
	// writing: horizontal text has Ascent spanning the font size along
	// the text-space y axis and a zero Descent; vertical text reaches
	// half the font size to either side of its baseline.
	Direction Point
	Ascent    Point
	Descent   Point
}

Glyph is one decoded glyph (or glyph cluster) with its position in unrotated page space.

The geometry is stored compactly — a document's glyph slice is its largest allocation — and Baseline and Quad derive the full shapes. A Glyph carrying only Text, X, Y, Advance, and Size (for example one supplied by an OCR hook) is treated as upright horizontal text.

func (Glyph) Baseline added in v0.3.0

func (g Glyph) Baseline() Line

Baseline returns the segment the glyph occupies along its writing direction: from the origin, |Advance| page units along Direction.

func (Glyph) Quad added in v0.3.0

func (g Glyph) Quad() Quad

Quad returns the glyph's region perimeter: the baseline offset by Descent, then the same edge returning along Ascent.

type LayoutMode added in v0.3.0

type LayoutMode uint8

LayoutMode selects how Page.Text reconstructs text.

const (
	// LayoutPosition groups glyphs using their geometric baselines.
	LayoutPosition LayoutMode = iota
	// LayoutContentOrder preserves the order in which glyphs occur in content
	// streams, inserting line breaks only when the baseline changes.
	LayoutContentOrder
	// LayoutColumns separates wide horizontal gaps into column regions and
	// reads each column from top to bottom.
	LayoutColumns
)

type LayoutOptions added in v0.3.0

type LayoutOptions struct {
	Mode LayoutMode
}

LayoutOptions controls plain-text reconstruction.

type Limits added in v0.3.0

type Limits struct {
	MaxStreamBytes      int
	MaxOperatorsPerPage int
	MaxGlyphsPerPage    int
	MaxFormDepth        int
}

Limits bounds work performed on untrusted documents. Zero fields use the package defaults.

type Line added in v0.3.0

type Line struct {
	Start Point
	End   Point
}

Line describes a directed line segment.

type Metadata added in v0.3.0

type Metadata struct {
	Title        string
	Author       string
	Subject      string
	Keywords     string
	Creator      string
	Producer     string
	CreationDate string
	ModifiedDate string
	Trapped      string
	XMP          []byte
	Custom       map[string]string
}

Metadata contains common document information dictionary and XMP fields. Dates retain their original PDF string representation.

type OCR added in v0.3.0

type OCR interface {
	ExtractPage(context.Context, OCRRequest) ([]Glyph, error)
}

OCR extracts text for an image-only page. Implementations may invoke an external renderer or OCR engine; the core package remains dependency-free.

type OCRFunc added in v0.3.0

type OCRFunc func(context.Context, OCRRequest) ([]Glyph, error)

OCRFunc adapts a function to OCR.

func (OCRFunc) ExtractPage added in v0.3.0

func (f OCRFunc) ExtractPage(ctx context.Context, req OCRRequest) ([]Glyph, error)

ExtractPage calls f.

type OCRRequest added in v0.3.0

type OCRRequest struct {
	Reader     io.ReaderAt
	Size       int64
	PageNumber int
	Page       Page
}

OCRRequest is passed to an external OCR implementation for a page whose content streams produced no text. Reader and Size identify the original PDF.

type Options added in v0.3.0

type Options struct {
	Password string
	Pages    []PageRange
	Strict   bool
	Limits   Limits
	Layout   LayoutOptions

	IgnoreArtifacts    bool
	IncludeMetadata    bool
	IncludeOutlines    bool
	IncludeAnnotations bool
	IncludeFormValues  bool

	// PreserveLigatures keeps the Unicode presentation forms U+FB00–U+FB06
	// (ff fi fl ffi ffl ſt st) in glyph text. By default they fold to their letter
	// sequences (ff, fi, fl, ffi, ffl, st) — the form searchers, tokenizers
	// and other extractors expect — whether the glyph reached the ligature
	// codepoint through a glyph name (Type1 /Differences, the standard
	// encodings, an embedded font program) or through a ToUnicode CMap.
	PreserveLigatures bool

	CMapResolver CMapResolver
	OCR          OCR

	// Concurrency bounds how many pages are extracted at once. Zero picks
	// a worker per available processor; one extracts sequentially.
	// Extraction stays deterministic either way: pages, their glyphs, and
	// their warnings come out in the same order.
	//
	// Pages run concurrently only when nothing observable depends on the
	// order they execute in. Strict stops at the first warning, and OCR
	// and CMapResolver are caller code, so those extract sequentially
	// whatever this is set to.
	Concurrency int
}

Options controls extraction. Its zero value preserves Extract's permissive, all-pages behavior.

type Outline added in v0.3.0

type Outline struct {
	Title       string
	Destination Destination
	Children    []Outline
}

Outline is one bookmark in the document outline tree.

type Page

type Page struct {
	Number      int
	Glyphs      []Glyph
	MediaBox    Rect
	CropBox     Rect
	Rotation    int
	Annotations []Annotation
	Warnings    []Warning
	// contains filtered or unexported fields
}

Page holds the glyphs of one page in content order.

func (Page) GlyphsIn added in v0.3.0

func (p Page) GlyphsIn(region Rect) []Glyph

GlyphsIn returns glyphs whose quadrilateral intersects region.

func (Page) Text

func (p Page) Text() string

Text reconstructs a page's plain text using glyph baselines and the layout selected at extraction time. Rotated and vertical runs retain their reading direction, and spaces are inferred from geometric gaps.

func (Page) TextIn added in v0.3.0

func (p Page) TextIn(region Rect, layout LayoutOptions) string

TextIn reconstructs text from glyphs intersecting region.

func (Page) TextWithOptions added in v0.3.0

func (p Page) TextWithOptions(layout LayoutOptions) string

TextWithOptions reconstructs page text using layout rather than the layout selected during extraction.

type PageRange added in v0.3.0

type PageRange struct {
	First int
	Last  int
}

PageRange selects an inclusive, one-based page range. A Last value of zero means the final page.

type Point added in v0.3.0

type Point struct {
	X float64
	Y float64
}

Point is a coordinate in unrotated PDF page space.

type Quad added in v0.3.0

type Quad [4]Point

Quad describes a glyph-region perimeter. The first edge follows the baseline direction and the opposite edge returns toward its origin.

type Rect added in v0.3.0

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

Rect is an axis-aligned rectangle in unrotated PDF page space.

type StrictError added in v0.3.0

type StrictError struct {
	Warning Warning
}

StrictError is returned when strict extraction encounters a condition that permissive extraction would record as a Warning.

func (*StrictError) Error added in v0.3.0

func (e *StrictError) Error() string

func (*StrictError) Unwrap added in v0.3.0

func (e *StrictError) Unwrap() error

type Warning added in v0.3.0

type Warning struct {
	Page int
	Code WarningCode
	Err  error
}

Warning describes a condition that may have made an extraction incomplete. Page is one-based, or zero for a document-level warning.

func (Warning) Error added in v0.3.0

func (w Warning) Error() string

func (Warning) Unwrap added in v0.3.0

func (w Warning) Unwrap() error

type WarningCode added in v0.3.0

type WarningCode string

WarningCode identifies a recoverable extraction problem.

const (
	// WarningMalformedDocument reports malformed document-level structures
	// such as outlines or form-field trees.
	WarningMalformedDocument WarningCode = "malformed_document"
	// WarningMalformedPage reports malformed page content or page extras.
	WarningMalformedPage WarningCode = "malformed_page"
	// WarningStream reports a stream decoding or filter failure.
	WarningStream WarningCode = "stream_error"
	// WarningStreamLimit reports decoded data truncated at a configured limit.
	WarningStreamLimit WarningCode = "stream_limit"
	// WarningWorkLimit reports an operator, glyph, or nesting budget.
	WarningWorkLimit WarningCode = "work_limit"
	// WarningUnsupported reports a recoverable unsupported PDF feature.
	WarningUnsupported WarningCode = "unsupported"
	// WarningOCR reports an external OCR implementation failure.
	WarningOCR WarningCode = "ocr"
)

Directories

Path Synopsis
internal
crypt
Package crypt implements the PDF standard security handler (ISO 32000-1 §7.6.3 and ISO 32000-2 §7.6.4): RC4 and AES stream/string decryption with the file key derived from a supplied user or owner password.
Package crypt implements the PDF standard security handler (ISO 32000-1 §7.6.3 and ISO 32000-2 §7.6.4): RC4 and AES stream/string decryption with the file key derived from a supplied user or owner password.
encoding
Package encoding implements the simple-font character encodings of ISO 32000-1 Annex D and the Adobe Glyph List glyph-name algorithm.
Package encoding implements the simple-font character encodings of ISO 32000-1 Annex D and the Adobe Glyph List glyph-name algorithm.
filter
Package filter implements the PDF stream decode filters of ISO 32000-1 §7.4 that text extraction needs: FlateDecode, LZWDecode, ASCIIHexDecode, ASCII85Decode and RunLengthDecode, with the PNG and TIFF predictors.
Package filter implements the PDF stream decode filters of ISO 32000-1 §7.4 that text extraction needs: FlateDecode, LZWDecode, ASCIIHexDecode, ASCII85Decode and RunLengthDecode, with the PNG and TIFF predictors.
object
Package object is a native PDF object layer: a lexer and parser for the COS object syntax (ISO 32000-1 §7.2–7.3), cross-reference tables and streams (§7.5), and a Value model for navigating the resulting object graph.
Package object is a native PDF object layer: a lexer and parser for the COS object syntax (ISO 32000-1 §7.2–7.3), cross-reference tables and streams (§7.5), and a Value model for navigating the resulting object graph.
safeio
Package safeio bounds reads from untrusted streams: decompression bombs and stalled filter chains must cost bounded memory and CPU.
Package safeio bounds reads from untrusted streams: decompression bombs and stalled filter chains must cost bounded memory and CPU.
Package pdftest builds minimal synthetic PDF files for tests, in the spirit of net/http/httptest.
Package pdftest builds minimal synthetic PDF files for tests, in the spirit of net/http/httptest.

Jump to

Keyboard shortcuts

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