pd4ml 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ # the source distribution: the module plus its documentation, examples, tests and tools
2
+ include README.md
3
+ recursive-include docs *.md
4
+ recursive-include examples *.py
5
+ recursive-include tests *.py
6
+ recursive-include tools *.py
7
+ prune tests/out
pd4ml-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.4
2
+ Name: pd4ml
3
+ Version: 1.0.0
4
+ Summary: PD4ML for Python: HTML and CSS to PDF, PDF/A, PDF/UA, ZUGFeRD, DOCX, RTF and images
5
+ Author-email: PD4ML <support@pd4ml.com>
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: Homepage, https://pd4ml.com
8
+ Project-URL: Documentation, https://pd4ml.com/docs/
9
+ Project-URL: Usage examples, https://pd4ml.com/docs/usage-examples.html
10
+ Keywords: pdf,html,html-to-pdf,pdf/a,pdf/ua,zugferd,factur-x,docx,rtf,pd4ml
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Programming Language :: Java
23
+ Classifier: Topic :: Printing
24
+ Classifier: Topic :: Office/Business
25
+ Classifier: Topic :: Text Processing :: Markup :: HTML
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.9
28
+ Description-Content-Type: text/markdown
29
+ Requires-Dist: JPype1>=1.5
30
+ Provides-Extra: test
31
+ Requires-Dist: pytest>=7; extra == "test"
32
+
33
+ # pd4ml — PD4ML for Python
34
+
35
+ HTML and CSS to **PDF**, **PDF/A**, **PDF/UA**, **ZUGFeRD/Factur-X**, **DOCX**, **RTF** and page
36
+ **images**, with the full public [PD4ML](https://pd4ml.com) API (Java package `com.pd4ml`)
37
+ available to Python applications.
38
+
39
+ PD4ML runs in a Java virtual machine inside the Python process, through
40
+ [JPype](https://jpype.readthedocs.io). Every public Java class, method, constant and enum can be
41
+ used directly, and the module adds Python I/O (paths, bytes, file objects) on top. New converters
42
+ use PD4ML's **v42 layout engine** by default.
43
+
44
+ ```
45
+ pip install pd4ml
46
+ ```
47
+
48
+ ```python
49
+ import pd4ml
50
+
51
+ pd = pd4ml.PD4ML() # v42 layout engine
52
+ pd.setPageSize(pd4ml.PageSize.A4)
53
+ pd.setPageMargins(pd4ml.PageMargins(15, 15, 15, 15, pd4ml.Units.MM))
54
+ pd.readHTML("invoice.html") # a path, URL, bytes or file object
55
+ pd.writePDF("invoice.pdf", pd4ml.PdfSpec.PDFA_3B) # a path or file object; no target: bytes
56
+ ```
57
+
58
+ The first use downloads the most recent PD4ML build (see *Installation* below).
59
+
60
+ ## Requirements
61
+
62
+ - Python 3.9 or later.
63
+ - Java 8 or later: a JDK or JRE of the same bitness as Python; 17 or 21 is recommended. JPype
64
+ finds it through `JAVA_HOME` or the system's default Java. To use a specific JVM library
65
+ (`jvm.dll`, `libjvm.so`), set `PD4ML_JVM`.
66
+
67
+ ## Installation
68
+
69
+ ```
70
+ pip install pd4ml
71
+ ```
72
+
73
+ The package contains the Python module only. The PD4ML library itself comes from the PD4ML
74
+ Maven repository (<https://pd4ml.com/maven2>):
75
+
76
+ - **Automatically.** If no PD4ML build is installed or configured, the first use of PD4ML
77
+ downloads the most recent 4.x release. Bouncy Castle (for encryption and signing) comes along
78
+ from Maven Central. Every file is verified against its published SHA-1 checksum and stored in
79
+ a per-user cache directory: `%LOCALAPPDATA%\pd4ml-python` on Windows, `~/.cache/pd4ml-python`
80
+ elsewhere, or `PD4ML_HOME`. A notice on standard error reports the download.
81
+ - **Ahead of time**, which is better for servers and container images:
82
+
83
+ ```
84
+ pd4ml install # the most recent 4.x release (also: python -m pd4ml install)
85
+ ```
86
+
87
+ Run it again to update to a newer release. From Python, call `pd4ml.install()`.
88
+
89
+ `PD4ML_AUTO_INSTALL=0` turns the automatic download off; the module then raises
90
+ `pd4ml.ClasspathError` until a build is installed or configured.
91
+
92
+ | Command | |
93
+ |---|---|
94
+ | `pd4ml install` | download the most recent 4.x release, or update to it |
95
+ | `pd4ml install --version 4.0.23fx2` | an exact version |
96
+ | `pd4ml install --snapshots` | the most recent build, including development snapshots |
97
+ | `pd4ml install --jakarta` | the `-jakarta` variant |
98
+ | `pd4ml install --list` | the available versions |
99
+ | `pd4ml install --home DIR` | install into `DIR` (then set `PD4ML_HOME=DIR` for the application) |
100
+ | `pd4ml version` | the PD4ML version the module runs |
101
+
102
+ **Other PD4ML builds.** The module takes PD4ML from the first of these that is set:
103
+
104
+ 1. `pd4ml.start(classpath=[...])`, called before first use;
105
+ 2. `PD4ML_CLASSPATH`: jars and class directories separated by `;` on Windows and `:` elsewhere,
106
+ with `dir/*` for all jars of a directory;
107
+ 3. `PD4ML_CLASSPATH_FILE`: a file with one entry per line;
108
+ 4. the build `pd4ml install` downloaded.
109
+
110
+ The v42 layout engine is new in PD4ML 4.2. With an earlier build, such as the published 4.1.x,
111
+ a converter uses that build's own engine and warns once. `pd4ml.has_layout_engines()` tells
112
+ which kind of build is loaded.
113
+
114
+ ## API
115
+
116
+ ### The Java API
117
+
118
+ All public classes of `com.pd4ml` are module attributes under their own names:
119
+
120
+ | Python | Java |
121
+ |---|---|
122
+ | `pd4ml.PD4ML` | `com.pd4ml.PD4ML` (wrapped, see below; the bare class is `pd4ml.JPD4ML`) |
123
+ | `pd4ml.Constants`, `pd4ml.LayoutEngine`, `pd4ml.ErrorPolicy` | `Constants`, `Constants.LayoutEngine`, `Constants.ErrorPolicy` |
124
+ | `pd4ml.PageSize`, `pd4ml.PageMargins`, `pd4ml.Dimensions`, `pd4ml.Units` | `PageSize`, `PageMargins`, `Dimensions`, `Dimensions.Units` |
125
+ | `pd4ml.PdfSpec`, `pd4ml.PdfDocument` | `PdfSpec`, `PdfDocument` |
126
+ | `pd4ml.ProgressListener`, `pd4ml.ResourceProvider`, `pd4ml.CustomTag`, `pd4ml.StatusMessage` | the same |
127
+ | `pd4ml.InvokeException`, `pd4ml.InvalidPasswordException`, `pd4ml.ForcedTerminationException`, `pd4ml.ConflictingParametersException`, `pd4ml.PdfSpecViolationException`, `pd4ml.PdfSpecUnsupportedException` | the exceptions |
128
+
129
+ Methods keep their Java names and take Python values:
130
+
131
+ - `str`, `int`, `float`, `bool`;
132
+ - `bytes` for `byte[]`;
133
+ - `None` for `null`.
134
+
135
+ Java `null` results become `None`, and Java strings become `str`. Any other Java class is
136
+ available through `pd4ml.java_class("java.util.HashMap")`.
137
+
138
+ Java exceptions are Python exceptions; catch them by their class:
139
+
140
+ ```python
141
+ try:
142
+ pd.readHTML("missing.html")
143
+ pd.writePDF("out.pdf")
144
+ except pd4ml.java_class("java.io.IOException") as e:
145
+ print("conversion failed:", e)
146
+ ```
147
+
148
+ ### `pd4ml.PD4ML`
149
+
150
+ `PD4ML(*args, engine="v42", html_width=None)` creates a `com.pd4ml.PD4ML`, passing `*args` to
151
+ the Java constructor: nothing, a license key, or an HTML width. It then selects the layout
152
+ engine: `engine="v40"` for the previous engine, `engine=None` for the Java default.
153
+
154
+ Every Java method is available. These also accept Python I/O:
155
+
156
+ | Method | Python additions |
157
+ |---|---|
158
+ | `readHTML(source, base_url=None, encoding=None)` | `source`: a path, `pathlib.Path`, URL string, bytes, binary file object, or Java `InputStream`/`URL` |
159
+ | `read_html_string(html, base_url=None)` | the document as a string |
160
+ | `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"` |
161
+ | `writeDOCX(target=None)`, `writeRTF(target=None, rtf_wmf=False)` | as `writePDF` |
162
+ | `renderAsImages(format)` | the pages as a list of bytes; `render_png()` for PNG |
163
+ | `monitorProgressWith(listener)` | a Python callable `(message_id, progress, note, msec)` |
164
+ | `useHtmlRenderer(engine, html_width=None, params=None)` | the engine as `"v42"`/`"v40"`; params as a dict |
165
+
166
+ Static members of the Java class are on the wrapper class: `pd4ml.PD4ML.getVersion()`,
167
+ `pd4ml.PD4ML.disableBatik = True`. The underlying Java object is `pd.java`.
168
+
169
+ ### Helpers
170
+
171
+ - `pd4ml.html_to_pdf(source, target=None, *, base_url, page_size, page_margins, spec, engine, html_width)`
172
+ - `pd4ml.open_pdf(source, password=None)`: a `PdfDocument` from a path, URL, bytes or file
173
+ - `pd4ml.write_pdf(document, target=None, password=None, permissions=None)`
174
+ - `pd4ml.merge_pdfs(first, second, target=None)`
175
+ - `pd4ml.pdf_spec(keyword)`: the `PdfSpec` for a keyword; unknown keywords raise `ValueError`
176
+ - `pd4ml.progress_listener(callback)`: a Java `ProgressListener` for a callable
177
+ - `pd4ml.version()`, `pd4ml.start(...)`, `pd4ml.is_started()`, `pd4ml.java_class(name)`
178
+ - `pd4ml.install(...)`, `pd4ml.installed_jar()`, `pd4ml.installed_version()`, `pd4ml.has_layout_engines()`
179
+
180
+ The JVM starts on the first use of PD4ML. To pass JVM options, call
181
+ `pd4ml.start(jvm_options=["-Xmx2g"])` first, or set `PD4ML_JVM_OPTIONS`. The JVM runs headless,
182
+ and it can be started only once per process.
183
+
184
+ ### Limitations
185
+
186
+ - `ResourceProvider` and `CustomTag` are Java classes to extend. Write those in Java and register
187
+ them by class name (`addCustomResourceProvider`) or instance. Python can implement only Java
188
+ *interfaces*, such as `ProgressListener`.
189
+ - As of PD4ML 4.2.0, the v42 layout engine reports only `DOC_WRITE_BEGIN` to a
190
+ `ProgressListener`; v40 reports every stage.
191
+
192
+ ## Documentation
193
+
194
+ - **User guide**: `docs/guide.md` in the source distribution (`pip download --no-binary :all: pd4ml`).
195
+ It covers prerequisites, installation, use cases with code, tips and troubleshooting.
196
+ - The PD4ML manual: <https://pd4ml.com/docs/>.
197
+ - Usage examples: <https://pd4ml.com/docs/usage-examples.html>.
198
+ - Licensing: without a license, PD4ML runs in evaluation mode and watermarks its output. See
199
+ <https://pd4ml.com/docs/#3.-licensing>.
200
+
201
+ ## Development
202
+
203
+ These steps need the source distribution or repository.
204
+
205
+ ```
206
+ pip install -e ".[test]"
207
+ python tools/pd4ml_classpath.py <pd4ml maven project> # writes ./pd4ml.classpath (optional)
208
+ python -m pytest
209
+ ```
210
+
211
+ - **Tests** use `./pd4ml.classpath` (or `PD4ML_CLASSPATH`/`PD4ML_CLASSPATH_FILE`), and are
212
+ skipped without one. `PD4ML_NETWORK_TESTS=1` adds tests that download the latest release, both
213
+ explicitly and through the automatic first-use download.
214
+ - **Type stubs**: `src/pd4ml/__init__.pyi` declares the whole API for IDEs and type checkers.
215
+ After an API change, regenerate it with `python tools/gen_stubs.py`.
216
+ - **Releases** to PyPI are described in `docs/releasing.md`.
217
+ - **Example**: `python examples/convert.py page.html page.pdf --pdfa pdf/a-2b`.
pd4ml-1.0.0/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # pd4ml — PD4ML for Python
2
+
3
+ HTML and CSS to **PDF**, **PDF/A**, **PDF/UA**, **ZUGFeRD/Factur-X**, **DOCX**, **RTF** and page
4
+ **images**, with the full public [PD4ML](https://pd4ml.com) API (Java package `com.pd4ml`)
5
+ available to Python applications.
6
+
7
+ PD4ML runs in a Java virtual machine inside the Python process, through
8
+ [JPype](https://jpype.readthedocs.io). Every public Java class, method, constant and enum can be
9
+ used directly, and the module adds Python I/O (paths, bytes, file objects) on top. New converters
10
+ use PD4ML's **v42 layout engine** by default.
11
+
12
+ ```
13
+ pip install pd4ml
14
+ ```
15
+
16
+ ```python
17
+ import pd4ml
18
+
19
+ pd = pd4ml.PD4ML() # v42 layout engine
20
+ pd.setPageSize(pd4ml.PageSize.A4)
21
+ pd.setPageMargins(pd4ml.PageMargins(15, 15, 15, 15, pd4ml.Units.MM))
22
+ pd.readHTML("invoice.html") # a path, URL, bytes or file object
23
+ pd.writePDF("invoice.pdf", pd4ml.PdfSpec.PDFA_3B) # a path or file object; no target: bytes
24
+ ```
25
+
26
+ The first use downloads the most recent PD4ML build (see *Installation* below).
27
+
28
+ ## Requirements
29
+
30
+ - Python 3.9 or later.
31
+ - Java 8 or later: a JDK or JRE of the same bitness as Python; 17 or 21 is recommended. JPype
32
+ finds it through `JAVA_HOME` or the system's default Java. To use a specific JVM library
33
+ (`jvm.dll`, `libjvm.so`), set `PD4ML_JVM`.
34
+
35
+ ## Installation
36
+
37
+ ```
38
+ pip install pd4ml
39
+ ```
40
+
41
+ The package contains the Python module only. The PD4ML library itself comes from the PD4ML
42
+ Maven repository (<https://pd4ml.com/maven2>):
43
+
44
+ - **Automatically.** If no PD4ML build is installed or configured, the first use of PD4ML
45
+ downloads the most recent 4.x release. Bouncy Castle (for encryption and signing) comes along
46
+ from Maven Central. Every file is verified against its published SHA-1 checksum and stored in
47
+ a per-user cache directory: `%LOCALAPPDATA%\pd4ml-python` on Windows, `~/.cache/pd4ml-python`
48
+ elsewhere, or `PD4ML_HOME`. A notice on standard error reports the download.
49
+ - **Ahead of time**, which is better for servers and container images:
50
+
51
+ ```
52
+ pd4ml install # the most recent 4.x release (also: python -m pd4ml install)
53
+ ```
54
+
55
+ Run it again to update to a newer release. From Python, call `pd4ml.install()`.
56
+
57
+ `PD4ML_AUTO_INSTALL=0` turns the automatic download off; the module then raises
58
+ `pd4ml.ClasspathError` until a build is installed or configured.
59
+
60
+ | Command | |
61
+ |---|---|
62
+ | `pd4ml install` | download the most recent 4.x release, or update to it |
63
+ | `pd4ml install --version 4.0.23fx2` | an exact version |
64
+ | `pd4ml install --snapshots` | the most recent build, including development snapshots |
65
+ | `pd4ml install --jakarta` | the `-jakarta` variant |
66
+ | `pd4ml install --list` | the available versions |
67
+ | `pd4ml install --home DIR` | install into `DIR` (then set `PD4ML_HOME=DIR` for the application) |
68
+ | `pd4ml version` | the PD4ML version the module runs |
69
+
70
+ **Other PD4ML builds.** The module takes PD4ML from the first of these that is set:
71
+
72
+ 1. `pd4ml.start(classpath=[...])`, called before first use;
73
+ 2. `PD4ML_CLASSPATH`: jars and class directories separated by `;` on Windows and `:` elsewhere,
74
+ with `dir/*` for all jars of a directory;
75
+ 3. `PD4ML_CLASSPATH_FILE`: a file with one entry per line;
76
+ 4. the build `pd4ml install` downloaded.
77
+
78
+ The v42 layout engine is new in PD4ML 4.2. With an earlier build, such as the published 4.1.x,
79
+ a converter uses that build's own engine and warns once. `pd4ml.has_layout_engines()` tells
80
+ which kind of build is loaded.
81
+
82
+ ## API
83
+
84
+ ### The Java API
85
+
86
+ All public classes of `com.pd4ml` are module attributes under their own names:
87
+
88
+ | Python | Java |
89
+ |---|---|
90
+ | `pd4ml.PD4ML` | `com.pd4ml.PD4ML` (wrapped, see below; the bare class is `pd4ml.JPD4ML`) |
91
+ | `pd4ml.Constants`, `pd4ml.LayoutEngine`, `pd4ml.ErrorPolicy` | `Constants`, `Constants.LayoutEngine`, `Constants.ErrorPolicy` |
92
+ | `pd4ml.PageSize`, `pd4ml.PageMargins`, `pd4ml.Dimensions`, `pd4ml.Units` | `PageSize`, `PageMargins`, `Dimensions`, `Dimensions.Units` |
93
+ | `pd4ml.PdfSpec`, `pd4ml.PdfDocument` | `PdfSpec`, `PdfDocument` |
94
+ | `pd4ml.ProgressListener`, `pd4ml.ResourceProvider`, `pd4ml.CustomTag`, `pd4ml.StatusMessage` | the same |
95
+ | `pd4ml.InvokeException`, `pd4ml.InvalidPasswordException`, `pd4ml.ForcedTerminationException`, `pd4ml.ConflictingParametersException`, `pd4ml.PdfSpecViolationException`, `pd4ml.PdfSpecUnsupportedException` | the exceptions |
96
+
97
+ Methods keep their Java names and take Python values:
98
+
99
+ - `str`, `int`, `float`, `bool`;
100
+ - `bytes` for `byte[]`;
101
+ - `None` for `null`.
102
+
103
+ Java `null` results become `None`, and Java strings become `str`. Any other Java class is
104
+ available through `pd4ml.java_class("java.util.HashMap")`.
105
+
106
+ Java exceptions are Python exceptions; catch them by their class:
107
+
108
+ ```python
109
+ try:
110
+ pd.readHTML("missing.html")
111
+ pd.writePDF("out.pdf")
112
+ except pd4ml.java_class("java.io.IOException") as e:
113
+ print("conversion failed:", e)
114
+ ```
115
+
116
+ ### `pd4ml.PD4ML`
117
+
118
+ `PD4ML(*args, engine="v42", html_width=None)` creates a `com.pd4ml.PD4ML`, passing `*args` to
119
+ the Java constructor: nothing, a license key, or an HTML width. It then selects the layout
120
+ engine: `engine="v40"` for the previous engine, `engine=None` for the Java default.
121
+
122
+ Every Java method is available. These also accept Python I/O:
123
+
124
+ | Method | Python additions |
125
+ |---|---|
126
+ | `readHTML(source, base_url=None, encoding=None)` | `source`: a path, `pathlib.Path`, URL string, bytes, binary file object, or Java `InputStream`/`URL` |
127
+ | `read_html_string(html, base_url=None)` | the document as a string |
128
+ | `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"` |
129
+ | `writeDOCX(target=None)`, `writeRTF(target=None, rtf_wmf=False)` | as `writePDF` |
130
+ | `renderAsImages(format)` | the pages as a list of bytes; `render_png()` for PNG |
131
+ | `monitorProgressWith(listener)` | a Python callable `(message_id, progress, note, msec)` |
132
+ | `useHtmlRenderer(engine, html_width=None, params=None)` | the engine as `"v42"`/`"v40"`; params as a dict |
133
+
134
+ Static members of the Java class are on the wrapper class: `pd4ml.PD4ML.getVersion()`,
135
+ `pd4ml.PD4ML.disableBatik = True`. The underlying Java object is `pd.java`.
136
+
137
+ ### Helpers
138
+
139
+ - `pd4ml.html_to_pdf(source, target=None, *, base_url, page_size, page_margins, spec, engine, html_width)`
140
+ - `pd4ml.open_pdf(source, password=None)`: a `PdfDocument` from a path, URL, bytes or file
141
+ - `pd4ml.write_pdf(document, target=None, password=None, permissions=None)`
142
+ - `pd4ml.merge_pdfs(first, second, target=None)`
143
+ - `pd4ml.pdf_spec(keyword)`: the `PdfSpec` for a keyword; unknown keywords raise `ValueError`
144
+ - `pd4ml.progress_listener(callback)`: a Java `ProgressListener` for a callable
145
+ - `pd4ml.version()`, `pd4ml.start(...)`, `pd4ml.is_started()`, `pd4ml.java_class(name)`
146
+ - `pd4ml.install(...)`, `pd4ml.installed_jar()`, `pd4ml.installed_version()`, `pd4ml.has_layout_engines()`
147
+
148
+ The JVM starts on the first use of PD4ML. To pass JVM options, call
149
+ `pd4ml.start(jvm_options=["-Xmx2g"])` first, or set `PD4ML_JVM_OPTIONS`. The JVM runs headless,
150
+ and it can be started only once per process.
151
+
152
+ ### Limitations
153
+
154
+ - `ResourceProvider` and `CustomTag` are Java classes to extend. Write those in Java and register
155
+ them by class name (`addCustomResourceProvider`) or instance. Python can implement only Java
156
+ *interfaces*, such as `ProgressListener`.
157
+ - As of PD4ML 4.2.0, the v42 layout engine reports only `DOC_WRITE_BEGIN` to a
158
+ `ProgressListener`; v40 reports every stage.
159
+
160
+ ## Documentation
161
+
162
+ - **User guide**: `docs/guide.md` in the source distribution (`pip download --no-binary :all: pd4ml`).
163
+ It covers prerequisites, installation, use cases with code, tips and troubleshooting.
164
+ - The PD4ML manual: <https://pd4ml.com/docs/>.
165
+ - Usage examples: <https://pd4ml.com/docs/usage-examples.html>.
166
+ - Licensing: without a license, PD4ML runs in evaluation mode and watermarks its output. See
167
+ <https://pd4ml.com/docs/#3.-licensing>.
168
+
169
+ ## Development
170
+
171
+ These steps need the source distribution or repository.
172
+
173
+ ```
174
+ pip install -e ".[test]"
175
+ python tools/pd4ml_classpath.py <pd4ml maven project> # writes ./pd4ml.classpath (optional)
176
+ python -m pytest
177
+ ```
178
+
179
+ - **Tests** use `./pd4ml.classpath` (or `PD4ML_CLASSPATH`/`PD4ML_CLASSPATH_FILE`), and are
180
+ skipped without one. `PD4ML_NETWORK_TESTS=1` adds tests that download the latest release, both
181
+ explicitly and through the automatic first-use download.
182
+ - **Type stubs**: `src/pd4ml/__init__.pyi` declares the whole API for IDEs and type checkers.
183
+ After an API change, regenerate it with `python tools/gen_stubs.py`.
184
+ - **Releases** to PyPI are described in `docs/releasing.md`.
185
+ - **Example**: `python examples/convert.py page.html page.pdf --pdfa pdf/a-2b`.