Metadata-Version: 2.4
Name: pd4ml
Version: 1.0.0
Summary: PD4ML for Python: HTML and CSS to PDF, PDF/A, PDF/UA, ZUGFeRD, DOCX, RTF and images
Author-email: PD4ML <support@pd4ml.com>
License-Expression: LicenseRef-Proprietary
Project-URL: Homepage, https://pd4ml.com
Project-URL: Documentation, https://pd4ml.com/docs/
Project-URL: Usage examples, https://pd4ml.com/docs/usage-examples.html
Keywords: pdf,html,html-to-pdf,pdf/a,pdf/ua,zugferd,factur-x,docx,rtf,pd4ml
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Java
Classifier: Topic :: Printing
Classifier: Topic :: Office/Business
Classifier: Topic :: Text Processing :: Markup :: HTML
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: JPype1>=1.5
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"

# pd4ml — PD4ML for Python

HTML and CSS to **PDF**, **PDF/A**, **PDF/UA**, **ZUGFeRD/Factur-X**, **DOCX**, **RTF** and page
**images**, with the full public [PD4ML](https://pd4ml.com) API (Java package `com.pd4ml`)
available to Python applications.

PD4ML runs in a Java virtual machine inside the Python process, through
[JPype](https://jpype.readthedocs.io). Every public Java class, method, constant and enum can be
used directly, and the module adds Python I/O (paths, bytes, file objects) on top. New converters
use PD4ML's **v42 layout engine** by default.

```
pip install pd4ml
```

```python
import pd4ml

pd = pd4ml.PD4ML()                                   # v42 layout engine
pd.setPageSize(pd4ml.PageSize.A4)
pd.setPageMargins(pd4ml.PageMargins(15, 15, 15, 15, pd4ml.Units.MM))
pd.readHTML("invoice.html")                          # a path, URL, bytes or file object
pd.writePDF("invoice.pdf", pd4ml.PdfSpec.PDFA_3B)    # a path or file object; no target: bytes
```

The first use downloads the most recent PD4ML build (see *Installation* below).

## Requirements

- Python 3.9 or later.
- Java 8 or later: a JDK or JRE of the same bitness as Python; 17 or 21 is recommended. JPype
  finds it through `JAVA_HOME` or the system's default Java. To use a specific JVM library
  (`jvm.dll`, `libjvm.so`), set `PD4ML_JVM`.

## Installation

```
pip install pd4ml
```

The package contains the Python module only. The PD4ML library itself comes from the PD4ML
Maven repository (<https://pd4ml.com/maven2>):

- **Automatically.** If no PD4ML build is installed or configured, the first use of PD4ML
  downloads the most recent 4.x release. Bouncy Castle (for encryption and signing) comes along
  from Maven Central. Every file is verified against its published SHA-1 checksum and stored in
  a per-user cache directory: `%LOCALAPPDATA%\pd4ml-python` on Windows, `~/.cache/pd4ml-python`
  elsewhere, or `PD4ML_HOME`. A notice on standard error reports the download.
- **Ahead of time**, which is better for servers and container images:

  ```
  pd4ml install                       # the most recent 4.x release (also: python -m pd4ml install)
  ```

  Run it again to update to a newer release. From Python, call `pd4ml.install()`.

`PD4ML_AUTO_INSTALL=0` turns the automatic download off; the module then raises
`pd4ml.ClasspathError` until a build is installed or configured.

| Command | |
|---|---|
| `pd4ml install` | download the most recent 4.x release, or update to it |
| `pd4ml install --version 4.0.23fx2` | an exact version |
| `pd4ml install --snapshots` | the most recent build, including development snapshots |
| `pd4ml install --jakarta` | the `-jakarta` variant |
| `pd4ml install --list` | the available versions |
| `pd4ml install --home DIR` | install into `DIR` (then set `PD4ML_HOME=DIR` for the application) |
| `pd4ml version` | the PD4ML version the module runs |

**Other PD4ML builds.** The module takes PD4ML from the first of these that is set:

1. `pd4ml.start(classpath=[...])`, called before first use;
2. `PD4ML_CLASSPATH`: jars and class directories separated by `;` on Windows and `:` elsewhere,
   with `dir/*` for all jars of a directory;
3. `PD4ML_CLASSPATH_FILE`: a file with one entry per line;
4. the build `pd4ml install` downloaded.

The v42 layout engine is new in PD4ML 4.2. With an earlier build, such as the published 4.1.x,
a converter uses that build's own engine and warns once. `pd4ml.has_layout_engines()` tells
which kind of build is loaded.

## API

### The Java API

All public classes of `com.pd4ml` are module attributes under their own names:

| Python | Java |
|---|---|
| `pd4ml.PD4ML` | `com.pd4ml.PD4ML` (wrapped, see below; the bare class is `pd4ml.JPD4ML`) |
| `pd4ml.Constants`, `pd4ml.LayoutEngine`, `pd4ml.ErrorPolicy` | `Constants`, `Constants.LayoutEngine`, `Constants.ErrorPolicy` |
| `pd4ml.PageSize`, `pd4ml.PageMargins`, `pd4ml.Dimensions`, `pd4ml.Units` | `PageSize`, `PageMargins`, `Dimensions`, `Dimensions.Units` |
| `pd4ml.PdfSpec`, `pd4ml.PdfDocument` | `PdfSpec`, `PdfDocument` |
| `pd4ml.ProgressListener`, `pd4ml.ResourceProvider`, `pd4ml.CustomTag`, `pd4ml.StatusMessage` | the same |
| `pd4ml.InvokeException`, `pd4ml.InvalidPasswordException`, `pd4ml.ForcedTerminationException`, `pd4ml.ConflictingParametersException`, `pd4ml.PdfSpecViolationException`, `pd4ml.PdfSpecUnsupportedException` | the exceptions |

Methods keep their Java names and take Python values:

- `str`, `int`, `float`, `bool`;
- `bytes` for `byte[]`;
- `None` for `null`.

Java `null` results become `None`, and Java strings become `str`. Any other Java class is
available through `pd4ml.java_class("java.util.HashMap")`.

Java exceptions are Python exceptions; catch them by their class:

```python
try:
    pd.readHTML("missing.html")
    pd.writePDF("out.pdf")
except pd4ml.java_class("java.io.IOException") as e:
    print("conversion failed:", e)
```

### `pd4ml.PD4ML`

`PD4ML(*args, engine="v42", html_width=None)` creates a `com.pd4ml.PD4ML`, passing `*args` to
the Java constructor: nothing, a license key, or an HTML width. It then selects the layout
engine: `engine="v40"` for the previous engine, `engine=None` for the Java default.

Every Java method is available. These also accept Python I/O:

| Method | Python additions |
|---|---|
| `readHTML(source, base_url=None, encoding=None)` | `source`: a path, `pathlib.Path`, URL string, bytes, binary file object, or Java `InputStream`/`URL` |
| `read_html_string(html, base_url=None)` | the document as a string |
| `writePDF(target=None, spec=None)` | `target`: a path, file object or Java `OutputStream`; with no target, returns bytes. `spec`: a `PdfSpec`, a keyword (`"pdf/a-2b"`, see `PdfSpec.listKeywords()`) or `"PDF"`/`"PDFA"`/`"PDFUA"` |
| `writeDOCX(target=None)`, `writeRTF(target=None, rtf_wmf=False)` | as `writePDF` |
| `renderAsImages(format)` | the pages as a list of bytes; `render_png()` for PNG |
| `monitorProgressWith(listener)` | a Python callable `(message_id, progress, note, msec)` |
| `useHtmlRenderer(engine, html_width=None, params=None)` | the engine as `"v42"`/`"v40"`; params as a dict |

Static members of the Java class are on the wrapper class: `pd4ml.PD4ML.getVersion()`,
`pd4ml.PD4ML.disableBatik = True`. The underlying Java object is `pd.java`.

### Helpers

- `pd4ml.html_to_pdf(source, target=None, *, base_url, page_size, page_margins, spec, engine, html_width)`
- `pd4ml.open_pdf(source, password=None)`: a `PdfDocument` from a path, URL, bytes or file
- `pd4ml.write_pdf(document, target=None, password=None, permissions=None)`
- `pd4ml.merge_pdfs(first, second, target=None)`
- `pd4ml.pdf_spec(keyword)`: the `PdfSpec` for a keyword; unknown keywords raise `ValueError`
- `pd4ml.progress_listener(callback)`: a Java `ProgressListener` for a callable
- `pd4ml.version()`, `pd4ml.start(...)`, `pd4ml.is_started()`, `pd4ml.java_class(name)`
- `pd4ml.install(...)`, `pd4ml.installed_jar()`, `pd4ml.installed_version()`, `pd4ml.has_layout_engines()`

The JVM starts on the first use of PD4ML. To pass JVM options, call
`pd4ml.start(jvm_options=["-Xmx2g"])` first, or set `PD4ML_JVM_OPTIONS`. The JVM runs headless,
and it can be started only once per process.

### Limitations

- `ResourceProvider` and `CustomTag` are Java classes to extend. Write those in Java and register
  them by class name (`addCustomResourceProvider`) or instance. Python can implement only Java
  *interfaces*, such as `ProgressListener`.
- As of PD4ML 4.2.0, the v42 layout engine reports only `DOC_WRITE_BEGIN` to a
  `ProgressListener`; v40 reports every stage.

## Documentation

- **User guide**: `docs/guide.md` in the source distribution (`pip download --no-binary :all: pd4ml`).
  It covers prerequisites, installation, use cases with code, tips and troubleshooting.
- The PD4ML manual: <https://pd4ml.com/docs/>.
- Usage examples: <https://pd4ml.com/docs/usage-examples.html>.
- Licensing: without a license, PD4ML runs in evaluation mode and watermarks its output. See
  <https://pd4ml.com/docs/#3.-licensing>.

## Development

These steps need the source distribution or repository.

```
pip install -e ".[test]"
python tools/pd4ml_classpath.py <pd4ml maven project>    # writes ./pd4ml.classpath (optional)
python -m pytest
```

- **Tests** use `./pd4ml.classpath` (or `PD4ML_CLASSPATH`/`PD4ML_CLASSPATH_FILE`), and are
  skipped without one. `PD4ML_NETWORK_TESTS=1` adds tests that download the latest release, both
  explicitly and through the automatic first-use download.
- **Type stubs**: `src/pd4ml/__init__.pyi` declares the whole API for IDEs and type checkers.
  After an API change, regenerate it with `python tools/gen_stubs.py`.
- **Releases** to PyPI are described in `docs/releasing.md`.
- **Example**: `python examples/convert.py page.html page.pdf --pdfa pdf/a-2b`.
