pub struct Text {
pub content: String,
pub style: TextStyle,
pub url: Option<String>,
pub anchor: Option<String>,
pub link_to: Option<String>,
pub outline_level: Option<u8>,
pub role: Option<ThemeRole>,
pub spans: Option<Box<Vec<Span>>>,
pub hyphenate: Option<HyphenationLanguage>,
pub common: Common,
}Fields§
§content: String§style: TextStyle§url: Option<String>§anchor: Option<String>Registers this element as an internal jump target other Text
elements can point at via .link_to(name) — analogous to an HTML
id. Independent of url/link_to: an element can be a target,
a source, both, or neither.
link_to: Option<String>Internal counterpart to url: jumps to whatever element in this
document called .anchor(name) with the same name, instead of an
external URI. If both url and link_to are set, url wins.
outline_level: Option<u8>Set by .heading1()/.heading2()/.heading3() (1/2/3), or
explicitly via .outline_level(n) for text that should appear in
the PDF bookmark sidebar without being an actual heading preset.
None (the default for plain Text) means “not a bookmark”.
role: Option<ThemeRole>Theme eligibility (Document::theme(..), ADR/issue #16): Some
means “resolve this element’s style from the theme’s matching role
the next time it’s added to a themed Document.” Text::new()
defaults this to Some(ThemeRole::Body); every style-mutating
method below (.size(), .bold(), .color(), …) clears it back
to None since the caller has taken over styling by hand. The
.heading1()/.heading2()/.heading3()/.caption()/.muted()/
.table_header() presets re-set a specific role afterwards.
spans: Option<Box<Vec<Span>>>Set only by Text::rich(..) (issue #11) — a sequence of
independently-styled runs instead of one style for the whole
content. When Some, layout/render use this instead of
content/style (content is still populated, as the spans’
text concatenated, so anything that only reads content — e.g. a
future plain-text export — degrades to unstyled text instead of
seeing nothing). Rich text doesn’t (yet) support
url/anchor/link_to/outline_level/Align::Justify — plain
Text remains the only way to get those.
Boxed, not Option<Vec<Span>> directly: Text is the payload of
Element’s largest variant (in turn embedded in LayoutResult
and every Row/Column’s children: Vec<Element>), and a bare
Vec here would cost every plain Text (the overwhelming
majority, where this field is always None) the full 24 bytes;
Option<Box<Vec<Span>>> costs 8.
hyphenate: Option<HyphenationLanguage>Set by .hyphenate(lang) (issue #13, Stage 2): before wrapping,
each word gets Knuth-Liang break points inserted as soft hyphens
(U+00AD) for lang, on top of Stage 1’s always-on soft-hyphen
support (an author-inserted U+00AD works with or without this).
None (the default) means “only break where the author put a
soft hyphen, if anywhere.” Only consulted for plain Text; a
Text::rich(..) ignores it, same as Align::Justify.
Requires the hyphenation cargo feature — with it disabled this
silently has no effect, since skipping automatic hyphenation only
changes where a line wraps, not what the text says.
common: CommonImplementations§
Source§impl Text
impl Text
Sourcepub fn new(content: impl Into<String>) -> Text
pub fn new(content: impl Into<String>) -> Text
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 rich(spans: impl IntoIterator<Item = Span>) -> Text
pub fn rich(spans: impl IntoIterator<Item = Span>) -> Text
A Text made of independently-styled Spans instead of one
uniform style — the paragraph still wraps and paginates as a
single unit, word boundaries and line breaks span across spans
freely, and mixed sizes on the same line share one baseline (see
lightweight-pdf-layout::text::wrap_spans).
pub fn url(self, url: impl Into<String>) -> Text
pub fn anchor(self, name: impl Into<String>) -> Text
pub fn link_to(self, anchor: impl Into<String>) -> Text
pub fn outline_level(self, level: u8) -> Text
Sourcepub fn hyphenate(self, lang: HyphenationLanguage) -> Text
pub fn hyphenate(self, lang: HyphenationLanguage) -> Text
Opts this Text into automatic (Knuth-Liang) hyphenation for
lang — see the hyphenate field’s doc comment for scope and the
hyphenation cargo feature it requires.
Sourcepub fn role(self, role: ThemeRole) -> Text
pub fn role(self, role: ThemeRole) -> Text
Opts a Text into theme resolution under role without going
through one of the named presets — e.g. a custom role-like use
that isn’t .heading1()/.caption()/etc.
Sourcepub fn size(self, size: f32) -> Text
pub fn size(self, size: f32) -> Text
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 bold(self) -> Text
pub fn bold(self) -> Text
Examples found in repository?
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}pub fn italic(self) -> Text
pub fn bold_italic(self) -> Text
pub fn font(self, font: FontKey) -> Text
pub fn color(self, color: Color) -> Text
Sourcepub fn align(self, align: Align) -> Text
pub fn align(self, align: Align) -> Text
Unlike the other style setters, .align() does not clear
role: alignment is a positioning choice independent of which
named style a Text resolves from (.heading1().align(Center)
should stay theme-eligible as a heading, just centered) — see
theme::apply_theme, which resolves every role field except
align and always leaves whatever .align() set alone.
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}pub fn line_height(self, line_height: f32) -> Text
Sourcepub fn heading1(self) -> Text
pub fn heading1(self) -> Text
Heading presets (Phase 6, plan/02-elementcatalog-and-features.md):
thin wrappers over .size()/.bold(), additionally setting
keep_with_next so a heading never ends up alone at the bottom of
a page without its following content
(plan/05-overflow-and-robustness.md Grundprinzip 9), and
outline_level so the PDF bookmark sidebar can be derived from the
heading hierarchy without a separate API (.outline_level(n)
overrides this for the rare case the derivation doesn’t fit).
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 heading2(self) -> Text
pub fn heading2(self) -> Text
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 heading3(self) -> Text
pub fn heading3(self) -> Text
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 caption(self) -> Text
pub fn caption(self) -> Text
Theme::caption preset — a smaller, muted-gray label (e.g. under
an image, or a secondary line under a heading).
Sourcepub fn muted(self) -> Text
pub fn muted(self) -> Text
Theme::muted preset — body-sized text in the same muted gray as
.caption(), for de-emphasized inline text rather than a label.
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 table_header(self) -> Text
pub fn table_header(self) -> Text
Theme::table_header preset. Table::header([...]) cells built
from plain strings pick this role up automatically (see
theme::apply_theme); use this directly for a Text header cell
built by hand, or for header-like text outside a Table.
pub fn width(self, width: f32) -> Text
pub fn height(self, height: f32) -> Text
Sourcepub fn flex(self, factor: f32) -> Text
pub fn flex(self, factor: f32) -> Text
Examples found in repository?
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}pub fn padding(self, padding: f32) -> Text
pub fn corner_radius(self, radius: f32) -> Text
pub fn overflow(self, overflow: Overflow) -> Text
pub fn background(self, color: Color) -> Text
pub fn border(self, border: Border) -> Text
Sourcepub fn keep_with_next(self) -> Text
pub fn keep_with_next(self) -> Text
See plan/05-overflow-and-robustness.md Grundprinzip 9: only
placed on a page if the following sibling also still fits.