# PDF Extraction Modes in book-to-skill: 4 Back-Ends Explained

> Explore the four PDF extraction modes in book-to-skill: pdftotext, pypdf, pdfminer, and docling. Understand how this library efficiently converts PDFs to Markdown.

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

---

**The book-to-skill library supports four PDF extraction modes—pdftotext, pypdf, pdfminer, and docling—that automatically cascade from fastest native binaries to layout-aware Markdown converters.**

The virgiliojr94/book-to-skill repository implements a resilient PDF processing pipeline in [`book_to_skill/parsers/pdf.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/parsers/pdf.py) designed to handle diverse document structures, from simple text-based PDFs to complex technical manuals containing tables and code blocks. By offering multiple PDF extraction modes through a unified interface, the library eliminates dependency friction while maximizing extraction quality across different system configurations.

## The Four PDF Extraction Back-Ends

Each extraction mode resides in [`book_to_skill/parsers/pdf.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/parsers/pdf.py) as an independent function, allowing the pipeline to attempt methods sequentially until successful text extraction occurs.

### pdftotext Mode (Fastest Native Option)

The **`extract_with_pdftotext(pdf_path: str) → str | None`** function represents the primary extraction strategy, invoking the external **pdftotext** command-line tool with the `-layout` flag and UTF-8 encoding. This C-based implementation delivers superior performance for text-based PDFs. After extraction, the function passes raw output through `clean_pdftotext` to remove repeated headers, footers, page numbers, and hyphenation artifacts. The system verifies binary availability using `shutil.which` before attempting execution.

### pypdf Mode (Pure-Python Fallback)

When the pdftotext binary is absent, the pipeline falls back to **`extract_with_pypdf(pdf_path: str) → str | None`**, which utilizes the pure-Python **pypdf** library. This method iterates through each page object, calling `page.extract_text()` and concatenating results with form-feed characters (`\f`). The inclusion of form-feeds ensures compatibility with `clean_pdftotext` for post-processing boilerplate removal, maintaining consistent text cleaning across extraction methods.

### pdfminer Mode (Alternative Python Implementation)

If pypdf is unavailable, the system attempts **`extract_with_pdfminer(pdf_path: str) → str | None`**, leveraging the **pdfminer.six** library's `extract_text` function. Unlike pypdf, pdfminer.six natively emits form-feed characters between pages, eliminating the need for manual insertion. The extracted content undergoes identical cleaning through `clean_pdftotext` to ensure uniform output formatting regardless of the source back-end.

### docling Mode (Layout-Aware Markdown Export)

For technical documentation requiring structural preservation, **`extract_with_docling(pdf_path: str) → str | None`** integrates the **Docling** library's `DocumentConverter` with specialized `PdfPipelineOptions`. This configuration disables OCR while enabling table-structure reconstruction, exporting content directly to Markdown format. This mode excels with documents containing complex layouts, multi-column text, or embedded code blocks, though it requires the optional `docling` dependency specified in [`book_to_skill/dependencies.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/dependencies.py).

## Automatic Fallback Pipeline Implementation

The orchestration logic resides in [`book_to_skill/utils.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/utils.py) within the `extract_single_file` function. This utility implements a cascading retry mechanism that attempts extraction modes in strict sequence—pdftotext, then pypdf, then pdfminer, then docling—halting immediately upon receiving a non-`None` string response. This architecture ensures maximum compatibility: systems with the pdftotext binary installed receive optimal performance, while restricted environments automatically degrade to pure-Python alternatives without manual configuration.

## Detecting Image-Only PDFs

Before initiating full text extraction, the library performs a lightweight pre-flight check using **`looks_image_only(pdf_path: str, pages: int = 5) → bool`** in [`book_to_skill/parsers/pdf.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/parsers/pdf.py). This function runs pdftotext against the first five pages of the document; if no extractable text is detected, the PDF is flagged as scanned or image-only. This detection allows calling applications to short-circuit the pipeline for documents requiring OCR rather than wasting cycles on text extraction attempts.

## Practical Usage Examples

Explicitly select a specific extraction mode by importing the corresponding function:

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

pdf_path = "example.pdf"

# 1. pdftotext (requires CLI binary)

text = extract_with_pdftotext(pdf_path)
if text is not None:
    print("Extracted via pdftotext")
    print(text)

# 2. pypdf (pure-Python fallback)

if text is None:
    text = extract_with_pypdf(pdf_path)
    if text is not None:
        print("Extracted via pypdf")
        print(text)

# 3. pdfminer (alternative pure-Python)

if text is None:
    text = extract_with_pdfminer(pdf_path)
    if text is not None:
        print("Extracted via pdfminer")
        print(text)

# 4. docling (layout-aware, optional)

if text is None:
    text = extract_with_docling(pdf_path)
    if text is not None:
        print("Extracted via docling")
        print(text)

```

Let the high-level utility automatically select the best available mode:

```python
from book_to_skill.utils import extract_single_file

pdf_path = "example.pdf"
text = extract_single_file(pdf_path)  # Internally cycles through modes

if text:
    print("Successfully extracted")
else:
    print("No text could be extracted")

```

Detect scanned documents before processing:

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

if looks_image_only(pdf_path):
    print("PDF appears to be scanned/image-only – skip text extraction")
else:
    print("Proceed with normal extraction")

```

## Summary

- **Four extraction modes** are available in [`book_to_skill/parsers/pdf.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/parsers/pdf.py): pdftotext (fastest native), pypdf (pure-Python), pdfminer (alternative Python), and docling (layout-aware Markdown).
- **Automatic cascading** occurs through `extract_single_file` in [`book_to_skill/utils.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/utils.py), attempting methods in order until successful.
- **Consistent post-processing** via `clean_pdftotext` ensures uniform output formatting regardless of the extraction back-end used.
- **Image-only detection** through `looks_image_only` prevents wasted processing on scanned documents lacking text layers.
- **Optional dependencies** for pypdf, pdfminer.six, and docling enable their respective modes without breaking core functionality when absent.

## Frequently Asked Questions

### Which PDF extraction mode should I use for technical documentation with tables?

Use the **docling** mode via `extract_with_docling` for technical PDFs containing tables, code blocks, or complex layouts. Unlike text-only extractors, docling preserves structural elements by exporting directly to Markdown format using `PdfPipelineOptions` with table reconstruction enabled.

### How do I check if a PDF is scanned or image-only before extracting text?

Call **`looks_image_only(pdf_path, pages=5)`** from `book_to_skill.parsers.pdf`. This function samples the first five pages using pdftotext; if no text is found, the document likely requires OCR processing rather than standard text extraction.

### Can I force book-to-skill to use a specific extraction method?

Yes. Instead of using the automatic `extract_single_file` utility, import the specific extraction function from `book_to_skill.parsers.pdf`—such as `extract_with_pypdf` or `extract_with_pdfminer`—and call it directly with your PDF path. This bypasses the cascading fallback logic.

### What happens if none of the PDF extraction modes return text?

If all four modes return `None`, the pipeline concludes that the document contains no extractable text layer (likely a scanned image) or that all optional dependencies are missing. The `extract_single_file` function will return `None`, allowing your application to implement OCR or alternative processing workflows.