1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//! **ZUGFeRD and Factur-X** — invoices that are a PDF *and* machine-readable data.
//!
//! A ZUGFeRD invoice is a **PDF that is also data**. The data half is CII and
//! belongs to [`crate::cii`]; the PDF half is PDF/A-3 with an embedded file and
//! specific XMP metadata, and that is the only part this module owns. Every
//! business rule is delegated — this module adds no rule coverage and needs no
//! rule tests.
//!
//! # Reading, which works today
//!
//! [`extract`] is the common direction — receiving is more common than sending
//! — and the one with no PDF/A risk: reading means walking the catalogue to
//! `/Names/EmbeddedFiles` and inflating one stream. No rendering, no fonts, no
//! text layout.
//!
//! ```no_run
//! use en16931_formats::zugferd::{self, IsInvoice};
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let pdf = std::fs::read("invoice.pdf")?;
//! let got = zugferd::extract(&pdf)?;
//!
//! // What the document *claims*, from its BT-24. Not every profile is an
//! // EN 16931 invoice: MINIMUM and BASIC WL carry no lines and cannot
//! // satisfy `BR-16`, so validating them would report a failure that is not one.
//! println!("{:?} in {}", got.profile, got.filename);
//! if got.profile.is_en16931_invoice() == IsInvoice::Yes {
//! let read = en16931_formats::cii::from_str(&got.xml)?;
//! println!("{}", en16931::validate(&read.invoice));
//! }
//! // `got.xml` is the payload **verbatim**: whoever diagnoses a rejected
//! // invoice needs the bytes the counterparty sent, not a reconstruction.
//! # Ok(()) }
//! ```
//!
//! `examples/zugferd_extract.rs` is a runnable version that builds its own PDF
//! when given no argument.
//!
//! # Writing: not implemented, and the reason is not effort
//!
//! There is **no `embed(pdf_bytes, &invoice) -> Vec<u8>`**, and asking for one
//! is entirely reasonable — so here is what stands in the way, because "not
//! yet" without a reason is the least useful thing a crate can say.
//!
//! A ZUGFeRD file is not "a PDF with an attachment". It is a **PDF/A-3**
//! document, and the conformance is normative: ZUGFeRD 2.x and Factur-X both
//! require it, and a file that is no longer valid PDF/A is no longer a valid
//! ZUGFeRD invoice. Embedding correctly means all of:
//!
//! * rewriting the cross-reference table and trailer without disturbing the
//! original's object numbering;
//! * an `/AF` associated-files array on the catalogue **and** an
//! `/AFRelationship` on the file specification — PDF/A-3's own requirement,
//! the part most implementations omit, and **the one this crate will not
//! guess**: see below;
//! * an XMP packet carrying the ZUGFeRD extension schema, whose
//! `DocumentFileName`, `Version` and `ConformanceLevel` agree with the
//! payload's BT-24 — a divergence this module already *detects* on the way in
//! ([`Divergence`]) and would have to be incapable of *creating* on the way
//! out;
//! * preserving whatever conformance the input had, `/OutputIntent`, embedded
//! fonts and metadata included.
//!
//! Most of that is checkable only against **veraPDF**, not against a Rust test.
//! A writer producing files that open happily in a viewer and fail a
//! recipient's conformance check would be worse than no writer: the failure
//! arrives at the counterparty, months later, on documents already sent.
//!
//! ## And one value this crate refuses to invent
//!
//! `/AFRelationship` decides whether the XML **is** the invoice or merely
//! accompanies one, which makes it legally load-bearing rather than
//! descriptive. Published guidance does not agree on it:
//!
//! | Profile | Guidance |
//! |---|---|
//! | MINIMUM, BASIC WL | `Data` — no lines; the pages are the invoice |
//! | BASIC, EN 16931, EXTENDED | German sources say `Alternative`; PDFlib documents `Source` for Factur-X to non-German recipients |
//!
//! Writing the wrong one yields a file that opens, passes PDF/A validation,
//! and extracts correctly with [`extract`] — and may not be a valid invoice
//! where it lands. That is the failure mode this whole crate is built against,
//! and the ⚠ below is not decoration: the specification is not among the
//! fetched artefacts, so there is nothing here to resolve the disagreement.
//!
//! What this module does instead is **read** it, report it on
//! [`Extracted::relationship`], and raise [`Divergence::Relationship`] for the
//! one case every source agrees is wrong — `Data` on a profile that carries
//! lines. Where the sources disagree it takes no position.
//!
//! **What composes today**, and it is most of the way there: render the PDF/A-3
//! with a toolchain that already guarantees conformance, take the payload from
//! [`crate::cii::to_string_for`] — which will not hand you XML until it has
//! validated the model against the profile you name — and have that toolchain
//! embed it. The half this crate can guarantee is the half it does.
//!
//! `render`, the visible-invoice feature, is downstream of the same problem and
//! likewise unimplemented.
//!
//! # ⚠ Provenance
//!
//! [`en16931`]'s design was written against artefacts fetched into `spec/` and
//! verified there. **The ZUGFeRD and Factur-X specifications are not among
//! them.** Claims marked ⚠ — profile names, attachment filenames, the XMP
//! structure — are stated from knowledge rather than a fetched specification,
//! and are the first thing to check before relying on them.
//!
//! That warning is not boilerplate. This project already had one incident where
//! two plausible specification identifiers were invented and an argument built
//! on them; the fix was to check every transcribed value against its source.
pub use ;
pub use ;
/// Anything that stopped a hybrid PDF being read.