PDF
Purpose
Read, manipulate, and produce PDFs — including the scanned ones that contain no text at all and the ones whose tables are drawn rather than structured.
When to Use
- Extracting text or tables from a PDF.
- Merging, splitting, or rotating pages.
- Filling a PDF form.
- OCR on a scanned document.
- Generating a PDF report or invoice.
Capabilities
- Text extraction, with layout preservation where it matters.
- Table extraction.
- Page operations: merge, split, rotate, reorder, watermark.
- Form field reading and filling.
- OCR for scanned or image-only PDFs.
- Generation with proper typography and pagination.
Inputs
- The source PDF, and whether it contains real text or images of text.
- The target: extracted data, a modified PDF, or a new one.
Outputs
- Extracted text or structured tables.
- A valid modified or generated PDF.
Workflow
- Determine whether it has text at all — Extract from the first page. If nothing comes out, it is a scan, and every text-based approach will silently return nothing. That is the single most important check.
- Choose the extractor by the task — Simple text extraction for prose; a layout-aware extractor for anything where column position carries meaning; a dedicated table extractor for tables.
- OCR only when necessary — It is slow and imperfect. If the PDF has a text layer, use it.
- Preserve what matters when editing — Merging PDFs drops bookmarks, form fields, and annotations unless you carry them across deliberately.
- Verify the output — Open it. A PDF that a library writes without error can still be structurally broken.
Best Practices
- A PDF that returns an empty string from text extraction is not empty; it is a scan. Check for this before concluding the file is corrupt.
- Tables in PDFs are usually drawn lines and positioned text, not structured tables. A general text extractor will interleave the columns into nonsense. Use a table-specific tool.
- Text extraction order follows the PDF's internal content stream, not the visual reading order. A two-column layout will frequently extract as interleaved lines.
- OCR quality depends overwhelmingly on the input image. Deskew and increase the contrast before OCR; it is worth more than any tuning of the OCR engine.
- Do not use
pypdf for text extraction quality — it is fine for page operations and poor at text. Use pdfplumber or PyMuPDF.
- A generated PDF needs embedded fonts, or it will render differently on every machine that lacks them.
Examples
The check that must come first:
import pdfplumber
with pdfplumber.open("document.pdf") as pdf:
first_page_text = pdf.pages[0].extract_text() or ""
if len(first_page_text.strip()) < 50:
# This is a scan. Every text-based approach will return nothing, silently.
# It is not a corrupt file and it is not an empty document.
text = ocr_pdf("document.pdf")
else:
text = "\n".join((p.extract_text() or "") for p in pdf.pages)
Table extraction, which text extraction cannot do:
with pdfplumber.open("invoice.pdf") as pdf:
for page in pdf.pages:
# A general text extractor turns this into interleaved gibberish, because
# the "table" is drawn lines and absolutely-positioned text.
for table in page.extract_tables():
header, *rows = table
df = pd.DataFrame(rows, columns=[h.strip() if h else "" for h in header])
yield df
OCR with preprocessing, which matters more than the OCR settings:
import fitz # PyMuPDF
import pytesseract
from PIL import Image, ImageOps
def ocr_pdf(path: str, dpi: int = 300) -> str:
doc = fitz.open(path)
pages = []
for page in doc:
# 300 DPI is the practical minimum for reliable OCR. 150 halves the accuracy.
pix = page.get_pixmap(dpi=dpi)
img = Image.frombytes("RGB", (pix.width, pix.height), pix.samples)
# Preprocessing is worth more than any OCR engine parameter.
img = ImageOps.grayscale(img)
img = ImageOps.autocontrast(img)
pages.append(pytesseract.image_to_string(img, config="--psm 6"))
return "\n\n".join(pages)
Notes
--psm 6 tells Tesseract to assume a uniform block of text, which is correct for most scanned documents and substantially more accurate than the default automatic page segmentation on a clean scan.
- PyMuPDF (
fitz) is significantly faster than the alternatives for rendering and text extraction, and its licence (AGPL) matters for commercial use — check before adopting it.
- A PDF form's fields are named, and the names are frequently not what the visible labels say. Enumerate the fields before filling them.
1---2name: pdf3description: Use when working with PDF files. Covers text and table extraction, merging and splitting, form filling, watermarking, OCR for scanned documents, and generating PDFs.4---56# PDF78## Purpose910Read, manipulate, and produce PDFs — including the scanned ones that contain no text at all and the ones whose tables are drawn rather than structured.1112## When to Use1314- Extracting text or tables from a PDF.15- Merging, splitting, or rotating pages.16- Filling a PDF form.17- OCR on a scanned document.18- Generating a PDF report or invoice.1920## Capabilities2122- Text extraction, with layout preservation where it matters.23- Table extraction.24- Page operations: merge, split, rotate, reorder, watermark.25- Form field reading and filling.26- OCR for scanned or image-only PDFs.27- Generation with proper typography and pagination.2829## Inputs3031- The source PDF, and whether it contains real text or images of text.32- The target: extracted data, a modified PDF, or a new one.3334## Outputs3536- Extracted text or structured tables.37- A valid modified or generated PDF.3839## Workflow40411. **Determine whether it has text at all** — Extract from the first page. If nothing comes out, it is a scan, and every text-based approach will silently return nothing. That is the single most important check.422. **Choose the extractor by the task** — Simple text extraction for prose; a layout-aware extractor for anything where column position carries meaning; a dedicated table extractor for tables.433. **OCR only when necessary** — It is slow and imperfect. If the PDF has a text layer, use it.444. **Preserve what matters when editing** — Merging PDFs drops bookmarks, form fields, and annotations unless you carry them across deliberately.455. **Verify the output** — Open it. A PDF that a library writes without error can still be structurally broken.4647## Best Practices4849- A PDF that returns an empty string from text extraction is not empty; it is a scan. Check for this before concluding the file is corrupt.50- Tables in PDFs are usually drawn lines and positioned text, not structured tables. A general text extractor will interleave the columns into nonsense. Use a table-specific tool.51- Text extraction order follows the PDF's internal content stream, not the visual reading order. A two-column layout will frequently extract as interleaved lines.52- OCR quality depends overwhelmingly on the input image. Deskew and increase the contrast before OCR; it is worth more than any tuning of the OCR engine.53- Do not use `pypdf` for text extraction quality — it is fine for page operations and poor at text. Use `pdfplumber` or `PyMuPDF`.54- A generated PDF needs embedded fonts, or it will render differently on every machine that lacks them.5556## Examples5758**The check that must come first:**5960```python61import pdfplumber6263with pdfplumber.open("document.pdf") as pdf:64 first_page_text = pdf.pages[0].extract_text() or ""6566if len(first_page_text.strip()) < 50:67 # This is a scan. Every text-based approach will return nothing, silently.68 # It is not a corrupt file and it is not an empty document.69 text = ocr_pdf("document.pdf")70else:71 text = "\n".join((p.extract_text() or "") for p in pdf.pages)72```7374**Table extraction, which text extraction cannot do:**7576```python77with pdfplumber.open("invoice.pdf") as pdf:78 for page in pdf.pages:79 # A general text extractor turns this into interleaved gibberish, because80 # the "table" is drawn lines and absolutely-positioned text.81 for table in page.extract_tables():82 header, *rows = table83 df = pd.DataFrame(rows, columns=[h.strip() if h else "" for h in header])84 yield df85```8687**OCR with preprocessing, which matters more than the OCR settings:**8889```python90import fitz # PyMuPDF91import pytesseract92from PIL import Image, ImageOps9394def ocr_pdf(path: str, dpi: int = 300) -> str:95 doc = fitz.open(path)96 pages = []9798 for page in doc:99 # 300 DPI is the practical minimum for reliable OCR. 150 halves the accuracy.100 pix = page.get_pixmap(dpi=dpi)101 img = Image.frombytes("RGB", (pix.width, pix.height), pix.samples)102103 # Preprocessing is worth more than any OCR engine parameter.104 img = ImageOps.grayscale(img)105 img = ImageOps.autocontrast(img)106107 pages.append(pytesseract.image_to_string(img, config="--psm 6"))108109 return "\n\n".join(pages)110```111112## Notes113114- `--psm 6` tells Tesseract to assume a uniform block of text, which is correct for most scanned documents and substantially more accurate than the default automatic page segmentation on a clean scan.115- PyMuPDF (`fitz`) is significantly faster than the alternatives for rendering and text extraction, and its licence (AGPL) matters for commercial use — check before adopting it.116- A PDF form's fields are named, and the names are frequently not what the visible labels say. Enumerate the fields before filling them.