# https://github.com/explosion/spacy-layout Project Manual

Generated at: 2026-07-13 16:42:17 UTC

## Table of Contents

- [Overview and Getting Started](#page-1)
- [Core API: spaCyLayout Class, Methods, and Extension Attributes](#page-2)
- [Document Processing Pipeline, Tables, and Data Extraction](#page-3)
- [Customization, Backend Configuration, and Common Issues](#page-4)

<a id='page-1'></a>

## Overview and Getting Started

### Related Pages

Related topics: [Core API: spaCyLayout Class, Methods, and Extension Attributes](#page-2), [Document Processing Pipeline, Tables, and Data Extraction](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [README.md](https://github.com/explosion/spacy-layout/blob/main/README.md)
- [spacy_layout/__init__.py](https://github.com/explosion/spacy-layout/blob/main/spacy_layout/__init__.py)
- [setup.cfg](https://github.com/explosion/spacy-layout/blob/main/setup.cfg)
- [setup.py](https://github.com/explosion/spacy-layout/blob/main/setup.py)
- [requirements.txt](https://github.com/explosion/spacy-layout/blob/main/requirements.txt)
</details>

# Overview and Getting Started

## What is spacy-layout

spacy-layout is a library that converts PDF documents into structured `spacy.tokens.Doc` objects, retaining layout information such as bounding boxes, page numbers, and table structure. It bridges [Docling](https://github.com/DS4SD/docling), the document parser used under the hood (`docling-parse`), with the spaCy NLP ecosystem, allowing downstream spaCy pipelines to operate on richly annotated document content. `Source: [README.md:1-15]()`

The library is small in scope but focused on a common pain point: extracting not only the text of a PDF but the structural and positional information needed to reason about documents (e.g., OCR pipelines, RAG ingestion, form understanding). `Source: [README.md:17-40]()`

## Installation

Install from PyPI:

```bash
pip install spacy-layout
```

The package metadata is declared in `setup.cfg`, where `spacy-layout` is described as "Process PDFs, spreadsheets and other documents with spaCy" and versioned per release. `Source: [setup.cfg:1-10]()`

Because spacy-layout depends on Docling for parsing, the install pulls in the Docling stack automatically. On Apple Silicon (macOS M1/M2/M3) users have reported install issues that mirror those seen with Docling itself (see issue #10). `Source: [requirements.txt:1-10]()`

### System requirements

- A working spaCy installation (the README examples use `spacy.blank("en")`).
- Sufficient disk for the Docling model assets that ship with the parser.
- On GPU machines, spaCy’s `require_cpu()` may not propagate into Docling’s internal device selection, which can lead to OOM errors when processing large batches (issue #37).

## Quick Start

### Basic usage

A minimal first script reads a PDF and produces a `Doc` with layout extension attributes:

```python
import spacy
from spacy_layout import spaCyLayout

nlp = spacy.blank("en")
layout = spaCyLayout(nlp)

doc = layout("path/to/document.pdf")
print(doc.text)
print(doc._.layout)        # layout spans with bounding boxes
print(doc._.markdown)      # Markdown representation (added in v0.0.9)
```

The entry point `spaCyLayout` is re-exported from the top-level package. `Source: [spacy_layout/__init__.py:1-10]()`

`spaCyLayout` exposes both `__call__` (single document) and `pipe` (stream of documents), added in v0.0.3. As of v0.0.12, `pipe` accepts an `as_tuples` argument analogous to spaCy’s `Language.pipe`. `Source: [README.md:42-70]()`

### Batch processing

For multiple documents, prefer `pipe` over repeated `__call__` calls. This matches the spaCy idiom and aligns with how the library tokenizes its own text. `Source: [README.md:55-72]()`

### Processing a `DoclingDocument` directly

Since v0.0.10, you can pass an already-parsed `DoclingDocument` to `spaCyLayout.__call__`, skipping the PDF parsing step when you have produced the Docling representation yourself (e.g., to access other Docling features such as image inventories — see issue #40). `Source: [README.md:74-88]()`

## Architecture and Processing Pipeline

The library follows a thin-wrapper design: spaCy is used for tokenization and `Doc` construction, while parsing, segmentation, and structural analysis are delegated to Docling. Layout information is attached via custom extension attributes on the resulting `Doc` and `Span`.

| Stage | Component | Responsibility |
|-------|-----------|----------------|
| Parse | Docling (`docling-parse` backend by default) | Read PDF / bytes / spreadsheet into `DoclingDocument` |
| Convert | spacy-layout | Walk `DoclingDocument`, emit layout spans with bounding boxes, page numbers, and (optionally) tables |
| Tokenize | spaCy `nlp` | Run tokenizer over the joined text |
| Annotate | Custom extensions | Attach `Doc._.layout`, `Doc._.tables`, `Doc._.markdown`, `Span._.data` |

`Source: [spacy_layout/__init__.py:1-25]()`, `Source: [README.md:30-70]()`

### Layout extensions

The most important extension attributes set on the output `Doc` include:

- `Doc._.layout` — a list of layout spans with bounding boxes, page numbers, and labels.
- `Doc._.tables` — shortcut to table spans (added in v0.0.6).
- `Doc._.markdown` — a Markdown representation of the document (added in v0.0.9).
- `Span._.data` — table data as a `pandas.DataFrame` (added in v0.0.6).
- Bounding boxes (top-left or bottom-left origin) are normalized by the `doc_height` argument; this was fixed for both conventions in v0.0.4 and v0.0.7.

`Source: [README.md:90-140]()`

### Headers, footers, and bounding-box granularity

By default, page headers and footers are excluded from the produced `Doc.text`. Issue #32 documents this behavior and points at the text-span assembly loop where it is controlled. If you need them, pass the relevant Docling options through to `spaCyLayout`. `Source: [README.md:100-115]()`

For finer-grained bounding boxes inside tables (rather than the table-as-a-whole), issue #23 requests per-cell coordinates. As of v0.0.12 only the table-level bounding box is attached to its `Span`. `Source: [README.md:110-125]()`

## Common pitfalls when getting started

- **Device selection.** spacy-layout runs Docling internally, and `spacy.require_cpu()` does not flow into Docling. For CPU-only testing, configure Docling’s device explicitly (issue #37).
- **Alternate parsers.** If you need a non-default Docling backend such as `PyPdfiumDocumentBackend`, instantiate Docling yourself and pass the resulting `DoclingDocument` to `spaCyLayout` (issue #28).
- **Saving output.** The library returns spaCy objects, not files. To export, serialize via `DocBin` (supported since v0.0.8) or write `doc._.markdown` / tables to disk yourself (issue #49).
- **scipy/numpy mismatch.** In some environments, `scipy.special.sph_legendre_p` raises a ufunc compatibility error during parsing. Pin compatible `numpy` and `scipy` versions until resolved (issue #47).

`Source: [requirements.txt:1-10]()`, `Source: [setup.cfg:1-10]()`

---

<a id='page-2'></a>

## Core API: spaCyLayout Class, Methods, and Extension Attributes

### Related Pages

Related topics: [Overview and Getting Started](#page-1), [Document Processing Pipeline, Tables, and Data Extraction](#page-3), [Customization, Backend Configuration, and Common Issues](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [spacy_layout/layout.py](https://github.com/explosion/spacy-layout/blob/main/spacy_layout/layout.py)
- [spacy_layout/types.py](https://github.com/explosion/spacy-layout/blob/main/spacy_layout/types.py)
- [spacy_layout/util.py](https://github.com/explosion/spacy-layout/blob/main/spacy_layout/util.py)
- [spacy_layout/__init__.py](https://github.com/explosion/spacy-layout/blob/main/spacy_layout/__init__.py)
- [README.md](https://github.com/explosion/spacy-layout/blob/main/README.md)
</details>

# Core API: spaCyLayout Class, Methods, and Extension Attributes

The `spaCyLayout` class is the single public entry point of the `spacy-layout` package. It wraps a Docling `DocumentConverter` and converts a PDF, image, Office, HTML, or pre-parsed `DoclingDocument` into a regular spaCy `Doc` whose tokens and spans carry layout metadata (bounding boxes, page numbers, labels) and Markdown/table payloads. The library exports only `spaCyLayout` from its package init, confirming that this class is the integration surface for end users. Source: [spacy_layout/__init__.py]().

## 1. Class Construction and Configuration

`spaCyLayout.__init__` accepts an existing spaCy `Language` and forwards most options into Docling's `DocumentConverter`. The most relevant parameters are:

| Parameter | Purpose |
| --- | --- |
| `nlp` | A spaCy `Language` used for tokenization after layout extraction. |
| `docling_model` | Optional pre-constructed `DocumentConverter` to reuse an externally configured parser. Source: [spacy_layout/layout.py]() |
| `headings` | Comma-separated Docling heading levels (`"1,2,3"`) treated as paragraph section headings. |
| `min_paragraphs` | Minimum paragraph count required to render a heading block. |
| `display_table` | Callable that controls how a table is rendered into `Doc.text`. Source: [spacy_layout/layout.py]() |
| `show_debug` | Enables debug logging of the Docling pipeline. |

If `docling_model` is omitted, `_get_default_docling_converter` builds a converter with sensible PDF and image pipeline defaults. Source: [spacy_layout/util.py]().

## 2. Primary Methods

The class exposes two execution methods that mirror spaCy's `Language.__call__` / `Language.pipe` semantics:

- `__call__(doc, path: str | bytes | Path | DoclingDocument | None = None)` — Processes a single document. It accepts either an empty `Doc` (with `path`/raw bytes pointing to the source) or an already-parsed `DoclingDocument` for re-use cases. It converts the result via `_convert_docling_doc`, calls the internal tokenizer (`self.nlp.tokenizer`), and returns the populated `Doc`. Source: [spacy_layout/layout.py]().
- `pipe(paths, as_tuples: bool = False, *, batch_size: int = 128, n_process: int = 1)` — Streams multiple inputs through `_convert_docling_doc` and `nlp.pipe`. The `as_tuples` flag mirrors `Language.pipe` so callers can interleave metadata with `(doc, context)` tuples. Source: [spacy_layout/layout.py](), [README.md]().

The conversion itself runs `_convert_docling_doc`, which iterates Docling items (`document.iterate_items`), builds `LayoutToken` and `LayoutSpan` objects, and stitches them into the target `Doc`. Header and footer filtering occurs here, which is the exact code path users cite when reporting that page headers and footers are dropped. Source: [spacy_layout/layout.py](), [issue #32]().

## 3. Data Model: LayoutToken, LayoutSpan, LayoutPage

All layout metadata is exposed through three immutable dataclasses defined in `types.py`:

```mermaid
classDiagram
    class LayoutPage {
      +page_no: int
      +page_size: tuple
      +blocks: list[LayoutSpan]
    }
    class LayoutSpan {
      +x: float
      +y: float
      +width: float
      +height: float
      +page: int
      +label: str
    }
    class LayoutToken {
      +x: float
      +y: float
      +width: float
      +height: float
      +text: str
      +span: Span
    }
    LayoutPage "1" o-- "*" LayoutSpan
    Doc "1" o-- "*" LayoutToken
    Doc "1" o-- "*" LayoutSpan
```

Source: [spacy_layout/types.py](). `LayoutToken` carries the coordinates and the reference to the spaCy `Span` produced during tokenization, while `LayoutSpan` represents higher-level blocks (paragraphs, headings, tables, list items) tagged by Docling. `LayoutPage` aggregates spans by page so they can be retrieved in order.

## 4. Extension Attributes Set on `Doc` and `Span`

`spaCyLayout` registers the following custom extensions on spaCy's `Doc` and `Span` objects:

- `Doc._.layout` — `list[LayoutToken]`, one entry per token with coordinates, page, and originating span. Source: [spacy_layout/layout.py](), [README.md]().
- `Doc._.pages` — `list[LayoutPage]`, blocks grouped by page. Source: [spacy_layout/types.py]().
- `Doc._.tables` — Convenience shortcut returning the table spans found in `Doc._.layout`. Source: [spacy_layout/layout.py](), [README.md]().
- `Doc._.markdown` — Markdown rendering of the parsed document. Source: [README.md](), [release v0.0.9]().
- `Span._.data` — When the span represents a table, this attribute exposes a `pandas.DataFrame` of its cells. Source: [README.md](), [release v0.0.6]().
- `Span._.page` — Page index for any layout span. Source: [README.md]().

These attributes are what downstream pipelines serialize via spaCy's `DocBin`, which the project explicitly added support for in v0.0.8 to keep `pandas.DataFrame` payloads intact. Source: [README.md](), [release v0.0.8]().

## 5. Practical Usage

The canonical workflow is:

```python
import spacy
from spacy_layout import spaCyLayout

nlp = spacy.blank("en")
layout = spaCyLayout(nlp)
doc = layout("report.pdf")

for token in doc._.layout:
    print(token.text, token.x, token.y, token.page)
for table in doc._.tables:
    print(table._.data)  # pandas.DataFrame
print(doc._.markdown)
```

Source: [README.md](), [spacy_layout/layout.py](). For batch workloads, swap `layout(...)` for `layout.pipe(["a.pdf", "b.pdf"], as_tuples=False, batch_size=64)`; passing `as_tuples=True` lets callers attach `(path, metadata)` pairs that flow back as `(doc, context)`. Source: [README.md](), [release v0.0.12]().

## 6. Known Limitations Reflected in Community Issues

Several community-reported gaps map directly to behaviors implemented in this class:

- Page headers and footers are excluded from the assembled text because `_convert_docling_doc` filters out items whose Docling label is a header/footer. Source: [spacy_layout/layout.py](), [issue #32]().
- The `display_table` callback is the supported extension point for customizing how tables appear in `Doc.text`. Granular per-cell bounding boxes are not currently provided by `LayoutSpan`. Source: [spacy_layout/layout.py](), [issue #23]().
- `spacy.require_cpu()` does not propagate to Docling's ML models; users on CPU-only hosts should construct a `docling_model` with CPU pipelines explicitly. Source: [issue #37]().

Together, the `spaCyLayout` class, its conversion helpers in `util.py`, the dataclasses in `types.py`, and the registered extension attributes form the complete public API surface of `spacy-layout`. Source: [spacy_layout/__init__.py](), [spacy_layout/layout.py](), [spacy_layout/util.py](), [spacy_layout/types.py](), [README.md]().

---

<a id='page-3'></a>

## Document Processing Pipeline, Tables, and Data Extraction

### Related Pages

Related topics: [Core API: spaCyLayout Class, Methods, and Extension Attributes](#page-2), [Customization, Backend Configuration, and Common Issues](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [spacy_layout/layout.py](https://github.com/explosion/spacy-layout/blob/main/spacy_layout/layout.py)
- [spacy_layout/types.py](https://github.com/explosion/spacy-layout/blob/main/spacy_layout/types.py)
- [spacy_layout/util.py](https://github.com/explosion/spacy-layout/blob/main/spacy_layout/util.py)
- [spacy_layout/span.py](https://github.com/explosion/spacy-layout/blob/main/spacy_layout/span.py)
- [spacy_layout/__init__.py](https://github.com/explosion/spacy-layout/blob/main/spacy_layout/__init__.py)
- [README.md](https://github.com/explosion/spacy-layout/blob/main/README.md)
- [pyproject.toml](https://github.com/explosion/spacy-layout/blob/main/pyproject.toml)
</details>

# Document Processing Pipeline, Tables, and Data Extraction

## Overview and Purpose

spaCy-layout is a thin spaCy component that wraps [Docling](https://github.com/DS4SD/docling) to convert structured documents (PDF, DOCX, images, HTML) into spaCy `Doc` objects with rich layout metadata. The library's core value is bridging Docling's deep document understanding with spaCy's text-processing ecosystem, exposing bounding boxes, pages, tables, and structural elements through `Doc` / `Span` custom attributes.

The high-level flow is: user input (path, bytes, or a pre-built `DoclingDocument`) → `spaCyLayout.__call__` → a configured Docling `DocumentConverter` → tokenization through `nlp.pipe` → a populated `Doc` whose `doc._.page` / `doc._.layout` spans carry the layout structure. Source: [spacy_layout/layout.py:1-80]().

The pipeline is registered as the spaCy factory `"spacy_layout"`, so once added to an `nlp` pipeline it behaves like any other component, including serialization via `DocBin`. Source: [README.md:1-60]().

## Pipeline Architecture

### Component Construction

`spaCyLayout` is instantiated with a spaCy `Language`, an optional `DoclingDocument`, and an optional `DocumentConverter`. These are stored and reused so that converting many files with `spaCyLayout.pipe` does not rebuild the underlying Docling converter every time. Source: [spacy_layout/layout.py:33-62]().

The `__call__` method dispatches on the input type:

- A `str` or `Path` is treated as a file path and handed to `DocumentConverter.convert`.
- `bytes` is written to a temporary file or streamed in.
- A `DoclingDocument` instance is reused directly (added in v0.0.10), which is useful when the caller already produced a Docling document and wants only the spaCy wrapping step. Source: [spacy_layout/layout.py:64-95]().

### Tokenization and Span Assembly

After Docling produces a `DoclingDocument`, layout walks the document tree, collecting nodes of interest (text, section headers, list items, tables, pictures). Each node's text becomes a layout span, with absolute character offsets that are later aligned to spaCy tokens by running `nlp.pipe` over the concatenated document text. Source: [spacy_layout/layout.py:96-180]().

The result is a list of `Span` objects keyed in `doc._.layout` plus per-page metadata in `doc._.page` and bounding boxes on each span. The token alignment step uses spaCy's tokenizer, which is why an `nlp` object must be passed in. Source: [spacy_layout/layout.py:180-220]().

### Batching with `spaCyLayout.pipe`

For multi-file workloads, `spaCyLayout.pipe(stream, as_tuples=False, batch_size=..., n_process=...)` mirrors spaCy's `Language.pipe` signature. Each input is converted independently, but the heavy Docling converter is shared, which amortizes model loading cost across the batch. Source: [spacy_layout/layout.py:240-290]().

The `as_tuples` argument (added in v0.0.12) lets callers pass `(context, doc)` pairs so that arbitrary metadata from the calling code (filenames, IDs, error flags) can flow alongside the produced `Doc`. Source: [README.md:60-120]().

## Table Extraction and Representation

### Tables as Layout Spans

Tables were promoted to first-class layout spans in v0.0.6. Within the pipeline, table cells are visited in document order and each table becomes a single `Span` whose `text` is the table content, with a `pandas.DataFrame` attached. Source: [spacy_layout/span.py:1-90]().

Two convenience attributes make tables easy to consume:

- `doc._.tables` returns every table `Span` in the document.
- `span._.data` returns the `pandas.DataFrame` for a given table span, with cell values and optionally headers. Source: [spacy_layout/types.py:1-120]().

### Customizing Table Display

When the document text is rendered (`doc.text` or `doc._.markdown`), tables are inserted as placeholder tokens. The `display_table` configuration callback (passed to `spaCyLayout.__init__`) controls how that placeholder appears. The default emits a Markdown-style table; users can override it to emit plain prose, CSV, or a custom tag. Source: [spacy_layout/layout.py:120-160]().

This callback receives the `Span` (with `_.data` already populated) and must return the string that should appear in `doc.text`. Source: [spacy_layout/util.py:1-80]().

### Known Limitations

Community issues highlight two recurring pain points with table data:

- Granular cell-level bounding boxes are not exposed — only the table-level bounding box is available. Issue #23 asks for per-cell boxes, which the public API does not currently provide. Source: [Issue #23](https://github.com/explosion/spacy-layout/issues/23).
- "Index" tables (tables of contents / indexes) are only partially supported. v0.0.12 added explicit handling for index tables, but extracting page numbers for them still requires custom logic. Source: [README.md:120-180]().

## Data Extraction APIs and Custom Attributes

spaCy-layout registers a small set of custom attributes on `Doc` and `Span` that surface the Docling structure:

| Attribute | Container | Meaning |
| --- | --- | --- |
| `doc._.layout` | Doc | Ordered `Span`s covering every layout element |
| `doc._.page` | Doc | Per-page metadata (size, index, image) |
| `doc._.pages` | Doc | List of per-page images (`PIL.Image`) |
| `doc._.tables` | Doc | Shortcut to layout spans where `label == "table"` |
| `doc._.markdown` | Doc | Markdown rendering (added v0.0.9) |
| `span._.x`, `span._.y`, `span._.width`, `span._.height` | Span | Bounding box in PDF points |
| `span._.page` | Span | 1-based page index |
| `span._.data` | Span | `pandas.DataFrame` for tables |

Bounding-box semantics differ by Docling configuration: v0.0.4–v0.0.7 fixed several coordinate-system bugs, and the coordinates now follow Docling's `BoundingBox` (with `top-left` origin by default). Source: [spacy_layout/util.py:80-140]().

Because the custom attributes are registered via spaCy's extension attribute system, they survive serialization through `DocBin`. This was explicitly fixed in v0.0.8 so that `pandas.DataFrame` payloads round-trip cleanly. Source: [spacy_layout/types.py:120-180]().

## Practical Usage Patterns

### Single Document

```python
import spacy
from spacy_layout import spaCyLayout

nlp = spacy.blank("en")
layout = spaCyLayout(nlp)
doc = layout("path/to/file.pdf")
```

### Batch Processing with Metadata

```python
files = ["a.pdf", "b.pdf", "c.pdf"]
for filename, doc in layout.pipe(files, as_tuples=True):
    for table in doc._.tables:
        df = table._.data
        df.to_csv(f"{filename}-{table._.page}.csv")
```

This pattern answers the common question raised in Issue #49 about exporting extracted content to files: write `doc._.markdown` for prose, iterate `doc._.tables` and call `.to_csv` / `.to_json` on `span._.data` for structured output. Source: [Issue #49](https://github.com/explosion/spacy-layout/issues/49).

### Selecting a Docling Backend

The `DocumentConverter` is configurable, so users can swap the PDF parser (e.g., `PyPdfiumDocumentBackend`) for performance or fidelity reasons. Pass a pre-built converter into `spaCyLayout(nlp, docling_converter=my_converter)`. Source: [spacy_layout/layout.py:33-62]().

## Operational Considerations

- **Device selection**: `spaCyLayout` relies on Docling's configured device. `spacy.require_cpu()` does not automatically propagate, so users running on shared GPU machines should pass an explicit CPU-bound converter (Issue #37). Source: [Issue #37](https://github.com/explosion/spacy-layout/issues/37).
- **Platform support**: macOS installation has historically required explicit `docling` wheels; check `pyproject.toml` for the supported Python versions before installing. Source: [pyproject.toml:1-40]().
- **Header / footer inclusion**: Docling's default reader drops page headers and footers; passing the appropriate `DocumentConverter` options preserves them (Issue #32). Source: [Issue #32](https://github.com/explosion/spacy-layout/issues/32).
- **Special-character handling**: Portuguese and other Latin-script PDFs sometimes show mojibake when fonts are subset; the upstream cause is Docling's text extraction, not spaCy-layout itself (Issue #16). Source: [Issue #16](https://github.com/explosion/spacy-layout/issues/16).

## Summary

The document-processing pipeline in spaCy-layout is a thin but carefully aligned bridge between Docling's structural understanding and spaCy's tokenization and serialization. Tables are first-class citizens exposed through `doc._.tables` and `span._.data`, bounding boxes and pages are normalized into `Span` custom attributes, and the pipeline scales through `spaCyLayout.pipe`. For most users, the key APIs are `doc._.layout`, `doc._.tables`, `doc._.markdown`, and `span._.data`, which together cover the vast majority of extraction and export use cases.

---

<a id='page-4'></a>

## Customization, Backend Configuration, and Common Issues

### Related Pages

Related topics: [Core API: spaCyLayout Class, Methods, and Extension Attributes](#page-2), [Document Processing Pipeline, Tables, and Data Extraction](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [spacy_layout/layout.py](https://github.com/explosion/spacy-layout/blob/main/spacy_layout/layout.py)
- [spacy_layout/types.py](https://github.com/explosion/spacy-layout/blob/main/spacy_layout/types.py)
- [spacy_layout/__init__.py](https://github.com/explosion/spacy-layout/blob/main/spacy_layout/__init__.py)
- [README.md](https://github.com/explosion/spacy-layout/blob/main/README.md)
- [pyproject.toml](https://github.com/explosion/spacy-layout/blob/main/pyproject.toml)
</details>

# Customization, Backend Configuration, and Common Issues

`spaCy-layout` wraps the Docling document-parsing pipeline and exposes a small surface area for customization. Most configuration happens at the `spaCyLayout` factory and through callback arguments that control how parsed content is mapped onto spaCy `Doc` and `Span` objects. Understanding these hooks — and the common pitfalls reported by users — is essential for adapting the library to non-default inputs (different PDF backends, multi-page documents, table-heavy reports, and constrained environments like CPU-only machines or Apple Silicon).

## Configuration of the `spaCyLayout` Component

The `spaCyLayout` class is the central configuration point. Its `__init__` accepts a spaCy `Language` pipeline, an optional Docling `PdfPipelineOptions` object, and a collection of rendering callbacks (`display_image`, `display_table`, `show_header`, `show_footer`) that determine which Docling nodes are surfaced as spaCy spans.

`Source: [spacy_layout/layout.py:1-80]()`

Typical usage wires the component into a blank or pre-trained pipeline:

```python
import spacy
from spacy_layout import spaCyLayout

nlp = spacy.blank("en")
layout = spaCyLayout(nlp)
doc = layout("path/to/document.pdf")
```

`Source: [README.md:1-60]()`

The component also exposes a `pipe` method for batch processing of multiple files. As of v0.0.12, `pipe` accepts an `as_tuples` argument that mirrors spaCy's `Language.pipe` semantics, allowing callers to stream `(context, path)` pairs through the pipeline.

`Source: [README.md:60-120]()`

## Backend Selection and Docling Interop

PDF parsing is delegated to Docling, and the backend can be swapped via `PdfPipelineOptions`. The community has asked how to substitute the default `docling-parse` backend with `PyPdfiumDocumentBackend` (issue #28). Users attempting this report little visible change in behavior because the layout mapping in `spaCy-layout` operates on the parsed `DoclingDocument` tree rather than on raw PDF bytes, so swapping the backend primarily affects text-extraction fidelity and speed — not the downstream span layout.

For users who already have a `DoclingDocument` (for example, from a custom Docling pipeline that counts images or extracts figures), v0.0.10 introduced support for passing the document directly to `spaCyLayout.__call__`, bypassing re-parsing.

`Source: [README.md:120-180]()`

### Configuration Options Overview

| Argument | Purpose | Notes |
|---|---|---|
| `nlp` | spaCy pipeline | Required; must be a `Language` instance |
| `docling_options` | Docling `PdfPipelineOptions` | Controls backend, OCR, accelerator |
| `display_image` | Callback returning placeholder text for images | Default returns `"<image>"` |
| `display_table` | Callback returning table preview text | Default returns a short snippet |
| `show_header` / `show_footer` | Toggles page header/footer spans | Default off — see issue #32 |

`Source: [spacy_layout/layout.py:80-160]()`

## Customizing Spans, Tables, and Extensions

Layout information is attached through custom extension attributes on `Doc` and `Span`. Key extensions include:

- `Doc._.layout` — list of layout spans with bounding boxes and page numbers.
- `Doc._.tables` — shortcut to all table spans (added in v0.0.6).
- `Span._.data` — `pandas.DataFrame` representation of a table cell.
- `Doc._.markdown` — Markdown rendering of the full document (v0.0.9).

`Source: [spacy_layout/types.py:1-60]()`

Tables are produced as layout spans; per-cell bounding boxes are not currently exposed, which is the limitation raised in issue #23. To omit tables entirely, callers can filter `doc._.layout` after processing rather than relying on a constructor flag.

The `display_table` callback lets users replace the inline placeholder with a custom string (for example, a Markdown link to a downstream index of `pandas.DataFrame` objects), giving finer control over `Doc.text` output.

`Source: [spacy_layout/layout.py:160-240]()`

## Common Issues and Workarounds

**Exporting results to a file** (issue #49). The library emits a spaCy `Doc`, not a serialized artifact. To persist output, iterate `doc._.layout` or `doc._.markdown` and write to disk via standard `open()` / `pandas.to_csv`.

**`spacy.require_cpu()` is not respected** (issue #37). When Docling's pipeline is configured with GPU acceleration, layout processing can allocate CUDA tensors even when spaCy is CPU-only. The workaround is to construct `PdfPipelineOptions` with `accelerator_options=AcceleratorOptions(num_threads=...)` and ensure Docling's own device selection is set to CPU before instantiating `spaCyLayout`.

**SciPy / NumPy incompatibility** (`sph_legendre_p`, issue #47). This error originates from a mismatched `scipy` install and is not caused by `spacy-layout` itself; pinning `numpy<2` or upgrading `scipy` to a version compiled against the installed NumPy resolves it.

**macOS / Apple Silicon installation** (issue #10). `pip install spacy-layout` on M-series Macs requires a compatible `torch` backend for Docling's ML models; installing `docling` first and then `spacy-layout` in a fresh virtual environment is the recommended sequence.

**Missing page headers and footers** (issue #32). Headers and footers are filtered out by default. The `show_header` and `show_footer` constructor flags re-enable them as layout spans.

**Special-character handling** (issue #16). Accented characters occasionally fail when Docling's font metrics differ from the source PDF. Replacing the default backend (see above) or pre-processing the PDF with an OCR pass typically improves fidelity.

**Accessing Docling features from `spacy-layout`** (issue #40). Because v0.0.10 accepts a pre-built `DoclingDocument`, users can run Docling's full feature set independently (image counting, figure export, logo filtering) and then pass the document to `spaCyLayout` to obtain the spaCy `Doc` view.

`Source: [README.md:180-260]()`

`Source: [pyproject.toml:1-40]()`

---

<!-- evidence_pipeline_checked: true -->
<!-- evidence_injected: true -->

---

## Pitfall Log

Project: explosion/spacy-layout

Summary: Found 12 structured pitfall item(s), including 3 high/blocking item(s). Top priority: Installation risk - Installation risk requires verification.

## 1. Installation risk - Installation risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/explosion/spacy-layout/issues/28

## 2. Installation risk - Installation risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/explosion/spacy-layout/issues/10

## 3. Installation risk - Installation risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/explosion/spacy-layout/issues/37

## 4. Capability evidence risk - Capability evidence risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.assumptions | https://github.com/explosion/spacy-layout

## 5. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/explosion/spacy-layout/issues/47

## 6. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/explosion/spacy-layout

## 7. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: downstream_validation.risk_items | https://github.com/explosion/spacy-layout

## 8. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: risks.scoring_risks | https://github.com/explosion/spacy-layout

## 9. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a security or permission risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/explosion/spacy-layout/issues/40

## 10. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a security or permission risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/explosion/spacy-layout/issues/49

## 11. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/explosion/spacy-layout

## 12. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/explosion/spacy-layout

<!-- canonical_name: explosion/spacy-layout; human_manual_source: deepwiki_human_wiki -->
