A lightweight, dependency-free Go library for splitting PDFs into individual pages while preserving page content, fonts, and images. It supports both conventional and stream-based cross-references, object streams, and password encryption handled by the PDF Standard Security Handler.
- Splits a PDF into one PDF file per page
- Uses only the Go standard library
- Supports conventional xref tables, xref streams, and object streams
- Opens RC4, AES-128, and AES-256 password-encrypted PDFs
- Preserves content streams, fonts, images, and Form XObjects
- Does not decode or recompress page content, image, or font streams
- Resolves recursively referenced page resources
- Resolves inherited
Resources,MediaBox,CropBox, andRotatevalues - Produces deterministic, self-contained PDF files with new object numbers and cross-reference tables
- Writes unencrypted one-page PDFs after opening an encrypted source
- Returns explicit errors for invalid passwords and unsupported security handlers
- Extracts ASCII/WinAnsi text and Japanese text backed by a
ToUnicodeCMap
- Go 1.22 or later
package main
import (
"errors"
"log"
"github.com/takeshy/minipdfsplit"
)
func main() {
err := pdfsplit.Split("input.pdf", "pages")
if err != nil {
switch {
case errors.Is(err, pdfsplit.ErrInvalidPassword):
log.Fatalf("a PDF password is required: %v", err)
case errors.Is(err, pdfsplit.ErrPublicKeyPDF),
errors.Is(err, pdfsplit.ErrEncryptedPDF):
log.Fatalf("unsupported PDF: %v", err)
default:
log.Fatal(err)
}
}
}Split creates the output directory when necessary and writes files in page
order:
pages/
├── page-0001.pdf
├── page-0002.pdf
└── page-0003.pdf
Existing files with these names are replaced atomically.
For a PDF with a non-empty user or owner password, call SplitWithPassword:
err := pdfsplit.SplitWithPassword("protected.pdf", "pages", "password")Split automatically opens encrypted PDFs whose user password is empty, which
is common for PDFs that open normally but restrict editing.
Use SplitBytes when the input is already available as a byte slice:
parts, err := pdfsplit.SplitBytes(data)
if err != nil {
return err
}
for pageIndex, pagePDF := range parts {
// pagePDF is a complete, one-page PDF.
_ = pageIndex
_ = pagePDF
}Use SplitReader for an io.Reader:
parts, err := pdfsplit.SplitReader(reader)Password variants are also available for in-memory input:
parts, err := pdfsplit.SplitBytesWithPassword(data, "password")
parts, err := pdfsplit.SplitReaderWithPassword(reader, "password")Both functions return one complete PDF byte slice per page, in the original page order.
ExtractTextFile returns plain text for each page:
pages, err := pdfsplit.ExtractTextFile("input.pdf")
if err != nil {
return err
}
for _, page := range pages {
fmt.Printf("Page %d\n%s\n", page.Number, page.Text)
}Byte slice, reader, and password variants are also available:
pages, err := pdfsplit.ExtractText(data)
pages, err := pdfsplit.ExtractTextReader(reader)
pages, err := pdfsplit.ExtractTextWithPassword(data, "password")
pages, err := pdfsplit.ExtractTextFileWithPassword("input.pdf", "password")
pages, err := pdfsplit.ExtractTextReaderWithPassword(reader, "password")Text extraction supports ASCII and WinAnsi simple fonts, plus Japanese and
other Unicode text when the PDF font provides a ToUnicode CMap. It handles
Tj, TJ, ', and " text operators and recursively extracts text from Form
XObjects.
The result follows content-stream order with basic line-break reconstruction.
It does not perform OCR, advanced multi-column reading-order analysis, vertical
writing reconstruction, or reliable CID-to-Unicode recovery when ToUnicode
is missing.
Images and fonts are not extracted, decoded, or recompressed. The library follows references from the page's resource dictionary and copies the original objects and stream bytes into the resulting PDF. This also covers resources referenced by Form XObjects, including nested images and fonts.
An image shared by several source pages is copied into each resulting PDF that uses it. Images used only by discarded annotations or form fields are not copied.
- Conventional cross-reference (
xref) tables - Cross-reference streams, including PNG Predictor decoding
- Object streams
/Prevxref chains and hybrid-reference files- Standard Security Handler revisions 2 through 6
- RC4, AES-128, and AES-256 crypt filters
- Empty, user, and owner passwords
- ASCII and WinAnsi text in simple fonts
ToUnicodeCMaps usingbfcharandbfrange- Horizontal text in page content and Form XObjects
- Catalog and page trees using indirect references
- Direct and indirect stream lengths
- Page content streams and resource dictionaries
- Fonts, image XObjects, Form XObjects, graphics states, color spaces, patterns, and shadings reachable from retained page data
- Direct or inherited
Resources,MediaBox,CropBox, andRotate - Page-level
BleedBox,TrimBox,ArtBox,Group, andUserUnit
The package deliberately stays small and does not attempt to support or repair every valid PDF. It rejects or does not implement:
- Public-key and certificate encryption:
ErrPublicKeyPDF - Custom security handlers and unknown crypt filters:
ErrEncryptedPDF - Missing or incorrect passwords:
ErrInvalidPassword - Malformed PDFs or unsupported object structures:
ErrMalformedPDF - OCR and text contained only in raster images
- Complete Unicode recovery for CID fonts without
ToUnicode - Advanced layout reconstruction for columns, tables, and vertical writing
The following document features are intentionally discarded:
- Annotations
- AcroForm fields and widget annotations
- Outlines and bookmarks
- Metadata
- Tagged-PDF structure
- Page transitions and presentation metadata
Because annotations and form fields are discarded, their appearance streams and resources are also omitted unless the retained page content references the same objects independently.
For each page, pdfsplit:
- Reads
startxref, follows all xref sections, and decodes xref streams when present. - Loads compressed objects from object streams.
- Authenticates the supplied password and decrypts strings and streams when the input uses the Standard Security Handler.
- Walks
Catalog -> Pages -> Kids -> Pageand resolves inherited page values. - Retains the page's display content and recursively collects referenced objects.
- Assigns new object numbers and rewrites every retained indirect reference.
- Writes an unencrypted, minimal one-page PDF with a new catalog, page tree, cross-reference table, and trailer.
After any required decryption, page content, image, and font streams are copied as opaque bytes without decoding their content filters.
All format-related errors can be checked with errors.Is:
if errors.Is(err, pdfsplit.ErrMalformedPDF) {
// The input is damaged or outside the parser's supported object syntax.
}Filesystem and reader errors retain their original error through wrapping.
Run the tests with:
go test ./...The test suite covers inherited page attributes, recursively referenced images
and fonts, direct and indirect stream lengths, xref and object streams,
password encryption, ASCII/WinAnsi and ToUnicode text extraction, Form
XObjects, file output, and malformed input.