> built 2026-10-15 02:14 UTC from 3971001 (master) · pdfdol 0.1.29. Details: build_info.json

# index.html.md

<!-- generated by epythet -->

# pdfdol

Data Object Layer for PDF data

To install:	`pip install pdfdol`

[Documentation](https://i2mint.github.io/pdfdol/)

# Examples

## Pdf “Stores”

Get a dict-like object to list and read the pdfs of a folder, as text:

```none
>>> from pdfdol import PdfFilesReader
>>> from pdfdol.tests import get_test_pdf_folder
>>> folder_path = get_test_pdf_folder()
>>> pdfs = PdfFilesReader(folder_path)
>>> sorted(pdfs)
['sample_pdf_1', 'sample_pdf_2']
>>> assert pdfs['sample_pdf_2'] == [
...     'Page 1\nThis is a sample text for testing Python PDF tools.'
... ]
```

See that the values of a `PdfFilesReader` are lists of pages.
If you need strings (i.e. all the pages together) you can add a decoder like so:

```python
from dol import add_decoder
page_separator = '---------------------'
pdfs = add_decoder(pdfs, decoder=page_separator.join)
```

If you need this at the level of the class, just do this:

```python
from dol import add_decoder
page_separator = '---------------------'
FilesReader = add_decoder(PdfFilesReader, decoder=page_separator.join)
# and then
pdfs = FilesReader(folder_path)
# ...
```

If you need to concatinate a bunch of pdfs together, you can do so in many
ways. Here’s one:

```python
from dol import Files
from pdfdol import concat_pdfs

s = Files('~/Downloads/cosmograph_documentation_pdfs/')
concat_pdfs(s, key_order=sorted)
```

## Converting ebooks and documents to PDF (optional Calibre integration)

pdfdol natively converts images, HTML, and Markdown to PDF.
For additional formats – EPUB, MOBI, DOCX, ODT, DJVU, RTF, and
[many more](https://manual.calibre-ebook.com/conversion.html) –
install [Calibre](https://calibre-ebook.com/download), which provides the
`ebook-convert` command-line tool.

pdfdol does **not** depend on Calibre; it auto-detects the tool at runtime
and uses it only for formats that have no built-in converter.

**Installing ebook-convert:**

| Platform                 | Command                                                                                              |
|--------------------------|------------------------------------------------------------------------------------------------------|
| macOS (Homebrew)         | `brew install --cask calibre`                                                                        |
| Debian / Ubuntu          | `sudo apt install calibre`                                                                           |
| Fedora / RHEL            | `sudo dnf install calibre`                                                                           |
| Linux servers (headless) | `sudo -v && wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sudo sh /dev/stdin` |

The Linux CLI installer is self-contained (no GUI/X11 needed) and recommended
for servers.  See https://calibre-ebook.com/download for all options.

```python
from pdfdol import ebook_convert_to_pdf, find_ebook_convert

# Check whether Calibre is available
if find_ebook_convert():
    pdf_bytes = ebook_convert_to_pdf("book.epub")
```

You can also go through the usual `get_pdf` entry point – it will
automatically route to `ebook-convert` when it recognises the file extension:

```python
from pdfdol import get_pdf
pdf_bytes = get_pdf("book.epub")                     # returns PDF bytes
get_pdf("book.epub", egress="book.pdf")              # saves to file
```

### What cannot be converted

DRM-protected ebooks (Amazon KFX/AZW, Adobe-DRM EPUB) are encrypted, so neither
Calibre nor pdfdol can read them; the DRM has to be removed first, where you are
legally entitled to do so. Image-only sources (CBZ/CBR, DJVU, scanned PDF) do
convert, but carry no text layer – extracting their words needs OCR, which
pdfdol does not do.

### Custom converters

pdfdol maintains a **format converter registry** that maps file extensions to
converter functions.  You can register your own:

```python
from pdfdol import register_format_converter, supported_extensions

def my_custom_converter(source):
    """source is a filepath (str) or raw bytes; must return PDF bytes."""
    ...

register_format_converter('.xyz', my_custom_converter)

# See everything that's currently supported
print(supported_extensions())
```

## Get pdf from various sources

Example with a URL

```py
pdf_data = get_pdf("https://pypi.org", src_kind="url")
print("Got PDF data of length:", len(pdf_data))
```

Example with HTML content

```py
html_content = "<html><body><h1>Hello, PDF!</h1></body></html>"
pdf_data = get_pdf(html_content, src_kind="html")
print("Got PDF data of length:", len(pdf_data))
```

Example saving to file

```py
filepath = get_pdf("https://pypi.org", egress="output.pdf", src_kind="url")
print("PDF saved to:", filepath)
```

<p class="epythet-aggregates">This documentation as a single file: <a href="pdfdol.md">pdfdol.md</a> (Markdown, for agents).</p>


# _autosummary/pdfdol.base.html.md

# pdfdol.base

Base objects for pdfdol

### Functions

| `html_to_pdf`(\*args, \*\*kwargs)      |    |
|----------------------------------------|----|
| `pdf_reader_to_text_pages`(pdf_reader) |    |


# _autosummary/pdfdol.bundle.html.md

# pdfdol.bundle

Bundle several PDFs into one, with a contents page, per-document headers and bookmarks.

`concat_pdfs` just joins documents. `bundle_pdf_bytes` keeps track of which
page each document starts on while joining, which is what makes these possible:

- `toc`: a contents page (several pages if needed) whose lines link to the
  documents’ first pages;
- `doc_header`: a band naming the document, added *above* its first page, so
  no content is covered;
- `outline`: one bookmark per document.

Rendering text uses `reportlab`, imported lazily: without `toc` and
`doc_header`, only `pypdf` is used.

```pycon
>>> import io, pypdf
>>> def blank(n, w=200, h=300):
...     writer = pypdf.PdfWriter()
...     for _ in range(n):
...         _ = writer.add_blank_page(width=w, height=h)
...     buf = io.BytesIO(); _ = writer.write(buf); return buf.getvalue()
>>> out = bundle_pdf_bytes({'a.pdf': blank(2), 'b.pdf': blank(1)}, outline=True)
>>> reader = pypdf.PdfReader(io.BytesIO(out))
>>> len(reader.pages), [o.title for o in reader.outline]
(3, ['a.pdf', 'b.pdf'])
```

### Module Attributes

| [`PAGE_SIZES`](_autosummary/pdfdol.bundle.html.md#pdfdol.bundle.PAGE_SIZES)   | Page sizes in points (1/72 inch), portrait.                                                                                                                                                                                              |
|---------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [`TocRenderer`](_autosummary/pdfdol.bundle.html.md#pdfdol.bundle.TocRenderer)  | A contents renderer maps `(entries, title)` to the contents PDF bytes, or to `(pdf_bytes, link_rects)` where `link_rects[i]` is `(toc_page_index, (x0, y0, x1, y1))` for entry `i`, making that region a link to the entry's first page. |

### Functions

| [`add_header_band`](_autosummary/pdfdol.bundle.html.md#pdfdol.bundle.add_header_band)(page, text, \*[, height, ...])   | Extend `page` upward by `height` and draw `text` in the new band (in place).          |
|---------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|
| [`bundle_pdf_bytes`](_autosummary/pdfdol.bundle.html.md#pdfdol.bundle.bundle_pdf_bytes)(items, \*[, toc, ...])          | Join PDFs, optionally adding a contents page, document headers and bookmarks.         |
| [`fit_pdf_to_page`](_autosummary/pdfdol.bundle.html.md#pdfdol.bundle.fit_pdf_to_page)(pdf_bytes[, page_size, ...])     | Put each page of `pdf_bytes` on a `page_size` page, scaled to fit and centered.       |
| [`render_contents_pdf`](_autosummary/pdfdol.bundle.html.md#pdfdol.bundle.render_contents_pdf)(entries[, title, ...])       | Default `TocRenderer`: a title, then one line per entry with its page range at right. |
| [`resolve_page_size`](_autosummary/pdfdol.bundle.html.md#pdfdol.bundle.resolve_page_size)(page_size)                     | Name (case-insensitive, see `PAGE_SIZES`) or `(w, h)` points -> `(w, h)`.             |

### Classes

| [`Entry`](_autosummary/pdfdol.bundle.html.md#pdfdol.bundle.Entry)(key, start_page, n_pages)   | One document in a bundle.   |
|------------------------------------------------------------------------------------|-----------------------------|

### *class* pdfdol.bundle.Entry(key, start_page, n_pages)

Bases: [`NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple)

One document in a bundle. `start_page` is 1-based, in the final PDF.

#### key *: [str](https://docs.python.org/3/builtins/stdtypes.html#str)*

Alias for field number 0

#### n_pages *: [int](https://docs.python.org/3/builtins/functions.html#int)*

Alias for field number 2

#### start_page *: [int](https://docs.python.org/3/builtins/functions.html#int)*

Alias for field number 1

### pdfdol.bundle.PAGE_SIZES *= {'A3': (841.89, 1190.55), 'A4': (595.28, 841.89), 'A5': (419.53, 595.28), 'legal': (612.0, 1008.0), 'letter': (612.0, 792.0)}*

Page sizes in points (1/72 inch), portrait.

### pdfdol.bundle.TocRenderer

A contents renderer maps `(entries, title)` to the contents PDF bytes, or to
`(pdf_bytes, link_rects)` where `link_rects[i]` is `(toc_page_index, (x0, y0, x1, y1))`
for entry `i`, making that region a link to the entry’s first page.

alias of [`Callable`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Callable)[[[`list`](https://docs.python.org/3/builtins/stdtypes.html#list), [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)], [`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes) | [`tuple`](https://docs.python.org/3/builtins/stdtypes.html#tuple)]

### pdfdol.bundle.add_header_band(page, text, , height=30, margin=36, font='Helvetica-Bold', font_size=9, gray=0.92)

Extend `page` upward by `height` and draw `text` in the new band (in place).

* **Return type:**
  [`None`](https://docs.python.org/3/builtins/constants.html#None)

### pdfdol.bundle.bundle_pdf_bytes(items, , toc=False, doc_header=None, outline=False, title=None, toc_title='Contents', header_height=30)

Join PDFs, optionally adding a contents page, document headers and bookmarks.

* **Parameters:**
  * **items** ([`Mapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping) | [`Iterable`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable)) – `{key: pdf_bytes}` or an iterable of `(key, pdf_bytes)`, in order.
  * **toc** ([`bool`](https://docs.python.org/3/builtins/functions.html#bool) | [`Callable`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Callable)[[[`list`](https://docs.python.org/3/builtins/stdtypes.html#list), [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)], `Union`[[`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes), [`tuple`](https://docs.python.org/3/builtins/stdtypes.html#tuple)]]) – `True` for the default contents page (`render_contents_pdf`), or a
    `TocRenderer` callable `(entries, title) -> bytes | (bytes, link_rects)`.
  * **doc_header** ([`None`](https://docs.python.org/3/builtins/constants.html#None) | [`bool`](https://docs.python.org/3/builtins/functions.html#bool) | [`str`](https://docs.python.org/3/builtins/stdtypes.html#str) | [`Callable`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Callable)[[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)], [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – Text of the band added above each document’s first page.
    `True` -> the key; a format string using `{key}`, `{start_page}`,
    `{n_pages}`; or a callable `key -> str`.
  * **outline** ([`bool`](https://docs.python.org/3/builtins/functions.html#bool)) – Add one bookmark per document.
  * **title** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str) | [`None`](https://docs.python.org/3/builtins/constants.html#None)) – PDF title metadata; also the contents page title if `toc_title` is
    left at its default.
  * **toc_title** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – Title of the contents page.
  * **header_height** ([`float`](https://docs.python.org/3/builtins/functions.html#float)) – Height in points of the header band.
* **Return type:**
  [`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes)

```pycon
>>> import io, pypdf
>>> w = pypdf.PdfWriter(); _ = w.add_blank_page(width=200, height=300)
>>> buf = io.BytesIO(); _ = w.write(buf); one = buf.getvalue()
>>> out = bundle_pdf_bytes({'a.pdf': one, 'b.pdf': one}, toc=True, doc_header='{key}')
>>> reader = pypdf.PdfReader(io.BytesIO(out))
>>> len(reader.pages)  # 1 contents page + 2 documents
3
>>> float(reader.pages[1].mediabox.height)  # 300 + header band
330.0
```

### pdfdol.bundle.fit_pdf_to_page(pdf_bytes, page_size='A4', , margin=36, landscape_if_wider=True)

Put each page of `pdf_bytes` on a `page_size` page, scaled to fit and centered.

A page wider than tall gets a landscape page when `landscape_if_wider`.

* **Return type:**
  [`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes)

```pycon
>>> import io, pypdf
>>> w = pypdf.PdfWriter(); _ = w.add_blank_page(width=1000, height=500)
>>> buf = io.BytesIO(); _ = w.write(buf)
>>> out = fit_pdf_to_page(buf.getvalue(), 'A4')
>>> [round(float(v)) for v in pypdf.PdfReader(io.BytesIO(out)).pages[0].mediabox[2:]]
[842, 595]
```

### pdfdol.bundle.render_contents_pdf(entries, title='Contents', , page_size='A4', margin=36, line_height=18, font='Helvetica', title_font='Helvetica-Bold', font_size=10, title_size=16)

Default `TocRenderer`: a title, then one line per entry with its page range at right.

Returns `(pdf_bytes, link_rects)`.

* **Return type:**
  [`tuple`](https://docs.python.org/3/builtins/stdtypes.html#tuple)

### pdfdol.bundle.resolve_page_size(page_size)

Name (case-insensitive, see `PAGE_SIZES`) or `(w, h)` points -> `(w, h)`.

* **Return type:**
  [`tuple`](https://docs.python.org/3/builtins/stdtypes.html#tuple)[[`float`](https://docs.python.org/3/builtins/functions.html#float), [`float`](https://docs.python.org/3/builtins/functions.html#float)]

```pycon
>>> resolve_page_size('a4')
(595.28, 841.89)
>>> resolve_page_size((100, 200))
(100.0, 200.0)
```


# _autosummary/pdfdol.download.html.md

# pdfdol.download

Download PDF articles from markdown-formatted text.

Extracts `[title](url)` links from markdown and downloads the PDFs,
with validation (Content-Type check + %PDF magic byte check).

Main entry points:

```default
>>> download_articles(md_string, save_dir='~/Downloads')
>>> download_articles_by_section(md_string, rootdir='~/Downloads')
```

### Functions

| [`download_articles`](_autosummary/pdfdol.download.html.md#pdfdol.download.download_articles)(md_string[, save_dir, ...])   | Download articles from markdown-formatted text and save them as PDFs.   |
|--------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------|
| [`download_articles_by_section`](_autosummary/pdfdol.download.html.md#pdfdol.download.download_articles_by_section)(md_string[, ...])  | Download articles organized by markdown sections into subdirectories.   |

### pdfdol.download.download_articles(md_string, save_dir='/home/runner/Downloads', , save_non_pdf=False, verbose=True)

Download articles from markdown-formatted text and save them as PDFs.

Extracts links matching `- **[title](url)**` and downloads the URL.
Only saves files whose Content-Type is `application/pdf` and whose
first bytes are `%PDF`.

* **Parameters:**
  * **md_string** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – Markdown text containing `- **[title](url)**` links.
  * **save_dir** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – Directory to save downloaded PDFs. Defaults to `~/Downloads`.
  * **save_non_pdf** ([`bool`](https://docs.python.org/3/builtins/functions.html#bool)) – If True, save non-PDF responses with a `_non_pdf.html` suffix.
  * **verbose** ([`bool`](https://docs.python.org/3/builtins/functions.html#bool)) – Print progress messages.
* **Returns:**
  * *list* – URLs that failed to download or were not valid PDFs.
  * *Tips*
  *  *—-*
  * *When your knowledge base has many files, consider aggregating them*
  * into a single PDF with `pdfdol.concat_pdfs`.

### pdfdol.download.download_articles_by_section(md_string, rootdir=None, save_non_pdf=False, , section_marker='###')

Download articles organized by markdown sections into subdirectories.

Parses sections delimited by `section_marker` (default `###`) and
creates a subdirectory for each section, then downloads articles within
each section using [`download_articles()`](_autosummary/pdfdol.download.html.md#pdfdol.download.download_articles).

* **Parameters:**
  * **md_string** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – Markdown text with sections and article links.
  * **rootdir** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – Root directory for section subdirectories. Defaults to `~/Downloads`.
  * **save_non_pdf** ([`bool`](https://docs.python.org/3/builtins/functions.html#bool)) – Whether to save non-PDF content.
  * **section_marker** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – Regex pattern for section headings (default `###`).
* **Returns:**
  Section names as keys, lists of failed URLs as values.
* **Return type:**
  [*dict*](https://docs.python.org/3/builtins/stdtypes.html#dict)


# _autosummary/pdfdol.html.md

# pdfdol

Data Object Layers for PDF data.

```pycon
>>> from pdfdol import PdfFilesReader
>>> from pdfdol.tests import get_test_pdf_folder
>>> folder_path = get_test_pdf_folder()
>>> s = PdfFilesReader(folder_path)
>>> sorted(s)
['sample_pdf_1', 'sample_pdf_2', 'sample_with_title']
>>> assert s['sample_pdf_2'] == [
...     'Page 1\nThis is a sample text for testing Python PDF tools.'
... ]
```

### Modules

| [`base`](_autosummary/pdfdol.base.html.md#module-pdfdol.base)         | Base objects for pdfdol                                                                 |
|----------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|
| [`bundle`](_autosummary/pdfdol.bundle.html.md#module-pdfdol.bundle)     | Bundle several PDFs into one, with a contents page, per-document headers and bookmarks. |
| [`download`](_autosummary/pdfdol.download.html.md#module-pdfdol.download) | Download PDF articles from markdown-formatted text.                                     |
| [`tools`](_autosummary/pdfdol.tools.html.md#module-pdfdol.tools)       | Pdf Tools.                                                                              |
| [`util`](_autosummary/pdfdol.util.html.md#module-pdfdol.util)         | Utils for pdfdol                                                                        |


# _autosummary/pdfdol.tools.html.md

# pdfdol.tools

Pdf Tools.

### Functions

| `add_css`(html_text[, css])                                                                         |                                                                                                                                 |
|-----------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------|
| [`any_to_pdf_bytes`](_autosummary/pdfdol.tools.html.md#pdfdol.tools.any_to_pdf_bytes)(src, \*[, src_kind])              | Convert any source (string, bytes, file path, URL, HTML, etc.) to PDF bytes.                                                    |
| [`concat_pdf_bytes`](_autosummary/pdfdol.tools.html.md#pdfdol.tools.concat_pdf_bytes)(list_of_pdf_bytes)                | Concatenate multiple PDF bytes into a single PDF bytes.                                                                         |
| [`concat_pdf_files`](_autosummary/pdfdol.tools.html.md#pdfdol.tools.concat_pdf_files)(pdf_filepaths[, save_filepath])   | Concatenate multiple PDF files into a single PDF file.                                                                          |
| [`concat_pdf_readers`](_autosummary/pdfdol.tools.html.md#pdfdol.tools.concat_pdf_readers)(pdf_readers)                    | Concatenate multiple PdfReader objects into a single PdfWriter object.                                                          |
| [`concat_pdfs`](_autosummary/pdfdol.tools.html.md#pdfdol.tools.concat_pdfs)(pdf_source[, save_filepath, ...])      | Concatenate multiple PDFs and/or images given as a mapping of filepaths to bytes.                                               |
| [`ebook_convert_to_pdf`](_autosummary/pdfdol.tools.html.md#pdfdol.tools.ebook_convert_to_pdf)(source, \*[, extension, ...]) | Convert a file to PDF using Calibre's `ebook-convert`.                                                                          |
| [`find_ebook_convert`](_autosummary/pdfdol.tools.html.md#pdfdol.tools.find_ebook_convert)()                               | Find the `ebook-convert` binary from Calibre.                                                                                   |
| [`get_format_converter`](_autosummary/pdfdol.tools.html.md#pdfdol.tools.get_format_converter)(extension)                    | Get the registered PDF converter for a file extension.                                                                          |
| [`get_pdf`](_autosummary/pdfdol.tools.html.md#pdfdol.tools.get_pdf)(src[, egress, src_kind, options, ...])     | Convert the given source to a PDF (bytes) and process it using the specified egress.                                            |
| [`key_and_value_to_pdf_bytes`](_autosummary/pdfdol.tools.html.md#pdfdol.tools.key_and_value_to_pdf_bytes)(key, value)             | Convert a key-value pair to PDF bytes, using the key's file extension to select a converter from the format converter registry. |
| `markdown_to_pdf`(md_src[, egress, ...])                                                            |                                                                                                                                 |
| [`pdf_to_metadata`](_autosummary/pdfdol.tools.html.md#pdfdol.tools.pdf_to_metadata)(pdf_src)                           | Extract metadata from a PDF source.                                                                                             |
| [`pdf_to_title`](_autosummary/pdfdol.tools.html.md#pdfdol.tools.pdf_to_title)(pdf_src)                              | Extract the document title from a PDF source.                                                                                   |
| [`register_format_converter`](_autosummary/pdfdol.tools.html.md#pdfdol.tools.register_format_converter)(extensions, ...[, ...])  | Register a PDF converter function for one or more file extensions.                                                              |
| [`supported_extensions`](_autosummary/pdfdol.tools.html.md#pdfdol.tools.supported_extensions)()                             | Return file extensions that have registered PDF converters.                                                                     |

### pdfdol.tools.any_to_pdf_bytes(src, , src_kind=None)

Convert any source (string, bytes, file path, URL, HTML, etc.) to PDF bytes.

This is a convenience function that uses get_pdf with egress=None to always
return PDF bytes regardless of the source type.

* **Parameters:**
  * **src** – Source content - can be a file path, URL, HTML string, markdown,
    image bytes, etc.
  * **src_kind** ([`Literal`](https://docs.python.org/3/library/typing.html#typing.Literal)[`'url'`, `'html'`, `'file'`, `'md'`, `'markdown'`, `'text'`, `'image'`, `'ebook'`]) – Optional hint about the source type. If not provided, it will
    be determined heuristically.
* **Returns:**
  PDF bytes
* **Return type:**
  [`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes)

### Examples

```pycon
>>> # Convert image bytes to PDF
>>> image_bytes = open('image.png', 'rb').read()
>>> pdf_bytes = any_to_pdf_bytes(image_bytes)
```

```pycon
>>> # Convert HTML string to PDF
>>> html = "<h1>Hello World</h1>"
>>> pdf_bytes = any_to_pdf_bytes(html, src_kind="html")
```

### pdfdol.tools.concat_pdf_bytes(list_of_pdf_bytes)

Concatenate multiple PDF bytes into a single PDF bytes.

* **Return type:**
  [`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes)

### pdfdol.tools.concat_pdf_files(pdf_filepaths, save_filepath='combined.pdf')

Concatenate multiple PDF files into a single PDF file.

### pdfdol.tools.concat_pdf_readers(pdf_readers)

Concatenate multiple PdfReader objects into a single PdfWriter object.

* **Return type:**
  `PdfWriter`

### pdfdol.tools.concat_pdfs(pdf_source, save_filepath=False, , filter_extensions=False, key_order=None, skip_errors=False, toc=False, doc_header=None, outline=False, image_page_size=None, title=None, \*\*kwargs)

Concatenate multiple PDFs and/or images given as a mapping of filepaths to bytes.

#### TIP
Pdfs are aggregated in the order of the mapping’s iteration order.
If you need these to be in a specific order, you can use the key_order argument
to sort the mapping, specifying either a callable that will be called on the keys
to sort them, or specifying an iterable of keys in the desired order.
Both the ordering function and the explicit list can also be used to filter
out some keys.

* **Parameters:**
  * **pdf_source** (`Union`[[`Iterable`](https://docs.python.org/3/library/typing.html#typing.Iterable)[[`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes)], [`Mapping`](https://docs.python.org/3/library/typing.html#typing.Mapping)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes)]]) – Mapping of filepaths to pdf bytes or an iterable of pdf bytes
  * **save_filepath** – Filepath to save the concatenated pdf.
    If `True`, the save_filepath will be taken from the rootdir of the pdf_source
    that attribute exists, and no file of that name (+’.pdf’) exists.
    If `False`, the pdf bytes are returned.
  * **filter_extensions** – If True, only files with recognized extensions
    (‘.pdf’, ‘.png’, ‘.jpg’, ‘.jpeg’, ‘.bmp’, ‘.gif’, ‘.tiff’) are considered
  * **key_order** (`Union`[[`Callable`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Callable), [`Iterable`](https://docs.python.org/3/library/typing.html#typing.Iterable)]) – Callable or iterable of keys to sort the mapping
  * **toc** – Add a contents page whose lines link to each document. `True`, or a
    renderer callable (see `pdfdol.bundle.bundle_pdf_bytes`). Uses reportlab.
  * **doc_header** – Text of a band added above each document’s first page (so no
    content is covered): `True` for the key, a format string with `{key}`,
    `{start_page}`, `{n_pages}`, or a callable `key -> str`. Uses reportlab.
  * **outline** – Add one bookmark per document.
  * **image_page_size** – Fit and center each image on a page of this size
    (`'A4'`, `'letter'`, … or `(w, h)` points; landscape for wide images)
    instead of a page sized from its pixels. `None`: current behavior.
  * **title** – PDF title metadata (and contents page title).
* **Return type:**
  [*str*](https://docs.python.org/3/builtins/stdtypes.html#str) | [*bytes*](https://docs.python.org/3/builtins/stdtypes.html#bytes)

The `toc`, `doc_header`, `outline` and `title` options are implemented in
`pdfdol.bundle.bundle_pdf_bytes`, which you can call directly on converted pdfs.

* **Return type:**
  [`str`](https://docs.python.org/3/builtins/stdtypes.html#str) | [`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes)
* **Returns:**
  The save_filepath if it was specified, otherwise the concatenated pdf bytes

```pycon
>>> s = Files('~/Downloads/')
>>> pdf_bytes = concat_pdfs(s, save_filepath=False, key_order=sorted)
```

### pdfdol.tools.ebook_convert_to_pdf(source, , extension=None, ebook_convert_path=None)

Convert a file to PDF using Calibre’s `ebook-convert`.

Requires Calibre to be installed ([https://calibre-ebook.com/download](https://calibre-ebook.com/download)).

* **Parameters:**
  * **source** – File path (str/Path) or file content (bytes).
  * **extension** – File extension hint (e.g. `'.epub'`).  Required when
    *source* is bytes; auto-detected from file path otherwise.
  * **ebook_convert_path** – Explicit path to the `ebook-convert` binary.
    Auto-detected if not provided.
* **Returns:**
  PDF content.
* **Return type:**
  [*bytes*](https://docs.python.org/3/builtins/stdtypes.html#bytes)
* **Raises:**
  * [**FileNotFoundError**](https://docs.python.org/3/builtins/exceptions.html#FileNotFoundError) – If ebook-convert is not installed.
  * [**RuntimeError**](https://docs.python.org/3/builtins/exceptions.html#RuntimeError) – If the conversion fails.

### pdfdol.tools.find_ebook_convert()

Find the `ebook-convert` binary from Calibre.

Checks the system PATH first, then common platform-specific install
locations (macOS `Calibre.app`, Windows `Program Files`).

* **Return type:**
  [`str`](https://docs.python.org/3/builtins/stdtypes.html#str) | [`None`](https://docs.python.org/3/builtins/constants.html#None)
* **Returns:**
  Path to the binary, or `None` if not found.

### pdfdol.tools.get_format_converter(extension)

Get the registered PDF converter for a file extension.

Checks the built-in registry first.  For extensions supported by
Calibre’s `ebook-convert` (but not natively by pdfdol), returns an
ebook-convert-based converter on the fly.

* **Parameters:**
  **extension** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – File extension (e.g. `'.epub'` or `'epub'`).
* **Return type:**
  [`Callable`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Callable) | [`None`](https://docs.python.org/3/builtins/constants.html#None)
* **Returns:**
  Converter callable, or `None` if the format is not supported.

### pdfdol.tools.get_pdf(src, egress=None, , src_kind=None, options=None, toc=None, cover=None, css=None, configuration=None, cover_first=False, verbose=False, \*\*kwargs)

Convert the given source to a PDF (bytes) and process it using the specified egress.

The source (src) can be:

> - a URL (e.g. “[https://example.com](https://example.com)”)
> - an HTML string
> - a file path to an HTML file

The egress parameter determines how the PDF bytes are returned:

> - If None, returns the PDF as bytes.
> - If a string, treats it as a file path where the PDF is saved.
> - If a callable, applies it to the PDF bytes and returns its result.
>   For example, you may want to specify egress=pypdf.PdfReader to get an object
>   that provides an interface of all PDF components, or you might want to
>   upload the PDF to a cloud storage service.

The src_kind parameter allows explicit specification of the source kind (“url”, “html”, or “file”).
If not provided, it is determined heuristically using \_resolve_src_kind.

* **Parameters:**
  * **src** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – The source to convert.
  * **egress** ([`None`](https://docs.python.org/3/builtins/constants.html#None) | [`str`](https://docs.python.org/3/builtins/stdtypes.html#str) | [`Callable`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Callable)) – How to handle the PDF bytes.
  * **src_kind** ([`Literal`](https://docs.python.org/3/library/typing.html#typing.Literal)[`'url'`, `'html'`, `'file'`, `'md'`, `'markdown'`, `'text'`, `'image'`, `'ebook'`]) – Explicit source kind; if omitted, determined automatically.
  * **options** – (optional) dict with wkhtmltopdf options, with or w/o ‘–’
  * **toc** – (optional) dict with toc-specific wkhtmltopdf options, with or w/o ‘–’
  * **cover** – (optional) string with url/filename with a cover html page
  * **css** – (optional) string with path to css file which will be added to a single input file
  * **configuration** – (optional) instance of pdfkit.configuration.Configuration()
  * **cover_first** – (optional) if True, cover always precedes TOC
  * **verbose** – (optional) By default ‘–quiet’ is passed to all calls, set this to False to get wkhtmltopdf output to stdout.
* **Returns:**
  The PDF bytes, or the result of processing them via the egress callable.
* **Return type:**
  [`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes) | [`Any`](https://docs.python.org/3/library/typing.html#typing.Any)

### Examples

# Example with a URL:
pdf_data = get_pdf(”[https://pypi.org](https://pypi.org)”, src_kind=”url”)
print(“Got PDF data of length:”, len(pdf_data))

# Example with HTML content:
html_content = “<html><body><h1>Hello, PDF!</h1></body></html>”
pdf_data = get_pdf(html_content, src_kind=”html”)
print(“Got PDF data of length:”, len(pdf_data))

# Example saving to file:
filepath = get_pdf(”[https://pypi.org](https://pypi.org)”, egress=”output.pdf”, src_kind=”url”)
print(“PDF saved to:”, filepath)

### pdfdol.tools.key_and_value_to_pdf_bytes(key, value)

Convert a key-value pair to PDF bytes, using the key’s file extension to
select a converter from the format converter registry.

* **Parameters:**
  * **key** – The key (usually a filename) whose extension determines the converter.
  * **value** – The value (usually bytes) to convert.
* **Returns:**
  PDF bytes
* **Return type:**
  [`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes)
* **Raises:**
  [**ValueError**](https://docs.python.org/3/builtins/exceptions.html#ValueError) – If the file type is unsupported or conversion fails.

### pdfdol.tools.pdf_to_metadata(pdf_src)

Extract metadata from a PDF source.

* **Parameters:**
  **pdf_src** (`Union`[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes), `PdfReader`]) – Can be a file path (str), PDF bytes, or a PdfReader object
* **Returns:**
  Dictionary containing metadata fields (title, author, subject, etc.)
  : Returns empty dict if no metadata or an error occurs.
* **Return type:**
  [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)

### Examples

```pycon
>>> from pathlib import Path
>>> from pdfdol.tests.utils_for_testing import get_test_pdf_folder
>>> test_folder = Path(get_test_pdf_folder())
>>> pdf_path = test_folder / "sample_with_title.pdf"
>>> metadata = pdf_to_metadata(str(pdf_path))
>>> metadata.get('Title')
'Sample PDF with Title'
>>> metadata.get('Author')
'Test Author'
```

### pdfdol.tools.pdf_to_title(pdf_src)

Extract the document title from a PDF source.

* **Parameters:**
  **pdf_src** (`Union`[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes), `PdfReader`]) – Can be a file path (str), PDF bytes, or a PdfReader object
* **Returns:**
  The title from the metadata, or None if not found or an error occurs.
* **Return type:**
  [`str`](https://docs.python.org/3/builtins/stdtypes.html#str) | [`None`](https://docs.python.org/3/builtins/constants.html#None)

### Examples

```pycon
>>> from pathlib import Path
>>> from pdfdol.tests.utils_for_testing import get_test_pdf_folder
>>> test_folder = Path(get_test_pdf_folder())
>>> # Test with file path
>>> pdf_path = test_folder / "sample_with_title.pdf"
>>> pdf_to_title(str(pdf_path))
'Sample PDF with Title'
>>> # Test with bytes
>>> pdf_bytes = pdf_path.read_bytes()
>>> pdf_to_title(pdf_bytes)
'Sample PDF with Title'
>>> # Test with PdfReader
>>> reader = pypdf.PdfReader(str(pdf_path))
>>> pdf_to_title(reader)
'Sample PDF with Title'
>>> # Test with no title
>>> pdf_path_no_title = test_folder / "sample_pdf_1.pdf"
>>> pdf_to_title(str(pdf_path_no_title)) is None
True
```

### pdfdol.tools.register_format_converter(extensions, converter, , force=False)

Register a PDF converter function for one or more file extensions.

* **Parameters:**
  * **extensions** – File extension(s) to register (e.g. `'.epub'` or
    `['.epub', '.mobi']`).
  * **converter** – Callable that takes a source (filepath str or content bytes)
    and returns PDF bytes.
  * **force** ([`bool`](https://docs.python.org/3/builtins/functions.html#bool)) – If True, overwrite any existing converter for these extensions.

### pdfdol.tools.supported_extensions()

Return file extensions that have registered PDF converters.

Includes natively supported extensions and, when Calibre’s ebook-convert
is installed, additional ebook and document format extensions.

* **Return type:**
  [`tuple`](https://docs.python.org/3/builtins/stdtypes.html#tuple)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`...`](https://docs.python.org/3/builtins/constants.html#Ellipsis)]


# _autosummary/pdfdol.util.html.md

# pdfdol.util

Utils for pdfdol

### Functions

| [`add_affix`](_autosummary/pdfdol.util.html.md#pdfdol.util.add_affix)(string, \*[, prefix, suffix])       | Add a suffix and/or prefix to a filepath.                        |
|------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
| [`affix_source_if_target_not_given`](_autosummary/pdfdol.util.html.md#pdfdol.util.affix_source_if_target_not_given)(src[, ...])  | If target is None, affix the source filepath and return it.      |
| [`ensure_pages`](_autosummary/pdfdol.util.html.md#pdfdol.util.ensure_pages)(pages)                           | Ensure that pages are given as a sequence of PageObject objects. |
| [`is_page_empty`](_autosummary/pdfdol.util.html.md#pdfdol.util.is_page_empty)(page[, min_n_characters])       | Check if a PDF page is empty.                                    |
| [`remove_empty_pages`](_autosummary/pdfdol.util.html.md#pdfdol.util.remove_empty_pages)(pages[, output_path, ...]) | Remove empty pages from a PDF file.                              |

### pdfdol.util.add_affix(string, , prefix=None, suffix=None)

Add a suffix and/or prefix to a filepath.

```pycon
>>> add_affix('file.pdf', prefix='new_', suffix='_for_you')
'new_file_for_you.pdf'
```

### pdfdol.util.affix_source_if_target_not_given(src, target=None, , prefix=None, suffix=None)

If target is None, affix the source filepath and return it.

```pycon
>>> affix_source_if_target_not_given(
...     'file.pdf', 'target_exists', prefix='new_', suffix='_for_you'
... )
'target_exists'
>>> affix_source_if_target_not_given(
...     'file.pdf', None, prefix='new_', suffix='_for_you'
... )
'new_file_for_you.pdf'
```

### pdfdol.util.ensure_pages(pages)

Ensure that pages are given as a sequence of PageObject objects.

* **Return type:**
  [`Iterable`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable)[`PageObject`]

### pdfdol.util.is_page_empty(page, min_n_characters=1)

Check if a PDF page is empty.

* **Return type:**
  [`bool`](https://docs.python.org/3/builtins/functions.html#bool)

### pdfdol.util.remove_empty_pages(pages, output_path=None, , page_is_empty=None, suppress_warnings=True)

Remove empty pages from a PDF file.


# about-this-build.html.md

<!-- generated by epythet -->

# About this build

This documentation was built on **2026-10-15 02:14 UTC** from commit <a href="https://github.com/i2mint/pdfdol/commit/39710016009154968a8d2d74ee3503b834d60cf6"><code>3971001</code></a> on branch <code>master</code>, for **pdfdol 0.1.29** (from <code>setup.cfg</code>).

#### WARNING
The documentation and the package may be misaligned:

- The documented version (0.1.29) is ahead of the latest release on PyPI (0.1.28): these docs describe unreleased code.

## Source

|                     |                                                                                                                                                      |
|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| Commit              | <a href="https://github.com/i2mint/pdfdol/commit/39710016009154968a8d2d74ee3503b834d60cf6"><code>39710016009154968a8d2d74ee3503b834d60cf6</code></a> |
| Branch              | <code>master</code>                                                                                                                                  |
| Tags at this commit | <code>0.1.29</code>                                                                                                                                  |
| Working tree        | clean                                                                                                                                                |
| Remote              | <code>https://github.com/i2mint/pdfdol</code>                                                                                                        |

## Continuous integration

|              |                                                                                            |
|--------------|--------------------------------------------------------------------------------------------|
| Repository   | <code>i2mint/pdfdol</code>                                                                 |
| Run          | <a href="https://github.com/i2mint/pdfdol/actions/runs/38669822244">38669822244</a>        |
| Ref          | <code>refs/heads/master</code>                                                             |
| Event commit | <code>bd17e3066b6d73a17d79b3a655310a683f68fd8d</code> (in the history of the built commit) |

## Tools

|          |         |
|----------|---------|
| epythet  | 0.2.17  |
| Sphinx   | 9.1.0   |
| docutils | 0.22.4  |
| Python   | 3.12.15 |

## Configuration as resolved

|               |                                                                   |
|---------------|-------------------------------------------------------------------|
| theme         | <code>auto</code> (Sphinx theme <code>pydata_sphinx_theme</code>) |
| accent        | <code>#006883</code>                                              |
| api_generator | <code>autosummary</code>                                          |
| ignore        | <code>tests/</code>, <code>scrap/</code>, <code>examples/</code>  |
| agent_outputs | <code>true</code>                                                 |
| aggregates    | <code>md</code>                                                   |
| ai_artifacts  | <code>true</code>                                                 |

## Package on PyPI

Latest release: <a href="https://pypi.org/project/pdfdol/0.1.28/">0.1.28</a>, older than the documented version (0.1.29).

## Reproduce

```bash
git clone https://github.com/i2mint/pdfdol && cd pdfdol
git checkout 39710016009154968a8d2d74ee3503b834d60cf6
pip install "epythet==0.2.17"
epythet quickstart . --ignore tests/ scrap/ examples/
```

The same data, for machines: <a href="build_info.json"><code>build_info.json</code></a> (schema version 1).


# ai-agents.html.md

<!-- generated by epythet -->

# For AI agents

`pdfdol` ships artifacts for coding agents alongside its code. This page lists
them, says where each lives in the repository, and points at the
machine-readable copies of this documentation.

## Skills

Skills are folders holding a `SKILL.md` (the [Agent Skills](https://agentskills.io) format): a description that tells an agent when to use it and a body with the procedure. Install one into your agent with `gh skill` (any host: `--agent claude-code`, `copilot`, `cursor`, `codex`, `gemini`), or use the copy bundled in the wheel.

### `pdfdol-convert`

Convert files, URLs, or raw content to PDF using pdfdol. Use this skill whenever the user wants to convert documents to PDF format – including EPUB, MOBI, DOCX, ODT, HTML, Markdown, images, URLs, or any other file format to PDF. Also use when the user asks about what formats can be converted to PDF, wants to register custom format converters, or needs to check whether Calibre/ebook-convert is available. Triggers on: “convert to pdf”, “make a pdf from”, “epub to pdf”, “save as pdf”, “export to pdf”, “pdf from url”, “screenshot to pdf”, “image to pdf”, “docx to pdf”.

Source: `pdfdol/data/skills/pdfdol-convert` (bundled with the pip package).

### `pdfdol-read`

Read, extract text from, and process PDF files using pdfdol. Use this skill whenever the user wants to extract text from PDFs, read PDF metadata (title, author), access a folder of PDFs as a dict-like store, concatenate or merge multiple PDFs, or do batch PDF operations. Triggers on: “extract text from pdf”, “read pdf”, “pdf to text”, “get pdf title”, “merge pdfs”, “combine pdfs”, “concatenate pdfs”, “pdf metadata”, “pdf folder”, “pdf store”, “batch pdf”.

Source: `pdfdol/data/skills/pdfdol-read` (bundled with the pip package).

The bundled skills are also on disk after `pip install pdfdol`, under the package’s `data/skills/` directory; link them into an agent without network access with `skill link-skills <that directory>`.

## Instruction files

Files agents read before working in this repository.

- `.claude/CLAUDE.md`: read by Claude Code

## Machine-readable documentation

This site publishes the same documentation in forms that fit an agent’s context window:

- <a href="llms.txt"><code>llms.txt</code></a>: an index of every page with a one-line description ([llms.txt](https://llmstxt.org) format)
- <a href="pdfdol.md"><code>pdfdol.md</code></a>: the whole documentation as one Markdown file
- `<page>.html.md`: a rendered Markdown twin of every page, advertised from each page’s `<head>` with `<link rel="alternate" type="text/markdown">`
- <a href="objects.inv"><code>objects.inv</code></a>: the Sphinx inventory: a symbol-to-URL index (`sphobjinv convert plain objects.inv -`)


# api.html.md

# API reference

| [`pdfdol`](_autosummary/pdfdol.html.md#module-pdfdol)   | Data Object Layers for PDF data.   |
|-------------------------------------------------------------------------|------------------------------------|


