Skip to content

Text Extraction

huzpsb edited this page Oct 26, 2026 · 2 revisions

Text Extraction

Extract plain text from PDFs with extract -m text — the fork's flagship feature, new in pdfcpu2. Output behavior is aligned with Apache PDFBox's default extractor, tuned via differential testing against PDFBox over a corpus of 1200+ real-world Chinese PDFs spanning different eras — from decades-old generators to modern ones.

Works on digital PDFs directly, and on scanned PDFs after running OCR.

Command line

pdfcpu2 extract -m text inFile outFile|-
Arg Meaning
-m, --mode text extraction mode t(ext) (also image, font, content, page, meta)
-p, --pages page selection, see Page Selection
outFile output file; - writes to stdout

Examples:

# Whole document to a file
pdfcpu2 extract -m text in.pdf out.txt

# Whole document to stdout
pdfcpu2 extract -m text in.pdf -

# Pages 1-3 to stdout
pdfcpu2 extract -m text -p 1-3 in.pdf -

# From stdin to stdout
cat in.pdf | pdfcpu2 extract -m text - -

Go API

Four entry points cover the common shapes:

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/huzpsb/pdfcpu2/pkg/api"
	"github.com/huzpsb/pdfcpu2/pkg/pdfcpu/model"
)

func main() {
	ctx := context.Background()
	conf := model.NewDefaultConfiguration()
	f, _ := os.Open("in.pdf")
	defer f.Close()

	// 1) Per-page extraction: paginated results incl. line coordinates
	texts, err := api.ExtractText(ctx, f, []string{"1-3"}, nil, conf)
	if err != nil {
		panic(err)
	}
	for _, pt := range texts {
		fmt.Printf("page %d:\n", pt.PageNr)
		for _, line := range pt.Lines {
			// line.Text, line.X, line.Y, line.XMax (user-space coordinates)
			fmt.Printf("  (%.1f,%.1f) %s\n", line.X, line.Y, line.Text)
		}
	}

	// 2) Whole document as one string
	f.Seek(0, 0)
	s, err := api.ExtractTextString(ctx, f, nil, nil, conf)
	if err != nil {
		panic(err)
	}
	fmt.Println(s)

	// 3) Streaming reader (constant memory)
	f.Seek(0, 0)
	rd, err := api.ExtractTextReader(ctx, f, nil, nil, conf)
	if err != nil {
		panic(err)
	}
	_, _ = rd.Read(make([]byte, 4096))

	// 4) One-shot file helper
	err = api.ExtractTextFile(ctx, "in.pdf", "out.txt", nil, nil, conf)
	_ = err
}

model.TextExtractOptions

A nil *TextExtractOptions means defaults. Fields:

Field Default Meaning
SortByPosition false Sort lines top-down, left-right. Off = content stream order, which usually reflects natural writing order.
LineTolerance built-in Max vertical distance (points) for glyphs to be grouped into one line.
WordGapFactor built-in Fraction of font size a horizontal gap must exceed to insert a space.
NormalizeWhitespace false Collapse whitespace runs; trim line edges.
PageSeparator "\f" Separator between pages in consolidated output.
EOL "\n" Line delimiter.
opts := &model.TextExtractOptions{
	SortByPosition:     true,
	NormalizeWhitespace: true,
	EOL:                "\r\n",
}
s, err := api.ExtractTextString(ctx, f, nil, opts, conf)

What is supported

  • ToUnicode CMaps — including Type42 fonts and array-form bfrange
  • Embedded TrueType fonts — cmap table fallback when no ToUnicode map exists
  • Legacy CJK encodings — GBK, Big5
  • CID fallbacks via character collections — Adobe-GB1, Adobe-CNS1
  • Simple fonts — Encoding + Differences arrays
  • Latin-1 last resort — matching PDFBox's behavior for broken mappings
  • Form XObjects — recursive descent through nested form hierarchies
  • Overprint deduplication — text drawn repeatedly at the same position is emitted once
  • Line reconstruction — stream order by default, optional sort by position

The OCR → text pipeline

Scanned pages contain images, not text. Run OCR first to embed an invisible text layer, then extract:

pdfcpu2 ocr scan.pdf scan_ocr.pdf
pdfcpu2 extract -m text scan_ocr.pdf -

Quality assurance

The extractor is regression-tested against PDFBox output over a 1200+ document corpus of real-world Chinese PDFs spanning different eras — old generators with legacy encodings (GBK/Big5, bare CID fonts without ToUnicode maps) alongside modern ones. Divergences from PDFBox are treated as bugs unless explicitly justified.

See also

Clone this wiki locally