| name | pdf-report-workflow-cli |
| description | Complete PDF workflow: verify, extract content, assemble reports, and generate output PDFs using command-line tools |
PDF Report Generation with Command-Line Tools
When working with PDFs in minimal/containerized environments where Python libraries may be unavailable, this skill provides a complete end-to-end workflow: verify source PDFs, extract content, assemble structured reports, and generate final PDF output.
When to Use This Skill
- Need to create new PDF reports from existing PDF sources
- Need to verify, extract, and combine PDF content
- Working in environments without Python PDF libraries
- Building document assembly pipelines in CI/CD or containers
Complete Workflow Overview
[Source PDFs] → [Verify] → [Extract] → [Assemble] → [Generate Output PDF]
Phase 1: Tool Availability Check
which pdfinfo && echo "✓ pdfinfo available" || echo "✗ pdfinfo missing"
which pdftotext && echo "✓ pdftotext available" || echo "✗ pdftotext missing"
which pdftk && echo "✓ pdftk available (PDF merging)" || true
which wkhtmltopdf && echo "✓ wkhtmltopdf available (HTML→PDF)" || true
which pandoc && echo "✓ pandoc available (document conversion)" || true
which enscript && echo "✓ enscript available (text→PS)" || true
which ps2pdf && echo "✓ ps2pdf available (PS→PDF)" || true
Install Required Tools
apt-get update && apt-get install -y poppler-utils
apt-get install -y pdftk
apt-get install -y wkhtmltopdf
apt-get install -y pandoc
apt-get install -y enscript ghostscript
yum install -y poppler-utils pdftk ghostscript
dnf install -y poppler-utils pdftk ghostscript
brew install poppler pdftk ghostscript
brew install --cask wkhtmltopdf
Phase 2: Verify Source PDFs
Check Page Count and Metadata
for pdf in source1.pdf source2.pdf source3.pdf; do
if [ ! -f "$pdf" ]; then
echo "ERROR: $pdf not found"
exit 1
fi
pages=$(pdfinfo "$pdf" | grep Pages | awk '{print $2}')
echo "$pdf: $pages pages"
done
Validate Content Presence
REQUIRED_TERMS=("checklist" "summary" "references")
for term in "${REQUIRED_TERMS[@]}"; do
if pdftotext source.pdf - | grep -qi "$term"; then
echo "✓ Found: $term"
else
echo "⚠ Missing: $term"
fi
done
Phase 3: Extract Content from Source PDFs
Extract Text to Temporary Files
WORK_DIR=$(mktemp -d)
echo "Working directory: $WORK_DIR"
for i in source1.pdf source2.pdf source3.pdf; do
base=$(basename "$i" .pdf)
pdftotext -layout "$i" "$WORK_DIR/${base}.txt"
echo "Extracted: $i → ${base}.txt"
done
pdfinfo source1.pdf > "$WORK_DIR/source1_metadata.txt"
Extract Specific Sections (Optional)
pdftotext -f 1 -l 3 source1.pdf "$WORK_DIR/source1_pages1-3.txt"
pdftotext source1.pdf - | grep -A 10 "Summary" > "$WORK_DIR/summary_section.txt"
Phase 4: Assemble Report Content
Create Structured Report (Markdown Format)
REPORT_MD="$WORK_DIR/report.md"
cat > "$REPORT_MD" << 'REPORT_HEADER'
**Generated:** $(date '+%Y-%m-%d %H:%M:%S')
---
REPORT_HEADER
echo "## Case Details" >> "$REPORT_MD"
echo "" >> "$REPORT_MD"
cat "$WORK_DIR/source1.txt" >> "$REPORT_MD"
echo "" >> "$REPORT_MD"
echo "## Supporting Documentation" >> "$REPORT_MD"
echo "" >> "$REPORT_MD"
cat "$WORK_DIR/source2.txt" >> "$REPORT_MD"
echo "" >> "$REPORT_MD"
echo "## References" >> "$REPORT_MD"
echo "" >> "$REPORT_MD"
cat "/source3.txt" >>
Create HTML Report (Alternative for Better Formatting)
REPORT_HTML="$WORK_DIR/report.html"
cat > "$REPORT_HTML" << 'HTML_HEADER'
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Case Creation Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
h1 { color:
h2 { color:
.section { margin: 20px 0; }
.metadata { font-size: 0.9em; color:
</style>
</head>
<body>
<h1>New Case Creation Report</h1>
<p class="metadata">Generated: HTML_HEADER
date '+%Y-%m-%d %H:%M:%S' >> "$REPORT_HTML"
cat >> "$REPORT_HTML" << 'HTML_MIDDLE'
</p>
<div class="section">
<h2>Case Details</h2>
HTML_MIDDLE
sed 's/^/<p>/; s/$/<\/p>/' "$WORK_DIR/source1.txt" >> "$REPORT_HTML"
cat >> "$REPORT_HTML" << 'HTML_END'
</div>
</body>
</html>
HTML_END
echo "HTML report created: $REPORT_HTML"
Phase 5: Generate Final PDF Output
Option A: Using pandoc (Recommended if available)
pandoc "$REPORT_MD" -o final_report.pdf \
--pdf-engine=xelatex \
-V geometry:margin=1in
pandoc "$REPORT_HTML" -o final_report.pdf \
--pdf-engine=webkit2png
Option B: Using wkhtmltopdf (HTML to PDF)
wkhtmltopdf \
--page-size A4 \
--margin-top 20mm \
--margin-bottom 20mm \
--margin-left 15mm \
--margin-right 15mm \
"$REPORT_HTML" \
final_report.pdf
Option C: Using enscript + ps2pdf (Text to PDF)
enscript \
--media=A4 \
--font=Courier10 \
--margins=20:20:20:20 \
-o "$WORK_DIR/report.ps" \
"$REPORT_MD"
ps2pdf "$WORK_DIR/report.ps" final_report.pdf
Option D: Merge Existing PDFs with pdftk
pdftk source1.pdf source2.pdf source3.pdf cat output combined_report.pdf
pdftk source1.pdf dump_data > "$WORK_DIR/bookmarks.txt"
pdftk source1.pdf update_info "$WORK_DIR/bookmarks.txt" output final_report.pdf
Complete End-to-End Example
#!/bin/bash
set -e
SOURCE_PDFS=("case_guide.pdf" "case_summary.pdf" "test_results.pdf")
OUTPUT_PDF="new_case_report.pdf"
WORK_DIR=$(mktemp -d)
echo "=== PDF Report Generation Workflow ==="
echo "Working directory: $WORK_DIR"
echo -e "\n[Phase 1] Checking tools..."
for tool in pdfinfo pdftotext; do
if ! command -v "$tool" &> /dev/null; then
echo "ERROR: $tool not found. Install poppler-utils."
exit 1
fi
done
echo -e "\n[Phase 2] Verifying source PDFs..."
for pdf in "${SOURCE_PDFS[@]}"; do
if [ ! -f "$pdf" ]; then
echo "ERROR: Source PDF not found: $pdf"
exit 1
fi
pages=$(pdfinfo "$pdf" | grep Pages | awk )
-e
i ;
pdf=
base=$( .pdf)
pdftotext -layout
-e
> <<
section_num=1
pdf ;
base=$( .pdf)
>> <<
>>
((section_num++))
-e
-v pandoc &> /dev/null;
pandoc -o --pdf-engine=xelatex
-v enscript &> /dev/null;
enscript -o
ps2pdf
-e
Python Integration Example
import subprocess
import tempfile
import os
from pathlib import Path
class PDFReportGenerator:
"""Complete PDF workflow: verify, extract, assemble, generate"""
def __init__(self, work_dir=None):
self.work_dir = Path(work_dir) if work_dir else Path(tempfile.mkdtemp())
self.extracted_files = []
def verify_pdf(self, pdf_path):
"""Verify PDF exists and get metadata"""
result = subprocess.run(
['pdfinfo', str(pdf_path)],
capture_output=True, text=True
)
if result.returncode != 0:
raise ValueError(f"Cannot read PDF: {pdf_path}")
metadata = {}
for line in result.stdout.split('\n'):
if ':' in line:
key, val = line.split(':', 1)
metadata[key.strip()] = val.strip()
return metadata
def extract_pdf(self, pdf_path, output_name=None):
"""Extract text from PDF"""
if output_name is :
output_name = Path(pdf_path).stem +
output_path = .work_dir / output_name
subprocess.run(
[, , (pdf_path), (output_path)],
check=
)
.extracted_files.append(output_path)
output_path.read_text()
():
output_md :
output_md = .work_dir /
(output_md, ) f:
f.write()
f.write()
i, (title, content) (sections, ):
f.write()
output_md
():
output_pdf :
output_pdf = .work_dir /
._command_exists():
subprocess.run(
[, (input_file), , (output_pdf), ],
check=
)
._command_exists():
ps_file = .work_dir /
subprocess.run([, , (ps_file), (input_file)], check=)
subprocess.run([, (ps_file), (output_pdf)], check=)
:
RuntimeError()
output_pdf
():
subprocess.run([, cmd], capture_output=).returncode ==
():
sections = []
pdf source_pdfs:
meta = .verify_pdf(pdf)
content = .extract_pdf(pdf)
sections.append((Path(pdf).stem, content))
report_md = .assemble_report(sections)
.generate_pdf(report_md, output_pdf)
Path(output_pdf)
generator = PDFReportGenerator()
final_pdf = generator.full_workflow(
source_pdfs=[, , ],
output_pdf=
)
()
Troubleshooting
No PDF generation tools available
- Minimum: Use
enscript + ps2pdf (usually available with ghostscript)
- Alternative: Generate HTML report and let browser print to PDF
- Last resort: Output as Markdown for manual conversion
pdftotext returns empty/garbled output
- PDF may be image-based (scanned) - requires OCR tools like
tesseract
- PDF may be encrypted - check with
pdfinfo for "Encrypted" field
- Try
pdftotext -layout or pdftotext -raw for different extraction modes
pandoc fails with missing LaTeX
- Install minimal LaTeX:
apt-get install texlive-latex-base
- Or use
--pdf-engine=wkhtmltopdf instead of xelatex
- Or fall back to enscript method
Report formatting is poor
- Use HTML intermediate format for better styling control
- Adjust pandoc variables:
-V geometry:margin=1in
- Add CSS when using wkhtmltopdf
Best Practices
- Verify before processing - Always check source PDFs exist and are readable before starting the workflow
- Use temporary directories - Keep intermediate files isolated with
mktemp -d
- Preserve extraction context - Save extracted text with clear naming that maps back to sources
- Choose generation tool wisely - pandoc for best quality, enscript for minimum dependencies
- Handle failures gracefully - Check tool availability at runtime, provide fallbacks
- Document the workflow - Include generation timestamp and source list in output
Tool Comparison
| Tool | Purpose | Pros | Cons |
|---|
| pdfinfo | Metadata extraction | Fast, reliable | Metadata only |
| pdftotext | Text extraction | Preserves structure | Struggles with complex layouts |
| pandoc | Document conversion | Best quality, flexible | Requires LaTeX for PDF |
| wkhtmltopdf | HTML→PDF | Great styling support | Larger dependency |
| enscript | Text→PS | Minimal dependencies | Basic formatting |
| pdftk | PDF manipulation | Powerful merging | Not for content generation |
| *** End Files | | | |