pub struct Document {Show 15 fields
pub page_format: PageFormat,
pub orientation: Orientation,
pub margin: Margin,
pub header: Option<Header>,
pub footer: Option<Footer>,
pub header_visible_from: usize,
pub footer_visible_from: usize,
pub watermark: Option<Watermark>,
pub metadata: DocumentMetadata,
pub theme: Option<Theme>,
pub pdf_a3b: bool,
pub zugferd_xml: Option<Vec<u8>>,
pub pdf_ua: bool,
pub lang: Option<String>,
pub children: Vec<Element>,
}Fields§
§page_format: PageFormat§orientation: Orientation§margin: Margin§header: Option<Header>Not representable in the JSON schema (issue #17 V1 scope): the
content is a Rust closure, re-evaluated per page. Always None on
a JSON-loaded Document; Document::to_json refuses to serialize
a Document that has one set rather than silently dropping it.
header_visible_from: usize§watermark: Option<Watermark>§metadata: DocumentMetadata§theme: Option<Theme>None (the default) means every element renders exactly as it
always did — Document::theme(..) opts in per-document, resolved
once per element as it’s .add()-ed (see theme::apply_theme).
pdf_a3b: boolSet by .pdf_a3b() (issue #25): asks the facade to write a
PDF/A-3b-conformant document (XMP metadata, /OutputIntent with an
embedded sRGB ICC profile, transparency-group colour space) instead
of the default output. Always present on Document regardless of
the facade’s pdf-a Cargo feature (this flag itself costs
nothing) — render() returns RenderError::PdfAFeatureDisabled if
this is true but that feature isn’t compiled in, rather than
silently rendering a non-conformant PDF.
zugferd_xml: Option<Vec<u8>>Set by .zugferd_xml(bytes) (issue #26): the raw bytes of a
caller-supplied ZUGFeRD/Factur-X invoice XML (EN 16931/Comfort
profile) to embed as an associated file. This crate embeds only —
it never generates or validates that XML itself (see ADR-018 in
the local plan/00-decisions.md). Not representable in the JSON
schema (same reasoning as Header/Footer: to_json() refuses
outright rather than silently dropping it).
pdf_ua: boolSet by .pdf_ua() (issue #27): asks the facade to write a Tagged
PDF/PDF-UA-conformant document — a structure tree (/StructTreeRoot,
one /StructElem per heading/paragraph/table/list/figure),
marked content (BDC/EMC with MCIDs) in every content stream,
and watermark/header/footer content marked as artifacts rather
than structure. Always present regardless of the facade’s
tagged-pdf Cargo feature (this flag costs nothing) —
render() returns RenderError::TaggedPdfFeatureDisabled if this
is true but that feature isn’t compiled in.
lang: Option<String>Document natural language (e.g. "en-US", "de-DE") for the
Catalog’s /Lang entry — required for PDF/UA, meaningful even
without it (screen readers use /Lang to pick a voice/language).
children: Vec<Element>Implementations§
Source§impl Document
impl Document
Sourcepub fn new(page_format: PageFormat) -> Document
pub fn new(page_format: PageFormat) -> Document
Examples found in repository?
42fn main() {
43 let mut doc = Document::new(PageFormat::A4)
44 .margin(Margin::symmetric(56.0, 56.0))
45 .theme(brand_theme())
46 .header(Header::new(20.0, |_ctx| {
47 Text::new("Jahresbericht 2026 \u{2014} Muster GmbH").size(9.0).into()
48 }))
49 .header_visible_from(2)
50 .footer(Footer::new(20.0, |ctx| {
51 Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages))
52 .size(9.0)
53 .align(Align::Center)
54 .into()
55 }))
56 .watermark(Watermark::new("ENTWURF"));
57
58 // --- cover page -----------------------------------------------------
59 doc.add(Spacer::new(220.0));
60 doc.add(Text::new("Jahresbericht 2026").heading1().align(Align::Center));
61 doc.add(Spacer::new(8.0));
62 doc.add(Text::new("Muster GmbH").align(Align::Center));
63 doc.add(Text::new("vorgelegt am 20. August 2026").muted().align(Align::Center));
64 doc.add(Element::PageBreak);
65
66 // --- content ---------------------------------------------------------
67 doc.add(Text::new("1. Zusammenfassung").heading1());
68 doc.add(Text::new(
69 "Das Geschäftsjahr 2026 war geprägt von stabilem Wachstum in allen \
70 Kernbereichen. Die folgenden Abschnitte fassen die wichtigsten \
71 Kennzahlen und Entwicklungen zusammen.",
72 ));
73 doc.add(Spacer::new(12.0));
74
75 doc.add(Text::new("2. Wichtigste Ereignisse").heading2());
76 doc.add(
77 List::new()
78 .bullet(Text::new("Markteinführung des neuen Produkts im zweiten Quartal"))
79 .bullet(Text::new("Erweiterung des Teams um 12 neue Mitarbeitende"))
80 .bullet(Text::new("Eröffnung eines zweiten Standorts")),
81 );
82 doc.add(Spacer::new(12.0));
83
84 doc.add(Text::new("3. Zeitplan").heading2());
85 doc.add(
86 List::new()
87 .numbered(Text::new("Kickoff und Planung (Januar \u{2013} Februar)"))
88 .numbered(Text::new("Umsetzung Phase 1 (M\u{e4}rz \u{2013} Juni)"))
89 .numbered(Text::new("Umsetzung Phase 2 (Juli \u{2013} Oktober)"))
90 .numbered(Text::new("Abschluss und Auswertung (November \u{2013} Dezember)")),
91 );
92 doc.add(Spacer::new(12.0));
93
94 doc.add(Text::new("3.1 Details").heading3());
95 doc.add(Text::new(
96 "Weitere Details zu den einzelnen Phasen finden sich im Anhang. \
97 Diese Überschrift bleibt dank keep_with_next garantiert mit \
98 diesem Absatz auf derselben Seite zusammen.",
99 ));
100
101 common::write_pdf(&doc, "report.pdf");
102}More examples
35fn main() {
36 let mut items = vec![
37 LineItem {
38 description: "Beratungsleistung Projekt Alpha".to_string(),
39 qty: 8,
40 unit_price_cents: 12_000,
41 },
42 LineItem {
43 description: "Lizenz Software-Paket (jährlich)".to_string(),
44 qty: 1,
45 unit_price_cents: 49_900,
46 },
47 LineItem {
48 description: "Individuelle Anpassung / Customizing".to_string(),
49 qty: 3,
50 unit_price_cents: 15_000,
51 },
52 ];
53 // A few filler positions so the table is guaranteed to span more than
54 // one page, demonstrating the header-repeat-on-split behavior.
55 for i in 1..=25 {
56 items.push(LineItem {
57 description: format!("Zusatzposition {i:02}"),
58 qty: 1,
59 unit_price_cents: 990,
60 });
61 }
62
63 let net_total: i64 = items.iter().map(|i| i.qty as i64 * i.unit_price_cents).sum();
64 let vat_rate = 19;
65 let vat_total = net_total * vat_rate / 100;
66 let gross_total = net_total + vat_total;
67
68 let top_margin = 15.0 * MM;
69 let mut doc = Document::new(PageFormat::A4)
70 .margin(Margin::symmetric(20.0 * MM, top_margin))
71 .footer(Footer::new(30.0, |ctx| {
72 Column::new()
73 .gap(2.0)
74 .child(Line::new())
75 .child(
76 Row::new()
77 .gap(20.0)
78 .child(Text::new("Musterbank · IBAN DE12 3456 7890 1234 5678 90 · BIC MUSTDEFF").size(8.0))
79 .child(Text::new("USt-IdNr. DE123456789").size(8.0).flex(1.0)),
80 )
81 .child(Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages)).size(8.0))
82 .into()
83 }));
84
85 // --- DIN 5008 Form A window-envelope address block ----------------
86 // Window starts ~45mm from the top, ~20mm from the left
87 // (`plan/02-elementcatalog-and-features.md`); ~85x40mm matches a
88 // typical C6/5-long window envelope opening. Position/size are a
89 // documented convention for this recipe, not parsed from any norm
90 // document — a caller with different envelope stock adjusts these.
91 doc.add(Spacer::new(45.0 * MM - top_margin));
92 doc.add(
93 Column::new()
94 .gap(2.0)
95 .width(85.0 * MM)
96 .height(40.0 * MM)
97 .child(Text::new("Muster GmbH · Musterstraße 1 · 12345 Musterstadt").size(7.0))
98 .child(Spacer::new(8.0))
99 .child(Text::new("Empfänger GmbH"))
100 .child(Text::new("Frau Erika Mustermann"))
101 .child(Text::new("Beispielweg 42"))
102 .child(Text::new("54321 Beispielstadt")),
103 );
104 doc.add(Spacer::new(10.0 * MM));
105
106 doc.add(Text::new("Rechnung").heading1());
107 doc.add(Text::new(
108 "Rechnungsnummer: RE-2026-0142 Rechnungsdatum: 20.08.2026 Leistungsdatum: 20.08.2026",
109 ));
110 doc.add(Spacer::new(10.0));
111
112 doc.add(
113 Table::new()
114 .columns([
115 TableColumn::flex(1.0),
116 TableColumn::fixed(50.0).align(Align::End),
117 TableColumn::fixed(65.0).align(Align::End),
118 TableColumn::fixed(70.0).align(Align::End),
119 ])
120 .header(["Beschreibung", "Menge", "Einzelpreis", "Gesamt"])
121 .striped(Color::rgb(0xF5, 0xF5, 0xF5))
122 .from_rows(&items),
123 );
124
125 doc.add(Spacer::new(14.0));
126
127 // --- summary block (Netto/USt/Brutto), right-aligned --------------
128 // Recipe (`plan/02-elementcatalog-and-features.md`): an outer, full-
129 // width auto Column with `.align(Align::End)` positions the fixed-
130 // width (200pt) inner summary Column at the right edge; within it,
131 // each label gets `.flex(1.0)` to push its value to that block's own
132 // right edge. No special "summary block" element needed.
133 doc.add(
134 Column::new()
135 .align(Align::End)
136 .child(Column::new().gap(2.0).width(200.0).children(vec![
137 Element::from(
138 Row::new()
139 .child(Text::new("Nettosumme").flex(1.0))
140 .child(Text::new(format_currency_de(net_total))),
141 ),
142 Element::from(
143 Row::new()
144 .child(Text::new(format!("zzgl. {vat_rate}% USt.")).flex(1.0))
145 .child(Text::new(format_currency_de(vat_total))),
146 ),
147 Element::from(Line::new()),
148 Element::from(
149 Row::new()
150 .child(Text::new("Gesamtbetrag").bold().flex(1.0))
151 .child(Text::new(format_currency_de(gross_total)).bold()),
152 ),
153 ])),
154 );
155
156 common::write_pdf(&doc, "invoice.pdf");
157}Sourcepub fn theme(self, theme: Theme) -> Document
pub fn theme(self, theme: Theme) -> Document
Examples found in repository?
42fn main() {
43 let mut doc = Document::new(PageFormat::A4)
44 .margin(Margin::symmetric(56.0, 56.0))
45 .theme(brand_theme())
46 .header(Header::new(20.0, |_ctx| {
47 Text::new("Jahresbericht 2026 \u{2014} Muster GmbH").size(9.0).into()
48 }))
49 .header_visible_from(2)
50 .footer(Footer::new(20.0, |ctx| {
51 Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages))
52 .size(9.0)
53 .align(Align::Center)
54 .into()
55 }))
56 .watermark(Watermark::new("ENTWURF"));
57
58 // --- cover page -----------------------------------------------------
59 doc.add(Spacer::new(220.0));
60 doc.add(Text::new("Jahresbericht 2026").heading1().align(Align::Center));
61 doc.add(Spacer::new(8.0));
62 doc.add(Text::new("Muster GmbH").align(Align::Center));
63 doc.add(Text::new("vorgelegt am 20. August 2026").muted().align(Align::Center));
64 doc.add(Element::PageBreak);
65
66 // --- content ---------------------------------------------------------
67 doc.add(Text::new("1. Zusammenfassung").heading1());
68 doc.add(Text::new(
69 "Das Geschäftsjahr 2026 war geprägt von stabilem Wachstum in allen \
70 Kernbereichen. Die folgenden Abschnitte fassen die wichtigsten \
71 Kennzahlen und Entwicklungen zusammen.",
72 ));
73 doc.add(Spacer::new(12.0));
74
75 doc.add(Text::new("2. Wichtigste Ereignisse").heading2());
76 doc.add(
77 List::new()
78 .bullet(Text::new("Markteinführung des neuen Produkts im zweiten Quartal"))
79 .bullet(Text::new("Erweiterung des Teams um 12 neue Mitarbeitende"))
80 .bullet(Text::new("Eröffnung eines zweiten Standorts")),
81 );
82 doc.add(Spacer::new(12.0));
83
84 doc.add(Text::new("3. Zeitplan").heading2());
85 doc.add(
86 List::new()
87 .numbered(Text::new("Kickoff und Planung (Januar \u{2013} Februar)"))
88 .numbered(Text::new("Umsetzung Phase 1 (M\u{e4}rz \u{2013} Juni)"))
89 .numbered(Text::new("Umsetzung Phase 2 (Juli \u{2013} Oktober)"))
90 .numbered(Text::new("Abschluss und Auswertung (November \u{2013} Dezember)")),
91 );
92 doc.add(Spacer::new(12.0));
93
94 doc.add(Text::new("3.1 Details").heading3());
95 doc.add(Text::new(
96 "Weitere Details zu den einzelnen Phasen finden sich im Anhang. \
97 Diese Überschrift bleibt dank keep_with_next garantiert mit \
98 diesem Absatz auf derselben Seite zusammen.",
99 ));
100
101 common::write_pdf(&doc, "report.pdf");
102}Sourcepub fn pdf_a3b(self) -> Document
pub fn pdf_a3b(self) -> Document
Opt in to PDF/A-3b-conformant output (issue #25) — see
Document::pdf_a3b’s field doc comment. Needs the facade’s pdf-a
Cargo feature; without it, render() returns
RenderError::PdfAFeatureDisabled rather than silently ignoring
this.
Sourcepub fn zugferd_xml(self, xml: impl Into<Vec<u8>>) -> Document
pub fn zugferd_xml(self, xml: impl Into<Vec<u8>>) -> Document
Embeds xml as the document’s ZUGFeRD/Factur-X invoice data
(EN 16931/Comfort profile, issue #26) — implies .pdf_a3b()
(ZUGFeRD/Factur-X is a PDF/A-3 file with an embedded invoice,
not an independent opt-in). xml must already be a valid EN
16931 CrossIndustryInvoice document; this crate embeds it
byte-for-byte and never generates or validates the XML itself
(ADR-018).
Sourcepub fn pdf_ua(self) -> Document
pub fn pdf_ua(self) -> Document
Opt in to Tagged PDF/PDF-UA output (issue #27) — see
Document::pdf_ua’s field doc comment. Implies .pdf_a3b(): both
need the same XMP/OutputIntent machinery, and a combined
PDF/A+PDF/UA document (archival and accessible) is what most
real producers of this document class actually want — PDF/UA
without PDF/A isn’t a supported combination (ADR-019 in the local
plan/00-decisions.md). Needs the facade’s tagged-pdf Cargo
feature; without it, render() returns
RenderError::TaggedPdfFeatureDisabled.
Sourcepub fn lang(self, lang: impl Into<String>) -> Document
pub fn lang(self, lang: impl Into<String>) -> Document
Sets the Catalog’s /Lang (e.g. "en-US").
Sourcepub fn page_size(&self) -> (f32, f32)
pub fn page_size(&self) -> (f32, f32)
Effective page dimensions (width, height) in PDF points, accounting for orientation.
pub fn orientation(self, orientation: Orientation) -> Document
pub fn landscape(self) -> Document
pub fn portrait(self) -> Document
pub fn title(self, title: impl Into<String>) -> Document
pub fn subject(self, subject: impl Into<String>) -> Document
pub fn keywords(self, keywords: impl Into<String>) -> Document
pub fn creator(self, creator: impl Into<String>) -> Document
pub fn creation_date(self, date: PdfDate) -> Document
pub fn mod_date(self, date: PdfDate) -> Document
Sourcepub fn margin(self, margin: Margin) -> Document
pub fn margin(self, margin: Margin) -> Document
Examples found in repository?
42fn main() {
43 let mut doc = Document::new(PageFormat::A4)
44 .margin(Margin::symmetric(56.0, 56.0))
45 .theme(brand_theme())
46 .header(Header::new(20.0, |_ctx| {
47 Text::new("Jahresbericht 2026 \u{2014} Muster GmbH").size(9.0).into()
48 }))
49 .header_visible_from(2)
50 .footer(Footer::new(20.0, |ctx| {
51 Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages))
52 .size(9.0)
53 .align(Align::Center)
54 .into()
55 }))
56 .watermark(Watermark::new("ENTWURF"));
57
58 // --- cover page -----------------------------------------------------
59 doc.add(Spacer::new(220.0));
60 doc.add(Text::new("Jahresbericht 2026").heading1().align(Align::Center));
61 doc.add(Spacer::new(8.0));
62 doc.add(Text::new("Muster GmbH").align(Align::Center));
63 doc.add(Text::new("vorgelegt am 20. August 2026").muted().align(Align::Center));
64 doc.add(Element::PageBreak);
65
66 // --- content ---------------------------------------------------------
67 doc.add(Text::new("1. Zusammenfassung").heading1());
68 doc.add(Text::new(
69 "Das Geschäftsjahr 2026 war geprägt von stabilem Wachstum in allen \
70 Kernbereichen. Die folgenden Abschnitte fassen die wichtigsten \
71 Kennzahlen und Entwicklungen zusammen.",
72 ));
73 doc.add(Spacer::new(12.0));
74
75 doc.add(Text::new("2. Wichtigste Ereignisse").heading2());
76 doc.add(
77 List::new()
78 .bullet(Text::new("Markteinführung des neuen Produkts im zweiten Quartal"))
79 .bullet(Text::new("Erweiterung des Teams um 12 neue Mitarbeitende"))
80 .bullet(Text::new("Eröffnung eines zweiten Standorts")),
81 );
82 doc.add(Spacer::new(12.0));
83
84 doc.add(Text::new("3. Zeitplan").heading2());
85 doc.add(
86 List::new()
87 .numbered(Text::new("Kickoff und Planung (Januar \u{2013} Februar)"))
88 .numbered(Text::new("Umsetzung Phase 1 (M\u{e4}rz \u{2013} Juni)"))
89 .numbered(Text::new("Umsetzung Phase 2 (Juli \u{2013} Oktober)"))
90 .numbered(Text::new("Abschluss und Auswertung (November \u{2013} Dezember)")),
91 );
92 doc.add(Spacer::new(12.0));
93
94 doc.add(Text::new("3.1 Details").heading3());
95 doc.add(Text::new(
96 "Weitere Details zu den einzelnen Phasen finden sich im Anhang. \
97 Diese Überschrift bleibt dank keep_with_next garantiert mit \
98 diesem Absatz auf derselben Seite zusammen.",
99 ));
100
101 common::write_pdf(&doc, "report.pdf");
102}More examples
35fn main() {
36 let mut items = vec![
37 LineItem {
38 description: "Beratungsleistung Projekt Alpha".to_string(),
39 qty: 8,
40 unit_price_cents: 12_000,
41 },
42 LineItem {
43 description: "Lizenz Software-Paket (jährlich)".to_string(),
44 qty: 1,
45 unit_price_cents: 49_900,
46 },
47 LineItem {
48 description: "Individuelle Anpassung / Customizing".to_string(),
49 qty: 3,
50 unit_price_cents: 15_000,
51 },
52 ];
53 // A few filler positions so the table is guaranteed to span more than
54 // one page, demonstrating the header-repeat-on-split behavior.
55 for i in 1..=25 {
56 items.push(LineItem {
57 description: format!("Zusatzposition {i:02}"),
58 qty: 1,
59 unit_price_cents: 990,
60 });
61 }
62
63 let net_total: i64 = items.iter().map(|i| i.qty as i64 * i.unit_price_cents).sum();
64 let vat_rate = 19;
65 let vat_total = net_total * vat_rate / 100;
66 let gross_total = net_total + vat_total;
67
68 let top_margin = 15.0 * MM;
69 let mut doc = Document::new(PageFormat::A4)
70 .margin(Margin::symmetric(20.0 * MM, top_margin))
71 .footer(Footer::new(30.0, |ctx| {
72 Column::new()
73 .gap(2.0)
74 .child(Line::new())
75 .child(
76 Row::new()
77 .gap(20.0)
78 .child(Text::new("Musterbank · IBAN DE12 3456 7890 1234 5678 90 · BIC MUSTDEFF").size(8.0))
79 .child(Text::new("USt-IdNr. DE123456789").size(8.0).flex(1.0)),
80 )
81 .child(Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages)).size(8.0))
82 .into()
83 }));
84
85 // --- DIN 5008 Form A window-envelope address block ----------------
86 // Window starts ~45mm from the top, ~20mm from the left
87 // (`plan/02-elementcatalog-and-features.md`); ~85x40mm matches a
88 // typical C6/5-long window envelope opening. Position/size are a
89 // documented convention for this recipe, not parsed from any norm
90 // document — a caller with different envelope stock adjusts these.
91 doc.add(Spacer::new(45.0 * MM - top_margin));
92 doc.add(
93 Column::new()
94 .gap(2.0)
95 .width(85.0 * MM)
96 .height(40.0 * MM)
97 .child(Text::new("Muster GmbH · Musterstraße 1 · 12345 Musterstadt").size(7.0))
98 .child(Spacer::new(8.0))
99 .child(Text::new("Empfänger GmbH"))
100 .child(Text::new("Frau Erika Mustermann"))
101 .child(Text::new("Beispielweg 42"))
102 .child(Text::new("54321 Beispielstadt")),
103 );
104 doc.add(Spacer::new(10.0 * MM));
105
106 doc.add(Text::new("Rechnung").heading1());
107 doc.add(Text::new(
108 "Rechnungsnummer: RE-2026-0142 Rechnungsdatum: 20.08.2026 Leistungsdatum: 20.08.2026",
109 ));
110 doc.add(Spacer::new(10.0));
111
112 doc.add(
113 Table::new()
114 .columns([
115 TableColumn::flex(1.0),
116 TableColumn::fixed(50.0).align(Align::End),
117 TableColumn::fixed(65.0).align(Align::End),
118 TableColumn::fixed(70.0).align(Align::End),
119 ])
120 .header(["Beschreibung", "Menge", "Einzelpreis", "Gesamt"])
121 .striped(Color::rgb(0xF5, 0xF5, 0xF5))
122 .from_rows(&items),
123 );
124
125 doc.add(Spacer::new(14.0));
126
127 // --- summary block (Netto/USt/Brutto), right-aligned --------------
128 // Recipe (`plan/02-elementcatalog-and-features.md`): an outer, full-
129 // width auto Column with `.align(Align::End)` positions the fixed-
130 // width (200pt) inner summary Column at the right edge; within it,
131 // each label gets `.flex(1.0)` to push its value to that block's own
132 // right edge. No special "summary block" element needed.
133 doc.add(
134 Column::new()
135 .align(Align::End)
136 .child(Column::new().gap(2.0).width(200.0).children(vec![
137 Element::from(
138 Row::new()
139 .child(Text::new("Nettosumme").flex(1.0))
140 .child(Text::new(format_currency_de(net_total))),
141 ),
142 Element::from(
143 Row::new()
144 .child(Text::new(format!("zzgl. {vat_rate}% USt.")).flex(1.0))
145 .child(Text::new(format_currency_de(vat_total))),
146 ),
147 Element::from(Line::new()),
148 Element::from(
149 Row::new()
150 .child(Text::new("Gesamtbetrag").bold().flex(1.0))
151 .child(Text::new(format_currency_de(gross_total)).bold()),
152 ),
153 ])),
154 );
155
156 common::write_pdf(&doc, "invoice.pdf");
157}Sourcepub fn header(self, header: Header) -> Document
pub fn header(self, header: Header) -> Document
Examples found in repository?
42fn main() {
43 let mut doc = Document::new(PageFormat::A4)
44 .margin(Margin::symmetric(56.0, 56.0))
45 .theme(brand_theme())
46 .header(Header::new(20.0, |_ctx| {
47 Text::new("Jahresbericht 2026 \u{2014} Muster GmbH").size(9.0).into()
48 }))
49 .header_visible_from(2)
50 .footer(Footer::new(20.0, |ctx| {
51 Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages))
52 .size(9.0)
53 .align(Align::Center)
54 .into()
55 }))
56 .watermark(Watermark::new("ENTWURF"));
57
58 // --- cover page -----------------------------------------------------
59 doc.add(Spacer::new(220.0));
60 doc.add(Text::new("Jahresbericht 2026").heading1().align(Align::Center));
61 doc.add(Spacer::new(8.0));
62 doc.add(Text::new("Muster GmbH").align(Align::Center));
63 doc.add(Text::new("vorgelegt am 20. August 2026").muted().align(Align::Center));
64 doc.add(Element::PageBreak);
65
66 // --- content ---------------------------------------------------------
67 doc.add(Text::new("1. Zusammenfassung").heading1());
68 doc.add(Text::new(
69 "Das Geschäftsjahr 2026 war geprägt von stabilem Wachstum in allen \
70 Kernbereichen. Die folgenden Abschnitte fassen die wichtigsten \
71 Kennzahlen und Entwicklungen zusammen.",
72 ));
73 doc.add(Spacer::new(12.0));
74
75 doc.add(Text::new("2. Wichtigste Ereignisse").heading2());
76 doc.add(
77 List::new()
78 .bullet(Text::new("Markteinführung des neuen Produkts im zweiten Quartal"))
79 .bullet(Text::new("Erweiterung des Teams um 12 neue Mitarbeitende"))
80 .bullet(Text::new("Eröffnung eines zweiten Standorts")),
81 );
82 doc.add(Spacer::new(12.0));
83
84 doc.add(Text::new("3. Zeitplan").heading2());
85 doc.add(
86 List::new()
87 .numbered(Text::new("Kickoff und Planung (Januar \u{2013} Februar)"))
88 .numbered(Text::new("Umsetzung Phase 1 (M\u{e4}rz \u{2013} Juni)"))
89 .numbered(Text::new("Umsetzung Phase 2 (Juli \u{2013} Oktober)"))
90 .numbered(Text::new("Abschluss und Auswertung (November \u{2013} Dezember)")),
91 );
92 doc.add(Spacer::new(12.0));
93
94 doc.add(Text::new("3.1 Details").heading3());
95 doc.add(Text::new(
96 "Weitere Details zu den einzelnen Phasen finden sich im Anhang. \
97 Diese Überschrift bleibt dank keep_with_next garantiert mit \
98 diesem Absatz auf derselben Seite zusammen.",
99 ));
100
101 common::write_pdf(&doc, "report.pdf");
102}Examples found in repository?
42fn main() {
43 let mut doc = Document::new(PageFormat::A4)
44 .margin(Margin::symmetric(56.0, 56.0))
45 .theme(brand_theme())
46 .header(Header::new(20.0, |_ctx| {
47 Text::new("Jahresbericht 2026 \u{2014} Muster GmbH").size(9.0).into()
48 }))
49 .header_visible_from(2)
50 .footer(Footer::new(20.0, |ctx| {
51 Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages))
52 .size(9.0)
53 .align(Align::Center)
54 .into()
55 }))
56 .watermark(Watermark::new("ENTWURF"));
57
58 // --- cover page -----------------------------------------------------
59 doc.add(Spacer::new(220.0));
60 doc.add(Text::new("Jahresbericht 2026").heading1().align(Align::Center));
61 doc.add(Spacer::new(8.0));
62 doc.add(Text::new("Muster GmbH").align(Align::Center));
63 doc.add(Text::new("vorgelegt am 20. August 2026").muted().align(Align::Center));
64 doc.add(Element::PageBreak);
65
66 // --- content ---------------------------------------------------------
67 doc.add(Text::new("1. Zusammenfassung").heading1());
68 doc.add(Text::new(
69 "Das Geschäftsjahr 2026 war geprägt von stabilem Wachstum in allen \
70 Kernbereichen. Die folgenden Abschnitte fassen die wichtigsten \
71 Kennzahlen und Entwicklungen zusammen.",
72 ));
73 doc.add(Spacer::new(12.0));
74
75 doc.add(Text::new("2. Wichtigste Ereignisse").heading2());
76 doc.add(
77 List::new()
78 .bullet(Text::new("Markteinführung des neuen Produkts im zweiten Quartal"))
79 .bullet(Text::new("Erweiterung des Teams um 12 neue Mitarbeitende"))
80 .bullet(Text::new("Eröffnung eines zweiten Standorts")),
81 );
82 doc.add(Spacer::new(12.0));
83
84 doc.add(Text::new("3. Zeitplan").heading2());
85 doc.add(
86 List::new()
87 .numbered(Text::new("Kickoff und Planung (Januar \u{2013} Februar)"))
88 .numbered(Text::new("Umsetzung Phase 1 (M\u{e4}rz \u{2013} Juni)"))
89 .numbered(Text::new("Umsetzung Phase 2 (Juli \u{2013} Oktober)"))
90 .numbered(Text::new("Abschluss und Auswertung (November \u{2013} Dezember)")),
91 );
92 doc.add(Spacer::new(12.0));
93
94 doc.add(Text::new("3.1 Details").heading3());
95 doc.add(Text::new(
96 "Weitere Details zu den einzelnen Phasen finden sich im Anhang. \
97 Diese Überschrift bleibt dank keep_with_next garantiert mit \
98 diesem Absatz auf derselben Seite zusammen.",
99 ));
100
101 common::write_pdf(&doc, "report.pdf");
102}More examples
35fn main() {
36 let mut items = vec![
37 LineItem {
38 description: "Beratungsleistung Projekt Alpha".to_string(),
39 qty: 8,
40 unit_price_cents: 12_000,
41 },
42 LineItem {
43 description: "Lizenz Software-Paket (jährlich)".to_string(),
44 qty: 1,
45 unit_price_cents: 49_900,
46 },
47 LineItem {
48 description: "Individuelle Anpassung / Customizing".to_string(),
49 qty: 3,
50 unit_price_cents: 15_000,
51 },
52 ];
53 // A few filler positions so the table is guaranteed to span more than
54 // one page, demonstrating the header-repeat-on-split behavior.
55 for i in 1..=25 {
56 items.push(LineItem {
57 description: format!("Zusatzposition {i:02}"),
58 qty: 1,
59 unit_price_cents: 990,
60 });
61 }
62
63 let net_total: i64 = items.iter().map(|i| i.qty as i64 * i.unit_price_cents).sum();
64 let vat_rate = 19;
65 let vat_total = net_total * vat_rate / 100;
66 let gross_total = net_total + vat_total;
67
68 let top_margin = 15.0 * MM;
69 let mut doc = Document::new(PageFormat::A4)
70 .margin(Margin::symmetric(20.0 * MM, top_margin))
71 .footer(Footer::new(30.0, |ctx| {
72 Column::new()
73 .gap(2.0)
74 .child(Line::new())
75 .child(
76 Row::new()
77 .gap(20.0)
78 .child(Text::new("Musterbank · IBAN DE12 3456 7890 1234 5678 90 · BIC MUSTDEFF").size(8.0))
79 .child(Text::new("USt-IdNr. DE123456789").size(8.0).flex(1.0)),
80 )
81 .child(Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages)).size(8.0))
82 .into()
83 }));
84
85 // --- DIN 5008 Form A window-envelope address block ----------------
86 // Window starts ~45mm from the top, ~20mm from the left
87 // (`plan/02-elementcatalog-and-features.md`); ~85x40mm matches a
88 // typical C6/5-long window envelope opening. Position/size are a
89 // documented convention for this recipe, not parsed from any norm
90 // document — a caller with different envelope stock adjusts these.
91 doc.add(Spacer::new(45.0 * MM - top_margin));
92 doc.add(
93 Column::new()
94 .gap(2.0)
95 .width(85.0 * MM)
96 .height(40.0 * MM)
97 .child(Text::new("Muster GmbH · Musterstraße 1 · 12345 Musterstadt").size(7.0))
98 .child(Spacer::new(8.0))
99 .child(Text::new("Empfänger GmbH"))
100 .child(Text::new("Frau Erika Mustermann"))
101 .child(Text::new("Beispielweg 42"))
102 .child(Text::new("54321 Beispielstadt")),
103 );
104 doc.add(Spacer::new(10.0 * MM));
105
106 doc.add(Text::new("Rechnung").heading1());
107 doc.add(Text::new(
108 "Rechnungsnummer: RE-2026-0142 Rechnungsdatum: 20.08.2026 Leistungsdatum: 20.08.2026",
109 ));
110 doc.add(Spacer::new(10.0));
111
112 doc.add(
113 Table::new()
114 .columns([
115 TableColumn::flex(1.0),
116 TableColumn::fixed(50.0).align(Align::End),
117 TableColumn::fixed(65.0).align(Align::End),
118 TableColumn::fixed(70.0).align(Align::End),
119 ])
120 .header(["Beschreibung", "Menge", "Einzelpreis", "Gesamt"])
121 .striped(Color::rgb(0xF5, 0xF5, 0xF5))
122 .from_rows(&items),
123 );
124
125 doc.add(Spacer::new(14.0));
126
127 // --- summary block (Netto/USt/Brutto), right-aligned --------------
128 // Recipe (`plan/02-elementcatalog-and-features.md`): an outer, full-
129 // width auto Column with `.align(Align::End)` positions the fixed-
130 // width (200pt) inner summary Column at the right edge; within it,
131 // each label gets `.flex(1.0)` to push its value to that block's own
132 // right edge. No special "summary block" element needed.
133 doc.add(
134 Column::new()
135 .align(Align::End)
136 .child(Column::new().gap(2.0).width(200.0).children(vec![
137 Element::from(
138 Row::new()
139 .child(Text::new("Nettosumme").flex(1.0))
140 .child(Text::new(format_currency_de(net_total))),
141 ),
142 Element::from(
143 Row::new()
144 .child(Text::new(format!("zzgl. {vat_rate}% USt.")).flex(1.0))
145 .child(Text::new(format_currency_de(vat_total))),
146 ),
147 Element::from(Line::new()),
148 Element::from(
149 Row::new()
150 .child(Text::new("Gesamtbetrag").bold().flex(1.0))
151 .child(Text::new(format_currency_de(gross_total)).bold()),
152 ),
153 ])),
154 );
155
156 common::write_pdf(&doc, "invoice.pdf");
157}Sourcepub fn header_visible_from(self, page: usize) -> Document
pub fn header_visible_from(self, page: usize) -> Document
First page number (1-based) on which the header is drawn. Cover-page
convenience, see plan/02-elementcatalog-and-features.md (“Deckblatt
/ Titelseite”).
Examples found in repository?
42fn main() {
43 let mut doc = Document::new(PageFormat::A4)
44 .margin(Margin::symmetric(56.0, 56.0))
45 .theme(brand_theme())
46 .header(Header::new(20.0, |_ctx| {
47 Text::new("Jahresbericht 2026 \u{2014} Muster GmbH").size(9.0).into()
48 }))
49 .header_visible_from(2)
50 .footer(Footer::new(20.0, |ctx| {
51 Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages))
52 .size(9.0)
53 .align(Align::Center)
54 .into()
55 }))
56 .watermark(Watermark::new("ENTWURF"));
57
58 // --- cover page -----------------------------------------------------
59 doc.add(Spacer::new(220.0));
60 doc.add(Text::new("Jahresbericht 2026").heading1().align(Align::Center));
61 doc.add(Spacer::new(8.0));
62 doc.add(Text::new("Muster GmbH").align(Align::Center));
63 doc.add(Text::new("vorgelegt am 20. August 2026").muted().align(Align::Center));
64 doc.add(Element::PageBreak);
65
66 // --- content ---------------------------------------------------------
67 doc.add(Text::new("1. Zusammenfassung").heading1());
68 doc.add(Text::new(
69 "Das Geschäftsjahr 2026 war geprägt von stabilem Wachstum in allen \
70 Kernbereichen. Die folgenden Abschnitte fassen die wichtigsten \
71 Kennzahlen und Entwicklungen zusammen.",
72 ));
73 doc.add(Spacer::new(12.0));
74
75 doc.add(Text::new("2. Wichtigste Ereignisse").heading2());
76 doc.add(
77 List::new()
78 .bullet(Text::new("Markteinführung des neuen Produkts im zweiten Quartal"))
79 .bullet(Text::new("Erweiterung des Teams um 12 neue Mitarbeitende"))
80 .bullet(Text::new("Eröffnung eines zweiten Standorts")),
81 );
82 doc.add(Spacer::new(12.0));
83
84 doc.add(Text::new("3. Zeitplan").heading2());
85 doc.add(
86 List::new()
87 .numbered(Text::new("Kickoff und Planung (Januar \u{2013} Februar)"))
88 .numbered(Text::new("Umsetzung Phase 1 (M\u{e4}rz \u{2013} Juni)"))
89 .numbered(Text::new("Umsetzung Phase 2 (Juli \u{2013} Oktober)"))
90 .numbered(Text::new("Abschluss und Auswertung (November \u{2013} Dezember)")),
91 );
92 doc.add(Spacer::new(12.0));
93
94 doc.add(Text::new("3.1 Details").heading3());
95 doc.add(Text::new(
96 "Weitere Details zu den einzelnen Phasen finden sich im Anhang. \
97 Diese Überschrift bleibt dank keep_with_next garantiert mit \
98 diesem Absatz auf derselben Seite zusammen.",
99 ));
100
101 common::write_pdf(&doc, "report.pdf");
102}Sourcepub fn watermark(self, watermark: Watermark) -> Document
pub fn watermark(self, watermark: Watermark) -> Document
Sets a document-wide diagonal stamp (“ENTWURF”, “STORNIERT”) — an independent layer, not a normal flow element (Phase 6).
Examples found in repository?
42fn main() {
43 let mut doc = Document::new(PageFormat::A4)
44 .margin(Margin::symmetric(56.0, 56.0))
45 .theme(brand_theme())
46 .header(Header::new(20.0, |_ctx| {
47 Text::new("Jahresbericht 2026 \u{2014} Muster GmbH").size(9.0).into()
48 }))
49 .header_visible_from(2)
50 .footer(Footer::new(20.0, |ctx| {
51 Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages))
52 .size(9.0)
53 .align(Align::Center)
54 .into()
55 }))
56 .watermark(Watermark::new("ENTWURF"));
57
58 // --- cover page -----------------------------------------------------
59 doc.add(Spacer::new(220.0));
60 doc.add(Text::new("Jahresbericht 2026").heading1().align(Align::Center));
61 doc.add(Spacer::new(8.0));
62 doc.add(Text::new("Muster GmbH").align(Align::Center));
63 doc.add(Text::new("vorgelegt am 20. August 2026").muted().align(Align::Center));
64 doc.add(Element::PageBreak);
65
66 // --- content ---------------------------------------------------------
67 doc.add(Text::new("1. Zusammenfassung").heading1());
68 doc.add(Text::new(
69 "Das Geschäftsjahr 2026 war geprägt von stabilem Wachstum in allen \
70 Kernbereichen. Die folgenden Abschnitte fassen die wichtigsten \
71 Kennzahlen und Entwicklungen zusammen.",
72 ));
73 doc.add(Spacer::new(12.0));
74
75 doc.add(Text::new("2. Wichtigste Ereignisse").heading2());
76 doc.add(
77 List::new()
78 .bullet(Text::new("Markteinführung des neuen Produkts im zweiten Quartal"))
79 .bullet(Text::new("Erweiterung des Teams um 12 neue Mitarbeitende"))
80 .bullet(Text::new("Eröffnung eines zweiten Standorts")),
81 );
82 doc.add(Spacer::new(12.0));
83
84 doc.add(Text::new("3. Zeitplan").heading2());
85 doc.add(
86 List::new()
87 .numbered(Text::new("Kickoff und Planung (Januar \u{2013} Februar)"))
88 .numbered(Text::new("Umsetzung Phase 1 (M\u{e4}rz \u{2013} Juni)"))
89 .numbered(Text::new("Umsetzung Phase 2 (Juli \u{2013} Oktober)"))
90 .numbered(Text::new("Abschluss und Auswertung (November \u{2013} Dezember)")),
91 );
92 doc.add(Spacer::new(12.0));
93
94 doc.add(Text::new("3.1 Details").heading3());
95 doc.add(Text::new(
96 "Weitere Details zu den einzelnen Phasen finden sich im Anhang. \
97 Diese Überschrift bleibt dank keep_with_next garantiert mit \
98 diesem Absatz auf derselben Seite zusammen.",
99 ));
100
101 common::write_pdf(&doc, "report.pdf");
102}Sourcepub fn add(&mut self, element: impl Into<Element>) -> &mut Document
pub fn add(&mut self, element: impl Into<Element>) -> &mut Document
Examples found in repository?
42fn main() {
43 let mut doc = Document::new(PageFormat::A4)
44 .margin(Margin::symmetric(56.0, 56.0))
45 .theme(brand_theme())
46 .header(Header::new(20.0, |_ctx| {
47 Text::new("Jahresbericht 2026 \u{2014} Muster GmbH").size(9.0).into()
48 }))
49 .header_visible_from(2)
50 .footer(Footer::new(20.0, |ctx| {
51 Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages))
52 .size(9.0)
53 .align(Align::Center)
54 .into()
55 }))
56 .watermark(Watermark::new("ENTWURF"));
57
58 // --- cover page -----------------------------------------------------
59 doc.add(Spacer::new(220.0));
60 doc.add(Text::new("Jahresbericht 2026").heading1().align(Align::Center));
61 doc.add(Spacer::new(8.0));
62 doc.add(Text::new("Muster GmbH").align(Align::Center));
63 doc.add(Text::new("vorgelegt am 20. August 2026").muted().align(Align::Center));
64 doc.add(Element::PageBreak);
65
66 // --- content ---------------------------------------------------------
67 doc.add(Text::new("1. Zusammenfassung").heading1());
68 doc.add(Text::new(
69 "Das Geschäftsjahr 2026 war geprägt von stabilem Wachstum in allen \
70 Kernbereichen. Die folgenden Abschnitte fassen die wichtigsten \
71 Kennzahlen und Entwicklungen zusammen.",
72 ));
73 doc.add(Spacer::new(12.0));
74
75 doc.add(Text::new("2. Wichtigste Ereignisse").heading2());
76 doc.add(
77 List::new()
78 .bullet(Text::new("Markteinführung des neuen Produkts im zweiten Quartal"))
79 .bullet(Text::new("Erweiterung des Teams um 12 neue Mitarbeitende"))
80 .bullet(Text::new("Eröffnung eines zweiten Standorts")),
81 );
82 doc.add(Spacer::new(12.0));
83
84 doc.add(Text::new("3. Zeitplan").heading2());
85 doc.add(
86 List::new()
87 .numbered(Text::new("Kickoff und Planung (Januar \u{2013} Februar)"))
88 .numbered(Text::new("Umsetzung Phase 1 (M\u{e4}rz \u{2013} Juni)"))
89 .numbered(Text::new("Umsetzung Phase 2 (Juli \u{2013} Oktober)"))
90 .numbered(Text::new("Abschluss und Auswertung (November \u{2013} Dezember)")),
91 );
92 doc.add(Spacer::new(12.0));
93
94 doc.add(Text::new("3.1 Details").heading3());
95 doc.add(Text::new(
96 "Weitere Details zu den einzelnen Phasen finden sich im Anhang. \
97 Diese Überschrift bleibt dank keep_with_next garantiert mit \
98 diesem Absatz auf derselben Seite zusammen.",
99 ));
100
101 common::write_pdf(&doc, "report.pdf");
102}More examples
35fn main() {
36 let mut items = vec![
37 LineItem {
38 description: "Beratungsleistung Projekt Alpha".to_string(),
39 qty: 8,
40 unit_price_cents: 12_000,
41 },
42 LineItem {
43 description: "Lizenz Software-Paket (jährlich)".to_string(),
44 qty: 1,
45 unit_price_cents: 49_900,
46 },
47 LineItem {
48 description: "Individuelle Anpassung / Customizing".to_string(),
49 qty: 3,
50 unit_price_cents: 15_000,
51 },
52 ];
53 // A few filler positions so the table is guaranteed to span more than
54 // one page, demonstrating the header-repeat-on-split behavior.
55 for i in 1..=25 {
56 items.push(LineItem {
57 description: format!("Zusatzposition {i:02}"),
58 qty: 1,
59 unit_price_cents: 990,
60 });
61 }
62
63 let net_total: i64 = items.iter().map(|i| i.qty as i64 * i.unit_price_cents).sum();
64 let vat_rate = 19;
65 let vat_total = net_total * vat_rate / 100;
66 let gross_total = net_total + vat_total;
67
68 let top_margin = 15.0 * MM;
69 let mut doc = Document::new(PageFormat::A4)
70 .margin(Margin::symmetric(20.0 * MM, top_margin))
71 .footer(Footer::new(30.0, |ctx| {
72 Column::new()
73 .gap(2.0)
74 .child(Line::new())
75 .child(
76 Row::new()
77 .gap(20.0)
78 .child(Text::new("Musterbank · IBAN DE12 3456 7890 1234 5678 90 · BIC MUSTDEFF").size(8.0))
79 .child(Text::new("USt-IdNr. DE123456789").size(8.0).flex(1.0)),
80 )
81 .child(Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages)).size(8.0))
82 .into()
83 }));
84
85 // --- DIN 5008 Form A window-envelope address block ----------------
86 // Window starts ~45mm from the top, ~20mm from the left
87 // (`plan/02-elementcatalog-and-features.md`); ~85x40mm matches a
88 // typical C6/5-long window envelope opening. Position/size are a
89 // documented convention for this recipe, not parsed from any norm
90 // document — a caller with different envelope stock adjusts these.
91 doc.add(Spacer::new(45.0 * MM - top_margin));
92 doc.add(
93 Column::new()
94 .gap(2.0)
95 .width(85.0 * MM)
96 .height(40.0 * MM)
97 .child(Text::new("Muster GmbH · Musterstraße 1 · 12345 Musterstadt").size(7.0))
98 .child(Spacer::new(8.0))
99 .child(Text::new("Empfänger GmbH"))
100 .child(Text::new("Frau Erika Mustermann"))
101 .child(Text::new("Beispielweg 42"))
102 .child(Text::new("54321 Beispielstadt")),
103 );
104 doc.add(Spacer::new(10.0 * MM));
105
106 doc.add(Text::new("Rechnung").heading1());
107 doc.add(Text::new(
108 "Rechnungsnummer: RE-2026-0142 Rechnungsdatum: 20.08.2026 Leistungsdatum: 20.08.2026",
109 ));
110 doc.add(Spacer::new(10.0));
111
112 doc.add(
113 Table::new()
114 .columns([
115 TableColumn::flex(1.0),
116 TableColumn::fixed(50.0).align(Align::End),
117 TableColumn::fixed(65.0).align(Align::End),
118 TableColumn::fixed(70.0).align(Align::End),
119 ])
120 .header(["Beschreibung", "Menge", "Einzelpreis", "Gesamt"])
121 .striped(Color::rgb(0xF5, 0xF5, 0xF5))
122 .from_rows(&items),
123 );
124
125 doc.add(Spacer::new(14.0));
126
127 // --- summary block (Netto/USt/Brutto), right-aligned --------------
128 // Recipe (`plan/02-elementcatalog-and-features.md`): an outer, full-
129 // width auto Column with `.align(Align::End)` positions the fixed-
130 // width (200pt) inner summary Column at the right edge; within it,
131 // each label gets `.flex(1.0)` to push its value to that block's own
132 // right edge. No special "summary block" element needed.
133 doc.add(
134 Column::new()
135 .align(Align::End)
136 .child(Column::new().gap(2.0).width(200.0).children(vec![
137 Element::from(
138 Row::new()
139 .child(Text::new("Nettosumme").flex(1.0))
140 .child(Text::new(format_currency_de(net_total))),
141 ),
142 Element::from(
143 Row::new()
144 .child(Text::new(format!("zzgl. {vat_rate}% USt.")).flex(1.0))
145 .child(Text::new(format_currency_de(vat_total))),
146 ),
147 Element::from(Line::new()),
148 Element::from(
149 Row::new()
150 .child(Text::new("Gesamtbetrag").bold().flex(1.0))
151 .child(Text::new(format_currency_de(gross_total)).bold()),
152 ),
153 ])),
154 );
155
156 common::write_pdf(&doc, "invoice.pdf");
157}