Skip to content

Repository files navigation

MiniPDFSplit

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.

日本語

Features

  • 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, and Rotate values
  • 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 ToUnicode CMap

Requirements

  • Go 1.22 or later

Usage

Split a file

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.

Split in memory

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.

Extract text

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

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.

Supported PDF scope

  • Conventional cross-reference (xref) tables
  • Cross-reference streams, including PNG Predictor decoding
  • Object streams
  • /Prev xref 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
  • ToUnicode CMaps using bfchar and bfrange
  • 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, and Rotate
  • Page-level BleedBox, TrimBox, ArtBox, Group, and UserUnit

Unsupported PDF scope

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.

How it works

For each page, pdfsplit:

  1. Reads startxref, follows all xref sections, and decodes xref streams when present.
  2. Loads compressed objects from object streams.
  3. Authenticates the supplied password and decrypts strings and streams when the input uses the Standard Security Handler.
  4. Walks Catalog -> Pages -> Kids -> Page and resolves inherited page values.
  5. Retains the page's display content and recursively collects referenced objects.
  6. Assigns new object numbers and rewrites every retained indirect reference.
  7. 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.

Error handling

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.

Development

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.

About

A lightweight, dependency-free Go library for splitting conventional, unencrypted PDFs into individual pages while preserving content, fonts, and images.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages