pub struct Document {
pub page_format: PageFormat,
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 children: Vec<Element>,
}Fields§
§page_format: PageFormat§margin: Margin§header: Option<Header>§header_visible_from: usize§watermark: Option<Watermark>§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?
examples/report.rs (line 14)
13fn main() {
14 let mut doc = Document::new(PageFormat::A4)
15 .margin(Margin::symmetric(56.0, 56.0))
16 .header(Header::new(20.0, |_ctx| {
17 Text::new("Jahresbericht 2026 \u{2014} Muster GmbH").size(9.0).into()
18 }))
19 .header_visible_from(2)
20 .footer(Footer::new(20.0, |ctx| {
21 Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages))
22 .size(9.0)
23 .align(Align::Center)
24 .into()
25 }))
26 .watermark(Watermark::new("ENTWURF"));
27
28 // --- cover page -----------------------------------------------------
29 doc.add(Spacer::new(220.0));
30 doc.add(Text::new("Jahresbericht 2026").heading1().align(Align::Center));
31 doc.add(Spacer::new(8.0));
32 doc.add(Text::new("Muster GmbH").align(Align::Center));
33 doc.add(Text::new("vorgelegt am 20. August 2026").align(Align::Center));
34 doc.add(Element::PageBreak);
35
36 // --- content ---------------------------------------------------------
37 doc.add(Text::new("1. Zusammenfassung").heading1());
38 doc.add(Text::new(
39 "Das Geschäftsjahr 2026 war geprägt von stabilem Wachstum in allen \
40 Kernbereichen. Die folgenden Abschnitte fassen die wichtigsten \
41 Kennzahlen und Entwicklungen zusammen.",
42 ));
43 doc.add(Spacer::new(12.0));
44
45 doc.add(Text::new("2. Wichtigste Ereignisse").heading2());
46 doc.add(
47 List::new()
48 .bullet(Text::new("Markteinführung des neuen Produkts im zweiten Quartal"))
49 .bullet(Text::new("Erweiterung des Teams um 12 neue Mitarbeitende"))
50 .bullet(Text::new("Eröffnung eines zweiten Standorts")),
51 );
52 doc.add(Spacer::new(12.0));
53
54 doc.add(Text::new("3. Zeitplan").heading2());
55 doc.add(
56 List::new()
57 .numbered(Text::new("Kickoff und Planung (Januar \u{2013} Februar)"))
58 .numbered(Text::new("Umsetzung Phase 1 (M\u{e4}rz \u{2013} Juni)"))
59 .numbered(Text::new("Umsetzung Phase 2 (Juli \u{2013} Oktober)"))
60 .numbered(Text::new("Abschluss und Auswertung (November \u{2013} Dezember)")),
61 );
62 doc.add(Spacer::new(12.0));
63
64 doc.add(Text::new("3.1 Details").heading3());
65 doc.add(Text::new(
66 "Weitere Details zu den einzelnen Phasen finden sich im Anhang. \
67 Diese Überschrift bleibt dank keep_with_next garantiert mit \
68 diesem Absatz auf derselben Seite zusammen.",
69 ));
70
71 common::write_pdf(&doc, "report.pdf");
72}More examples
examples/invoice.rs (line 57)
23fn main() {
24 let mut items = vec![
25 LineItem {
26 description: "Beratungsleistung Projekt Alpha".to_string(),
27 qty: 8,
28 unit_price_cents: 12_000,
29 },
30 LineItem {
31 description: "Lizenz Software-Paket (jährlich)".to_string(),
32 qty: 1,
33 unit_price_cents: 49_900,
34 },
35 LineItem {
36 description: "Individuelle Anpassung / Customizing".to_string(),
37 qty: 3,
38 unit_price_cents: 15_000,
39 },
40 ];
41 // A few filler positions so the table is guaranteed to span more than
42 // one page, demonstrating the header-repeat-on-split behavior.
43 for i in 1..=25 {
44 items.push(LineItem {
45 description: format!("Zusatzposition {i:02}"),
46 qty: 1,
47 unit_price_cents: 990,
48 });
49 }
50
51 let net_total: i64 = items.iter().map(|i| i.qty as i64 * i.unit_price_cents).sum();
52 let vat_rate = 19;
53 let vat_total = net_total * vat_rate / 100;
54 let gross_total = net_total + vat_total;
55
56 let top_margin = 15.0 * MM;
57 let mut doc = Document::new(PageFormat::A4)
58 .margin(Margin::symmetric(20.0 * MM, top_margin))
59 .footer(Footer::new(30.0, |ctx| {
60 Column::new()
61 .gap(2.0)
62 .child(Line::new())
63 .child(
64 Row::new()
65 .gap(20.0)
66 .child(Text::new("Musterbank · IBAN DE12 3456 7890 1234 5678 90 · BIC MUSTDEFF").size(8.0))
67 .child(Text::new("USt-IdNr. DE123456789").size(8.0).flex(1.0)),
68 )
69 .child(Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages)).size(8.0))
70 .into()
71 }));
72
73 // --- DIN 5008 Form A window-envelope address block ----------------
74 // Window starts ~45mm from the top, ~20mm from the left
75 // (`plan/02-elementcatalog-and-features.md`); ~85x40mm matches a
76 // typical C6/5-long window envelope opening. Position/size are a
77 // documented convention for this recipe, not parsed from any norm
78 // document — a caller with different envelope stock adjusts these.
79 doc.add(Spacer::new(45.0 * MM - top_margin));
80 doc.add(
81 Column::new()
82 .gap(2.0)
83 .width(85.0 * MM)
84 .height(40.0 * MM)
85 .child(Text::new("Muster GmbH · Musterstraße 1 · 12345 Musterstadt").size(7.0))
86 .child(Spacer::new(8.0))
87 .child(Text::new("Empfänger GmbH"))
88 .child(Text::new("Frau Erika Mustermann"))
89 .child(Text::new("Beispielweg 42"))
90 .child(Text::new("54321 Beispielstadt")),
91 );
92 doc.add(Spacer::new(10.0 * MM));
93
94 doc.add(Text::new("Rechnung").heading1());
95 doc.add(Text::new(
96 "Rechnungsnummer: RE-2026-0142 Rechnungsdatum: 20.08.2026 Leistungsdatum: 20.08.2026",
97 ));
98 doc.add(Spacer::new(10.0));
99
100 doc.add(
101 Table::new()
102 .columns([
103 TableColumn::flex(1.0),
104 TableColumn::fixed(50.0).align(Align::End),
105 TableColumn::fixed(65.0).align(Align::End),
106 TableColumn::fixed(70.0).align(Align::End),
107 ])
108 .header(["Beschreibung", "Menge", "Einzelpreis", "Gesamt"])
109 .striped(Color::rgb(0xF5, 0xF5, 0xF5))
110 .rows(items.iter().map(|item| {
111 let total = item.qty as i64 * item.unit_price_cents;
112 vec![
113 Element::from(item.description.as_str()),
114 Element::from(item.qty.to_string()),
115 Element::from(format_currency_de(item.unit_price_cents)),
116 Element::from(format_currency_de(total)),
117 ]
118 })),
119 );
120
121 doc.add(Spacer::new(14.0));
122
123 // --- summary block (Netto/USt/Brutto), right-aligned --------------
124 // Recipe (`plan/02-elementcatalog-and-features.md`): an outer, full-
125 // width auto Column with `.align(Align::End)` positions the fixed-
126 // width (200pt) inner summary Column at the right edge; within it,
127 // each label gets `.flex(1.0)` to push its value to that block's own
128 // right edge. No special "summary block" element needed.
129 doc.add(
130 Column::new()
131 .align(Align::End)
132 .child(Column::new().gap(2.0).width(200.0).children(vec![
133 Element::from(
134 Row::new()
135 .child(Text::new("Nettosumme").flex(1.0))
136 .child(Text::new(format_currency_de(net_total))),
137 ),
138 Element::from(
139 Row::new()
140 .child(Text::new(format!("zzgl. {vat_rate}% USt.")).flex(1.0))
141 .child(Text::new(format_currency_de(vat_total))),
142 ),
143 Element::from(Line::new()),
144 Element::from(
145 Row::new()
146 .child(Text::new("Gesamtbetrag").bold().flex(1.0))
147 .child(Text::new(format_currency_de(gross_total)).bold()),
148 ),
149 ])),
150 );
151
152 common::write_pdf(&doc, "invoice.pdf");
153}Sourcepub fn margin(self, margin: Margin) -> Document
pub fn margin(self, margin: Margin) -> Document
Examples found in repository?
examples/report.rs (line 15)
13fn main() {
14 let mut doc = Document::new(PageFormat::A4)
15 .margin(Margin::symmetric(56.0, 56.0))
16 .header(Header::new(20.0, |_ctx| {
17 Text::new("Jahresbericht 2026 \u{2014} Muster GmbH").size(9.0).into()
18 }))
19 .header_visible_from(2)
20 .footer(Footer::new(20.0, |ctx| {
21 Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages))
22 .size(9.0)
23 .align(Align::Center)
24 .into()
25 }))
26 .watermark(Watermark::new("ENTWURF"));
27
28 // --- cover page -----------------------------------------------------
29 doc.add(Spacer::new(220.0));
30 doc.add(Text::new("Jahresbericht 2026").heading1().align(Align::Center));
31 doc.add(Spacer::new(8.0));
32 doc.add(Text::new("Muster GmbH").align(Align::Center));
33 doc.add(Text::new("vorgelegt am 20. August 2026").align(Align::Center));
34 doc.add(Element::PageBreak);
35
36 // --- content ---------------------------------------------------------
37 doc.add(Text::new("1. Zusammenfassung").heading1());
38 doc.add(Text::new(
39 "Das Geschäftsjahr 2026 war geprägt von stabilem Wachstum in allen \
40 Kernbereichen. Die folgenden Abschnitte fassen die wichtigsten \
41 Kennzahlen und Entwicklungen zusammen.",
42 ));
43 doc.add(Spacer::new(12.0));
44
45 doc.add(Text::new("2. Wichtigste Ereignisse").heading2());
46 doc.add(
47 List::new()
48 .bullet(Text::new("Markteinführung des neuen Produkts im zweiten Quartal"))
49 .bullet(Text::new("Erweiterung des Teams um 12 neue Mitarbeitende"))
50 .bullet(Text::new("Eröffnung eines zweiten Standorts")),
51 );
52 doc.add(Spacer::new(12.0));
53
54 doc.add(Text::new("3. Zeitplan").heading2());
55 doc.add(
56 List::new()
57 .numbered(Text::new("Kickoff und Planung (Januar \u{2013} Februar)"))
58 .numbered(Text::new("Umsetzung Phase 1 (M\u{e4}rz \u{2013} Juni)"))
59 .numbered(Text::new("Umsetzung Phase 2 (Juli \u{2013} Oktober)"))
60 .numbered(Text::new("Abschluss und Auswertung (November \u{2013} Dezember)")),
61 );
62 doc.add(Spacer::new(12.0));
63
64 doc.add(Text::new("3.1 Details").heading3());
65 doc.add(Text::new(
66 "Weitere Details zu den einzelnen Phasen finden sich im Anhang. \
67 Diese Überschrift bleibt dank keep_with_next garantiert mit \
68 diesem Absatz auf derselben Seite zusammen.",
69 ));
70
71 common::write_pdf(&doc, "report.pdf");
72}More examples
examples/invoice.rs (line 58)
23fn main() {
24 let mut items = vec![
25 LineItem {
26 description: "Beratungsleistung Projekt Alpha".to_string(),
27 qty: 8,
28 unit_price_cents: 12_000,
29 },
30 LineItem {
31 description: "Lizenz Software-Paket (jährlich)".to_string(),
32 qty: 1,
33 unit_price_cents: 49_900,
34 },
35 LineItem {
36 description: "Individuelle Anpassung / Customizing".to_string(),
37 qty: 3,
38 unit_price_cents: 15_000,
39 },
40 ];
41 // A few filler positions so the table is guaranteed to span more than
42 // one page, demonstrating the header-repeat-on-split behavior.
43 for i in 1..=25 {
44 items.push(LineItem {
45 description: format!("Zusatzposition {i:02}"),
46 qty: 1,
47 unit_price_cents: 990,
48 });
49 }
50
51 let net_total: i64 = items.iter().map(|i| i.qty as i64 * i.unit_price_cents).sum();
52 let vat_rate = 19;
53 let vat_total = net_total * vat_rate / 100;
54 let gross_total = net_total + vat_total;
55
56 let top_margin = 15.0 * MM;
57 let mut doc = Document::new(PageFormat::A4)
58 .margin(Margin::symmetric(20.0 * MM, top_margin))
59 .footer(Footer::new(30.0, |ctx| {
60 Column::new()
61 .gap(2.0)
62 .child(Line::new())
63 .child(
64 Row::new()
65 .gap(20.0)
66 .child(Text::new("Musterbank · IBAN DE12 3456 7890 1234 5678 90 · BIC MUSTDEFF").size(8.0))
67 .child(Text::new("USt-IdNr. DE123456789").size(8.0).flex(1.0)),
68 )
69 .child(Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages)).size(8.0))
70 .into()
71 }));
72
73 // --- DIN 5008 Form A window-envelope address block ----------------
74 // Window starts ~45mm from the top, ~20mm from the left
75 // (`plan/02-elementcatalog-and-features.md`); ~85x40mm matches a
76 // typical C6/5-long window envelope opening. Position/size are a
77 // documented convention for this recipe, not parsed from any norm
78 // document — a caller with different envelope stock adjusts these.
79 doc.add(Spacer::new(45.0 * MM - top_margin));
80 doc.add(
81 Column::new()
82 .gap(2.0)
83 .width(85.0 * MM)
84 .height(40.0 * MM)
85 .child(Text::new("Muster GmbH · Musterstraße 1 · 12345 Musterstadt").size(7.0))
86 .child(Spacer::new(8.0))
87 .child(Text::new("Empfänger GmbH"))
88 .child(Text::new("Frau Erika Mustermann"))
89 .child(Text::new("Beispielweg 42"))
90 .child(Text::new("54321 Beispielstadt")),
91 );
92 doc.add(Spacer::new(10.0 * MM));
93
94 doc.add(Text::new("Rechnung").heading1());
95 doc.add(Text::new(
96 "Rechnungsnummer: RE-2026-0142 Rechnungsdatum: 20.08.2026 Leistungsdatum: 20.08.2026",
97 ));
98 doc.add(Spacer::new(10.0));
99
100 doc.add(
101 Table::new()
102 .columns([
103 TableColumn::flex(1.0),
104 TableColumn::fixed(50.0).align(Align::End),
105 TableColumn::fixed(65.0).align(Align::End),
106 TableColumn::fixed(70.0).align(Align::End),
107 ])
108 .header(["Beschreibung", "Menge", "Einzelpreis", "Gesamt"])
109 .striped(Color::rgb(0xF5, 0xF5, 0xF5))
110 .rows(items.iter().map(|item| {
111 let total = item.qty as i64 * item.unit_price_cents;
112 vec![
113 Element::from(item.description.as_str()),
114 Element::from(item.qty.to_string()),
115 Element::from(format_currency_de(item.unit_price_cents)),
116 Element::from(format_currency_de(total)),
117 ]
118 })),
119 );
120
121 doc.add(Spacer::new(14.0));
122
123 // --- summary block (Netto/USt/Brutto), right-aligned --------------
124 // Recipe (`plan/02-elementcatalog-and-features.md`): an outer, full-
125 // width auto Column with `.align(Align::End)` positions the fixed-
126 // width (200pt) inner summary Column at the right edge; within it,
127 // each label gets `.flex(1.0)` to push its value to that block's own
128 // right edge. No special "summary block" element needed.
129 doc.add(
130 Column::new()
131 .align(Align::End)
132 .child(Column::new().gap(2.0).width(200.0).children(vec![
133 Element::from(
134 Row::new()
135 .child(Text::new("Nettosumme").flex(1.0))
136 .child(Text::new(format_currency_de(net_total))),
137 ),
138 Element::from(
139 Row::new()
140 .child(Text::new(format!("zzgl. {vat_rate}% USt.")).flex(1.0))
141 .child(Text::new(format_currency_de(vat_total))),
142 ),
143 Element::from(Line::new()),
144 Element::from(
145 Row::new()
146 .child(Text::new("Gesamtbetrag").bold().flex(1.0))
147 .child(Text::new(format_currency_de(gross_total)).bold()),
148 ),
149 ])),
150 );
151
152 common::write_pdf(&doc, "invoice.pdf");
153}Sourcepub fn header(self, header: Header) -> Document
pub fn header(self, header: Header) -> Document
Examples found in repository?
examples/report.rs (lines 16-18)
13fn main() {
14 let mut doc = Document::new(PageFormat::A4)
15 .margin(Margin::symmetric(56.0, 56.0))
16 .header(Header::new(20.0, |_ctx| {
17 Text::new("Jahresbericht 2026 \u{2014} Muster GmbH").size(9.0).into()
18 }))
19 .header_visible_from(2)
20 .footer(Footer::new(20.0, |ctx| {
21 Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages))
22 .size(9.0)
23 .align(Align::Center)
24 .into()
25 }))
26 .watermark(Watermark::new("ENTWURF"));
27
28 // --- cover page -----------------------------------------------------
29 doc.add(Spacer::new(220.0));
30 doc.add(Text::new("Jahresbericht 2026").heading1().align(Align::Center));
31 doc.add(Spacer::new(8.0));
32 doc.add(Text::new("Muster GmbH").align(Align::Center));
33 doc.add(Text::new("vorgelegt am 20. August 2026").align(Align::Center));
34 doc.add(Element::PageBreak);
35
36 // --- content ---------------------------------------------------------
37 doc.add(Text::new("1. Zusammenfassung").heading1());
38 doc.add(Text::new(
39 "Das Geschäftsjahr 2026 war geprägt von stabilem Wachstum in allen \
40 Kernbereichen. Die folgenden Abschnitte fassen die wichtigsten \
41 Kennzahlen und Entwicklungen zusammen.",
42 ));
43 doc.add(Spacer::new(12.0));
44
45 doc.add(Text::new("2. Wichtigste Ereignisse").heading2());
46 doc.add(
47 List::new()
48 .bullet(Text::new("Markteinführung des neuen Produkts im zweiten Quartal"))
49 .bullet(Text::new("Erweiterung des Teams um 12 neue Mitarbeitende"))
50 .bullet(Text::new("Eröffnung eines zweiten Standorts")),
51 );
52 doc.add(Spacer::new(12.0));
53
54 doc.add(Text::new("3. Zeitplan").heading2());
55 doc.add(
56 List::new()
57 .numbered(Text::new("Kickoff und Planung (Januar \u{2013} Februar)"))
58 .numbered(Text::new("Umsetzung Phase 1 (M\u{e4}rz \u{2013} Juni)"))
59 .numbered(Text::new("Umsetzung Phase 2 (Juli \u{2013} Oktober)"))
60 .numbered(Text::new("Abschluss und Auswertung (November \u{2013} Dezember)")),
61 );
62 doc.add(Spacer::new(12.0));
63
64 doc.add(Text::new("3.1 Details").heading3());
65 doc.add(Text::new(
66 "Weitere Details zu den einzelnen Phasen finden sich im Anhang. \
67 Diese Überschrift bleibt dank keep_with_next garantiert mit \
68 diesem Absatz auf derselben Seite zusammen.",
69 ));
70
71 common::write_pdf(&doc, "report.pdf");
72}Examples found in repository?
examples/report.rs (lines 20-25)
13fn main() {
14 let mut doc = Document::new(PageFormat::A4)
15 .margin(Margin::symmetric(56.0, 56.0))
16 .header(Header::new(20.0, |_ctx| {
17 Text::new("Jahresbericht 2026 \u{2014} Muster GmbH").size(9.0).into()
18 }))
19 .header_visible_from(2)
20 .footer(Footer::new(20.0, |ctx| {
21 Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages))
22 .size(9.0)
23 .align(Align::Center)
24 .into()
25 }))
26 .watermark(Watermark::new("ENTWURF"));
27
28 // --- cover page -----------------------------------------------------
29 doc.add(Spacer::new(220.0));
30 doc.add(Text::new("Jahresbericht 2026").heading1().align(Align::Center));
31 doc.add(Spacer::new(8.0));
32 doc.add(Text::new("Muster GmbH").align(Align::Center));
33 doc.add(Text::new("vorgelegt am 20. August 2026").align(Align::Center));
34 doc.add(Element::PageBreak);
35
36 // --- content ---------------------------------------------------------
37 doc.add(Text::new("1. Zusammenfassung").heading1());
38 doc.add(Text::new(
39 "Das Geschäftsjahr 2026 war geprägt von stabilem Wachstum in allen \
40 Kernbereichen. Die folgenden Abschnitte fassen die wichtigsten \
41 Kennzahlen und Entwicklungen zusammen.",
42 ));
43 doc.add(Spacer::new(12.0));
44
45 doc.add(Text::new("2. Wichtigste Ereignisse").heading2());
46 doc.add(
47 List::new()
48 .bullet(Text::new("Markteinführung des neuen Produkts im zweiten Quartal"))
49 .bullet(Text::new("Erweiterung des Teams um 12 neue Mitarbeitende"))
50 .bullet(Text::new("Eröffnung eines zweiten Standorts")),
51 );
52 doc.add(Spacer::new(12.0));
53
54 doc.add(Text::new("3. Zeitplan").heading2());
55 doc.add(
56 List::new()
57 .numbered(Text::new("Kickoff und Planung (Januar \u{2013} Februar)"))
58 .numbered(Text::new("Umsetzung Phase 1 (M\u{e4}rz \u{2013} Juni)"))
59 .numbered(Text::new("Umsetzung Phase 2 (Juli \u{2013} Oktober)"))
60 .numbered(Text::new("Abschluss und Auswertung (November \u{2013} Dezember)")),
61 );
62 doc.add(Spacer::new(12.0));
63
64 doc.add(Text::new("3.1 Details").heading3());
65 doc.add(Text::new(
66 "Weitere Details zu den einzelnen Phasen finden sich im Anhang. \
67 Diese Überschrift bleibt dank keep_with_next garantiert mit \
68 diesem Absatz auf derselben Seite zusammen.",
69 ));
70
71 common::write_pdf(&doc, "report.pdf");
72}More examples
examples/invoice.rs (lines 59-71)
23fn main() {
24 let mut items = vec![
25 LineItem {
26 description: "Beratungsleistung Projekt Alpha".to_string(),
27 qty: 8,
28 unit_price_cents: 12_000,
29 },
30 LineItem {
31 description: "Lizenz Software-Paket (jährlich)".to_string(),
32 qty: 1,
33 unit_price_cents: 49_900,
34 },
35 LineItem {
36 description: "Individuelle Anpassung / Customizing".to_string(),
37 qty: 3,
38 unit_price_cents: 15_000,
39 },
40 ];
41 // A few filler positions so the table is guaranteed to span more than
42 // one page, demonstrating the header-repeat-on-split behavior.
43 for i in 1..=25 {
44 items.push(LineItem {
45 description: format!("Zusatzposition {i:02}"),
46 qty: 1,
47 unit_price_cents: 990,
48 });
49 }
50
51 let net_total: i64 = items.iter().map(|i| i.qty as i64 * i.unit_price_cents).sum();
52 let vat_rate = 19;
53 let vat_total = net_total * vat_rate / 100;
54 let gross_total = net_total + vat_total;
55
56 let top_margin = 15.0 * MM;
57 let mut doc = Document::new(PageFormat::A4)
58 .margin(Margin::symmetric(20.0 * MM, top_margin))
59 .footer(Footer::new(30.0, |ctx| {
60 Column::new()
61 .gap(2.0)
62 .child(Line::new())
63 .child(
64 Row::new()
65 .gap(20.0)
66 .child(Text::new("Musterbank · IBAN DE12 3456 7890 1234 5678 90 · BIC MUSTDEFF").size(8.0))
67 .child(Text::new("USt-IdNr. DE123456789").size(8.0).flex(1.0)),
68 )
69 .child(Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages)).size(8.0))
70 .into()
71 }));
72
73 // --- DIN 5008 Form A window-envelope address block ----------------
74 // Window starts ~45mm from the top, ~20mm from the left
75 // (`plan/02-elementcatalog-and-features.md`); ~85x40mm matches a
76 // typical C6/5-long window envelope opening. Position/size are a
77 // documented convention for this recipe, not parsed from any norm
78 // document — a caller with different envelope stock adjusts these.
79 doc.add(Spacer::new(45.0 * MM - top_margin));
80 doc.add(
81 Column::new()
82 .gap(2.0)
83 .width(85.0 * MM)
84 .height(40.0 * MM)
85 .child(Text::new("Muster GmbH · Musterstraße 1 · 12345 Musterstadt").size(7.0))
86 .child(Spacer::new(8.0))
87 .child(Text::new("Empfänger GmbH"))
88 .child(Text::new("Frau Erika Mustermann"))
89 .child(Text::new("Beispielweg 42"))
90 .child(Text::new("54321 Beispielstadt")),
91 );
92 doc.add(Spacer::new(10.0 * MM));
93
94 doc.add(Text::new("Rechnung").heading1());
95 doc.add(Text::new(
96 "Rechnungsnummer: RE-2026-0142 Rechnungsdatum: 20.08.2026 Leistungsdatum: 20.08.2026",
97 ));
98 doc.add(Spacer::new(10.0));
99
100 doc.add(
101 Table::new()
102 .columns([
103 TableColumn::flex(1.0),
104 TableColumn::fixed(50.0).align(Align::End),
105 TableColumn::fixed(65.0).align(Align::End),
106 TableColumn::fixed(70.0).align(Align::End),
107 ])
108 .header(["Beschreibung", "Menge", "Einzelpreis", "Gesamt"])
109 .striped(Color::rgb(0xF5, 0xF5, 0xF5))
110 .rows(items.iter().map(|item| {
111 let total = item.qty as i64 * item.unit_price_cents;
112 vec![
113 Element::from(item.description.as_str()),
114 Element::from(item.qty.to_string()),
115 Element::from(format_currency_de(item.unit_price_cents)),
116 Element::from(format_currency_de(total)),
117 ]
118 })),
119 );
120
121 doc.add(Spacer::new(14.0));
122
123 // --- summary block (Netto/USt/Brutto), right-aligned --------------
124 // Recipe (`plan/02-elementcatalog-and-features.md`): an outer, full-
125 // width auto Column with `.align(Align::End)` positions the fixed-
126 // width (200pt) inner summary Column at the right edge; within it,
127 // each label gets `.flex(1.0)` to push its value to that block's own
128 // right edge. No special "summary block" element needed.
129 doc.add(
130 Column::new()
131 .align(Align::End)
132 .child(Column::new().gap(2.0).width(200.0).children(vec![
133 Element::from(
134 Row::new()
135 .child(Text::new("Nettosumme").flex(1.0))
136 .child(Text::new(format_currency_de(net_total))),
137 ),
138 Element::from(
139 Row::new()
140 .child(Text::new(format!("zzgl. {vat_rate}% USt.")).flex(1.0))
141 .child(Text::new(format_currency_de(vat_total))),
142 ),
143 Element::from(Line::new()),
144 Element::from(
145 Row::new()
146 .child(Text::new("Gesamtbetrag").bold().flex(1.0))
147 .child(Text::new(format_currency_de(gross_total)).bold()),
148 ),
149 ])),
150 );
151
152 common::write_pdf(&doc, "invoice.pdf");
153}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?
examples/report.rs (line 19)
13fn main() {
14 let mut doc = Document::new(PageFormat::A4)
15 .margin(Margin::symmetric(56.0, 56.0))
16 .header(Header::new(20.0, |_ctx| {
17 Text::new("Jahresbericht 2026 \u{2014} Muster GmbH").size(9.0).into()
18 }))
19 .header_visible_from(2)
20 .footer(Footer::new(20.0, |ctx| {
21 Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages))
22 .size(9.0)
23 .align(Align::Center)
24 .into()
25 }))
26 .watermark(Watermark::new("ENTWURF"));
27
28 // --- cover page -----------------------------------------------------
29 doc.add(Spacer::new(220.0));
30 doc.add(Text::new("Jahresbericht 2026").heading1().align(Align::Center));
31 doc.add(Spacer::new(8.0));
32 doc.add(Text::new("Muster GmbH").align(Align::Center));
33 doc.add(Text::new("vorgelegt am 20. August 2026").align(Align::Center));
34 doc.add(Element::PageBreak);
35
36 // --- content ---------------------------------------------------------
37 doc.add(Text::new("1. Zusammenfassung").heading1());
38 doc.add(Text::new(
39 "Das Geschäftsjahr 2026 war geprägt von stabilem Wachstum in allen \
40 Kernbereichen. Die folgenden Abschnitte fassen die wichtigsten \
41 Kennzahlen und Entwicklungen zusammen.",
42 ));
43 doc.add(Spacer::new(12.0));
44
45 doc.add(Text::new("2. Wichtigste Ereignisse").heading2());
46 doc.add(
47 List::new()
48 .bullet(Text::new("Markteinführung des neuen Produkts im zweiten Quartal"))
49 .bullet(Text::new("Erweiterung des Teams um 12 neue Mitarbeitende"))
50 .bullet(Text::new("Eröffnung eines zweiten Standorts")),
51 );
52 doc.add(Spacer::new(12.0));
53
54 doc.add(Text::new("3. Zeitplan").heading2());
55 doc.add(
56 List::new()
57 .numbered(Text::new("Kickoff und Planung (Januar \u{2013} Februar)"))
58 .numbered(Text::new("Umsetzung Phase 1 (M\u{e4}rz \u{2013} Juni)"))
59 .numbered(Text::new("Umsetzung Phase 2 (Juli \u{2013} Oktober)"))
60 .numbered(Text::new("Abschluss und Auswertung (November \u{2013} Dezember)")),
61 );
62 doc.add(Spacer::new(12.0));
63
64 doc.add(Text::new("3.1 Details").heading3());
65 doc.add(Text::new(
66 "Weitere Details zu den einzelnen Phasen finden sich im Anhang. \
67 Diese Überschrift bleibt dank keep_with_next garantiert mit \
68 diesem Absatz auf derselben Seite zusammen.",
69 ));
70
71 common::write_pdf(&doc, "report.pdf");
72}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?
examples/report.rs (line 26)
13fn main() {
14 let mut doc = Document::new(PageFormat::A4)
15 .margin(Margin::symmetric(56.0, 56.0))
16 .header(Header::new(20.0, |_ctx| {
17 Text::new("Jahresbericht 2026 \u{2014} Muster GmbH").size(9.0).into()
18 }))
19 .header_visible_from(2)
20 .footer(Footer::new(20.0, |ctx| {
21 Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages))
22 .size(9.0)
23 .align(Align::Center)
24 .into()
25 }))
26 .watermark(Watermark::new("ENTWURF"));
27
28 // --- cover page -----------------------------------------------------
29 doc.add(Spacer::new(220.0));
30 doc.add(Text::new("Jahresbericht 2026").heading1().align(Align::Center));
31 doc.add(Spacer::new(8.0));
32 doc.add(Text::new("Muster GmbH").align(Align::Center));
33 doc.add(Text::new("vorgelegt am 20. August 2026").align(Align::Center));
34 doc.add(Element::PageBreak);
35
36 // --- content ---------------------------------------------------------
37 doc.add(Text::new("1. Zusammenfassung").heading1());
38 doc.add(Text::new(
39 "Das Geschäftsjahr 2026 war geprägt von stabilem Wachstum in allen \
40 Kernbereichen. Die folgenden Abschnitte fassen die wichtigsten \
41 Kennzahlen und Entwicklungen zusammen.",
42 ));
43 doc.add(Spacer::new(12.0));
44
45 doc.add(Text::new("2. Wichtigste Ereignisse").heading2());
46 doc.add(
47 List::new()
48 .bullet(Text::new("Markteinführung des neuen Produkts im zweiten Quartal"))
49 .bullet(Text::new("Erweiterung des Teams um 12 neue Mitarbeitende"))
50 .bullet(Text::new("Eröffnung eines zweiten Standorts")),
51 );
52 doc.add(Spacer::new(12.0));
53
54 doc.add(Text::new("3. Zeitplan").heading2());
55 doc.add(
56 List::new()
57 .numbered(Text::new("Kickoff und Planung (Januar \u{2013} Februar)"))
58 .numbered(Text::new("Umsetzung Phase 1 (M\u{e4}rz \u{2013} Juni)"))
59 .numbered(Text::new("Umsetzung Phase 2 (Juli \u{2013} Oktober)"))
60 .numbered(Text::new("Abschluss und Auswertung (November \u{2013} Dezember)")),
61 );
62 doc.add(Spacer::new(12.0));
63
64 doc.add(Text::new("3.1 Details").heading3());
65 doc.add(Text::new(
66 "Weitere Details zu den einzelnen Phasen finden sich im Anhang. \
67 Diese Überschrift bleibt dank keep_with_next garantiert mit \
68 diesem Absatz auf derselben Seite zusammen.",
69 ));
70
71 common::write_pdf(&doc, "report.pdf");
72}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?
examples/report.rs (line 29)
13fn main() {
14 let mut doc = Document::new(PageFormat::A4)
15 .margin(Margin::symmetric(56.0, 56.0))
16 .header(Header::new(20.0, |_ctx| {
17 Text::new("Jahresbericht 2026 \u{2014} Muster GmbH").size(9.0).into()
18 }))
19 .header_visible_from(2)
20 .footer(Footer::new(20.0, |ctx| {
21 Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages))
22 .size(9.0)
23 .align(Align::Center)
24 .into()
25 }))
26 .watermark(Watermark::new("ENTWURF"));
27
28 // --- cover page -----------------------------------------------------
29 doc.add(Spacer::new(220.0));
30 doc.add(Text::new("Jahresbericht 2026").heading1().align(Align::Center));
31 doc.add(Spacer::new(8.0));
32 doc.add(Text::new("Muster GmbH").align(Align::Center));
33 doc.add(Text::new("vorgelegt am 20. August 2026").align(Align::Center));
34 doc.add(Element::PageBreak);
35
36 // --- content ---------------------------------------------------------
37 doc.add(Text::new("1. Zusammenfassung").heading1());
38 doc.add(Text::new(
39 "Das Geschäftsjahr 2026 war geprägt von stabilem Wachstum in allen \
40 Kernbereichen. Die folgenden Abschnitte fassen die wichtigsten \
41 Kennzahlen und Entwicklungen zusammen.",
42 ));
43 doc.add(Spacer::new(12.0));
44
45 doc.add(Text::new("2. Wichtigste Ereignisse").heading2());
46 doc.add(
47 List::new()
48 .bullet(Text::new("Markteinführung des neuen Produkts im zweiten Quartal"))
49 .bullet(Text::new("Erweiterung des Teams um 12 neue Mitarbeitende"))
50 .bullet(Text::new("Eröffnung eines zweiten Standorts")),
51 );
52 doc.add(Spacer::new(12.0));
53
54 doc.add(Text::new("3. Zeitplan").heading2());
55 doc.add(
56 List::new()
57 .numbered(Text::new("Kickoff und Planung (Januar \u{2013} Februar)"))
58 .numbered(Text::new("Umsetzung Phase 1 (M\u{e4}rz \u{2013} Juni)"))
59 .numbered(Text::new("Umsetzung Phase 2 (Juli \u{2013} Oktober)"))
60 .numbered(Text::new("Abschluss und Auswertung (November \u{2013} Dezember)")),
61 );
62 doc.add(Spacer::new(12.0));
63
64 doc.add(Text::new("3.1 Details").heading3());
65 doc.add(Text::new(
66 "Weitere Details zu den einzelnen Phasen finden sich im Anhang. \
67 Diese Überschrift bleibt dank keep_with_next garantiert mit \
68 diesem Absatz auf derselben Seite zusammen.",
69 ));
70
71 common::write_pdf(&doc, "report.pdf");
72}More examples
examples/invoice.rs (line 79)
23fn main() {
24 let mut items = vec![
25 LineItem {
26 description: "Beratungsleistung Projekt Alpha".to_string(),
27 qty: 8,
28 unit_price_cents: 12_000,
29 },
30 LineItem {
31 description: "Lizenz Software-Paket (jährlich)".to_string(),
32 qty: 1,
33 unit_price_cents: 49_900,
34 },
35 LineItem {
36 description: "Individuelle Anpassung / Customizing".to_string(),
37 qty: 3,
38 unit_price_cents: 15_000,
39 },
40 ];
41 // A few filler positions so the table is guaranteed to span more than
42 // one page, demonstrating the header-repeat-on-split behavior.
43 for i in 1..=25 {
44 items.push(LineItem {
45 description: format!("Zusatzposition {i:02}"),
46 qty: 1,
47 unit_price_cents: 990,
48 });
49 }
50
51 let net_total: i64 = items.iter().map(|i| i.qty as i64 * i.unit_price_cents).sum();
52 let vat_rate = 19;
53 let vat_total = net_total * vat_rate / 100;
54 let gross_total = net_total + vat_total;
55
56 let top_margin = 15.0 * MM;
57 let mut doc = Document::new(PageFormat::A4)
58 .margin(Margin::symmetric(20.0 * MM, top_margin))
59 .footer(Footer::new(30.0, |ctx| {
60 Column::new()
61 .gap(2.0)
62 .child(Line::new())
63 .child(
64 Row::new()
65 .gap(20.0)
66 .child(Text::new("Musterbank · IBAN DE12 3456 7890 1234 5678 90 · BIC MUSTDEFF").size(8.0))
67 .child(Text::new("USt-IdNr. DE123456789").size(8.0).flex(1.0)),
68 )
69 .child(Text::new(format!("Seite {} von {}", ctx.page, ctx.total_pages)).size(8.0))
70 .into()
71 }));
72
73 // --- DIN 5008 Form A window-envelope address block ----------------
74 // Window starts ~45mm from the top, ~20mm from the left
75 // (`plan/02-elementcatalog-and-features.md`); ~85x40mm matches a
76 // typical C6/5-long window envelope opening. Position/size are a
77 // documented convention for this recipe, not parsed from any norm
78 // document — a caller with different envelope stock adjusts these.
79 doc.add(Spacer::new(45.0 * MM - top_margin));
80 doc.add(
81 Column::new()
82 .gap(2.0)
83 .width(85.0 * MM)
84 .height(40.0 * MM)
85 .child(Text::new("Muster GmbH · Musterstraße 1 · 12345 Musterstadt").size(7.0))
86 .child(Spacer::new(8.0))
87 .child(Text::new("Empfänger GmbH"))
88 .child(Text::new("Frau Erika Mustermann"))
89 .child(Text::new("Beispielweg 42"))
90 .child(Text::new("54321 Beispielstadt")),
91 );
92 doc.add(Spacer::new(10.0 * MM));
93
94 doc.add(Text::new("Rechnung").heading1());
95 doc.add(Text::new(
96 "Rechnungsnummer: RE-2026-0142 Rechnungsdatum: 20.08.2026 Leistungsdatum: 20.08.2026",
97 ));
98 doc.add(Spacer::new(10.0));
99
100 doc.add(
101 Table::new()
102 .columns([
103 TableColumn::flex(1.0),
104 TableColumn::fixed(50.0).align(Align::End),
105 TableColumn::fixed(65.0).align(Align::End),
106 TableColumn::fixed(70.0).align(Align::End),
107 ])
108 .header(["Beschreibung", "Menge", "Einzelpreis", "Gesamt"])
109 .striped(Color::rgb(0xF5, 0xF5, 0xF5))
110 .rows(items.iter().map(|item| {
111 let total = item.qty as i64 * item.unit_price_cents;
112 vec![
113 Element::from(item.description.as_str()),
114 Element::from(item.qty.to_string()),
115 Element::from(format_currency_de(item.unit_price_cents)),
116 Element::from(format_currency_de(total)),
117 ]
118 })),
119 );
120
121 doc.add(Spacer::new(14.0));
122
123 // --- summary block (Netto/USt/Brutto), right-aligned --------------
124 // Recipe (`plan/02-elementcatalog-and-features.md`): an outer, full-
125 // width auto Column with `.align(Align::End)` positions the fixed-
126 // width (200pt) inner summary Column at the right edge; within it,
127 // each label gets `.flex(1.0)` to push its value to that block's own
128 // right edge. No special "summary block" element needed.
129 doc.add(
130 Column::new()
131 .align(Align::End)
132 .child(Column::new().gap(2.0).width(200.0).children(vec![
133 Element::from(
134 Row::new()
135 .child(Text::new("Nettosumme").flex(1.0))
136 .child(Text::new(format_currency_de(net_total))),
137 ),
138 Element::from(
139 Row::new()
140 .child(Text::new(format!("zzgl. {vat_rate}% USt.")).flex(1.0))
141 .child(Text::new(format_currency_de(vat_total))),
142 ),
143 Element::from(Line::new()),
144 Element::from(
145 Row::new()
146 .child(Text::new("Gesamtbetrag").bold().flex(1.0))
147 .child(Text::new(format_currency_de(gross_total)).bold()),
148 ),
149 ])),
150 );
151
152 common::write_pdf(&doc, "invoice.pdf");
153}Trait Implementations§
Source§impl DocumentExt for Document
impl DocumentExt for Document
fn render(&self) -> Result<Vec<u8>, RenderError>
fn render_with_diagnostics( &self, ) -> Result<(Vec<u8>, Vec<LayoutWarning>), RenderError>
fn render_with_fonts( &self, fonts: &FontRegistry, ) -> Result<Vec<u8>, RenderError>
fn render_with_fonts_and_diagnostics( &self, fonts: &FontRegistry, ) -> Result<(Vec<u8>, Vec<LayoutWarning>), RenderError>
Auto Trait Implementations§
impl !RefUnwindSafe for Document
impl !Send for Document
impl !Sync for Document
impl !UnwindSafe for Document
impl Freeze for Document
impl Unpin for Document
impl UnsafeUnpin for Document
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more