Skip to main content

styled_tables/
styled_tables.rs

1//! Styled tables showcase — demonstrates all table formatting options.
2//!
3//! Run with: cargo run --example styled_tables
4
5use std::path::Path;
6
7use rdocx::{BorderStyle, Document, Length, VerticalAlignment};
8
9fn main() {
10    let samples_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
11        .parent()
12        .unwrap()
13        .parent()
14        .unwrap()
15        .join("samples");
16    std::fs::create_dir_all(&samples_dir).unwrap();
17
18    let out = samples_dir.join("styled_tables.docx");
19    generate_styled_tables(&out);
20    println!("  Created: styled_tables.docx");
21    println!("\nDone!");
22}
23
24fn generate_styled_tables(path: &Path) {
25    let mut doc = Document::new();
26    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
27    doc.set_margins(
28        Length::inches(0.75),
29        Length::inches(0.75),
30        Length::inches(0.75),
31        Length::inches(0.75),
32    );
33
34    doc.add_paragraph("Styled Tables Showcase")
35        .style("Heading1");
36
37    doc.add_paragraph("");
38
39    // =========================================================================
40    // 1. Professional report table with alternating rows
41    // =========================================================================
42    doc.add_paragraph("1. Report Table with Alternating Row Colors")
43        .style("Heading2");
44
45    {
46        let mut tbl = doc.add_table(8, 4);
47        tbl = tbl.borders(BorderStyle::Single, 2, "BFBFBF");
48        tbl = tbl.width_pct(100.0);
49
50        // Header row
51        let headers = ["Product", "Q1 Sales", "Q2 Sales", "Growth"];
52        for (col, h) in headers.iter().enumerate() {
53            tbl.cell(0, col).unwrap().shading("2E75B6");
54            tbl.cell(0, col).unwrap().set_text(h);
55        }
56        tbl.row(0).unwrap().header();
57
58        // Data with alternating shading
59        let data = [
60            ["Enterprise Suite", "$245,000", "$312,000", "+27.3%"],
61            ["Professional", "$189,000", "$201,000", "+6.3%"],
62            ["Starter Pack", "$67,000", "$84,500", "+26.1%"],
63            ["Add-ons", "$34,000", "$41,200", "+21.2%"],
64            ["Training", "$22,000", "$28,900", "+31.4%"],
65            ["Support Plans", "$56,000", "$62,300", "+11.3%"],
66        ];
67
68        for (i, row) in data.iter().enumerate() {
69            let row_idx = i + 1;
70            for (col, val) in row.iter().enumerate() {
71                tbl.cell(row_idx, col).unwrap().set_text(val);
72                // Alternate row colors
73                if i % 2 == 0 {
74                    tbl.cell(row_idx, col).unwrap().shading("F2F7FB");
75                }
76            }
77        }
78
79        // Total row
80        tbl.cell(7, 0).unwrap().set_text("TOTAL");
81        tbl.cell(7, 0).unwrap().shading("D6E4F0");
82        tbl.cell(7, 1).unwrap().set_text("$613,000");
83        tbl.cell(7, 1).unwrap().shading("D6E4F0");
84        tbl.cell(7, 2).unwrap().set_text("$729,900");
85        tbl.cell(7, 2).unwrap().shading("D6E4F0");
86        tbl.cell(7, 3).unwrap().set_text("+19.1%");
87        tbl.cell(7, 3).unwrap().shading("D6E4F0");
88    }
89
90    doc.add_paragraph("");
91
92    // =========================================================================
93    // 2. Invoice-style table with merged header
94    // =========================================================================
95    doc.add_paragraph("2. Invoice Table with Merged Header & Row Spans")
96        .style("Heading2");
97
98    {
99        let mut tbl = doc.add_table(7, 4);
100        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
101        tbl = tbl.width_pct(100.0);
102
103        // Merged title row
104        tbl.cell(0, 0).unwrap().set_text("INVOICE #2026-0042");
105        tbl.cell(0, 0).unwrap().grid_span(4);
106        tbl.cell(0, 0).unwrap().shading("1F4E79");
107
108        // Column headers
109        let headers = ["Item", "Description", "Qty", "Amount"];
110        for (col, h) in headers.iter().enumerate() {
111            tbl.cell(1, col).unwrap().set_text(h);
112            tbl.cell(1, col).unwrap().shading("D6E4F0");
113        }
114
115        // Line items
116        tbl.cell(2, 0).unwrap().set_text("LIC-ENT-500");
117        tbl.cell(2, 1)
118            .unwrap()
119            .set_text("Enterprise License (500 seats)");
120        tbl.cell(2, 2).unwrap().set_text("1");
121        tbl.cell(2, 3).unwrap().set_text("$60,000");
122
123        tbl.cell(3, 0).unwrap().set_text("SVC-IMPL");
124        tbl.cell(3, 1).unwrap().set_text("Implementation Services");
125        tbl.cell(3, 2).unwrap().set_text("1");
126        tbl.cell(3, 3).unwrap().set_text("$25,000");
127
128        tbl.cell(4, 0).unwrap().set_text("SVC-TRAIN");
129        tbl.cell(4, 1)
130            .unwrap()
131            .set_text("On-site Training (3 days)");
132        tbl.cell(4, 2).unwrap().set_text("1");
133        tbl.cell(4, 3).unwrap().set_text("$4,500");
134
135        // Subtotal
136        tbl.cell(5, 0).unwrap().set_text("Subtotal");
137        tbl.cell(5, 0).unwrap().grid_span(3);
138        tbl.cell(5, 0).unwrap().shading("F2F2F2");
139        tbl.cell(5, 3).unwrap().set_text("$89,500");
140        tbl.cell(5, 3).unwrap().shading("F2F2F2");
141
142        // Total
143        tbl.cell(6, 0).unwrap().set_text("TOTAL DUE");
144        tbl.cell(6, 0).unwrap().grid_span(3);
145        tbl.cell(6, 0).unwrap().shading("1F4E79");
146        tbl.cell(6, 3).unwrap().set_text("$89,500");
147        tbl.cell(6, 3).unwrap().shading("1F4E79");
148    }
149
150    doc.add_paragraph("");
151
152    // =========================================================================
153    // 3. Specification table with vertical merge
154    // =========================================================================
155    doc.add_paragraph("3. Specification Table with Vertical Merges")
156        .style("Heading2");
157
158    {
159        let mut tbl = doc.add_table(8, 3);
160        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
161        tbl = tbl.width_pct(100.0);
162
163        // Header
164        tbl.cell(0, 0).unwrap().set_text("Category");
165        tbl.cell(0, 0).unwrap().shading("2E75B6");
166        tbl.cell(0, 1).unwrap().set_text("Specification");
167        tbl.cell(0, 1).unwrap().shading("2E75B6");
168        tbl.cell(0, 2).unwrap().set_text("Value");
169        tbl.cell(0, 2).unwrap().shading("2E75B6");
170
171        // "Hardware" spans 3 rows
172        tbl.cell(1, 0).unwrap().set_text("Hardware");
173        tbl.cell(1, 0).unwrap().v_merge_restart();
174        tbl.cell(1, 0).unwrap().shading("E2EFDA");
175        tbl.cell(1, 0)
176            .unwrap()
177            .vertical_alignment(VerticalAlignment::Center);
178        tbl.cell(1, 1).unwrap().set_text("Processor");
179        tbl.cell(1, 2).unwrap().set_text("Intel Xeon E-2388G");
180
181        tbl.cell(2, 0).unwrap().v_merge_continue();
182        tbl.cell(2, 1).unwrap().set_text("Memory");
183        tbl.cell(2, 2).unwrap().set_text("64 GB DDR4 ECC");
184
185        tbl.cell(3, 0).unwrap().v_merge_continue();
186        tbl.cell(3, 1).unwrap().set_text("Storage");
187        tbl.cell(3, 2).unwrap().set_text("2x 1TB NVMe SSD (RAID 1)");
188
189        // "Network" spans 2 rows
190        tbl.cell(4, 0).unwrap().set_text("Network");
191        tbl.cell(4, 0).unwrap().v_merge_restart();
192        tbl.cell(4, 0).unwrap().shading("FCE4D6");
193        tbl.cell(4, 0)
194            .unwrap()
195            .vertical_alignment(VerticalAlignment::Center);
196        tbl.cell(4, 1).unwrap().set_text("Ethernet");
197        tbl.cell(4, 2).unwrap().set_text("4x 10GbE SFP+");
198
199        tbl.cell(5, 0).unwrap().v_merge_continue();
200        tbl.cell(5, 1).unwrap().set_text("Management");
201        tbl.cell(5, 2).unwrap().set_text("1x 1GbE IPMI");
202
203        // "Software" spans 2 rows
204        tbl.cell(6, 0).unwrap().set_text("Software");
205        tbl.cell(6, 0).unwrap().v_merge_restart();
206        tbl.cell(6, 0).unwrap().shading("D6E4F0");
207        tbl.cell(6, 0)
208            .unwrap()
209            .vertical_alignment(VerticalAlignment::Center);
210        tbl.cell(6, 1).unwrap().set_text("Operating System");
211        tbl.cell(6, 2).unwrap().set_text("Ubuntu 24.04 LTS");
212
213        tbl.cell(7, 0).unwrap().v_merge_continue();
214        tbl.cell(7, 1).unwrap().set_text("Monitoring");
215        tbl.cell(7, 2).unwrap().set_text("Prometheus + Grafana");
216    }
217
218    doc.add_paragraph("");
219
220    // =========================================================================
221    // 4. Nested table (table inside a cell)
222    // =========================================================================
223    doc.add_paragraph("4. Nested Table").style("Heading2");
224
225    {
226        let mut tbl = doc.add_table(2, 2);
227        tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
228        tbl = tbl.width_pct(100.0);
229        tbl = tbl.cell_margins(
230            Length::twips(72),
231            Length::twips(108),
232            Length::twips(72),
233            Length::twips(108),
234        );
235
236        tbl.cell(0, 0).unwrap().set_text("Project Alpha");
237        tbl.cell(0, 0).unwrap().shading("2E75B6");
238        tbl.cell(0, 1).unwrap().set_text("Project Beta");
239        tbl.cell(0, 1).unwrap().shading("2E75B6");
240
241        // Nested table in cell (1,0)
242        {
243            let mut cell = tbl.cell(1, 0).unwrap();
244            cell.set_text("Milestones:");
245            let mut inner = cell.add_table(3, 2);
246            inner = inner.borders(BorderStyle::Single, 2, "70AD47");
247            inner.cell(0, 0).unwrap().set_text("Phase");
248            inner.cell(0, 0).unwrap().shading("E2EFDA");
249            inner.cell(0, 1).unwrap().set_text("Status");
250            inner.cell(0, 1).unwrap().shading("E2EFDA");
251            inner.cell(1, 0).unwrap().set_text("Design");
252            inner.cell(1, 1).unwrap().set_text("Complete");
253            inner.cell(2, 0).unwrap().set_text("Build");
254            inner.cell(2, 1).unwrap().set_text("In Progress");
255        }
256
257        // Nested table in cell (1,1)
258        {
259            let mut cell = tbl.cell(1, 1).unwrap();
260            cell.set_text("Budget:");
261            let mut inner = cell.add_table(3, 2);
262            inner = inner.borders(BorderStyle::Single, 2, "ED7D31");
263            inner.cell(0, 0).unwrap().set_text("Category");
264            inner.cell(0, 0).unwrap().shading("FCE4D6");
265            inner.cell(0, 1).unwrap().set_text("Amount");
266            inner.cell(0, 1).unwrap().shading("FCE4D6");
267            inner.cell(1, 0).unwrap().set_text("Development");
268            inner.cell(1, 1).unwrap().set_text("$120,000");
269            inner.cell(2, 0).unwrap().set_text("Testing");
270            inner.cell(2, 1).unwrap().set_text("$35,000");
271        }
272    }
273
274    doc.add_paragraph("");
275
276    // =========================================================================
277    // 5. Form-style table with labels
278    // =========================================================================
279    doc.add_paragraph("5. Form-Style Table").style("Heading2");
280
281    {
282        let mut tbl = doc.add_table(6, 4);
283        tbl = tbl.borders(BorderStyle::Single, 4, "808080");
284        tbl = tbl.width_pct(100.0);
285
286        // Row 0: Full-width title
287        tbl.cell(0, 0)
288            .unwrap()
289            .set_text("Customer Registration Form");
290        tbl.cell(0, 0).unwrap().grid_span(4);
291        tbl.cell(0, 0).unwrap().shading("404040");
292
293        // Row 1: Name fields
294        tbl.cell(1, 0).unwrap().set_text("First Name");
295        tbl.cell(1, 0).unwrap().shading("E8E8E8");
296        tbl.cell(1, 1).unwrap().set_text("John");
297        tbl.cell(1, 2).unwrap().set_text("Last Name");
298        tbl.cell(1, 2).unwrap().shading("E8E8E8");
299        tbl.cell(1, 3).unwrap().set_text("Smith");
300
301        // Row 2: Contact
302        tbl.cell(2, 0).unwrap().set_text("Email");
303        tbl.cell(2, 0).unwrap().shading("E8E8E8");
304        tbl.cell(2, 1).unwrap().set_text("john.smith@example.com");
305        tbl.cell(2, 1).unwrap().grid_span(3);
306
307        // Row 3: Phone
308        tbl.cell(3, 0).unwrap().set_text("Phone");
309        tbl.cell(3, 0).unwrap().shading("E8E8E8");
310        tbl.cell(3, 1).unwrap().set_text("+1 (555) 123-4567");
311        tbl.cell(3, 2).unwrap().set_text("Company");
312        tbl.cell(3, 2).unwrap().shading("E8E8E8");
313        tbl.cell(3, 3).unwrap().set_text("Acme Corp");
314
315        // Row 4: Address (spanning)
316        tbl.cell(4, 0).unwrap().set_text("Address");
317        tbl.cell(4, 0).unwrap().shading("E8E8E8");
318        tbl.cell(4, 1)
319            .unwrap()
320            .set_text("123 Business Ave, Suite 400, Portland, OR 97201");
321        tbl.cell(4, 1).unwrap().grid_span(3);
322
323        // Row 5: Notes
324        tbl.cell(5, 0).unwrap().set_text("Notes");
325        tbl.cell(5, 0).unwrap().shading("E8E8E8");
326        tbl.cell(5, 0)
327            .unwrap()
328            .vertical_alignment(VerticalAlignment::Top);
329        {
330            let mut cell = tbl.cell(5, 1).unwrap().grid_span(3);
331            cell.set_text("Premium customer since 2020. Preferred contact method: email.");
332            cell.add_paragraph("Annual review scheduled for March 2026.");
333        }
334    }
335
336    doc.add_paragraph("");
337
338    // =========================================================================
339    // 6. Comparison table with border styles
340    // =========================================================================
341    doc.add_paragraph("6. Comparison Table with Custom Borders")
342        .style("Heading2");
343
344    {
345        let mut tbl = doc.add_table(5, 3);
346        tbl = tbl.borders(BorderStyle::Double, 4, "2E75B6");
347        tbl = tbl.width_pct(100.0);
348
349        // Header
350        tbl.cell(0, 0).unwrap().set_text("Feature");
351        tbl.cell(0, 0).unwrap().shading("2E75B6");
352        tbl.cell(0, 1).unwrap().set_text("Basic Plan");
353        tbl.cell(0, 1).unwrap().shading("2E75B6");
354        tbl.cell(0, 2).unwrap().set_text("Enterprise Plan");
355        tbl.cell(0, 2).unwrap().shading("2E75B6");
356
357        tbl.cell(1, 0).unwrap().set_text("Users");
358        tbl.cell(1, 1).unwrap().set_text("Up to 10");
359        tbl.cell(1, 2).unwrap().set_text("Unlimited");
360        tbl.cell(1, 2).unwrap().shading("E2EFDA");
361
362        tbl.cell(2, 0).unwrap().set_text("Storage");
363        tbl.cell(2, 1).unwrap().set_text("50 GB");
364        tbl.cell(2, 2).unwrap().set_text("5 TB");
365        tbl.cell(2, 2).unwrap().shading("E2EFDA");
366
367        tbl.cell(3, 0).unwrap().set_text("Support");
368        tbl.cell(3, 1).unwrap().set_text("Email only");
369        tbl.cell(3, 2).unwrap().set_text("24/7 Phone + Email");
370        tbl.cell(3, 2).unwrap().shading("E2EFDA");
371
372        tbl.cell(4, 0).unwrap().set_text("Price");
373        tbl.cell(4, 0).unwrap().shading("F2F2F2");
374        tbl.cell(4, 1).unwrap().set_text("$29/month");
375        tbl.cell(4, 1).unwrap().shading("F2F2F2");
376        tbl.cell(4, 2).unwrap().set_text("$199/month");
377        tbl.cell(4, 2).unwrap().shading("C6EFCE");
378    }
379
380    doc.add_paragraph("");
381
382    // =========================================================================
383    // 7. Wide table with fixed layout and row height
384    // =========================================================================
385    doc.add_paragraph("7. Fixed Layout Table with Row Height Control")
386        .style("Heading2");
387
388    {
389        let mut tbl = doc.add_table(4, 5);
390        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
391        tbl = tbl.width(Length::inches(7.0));
392        tbl = tbl.layout_fixed();
393
394        // Set column widths
395        for col in 0..5 {
396            tbl.cell(0, col).unwrap().width(Length::inches(1.4));
397        }
398
399        // Header with exact height
400        tbl.row(0).unwrap().height_exact(Length::twips(480));
401        tbl.row(0).unwrap().header();
402        tbl.row(0).unwrap().cant_split();
403
404        let headers = ["Mon", "Tue", "Wed", "Thu", "Fri"];
405        for (col, h) in headers.iter().enumerate() {
406            tbl.cell(0, col).unwrap().set_text(h);
407            tbl.cell(0, col).unwrap().shading("404040");
408            tbl.cell(0, col)
409                .unwrap()
410                .vertical_alignment(VerticalAlignment::Center);
411        }
412
413        // Schedule rows with minimum height
414        tbl.row(1).unwrap().height(Length::twips(600));
415        tbl.cell(1, 0).unwrap().set_text("9:00 Standup");
416        tbl.cell(1, 1).unwrap().set_text("9:00 Standup");
417        tbl.cell(1, 2).unwrap().set_text("9:00 Standup");
418        tbl.cell(1, 3).unwrap().set_text("9:00 Standup");
419        tbl.cell(1, 4).unwrap().set_text("9:00 Standup");
420
421        tbl.row(2).unwrap().height(Length::twips(600));
422        tbl.cell(2, 0).unwrap().set_text("10:00 Dev");
423        tbl.cell(2, 0).unwrap().shading("D6E4F0");
424        tbl.cell(2, 1).unwrap().set_text("10:00 Design Review");
425        tbl.cell(2, 1).unwrap().shading("FCE4D6");
426        tbl.cell(2, 2).unwrap().set_text("10:00 Dev");
427        tbl.cell(2, 2).unwrap().shading("D6E4F0");
428        tbl.cell(2, 3).unwrap().set_text("10:00 Sprint Planning");
429        tbl.cell(2, 3).unwrap().shading("E2EFDA");
430        tbl.cell(2, 4).unwrap().set_text("10:00 Dev");
431        tbl.cell(2, 4).unwrap().shading("D6E4F0");
432
433        tbl.row(3).unwrap().height(Length::twips(600));
434        tbl.cell(3, 0).unwrap().set_text("14:00 Code Review");
435        tbl.cell(3, 1).unwrap().set_text("14:00 Dev");
436        tbl.cell(3, 1).unwrap().shading("D6E4F0");
437        tbl.cell(3, 2).unwrap().set_text("14:00 Demo");
438        tbl.cell(3, 2).unwrap().shading("FCE4D6");
439        tbl.cell(3, 3).unwrap().set_text("14:00 Dev");
440        tbl.cell(3, 3).unwrap().shading("D6E4F0");
441        tbl.cell(3, 4).unwrap().set_text("14:00 Retro");
442        tbl.cell(3, 4).unwrap().shading("E2EFDA");
443    }
444
445    doc.set_title("Styled Tables Showcase");
446    doc.set_author("rdocx");
447
448    doc.save(path).unwrap();
449}