Skip to main content

PDF Processing Guide

Overview

This guide covers essential PDF processing operations using Python libraries and command-line tools. For advanced features, JavaScript libraries, and detailed examples, see REFERENCE.md. If you need to fill out a PDF form, read FORMS.md and follow its instructions.

Quick Start

Python Libraries

pypdf - Basic Operations

Merge PDFs

Split PDF

Extract Metadata

Rotate Pages

pdfplumber - Text and Table Extraction

Extract Text with Layout

Extract Tables

Advanced Table Extraction

reportlab - Create PDFs

🚨 MANDATORY RULE — Read before writing any reportlab code Every reportlab script MUST import and call setup_chinese_pdf() first — even for English-only content. Step 0 — import the helper (this skill ships it as scripts/setup_chinese_pdf.py):
If your script is NOT in the skill root directory, use the absolute path to scripts/ instead. Then follow these rules:
  1. Call cn_font, styles = setup_chinese_pdf() as the very first reportlab operation.
  2. Use the returned styles for all Paragraph(...) calls — do NOT call getSampleStyleSheet() separately.
  3. For any custom ParagraphStyle, use parent=styles['X'] so it inherits the CJK font.
  4. For Canvas API, call c.setFont(cn_font, size) before every c.drawString() / c.drawCentredString().
  5. NEVER use Helvetica, Times-Roman, or Courier for any text that may contain Chinese.
Skipping any of these steps causes every Chinese character to render as a solid black box. There is NO error — the script exits 0 and the PDF looks silently broken. All examples below already follow this pattern. You can use any of them as a starting template.

Basic PDF Creation

Create PDF with Multiple Pages

Chinese / CJK PDF Content

⚠️ IMPORTANT — NEVER fall back to English: If the user asked for Chinese content, the PDF must contain Chinese. Do not replace Chinese content with English and claim it is a “limitation” — that is incorrect behaviour. Follow the instructions below to produce correct Chinese output on every platform.
ReportLab’s built-in fonts (Helvetica, Times-Roman, Courier) are Latin-only — they contain no CJK glyphs. Passing Chinese text to these fonts causes characters to silently render as blank spaces or boxes. The script exits with code 0, so there is no error to catch; the PDF simply has no Chinese text. Why intermittent garbling happens — three traps in reportlab:
  1. ParagraphStyle copies parent attributes at construction time. ParagraphStyle('X', parent=styles['Title']) copies fontName='Helvetica-Bold' into the new object’s __dict__ immediately. Patching the parent afterwards has no effect on already-created children. Every style that may contain Chinese MUST receive fontName=cn_font explicitly, or inherit from an already-patched parent.
  2. getSampleStyleSheet() returns a new instance every call. Patching one instance does not affect the next call. So styles['Normal'].fontName = cn_font only works for that one styles object.
  3. TableStyle FONTNAME is silently ignored for Paragraph cells. When a table cell contains a Paragraph object, the Paragraph’s own fontName wins — the TableStyle('FONTNAME', ...) setting has zero effect on it.
Fix: use setup_chinese_pdf() below. It returns (cn_font, styles) where styles is a pre-patched stylesheet. Use it for ALL text — then Chinese never breaks.
The complete solution — setup_chinese_pdf()
Preferred: import from the pre-built module scripts/setup_chinese_pdf.py (see MANDATORY RULE above).
Fallback: if importing is not possible (e.g. Windows base64 workflow), copy the function below verbatim into your script.
Source code: scripts/setup_chinese_pdf.py
Using setup_chinese_pdf() — Platypus (Paragraph / Table)
Using setup_chinese_pdf() — Canvas API
Canvas drawString / drawCentredString requires explicit c.setFont(cn_font, size) before every call that contains Chinese. The font is NOT inherited between draw calls.
Windows: write Python scripts without any Chinese in the shell command
On Windows, both PowerShell and CMD interpret the command line before passing it to Python. Their default encoding (GBK / CP936) corrupts Chinese string literals in the shell command before Python ever sees them — even inside python -c "...". Do NOT do this (Chinese in the shell string — always breaks on GBK consoles):
Correct approach: encode Chinese content as base64, decode inside Python The shell command contains only ASCII. Python receives the base64 string intact regardless of the console encoding, then decodes it back to UTF-8 internally.
Full workflow example (model should follow this pattern every time):
Why this works: base64.b64decode(...) is pure ASCII in the shell. The Chinese characters are encoded as \uXXXX Unicode escapes (ASCII-safe) inside the script string, and Python’s textwrap.dedent + UTF-8 encode/decode reconstruct the original Chinese bytes correctly — the console encoding never touches the content.
Rule: On Windows, never put Chinese string literals inside a shell command (python -c "...", PowerShell heredoc, or echo). Always use base64 or Unicode escapes (\uXXXX) to carry non-ASCII content through the shell layer.

Subscripts and Superscripts

IMPORTANT: Never use Unicode subscript/superscript characters (₀₁₂₃₄₅₆₇₈₉, ⁰¹²³⁴⁵⁶⁷⁸⁹) in ReportLab PDFs. The built-in fonts do not include these glyphs, causing them to render as solid black boxes. Instead, use ReportLab’s XML markup tags in Paragraph objects:
For canvas-drawn text (not Paragraph objects), manually adjust font the size and position rather than using Unicode subscripts/superscripts.

Command-Line Tools

pdftotext (poppler-utils)

qpdf

pdftk (if available)

Common Tasks

Extract Text from Scanned PDFs

Add Watermark

Extract Images

Password Protection

Quick Reference

Next Steps

  • For advanced pypdfium2 usage, see REFERENCE.md
  • For JavaScript libraries (pdf-lib), see REFERENCE.md
  • If you need to fill out a PDF form, follow the instructions in FORMS.md
  • For troubleshooting guides, see REFERENCE.md