2026 · command-line tool
pdfctl
a command-line toolkit for PDF manipulation: merge, split, delete, rotate, and extract text.
why
PDFs are one of those formats everyone uses and almost nobody understands. i wanted to know what's actually inside one, and the fastest way to find out was to build a tool that operates on them: open, rearrange, and pull text out of real documents, from the command line, with no black boxes.
the problem
a PDF isn't a document the way a text file is text. it's a container of content streams, fonts, images, annotations, and cross-references, and every operation has to decide what survives the trip. merge two files and whose metadata wins? delete pages and what happens to the links that pointed at them? the interesting work is all in these preservation questions, not in parsing bytes.
architecture
the codebase is layered so each concern has exactly one home:
CLI (picocli)
→ UseCase (validation, temp file, atomic move)
→ PdfBoxService (interface)
→ PDFBox (Loader, PDDocument, importPage, PDFTextStripper)
an explicit composition root wires everything together, so the use cases never know which PDF library sits underneath. commands are atomic: work happens on a temp file, then moves into place, and a small exit-code contract (0 for success, 1–4 for usage, I/O, corrupt input, and encryption failures) makes the tool scriptable.
what i learned
three things stuck. first, interfaces at the boundary earn their keep: the service interface is the only reason the core logic is testable without real PDFs. second, test fixtures should be generated, not committed: all 165 tests build their PDFs in code, so the repo carries no binary blobs. third, CLIs are contracts: exit codes, 1-indexed page ranges, and --force semantics are the API, and they deserve the same care as a REST schema.
what broke
the honest limitations, stated upfront in the README rather than discovered by users: bookmarks, forms, and embedded files don't survive page operations; cross-page links can dangle when their target is deleted; large multi-file merges hold the destination in memory. each one taught me something about what a PDF actually is, which was the point all along.
what's next
smaller memory footprint for large merges, and content-aware work: understanding images, fonts, and text streams inside the file, which is where real size optimization would live.
all projects