PDF Split & Convert
Convert a PDF file to readable markdown text. Handles large PDFs efficiently.
Input
$ARGUMENTS[0]— Path to the PDF file (required)
Choose the method first
Before reading anything, check whether pdftotext (part of poppler) is available:
bashwhich pdftotext
- If available → use the pdftotext path below. It is 10–100× faster than the Read tool, uses no model context for the text content, and doesn't suffer from stream idle timeouts. Always prefer this for PDFs longer than ~30 pages.
- If not available → fall back to the Read-tool path. Warn the user that large PDFs (>40 pages) may hit stream idle timeouts when run in subagents (~12 min cap). Prefer running in the main conversation for large files.
Resolve the path and get page count
- If the path is relative, resolve it relative to the current working directory.
- Run
mdls -name kMDItemNumberOfPages "<pdf_path>"to get the total page count. Ifmdlsis unavailable or returns(null), usepdftotextor Read to probe.
Method A — pdftotext (preferred)
Extract the full PDF to text in one shot, then trim at references/appendix and add page markers. pdftotext emits a form-feed character (\f) at every page break — use that for pagination.
Reference implementation (bash + awk):
bashPDF="$1" OUT="${PDF%.pdf}.md" TITLE=$(basename "${PDF%.pdf}") TMPTXT=$(mktemp) pdftotext -layout "$PDF" "$TMPTXT" awk -v title="$TITLE" ' BEGIN { print "# " title print "" page = 1 printf "---\n## Pages %d-%d\n---\n", page, page+19 next_marker = page + 20 } { # Convert form-feed page breaks to newlines and count pages n = gsub(/\f/, "\n") if (n > 0) { page += n if (page >= next_marker) { printf "\n---\n## Pages %d-%d\n---\n", next_marker, next_marker+19 next_marker += 20 } } # Build a stripped copy for heading detection. # CRITICAL: strip both form feeds AND embedded newlines — gsub above inserts # newlines into $0, which will defeat regex anchors like ^ and $ if you skip this. stripped = $0 gsub(/[\f\n]/, "", stripped) sub(/^[ \t]+/, "", stripped) sub(/[ \t]+$/, "", stripped) if (length(stripped) > 0) { # References / Bibliography — standalone word, optionally numbered, short, no prose punctuation if (length(stripped) < 50 && stripped !~ /[(),;]/) { if (stripped ~ /^([0-9]+\.?[ \t]+)?(References|REFERENCES|Bibliography|BIBLIOGRAPHY)$/) exit if (stripped == "Works Cited") exit } # Appendix — length up to ~120 chars (some titles are long), no prose punctuation if (length(stripped) < 120 && stripped !~ /[(),;]/) { # "Appendix A" alone (bare letter, no title) if (stripped ~ /^Appendix[ \t]+[A-Z][0-9]*$/) exit # "Appendix A. Title" or "Appendix A: Title" — punctuation REQUIRED to avoid # matching body-text references like "Appendix H examines the effect..." if (stripped ~ /^Appendix[ \t]+[A-Z][0-9]*[.:][ \t]+[A-Z].*$/) exit # "APPENDIX A" variants if (stripped ~ /^APPENDIX[ \t]+[A-Z][0-9]*([ \t]+.*)?$/) exit # "Online Appendix [A]" if (stripped ~ /^Online[ \t]+Appendix([ \t]+[A-Z].*)?$/) exit # "Supplemental/Supplementary/Internet Appendix" if (stripped ~ /^(Supplement(al|ary)|Internet)[ \t]+Appendix([ \t]+.*)?$/) exit } } print } ' "$TMPTXT" > "$OUT" rm -f "$TMPTXT" echo "Wrote $OUT ($(wc -l < "$OUT") lines)"
After running, sanity-check the output:
bash# Should print nothing if trimming worked grep -cE "^[[:space:]]*(References|Bibliography|REFERENCES|BIBLIOGRAPHY)[[:space:]]*$" "$OUT" # Eyeball last few lines — should be prose/conclusion, not body-of-table or mid-paragraph tail -5 "$OUT"
If the tail looks truncated mid-paragraph, the heading detection likely fired on a false positive. If the tail shows references or appendix content, the detection missed the heading — inspect the PDF text around that area and extend the regex.
Method B — Read tool (fallback when pdftotext unavailable)
- Read the PDF in chunks of up to 20 pages at a time using the Read tool's
pagesparameter: 1-20, 21-40, 41-60, etc. - Focus on the MAIN TEXT ONLY. Stop including content once you hit "References", "Bibliography", "Works Cited", or an appendix section. If references appear mid-chunk, keep everything before them and drop the rest.
- Compile output:
- Save alongside the PDF with a
.mdextension. - Header:
# [Original Filename] - Page markers between chunks:
---\n## Pages X-Y\n--- - Preserve extracted text as-is.
- Save alongside the PDF with a
Warning: The Read tool is slow for large PDFs (roughly 30–60 seconds per 20-page chunk). A 60-page paper can take 3–4 minutes, and sub-agents have a ~13-minute stream idle timeout that this can hit. When running a batch of conversions, run them sequentially in the main conversation or use Method A.
Heading-detection pitfalls (hard-won lessons)
These false positives broke earlier attempts — keep them in mind whether you use Method A or B:
- Parenthetical references in body text like
(see Appendix B.5)— exclude lines containing(,),,, or;. - Body text starting with "Appendix X ..." like
Appendix H examines the differential effect...— require punctuation (.or:) immediately after the appendix letter when a title follows. Bare "Appendix A" alone on a line is still valid. - Line-wrapped headings —
pdftotextcan wrapOnline Appendixacross two lines if the PDF's layout is unusual. You'll seeOnlineon one line andAppendixon the next. The regex above matches the joined form; if you see false trims at a loneAppendixline, inspect and tighten. - Form-feed at start of page —
pdftotextemits\fas the first character on every new page. Aftergsub(/\f/, "\n")on$0, the line has an embedded\nthat defeats^/$anchors unless you alsogsub(/[\f\n]/, "", stripped)on your detection copy. - Length thresholds — simple "< 50 chars" is too tight for appendix titles. "Appendix A. Merging Mortgages with the Real Estate Database" is 59 chars. Use ~120 for appendix patterns, ~50 for bare References.
- Numbered section headings — some papers format as
7 Referencesor7. References. Allow an optional leading number.
Report results
Tell the user:
- Method used (pdftotext vs Read tool)
- Total pages processed
- Output file path and final line count
- Where trimming occurred (last section / page number included)
- Any pages that were unreadable or empty
For batch conversions, print a summary table and note any files whose trim point looks suspicious (very short output, or output ending mid-sentence).


