3 elements::{Paragraph, Break, Text, TableLayout, FrameCellDecorator},
4 style::{Style, StyledString},
13 use crate::data::{Invoice, Record, Business};
14 use super::elements::*;
16 const FONT_DIRS: &[&str] = &[
17 "/usr/share/fonts/liberation",
18 "/usr/share/fonts/Liberation",
19 "/usr/local/share/fonts/liberation",
20 "/usr/local/share/fonts/Liberation",
22 const DEFAULT_FONT_NAME: &str = "LiberationSans";
25 [$($e:expr),* $(,)?] => {
27 let font_dir = FONT_DIRS
29 .find(|path| Path::new(path).exists())
31 let default_font = fonts::from_files(font_dir, DEFAULT_FONT_NAME, None)
33 let mut doc = Document::new(default_font);
43 {($($width:expr),* $(,)?) => [ $($e:expr),* $(,)? ]} => {
45 let mut table = TableLayout::new(vec![$($width),*]);
46 $(table.push_row($e).unwrap();)*
52 fn text(text: impl Into<String>, style: Style) -> Text {
53 Text::new(StyledString::new(text, style))
56 pub fn render_to_file(invoice: &Invoice, path: &Path) -> Result<(), Error> {
57 let doc = render(invoice);
58 doc.render_to_file(path)
61 pub fn render(invoice: &Invoice) -> Document {
62 let biz = &invoice.business;
63 let cus = &invoice.customer;
65 let s8 = Style::new().with_font_size(8);
66 let s10 = Style::new().with_font_size(10);
67 let s12 = Style::new().with_font_size(12);
68 let s14 = Style::new().with_font_size(14);
69 let s16 = Style::new().with_font_size(16);
70 let s28 = Style::new().with_font_size(28);
73 Paragraph::new(&biz.name).aligned(Alignment::Center).styled(s28.bold()),
75 UnderlinedText::new(format!("{}, {}, {}", biz.owner_name, biz.address, biz.zip_city), s8),
77 text(&cus.address, s12),
78 text(&cus.zip_city, s12),
80 Paragraph::new(format!("Datum: {}", invoice.date())).aligned(Alignment::Right),
81 text(format!("Kunde {}", cus.id), s12),
82 text(format!("Rechnung {}", invoice.id), s16.bold()),
84 table(&invoice.records, s14.bold(), s12),
85 table2(&invoice.records, s12.bold()),
87 text("zahlbar nach Rechnungserhalt.", s10),
88 text("Kein Ausweis von Umsatzsteuer,", s10),
89 text("da Kleinunternehmer gemäß $ 19 USTG.", s10),
91 text("Vielen Dank für Ihren Auftrag und Ihr Vertrauen.", s10),
92 Break::new(18 - invoice.records.len() as i32),
97 doc.set_title(format!("Invoice {} for {}", invoice.id, cus.id));
98 doc.set_minimal_conformance();
99 doc.set_line_spacing(1.25);
100 doc.set_paper_size(Size::new(210, 297)); // A4
102 let mut decorator = SimplePageDecorator::new();
103 decorator.set_margins(10);
104 doc.set_page_decorator(decorator);
109 fn table(records: &[Record], header: Style, normal: Style) -> TableLayout {
110 let header = |text: &str, align: Alignment| {
111 Box::new(Paragraph::new(text).aligned(align).styled(header))
114 let row = |descr: &str, date: String, n: f32, c: f32| -> Vec<Box<dyn Element>> {
115 let f = |text: StyledString| {
116 let p = Paragraph::new(text)
117 .aligned(Alignment::Center)
122 Box::new(Paragraph::new(format!(" {descr}")).styled(normal)),
124 f(format!("{n:.2}").into()),
125 f(format!("{c:.2} EUR").into()),
126 f(format!("{:.2} EUR", n * c).into()),
130 let mut table = table! {
131 (15, 4, 3, 5, 5) => [
133 header(" Beschreibung", Alignment::Left),
134 header("Datum", Alignment::Center),
135 header("Anzahl", Alignment::Center),
136 header("Preis", Alignment::Center),
137 header("Gesamt", Alignment::Center),
143 table.push_row(row(&r.description, r.date(), r.count, r.price)).unwrap();
146 table.set_cell_decorator(FrameCellDecorator::new(true, true, false));
150 fn table2(records: &[Record], style: Style) -> TableLayout {
151 let total: f32 = records
153 .map(|r| r.count * r.price)
156 let mut table = table! {
159 Box::new(Paragraph::new(" Netto").styled(style)),
160 Box::new(Paragraph::new(format!("{total:.2} EUR ")).aligned(Alignment::Right).styled(style)),
165 table.set_cell_decorator(FrameCellDecorator::new(false, true, false));
169 fn footer(biz: &Business, style: Style) -> TableLayout {
170 let f = |text, align| {
171 Box::new(Paragraph::new(text).aligned(align).styled(style))
173 let row = |a: &str, b: &str, c: &str| -> Vec<Box<dyn Element>> {
175 f(a.to_string(), Alignment::Left),
176 f(b.to_string(), Alignment::Center),
177 f(c.to_string(), Alignment::Right)
182 row(&biz.owner_name, "", &biz.iban),
183 row(&biz.address, &biz.ustid, &biz.bic),
184 row(&biz.zip_city, "", &biz.bank),
185 row(&biz.email, "", ""),