Metadata-Version: 2.4
Name: ferri-pdf
Version: 0.1.8
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Rust
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
Classifier: Topic :: Printing
Classifier: Topic :: Text Processing :: Markup :: HTML
Classifier: Typing :: Typed
License-File: LICENSE
License-File: assets/fonts/FONT-LICENSE.txt
License-File: assets/fonts/LIBERATION-LICENSE.txt
License-File: assets/hyphenation/PATTERNS.txt
License-File: spec/cldr/LICENSE
License-File: spec/emoji/LICENSE
License-File: spec/css/mdn-data/LICENSE
License-File: src/document/inline/Unicode-LICENSE.txt
License-File: src/svg/krilla_svg/LICENSE_APACHE
License-File: src/svg/krilla_svg/LICENSE_MIT
License-File: src/svg/resvg/LICENSE-APACHE
License-File: src/svg/resvg/LICENSE-MIT
License-File: src/svg/usvg/LICENSE-APACHE
License-File: src/svg/usvg/LICENSE-MIT
License-File: vendor/avif-decode/LICENSE
License-File: vendor/heic-decoder/LICENSE-APACHE
License-File: vendor/heic-decoder/LICENSE-MIT
License-File: vendor/icu_casemap/LICENSE
License-File: vendor/krilla/LICENSE_APACHE
License-File: vendor/krilla/LICENSE_MIT
License-File: vendor/krilla/LICENSE_MPL
License-File: vendor/selectors/LICENSE
License-File: vendor/zenavif-parse/LICENSE
License-File: vendor/rav1d/COPYING
License-File: vendor/krilla/NOTICE.md
Summary: HTML and CSS to PDF renderer for print, in pure Rust
Keywords: pdf,html,css,print,renderer
License: MIT AND (Apache-2.0 OR MIT) AND Unicode-3.0 AND CC0-1.0 AND Bitstream-Vera AND
	LicenseRef-Arev AND OFL-1.1-RFN AND BSD-3-Clause AND Unlicense AND FSFAP-no-
	warranty-disclaimer
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Repository, https://github.com/apollohg/ferri-pdf

# Ferri PDF for Python

An HTML and CSS to PDF renderer for print, written in pure Rust. No browser and no JavaScript.

```sh
pip install ferri-pdf
```

Wheels are prebuilt for CPython 3.10 and later on Linux, macOS and Windows.

## Rendering

```python
import ferri_pdf

output = ferri_pdf.render("<h1>Hello</h1>")
with open("hello.pdf", "wb") as file:
    file.write(output.pdf)
```

`render` takes a `str`, or `bytes` decoded by the HTML encoding rules. It returns the PDF bytes, `page_count`, the located `headings` and the `diagnostics` for recovered input. Invalid markup, unsupported CSS and missing resources never raise; they are recovered as the specifications say and listed in `diagnostics`.

The interpreter lock is released while rendering, so threads render in parallel.

## Options

```python
options = ferri_pdf.RenderOptions(
    base="templates/invoice",
    stylesheets=["@page { size: A4; margin: 2cm }"],
    serif=["Georgia"],
    jpeg_quality=85,
    image_dpi=150,
    allow_http=False,
)
output = ferri_pdf.render(html, options)
```

| Option | Effect |
|---|---|
| `base` | Directory that relative URLs resolve against (default: the working directory) |
| `base_url` | Document URL that relative URLs resolve against, instead of `base` |
| `stylesheets` | Author stylesheets applied after the document's own |
| `user_stylesheets` | User-origin stylesheets |
| `paper` | Paper size `(width, height)` in points when CSS sets none (default: A4) |
| `color_scheme` | `"light"` or `"dark"` for `prefers-color-scheme` |
| `contrast` | `"no-preference"`, `"less"`, `"more"` or `"custom"` for `prefers-contrast` |
| `reduced_motion` | `"no-preference"` or `"reduce"` for `prefers-reduced-motion` |
| `serif`, `sans_serif`, `monospace` | Installed families for each generic font |
| `fallback` | Installed families tried for missing glyphs |
| `image_dpi` | Downsample raster images to this resolution |
| `jpeg_quality` | Re-encode opaque photos as JPEG at this quality, 1 to 100 |
| `include_layout` | Return the measured layout as `output.layout` |
| `allow_content_loss` | Return the PDF even when a resource limit left content out |
| `allow_http` | Fetch HTTP(S) resources with the built-in client (default: `True`) |
| `file_root` | Refuse local files outside this directory |
| `fetch` | Callable that loads every URL other than local files and data URLs |

Rendering untrusted HTML? Set `allow_http=False` and `file_root` so a document cannot reach your network or read files outside its own assets, or route requests through `fetch` and decide there.

## Fetching resources yourself

`fetch` receives each absolute URL that is not a `file:` or `data:` URL: HTTP(S), and any scheme your `base_url` uses, such as `app://`. It replaces the built-in HTTP client. Return the body as `bytes`, a `ferri_pdf.Resource` to add a content type or the final URL after redirects, or `None` when the resource is unavailable.

```python
import httpx
import ferri_pdf

client = httpx.Client(headers={"Authorization": f"Bearer {token}"}, timeout=10)

def fetch(url: str) -> ferri_pdf.Resource | None:
    if not url.startswith("https://assets.example.com/"):
        return None
    response = client.get(url, follow_redirects=True)
    response.raise_for_status()
    return ferri_pdf.Resource(
        response.content,
        content_type=response.headers.get("content-type"),
        url=str(response.url),
    )

output = ferri_pdf.render(html, ferri_pdf.RenderOptions(fetch=fetch))
```

Relative URLs inside a fetched resource resolve against its `url`. An `Exception` raised by `fetch`, like `None`, leaves that resource unavailable and is reported in `diagnostics`; rendering carries on. `KeyboardInterrupt`, `SystemExit` and a return value of the wrong type stop further fetches and are raised from `render`. Local files still load from disk, within `file_root` when set.

## Several documents in one PDF

```python
output = ferri_pdf.render_parts([
    ferri_pdf.Part(cover_html, base="cover"),
    ferri_pdf.Part(body_html, base="body"),
])
for part in output.parts:
    print(part.first_page, part.page_count)
```

Each part starts on a new page. Parts can also be plain `str` or `bytes`, which use the options' base.

## Errors

`ferri_pdf.RenderError` is raised when rendering fails. Its subclass `ferri_pdf.ContentLostError` is raised when a resource limit left content out of the PDF; its `diagnostics` list the losses and `part` names the one-based part of a multipart render. Pass `allow_content_loss=True` to get the PDF anyway. Invalid option values raise `ValueError`.

## Licence

MIT. The bundled fonts, hyphenation patterns and specification data keep their own licences; see the [repository](https://github.com/apollohg/ferri-pdf).

