# PDF Extraction in Book-to-Skill: A Four-Tier Fallback Strategy

> Discover how Book-to-Skill extracts PDF content using a four-tier fallback: pdftotext, pypdf, pdfminer.six, and docling. Optimize your document processing.

- Repository: [Virgilio Junior/book-to-skill](https://github.com/virgiliojr94/book-to-skill)
- Tags: deep-dive
- Published: 2026-08-29

---

**Book-to-Skill extracts PDF content through a prioritized fallback chain that attempts `pdftotext` first for speed, then cascades through `pypdf`, `pdfminer.six`, and finally `docling` for technical documents.**

The `virgiliojr94/book-to-skill` repository implements a resilient text extraction pipeline designed to handle everything from plain text PDFs to complex technical documents containing tables and formulas. The core extraction logic resides in [`book_to_skill/parsers/pdf.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/parsers/pdf.py), while dependency requirements are defined in [`book_to_skill/dependencies.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/dependencies.py).

## The Four-Tier Extraction Hierarchy

The system implements a cascading strategy that selects the best available tool based on system capabilities and document type.

### pdftotext (Poppler-utils) – Priority One

When available on the host system, `pdftotext` serves as the primary extraction engine. Located via `shutil.which("pdftotext")`, this native command-line utility executes through `subprocess.run` with the `-layout` flag to preserve page structure. In [`book_to_skill/parsers/pdf.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/parsers/pdf.py) (lines 77-88), the output undergoes post-processing via `clean_pdftotext` to normalize whitespace and formatting artifacts.

### pypdf – Pure Python Fallback

If the system binary is unavailable, the parser attempts to import `pypdf` (line 14 of [`pdf.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/pdf.py)). This pure-Python library iterates through each page object, calling `page.extract_text()` and joining results with form-feed characters to maintain page boundaries (lines 12-26). This approach eliminates external dependencies while handling standard text-based PDFs reliably.

### pdfminer.six – Layout-Preserving Alternative

When `pypdf` cannot be imported or fails, Book-to-Skill falls back to `pdfminer.six`. The implementation calls `pdfminer.high_level.extract_text` (lines 33-38), which similarly preserves page boundaries as form-feeds. This tool excels with complex layouts where simpler extractors might concatenate text incorrectly.

### docling – Technical Document Specialist

For *technical* extraction mode, the system attempts to import `docling` and executes a full pipeline conversion to Markdown (lines 45-64). Unlike the text-focused alternatives, `docling` understands document layout, accurately extracting tables, code blocks, and mathematical formulas. This requires explicit activation via the extraction mode parameter.

## Dependency Configuration

The extraction requirements are declared in [`book_to_skill/dependencies.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/dependencies.py) (lines 18-24) within the "PDF (text-heavy)" group. This configuration marks any of the three Python modules (`pypdf`, `pdfminer`, `docling`) as sufficient for operation, while also recognizing the system binary `pdftotext` as an acceptable alternative. This flexible definition ensures the application installs successfully regardless of which specific toolchain is present.

## Implementation Examples

### Command-Line Extraction

Use the CLI entry point in [`scripts/extract.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/scripts/extract.py) to process files with automatic tool selection:

```bash

# Extract using the default text-heavy mode (auto-selects pdftotext → pypdf → pdfminer)

python scripts/extract.py path/to/document.pdf

# Extract technical PDF with layout awareness (requires docling)

python scripts/extract.py path/to/technical.pdf --mode technical

```

### Programmatic Tool Selection

Access individual extractors directly from `book_to_skill/parsers.pdf` for fine-grained control:

```python
from book_to_skill.parsers.pdf import (
    extract_with_pdftotext,
    extract_with_pypdf,
    extract_with_pdfminer,
    extract_with_docling,
)

pdf_path = "my_book.pdf"

# Attempt extraction in priority order

text = extract_with_pdftotext(pdf_path)
if text is None:
    text = extract_with_pypdf(pdf_path)
if text is None:
    text = extract_with_pdfminer(pdf_path)
if text is None:
    # Final fallback for complex technical documents

    text = extract_with_docling(pdf_path)

print(text or "No extractable text found")

```

### Detecting Image-Only PDFs

Before attempting text extraction, verify the PDF contains extractable text rather than scanned images:

```python
from book_to_skill.parsers.pdf import looks_image_only

if looks_image_only("scanned_document.pdf"):
    print("PDF contains only images – requires OCR processing")
else:
    # Safe to proceed with extraction pipeline

    text = extract_with_pdftotext("scanned_document.pdf")

```

## Summary

- **Primary tool**: `pdftotext` (Poppler-utils) provides the fastest extraction when installed on the host system, invoked via `subprocess.run` with layout preservation.
- **Python fallbacks**: `pypdf` and `pdfminer.six` offer pure-Python alternatives that maintain page boundaries using form-feed delimiters.
- **Technical documents**: `docling` handles complex layouts including tables and formulas when operating in technical extraction mode.
- **Source locations**: Core logic lives in [`book_to_skill/parsers/pdf.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/parsers/pdf.py) with dependency definitions in [`book_to_skill/dependencies.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/dependencies.py).
- **Pre-flight check**: The `looks_image_only` function prevents wasted processing on scanned image PDFs.

## Frequently Asked Questions

### What is the fastest PDF extraction method available in Book-to-Skill?

The `pdftotext` system binary from Poppler-utils delivers the fastest performance according to the source code implementation. When `shutil.which("pdftotext")` locates this executable, Book-to-Skill executes it via `subprocess.run` with the `-layout` flag, then post-processes the output through `clean_pdftotext` to normalize the text.

### How does Book-to-Skill handle scanned or image-only PDFs?

Before extraction, the system calls `looks_image_only` from [`book_to_skill/parsers/pdf.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/parsers/pdf.py) to analyze the PDF structure. If the document contains only images without text layers, the function returns `True`, allowing the application to skip the extraction pipeline and either flag the file for OCR processing or report that no extractable text exists.

### What is the difference between text-heavy and technical extraction modes?

Text-heavy mode uses the standard fallback chain (`pdftotext` → `pypdf` → `pdfminer`) optimized for narrative content, while technical mode activates `docling` (lines 45-64 in [`pdf.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/pdf.py)) to parse complex document layouts. The technical mode converts PDFs to Markdown, preserving table structures, code blocks, and mathematical formulas that standard text extractors would corrupt or omit.

### Which Python packages are required for PDF extraction in Book-to-Skill?

None are strictly mandatory. The dependency group in [`book_to_skill/dependencies.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/dependencies.py) (lines 18-24) specifies `pypdf`, `pdfminer`, and `docling` as optional alternatives, with `pdftotext` serving as a system-level option. The application functions as long as at least one extraction backend is available, whether a Python module or the Poppler system binary.