pub struct Column {
pub children: Vec<Element>,
pub gap: f32,
pub align: Align,
pub common: Common,
}Fields§
§children: Vec<Element>§gap: f32§align: Align§common: CommonImplementations§
Source§impl Column
impl Column
Sourcepub fn new() -> Column
pub fn new() -> Column
Examples found in repository?
examples/invoice.rs (line 60)
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 child(self, child: impl Into<Element>) -> Column
pub fn child(self, child: impl Into<Element>) -> Column
Examples found in repository?
examples/invoice.rs (line 62)
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 children(
self,
children: impl IntoIterator<Item = impl Into<Element>>,
) -> Column
pub fn children( self, children: impl IntoIterator<Item = impl Into<Element>>, ) -> Column
Examples found in repository?
examples/invoice.rs (lines 132-149)
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 gap(self, gap: f32) -> Column
pub fn gap(self, gap: f32) -> Column
Examples found in repository?
examples/invoice.rs (line 61)
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 align(self, align: Align) -> Column
pub fn align(self, align: Align) -> Column
Examples found in repository?
examples/invoice.rs (line 131)
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 width(self, width: f32) -> Column
pub fn width(self, width: f32) -> Column
Examples found in repository?
examples/invoice.rs (line 83)
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 height(self, height: f32) -> Column
pub fn height(self, height: f32) -> Column
Examples found in repository?
examples/invoice.rs (line 84)
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}pub fn flex(self, factor: f32) -> Column
pub fn padding(self, padding: f32) -> Column
pub fn overflow(self, overflow: Overflow) -> Column
pub fn background(self, color: Color) -> Column
pub fn border(self, border: Border) -> Column
Sourcepub fn keep_with_next(self) -> Column
pub fn keep_with_next(self) -> Column
See plan/05-overflow-and-robustness.md Grundprinzip 9: only
placed on a page if the following sibling also still fits.
Trait Implementations§
Source§impl Layoutable for Column
impl Layoutable for Column
fn measure(&self, ctx: &LayoutCtx<'_>, constraints: Constraints) -> Size
fn layout( &self, ctx: &LayoutCtx<'_>, area: Rect, warnings: &mut Vec<LayoutWarning>, page: usize, ) -> LayoutResult
Auto Trait Implementations§
impl Freeze for Column
impl RefUnwindSafe for Column
impl Send for Column
impl Sync for Column
impl Unpin for Column
impl UnsafeUnpin for Column
impl UnwindSafe for Column
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