3
PDF Extraction
padmanto edited this page 2026-07-01 06:36:43 +07:00

PDF Extraction with pi — Read, Search, and Extract Images

pi can read PDFs, extract specific sections, and pull out embedded images using standard command-line tools. No Python, no OCR — just fast native binaries.

Install the Tools

Linux (Debian/Ubuntu)

sudo apt update
sudo apt install -y poppler-utils ripgrep imagemagick
Package Provides Purpose
poppler-utils pdftotext, pdfinfo, pdfimages, pdftoppm Text extraction, metadata, image extraction, page rendering
ripgrep rg Fast text search with context
imagemagick magick (v7) or convert (v6) Image format conversion, cropping

macOS

brew install poppler ripgrep imagemagick

Verify

pdftotext -v
pdfinfo -v
rg --version
magick --version

Quick Overview

PDF file
  │
  ├─► pdfinfo manual.pdf          → Metadata: pages, author, title
  ├─► pdftotext manual.pdf -      → Full text to stdout
  ├─► pdftotext -layout manual.pdf → Preserve column layout
  ├─► pdfimages -j manual.pdf out  → Extract embedded JPEG/PNG images
  ├─► pdftoppm manual.pdf page     → Render page to PNG (for non-embedded images)
  └─► magick input.jpg output.png  → Convert between formats

Workflow 1: Extract Specific Section from a Manual

Step 1: Get the PDF metadata

User: tell me about this PDF: /path/to/manual.pdf

Agent runs: pdfinfo /path/to/manual.pdf

Output:
  Title:          HISv3 Operator Manual
  Author:         HIS Indonesia
  Pages:          234
  Page size:      595 x 842 pts (A4)
  PDF version:    1.7
User: does this manual mention "billing cancellation"?

Agent runs:
  pdftotext /path/to/manual.pdf - | rg -i -C 2 "billing cancellation"

Output (with 2 lines of context):
  134:  5.3 Billing Cancellation Procedure
  135:  To cancel a billing entry, navigate to Billing > Active Bills.
  136:  Select the bill, click "Cancel", and provide a reason. The system
  137:  will reverse all charges and mark the bill as void.

Step 3: Pinpoint the exact pages

User: extract pages 134-136 as text

Agent runs:
  pdftotext -f 134 -l 136 -layout /path/to/manual.pdf -

Output:
  5.3 Billing Cancellation Procedure

  To cancel a billing entry, navigate to Billing > Active Bills.
  Select the bill, click "Cancel", and provide a reason. The system
  will reverse all charges and mark the bill as void.

  5.3.1 Partial Cancellation

  For partial cancellation, select individual items within the bill
  and click "Cancel Selected". Only unbilled items can be cancelled.
  Items already dispatched or consumed cannot be reversed.

Step 4: Find all occurrences across the manual

User: find every page that mentions "cancellation" with page numbers

Agent runs:
  pdftotext /path/to/manual.pdf - | rg -n "cancellation" -i

Output:
  3523: 5.3 Billing Cancellation Procedure
  3551: For partial cancellation, select individual items...
  7823: 12.1 Order Cancellation
  9102: cancellation reason codes:

Workflow 2: Extract Specific Text with Table Layout

User: extract the table of billing codes from page 67, keep the columns

Agent runs:
  pdftotext -f 67 -l 67 -layout /path/to/manual.pdf -

Output:
  Code    Description                    Price      Category
  B001    General Consultation           150,000    OPD
  B002    Specialist Consultation        250,000    OPD
  B003    Emergency Room Visit           350,000    ER
  B004    Inpatient Daily Room           500,000    IPD
  B005    ICU Daily Rate               1,200,000    ICU

Workflow 3: Extract Images from PDF

Check what images are embedded

User: what images are inside this PDF?

Agent runs: pdfimages -list /path/to/manual.pdf

Output:
  page  num  type  width  height  color  comp  bpc  enc
  ----  ---  ----  -----  ------  -----  ----  ---  ---
     1    0  image   800    600   rgb      3    8  jpeg
     1    1  image   200    150   gray     1    8  jpeg
    45    2  image  1200    900   rgb      3    8  jpeg
    67    3  image   400    300   rgb      3    8  jpeg
    89    4  image   600    400   rgb      3    8  png

Extract all images

mkdir -p images
pdfimages -j /path/to/manual.pdf images/img

-j writes JPEG images as .jpg (not .ppm). PNG images get .png.

ls images/
  img-000.jpg   img-001.jpg   img-002.jpg   img-003.jpg   img-004.png

Extract a specific page as an image (for diagrams not embedded)

Not everything in a PDF is an embedded image — diagrams and screenshots may be vector graphics. Render the page to PNG:

User: render page 67 as an image

Agent runs:
  pdftoppm -f 67 -l 67 -r 150 -png /path/to/manual.pdf page67

Output:
  page67-67.png  (150 DPI, full A4 page)

Crop a region from the rendered page

User: just the billing codes table from that page image

Agent crops using ImageMagick:
  magick page67-67.png -crop 800x400+100+300 cropped-table.png

Output:
  cropped-table.png  (800x400px, just the table area)

Workflow 4: Full Deep-Dive into One Section

User: I need everything about "admission workflow" from the hospital manual.
      Give me text, find images, and tell me which pages.

Agent does 4 things:

1. Metadata:
   pdfinfo /path/to/manual.pdf
   → 234 pages, A4

2. Find section boundaries:
   pdftotext /path/to/manual.pdf - | rg -n -i "admission workflow"
   → Lines 4521-4890 mention "admission workflow"

3. Extract the section text:
   pdftotext -f 30 -l 34 -layout /path/to/manual.pdf -
   → Full text of the admission workflow section

4. Extract images from those pages:
   pdfimages -f 30 -l 34 -j /path/to/manual.pdf images/admission
   → admission-003.jpg, admission-004.png

   And render pages as images:
   pdftoppm -f 30 -l 34 -r 150 -png /path/to/manual.pdf pages/admission
   → pages/admission-30.png through admission-34.png

Agent reports:
  Section "Admission Workflow" spans pages 30-34 (5 pages)
  2 embedded images extracted
  5 page renders saved for visual reference
  Full text ready for analysis

Workflow 5: Cross-Reference Manual with Code Repo

[!] Disclaimer: All code in this example is fictional and for demonstration purposes only. It does not represent any real codebase, API, or database schema. Use the techniques shown here against your own PDFs and repositories.

Realistic scenario: you have a hospital HIS operation manual (PDF) and the corresponding source code. pi extracts the manual's specs, finds matching code, and exports everything.

Setup

User: I have the HIS Operator Manual at docs/manual.pdf and the backend code at ./
      Cross-reference the "Billing Cancellation" procedure from the manual
      with the actual code. Find the API endpoint, GraphQL mutation,
      storage query, and any validation logic. Export everything.

Step 1: Extract the procedure from the manual

# Find where billing cancellation is documented
pdftotext docs/manual.pdf - | rg -n -i "billing cancellation"

# Line 4521-4602 → around page 134
# Extract pages 133-136 with layout
pdftotext -f 133 -l 136 -layout docs/manual.pdf -

Extracted manual text (cleaned):

5.3 Billing Cancellation Procedure

Endpoint: POST /api/v1/billing/cancel

Request:
  - bill_id: string (required) — UUID of the bill
  - items: array of item_id (optional) — specific items to cancel
  - reason: string (required) — cancellation reason code

Business Rules:
  - Only bills with status "active" or "draft" can be cancelled
  - Cancelled items must not be already dispatched
  - Partial cancellation allowed only if bill is "active"
  - Reason code must be from master_reason_codes table
  - Cancellation triggers stock reversal for pharmacy items

Response:
  - success: boolean
  - cancelled_items: array
  - reversal_transactions: array

Step 2: Find the matching code in the repo

Agent searches the codebase using extracted keywords:

# Find the API endpoint
rg -l "billing.*cancel" internal/
→ internal/service/billing/cancel.go
→ internal/storage/billing/cancel.go
→ internal/handler/billing_handler.go

# Find the GraphQL mutation
rg -l "cancelBilling\|CancelBilling" src/
→ src/modules/billing/mutations/cancelBilling.ts

# Find the route registration
rg "billing/cancel" internal/handler/
→ handler.POST("/api/v1/billing/cancel", h.CancelBilling)

Step 3: Read and export the code

Handler — internal/handler/billing_handler.go:

// POST /api/v1/billing/cancel
func (h *BillingHandler) CancelBilling(c *gin.Context) {
    var req dto.CancelBillingRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        respondError(c, http.StatusBadRequest, "invalid request")
        return
    }

    result, err := h.svc.CancelBilling(c.Request.Context(), req)
    if err != nil {
        respondError(c, codeFromErr(err), err.Error())
        return
    }
    respondOK(c, result)
}

Service — internal/service/billing/cancel.go:

func (s *BillingService) CancelBilling(ctx context.Context, req dto.CancelBillingRequest) (*dto.CancelBillingResponse, error) {
    // Rule 1: Only active or draft bills
    bill, err := s.storage.GetBill(ctx, req.BillID)
    if err != nil {
        return nil, fmt.Errorf("bill not found: %w", err)
    }
    if bill.Status != "active" && bill.Status != "draft" {
        return nil, fmt.Errorf("bill status %s cannot be cancelled", bill.Status)
    }

    // Rule 2: Validate reason code
    reason, err := s.storage.GetReasonCode(ctx, req.Reason)
    if err != nil || !reason.IsActive {
        return nil, fmt.Errorf("invalid reason code: %s", req.Reason)
    }

    // Rule 3: Check dispatched items
    if req.Items != nil {
        for _, itemID := range req.Items {
            item, _ := s.storage.GetBillItem(ctx, itemID)
            if item.IsDispatched {
                return nil, fmt.Errorf("item %s already dispatched", itemID)
            }
        }
    }

    // Execute cancellation + stock reversal
    return s.storage.CancelBilling(ctx, req)
}

Storage — internal/storage/billing/cancel.go:

func (s *BillingStorage) CancelBilling(ctx context.Context, req dto.CancelBillingRequest) (*dto.CancelBillingResponse, error) {
    tx, err := s.db.Begin(ctx)
    if err != nil {
        return nil, err
    }
    defer tx.Rollback(ctx)

    // Cancel bill items
    query := `
        UPDATE billing_items
        SET status = 'cancelled', cancelled_at = NOW(), cancelled_by = $4
        WHERE bill_id = $1 AND status = 'active'
          AND ($2::uuid[] IS NULL OR id = ANY($2))`

    _, err = tx.Exec(ctx, query, req.BillID, req.Items, req.Reason, req.UserID)
    if err != nil {
        return nil, fmt.Errorf("cancel billing items: %w", err)
    }

    // Reverse pharmacy stock
    _, err = tx.Exec(ctx, `
        UPDATE pharmacy_stock ps
        SET quantity = ps.quantity + bi.quantity
        FROM billing_items bi
        WHERE bi.bill_id = $1
          AND bi.item_type = 'pharmacy'
          AND ps.drug_id = bi.drug_id`, req.BillID)
    if err != nil {
        return nil, fmt.Errorf("reverse stock: %w", err)
    }

    return &dto.CancelBillingResponse{
        Success:            true,
        CancelledItems:     cancelledItems,
        ReversalTransactions: reversals,
    }, tx.Commit(ctx)
}

Frontend — src/modules/billing/mutations/cancelBilling.ts:

export const CANCEL_BILLING = graphql(`
  mutation CancelBilling($input: CancelBillingInput!) {
    cancelBilling(input: $input) {
      success
      cancelledItems {
        id
        name
        quantity
      }
      reversalTransactions {
        id
        drugId
        quantity
      }
    }
  }
`);

DTO — internal/dto/billing.go:

type CancelBillingRequest struct {
    BillID string   `json:"bill_id" binding:"required,uuid"`
    Items  []string `json:"items"`
    Reason string   `json:"reason" binding:"required"`
    UserID string   `json:"-"`
}

type CancelBillingResponse struct {
    Success              bool                    `json:"success"`
    CancelledItems       []CancelledItem         `json:"cancelled_items"`
    ReversalTransactions []ReversalTransaction   `json:"reversal_transactions"`
}

Step 4: Cross-reference summary

Agent produces a traceability table linking manual to code:

Manual Spec Code Location Match?
Endpoint POST /api/v1/billing/cancel internal/handler/billing_handler.go:45 [+] Match
bill_id required UUID dto.CancelBillingRequest.BillID with binding:"required,uuid" [+] Match
Only active/draft status service/cancel.go:12bill.Status != "active" && bill.Status != "draft" [+] Match
Reason code validation service/cancel.go:18GetReasonCode lookup [+] Match
Stock reversal for pharmacy storage/cancel.go:30UPDATE pharmacy_stock [+] Match
Items must not be dispatched service/cancel.go:24item.IsDispatched check [+] Match
Partial cancellation items optional in DTO + $2::uuid[] IS NULL in SQL [+] Match
GraphQL mutation mutations/cancelBilling.tscancelBilling(input) [+] Match

Step 5: Export everything

# Export all findings to a report
mkdir -p export/billing-cancellation

# Export manual section
pdftotext -f 133 -l 136 -layout docs/manual.pdf export/billing-cancellation/manual-section.txt

# Export code snippets
rg -C 5 "CancelBilling\|cancel.*billing" internal/ --type go > export/billing-cancellation/backend-code.txt
rg -C 5 "cancelBilling\|CANCEL_BILLING" src/ --type ts > export/billing-cancellation/frontend-code.txt

# Export the SQL query
rg -C 10 "UPDATE billing_items.*cancelled" internal/storage/ > export/billing-cancellation/sql-query.sql

# Export rendered pages
pdftoppm -f 133 -l 136 -r 150 -png docs/manual.pdf export/billing-cancellation/page

# Bundle as a single report
cat export/billing-cancellation/*.txt > export/billing-cancellation/full-report.md

Agent summary:

Billing Cancellation — Manual vs Code Cross-Reference

All 8 manual specifications have matching code implementations.

Files exported to export/billing-cancellation/:
  manual-section.txt      — Original manual text (pages 133-136)
  backend-code.txt        — Handler, service, storage, DTO
  frontend-code.txt       — GraphQL mutation
  sql-query.sql           — Cancellation + stock reversal queries
  page-133.png..136.png   — Rendered manual pages
  full-report.md          — Combined report

No gaps found between documentation and code.

Troubleshooting

Symptom Likely Cause Fix
pdftotext: command not found poppler-utils not installed sudo apt install poppler-utils
rg: command not found ripgrep not installed sudo apt install ripgrep
magick: command not found ImageMagick v7 Use convert for v6 or install v7
Extracted text is garbled PDF is scanned (image-based) Use OCR tool instead: tesseract
pdfimages extracts no files Images are vector, not raster Use pdftoppm to render the page
pdfimages -list shows smask type Image has soft mask Re-run without -j flag
Text search misses results PDF uses ligatures/fancy fonts Try pdftotext -raw (no layout)

pi Commands Cheat Sheet

# Metadata
@bash pdfinfo file.pdf

# Full text dump
@bash pdftotext file.pdf -

# Layout-preserved text (tables)
@bash pdftotext -layout file.pdf -

# Specific page range
@bash pdftotext -f 10 -l 15 -layout file.pdf -

# Search with context
@bash pdftotext file.pdf - | rg -i -C 3 "keyword"

# Search with page line numbers
@bash pdftotext file.pdf - | rg -n -i "keyword"

# List embedded images
@bash pdfimages -list file.pdf

# Extract all images
@bash pdfimages -j file.pdf images/prefix

# Extract images from page range
@bash pdfimages -f 10 -l 15 -j file.pdf images/prefix

# Render page as image
@bash pdftoppm -f 10 -l 10 -r 150 -png file.pdf page

# Crop rendered image (ImageMagick v7)
@bash magick input.png -crop WxH+X+Y output.png