Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/api-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ Use this group for password protection, permissions, signatures, and standards.
- Archival/print conformance:
[convert_to_pdfa][pdfrest.PdfRestClient.convert_to_pdfa],
[convert_to_pdfx][pdfrest.PdfRestClient.convert_to_pdfx]
- Electronic invoices:
[convert_invoice_xml_to_zugferd_pdf][pdfrest.PdfRestClient.convert_invoice_xml_to_zugferd_pdf]
generates a PDF/A-3 invoice from CII XML, while
[convert_pdf_to_zugferd_pdf][pdfrest.PdfRestClient.convert_pdf_to_zugferd_pdf]
embeds CII XML into an existing matching PDF. The latter preserves the PDF by
default; set `allow_regeneration=True` only when the service may replace a
mismatched PDF with a newly rendered invoice.

## PDF cleanup and rendering normalization

Expand All @@ -130,6 +137,8 @@ Use this group when the user starts with non-PDF content or needs downstream
formats.

- Into PDF:
[convert_invoice_xml_to_zugferd_pdf][pdfrest.PdfRestClient.convert_invoice_xml_to_zugferd_pdf],
[convert_pdf_to_zugferd_pdf][pdfrest.PdfRestClient.convert_pdf_to_zugferd_pdf],
[convert_office_to_pdf][pdfrest.PdfRestClient.convert_office_to_pdf],
[convert_postscript_to_pdf][pdfrest.PdfRestClient.convert_postscript_to_pdf],
[convert_email_to_pdf][pdfrest.PdfRestClient.convert_email_to_pdf],
Expand Down
5 changes: 5 additions & 0 deletions docs/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ to the API guide and API reference for exact method names and options.
- Apply an uploaded `.joboptions` Distiller profile when converting PostScript
or EPS with `convert_postscript_to_pdf`
- Convert to standards formats (`PDF/A`, `PDF/X`)
- Create validated ZUGFeRD or Factur-X PDF/A-3 invoices from uploaded CII XML
with ``convert_invoice_xml_to_zugferd_pdf`` and optional logo/render controls
- Embed CII XML into a matching PDF with ``convert_pdf_to_zugferd_pdf``;
preserve the supplied PDF by default or explicitly permit regeneration when
the PDF and invoice do not match

### Extraction and Analysis

Expand Down
5 changes: 5 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ supported interpreter matrix.
synchronously convert selected PDF pages and verify the PostScript output.
- `examples/convert_pdf_to_postscript/async_convert_pdf_to_postscript_example.py`
– asynchronously convert a PDF page and verify the PostScript output.
- `examples/convert_invoice_xml_to_zugferd_pdf/convert_invoice_xml_to_zugferd_pdf_example.py`
– create a formatted ZUGFeRD PDF/A-3 invoice from uploaded CII XML.
- `examples/convert_pdf_to_zugferd_pdf/convert_pdf_to_zugferd_pdf_example.py` –
combine an uploaded PDF with CII XML and explicitly permit replacement when
the visual PDF does not match the invoice.
- `examples/delete/delete_example.py` – demonstrate file deletion (sync + async
variants).
- `examples/extract_text/extract_pdf_text_example.py` – run `extract_pdf_text`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# /// script
# requires-python = ">=3.10"
# dependencies = ["pdfrest", "python-dotenv"]
# ///
"""Create a branded ZUGFeRD PDF/A-3 invoice from CII XML.

This sample demonstrates how to:

1. Upload the checked-in invoice XML resource.
2. Generate a ZUGFeRD PDF with typed appearance and locale options.
3. Download and verify the generated PDF.
4. Print the result metadata and input resource IDs.

Set ``PDFREST_API_KEY`` and optionally ``PDFREST_BASE_URL`` for a self-hosted
deployment, then run from the repository root with
``uv run examples/convert_invoice_xml_to_zugferd_pdf/convert_invoice_xml_to_zugferd_pdf_example.py``.
The required XML input is included in the repository.
"""

from __future__ import annotations

import os
from pathlib import Path

from dotenv import load_dotenv

from pdfrest import PdfRestClient
from pdfrest.types import PdfZugferdRenderOptions

RESOURCE_DIRECTORY = Path(__file__).resolve().parents[1] / "resources"
INVOICE_PATH = RESOURCE_DIRECTORY / "zugferd-minimum-invoice.xml"


def convert_invoice_xml_to_zugferd_pdf() -> None:
"""Upload invoice XML, create the PDF/A-3 invoice, and verify it."""
load_dotenv()
base_url = os.getenv("PDFREST_BASE_URL")
render_options = PdfZugferdRenderOptions(
font="courier",
bold_font="courierbold",
accent_color=(20, 64, 115),
locale="de-DE",
label_language="en",
currency_display="symbol",
date_format="dd.MM.yyyy",
decimal_precision=2,
footer_message="Thank you for your business.",
)

with PdfRestClient(base_url=base_url) as client:
invoice = client.files.create_from_paths(INVOICE_PATH)[0]
response = client.convert_invoice_xml_to_zugferd_pdf(
invoice,
render_options=render_options,
output="zugferd-invoice",
)
pdf_bytes = client.files.read_bytes(response.output_file)

if not pdf_bytes.startswith(b"%PDF-"):
msg = "The generated resource is not a PDF."
raise RuntimeError(msg)

output = response.output_file
print(f"Created: {output.name}")
print(f"MIME type: {output.type}")
print(f"Size: {output.size} bytes")
print(f"Input IDs: {', '.join(response.input_ids)}")
print(f"Download URL: {output.url}")


if __name__ == "__main__": # pragma: no cover - manual example
convert_invoice_xml_to_zugferd_pdf()
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# /// script
# requires-python = ">=3.10"
# dependencies = ["pdfrest", "python-dotenv"]
# ///
"""Create a ZUGFeRD PDF/A-3 invoice from a visual PDF and CII XML.

This sample demonstrates how to:

1. Upload the checked-in PDF and invoice XML resources.
2. Combine them while explicitly allowing a regenerated visual PDF when the
supplied PDF does not match the invoice data.
3. Download and verify the resulting ZUGFeRD PDF.
4. Print the result metadata and input resource IDs.

Set ``PDFREST_API_KEY`` and optionally ``PDFREST_BASE_URL`` for a self-hosted
deployment, then run from the repository root with
``uv run examples/convert_pdf_to_zugferd_pdf/convert_pdf_to_zugferd_pdf_example.py``.
The required inputs are included in the repository.
"""

from __future__ import annotations

import os
from pathlib import Path

from dotenv import load_dotenv

from pdfrest import PdfRestClient

RESOURCE_DIRECTORY = Path(__file__).resolve().parents[1] / "resources"
PDF_PATH = RESOURCE_DIRECTORY / "report.pdf"
INVOICE_PATH = RESOURCE_DIRECTORY / "zugferd-minimum-invoice.xml"


def convert_pdf_to_zugferd_pdf() -> None:
"""Upload PDF/XML inputs, create the PDF/A-3 invoice, and verify it."""
load_dotenv()
base_url = os.getenv("PDFREST_BASE_URL")

with PdfRestClient(base_url=base_url) as client:
pdf, invoice = client.files.create_from_paths([PDF_PATH, INVOICE_PATH])
response = client.convert_pdf_to_zugferd_pdf(
pdf,
invoice=invoice,
allow_regeneration=True,
output="zugferd-from-pdf",
)
pdf_bytes = client.files.read_bytes(response.output_file)

if not pdf_bytes.startswith(b"%PDF-"):
msg = "The generated resource is not a PDF."
raise RuntimeError(msg)

output = response.output_file
print(f"Created: {output.name}")
print(f"MIME type: {output.type}")
print(f"Size: {output.size} bytes")
print(f"Input IDs: {', '.join(response.input_ids)}")
print(f"Download URL: {output.url}")


if __name__ == "__main__": # pragma: no cover - manual example
convert_pdf_to_zugferd_pdf()
51 changes: 51 additions & 0 deletions examples/resources/zugferd-minimum-invoice.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Reduced from the official ZUGFeRD 2.4 MINIMUM_Rechnung.xml example.
This deterministic fixture exercises the smallest supported invoice profile.
-->
<rsm:CrossIndustryInvoice
xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
<rsm:ExchangedDocumentContext>
<ram:GuidelineSpecifiedDocumentContextParameter>
<ram:ID>urn:factur-x.eu:1p0:minimum</ram:ID>
</ram:GuidelineSpecifiedDocumentContextParameter>
</rsm:ExchangedDocumentContext>
<rsm:ExchangedDocument>
<ram:ID>471102</ram:ID>
<ram:TypeCode>380</ram:TypeCode>
<ram:IssueDateTime>
<udt:DateTimeString format="102">20200305</udt:DateTimeString>
</ram:IssueDateTime>
</rsm:ExchangedDocument>
<rsm:SupplyChainTradeTransaction>
<ram:ApplicableHeaderTradeAgreement>
<ram:SellerTradeParty>
<ram:Name>Lieferant GmbH</ram:Name>
<ram:PostalTradeAddress>
<ram:CountryID>DE</ram:CountryID>
</ram:PostalTradeAddress>
<ram:SpecifiedTaxRegistration>
<ram:ID schemeID="FC">201/113/40209</ram:ID>
</ram:SpecifiedTaxRegistration>
<ram:SpecifiedTaxRegistration>
<ram:ID schemeID="VA">DE123456789</ram:ID>
</ram:SpecifiedTaxRegistration>
</ram:SellerTradeParty>
<ram:BuyerTradeParty>
<ram:Name>Kunden AG Frankreich</ram:Name>
</ram:BuyerTradeParty>
</ram:ApplicableHeaderTradeAgreement>
<ram:ApplicableHeaderTradeDelivery/>
<ram:ApplicableHeaderTradeSettlement>
<ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
<ram:TaxBasisTotalAmount>198.00</ram:TaxBasisTotalAmount>
<ram:TaxTotalAmount currencyID="EUR">37.62</ram:TaxTotalAmount>
<ram:GrandTotalAmount>235.62</ram:GrandTotalAmount>
<ram:DuePayableAmount>235.62</ram:DuePayableAmount>
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
</ram:ApplicableHeaderTradeSettlement>
</rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "pdfrest"
version = "1.4.1"
version = "1.5.0"
description = "Python client library for interacting with the pdfRest API"
readme = { file = "README.md", content-type = "text/markdown" }
license = "Apache-2.0"
Expand Down
Loading
Loading